本文にスキップする
Version: 6.0

1対1通話画面共有

1対1通話で画面共有を実装するサンプルコードです。

前提条件

画面共有を実装する前に、次の作業が必要です。

  • アプリケーションで画面共有をキャプチャーするには、Broadcast Upload Extensionまたはそれに相当する機能を実装する必要があります。
    • Broadcast Upload Extensionを実装するには、Xcodeでプロジェクトに新しいTargetを追加し、「Broadcast Upload Extension」テンプレートを選択した後、この拡張機能を有効にしてください。
  • 画面共有ストリームの転送に使用するポート番号、受信トークン、および送信トークンを定義してください。

送信側 - 画面共有の送信を開始する

画面共有を開始するためのコード実装方法は次のとおりです。

PlanetKitに画面共有キー設定

送信側のアプリクライアントであらかじめ定義したポート番号、受信トークン、および送信トークンを発信者の場合はPlanetKitMakeCallSettingBuilder、受信者の場合はPlanetKitVerifyCallSettingBuilderwithEnableScreenShareKey(broadcastPort:broadcastPeerToken:broadcastMyToken:)に設定します。

// If the sender is a caller
var settingsBuilder = PlanetKitMakeCallSettingBuilder()
.withEnableScreenShareKey(broadcastPort: PORT_NUMBER, broadcastPeerToken: "USER_DEFINED_TOKEN_EXT", broadcastMyToken: "USER_DEFINED_TOKEN_APP")

// If the sender is a callee
var settingsBuilder = PlanetKitVerifyCallSettingBuilder()
.withEnableScreenShareKey(broadcastPort: PORT_NUMBER, broadcastPeerToken: "USER_DEFINED_TOKEN_EXT", broadcastMyToken: "USER_DEFINED_TOKEN_APP")

画面キャプチャーおよび送信モジュール実装

NWConnectionを通じてアプリとSDK間の接続を設定する画面キャプチャーモジュールを実装します。

class BroadcastSender {
private enum State {
case started
case handshaking
case connected
case failed
}

private let connection: NWConnection
private let queue = DispatchQueue(label: "BroadcastSender.Queue")

static func connection(broadcastPort : UInt16) throws -> NWConnection {
guard let port = NWEndpoint.Port(rawValue: broadcastPort) else {
throw Error.invalidPort
}

let options = NWProtocolTCP.Options()
options.noDelay = true

return NWConnection(host: .ipv4(.loopback), port: port, using: .init(tls: nil, tcp: options))
}

init(broadcastPort: UInt16, rxToken : String, txToken : String) throws {
self.delegate = delegate
self.rxToken = rxToken
self.txToken = txToken

connection = try BroadcastSender.connection(broadcastPort: broadcastPort)
connection.stateUpdateHandler = { [weak self] in
self?.handleConnection(newState: $0)
}
connection.start(queue: DispatchQueue(label: "BroadcastSender.NetworkQueue"))
}

func handShake() {
guard state == .started, let data = txToken.data(using: .utf8) else {
return
}

state = .handshaking

connection.send(content: data, completion: .contentProcessed({ (error) in
self.handShakeProcessed(error: error)
}))
}

func sendVideo(sampleBuffer: CMSampleBuffer) throws {
guard state == .connected,
!sending else {
return
}

try queue.sync {
let data: Data?
// create data with CMSampleBuffer
connection.send(data, completion: .contentProcessed({(error) in NSLog("data processed \(error)")}))
}
}

func handShakeProcessed(error: NWError?) {
queue.sync {
if let error = error {
didFail(error: error)
} else {
handShakeAck()
}
}
}

func handShakeAck() {
guard state == .handshaking, let token = rxToken.data(using: .utf8) else {
return
}

connection.receive(minimumIncompleteLength: token.count, maximumLength: token.count) { (data, context, final, error) in
self.handShakeAckProcessed(data: data, error: error)
}
}

func handShakeAckProcessed(data: Data?, error: NWError?) {
queue.sync {
guard state == .handshaking, let token = rxToken.data(using: .utf8) else {
return
}
if data == token {
state = .connected

} else {

didFail(error: .rejected)
}
}
}

func handleConnection(newState: NWConnection.State) {
queue.sync {
switch newState {
case .ready:
handShake()
case .waiting(let error), .failed(let error):
didFail(error: error)
default:
break
}
}
}
}

接続が正常に設定されると、作成されたNWConnectionに画面共有ストリームを送る必要があります。キャプチャーした画面共有ストリームを送るためのSampleHandlerクラスを実装します。

class SampleHandler: RPBroadcastSampleHandler {
private var sender: BroadcastSender?
...
override func broadcastFinished() {
// User has requested to finish the broadcast.
sender?.cancel()
sender = nil
}

let rxToken : String = "USER_DEFINED_TOKEN_APP"
let txToken : String = "USER_DEFINED_TOKEN_EXT"
let broadcastPort : UInt16 = PORT_NUMBER

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case RPSampleBufferType.video:
do {
if let sender = sender {
try autoreleasepool {
try sender.sendVideo(sampleBuffer: sampleBuffer)
}
} else {
sender = try BroadcastSender(delegate: self, broadcastPort: broadcastPort, rxToken: rxToken, txToken: txToken)
}
} catch {
finish(error: error)
}
break
...
}
}

private func finish(error: Error) {
sender?.cancel()
sender = nil

if let description = (error as? LocalizedError)?.errorDescription {
self.finishBroadcastWithError(NSError(domain: "BroadcastSender.ErrorDomain", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: description]))
} else {
self.finishBroadcastWithError(error)
}
}
}

PlanetKitで画面共有開始

PlanetKitCallDelegatedidStartMyBroadcast()を実装します。画面共有ストリームの送信を開始するには、didStartMyBroadcast()startMyScreenShare()を呼び出します。

extension VideoCallViewController: PlanetKitCallDelegate {
...

func didStartMyBroadcast(_ call: PlanetKitCall) {
NSLog("Broadcast: start")
DispatchQueue.main.async {
call.startMyScreenShare() { success in
guard success else {
return
}
}
}
}

...
}

送信側 - 画面共有の送信を中止する

画面共有を中止するためのコード実装方法は次のとおりです。

PlanetKitCallDelegatedidFinishMyBroadcast()didErrorMyBroadcast()を実装します。画面共有を中止するには、didFinishMyBroadcast()didErrorMyBroadcast()stopMyScreenShare()を呼び出します。

extension VideoCallViewController: PlanetKitCallDelegate {
...

func didFinishMyBroadcast(_ call: PlanetKitCall) {
NSLog("Broadcast: finish")
DispatchQueue.main.async {
self.call?.stopMyScreenShare() { success in
}
}
}

func didErrorMyBroadcast(_ call: PlanetKitCall, error: PlanetKitScreenShare.BroadcastError) {
NSLog("Broadcast: error \(error)")
DispatchQueue.main.async {
self.call?.stopMyScreenShare() { success in
}
}
}
}

受信側 - 画面共有のアップデートイベントを受信する

PlanetKitCallDelegatepeerDidStartScreenShareまたはpeerDidStopScreenShareイベントを通じて、ピアの画面共有が開始あるいは中止されたかを確認します。

extension VideoCallViewController: PlanetKitCallDelegate {
...
func peerDidStartScreenShare(_ call: PlanetKitCall) {
NSLog("peer screen share started")

// UI code here
DispatchQueue.main.async {
self.setPeerScreenShareEnabled(true)
}
}

func peerDidStopScreenShare(_ call: PlanetKitCall, reason: Int32) {
NSLog("peer screen share stopped")

// UI code here
DispatchQueue.main.async {
self.setPeerScreenShareEnabled(false)
}
}
}

受信側 - ピアの画面共有ビデオをレンダリングする

peerDidStartScreenShareイベントが発生した後、画面共有ビューを追加して共有画面を表示できます。

func setPeerScreenShareEnabled(_ enable: Bool) {
if enable {
let newView = PlanetKitMTKView(frame: view.bounds, device: nil)
view.addSubview(newView)
self.screenShareView = newView

call.addPeerScreenShareView(delegate: newView)
...
} else {
...
}
}

受信側 - ピアの画面共有ビデオを除去する

ピアの画面共有をレンダリングしたくない場合は、PlanetKitCallで画面共有ビューを除去します。

func setPeerScreenShareEnabled(_ enable: Bool) {
if enable {
...
} else {
if let oldView = screenShareView {
oldView.removeFromSuperview()
screenShareView = nil
call.removePeerScreenShareView(delegate: oldView)
}
}
}

関連サンプルコード

関連ドキュメント