diff --git a/apps/web/src/components/CallModal.tsx b/apps/web/src/components/CallModal.tsx
index 53467e5..2a011d8 100644
--- a/apps/web/src/components/CallModal.tsx
+++ b/apps/web/src/components/CallModal.tsx
@@ -226,13 +226,17 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
const streams = event.streams;
console.log(`[WebRTC] Received track: ${track.kind} state: ${track.readyState} streams: ${streams.length}`);
+ // Ensure we have a stable remote stream object
+ if (!remoteStreamRef.current) {
+ remoteStreamRef.current = streams[0] || new MediaStream([track]);
+ } else if (!remoteStreamRef.current.getTracks().includes(track)) {
+ remoteStreamRef.current.addTrack(track);
+ }
+
+ const stream = remoteStreamRef.current;
+
// Helper to update video UI state
const checkVideo = () => {
- const stream = remoteStreamRef.current;
- if (!stream) {
- setHasRemoteVideo(false);
- return;
- }
const videoTracks = stream.getVideoTracks();
const hasVideo = videoTracks.length > 0 && videoTracks.some(
t => t.readyState === 'live' && t.enabled && !t.muted
@@ -244,36 +248,35 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
setHasRemoteVideo(hasVideo);
if (hasVideo && callType !== 'video') {
- console.log('[checkVideo] Auto-switching to video mode');
setCallType('video');
}
};
- // Ensure we have a stream object
- if (!remoteStreamRef.current) {
- remoteStreamRef.current = streams[0] || new MediaStream([track]);
- } else if (!remoteStreamRef.current.getTracks().includes(track)) {
- remoteStreamRef.current.addTrack(track);
+ // Force binding to the SINGLE stable video element
+ if (remoteVideoRef.current) {
+ if (remoteVideoRef.current.srcObject !== stream) {
+ console.log('[WebRTC] Binding stream to video element');
+ remoteVideoRef.current.srcObject = stream;
+ }
+ // Try to play. If fails, it's usually autoplay policy
+ remoteVideoRef.current.play().catch(() => {
+ console.warn('[WebRTC] Autoplay blocked, waiting for interaction');
+ // Important: we keep trying to play because users often click the UI
+ const retryPlay = () => {
+ remoteVideoRef.current?.play().then(() => {
+ console.log('[WebRTC] Video started after retry');
+ }).catch(() => setTimeout(retryPlay, 2000));
+ };
+ retryPlay();
+ });
}
- const stream = remoteStreamRef.current;
-
- // Link to DOM elements
- if (track.kind === 'video' && remoteVideoRef.current) {
- remoteVideoRef.current.srcObject = stream;
- // MUST be muted to autoplay in most browsers
- remoteVideoRef.current.muted = true;
- remoteVideoRef.current.play().catch(err => console.warn('[WebRTC] video play failed:', err));
- checkVideo();
- }
-
- if (track.kind === 'audio' || (track.kind === 'video' && !remoteAudioRef.current?.srcObject)) {
- if (remoteAudioRef?.current) {
- remoteAudioRef.current.srcObject = stream;
- remoteAudioRef.current.play().catch(err => {
- console.warn('[WebRTC] Audio play failed, user gesture needed:', err);
- });
+ // Handle audio via stable audio element
+ if (remoteAudioRef.current) {
+ if (remoteAudioRef.current.srcObject !== stream) {
+ remoteAudioRef.current.srcObject = stream;
}
+ remoteAudioRef.current.play().catch(e => console.warn('[WebRTC] Audio play failed:', e));
}
track.onunmute = checkVideo;
@@ -1437,9 +1440,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
- {/* Hidden remote video/audio elements for background processing */}
-
-
+ {/* No hidden elements here - they are managed in the main content area */}
) : (