iOS11から搭載されるスクリーンレコーディングでの録画を検知する方法

iOS11の新機能の一つに**Screen Recording**というものがあります。

とても素敵な機能で、コントロールセンターにあるボタンを押すだけで簡単に画面の録画ができるようになりました。(超便利！)
しかしアプリによっては「この画面を録画されると都合が悪い」といったケースもあるでしょう。
そこで、アプリの中で録画の検知をする方法をTipsとしてまとめます。

# アプリ内で録画を検知するためのプロパティ
Screen Recordingへ合わせる形で、iOS11からUIScreenに以下のAPIが追加されました。

- [isCaptured - UIScreen | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uiscreen/2921651-iscaptured)

このプロパティは、**「スクリーンのキャプチャ」**が行われている間はtrueを返します。

```swift
UIScreen.main.isCaptured
```

ここでいう「スクリーンのキャプチャ」にはScreen Recordingも含まれます。
レコードを開始した上でプロパティの値を確認してみるとわかるのですが、確かにレコード中はtrueを返して、やめるとfalseを返します。
注意点として、「含まれます」というのが曲者で、AirPlayやミラーリング中でも同じくtrueを返す仕様となっています。
従って、AirPlayをサポートしている映像サービスだと、AirPlayのケースを取り除かないとならないでしょう。

AVPlayerを使って映像を再生しているケースであれば、以下のAPIを組み合わせることで**スクリーンをキャプチャしているがAirPlayではない**というのを判定できるかと思います。

- [isExternalPlaybackActive - AVPlayer | Apple Developer Documentation](https://developer.apple.com/documentation/avfoundation/avplayer/1388982-isexternalplaybackactive)

Screen Recordingもしくはミラーリング中であるかどうかは上記2つのプロパティを使って判定します。

```swift
// player: AVPlayer
UIScreen.main.isCaptured && !player.isExternalPlaybackActive
```

ミラーリングを検出したければ、UIScreenのmirroredというプロパティが使用できます。

- [mirrored - UIScreen | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uiscreen/1617829-mirrored)

試してみた所、以下のように呼ぶとミラーリング中はUIScreenのオブジェクトが入り、ミラーリングしていなければnilが入りました。

```swift
UISCreen.screens.map { $0.mirrored }
```

ただし、このプロパティを組み合わせても、「ミラーリング中にスクリーンレコーディングを行っている」という状態を検出することはできません。そのため、厳格にいくのであれば、ミラーリングを除くのは難しいでしょう。

## 追記

iOS11.1以降では、ミラーリング中にスクリーンレコーディングができないよう変更されているのを確認しました。そのため、上記対応をすれば問題ないかもしれません。

# アプリ内で録画を検知するためのNotification

前述のプロパティを使い、UIのライフサイクルイベントや通信完了ハンドラなどで検知できるようになりました。

ただしこのままだと不十分です。画面遷移後ににコントロールセンターを開き、録画を開始したときにはどうすればいいでしょうか。寧ろそういうパターンのほうが多いでしょう。
そういった場合でも対応できるよう、Notificationにも新たな通知が追加されました。

- [UIScreenCapturedDidChangeNotification - UIKit | Apple Developer](https://developer.apple.com/documentation/foundation/nsnotification.name/2921652-uiscreencaptureddidchange)

リファレンスを見る限り、通知を受け取ってもuserInfoには何も入っていないようです。通知の中で先程のisCapturedを呼んで、判定をしましょう。
また、UIScreenのisCapturedをKVOで監視するという方法でももちろんよいでしょう。

```swift
let obserber = NotificationCenter
            .default
            .addObserver(forName: .UIScreenCapturedDidChange, object: nil, queue: .main) { [weak self] (notif) in
                if let player = self?.player, !player.isExternalPlaybackActive, UIScreen.main.isCaptured {
                    // ここで何とかする
                }
        }
```

# まとめ

1. 画面遷移してデータを取ってきたタイミングで検知したいのであればUIScreenの***isCaptured***を呼ぶ
2. ただし1.のみだとAirPlayやミラーリングも検知してしまうので、AVPlayerの***isExternalPlaybackActive***なども適宜組み合わせる
3. レコーディング開始のタイミングを検知したければ**UIScreenCapturedDidChangeNotification**を監視する

上記方法で、**Screen Recordingもしくはミラーリング中**という状態は取れるようになりました。以上です。

# おまけ - 映像のレコーディングをサーバーで防ぐ方法
著作権が絡む映像の場合、HLSで配信するときにFPSを使ってレコーディングそのものを実行させないという方法もあります。かなり遡りますが、このあたりの話については2015年のWWDCで言及されています。

- [Content Protection for HTTP Live Streaming](https://developer.apple.com/videos/play/wwdc2015/502/?time=1114)

# 参考資料

- [iOS 11 Screen Recording - Copyright Risks | Apple Developer Forums](https://forums.developer.apple.com/thread/86521)
- [isCaptured - UIScreen | Apple Developer Documentation](https://developer.apple.com/documentation/uikit/uiscreen/2921651-iscaptured)
- [isExternalPlaybackActive - AVPlayer | Apple Developer Documentation](https://developer.apple.com/documentation/avfoundation/avplayer/1388982-isexternalplaybackactive)
- [UIScreenCapturedDidChangeNotification - UIKit | Apple Developer](https://developer.apple.com/documentation/foundation/nsnotification.name/2921652-uiscreencaptureddidchange)
- [Content Protection for HTTP Live Streaming](https://developer.apple.com/videos/play/wwdc2015/502/?time=1114)