192 lines
5.9 KiB
JavaScript
192 lines
5.9 KiB
JavaScript
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';
|
|
}
|
|
}
|