@stream-io/video-react-native-sdk 1.41.0 → 1.43.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 +40 -0
- package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +79 -54
- package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt +6 -2
- package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +3 -3
- 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/contexts/ThemeContext.js +10 -0
- package/dist/commonjs/contexts/ThemeContext.js.map +1 -1
- package/dist/commonjs/hooks/index.js +11 -0
- package/dist/commonjs/hooks/index.js.map +1 -1
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +73 -0
- package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/commonjs/hooks/useAudioDeviceStatus.js +76 -0
- package/dist/commonjs/hooks/useAudioDeviceStatus.js.map +1 -0
- package/dist/commonjs/hooks/useScreenShareButton.js.map +1 -1
- package/dist/commonjs/modules/call-manager/CallManager.js +94 -47
- 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 +0 -7
- package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/commonjs/utils/push/setupCallingExpEvents.js +0 -2
- package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -1
- package/dist/commonjs/version.js +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/contexts/ThemeContext.js +10 -0
- package/dist/module/contexts/ThemeContext.js.map +1 -1
- package/dist/module/hooks/index.js +1 -0
- package/dist/module/hooks/index.js.map +1 -1
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +73 -0
- package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
- package/dist/module/hooks/useAudioDeviceStatus.js +71 -0
- package/dist/module/hooks/useAudioDeviceStatus.js.map +1 -0
- package/dist/module/hooks/useScreenShareButton.js.map +1 -1
- package/dist/module/modules/call-manager/CallManager.js +94 -47
- 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 +0 -7
- package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
- package/dist/module/utils/push/setupCallingExpEvents.js +0 -2
- package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -1
- package/dist/module/version.js +1 -1
- package/dist/typescript/components/Participant/FloatingParticipantView/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/index.d.ts +1 -0
- package/dist/typescript/hooks/index.d.ts.map +1 -1
- package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
- package/dist/typescript/hooks/useAudioDeviceStatus.d.ts +21 -0
- package/dist/typescript/hooks/useAudioDeviceStatus.d.ts.map +1 -0
- 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 +25 -11
- package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
- package/dist/typescript/modules/call-manager/types.d.ts +29 -3
- 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.map +1 -1
- package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
- package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -1
- package/dist/typescript/version.d.ts +1 -1
- package/expo-config-plugin/dist/index.js +2 -0
- package/expo-config-plugin/dist/withAndroidMessagingService.js +313 -0
- 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 +7 -0
- package/ios/StreamInCallManager.swift +258 -15
- package/ios/StreamVideoReactNative.m +15 -5
- package/package.json +27 -25
- package/src/components/Call/RingingCallContent/OutgoingCall.tsx +1 -2
- package/src/components/Participant/FloatingParticipantView/index.tsx +1 -4
- package/src/contexts/ThemeContext.tsx +10 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +95 -0
- package/src/hooks/useAudioDeviceStatus.ts +90 -0
- package/src/hooks/useScreenShareButton.ts +2 -1
- package/src/modules/call-manager/CallManager.ts +123 -66
- package/src/modules/call-manager/native-module.d.ts +14 -7
- package/src/modules/call-manager/types.ts +30 -3
- package/src/providers/NoiseCancellation/lib.ts +1 -2
- package/src/providers/StreamCall/AudioInterruptionTracer.tsx +1 -2
- package/src/utils/StreamVideoRN/types.ts +1 -3
- package/src/utils/internal/callingx/callingx.ts +0 -8
- package/src/utils/push/setupCallingExpEvents.ts +0 -2
- package/src/version.ts +1 -1
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js +0 -68
- package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js.map +0 -1
- package/dist/module/utils/internal/callingx/audioSessionPromise.js +0 -63
- package/dist/module/utils/internal/callingx/audioSessionPromise.js.map +0 -1
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts +0 -16
- package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts.map +0 -1
- package/src/utils/internal/callingx/audioSessionPromise.ts +0 -65
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
//
|
|
2
|
+
// AudioUploader.swift
|
|
3
|
+
// Broadcast Extension
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
import AudioToolbox
|
|
7
|
+
import CoreMedia
|
|
8
|
+
import Foundation
|
|
9
|
+
import OSLog
|
|
10
|
+
|
|
11
|
+
private enum AudioConstants {
|
|
12
|
+
static let bufferMaxLength = 10240
|
|
13
|
+
static let magic: UInt32 = 0x5353_4155 // "SSAU"
|
|
14
|
+
static let headerSize = 24
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class AudioUploader {
|
|
18
|
+
|
|
19
|
+
@Atomic private var isReady = false
|
|
20
|
+
private var connection: SocketConnection
|
|
21
|
+
|
|
22
|
+
private var dataToSend: Data?
|
|
23
|
+
private var byteIndex = 0
|
|
24
|
+
|
|
25
|
+
private let serialQueue: DispatchQueue
|
|
26
|
+
|
|
27
|
+
init(connection: SocketConnection) {
|
|
28
|
+
self.connection = connection
|
|
29
|
+
self.serialQueue = DispatchQueue(label: "org.getstream.audioUploader")
|
|
30
|
+
|
|
31
|
+
setupConnection()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@discardableResult func send(sample buffer: CMSampleBuffer) -> Bool {
|
|
35
|
+
// Drop the incoming buffer while one is still in flight. Audio buffers
|
|
36
|
+
// are tiny (a few KB) so this is rare; staying current beats queuing.
|
|
37
|
+
guard isReady else { return false }
|
|
38
|
+
guard let data = AudioUploader.serialize(sample: buffer) else { return false }
|
|
39
|
+
|
|
40
|
+
isReady = false
|
|
41
|
+
serialQueue.async { [weak self] in
|
|
42
|
+
self?.dataToSend = data
|
|
43
|
+
self?.byteIndex = 0
|
|
44
|
+
self?.sendDataChunk()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return true
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private extension AudioUploader {
|
|
52
|
+
|
|
53
|
+
func setupConnection() {
|
|
54
|
+
connection.didOpen = { [weak self] in
|
|
55
|
+
self?.isReady = true
|
|
56
|
+
}
|
|
57
|
+
connection.streamHasSpaceAvailable = { [weak self] in
|
|
58
|
+
self?.serialQueue.async {
|
|
59
|
+
if let success = self?.sendDataChunk() {
|
|
60
|
+
self?.isReady = !success
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@discardableResult func sendDataChunk() -> Bool {
|
|
67
|
+
guard let dataToSend = dataToSend else {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var bytesLeft = dataToSend.count - byteIndex
|
|
72
|
+
var length = bytesLeft > AudioConstants.bufferMaxLength ? AudioConstants.bufferMaxLength : bytesLeft
|
|
73
|
+
|
|
74
|
+
length = dataToSend[byteIndex..<(byteIndex + length)].withUnsafeBytes {
|
|
75
|
+
guard let ptr = $0.bindMemory(to: UInt8.self).baseAddress else {
|
|
76
|
+
return 0
|
|
77
|
+
}
|
|
78
|
+
return connection.writeToStream(buffer: ptr, maxLength: length)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if length > 0 {
|
|
82
|
+
byteIndex += length
|
|
83
|
+
bytesLeft -= length
|
|
84
|
+
|
|
85
|
+
if bytesLeft == 0 {
|
|
86
|
+
self.dataToSend = nil
|
|
87
|
+
byteIndex = 0
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
os_log(.error, log: broadcastLogger, "audio writeBufferToStream failure")
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// Extracts interleaved PCM + format from the sample buffer and frames it.
|
|
97
|
+
static func serialize(sample buffer: CMSampleBuffer) -> Data? {
|
|
98
|
+
guard let formatDescription = CMSampleBufferGetFormatDescription(buffer) else {
|
|
99
|
+
return nil
|
|
100
|
+
}
|
|
101
|
+
guard let asbdPtr = CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) else {
|
|
102
|
+
return nil
|
|
103
|
+
}
|
|
104
|
+
let asbd = asbdPtr.pointee
|
|
105
|
+
|
|
106
|
+
let flags = asbd.mFormatFlags
|
|
107
|
+
let isFloat = (flags & kAudioFormatFlagIsFloat) != 0
|
|
108
|
+
let isSignedInt = (flags & kAudioFormatFlagIsSignedInteger) != 0
|
|
109
|
+
let isNonInterleaved = (flags & kAudioFormatFlagIsNonInterleaved) != 0
|
|
110
|
+
let isBigEndian = (flags & kAudioFormatFlagIsBigEndian) != 0
|
|
111
|
+
let bits = Int(asbd.mBitsPerChannel)
|
|
112
|
+
let channels = Int(asbd.mChannelsPerFrame)
|
|
113
|
+
|
|
114
|
+
guard asbd.mFormatID == kAudioFormatLinearPCM else {
|
|
115
|
+
return nil
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Only the two formats the app-side reconstruction understands.
|
|
119
|
+
let supported = (isFloat && bits == 32) || (isSignedInt && bits == 16)
|
|
120
|
+
guard supported, channels > 0 else {
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
guard var pcm = interleavedPCM(from: buffer, channels: channels, bitsPerChannel: bits, isNonInterleaved: isNonInterleaved) else {
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if isBigEndian, bits > 8 {
|
|
129
|
+
pcm = byteSwapped(pcm, bytesPerSample: bits / 8)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Build a length-prefixed binary frame (24-byte LE header + PCM body).
|
|
133
|
+
// We always emit interleaved PCM, so isInterleaved is 1.
|
|
134
|
+
var frame = Data(capacity: AudioConstants.headerSize + pcm.count)
|
|
135
|
+
appendLE(&frame, AudioConstants.magic) // 0: magic
|
|
136
|
+
appendLE(&frame, asbd.mSampleRate.bitPattern) // 4: sampleRate (Float64 bits)
|
|
137
|
+
appendLE(&frame, UInt16(channels)) // 12: channels
|
|
138
|
+
appendLE(&frame, UInt16(bits)) // 14: bits
|
|
139
|
+
frame.append(isFloat ? 1 : 0) // 16: isFloat
|
|
140
|
+
frame.append(1) // 17: isInterleaved
|
|
141
|
+
frame.append(0) // 18: reserved
|
|
142
|
+
frame.append(0) // 19: reserved
|
|
143
|
+
appendLE(&frame, UInt32(pcm.count)) // 20: payloadLength
|
|
144
|
+
frame.append(pcm) // 24: PCM body
|
|
145
|
+
return frame
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Swaps byte order of each PCM sample (for 16- or 32-bit samples).
|
|
149
|
+
static func byteSwapped(_ data: Data, bytesPerSample: Int) -> Data {
|
|
150
|
+
guard bytesPerSample == 2 || bytesPerSample == 4 else { return data }
|
|
151
|
+
var out = data
|
|
152
|
+
out.withUnsafeMutableBytes { raw in
|
|
153
|
+
guard let base = raw.baseAddress else { return }
|
|
154
|
+
let count = raw.count
|
|
155
|
+
if bytesPerSample == 2 {
|
|
156
|
+
let p = base.assumingMemoryBound(to: UInt16.self)
|
|
157
|
+
for i in 0..<(count / 2) { p[i] = p[i].byteSwapped }
|
|
158
|
+
} else {
|
|
159
|
+
let p = base.assumingMemoryBound(to: UInt32.self)
|
|
160
|
+
for i in 0..<(count / 4) { p[i] = p[i].byteSwapped }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return out
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// MARK: - Little-endian encoders
|
|
167
|
+
|
|
168
|
+
static func appendLE<T: FixedWidthInteger>(_ data: inout Data, _ value: T) {
|
|
169
|
+
var v = value.littleEndian
|
|
170
|
+
withUnsafeBytes(of: &v) { data.append(contentsOf: $0) }
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
static func interleavedPCM(from buffer: CMSampleBuffer, channels: Int, bitsPerChannel: Int, isNonInterleaved: Bool) -> Data? {
|
|
174
|
+
if !isNonInterleaved {
|
|
175
|
+
// Interleaved: the sample buffer's data buffer is already contiguous.
|
|
176
|
+
guard let blockBuffer = CMSampleBufferGetDataBuffer(buffer) else {
|
|
177
|
+
return nil
|
|
178
|
+
}
|
|
179
|
+
var length = 0
|
|
180
|
+
var dataPointer: UnsafeMutablePointer<Int8>?
|
|
181
|
+
let st = CMBlockBufferGetDataPointer(blockBuffer, atOffset: 0, lengthAtOffsetOut: nil, totalLengthOut: &length, dataPointerOut: &dataPointer)
|
|
182
|
+
guard st == kCMBlockBufferNoErr, let dataPointer = dataPointer, length > 0 else {
|
|
183
|
+
return nil
|
|
184
|
+
}
|
|
185
|
+
return Data(bytes: dataPointer, count: length)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Non-interleaved (planar): use an AudioBufferList to reach each plane.
|
|
189
|
+
var blockBuffer: CMBlockBuffer?
|
|
190
|
+
let maxBuffers = max(channels, 1)
|
|
191
|
+
let buffers = AudioBufferList.allocate(maximumBuffers: maxBuffers)
|
|
192
|
+
defer { free(buffers.unsafeMutablePointer) }
|
|
193
|
+
|
|
194
|
+
let status = CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(
|
|
195
|
+
buffer,
|
|
196
|
+
bufferListSizeNeededOut: nil,
|
|
197
|
+
bufferListOut: buffers.unsafeMutablePointer,
|
|
198
|
+
bufferListSize: AudioBufferList.sizeInBytes(maximumBuffers: maxBuffers),
|
|
199
|
+
blockBufferAllocator: nil,
|
|
200
|
+
blockBufferMemoryAllocator: nil,
|
|
201
|
+
flags: kCMSampleBufferFlag_AudioBufferList_Assure16ByteAlignment,
|
|
202
|
+
blockBufferOut: &blockBuffer
|
|
203
|
+
)
|
|
204
|
+
guard status == noErr else {
|
|
205
|
+
return nil
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
let bytesPerSample = bitsPerChannel / 8
|
|
209
|
+
guard bytesPerSample > 0, buffers.count == channels else {
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let minPlaneBytes = buffers.reduce(Int.max) { min($0, Int($1.mDataByteSize)) }
|
|
214
|
+
let frameCount = minPlaneBytes / bytesPerSample
|
|
215
|
+
guard frameCount > 0 else { return nil }
|
|
216
|
+
var output = Data(count: frameCount * channels * bytesPerSample)
|
|
217
|
+
output.withUnsafeMutableBytes { dstRaw in
|
|
218
|
+
guard let dst = dstRaw.baseAddress else { return }
|
|
219
|
+
for ch in 0..<channels {
|
|
220
|
+
guard let src = buffers[ch].mData else { continue }
|
|
221
|
+
for frame in 0..<frameCount {
|
|
222
|
+
let srcOffset = frame * bytesPerSample
|
|
223
|
+
let dstOffset = (frame * channels + ch) * bytesPerSample
|
|
224
|
+
memcpy(dst + dstOffset, src + srcOffset, bytesPerSample)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return output
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -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
|
}
|
|
@@ -17,6 +17,13 @@ RCT_EXTERN_METHOD(stop)
|
|
|
17
17
|
|
|
18
18
|
RCT_EXTERN_METHOD(showAudioRoutePicker)
|
|
19
19
|
|
|
20
|
+
RCT_EXTERN_METHOD(getAudioDeviceStatus:(RCTPromiseResolveBlock)resolve
|
|
21
|
+
reject:(RCTPromiseRejectBlock)reject)
|
|
22
|
+
|
|
23
|
+
RCT_EXTERN_METHOD(chooseAudioDeviceEndpoint:(NSString *)id)
|
|
24
|
+
|
|
25
|
+
RCT_EXTERN_METHOD(reapplyAudioRoute)
|
|
26
|
+
|
|
20
27
|
RCT_EXTERN_METHOD(setForceSpeakerphoneOn:(BOOL)enable)
|
|
21
28
|
|
|
22
29
|
RCT_EXTERN_METHOD(setMicrophoneMute:(BOOL)enable)
|