diff --git a/ios-broadcast-upload-extension/Classes/BBBSampleHandler.swift b/ios-broadcast-upload-extension/Classes/BBBSampleHandler.swift index 9a5d97d..09c0155 100644 --- a/ios-broadcast-upload-extension/Classes/BBBSampleHandler.swift +++ b/ios-broadcast-upload-extension/Classes/BBBSampleHandler.swift @@ -14,6 +14,7 @@ open class BBBSampleHandler : RPBroadcastSampleHandler { private var appGroupName:String = ""; private var createOfferCallObserver:NSKeyValueObservation?; private var setRemoteSDPCallObserver:NSKeyValueObservation?; + private var addScreenShareRemoteIceCandidateObserver:NSKeyValueObservation?; private var screenBroadcaster:ScreenBroadcaster?; open func setAppGroupName(appGroupName:String) { @@ -57,7 +58,8 @@ open class BBBSampleHandler : RPBroadcastSampleHandler { logger.info("Configuring observer for setRemoteSDP") self.setRemoteSDPCallObserver = userDefaults.observe(\.setScreenShareRemoteSDP, options: [.new]) { (defaults, change) in let payload:String = (change.newValue!); - self.logger.info("Observer detected a setScreenShareRemoteSDP request with payload \(payload)") + // self.logger.info("Observer detected a setScreenShareRemoteSDP request with payload \(payload)") + self.logger.info("Observer detected a setScreenShareRemoteSDP request") let payloadData = payload.data(using: .utf8)! let decodedPayload = (try? JSONDecoder().decode([String: String].self, from: payloadData))! let sdp = decodedPayload["sdp"] @@ -73,6 +75,29 @@ open class BBBSampleHandler : RPBroadcastSampleHandler { } } } + + logger.info("Configuring observer for addScreenShareRemoteIceCandidate") + self.addScreenShareRemoteIceCandidateObserver = userDefaults.observe(\.addScreenShareRemoteIceCandidate, options: [.new]) { (defaults, change) in + let payload:String = (change.newValue!); + // self.logger.info("Observer detected a addScreenShareRemoteIceCandidate request with payload \(payload)") + self.logger.info("Observer detected a addScreenShareRemoteIceCandidate request") + let payloadData = payload.data(using: .utf8)! + let decodedPayload = (try? JSONDecoder().decode([String: String].self, from: payloadData))! + let candidateAsString = decodedPayload["candidate"]! + let candidateAsData = candidateAsString.data(using: .utf8)! + let candidate = (try? JSONDecoder().decode(IceCandidate.self, from: candidateAsData)) + + Task.init { + let remoteCandidateAdded = await self.screenBroadcaster!.addRemoteCandidate(remoteCandidate: candidate!) + + if(remoteCandidateAdded){ + self.logger.info("Remote candidate added!") + BBBSharedData + .getUserDefaults(appGroupName: self.appGroupName) + .set(BBBSharedData.generatePayload(), forKey: BBBSharedData.SharedData.addScreenShareRemoteIceCandidateCompleted) + } + } + } } open override func broadcastPaused() { diff --git a/ios-broadcast-upload-extension/Classes/ScreenBroadcaster.swift b/ios-broadcast-upload-extension/Classes/ScreenBroadcaster.swift index 3816f49..d1f2555 100644 --- a/ios-broadcast-upload-extension/Classes/ScreenBroadcaster.swift +++ b/ios-broadcast-upload-extension/Classes/ScreenBroadcaster.swift @@ -44,6 +44,17 @@ open class ScreenBroadcaster { return false } } + + public func addRemoteCandidate(remoteCandidate:IceCandidate) async -> Bool { + do { + try await self.webRTCClient.setRemoteCandidate(remoteIceCandidate: remoteCandidate) + return true + } + catch { + return false + } + } + } extension ScreenBroadcaster: WebRTCClientDelegate { diff --git a/ios-common/Classes/BBBSharedData.swift b/ios-common/Classes/BBBSharedData.swift index 29aa34d..93a3ce5 100644 --- a/ios-common/Classes/BBBSharedData.swift +++ b/ios-common/Classes/BBBSharedData.swift @@ -25,9 +25,13 @@ open class BBBSharedData { public static let setScreenShareRemoteSDP = "setScreenShareRemoteSDP" // UI APP -> Broadcaster public static let setScreenShareRemoteSDPCompleted = "setScreenShareRemoteSDPCompleted" // Broadcaster -> UI APP + public static let addScreenShareRemoteIceCandidate = "addScreenShareRemoteIceCandidate" // UI APP -> Broadcaster + public static let addScreenShareRemoteIceCandidateCompleted = "addScreenShareRemoteIceCandidateCompleted" // Broadcaster -> UI APP + public static let onScreenShareLocalIceCandidate = "onScreenShareLocalIceCandidate" // Broadcaster -> UI APP public static let onScreenShareSignalingStateChange = "onScreenShareSignalingStateChange" // Broadcaster -> UI APP + } // Get reference to userDefaults object (that's actually the object used to share information among UI APP and the BroadcastUploadExtension APP) diff --git a/ios-common/Classes/UserDefaults.swift b/ios-common/Classes/UserDefaults.swift index a1c8d03..3b63375 100644 --- a/ios-common/Classes/UserDefaults.swift +++ b/ios-common/Classes/UserDefaults.swift @@ -45,6 +45,16 @@ extension UserDefaults { return string(forKey: BBBSharedData.SharedData.setScreenShareRemoteSDPCompleted) ?? "" } + // UI APP -> Broadcaster + @objc open dynamic var addScreenShareRemoteIceCandidate: String { + return string(forKey: BBBSharedData.SharedData.addScreenShareRemoteIceCandidate) ?? "" + } + + // Broadcaster -> UI APP + @objc open dynamic var addScreenShareRemoteIceCandidateCompleted: String { + return string(forKey: BBBSharedData.SharedData.addScreenShareRemoteIceCandidateCompleted) ?? "" + } + // Broadcaster -> UI APP @objc open dynamic var onScreenShareLocalIceCandidate: String { return string(forKey: BBBSharedData.SharedData.onScreenShareLocalIceCandidate) ?? "" diff --git a/ios-common/Classes/WebRTCClient.swift b/ios-common/Classes/WebRTCClient.swift index 2f69052..96a12aa 100644 --- a/ios-common/Classes/WebRTCClient.swift +++ b/ios-common/Classes/WebRTCClient.swift @@ -66,7 +66,7 @@ open class WebRTCClient: NSObject { self.peerConnection = peerConnection super.init() - // createMediaSenders() + createMediaSenders() // configureAudioSession() self.peerConnection.delegate = self } @@ -85,6 +85,10 @@ open class WebRTCClient: NSObject { try await self.peerConnection.setRemoteDescription(rtcSessionDescription) } + public func setRemoteCandidate(remoteIceCandidate: IceCandidate) async throws { + let rtcRemoteCandidate = RTCIceCandidate(sdp: remoteIceCandidate.candidate, sdpMLineIndex: remoteIceCandidate.sdpMLineIndex, sdpMid: remoteIceCandidate.sdpMid) + try await self.peerConnection.add(rtcRemoteCandidate) + } func set(remoteCandidate: RTCIceCandidate, completion: @escaping (Error?) -> ()) { self.peerConnection.add(remoteCandidate, completionHandler: completion) @@ -109,18 +113,18 @@ open class WebRTCClient: NSObject { self.rtcAudioSession.unlockForConfiguration() }*/ - /*private func createMediaSenders() { + private func createMediaSenders() { let streamId = "stream" // Audio - let audioTrack = self.createAudioTrack() - self.peerConnection.add(audioTrack, streamIds: [streamId]) + // let audioTrack = self.createAudioTrack() + // self.peerConnection.add(audioTrack, streamIds: [streamId]) // Video let videoTrack = self.createVideoTrack() self.localVideoTrack = videoTrack self.peerConnection.add(videoTrack, streamIds: [streamId]) - }*/ + } /*private func createAudioTrack() -> RTCAudioTrack { let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil) @@ -129,14 +133,14 @@ open class WebRTCClient: NSObject { return audioTrack }*/ - /*private func createVideoTrack() -> RTCVideoTrack { + private func createVideoTrack() -> RTCVideoTrack { videoSource = WebRTCClient.factory.videoSource(forScreenCast: true) videoCapturer = RTCVideoCapturer(delegate: videoSource!) videoSource!.adaptOutputFormat(toWidth: 600, height: 800, fps: 15) let videoTrack = WebRTCClient.factory.videoTrack(with: videoSource!, trackId: "video0") videoTrack.isEnabled = true return videoTrack - }*/ + } } // MARK: RTCPeerConnectionDelegate Methods diff --git a/ios/NativeOnly/BigBlueButtonSDK.swift b/ios/NativeOnly/BigBlueButtonSDK.swift index fa47745..13f6088 100644 --- a/ios/NativeOnly/BigBlueButtonSDK.swift +++ b/ios/NativeOnly/BigBlueButtonSDK.swift @@ -20,6 +20,7 @@ open class BigBlueButtonSDK: NSObject { private static var observer3: NSKeyValueObservation? private static var observer4: NSKeyValueObservation? private static var observer5: NSKeyValueObservation? + private static var observer6: NSKeyValueObservation? public static func initialize(broadcastExtensionBundleId:String, appGroupName:String) { self.broadcastExtensionBundleId = broadcastExtensionBundleId @@ -77,6 +78,12 @@ open class BigBlueButtonSDK: NSObject { ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onScreenShareSignalingStateChange.rawValue, body: newState) } + + //addScreenShareRemoteIceCandidateCompleted + observer6 = userDefaults?.observe(\.addScreenShareRemoteIceCandidateCompleted, options: [.new]) { (defaults, change) in + logger.info("Detected a change in userDefaults for key addScreenShareRemoteIceCandidateCompleted") + ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onAddScreenShareRemoteIceCandidateCompleted.rawValue, body: nil) + } } public static func getBroadcastExtensionBundleId() -> String { diff --git a/ios/ReactExported/ReactNativeEventEmitter.swift b/ios/ReactExported/ReactNativeEventEmitter.swift index 17942cf..11cca17 100644 --- a/ios/ReactExported/ReactNativeEventEmitter.swift +++ b/ios/ReactExported/ReactNativeEventEmitter.swift @@ -22,6 +22,7 @@ open class ReactNativeEventEmitter: RCTEventEmitter { case onSetScreenShareRemoteSDPCompleted = "onSetScreenShareRemoteSDPCompleted" case onScreenShareLocalIceCandidate = "onScreenShareLocalIceCandidate" case onScreenShareSignalingStateChange = "onScreenShareSignalingStateChange" + case onAddScreenShareRemoteIceCandidateCompleted = "onAddScreenShareRemoteIceCandidateCompleted" } override init() { diff --git a/ios/ReactExported/ScreenShareServiceManager.m b/ios/ReactExported/ScreenShareServiceManager.m index b98c1b8..79f041e 100644 --- a/ios/ReactExported/ScreenShareServiceManager.m +++ b/ios/ReactExported/ScreenShareServiceManager.m @@ -12,4 +12,5 @@ RCT_EXTERN_METHOD(initializeScreenShare) RCT_EXTERN_METHOD(createScreenShareOffer) RCT_EXTERN_METHOD(setScreenShareRemoteSDP: (NSString *)remoteSDP) +RCT_EXTERN_METHOD(addScreenShareRemoteIceCandidate: (NSString *)remoteCandidate) @end diff --git a/ios/ReactExported/ScreenShareServiceManager.swift b/ios/ReactExported/ScreenShareServiceManager.swift index 5406527..ec12fdd 100644 --- a/ios/ReactExported/ScreenShareServiceManager.swift +++ b/ios/ReactExported/ScreenShareServiceManager.swift @@ -52,4 +52,19 @@ class ScreenShareServiceManager: NSObject { ]), forKey: BBBSharedData.SharedData.setScreenShareRemoteSDP) } + + + @objc func addScreenShareRemoteIceCandidate(_ remoteCandidate:String) -> Void { + logger.info("addScreenShareRemoteIceCandidate call arrived on swift: \(remoteCandidate)") + // Send request of "add remote ICE candidate" to broadcast upload extension + // TIP - the handling of this method response is done in observer6 of BigBlueButtonSDK class + logger.info("addScreenShareRemoteIceCandidate - persisting information on UserDefaults") + BBBSharedData + .getUserDefaults(appGroupName: BigBlueButtonSDK.getAppGroupName()) + .set(BBBSharedData.generatePayload(properties: [ + "candidate": remoteCandidate + ]), forKey: BBBSharedData.SharedData.addScreenShareRemoteIceCandidate) + + } + } diff --git a/src/methods/addScreenShareRemoteIceCandidate.tsx b/src/methods/addScreenShareRemoteIceCandidate.tsx new file mode 100644 index 0000000..cdb9a6a --- /dev/null +++ b/src/methods/addScreenShareRemoteIceCandidate.tsx @@ -0,0 +1,34 @@ +import { addScreenShareRemoteIceCandidate as nativeAddScreenShareRemoteIceCandidate } from '../native-components/BBBN_ScreenShareService'; +import nativeEmitter from '../native-messaging/emitter'; + +// Reference to the resolver of last call +let resolve = (value: unknown) => { + console.log( + `default resolve function called, this should never happen: ${value}` + ); +}; + +// Resolve promise when SDP offer is available +nativeEmitter.addListener('onAddScreenShareRemoteIceCandidateCompleted', () => { + resolve(undefined); +}); + +// Entry point of this method +function addScreenShareRemoteIceCandidate(remoteCandidateJson: string) { + return new Promise((res, rej) => { + // store the resolver for later call (when event is received) + resolve = res; + + try { + console.log( + `>nativeAddScreenShareRemoteIceCandidate ${remoteCandidateJson}` + ); + // call native swift method that triggers the broadcast popup + nativeAddScreenShareRemoteIceCandidate(remoteCandidateJson); + } catch (e) { + rej(`Call to nativeAddScreenShareRemoteIceCandidate failed`); + } + }); +} + +export default addScreenShareRemoteIceCandidate; diff --git a/src/native-components/BBBN_ScreenShareService.tsx b/src/native-components/BBBN_ScreenShareService.tsx index 62fd8fd..41a372a 100644 --- a/src/native-components/BBBN_ScreenShareService.tsx +++ b/src/native-components/BBBN_ScreenShareService.tsx @@ -13,3 +13,7 @@ export function createScreenShareOffer() { export function setScreenShareRemoteSDP(remoteSDP: string) { ScreenShareService.setScreenShareRemoteSDP(remoteSDP); } + +export function addScreenShareRemoteIceCandidate(remoteCandidateJson: string) { + ScreenShareService.addScreenShareRemoteIceCandidate(remoteCandidateJson); +} diff --git a/src/webview/message-handler.tsx b/src/webview/message-handler.tsx index 63de7d0..a294d32 100644 --- a/src/webview/message-handler.tsx +++ b/src/webview/message-handler.tsx @@ -3,6 +3,7 @@ import type { WebView, WebViewMessageEvent } from 'react-native-webview'; import initializeScreenShare from '../methods/initializeScreenShare'; import createScreenShareOffer from '../methods/createScreenShareOffer'; import setScreenShareRemoteSDP from '../methods/setScreenShareRemoteSDP'; +import addScreenShareRemoteIceCandidate from '../methods/addScreenShareRemoteIceCandidate'; function observePromiseResult( webViewRef: MutableRefObject, @@ -47,6 +48,11 @@ export function handleWebviewMessage( case 'setRemoteDescription': promise = setScreenShareRemoteSDP(data?.arguments[0].sdp); break; + case 'addRemoteIceCandidate': + promise = addScreenShareRemoteIceCandidate( + JSON.stringify(data?.arguments[0]) + ); + break; default: throw `Unknown method ${data?.method}`; }