Skip to content

Commit

Permalink
fix(react-native): initial media stream management according to BE an…
Browse files Browse the repository at this point in the history
…d SDK settings (#1110)
  • Loading branch information
khushal87 authored Sep 28, 2023
1 parent fd58bda commit cca7cf6
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 131 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useCallStateHooks } from '@stream-io/video-react-bindings';
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
import React from 'react';
import { useTheme } from '../../../contexts';
import { Mic, MicOff } from '../../../icons';
import { useMediaStreamManagement } from '../../../providers';
import { CallControlsButton } from './CallControlsButton';

/**
Expand All @@ -29,17 +28,16 @@ export const ToggleAudioPreviewButton = ({
variants: { buttonSizes },
},
} = useTheme();
const call = useCall();
const { useMicrophoneState } = useCallStateHooks();
const { status } = useMicrophoneState();

const { toggleInitialAudioMuteState } = useMediaStreamManagement();

const onPress = () => {
const onPress = async () => {
if (onPressHandler) {
onPressHandler();
return;
}
toggleInitialAudioMuteState();
await call?.microphone.toggle();
};

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React from 'react';
import { useCallStateHooks } from '@stream-io/video-react-bindings';
import { useMediaStreamManagement } from '../../../providers';
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
import { useTheme } from '../../../contexts';
import { CallControlsButton } from './CallControlsButton';
import { Video, VideoSlash } from '../../../icons';
Expand Down Expand Up @@ -29,15 +28,15 @@ export const ToggleVideoPreviewButton = ({
variants: { buttonSizes },
},
} = useTheme();
const { toggleInitialVideoMuteState } = useMediaStreamManagement();
const call = useCall();
const { useCameraState } = useCallStateHooks();
const { status } = useCameraState();
const onPress = () => {
const onPress = async () => {
if (onPressHandler) {
onPressHandler();
return;
}
toggleInitialVideoMuteState();
await call?.camera.toggle();
};

return (
Expand Down
7 changes: 1 addition & 6 deletions packages/react-native-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export * from './translations';

// Overriding 'StreamVideo' from '@stream-io/video-react-bindings'
// Explicitly re-exporting to resolve ambiguity.
export {
StreamVideo,
StreamCall,
MediaStreamManagement,
useMediaStreamManagement,
} from './providers';
export { StreamVideo, StreamCall, MediaStreamManagement } from './providers';

setClientDetails();
153 changes: 39 additions & 114 deletions packages/react-native-sdk/src/providers/MediaStreamManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import React, {
createContext,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from 'react';
import React, { PropsWithChildren, useEffect, useState } from 'react';
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
import { useAppStateListener } from '../utils/hooks';

Expand All @@ -21,29 +13,9 @@ export type MediaDevicesInitialState = {
initialVideoEnabled?: boolean;
};

/**
* API to control device enablement, device selection and media stream access for a call.
* @category Device Management
*/
export type MediaStreamManagementContextAPI = {
/**
* Toggles the initialAudioEnabled boolean flag.
* The latest value set will be used to decide, whether audio stream will be published when joining a call.
*/
toggleInitialAudioMuteState: () => void;
/**
* Toggles the initialAudioEnabled boolean flag.
* The latest value set will be used to decide, whether audio stream will be published when joining a call.
*/
toggleInitialVideoMuteState: () => void;
};

const MediaStreamContext =
createContext<MediaStreamManagementContextAPI | null>(null);

/**
*
* Provides `MediaStreamManagementContextAPI` that allow the integrators to handle:
* Provides `MediaStreamManagement` wrapper that allow the integrators to handle:
* 1. the initial device state enablement (for example in a custom lobby component)
* 2. media stream publishing
* @param params
Expand Down Expand Up @@ -71,121 +43,74 @@ export const MediaStreamManagement = ({
);

const [{ initialAudioEnabled, initialVideoEnabled }, setInitialDeviceState] =
useState({
initialAudioEnabled: !!propInitialAudioEnabled,
initialVideoEnabled: !!propInitialVideoEnabled,
useState<MediaDevicesInitialState>({
initialAudioEnabled: undefined,
initialVideoEnabled: undefined,
});

const settings = useCallSettings();

// if prop is set, use that value.. the prop should override the backend settings
// Use backend settings to set initial audio/video enabled state
// It is applied only if the prop was undefined -- meaning user did not provide any value
useEffect(() => {
if (!settings) {
return;
}

const { audio, video } = settings;
setInitialDeviceState((prev) => {
let initAudio = prev.initialAudioEnabled;
if (typeof propInitialAudioEnabled !== 'undefined') {
initAudio = propInitialAudioEnabled;
if (typeof propInitialAudioEnabled === 'undefined') {
initAudio = audio.mic_default_on;
}
let initVideo = prev.initialVideoEnabled;
if (typeof propInitialVideoEnabled !== 'undefined') {
initVideo = propInitialVideoEnabled;
if (typeof propInitialVideoEnabled === 'undefined') {
initVideo = video.camera_default_on;
}
return { initialAudioEnabled: initAudio, initialVideoEnabled: initVideo };
});
}, [propInitialAudioEnabled, propInitialVideoEnabled]);
}, [propInitialAudioEnabled, propInitialVideoEnabled, settings]);

// use backend settings to set initial audio/video enabled state
// ONLY if the prop was undefined -- meaning user did not provide any value
// Apply SDK settings to set the initial audio/video enabled state
useEffect(() => {
if (!settings) {
return;
}

const { audio, video } = settings;
setInitialDeviceState((prev) => {
let initAudio = prev.initialAudioEnabled;
if (
typeof propInitialAudioEnabled === 'undefined' &&
audio.mic_default_on
) {
initAudio = true;
if (typeof propInitialAudioEnabled !== 'undefined') {
initAudio = propInitialAudioEnabled;
}
let initVideo = prev.initialVideoEnabled;
if (
typeof propInitialVideoEnabled === 'undefined' &&
video.camera_default_on
) {
initVideo = true;
if (typeof propInitialVideoEnabled !== 'undefined') {
initVideo = propInitialVideoEnabled;
}
return { initialAudioEnabled: initAudio, initialVideoEnabled: initVideo };

return {
initialAudioEnabled: initAudio,
initialVideoEnabled: initVideo,
};
});
}, [propInitialAudioEnabled, propInitialVideoEnabled, settings]);
}, [propInitialAudioEnabled, propInitialVideoEnabled]);

// The main logic
// Enable or Disable the audio/video stream based on the initial state
useEffect(() => {
if (
initialAudioEnabled &&
(call?.microphone.state.status === undefined ||
call?.microphone.state.status === 'disabled')
) {
if (initialAudioEnabled === undefined) {
return;
}
if (initialVideoEnabled === undefined) {
return;
}
if (initialAudioEnabled) {
call?.microphone.enable();
} else if (
!initialAudioEnabled &&
call?.microphone.state.status === 'enabled'
) {
} else {
call?.microphone.disable();
}

if (
initialVideoEnabled &&
(call?.camera.state.status === undefined ||
call?.camera.state.status === 'disabled')
) {
if (initialVideoEnabled) {
call?.camera.enable();
} else if (
!initialVideoEnabled &&
call?.camera.state.status === 'enabled'
) {
} else {
call?.camera.disable();
}
}, [call, initialAudioEnabled, initialVideoEnabled]);

const toggleInitialAudioMuteState = useCallback(() => {
call?.microphone.state.status === 'enabled'
? call?.microphone.disable()
: call?.microphone.enable();
}, [call]);

const toggleInitialVideoMuteState = useCallback(() => {
call?.camera.state.status === 'enabled'
? call?.camera.disable()
: call?.camera.enable();
}, [call]);

const contextValue = useMemo(() => {
return {
toggleInitialAudioMuteState,
toggleInitialVideoMuteState,
};
}, [toggleInitialAudioMuteState, toggleInitialVideoMuteState]);

return (
<MediaStreamContext.Provider value={contextValue}>
{children}
</MediaStreamContext.Provider>
);
};

/**
* Context consumer retrieving MediaStreamManagementContextAPI.
* @returns
*
* @category Device Management
*/
export const useMediaStreamManagement = () => {
const value = useContext(MediaStreamContext);
if (!value) {
console.warn('Null MediaDevicesContext');
}
return value as MediaStreamManagementContextAPI;
return <>{children}</>;
};

0 comments on commit cca7cf6

Please sign in to comment.