From 53b4abd80c17fc21d7f3a93dd103946ef0b3f080 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Tue, 12 Sep 2023 12:28:28 +0200 Subject: [PATCH] fix(react-native): remove method to inform SDK about native permissions (#1072) Co-authored-by: Vishal Narkhede --- packages/react-native-sdk/README.md | 4 +- .../02-installation/01-react-native.mdx | 17 +-- .../01-setup/02-installation/02-expo.mdx | 12 +- .../02-tutorials/01-video-calling.mdx | 4 - .../02-tutorials/02-audio-room.mdx | 4 - .../02-tutorials/03-livestream.mdx | 11 -- .../03-core/08-native-permissions.mdx | 142 ++---------------- .../CallControls/ToggleAudioPreviewButton.tsx | 6 +- .../CallControls/ToggleVideoPreviewButton.tsx | 7 +- .../src/providers/MediaDevices.tsx | 61 -------- .../src/providers/MediaStreamManagement.tsx | 131 +++++++++------- .../src/providers/StreamVideo.tsx | 2 - .../react-native-sdk/src/translations/en.json | 4 +- .../src/utils/StreamVideoRN/index.ts | 21 --- .../src/utils/StreamVideoRN/permissions.ts | 25 --- .../android/app/src/main/AndroidManifest.xml | 7 +- .../dogfood/src/hooks/useSyncPermissions.ts | 62 +------- .../components/MeetingUI.tsx | 6 - .../components/UsersList.tsx | 7 +- 19 files changed, 117 insertions(+), 416 deletions(-) delete mode 100644 packages/react-native-sdk/src/providers/MediaDevices.tsx delete mode 100644 packages/react-native-sdk/src/utils/StreamVideoRN/permissions.ts diff --git a/packages/react-native-sdk/README.md b/packages/react-native-sdk/README.md index db8a952846..6bda166633 100644 --- a/packages/react-native-sdk/README.md +++ b/packages/react-native-sdk/README.md @@ -126,10 +126,10 @@ Stream's video roadmap and changelog are available [here](https://github.com/Get ### 0.5 Milestones - [ ] Audio & Video filters -- [ ] Screensharing +- [ ] Screen-share media track support - [ ] CPU usage improvement - [ ] Analytics Integration -- [ ] Demo app on playstore and app store +- [ ] Demo app on play-store and app-store - [ ] Long press to focus - [ ] Dynascale 2.0 - [ ] Test coverage diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/01-react-native.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/01-react-native.mdx index 92065a639c..89a1f95803 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/01-react-native.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/01-react-native.mdx @@ -115,7 +115,7 @@ If you require R8/ProGuard support then in `android/app/proguard-rules.pro` add ### Declaring Permissions -Making video or audio calls requires the usage of the device's camera and microphone accordingly. Therefore, before you can make and answer calls, you need to request permission to use them within your application. First, in both platforms we must declare the permissions. +Making video or audio calls requires the usage of the device's camera and microphone accordingly. In both platforms, we must declare the permissions. #### iOS @@ -154,21 +154,6 @@ If you plan to also support Bluetooth devices then also add the following. ``` -:::note -The SDK needs to know to the state of camera and microphone permissions to get video and audio streams. We can inform the states to the SDK through the following: - -```ts -import { StreamVideoRN } from '@stream-io/video-react-native-sdk'; - -StreamVideoRN.setPermissions({ - isCameraPermissionGranted: true, - isMicPermissionGranted: true, -}); -``` - -We have discussed a detailed solution to manage native runtime permissions in the [Manage Native Permissions](../../core/native-permissions) guide. -::: - ### Optional peer dependencies Some of the optional features we provide require additional dependencies to be installed in order to work properly. diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/02-expo.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/02-expo.mdx index 4f63c48841..359babe425 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/02-expo.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/01-setup/02-installation/02-expo.mdx @@ -58,7 +58,7 @@ Add the config plugin for [`@stream-io/video-react-native-sdk`](https://github.c ``` :::note -The `POST_NOTIFICATIONS`, `BLUETOOTH_CONNECT`, `BLUETOOTH` and `BLUETOOTH_ADMIN` permissions need to be requested and granted by the user as well. [PermissionsAndroid](https://reactnative.dev/docs/permissionsandroid) module can be used to request permissions in Android. For example, below is a way to request Bluetooth permissions in Android according to the OS version: +The `POST_NOTIFICATIONS` and `BLUETOOTH_CONNECT` permissions need to be requested and granted by the user as well. [PermissionsAndroid](https://reactnative.dev/docs/permissionsandroid) module can be used to request permissions in Android. For example, below is a way to request permissions in Android: ```js import { useEffect } from 'react'; @@ -66,16 +66,12 @@ import { PermissionsAndroid, Platform } from 'react-native'; useEffect(() => { const run = async () => { - if (Platform.OS === 'android' && Platform.Version <= 30) { + if (Platform.OS === 'android') { // highlight-start await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADMIN, + 'android.permission.POST_NOTIFICATIONS', + 'android.permission.BLUETOOTH_CONNECT', ]); - } else { - await PermissionsAndroid.request( - PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, - ); // highlight-end } }; diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/01-video-calling.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/01-video-calling.mdx index 501661f21d..d2ccf04dd8 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/01-video-calling.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/01-video-calling.mdx @@ -164,10 +164,6 @@ Add the following keys and values to `Info.plist` file, under `dict` tag. -:::note -For simplicity, in this tutorial, we do not cover about managing native runtime permissions. Before starting the next step, ensure that microphone permissions are given for this app by granting them in the native settings app. We have discussed a detailed solution to manage native runtime permissions in the [Manage Native Permissions](../../core/native-permissions) guide. -::: - #### Android Specific installation In `android/app/build.gradle` add the following inside the `android` section: diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/02-audio-room.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/02-audio-room.mdx index 6d863129d3..74916e23c5 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/02-audio-room.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/02-audio-room.mdx @@ -153,10 +153,6 @@ Add the following keys and values to `Info.plist` file, under `dict` tag. -:::note -For simplicity, in this tutorial, we do not cover about managing native runtime permissions. Before starting the next step, ensure that microphone permissions are given for this app by granting them in the native settings app. We have discussed a detailed solution to manage native runtime permissions in the [Manage Native Permissions](../../core/native-permissions) guide. -::: - #### Android Specific installation In `android/app/build.gradle` add the following inside the `android` section: diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/03-livestream.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/03-livestream.mdx index 737e9aa899..8a670e45c7 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/03-livestream.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/02-tutorials/03-livestream.mdx @@ -163,10 +163,6 @@ Add the following keys and values to `Info.plist` file, under `dict` tag. -:::note -For simplicity, in this tutorial, we do not cover about managing native runtime permissions. Before starting the next step, ensure that microphone permissions are given for this app by granting them in the native settings app. We have discussed a detailed solution to manage native runtime permissions in the [Manage Native Permissions](../../core/native-permissions) guide. -::: - #### Android Specific installation In `android/app/build.gradle` add the following inside the `android` section: @@ -216,17 +212,10 @@ import { StreamCall, StreamVideo, StreamVideoClient, - StreamVideoRN, User, } from '@stream-io/video-react-native-sdk'; import { SafeAreaView, Text } from 'react-native'; -// for simplicity, we assume that permission was granted through the native settings app -StreamVideoRN.setPermissions({ - isCameraPermissionGranted: true, - isMicPermissionGranted: true, -}); - const apiKey = 'REPLACE_WITH_API_KEY'; // the API key can be found in the "Credentials" section const token = 'REPLACE_WITH_TOKEN'; // the token can be found in the "Credentials" section const userId = 'REPLACE_WITH_USER_ID'; // the user id can be found in the "Credentials" section diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/08-native-permissions.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/08-native-permissions.mdx index e9532a4dfc..f39a573087 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/08-native-permissions.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/03-core/08-native-permissions.mdx @@ -3,22 +3,9 @@ id: native-permissions title: Manage Native Permissions --- -Rendering a video call or audio call requires native platform permissions, which are mandatory to access a camera and microphone respectively. The SDK needs to know the state of the permission of the camera and microphone to get video and audio streams. We can inform the states to the SDK through the following: +In this guide, we will create a function to request the native permissions required for the app. -```ts -import { StreamVideoRN } from '@stream-io/video-react-native-sdk'; - -StreamVideoRN.setPermissions({ - isCameraPermissionGranted: true, - isMicPermissionGranted: true, -}); -``` - -In this guide, we will create a hook named `useSyncPermissions` to manage the native permissions required for the app. This hook will be used to request the app's permissions and inform them to the SDK. - -The hook will request the relevant permissions when the screen is mounted and also informs the permission state to the SDK when the app state changes to the foreground. This ensures the latest permissions are synced with the SDK. That way the SDK can get video and audio streams for a call. - -Once the component using this hook is mounted, we should see permissions being requested like below: +Once the function is called, we should see permissions being requested like below: ![Preview of the final result](../assets/03-core/08-native-permissions/permissions.png) @@ -26,7 +13,7 @@ Once the component using this hook is mounted, we should see permissions being r Ensure that relevant permissions are declared in your `AndroidManifest.xml` and `Info.plist` as mentioned in the [installation](../setup/installation/) guide. -Additionally, to request permissions in both platforms easily we will use the [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) library. You can run the following command to install it: +Additionally, to easily request permissions on both platforms, we will use the [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) library. You can run the following command to install it: ```bash title=Terminal yarn add react-native-permissions @@ -34,7 +21,7 @@ yarn add react-native-permissions #### iOS -Additionally, for iOS, you need update your `package.json` by adding the permissions used in your app. +Additionally, for iOS, you need to update your `package.json` by adding the permissions used in your app. ```js title=package.json { @@ -52,44 +39,9 @@ npx react-native setup-ios-permissions npx pod-install ``` -## Step 1 - Add functions to update permissions to the SDK - -In this step, we create two functions called `androidProcessPermissions` and `iOSProcessPermissions`. These functions are responsible for informing the status of the permission grants to the SDK. - -```ts title=src/utils/updatePermissions.ts -import { PERMISSIONS, RESULTS, PermissionStatus } from 'react-native-permissions'; -import { StreamVideoRN } from '@stream-io/video-react-native-sdk'; - -export const androidProcessPermissions = ( - results: Record< - 'android.permission.CAMERA' | 'android.permission.RECORD_AUDIO', - PermissionStatus - >, -) => - StreamVideoRN.setPermissions({ - isCameraPermissionGranted: - results[PERMISSIONS.ANDROID.CAMERA] === RESULTS.GRANTED, - isMicPermissionGranted: - results[PERMISSIONS.ANDROID.RECORD_AUDIO] === RESULTS.GRANTED, - }); - -export const iosProcessPermissions = ( - results: Record< - 'ios.permission.CAMERA' | 'ios.permission.MICROPHONE', - PermissionStatus - >, -) => - StreamVideoRN.setPermissions({ - isCameraPermissionGranted: - results[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED, - isMicPermissionGranted: - results[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED, - }); -``` - -## Step 2 - Add a function to request permissions in the app and update the SDK +## Step 1 - Add a function to request permissions in the app -In this step, we create a function called `requestAndUpdatePermissions`. This function will be responsible for requesting permissions and also informing about the status to the SDK using the functions created in step-1. +In this step, we create a function called `requestAndUpdatePermissions`. This function will be responsible for requesting permissions. ```ts title=src/utils/requestAndUpdatePermissions.ts import { Platform } from 'react-native'; @@ -103,99 +55,33 @@ export const requestAndUpdatePermissions = async () => { PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE, ]); - // Sync the permissions with the Stream Video SDK - iosProcessPermissions(results); } else if (Platform.OS === 'android') { - // Request camera and mic permissions on Android + // Request camera, mic, bluetooth and notification permissions on Android const results = await requestMultiple([ PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.RECORD_AUDIO, + PERMISSIONS.ANDROID.BLUETOOTH_CONNECT, + PERMISSIONS.ANDROID.POST_NOTIFICATIONS, ]); - // Sync the permissions with the Stream Video SDK - androidProcessPermissions(results); - } -}; -``` - -## Step 3 - Add a function to check permissions in the app and update the SDK - -In this step, we create a function called `checkAndUpdatePermissions`. This function will be responsible for checking permissions and also informing about the status to the SDK using the functions created in step-1. This function will be used later to check permissions when the app moves to the foreground from the background. - -```ts title=src/utils/checkAndUpdatePermissions.ts -import { Platform } from 'react-native'; -import { PERMISSIONS, checkMultiple } from 'react-native-permissions'; -import { androidProcessPermissions, iosProcessPermissions } from 'src/utils/updatePermissions'; - -export const checkAndUpdatePermissions = async () => { - if (Platform.OS === 'ios') { - // Check, update and sync permissions on iOS - const results = await checkMultiple([ - PERMISSIONS.IOS.CAMERA, - PERMISSIONS.IOS.MICROPHONE, - ]); - // Sync the permissions with the Stream Video SDK - iosProcessPermissions(results); - } else if (Platform.OS === 'android') { - // Check, update and sync permissions on Android - const results = await checkMultiple([ - PERMISSIONS.ANDROID.CAMERA, - PERMISSIONS.ANDROID.RECORD_AUDIO, - ]); - // Sync the permissions with the Stream Video SDK - androidProcessPermissions(results); } }; ``` -## Step 4 - Create the hook to manage permissions +## Step 2 - Use the function on your desired screen -In the step, we create the `useSyncPermissions` hook. As mentioned earlier, this hook will be used to request the app's permissions and inform them to the SDK. - -In addition to updating permissions after requesting them, it is also necessary to check the status of permissions when the app comes to the foreground. This will ensure that the permissions are updated when the user comes back to the app from the background after changing the permissions in the native settings screen. In order to do this, we will also add a listener to the app state change event in this hook. +In this final step, we use the `requestAndUpdatePermissions` function in the screen of our choice. As an example below, we use it in the screen where we pass the `call` object to the SDK. ```tsx -import { useEffect, useRef } from 'react'; -import { AppState } from 'react-native'; +import { useEffect } from 'react'; import { requestAndUpdatePermissions } from 'src/utils/requestAndUpdatePermissions'; -import { checkAndUpdatePermissions } from 'src/utils/checkAndUpdatePermissions'; +import { StreamVideo, StreamCall } from '@stream-io/video-react-native-sdk'; -export const useSyncPermissions = () => { +const MyApp = () => { // request permissions on mount useEffect(() => { requestAndUpdatePermissions(); }, []); - // check permissions on foreground - const appStateRef = useRef(AppState.currentState); - useEffect(() => { - const subscription = AppState.addEventListener('change', nextAppState => { - if ( - appStateRef.current.match(/inactive|background/) && - nextAppState === 'active' - ) { - checkAndUpdatePermissions();; - } - - appStateRef.current = nextAppState; - }); - - return () => { - subscription.remove(); - }; - }, []); -}; -``` - -## Step 5 - Use the Hook - -In this final step, we use the `useSyncPermissions` hook in the screen of our choice. As an example below, we use it in the screen where we pass the `call` object to the SDK. - -```tsx -import { StreamVideo, StreamCall } from '@stream-io/video-react-native-sdk'; - -const MyApp = () => { - //.. - useSyncPermissions(); return ( diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx index d97313d5dc..b7ca63244c 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleAudioPreviewButton.tsx @@ -56,10 +56,10 @@ export const ToggleAudioPreviewButton = ({ svgContainer: toggleAudioPreviewButton.svgContainer, }} > - {status === 'disabled' ? ( - - ) : ( + {status === 'enabled' ? ( + ) : ( + )} ); diff --git a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx index 74477ddcef..673a06d7a4 100644 --- a/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx +++ b/packages/react-native-sdk/src/components/Call/CallControls/ToggleVideoPreviewButton.tsx @@ -39,6 +39,7 @@ export const ToggleVideoPreviewButton = ({ } toggleInitialVideoMuteState(); }; + return ( - {status === 'disabled' ? ( - - ) : ( + {status === 'enabled' ? ( ); diff --git a/packages/react-native-sdk/src/providers/MediaDevices.tsx b/packages/react-native-sdk/src/providers/MediaDevices.tsx deleted file mode 100644 index 785559bb8b..0000000000 --- a/packages/react-native-sdk/src/providers/MediaDevices.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { getAudioDevices, getVideoDevices } from '@stream-io/video-client'; -import { MediaDeviceInfo, useStreamVideoStoreSetState } from '../contexts'; -import { - isCameraPermissionGranted$, - isMicPermissionGranted$, - subscribeToDevicesWhenPermissionGranted, -} from '../utils/StreamVideoRN/permissions'; - -/** - * A renderless component that provides the audio and video devices to the store - * This component must be a child of StreamVideoStoreProvider - * @internal - * - * @category Device Management - */ -export const MediaDevices = (): React.ReactElement | null => { - const setState = useStreamVideoStoreSetState(); - const initialVideoDeviceSet = useRef(false); - - useEffect(() => { - const setAudioDevices = (audioDevices: MediaDeviceInfo[]) => { - setState({ audioDevices, currentAudioDevice: audioDevices[0] }); - }; - - const subscription = subscribeToDevicesWhenPermissionGranted( - isMicPermissionGranted$, - // @ts-expect-error Due to DOM typing incompatible with RN - getAudioDevices, - setAudioDevices, - ); - return () => subscription.unsubscribe(); - }, [setState]); - - useEffect(() => { - const setVideoDevices = (videoDevices: MediaDeviceInfo[]) => { - if (videoDevices.length > 0 && !initialVideoDeviceSet.current) { - const frontFacingVideoDevice = videoDevices.find( - (videoDevice) => - videoDevice.kind === 'videoinput' && videoDevice.facing === 'front', - ); - const initialVideoDevice = frontFacingVideoDevice ?? videoDevices[0]; - if (initialVideoDevice) { - initialVideoDeviceSet.current = true; - setState({ videoDevices, currentVideoDevice: initialVideoDevice }); - return; - } - } - setState({ videoDevices }); - }; - const subscription = subscribeToDevicesWhenPermissionGranted( - isCameraPermissionGranted$, - // @ts-expect-error Due to DOM typing incompatible with RN - getVideoDevices, - setVideoDevices, - ); - return () => subscription.unsubscribe(); - }, [setState]); - - return null; -}; diff --git a/packages/react-native-sdk/src/providers/MediaStreamManagement.tsx b/packages/react-native-sdk/src/providers/MediaStreamManagement.tsx index d69d200baa..600cbd0824 100644 --- a/packages/react-native-sdk/src/providers/MediaStreamManagement.tsx +++ b/packages/react-native-sdk/src/providers/MediaStreamManagement.tsx @@ -5,13 +5,9 @@ import React, { useContext, useEffect, useMemo, + useState, } from 'react'; -import { useCall, useI18n } from '@stream-io/video-react-bindings'; -import { - isCameraPermissionGranted$, - isMicPermissionGranted$, -} from '../utils/StreamVideoRN/permissions'; -import { Alert } from 'react-native'; +import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings'; import { useAppStateListener } from '../utils/hooks'; export type MediaDevicesInitialState = { @@ -56,12 +52,12 @@ const MediaStreamContext = * @category Device Management */ export const MediaStreamManagement = ({ - initialAudioEnabled, - initialVideoEnabled, + initialAudioEnabled: propInitialAudioEnabled, + initialVideoEnabled: propInitialVideoEnabled, children, }: PropsWithChildren) => { const call = useCall(); - const { t } = useI18n(); + const { useCallSettings } = useCallStateHooks(); // Resume/Disable video stream tracks when app goes to background/foreground // To save on CPU resources @@ -74,64 +70,96 @@ export const MediaStreamManagement = ({ }, ); + const [{ initialAudioEnabled, initialVideoEnabled }, setInitialDeviceState] = + useState({ + initialAudioEnabled: !!propInitialAudioEnabled, + initialVideoEnabled: !!propInitialVideoEnabled, + }); + + const settings = useCallSettings(); + + // if prop is set, use that value.. the prop should override the backend settings useEffect(() => { - if ( - typeof initialAudioEnabled !== 'undefined' && - isMicPermissionGranted$.getValue() - ) { + setInitialDeviceState((prev) => { + let initAudio = prev.initialAudioEnabled; + if (typeof propInitialAudioEnabled !== 'undefined') { + initAudio = propInitialAudioEnabled; + } + let initVideo = prev.initialVideoEnabled; + if (typeof propInitialVideoEnabled !== 'undefined') { + initVideo = propInitialVideoEnabled; + } + return { initialAudioEnabled: initAudio, initialVideoEnabled: initVideo }; + }); + }, [propInitialAudioEnabled, propInitialVideoEnabled]); + + // use backend settings to set initial audio/video enabled state + // 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 ( - initialAudioEnabled && - (call?.microphone.state.status === undefined || - call?.microphone.state.status === 'disabled') + typeof propInitialAudioEnabled === 'undefined' && + audio.mic_default_on ) { - call?.microphone.enable(); - } else { - call?.microphone.disable(); + initAudio = true; } - } - if ( - typeof initialVideoEnabled !== 'undefined' && - isCameraPermissionGranted$.getValue() - ) { + let initVideo = prev.initialVideoEnabled; if ( - initialVideoEnabled && - (call?.camera.state.status === undefined || - call?.camera.state.status === 'disabled') + typeof propInitialVideoEnabled === 'undefined' && + video.camera_default_on ) { - call?.camera.enable(); - } else { - call?.camera.disable(); + initVideo = true; } - } - }, [call, initialAudioEnabled, initialVideoEnabled]); + return { initialAudioEnabled: initAudio, initialVideoEnabled: initVideo }; + }); + }, [propInitialAudioEnabled, propInitialVideoEnabled, settings]); - const toggleInitialAudioMuteState = useCallback(() => { + // The main logic + // Enable or Disable the audio/video stream based on the initial state + useEffect(() => { if ( - !isMicPermissionGranted$.getValue() && - call?.microphone.state.status === 'disabled' + initialAudioEnabled && + (call?.microphone.state.status === undefined || + call?.microphone.state.status === 'disabled') ) { - Alert.alert(t('Microphone Permission Required To Enable Audio')); - return false; + call?.microphone.enable(); + } else if ( + !initialAudioEnabled && + call?.microphone.state.status === 'enabled' + ) { + call?.microphone.disable(); } - call?.microphone.state.status === 'disabled' - ? call?.microphone.enable() - : call?.microphone.disable(); - }, [call, t]); - - const toggleInitialVideoMuteState = useCallback(() => { if ( - !isCameraPermissionGranted$.getValue() && - call?.camera.state.status === 'disabled' + initialVideoEnabled && + (call?.camera.state.status === undefined || + call?.camera.state.status === 'disabled') ) { - Alert.alert(t('Camera Permission Required To Enable Video')); - return false; + call?.camera.enable(); + } else if ( + !initialVideoEnabled && + call?.camera.state.status === 'enabled' + ) { + call?.camera.disable(); } + }, [call, initialAudioEnabled, initialVideoEnabled]); + + const toggleInitialAudioMuteState = useCallback(() => { + call?.microphone.state.status === 'enabled' + ? call?.microphone.disable() + : call?.microphone.enable(); + }, [call]); - call?.camera.state.status === 'disabled' - ? call?.camera.enable() - : call?.camera.disable(); - }, [call, t]); + const toggleInitialVideoMuteState = useCallback(() => { + call?.camera.state.status === 'enabled' + ? call?.camera.disable() + : call?.camera.enable(); + }, [call]); const contextValue = useMemo(() => { return { @@ -139,6 +167,7 @@ export const MediaStreamManagement = ({ toggleInitialVideoMuteState, }; }, [toggleInitialAudioMuteState, toggleInitialVideoMuteState]); + return ( {children} diff --git a/packages/react-native-sdk/src/providers/StreamVideo.tsx b/packages/react-native-sdk/src/providers/StreamVideo.tsx index 33c8e9796c..d811856a20 100644 --- a/packages/react-native-sdk/src/providers/StreamVideo.tsx +++ b/packages/react-native-sdk/src/providers/StreamVideo.tsx @@ -6,7 +6,6 @@ import { import React, { PropsWithChildren, useEffect } from 'react'; import { StreamVideoStoreProvider } from '../contexts/StreamVideoContext'; import NetInfo from '@react-native-community/netinfo'; -import { MediaDevices } from './MediaDevices'; import { usePushRegisterEffect } from '../hooks'; import { translations } from '../translations'; import { DeepPartial, ThemeProvider } from '../contexts/ThemeContext'; @@ -66,7 +65,6 @@ export const StreamVideo = ( > - {children} diff --git a/packages/react-native-sdk/src/translations/en.json b/packages/react-native-sdk/src/translations/en.json index e4e0e68573..556b734f35 100644 --- a/packages/react-native-sdk/src/translations/en.json +++ b/packages/react-native-sdk/src/translations/en.json @@ -10,7 +10,5 @@ "Participants ({{ numberOfParticipants }})": "Participants ({{ numberOfParticipants }})", "{{ userName }} is sharing their screen": "{{ userName }} is sharing their screen", "{{ numberOfParticipants }} participant(s) are in the call.": "{{ numberOfParticipants }} participant(s) are in the call.", - "You are about to join a call with id {{ callId }}.": "You are about to join a call with id {{ callId }}.", - "Microphone Permission Required To Enable Audio": "Microphone permission not granted, can not enable audio", - "Camera Permission Required To Enable Video": "Camera permission not granted, can not enable video" + "You are about to join a call with id {{ callId }}.": "You are about to join a call with id {{ callId }}." } diff --git a/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts b/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts index 11f4d0d41f..f5d836ec76 100644 --- a/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts +++ b/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts @@ -2,10 +2,6 @@ import { AndroidImportance } from '@notifee/react-native'; import { defaultEmojiReactions } from '../../constants'; import { setupFirebaseHandlerAndroid } from '../push/android'; import { StreamVideoConfig } from './types'; -import { - isCameraPermissionGranted$, - isMicPermissionGranted$, -} from './permissions'; const DEFAULT_STREAM_VIDEO_CONFIG: StreamVideoConfig = { supportedReactions: defaultEmojiReactions, @@ -65,23 +61,6 @@ export class StreamVideoRN { setupFirebaseHandlerAndroid(pushConfig); } - /** - * Set native permissions config for StreamVideoRN. - * Note: This function should be called after the user has declined/granted camera and mic permissions. - * @example - * See sample-apps/react-native/dogfood/src/hooks/useSyncPermissions.ts - */ - static setPermissions({ - isCameraPermissionGranted, - isMicPermissionGranted, - }: { - isCameraPermissionGranted: boolean; - isMicPermissionGranted: boolean; - }) { - isCameraPermissionGranted$.next(isCameraPermissionGranted); - isMicPermissionGranted$.next(isMicPermissionGranted); - } - static getConfig() { return this.config; } diff --git a/packages/react-native-sdk/src/utils/StreamVideoRN/permissions.ts b/packages/react-native-sdk/src/utils/StreamVideoRN/permissions.ts deleted file mode 100644 index 03d18ae9d9..0000000000 --- a/packages/react-native-sdk/src/utils/StreamVideoRN/permissions.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { switchMap } from 'rxjs/operators'; -import { BehaviorSubject, EMPTY, Observable } from 'rxjs'; -import { MediaDeviceInfo } from '../../contexts'; -export const isCameraPermissionGranted$ = new BehaviorSubject(false); -export const isMicPermissionGranted$ = new BehaviorSubject(false); - -export const subscribeToDevicesWhenPermissionGranted = ( - isDevicePermissionGranted$: BehaviorSubject, - getDevicesFunc: () => Observable, - subscriptionCallback: (videoDevices: MediaDeviceInfo[]) => void, -) => - isDevicePermissionGranted$ - .pipe( - switchMap((isDevicePermissionGranted) => { - // if we don't have mic permission, we don't need to get the audio devices - // because we won't be able to use them anyway and this will trigger a permission request - // from RN WebRTC lib. This is not ideal because we want to control when the permission. - if (!isDevicePermissionGranted) { - // otherwise return EMPTY, which is an Observable that does nothing and just completes immediately - return EMPTY; - } - return getDevicesFunc(); - }), - ) - .subscribe(subscriptionCallback); diff --git a/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml b/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml index 73a9615b86..cd8a406e18 100644 --- a/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml +++ b/sample-apps/react-native/dogfood/android/app/src/main/AndroidManifest.xml @@ -8,9 +8,10 @@ + - + + @@ -69,4 +70,4 @@ - \ No newline at end of file + diff --git a/sample-apps/react-native/dogfood/src/hooks/useSyncPermissions.ts b/sample-apps/react-native/dogfood/src/hooks/useSyncPermissions.ts index d5009280d8..0c81843b64 100644 --- a/sample-apps/react-native/dogfood/src/hooks/useSyncPermissions.ts +++ b/sample-apps/react-native/dogfood/src/hooks/useSyncPermissions.ts @@ -1,14 +1,6 @@ import { useEffect } from 'react'; -import { useAppStateListener } from 'stream-chat-react-native'; import { Platform } from 'react-native'; -import { StreamVideoRN } from '@stream-io/video-react-native-sdk'; -import { - checkMultiple, - PERMISSIONS, - PermissionStatus, - requestMultiple, - RESULTS, -} from 'react-native-permissions'; +import { PERMISSIONS, requestMultiple } from 'react-native-permissions'; /** * This hook is used to sync the permissions of the app with the Stream Video SDK. @@ -20,65 +12,17 @@ export const useSyncPermissions = () => { useEffect(() => { requestAndUpdatePermissions(); }, []); - useAppStateListener(checkAndUpdatePermissions, () => {}); }; const requestAndUpdatePermissions = async () => { if (Platform.OS === 'ios') { - const results = await requestMultiple([ - PERMISSIONS.IOS.CAMERA, - PERMISSIONS.IOS.MICROPHONE, - ]); - iOSProcessResultsAndSetToConfig(results); - return; + await requestMultiple([PERMISSIONS.IOS.CAMERA, PERMISSIONS.IOS.MICROPHONE]); } else if (Platform.OS === 'android') { - const results = await requestMultiple([ + await requestMultiple([ PERMISSIONS.ANDROID.CAMERA, PERMISSIONS.ANDROID.RECORD_AUDIO, PERMISSIONS.ANDROID.BLUETOOTH_CONNECT, PERMISSIONS.ANDROID.POST_NOTIFICATIONS, ]); - androidProcessResultsAndSetToConfig(results); } }; -const checkAndUpdatePermissions = async () => { - if (Platform.OS === 'ios') { - const results = await checkMultiple([ - PERMISSIONS.IOS.CAMERA, - PERMISSIONS.IOS.MICROPHONE, - ]); - iOSProcessResultsAndSetToConfig(results); - } else if (Platform.OS === 'android') { - const results = await checkMultiple([ - PERMISSIONS.ANDROID.CAMERA, - PERMISSIONS.ANDROID.RECORD_AUDIO, - ]); - androidProcessResultsAndSetToConfig(results); - } -}; - -const androidProcessResultsAndSetToConfig = ( - results: Record< - 'android.permission.CAMERA' | 'android.permission.RECORD_AUDIO', - PermissionStatus - >, -) => - StreamVideoRN.setPermissions({ - isCameraPermissionGranted: - results[PERMISSIONS.ANDROID.CAMERA] === RESULTS.GRANTED, - isMicPermissionGranted: - results[PERMISSIONS.ANDROID.RECORD_AUDIO] === RESULTS.GRANTED, - }); - -const iOSProcessResultsAndSetToConfig = ( - results: Record< - 'ios.permission.CAMERA' | 'ios.permission.MICROPHONE', - PermissionStatus - >, -) => - StreamVideoRN.setPermissions({ - isCameraPermissionGranted: - results[PERMISSIONS.IOS.CAMERA] === RESULTS.GRANTED, - isMicPermissionGranted: - results[PERMISSIONS.IOS.MICROPHONE] === RESULTS.GRANTED, - }); diff --git a/sample-apps/react-native/expo-video-sample/components/MeetingUI.tsx b/sample-apps/react-native/expo-video-sample/components/MeetingUI.tsx index 6abf4cc0a5..5f1cfb77c1 100644 --- a/sample-apps/react-native/expo-video-sample/components/MeetingUI.tsx +++ b/sample-apps/react-native/expo-video-sample/components/MeetingUI.tsx @@ -3,7 +3,6 @@ import { CallContent, CallingState, Lobby, - StreamVideoRN, useCallStateHooks, } from '@stream-io/video-react-native-sdk'; import { useRouter } from 'expo-router'; @@ -16,11 +15,6 @@ export const MeetingUI = () => { const { useCallCallingState } = useCallStateHooks(); const callingState = useCallCallingState(); - StreamVideoRN.setPermissions({ - isCameraPermissionGranted: true, - isMicPermissionGranted: true, - }); - const onHangupCallHandler = () => { router.back(); }; diff --git a/sample-apps/react-native/expo-video-sample/components/UsersList.tsx b/sample-apps/react-native/expo-video-sample/components/UsersList.tsx index 11a44ac35b..4f18b6fe40 100644 --- a/sample-apps/react-native/expo-video-sample/components/UsersList.tsx +++ b/sample-apps/react-native/expo-video-sample/components/UsersList.tsx @@ -21,12 +21,7 @@ export const UsersList = () => { const run = async () => { if (Platform.OS === 'android') { await notifee.requestPermission(); - if (Platform.Version <= 30) { - await PermissionsAndroid.requestMultiple([ - PermissionsAndroid.PERMISSIONS.BLUETOOTH, - PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADMIN, - ]); - } else { + if (Platform.Version > 30) { await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT, );