ReplayKit の RPScreenRecorder#startCapture を使ってアプリをキャプチャする


iOS9でReplayKitが登場し、iOS10でReplayKitにライブ機能が追加されました。
そしてiOS11で ReplayKit Live に対応したアプリでiPhoneの画面自体をキャプチャできるようになるなど新たな機能が追加されました。

今回はiOS11で追加された RPScreenRecorder#startCapture を触ってみたのでメモしていきます。
AVPlayerMetalSpriteKit をキャプチャし動画に書き出すサンプルを作ってみたので興味があれば見てみてください。
https://github.com/yoshimin/ReplayKitSample

※左がMetalで右がSpriteKit

RPScreenRecorderとは

RPScreenRecorder 自体はiOS9で登場したアプリ画面をキャプチャしてくれるクラスです。
ただ、iOS9で登場した機能は

func startRecording(handler: ((Error?) -> Void)? = nil)

により画面のキャプチャを開始し

func stopRecording(handler: ((RPPreviewViewController?, Error?) -> Void)? = nil)

によりキャプチャを終了すると、 RPPreviewViewController というキャプチャした動画を再生するためのViewControllerが渡されるので、それを表示することでユーザーが動画の確認やカメラロールへの保存、シェアなどができるというものでした。
実装はシンプルでかんたんなのですが、プログラム側で RPScreenRecorder 録画された動画を取得することはできず使いみちは限られていました。

それが今回iOS11で RPScreenRecorder でキャプチャした映像をリアルタイムに受け取ることができるようになりました。

PScreenRecorder#startCaptureの使い方

ではどうやってキャプチャするかというと

func startCapture(handler captureHandler: ((CMSampleBuffer, RPSampleBufferType, Error?) -> Void)?, completionHandler: ((Error?) -> Void)? = nil)

で開始して

func stopCapture(handler: ((Error?) -> Void)? = nil)

で終了する。
これだけです。

startCapture でキャプチャを開始すると第二引数のコールバックが呼ばれます。
もし開始に失敗した場合はここで Error が返ってきます。
第一引数の captureHandler はキャプチャ中継続的に呼ばれ、ここで CMSampleBuffer が返ってくるのであとはこれを好きに扱うことができます。
今回作ったサンプルのように動画に書き出す場合、 返された CMSampleBuffer が音声データなのか映像データなのかを判別する必要がありますが、それは captureHandler の第二引数である
RPSampleBufferType を使います。
RPSampleBufferType は以下のように3つのタイプがあり、 .video なら映像データ、 .audioApp ならアプリ音声と判別ができます。

public enum RPSampleBufferType : Int {
    case video
    case audioApp
    case audioMic
}

3つめに audioMic というのがありますが、マイク音声も取れます。
マイク音声を録音するには startCapture する前に

RPScreenRecorder.shared().isMicrophoneEnabled = true

を1行書いてあげればOKです。
ただし、音声が録音できるのは isMicrophoneEnabledtrue にした状態で startCapture すると、以下のようなダイアログが表示されるので、ユーザーが「画面とマイクを収録」を選んでくれた場合のみです。

使ってみて

使い方は簡単だし、 CMSampleBuffer があれば使い方は開発者の自由なので実装の幅が広がりそうかなと思いました。
UIView だけでなく MetalSpriteKit の画面も簡単にキャプチャできるのでゲーム配信とかライトに作れそう。(SpriteKit使ってゲーム作る人は少なそうですが)
使ってみて気づいたのは、サイレントモード中の消音や、端末のボリュームの上げ下げはそのままキャプチャに反映されます(ただしボリュームを上げ下げしたときのUIまでは映り込みません)。
アプリの画面上に映るものはキャプチャされてしまうのでサンプルアプリも「録画/停止」ボタン(上のスクショを参照)も一緒にキャプチャされています。
Metal による描画だけをキャプチャしたいとかはできなそうです。
あと、startCapture したときにユーザーに「画面の収録を許可しますか?」というダイアログをOSが出してきますが、このダイアログのメッセージ部分には「収録内容はカメラロールに保存したり友達と共有したりできます。」と書いてます。
実装者がそのような機能を実装しないと保存も共有もできないのでどうゆう意図なのか気になります。
これらの機能を実装しないとリジェクトされたりするのでしょうか?

ということで RPScreenRecorder#startCapture を使ってみたまとめでした。