@stream-io/video-react-native-sdk 1.27.3 → 1.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoAppLifecycleModule.kt +86 -0
  4. package/android/src/main/java/com/streamvideo/reactnative/StreamVideoReactNativePackage.kt +5 -1
  5. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +19 -11
  6. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioFocusUtil.kt +23 -9
  7. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +48 -14
  8. package/dist/commonjs/index.js +2 -0
  9. package/dist/commonjs/index.js.map +1 -1
  10. package/dist/commonjs/modules/call-manager/CallManager.js +5 -0
  11. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
  12. package/dist/commonjs/providers/StreamCall/AppStateListener.js +74 -16
  13. package/dist/commonjs/providers/StreamCall/AppStateListener.js.map +1 -1
  14. package/dist/commonjs/utils/internal/registerSDKGlobals.js +34 -0
  15. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -0
  16. package/dist/commonjs/version.js +1 -1
  17. package/dist/module/index.js +2 -0
  18. package/dist/module/index.js.map +1 -1
  19. package/dist/module/modules/call-manager/CallManager.js +5 -0
  20. package/dist/module/modules/call-manager/CallManager.js.map +1 -1
  21. package/dist/module/providers/StreamCall/AppStateListener.js +74 -16
  22. package/dist/module/providers/StreamCall/AppStateListener.js.map +1 -1
  23. package/dist/module/utils/internal/registerSDKGlobals.js +28 -0
  24. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -0
  25. package/dist/module/version.js +1 -1
  26. package/dist/typescript/index.d.ts.map +1 -1
  27. package/dist/typescript/modules/call-manager/CallManager.d.ts +2 -0
  28. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
  29. package/dist/typescript/modules/call-manager/types.d.ts +1 -0
  30. package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
  31. package/dist/typescript/providers/StreamCall/AppStateListener.d.ts.map +1 -1
  32. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts +2 -0
  33. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -0
  34. package/dist/typescript/version.d.ts +1 -1
  35. package/ios/StreamInCallManager.m +4 -0
  36. package/ios/StreamInCallManager.swift +213 -88
  37. package/package.json +7 -7
  38. package/src/index.ts +2 -0
  39. package/src/modules/call-manager/CallManager.ts +5 -0
  40. package/src/modules/call-manager/native-module.d.ts +11 -0
  41. package/src/modules/call-manager/types.ts +1 -0
  42. package/src/providers/StreamCall/AppStateListener.tsx +116 -17
  43. package/src/utils/internal/registerSDKGlobals.ts +30 -0
  44. package/src/version.ts +1 -1
  45. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioSetupStoreUtil.kt +0 -41
@@ -2,6 +2,7 @@ import { useCall } from '@stream-io/video-react-bindings';
2
2
  import { useEffect, useRef } from 'react';
3
3
  import {
4
4
  AppState,
5
+ type AppStateStatus,
5
6
  NativeEventEmitter,
6
7
  NativeModules,
7
8
  Platform,
@@ -11,6 +12,8 @@ import { disablePiPMode$, isInPiPMode$ } from '../../utils/internal/rxSubjects';
11
12
  import { RxUtils, videoLoggerSystem } from '@stream-io/video-client';
12
13
 
13
14
  const PIP_CHANGE_EVENT = 'StreamVideoReactNative_PIP_CHANGE_EVENT';
15
+ const ANDROID_APP_STATE_CHANGED_EVENT =
16
+ 'StreamVideoAppLifecycle_APP_STATE_CHANGED';
14
17
 
15
18
  const isAndroid8OrAbove = Platform.OS === 'android' && Platform.Version >= 26;
16
19
 
@@ -19,7 +22,7 @@ const isAndroid8OrAbove = Platform.OS === 'android' && Platform.Version >= 26;
19
22
  // 2. Handle PiP mode in Android
20
23
  export const AppStateListener = () => {
21
24
  const call = useCall();
22
- const appState = useRef(AppState.currentState);
25
+ const appState = useRef<AppStateStatus>(AppState.currentState);
23
26
  const cameraDisabledByAppState = useRef<boolean>(false);
24
27
 
25
28
  // on mount: set initial PiP mode and listen to PiP events
@@ -62,25 +65,65 @@ export const AppStateListener = () => {
62
65
  }, []);
63
66
 
64
67
  useEffect(() => {
65
- // due to strange behavior in iOS when app goes to "inactive" state
66
- // we dont check for inactive states
67
- // ref: https://www.reddit.com/r/reactnative/comments/15kib42/appstate_behavior_in_ios_when_swiping_down_to/
68
- const subscription = AppState.addEventListener('change', (nextAppState) => {
69
- const logger = videoLoggerSystem.getLogger('AppStateListener');
68
+ const logger = videoLoggerSystem.getLogger('AppStateListener');
69
+
70
+ const handleAppStateChange = (nextAppState: AppStateStatus) => {
71
+ logger.debug(
72
+ 'AppState changed to ',
73
+ nextAppState,
74
+ ' from ',
75
+ appState.current,
76
+ );
77
+
78
+ // due to strange behavior in iOS when app goes to "inactive" state
79
+ // we dont check for inactive states
80
+ // ref: https://www.reddit.com/r/reactnative/comments/15kib42/appstate_behavior_in_ios_when_swiping_down_to/
70
81
  if (appState.current.match(/background/) && nextAppState === 'active') {
71
82
  if (call?.camera?.state.status === 'enabled') {
83
+ logger.debug(
84
+ 'attempt to Disable and reenable camera as app came to foreground',
85
+ );
72
86
  // Android: when device is locked and resumed, the status isnt made disabled but stays enabled
73
87
  // iOS PiP: when local track was replaced by remote track, the local track shown is blank
74
88
  // as a workaround we stop the track and enable again if its already in enabled state
75
- call?.camera?.disable(true).then(() => {
76
- call?.camera?.enable();
77
- });
78
- logger.debug('Disable and reenable camera as app came to foreground');
89
+ const renableCamera = () => {
90
+ const camera = call?.camera;
91
+ if (!camera) return Promise.resolve();
92
+ return camera
93
+ .disable(true)
94
+ .then(() => camera.enable())
95
+ .catch((e) => {
96
+ logger.warn(
97
+ 'Failed to disable+reenable camera as app came to foreground',
98
+ e,
99
+ );
100
+ });
101
+ };
102
+ if (Platform.OS === 'android') {
103
+ NativeModules.StreamVideoReactNative.isCallAliveConfigured().then(
104
+ (isCallAliveConfigured: boolean) => {
105
+ if (!isCallAliveConfigured) {
106
+ renableCamera();
107
+ }
108
+ },
109
+ );
110
+ } else {
111
+ renableCamera();
112
+ }
79
113
  } else {
80
114
  if (cameraDisabledByAppState.current) {
81
- call?.camera?.resume();
82
- cameraDisabledByAppState.current = false;
83
- logger.debug('Resume camera as app came to foreground');
115
+ call?.camera
116
+ ?.resume()
117
+ .then(() => {
118
+ cameraDisabledByAppState.current = false;
119
+ logger.debug('Camera resumed as app came to foreground');
120
+ })
121
+ .catch((e) => {
122
+ logger.warn(
123
+ 'Failed to resume camera as app came to foreground',
124
+ e,
125
+ );
126
+ });
84
127
  }
85
128
  }
86
129
  appState.current = nextAppState;
@@ -90,9 +133,18 @@ export const AppStateListener = () => {
90
133
  ) {
91
134
  const disableCameraIfNeeded = () => {
92
135
  if (call?.camera?.state.status === 'enabled') {
93
- cameraDisabledByAppState.current = true;
94
- call?.camera?.disable();
95
- logger.debug('Camera disabled by app going to background');
136
+ call?.camera
137
+ ?.disable()
138
+ .then(() => {
139
+ cameraDisabledByAppState.current = true;
140
+ logger.debug('Camera disabled by app going to background');
141
+ })
142
+ .catch((e) => {
143
+ logger.warn(
144
+ 'Failed to disable camera as app went to background',
145
+ e,
146
+ );
147
+ });
96
148
  }
97
149
  };
98
150
  if (Platform.OS === 'android') {
@@ -113,7 +165,15 @@ export const AppStateListener = () => {
113
165
  // this happens on foreground push notifications
114
166
  return;
115
167
  }
116
- disableCameraIfNeeded();
168
+ // check if keep call alive is configured
169
+ // if not, then disable the camera as we are not able to keep the call alive in the background
170
+ NativeModules.StreamVideoReactNative.isCallAliveConfigured().then(
171
+ (isCallAliveConfigured: boolean) => {
172
+ if (!isCallAliveConfigured) {
173
+ disableCameraIfNeeded();
174
+ }
175
+ },
176
+ );
117
177
  }
118
178
  },
119
179
  );
@@ -128,6 +188,45 @@ export const AppStateListener = () => {
128
188
  }
129
189
  appState.current = nextAppState;
130
190
  }
191
+ };
192
+
193
+ // for Android use our custom native module to listen to app state changes
194
+ // because the default react-native AppState listener works for activity and ours works for application process
195
+ if (Platform.OS === 'android') {
196
+ const nativeModule = NativeModules.StreamVideoAppLifecycle;
197
+ const eventEmitter = new NativeEventEmitter(nativeModule);
198
+ let cancelled = false;
199
+
200
+ nativeModule
201
+ .getCurrentAppState()
202
+ .then((initialState: AppStateStatus | null | undefined) => {
203
+ if (cancelled) return;
204
+ if (initialState === 'active' || initialState === 'background') {
205
+ appState.current = initialState;
206
+ }
207
+ })
208
+ .catch(() => {
209
+ logger.warn('Failed to get current app state from native module');
210
+ });
211
+
212
+ const subscription = eventEmitter.addListener(
213
+ ANDROID_APP_STATE_CHANGED_EVENT,
214
+ (nextAppState: AppStateStatus) => {
215
+ if (nextAppState === 'active' || nextAppState === 'background') {
216
+ handleAppStateChange(nextAppState);
217
+ }
218
+ },
219
+ );
220
+
221
+ return () => {
222
+ cancelled = true;
223
+ subscription.remove();
224
+ };
225
+ }
226
+
227
+ // for iOS use the default react-native AppState listener
228
+ const subscription = AppState.addEventListener('change', (nextAppState) => {
229
+ handleAppStateChange(nextAppState);
131
230
  });
132
231
 
133
232
  return () => {
@@ -0,0 +1,30 @@
1
+ import { StreamRNVideoSDKGlobals } from '@stream-io/video-client';
2
+ import { NativeModules } from 'react-native';
3
+
4
+ const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager;
5
+
6
+ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
7
+ callManager: {
8
+ setup: ({ default_device }) => {
9
+ StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
10
+ default_device,
11
+ );
12
+ StreamInCallManagerNativeModule.setup();
13
+ },
14
+ start: () => {
15
+ StreamInCallManagerNativeModule.start();
16
+ },
17
+ stop: () => {
18
+ StreamInCallManagerNativeModule.stop();
19
+ },
20
+ },
21
+ };
22
+
23
+ // Note: The global type declaration for `streamRNVideoSDK` is defined in
24
+ // @stream-io/video-client/src/types.ts and is automatically available when
25
+ // importing from the client package.
26
+ export function registerSDKGlobals() {
27
+ if (!global.streamRNVideoSDK) {
28
+ global.streamRNVideoSDK = streamRNVideoSDKGlobals;
29
+ }
30
+ }
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.27.3';
1
+ export const version = '1.28.0';
@@ -1,41 +0,0 @@
1
- package com.streamvideo.reactnative.audio.utils
2
-
3
- import android.media.AudioManager
4
- import android.util.Log
5
- import com.facebook.react.bridge.ReactContext
6
- import com.streamvideo.reactnative.audio.AudioDeviceManager
7
- import com.streamvideo.reactnative.callmanager.StreamInCallManagerModule.Companion.TAG
8
-
9
- class AudioSetupStoreUtil(
10
- private val mReactContext: ReactContext,
11
- private val mAudioManager: AudioManager,
12
- private val mAudioDeviceManager: AudioDeviceManager
13
- ) {
14
- private var isOrigAudioSetupStored = false
15
- private var origIsSpeakerPhoneOn = false
16
- private var origIsMicrophoneMute = false
17
- private var origAudioMode = AudioManager.MODE_NORMAL
18
-
19
- fun storeOriginalAudioSetup() {
20
- if (!isOrigAudioSetupStored) {
21
- origAudioMode = mAudioManager.mode
22
- origIsSpeakerPhoneOn = AudioManagerUtil.isSpeakerphoneOn(mAudioManager)
23
- origIsMicrophoneMute = mAudioManager.isMicrophoneMute
24
- isOrigAudioSetupStored = true
25
- }
26
- }
27
-
28
- fun restoreOriginalAudioSetup() {
29
- if (isOrigAudioSetupStored) {
30
- if (origIsSpeakerPhoneOn) {
31
- mAudioDeviceManager.setSpeakerphoneOn(true)
32
- }
33
- mAudioManager.setMicrophoneMute(origIsMicrophoneMute)
34
- mAudioManager.mode = origAudioMode
35
- mReactContext.currentActivity?.apply {
36
- volumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE
37
- }
38
- isOrigAudioSetupStored = false
39
- }
40
- }
41
- }