Youtubeの好きな動画の好きな部分だけをリスト再生するWebページの作り方

はじめまして。ひょんなことからVtuberにハマり、色々な物を書いている夕星（ゆうづつ）と申します。
この度、推しのプレイリストが欲しいなぁという欲望の結果、簡素ながらもそれなりに動く物が出来たので備忘録を兼ねて記します。

まず完成品がこちらになります。
[加賀美ハヤト「ロックコンピ」プレイヤー｜夕星の長い話置き場](https://fvblog-2434.web.app/list_rock.html)
（にじさんじ所属Vtuber「[加賀美ハヤト](https://twitter.com/H_KAGAMI2434)」の配信から楽曲を抜粋したリストです）

# 概要
構成は静的HTML＋ピュアJavascript（Youtube Iframe API使用）。
ネットに繋がってさえいればローカルでも動作しますので、自分用かWebサイトに載せて仲間内で楽しむ等の用途が想定されます。
なおAPIの利用規約はご自身で最新版をご確認ください。（基本は非営利利用限定の様子？）

動画の再生数は本家にカウントされ、広告も再生されます。
「切り抜き動画では本家に還元されない」「プレイリストだと動画丸ごとしか再生出来ない」「作業BGMとして良い場面だけリピート再生したい」という悩みやニーズに応えてます。
（あと上手くやるとスマホでバックグラウンド再生出来たりもします）


# ソースコード
ほぼ最小構成が以下です（リストが必要無いなら更に削れます）。デザイン等はご自由に調整ください。
かなり雑でDOM構成もやっつけなので、実際に使用する場合はご利用中の環境に合わせて適宜編集推奨です。
<details><summary>少し長いので折り畳んでいます</summary><div>

```HTML
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ページタイトル</title>

    <style type="text/css">
      #playlist {
        background-color: black;
        color: #aaaaaa;
      }
      .playparts {
        padding: 10px;
        cursor: pointer;
      }
      .playparts:not(:last-child) {
        border-bottom: 1px #aaa solid;
      }
      .songtitle {
        color:white;
      }
    </style>
  </head>
  <body>
    <div id="player"></div>
    <div id="playlist"></div>

    <script>
      // 動画リスト
      var playList = [
        {videoId:'',startSeconds:0, endSeconds:0, title:'', artist:''},
      ];

      // Iframe APIの準備
      var tag = document.createElement('script');
      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      // プレイヤーの設定
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360',
          width: '640',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // プレイヤーの準備完了後の処理（最初の動画のセット）
      var isReady = false;
      var nowPlaying = 0;
      var isNextVideo = false;
      function onPlayerReady(event) {
        player.cueVideoById(playList[nowPlaying]);
        document.getElementById("song"+nowPlaying).style.color = 'red';
        isReady = true;
      }

      // プレイヤーの状態が変化した時の処理（次の動画へ進む）
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.ENDED && !isNextVideo) {
          document.getElementById("song"+nowPlaying).style.color = 'white';
          nowPlaying += 1;
          if (playList.length <= nowPlaying) nowPlaying = 0;
          isNextVideo = true;
          player.loadVideoById(playList[nowPlaying]);
          document.getElementById("song"+nowPlaying).style.color = 'red';
        } else if (event.data == YT.PlayerState.PLAYING && isNextVideo) {
          isNextVideo = false;
        }
      }

      // リストの表示
      function showList () {
        var parent = document.getElementById("playlist");
        for (let i = 0; i < playList.length; i++) {
          // 動画の時間計算
          var min = Math.floor((playList[i].endSeconds - playList[i].startSeconds) / 60);
          var sec = ( '00' + (playList[i].endSeconds - playList[i].startSeconds) % 60 ).slice( -2 );

          // 要素を記述
          var parts = document.createElement("div");
          parent.appendChild(parts);
          var html = '<p><span class="songtitle" id="song'+i+'">';
          html += playList[i].title;
          html += '</span><span class="time">（'+min+':'+sec+'）</span></p><p class="artist">';
          html += playList[i].artist;
          html += '</p>';
          parts.innerHTML += html;

          parts.classList.add("playparts")
          parts.addEventListener("click", () => { pushList(i+""); }, false);
        }
      }
      showList();

      // リストをクリックされた時の処理
      function pushList (id) {
        if (!isReady) return;
        document.getElementById("song"+nowPlaying).style.color = 'white';
        nowPlaying = Number(id);
        player.loadVideoById(playList[nowPlaying]);
        document.getElementById("song"+nowPlaying).style.color = 'red';
      }
    </script>
  </body>
</html>
```
</div></details>

# 動画リストの設定
`playList`の中にオブジェクトを突っ込みます。
`videoId`, `startSeconds`, `endSeconds` はAPIで必要な情報なのでプロパティ名変更不可。
それ以外はリストで表示する為の情報なのでお好きに変更可能です。

|プロパティ名|内容|
|:-----------|:------------|
|videoId|動画のID。動画のURLに含まれています。（もしURLが「`https://youtu.be/fGvYwVW38mc`」なら「fGvYwVW38mc」がIDです）|
|startSeconds|開始地点の秒数。|
|endSeconds|終了時点の秒数。動画まるごとの場合は「0」指定でも動きます。|

以下、設定例です。

```javascript
var playList = [
  {videoId:'fGvYwVW38mc',startSeconds:0, endSeconds:262, title:'WITHIN', artist:'加賀美ハヤト'},
  {videoId:'SSY5z5vUgHA',startSeconds:462, endSeconds:616, title:'どうにもとまらない', artist:'9mm Parabellum Bullet（山本リンダ）'},
  {videoId:'Wq8i7lJVYGw',startSeconds:1062,endSeconds:1330, title:'ソラニン', artist:'ASIAN KUNG-FU GENERATION'},
  {videoId:'MpBnxbMzaXA',startSeconds:0,endSeconds:260, title:'シュガーソングとビターステップ（with SMC組）', artist:'UNISON SQUARE GARDEN'}
];
```

# その他技術的な話
基本的に全部公式リファレンスに書いてあります。というか半分ここのコピペです。
[iframe 組み込みの YouTube Player API リファレンス  |  YouTube IFrame Player API](https://developers.google.com/youtube/iframe_api_reference?hl=ja)

但し動画のデータがオブジェクト型でないとendSecondsが使えません。上記ページのコピペだと最初の動画をオブジェクト型で渡せませんでした。
その為に`new YT.Player`時点では動画を指定せず、`onPlayerReady`で`cueVideoById`を使っています。
今回は開始時にワンクリック挟みたかったのでわざと`cueVideoById`としていますが、`loadVideoById`にすると勝手に再生が始まります。

（2020/06/18追記）
一部の動画について、「この動画は再生できません」と動画表示領域に出て再生が不可能になる不具合が発生しています。（私の環境で確認する限り、初日は再生出来ていたのに翌日この状態になった動画があります）
恐らくYoutube側の問題だと思う（流行病の影響によるバグ？）ので現状様子見しています。
もし上記コードを使って該当の不具合が発生した場合、申し訳ありませんがその動画はplayListから除外して頂ければ幸いです。（原因等をご存知の方は情報お待ちしております！）

# 最後に
分かってしまえばとても手軽に使えますので、自分の推しのも作りたい！と思った方はご参考にして頂けるととても嬉しいです。
（[Twitter](https://twitter.com/yudutu_ps)で「使ったよ！」と一声頂けたら喜び勇んで見に行きます）