diff --git a/api/package.json b/api/package.json index 8fc6b73384..d2021dc7d6 100644 --- a/api/package.json +++ b/api/package.json @@ -82,7 +82,6 @@ "mongoose": "^8.9.5", "multer": "^1.4.5-lts.1", "nanoid": "^3.3.7", - "node-pre-gyp": "^0.17.0", "nodemailer": "^6.9.15", "ollama": "^0.5.0", "openai": "^4.47.1", diff --git a/api/server/services/WebSocket/WebSocketServer.js b/api/server/services/WebSocket/WebSocketServer.js index b5339377c2..268073bb7c 100644 --- a/api/server/services/WebSocket/WebSocketServer.js +++ b/api/server/services/WebSocket/WebSocketServer.js @@ -1,5 +1,5 @@ const { Server } = require('socket.io'); -const { RTCPeerConnection, RTCIceCandidate } = require('wrtc'); +const { RTCPeerConnection, RTCIceCandidate, MediaStream } = require('wrtc'); class WebRTCConnection { constructor(socket, config) { @@ -14,38 +14,31 @@ class WebRTCConnection { async handleOffer(offer) { try { - // Create new peer connection if needed if (!this.peerConnection) { this.peerConnection = new RTCPeerConnection(this.config.rtcConfig); this.setupPeerConnectionListeners(); } - // Set the remote description (client's offer) await this.peerConnection.setRemoteDescription(offer); - // Set up audio transceiver for two-way audio + // Create MediaStream instance properly + const mediaStream = new MediaStream(); + this.audioTransceiver = this.peerConnection.addTransceiver('audio', { direction: 'sendrecv', + streams: [mediaStream], }); - // Create and set local description (answer) const answer = await this.peerConnection.createAnswer(); await this.peerConnection.setLocalDescription(answer); - - // Send answer to client this.socket.emit('webrtc-answer', answer); - - // Process any pending ICE candidates - while (this.pendingCandidates.length) { - const candidate = this.pendingCandidates.shift(); - await this.addIceCandidate(candidate); - } - - this.state = 'connecting'; } catch (error) { this.log(`Error handling offer: ${error}`, 'error'); - this.socket.emit('error', { message: 'Failed to process offer' }); - this.cleanup(); + // Don't throw, handle gracefully + this.socket.emit('webrtc-error', { + message: error.message, + code: 'OFFER_ERROR', + }); } } diff --git a/client/src/components/Chat/Input/Call.tsx b/client/src/components/Chat/Input/Call.tsx index 15021a6801..c60900a68a 100644 --- a/client/src/components/Chat/Input/Call.tsx +++ b/client/src/components/Chat/Input/Call.tsx @@ -43,6 +43,8 @@ export const Call: React.FC = () => { useEffect(() => { if (remoteAudioRef.current && remoteStream) { remoteAudioRef.current.srcObject = remoteStream; + + remoteAudioRef.current.play().catch((err) => console.error('Error playing audio:', err)); } }, [remoteStream]); @@ -98,6 +100,36 @@ export const Call: React.FC = () => { const isActive = callState === CallState.ACTIVE; const isError = callState === CallState.ERROR; + // TESTS + + useEffect(() => { + if (remoteAudioRef.current && remoteStream) { + console.log('Setting up remote audio:', { + tracks: remoteStream.getTracks().length, + active: remoteStream.active, + }); + + remoteAudioRef.current.srcObject = remoteStream; + remoteAudioRef.current.muted = false; + remoteAudioRef.current.volume = 1.0; + + const playPromise = remoteAudioRef.current.play(); + if (playPromise) { + playPromise.catch((err) => { + console.error('Error playing audio:', err); + // Retry play on user interaction + document.addEventListener( + 'click', + () => { + remoteAudioRef.current?.play(); + }, + { once: true }, + ); + }); + } + } + }, [remoteStream]); + return ( @@ -200,7 +232,11 @@ export const Call: React.FC = () => { {/* Hidden Audio Element */} -