GoogleAPIClientForRESTを使ってYouTubeをiOSで再生

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

# 実装サンプル
https://github.com/KazukiTanaka/YouTubeClient

# 使うライブラリ
### [YouTubeiOSPlayerHelper](https://github.com/youtube/youtube-ios-player-helper)
- 再生に使う
    - メンテされてないけど、動く
    - webviewを生成して、Youtube iFrame APIを実行しているだけ
- [
YouTube 埋め込みプレーヤーとプレーヤーのパラメータ](https://developers.google.com/youtube/player_parameters?hl=ja)が指定可能

### [GoogleAPIClientForREST](https://github.com/google/google-api-objectivec-client-for-rest)
- 動画リストを取得に使う
    - obj-cだけどメンテされてる (公式にはswift版なし)
    - YouTube Data API (v3)をラッピングしている
        - JSONパースするよりは短く記述できる
- Responseの各データが階層化されており、最初は追うのが大変
    - 公式の[
API Reference](https://developers.google.com/youtube/v3/docs/?hl=ja)を追っていくのをおすすめ

### RxSwiftとRxCocoa
- TableViewに使用
    - [RxSwiftのUITableViewとのバインディングまとめ](https://qiita.com/hironytic/items/71bc729abe73ab9f0879)

### Nuke
- 画像表示に利用

### R.swift
- リソースのコード補完

# 実装
- Youtubeでsearch video した結果を表示するサンプルを元に解説
    - https://github.com/KazukiTanaka/YouTubeClient

## 初期設定
1. YouTube Data API公式の[作業を始める前に](https://developers.google.com/youtube/v3/getting-started?hl=ja)を参照にYOUR_API_KEYを取得しておく
2. ``pod install`` を実行
3. YOUR_API_KEY を ``YouTubeClient/Models/VideoPlaylistFactory.swift`` に設定

## 一部解説

### 取得

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

```swift: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バインディングしてるので、慣れてないとわかりにくいコードかも
    - 慣れるとスッキリかけて好き
    - 詳しくは[RxSwiftのUITableViewとのバインディングまとめ](https://qiita.com/hironytic/items/71bc729abe73ab9f0879)


```swift: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` インライン再生かと
- `YTPlayerViewDelegate`の `playerViewDidBecomeReady `を実装して、ロード終わったら即再生するのも定番

```swift: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のリクエスト・レスポンス構造さえ覚えちゃえば、キャストも迷わず楽になるのでおすすめ