UNPKG

mediasfu-reactnative-expo

Version:

mediasfu-reactnative-expo – Expo-managed React Native WebRTC SDK for video conferencing, webinars, live streaming, broadcast, screen sharing, whiteboard, chat, recording, live subtitles, translation, and AI agent rooms on iOS, Android, and web. Prebuilt r

457 lines 20.8 kB
import React from 'react'; import MiniCard from '../components/displayComponents/MiniCard'; import VideoCard from '../components/displayComponents/VideoCard'; import AudioCard from '../components/displayComponents/AudioCard'; import { buildAddVideosGridPlan } from 'mediasfu-shared'; /** * Adds participants to the main and alternate video grids based on the provided parameters. * * This function populates video grids with participant cards, supporting both custom render functions * and component overrides for full UI customization. It uses a two-tier override system: * 1. Custom render functions (customVideoCard, customAudioCard, customMiniCard) - Full control via functions * 2. Component overrides (videoCardComponent, audioCardComponent, miniCardComponent) - Replace default components * * @function * @async * @param {AddVideosGridOptions} options - The options for adding videos to the grid. * @param {Array} options.mainGridStreams - The main grid streams containing participant or stream data. * @param {Array} options.altGridStreams - The alternate grid streams containing participant or stream data. * @param {number} options.numtoadd - The number of participants to add to the grid. * @param {number} options.numRows - The number of rows in the grid layout. * @param {number} options.numCols - The number of columns in the grid layout. * @param {number} options.actualRows - The actual number of rows currently filled in the grid. * @param {number} options.lastrowcols - The number of columns in the last row of the grid. * @param {boolean} options.removeAltGrid - Flag indicating whether to remove the alternate grid. * @param {AddVideosGridParameters} options.parameters - Additional parameters required for the function. * @param {string} options.parameters.eventType - The type of event (e.g., meeting, conference). * @param {Function} options.parameters.updateAddAltGrid - Callback to update the status of the alternate grid. * @param {Array} options.parameters.ref_participants - A reference list of participants. * @param {string} options.parameters.islevel - The participation level of the user. * @param {boolean} options.parameters.videoAlreadyOn - Indicates if video streaming is already active. * @param {MediaStream} options.parameters.localStreamVideo - The user's local video stream. * @param {boolean} options.parameters.keepBackground - Flag to determine if the background should be retained. * @param {MediaStream} options.parameters.virtualStream - The virtual stream to use. * @param {boolean} options.parameters.forceFullDisplay - Flag to enforce full display mode. * @param {Array} options.parameters.otherGridStreams - Additional streams for the grid. * @param {Function} options.parameters.updateOtherGridStreams - Callback to update other grid streams. * @param {Function} options.parameters.updateMiniCardsGrid - Callback to update the mini card display. * @param {Function} options.parameters.getUpdatedAllParams - Function to retrieve updated parameters. * * **Custom UI & Component Overrides:** * @param {CustomVideoCardType} [options.parameters.customVideoCard] - Custom render function for video cards. * Receives props and returns JSX. Use for complete control over video card rendering. * @param {CustomAudioCardType} [options.parameters.customAudioCard] - Custom render function for audio cards. * Receives props and returns JSX. Use for complete control over audio card rendering. * @param {CustomMiniCardType} [options.parameters.customMiniCard] - Custom render function for mini cards. * Receives props and returns JSX. Use for complete control over mini card rendering. * @param {React.ComponentType} [options.parameters.videoCardComponent] - Component override for VideoCard. * Replaces the default VideoCard component. Use when you want to replace the component but not control rendering. * @param {React.ComponentType} [options.parameters.audioCardComponent] - Component override for AudioCard. * Replaces the default AudioCard component. * @param {React.ComponentType} [options.parameters.miniCardComponent] - Component override for MiniCard. * Replaces the default MiniCard component. * * @returns {Promise<void>} A promise that resolves when the grid has been updated successfully. * * @example * // Basic usage without customization * import { addVideosGrid } from 'mediasfu-reactnative-expo'; * * await addVideosGrid({ * mainGridStreams: mainGridStreams, * altGridStreams: altGridStreams, * numtoadd: 4, * numRows: 2, * numCols: 2, * actualRows: 2, * lastrowcols: 2, * removeAltGrid: false, * parameters: { * eventType: 'conference', * videoAlreadyOn: true, * // ... other required parameters * }, * }); * * @example * // Using custom render function for complete control * import { addVideosGrid } from 'mediasfu-reactnative-expo'; * * const CustomVideoCard = ({ participant, videoStream, showControls }) => ( * <View style={{ border: '2px solid blue' }}> * <Text>{participant.name}</Text> * {/* Your custom video rendering logic *\/} * </View> * ); * * await addVideosGrid({ * mainGridStreams: streams, * altGridStreams: [], * numtoadd: 4, * numRows: 2, * numCols: 2, * actualRows: 2, * lastrowcols: 2, * removeAltGrid: false, * parameters: { * customVideoCard: CustomVideoCard, // Custom render function * eventType: 'conference', * // ... other parameters * }, * }); * * @example * // Using component override to replace default component * import { addVideosGrid } from 'mediasfu-reactnative-expo'; * import { MyCustomVideoCard } from './MyCustomVideoCard'; * * await addVideosGrid({ * mainGridStreams: streams, * altGridStreams: [], * numtoadd: 4, * numRows: 2, * numCols: 2, * actualRows: 2, * lastrowcols: 2, * removeAltGrid: false, * parameters: { * videoCardComponent: MyCustomVideoCard, // Component override * audioCardComponent: MyCustomAudioCard, * miniCardComponent: MyCustomMiniCard, * eventType: 'conference', * // ... other parameters * }, * }); */ export async function addVideosGrid({ mainGridStreams, altGridStreams, numtoadd, numRows, numCols, actualRows, lastrowcols, removeAltGrid, parameters, }) { const { getUpdatedAllParams } = parameters; parameters = getUpdatedAllParams(); const { eventType, updateAddAltGrid, ref_participants, islevel, videoAlreadyOn, localStreamVideo, keepBackground, virtualStream, forceFullDisplay, otherGridStreams, updateOtherGridStreams, updateMiniCardsGrid, customVideoCard, customAudioCard, customMiniCard, videoCardComponent, audioCardComponent, miniCardComponent, translationTranscripts = [], showSubtitlesOnCards = true, } = parameters; const getSubtitleForSpeaker = (speakerId, speakerName) => { if (!showSubtitlesOnCards || !Array.isArray(translationTranscripts)) { return null; } const now = Date.now(); const ttlMs = 8000; for (let i = translationTranscripts.length - 1; i >= 0; i -= 1) { const entry = translationTranscripts[i]; if (!entry) continue; const matches = (speakerId && entry.speakerId === speakerId) || (speakerName && entry.speakerName === speakerName); if (!matches) continue; if (!entry.timestamp || now - entry.timestamp > ttlMs) { continue; } return entry.translatedText || entry.originalText || null; } return null; }; // Create component overrides with fallbacks const VideoCardComponentToUse = videoCardComponent ?? VideoCard; const AudioCardComponentToUse = audioCardComponent ?? AudioCard; const MiniCardComponentToUse = miniCardComponent ?? MiniCard; const isDarkMode = typeof parameters?.isDarkModeValue === 'boolean' ? parameters.isDarkModeValue : true; const participantCardTextColor = isDarkMode ? '#f8fafc' : '#0f172a'; // Helper functions to build cards with proper override handling const buildVideoCard = ({ key, videoStream, remoteProducerId = '', eventType: cardEventType, forceFullDisplay: cardForceFullDisplay = false, customStyle, participant: cardParticipant, backgroundColor, showControls = false, showInfo = true, name = '', doMirror = false, }) => { const subtitle = getSubtitleForSpeaker(cardParticipant.id || '', cardParticipant.name); if (customVideoCard) { return React.createElement(customVideoCard, { key, videoStream: videoStream || new MediaStream(), remoteProducerId, eventType: cardEventType, forceFullDisplay: cardForceFullDisplay, customStyle, participant: cardParticipant, backgroundColor, showControls, showInfo, name, doMirror, liveSubtitleText: subtitle, showSubtitles: showSubtitlesOnCards, parameters, }); } return (<VideoCardComponentToUse key={key} videoStream={videoStream} remoteProducerId={remoteProducerId} eventType={cardEventType} forceFullDisplay={cardForceFullDisplay} customStyle={customStyle} participant={cardParticipant} backgroundColor={backgroundColor} showControls={showControls} showInfo={showInfo} name={name} doMirror={doMirror} liveSubtitleText={subtitle} showSubtitles={showSubtitlesOnCards} {...{ parameters }}/>); }; const buildAudioCard = ({ key, name, barColor = 'red', textColor = 'white', customStyle, roundedImage = true, backgroundColor = 'transparent', showControls = false, participant: cardParticipant, }) => { const subtitle = getSubtitleForSpeaker(cardParticipant.id || '', cardParticipant.name); if (customAudioCard) { return React.createElement(customAudioCard, { key, name, barColor, textColor, customStyle, controlsPosition: 'topLeft', infoPosition: 'topRight', roundedImage, parameters, backgroundColor, showControls, participant: cardParticipant, liveSubtitleText: subtitle, showSubtitles: showSubtitlesOnCards, }); } return (<AudioCardComponentToUse key={key} name={name} barColor={barColor} textColor={textColor} customStyle={customStyle} controlsPosition="topLeft" infoPosition="topRight" roundedImage={roundedImage} parameters={parameters} backgroundColor={backgroundColor} showControls={showControls} participant={cardParticipant} liveSubtitleText={subtitle} showSubtitles={showSubtitlesOnCards}/>); }; const buildMiniCard = ({ key, initials, fontSize = 20, customStyle, }) => { if (customMiniCard) { return React.createElement(customMiniCard, { key, initials, fontSize, name: initials, showVideoIcon: false, showAudioIcon: false, imageSource: '', roundedImage: true, imageStyle: {}, parameters, }); } return (<MiniCardComponentToUse key={key} initials={initials} fontSize={fontSize} customStyle={customStyle} parameters={parameters}/>); }; const newComponents = [[], []]; let participant; let remoteProducerId = ''; const gridPlan = buildAddVideosGridPlan({ mainGridStreams, altGridStreams, numToAdd: numtoadd, }); const mainEntries = gridPlan.mainEntries; const altEntries = gridPlan.altEntries; numtoadd = mainEntries.length; if (removeAltGrid) { updateAddAltGrid(false); } // Add participants to the main grid for (let i = 0; i < numtoadd; i++) { participant = mainEntries[i].stream; remoteProducerId = participant.producerId; const pseudoName = !remoteProducerId || remoteProducerId === ''; if (pseudoName) { remoteProducerId = participant.name; if (participant.audioID) { newComponents[0].push(buildAudioCard({ key: `audio-${participant.id}`, name: participant.name || '', barColor: 'red', textColor: participantCardTextColor, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, roundedImage: true, backgroundColor: 'transparent', showControls: eventType !== 'chat', participant: participant, })); } else { newComponents[0].push(buildMiniCard({ key: `mini-${participant.id}`, initials: participant.name, fontSize: 20, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, })); } } else if (remoteProducerId === 'youyou' || remoteProducerId === 'youyouyou') { let name = 'You'; if (islevel === '2' && eventType !== 'chat') { name = 'You (Host)'; } if (!videoAlreadyOn) { newComponents[0].push(buildMiniCard({ key: 'mini-you', initials: name, fontSize: 20, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, })); } else { participant = { id: 'youyouyou', stream: keepBackground && virtualStream ? virtualStream : localStreamVideo, name: 'youyouyou', }; newComponents[0].push(buildVideoCard({ key: 'video-you', videoStream: participant.stream, remoteProducerId: participant.stream?.id || '', eventType: eventType, forceFullDisplay: eventType === 'webinar' ? false : forceFullDisplay, customStyle: { borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, participant: participant, backgroundColor: 'transparent', showControls: false, showInfo: false, name: participant.name, doMirror: true, })); } } else { const participant_ = ref_participants.find((obj) => obj.videoID === remoteProducerId); if (participant_) { newComponents[0].push(buildVideoCard({ key: `video-${participant_.id}`, videoStream: participant.stream, remoteProducerId: remoteProducerId || '', eventType: eventType, forceFullDisplay: forceFullDisplay, customStyle: { borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, participant: participant_, backgroundColor: 'transparent', showControls: eventType !== 'chat', showInfo: true, name: participant_.name || '', doMirror: false, })); } } if (i === numtoadd - 1) { otherGridStreams[0] = newComponents[0]; await updateMiniCardsGrid({ rows: numRows, cols: numCols, defal: true, actualRows, parameters, }); updateOtherGridStreams(otherGridStreams); await updateMiniCardsGrid({ rows: numRows, cols: numCols, defal: true, actualRows, parameters, }); } } // Handle the alternate grid streams if (!removeAltGrid) { for (let i = 0; i < altEntries.length; i++) { participant = altEntries[i].stream; remoteProducerId = participant.producerId; const pseudoName = !remoteProducerId || remoteProducerId === ''; if (pseudoName) { if (participant.audioID) { newComponents[1].push(buildAudioCard({ key: `audio-alt-${participant.id}`, name: participant.name, barColor: 'red', textColor: participantCardTextColor, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, roundedImage: true, backgroundColor: 'transparent', showControls: eventType !== 'chat', participant: participant, })); } else { newComponents[1].push(buildMiniCard({ key: `mini-alt-${participant.id}`, initials: participant.name, fontSize: 20, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, })); } } else { const participant_ = ref_participants.find((obj) => obj.videoID === remoteProducerId); if (participant_) { newComponents[1].push(buildVideoCard({ key: `video-alt-${participant_.id}`, videoStream: participant.stream, remoteProducerId: remoteProducerId || '', eventType: eventType, forceFullDisplay: forceFullDisplay, customStyle: { borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, participant: participant_, backgroundColor: 'transparent', showControls: eventType !== 'chat', showInfo: true, name: participant.name, doMirror: false, })); } } if (i === altGridStreams.length - 1) { otherGridStreams[1] = newComponents[1]; await updateMiniCardsGrid({ rows: 1, cols: lastrowcols, defal: false, actualRows, parameters, }); updateOtherGridStreams(otherGridStreams); await updateMiniCardsGrid({ rows: numRows, cols: numCols, defal: true, actualRows, parameters, }); } } } else { updateAddAltGrid(false); otherGridStreams[1] = []; await updateMiniCardsGrid({ rows: 0, cols: 0, defal: false, actualRows, parameters, }); updateOtherGridStreams(otherGridStreams); await updateMiniCardsGrid({ rows: numRows, cols: numCols, defal: true, actualRows, parameters, }); } } //# sourceMappingURL=addVideosGrid.js.map