Youtube APIで歌詞乗っ取りゲーム風のWEBアプリ「ノットリチューブ」を作成しました

今回もiframe 組み込みの YouTube Player API リファレンスを参考にして簡単なWEBアプリケーションを作ってみました。

 

その前に。。。

歌詞乗っ取りゲームというのはご存知でしょうか?

通常のしりとりの歌を歌いながら行うゲームで、相手が歌っているフレーズや単語を乗っ取って別の曲のフレーズを歌うというゲームです。

 

以下が歌詞乗っ取りゲームの動画です。

上記の様なゲームをYoutube APIのプレイリスト機能にタイマー処理を加えれば出来るのではと思い作ってみました。

 

名付けて

「ノットリチューブ」

………………….,。

 

相変わらずのネーミングセンスの無さは置いといて以下が作成したペラアプリとなります。
ノットリチューブ – Youtube動画で歌詞乗っ取り

 

アプリ内容

10秒間再生される追加済みのプレイリストのフレーズにあった動画を探し、フレーズが繋がる様に探した曲をプレイリストにどんどん追加していくアプリです。

 

フレーズを合わせは、Youtube動画の開始位置を調整する様にしています。

このアプリを使うと以下の様な歌詞乗っ取りゲーム風のプレイリストが作成できます。

安室奈美恵のHEROからスタート

 

 

アプリのソース

いつもの様にフロントはbootstrap、Jquery、Javascript、サーバーサイドはPHPです。

<?php
        //HTMLエンティティに変換
    function h($value){
        return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
    }
?>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="description" content="Youtube動画で歌詞乗っ取りゲーム!動画を再生すると曲が10秒間再生されるので、同じフレーズの別の動画を探してプレイリストに追加していくアプリです。SNSで共有して繋がる動画をみんなで探そう">
    <!-- 表示領域を端末や ブラウザに合わせ、初期倍率を1に設定 -->
    <meta name="viewport" content="width=device-width,initial-scale=1.0">


    <!-- flatly bootstrap css  -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/flatly/bootstrap.min.css">
    <?php if(empty($_GET['title'])): ?>
        <title>ノットリチューブ - Youtube動画で歌詞乗っ取り</title>
    <?php else: ?>
        <title><?php echo h($_GET['title']) ?> - ノットリチューブ</title>
    <?php endif; ?>
</head>
<body>
<!-- ソーシャルボタン用 facebook sdk -->
<div id="fb-root"></div>
<script>(function(d, s, id) {
  var js, fjs = d.getElementsByTagName(s)[0];
  if (d.getElementById(id)) return;
  js = d.createElement(s); js.id = id;
  js.src = 'https://connect.facebook.net/ja_JP/sdk.js#xfbml=1&version=v2.12&appId=910672038986001&autoLogAppEvents=1';
  fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));</script>

<div class="navbar navbar-default navbar-fixed-top">
    <div class="container">
        <div class="navbar-header">
            <a href="/app/nottoritube.php" class="navbar-brand">ノットリチューブ</a>
        </div>
        <div class="navbar-collapse collapse">
            <ul class="nav navbar-nav">
                <li><a href="#">未知の曲に出会える</a></li>
            </ul>
        </div>
    </div>
</div>
<div class="container" style="margin-top:100px;">
    <div class="row">
        <div class="col-lg-12">
        <form method="get">

              <div class="panel panel-default">
                <div class="panel-heading text-center">
                    <h4>Youtube動画で歌詞乗っ取り!SNSで共有してみんなで曲を繋げよう</h4>
                    <?php
                        // スマホ判定用にユーザエージェントを取得
                        $ua=$_SERVER['HTTP_USER_AGENT'];
                    ?>
                    <?php if((strpos($ua, 'iPhone') !== false) || (strpos($ua, 'iPod') !== false) ||(strpos($ua, 'Android') !== false)): ?>
                        (スマホではうまく再生されない場合があります)
                    <?php endif; ?>
                </div>
                <div class="panel-body text-center">
                <!-- YoutubeのIFrameを以下(idがplayer)の要素に設定する -->
                    <div id="player" class="hide"></div>
                </div>
                <?php if(!empty($_GET['s-msg']) && !empty($_GET['p']) && empty($_GET['cancel'])): ?>
                    <div class="panel-body after-set">
                        <div class="alert alert-dismissible alert-success text-center ">
                            <button type="button" class="close" data-dismiss="alert">×</button>
                            <strong>プレイリストに設定されました。このプレイリストを共有しよう</strong>
                            <div class="panel-body">
                                <button class="btn btn-default" id="share" type="button"><span class="glyphicon glyphicon-share-alt"></span> 共有</button>
                            </div>
                        </div>
                    </div>
                <?php endif; ?>
                <!-- プレイリストが設定されている場合に表示 -->
                <?php if(!empty($_GET['p'])): ?>
                    <div class="panel-body text-center">
                        <button class="btn btn-primary after-set" id="add" type="button"><span class="glyphicon glyphicon-plus"></span> ノットる</button>
                    </div>
                <?php endif; ?>
                <!-- プレイリストが未設定の場合に初期表示 -->
                <div class="condition" style="<?= !empty($_GET['p'])?'display:none;':'' ?>">
                    <div class="panel-body">
                        <div class="alert alert-dismissible text-center alert-warning message">
                            <button type="button" class="close" data-dismiss="alert">×</button>

                            <?php if(empty($_GET['p'])): ?>
                            <!-- プレイリストが未設定の場合 -->
                                <h4><strong>プレイリストを作成します</strong></h4>
                                <p>・最初の曲(YoutubeのURL)を指定してください。</p>
                                <p>・開始位置を調整して次の曲に繋がるフレーズになる様にしましょう。</p>
                                <p>・「設定」ボタンをクリックするとプレイリストに追加されます。</p>
                            <?php else: ?>
                            <!-- プレイリストが設定済みの場合 -->
                                <?php if(!empty($_GET['title'])): ?>
                                    <h4 class="text-center"><strong>お題:<?php echo h($_GET['title']) ?></strong></h4>
                                <?php else: ?>
                                    <h4><strong>プレイリストに追加します</strong></h4>
                                    <p>・最後のプレイリストのフレーズに繋がる曲を探して開始位置を調整してください。</p>
                                <?php endif; ?>
                                <p>・URL、開始位置指定後、「設定」ボタンをクリックするとプレイリストに追加されます。</p>
                            <?php endif; ?>
                            <p>・動画は指定した開始位置から約10秒間再生するよう設定されます。</p>
                        </div>
                        <!-- 調整ボタンがクリックされた場合に表示 -->
                        <div class="alert alert-dismissible alert-danger customize" style="display:none;">
                            <button type="button" class="close" data-dismiss="alert">×</button>
                            <strong>調整中</strong>
                        </div>
                    </div>
                    <div class="panel-body customize" style="display:none;">
                        <h4>
                            <i class="glyphicon glyphicon-info-sign"></i>
                             調整する曲を選択してください
                        </h4>
                        <select class="form-control" id="number">
                            <?php if (isset($_GET['p'])){
                                $lists=explode(',',$_GET['p']);
                            }else{
                                $lists=[];
                            } ?>
                            <?php for($i=0;$i<count($lists);$i++): ?>
                                <option value="<?php echo $i ?>"><?php echo ($i+1) ?>曲目</option>
                            <?php endfor ?>
                        </select>
                    </div>
                    <div class="panel-body">
                        <h4>
                            <i class="glyphicon glyphicon-flag"></i>
                             1.Youtube動画のURLを指定(コピペ)してください
                        </h4>
                        <div class="input-group">
                            <span class="input-group-btn">
                            <!-- youtubeサイトのリンク先を指定 -->
                                <a href="https://www.youtube.com/channel/UC-9-kyTW8ZkZNDHQJ6FgpwQ/featured" class="btn btn-info" target="_blank">Yotube動画 <span class="glyphicon glyphicon-new-window"></span></a>
                            </span>
                            <input class="form-control" id="youtube-url">
                        </div>
                    </div>
                    <div class="panel-body">
                        <h4>
                            <i class="glyphicon glyphicon-flag"></i>
                             2.開始位置(秒)を指定してください。指定した開始位置から約10秒間曲が再生されます。
                            <span class="glyphicon glyphicon-question-sign" data-toggle="tooltip" data-html="true" title="<p>・小数の指定も可能です<br>例)18.5</p><p>・動画を確認中に「秒取得」<br>をクリックすると閲覧中動画の経過時間(秒)を取得し設定します。</p><p>最後まで再生したい場合は開始時間を空白にして「確認再生」を行なってください。</p>"></span>
                        </h4>
                        <div class="input-group">
                            <span class="input-group-btn">
                                <button class="btn btn-info sec-set" <?= empty($_GET['p'])?'disabled':'' ?> type="button" id="sec-set">秒取得 <span class="glyphicon glyphicon-plus-sign"></span></button>
                            </span>
                            <input class="form-control" id="start">
                        </div>
                    </div>
                    <!-- 現状非表示
                    <div class="panel-body title-section hide <?php echo empty($_GET['p'])?'':'hide' ?>">
                        <h4>
                            <i class="glyphicon glyphicon-flag"></i>
                             3.お題(任意入力)
                        </h4>
                        <input name="title" class="form-control" id="start" value="歌詞乗っ取りゲーム">
                    </div>
                     -->
                    <div class="panel-body text-center">
                        <button class="btn btn-sm btn-success" id="confirm" type="button"><span class="glyphicon glyphicon-play"></span> 確認再生</button>
                        <button class="btn btn-sm btn-success" id="set" type="button"><span class="glyphicon glyphicon glyphicon-ok"></span> 設定する</button>
                        <button class="btn btn-sm btn-danger customize hide" id="delete" type="button"><span class="glyphicon glyphicon-remove"></span> 削除</button>
                        <button class="btn btn-sm btn-default" style="display:none;" name="cancel" value="1" id="cancel" type="submit"><span class="glyphicon glyphicon-repeat"></span> 取り消し</button>
                    </div>
                </div>

                <?php if(!empty($_GET['p'])): ?>
                    <div class="panel-body text-right footer-btn">
                        <button class="btn btn-xs btn-default" id="play" type="button"><span class="glyphicon glyphicon glyphicon-play"></span> 1曲目から再生</button>
                        <button class="btn btn-xs btn-default" id="stop" type="button"><span class="glyphicon glyphicon glyphicon glyphicon-stop"></span> 停止</button>
                        <button class="btn btn-xs btn-default" id="customize" type="button"><span class="glyphicon glyphicon glyphicon-cog"></span> 調整</button>
                        <button class="btn btn-xs btn-info" id="share" type="button"><span class="glyphicon glyphicon-share-alt"></span> 共有</button>
                    </div>
                <?php endif; ?>
                <input type="hidden" name="p" id="play-list" value="<?php echo empty($_GET['p'])?'':h($_GET['p']) ?>">
                <input type="hidden" name="s" id="start-list" value="<?php echo empty($_GET['s'])?'':h($_GET['s']) ?>">
                <input type="hidden" name="i" id="start-list" value="<?php echo empty($_GET['i'])?uniqid():h($_GET['i']) ?>">
                <input type="hidden" name="s-msg" id="share-message" value="1">
            </form>
            <input type="hidden" id="edit-mode" value="<?php echo empty($_GET['p'])?'add':'edit' ?>">
          </div>

        </div>
    </div>
</div>



<?php
    //各ソーシャルボタンのタイトル、URLの設定
    $title='ノットリチューブ';
    $url=$_SERVER["HTTP_HOST"].$_SERVER["REQUEST_URI"];

    //以下のクエリパラメータ削除
    $url=str_replace('&play=1','',$url);
    $url=str_replace('&s-msg=1','',$url);

    $full_url=(empty($_SERVER["HTTPS"]) ? "http://" : "https://").$url
?>
<!-- 共有ボタンクリック時に表示されるダイアログ -->
<div class="modal fade" id="share_box">
  <div class="modal-dialog">
      <div class="modal-content">


      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
        <h4 class="modal-title">リンクを共有</h4>
      </div>
      <div class="modal-body">
        <div class="form-group">
            <input class="form-control" id="link-text" value="<?php echo h($full_url); ?>">
        </div>
        <div class="form-group text-center">
            <ul class="nav nav-pills">
                <li class="list-group-item">
                    <a href="http://b.hatena.ne.jp/entry/<?php $url ?>" class="hatena-bookmark-button" data-hatena-bookmark-layout="vertical-normal" data-hatena-bookmark-lang="ja" title="このエントリーをはてなブックマークに追加"><img src="https://b.st-hatena.com/images/entry-button/button-only@2x.png" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" /></a><script type="text/javascript" src="https://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></script>
                </li>
                <li class="list-group-item">
                    <div class="fb-like" data-href="<?php echo $full_url ?>" data-layout="box_count" data-action="like" data-size="small" data-show-faces="true" data-share="true"></div>
                </li>
                <li class="list-group-item">
                    <a href="https://twitter.com/share" class="twitter-share-button" data-url="<?php echo $full_url ?>" data-text="<?php echo $title; ?>">Tweet</a>
                    <script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
                </li>
                <li class="list-group-item">
                    <div class="line-it-button" data-lang="ja" data-type="share-a" data-url="<?php echo $full_url ?>" style="display: none;"></div>
                    <script src="https://d.line-scdn.net/r/web/social-plugin/js/thirdparty/loader.min.js" async="async" defer="defer"></script>
                </li>
            </ul>
        </div>
      </div>


    </div>
  </div>
</div>




<!-- jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

<!-- bootstrap JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>



<script>

    //--- 非同期でIFrame Player Apiコードを読み込み ---
    //scriptタグを生成
    var tag = document.createElement('script');

    //生成したscriptのソースを設定
    tag.src = "https://www.youtube.com/iframe_api";

    //先頭のscriptタグを取得
    var firstScriptTag = document.getElementsByTagName('script')[0];

    //先頭のscriptタグの前に生成したscriptタグを挿入
    firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);


    //IFrameを作成します。上記APIコードの読み込みが完了後、呼び出される
    var player;
    function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
            width:'100%',
            loop:0,
            events: {
              'onReady': onPlayerReady,
              'onStateChange': onPlayerStateChange
            }
        });
    }

    //上記で設定したプレーヤーの準備ができたときに実行される
    function onPlayerReady(event) {
        <?php if(!empty($_GET['p'])): ?>
            //「play」クエリパラメータが設定されているか判定
            <?php if(empty($_GET['play'])): ?>
                //動画を読み込み再生させない
                cueVideo($('#play-list').val(),false,0);
            <?php else: ?>
                //動画を読み込み再生させる
                cueVideo($('#play-list').val(),1,0);
            <?php endif; ?>
        <?php endif; ?>
    }


    //各変数初期化
    var checkID=0;


    //プレーヤーの状態が変更される度に発生
    function onPlayerStateChange(event) {

        //プレイリストを配列に格納
        var playList=$('#play-list').val();
        var playArr=playList.split(',');
        //現在の再生中のプレイリストのインデックスを取得
        var curIdx = player.getPlaylistIndex();
        if (curIdx==-1){
        //確認再生の場合
            var startList = $('#start').val();
            curIdx=startList?0:-1;
        }else{
        //プレイリスト再生
            var startList=$('#start-list').val();
        }
        //開始位置を配列に格納
        var startArr=startList.split(',');
        var start = startArr[curIdx];

        //画面上の設定項目に現在のプレイリスト情報を反映
        if($('#edit-mode').val()=="edit"){
            $("#number").val(curIdx);
            $("#youtube-url").val('https://www.youtube.com/watch?v='+playArr[curIdx]);
            $("#start").val(start);
        }
        if (event.data == YT.PlayerState.PLAYING && !isNaN(start)) {


            //現在の再生中の経過時間が設定した開始時間と一致していなくて動画設定が非表示の場合、開始位置を設定
            if (Math.floor(start) != Math.floor(player.getCurrentTime())){
                player.seekTo(start);
                return;
            }


            //タイマー処理により再生中の動画を監視
            clearTimeout(checkID);
            checkID=setTimeout(function(){
                if(player.getPlayerState()==YT.PlayerState.PAUSED || player.getPlayerState()==YT.PlayerState.ENDED){
                    //動画が一時停止、または終了した場合、タイマー処理をクリア
                    clearTimeout(checkID);
                    return;
                }
                if (startArr.length-1 > curIdx){
                    //プレイリストが存在する場合、次の動画を読み込み再生
                    player.nextVideo();             //
                }else{
                    //プレイリストが存在しない場合、一時停止
                    player.pauseVideo();
                }


            }, 10000);
        }
    }

    //動画の読み込み、再生
    function cueVideo(playList,play,startIndex){

        if(!playList){
            alert('再生対象の動画が存在しません');
            return false;
        }


        $('#player').removeClass('hide');
        $('#sec-set').prop('disabled',false);

        if (play=='1'){
        //最初から再生。プレイリストを最初から再生
            player.loadPlaylist(playList,startIndex,  0, "large");
        }else if (play=='2'){
        //確認再生。指定動画だけ再生
            player.loadVideoById(playList,  0, "large");
        }else{
        //プレイリストを読み込み
            player.cuePlaylist(playList,startIndex, 0, "large");
        }

    }

    //keyで指定したクエリパラメータ取得
    function getQueries(key,url){
        var query = {},
            queries = url.slice(url.indexOf('?') + 1).split('&'), //クエリパラメーターを&で区切り配列に格納
            i = 0;

        //パラメータ名を配列の添え字にし、各パラメータ値を配列に格納
        for(i; i < queries.length; i ++) {
            var t = queries[i].split("=");
            query[t[0]] = t[1];
        }

        //keyで指定されたクエリパラメータを返す
        return (query === null ? null : query[key] ? query[key] : null);

    }

    //入力チェック
    function doValidation(confirm){
        var v=getQueries('v',$('#youtube-url').val());
        if((!$('#youtube-url').val() || !v) && $('.condition').is(':visible')){
            alert('YoutubeのURLを正しく指定してください');
            return false;
        }

        if(isNaN($('#start').val()) || (!$('#start').val() && !confirm && $('.condition').is(':visible'))){
            alert('開始位置(秒)を正しく指定してください');
            return false;
        }

        if(!$('#number').val() && $('.customize').is(':visible')){
            alert('調整する曲を選択してください');
            return false;
        }

        return true;
    }


    $(function () {
        //bootstrapのツールチップ。「?」マーククリック用
        $('[data-toggle="tooltip"]').tooltip({
            container:'body'
        });


        //「秒取得」クリック時に現在の再生経過時間を設定する
        $('#sec-set').on('click',function(e) {
            var current=player.getCurrentTime();
            $('#start').val(current.toFixed(1));

        });


        //「ノットる」クリック時に設定項目を開閉する
        $('#add').on('click',function(e) {
            //モードを追加モードに設定
            $('#edit-mode').val('add');

            //設定項目をクリア
            $('#youtube-url').val('');
            $('#start').val('');

            $('.after-set').hide();   //ノットるボタン非表示
            $('.footer-btn').hide();   //下部のボタン非表示
            $('#cancel').show();   //キャンセルボタン表示
            $('#sec-set').prop('disabled',true);   //秒取得を使用不可
            $('.condition').slideDown();  //上から下にスライドして開く
        });
        //「確認」クリック時
        $('#confirm').on('click',function(e) {
            //入力チェック
            if (!doValidation('confirm')){
                return;
            }

            var v=getQueries('v',$('#youtube-url').val());
            cueVideo(v,2,0);

        });

        //「曲」選択時
        $('#number').on('change',function(e) {
            cueVideo($('#play-list').val(),0,$(this).val());

        });


        //「設定」クリック時
        $('#set').on('click',function(e) {
            //入力チェック
            if (!doValidation()){
                return;
            }

            if(!window.confirm('プレイリストに設定します。よろしいですか?\n設定後も動画、開始位置は調整できます。')){
                return;
            }
            //プレイリストを配列に格納
            var list=$('#play-list').val();
            var listArr=list.split(',');

            //開始位置を配列に格納
            var start=$('#start-list').val();
            var startArr=start.split(',');

            //videoID取得
            var v=getQueries('v',$('#youtube-url').val());
            if ($('#edit-mode').val()=='add'){
                //「乗っ取る」設定時
                if (list){
                    //プレイリストが存在する既存の設定値に追加
                    $('#play-list').val(list+','+v);
                    $('#start-list').val(start+','+$('#start').val());
                }else{
                    //プレイリストが存在しない場合
                    $('#play-list').val(v);
                    $('#start-list').val($('#start').val());
                }
            }else{
                //調整時、選択した曲(インデックス番号)を配列に設定
                var index = $('#number').val();;
                listArr[index]=v;
                startArr[index]=$('#start').val();

                //配列の要素をカンマ区切りの文字列にし設定
                $('#play-list').val(listArr.join(','));
                $('#start-list').val(startArr.join(','));
            }

            //フォームサブミット(URLにクエリパラメーターを設定させる)
            $('form').submit();
        });

        //「削除」クリック時
        $('#delete').on('click',function(e) {
            if (!doValidation()){
                return;
            }

            var index = $('#number').val();;
            if(!window.confirm(Number(index)+1 + '曲目を削除します。よろしいですか?')){
                return;
            }

            //プレイリストを配列に格納
            var list=$('#play-list').val();
            var listArr=list.split(',');

            //開始位置を配列に格納
            var start=$('#start-list').val();
            var startArr=start.split(',');

            //現在のプレイリストインデックスを取得し配列に設定
            listArr.splice(index,1);
            startArr.splice(index,1);

            //配列の要素をカンマ区切りの文字列にし設定
            $('#play-list').val(listArr.join(','));
            $('#start-list').val(startArr.join(','));


            //フォームサブミット(URLにクエリパラメーターを設定させる)
            $('form').submit();
        });

        //「再生」クリック時に入力チェックを行い、動画リストを最初から再生する
        $('#play').on('click',function(e) {
            if (!doValidation()){
                return;
            }

            var list=$('#play-list').val();
            var lists=list.split(',');

            clearTimeout(checkID);
            cueVideo($('#play-list').val(),1,0);

        });

        //「停止」クリック時に動画を停止する
        $("#stop").click(function(){
            clearTimeout(checkID);
            player.stopVideo();
        });

        //「調整」クリック時に設定項目を開閉する
        $('#customize').on('click',function(e) {
            //モードを編集モードに設定
            $('#edit-mode').val('edit');
            $('.message').hide();
            if ($('.condition').is(':visible')){
                $('.customize').hide()     //調整関連項目非表示
                $('.condition').slideUp(); //下から上にスライドして閉じる
                $('.after-set').show();   //ノットるボタン表示
                $('#cancel').hide();   //取り消しボタン表示
            }else{
                $('.condition').slideDown();  //上から下にスライドして開く
                $('.customize').show()     //調整関連項目表示
                $('.after-set').hide();   //ノットるボタン非表示
                $('#cancel').show();   //取り消しボタン表示
                cueVideo($('#play-list').val(),0,player.getPlaylistIndex()); //動画読み込み
            }
        });

        //共有ボタンクリック用の処理。
        $("#share").click(function(){
            $('#share_box').modal('show');
        });

        //共有リンク用テキストボックスクリック時にURLを選択状態にする
        $("#link-text").click(function(){
            $(this).select();
        });




    })


</script>
</body>
</html>

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です