@stream-io/video-react-native-sdk 1.43.0 → 1.45.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 (150) hide show
  1. package/CHANGELOG.md +44 -1
  2. package/dist/commonjs/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
  3. package/dist/commonjs/components/Call/Lobby/Lobby.js +4 -9
  4. package/dist/commonjs/components/Call/Lobby/Lobby.js.map +1 -1
  5. package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js +93 -0
  6. package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
  7. package/dist/commonjs/components/Call/Lobby/index.js +11 -0
  8. package/dist/commonjs/components/Call/Lobby/index.js.map +1 -1
  9. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +4 -15
  10. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  11. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js +10 -4
  12. package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
  13. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +9 -5
  14. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  15. package/dist/commonjs/hooks/useLoopbackRecording.js +10 -127
  16. package/dist/commonjs/hooks/useLoopbackRecording.js.map +1 -1
  17. package/dist/commonjs/modules/call-manager/CallManager.js +37 -54
  18. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
  19. package/dist/commonjs/utils/StreamVideoRN/index.js +2 -0
  20. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  21. package/dist/commonjs/utils/internal/callingx/callingx.js +28 -39
  22. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  23. package/dist/commonjs/utils/internal/registerMediaEngine.js +41 -0
  24. package/dist/commonjs/utils/internal/registerMediaEngine.js.map +1 -0
  25. package/dist/commonjs/utils/internal/registerSDKGlobals.js +93 -35
  26. package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
  27. package/dist/commonjs/utils/push/android.js +13 -175
  28. package/dist/commonjs/utils/push/android.js.map +1 -1
  29. package/dist/commonjs/utils/push/internal/android.js +186 -0
  30. package/dist/commonjs/utils/push/internal/android.js.map +1 -0
  31. package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js +8 -9
  32. package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js.map +1 -1
  33. package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js +1 -1
  34. package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
  35. package/dist/commonjs/utils/push/setupAndroidPushEvents.js +25 -0
  36. package/dist/commonjs/utils/push/setupAndroidPushEvents.js.map +1 -0
  37. package/dist/commonjs/utils/push/setupCallingExpEvents.js +6 -2
  38. package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -1
  39. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +6 -7
  40. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
  41. package/dist/commonjs/utils/push/utils.js.map +1 -1
  42. package/dist/commonjs/version.js +1 -1
  43. package/dist/module/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
  44. package/dist/module/components/Call/Lobby/Lobby.js +4 -9
  45. package/dist/module/components/Call/Lobby/Lobby.js.map +1 -1
  46. package/dist/module/components/Call/Lobby/LobbyCameraPreview.js +87 -0
  47. package/dist/module/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
  48. package/dist/module/components/Call/Lobby/index.js +1 -0
  49. package/dist/module/components/Call/Lobby/index.js.map +1 -1
  50. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +6 -17
  51. package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
  52. package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js +11 -5
  53. package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
  54. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +9 -5
  55. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  56. package/dist/module/hooks/useLoopbackRecording.js +10 -127
  57. package/dist/module/hooks/useLoopbackRecording.js.map +1 -1
  58. package/dist/module/modules/call-manager/CallManager.js +36 -53
  59. package/dist/module/modules/call-manager/CallManager.js.map +1 -1
  60. package/dist/module/utils/StreamVideoRN/index.js +2 -0
  61. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  62. package/dist/module/utils/internal/callingx/callingx.js +27 -40
  63. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  64. package/dist/module/utils/internal/registerMediaEngine.js +37 -0
  65. package/dist/module/utils/internal/registerMediaEngine.js.map +1 -0
  66. package/dist/module/utils/internal/registerSDKGlobals.js +95 -37
  67. package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
  68. package/dist/module/utils/push/android.js +16 -178
  69. package/dist/module/utils/push/android.js.map +1 -1
  70. package/dist/module/utils/push/internal/android.js +182 -0
  71. package/dist/module/utils/push/internal/android.js.map +1 -0
  72. package/dist/module/utils/push/libs/firebaseMessaging/index.js +8 -8
  73. package/dist/module/utils/push/libs/firebaseMessaging/index.js.map +1 -1
  74. package/dist/module/utils/push/libs/firebaseMessaging/lib.js +1 -1
  75. package/dist/module/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
  76. package/dist/module/utils/push/setupAndroidPushEvents.js +21 -0
  77. package/dist/module/utils/push/setupAndroidPushEvents.js.map +1 -0
  78. package/dist/module/utils/push/setupCallingExpEvents.js +6 -2
  79. package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -1
  80. package/dist/module/utils/push/setupIosVoipPushEvents.js +6 -7
  81. package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
  82. package/dist/module/utils/push/utils.js.map +1 -1
  83. package/dist/module/version.js +1 -1
  84. package/dist/typescript/components/Call/Lobby/Lobby.d.ts.map +1 -1
  85. package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts +28 -0
  86. package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts.map +1 -0
  87. package/dist/typescript/components/Call/Lobby/index.d.ts +1 -0
  88. package/dist/typescript/components/Call/Lobby/index.d.ts.map +1 -1
  89. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -6
  90. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
  91. package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/index.d.ts.map +1 -1
  92. package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
  93. package/dist/typescript/hooks/useLoopbackRecording.d.ts +12 -69
  94. package/dist/typescript/hooks/useLoopbackRecording.d.ts.map +1 -1
  95. package/dist/typescript/modules/call-manager/CallManager.d.ts +28 -6
  96. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
  97. package/dist/typescript/modules/call-manager/types.d.ts +0 -1
  98. package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
  99. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  100. package/dist/typescript/utils/StreamVideoRN/types.d.ts +3 -0
  101. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  102. package/dist/typescript/utils/internal/callingx/callingx.d.ts +2 -0
  103. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  104. package/dist/typescript/utils/internal/registerMediaEngine.d.ts +15 -0
  105. package/dist/typescript/utils/internal/registerMediaEngine.d.ts.map +1 -0
  106. package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
  107. package/dist/typescript/utils/push/android.d.ts +3 -4
  108. package/dist/typescript/utils/push/android.d.ts.map +1 -1
  109. package/dist/typescript/utils/push/internal/android.d.ts +8 -0
  110. package/dist/typescript/utils/push/internal/android.d.ts.map +1 -0
  111. package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts +6 -5
  112. package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts.map +1 -1
  113. package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts +2 -2
  114. package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts.map +1 -1
  115. package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts +3 -0
  116. package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts.map +1 -0
  117. package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -1
  118. package/dist/typescript/utils/push/setupIosVoipPushEvents.d.ts.map +1 -1
  119. package/dist/typescript/utils/push/utils.d.ts +2 -2
  120. package/dist/typescript/utils/push/utils.d.ts.map +1 -1
  121. package/dist/typescript/version.d.ts +1 -1
  122. package/ios/StreamInCallManager.m +4 -0
  123. package/ios/StreamInCallManager.swift +42 -10
  124. package/ios/TracksRecorder/RecorderVideoSink.swift +92 -5
  125. package/package.json +8 -8
  126. package/src/components/Call/CallControls/internal/ReactionsPicker.tsx +2 -2
  127. package/src/components/Call/Lobby/Lobby.tsx +4 -11
  128. package/src/components/Call/Lobby/LobbyCameraPreview.tsx +99 -0
  129. package/src/components/Call/Lobby/index.ts +1 -0
  130. package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +56 -75
  131. package/src/components/Participant/ParticipantView/VideoRenderer/index.tsx +18 -7
  132. package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +12 -5
  133. package/src/hooks/useLoopbackRecording.ts +36 -248
  134. package/src/modules/call-manager/CallManager.ts +40 -71
  135. package/src/modules/call-manager/native-module.d.ts +13 -0
  136. package/src/modules/call-manager/types.ts +0 -1
  137. package/src/utils/StreamVideoRN/index.ts +2 -0
  138. package/src/utils/StreamVideoRN/types.ts +3 -0
  139. package/src/utils/internal/callingx/callingx.ts +37 -46
  140. package/src/utils/internal/registerMediaEngine.ts +42 -0
  141. package/src/utils/internal/registerSDKGlobals.ts +115 -42
  142. package/src/utils/push/android.ts +20 -253
  143. package/src/utils/push/internal/android.ts +252 -0
  144. package/src/utils/push/libs/firebaseMessaging/index.ts +14 -22
  145. package/src/utils/push/libs/firebaseMessaging/lib.ts +3 -3
  146. package/src/utils/push/setupAndroidPushEvents.ts +25 -0
  147. package/src/utils/push/setupCallingExpEvents.ts +6 -3
  148. package/src/utils/push/setupIosVoipPushEvents.ts +8 -7
  149. package/src/utils/push/utils.ts +2 -4
  150. package/src/version.ts +1 -1
@@ -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 { NativeModules, Platform } from 'react-native';
5
+ import { 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 type {
@@ -14,46 +14,6 @@ import { CallingState, videoLoggerSystem } from '@stream-io/video-client';
14
14
 
15
15
  const CallingxModule = getCallingxLibIfAvailable();
16
16
 
17
- /**
18
- * Fallback when Telecom registration fails/times out on Android: no component would own audio
19
- * (Telecom never took over), so re-establish StreamInCallManager in its classic (non-telecom)
20
- * mode to keep the call audible.
21
- */
22
- function recoverAudioToClassicMode() {
23
- if (Platform.OS !== 'android') {
24
- return;
25
- }
26
- if (!CallingxModule?.isSetup || !CallingxModule.isTelecomBacked) {
27
- return;
28
- }
29
- // If a call is already registered, Telecom owns audio for it (the registration
30
- // partially succeeded). Switching to classic mode here would fight Telecom's
31
- // routing/focus — leave ownership intact.
32
- if (CallingxModule.hasRegisteredCall()) {
33
- videoLoggerSystem
34
- .getLogger('callingx')
35
- .debug(
36
- 'recoverAudioToClassicMode: Telecom already owns a registered call; skipping classic fallback',
37
- );
38
- return;
39
- }
40
- const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager;
41
- if (!StreamInCallManagerNativeModule) {
42
- return;
43
- }
44
- const logger = videoLoggerSystem.getLogger('callingx');
45
- logger.warn(
46
- 'Telecom registration failed; falling back to classic StreamInCallManager audio',
47
- );
48
- try {
49
- StreamInCallManagerNativeModule.stop();
50
- StreamInCallManagerNativeModule.setTelecomManagedMode(false);
51
- StreamInCallManagerNativeModule.start();
52
- } catch (error) {
53
- logger.error('recoverAudioToClassicMode: failed to recover audio', error);
54
- }
55
- }
56
-
57
17
  /**
58
18
  * Gets the call display name. To be used for display in native call screen.
59
19
  */
@@ -88,7 +48,7 @@ export function getCallDisplayName(
88
48
  if (names.length === 0) {
89
49
  names = [
90
50
  participants.find((participant) => participant.userId === currentUserId)
91
- ?.name ?? 'Call',
51
+ ?.name || 'Call',
92
52
  ];
93
53
  }
94
54
 
@@ -97,7 +57,7 @@ export function getCallDisplayName(
97
57
 
98
58
  function getCallDisplayNameFromCall(call: Call): string {
99
59
  return (
100
- call.state.custom?.display_name ??
60
+ call.state.custom?.display_name ||
101
61
  getCallDisplayName(
102
62
  call.state.members,
103
63
  call.state.participants,
@@ -136,7 +96,6 @@ export async function registerOutgoingCall(call: Call) {
136
96
  `registerOutgoingCall: Error registering outgoing call in callingx: ${call.cid}`,
137
97
  error,
138
98
  );
139
- recoverAudioToClassicMode();
140
99
  }
141
100
  }
142
101
 
@@ -183,7 +142,6 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
183
142
  `startCallingxCall: Error starting call in callingx: ${call.cid}`,
184
143
  error,
185
144
  );
186
- recoverAudioToClassicMode();
187
145
  }
188
146
  } else if (isIncomingCall) {
189
147
  logger.debug(`joinCallingxCall: Joining incoming call ${call.cid}`);
@@ -221,7 +179,6 @@ export async function joinCallingxCall(call: Call, activeCalls: Call[]) {
221
179
  `Error joining incoming call in callingx: ${call.cid}`,
222
180
  error,
223
181
  );
224
- recoverAudioToClassicMode();
225
182
  }
226
183
  }
227
184
  }
@@ -246,3 +203,37 @@ export async function endCallingxCall(call: Call, reason?: EndCallReason) {
246
203
  );
247
204
  }
248
205
  }
206
+
207
+ export async function wireAudioEngineSubscription() {
208
+ if (!CallingxModule || !CallingxModule.isSetup || Platform.OS !== 'ios') {
209
+ return;
210
+ }
211
+ const logger = videoLoggerSystem.getLogger('callingx');
212
+
213
+ try {
214
+ logger.debug('wireEngineSubscription: Wiring engine subscription');
215
+ CallingxModule.wireAudioEngineSubscription();
216
+ } catch (error) {
217
+ logger.error(
218
+ 'wireAudioEngineSubscription: Error wiring engine subscription',
219
+ error,
220
+ );
221
+ }
222
+ }
223
+
224
+ export function unwireAudioEngineSubscription() {
225
+ if (!CallingxModule || !CallingxModule.isSetup || Platform.OS !== 'ios') {
226
+ return;
227
+ }
228
+ const logger = videoLoggerSystem.getLogger('callingx');
229
+
230
+ try {
231
+ logger.debug('unwireEngineSubscription: Cancelling engine subscription');
232
+ CallingxModule.unwireAudioEngineSubscription();
233
+ } catch (error) {
234
+ logger.error(
235
+ 'unwireAudioEngineSubscription: Error cancelling engine subscription',
236
+ error,
237
+ );
238
+ }
239
+ }
@@ -0,0 +1,42 @@
1
+ import {
2
+ type CallMediaEngine,
3
+ setCallMediaEngineProvider,
4
+ videoLoggerSystem,
5
+ } from '@stream-io/video-client';
6
+ import { CallFactory } from '@stream-io/react-native-webrtc';
7
+ import { callManager } from '../../modules/call-manager';
8
+
9
+ const logger = videoLoggerSystem.getLogger('CallMediaEngine');
10
+
11
+ /**
12
+ * Registers the React Native {@link CallMediaEngine} provider.
13
+ *
14
+ * Once registered, every `Call.ensureMediaFactory()` builds the call's native
15
+ * `PeerConnectionFactory` (with the call's audio configuration) and sets it as
16
+ * the single live factory. The WebRTC globals (`getUserMedia`/`getDisplayMedia`/
17
+ * `new RTCPeerConnection`) then resolve to it, so the call's tracks and peer
18
+ * connections are created with — and torn down with — that one native factory,
19
+ * which is what lets the call own its AudioDeviceModule. The engine itself only
20
+ * manages the factory lifecycle (`dispose` at leave).
21
+ *
22
+ * @internal
23
+ */
24
+ export function registerCallMediaEngine() {
25
+ setCallMediaEngineProvider(async (): Promise<CallMediaEngine> => {
26
+ const config = callManager.getStoredConfig();
27
+ const bypassVoiceProcessing = config?.audioRole === 'listener';
28
+ const factory = await CallFactory.create({
29
+ bypassVoiceProcessing,
30
+ });
31
+ logger.debug(
32
+ `Created per-call factory (bypassVoiceProcessing=${bypassVoiceProcessing})`,
33
+ );
34
+
35
+ return {
36
+ dispose: async () => {
37
+ logger.debug('Disposing per-call factory');
38
+ return await factory.dispose();
39
+ },
40
+ };
41
+ });
42
+ }
@@ -1,7 +1,9 @@
1
- import { StreamRNVideoSDKGlobals } from '@stream-io/video-client';
1
+ import {
2
+ StreamRNVideoSDKGlobals,
3
+ videoLoggerSystem,
4
+ } from '@stream-io/video-client';
2
5
  import { NativeModules, PermissionsAndroid, Platform } from 'react-native';
3
6
  import {
4
- AudioDeviceModule,
5
7
  AudioEngineMuteMode,
6
8
  audioDeviceModuleEvents,
7
9
  } from '@stream-io/react-native-webrtc';
@@ -10,7 +12,11 @@ import {
10
12
  endCallingxCall,
11
13
  registerOutgoingCall,
12
14
  joinCallingxCall,
15
+ wireAudioEngineSubscription,
16
+ unwireAudioEngineSubscription,
13
17
  } from './callingx/callingx';
18
+ import { registerCallMediaEngine } from './registerMediaEngine';
19
+ import { callManager as publicCallManager } from '../../modules/call-manager';
14
20
 
15
21
  const StreamInCallManagerNativeModule = NativeModules.StreamInCallManager;
16
22
  const StreamVideoReactNativeModule = NativeModules.StreamVideoReactNative as {
@@ -19,6 +25,18 @@ const StreamVideoReactNativeModule = NativeModules.StreamVideoReactNative as {
19
25
 
20
26
  const CallingxModule = getCallingxLibIfAvailable();
21
27
 
28
+ /**
29
+ * Runs a fire-and-forget native call, logging instead of throwing on a bridge
30
+ * error so it can't crash the caller (e.g. the join/leave flow).
31
+ */
32
+ const safeNativeCall = (label: string, fn: () => void): void => {
33
+ try {
34
+ fn();
35
+ } catch (error) {
36
+ videoLoggerSystem.getLogger('CallManager').warn(`${label} failed`, error);
37
+ }
38
+ };
39
+
22
40
  /**
23
41
  * Checks if StreamInCallManager should be bypassed because CallKit is handling
24
42
  * the audio session via CallingX.
@@ -52,22 +70,17 @@ const shouldBypassForCallKit = ({
52
70
  * StreamInCallManager entirely — Telecom provides no proximity/keep-screen-on — instead we
53
71
  * run it in "telecom-managed" mode where it only keeps proximity/keep-screen-on/mute.
54
72
  */
55
- const isAndroidTelecomManaged = ({
56
- isRingingTypeCall,
57
- }: {
58
- isRingingTypeCall: boolean;
59
- }): boolean => {
60
- if (Platform.OS !== 'android') {
73
+ const isAndroidTelecomManaged = ({ cid }: { cid: string }): boolean => {
74
+ if (Platform.OS !== 'android' || !CallingxModule) {
61
75
  return false;
62
76
  }
63
- if (!CallingxModule) {
77
+ if (!CallingxModule.isSetup || !CallingxModule.isTelecomBacked) {
64
78
  return false;
65
79
  }
66
- return (
67
- CallingxModule.isSetup &&
68
- CallingxModule.isTelecomBacked &&
69
- (isRingingTypeCall || CallingxModule.isOngoingCallsEnabled)
70
- );
80
+ // Trust Telecom's actual per-call registration. A call that never registered
81
+ // (e.g. Telecom registration failed) is not managed, so start() falls through
82
+ // to the classic path — this is where audio recovery happens.
83
+ return CallingxModule.isCallTracked(cid);
71
84
  };
72
85
 
73
86
  const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
@@ -75,43 +88,101 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
75
88
  joinCall: joinCallingxCall,
76
89
  endCall: endCallingxCall,
77
90
  registerOutgoingCall: registerOutgoingCall,
91
+ wireAudioEngineSubscription: wireAudioEngineSubscription,
92
+ unwireAudioEngineSubscription: unwireAudioEngineSubscription,
78
93
  },
79
94
  callManager: {
80
- setup: ({ defaultDevice, isRingingTypeCall }) => {
81
- if (shouldBypassForCallKit({ isRingingTypeCall })) {
82
- // Forward the sticky preference; callingx reads it on next CallKit activation.
83
- CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice);
84
- return;
95
+ setup: ({ defaultDevice, isRingingTypeCall, cid }) => {
96
+ const isTelecomManaged = isAndroidTelecomManaged({ cid });
97
+ const isCallKitManaged = shouldBypassForCallKit({ isRingingTypeCall });
98
+ if (Platform.OS === 'android') {
99
+ StreamInCallManagerNativeModule.setTelecomManagedMode(isTelecomManaged);
85
100
  }
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;
101
+
102
+ if (isTelecomManaged || isCallKitManaged) {
103
+ safeNativeCall('setup defaultAudioDevice (callingx)', () =>
104
+ CallingxModule?.setDefaultAudioDeviceEndpointType(defaultDevice),
105
+ );
93
106
  }
94
- if (Platform.OS === 'android') {
95
- StreamInCallManagerNativeModule.setTelecomManagedMode(false);
107
+
108
+ if (!isTelecomManaged) {
109
+ safeNativeCall('setup defaultAudioDevice', () =>
110
+ StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
111
+ defaultDevice,
112
+ ),
113
+ );
96
114
  }
97
- StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
98
- defaultDevice,
99
- );
100
- StreamInCallManagerNativeModule.setup();
101
115
  },
102
- start: ({ isRingingTypeCall }) => {
116
+ start: ({ isRingingTypeCall, cid }) => {
117
+ // Apply the audio config a consumer recorded via `callManager.start(config)` at this single
118
+ // join-time start, before the native audio manager is activated.
119
+ const config = publicCallManager.getStoredConfig();
120
+ const deviceOverride =
121
+ config?.audioRole === 'communicator'
122
+ ? config.deviceEndpointType
123
+ : undefined;
124
+
103
125
  if (shouldBypassForCallKit({ isRingingTypeCall })) {
126
+ // CallKit owns activation. Only forward an explicit endpoint override; the
127
+ // SpeakerManager-derived default was already forwarded via `setup`.
128
+ if (deviceOverride) {
129
+ safeNativeCall('start defaultAudioDevice (callingx)', () =>
130
+ CallingxModule?.setDefaultAudioDeviceEndpointType(deviceOverride),
131
+ );
132
+ safeNativeCall('start defaultAudioDevice', () =>
133
+ StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
134
+ deviceOverride,
135
+ ),
136
+ );
137
+ }
104
138
  return;
105
139
  }
106
- // Android telecom-managed calls still start (for proximity/keep-screen-on);
107
- // the native side skips routing/focus internally.
140
+
141
+ const isTelecomManaged = isAndroidTelecomManaged({ cid });
142
+ if (Platform.OS === 'android') {
143
+ StreamInCallManagerNativeModule.setTelecomManagedMode(isTelecomManaged);
144
+ }
145
+ if (config?.audioRole) {
146
+ StreamInCallManagerNativeModule.setAudioRole(config.audioRole);
147
+ }
148
+ if (deviceOverride) {
149
+ // Override the SpeakerManager-derived default device (set via `setup`).
150
+ if (isTelecomManaged) {
151
+ safeNativeCall('start defaultAudioDevice (callingx)', () =>
152
+ CallingxModule?.setDefaultAudioDeviceEndpointType(deviceOverride),
153
+ );
154
+ } else {
155
+ safeNativeCall('start defaultAudioDevice', () =>
156
+ StreamInCallManagerNativeModule.setDefaultAudioDeviceEndpointType(
157
+ deviceOverride,
158
+ ),
159
+ );
160
+ }
161
+ }
162
+
163
+ const stereoOutput = config?.audioRole === 'listener';
164
+ StreamInCallManagerNativeModule.setEnableStereoAudioOutput(stereoOutput);
108
165
  StreamInCallManagerNativeModule.start();
109
166
  },
110
- stop: ({ isRingingTypeCall }) => {
111
- if (shouldBypassForCallKit({ isRingingTypeCall })) {
112
- return;
167
+ stop: ({ isRingingTypeCall, shouldStopCallManager }) => {
168
+ // Clear the stored audio config so it doesn't carry into the next call.
169
+ publicCallManager.stop();
170
+
171
+ // We want to interact with ADM only when it was instantiated. This guards a case when
172
+ // leave is invoked for ringing call - in this case PC Factory and ADM are not yet created.
173
+ if (shouldStopCallManager) {
174
+ // Teardown of setMutedRecordingPrepared. Done here (before the CallKit gate)
175
+ // so it runs on both paths and while the call factory is still alive: leave()
176
+ // calls stop() before disposing the engine, so the ADM resolves to the call's
177
+ // factory rather than a default.
178
+ if (Platform.OS === 'ios') {
179
+ StreamInCallManagerNativeModule.setRecordingAlwaysPreparedMode(false);
180
+ }
181
+ if (shouldBypassForCallKit({ isRingingTypeCall })) {
182
+ return;
183
+ }
184
+ StreamInCallManagerNativeModule.stop();
113
185
  }
114
- StreamInCallManagerNativeModule.stop();
115
186
  },
116
187
  // iOS-only. Keep the AVAudioEngine mic-input (voice-processing) chain
117
188
  // prepared while muted so the engine stays full-duplex and remote audio
@@ -125,11 +196,11 @@ const streamRNVideoSDKGlobals: StreamRNVideoSDKGlobals = {
125
196
  if (enabled) {
126
197
  // Mute via the voice-processing unit (it's the default, fail safe config here) so the input chain
127
198
  // stays built while muted, rather than tearing the engine down.
128
- AudioDeviceModule.setMuteMode(
199
+ StreamInCallManagerNativeModule.setMuteMode(
129
200
  AudioEngineMuteMode.VoiceProcessing,
130
- ).catch(() => {});
201
+ );
131
202
  }
132
- AudioDeviceModule.setRecordingAlwaysPreparedMode(enabled).catch(() => {});
203
+ StreamInCallManagerNativeModule.setRecordingAlwaysPreparedMode(enabled);
133
204
  },
134
205
  },
135
206
  permissions: {
@@ -169,4 +240,6 @@ export function registerSDKGlobals() {
169
240
  if (!globalThis.streamRNVideoSDK) {
170
241
  globalThis.streamRNVideoSDK = streamRNVideoSDKGlobals;
171
242
  }
243
+
244
+ registerCallMediaEngine();
172
245
  }
@@ -1,20 +1,8 @@
1
- import {
2
- CallingState,
3
- StreamVideoClient,
4
- videoLoggerSystem,
5
- } from '@stream-io/video-client';
6
- import { AppState, Platform } from 'react-native';
1
+ import { StreamVideoClient, videoLoggerSystem } from '@stream-io/video-client';
2
+ import { Platform } from 'react-native';
7
3
  import type { StreamVideoConfig } from '../StreamVideoRN/types';
8
- import {
9
- type FirebaseMessagingTypes,
10
- getFirebaseMessagingLib,
11
- getFirebaseMessagingLibNoThrow,
12
- } from './libs';
13
- import { pushUnsubscriptionCallbacks } from './internal/constants';
14
- import { canListenToWS, shouldCallBeClosed } from './internal/utils';
4
+ import { getFirebaseMessagingLib } from './libs';
15
5
  import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
16
- import { StreamVideoRN } from '../StreamVideoRN';
17
- import { getCallingxLib } from './libs/callingx';
18
6
 
19
7
  type PushConfig = NonNullable<StreamVideoConfig['push']>;
20
8
 
@@ -60,248 +48,27 @@ export async function initAndroidPushToken(
60
48
  await client.addDevice(token, 'firebase', push_provider_name);
61
49
  };
62
50
 
63
- const messaging = pushConfig.isExpo
64
- ? getFirebaseMessagingLibNoThrow(true)
65
- : getFirebaseMessagingLib();
66
- if (messaging) {
67
- logger.debug(`setting firebase token listeners`);
68
- const unsubscribe = messaging().onTokenRefresh((refreshedToken) =>
69
- setDeviceToken(refreshedToken),
70
- );
71
- setUnsubscribeListener(unsubscribe);
72
- const token = await messaging().getToken();
73
- await setDeviceToken(token);
74
- }
51
+ const messaging = getFirebaseMessagingLib();
52
+ logger.debug(`setting firebase token listeners`);
53
+ const unsubscribe = messaging().onTokenRefresh((refreshedToken) =>
54
+ setDeviceToken(refreshedToken),
55
+ );
56
+ setUnsubscribeListener(unsubscribe);
57
+ const token = await messaging().getToken();
58
+ await setDeviceToken(token);
75
59
  }
76
60
 
61
+ let firebaseDataHandlerDeprecationLogged = false;
77
62
  /**
78
- * Creates notification from the push message data.
79
- * For Ringing and Non-Ringing calls.
63
+ * @deprecated Ring notifications are now handled by the SDK internally. This method is a no-op;
64
+ * you can safely remove `firebaseDataHandler(...)` wiring from your Firebase messaging handlers.
80
65
  */
81
-
82
- export const firebaseDataHandler = async (
83
- data: FirebaseMessagingTypes.RemoteMessage['data'],
84
- ) => {
85
- /* Example data from firebase
86
- "message": {
87
- "data": {
88
- call_cid: 'audio_room:dcc1638c-e90d-4dcb-bf3b-8fa7767bfbb0',
89
- call_display_name: '',
90
- created_by_display_name: 'tommaso',
91
- created_by_id: 'tommaso-03dcddb7-e9e2-42ec-b2f3-5043aac666ee',
92
- receiver_id: 'martin-21824f17-319b-401b-a61b-fcab646f0d3f',
93
- sender: 'stream.video',
94
- type: 'call.live_started',
95
- version: 'v2'
96
- },
97
- // other stuff
98
- }
99
- */
100
- if (Platform.OS !== 'android') return;
101
-
102
- const logger = videoLoggerSystem.getLogger('firebaseDataHandler');
103
- const pushConfig = StreamVideoRN.getConfig().push;
104
- if (!pushConfig || !data || data.sender !== 'stream.video') {
105
- return;
106
- }
107
-
108
- if (data.type === 'call.ring') {
109
- const call_cid = data.call_cid as string;
110
- const callingx = getCallingxLib();
111
-
112
- const client = await pushConfig.createStreamVideoClient();
113
- if (!client) {
114
- logger.debug(
115
- `video client not found, skipping the call.ring notification`,
116
- );
117
- await callingx.stopService();
118
- return;
119
- }
120
-
121
- const asForegroundService = canListenToWS();
122
-
123
- if (asForegroundService) {
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
-
162
- callingx.log(
163
- `Ending call with callCid: ${call_cid} endCallReason: ${endCallReason}`,
164
- 'debug',
165
- );
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
- }
175
-
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
- }
190
-
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 },
199
- );
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
- ) {
214
- logger.debug(
215
- `Closing fg service from callingState callCid: ${call_cid} callingState: ${callingState}`,
216
- );
217
- unsubscribeFunctions.forEach((fn) => fn());
218
- callingx.log(
219
- `Ending call with callCid: ${call_cid} callingState: ${callingState}`,
220
- 'debug',
221
- );
222
- finishBackgroundTask();
223
- }
224
- },
225
- );
226
-
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',
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
- );
250
-
251
- //stop background task when app comes to foreground
252
- const appStateSubscription = AppState.addEventListener(
253
- 'change',
254
- (nextAppState) => {
255
- const _canListenToWS = canListenToWS();
256
- callingx.log(
257
- `AppState changed to: ${nextAppState} for callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
258
- 'debug',
259
- );
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
- })();
282
- }
283
-
284
- if (asForegroundService) {
285
- // no need to check if call has be closed as that will be handled by the fg service
286
- return;
287
- }
288
-
289
- const callFromPush = await client.onRingingCall(call_cid);
290
-
291
- const { mustEndCall, endCallReason } = shouldCallBeClosed(
292
- callFromPush,
293
- data,
66
+ export const firebaseDataHandler = async (data?: any) => {
67
+ void data;
68
+ if (!firebaseDataHandlerDeprecationLogged) {
69
+ firebaseDataHandlerDeprecationLogged = true;
70
+ console.warn(
71
+ '[@stream-io/video-react-native-sdk] `firebaseDataHandler` is deprecated. Ring notifications are now handled by the SDK internally — you can remove calls to it from your Firebase messaging handlers.',
294
72
  );
295
- if (mustEndCall) {
296
- logger.debug(
297
- `Removing incoming call notification immediately with callCid: ${call_cid} as it should be closed`,
298
- );
299
- callingx.endCallWithReason(call_cid, endCallReason);
300
- callFromPush.leave({ reject: false }).catch((error) => {
301
- logger.error(
302
- `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
303
- );
304
- });
305
- }
306
73
  }
307
74
  };