npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mediasfu-reactnative-expo

v1.0.2

Published

MediaSFU Prebuilt React Native (Expo) SDK

Downloads

92

Readme

MediaSFU offers a cutting-edge streaming experience that empowers users to customize their recordings and engage their audience with high-quality streams. Whether you're a content creator, educator, or business professional, MediaSFU provides the tools you need to elevate your streaming game.


MediaSFU React Native (Expo) Module Documentation

Table of Contents

Features

MediaSFU's React Native (Expo) SDK comes with a host of powerful features out of the box:

  1. Breakout Rooms: Create multiple sub-meetings within a single session to enhance collaboration and focus.
  2. Pagination: Efficiently handle large participant lists with seamless pagination.
  3. Polls: Conduct real-time polls to gather instant feedback from participants.
  4. Media Access Requests Management: Manage media access requests with ease to ensure smooth operations.
  5. Video Effects: Apply various video effects, including virtual backgrounds, to enhance the visual experience.
  6. Chat (Direct & Group): Facilitate communication with direct and group chat options.
  7. Cloud Recording (track-based): Customize recordings with track-based options, including watermarks, name tags, background colors, and more.
  8. Managed Events: Manage events with features to handle abandoned and inactive participants, as well as enforce time and capacity limits.

Getting Started

This section will guide users through the initial setup and installation of the npm module.

Note: this is specifically for React-Native-Expo. If you are integrating into a React Native CLI app, the best option is to use the core mediasfu-reactnative package, which you can find on npm at mediasfu-reactnative.

Documentation Reference

For comprehensive documentation on the available methods, components, and functions, please visit mediasfu.com. This resource provides detailed information for this guide and additional documentation.

Installation

Instructions on how to install the module using npm.

1. Add the Package to Your Project

npm install mediasfu-reactnative-expo

2. Obtain an API Key from MediaSFU

You can get your API key by signing up or logging into your account at mediasfu.com.

3. Configure Your Project

Before proceeding, ensure that your project is properly configured to work with mediasfu-reactnative-expo. Follow the steps below to set up the necessary configuration files.

a. Update app.json

Ensure that your app.json does not have the output field set to 'static' in the web configuration. Remove or clear this field to prevent build issues.

{
  "expo": {
    // ... other configurations
    "web": {
      // Remove or ensure the output field is not set to 'static'
      // "output": "static" // This line should be removed or cleared
    }
  }
}

b. Configure babel.config.js

Your babel.config.js should include the necessary presets and plugins for Expo and React Native Reanimated. Here is an example configuration:

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['babel-preset-expo'],
    plugins: [
      'react-native-reanimated/plugin',
      '@babel/plugin-transform-block-scoping'
    ],
  };
};

c. Configure metro.config.js

Ensure your metro.config.js file includes the correct settings for Metro bundler:

const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

const config = getDefaultConfig(__dirname);
config.resolver.nodeModulesPaths = [path.resolve(__dirname, 'node_modules')];

module.exports = config;

4. Complete the Setup

After completing the above steps, you can proceed to set up and run your project.

  • Start the Development Server:

    npx expo start
  • Create a Development Build:

    Since react-native-webrtc does not run on Expo Go, you will need to create a development build to test WebRTC functionalities on an actual device. Follow the Expo Development Builds documentation for guidance on creating a development build.

Basic Usage Guide

A basic guide on how to use the module for common tasks.

This section will guide users through the initial setup and installation of the npm module.

This guide provides a basic overview of how to set up and use the mediasfu-reactnative-expo module for common tasks across platforms.

Initial Setup and Installation

To get started with mediasfu-reactnative-expo, follow the instructions below. This module is optimized for use with Expo dependencies, making it ideal for projects that require seamless deployment across web and mobile platforms.

Note: If you are integrating into a React Native CLI app, the best option is to use the core mediasfu-reactnative package, which you can find on npm at mediasfu-reactnative.

Documentation Reference

For comprehensive documentation on the available methods, components, and functions, please visit mediasfu.com. This resource provides detailed information for this guide and additional documentation.

Introduction

MediaSFU is a 2-page application consisting of a prejoin/welcome page and the main events room page. This guide will walk you through the basic usage of the module for setting up these pages.

Prebuilt Event Rooms

MediaSFU provides prebuilt event rooms for various purposes. These rooms are rendered as full pages and can be easily imported and used in your application. Here are the available prebuilt event rooms:

  1. MediasfuGeneric: A generic event room suitable for various types of events.
  2. MediasfuBroadcast: A room optimized for broadcasting events.
  3. MediasfuWebinar: Specifically designed for hosting webinars.
  4. MediasfuConference: Ideal for hosting conferences.
  5. MediasfuChat: A room tailored for interactive chat sessions.

Users can easily pick an interface and render it in their app.

If no API credentials are provided, a default home page will be displayed where users can scan or manually enter the event details.

To use these prebuilt event rooms, simply import them into your application:

import { MediasfuGeneric, MediasfuBroadcast, MediasfuWebinar, MediasfuConference, MediasfuChat } from 'mediasfu-reactnative-expo';

Simplest Usage

The simplest way to use MediaSFU is by directly rendering a prebuilt event room component, such as MediasfuGeneric:

import { MediasfuGeneric } from 'mediasfu-reactnative-expo';

const App = () => {
  return (
    <MediasfuGeneric />
  );
}

export default App;

Programmatically Fetching Tokens

If you prefer to fetch the required tokens programmatically without visiting MediaSFU's website, you can use the PreJoinPage component and pass your credentials as props:

import { MediasfuGeneric, PreJoinPage } from 'mediasfu-reactnative-expo';

const App = () => {
  const credentials = { apiUserName: "yourAPIUserName", apiKey: "yourAPIKey" };

  return (
    <MediasfuGeneric PrejoinPage={PreJoinPage} credentials={credentials} />
  );
}

export default App;

Preview of Welcome Page

 

Preview of Prejoin Page

Custom Welcome/Prejoin Page

Alternatively, you can design your own welcome/prejoin page. The core function of this page is to fetch user tokens from MediaSFU's API and establish a connection with the returned link if valid.

Parameters Passed to Custom Page

MediaSFU passes relevant parameters to the custom welcome/prejoin page:

let { showAlert, updateIsLoadingModalVisible, connectSocket, updateSocket, updateValidated,
     updateApiUserName, updateApiToken, updateLink, updateRoomName, updateMember } = parameters;

Ensure that your custom page implements the following updates:

updateSocket(socket);
updateApiUserName(apiUserName);
updateApiToken(apiToken);
updateLink(link);
updateRoomName(apiUserName);
updateMember(userName);
updateValidated(true);

See the following code for the PreJoinPage page logic:

import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  TextInput,
  Pressable,
  Image,
  StyleSheet,
  ScrollView,
  KeyboardAvoidingView,
  Platform,
} from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import Orientation from 'react-native-orientation-locker';
import { Socket } from 'socket.io-client';
import { ConnectSocketType, ShowAlert } from '../../@types/types';
import RNPickerSelect from 'react-native-picker-select';

const MAX_ATTEMPTS = 10; // Maximum number of unsuccessful attempts before rate limiting
const RATE_LIMIT_DURATION = 3 * 60 * 60 * 1000; // 3 hours in milliseconds


// Type definitions for parameters and credentials
export interface PreJoinPageParameters {
  imgSrc?: string;
  showAlert?: ShowAlert;
  updateIsLoadingModalVisible: (visible: boolean) => void;
  connectSocket: ConnectSocketType;
  updateSocket: (socket: Socket) => void;
  updateValidated: (validated: boolean) => void;
  updateApiUserName: (userName: string) => void;
  updateApiToken: (token: string) => void;
  updateLink: (link: string) => void;
  updateRoomName: (roomName: string) => void;
  updateMember: (member: string) => void;
}

export interface Credentials {
  apiUserName: string;
  apiKey: string;
}

export interface PreJoinPageOptions {
  parameters: PreJoinPageParameters;
  credentials?: Credentials;
}

export type PreJoinPageType = (options: PreJoinPageOptions) => JSX.Element;

export interface CreateJoinRoomResponse {
  message: string;
  roomName: string;
  secureCode?: string;
  publicURL: string;
  link: string;
  secret: string;
  success: boolean;
}

export interface CreateJoinRoomError {
  error: string;
  success?: boolean;
}

export type CreateJoinRoomType = (options: {
  payload: any;
  apiUserName: string;
  apiKey: string;
}) => Promise<{
  data: CreateJoinRoomResponse | CreateJoinRoomError | null;
  success: boolean;
}>;

export type CreateRoomOnMediaSFUType = (options: {
  payload: any;
  apiUserName: string;
  apiKey: string;
}) => Promise<{
  data: CreateJoinRoomResponse | CreateJoinRoomError | null;
  success: boolean;
}>;

export async function joinRoomOnMediaSFU({
  payload,
  apiUserName,
  apiKey,
}: {
  payload: any;
  apiUserName: string;
  apiKey: string;
}): Promise<{
  data: CreateJoinRoomResponse | CreateJoinRoomError | null;
  success: boolean;
}> {
  try {
    if (
      !apiUserName ||
      !apiKey ||
      apiUserName === "yourAPIUSERNAME" ||
      apiKey === "yourAPIKEY" ||
      apiKey.length !== 64 ||
      apiUserName.length < 6
    ) {
      return { data: { error: "Invalid credentials" }, success: false };
    }

    const response = await fetch('https://mediasfu.com/v1/rooms/', 
    {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${apiUserName}:${apiKey}`,
        },
        body: JSON.stringify(payload),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    return { data, success: true };
  } catch (error) {
    const errorMessage = (error as any).reason ? (error as any).reason : 'unknown error';
    return {
      data: { error: `Unable to join room, ${errorMessage}` },
      success: false,
    };
  }
}

export async function createRoomOnMediaSFU({
  payload,
  apiUserName,
  apiKey,
}: {
  payload: any;
  apiUserName: string;
  apiKey: string;
}): Promise<{
  data: CreateJoinRoomResponse | CreateJoinRoomError | null;
  success: boolean;
}> {
  try {
    if (
      !apiUserName ||
      !apiKey ||
      apiUserName === "yourAPIUSERNAME" ||
      apiKey === "yourAPIKEY" ||
      apiKey.length !== 64 ||
      apiUserName.length < 6
    ) {
      return { data: { error: "Invalid credentials" }, success: false };
    }
    
    const response = await fetch('https://mediasfu.com/v1/rooms/', 
      {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${apiUserName}:${apiKey}`,
        },
        body: JSON.stringify(payload),
      }
    );

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    return { data, success: true };
  } catch (error) {
    const errorMessage = (error as any).reason ? (error as any).reason : 'unknown error';
    return {
      data: { error: `Unable to create room, ${errorMessage}` },
      success: false,
    };
  }
}

/**
 * PreJoinPage component allows users to either create a new room or join an existing one.
 * 
 * @component
 * @param {PreJoinPageOptions} props - The properties for the PreJoinPage component.
 * @param {Object} props.parameters - Various parameters required for the component.
 * @param {Function} props.parameters.showAlert - Function to show alert messages.
 * @param {Function} props.parameters.updateIsLoadingModalVisible - Function to update the loading modal visibility.
 * @param {Function} props.parameters.connectSocket - Function to connect to the socket.
 * @param {Function} props.parameters.updateSocket - Function to update the socket.
 * @param {Function} props.parameters.updateValidated - Function to update the validation status.
 * @param {Function} props.parameters.updateApiUserName - Function to update the API username.
 * @param {Function} props.parameters.updateApiToken - Function to update the API token.
 * @param {Function} props.parameters.updateLink - Function to update the link.
 * @param {Function} props.parameters.updateRoomName - Function to update the room name.
 * @param {Function} props.parameters.updateMember - Function to update the member.
 * @param {string} [props.parameters.imgSrc] - The source URL for the logo image.
 * @param {Object} [props.credentials=user_credentials] - The user credentials.
 * 
 * @returns {JSX.Element} The rendered PreJoinPage component.
 * 
 * @example
 * <PreJoinPage
 *   parameters={{
 *     showAlert: showAlertFunction,
 *     updateIsLoadingModalVisible: updateLoadingFunction,
 *     connectSocket: connectSocketFunction,
 *     updateSocket: updateSocketFunction,
 *     updateValidated: updateValidatedFunction,
 *     updateApiUserName: updateApiUserNameFunction,
 *     updateApiToken: updateApiTokenFunction,
 *     updateLink: updateLinkFunction,
 *     updateRoomName: updateRoomNameFunction,
 *     updateMember: updateMemberFunction,
 *     imgSrc: "https://example.com/logo.png"
 *   }}
 *   credentials={{
 *     apiUserName: "user123",
 *     apiKey: "apikey123"
 *   }}
 * />
 */
const PreJoinPage: React.FC<PreJoinPageOptions> = ({
  parameters,
  credentials,
}) => {
  // State variables
  const [isCreateMode, setIsCreateMode] = useState<boolean>(false);
  const [name, setName] = useState<string>('');
  const [duration, setDuration] = useState<string>('');
  const [eventType, setEventType] = useState<string>('');
  const [capacity, setCapacity] = useState<string>('');
  const [eventID, setEventID] = useState<string>('');
  const [error, setError] = useState<string>('');

  // Destructure parameters
  const {
    showAlert,
    updateIsLoadingModalVisible,
    connectSocket,
    updateSocket,
    updateValidated,
    updateApiUserName,
    updateApiToken,
    updateLink,
    updateRoomName,
    updateMember,
  } = parameters;

  /**
   * Checks rate limits and makes a socket connection request.
   */
  const checkLimitsAndMakeRequest = async ({
    apiUserName,
    apiToken,
    link,
    apiKey = '',
    userName,
  }: {
    apiUserName: string;
    apiToken: string;
    link: string;
    apiKey?: string;
    userName: string;
  }) => {
    const TIMEOUT_DURATION = 10000; // 10 seconds

    try {
      // Retrieve unsuccessful attempts and last request timestamp from AsyncStorage
      let unsuccessfulAttempts = parseInt(
        (await AsyncStorage.getItem('unsuccessfulAttempts')) || '0',
       10);
      const lastRequestTimestamp = parseInt(
        (await AsyncStorage.getItem('lastRequestTimestamp')) || '0',
       10);

      // Check if user has exceeded maximum attempts
      if (
        unsuccessfulAttempts >= MAX_ATTEMPTS
        && Date.now() - lastRequestTimestamp < RATE_LIMIT_DURATION
      ) {
        showAlert?.({
          message: 'Too many unsuccessful attempts. Please try again later.',
          type: 'danger',
          duration: 3000,
        });
        await AsyncStorage.setItem(
          'lastRequestTimestamp',
          Date.now().toString(),
        );
        return;
      } if (unsuccessfulAttempts >= MAX_ATTEMPTS) {
        // Reset unsuccessful attempts after rate limit duration
        unsuccessfulAttempts = 0;
        await AsyncStorage.setItem(
          'unsuccessfulAttempts',
          unsuccessfulAttempts.toString(),
        );
        await AsyncStorage.setItem(
          'lastRequestTimestamp',
          Date.now().toString(),
        );
      }

      // Show loading modal
      updateIsLoadingModalVisible(true);

      // Attempt to connect to socket with a timeout
      const socketPromise = connectSocket({
        apiUserName,
        apiKey,
        apiToken,
        link,
      });
      const timeoutPromise = new Promise<never>((_, reject) => setTimeout(
        () => reject(new Error('Request timed out')),
        TIMEOUT_DURATION,
      ));

      const socket = await Promise.race([socketPromise, timeoutPromise]);

      if (socket && socket.id) {
        // Successful connection
        unsuccessfulAttempts = 0;
        await AsyncStorage.setItem(
          'unsuccessfulAttempts',
          unsuccessfulAttempts.toString(),
        );
        await AsyncStorage.setItem(
          'lastRequestTimestamp',
          Date.now().toString(),
        );

        // Update parent state with socket and user details
        updateSocket(socket);
        updateApiUserName(apiUserName);
        updateApiToken(apiToken);
        updateLink(link);
        updateRoomName(apiUserName);
        updateMember(userName);
        updateValidated(true);
      } else {
        // Unsuccessful connection
        unsuccessfulAttempts += 1;
        await AsyncStorage.setItem(
          'unsuccessfulAttempts',
          unsuccessfulAttempts.toString(),
        );
        await AsyncStorage.setItem(
          'lastRequestTimestamp',
          Date.now().toString(),
        );
        updateIsLoadingModalVisible(false);

        if (unsuccessfulAttempts >= MAX_ATTEMPTS) {
          showAlert?.({
            message: 'Too many unsuccessful attempts. Please try again later.',
            type: 'danger',
            duration: 3000,
          });
        } else {
          showAlert?.({
            message: 'Invalid credentials.',
            type: 'danger',
            duration: 3000,
          });
        }
      }
    } catch (error) {
      // Handle errors during connection
      console.error('Error connecting to socket:', error);
      showAlert?.({
        message: 'Unable to connect. Check your credentials and try again.',
        type: 'danger',
        duration: 3000,
      });

      // Increment unsuccessful attempts
      let unsuccessfulAttempts = parseInt(
        (await AsyncStorage.getItem('unsuccessfulAttempts')) || '0',
       10);
      unsuccessfulAttempts += 1;
      await AsyncStorage.setItem(
        'unsuccessfulAttempts',
        unsuccessfulAttempts.toString(),
      );
      await AsyncStorage.setItem('lastRequestTimestamp', Date.now().toString());
      updateIsLoadingModalVisible(false);
    }
  };

  /**
   * Handles toggling between Create Mode and Join Mode.
   */
  const handleToggleMode = () => {
    setIsCreateMode((prevMode) => !prevMode);
    setError('');
  };

  /**
   * Handles the creation of a new room.
   */
  const handleCreateRoom = async () => {
    try {
      setError('');

      // Validate input fields
      if (!name || !duration || !eventType || !capacity) {
        setError('Please fill all the fields.');
        return;
      }

      // Validate event type
      const validEventTypes = ['broadcast', 'chat', 'webinar', 'conference'];
      if (!validEventTypes.includes(eventType.toLowerCase())) {
        setError(
          'Event type must be one of "broadcast", "chat", "webinar", or "conference".',
        );
        return;
      }

      // Validate numeric fields
      const durationNum = parseInt(duration, 10);
      const capacityNum = parseInt(capacity, 10);
      if (isNaN(durationNum) || isNaN(capacityNum)) {
        setError('Duration and Capacity must be valid numbers.');
        return;
      }

      // Prepare payload
      const payload = {
        action: 'create',
        duration: durationNum,
        capacity: capacityNum,
        eventType: eventType.toLowerCase(),
        userName: name,
      };

      // Make API call to create room
      updateIsLoadingModalVisible(true);

      const response = await createRoomOnMediaSFU({
        payload,
        apiUserName: credentials.apiUserName,
        apiKey: credentials.apiKey,
      });

      if (response.success && response.data && 'roomName' in response.data) {
        // Handle successful room creation
        await checkLimitsAndMakeRequest({
          apiUserName: response.data.roomName,
          apiToken: response.data.secret,
          link: response.data.link,
          userName: name,
        });
        setError('');
      } else {
        // Handle failed room creation
        updateIsLoadingModalVisible(false);
        setError(
          `Unable to create room. ${
            response.data && 'error' in response.data ? response.data.error : ''
          }`,
        );
      }
    } catch (error) {
      updateIsLoadingModalVisible(false);
      setError(`Unable to connect. ${error.message}`);
      showAlert?.({
        message: `Unable to connect. ${error.message}`,
        type: 'danger',
        duration: 3000,
      });
    }
  };

  /**
   * Handles joining an existing room.
   */
  const handleJoinRoom = async () => {
    try {
      setError('');

      // Validate input fields
      if (!name || !eventID) {
        setError('Please fill all the fields.');
        return;
      }

      // Prepare payload
      const payload = {
        action: 'join',
        meetingID: eventID,
        userName: name,
      };

      // Make API call to join room
      updateIsLoadingModalVisible(true);

      const response = await joinRoomOnMediaSFU({
        payload,
        apiUserName: credentials.apiUserName,
        apiKey: credentials.apiKey,
      });

      if (response.success && response.data && 'roomName' in response.data) {
        // Handle successful room join
        await checkLimitsAndMakeRequest({
          apiUserName: response.data.roomName,
          apiToken: response.data.secret,
          link: response.data.link,
          userName: name,
        });
        setError('');
      } else {
        // Handle failed room join
        updateIsLoadingModalVisible(false);
        setError(
          `Unable to connect to room. ${
            response.data && 'error' in response.data ? response.data.error : ''
          }`,
        );
      }
    } catch (error) {
      updateIsLoadingModalVisible(false);
      setError(`Unable to connect. ${error.message}`);
      showAlert?.({
        message: `Unable to connect. ${error.message}`,
        type: 'danger',
        duration: 3000,
      });
    }
  };

  /**
   * Locks the orientation to portrait mode when the component mounts and unlocks on unmount.
   */
  useEffect(() => {
    Orientation.lockToPortrait();

    return () => {
      Orientation.unlockAllOrientations();
    };
  }, []);


   return (
    // your element
   )
};

export default PreJoinPage;

IP Blockage Warning And Local UI Development

Entering the event room without the correct credentials may result in IP blockage, as the page automatically attempts to connect with MediaSFU servers, which rate limit bad requests based on IP address.

If users attempt to enter the event room without valid credentials or tokens, it may lead to IP blockage due to MediaSFU servers' rate limiting mechanism. To avoid unintentional connections to MediaSFU servers during UI development, users can pass the useLocalUIMode parameter as true.

In this mode, the module will operate locally without making requests to MediaSFU servers. However, to render certain UI elements such as messages, participants, requests, etc., users may need to provide seed data. They can achieve this by importing random data generators and passing the generated data to the event room component.

Example for Broadcast Room

import { MediasfuBroadcast, generateRandomParticipants, generateRandomMessages } from 'mediasfu-reactnative-expo';

function App() {
  const useSeed = true;
  let seedData = {};

  if (useSeed) {
    const memberName = 'Alice';
    const hostName = 'Fred';
    const participants_ = generateRandomParticipants(memberName, "", hostName, true);
    const messages_ = generateRandomMessages(participants_, memberName, "", hostName, true);

    seedData = {
      participants: participants_,
      messages: messages_,
      member: memberName,
      host: hostName,
    };
  }

  const useLocalUIMode = useSeed ? true : false;

  return (
    <MediasfuBroadcast useLocalUIMode={useLocalUIMode} useSeed={useSeed} seedData={useSeed ? seedData : {}} />
  );
}

export default App;

Example for Generic View

// Import specific Mediasfu view components
// Import the PreJoinPage component for the Pre-Join Page use case
import { MediasfuGeneric,
    MediasfuBroadcast, MediasfuChat, MediasfuWebinar, MediasfuConference, PreJoinPage
 } from 'mediasfu-reactnative-expo'


// Import methods for generating random participants, messages, requests, and waiting room lists if using seed data
import { generateRandomParticipants, generateRandomMessages, generateRandomRequestList, generateRandomWaitingRoomList,
} from 'mediasfu-reactnative-expo';

/**
 * The main application component for MediaSFU.
 *
 * This component initializes the necessary credentials and configuration for the MediaSFU application,
 * including options for using seed data for generating random participants and messages.
 *
 * @returns {JSX.Element} The rendered Mediasfu component with the specified props.
 *
 * @remarks
 * - The `credentials` object contains the API username and API key for the Mediasfu account.
 * - The `useSeed` flag determines whether to use seed data for generating random participants and messages.
 * - The `eventType` variable indicates the type of UI display (e.g., 'broadcast', 'chat', 'webinar', 'conference').
 * - If `useSeed` is true, random participants, messages, requests, and waiting lists are generated and assigned to `seedData`.
 * - The `useLocalUIMode` flag is set to true if `useSeed` is true, preventing requests to the Mediasfu servers during UI development.
 *
 * @component
 * @example
 * // Example usage of the App component
 * <App />
 */

const App = () => {
  // Mediasfu account credentials
  // Replace 'your_api_username' and 'your_api_key' with your actual credentials
  const credentials = { apiUserName: 'your_api_username', apiKey: 'your_api_key' };

  // Whether to use seed data for generating random participants and messages
  // Set to true if you want to run the application in local UI mode with seed data
  const useSeed = false;
  let seedData = {};

  // Event type ('broadcast', 'chat', 'webinar', 'conference')
  // Set this to match the component you are using
  let eventType = 'broadcast';

  // If using seed data, generate random participants and messages
  if (useSeed) {
    // Name of the member
    const memberName = 'Prince';

    // Name of the host
    const hostName = 'Fred';

    // Generate random participants
    const participants_ = generateRandomParticipants({
      member: memberName,
      coHost: '',
      host: hostName,
      forChatBroadcast: eventType === 'broadcast' || eventType === 'chat',
    });

    // Generate random messages
    const messages_ = generateRandomMessages({
      participants: participants_,
      member: memberName,
      host: hostName,
      forChatBroadcast: eventType === 'broadcast' || eventType === 'chat',
    });

    // Generate random requests
    const requests_ = generateRandomRequestList({
      participants: participants_,
      hostName: memberName,
      coHostName: '',
      numberOfRequests: 3,
    });

    // Generate random waiting list
    const waitingList_ = generateRandomWaitingRoomList();

    // Assign generated data to seedData
    seedData = {
      participants: participants_,
      messages: messages_,
      requests: requests_,
      waitingList: waitingList_,
      member: memberName,
      host: hostName,
      eventType: eventType,
    };
  }

  // Whether to use local UI mode; prevents making requests to the Mediasfu servers during UI development
  const useLocalUIMode = useSeed;

  // Choose the Mediasfu component based on the event type
  // Uncomment the component corresponding to your use case

  // Simple Use Case (Welcome Page)
  // Renders the default welcome page
  // No additional inputs required
  // return <MediasfuGeneric />;

  // Use Case with Pre-Join Page (Credentials Required)
  // Uses a pre-join page that requires users to enter credentials
  // return <MediasfuGeneric PrejoinPage={PreJoinPage} credentials={credentials} />;

  // Use Case with Local UI Mode (Seed Data Required)
  // Runs the application in local UI mode using seed data
  // return <MediasfuGeneric useLocalUIMode={true} useSeed={true} seedData={seedData} />;

  // MediasfuBroadcast Component
  // Uncomment to use the broadcast event type
  // return (
  //   <MediasfuBroadcast
  //     credentials={credentials}
  //     useLocalUIMode={useLocalUIMode}
  //     useSeed={useSeed}
  //     seedData={useSeed ? seedData : {}}
  //   />
  // );

  // MediasfuChat Component
  // Uncomment to use the chat event type
  // return (
  //   <MediasfuChat
  //     credentials={credentials}
  //     useLocalUIMode={useLocalUIMode}
  //     useSeed={useSeed}
  //     seedData={useSeed ? seedData : {}}
  //   />
  // );

  // MediasfuWebinar Component
  // Uncomment to use the webinar event type
  // return (
  //   <MediasfuWebinar
  //     credentials={credentials}
  //     useLocalUIMode={useLocalUIMode}
  //     useSeed={useSeed}
  //     seedData={useSeed ? seedData : {}}
  //   />
  // );

  // MediasfuConference Component
  // Uncomment to use the conference event type
  // return (
  //   <MediasfuConference
  //     credentials={credentials}
  //     useLocalUIMode={useLocalUIMode}
  //     useSeed={useSeed}
  //     seedData={useSeed ? seedData : {}}
  //   />
  // );

  // Default to MediasfuGeneric without any props
  // This will render the welcome page
  return <MediasfuGeneric />;
};

export default App;

In the provided examples, users can set useLocalUIMode to true during UI development to prevent unwanted connections to MediaSFU servers. Additionally, they can generate seed data for rendering UI components locally by using random data generators provided by the module.

Local UI Development in MediaSFU React Native (Expo) Module

During local UI development, the MediaSFU view is designed to be responsive to changes in screen size and orientation, adapting its layout accordingly. However, since UI changes are typically linked to communication with servers, developing the UI locally might result in less responsiveness due to the lack of real-time data updates. To mitigate this, users can force trigger changes in the UI by rotating the device, resizing the window, or simulating server responses by clicking on buttons within the page.

While developing locally, users may encounter occasional error warnings as the UI attempts to communicate with the server. These warnings can be safely ignored, as they are simply indicative of unsuccessful server requests in the local development environment.

Intermediate Usage Guide

Expands on the basic usage, covering more advanced features and scenarios.

Intermediate Usage Guide

In the Intermediate Usage Guide, we'll explore the core components and functionalities of the MediaSFU ReactNative-Expo module, focusing on media display, controls, and modal interactions. Click on any listed component/method to open the full documentation.

Core Components Overview

The main items displayed on an event page are media components (such as video, audio, and blank cards) and control components (for pagination, navigation, etc.).

Media Display Components

| Component Name | Description | |--------------------------|-------------------------------------------------------------------------------------------------| | MainAspectComponent | Serves as a container for the primary aspect of the user interface, typically containing the main content or focus of the application. | | MainScreenComponent | Responsible for rendering the main screen layout of the application, providing the foundation for displaying various elements and content. | | MainGridComponent | Crucial part of the user interface, organizing and displaying primary content or elements in a grid layout format. | | SubAspectComponent | Acts as a secondary container within the user interface, often housing additional elements or controls related to the main aspect. | | MainContainerComponent | Primary container for the application's content, encapsulating all major components and providing structural organization. | | OtherGridComponent | Complements the Main Grid Component by offering additional grid layouts, typically used for displaying secondary or auxiliary content. |

Control Components

| Component Name | Description | |-------------------------------|-------------------------------------------------------------------------------------------------| | ControlButtonsComponent | Comprises a set of buttons or controls used for navigating, interacting, or managing various aspects of the application's functionality. | | ControlButtonsAltComponent | Provides alternative button configurations or styles for controlling different aspects of the application. | | ControlButtonsComponentTouch | Specialized component designed for touch-enabled devices, offering floating buttons or controls for intuitive interaction with the application's features. |

These components collectively contribute to the overall user interface, facilitating navigation, interaction, and content display within the application.

Modal Components

| Modal Component | Description | |-----------------|-------------| | LoadingModal | Modal for displaying loading indicator during data fetching or processing. | | MainAspectComponent | Component responsible for displaying the main aspect of the event page. | | ControlButtonsComponent | Component for displaying control buttons such as pagination controls. | | ControlButtonsAltComponent | Alternate control buttons component for specific use cases. | | ControlButtonsComponentTouch | Touch-enabled control buttons component for mobile devices. | | OtherGridComponent | Component for displaying additional grid elements on the event page. | | MainScreenComponent | Component for rendering the main screen content of the event. | | MainGridComponent | Main grid component for displaying primary event content. | | SubAspectComponent | Component for displaying secondary aspects of the event page. | | MainContainerComponent | Main container component for the event page content. | | AlertComponent | Modal for displaying alert messages to the user. | | MenuModal | Modal for displaying a menu with various options. | | RecordingModal | Modal for managing recording functionality during the event. | | RequestsModal | Modal for handling requests from participants during the event. | | WaitingRoomModal | Modal for managing waiting room functionality during the event. | | DisplaySettingsModal | Modal for adjusting display settings during the event. | | EventSettingsModal | Modal for configuring event settings. | | CoHostModal | Modal for managing co-host functionality during the event. | | ParticipantsModal | Modal for displaying participant information and controls. | | MessagesModal | Modal for managing messages and chat functionality during the event. | | MediaSettingsModal | Modal for adjusting media settings during the event. | | ConfirmExitModal | Modal for confirming exit from the event. | | ConfirmHereModal | Modal for confirming certain actions or selections. | | ShareEventModal | Modal for sharing the event with others. | | WelcomePage | Welcome page modal for the event. | | PreJoinPage | Prejoin page modal for the event. | | PollModal | Modal for conducting polls or surveys during the event. | | BreakoutRoomsModal | Modal for managing breakout rooms during the event. |

Modal Interactions

Each modal has corresponding functions to trigger its usage:

  1. launchMenuModal: Launches the menu modal for settings and configurations.
  2. launchRecording: Initiates the recording modal for recording functionalities.
  3. startRecording: Starts the recording process.
  4. confirmRecording: Confirms and finalizes the recording.
  5. launchWaiting: Opens the waiting room modal for managing waiting room interactions.
  6. launchCoHost: Opens the co-host modal for managing co-host functionalities.
  7. launchMediaSettings: Launches the media settings modal for adjusting media-related configurations.
  8. launchDisplaySettings: Opens the display settings modal for adjusting display configurations.
  9. launchSettings: Initiates the settings modal for general event settings and configurations.
  10. launchRequests: Opens the requests modal for managing user requests.
  11. launchParticipants: Displays the participants modal for viewing and managing event participants.
  12. launchMessages: Opens the messages modal for communication through chat messages.
  13. launchConfirmExit: Prompts users to confirm before exiting the event.

Media Display and Controls

These components facilitate media display and control functionalities:

  1. Pagination: Handles pagination and page switching.
  2. FlexibleGrid: Renders flexible grid layouts for media display.
  3. FlexibleVideo: Displays videos in a flexible manner within the grid.
  4. AudioGrid: Renders audio components within the grid layout.

These components enable seamless media presentation and interaction within the event environment, providing users with a rich and immersive experience.

| UI Media Component | Description | |--------------|-------------| | MeetingProgressTimer | Component for displaying a timer indicating the progress of a meeting or event. | | MiniAudio | Component for rendering a compact audio player with basic controls. | | MiniCard | Component for displaying a minimized card view with essential information. | | AudioCard | Component for displaying audio content with control elements, details, and audio decibels. | | VideoCard | Component for displaying video content with control elements, details, and audio decibels. | | CardVideoDisplay | Video player component for displaying embedded videos with controls and details. | | MiniCardAudio | Component for rendering a compact card view with audio content and controls. | | MiniAudioPlayer | Utility method for playing audio and rendering a mini audio modal when the user is not actively displayed on the page. |


With the Intermediate Usage Guide, users can explore and leverage the core components and functionalities of the MediaSFU ReactNative-Expo module to enhance their event hosting and participation experiences.

Here's a sample import and usage code for a Broadcast screen:

import React, { useState, useEffect, useRef } from 'react';
import { PrejoinPage, MainContainerComponent, MainAspectComponent, MainScreenComponent, MainGridComponent, FlexibleVideo, ControlButtonsComponentTouch, AudioGrid } from 'mediasfu-reactnative-expo';

const BroadcastScreen = () => {
    // State variables and constants
    const [validated, setValidated] = useState<boolean>(useLocalUIMode); // Validated state as boolean
    const confirmedToRecord = useRef<boolean>(false); // True if the user has confirmed to record as boolean
    const meetingDisplayType = useRef<string>("media"); // Meeting display type as string

    // Sample control button configurations
    const controlBroadcastButtons = [/* define your control buttons here */];
    const recordButton = [/* define your record button here */];
    const recordButtons = [/* define your record buttons here */];

    // Sample component sizes
    const componentSizes = useRef<ComponentSizes>({
        // Component sizes as ComponentSizes
        mainHeight: 0,
        otherHeight: 0,
        mainWidth: 0,
        otherWidth: 0,
    }); // Component sizes

    // Sample function to update component sizes
    const updateComponentSizes = (sizes: ComponentSizes) => {
        componentSizes.current = sizes;
    };


    // Sample function to update validation state
    const updateValidated = (value: boolean) => {
        setValidated(value);
    };

    // Sample credentials
    const credentials = {
        apiUserName: "yourAPIUserName",
        apiKey: "yourAPIKey"
    };

    // Sample socket
    const socket = useRef<Socket>({} as Socket); // Socket for the media server, type Socket 

    // Sample meeting progress time
    const [meetingProgressTime, setMeetingProgressTime] =
    useState<string>("00:00:00"); // Meeting progress time as string

    // Sample record state
    const [recordState, setRecordState] = useState<string>("green"); // Recording state with specific values

    // Sample main grid and other grid elements
    const mainGridStream = useRef<JSX.Element[]>([]); // Array of main grid streams as JSX.Element[]
    const [otherGridStreams, setOtherGridStreams] = useState<JSX.Element[][]>([
        [],
        [],
    ]); // Other grid streams as 2D array of JSX.Element[]
  

    // Sample audio only streams
    const audioOnlyStreams = useRef<JSX.Element[]>([]); // Array of audio-only streams

    // Sample main height and width
    const [mainHeightWidth, setMainHeightWidth] = useState<number>(100); // Main height and width as number

    // Render the PrejoinPage if not validated, otherwise render the main components
    return (
    <SafeAreaProvider
      style={{
        marginTop: Platform.OS === "ios" ? 0 : Constants.statusBarHeight,
      }}
    >
      <StatusBar
        animated
        hidden={false}
        networkActivityIndicatorVisible={true}
        translucent={true} // Keep this if you want the content to show behind the status bar
        backgroundColor="rgba(0, 0, 0, 0.2)" 
        style="light"
      />

      {/* MainContainerComponent displays the room and controls */}
      {/* WelcomePage is for authentication of room credentials */}

      {!validated ? (
        <PrejoinPage
          parameters={{
            imgSrc,
            showAlert,
            updateIsLoadingModalVisible,
            connectSocket,
            updateSocket,
            updateValidated,
            updateApiUserName,
            updateApiToken,
            updateLink,
            updateRoomName,
            updateMember,
          }}
          credentials={credentials}
        />
      ) : (
        <MainContainerComponent>
          {/* Main aspect component containsa ll but the control buttons (as used for webinar and conference) */}
          <MainAspectComponent
            backgroundColor="rgba(217, 227, 234, 0.99)"
            defaultFraction={1 - controlHeight}
            updateIsWideScreen={updateIsWideScreen}
            updateIsMediumScreen={updateIsMediumScreen}
            updateIsSmallScreen={updateIsSmallScreen}
            showControls={
              eventType.current === "webinar" ||
              eventType.current === "conference"
            }
          >
            {/* MainScreenComponent contains the main grid view and the minor grid view */}
            <MainScreenComponent
              doStack={true}
              mainSize={mainHeightWidth}
              updateComponentSizes={updateComponentSizes}
              defaultFraction={1 - controlHeight}
              componentSizes={componentSizes.current}
              showControls={
                eventType.current === "webinar" ||
                eventType.current === "conference"
              }
            >
              {/* MainGridComponent shows the main grid view - not used at all in chat event type  and conference event type when screenshare is not active*/}
              {/* MainGridComponent becomes the dominant grid view in broadcast and webinar event types */}
              {/* MainGridComponent becomes the dominant grid view in conference event type when screenshare is active */}

              <MainGridComponent
                height={componentSizes.current.mainHeight}
                width={componentSizes.current.mainWidth}
                backgroundColor="rgba(217, 227, 234, 0.99)"
                showAspect={mainHeightWidth > 0 ? true : false}
                timeBackgroundColor={recordState}
                meetingProgressTime={meetingProgressTime}
              >
                <FlexibleVideo
                  customWidth={componentSizes.current.mainWidth}
                  customHeight={componentSizes.current.mainHeight}
                  rows={1}
                  columns={1}
                  componentsToRender={
                    mainGridStream.current ? mainGridStream.current : []
                  }
                  showAspect={
                    mainGridStream.current.length > 0 &&
                    !(whiteboardStarted.current && !whiteboardEnded.current)
                  }
                />

                <ControlButtonsComponentTouch
                  buttons={controlBroadcastButtons}
                  position={"right"}
                  location={"bottom"}
                  direction={"vertical"}
                  showAspect={eventType.current === "broadcast"}
                />

                {/* Button to launch recording modal */}
                <ControlButtonsComponentTouch
                  buttons={recordButton}
                  direction={"horizontal"}
                  showAspect={
                    eventType.current === "broadcast" &&
                    !showRecordButtons &&
                    islevel.current === "2"
                  }
                  location="bottom"
                  position="middle"
                />

                {/* Buttons to control recording */}
                <ControlButtonsComponentTouch
                  buttons={recordButtons}
                  direction={"horizontal"}
                  showAspect={
                    eventType.current === "broadcast" &&
                    showRecordButtons &&
                    islevel.current === "2"
                  }
                  location="bottom"
                  position="middle"
                />
              </MainGridComponent>

              {/* AudioGrid contains all the audio only streams */}
              {/* If broadcasting and there are audio only streams (just one), the audio only streams are displayed in the main grid view */}
              {/* If webinar and you are the host, the audio only streams (just one), are displayed in the main grid view */}
              <AudioGrid
                componentsToRender={
                  audioOnlyStreams.current ? audioOnlyStreams.current : []
                }
              />
            </MainScreenComponent>
          </MainAspectComponent>
        </MainContainerComponent>
      )}

      <ParticipantsModal
        backgroundColor="rgba(217, 227, 234, 0.99)"
        isParticipantsModalVisible={isParticipantsModalVisible}
        onParticipantsClose={() => updateIsParticipantsModalVisible(false)}
        participantsCounter={participantsCounter.current}
        onParticipantsFilterChange={onParticipantsFilterChange}
        parameters={{
          updateParticipants: updateParticipants,
          updateIsParticipantsModalVisible: updateIsParticipantsModalVisible,

          updateDirectMessageDetails,
          updateStartDirectMessage,
          updateIsMessagesModalVisible,

          showAlert: showAlert,

          filteredParticipants: filteredParticipants.current,
          participants: filteredParticipants.current,
          roomName: roomName.current,
          islevel: islevel.current,
          member: member.current,
          coHostResponsibility: coHostResponsibility.current,
          coHost: coHost.current,
          eventType: eventType.current,

          startDirectMessage: startDirectMessage.current,
          directMessageDetails: directMessageDetails.current,
          socket: socket.current,

          getUpdatedAllParams: getAllParams,
        }}
      />

      <RecordingModal
        backgroundColor="rgba(217, 227, 234, 0.99)"
        isRecordingModalVisible={isRecordingModalVisible}
        onClose={() => updateIsRecordingModalVisible(false)}
        startRecording={startRecording}
        confirmRecording={confirmRecording}
        parameters={{
          ...getAllParams(),
          ...mediaSFUFunctions(),
        }}
      />

      <MessagesModal
        backgroundColor={
          eventType.current === "webinar" || eventType.current === "conference"
            ? "#f5f5f5"
            : "rgba(255, 255, 255, 0.25)"
        }
        isMessagesModalVisible={isMessagesModalVisible}
        onMessagesClose={() => updateIsMessagesModalVisible(false)}
        messages={messages.current}
        eventType={eventType.current}
        member={member.current}
        islevel={islevel.current}
        coHostResponsibility={coHostResponsibility.current}
        coHost={coHost.current}
        startDirectMessage={startDirectMessage.current}
        directMessageDetails={directMessageDetails.current}
        updateStartDirectMessage={updateStartDirectMessage}
        updateDirectMessageDetails={updateDirectMessageDetails}
        showAlert={showAlert}
        roomName={roomName.current}
        socket={socket.current}
        chatSetting={chatSetting.current}
      />

      <ConfirmExitModal
        backgroundColor="rgba(181, 233, 229, 0.97)"
        isConfirmExitModalVisible={isConfirmExitModalVisible}
        onConfirmExitClose={() => updateIsConfirmExitModalVisible(false)}
        member={member.current}
        roomName={roomName.current}
        socket={socket.current}
        islevel={islevel.current}
      />

      <ConfirmHereModal
        backgroundColor="rgba(181, 233, 229, 0.97)"
        isConfirmHereModalVisible={isConfirmHereModalVisible}
        onConfirmHereClose={() => updateIsConfirmHereModalVisible(false)}
        member={member.current}
        roomName={roomName.current}
        socket={socket.current}
      />

      <ShareEventModal
        isShareEventModalVisible={isShareEventModalVisible}
        onShareEventClose={() => updateIsShareEventModalVisible(false)}
        roomName={roomName.current}
        islevel={islevel.current}
        adminPasscode={adminPasscode.current}
        eventType={eventType.current}
      />

      <AlertComponent
        visible={alertVisible}
        message={alertMessage}
        type={alertType}
        duration={alertDuration}
        onHide={() => setAlertVisible(false)}
        textColor={"#ffffff"}
      />

      <LoadingModal
        isVisible={isLoadingModalVisible}
        backgroundColor="rgba(217, 227, 234, 0.99)"
        displayColor="black"
      />
    </SafeAreaProvider>
  );
};

export default BroadcastScreen;

This sample code demonstrates the import and usage of various components and features for a Broadcast screen, including rendering different UI components based on the validation state, handling socket connections, displaying video streams, controlling recording, and managing component sizes.

Here's a sample usage of the control button components as used above:

      const recordButton = [
      // recording button (to launch recording modal)
      // Replace or remove any of the buttons as you wish
      {
        icon: "record-vinyl",
        text: "Record",
        onPress: () => {
          // Action for the Record button
          launchRecording({
            updateIsRecordingModalVisible: updateIsRecordingModalVisible,
            isRecordingModalVisible: isRecordingModalVisible,
            showAlert: showAlert,
            stopLaunchRecord: stopLaunchRecord.current,
            canLaunchRecord: canLaunchRecord.current,
            recordingAudioSupport: recordingAudioSupport.current,
            recordingVideoSupport: recordingVideoSupport.current,
            updateCanRecord: updateCanRecord,
            updateClearedToRecord: updateClearedToRecord,
            recordStarted: recordStarted.current,
            recordPaused: recordPaused.current,
            localUIMode: localUIMode.current,
          });
        },
        activeColor: "black",
        inActiveColor: "black",
        show: true,
      },
    ];

    const recordButtons = [
      // recording state control and recording timer buttons
      // Replace or remove any of the buttons as you wish

      // Refer to ControlButtonsAltComponent for more details on how to add custom buttons

      {
        // name: 'Pause',
        icon: "play-circle",
        active: recordPaused.current === false,
        onPress: () => {
          updateRecording({
            parameters: { ...getAllParams(), ...mediaSFUFunctions() },
          });
        },
        activeColor: "black",
        inActiveColor: "black",
        alternateIcon: "pause-circle",
        show: true,
      },
      {
        // name: 'Stop',
        icon: "stop-circle",
        active: false,
        onPress: () => {
          stopRecording({
            parameters: { ...getAllParams(), ...mediaSFUFunctions() },
          });
        },
        activeColor: "green",
        inActiveColor: "black",
        show: true,
      },
      {
        // name: 'Timer',
        customComponent: (
          <View
            style={{
              backgroundColor: "transparent",
              borderWidth: 0,
              padding: 0,
              margin: 2,
            }}
          >
            <Text
              style={{
                backgroundColor: "transparent",
                borderWidth: 0,
                padding: 0,
                margin: 0,
              }}
            >
              {recordingProgressTime}
            </Text>
          </View>
        ),
        show: true,
      },
      {
        // name: 'Status',
        icon: "dot-circle",
        active: false,
        onPress: () => console.log("Status pressed"),
        activeColor: "black",
        inActiveColor: recordPaused.current === false ? "red" : "yellow",
        show: true,
      },
      {
        // name: 'Settings',
        icon: "cog",
        active: false,
        onPress: () => {
          launchRecording({
            updateIsRecordingModalVisible: updateIsRecordingModalVisible,
            isRecordingModalVisible: isRecordingModalVisible,
            showAlert: showAlert,
            stopLaunchRecord: stopLaunchRecord.current,
            canLaunchRecord: canLaunchRecord.current,
            recordingAudioSupport: recordingAudioSupport.current,
            recordingVideoSupport: recordingVideoSupport.current,
            updateCanRecord: updateCanRecord,
            updateClearedToRecord: updateClearedToRecord,
            recordStarted: recordStarted.current,
            recordPaused: recordPaused.current,
            localUIMode: localUIMode.current,
          });
        },
        activeColor: "green",
        inActiveColor: "black",
        show: true,
      },
    ];

    const controlBroadcastButtons: ButtonTouch[] = [
      // control buttons for broadcast
      // Replace or remove any of the buttons as you wish

      // Refer to ControlButtonsComponentTouch.js for more details on how to add custom buttons

      {
        // users button
        icon: "users",
        active: true,
        alternateIcon: "users",
        onPress: () => {
          launchParticipants({
            updateIsParticipantsModalVisible,