自宅で夏の散歩ができる「夏休み君」をYoutube Player APIで作ってみた

#作業用BGM多すぎ長すぎィ！
皆さんは何か作業に没頭するときに、__作業用BGM__をかけたりしますか？
人によっては音楽をかけたり、人によってはYoutubeをただ流したり、ある人は耳栓をして無音で頑張ったり、色々なスタイルがあると思います。

今年はコロナの影響で、夏のイベントが何から何まで無くなってしまいました。
外出できなくても__夏気分を味わいたいなあ__と思い、__youtubeでフリー環境音BGM__を探して流してみようと考えてみました。
森の音、海の音、川の音、虫の音、、、などなどいろいろあるようですが、どうせなら一つのBGMをずっと聞くより__色んなBGMを聞きたいなあ__と思い、Youtube Player APIを使って実装してみました。

Webアプリはデプロイしてあるので、お散歩気分でお楽しみください。

コードは[こちらから！](https://gist.github.com/canonno/4b8e7cbc58dcfe8952870ed96133d43d)
アプリは[こちらから！](https://cranky-nobel-9a6c3c.netlify.app/hoshizora.html)

#完成デモ
##基本
「森や雨の音」と「川の音」というように__基本的に2つの音源を同時に流しています__。
音源は5分おきにランダムで切り替わり、__時々雨も降ったりもします__。
実は__音量のバランスも毎分変わっている__ので、同じ組み合わせでも聞こえ方が変わったり、川の音がしたとしても天気が違うなど、何回聞いても楽しめる内容にしています。

シーンを「星空」「花火」「川」の3つ用意しており、__左上の「星空を見る」「花火を歩く」「川へ歩く」をクリックして移動できます__。
好きなシーンに合わせて__「耳をすます」をクリックするとBGMが再生されます__。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">夏のお散歩ができるWebアプリ「夏休み君」作りました！<br>「星空」「花火」「川」の3つのモードがあり作業用BGMが再生されます。<br>同じ「星空」でも5分間隔でBGMが切り替わるので、ときどき雨が降ってきたりします。<br>夏の散歩感覚でお楽しみください。<a href="https://t.co/y4ZcUIv4yo">https://t.co/y4ZcUIv4yo</a><a href="https://twitter.com/hashtag/javascript?src=hash&amp;ref_src=twsrc%5Etfw">#javascript</a> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/Ml05zyKKR0">pic.twitter.com/Ml05zyKKR0</a></p>&mdash; canonno (@canonno_blog) <a href="https://twitter.com/canonno_blog/status/1295950699499409409?ref_src=twsrc%5Etfw">August 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


##星空を見る
星空の背景を眺めながら、夏の音を楽しめます。
25％の確率で雨が降ります。
夏の夜独特の雰囲気を味わってみてください。

[星空を見に行く](https://cranky-nobel-9a6c3c.netlify.app/hoshizora.html)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">星空(晴れ)です。<br>25％の確率で雨が降ります。<a href="https://twitter.com/hashtag/javascript?src=hash&amp;ref_src=twsrc%5Etfw">#javascript</a> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/html?src=hash&amp;ref_src=twsrc%5Etfw">#html</a> <a href="https://t.co/cRD3qKRAl3">pic.twitter.com/cRD3qKRAl3</a></p>&mdash; canonno (@canonno_blog) <a href="https://twitter.com/canonno_blog/status/1295951438653267968?ref_src=twsrc%5Etfw">August 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

##花火を見る
夏と言えば花火！
花火大会独特の雰囲気を味わってください。
花火の日なので雨は降らず、背景の虫の音声が変わるようになっています。
花火は__結構激しい__です。

[花火を見に行く](https://cranky-nobel-9a6c3c.netlify.app/hanabi.html)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">花火です。<br>花火モードには雨は実装していません。<a href="https://twitter.com/hashtag/javascript?src=hash&amp;ref_src=twsrc%5Etfw">#javascript</a> <a href="https://twitter.com/hashtag/html?src=hash&amp;ref_src=twsrc%5Etfw">#html</a> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/my9zCP939O">pic.twitter.com/my9zCP939O</a></p>&mdash; canonno (@canonno_blog) <a href="https://twitter.com/canonno_blog/status/1295951734766936065?ref_src=twsrc%5Etfw">August 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


##川へ歩く
夏の川遊びも楽しいですよね。
川の音と自然の雰囲気を楽しんでください。
こちらも33％の確率で雨が降ります。

[川へ遊びに行く](https://cranky-nobel-9a6c3c.netlify.app/kawa.html)

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">川歩き(晴れ）です<a href="https://twitter.com/hashtag/html?src=hash&amp;ref_src=twsrc%5Etfw">#html</a> <a href="https://twitter.com/hashtag/javascript?src=hash&amp;ref_src=twsrc%5Etfw">#javascript</a> <a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://t.co/eK64NzxQbl">pic.twitter.com/eK64NzxQbl</a></p>&mdash; canonno (@canonno_blog) <a href="https://twitter.com/canonno_blog/status/1295950970732441602?ref_src=twsrc%5Etfw">August 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


#実装こまごま
##htmlとcssの実装
全体としては
・3つのhtml：花火・川・星空、各シチュエーションにあったBGM再生
・1つのcss：ボタンのデザイン
という構成になっています。

htmlは基本的には背景にそれっぽい画像を表示する以外はしておらず、インラインのJavascriptでメインの処理を担っています。
途中のid=player1 id=player2のdivタグ部分にYoutubePlayerが挿入され音声が再生されます。
今回は__縦横0pxのplayerを用意し、音は流してplayer本体は隠すように__しています。

```html
<html>
  <head>
    <link rel="stylesheet" href="image-button.css">
  </head>
  <title>
    夏休み君
  </title>

  <style>
    body {
    background-image: url('hoshizora.jpg');
    background-size: 100% auto;
    }
  </style>
    <body>
    <a href="hanabi.html" class="btn-cross">花火を見る</a>
    <a href="kawa.html" class="btn-cross">川へ歩く</a>
    <a class="btn-cross" onclick="startSound()">耳を澄ます</a>

    <div id="player1"></div>
    <div id="player2"></div>

    <script>
    //ここに処理を記述する
    </script>
  </body>
</html>
```

cssはこちら。
何かおしゃれなcssは無いかなあと思い探した結果、良さそうなのがあったのでお借りいたしました。
[こちらのサイト](https://saruwakakun.com/html-css/reference/buttons)が優秀なので是非是非ご覧になってください！

```css
.btn-cross {
    display: inline-block;
    position: relative;
    padding: 0.25em 1em;
    border-top: solid 2px rgb(206, 206, 206);
    border-bottom: solid 2px rgb(206, 206, 206);
    text-decoration: none;
    font-weight: bold;
    color: #9ee0ff;
  }
  .btn-cross:before, .btn-cross:after {
    content: '';
    position: absolute;
    top: -7px;
    width: 2px;
    height: -webkit-calc(100% + 14px);
    height: calc(100% + 14px);
    background-color: rgb(206, 206, 206);
    transition: .3s;
  }
  .btn-cross:before {
    left: 7px;
  }
  .btn-cross:after {
    right: 7px;
  }
  .btn-cross:hover:before {
    top: 0px;
    left:0;
    height: 100%;
  }
  .btn-cross:hover:after {
    top: 0px;
    right: 0;
    height: 100%;
  }


  .btn-cross2 {
    display: inline-block;
    position: relative;
    padding: 0.25em 1em;
    border-top: solid 2px rgb(182, 242, 247);
    border-bottom: solid 2px rgb(182, 242, 247);
    text-decoration: none;
    font-weight: bold;
    color: #cbf1f8;
  }
  .btn-cross2:before, .btn-cross2:after {
    content: '';
    position: absolute;
    top: -7px;
    width: 2px;
    height: -webkit-calc(100% + 14px);
    height: calc(100% + 14px);
    background-color: rgb(182, 242, 247);
    transition: .3s;
  }
  .btn-cross2:before {
    left: 7px;
  }
  .btn-cross2:after {
    right: 7px;
  }
  .btn-cross2:hover:before {
    top: 0px;
    left:0;
    height: 100%;
  }
  .btn-cross2:hover:after {
    top: 0px;
    right: 0;
    height: 100%;
  }

```
これで、マウスを合わせると枠が広がるボタンを実装できます。
是非是非お試しください！
![button.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/666990/255ab655-58db-b471-9d81-5730afd74ced.png)

##Youtube Playerの埋め込み
今回はYoutube Player APIを利用しました。
[ドキュメントサイト](https://developers.google.com/youtube/iframe_api_reference?hl=ja)を参考に処理を実装しています。
Youtube Player APIの読み込みが始まると同時にonYoutubeIframeAPIReady()メソッドが走ります。

```js
      //おまじない
      var tag = document.createElement('script');
      tag.src = "https://www.youtube.com/iframe_api";
      var firstScriptTag = document.getElementsByTagName('script')[0];
      firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

      //YoutubePlayerを埋め込む処理
      var player1; //川の音,海の音
      var player2; //雨や虫、森の音など
      
      //読み込んだ際に最初に走る処理
      function onYouTubeIframeAPIReady() {
        //player1の定義
        player1 = new YT.Player('player1', {
          height: '0',
          width: '0',
          videoId:"",
          events: {
            'onReady': onPlayerReady1,
          }
        });

        //player2の定義
        player2 = new YT.Player('player2', {
          height: "0",
          width: '0',
          videoId:"",
          events: {
            'onReady': onPlayerReady2,
          }
        });
      }
```

##ランダム再生
Playerの準備が完了すると、eventsのonReadyが発火し、onPlayerReady1とonPlayerReady2が走ります。
ここでPlayerに再生リストをセットし、シャッフル設定をします。
Youtubeの動画は自力で聞いて選抜したものを入れています（大変でした）。

```js

      //Youtubeのビデオリスト
      var videolist = [
          ['p2st1-AHa5Y','_FDWgFrsSVA','xAL1rVb4EG4'], //花火
          ['N3mnyBh8y5A','2PC2Oh0vqb0','hnqk8k6a_Cw'],　//虫、川と虫、虫
          ]
      //PlayerがReadyになったときに走る処理
      function onPlayerReady1(event) {
        //音量をセット
        player = event.target
        player.setVolume(p1vol);

        //Playlistをセットして、シャッフル再生
        videolength = videolist[0].length
        player.cuePlaylist(videolist[0],Math.floor(Math.random()*videolength));
        player.setShuffle(true);
        player.playVideo();
      }
      
      //PlayerがReadyになったときに走る処理
      function onPlayerReady2(event) {
        //音量をセット
        player = event.target
        player.setVolume(p2vol);
        player.setShuffle(true); 

        //Playlistをセットして、シャッフル再生 
        videolength = videolist[0].length
        player.cuePlaylist(videolist[1],Math.floor(Math.random()*videolength));
        player.setShuffle(true);
        player.playVideo();
      }

```
そして「耳をすます」ボタンをクリックすると、仕込ませたstartSound()メソッドを実行します。
player1・player2ともに15秒ずつ音量を変え、音量0になったら次の動画へ移ります。
両方が同時に音量0にならないよう、player1で音量が下がっている時はplayer2で音量が上がるように仕込んでおります。

```js
      //音量と音量の変化量を定義
      maxvol = 20;
      p1vol = 6;
      dp1vol = -2;
      p2vol = 16;
      dp2vol = 2;
      //volumeを一段階変える時間
      var timeToChangeVolume = 15; //秒

      function startSound(){
        player1.playVideo();
        player2.playVideo();
        setInterval(gradientChange,timeToChangeVolume*1000);
      }

      function gradientChange(){
        //音量を変える
        p1vol += dp1vol;
        player1.setVolume(p1vol)

        //音量がゼロになったら次の動画へ
        if (p1vol == 0){
          dp1vol = -1 * dp1vol;
          moveNext(player1);
        }else if (p1vol == maxvol){

          //音量がmaxvolになったら音量を減らすように変更
          dp1vol = -1 * dp1vol;
        }

        //音量を変える
        p2vol += dp2vol;
        player2.setVolume(p2vol)

        //音量がゼロになったら次の動画へ
        if (p2vol == 0){
          dp2vol = -1 * dp2vol;
          moveNext(player2);
        }else if (p2vol == maxvol){
          //音量がmaxvolになったら音量を減らすように変更
          dp2vol = -1 * dp2vol;
        }
      }

      //次の動画へ映る
      function moveNext(player){
        player.setShuffle(true);
        player.nextVideo();
        player.playVideo();
      }
```

本当は画面遷移したら自動再生されるような実装にしたかったです。
ただ、なぜか時々再生されて時々再生されない不具合が起き、今回はbuttonでの再生としました。
読み込み完了のタイミングなどが何か関連しているんでしょうか・・・？もしご存知の方いらっしゃいましたらコメントいただけると嬉しいです。

コード全体はGist([こちらから](https://gist.github.com/canonno/4b8e7cbc58dcfe8952870ed96133d43d)）に載せてあるので、是非是非ご覧になってくださいね。


#今後やりたいこと
Youtube Player API面白い・・・結構なんでもできますし夢があるAPIだなと思いました。
前回の記事「[光センサで遊ぶ「かくれんぼ」をobnizとp5jsで作ってみた](https://qiita.com/canonno/items/00738c7d928c3ec655d5)」では背景の色を時間経過とともに少しずつ変える処理を実装しました。
今回はその処理を音量に使ってみるなど、自分としても色々技術の応用ができて楽しい実装でした。

最後までご覧いただきありがとうございました！
LGTMつけていただけると励みになります、よろしくお願いいたします！

↓こちらも合わせてご覧ください！
◇[結局Qiita記事ってどれぐらい書けばいいのさ](https://qiita.com/canonno/items/57f917147b3d6ae106e9)：
　Qiita APIから記事の情報を取って分析してみました！
◇[入社一年間で学んだ「DX」「UX」を整理してみたい](https://qiita.com/canonno/items/fecf2c76529c55d5e15d)：
　社会人2年目になったので1年間の振り返りをしました！
◇[男「君の死神の目は、どれぐらい顔が見えればいいんだい」女「私の目cloud visionなの」](https://qiita.com/canonno/items/8ab4da52c6fe7816e048)：
　cloud vision apiの顔認識の精度検証をしました！