diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx index e7bbf47fe4..0e71e923fa 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/05-ui-cookbook/02-replacing-call-controls.mdx @@ -4,10 +4,8 @@ description: A guide on how to add/remove or replace call controls --- import ImageShowcase from '@site/src/components/ImageShowcase'; -import CallControlsViewMediaButtonOn - from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png'; -import CallControlsViewMediaButtonOff - from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png'; +import CallControlsViewMediaButtonOn from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-on.png'; +import CallControlsViewMediaButtonOff from '../assets/05-ui-cookbook/02-replacing-call-controls/call-controls-button-media-off.png'; The Stream Video React Native SDK allows building your own Call controls view/layout. Call Controls View generally comprises several buttons that control the call. Each button controls its area of responsibility. Our task, as integrators, is to create a component that puts these buttons together as we wish. In this example, we intend to show how to do just that. @@ -151,48 +149,28 @@ const styles = StyleSheet.create({ Toggling microphone in an active call turns around publishing audio input streams and enabling the audio state. The bare-bones button to toggle audio in an active call could look like the following: ```tsx -import { useCallback } from 'react'; -import { Pressable, Text } from 'react-native'; -import { - SfuModels, - useLocalParticipant, - useMediaStreamManagement, -} from '@stream-io/video-react-native-sdk'; +import { useMediaStreamManagement } from '@stream-io/video-react-native-sdk'; export const ToggleAudioButton = () => { - const localParticipant = useLocalParticipant(); - const isAudioPublished = localParticipant?.publishedTracks.includes( - SfuModels.TrackType.AUDIO, - ); - - const { publishAudioStream, stopPublishingAudio } = - useMediaStreamManagement(); - - const toggleAudioMuted = useCallback(async () => { - if (isAudioPublished) { - stopPublishingAudio(); - } else { - await publishAudioStream(); - } - }, [isAudioPublished, publishAudioStream, stopPublishingAudio]); + const { isAudioMuted, toggleAudioMuted } = useMediaStreamManagement(); const audioButtonStyles = [ styles.button, { - backgroundColor: !isAudioPublished ? '#080707dd' : 'white', + backgroundColor: !isAudioMuted ? '#080707dd' : 'white', }, ]; const audioButtonTextStyles = [ styles.mediaButtonText, { - color: !isAudioPublished ? 'white' : '#080707dd', + color: !isAudioMuted ? 'white' : '#080707dd', }, ]; return ( - {isAudioPublished ? ( + {!isAudioMuted ? ( Audio On ) : ( Audio Off @@ -214,7 +192,7 @@ const styles = StyleSheet.create({ mediaButtonText: { textAlign: 'center', }, -}) +}); ``` We check the audio mute status by checking if `SfuModels.TrackType.AUDIO` exists in the array of types of tracks published by `localParticipant`. @@ -225,48 +203,28 @@ To mute and unmute the local participant's audio, we use `publishAudioStream` an Toggling video in an active call turns around publishing video input streams and enabling the video state. The bare-bones button to toggle video in an active call could look like the following: ```tsx -import { useCallback } from 'react'; -import { Pressable, Text } from 'react-native'; -import { - SfuModels, - useLocalParticipant, - useMediaStreamManagement, -} from '@stream-io/video-react-native-sdk'; - -export const ToggleVideoButton = () => { - const localParticipant = useLocalParticipant(); - const isVideoPublished = localParticipant?.publishedTracks.includes( - SfuModels.TrackType.VIDEO, - ); +import { useMediaStreamManagement } from '@stream-io/video-react-native-sdk'; - const { publishVideoStream, stopPublishingVideo } = - useMediaStreamManagement(); - - const toggleVideoMuted = useCallback(async () => { - if (isVideoPublished) { - stopPublishingVideo(); - } else { - await publishVideoStream(); - } - }, [isVideoPublished, publishVideoStream, stopPublishingVideo]); +export const ToggleAudioButton = () => { + const { isVideoMuted, toggleVideoMuted } = useMediaStreamManagement(); const videoButtonStyles = [ styles.button, { - backgroundColor: !isVideoPublished ? '#080707dd' : 'white', + backgroundColor: !isVideoMuted ? '#080707dd' : 'white', }, ]; const videoButtonTextStyles = [ styles.mediaButtonText, { - color: !isVideoPublished ? 'white' : '#080707dd', + color: !isVideoMuted ? 'white' : '#080707dd', }, ]; return ( - {isVideoPublished ? ( + {!isVideoMuted ? ( Video On ) : ( Video Off @@ -288,7 +246,7 @@ const styles = StyleSheet.create({ mediaButtonText: { textAlign: 'center', }, -}) +}); ``` We check the video mute status by checking if `SfuModels.TrackType.VIDEO` exists in the array of types of tracks published by `localParticipant`. @@ -301,10 +259,7 @@ Toggling camera face for mobile devices is an important feature. So, to built it The `isCameraOnFrontFacingMode` is a boolean that tracks if the camera is `front` facing. The `toggleCameraFacingMode` is used to toggle camera between `front` and `back`/`environment` facing modes. ```tsx -import { Pressable, Text } from 'react-native'; -import { - useMediaStreamManagement, -} from '@stream-io/video-react-native-sdk'; +import { useMediaStreamManagement } from '@stream-io/video-react-native-sdk'; export const ToggleCameraFaceButton = () => { const { isCameraOnFrontFacingMode, toggleCameraFacingMode } = @@ -348,7 +303,7 @@ const styles = StyleSheet.create({ mediaButtonText: { textAlign: 'center', }, -}) +}); ``` ### Assembling it all together @@ -376,7 +331,7 @@ const styles = StyleSheet.create({ justifyContent: 'space-evenly', paddingVertical: 10, }, -}) +}); ``` #### Media Button @@ -415,5 +370,5 @@ const styles = StyleSheet.create({ justifyContent: 'space-evenly', paddingVertical: 10, }, -}) +}); ``` diff --git a/packages/react-native-sdk/src/components/utility/internal/ToggleAudioButton.tsx b/packages/react-native-sdk/src/components/utility/internal/ToggleAudioButton.tsx index c1807f9fdf..fecc7b0fbf 100644 --- a/packages/react-native-sdk/src/components/utility/internal/ToggleAudioButton.tsx +++ b/packages/react-native-sdk/src/components/utility/internal/ToggleAudioButton.tsx @@ -7,22 +7,21 @@ import { useI18n, } from '@stream-io/video-react-bindings'; import { CallControlsButton } from './CallControlsButton'; -import { useCallControls, usePermissionNotification } from '../../../hooks'; +import { usePermissionNotification } from '../../../hooks'; import { theme } from '../../../theme'; import { Mic, MicOff } from '../../../icons'; import { Alert, StyleSheet } from 'react-native'; import { muteStatusColor } from '../../../utils'; +import { useMediaStreamManagement } from '../../../providers'; export const ToggleAudioButton = () => { const [isAwaitingApproval, setIsAwaitingApproval] = useState(false); - const { toggleAudioMuted, isAudioPublished } = useCallControls(); + const { isAudioMuted, toggleAudioMuted } = useMediaStreamManagement(); const userHasSendAudioCapability = useHasPermissions( OwnCapability.SEND_AUDIO, ); const { t } = useI18n(); - const isAudioMuted = !isAudioPublished; - usePermissionNotification({ permission: OwnCapability.SEND_AUDIO, messageApproved: t('You can now speak.'), diff --git a/packages/react-native-sdk/src/components/utility/internal/ToggleVideoButton.tsx b/packages/react-native-sdk/src/components/utility/internal/ToggleVideoButton.tsx index 935dcd6499..04a96d3964 100644 --- a/packages/react-native-sdk/src/components/utility/internal/ToggleVideoButton.tsx +++ b/packages/react-native-sdk/src/components/utility/internal/ToggleVideoButton.tsx @@ -11,15 +11,14 @@ import { muteStatusColor } from '../../../utils'; import { Alert, StyleSheet } from 'react-native'; import { theme } from '../../../theme'; import { Video, VideoSlash } from '../../../icons'; -import { useCallControls, usePermissionNotification } from '../../../hooks'; +import { usePermissionNotification } from '../../../hooks'; +import { useMediaStreamManagement } from '../../../providers'; export const ToggleVideoButton = () => { const [isAwaitingApproval, setIsAwaitingApproval] = useState(false); - const { toggleVideoMuted, isVideoPublished } = useCallControls(); + const { isVideoMuted, toggleVideoMuted } = useMediaStreamManagement(); const { t } = useI18n(); - const isVideoMuted = !isVideoPublished; - const userHasSendVideoCapability = useHasPermissions( OwnCapability.SEND_VIDEO, ); diff --git a/packages/react-native-sdk/src/hooks/index.ts b/packages/react-native-sdk/src/hooks/index.ts index 8a2f4fb445..a4178eed39 100644 --- a/packages/react-native-sdk/src/hooks/index.ts +++ b/packages/react-native-sdk/src/hooks/index.ts @@ -1,8 +1,6 @@ -export * from './useCallControls'; export * from './useLocalVideoStream'; export * from './useIncallManager'; export * from './usePermissionRequest'; export * from './usePermissionNotification'; export * from './push'; export * from './useAndroidKeepCallAliveEffect'; -export * from './useCallControls'; diff --git a/packages/react-native-sdk/src/hooks/useCallControls.tsx b/packages/react-native-sdk/src/hooks/useCallControls.tsx deleted file mode 100644 index 4ba71f59f5..0000000000 --- a/packages/react-native-sdk/src/hooks/useCallControls.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { SfuModels } from '@stream-io/video-client'; -import { useLocalParticipant } from '@stream-io/video-react-bindings'; -import { useCallback } from 'react'; -import { useMediaStreamManagement } from '../providers/MediaStreamManagement'; - -/** - * A helper hook which exposes audio, video publishing state and - * their respective functions to toggle state - * - * @category Device Management - */ -export const useCallControls = () => { - const localParticipant = useLocalParticipant(); - - const { - publishAudioStream, - publishVideoStream, - stopPublishingAudio, - stopPublishingVideo, - } = useMediaStreamManagement(); - - const isAudioPublished = localParticipant?.publishedTracks.includes( - SfuModels.TrackType.AUDIO, - ); - const isVideoPublished = localParticipant?.publishedTracks.includes( - SfuModels.TrackType.VIDEO, - ); - - const toggleVideoMuted = useCallback(async () => { - if (isVideoPublished) { - stopPublishingVideo(); - } else { - publishVideoStream(); - } - }, [isVideoPublished, publishVideoStream, stopPublishingVideo]); - - const toggleAudioMuted = useCallback(async () => { - if (isAudioPublished) { - stopPublishingAudio(); - } else { - publishAudioStream(); - } - }, [isAudioPublished, publishAudioStream, stopPublishingAudio]); - - return { - isAudioPublished, - isVideoPublished, - toggleAudioMuted, - toggleVideoMuted, - }; -}; diff --git a/packages/react-native-sdk/src/providers/MediaStreamManagement/index.tsx b/packages/react-native-sdk/src/providers/MediaStreamManagement/index.tsx index 6dc3c7ed79..efb52f7627 100644 --- a/packages/react-native-sdk/src/providers/MediaStreamManagement/index.tsx +++ b/packages/react-native-sdk/src/providers/MediaStreamManagement/index.tsx @@ -38,6 +38,14 @@ export type MediaStreamManagementContextAPI = { * Publishes video stream for currently selected video input (camera) device to other call participants. */ publishVideoStream: () => Promise; + /** + * Signals whether local audio stream is muted/unmuted when in the call. + */ + isAudioMuted: boolean; + /** + * Signals whether local video stream is muted/unmuted when in the call. + */ + isVideoMuted: boolean; /** * Signals whether audio stream will be published when the call is joined. */ @@ -60,6 +68,14 @@ export type MediaStreamManagementContextAPI = { * The latest value set will be used to decide, whether audio stream will be published when joining a call. */ toggleInitialVideoMuteState: () => void; + /** + * Toggles the video mute status, when in the call. + */ + toggleVideoMuted: () => void; + /** + * Toggles the audio mute status, when in the call. + */ + toggleAudioMuted: () => void; /** * Toggles the camera facing mode between front and back camera. */ @@ -89,9 +105,16 @@ const MediaStreamContext = */ export const MediaStreamManagement = ({ children }: PropsWithChildren<{}>) => { const call = useCall(); + const localParticipant = useLocalParticipant(); const callingState = useCallCallingState(); const videoDevices = useStreamVideoStoreValue((store) => store.videoDevices); const localVideoStream = useLocalParticipant()?.videoStream; + const isAudioMuted = !localParticipant?.publishedTracks.includes( + SfuModels.TrackType.AUDIO, + ); + const isVideoMuted = !localParticipant?.publishedTracks.includes( + SfuModels.TrackType.VIDEO, + ); const [isCameraOnFrontFacingMode, setIsCameraOnFrontFacingMode] = useState(true); @@ -152,6 +175,22 @@ export const MediaStreamManagement = ({ children }: PropsWithChildren<{}>) => { } }, [call, callingState]); + const toggleVideoMuted = useCallback(async () => { + if (isVideoMuted) { + publishVideoStream(); + } else { + stopPublishingVideo(); + } + }, [isVideoMuted, publishVideoStream, stopPublishingVideo]); + + const toggleAudioMuted = useCallback(async () => { + if (isAudioMuted) { + publishAudioStream(); + } else { + stopPublishingAudio(); + } + }, [isAudioMuted, publishAudioStream, stopPublishingAudio]); + const toggleInitialAudioMuteState = useCallback( () => setInitialAudioEnabled((prev) => { @@ -191,9 +230,13 @@ export const MediaStreamManagement = ({ children }: PropsWithChildren<{}>) => { const contextValue = useMemo(() => { return { + isAudioMuted, + isVideoMuted, initialAudioEnabled: initAudioEnabled, initialVideoEnabled: initVideoEnabled, isCameraOnFrontFacingMode, + toggleAudioMuted, + toggleVideoMuted, toggleInitialAudioMuteState, toggleInitialVideoMuteState, toggleCameraFacingMode, @@ -203,9 +246,13 @@ export const MediaStreamManagement = ({ children }: PropsWithChildren<{}>) => { stopPublishingVideo, }; }, [ + isAudioMuted, + isVideoMuted, initAudioEnabled, initVideoEnabled, isCameraOnFrontFacingMode, + toggleAudioMuted, + toggleVideoMuted, toggleInitialAudioMuteState, toggleInitialVideoMuteState, toggleCameraFacingMode,