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
534 lines • 27.6 kB
JavaScript
import React from 'react';
import { Platform } from 'react-native';
import MiniAudioPlayer from '../methods/utils/MiniAudioPlayer/MiniAudioPlayer';
import MiniAudio from '../components/displayComponents/MiniAudio';
import { MediaStream as NativeMediaStream, RTCView, } from '../methods/utils/webrtc/webrtc';
/**
* Resumes a consumer transport by handling the provided track and updating the application state.
*
* This function manages the resumption of media consumer transports (audio/video/screen) and updates
* the UI accordingly. It supports custom UI components through both custom render functions and
* component overrides, particularly for the MiniAudioPlayer component.
*
* Key responsibilities:
* - Creates MediaStream from received track
* - Updates stream arrays (audio, video, screen)
* - Triggers UI updates through reorderStreams and prepopulateUserMedia
* - Handles MiniAudioPlayer rendering for off-screen audio participants
* - Manages consumer transport state
*
* @function
* @async
* @param {ConsumerResumeOptions} options - The options for resuming the consumer.
* @param {MediaStreamTrack} options.track - The media stream track to resume (audio or video).
* @param {string} options.remoteProducerId - The ID of the remote producer.
* @param {ResumeParams} options.params - The parameters required for resuming the consumer.
* @param {string} options.params.id - Consumer ID.
* @param {string} options.params.producerId - Producer ID.
* @param {string} options.params.kind - Media kind ('audio' or 'video').
* @param {Object} options.params.rtpParameters - RTP parameters for the consumer.
* @param {ConsumerResumeParameters} options.parameters - The parameters for updating the state.
* @param {Socket} options.nsock - The socket instance for communication.
* @param {Consumer} options.consumer - The consumer instance to resume.
*
* **State Parameters:**
* @param {MediaStream|null} options.parameters.nStream - New stream being processed.
* @param {Array<Stream|Participant>} options.parameters.allAudioStreams - All audio streams in the session.
* @param {Array<Stream|Participant>} options.parameters.allVideoStreams - All video streams in the session.
* @param {Array<Stream>} options.parameters.streamNames - Named video streams.
* @param {Array<Stream>} options.parameters.audStreamNames - Named audio streams.
* @param {boolean} options.parameters.updateMainWindow - Whether to update main window.
* @param {boolean} options.parameters.shared - Whether screen sharing is active.
* @param {boolean} options.parameters.shareScreenStarted - Whether screen share has started.
* @param {string} [options.parameters.screenId] - ID of screen sharing participant.
* @param {Array<Participant>} options.parameters.participants - All session participants.
* @param {EventType} options.parameters.eventType - Event type ('chat', 'broadcast', 'conference', 'webinar').
* @param {string} options.parameters.meetingDisplayType - Display type ('video', 'media', 'all').
* @param {boolean} options.parameters.mainScreenFilled - Whether main screen is occupied.
* @param {boolean} options.parameters.first_round - Whether this is the first round of streaming.
* @param {boolean} options.parameters.lock_screen - Whether screen is locked.
* @param {Array<Stream|Participant>} options.parameters.oldAllStreams - Previous stream state.
* @param {string} [options.parameters.adminVidID] - Admin's video ID.
* @param {number} options.parameters.mainHeightWidth - Main screen dimensions.
* @param {string} options.parameters.member - Current user's name.
* @param {Array<JSX.Element>} options.parameters.audioOnlyStreams - Audio-only stream components.
* @param {boolean} options.parameters.gotAllVids - Whether all videos have been received.
* @param {boolean} options.parameters.defer_receive - Whether to defer receiving streams.
* @param {boolean} options.parameters.firstAll - Whether this is first all-streams update.
* @param {Array<Stream>} options.parameters.remoteScreenStream - Remote screen share streams.
* @param {boolean} options.parameters.hostLabel - Host label visibility.
* @param {boolean} options.parameters.whiteboardStarted - Whether whiteboard is active.
* @param {boolean} options.parameters.whiteboardEnded - Whether whiteboard has ended.
* @param {string} [options.parameters.recordingDisplayType] - Recording display type.
* @param {string} options.parameters.recordingVideoOptimized - Recording optimization setting.
* @param {Array<Object>} options.parameters.consumerTransports - Consumer transport array.
* @param {string} options.parameters.islevel - User's participation level.
* @param {MediaStream|null} options.parameters.localStreamVideo - Local video stream.
*
* **Custom UI & Component Overrides:**
* @param {React.ComponentType} [options.parameters.miniAudioComponent] - Component override for MiniAudio.
* This component is rendered within MiniAudioPlayer when an audio-only participant is not visible
* on the main grid. Use this to customize the mini audio player's waveform visualization.
* @example
* const CustomMiniAudio = ({ stream, name }) => (
* <View><Text>🎵 {name}</Text></View>
* );
*
* @param {React.ComponentType} [options.parameters.videoCardComponent] - Component override for VideoCard.
* @param {React.ComponentType} [options.parameters.audioCardComponent] - Component override for AudioCard.
* @param {React.ComponentType} [options.parameters.miniCardComponent] - Component override for MiniCard.
* @param {CustomVideoCardType} [options.parameters.customVideoCard] - Custom render function for video cards.
* @param {CustomAudioCardType} [options.parameters.customAudioCard] - Custom render function for audio cards.
* @param {CustomMiniCardType} [options.parameters.customMiniCard] - Custom render function for mini cards.
*
* **Callback Functions:**
* @param {Function} options.parameters.updateNStream - Update new stream state.
* @param {Function} options.parameters.updateAllAudioStreams - Update all audio streams.
* @param {Function} options.parameters.updateAllVideoStreams - Update all video streams.
* @param {Function} options.parameters.updateStreamNames - Update named video streams.
* @param {Function} options.parameters.updateAudStreamNames - Update named audio streams.
* @param {Function} options.parameters.updateUpdateMainWindow - Update main window state.
* @param {Function} options.parameters.updateAudioOnlyStreams - Update audio-only components.
* @param {Function} options.parameters.updateGotAllVids - Update all videos received state.
* @param {Function} options.parameters.updateDefer_receive - Update defer receive state.
* @param {Function} options.parameters.updateFirstAll - Update first all state.
* @param {Function} options.parameters.updateRemoteScreenStream - Update remote screen streams.
* @param {Function} options.parameters.updateOldAllStreams - Update previous streams state.
* @param {Function} options.parameters.updateConsumerTransports - Update consumer transports.
* @param {Function} options.parameters.reorderStreams - Function to reorder stream display.
* @param {Function} options.parameters.prepopulateUserMedia - Function to populate user media grid.
* @param {Function} options.parameters.getUpdatedAllParams - Get updated parameters.
*
* @returns {Promise<void>} A promise that resolves when the consumer is successfully resumed.
*
* @throws {Error} Throws an error if the resumption fails or if there is an issue with the parameters.
*
* @example
* // Basic usage
* import { consumerResume } from 'mediasfu-reactnative-expo';
*
* await consumerResume({
* track: audioTrack,
* remoteProducerId: 'producer-123',
* params: {
* id: 'consumer-456',
* producerId: 'producer-123',
* kind: 'audio',
* rtpParameters: rtpParams,
* },
* consumer: consumerInstance,
* nsock: socketInstance,
* parameters: {
* eventType: 'conference',
* participants: allParticipants,
* // ... other required parameters
* },
* });
*
* @example
* // With custom MiniAudio component for off-screen audio visualization
* import { consumerResume } from 'mediasfu-reactnative-expo';
* import { CustomMiniAudio } from './components/CustomMiniAudio';
*
* await consumerResume({
* track: audioTrack,
* remoteProducerId: 'producer-123',
* params: {
* id: 'consumer-456',
* producerId: 'producer-123',
* kind: 'audio',
* rtpParameters: rtpParams,
* },
* consumer: consumerInstance,
* nsock: socketInstance,
* parameters: {
* miniAudioComponent: CustomMiniAudio, // Custom audio waveform component
* eventType: 'conference',
* participants: allParticipants,
* // ... other parameters
* },
* });
*
* @example
* // With full component override suite
* import { consumerResume } from 'mediasfu-reactnative-expo';
*
* await consumerResume({
* track: videoTrack,
* remoteProducerId: 'producer-789',
* params: {
* id: 'consumer-101',
* producerId: 'producer-789',
* kind: 'video',
* rtpParameters: rtpParams,
* },
* consumer: consumerInstance,
* nsock: socketInstance,
* parameters: {
* audioOnlyStreams: [],
* gotAllVids: false,
* defer_receive: false,
* firstAll: false,
* remoteScreenStream: [],
* hostLabel: 'host',
* whiteboardStarted: false,
* whiteboardEnded: false,
* updateUpdateMainWindow: (value) => { console.log('updated')},
* updateAllAudioStreams: (streams) => { console.log('updated')},
* updateAllVideoStreams: (streams) => { console.log('updated')},
* updateStreamNames: (streams) => { console.log('updated')},
* updateAudStreamNames: (streams) => { console.log('updated')},
* updateNStream: (stream) => { console.log('updated')},
* updateMainHeightWidth: (value) => { console.log('updated')},
* updateLock_screen: (value) => { console.log('updated')},
* updateFirstAll: (value) => { console.log('updated')},
* updateRemoteScreenStream: (streams) => { console.log('updated')},
* updateOldAllStreams: (streams) => { console.log('updated')},
* updateAudioOnlyStreams: (streams) => { console.log('updated')},
* updateShareScreenStarted: (value) => { console.log('updated')},
* updateGotAllVids: (value) => { console.log('updated')},
* updateScreenId: (id) => { console.log('updated')},
* updateDefer_receive: (value) => { console.log('updated')},
* reorderStreams: (params) => { console.log('reordered') }
* prepopulateUserMedia: (params) => { console.log('prepopulated') },
* },
* nsock: socketInstance,
* };
*
* consumerResume(options)
* .then(() => {
* console.log('Consumer resumed successfully');
* })
* .catch((error) => {
* console.error('Error resuming consumer:', error);
* });
*/
export const consumerResume = async ({ track, remoteProducerId, params, parameters, nsock, consumer, }) => {
try {
// Get updated parameters
parameters = parameters.getUpdatedAllParams();
// Destructure parameters
let { nStream, allAudioStreams, allVideoStreams, streamNames, audStreamNames, updateMainWindow, shared, shareScreenStarted, screenId, participants, eventType, meetingDisplayType, mainScreenFilled, first_round, lock_screen, oldAllStreams, adminVidID, mainHeightWidth, member, audioOnlyStreams, gotAllVids, defer_receive, firstAll, remoteScreenStream, hostLabel, whiteboardStarted, whiteboardEnded, videoCardComponent, audioCardComponent, miniCardComponent, miniAudioComponent, miniAudioPlayerComponent, updateUpdateMainWindow, updateAllAudioStreams, updateAllVideoStreams, updateStreamNames, updateAudStreamNames, updateNStream, updateMainHeightWidth, updateLock_screen, updateFirstAll, updateRemoteScreenStream, updateOldAllStreams, updateAudioOnlyStreams, updateShareScreenStarted, updateGotAllVids, updateScreenId, updateDefer_receive,
// mediasfu functions
reorderStreams, prepopulateUserMedia, } = parameters;
const MiniAudioComponentToUse = miniAudioComponent ?? MiniAudio;
const MiniAudioPlayerComponentToUse = miniAudioPlayerComponent ?? MiniAudioPlayer;
const isDarkMode = typeof parameters?.isDarkModeValue === 'boolean'
? parameters.isDarkModeValue
: true;
const participantCardTextColor = isDarkMode ? '#f8fafc' : '#0f172a';
if (params.kind === 'audio') {
// Audio resumed
const activeTranslationProducerIds = parameters.activeTranslationProducerIds;
const isTranslationAudio = activeTranslationProducerIds?.has(remoteProducerId)
|| consumer.appData?.type === 'translation'
|| consumer.appData?.isTranslation;
// Check if the participant with audioID == remoteProducerId has a valid videoID
const participant = participants.filter((p) => p.audioID === remoteProducerId);
let name__ = participant.length > 0 ? participant[0].name || '' : '';
if (isTranslationAudio && !name__) {
const translationMeta = consumer.appData?.translationMeta;
name__ = translationMeta?.speakerName || `Translation-${remoteProducerId.slice(0, 8)}`;
}
if (name__ === member && !isTranslationAudio)
return;
// find any participants with ScreenID not null and ScreenOn == true
const screenParticipant_alt = participants.filter((participant) => participant.ScreenID != null
&& participant.ScreenOn == true
&& participant.ScreenID != '');
if (screenParticipant_alt.length > 0) {
screenId = screenParticipant_alt[0].ScreenID;
updateScreenId(screenId);
if (!shared) {
shareScreenStarted = true;
updateShareScreenStarted(shareScreenStarted);
}
}
else if (whiteboardStarted && !whiteboardEnded) {
// Whiteboard is active
}
else {
screenId = '';
updateScreenId(screenId);
updateShareScreenStarted(false);
}
// Media display and UI update to prioritize audio/video
nStream = new NativeMediaStream([track]);
updateNStream(nStream);
if (isTranslationAudio) {
const addTranslationStream = parameters.addTranslationStream;
const translationTrack = Platform.OS === 'web' ? (<RTCView key={`translation-${remoteProducerId}`} stream={nStream} style={{ width: 0, height: 0 }}/>) : (<RTCView key={`translation-${remoteProducerId}`} streamURL={nStream.toURL()} style={{ width: 0, height: 0 }}/>);
if (addTranslationStream) {
addTranslationStream(translationTrack);
}
allAudioStreams = [
...allAudioStreams,
{ producerId: remoteProducerId, stream: nStream },
];
updateAllAudioStreams(allAudioStreams);
audStreamNames = [
...audStreamNames,
{ producerId: remoteProducerId, name: name__ },
];
updateAudStreamNames(audStreamNames);
return;
}
// Create MiniAudioPlayer track
const nTrack = (<MiniAudioPlayerComponentToUse stream={nStream} remoteProducerId={remoteProducerId} parameters={parameters} consumer={consumer} MiniAudioComponent={MiniAudioComponentToUse} miniAudioProps={{
customStyle: { backgroundColor: 'gray' },
name: name__,
showWaveform: true,
overlayPosition: 'topRight',
barColor: participantCardTextColor,
textColor: participantCardTextColor,
imageSource: 'https://mediasfu.com/images/logo192.png',
roundedImage: true,
imageStyle: {},
}}/>);
// Add to audioOnlyStreams array
audioOnlyStreams.push(nTrack);
updateAudioOnlyStreams(audioOnlyStreams);
// Add to allAudioStreams array; add producerId, stream
allAudioStreams = [
...allAudioStreams,
{ producerId: remoteProducerId, stream: nStream },
];
updateAllAudioStreams(allAudioStreams);
let name;
try {
name = participant[0].name;
}
catch {
name = '';
}
if (name) {
// Add to audStreamNames array; add producerId, name
audStreamNames = [
...audStreamNames,
{ producerId: remoteProducerId, name: name__ },
];
updateAudStreamNames(audStreamNames);
if (!mainScreenFilled && participant[0].islevel === '2') {
updateMainWindow = true;
updateUpdateMainWindow(updateMainWindow);
await prepopulateUserMedia({
name: hostLabel,
parameters: { ...parameters, audStreamNames, allAudioStreams, videoCardComponent, audioCardComponent, miniCardComponent },
});
updateMainWindow = false;
updateUpdateMainWindow(updateMainWindow);
}
}
else {
return;
}
// Checks for display type and updates the UI
let checker;
let alt_checker = false;
if (meetingDisplayType == 'video') {
checker = participant[0].videoID != null
&& participant[0].videoID != ''
&& participant[0].videoID != undefined;
}
else {
checker = true;
alt_checker = true;
}
if (checker) {
if (shareScreenStarted || shared) {
if (!alt_checker) {
await reorderStreams({
parameters: { ...parameters, audStreamNames, allAudioStreams },
});
}
}
else if (alt_checker && meetingDisplayType != 'video') {
await reorderStreams({
add: false,
screenChanged: true,
parameters: { ...parameters, audStreamNames, allAudioStreams },
});
}
}
}
else {
// Video resumed
nStream = new NativeMediaStream([track]);
updateNStream(nStream);
// find any participants with ScreenID not null and ScreenOn == true
const screenParticipant_alt = participants.filter((participant) => participant.ScreenID != null
&& participant.ScreenOn == true
&& participant.ScreenID != '');
if (screenParticipant_alt.length > 0) {
screenId = screenParticipant_alt[0].ScreenID;
updateScreenId(screenId);
if (!shared) {
shareScreenStarted = true;
updateShareScreenStarted(shareScreenStarted);
}
}
else if (whiteboardStarted && !whiteboardEnded) {
// Whiteboard is active
}
else {
screenId = '';
updateScreenId(screenId);
updateShareScreenStarted(false);
}
// Check for display type and update the UI
if (remoteProducerId == screenId) {
// Put on main screen for screen share
updateMainWindow = true;
updateUpdateMainWindow(updateMainWindow);
remoteScreenStream = [
{ producerId: remoteProducerId, stream: nStream },
];
updateRemoteScreenStream(remoteScreenStream);
if (eventType == 'conference') {
if (shared || shareScreenStarted || (whiteboardStarted && !whiteboardEnded)) {
if (mainHeightWidth == 0) {
updateMainHeightWidth(84);
}
}
else if (mainHeightWidth > 0) {
updateMainHeightWidth(0);
}
}
if (!lock_screen) {
await prepopulateUserMedia({ name: hostLabel, parameters: { ...parameters, videoCardComponent, audioCardComponent, miniCardComponent } });
await reorderStreams({
add: false,
screenChanged: true,
parameters: { ...parameters, remoteScreenStream, allVideoStreams },
});
}
else if (!first_round) {
await prepopulateUserMedia({
name: hostLabel,
parameters: {
...parameters,
remoteScreenStream,
allVideoStreams,
videoCardComponent,
audioCardComponent,
miniCardComponent,
},
});
await reorderStreams({
add: false,
screenChanged: true,
parameters: {
...parameters,
remoteScreenStream,
allVideoStreams,
},
});
}
lock_screen = true;
updateLock_screen(lock_screen);
firstAll = true;
updateFirstAll(firstAll);
}
else {
// Non-screen share video resumed
// Operations to add video to the UI (either main screen or mini screen)
parameters = parameters.getUpdatedAllParams();
// Get the name of the participant with videoID == remoteProducerId
const participant = participants.filter((participant) => participant.videoID == remoteProducerId);
if (participant.length > 0
&& participant[0].name != null
&& participant[0].name != ''
&& participant[0].name != undefined
&& participant[0].name !== member) {
allVideoStreams = [
...allVideoStreams,
{ producerId: remoteProducerId, stream: nStream, socket_: nsock },
];
updateAllVideoStreams(allVideoStreams);
}
if (participant.length > 0) {
const { name } = participant[0];
streamNames = [
...streamNames,
{ producerId: remoteProducerId, name: name || '' },
];
updateStreamNames(streamNames);
}
// If not screenshare, filter out the stream that belongs to the participant with isAdmin = true and islevel == '2' (host)
// Find the ID of the participant with isAdmin = true and islevel == '2'
if (!shareScreenStarted) {
const admin = participants.filter((participant) => (participant.isAdmin == true || participant.isHost == true) && participant.islevel == '2');
// Remove video stream with producerId == admin.id
// Get the videoID of the admin
if (admin.length > 0) {
adminVidID = admin[0].videoID;
if (adminVidID != null && adminVidID != '') {
let oldAllStreams_ = [];
// Check if the length of allVideoStreams is > 0
if (oldAllStreams.length > 0) {
oldAllStreams_ = oldAllStreams;
}
oldAllStreams = allVideoStreams.filter((streame) => streame.producerId == adminVidID);
updateOldAllStreams(oldAllStreams);
if (oldAllStreams.length < 1) {
oldAllStreams = oldAllStreams_;
updateOldAllStreams(oldAllStreams);
}
allVideoStreams = allVideoStreams.filter((streame) => streame.producerId != adminVidID);
updateAllVideoStreams(allVideoStreams);
if (remoteProducerId == adminVidID) {
updateMainWindow = true;
}
}
gotAllVids = true;
updateGotAllVids(gotAllVids);
}
}
else {
// Check if the videoID is either that of the admin or that of the screen participant
const screenParticipant = participants.filter((participant) => participant.ScreenID == screenId);
// See if producerId is that of admin videoID or screenParticipant videoID
let adminVidID;
let screenParticipantVidID;
if (screenParticipant.length > 0) {
screenParticipantVidID = screenParticipant[0].videoID;
}
if ((adminVidID != null && adminVidID != '')
|| (screenParticipantVidID != null && screenParticipantVidID != '')) {
if (adminVidID == remoteProducerId
|| screenParticipantVidID == remoteProducerId) {
await reorderStreams({
parameters: { ...parameters, allVideoStreams },
});
return;
}
}
}
// Update the UI
if (lock_screen || shared) {
defer_receive = true;
updateDefer_receive(defer_receive);
if (!first_round) {
await reorderStreams({
add: false,
screenChanged: true,
parameters: { ...parameters, allVideoStreams },
});
}
}
else {
await reorderStreams({
add: false,
screenChanged: true,
parameters: { ...parameters, allVideoStreams },
});
}
}
}
}
catch (error) {
console.log('consumerResume error', error);
// throw error;
}
};
//# sourceMappingURL=consumerResume.js.map