🍏

ReplayKit+Agoraで画面共有がしたい

2023/04/16に公開

Agora導入手順

まずはAgoraのアカウント等作成しておく

コンソールを開いてProject > More

こちらの鍵マークからTokenを生成する

Tokenは24時間限定の一時的なもの

ChannelNameを設定して生成

今回はScreenShareとする

こちらのiOSのAPIExampleをビルドしていく

https://github.com/AgoraIO/API-Examples

初めに設定しておくのは以下の箇所

  • KeyCenter.swiftのAppIdTokenを書き換える
struct KeyCenter {
    static let AppId: String = "YourAppID"
    
    // assign token to nil if you have not enabled app certificate
    static var Token: String? = "TempToken"
}
  • Agora-ScreenShare-Extension > AgoraUoloader.swiftのbyToken: nilを書き換える
static func startBroadcast(to channel: String) {
    sharedAgoraEngine.joinChannel(byToken: KeyCenter.Token, channelId: channel, info: nil, uid: 0, joinSuccess: nil)
}

ScreenShareを選択しチャンネル名を指定してjoin

ビデオ通話、画面共有が確認できる

WebSDKとの相互運用

デモページから確認できる
https://agoraio-community.github.io/AgoraWebSDK-NG/demo/basicVideoCall/index.html

ビデオ通話について

ReplayKit

アプリ内で画面録画、音声録音、ライブストリーミングなどを可能にするフレームワーク。
ReplayKitを使用することで、簡単に画面の録画やライブストリーミングを行うことができます。

BroadcastUploadExtension

ReplayKitを使用してiOSアプリで画面共有機能を実装するための拡張機能。
BroadcastExtensionを追加することで、アプリ内で録画した画面を外部のアプリやWebページなどに配信することができます。
画面共有に関してはもう一人ユーザーがjoinするイメージ。

Extensionの追加

Fileメニューから New > Target > Broadcastと入力

画面共有用のボタン

赤枠のボタンを追加していく

let frame = CGRect(x: 0, y:0, width: 60, height: 60)
let systemBroadcastPicker = RPSystemBroadcastPickerView(frame: frame)
systemBroadcastPicker.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
if let url = Bundle.main.url(forResource: "Agora-ScreenShare-Extension", withExtension: "appex", subdirectory: "PlugIns") {
    if let bundle = Bundle(url: url) {
        systemBroadcastPicker.preferredExtension = bundle.bundleIdentifier
    }
}
broadcasterPickerContainer.addSubview(systemBroadcastPicker)

タップするとBroadcastPickerViewが表示される

SampleHandlerに処理を書いていく

import ReplayKit

class SampleHandler: RPBroadcastSampleHandler {
    // 配信開始時に呼ばれる
    override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
        
        if let setupInfo = setupInfo, let channel = setupInfo["channelName"] as? String {
            //In-App Screen Capture
            AgoraUploader.startBroadcast(to: channel)
        } else {
            //iOS Screen Record and Broadcast
            AgoraUploader.startBroadcast(to: "channel")
        }
    }
    // 配信が一時停止したときに呼ばれる
    override func broadcastPaused() {
        // User has requested to pause the broadcast. Samples will stop being delivered.
    }
    // 配信が一時停止された後、再開されたときに呼ばれる
    override func broadcastResumed() {
        // User has requested to resume the broadcast. Samples delivery will resume.
    }
    // 配信終了時に呼ばれる
    override func broadcastFinished() {
        AgoraUploader.stopBroadcast()
    }
    // エラーが発生して終了するときに呼ばれる
    override func finishBroadcastWithError(_ error: Error) {
        print("finishBroadcastWithError: \(error)")
    }
    // SampleBufferを生成するたびに呼ばれる(受け取ったSampleBufferの処理など)
    override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
        DispatchQueue.main.async {
            switch sampleBufferType {
            case .video:
	        // ここでsampleBufferを送っている
                AgoraUploader.sendVideoBuffer(sampleBuffer)
            case .audioApp:
                AgoraUploader.sendAudioAppBuffer(sampleBuffer)
            case .audioMic:
                AgoraUploader.sendAudioMicBuffer(sampleBuffer)
            @unknown default:
                break
            }
        }
    }
}

アプリから終了するには?

アプリ側から終了させるにはエラー終了させる必要があります。

override func finishBroadcastWithError(_ error: Error) {
    print("finishBroadcastWithError: \(error)")
}

ユーザーがチャンネルを離れたタイミングでエラー終了させる。

let message = "画面共有を停止しました"
let userInfo = [NSLocalizedFailureReasonErrorKey: message]
let error = NSError(domain: "BroadcastExtension", code: 1, userInfo: userInfo)
super.finishBroadcastWithError(error)

Extensionのデバッグ方法

BroadcastUploadExtensionを指定して、アプリを選択してビルドする

データの共有方法

AppGroupsを追加してUserDefaultsで共有する

// 保存
UserDefaults(suiteName: "group.jp.hoge")?.setValue("abcdefg", forKey: "key")
// 取得
UserDefaults(suiteName: "group.jp.hoge")?.string(forKey: "key")

ファイルの共有方法

Extensionに共有したいファイルを選択→InspectorのTargetMemberShipでExtensionを選択する

まとめ

基本的にドキュメントが豊富で、サンプルを動かしてみることで実装方法は概ね理解できました。
Web側との確認用も準備されているのでやりとりもしやすかったです。

とてもとても参考にさせていただきました

https://speakerdeck.com/fromatom/iosdc2019
https://speakerdeck.com/matsuokah/live-streaming-with-screen-recording

Discussion