diff --git a/common/ios/Classes/BBBSampleHandler.swift b/common/ios/Classes/BBBSampleHandler.swift index c4e3d71..8475836 100644 --- a/common/ios/Classes/BBBSampleHandler.swift +++ b/common/ios/Classes/BBBSampleHandler.swift @@ -11,18 +11,35 @@ open class BBBSampleHandler : RPBroadcastSampleHandler { // Logger (these messages are displayed in the console application) private var logger = os.Logger(subsystem: "BigBlueButtonMobileSDK", category: "BBBSampleHandler") private var appGroupName:String = ""; + private var observer:NSKeyValueObservation?; open func setAppGroupName(appGroupName:String) { - self.appGroupName = appGroupName; + logger.info("Received appGroupName: \(appGroupName)") + self.appGroupName = appGroupName } + // Called by IOS when the user authorized to start the broadcast open override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) { logger.info("ReplayKit2 event - broadcastStarted") - logger.info("ReplayKit2 event - broadcastStarted - persisting information on UserDefaults") - BBBSharedData + // Object used to share data + let userDefaults = BBBSharedData .getUserDefaults(appGroupName: self.appGroupName) - .set(BBBSharedData.generatePayload(), forKey: BBBSharedData.SharedData.broadcastStarted) + + // Notify the UI app that the broadcast has been started + logger.info("ReplayKit2 event - broadcastStarted - persisting information on UserDefaults") + userDefaults.set(BBBSharedData.generatePayload(), forKey: BBBSharedData.SharedData.broadcastStarted) + + // Listen for createOffer requests from the UI APP + logger.info("Configuring observer") + self.observer = userDefaults.observe(\.createScreenShareOffer, options: [.new]) { (defaults, change) in + self.logger.info("Observer detected a createScreenShareOffer request!") + BBBSharedData + .getUserDefaults(appGroupName: self.appGroupName) + .set(BBBSharedData.generatePayload(properties: [ + "sdp": "this is SDP from extension" + ]), forKey: BBBSharedData.SharedData.screenShareOfferCreated) + } } open override func broadcastPaused() { diff --git a/common/ios/Classes/BBBSharedData.swift b/common/ios/Classes/BBBSharedData.swift index f5f2235..eb95d4a 100644 --- a/common/ios/Classes/BBBSharedData.swift +++ b/common/ios/Classes/BBBSharedData.swift @@ -14,12 +14,15 @@ open class BBBSharedData { private static var userDefaultsGroup:String? public enum SharedData { - public static let broadcastStarted = "broadcastStarted" - public static let broadcastPaused = "broadcastPaused" - public static let broadcastResumed = "broadcastResumed" - public static let broadcastFinished = "broadcastFinished" + public static let broadcastStarted = "broadcastStarted" // Broadcaster -> UI APP + public static let broadcastPaused = "broadcastPaused" // Broadcaster -> UI APP + public static let broadcastResumed = "broadcastResumed" // Broadcaster -> UI APP + public static let broadcastFinished = "broadcastFinished" // Broadcaster -> UI APP + public static let createScreenShareOffer = "createScreenShareOffer" // UI APP -> Broadcaster + public static let screenShareOfferCreated = "screenShareOfferCreated" // Broadcaster -> UI APP } + // Get reference to userDefaults object (that's actually the object used to share information among UI APP and the BroadcastUploadExtension APP) public static func getUserDefaults(appGroupName:String) -> UserDefaults { if(userDefaults == nil || userDefaultsGroup == nil || userDefaultsGroup != appGroupName) { logger.info("getUserDefaults \(appGroupName) -> Created") @@ -33,10 +36,22 @@ open class BBBSharedData { } // Generates a unique payload - public static func generatePayload() -> String { - // TODO - replace by UUID + public static func generatePayload(properties:Dictionary = [:]) -> String { let now=String(DateFormatter.localizedString(from: Date(), dateStyle: .medium, timeStyle: .short)); - return "{\"timestamp\": \(now)}"; + var payload = properties; + payload["uuid"] = UUID().uuidString; + payload["timestamp"] = now; + + let encoder = JSONEncoder() + if let jsonData = try? encoder.encode(payload) { + if let jsonString = String(data: jsonData, encoding: .utf8) { + print("JSON = \(jsonString)") + return jsonString + } + } + + logger.error("JSON encoder error, returning empty object") + return "{}"; } } diff --git a/common/ios/Classes/UserDefaults.swift b/common/ios/Classes/UserDefaults.swift index 22d05a3..f89dd5f 100644 --- a/common/ios/Classes/UserDefaults.swift +++ b/common/ios/Classes/UserDefaults.swift @@ -5,21 +5,34 @@ // extension UserDefaults { - + // Broadcaster -> UI APP @objc open dynamic var broadcastStarted: String { return string(forKey: BBBSharedData.SharedData.broadcastStarted) ?? "" } + // Broadcaster -> UI APP @objc open dynamic var broadcastPaused: String { return string(forKey: BBBSharedData.SharedData.broadcastPaused) ?? "" } + // Broadcaster -> UI APP @objc open dynamic var broadcastResumed: String { return string(forKey: BBBSharedData.SharedData.broadcastResumed) ?? "" } + // Broadcaster -> UI APP @objc open dynamic var broadcastFinished: String { return string(forKey: BBBSharedData.SharedData.broadcastFinished) ?? "" } + + // UI APP -> Broadcaster + @objc open dynamic var createScreenShareOffer: String { + return string(forKey: BBBSharedData.SharedData.createScreenShareOffer) ?? "" + } + + // Broadcaster -> UI APP + @objc open dynamic var screenShareOfferCreated: String { + return string(forKey: BBBSharedData.SharedData.createScreenShareOffer) ?? "" + } } diff --git a/ios/NativeOnly/BigBlueButtonSDK.swift b/ios/NativeOnly/BigBlueButtonSDK.swift index ae693f4..c8c8c33 100644 --- a/ios/NativeOnly/BigBlueButtonSDK.swift +++ b/ios/NativeOnly/BigBlueButtonSDK.swift @@ -15,7 +15,8 @@ open class BigBlueButtonSDK: NSObject { private static var broadcastExtensionBundleId = "" private static var appGroupName = "" private static var userDefaults:UserDefaults? - private static var observer: NSKeyValueObservation? + private static var observer1: NSKeyValueObservation? + private static var observer2: NSKeyValueObservation? public static func initialize(broadcastExtensionBundleId:String, appGroupName:String) { self.broadcastExtensionBundleId = broadcastExtensionBundleId @@ -26,10 +27,22 @@ open class BigBlueButtonSDK: NSObject { // Observe keys modified by BroadcastUploadExtension and emit this event to react native //broadcastStarted - observer = userDefaults?.observe(\.broadcastStarted, options: [.new]) { (defaults, change) in + observer1 = userDefaults?.observe(\.broadcastStarted, options: [.new]) { (defaults, change) in + logger.info("Detected a change in userDefaults for key broadcastStarted") ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onBroadcastStarted.rawValue, body: nil) } + //screenShareOfferCreated + observer2 = userDefaults?.observe(\.screenShareOfferCreated, options: [.new]) { (defaults, change) in + let payload:String = (change.newValue!); + logger.info("Detected a change in userDefaults for key screenShareOfferCreated \(payload)") + let payloadData = payload.data(using: .utf8)! + + let decodedPayload = (try? JSONDecoder().decode([String: String].self, from: payloadData))! + let sdp = decodedPayload["sdp"] + + ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onScreenShareOfferCreated.rawValue, body: sdp) + } } @@ -37,8 +50,13 @@ open class BigBlueButtonSDK: NSObject { return self.broadcastExtensionBundleId; } + public static func getAppGroupName() -> String { + return self.appGroupName; + } + public static func deinitialize () { - observer?.invalidate() + observer1?.invalidate() + observer2?.invalidate() } } diff --git a/ios/ReactExported/ReactNativeEventEmitter.swift b/ios/ReactExported/ReactNativeEventEmitter.swift index 19a11cc..7084e34 100644 --- a/ios/ReactExported/ReactNativeEventEmitter.swift +++ b/ios/ReactExported/ReactNativeEventEmitter.swift @@ -18,6 +18,7 @@ open class ReactNativeEventEmitter: RCTEventEmitter { case onBroadcastPaused = "onBroadcastPaused" case onBroadcastResumed = "onBroadcastResumed" case onBroadcastFinished = "onBroadcastFinished" + case onScreenShareOfferCreated = "onScreenShareOfferCreated" } override init() { diff --git a/ios/ReactExported/ScreenShareServiceManager.m b/ios/ReactExported/ScreenShareServiceManager.m index 92d50be..d005e89 100644 --- a/ios/ReactExported/ScreenShareServiceManager.m +++ b/ios/ReactExported/ScreenShareServiceManager.m @@ -10,4 +10,5 @@ @interface RCT_EXTERN_REMAP_MODULE(BBBN_ScreenShareService, ScreenShareServiceManager, NSObject) RCT_EXTERN_METHOD(initializeScreenShare) +RCT_EXTERN_METHOD(createScreenShareOffer) @end diff --git a/ios/ReactExported/ScreenShareServiceManager.swift b/ios/ReactExported/ScreenShareServiceManager.swift index 7d7dc80..1075112 100644 --- a/ios/ReactExported/ScreenShareServiceManager.swift +++ b/ios/ReactExported/ScreenShareServiceManager.swift @@ -6,6 +6,7 @@ import Foundation import os +import bigbluebutton_mobile_sdk_common @objc(ScreenShareServiceManager) class ScreenShareServiceManager: NSObject { @@ -25,4 +26,17 @@ class ScreenShareServiceManager: NSObject { ReactNativeEventEmitter.emitter.sendEvent(withName: eventName, body: nil); } + // React native exposed method (called when user click the button to share screen) + @objc func createScreenShareOffer() -> Void { + logger.info("createScreenShareOffer") + + // Send request of SDP to the broadcast upload extension + // TIP - the handling of SDP response is done in observer2 of BigBlueButtonSDK class + logger.info("createScreenShareOffer - persisting information on UserDefaults") + BBBSharedData + .getUserDefaults(appGroupName: BigBlueButtonSDK.getAppGroupName()) + .set(BBBSharedData.generatePayload(), forKey: BBBSharedData.SharedData.createScreenShareOffer) + + + } } diff --git a/src/methods/createScreenShareOffer.tsx b/src/methods/createScreenShareOffer.tsx new file mode 100644 index 0000000..16378be --- /dev/null +++ b/src/methods/createScreenShareOffer.tsx @@ -0,0 +1,32 @@ +import { createScreenShareOffer as nativeCreateScreenShareOffer } from '../native-components/BBBN_ScreenShareService'; +import nativeEmitter from '../native-messaging/emitter'; + +// Reference to the resolver of last call +let resolve = (a: String) => { + console.log( + `default resolve function called, this should never happen: ${a}` + ); +}; + +// Resolve promise when SDP offer is available +nativeEmitter.addListener('onScreenShareOfferCreated', (sdp) => { + resolve(sdp); +}); + +// Entry point of this method +function createScreenShareOffer() { + return new Promise((res, rej) => { + // store the resolver for later call (when event is received) + resolve = res; + + try { + console.log(`>nativeCreateScreenShareOffer`); + // call native swift method that triggers the broadcast popup + nativeCreateScreenShareOffer(); + } catch (e) { + rej(`Call to nativeCreateScreenShareOffer failed`); + } + }); +} + +export default createScreenShareOffer; diff --git a/src/methods/initializeScreenShare.tsx b/src/methods/initializeScreenShare.tsx index 6f68303..5b4e72a 100644 --- a/src/methods/initializeScreenShare.tsx +++ b/src/methods/initializeScreenShare.tsx @@ -2,7 +2,7 @@ import { initializeScreenShare as nativeInitializeScreenShare } from '../native- import nativeEmitter from '../native-messaging/emitter'; // Reference to the resolver of last call -let resolve = (a: String) => { +let resolve = (a: String | null) => { console.log( `default resolve function called, this should never happen: ${a}` ); @@ -15,7 +15,7 @@ nativeEmitter.addListener('onBroadcastRequested', () => { // Resolve promise when broadcast is started (this event means that user confirmed the screenshare) nativeEmitter.addListener('onBroadcastStarted', () => { - resolve('null'); + resolve(null); }); // Entry point of this method @@ -26,9 +26,10 @@ function initializeScreenShare() { try { // call native swift method that triggers the broadcast popup + console.log(`>nativeInitializeScreenShare`); nativeInitializeScreenShare(); } catch (e) { - rej(`Call to nativeInitializeScreenShare failed`); + rej(`Call to nativeInitializeScreenShare failed zzy`); } }); } diff --git a/src/native-components/BBBN_ScreenShareService.tsx b/src/native-components/BBBN_ScreenShareService.tsx index 1b93a13..571690f 100644 --- a/src/native-components/BBBN_ScreenShareService.tsx +++ b/src/native-components/BBBN_ScreenShareService.tsx @@ -5,3 +5,7 @@ const ScreenShareService = NativeModules.BBBN_ScreenShareService; export function initializeScreenShare() { ScreenShareService.initializeScreenShare(); } + +export function createScreenShareOffer() { + ScreenShareService.createScreenShareOffer(); +} diff --git a/src/webview/message-handler.tsx b/src/webview/message-handler.tsx index bd090cc..4225e04 100644 --- a/src/webview/message-handler.tsx +++ b/src/webview/message-handler.tsx @@ -1,6 +1,7 @@ import type { MutableRefObject } from 'react'; import type { WebView, WebViewMessageEvent } from 'react-native-webview'; import initializeScreenShare from '../methods/initializeScreenShare'; +import createScreenShareOffer from '../methods/createScreenShareOffer'; function observePromiseResult( webViewRef: MutableRefObject, @@ -39,6 +40,9 @@ export function handleWebviewMessage( case 'initializeScreenShare': promise = initializeScreenShare(); break; + case 'createOffer': + promise = createScreenShareOffer(); + break; default: throw `Unknown method ${data?.method}`; }