@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
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// SampleHandler.swift
|
|
3
3
|
// Broadcast Extension
|
|
4
4
|
//
|
|
5
|
+
|
|
5
6
|
import ReplayKit
|
|
6
7
|
import OSLog
|
|
7
8
|
|
|
@@ -9,26 +10,47 @@ let broadcastLogger = OSLog(subsystem: "io.getstream.reactnative", category: "Br
|
|
|
9
10
|
private enum Constants {
|
|
10
11
|
// the App Group ID value that the app and the broadcast extension targets are setup with. It differs for each app.
|
|
11
12
|
static let appGroupIdentifier = "group.com.example.broadcast.appgroup"
|
|
13
|
+
static let videoSocketName = "rtc_SSFD"
|
|
14
|
+
static let audioSocketName = "rtc_SSFD_audio"
|
|
15
|
+
static let audioConnectMaxAttempts = 300
|
|
12
16
|
}
|
|
13
17
|
class SampleHandler: RPBroadcastSampleHandler {
|
|
14
18
|
|
|
15
|
-
private var
|
|
16
|
-
private var
|
|
19
|
+
private var videoClientConnection: SocketConnection?
|
|
20
|
+
private var videoUploader: SampleUploader?
|
|
21
|
+
|
|
22
|
+
private var audioClientConnection: SocketConnection?
|
|
23
|
+
private var audioUploader: AudioUploader?
|
|
17
24
|
|
|
18
25
|
private var frameCount: Int = 0
|
|
19
26
|
|
|
27
|
+
private func socketFilePath(for name: String) -> String {
|
|
28
|
+
let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
|
|
29
|
+
return sharedContainer?.appendingPathComponent(name).path ?? ""
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
var socketFilePath: String {
|
|
21
|
-
|
|
22
|
-
|
|
33
|
+
socketFilePath(for: Constants.videoSocketName)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var audioSocketFilePath: String {
|
|
37
|
+
socketFilePath(for: Constants.audioSocketName)
|
|
23
38
|
}
|
|
24
39
|
|
|
25
40
|
override init() {
|
|
26
41
|
super.init()
|
|
27
42
|
if let connection = SocketConnection(filePath: socketFilePath) {
|
|
28
|
-
|
|
29
|
-
|
|
43
|
+
videoClientConnection = connection
|
|
44
|
+
setupVideoConnection()
|
|
30
45
|
|
|
31
|
-
|
|
46
|
+
videoUploader = SampleUploader(connection: connection)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if let connection = SocketConnection(filePath: audioSocketFilePath) {
|
|
50
|
+
audioClientConnection = connection
|
|
51
|
+
setupAudioConnection()
|
|
52
|
+
|
|
53
|
+
audioUploader = AudioUploader(connection: connection)
|
|
32
54
|
}
|
|
33
55
|
os_log(.debug, log: broadcastLogger, "%{public}s", socketFilePath)
|
|
34
56
|
}
|
|
@@ -38,7 +60,8 @@ class SampleHandler: RPBroadcastSampleHandler {
|
|
|
38
60
|
frameCount = 0
|
|
39
61
|
|
|
40
62
|
DarwinNotificationCenter.shared.postNotification(.broadcastStarted)
|
|
41
|
-
|
|
63
|
+
openVideoConnection()
|
|
64
|
+
openAudioConnection()
|
|
42
65
|
}
|
|
43
66
|
|
|
44
67
|
override func broadcastPaused() {
|
|
@@ -52,14 +75,18 @@ class SampleHandler: RPBroadcastSampleHandler {
|
|
|
52
75
|
override func broadcastFinished() {
|
|
53
76
|
// User has requested to finish the broadcast.
|
|
54
77
|
DarwinNotificationCenter.shared.postNotification(.broadcastStopped)
|
|
55
|
-
|
|
78
|
+
videoClientConnection?.close()
|
|
79
|
+
audioClientConnection?.close()
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
|
|
59
83
|
switch sampleBufferType {
|
|
60
84
|
case RPSampleBufferType.video:
|
|
61
|
-
|
|
85
|
+
videoUploader?.send(sample: sampleBuffer)
|
|
86
|
+
case RPSampleBufferType.audioApp:
|
|
87
|
+
audioUploader?.send(sample: sampleBuffer)
|
|
62
88
|
default:
|
|
89
|
+
// .audioMic is handled by the app's audio device module; ignore it here.
|
|
63
90
|
break
|
|
64
91
|
}
|
|
65
92
|
}
|
|
@@ -67,8 +94,8 @@ class SampleHandler: RPBroadcastSampleHandler {
|
|
|
67
94
|
|
|
68
95
|
private extension SampleHandler {
|
|
69
96
|
|
|
70
|
-
func
|
|
71
|
-
|
|
97
|
+
func setupVideoConnection() {
|
|
98
|
+
videoClientConnection?.didClose = { [weak self] error in
|
|
72
99
|
os_log(.debug, log: broadcastLogger, "client connection did close \(String(describing: error))")
|
|
73
100
|
|
|
74
101
|
if let error = error {
|
|
@@ -82,12 +109,19 @@ private extension SampleHandler {
|
|
|
82
109
|
}
|
|
83
110
|
}
|
|
84
111
|
|
|
85
|
-
func
|
|
112
|
+
func setupAudioConnection() {
|
|
113
|
+
// closed audio socket must not tear down the whole broadcast.
|
|
114
|
+
audioClientConnection?.didClose = { error in
|
|
115
|
+
os_log(.debug, log: broadcastLogger, "audio connection did close \(String(describing: error))")
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
func openVideoConnection() {
|
|
86
120
|
let queue = DispatchQueue(label: "broadcast.connectTimer")
|
|
87
121
|
let timer = DispatchSource.makeTimerSource(queue: queue)
|
|
88
122
|
timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
|
|
89
123
|
timer.setEventHandler { [weak self] in
|
|
90
|
-
guard self?.
|
|
124
|
+
guard self?.videoClientConnection?.open() == true else {
|
|
91
125
|
return
|
|
92
126
|
}
|
|
93
127
|
|
|
@@ -96,4 +130,23 @@ private extension SampleHandler {
|
|
|
96
130
|
|
|
97
131
|
timer.resume()
|
|
98
132
|
}
|
|
133
|
+
|
|
134
|
+
func openAudioConnection() {
|
|
135
|
+
let queue = DispatchQueue(label: "broadcast.audioConnectTimer")
|
|
136
|
+
let timer = DispatchSource.makeTimerSource(queue: queue)
|
|
137
|
+
timer.schedule(deadline: .now(), repeating: .milliseconds(100), leeway: .milliseconds(500))
|
|
138
|
+
var attempts = 0
|
|
139
|
+
timer.setEventHandler { [weak self] in
|
|
140
|
+
attempts += 1
|
|
141
|
+
if self?.audioClientConnection?.open() == true {
|
|
142
|
+
timer.cancel()
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if attempts >= Constants.audioConnectMaxAttempts {
|
|
146
|
+
timer.cancel()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
timer.resume()
|
|
151
|
+
}
|
|
99
152
|
}
|
|
@@ -9,6 +9,10 @@ RCT_EXTERN_METHOD(setDefaultAudioDeviceEndpointType:(NSString *)endpointType)
|
|
|
9
9
|
|
|
10
10
|
RCT_EXTERN_METHOD(setEnableStereoAudioOutput:(BOOL)enable)
|
|
11
11
|
|
|
12
|
+
RCT_EXTERN_METHOD(setMuteMode:(NSInteger)mode)
|
|
13
|
+
|
|
14
|
+
RCT_EXTERN_METHOD(setRecordingAlwaysPreparedMode:(BOOL)enabled)
|
|
15
|
+
|
|
12
16
|
RCT_EXTERN_METHOD(setup)
|
|
13
17
|
|
|
14
18
|
RCT_EXTERN_METHOD(start)
|
|
@@ -124,7 +124,30 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
124
124
|
self.enableStereo = enabled
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
|
+
@objc(setMuteMode:)
|
|
129
|
+
func setMuteMode(mode: NSInteger) {
|
|
130
|
+
audioSessionQueue.async { [self] in
|
|
131
|
+
guard let adm = getAudioDeviceModule() else {
|
|
132
|
+
log("setMuteMode(\(mode)) skipped: no live call ADM")
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
let muteMode = RTCAudioEngineMuteMode(rawValue: mode) ?? .voiceProcessing
|
|
136
|
+
_ = adm.setMuteMode(muteMode)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@objc(setRecordingAlwaysPreparedMode:)
|
|
141
|
+
func setRecordingAlwaysPreparedMode(enabled: Bool) {
|
|
142
|
+
audioSessionQueue.async { [self] in
|
|
143
|
+
guard let adm = getAudioDeviceModule() else {
|
|
144
|
+
log("setRecordingAlwaysPreparedMode(\(enabled)) skipped: no live call ADM")
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
_ = adm.setRecordingAlwaysPreparedMode(enabled)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
128
151
|
/// Builds the audio config for the current role/device and sets it as WebRTC's default.
|
|
129
152
|
private func makeAudioConfiguration(for routing: OutputRouting?) -> RTCAudioSessionConfiguration {
|
|
130
153
|
let category: AVAudioSession.Category
|
|
@@ -152,7 +175,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
152
175
|
// only exists under .playAndRecord.
|
|
153
176
|
category = .playback
|
|
154
177
|
mode = .spokenAudio
|
|
155
|
-
options = []
|
|
178
|
+
options = [.mixWithOthers]
|
|
156
179
|
} else {
|
|
157
180
|
// XCode 16 and older don't expose .allowBluetoothHFP
|
|
158
181
|
// https://forums.swift.org/t/xcode-26-avaudiosession-categoryoptions-allowbluetooth-deprecated/80956
|
|
@@ -170,7 +193,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
170
193
|
// then we always can't use defaultToSpeaker
|
|
171
194
|
mustSetDefaultToSpeaker = false
|
|
172
195
|
}
|
|
173
|
-
options = mustSetDefaultToSpeaker ? [bluetoothOption, .defaultToSpeaker] : [bluetoothOption]
|
|
196
|
+
options = mustSetDefaultToSpeaker ? [bluetoothOption, .defaultToSpeaker, .mixWithOthers] : [bluetoothOption, .mixWithOthers]
|
|
174
197
|
}
|
|
175
198
|
|
|
176
199
|
let rtcConfig = RTCAudioSessionConfiguration.webRTC()
|
|
@@ -199,11 +222,11 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
199
222
|
// This path (no CallKit) owns audio-session activation itself, so the
|
|
200
223
|
// engine must be allowed to start. Restores availability in case a
|
|
201
224
|
// prior CallKit call had gated it off.
|
|
202
|
-
_ = adm
|
|
225
|
+
_ = adm?.setEngineAvailability(.default)
|
|
203
226
|
|
|
204
227
|
// Stereo is listener-only and applies live. Cleared by stop()'s reset().
|
|
205
228
|
if callAudioRole == .listener && enableStereo {
|
|
206
|
-
adm
|
|
229
|
+
adm?.setStereoPlayoutPreference(true)
|
|
207
230
|
}
|
|
208
231
|
|
|
209
232
|
let rtcConfig = makeAudioConfiguration(for: selectedOutput)
|
|
@@ -228,7 +251,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
228
251
|
#if DEBUG
|
|
229
252
|
NSLog("%@","[StreamInCallManager][wireEngineSubscription]")
|
|
230
253
|
#endif
|
|
231
|
-
engineSubscription = adm
|
|
254
|
+
engineSubscription = adm?.publisher.sink { [weak self] event in
|
|
232
255
|
guard let self else { return }
|
|
233
256
|
self.audioSessionQueue.async {
|
|
234
257
|
switch event {
|
|
@@ -339,7 +362,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
339
362
|
// session.setActive(true) here.
|
|
340
363
|
do {
|
|
341
364
|
let adm = getAudioDeviceModule()
|
|
342
|
-
try adm
|
|
365
|
+
try adm?.setPlayout(true)
|
|
343
366
|
self.log("adm.setPlayout(true) done")
|
|
344
367
|
} catch {
|
|
345
368
|
// String(describing:) surfaces the real error code (see setup()).
|
|
@@ -357,7 +380,7 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
357
380
|
return
|
|
358
381
|
}
|
|
359
382
|
let adm = getAudioDeviceModule()
|
|
360
|
-
adm
|
|
383
|
+
adm?.reset()
|
|
361
384
|
clearOutputRouting()
|
|
362
385
|
// Deactivate directly: the .didDisableAudioEngine sink is async and we cancel it below.
|
|
363
386
|
applyConfigForEngineDisable()
|
|
@@ -603,7 +626,10 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
603
626
|
@objc(getAudioStateLog)
|
|
604
627
|
func getAudioStateLog() -> String {
|
|
605
628
|
let session = AVAudioSession.sharedInstance()
|
|
606
|
-
|
|
629
|
+
|
|
630
|
+
guard let adm = getAudioDeviceModule() else {
|
|
631
|
+
return "No audio device module found"
|
|
632
|
+
}
|
|
607
633
|
|
|
608
634
|
// WebRTC wraps AVAudioSession with RTCAudioSession; log its state as well.
|
|
609
635
|
let rtcSession = RTCAudioSession.sharedInstance()
|
|
@@ -798,7 +824,11 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
798
824
|
stereoRefreshWorkItem?.cancel()
|
|
799
825
|
// Create a new debounced work item
|
|
800
826
|
let workItem = DispatchWorkItem { [weak self] in
|
|
801
|
-
self?.getAudioDeviceModule()
|
|
827
|
+
guard let adm = self?.getAudioDeviceModule() else {
|
|
828
|
+
self?.log("Audio device module is not ready")
|
|
829
|
+
return
|
|
830
|
+
}
|
|
831
|
+
adm.refreshStereoPlayoutState()
|
|
802
832
|
self?.log("Executed debounced refreshStereoPlayoutState")
|
|
803
833
|
}
|
|
804
834
|
stereoRefreshWorkItem = workItem
|
|
@@ -863,12 +893,14 @@ class StreamInCallManager: RCTEventEmitter {
|
|
|
863
893
|
}
|
|
864
894
|
|
|
865
895
|
// MARK: - Helper Methods
|
|
866
|
-
private func getAudioDeviceModule() -> AudioDeviceModule {
|
|
896
|
+
private func getAudioDeviceModule() -> AudioDeviceModule? {
|
|
867
897
|
guard let webrtcModule = moduleRegistry?.module(forName: "WebRTCModule") as? WebRTCModule else {
|
|
868
898
|
fatalError("WebRTCModule is required but not registered with the module registry")
|
|
869
899
|
}
|
|
870
900
|
|
|
871
|
-
|
|
901
|
+
// Follow the live call's ADM; fall back to the default only when no call factory is
|
|
902
|
+
// active (bare-fork, or an in-call-manager op firing outside the join↔leave window).
|
|
903
|
+
return webrtcModule.currentAudioDeviceModuleOrNil()
|
|
872
904
|
}
|
|
873
905
|
|
|
874
906
|
private func getCurrentWindow() -> UIWindow? {
|
|
@@ -51,6 +51,7 @@ void broadcastNotificationCallback(CFNotificationCenterRef center,
|
|
|
51
51
|
bool hasListeners;
|
|
52
52
|
CFNotificationCenterRef _notificationCenter;
|
|
53
53
|
AVAudioPlayer *_busyTonePlayer; // Instance variable
|
|
54
|
+
ScreenAudioCapture *_screenAudioCapture; // Broadcast screen-share audio receiver
|
|
54
55
|
}
|
|
55
56
|
|
|
56
57
|
RCT_EXPORT_MODULE();
|
|
@@ -647,19 +648,25 @@ RCT_EXPORT_METHOD(startScreenShareAudioMixing:(RCTPromiseResolveBlock)resolve
|
|
|
647
648
|
if (apmId && [apmId isKindOfClass:[RTCDefaultAudioProcessingModule class]]) {
|
|
648
649
|
RTCDefaultAudioProcessingModule *apm = (RTCDefaultAudioProcessingModule *)apmId;
|
|
649
650
|
apm.capturePostProcessingDelegate = mixer;
|
|
650
|
-
NSLog(@"[SSAMixer] Set capturePostProcessingDelegate on APM");
|
|
651
651
|
} else {
|
|
652
|
-
NSLog(@"[
|
|
652
|
+
NSLog(@"[SSAudio][Native] WARNING: No RTCDefaultAudioProcessingModule available, screen-share audio mixing will not work");
|
|
653
653
|
}
|
|
654
654
|
|
|
655
655
|
[mixer startMixing];
|
|
656
656
|
|
|
657
|
-
// Wire audio buffer handler on the active capturer → mixer.enqueue
|
|
658
657
|
InAppScreenCapturer *capturer = options.activeInAppScreenCapturer;
|
|
659
658
|
if (capturer) {
|
|
660
659
|
capturer.audioBufferHandler = ^(CMSampleBufferRef sampleBuffer) {
|
|
661
660
|
[mixer enqueue:sampleBuffer];
|
|
662
661
|
};
|
|
662
|
+
} else {
|
|
663
|
+
if (!_screenAudioCapture) {
|
|
664
|
+
_screenAudioCapture = [ScreenAudioCapture new];
|
|
665
|
+
}
|
|
666
|
+
_screenAudioCapture.onAudioBuffer = ^(AVAudioPCMBuffer *pcmBuffer) {
|
|
667
|
+
[mixer enqueuePCM:pcmBuffer];
|
|
668
|
+
};
|
|
669
|
+
[_screenAudioCapture start];
|
|
663
670
|
}
|
|
664
671
|
|
|
665
672
|
resolve(nil);
|
|
@@ -671,12 +678,16 @@ RCT_EXPORT_METHOD(stopScreenShareAudioMixing:(RCTPromiseResolveBlock)resolve
|
|
|
671
678
|
WebRTCModule *webrtcModule = [self.moduleRegistry moduleForName:"WebRTCModule"];
|
|
672
679
|
WebRTCModuleOptions *options = [WebRTCModuleOptions sharedInstance];
|
|
673
680
|
|
|
674
|
-
// Stop feeding audio to the mixer
|
|
675
681
|
InAppScreenCapturer *capturer = options.activeInAppScreenCapturer;
|
|
676
682
|
if (capturer) {
|
|
677
683
|
capturer.audioBufferHandler = nil;
|
|
678
684
|
}
|
|
679
685
|
|
|
686
|
+
if (_screenAudioCapture) {
|
|
687
|
+
_screenAudioCapture.onAudioBuffer = nil;
|
|
688
|
+
[_screenAudioCapture stop];
|
|
689
|
+
}
|
|
690
|
+
|
|
680
691
|
// Stop mixing
|
|
681
692
|
ScreenShareAudioMixer *mixer = webrtcModule.audioDeviceModule.screenShareAudioMixer;
|
|
682
693
|
[mixer stopMixing];
|
|
@@ -686,7 +697,6 @@ RCT_EXPORT_METHOD(stopScreenShareAudioMixing:(RCTPromiseResolveBlock)resolve
|
|
|
686
697
|
if (apmId && [apmId isKindOfClass:[RTCDefaultAudioProcessingModule class]]) {
|
|
687
698
|
RTCDefaultAudioProcessingModule *apm = (RTCDefaultAudioProcessingModule *)apmId;
|
|
688
699
|
apm.capturePostProcessingDelegate = nil;
|
|
689
|
-
NSLog(@"[SSAMixer] Cleared capturePostProcessingDelegate on APM");
|
|
690
700
|
}
|
|
691
701
|
|
|
692
702
|
resolve(nil);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stream-io/video-react-native-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.44.0",
|
|
4
4
|
"description": "Stream Video SDK for React Native",
|
|
5
5
|
"author": "https://getstream.io",
|
|
6
6
|
"homepage": "https://getstream.io/video/docs/react-native/",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"build:expo-plugin": "rimraf expo-config-plugin/dist && tsc --project expo-config-plugin/tsconfig.json",
|
|
17
17
|
"build": "yarn copy-version && bob build && yarn build:expo-plugin",
|
|
18
18
|
"test:expo-plugin": "jest --config=expo-config-plugin/jest.config.js --coverage",
|
|
19
|
-
"test": "yarn copy-version && jest --coverage && yarn test:expo-plugin",
|
|
19
|
+
"test": "yarn copy-version && jest --coverage && yarn test:types && yarn test:expo-plugin",
|
|
20
20
|
"test:types": "yarn copy-version && tsc -p tsconfig.spec.json",
|
|
21
21
|
"copy-version": "echo \"export const version = '$npm_package_version';\" > ./src/version.ts"
|
|
22
22
|
},
|
|
@@ -52,8 +52,8 @@
|
|
|
52
52
|
"!**/.*"
|
|
53
53
|
],
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@stream-io/video-client": "1.
|
|
56
|
-
"@stream-io/video-react-bindings": "1.
|
|
55
|
+
"@stream-io/video-client": "1.58.0",
|
|
56
|
+
"@stream-io/video-react-bindings": "1.20.0",
|
|
57
57
|
"intl-pluralrules": "2.0.1",
|
|
58
58
|
"react-native-url-polyfill": "^3.0.0",
|
|
59
59
|
"rxjs": "~7.8.2",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"@react-native-firebase/messaging": ">=17.5.0",
|
|
66
66
|
"@stream-io/noise-cancellation-react-native": ">=0.1.0",
|
|
67
67
|
"@stream-io/react-native-callingx": ">=0.1.0",
|
|
68
|
-
"@stream-io/react-native-webrtc": "^145.
|
|
68
|
+
"@stream-io/react-native-webrtc": "^145.3.1",
|
|
69
69
|
"@stream-io/video-filters-react-native": ">=0.1.0",
|
|
70
70
|
"expo": ">=47.0.0",
|
|
71
71
|
"expo-notifications": "*",
|
|
@@ -106,37 +106,39 @@
|
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"@babel/core": "^7.29.7",
|
|
109
|
-
"@
|
|
110
|
-
"@
|
|
109
|
+
"@babel/preset-env": "^7.29.7",
|
|
110
|
+
"@babel/preset-typescript": "^7.29.7",
|
|
111
|
+
"@expo/config-plugins": "^57.0.6",
|
|
112
|
+
"@expo/config-types": "^57.0.2",
|
|
111
113
|
"@expo/plist": "^0.7.0",
|
|
112
114
|
"@react-native-community/netinfo": "12.0.1",
|
|
113
|
-
"@react-native-firebase/app": "^24.
|
|
114
|
-
"@react-native-firebase/messaging": "^24.
|
|
115
|
-
"@react-native/babel-preset": "0.
|
|
116
|
-
"@react-native/
|
|
117
|
-
"@
|
|
118
|
-
"@stream-io/react-native
|
|
119
|
-
"@stream-io/react-native-
|
|
115
|
+
"@react-native-firebase/app": "^24.1.1",
|
|
116
|
+
"@react-native-firebase/messaging": "^24.1.1",
|
|
117
|
+
"@react-native/babel-preset": "0.86.2",
|
|
118
|
+
"@react-native/jest-preset": "0.86.2",
|
|
119
|
+
"@react-native/metro-config": "0.86.2",
|
|
120
|
+
"@stream-io/noise-cancellation-react-native": "^0.11.0",
|
|
121
|
+
"@stream-io/react-native-callingx": "^0.10.0",
|
|
122
|
+
"@stream-io/react-native-webrtc": "145.3.1",
|
|
120
123
|
"@stream-io/typescript-config": "^0.0.0",
|
|
121
|
-
"@stream-io/video-filters-react-native": "^0.
|
|
124
|
+
"@stream-io/video-filters-react-native": "^0.16.0",
|
|
122
125
|
"@testing-library/jest-native": "^5.4.3",
|
|
123
126
|
"@testing-library/react-native": "13.3.3",
|
|
124
127
|
"@tsconfig/node18": "^18.2.6",
|
|
125
128
|
"@types/jest": "^29.5.14",
|
|
126
|
-
"@types/node": "^25.9.
|
|
127
|
-
"@types/react": "~19.2.
|
|
129
|
+
"@types/node": "^25.9.5",
|
|
130
|
+
"@types/react": "~19.2.18",
|
|
128
131
|
"@types/react-test-renderer": "^19.1.0",
|
|
129
|
-
"expo": "~
|
|
130
|
-
"expo-
|
|
131
|
-
"expo-notifications": "~56.0.16",
|
|
132
|
+
"expo": "~57.0.9",
|
|
133
|
+
"expo-notifications": "~57.0.8",
|
|
132
134
|
"jest": "^29.7.0",
|
|
133
135
|
"react": "19.2.3",
|
|
134
|
-
"react-native": "0.
|
|
135
|
-
"react-native-builder-bob": "^0.
|
|
136
|
-
"react-native-gesture-handler": "~2.
|
|
137
|
-
"react-native-reanimated": "4.3
|
|
136
|
+
"react-native": "0.86.2",
|
|
137
|
+
"react-native-builder-bob": "^0.43.0",
|
|
138
|
+
"react-native-gesture-handler": "~2.32.0",
|
|
139
|
+
"react-native-reanimated": "4.5.3",
|
|
138
140
|
"react-native-svg": "15.15.5",
|
|
139
|
-
"react-native-worklets": "0.
|
|
141
|
+
"react-native-worklets": "0.10.1",
|
|
140
142
|
"react-test-renderer": "19.2.3",
|
|
141
143
|
"rimraf": "^6.1.3",
|
|
142
144
|
"typescript": "^6.0.3"
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from 'react-native';
|
|
9
9
|
import { useCall } from '@stream-io/video-react-bindings';
|
|
10
10
|
import {
|
|
11
|
-
type
|
|
11
|
+
type SendVideoReactionRequest,
|
|
12
12
|
videoLoggerSystem,
|
|
13
13
|
} from '@stream-io/video-client';
|
|
14
14
|
import { ComponentTestIds } from '../../../../constants/TestIds';
|
|
@@ -82,7 +82,7 @@ export const ReactionsPicker = ({
|
|
|
82
82
|
left: reactionsButtonLayoutRectangle?.x,
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const onClose = (reaction?:
|
|
85
|
+
const onClose = (reaction?: SendVideoReactionRequest) => {
|
|
86
86
|
if (reaction) {
|
|
87
87
|
call?.sendReaction(reaction).catch((e) => {
|
|
88
88
|
const logger = videoLoggerSystem.getLogger('ReactionsPicker');
|
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
} from '@stream-io/video-react-bindings';
|
|
8
8
|
import { Avatar } from '../../utility/Avatar';
|
|
9
9
|
import type { StreamVideoParticipant } from '@stream-io/video-client';
|
|
10
|
-
import type { MediaStream } from '@stream-io/react-native-webrtc';
|
|
11
|
-
import { RTCView } from '@stream-io/react-native-webrtc';
|
|
12
10
|
import { LobbyControls as DefaultLobbyControls } from '../CallControls/LobbyControls';
|
|
11
|
+
import { LobbyCameraPreview } from './LobbyCameraPreview';
|
|
13
12
|
import {
|
|
14
13
|
JoinCallButton as DefaultJoinCallButton,
|
|
15
14
|
type JoinCallButtonProps,
|
|
@@ -65,9 +64,8 @@ export const Lobby = ({
|
|
|
65
64
|
const { useCameraState, useCallSettings } = useCallStateHooks();
|
|
66
65
|
const callSettings = useCallSettings();
|
|
67
66
|
const isVideoEnabledInCall = callSettings?.video.enabled;
|
|
68
|
-
const {
|
|
67
|
+
const { optimisticIsMute: cameraIsMuted } = useCameraState();
|
|
69
68
|
const { t } = useI18n();
|
|
70
|
-
const localVideoStream = mediaStream as unknown as MediaStream | undefined;
|
|
71
69
|
|
|
72
70
|
const connectedUserAsParticipant = useMemo(
|
|
73
71
|
() =>
|
|
@@ -98,13 +96,8 @@ export const Lobby = ({
|
|
|
98
96
|
]}
|
|
99
97
|
>
|
|
100
98
|
<View style={styles.topView} />
|
|
101
|
-
{!cameraIsMuted
|
|
102
|
-
<
|
|
103
|
-
mirror={true}
|
|
104
|
-
streamURL={localVideoStream.toURL()}
|
|
105
|
-
objectFit="cover"
|
|
106
|
-
style={StyleSheet.absoluteFill}
|
|
107
|
-
/>
|
|
99
|
+
{!cameraIsMuted ? (
|
|
100
|
+
<LobbyCameraPreview objectFit="cover" />
|
|
108
101
|
) : (
|
|
109
102
|
<View style={[styles.avatarContainer, lobby.avatarContainer]}>
|
|
110
103
|
<Avatar participant={connectedUserAsParticipant} />
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { Platform, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
permissions,
|
|
5
|
+
RTCCameraPreviewView,
|
|
6
|
+
} from '@stream-io/react-native-webrtc';
|
|
7
|
+
import { useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Props for the {@link LobbyCameraPreview} component.
|
|
11
|
+
*/
|
|
12
|
+
export type LobbyCameraPreviewProps = {
|
|
13
|
+
/**
|
|
14
|
+
* Resembles the CSS style object-fit.
|
|
15
|
+
*
|
|
16
|
+
* @default 'cover'
|
|
17
|
+
*/
|
|
18
|
+
objectFit?: 'contain' | 'cover';
|
|
19
|
+
/**
|
|
20
|
+
* Style applied to the preview view.
|
|
21
|
+
*/
|
|
22
|
+
style?: ViewStyle;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The lobby runs before the call is joined. This component drives the native
|
|
27
|
+
* camera capturer directly via {@link RTCCameraPreviewView} which requires
|
|
28
|
+
* no track/peer connection factory to be created.
|
|
29
|
+
*
|
|
30
|
+
* It is driven by the *intended* camera state (optimistic): the lobby camera
|
|
31
|
+
* toggle only updates `optimisticStatus`; the real WebRTC track is acquired and
|
|
32
|
+
* published at join.
|
|
33
|
+
*/
|
|
34
|
+
export const LobbyCameraPreview = ({
|
|
35
|
+
style,
|
|
36
|
+
objectFit = 'cover',
|
|
37
|
+
}: LobbyCameraPreviewProps) => {
|
|
38
|
+
const { useCameraState, useCallSettings } = useCallStateHooks();
|
|
39
|
+
|
|
40
|
+
const { optimisticIsMute, direction, selectedDevice } = useCameraState();
|
|
41
|
+
const settings = useCallSettings();
|
|
42
|
+
|
|
43
|
+
const wantsCamera = !optimisticIsMute;
|
|
44
|
+
const hasCameraPermission = useEnsureCameraPermission(wantsCamera);
|
|
45
|
+
|
|
46
|
+
const facing = direction === 'back' ? 'back' : 'front';
|
|
47
|
+
|
|
48
|
+
// Capture at the call's target resolution so the running preview capturer matches
|
|
49
|
+
// what the track publishes and can be adopted at join without reconfiguring.
|
|
50
|
+
// Normalize to landscape, matching CameraManager.selectTargetResolution.
|
|
51
|
+
const targetResolution = settings?.video.target_resolution;
|
|
52
|
+
let captureWidth = targetResolution?.width ?? 1280;
|
|
53
|
+
let captureHeight = targetResolution?.height ?? 720;
|
|
54
|
+
if (captureWidth < captureHeight) {
|
|
55
|
+
[captureWidth, captureHeight] = [captureHeight, captureWidth];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<RTCCameraPreviewView
|
|
60
|
+
style={[StyleSheet.absoluteFill, style as any]}
|
|
61
|
+
isActive={wantsCamera && hasCameraPermission}
|
|
62
|
+
facing={facing}
|
|
63
|
+
deviceId={selectedDevice}
|
|
64
|
+
mirror={Platform.OS === 'ios' ? true : facing === 'front'}
|
|
65
|
+
objectFit={objectFit}
|
|
66
|
+
captureWidth={captureWidth}
|
|
67
|
+
captureHeight={captureHeight}
|
|
68
|
+
/>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const useEnsureCameraPermission = (enabled: boolean): boolean => {
|
|
73
|
+
const [granted, setGranted] = useState(false);
|
|
74
|
+
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (!enabled || granted) return;
|
|
77
|
+
|
|
78
|
+
let cancelled = false;
|
|
79
|
+
(async () => {
|
|
80
|
+
try {
|
|
81
|
+
const status = await permissions.query({ name: 'camera' });
|
|
82
|
+
if (status === 'granted') {
|
|
83
|
+
if (!cancelled) setGranted(true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const result = await permissions.request({ name: 'camera' });
|
|
87
|
+
if (!cancelled) setGranted(!!result);
|
|
88
|
+
} catch {
|
|
89
|
+
if (!cancelled) setGranted(false);
|
|
90
|
+
}
|
|
91
|
+
})();
|
|
92
|
+
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
};
|
|
96
|
+
}, [enabled, granted]);
|
|
97
|
+
|
|
98
|
+
return granted;
|
|
99
|
+
};
|
|
@@ -109,8 +109,7 @@ const Background = () => {
|
|
|
109
109
|
const { useCameraState } = useCallStateHooks();
|
|
110
110
|
const { isMute, camera } = useCameraState();
|
|
111
111
|
const localVideoStream = camera.state.mediaStream as unknown as
|
|
112
|
-
|
|
|
113
|
-
| undefined;
|
|
112
|
+
MediaStream | undefined;
|
|
114
113
|
|
|
115
114
|
if (isMute || !localVideoStream) {
|
|
116
115
|
return (
|
|
@@ -25,10 +25,7 @@ import { useFloatingVideoDimensions } from './useFloatingVideoDimensions';
|
|
|
25
25
|
import { type StreamVideoParticipant } from '@stream-io/video-client';
|
|
26
26
|
|
|
27
27
|
export type FloatingParticipantViewAlignment =
|
|
28
|
-
| 'top-left'
|
|
29
|
-
| 'top-right'
|
|
30
|
-
| 'bottom-left'
|
|
31
|
-
| 'bottom-right';
|
|
28
|
+
'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
31
|
* Props to be passed for the LocalVideoView component.
|