potatotips #74 で「5分でSharePlay入門」のLTをしました

2021年6月28日
iOS / SharePlay / potatotips /

image

potatotips #74

2021年6月23日(水)にWantedlyさんご主催のオンラインpotatotips(iOS/Android開発Tips共有会)が開催されました。

私はpotatotipsの運営窓口を担当しているのですが、今回はひさびさにLTもさせていただきました。

LTの内容は「5分でSharePlay」です! スライドは コチラ

今回は、このLTの内容をこちらにブログ記事としてまとめさせていただきます。

SharePlayとは?

image

SharePlayとは、FaceTime通話中に離れた場所の友達とアプリのコンテンツを共有する機能です。 このスクショは離れた場所にいる2人が不動産アプリを一緒に見ながら新しい家の候補を決めている様子です。

利用シーン

image

SharePlayの利用シーンは様々です。 WWDC21の各セッションの中でも様々なシーンが紹介されています。

3種のSharePlay

image

SharePlayには大きく3種あります。

※カスタムについてはこの記事では紹介しませんが、デバイス間でカスタムなコマンドを自由に送受信できる柔軟な仕組みがあります

画面共有への対応

SharePlayの画面共有に対応するには各アプリでどの程度の実装が必要でしょうか?

じつは各アプリでの対応は必要なく、なにもしなくても画面共有に対応できます。 正確には画面共有はホーム画面ごと共有され、その時開いているアプリの画面もそのまま共有されます。

自動的に隠される要素

画面共有は自動的にされる(されてしまう)のですが、一部、共有されない要素があります。

です。 その他、必要なら各アプリで隠したい要素(View)をカスタムすることもできます。

動画の共有への対応

最後に動画の共有への対応についてです。

AppleのTVアプリの例

AppleオフィシャルのTVアプリでは次の手順で動画のSharePlayを開始できます。

image

まず、FaceTime中にTVアプリを起動すると、コンテンツ表示部分に SharePlayが可能であることを示すアイコン が表示されます。

image

このとき動画を再生しようとすると、 SharePlayするかどうかを確認するダイアログ が表示されます。 ここで SharePlay を選べば動画のSharePlayの開始です。

動画のSharePlayでできること

動画のSharePlayをすると、

などがデフォルトでサポートされます。

動画のSharePlay対応に必要なコード

実際に動画のSharePlayに対応してみた ViewController のコードが以下です。

import AVKit
import GroupActivities
import UIKit

class ViewController: AVPlayerViewController {
    private var groupSession: GroupSession<MovieWatchingActivity>?

    override func viewDidLoad() {
        super.viewDidLoad()

        setupPlayer()
        prepareSharePlay()
        listenForGroupSession()
    }

    private func setupPlayer() {
        guard player == nil, let movieURL = MovieWatchingActivity.movieURL else {
            return
        }

        let player = AVPlayer(url: movieURL)
        self.player = player
        player.play()
    }
    
    private func prepareSharePlay() {
        let activity = MovieWatchingActivity()
        
        async {
            switch await activity.prepareForActivation() {
            case .activationDisabled:
                break
            case .activationPreferred:
                activity.activate()
            case .cancelled:
                break
            default: ()
            }
        }
    }

    private func listenForGroupSession() {
        async {
            for await session in MovieWatchingActivity.sessions() {
                groupSession = session
                player?.playbackCoordinator.coordinateWithSession(session)
                session.join()
            }
        }
    }
}

struct MovieWatchingActivity: GroupActivity {
    static let movieURL: URL? = URL(string: "https://devstreaming-cdn.apple.com/videos/wwdc/2019/408bmshwds7eoqow1ud/408/hls_vod_mvp.m3u8")

    static let activityIdentifier = "work.spinners.SharePlaySample.GroupWatching"
    
    var metadata: GroupActivityMetadata {
        var metadata = GroupActivityMetadata()
        metadata.fallbackURL = Self.movieURL
        metadata.previewImage = UIImage(named: "wwdc19")?.cgImage
        metadata.title = "Sample"
        metadata.subtitle = "WWDC19 Session Video"
        return metadata
    }
}

このわずか50行程度(空行などを省いて)で、

までひととおり実装できました。

もう少し重要なところを抜粋して紹介します。

GroupActivityの用意

まず、必要なのが GroupActivity を実装したものです。

struct MovieWatchingActivity: GroupActivity {
    static let movieURL: URL? = URL(string: "https://devstreaming-cdn.apple.com/videos/wwdc/2019/408bmshwds7eoqow1ud/408/hls_vod_mvp.m3u8")

    static let activityIdentifier = "work.spinners.SharePlaySample.GroupWatching"
    
    var metadata: GroupActivityMetadata {
        var metadata = GroupActivityMetadata()
        metadata.fallbackURL = Self.movieURL
        metadata.previewImage = UIImage(named: "wwdc19")?.cgImage
        metadata.title = "Sample"
        metadata.subtitle = "WWDC19 Session Video"
        return metadata
    }
}

といっても、共有するコンテンツのメタデータを返すだけの簡単なものです。

GroupActivityのアクティベート

そしてその GroupActivity をインスタンス化して prepareForActivation を呼びます。すると、さきほど紹介した SharePlayをするかどうかの確認ダイアログ が画面に表示されます。

ここでユーザが SharePlay を選ぶと .activationPreferred が返るので、そこで GroupActivityactivate してあげるだけです。

let activity = MovieWatchingActivity()

async {
    switch await activity.prepareForActivation() {
    case .activationDisabled:
        break
    case .activationPreferred:
        activity.activate()
    case .cancelled:
        break
    default: ()
    }
}

これだけで動画の共有が可能です。

再生位置の同期をサポート

最後に再生位置の同期です。

これも簡単で、GroupActivity のセッションが開始されたら、そのセッションと動画再生に利用している AVPlayer バインドして、セッションに join するだけです。

async {
    for await session in MovieWatchingActivity.sessions() {
        groupSession = session
        player?.playbackCoordinator.coordinateWithSession(session)
        session.join()
    }
}

これだけで再生位置の同期がサポートできてしまいます。

まとめ

Related Entries
Latest Entries