@stream-io/video-react-native-sdk 1.40.1-beta.0 → 1.40.2

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 (59) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +45 -16
  3. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +12 -0
  4. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +1 -1
  5. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  6. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +36 -4
  7. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  8. package/dist/commonjs/modules/call-manager/CallManager.js +133 -1
  9. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
  10. package/dist/commonjs/utils/internal/callingx/callingx.js +37 -0
  11. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  12. package/dist/commonjs/utils/internal/registerSDKGlobals.js +34 -0
  13. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
  14. package/dist/commonjs/utils/push/android.js +101 -103
  15. package/dist/commonjs/utils/push/android.js.map +1 -1
  16. package/dist/commonjs/utils/push/libs/callingx.js +3 -0
  17. package/dist/commonjs/utils/push/libs/callingx.js.map +1 -1
  18. package/dist/commonjs/version.js +1 -1
  19. package/dist/commonjs/version.js.map +1 -1
  20. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +2 -2
  21. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  22. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +36 -4
  23. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  24. package/dist/module/modules/call-manager/CallManager.js +133 -1
  25. package/dist/module/modules/call-manager/CallManager.js.map +1 -1
  26. package/dist/module/utils/internal/callingx/callingx.js +38 -1
  27. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  28. package/dist/module/utils/internal/registerSDKGlobals.js +34 -0
  29. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
  30. package/dist/module/utils/push/android.js +101 -103
  31. package/dist/module/utils/push/android.js.map +1 -1
  32. package/dist/module/utils/push/libs/callingx.js +3 -0
  33. package/dist/module/utils/push/libs/callingx.js.map +1 -1
  34. package/dist/module/version.js +1 -1
  35. package/dist/module/version.js.map +1 -1
  36. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
  37. package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
  38. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
  39. package/dist/typescript/utils/StreamVideoRN/types.d.ts +5 -0
  40. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  41. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  42. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
  43. package/dist/typescript/utils/push/android.d.ts.map +1 -1
  44. package/dist/typescript/utils/push/libs/callingx.d.ts.map +1 -1
  45. package/dist/typescript/version.d.ts +1 -1
  46. package/dist/typescript/version.d.ts.map +1 -1
  47. package/expo-config-plugin/dist/index.js +0 -2
  48. package/package.json +8 -12
  49. package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +3 -2
  50. package/src/hooks/useAndroidKeepCallAliveEffect.ts +54 -10
  51. package/src/modules/call-manager/CallManager.ts +162 -1
  52. package/src/modules/call-manager/native-module.d.ts +8 -0
  53. package/src/utils/StreamVideoRN/types.ts +5 -0
  54. package/src/utils/internal/callingx/callingx.ts +44 -1
  55. package/src/utils/internal/registerSDKGlobals.ts +38 -0
  56. package/src/utils/push/android.ts +141 -140
  57. package/src/utils/push/libs/callingx.ts +4 -0
  58. package/src/version.ts +1 -1
  59. package/expo-config-plugin/dist/withBuildProperties.js +0 -13
@@ -2,7 +2,7 @@
2
2
  * Internal utils for callingx library usage from video-client.
3
3
  * See @./registerSDKGlobals.ts for more usage details.
4
4
  */
5
- import { Platform } from 'react-native';
5
+ import { NativeModules, Platform } from 'react-native';
6
6
  import type { EndCallReason } from '@stream-io/react-native-callingx';
7
7
  import { getCallingxLibIfAvailable } from '../../push/libs/callingx';
8
8
  import { waitForAudioSessionActivation } from './audioSessionPromise';
@@ -15,6 +15,46 @@ import { CallingState, videoLoggerSystem } from '@stream-io/video-client';
15
15
 
16
16
  const CallingxModule = getCallingxLibIfAvailable();
17
17
 
18
+ /**
19
+ * Fallback when Telecom registration fails/times out on Android: no component would own audio
20
+ * (Telecom never took over), so re-establish StreamInCallManager in its classic (non-telecom)
21
+ * mode to keep the call audible.
22
+ */
23
+ function recoverAudioToClassicMode() {
24
+ if (Platform.OS !== 'android') {
25
+ return;
26
+ }
27
+ if (!CallingxModule?.isSetup || !CallingxModule.isTelecomBacked) {
28
+ return;
29
+ }
30
+ // If a call is already registered, Telecom owns audio for it (the registration
31
+ // partially succeeded). Switching to classic mode here would fight Telecom's
32
+ // routing/focus — leave ownership intact.
33
+ if (CallingxModule.hasRegisteredCall()) {
34
+ videoLoggerSystem
35
+ .getLogger('callingx')
36
+ .debug(
37
+ 'recoverAudioToClassicMode: Telecom already owns a registered call; skipping classic fallback',
38
+ );
39
+ return;
40
+ }
41
+ const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager;
42
+ if (!StreamInCallManagerNativeModule) {
43
+ return;
44
+ }
45
+ const logger = videoLoggerSystem.getLogger('callingx');
46
+ logger.warn(
47
+ 'Telecom registration failed; falling back to classic StreamInCallManager audio',
48
+ );
49
+ try {
50
+ StreamInCallManagerNativeModule.stop();
51
+ StreamInCallManagerNativeModule.setTelecomManagedMode(false);
52
+ StreamInCallManagerNativeModule.start();
53
+ } catch (error) {
54
+ logger.error('recoverAudioToClassicMode: failed to recover audio', error);
55
+ }
56
+ }
57
+
18
58
  /**
19
59
  * Gets the call display name. To be used for display in native call screen.
20
60
  */
@@ -97,6 +137,7 @@ export async function registerOutgoingCall(call: Call) {
97
137
  `registerOutgoingCall: Error registering outgoing call in callingx: ${call.cid}`,
98
138
  error,
99
139
  );
140
+ recoverAudioToClassicMode();
100
141
  }
101
142
  }
102
143
 
@@ -146,6 +187,7 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
146
187
  `startCallingxCall: Error starting call in callingx: ${call.cid}`,
147
188
  error,
148
189
  );
190
+ recoverAudioToClassicMode();
149
191
  }
150
192
  } else if (isIncomingCall) {
151
193
  logger.debug(`joinCallingxCall: Joining incoming call ${call.cid}`);
@@ -187,6 +229,7 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
187
229
  `Error joining incoming call in callingx: ${call.cid}`,
188
230
  error,
189
231
  );
232
+ recoverAudioToClassicMode();
190
233
  }
191
234
  }
192
235
  }
@@ -45,6 +45,31 @@ const shouldBypassForCallKit = ({
45
45
  return bypass;
46
46
  };
47
47
 
48
+ /**
49
+ * On Android, when the call is registered with the Telecom stack (via CallingX),
50
+ * routing/focus/mode must be owned by Telecom (per Android guidance we must not use
51
+ * AudioManager.setCommunicationDevice / startBluetoothSco). Unlike iOS, we do NOT skip
52
+ * StreamInCallManager entirely — Telecom provides no proximity/keep-screen-on — instead we
53
+ * run it in "telecom-managed" mode where it only keeps proximity/keep-screen-on/mute.
54
+ */
55
+ const isAndroidTelecomManaged = ({
56
+ isRingingTypeCall,
57
+ }: {
58
+ isRingingTypeCall: boolean;
59
+ }): boolean => {
60
+ if (Platform.OS !== 'android') {
61
+ return false;
62
+ }
63
+ if (!CallingxModule) {
64
+ return false;
65
+ }
66
+ return (
67
+ CallingxModule.isSetup &&
68
+ CallingxModule.isTelecomBacked &&
69
+ (isRingingTypeCall || CallingxModule.isOngoingCallsEnabled)
70
+ );
71
+ };
72
+
48
73
  const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
49
74
  callingX: {
50
75
  joinCall: joinCallingxCall,
@@ -58,6 +83,17 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
58
83
  CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice);
59
84
  return;
60
85
  }
86
+ if (isAndroidTelecomManaged({ isRingingTypeCall })) {
87
+ // Telecom owns routing; forward the sticky preference to callingx and run the
88
+ // in-call manager in telecom-managed mode (proximity/keep-screen-on only).
89
+ CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice);
90
+ StreamInCallManagerNativeModule.setTelecomManagedMode(true);
91
+ StreamInCallManagerNativeModule.setup();
92
+ return;
93
+ }
94
+ if (Platform.OS === 'android') {
95
+ StreamInCallManagerNativeModule.setTelecomManagedMode(false);
96
+ }
61
97
  StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
62
98
  defaultDevice,
63
99
  );
@@ -67,6 +103,8 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
67
103
  if (shouldBypassForCallKit({ isRingingTypeCall })) {
68
104
  return;
69
105
  }
106
+ // Android telecom-managed calls still start (for proximity/keep-screen-on);
107
+ // the native side skips routing/focus internally.
70
108
  StreamInCallManagerNativeModule.start();
71
109
  },
72
110
  stop: ({ isRingingTypeCall }) => {
@@ -121,163 +121,164 @@ export const firebaseDataHandler = async (
121
121
  const asForegroundService = canListenToWS();
122
122
 
123
123
  if (asForegroundService) {
124
- // Listen to call events from WS through fg service
125
- // note: this will replace the current empty fg service runner
126
- //we need to start service (e.g. by calling display incoming call) and than launch bg task, consider making those steps independent
127
- await callingx.startBackgroundTask((_: unknown, stopTask: () => void) => {
128
- return new Promise((resolve) => {
129
- const finishBackgroundTask = () => {
124
+ // Listen to call events from WS with headless task, bound to call service.
125
+ // By this moment the call service is already running, so we can acquire the background task.
126
+ const backgroundTaskOwner = `push:${call_cid}`;
127
+ await callingx.acquireBackgroundTask(backgroundTaskOwner).catch((e) => {
128
+ logger.error(
129
+ `Failed to acquire background task for callCid: ${call_cid} error: ${e}`,
130
+ );
131
+ });
132
+
133
+ const finishBackgroundTask = () => {
134
+ callingx.log(
135
+ `Finishing background task for callCid: ${call_cid}`,
136
+ 'debug',
137
+ );
138
+ callingx.releaseBackgroundTask(backgroundTaskOwner);
139
+ };
140
+
141
+ (async () => {
142
+ try {
143
+ const _client = await pushConfig.createStreamVideoClient();
144
+ if (!_client) {
145
+ logger.debug(
146
+ `Closing fg service as there is no client to create from push config`,
147
+ );
148
+ finishBackgroundTask();
149
+ return;
150
+ }
151
+
152
+ const callFromPush = await _client.onRingingCall(call_cid);
153
+ const { mustEndCall, endCallReason } = shouldCallBeClosed(
154
+ callFromPush,
155
+ data,
156
+ );
157
+ if (mustEndCall) {
158
+ logger.debug(
159
+ `Closing fg service callCid: ${call_cid} endCallReason: ${endCallReason}`,
160
+ );
161
+
130
162
  callingx.log(
131
- `Finishing background task for callCid: ${call_cid}`,
163
+ `Ending call with callCid: ${call_cid} endCallReason: ${endCallReason}`,
132
164
  'debug',
133
165
  );
134
- resolve(undefined);
135
- stopTask();
136
- };
166
+ callingx.endCallWithReason(call_cid, endCallReason);
167
+ callFromPush.leave({ reject: false }).catch((error) => {
168
+ logger.error(
169
+ `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
170
+ );
171
+ });
172
+ finishBackgroundTask();
173
+ return;
174
+ }
137
175
 
138
- (async () => {
139
- try {
140
- const _client = await pushConfig.createStreamVideoClient();
141
- if (!_client) {
142
- logger.debug(
143
- `Closing fg service as there is no client to create from push config`,
144
- );
145
- finishBackgroundTask();
146
- return;
147
- }
176
+ const unsubscribeFunctions: Array<() => void> = [];
177
+ // check if service needs to be closed if accept/decline event was done on another device
178
+ const unsubscribe = callFromPush.on('all', (event) => {
179
+ const _canListenToWS = canListenToWS();
180
+ if (!_canListenToWS) {
181
+ logger.debug(
182
+ `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
183
+ { event },
184
+ );
185
+ unsubscribeFunctions.forEach((fn) => fn());
186
+
187
+ finishBackgroundTask();
188
+ return;
189
+ }
148
190
 
149
- const callFromPush = await _client.onRingingCall(call_cid);
150
- const { mustEndCall, endCallReason } = shouldCallBeClosed(
151
- callFromPush,
152
- data,
191
+ const {
192
+ mustEndCall: mustEndCallFromEvent,
193
+ endCallReason: endCallReasonFromEvent,
194
+ } = shouldCallBeClosed(callFromPush, data);
195
+ if (mustEndCallFromEvent) {
196
+ logger.debug(
197
+ `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS} shouldCallBeClosed`,
198
+ { event },
153
199
  );
154
- if (mustEndCall) {
200
+ unsubscribeFunctions.forEach((fn) => fn());
201
+
202
+ callingx.endCallWithReason(call_cid, endCallReasonFromEvent);
203
+ finishBackgroundTask();
204
+ }
205
+ });
206
+
207
+ // check if service needs to be closed if call was left
208
+ const stateSubscription = callFromPush.state.callingState$.subscribe(
209
+ (callingState) => {
210
+ if (
211
+ callingState === CallingState.IDLE ||
212
+ callingState === CallingState.LEFT
213
+ ) {
155
214
  logger.debug(
156
- `Closing fg service callCid: ${call_cid} endCallReason: ${endCallReason}`,
215
+ `Closing fg service from callingState callCid: ${call_cid} callingState: ${callingState}`,
157
216
  );
158
-
217
+ unsubscribeFunctions.forEach((fn) => fn());
159
218
  callingx.log(
160
- `Ending call with callCid: ${call_cid} endCallReason: ${endCallReason}`,
219
+ `Ending call with callCid: ${call_cid} callingState: ${callingState}`,
161
220
  'debug',
162
221
  );
163
- callingx.endCallWithReason(call_cid, endCallReason);
164
- callFromPush.leave({ reject: false }).catch((error) => {
165
- logger.error(
166
- `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
167
- );
168
- });
169
- resolve(undefined);
170
- return;
222
+ finishBackgroundTask();
171
223
  }
224
+ },
225
+ );
172
226
 
173
- const unsubscribeFunctions: Array<() => void> = [];
174
- // check if service needs to be closed if accept/decline event was done on another device
175
- const unsubscribe = callFromPush.on('all', (event) => {
176
- const _canListenToWS = canListenToWS();
177
- if (!_canListenToWS) {
178
- logger.debug(
179
- `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
180
- { event },
181
- );
182
- unsubscribeFunctions.forEach((fn) => fn());
183
-
184
- finishBackgroundTask();
185
- return;
186
- }
187
-
188
- const {
189
- mustEndCall: mustEndCallFromEvent,
190
- endCallReason: endCallReasonFromEvent,
191
- } = shouldCallBeClosed(callFromPush, data);
192
- if (mustEndCallFromEvent) {
193
- logger.debug(
194
- `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS} shouldCallBeClosed`,
195
- { event },
196
- );
197
- unsubscribeFunctions.forEach((fn) => fn());
198
-
199
- callingx.endCallWithReason(call_cid, endCallReasonFromEvent);
200
- resolve(undefined);
201
- }
202
- });
203
-
204
- // check if service needs to be closed if call was left
205
- const stateSubscription =
206
- callFromPush.state.callingState$.subscribe((callingState) => {
207
- if (
208
- callingState === CallingState.IDLE ||
209
- callingState === CallingState.LEFT
210
- ) {
211
- logger.debug(
212
- `Closing fg service from callingState callCid: ${call_cid} callingState: ${callingState}`,
213
- );
214
- unsubscribeFunctions.forEach((fn) => fn());
215
- callingx.log(
216
- `Ending call with callCid: ${call_cid} callingState: ${callingState}`,
217
- 'debug',
218
- );
219
- resolve(undefined);
220
- }
227
+ const endCallSubscription = callingx.addEventListener(
228
+ 'endCall',
229
+ async ({ callId }: { callId: string }) => {
230
+ unsubscribeFunctions.forEach((fn) => fn());
231
+ try {
232
+ await callFromPush.leave({
233
+ reject:
234
+ callFromPush.state.callingState === CallingState.RINGING,
235
+ reason: 'decline',
221
236
  });
237
+ } catch (error) {
238
+ logger.error(
239
+ `Failed to leave call with callCid: ${call_cid} error: ${error}`,
240
+ );
241
+ } finally {
242
+ callingx.log(
243
+ `Ending call with callCid: ${call_cid} callId: ${callId}`,
244
+ 'debug',
245
+ );
246
+ finishBackgroundTask();
247
+ }
248
+ },
249
+ );
222
250
 
223
- const endCallSubscription = callingx.addEventListener(
224
- 'endCall',
225
- async ({ callId }: { callId: string }) => {
226
- unsubscribeFunctions.forEach((fn) => fn());
227
- try {
228
- await callFromPush.leave({
229
- reject:
230
- callFromPush.state.callingState ===
231
- CallingState.RINGING,
232
- reason: 'decline',
233
- });
234
- } catch (error) {
235
- logger.error(
236
- `Failed to leave call with callCid: ${call_cid} error: ${error}`,
237
- );
238
- } finally {
239
- callingx.log(
240
- `Ending call with callCid: ${call_cid} callId: ${callId}`,
241
- 'debug',
242
- );
243
- resolve(undefined);
244
- }
245
- },
246
- );
247
-
248
- //stop background task when app comes to foreground
249
- const appStateSubscription = AppState.addEventListener(
250
- 'change',
251
- (nextAppState) => {
252
- const _canListenToWS = canListenToWS();
253
- callingx.log(
254
- `AppState changed to: ${nextAppState} for callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
255
- 'debug',
256
- );
257
- if (!_canListenToWS) {
258
- unsubscribeFunctions.forEach((fn) => fn());
259
- finishBackgroundTask();
260
- return;
261
- }
262
- },
263
- );
264
-
265
- unsubscribeFunctions.push(unsubscribe);
266
- unsubscribeFunctions.push(() => stateSubscription.unsubscribe());
267
- unsubscribeFunctions.push(() => endCallSubscription.remove());
268
- unsubscribeFunctions.push(() => appStateSubscription.remove());
269
- pushUnsubscriptionCallbacks.get(call_cid)?.forEach((cb) => cb());
270
- pushUnsubscriptionCallbacks.set(call_cid, unsubscribeFunctions);
271
- } catch (error) {
251
+ //stop background task when app comes to foreground
252
+ const appStateSubscription = AppState.addEventListener(
253
+ 'change',
254
+ (nextAppState) => {
255
+ const _canListenToWS = canListenToWS();
272
256
  callingx.log(
273
- `Failed to start background task with callCid: ${call_cid} error: ${error}`,
274
- 'error',
257
+ `AppState changed to: ${nextAppState} for callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
258
+ 'debug',
275
259
  );
276
- finishBackgroundTask();
277
- }
278
- })();
279
- });
280
- });
260
+ if (!_canListenToWS) {
261
+ unsubscribeFunctions.forEach((fn) => fn());
262
+ finishBackgroundTask();
263
+ return;
264
+ }
265
+ },
266
+ );
267
+
268
+ unsubscribeFunctions.push(unsubscribe);
269
+ unsubscribeFunctions.push(() => stateSubscription.unsubscribe());
270
+ unsubscribeFunctions.push(() => endCallSubscription.remove());
271
+ unsubscribeFunctions.push(() => appStateSubscription.remove());
272
+ pushUnsubscriptionCallbacks.get(call_cid)?.forEach((cb) => cb());
273
+ pushUnsubscriptionCallbacks.set(call_cid, unsubscribeFunctions);
274
+ } catch (error) {
275
+ callingx.log(
276
+ `Failed to start background task with callCid: ${call_cid} error: ${error}`,
277
+ 'error',
278
+ );
279
+ finishBackgroundTask();
280
+ }
281
+ })();
281
282
  }
282
283
 
283
284
  if (asForegroundService) {
@@ -84,6 +84,10 @@ export function extractCallingExpOptions(
84
84
  androidOptions.skipIncomingPushInForeground =
85
85
  pushConfig.android.skipIncomingPushInForeground;
86
86
  }
87
+ if (pushConfig.android.defaultDeviceEndpointType !== undefined) {
88
+ androidOptions.defaultDeviceEndpointType =
89
+ pushConfig.android.defaultDeviceEndpointType;
90
+ }
87
91
  }
88
92
 
89
93
  if (foregroundServiceConfig.android.channel) {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '1.40.1-beta.0';
1
+ export const version = '1.40.2';
@@ -1,13 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const expo_build_properties_1 = require("expo-build-properties");
4
- const withStreamVideoReactNativeBuildProperties = (configuration) => {
5
- return (0, expo_build_properties_1.withBuildProperties)(configuration, {
6
- android: {
7
- extraProguardRules: `
8
- -keep class org.webrtc.** { *; }
9
- `,
10
- },
11
- });
12
- };
13
- exports.default = withStreamVideoReactNativeBuildProperties;