Add ICE negotiation

This commit is contained in:
Tiago Jacobs
2022-03-27 22:18:34 -03:00
parent 6fce1d61f3
commit 63fe888544
11 changed files with 224 additions and 14 deletions

View File

@@ -33,7 +33,7 @@ open class BBBSampleHandler : RPBroadcastSampleHandler {
logger.info("ReplayKit2 event - broadcastStarted - persisting information on UserDefaults")
userDefaults.set(BBBSharedData.generatePayload(), forKey: BBBSharedData.SharedData.broadcastStarted)
self.screenBroadcaster = ScreenBroadcaster()
self.screenBroadcaster = ScreenBroadcaster(appGroupName: appGroupName)
// Listen for createOffer requests from the UI APP
logger.info("Configuring observer for createOffer")

View File

@@ -5,18 +5,24 @@
//
import os
import bigbluebutton_mobile_sdk_common
import WebRTC
open class ScreenBroadcaster {
// Logger (these messages are displayed in the console application)
private var logger = os.Logger(subsystem: "BigBlueButtonMobileSDK", category: "ScreenBroadcaster")
private var webRTCClient:WebRTCClient
private var appGroupName:String
private let encoder = JSONEncoder()
init() {
init(appGroupName: String) {
self.appGroupName = appGroupName
webRTCClient = WebRTCClient(iceServers: ["stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:19302",
"stun:stun2.l.google.com:19302",
"stun:stun3.l.google.com:19302",
"stun:stun4.l.google.com:19302"])
webRTCClient.delegate = self
}
public func createOffer() async -> String? {
@@ -38,6 +44,91 @@ open class ScreenBroadcaster {
return false
}
}
}
extension ScreenBroadcaster: WebRTCClientDelegate {
public func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate rtcIceCandidate: RTCIceCandidate) {
do {
let iceCandidate = IceCandidate(from: rtcIceCandidate)
let iceCandidateAsJsonData = try self.encoder.encode(iceCandidate)
let iceCandidateAsJsonString = String(decoding: iceCandidateAsJsonData, as: UTF8.self)
BBBSharedData
.getUserDefaults(appGroupName: self.appGroupName)
.set(BBBSharedData.generatePayload(properties: [
"iceJson": iceCandidateAsJsonString
]), forKey: BBBSharedData.SharedData.onScreenShareLocalIceCandidate)
} catch {
self.logger.error("Error handling ICE candidate")
}
}
public func webRTCClient(_ client: WebRTCClient, didChangeIceConnectionState state: RTCIceConnectionState) {
switch state {
case .connected:
self.logger.info("didChangeConnectionState -> connected")
case .completed:
self.logger.info("didChangeConnectionState -> completed")
case .disconnected:
self.logger.info("didChangeConnectionState -> disconnected")
case .failed:
self.logger.info("didChangeConnectionState -> failed")
case .closed:
self.logger.info("didChangeConnectionState -> closed")
case .new, .checking, .count:
break
@unknown default:
print("Unknown connection state.")
}
}
public func webRTCClient(_ client: WebRTCClient, didChangeIceGatheringState state: RTCIceGatheringState) {
switch state {
case .new:
self.logger.info("didChangeGatheringState -> new")
case .gathering:
self.logger.info("didChangeGatheringState -> gathering")
case .complete:
self.logger.info("didChangeGatheringState -> complete")
@unknown default:
self.logger.error("Unknown gathering state: \(state.rawValue)")
}
}
public func webRTCClient(_ client: WebRTCClient, didChangeSignalingState state: RTCSignalingState) {
var stateString = ""
switch(state) {
case .haveLocalOffer:
self.logger.info("peerConnection new signaling state -> haveLocalOffer")
stateString = "have-local-offer"
case .haveLocalPrAnswer:
self.logger.info("peerConnection new signaling state -> haveLocalPrAnswer")
stateString = "have-local-pranswer"
case .haveRemoteOffer:
self.logger.info("peerConnection new signaling state -> haveRemoteOffer")
stateString = "have-remote-offer"
case .haveRemotePrAnswer:
self.logger.info("peerConnection new signaling state -> haveRemotePrAnswer")
stateString = "have-remote-pranswer"
case .stable:
self.logger.info("peerConnection new signaling state -> stable")
stateString = "stable"
case .closed:
self.logger.info("peerConnection new signaling state -> closed")
stateString = "closed"
default:
self.logger.error("peerConnection new signaling state -> UNKNOWN")
}
BBBSharedData
.getUserDefaults(appGroupName: self.appGroupName)
.set(BBBSharedData.generatePayload(properties: [
"newState": stateString
]), forKey: BBBSharedData.SharedData.onScreenShareSignalingStateChange)
}
}

View File

@@ -18,10 +18,16 @@ open class BBBSharedData {
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
public static let setScreenShareRemoteSDP = "setScreenShareRemoteSDP" // UI APP -> Broadcaster
public static let setScreenShareRemoteSDPCompleted = "setScreenShareRemoteSDPCompleted" // 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)

View File

@@ -0,0 +1,26 @@
//
// IceCandidate.swift
// WebRTC-Demo
//
// Created by Stasel on 20/02/2019.
// Copyright © 2019 Stasel. All rights reserved.
//
import Foundation
import WebRTC
/// This struct is a swift wrapper over `RTCIceCandidate` for easy encode and decode
public struct IceCandidate: Codable {
let candidate: String
let sdpMLineIndex: Int32
let sdpMid: String?
public init(from iceCandidate: RTCIceCandidate) {
self.sdpMLineIndex = iceCandidate.sdpMLineIndex
self.sdpMid = iceCandidate.sdpMid
self.candidate = iceCandidate.sdp
}
var rtcIceCandidate: RTCIceCandidate {
return RTCIceCandidate(sdp: self.candidate, sdpMLineIndex: self.sdpMLineIndex, sdpMid: self.sdpMid)
}
}

View File

@@ -44,5 +44,15 @@ extension UserDefaults {
@objc open dynamic var setScreenShareRemoteSDPCompleted: String {
return string(forKey: BBBSharedData.SharedData.setScreenShareRemoteSDPCompleted) ?? ""
}
// Broadcaster -> UI APP
@objc open dynamic var onScreenShareLocalIceCandidate: String {
return string(forKey: BBBSharedData.SharedData.onScreenShareLocalIceCandidate) ?? ""
}
// Broadcaster -> UI APP
@objc open dynamic var onScreenShareSignalingStateChange: String {
return string(forKey: BBBSharedData.SharedData.onScreenShareSignalingStateChange) ?? ""
}
}

View File

@@ -9,9 +9,11 @@ import Foundation
import WebRTC
import os
protocol WebRTCClientDelegate: AnyObject {
public protocol WebRTCClientDelegate: AnyObject {
func webRTCClient(_ client: WebRTCClient, didDiscoverLocalCandidate candidate: RTCIceCandidate)
func webRTCClient(_ client: WebRTCClient, didChangeConnectionState state: RTCIceConnectionState)
func webRTCClient(_ client: WebRTCClient, didChangeIceConnectionState state: RTCIceConnectionState)
func webRTCClient(_ client: WebRTCClient, didChangeIceGatheringState state: RTCIceGatheringState)
func webRTCClient(_ client: WebRTCClient, didChangeSignalingState state: RTCSignalingState)
}
open class WebRTCClient: NSObject {
@@ -27,7 +29,7 @@ open class WebRTCClient: NSObject {
return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory)
}()
weak var delegate: WebRTCClientDelegate?
public weak var delegate: WebRTCClientDelegate?
private let peerConnection: RTCPeerConnection
private let rtcAudioSession = RTCAudioSession.sharedInstance()
private let audioQueue = DispatchQueue(label: "audio")
@@ -78,13 +80,9 @@ open class WebRTCClient: NSObject {
return sdp
}
public func setRemoteSDP(remoteSDP: String) async {
do {
let rtcSessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: remoteSDP)
try await self.peerConnection.setRemoteDescription(rtcSessionDescription)
} catch {
self.logger.error("Error setting remote SDP")
}
public func setRemoteSDP(remoteSDP: String) async throws {
let rtcSessionDescription = RTCSessionDescription(type: RTCSdpType.answer, sdp: remoteSDP)
try await self.peerConnection.setRemoteDescription(rtcSessionDescription)
}
@@ -147,6 +145,8 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
self.logger.info("peerConnection new signaling state: \(stateChanged.rawValue)")
self.delegate?.webRTCClient(self, didChangeSignalingState: stateChanged)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
@@ -163,11 +163,20 @@ extension WebRTCClient: RTCPeerConnectionDelegate {
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
self.logger.info("peerConnection new connection state: \(newState.rawValue)")
self.delegate?.webRTCClient(self, didChangeConnectionState: newState)
self.delegate?.webRTCClient(self, didChangeIceConnectionState: newState)
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
self.logger.info("peerConnection new gathering state: \(newState.rawValue)")
self.delegate?.webRTCClient(self, didChangeIceGatheringState: newState)
if(newState == .complete) {
self.logger.info("peerConnection new gathering state is COMPLETE")
} else if(newState == .gathering) {
self.logger.info("peerConnection new gathering state is GATHERING")
} else if(newState == .new) {
self.logger.info("peerConnection new gathering state is NEW")
}
}
public func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {

View File

@@ -18,6 +18,8 @@ open class BigBlueButtonSDK: NSObject {
private static var observer1: NSKeyValueObservation?
private static var observer2: NSKeyValueObservation?
private static var observer3: NSKeyValueObservation?
private static var observer4: NSKeyValueObservation?
private static var observer5: NSKeyValueObservation?
public static func initialize(broadcastExtensionBundleId:String, appGroupName:String) {
self.broadcastExtensionBundleId = broadcastExtensionBundleId
@@ -50,6 +52,32 @@ open class BigBlueButtonSDK: NSObject {
logger.info("Detected a change in userDefaults for key setScreenShareRemoteSDPCompleted")
ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onSetScreenShareRemoteSDPCompleted.rawValue, body: nil)
}
//setScreenShareRemoteSDPCompleted
observer4 = userDefaults?.observe(\.onScreenShareLocalIceCandidate, options: [.new]) { (defaults, change) in
let payload:String = (change.newValue!);
logger.info("Detected a change in userDefaults for key onScreenShareLocalIceCandidate \(payload)")
let payloadData = payload.data(using: .utf8)!
let decodedPayload = (try? JSONDecoder().decode([String: String].self, from: payloadData))!
let iceJson = decodedPayload["iceJson"]
logger.info("")
ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onScreenShareLocalIceCandidate.rawValue, body: iceJson)
}
//onScreenShareSignalingStateChange
observer5 = userDefaults?.observe(\.onScreenShareSignalingStateChange, options: [.new]) { (defaults, change) in
let payload:String = (change.newValue!);
logger.info("Detected a change in userDefaults for key onScreenShareSignalingStateChange \(payload)")
let payloadData = payload.data(using: .utf8)!
let decodedPayload = (try? JSONDecoder().decode([String: String].self, from: payloadData))!
let newState = decodedPayload["newState"]
ReactNativeEventEmitter.emitter.sendEvent(withName: ReactNativeEventEmitter.EVENT.onScreenShareSignalingStateChange.rawValue, body: newState)
}
}
public static func getBroadcastExtensionBundleId() -> String {

View File

@@ -20,6 +20,8 @@ open class ReactNativeEventEmitter: RCTEventEmitter {
case onBroadcastFinished = "onBroadcastFinished"
case onScreenShareOfferCreated = "onScreenShareOfferCreated"
case onSetScreenShareRemoteSDPCompleted = "onSetScreenShareRemoteSDPCompleted"
case onScreenShareLocalIceCandidate = "onScreenShareLocalIceCandidate"
case onScreenShareSignalingStateChange = "onScreenShareSignalingStateChange"
}
override init() {

View File

@@ -0,0 +1,17 @@
import type { MutableRefObject } from 'react';
import nativeEmitter from '../native-messaging/emitter';
export function setupListener(_webViewRef: MutableRefObject<any>) {
// Resolve promise when SDP offer is available
nativeEmitter.addListener(
'onScreenShareLocalIceCandidate',
(jsonEncodedIceCandidate) => {
const iceCandidate = JSON.parse(jsonEncodedIceCandidate);
_webViewRef.current.injectJavaScript(
`window.bbbMobileScreenShareIceCandidateCallback(${JSON.stringify(
iceCandidate
)});`
);
}
);
}

View File

@@ -0,0 +1,14 @@
import type { MutableRefObject } from 'react';
import nativeEmitter from '../native-messaging/emitter';
export function setupListener(_webViewRef: MutableRefObject<any>) {
// Resolve promise when SDP offer is available
nativeEmitter.addListener('onScreenShareSignalingStateChange', (newState) => {
console.log(`Temos um novo state: ${newState}`);
_webViewRef.current.injectJavaScript(
`window.bbbMobileScreenShareSignalingStateChangeCallback(${JSON.stringify(
newState
)});`
);
});
}

View File

@@ -1,8 +1,10 @@
import { Platform, ViewStyle } from 'react-native';
import React, { useRef } from 'react';
import React, { useEffect, useRef } from 'react';
import BBBN_SystemBroadcastPicker from './native-components/BBBN_SystemBroadcastPicker';
import { WebView } from 'react-native-webview';
import { handleWebviewMessage } from './webview/message-handler';
import * as onScreenShareLocalIceCandidate from './events/onScreenShareLocalIceCandidate';
import * as onScreenShareSignalingStateChange from './events/onScreenShareSignalingStateChange';
type BigbluebuttonMobileSdkProps = {
url: string;
@@ -21,6 +23,11 @@ export const BigbluebuttonMobile = ({
}: BigbluebuttonMobileSdkProps) => {
const webViewRef = useRef(null);
useEffect(() => {
onScreenShareLocalIceCandidate.setupListener(webViewRef);
onScreenShareSignalingStateChange.setupListener(webViewRef);
}, [webViewRef]);
return (
<>
{renderPlatformSpecificComponents()}