GoogleAPIClientForRESTを使ってYouTubeをiOSで再生


はじめに

  • iOSアプリでYouTube動画を再生しようとした場合に、GoogleAPIClientForRESTを使った、まとまった記事がなかったので記述
    • GoogleAPIClientForRESTを使わずにAPI叩いてJSONパースしてる記事が多かった
    • Googleに未ログインで取得できる情報のみに対応
  • RxSwift + RxCocoa を利用

実装サンプル

使うライブラリ

YouTubeiOSPlayerHelper

GoogleAPIClientForREST

  • 動画リストを取得に使う
    • obj-cだけどメンテされてる (公式にはswift版なし)
    • YouTube Data API (v3)をラッピングしている
      • JSONパースするよりは短く記述できる
  • Responseの各データが階層化されており、最初は追うのが大変

RxSwiftとRxCocoa

Nuke

  • 画像表示に利用

R.swift

  • リソースのコード補完

実装

初期設定

  1. YouTube Data API公式の作業を始める前にを参照にYOUR_API_KEYを取得しておく
  2. pod install を実行
  3. YOUR_API_KEY を YouTubeClient/Models/VideoPlaylistFactory.swift に設定

一部解説

取得

VideoPlaylistFactory.swift

  • ここはハマる箇所は少なめ。リクエストを組み立てていくだけ。
  • 検索したい場合は、GTLRYouTubeQuery_SearchListを使う。利用したいAPIと対応したquery見つけるのに少し手間取るかも
    • apiKey以外に、iOS限定のリクエスト制限をかけている場合APIKeyRestrictionBundleIDを設定可能
  • query組み立ては、 公式リファレンスSearch: list を参照
    • query(withPart: "snippet")のpartは "snippet"を指定しないとresponseに個々のビデオ情報が含まれないので注意
    • query.type = "video"としないとプレイリストやチャンネルも返ってくるのでご注意
  • response objectはそのまま扱えず、利用したAPIにあったclassを指定する必要あり
    • 今回の場合は GTLRYouTube_SearchListResponse
    • 最終的には response.itemsにてGTLRYouTube_SearchResultを返す
YouTubeClient/Models/VideoPlaylistFactory.swift
import RxSwift
import GoogleAPIClientForREST

class VideoPlaylistFactory {
    let result = BehaviorSubject<[GTLRYouTube_SearchResult]>(value: [])

    func search(keyward: String = "音楽",
                maxResult: UInt = 50) {

        let query = GTLRYouTubeQuery_SearchList.query(withPart: "snippet")
        query.q = keyward
        query.type = "video"
        query.maxResults = maxResult

        let service = GTLRYouTubeService()
        service.apiKey = "YOUR_API_KEY"

        service.executeQuery(query) { (_, object, error) in
            if error != nil {
                self.result.onNext([])
                return
            }
            guard let response = object as? GTLRYouTube_SearchListResponse,
                let playlist = response.items else {
                    self.result.onNext([])
                    return
            }

            self.result.onNext(playlist)
        }
    }
}

リスト表示(見出し画像やタイトル)

VideoListViewController.swift

  • 今回は、取得した GTLRYouTube_SearchResultに snippetが含まれるので、そちらからサムネイルやタイトルを表示する
    • let entity: GTLRYouTube_SearchResultSnippet = element.snippet の部分
    • 後は、cell側で entity.titleしたり、entity.thumbnails?.medium?.urlしたりして、表示する
  • RxCocoaのTableViewバインディングしてるので、慣れてないとわかりにくいコードかも
YouTubeClient/ViewControllers/VideoListViewController.swift

// 中略

    private func bindData() {
        self.playlistFactory
            .result
            .bind(to: self.tableView.rx.items) { tableView, row, element in

                guard let cell = tableView.dequeueReusableCell(
                    withIdentifier: R.reuseIdentifier.videoListTableViewCell,
                    for: IndexPath(row: row, section: 0)),
                    let entity: GTLRYouTube_SearchResultSnippet = element.snippet else {
                        return UITableViewCell()
                }

                cell.configure(entity: entity)
                return cell
            }
            .disposed(by: disposeBag)
    }

再生

VideoDetailViewController.swift

  • webviewを生成して、Youtube iFrame APIを実行しているだけなので、iFrame APIのリファレンスが参考になる
    • よく使うパラメータは "playsinline": 1 インライン再生かと
  • YTPlayerViewDelegateplayerViewDidBecomeReadyを実装して、ロード終わったら即再生するのも定番
YouTubeClient/ViewControllers/VideoDetailViewController.swift
// 中略
extension VideoDetailViewController {
    private func loadVideo(videoId: String = "") {

        let playerVars = [
            "playsinline": 1
        ]
        self.playerView.load(withVideoId: videoId,
                             playerVars: playerVars)
    }
}

extension VideoDetailViewController: YTPlayerViewDelegate {
    func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
        self.playerView.playVideo()
    }
    func playerView(_ playerView: YTPlayerView, didChangeTo state: YTPlayerState) {}
    func playerView(_ playerView: YTPlayerView, didChangeTo quality: YTPlaybackQuality) {}
    func playerView(_ playerView: YTPlayerView, receivedError error: YTPlayerError) {}
    func playerView(_ playerView: YTPlayerView, didPlayTime playTime: Float) {}
    func playerViewPreferredWebViewBackgroundColor(_ playerView: YTPlayerView) -> UIColor { return .black }
    func playerViewPreferredInitialLoading(_ playerView: YTPlayerView) -> UIView? { return nil }
}

おわりに

  • GoogleAPIClientForREST使ってみたけど、レスポンスやitemsを都度キャストする手間があるのが、もどかしい
  • (リファレンスを参考に)APIのリクエスト・レスポンス構造さえ覚えちゃえば、キャストも迷わず楽になるのでおすすめ