import { createParticipantTile, getParticipantTile } from '../participants/renderer-participant-grid.js'; import { createLogger } from '../../shared/logger.js'; const logger = createLogger('renderer-media'); export function getVideoResolution(track) { if (track && track.getSettings) { const settings = track.getSettings(); return { width: settings.width || 640, height: settings.height || 480 }; } return { width: 640, height: 480 }; } export function adjustVideoSize(videoElement) { if (!videoElement) return; const container = videoElement.parentElement; if (!container) return; videoElement.style.transform = 'translateZ(0)'; videoElement.style.willChange = 'transform'; container.style.display = 'flex'; container.style.alignItems = 'center'; container.style.justifyContent = 'center'; videoElement.style.imageRendering = 'auto'; videoElement.style.maxWidth = '100%'; videoElement.style.maxHeight = '100%'; videoElement.style.objectFit = 'contain'; } export function renderParticipantStreamMedia({ grid, stream, connectionId, displayName, getGridTemplateColumns, remoteVideo, connectingOverlay, remoteVideoPlaceholder }) { if (!grid) return; grid.classList.remove('hidden'); let tile = getParticipantTile(grid, connectionId); if (!tile) { tile = createParticipantTile(connectionId, displayName); grid.appendChild(tile); logger.debug(`Created participant video tile for ${connectionId}`); } const video = tile.querySelector('video'); if (video && stream) { if (video.srcObject === stream) { logger.debug(`Same stream for participant ${connectionId}, ensuring playback`); video.play().catch(error => logger.debug('Auto-play prevented:', error.message)); } else { video.srcObject = stream; video.play().catch(error => logger.debug('Auto-play prevented:', error.message)); logger.debug(`Set remote stream for participant tile ${connectionId}`); } } const remoteVideoContainer = remoteVideo?.closest('.absolute.inset-0.video-fade-in'); if (remoteVideoContainer) { remoteVideoContainer.classList.add('hidden'); } const tileCount = grid.querySelectorAll('[data-participant-id]').length; grid.style.gridTemplateColumns = getGridTemplateColumns(tileCount); if (connectingOverlay) { connectingOverlay.classList.add('hidden'); } if (remoteVideoPlaceholder) { remoteVideoPlaceholder.classList.add('hidden'); } } export function renderSingleRemoteStreamMedia({ remoteVideo, stream, disconnectedOverlay, remoteVideoPlaceholder, connectingOverlay }) { if (!remoteVideo || !stream) { logger.error('Either remoteVideo element or stream is missing'); return; } logger.debug('Rendering remote stream:', stream, 'tracks:', stream.getTracks().map(track => `${track.kind}(${track.readyState})`)); if (remoteVideo.srcObject === stream) { logger.debug('Same stream object, track added - ensuring playback'); remoteVideo.play().catch(error => logger.debug('Auto-play prevented:', error.message)); return; } remoteVideo.srcObject = stream; remoteVideo.autoplay = true; remoteVideo.playsinline = true; remoteVideo.muted = false; remoteVideo.play().catch(error => { logger.debug('Auto-play prevented, will retry on interaction:', error.message); }); if (disconnectedOverlay) { disconnectedOverlay.classList.add('hidden'); } const videoTracks = stream.getVideoTracks(); const audioTracks = stream.getAudioTracks(); logger.debug(`Stream has ${videoTracks.length} video tracks, ${audioTracks.length} audio tracks`); if (videoTracks.length === 0) { logger.debug('Audio-only stream, waiting for video track...'); return; } if (remoteVideoPlaceholder) { remoteVideoPlaceholder.classList.add('hidden'); } if (connectingOverlay) { connectingOverlay.classList.add('hidden'); } const activeVideoTrack = videoTracks.find(track => track.readyState === 'live'); if (!activeVideoTrack) return; adjustVideoSize(remoteVideo, getVideoResolution(activeVideoTrack)); activeVideoTrack.addEventListener('resize', () => { adjustVideoSize(remoteVideo, getVideoResolution(activeVideoTrack)); }); } export function clearParticipantGrid(grid) { if (!grid) return; grid.querySelectorAll('[data-participant-id]').forEach(tile => { const video = tile.querySelector('video'); if (video) { video.srcObject = null; } tile.remove(); }); grid.classList.add('hidden'); } export function removeParticipantTile({ grid, connectionId, getGridTemplateColumns, remoteVideo, remoteVideoPlaceholder, remoteNetworkIndicator }) { if (!grid) return; const tile = getParticipantTile(grid, connectionId); if (tile) { const video = tile.querySelector('video'); if (video) { video.srcObject = null; } tile.remove(); logger.debug(`Removed participant video tile for ${connectionId}`); } const remainingTiles = grid.querySelectorAll('[data-participant-id]'); if (remainingTiles.length === 0) { grid.classList.add('hidden'); const remoteVideoContainer = remoteVideo?.closest('.absolute.inset-0.video-fade-in'); if (remoteVideoContainer) { remoteVideoContainer.classList.remove('hidden'); } if (remoteVideoPlaceholder) { remoteVideoPlaceholder.classList.remove('hidden'); } } else { grid.style.gridTemplateColumns = getGridTemplateColumns(remainingTiles.length); } if (remoteNetworkIndicator) { remoteNetworkIndicator.className = 'w-2 h-2 bg-gray-500 rounded-full'; } }