YouTubeの再生リストを取得してjsで出力するサンプル(YouTube API活用編)


[前回投稿したサンプル](http://qiita.com/chieeeeno/items/67e2a3f7f3cb5afe916c)だと、PCブラウザやAndroid端末で動画が同時再生できてしまっていたので、
それはあまりよろしくないと思い、一部コードを修正いたしました。

APIキーが必要になりますので、下記リンクを参考に、APIキーを取得してください。
[YouTube API APIキーの取得方法](http://qiita.com/chieeeeno/items/ba0d2fb0a45db786746f)

##サンプルファイル
[デモ](http://chieeeeno.github.io/youtube_playlist_sample/sample2)
[デモファイルDL](https://github.com/chieeeeno/youtube_playlist_sample)

- youtube_playlist
    + sample2
	    - css　：CSSファイル
	    - img　：画像ファイル
	    + js　：jsファイル
		    - lib　：jsライブラリ（jQueryなど）
		    - [index.js](https://github.com/chieeeeno/youtube_playlist_sample/blob/master/sample2/js/index.js)
		    - [youtube_api_info.json](https://github.com/chieeeeno/youtube_playlist_sample/blob/master/sample2/js/youtube_api_info.json)
	    + [index.html](https://github.com/chieeeeno/youtube_playlist_sample/blob/master/sample2/index.html)



##主な変更点
[前回](http://qiita.com/chieeeeno/items/67e2a3f7f3cb5afe916c)は、再生リストのjsonを取得するのに`$.ajax()`関数を使って取得していましたが、
jQuery依存度を少しでも低くしたかったので、Google純正のAPIである[API Client Library for JavaScript (Beta)](https://developers.google.com/api-client-library/javascript/)を使って取得する方法に変更しました。
また、プレーヤーの動作状態を監視するために、プレーヤーの出力方法を`<iframe>`から[YouTube IFrame API](https://developers.google.com/youtube/js_api_reference?hl=ja)を使用する方法に変更しました。

##解説
###ライブラリ使用準備
YouTube IFrame APIを使用するために`index.js`内に下記のコードを挿入し、APIを読み込みます。

```javascript:index.js
// YouTube iframeAPIを読み込む
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
```

API Client Libraryを使用するために、`index.html`に下記のコードを挿入し、APIを読み込みます。

```html:index.html
<script src="https://apis.google.com/js/client.js?onload=googleApiClientReady"></script>
```

パラメータに`?onload=javascript関数名`と記述すると、ライブラリファイルの読み込みが完了した直後に、指定した名前の関数が実行されます。
実行したい関数が読み込まれた後じゃないと、関数が見つからず、処理が実行されないため、別途実装したjsファイルは、ライブラリファイルよりも手前で読み込むようにしてください。

###再生リストの取得
```javascript:index.js
/*===================================
 YouTube Data API setting
===================================*/
var pageToken;

//YouTube Data APIに送るパラメータ
var apiOptions = {
  part:'snippet',
  playlistId:'',
  maxResults:REQ_QTY,
  pageToken:''
};

//clientライブラリを使う時に必要なパラメータ
var requestParam = {
  mine:false,
  path:'/youtube/v3/playlistItems',
  params:apiOptions
}

/**
 * google clientライブラリが読み込まれた直後に呼ばれる関数
 * APIキー等の情報を取得し、YouTube Data APIに再生リスト取得のリクエストを送る
 */
function googleApiClientReady(){
  //console.log('googleApiClientReady');
  getJsonData(YT_API_JSON_INFO_URL)
    .then(function(_jsonData){
    apiOptions.playlistId = _jsonData.listId;

    gapi.client.setApiKey(_jsonData.apiKey);
    gapi.client.load('youtube', 'v3', YTApiRequest);
  });
}

/**
 * YouTube Data APIを使って再生リストを取得する
 */
function YTApiRequest(){
  loadingFlg = true;
  if(pageToken){
    apiOptions.pageToken = pageToken;
  }

  var request=gapi.client.request(requestParam);

  request.execute(function(_res) {
    if(_res.error){ //エラーの時
      console.log('error!!!');
    }else{  //リクエスト成功の時
      totalResults = _res.pageInfo.totalResults;
      totalPage = Math.ceil(totalResults / REQ_QTY);
      addContent(_res);
      loadingFlg = false;
    }
  });
}

/**
 * jsonのデータを取得する
 * @param {String} JSONデータのURL
 * @return {$.Deferred}
 */
function getJsonData(_url){
  console.log('getJsonData()開始：'+_url);
  var $dfd = $.Deferred();
  $.ajax({
    url:_url,
    dataType:'json',
    cache:false,
    timeout:15000
  })
    .done(function(_data){
    console.log('json取得成功：'+_url);
    $dfd.resolve(_data);
  })
    .fail(function(_data){
    console.log('json取得error：'+_url);
    $dfd.reject(_data);
  })
    .always(function(_data){
    console.log('getJsonData()終了：'+_url);
  });
  return $dfd.promise();
}
```

API Client Libraryが読み込まれた直後に`googleApiClientReady()`が呼び出されるので、
`googleApiClientReady()`内の`getJsonData()`関数で、APIキーや再生リストのIDなど、別途jsonファイルに持たせていた情報を取得し、
YouTube Data APIのパラメータとして値をセットします。

```javascript
gapi.client.setApiKey(_jsonData.apiKey);
gapi.client.load('youtube', 'v3', YTApiRequest);
```    
上記１行目でAPIキーをセットし、
2行目の第1、第2引数でインポートするライブラリファイルを指定（この場合、youtube Data apiのバージョン3を指定）。
第3引数でライブラリ読み込み完了時にコールバックされる関数を指定します。

```javascript
/**
 * YouTube Data APIを使って再生リストを取得する
 */
function YTApiRequest(){
  loadingFlg = true;
  if(pageToken){
    apiOptions.pageToken = pageToken;
  }

  var request=gapi.client.request(requestParam);

  request.execute(function(_res) {
    if(_res.error){ //エラーの時
      console.log('error!!!');
    }else{  //リクエスト成功の時
      addContent(_res);
      loadingFlg = false;
    }
  });
}
```

`YTApiRequest()`関数を実行してリクエストに成功すると、リクエストで取得した情報を`addContent()`関数に渡し、プレーヤーの読み込み処理を実行します。

###プレーヤーの埋め込み

```javascript:index.js
/**
 * 再生リストの情報からDOMにコンテンツを追加する
 * @param {Object} _jsonData youtubeの再生リストのjsonデータ
 */
function addContent(_jsonData){
  pageToken = _jsonData.nextPageToken != undefined ? _jsonData.nextPageToken : '';
  var insHtmlTag = '';

  for(var i = 0; i < _jsonData.items.length; i++){
    var videoId = _jsonData.items[i].snippet.resourceId.videoId;
    var videoCount = videoIdList.length;
    insHtmlTag = 
      '<article>' +
      '<div class="videoPlayer">' +
      '<div id="' + videoId + '"></div>' +
      '</div>' +
      '<dl>' +
      '<dt class="videoTitle">' +_jsonData.items[i].snippet.title + '</dt>' +
      '<dd class="videoDesc">' + _jsonData.items[i].snippet.description + '</dd>' +
      '</dl>' +
      '</article>';
    $('#videoContainer').append(insHtmlTag);

    ytPlayer[videoCount] = new YT.Player(videoId, {
      height: ytHeight,
      width: ytWidth,
      videoId: videoId,
      events: {
        'onReady' : onPlayerReady,
        'onStateChange': onPlayerStateChange
      }
    });
    videoIdList.push(videoIdList)
  }
  currentPage++;
}

```

上記の`addContent()`関数で、取得した再生リストのjsonをパースして、HTMLタグを生成します。
関数内の下記の部分でYouTube IFrame APIから動画プレーヤーの埋め込みを行っています。

動画のIDはプレーヤーの状態監視のために使用するため、別途配列に格納しておきます。

```javascript
ytPlayer[videoCount] = new YT.Player('YouTube動画のID', {
  height: '動画の高さ（数値）',
  width: '動画の幅（数値）',
  videoId: 'YouTube動画のID',
  events: {
    'onReady' :'プレーヤー読み込み直後に呼ばれる関数',
    'onStateChange': 'プレーヤーの状態が変更になった時に呼ばれる関数（再生・停止等）'
  }
});
videoIdList.push(videoIdList);	//プレーヤーの状態監視用にIDを配列に格納する
```

###プレーヤーの状態監視
[前回](http://qiita.com/chieeeeno/items/67e2a3f7f3cb5afe916c)は、プレーヤーを複数クリックすると、動画が同時再生していました。
同時再生をさせないために、プレーヤーの状態を監視する処理を加えます。

```javascript
/**
 * プレーヤーの状態に変化があった時に実行
 */
function onPlayerStateChange(event) {
  // 各プレーヤーの状態を確認
  for(var i = 0; i < videoIdList.length; i++) {
    var thisState = ytPlayer[i].getPlayerState();
    if( thisState == 1 && ytPlaying == undefined) {
      ytPlaying = i;
    } else if(thisState == 1 && ytPlaying != i) {
      ytStop = ytPlaying;
      ytPlay = i;
    } else {
    }
  }
  // 同時再生があった場合、元々再生していた方を停止する
  if(ytStop != undefined) {
    ytPlayer[ytStop].pauseVideo();
    ytStop = undefined;
  }
  // 現在再生中のプレーヤー番号を保存しておく
  if(ytPlay != undefined) {
    ytPlaying = ytPlay;
    ytPlay = undefined;
  }
}
```

プレーヤーの状態が変更になると、`onPlayerStateChange()`関数が実行されるため、
この中で各プレーヤーの状態を確認し、直前に再生されていたプレーヤーの停止を行う処理を書きます。

###無限スクロール処理

```javascript:index.js
$(function(){
  $(window).bind('scroll', function(e) {
    if(currentPage == totalPage){
      $(window).unbind('scroll');
      return;
    }
    scrollHeight = $(document).height();
    scrollPosition = $(window).height() + $(window).scrollTop();
    if ((scrollHeight - scrollPosition) / scrollHeight <= 0.05){
      //スクロールの位置が下部5%の範囲に来た場合
      if(!loadingFlg){
        YTApiRequest();
      }
    }
  });
});
```
※前回からは、実行する関数名が変わったくらいで、処理自体に大きく変更はありません。

1. スクロールをして、スクロール位置が下部5％の位置に来た時に、再び`addContent()`関数を実行し、DOMの追加処理を行います。
この時、`loadingFlg=true`の時はDOMへ追加する処理を行いません。
2. ページの読み込みを進めて、現在のページが最後のページになった時は、スクロール処理をunbindします。

##課題
GoogleのAPIを使用することで、ajax関連の処理をjQueryから切り離すことができた。
DOM追加やスクロール処理をReact.jsなどの別のライブラリでも書けるようにするのが今後の課題。