@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.
Files changed (98) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/android/src/main/java/com/streamvideo/reactnative/audio/AudioDeviceManager.kt +79 -54
  3. package/android/src/main/java/com/streamvideo/reactnative/audio/utils/AudioManagerUtil.kt +6 -2
  4. package/android/src/main/java/com/streamvideo/reactnative/callmanager/StreamInCallManagerModule.kt +3 -3
  5. package/dist/commonjs/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  6. package/dist/commonjs/components/Participant/FloatingParticipantView/index.js.map +1 -1
  7. package/dist/commonjs/contexts/ThemeContext.js +10 -0
  8. package/dist/commonjs/contexts/ThemeContext.js.map +1 -1
  9. package/dist/commonjs/hooks/index.js +11 -0
  10. package/dist/commonjs/hooks/index.js.map +1 -1
  11. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +73 -0
  12. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  13. package/dist/commonjs/hooks/useAudioDeviceStatus.js +76 -0
  14. package/dist/commonjs/hooks/useAudioDeviceStatus.js.map +1 -0
  15. package/dist/commonjs/hooks/useScreenShareButton.js.map +1 -1
  16. package/dist/commonjs/modules/call-manager/CallManager.js +94 -47
  17. package/dist/commonjs/modules/call-manager/CallManager.js.map +1 -1
  18. package/dist/commonjs/providers/NoiseCancellation/lib.js.map +1 -1
  19. package/dist/commonjs/providers/StreamCall/AudioInterruptionTracer.js.map +1 -1
  20. package/dist/commonjs/utils/internal/callingx/callingx.js +0 -7
  21. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  22. package/dist/commonjs/utils/push/setupCallingExpEvents.js +0 -2
  23. package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -1
  24. package/dist/commonjs/version.js +1 -1
  25. package/dist/module/components/Call/RingingCallContent/OutgoingCall.js.map +1 -1
  26. package/dist/module/components/Participant/FloatingParticipantView/index.js.map +1 -1
  27. package/dist/module/contexts/ThemeContext.js +10 -0
  28. package/dist/module/contexts/ThemeContext.js.map +1 -1
  29. package/dist/module/hooks/index.js +1 -0
  30. package/dist/module/hooks/index.js.map +1 -1
  31. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +73 -0
  32. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  33. package/dist/module/hooks/useAudioDeviceStatus.js +71 -0
  34. package/dist/module/hooks/useAudioDeviceStatus.js.map +1 -0
  35. package/dist/module/hooks/useScreenShareButton.js.map +1 -1
  36. package/dist/module/modules/call-manager/CallManager.js +94 -47
  37. package/dist/module/modules/call-manager/CallManager.js.map +1 -1
  38. package/dist/module/providers/NoiseCancellation/lib.js.map +1 -1
  39. package/dist/module/providers/StreamCall/AudioInterruptionTracer.js.map +1 -1
  40. package/dist/module/utils/internal/callingx/callingx.js +0 -7
  41. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  42. package/dist/module/utils/push/setupCallingExpEvents.js +0 -2
  43. package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -1
  44. package/dist/module/version.js +1 -1
  45. package/dist/typescript/components/Participant/FloatingParticipantView/index.d.ts.map +1 -1
  46. package/dist/typescript/contexts/ThemeContext.d.ts +10 -1
  47. package/dist/typescript/contexts/ThemeContext.d.ts.map +1 -1
  48. package/dist/typescript/hooks/index.d.ts +1 -0
  49. package/dist/typescript/hooks/index.d.ts.map +1 -1
  50. package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
  51. package/dist/typescript/hooks/useAudioDeviceStatus.d.ts +21 -0
  52. package/dist/typescript/hooks/useAudioDeviceStatus.d.ts.map +1 -0
  53. package/dist/typescript/hooks/useScreenShareButton.d.ts +2 -1
  54. package/dist/typescript/hooks/useScreenShareButton.d.ts.map +1 -1
  55. package/dist/typescript/modules/call-manager/CallManager.d.ts +25 -11
  56. package/dist/typescript/modules/call-manager/CallManager.d.ts.map +1 -1
  57. package/dist/typescript/modules/call-manager/types.d.ts +29 -3
  58. package/dist/typescript/modules/call-manager/types.d.ts.map +1 -1
  59. package/dist/typescript/providers/NoiseCancellation/lib.d.ts.map +1 -1
  60. package/dist/typescript/providers/StreamCall/AudioInterruptionTracer.d.ts.map +1 -1
  61. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  62. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  63. package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -1
  64. package/dist/typescript/version.d.ts +1 -1
  65. package/expo-config-plugin/dist/index.js +2 -0
  66. package/expo-config-plugin/dist/withAndroidMessagingService.js +313 -0
  67. package/expo-config-plugin/dist/withIosScreenCapture/addBroadcastExtensionXcodeTarget.js +84 -2
  68. package/expo-config-plugin/dist/withIosScreenCapture/withPlistUpdates.js +3 -6
  69. package/expo-config-plugin/dist/withIosScreenCapture/withTarget.js +6 -4
  70. package/expo-config-plugin/static/AudioUploader.swift +230 -0
  71. package/expo-config-plugin/static/SampleHandler.swift +67 -14
  72. package/ios/StreamInCallManager.m +7 -0
  73. package/ios/StreamInCallManager.swift +258 -15
  74. package/ios/StreamVideoReactNative.m +15 -5
  75. package/package.json +27 -25
  76. package/src/components/Call/RingingCallContent/OutgoingCall.tsx +1 -2
  77. package/src/components/Participant/FloatingParticipantView/index.tsx +1 -4
  78. package/src/contexts/ThemeContext.tsx +10 -1
  79. package/src/hooks/index.ts +1 -0
  80. package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +95 -0
  81. package/src/hooks/useAudioDeviceStatus.ts +90 -0
  82. package/src/hooks/useScreenShareButton.ts +2 -1
  83. package/src/modules/call-manager/CallManager.ts +123 -66
  84. package/src/modules/call-manager/native-module.d.ts +14 -7
  85. package/src/modules/call-manager/types.ts +30 -3
  86. package/src/providers/NoiseCancellation/lib.ts +1 -2
  87. package/src/providers/StreamCall/AudioInterruptionTracer.tsx +1 -2
  88. package/src/utils/StreamVideoRN/types.ts +1 -3
  89. package/src/utils/internal/callingx/callingx.ts +0 -8
  90. package/src/utils/push/setupCallingExpEvents.ts +0 -2
  91. package/src/version.ts +1 -1
  92. package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js +0 -68
  93. package/dist/commonjs/utils/internal/callingx/audioSessionPromise.js.map +0 -1
  94. package/dist/module/utils/internal/callingx/audioSessionPromise.js +0 -63
  95. package/dist/module/utils/internal/callingx/audioSessionPromise.js.map +0 -1
  96. package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts +0 -16
  97. package/dist/typescript/utils/internal/callingx/audioSessionPromise.d.ts.map +0 -1
  98. 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 clientConnection: SocketConnection?
16
- private var uploader: SampleUploader?
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
- let sharedContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: Constants.appGroupIdentifier)
22
- return sharedContainer?.appendingPathComponent("rtc_SSFD").path ?? ""
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
- clientConnection = connection
29
- setupConnection()
43
+ videoClientConnection = connection
44
+ setupVideoConnection()
30
45
 
31
- uploader = SampleUploader(connection: connection)
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
- openConnection()
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
- clientConnection?.close()
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
- uploader?.send(sample: sampleBuffer)
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 setupConnection() {
71
- clientConnection?.didClose = { [weak self] error in
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 openConnection() {
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?.clientConnection?.open() == true else {
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)