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

475 lines 23.5 kB
import React from 'react'; import MiniCard from '../components/displayComponents/MiniCard'; import VideoCard from '../components/displayComponents/VideoCard'; import AudioCard from '../components/displayComponents/AudioCard'; import { buildMainHostCardPlan, buildMainScreenState, buildPrepopulateUserMediaPlan, buildScreenShareHostCardPlan } from 'mediasfu-shared'; /** * Populates the main screen grid with participant video/audio cards based on current streams and settings. * * This function is responsible for rendering the main user media grid, supporting both custom render functions * and component overrides for comprehensive UI customization. It implements a two-tier override system: * 1. Custom render functions (customVideoCard, customAudioCard, customMiniCard) - Full rendering control * 2. Component overrides (videoCardComponent, audioCardComponent, miniCardComponent) - Component replacement * * The function intelligently determines which type of card to display (video, audio, or mini) based on * participant media state and event configuration, then uses helper functions to properly invoke custom * render functions or render component overrides. * * @function * @async * @param {PrepopulateUserMediaOptions} options - The options for populating user media. * @param {string} options.name - The name identifier for the operation. * @param {PrepopulateUserMediaParameters} options.parameters - Configuration and state parameters. * @param {Array<Participant>} options.parameters.participants - List of all participants in the session. * @param {Array<Stream|Participant>} options.parameters.allVideoStreams - All available video streams. * @param {string} options.parameters.islevel - The user's participation level ('0', '1', '2'). * @param {string} options.parameters.member - The current user's name/identifier. * @param {boolean} options.parameters.shared - Whether screen sharing is active. * @param {boolean} options.parameters.shareScreenStarted - Whether screen share has been initiated. * @param {EventType} options.parameters.eventType - Type of event ('chat', 'broadcast', 'conference', 'webinar'). * @param {string} [options.parameters.screenId] - ID of the screen sharing participant. * @param {boolean} options.parameters.forceFullDisplay - Force full screen display mode. * @param {boolean} options.parameters.updateMainWindow - Whether to update the main window. * @param {boolean} options.parameters.mainScreenFilled - Whether main screen is occupied. * @param {boolean} options.parameters.adminOnMainScreen - Whether admin is on main screen. * @param {string} options.parameters.mainScreenPerson - Name of person on main screen. * @param {boolean} options.parameters.videoAlreadyOn - Whether user's video is active. * @param {boolean} options.parameters.audioAlreadyOn - Whether user's audio is active. * @param {Array<Stream|Participant>} options.parameters.oldAllStreams - Previous stream state for comparison. * @param {Function} options.parameters.checkOrientation - Function to check device orientation. * @param {boolean} options.parameters.screenForceFullDisplay - Force full display for screen share. * @param {MediaStream|null} options.parameters.localStreamScreen - Local screen share stream. * @param {Array<Stream>} options.parameters.remoteScreenStream - Remote screen share streams. * @param {MediaStream|null} options.parameters.localStreamVideo - Local video stream. * @param {number} options.parameters.mainHeightWidth - Main screen height/width value. * @param {boolean} options.parameters.isWideScreen - Whether display is wide screen. * @param {boolean} options.parameters.localUIMode - Whether in local UI development mode. * @param {boolean} options.parameters.whiteboardStarted - Whether whiteboard is active. * @param {boolean} options.parameters.whiteboardEnded - Whether whiteboard has ended. * @param {MediaStream|null} options.parameters.virtualStream - Virtual background stream. * @param {boolean} options.parameters.keepBackground - Whether to keep virtual background. * @param {boolean} options.parameters.annotateScreenStream - Whether screen annotation is active. * * **Custom UI & Component Overrides:** * @param {CustomVideoCardType} [options.parameters.customVideoCard] - Custom render function for video cards. * This function receives all video card props and should return a React element. Use React.createElement * internally to invoke this function with proper props. * @example * const customVideoCard = ({ participant, videoStream, showControls }) => ( * <View><Text>{participant.name}</Text></View> * ); * * @param {CustomAudioCardType} [options.parameters.customAudioCard] - Custom render function for audio cards. * Receives audio card props and returns a React element for audio-only participants. * @example * const customAudioCard = ({ participant, showWaveform }) => ( * <View><Text>🎤 {participant.name}</Text></View> * ); * * @param {CustomMiniCardType} [options.parameters.customMiniCard] - Custom render function for mini cards. * Receives mini card props and returns a React element for minimized participant views. * @example * const customMiniCard = ({ participant }) => ( * <View style={{ width: 50, height: 50 }}><Text>{participant.name[0]}</Text></View> * ); * * @param {React.ComponentType} [options.parameters.videoCardComponent] - Component override for VideoCard. * Replaces the default VideoCard component entirely. The component receives the same props as VideoCard. * @example * import { MyCustomVideoCard } from './components/MyCustomVideoCard'; * // Pass: videoCardComponent: MyCustomVideoCard * * @param {React.ComponentType} [options.parameters.audioCardComponent] - Component override for AudioCard. * Replaces the default AudioCard component entirely. * * @param {React.ComponentType} [options.parameters.miniCardComponent] - Component override for MiniCard. * Replaces the default MiniCard component entirely. * * @param {Function} options.parameters.updateMainScreenPerson - Callback to update main screen person. * @param {Function} options.parameters.updateMainScreenFilled - Callback to update main screen filled state. * @param {Function} options.parameters.updateAdminOnMainScreen - Callback to update admin on main screen state. * @param {Function} options.parameters.updateMainHeightWidth - Callback to update main screen dimensions. * @param {Function} options.parameters.updateScreenForceFullDisplay - Callback to update screen force full display. * @param {Function} options.parameters.updateUpdateMainWindow - Callback to update main window state. * @param {Function} options.parameters.updateMainGridStream - Callback to update main grid with new components. * @param {Function} options.parameters.getUpdatedAllParams - Function to retrieve updated parameters. * * @returns {Promise<JSX.Element[]|void>} Array of React elements representing participant cards, or void. * * @throws {Error} Throws error if there's an issue processing streams or rendering components. * * @example * // Basic usage without customization * import { prepopulateUserMedia } from 'mediasfu-reactnative-expo'; * * const components = await prepopulateUserMedia({ * name: 'mainGrid', * parameters: { * participants: allParticipants, * allVideoStreams: videoStreams, * eventType: 'conference', * islevel: '2', * member: 'John Doe', * // ... other required parameters * }, * }); * * @example * // Using custom render functions for full control * import { prepopulateUserMedia } from 'mediasfu-reactnative-expo'; * * const CustomVideo = ({ participant, videoStream }) => ( * <View style={{ borderWidth: 3, borderColor: 'gold' }}> * <Text style={{ fontSize: 20 }}>{participant.name}</Text> * {/* Custom video rendering *\/} * </View> * ); * * const components = await prepopulateUserMedia({ * name: 'mainGrid', * parameters: { * participants: allParticipants, * customVideoCard: CustomVideo, * customAudioCard: CustomAudio, * customMiniCard: CustomMini, * eventType: 'conference', * // ... other parameters * }, * }); * * @example * // Using component overrides to replace defaults * import { prepopulateUserMedia } from 'mediasfu-reactnative-expo'; * import { MyVideoCard, MyAudioCard, MyMiniCard } from './custom-components'; * * const components = await prepopulateUserMedia({ * name: 'mainGrid', * parameters: { * participants: allParticipants, * videoCardComponent: MyVideoCard, * audioCardComponent: MyAudioCard, * miniCardComponent: MyMiniCard, * eventType: 'webinar', * // ... other parameters * }, * }); * * @example * remoteScreenStream: [], * oldAllStreams: [], * newLimitedStreams: [], * sleep: sleepFunction, * getUpdatedAllParams: () => parameters, * }, * }); * ``` */ export async function prepopulateUserMedia({ name, parameters, }) { try { // Destructure parameters const { getUpdatedAllParams } = parameters; parameters = getUpdatedAllParams(); let { participants, allVideoStreams, islevel, member, shared, shareScreenStarted, eventType, screenId, forceFullDisplay, updateMainWindow, mainScreenFilled, adminOnMainScreen, mainScreenPerson, videoAlreadyOn, audioAlreadyOn, oldAllStreams, checkOrientation, screenForceFullDisplay, localStreamScreen, remoteScreenStream, localStreamVideo, mainHeightWidth, isWideScreen, localUIMode, whiteboardStarted, whiteboardEnded, virtualStream, keepBackground, annotateScreenStream, updateMainScreenPerson, updateMainScreenFilled, updateAdminOnMainScreen, updateMainHeightWidth, updateScreenForceFullDisplay, updateUpdateMainWindow, updateMainGridStream, 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; }; const VideoCardComponentOverride = (videoCardComponent ?? VideoCard); const AudioCardComponentOverride = (audioCardComponent ?? AudioCard); const MiniCardComponentOverride = (miniCardComponent ?? MiniCard); const isDarkMode = typeof parameters?.isDarkModeValue === 'boolean' ? parameters.isDarkModeValue : true; const participantCardTextColor = isDarkMode ? '#f8fafc' : '#0f172a'; 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 (<VideoCardComponentOverride 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", participant: cardParticipant, }) => { const subtitle = getSubtitleForSpeaker(cardParticipant.id || '', cardParticipant.name); if (customAudioCard) { return React.createElement(customAudioCard, { key, name, barColor, textColor, imageSource: "", roundedImage, imageStyle: {}, liveSubtitleText: subtitle, showSubtitles: showSubtitlesOnCards, parameters, }); } return (<AudioCardComponentOverride key={key} name={name} barColor={barColor} textColor={textColor} customStyle={customStyle} controlsPosition="topLeft" infoPosition="topRight" roundedImage={roundedImage} parameters={parameters} showControls={false} backgroundColor={backgroundColor} participant={cardParticipant} liveSubtitleText={subtitle} showSubtitles={showSubtitlesOnCards}/>); }; const buildMiniCard = ({ key, initials, fontSize = 20, borderColor, }) => { if (customMiniCard) { return React.createElement(customMiniCard, { key, initials, fontSize, name: initials, showVideoIcon: false, showAudioIcon: false, imageSource: "", roundedImage: true, imageStyle: {}, parameters, }); } return (<MiniCardComponentOverride key={key} initials={initials} fontSize={fontSize} customStyle={{ backgroundColor: "transparent", borderColor: borderColor, }} parameters={parameters}/>); }; const applyMainScreenState = ({ filled, adminOnMainScreen: nextAdminOnMainScreen, mainScreenPerson: nextMainScreenPerson, }) => { mainScreenFilled = filled; adminOnMainScreen = nextAdminOnMainScreen; mainScreenPerson = nextMainScreenPerson; updateMainScreenFilled(mainScreenFilled); updateAdminOnMainScreen(adminOnMainScreen); updateMainScreenPerson(mainScreenPerson); }; const renderMainHostCard = ({ plan, host, }) => { if (plan.kind === 'audio') { try { newComponent.push(buildAudioCard({ key: plan.key, name: plan.name, barColor: "red", textColor: participantCardTextColor, customStyle: { backgroundColor: 'transparent', borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, roundedImage: true, backgroundColor: "transparent", participant: host, })); updateMainGridStream(newComponent); } catch { // Handle audio card creation error } applyMainScreenState(plan.state); return; } if (plan.kind === 'mini') { try { newComponent.push(buildMiniCard({ key: plan.key, initials: plan.initials || name, fontSize: 20, borderColor: eventType !== 'broadcast' ? 'black' : undefined, })); updateMainGridStream(newComponent); } catch { // Handle mini card creation error } applyMainScreenState(plan.state); return; } try { newComponent.push(buildVideoCard({ key: plan.key, videoStream: plan.videoStream || null, remoteProducerId: plan.remoteProducerId || '', eventType: eventType, forceFullDisplay: forceFullDisplay, customStyle: { borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, participant: host, backgroundColor: "rgba(217, 227, 234, 0.99)", showControls: false, showInfo: true, name: plan.name, doMirror: plan.doMirror || false, })); updateMainGridStream(newComponent); applyMainScreenState(plan.state); } catch { // Handle video card creation error } }; const renderNoHostCard = () => { try { newComponent.push(buildMiniCard({ key: name, initials: name, fontSize: 20, borderColor: eventType !== 'broadcast' ? 'black' : undefined, })); updateMainGridStream(newComponent); applyMainScreenState(buildMainScreenState({ filled: false, adminOnMainScreen: false, mainScreenPerson: '', })); } catch { // Handle mini card creation error } }; // If the event type is 'chat', return early if (eventType == 'chat') { return; } // Initialize variables let host = null; let hostStream; const newComponent = []; const prepopulatePlan = buildPrepopulateUserMediaPlan({ participants, allVideoStreams: allVideoStreams, member, islevel, shared, shareScreenStarted, eventType, screenId, whiteboardStarted, whiteboardEnded, remoteScreenStream, localStreamScreen, checkOrientation, isWideScreen, forceFullDisplay, includeWhiteboardAsScreenFlow: true, }); if (prepopulatePlan.screenFlowActive) { if (eventType == 'conference') { if (mainHeightWidth == 0) { updateMainHeightWidth(84); } } else if (mainHeightWidth != 84) { updateMainHeightWidth(84); } } screenForceFullDisplay = prepopulatePlan.screenForceFullDisplay; updateScreenForceFullDisplay(screenForceFullDisplay); host = prepopulatePlan.host; hostStream = prepopulatePlan.hostStream; if (prepopulatePlan.shouldUpdateAdminOnMainScreen) { adminOnMainScreen = prepopulatePlan.adminOnMainScreen; updateAdminOnMainScreen(adminOnMainScreen); } mainScreenPerson = prepopulatePlan.mainScreenPerson; updateMainScreenPerson(mainScreenPerson); if (prepopulatePlan.shouldReturnEarly) { return; } // If host is not null, check if host videoIsOn if (host) { // Populate the main screen with the host video if (shareScreenStarted || shared) { forceFullDisplay = screenForceFullDisplay; const screenShareHostCardPlan = buildScreenShareHostCardPlan({ hostName: host.name || '', hostScreenID: host.ScreenID, hostIsAdmin: host.islevel === '2', shared, hostStream, screenForceFullDisplay, annotateScreenStream, }); if (whiteboardStarted && !whiteboardEnded) { // Whiteboard is active } else { newComponent.push(buildVideoCard({ key: screenShareHostCardPlan.key, videoStream: screenShareHostCardPlan.videoStream, remoteProducerId: screenShareHostCardPlan.remoteProducerId, eventType: eventType, forceFullDisplay: screenShareHostCardPlan.forceFullDisplay, customStyle: { borderWidth: eventType !== 'broadcast' ? 2 : 0, borderColor: 'black', }, participant: host, backgroundColor: "rgba(217, 227, 234, 0.99)", showControls: false, showInfo: true, name: screenShareHostCardPlan.name, doMirror: screenShareHostCardPlan.doMirror, })); } updateMainGridStream(newComponent); applyMainScreenState(screenShareHostCardPlan.state); return newComponent; } const mainHostCardPlan = buildMainHostCardPlan({ islevel, localUIMode, videoAlreadyOn, audioAlreadyOn, hostVideoOn: !!host.videoOn, hostMuted: host.muted, hostIsAdmin: host.islevel === '2', hostName: host.name || '', hostVideoID: host.videoID, fallbackName: name, member, keepBackground, virtualStream, localStreamVideo, oldAllStreams: oldAllStreams, }); renderMainHostCard({ plan: mainHostCardPlan, host }); } else { renderNoHostCard(); } updateMainWindow = false; updateUpdateMainWindow(updateMainWindow); return newComponent; } catch { // Handle errors during the process of preparing and populating the main screen // throw error; } } //# sourceMappingURL=prepopulateUserMedia.js.map