@stream-io/video-react-native-sdk 1.42.0 → 1.44.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.
- package/CHANGELOG.md +41 -6
- package/dist/commonjs/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/Lobby.js +4 -9
- package/dist/commonjs/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js +93 -0
- package/dist/commonjs/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
- package/dist/commonjs/components/Call/Lobby/index.js +11 -0
- package/dist/commonjs/components/Call/Lobby/index.js.map +1 -1
- package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/commonjs/components/Participant/FloatingParticipantView/index.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +4 -15
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js +10 -4
- package/dist/commonjs/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
- package/dist/commonjs/contexts/ThemeContext.js +10 -0
- package/dist/commonjs/contexts/ThemeContext.js.map +1 -1
- package/dist/commonjs/hooks/useScreenShareButton.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +37 -54
- package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
- package/dist/commonjs/providers/NoiseCancellation/lib.js.map +1 -1
- package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js.map +1 -1
- package/dist/commonjs/utils/internal/callingx/callingx.js +26 -37
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/internal/registerMediaEngine.js +41 -0
- package/dist/commonjs/utils/internal/registerMediaEngine.js.map +1 -0
- package/dist/commonjs/utils/internal/registerSDKGlobals.js +93 -35
- package/dist/commonjs/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/commonjs/utils/push/android.js +6 -8
- package/dist/commonjs/utils/push/android.js.map +1 -1
- package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js +0 -9
- package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js.map +1 -1
- package/dist/commonjs/version.js +1 -1
- package/dist/module/components/Call/CallControls/internal/ReactionsPicker.js.map +1 -1
- package/dist/module/components/Call/Lobby/Lobby.js +4 -9
- package/dist/module/components/Call/Lobby/Lobby.js.map +1 -1
- package/dist/module/components/Call/Lobby/LobbyCameraPreview.js +87 -0
- package/dist/module/components/Call/Lobby/LobbyCameraPreview.js.map +1 -0
- package/dist/module/components/Call/Lobby/index.js +1 -0
- package/dist/module/components/Call/Lobby/index.js.map +1 -1
- package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
- package/dist/module/components/Participant/FloatingParticipantView/index.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js +6 -17
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.js.map +1 -1
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js +11 -5
- package/dist/module/components/Participant/ParticipantView/VideoRenderer/index.js.map +1 -1
- package/dist/module/contexts/ThemeContext.js +10 -0
- package/dist/module/contexts/ThemeContext.js.map +1 -1
- package/dist/module/hooks/useScreenShareButton.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +36 -53
- package/dist/module/modules/call-manager/CallManager.js.map +1 -1
- package/dist/module/providers/NoiseCancellation/lib.js.map +1 -1
- package/dist/module/providers/StreamCall/AudioInterruptionTracer.js.map +1 -1
- package/dist/module/utils/internal/callingx/callingx.js +25 -38
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/internal/registerMediaEngine.js +37 -0
- package/dist/module/utils/internal/registerMediaEngine.js.map +1 -0
- package/dist/module/utils/internal/registerSDKGlobals.js +95 -37
- package/dist/module/utils/internal/registerSDKGlobals.js.map +1 -1
- package/dist/module/utils/push/android.js +7 -9
- package/dist/module/utils/push/android.js.map +1 -1
- package/dist/module/utils/push/libs/firebaseMessaging/index.js +0 -8
- package/dist/module/utils/push/libs/firebaseMessaging/index.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Call/Lobby/Lobby.d.ts.map +1 -1
- package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts +28 -0
- package/dist/typescript/components/Call/Lobby/LobbyCameraPreview.d.ts.map +1 -0
- package/dist/typescript/components/Call/Lobby/index.d.ts +1 -0
- package/dist/typescript/components/Call/Lobby/index.d.ts.map +1 -1
- package/dist/typescript/components/Participant/FloatingParticipantView/index.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts +11 -6
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.d.ts.map +1 -1
- package/dist/typescript/components/Participant/ParticipantView/VideoRenderer/index.d.ts.map +1 -1
- package/dist/typescript/contexts/ThemeContext.d.ts +10 -1
- package/dist/typescript/contexts/ThemeContext.d.ts.map +1 -1
- package/dist/typescript/hooks/useScreenShareButton.d.ts +2 -1
- package/dist/typescript/hooks/useScreenShareButton.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/CallManager.d.ts +28 -6
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/types.d.ts +0 -1
- package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
- package/dist/typescript/providers/NoiseCancellation/lib.d.ts.map +1 -1
- package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts.map +1 -1
- package/dist/typescript/utils/StreamVideoRN/types.d.ts +3 -0
- package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts +2 -0
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/internal/registerMediaEngine.d.ts +15 -0
- package/dist/typescript/utils/internal/registerMediaEngine.d.ts.map +1 -0
- package/dist/typescript/utils/internal/registerSDKGlobals.d.ts.map +1 -1
- package/dist/typescript/utils/push/android.d.ts.map +1 -1
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts +0 -1
- package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/expo-config-plugin/dist/withAndroidMessagingService.js +29 -3
- package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.js +84 -2
- package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.js +3 -6
- package/expo-config-plugin/dist/withIosScreenCapture/withTarget.js +6 -4
- package/expo-config-plugin/static/AudioUploader.swift +230 -0
- package/expo-config-plugin/static/SampleHandler.swift +67 -14
- package/ios/StreamInCallManager.m +4 -0
- package/ios/StreamInCallManager.swift +44 -12
- package/ios/StreamVideoReactNative.m +15 -5
- package/package.json +27 -25
- package/src/components/Call/CallControls/internal/ReactionsPicker.tsx +2 -2
- package/src/components/Call/Lobby/Lobby.tsx +4 -11
- package/src/components/Call/Lobby/LobbyCameraPreview.tsx +99 -0
- package/src/components/Call/Lobby/index.ts +1 -0
- package/src/components/Call/RingingCallContent/OutgoingCall.tsx +1 -2
- package/src/components/Participant/FloatingParticipantView/index.tsx +1 -4
- package/src/components/Participant/ParticipantView/VideoRenderer/TrackSubscriber.tsx +56 -75
- package/src/components/Participant/ParticipantView/VideoRenderer/index.tsx +18 -7
- package/src/contexts/ThemeContext.tsx +10 -1
- package/src/hooks/useScreenShareButton.ts +2 -1
- package/src/modules/call-manager/CallManager.ts +41 -73
- package/src/modules/call-manager/native-module.d.ts +13 -0
- package/src/modules/call-manager/types.ts +0 -1
- package/src/providers/NoiseCancellation/lib.ts +1 -2
- package/src/providers/StreamCall/AudioInterruptionTracer.tsx +1 -2
- package/src/utils/StreamVideoRN/types.ts +4 -3
- package/src/utils/internal/callingx/callingx.ts +35 -44
- package/src/utils/internal/registerMediaEngine.ts +42 -0
- package/src/utils/internal/registerSDKGlobals.ts +115 -42
- package/src/utils/push/android.ts +9 -17
- package/src/utils/push/libs/firebaseMessaging/index.ts +0 -19
- package/src/version.ts +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LayoutChangeEvent } from 'react-native';
|
|
1
|
+
import { useEffect } from 'react';
|
|
3
2
|
import {
|
|
4
3
|
Call,
|
|
5
4
|
CallingState,
|
|
@@ -18,15 +17,17 @@ import {
|
|
|
18
17
|
filter,
|
|
19
18
|
map,
|
|
20
19
|
} from 'rxjs';
|
|
21
|
-
export type TrackSubscriberHandle = {
|
|
22
|
-
onLayoutUpdate: (event: LayoutChangeEvent) => void;
|
|
23
|
-
};
|
|
24
20
|
|
|
25
21
|
type TrackSubscriberProps = {
|
|
26
22
|
participantSessionId: string;
|
|
27
23
|
call: Call;
|
|
28
24
|
trackType: VideoTrackType;
|
|
29
25
|
isVisible: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* The dimensions of the view rendering the track, owned by the parent so that
|
|
28
|
+
* the last reported layout survives a remount of this component.
|
|
29
|
+
*/
|
|
30
|
+
dimensions$: BehaviorSubject<SfuModels.VideoDimension | undefined>;
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -40,81 +41,61 @@ type TrackSubscriberProps = {
|
|
|
40
41
|
* 1. When the participant stops publishing the video track
|
|
41
42
|
* 2. When the participant becomes invisible
|
|
42
43
|
*/
|
|
43
|
-
const TrackSubscriber =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const dimensions$ = useMemo(() => {
|
|
47
|
-
return new BehaviorSubject<SfuModels.VideoDimension | undefined>(
|
|
48
|
-
undefined,
|
|
49
|
-
);
|
|
50
|
-
}, []);
|
|
51
|
-
|
|
52
|
-
useEffect(() => {
|
|
53
|
-
const requestTrackWithDimensions = (
|
|
54
|
-
debounceType: DebounceType,
|
|
55
|
-
dimension: SfuModels.VideoDimension | undefined,
|
|
56
|
-
) => {
|
|
57
|
-
if (dimension && (dimension.width === 0 || dimension.height === 0)) {
|
|
58
|
-
// ignore 0x0 dimensions. this can happen when the video element
|
|
59
|
-
// is not visible (e.g., has display: none).
|
|
60
|
-
// we treat this as "unsubscription" as we don't want to keep
|
|
61
|
-
// consuming bandwidth for a video that is not visible on the screen.
|
|
62
|
-
dimension = undefined;
|
|
63
|
-
}
|
|
64
|
-
call.state.updateParticipantTracks(trackType, {
|
|
65
|
-
[participantSessionId]: { dimension },
|
|
66
|
-
});
|
|
67
|
-
call.trackSubscriptionManager.apply(debounceType);
|
|
68
|
-
};
|
|
69
|
-
const isPublishingTrack$ = call.state.participants$.pipe(
|
|
70
|
-
map((ps) => ps.find((p) => p.sessionId === participantSessionId)),
|
|
71
|
-
filter((p): p is StreamVideoParticipant => !!p),
|
|
72
|
-
distinctUntilKeyChanged('publishedTracks'),
|
|
73
|
-
map((p) =>
|
|
74
|
-
trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p),
|
|
75
|
-
),
|
|
76
|
-
distinctUntilChanged(),
|
|
77
|
-
);
|
|
78
|
-
const isJoinedState$ = call.state.callingState$.pipe(
|
|
79
|
-
map((callingState) => callingState === CallingState.JOINED),
|
|
80
|
-
);
|
|
44
|
+
const TrackSubscriber = (props: TrackSubscriberProps) => {
|
|
45
|
+
const { call, participantSessionId, trackType, isVisible, dimensions$ } =
|
|
46
|
+
props;
|
|
81
47
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const requestTrackWithDimensions = (
|
|
50
|
+
debounceType: DebounceType,
|
|
51
|
+
dimension: SfuModels.VideoDimension | undefined,
|
|
52
|
+
) => {
|
|
53
|
+
if (dimension && (dimension.width === 0 || dimension.height === 0)) {
|
|
54
|
+
// ignore 0x0 dimensions. this can happen when the video element
|
|
55
|
+
// is not visible (e.g., has display: none).
|
|
56
|
+
// we treat this as "unsubscription" as we don't want to keep
|
|
57
|
+
// consuming bandwidth for a video that is not visible on the screen.
|
|
58
|
+
dimension = undefined;
|
|
59
|
+
}
|
|
60
|
+
call.state.updateParticipantTracks(trackType, {
|
|
61
|
+
[participantSessionId]: { dimension },
|
|
94
62
|
});
|
|
63
|
+
call.trackSubscriptionManager.apply(debounceType);
|
|
64
|
+
};
|
|
65
|
+
const isPublishingTrack$ = call.state.participants$.pipe(
|
|
66
|
+
map((ps) => ps.find((p) => p.sessionId === participantSessionId)),
|
|
67
|
+
filter((p): p is StreamVideoParticipant => !!p),
|
|
68
|
+
distinctUntilKeyChanged('publishedTracks'),
|
|
69
|
+
map((p) =>
|
|
70
|
+
trackType === 'videoTrack' ? hasVideo(p) : hasScreenShare(p),
|
|
71
|
+
),
|
|
72
|
+
distinctUntilChanged(),
|
|
73
|
+
);
|
|
74
|
+
const isJoinedState$ = call.state.callingState$.pipe(
|
|
75
|
+
map((callingState) => callingState === CallingState.JOINED),
|
|
76
|
+
);
|
|
95
77
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
78
|
+
const subscription = combineLatest([
|
|
79
|
+
dimensions$,
|
|
80
|
+
isPublishingTrack$,
|
|
81
|
+
isJoinedState$,
|
|
82
|
+
]).subscribe(([dimension, isPublishing, isJoined]) => {
|
|
83
|
+
if (isJoined) {
|
|
84
|
+
if (!isVisible || !isPublishing) {
|
|
85
|
+
requestTrackWithDimensions(DebounceType.MEDIUM, undefined);
|
|
86
|
+
} else if (dimension) {
|
|
87
|
+
requestTrackWithDimensions(DebounceType.IMMEDIATE, dimension);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
100
91
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const dimension = {
|
|
106
|
-
width: Math.trunc(event.nativeEvent.layout.width),
|
|
107
|
-
height: Math.trunc(event.nativeEvent.layout.height),
|
|
108
|
-
};
|
|
109
|
-
dimensions$.next(dimension);
|
|
110
|
-
},
|
|
111
|
-
}),
|
|
112
|
-
[dimensions$],
|
|
113
|
-
);
|
|
92
|
+
return () => {
|
|
93
|
+
subscription.unsubscribe();
|
|
94
|
+
};
|
|
95
|
+
}, [call, participantSessionId, trackType, isVisible, dimensions$]);
|
|
114
96
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
97
|
+
return null;
|
|
98
|
+
};
|
|
118
99
|
|
|
119
100
|
TrackSubscriber.displayName = 'TrackSubscriber';
|
|
120
101
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect, useRef } from 'react';
|
|
2
|
-
import { Platform, StyleSheet, View } from 'react-native';
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react';
|
|
2
|
+
import { LayoutChangeEvent, Platform, StyleSheet, View } from 'react-native';
|
|
3
3
|
import type { MediaStream } from '@stream-io/react-native-webrtc';
|
|
4
4
|
import { RTCView } from '@stream-io/react-native-webrtc';
|
|
5
5
|
import type { ParticipantViewProps } from '../ParticipantView';
|
|
@@ -7,15 +7,17 @@ import {
|
|
|
7
7
|
hasPausedTrack,
|
|
8
8
|
hasScreenShare,
|
|
9
9
|
hasVideo,
|
|
10
|
+
SfuModels,
|
|
10
11
|
type VideoTrackType,
|
|
11
12
|
VisibilityState,
|
|
12
13
|
} from '@stream-io/video-client';
|
|
13
14
|
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
15
|
+
import { BehaviorSubject } from 'rxjs';
|
|
14
16
|
import { ParticipantVideoFallback as DefaultParticipantVideoFallback } from '../ParticipantVideoFallback';
|
|
15
17
|
import { useTheme } from '../../../../contexts/ThemeContext';
|
|
16
18
|
import { useTrackDimensions } from '../../../../hooks/useTrackDimensions';
|
|
17
19
|
import { useScreenshotIosContext } from '../../../../contexts/internal/ScreenshotIosContext';
|
|
18
|
-
import TrackSubscriber
|
|
20
|
+
import TrackSubscriber from './TrackSubscriber';
|
|
19
21
|
|
|
20
22
|
const DEFAULT_VIEWPORT_VISIBILITY_STATE: Record<
|
|
21
23
|
VideoTrackType,
|
|
@@ -59,7 +61,13 @@ export const VideoRenderer = React.memo(
|
|
|
59
61
|
} = useTheme();
|
|
60
62
|
const call = useCall();
|
|
61
63
|
const { useCameraState, useIncomingVideoSettings } = useCallStateHooks();
|
|
62
|
-
|
|
64
|
+
// a sticky note of the dimensions of the RTCView from onLayout
|
|
65
|
+
// to reuse for a new participant even if dimensions dont change
|
|
66
|
+
const dimensions$ = useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
new BehaviorSubject<SfuModels.VideoDimension | undefined>(undefined),
|
|
69
|
+
[],
|
|
70
|
+
);
|
|
63
71
|
const { isParticipantVideoEnabled } = useIncomingVideoSettings();
|
|
64
72
|
const { direction } = useCameraState();
|
|
65
73
|
const viewRef = useRef(null);
|
|
@@ -199,9 +207,12 @@ export const VideoRenderer = React.memo(
|
|
|
199
207
|
]);
|
|
200
208
|
|
|
201
209
|
const onLayout: React.ComponentProps<typeof RTCView>['onLayout'] = (
|
|
202
|
-
event,
|
|
210
|
+
event: LayoutChangeEvent,
|
|
203
211
|
) => {
|
|
204
|
-
|
|
212
|
+
dimensions$.next({
|
|
213
|
+
width: Math.trunc(event.nativeEvent.layout.width),
|
|
214
|
+
height: Math.trunc(event.nativeEvent.layout.height),
|
|
215
|
+
});
|
|
205
216
|
};
|
|
206
217
|
|
|
207
218
|
return (
|
|
@@ -211,11 +222,11 @@ export const VideoRenderer = React.memo(
|
|
|
211
222
|
>
|
|
212
223
|
{call && !isLocalParticipant && (
|
|
213
224
|
<TrackSubscriber
|
|
214
|
-
ref={trackSubscriberRef}
|
|
215
225
|
call={call}
|
|
216
226
|
participantSessionId={sessionId}
|
|
217
227
|
trackType={trackType}
|
|
218
228
|
isVisible={isVisible}
|
|
229
|
+
dimensions$={dimensions$}
|
|
219
230
|
/>
|
|
220
231
|
)}
|
|
221
232
|
{canShowVideo &&
|
|
@@ -7,8 +7,17 @@ import React, {
|
|
|
7
7
|
|
|
8
8
|
import { defaultTheme, type Theme } from '../theme/theme';
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Recursively marks every property of `T` as optional.
|
|
12
|
+
*
|
|
13
|
+
* The `extends object` guard is required, not cosmetic: `Theme` carries a
|
|
14
|
+
* `[component: string]: any` index signature, which the mapped type inherits.
|
|
15
|
+
* Without the guard that index signature becomes `DeepPartial<any>`, an
|
|
16
|
+
* all-object type that no primitive leaf can satisfy, and every theme override
|
|
17
|
+
* fails to typecheck. Guarding short-circuits `any` back to `any`.
|
|
18
|
+
*/
|
|
10
19
|
export type DeepPartial<T> = {
|
|
11
|
-
[P in keyof T]?: DeepPartial<T[P]
|
|
20
|
+
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
|
|
12
21
|
};
|
|
13
22
|
|
|
14
23
|
export type StreamThemeInputValue = {
|
|
@@ -37,7 +37,8 @@ export type ScreenShareOptions = {
|
|
|
37
37
|
* (e.g., YouTube video audio) mixed with the user's microphone.
|
|
38
38
|
*
|
|
39
39
|
* - iOS in-app: Audio captured from RPScreenRecorder `.audioApp` buffers.
|
|
40
|
-
* - iOS broadcast: Audio
|
|
40
|
+
* - iOS broadcast: Audio captured in the Broadcast Upload Extension and
|
|
41
|
+
* streamed to the app over a dedicated socket, then mixed into the audio track.
|
|
41
42
|
* - Android: Audio captured via AudioPlaybackCaptureConfiguration (API 29+).
|
|
42
43
|
*
|
|
43
44
|
* Default: `false`.
|
|
@@ -9,8 +9,8 @@ import type {
|
|
|
9
9
|
AudioEndpoint as CallingxAudioEndpoint,
|
|
10
10
|
AudioEndpointsSnapshot as CallingxAudioSnapshot,
|
|
11
11
|
} from '@stream-io/react-native-callingx';
|
|
12
|
-
import { getCallingxLibIfAvailable } from '../../utils/push/libs/callingx';
|
|
13
12
|
import { videoLoggerSystem } from '@stream-io/video-client';
|
|
13
|
+
import { getCallingxLibIfAvailable } from '../../utils/push/libs';
|
|
14
14
|
|
|
15
15
|
const NativeManager = NativeModules.StreamInCallManager;
|
|
16
16
|
const CallingxModule = getCallingxLibIfAvailable();
|
|
@@ -55,8 +55,7 @@ const isAndroidTelecomManaged = (): boolean => {
|
|
|
55
55
|
* the extra narrowing here is what makes that provable to the type-checker.
|
|
56
56
|
*/
|
|
57
57
|
const getTelecomContext = ():
|
|
58
|
-
|
|
59
|
-
| undefined => {
|
|
58
|
+
{ cx: NonNullable<typeof CallingxModule>; callId: string } | undefined => {
|
|
60
59
|
if (!isAndroidTelecomManaged() || !CallingxModule) {
|
|
61
60
|
return undefined;
|
|
62
61
|
}
|
|
@@ -297,26 +296,32 @@ class SpeakerManager {
|
|
|
297
296
|
};
|
|
298
297
|
}
|
|
299
298
|
|
|
300
|
-
const shouldBypassForCallKit = (): boolean => {
|
|
301
|
-
if (Platform.OS !== 'ios') {
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
if (!CallingxModule) {
|
|
305
|
-
return false;
|
|
306
|
-
}
|
|
307
|
-
return (
|
|
308
|
-
CallingxModule.isSetup &&
|
|
309
|
-
(CallingxModule.hasRegisteredCall() || CallingxModule.isOngoingCallsEnabled)
|
|
310
|
-
);
|
|
311
|
-
};
|
|
312
|
-
|
|
313
299
|
export class CallManager {
|
|
314
300
|
audioDevices = new AudioDevicesManager();
|
|
315
301
|
ios = new IOSCallManager();
|
|
316
302
|
speaker = new SpeakerManager();
|
|
317
303
|
|
|
318
304
|
/**
|
|
319
|
-
*
|
|
305
|
+
* The audio config recorded via {@link start}. The SDK's internal call manager reads it at the
|
|
306
|
+
* next join-time start and applies it before the native audio manager is activated.
|
|
307
|
+
*/
|
|
308
|
+
private storedConfig?: StreamInCallManagerConfig;
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* The config recorded via {@link start}.
|
|
312
|
+
*
|
|
313
|
+
* @internal Read by the SDK's internal call manager at join; not intended for app use.
|
|
314
|
+
*/
|
|
315
|
+
getStoredConfig = (): StreamInCallManagerConfig | undefined =>
|
|
316
|
+
this.storedConfig;
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Records the desired audio config for the call.
|
|
320
|
+
*
|
|
321
|
+
* This does NOT start the native audio manager — the SDK owns native start/stop and applies this
|
|
322
|
+
* config at the next join-time start (before the audio manager is activated). Call it **before**
|
|
323
|
+
* joining. Calling it mid-call only updates the stored config; it does not change the running
|
|
324
|
+
* call's audio, and the new config takes effect on the next call/rejoin.
|
|
320
325
|
*
|
|
321
326
|
* @param config.audioRole The audio role to set. It can be one of the following:
|
|
322
327
|
* - `'communicator'`: (Default) For use cases like video or voice calls.
|
|
@@ -326,80 +331,43 @@ export class CallManager {
|
|
|
326
331
|
* It prioritizes high-quality stereo audio streaming.
|
|
327
332
|
* Audio routing is controlled by the OS, and manual switching is not supported.
|
|
328
333
|
*
|
|
329
|
-
* @param config.deviceEndpointType
|
|
330
|
-
*
|
|
334
|
+
* @param config.deviceEndpointType Overrides the default audio device endpoint. When omitted,
|
|
335
|
+
* the SDK uses the device derived from the call settings. It can be one of the following:
|
|
336
|
+
* - `'speaker'`: For normal video or voice calls.
|
|
331
337
|
* - `'earpiece'`: For voice-only mobile call type scenarios.
|
|
332
|
-
*
|
|
333
|
-
* @param config.enableStereoAudioOutput Whether to enable stereo audio output. Only supported for listener audio role.
|
|
334
338
|
*/
|
|
335
339
|
start = (config?: StreamInCallManagerConfig): void => {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
CallingxModule.setDefaultAudioDeviceEndpointType(type),
|
|
341
|
-
);
|
|
342
|
-
safeNativeCall('setDefaultAudioDeviceEndpointType', () =>
|
|
343
|
-
NativeManager.setDefaultAudioDeviceEndpointType(type),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
videoLoggerSystem
|
|
347
|
-
.getLogger('CallManager')
|
|
348
|
-
.debug(
|
|
349
|
-
'start: skipping start as callkit is handling the audio session',
|
|
350
|
-
);
|
|
351
|
-
return;
|
|
352
|
-
}
|
|
353
|
-
if (isAndroidTelecomManaged()) {
|
|
354
|
-
// Telecom owns routing/focus; forward the sticky preference to callingx and run in
|
|
355
|
-
// telecom-managed mode (StreamInCallManager keeps proximity/keep-screen-on only).
|
|
356
|
-
if (config?.audioRole !== 'listener' && CallingxModule) {
|
|
357
|
-
safeNativeCall('setDefaultAudioDeviceEndpointType (callingx)', () =>
|
|
358
|
-
CallingxModule.setDefaultAudioDeviceEndpointType(
|
|
359
|
-
config?.deviceEndpointType ?? 'speaker',
|
|
360
|
-
),
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
NativeManager.setTelecomManagedMode(true);
|
|
364
|
-
NativeManager.setAudioRole(config?.audioRole ?? 'communicator');
|
|
365
|
-
NativeManager.start();
|
|
366
|
-
return;
|
|
367
|
-
}
|
|
368
|
-
if (Platform.OS === 'android') {
|
|
369
|
-
NativeManager.setTelecomManagedMode(false);
|
|
370
|
-
}
|
|
371
|
-
NativeManager.setAudioRole(config?.audioRole ?? 'communicator');
|
|
372
|
-
if (config?.audioRole === 'communicator') {
|
|
373
|
-
const type = config.deviceEndpointType ?? 'speaker';
|
|
374
|
-
NativeManager.setDefaultAudioDeviceEndpointType(type);
|
|
375
|
-
}
|
|
376
|
-
if (config?.audioRole === 'listener' && config.enableStereoAudioOutput) {
|
|
377
|
-
NativeManager.setEnableStereoAudioOutput(true);
|
|
378
|
-
}
|
|
379
|
-
NativeManager.start();
|
|
340
|
+
this.storedConfig = config;
|
|
341
|
+
videoLoggerSystem
|
|
342
|
+
.getLogger('CallManager')
|
|
343
|
+
.debug('start: stored call manager config', { config });
|
|
380
344
|
};
|
|
381
345
|
|
|
382
346
|
/**
|
|
383
|
-
*
|
|
347
|
+
* Clears the stored audio config.
|
|
384
348
|
*/
|
|
385
349
|
stop = (): void => {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
NativeManager.stop();
|
|
350
|
+
this.storedConfig = undefined;
|
|
351
|
+
videoLoggerSystem
|
|
352
|
+
.getLogger('CallManager')
|
|
353
|
+
.debug('[public] stop(): cleared stored config');
|
|
393
354
|
};
|
|
394
355
|
|
|
395
356
|
/**
|
|
396
357
|
* For debugging purposes, will emit a log event with the current audio state.
|
|
397
358
|
* in the native layer.
|
|
359
|
+
*
|
|
360
|
+
* NOTE: This method might be called outside of the call JOIN/LEFT window,
|
|
361
|
+
* so it may lead to default peer connection factory and adm being created.
|
|
398
362
|
*/
|
|
399
363
|
logAudioState = (): void => NativeManager.logAudioState();
|
|
400
364
|
|
|
401
365
|
/**
|
|
402
366
|
* For debugging purposes, returns the current audio state as a string.
|
|
367
|
+
*
|
|
368
|
+
* NOTE: This method might be called outside of the call JOIN/LEFT window,
|
|
369
|
+
* so it may lead to default peer connection factory and adm being created.
|
|
370
|
+
*
|
|
403
371
|
* @returns A string containing the current audio state information.
|
|
404
372
|
*/
|
|
405
373
|
getAudioStateLog = (): string => NativeManager.getAudioStateLog();
|
|
@@ -92,6 +92,19 @@ export interface CallManager extends NativeModule {
|
|
|
92
92
|
*/
|
|
93
93
|
setEnableStereoAudioOutput: (enable: boolean) => void;
|
|
94
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Sets the microphone mute mode on the call's ADM. No-ops when no call ADM is active. iOS-only.
|
|
97
|
+
* @param mode - The `AudioEngineMuteMode` value.
|
|
98
|
+
*/
|
|
99
|
+
setMuteMode: (mode: number) => void;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Keeps the recording chain prepared while muted so the engine stays
|
|
103
|
+
* full-duplex. No-ops when no call ADM is active. iOS-only.
|
|
104
|
+
* @param enabled - Whether to keep recording always prepared.
|
|
105
|
+
*/
|
|
106
|
+
setRecordingAlwaysPreparedMode: (enabled: boolean) => void;
|
|
107
|
+
|
|
95
108
|
/**
|
|
96
109
|
* Log the current audio state natively.
|
|
97
110
|
* Meant for debugging purposes.
|
|
@@ -17,8 +17,7 @@ export function getNoiseCancellationLibThrowIfNotInstalled() {
|
|
|
17
17
|
|
|
18
18
|
export class NoiseCancellationWrapper {
|
|
19
19
|
private static noiseCancellationInstance:
|
|
20
|
-
|
|
21
|
-
| undefined;
|
|
20
|
+
InstanceType<Type['NoiseCancellation']> | undefined;
|
|
22
21
|
|
|
23
22
|
private constructor() {}
|
|
24
23
|
|
|
@@ -34,8 +34,7 @@ export const AudioInterruptionTracer = () => {
|
|
|
34
34
|
const removeInCallManagerListener =
|
|
35
35
|
callManager.ios.addAudioInterruptionListener(traceAudioInterruption);
|
|
36
36
|
const callingx = getCallingxLibIfAvailable() as
|
|
37
|
-
|
|
|
38
|
-
| undefined;
|
|
37
|
+
CallingxAudioInterruptionEvents | undefined;
|
|
39
38
|
const callingxSubscription = callingx?.addEventListener(
|
|
40
39
|
'didAudioInterruption',
|
|
41
40
|
traceAudioInterruption,
|
|
@@ -17,9 +17,7 @@ export type KeepAliveAndroidNotificationTexts = {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
export type NonRingingPushEvent =
|
|
20
|
-
| 'call.
|
|
21
|
-
| 'call.notification'
|
|
22
|
-
| 'call.missed';
|
|
20
|
+
'call.live_started' | 'call.notification' | 'call.missed';
|
|
23
21
|
|
|
24
22
|
export type StreamVideoConfig = {
|
|
25
23
|
/**
|
|
@@ -28,6 +26,9 @@ export type StreamVideoConfig = {
|
|
|
28
26
|
* @default undefined
|
|
29
27
|
*/
|
|
30
28
|
push?: {
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Expo is auto-detected; this value is ignored and the property will be removed in a future major version.
|
|
31
|
+
*/
|
|
31
32
|
isExpo?: boolean;
|
|
32
33
|
/**
|
|
33
34
|
* The publish options to be used when joining a call from a push notification.
|
|
@@ -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 {
|
|
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
|
*/
|
|
@@ -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
|
+
}
|