YouTubeをJavaScriptで制御できるIFrame Player API に悪戦苦闘

WordPressでWebサイトを作成していて、YouTubeを50人分ぐらい載せることになって、動画を操作したいということでIFrame Player APIを使えば良いようです。

IFrame Player API とは?

IFrame Player API

YouTube 動画プレーヤーをウェブサイトに埋め込み、JavaScript でプレーヤーを制御できます。ウェブページに Flash オブジェクトを埋め込む FlashJavaScript のプレーヤー API とは異なり、IFrame API はコンテンツをページの <iframe>タグに投稿します。この方法は従来の API よりも柔軟で、Flash をサポートしていないモバイル デバイスの場合に、Flash プレーヤーではなく HTML5 プレーヤーで YouTube を利用できます。

この APIJavaScript 関数を使うことで、再生する動画の頭出し、動画の再生 / 一時停止 / 停止、プレーヤーの音量の調節、再生中の動画に関する情報の取得といったことができます。また、特定のプレーヤー イベント(プレーヤーの状態の変化や動画の再生画質の変化など)に応じて実行されるイベント リスナーを追加できます。

iframe 組み込みの YouTube Player API リファレンス  |  YouTube IFrame Player API

JavaScriptyoutubeの動画を操作したい場合は、IFrame Player APIを使うのが良いでしょう、ということらしいです。 

要件の解釈と用例で混乱

iframe 組み込みの YouTube Player API リファレンス  |  YouTube IFrame Player API によると、要件という項目で、

  • HTML5 の postMessage をサポートするブラウザを使用する必要があります。
  • onYouTubeIframeAPIReady – ページでプレーヤー API 用の JavaScript のダウンロードが完了すると API がこの関数を呼び出します。これにより、ページで API を使用できるようになります。この関数では、ページが読み込まれたときに表示するプレーヤー オブジェクトを作成できます。

となっていて、用例では、 

<!DOCTYPE html>
<html>
  <body>
    <!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
    <div id="player"></div>

    <script>
      // 2. This code loads the IFrame Player API code asynchronously.
      var tag = document.createElement('script');

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

      // 3. This function creates an <iframe> (and YouTube player)
      //    after the API code downloads.
      var player;
      function onYouTubeIframeAPIReady() {
        player = new YT.Player('player', {
          height: '360',
          width: '640',
          videoId: 'M7lc1UVf-VE',
          events: {
            'onReady': onPlayerReady,
            'onStateChange': onPlayerStateChange
          }
        });
      }

      // 4. The API will call this function when the video player is ready.
      function onPlayerReady(event) {
        event.target.playVideo();
      }

      // 5. The API calls this function when the player's state changes.
      //    The function indicates that when playing a video (state=1),
      //    the player should play for six seconds and then stop.
      var done = false;
      function onPlayerStateChange(event) {
        if (event.data == YT.PlayerState.PLAYING && !done) {
          setTimeout(stopVideo, 6000);
          done = true;
        }
      }
      function stopVideo() {
        player.stopVideo();
      }
    </script>
  </body>
</html>    

1. の部分は<div>タグが<iframe>タグに置換されるよ、ってことらしく<div>タグに指定したidで置換する場所を指定してるみたいです。

2. IFrame Player API JavaScript コードが読み込まれます。<script src = "https://www.youtube.com/iframe_api"></script>を非同期で読み込みたいようです。<script>タグの async 属性(非同期のダウンロードを有効化)は最新のブラウザで対応していないための記述らしいです。

3. IFrame Player API JavaScript コードがダウンロードされるとonYouTubeIframeAPIReady 関数が実行されます。

はい、この部分で思いっきりハマりました。

自分は、この説明を、<script src = "https://www.youtube.com/iframe_api"></script>がダウンロードされないとonYouTubeIframeAPIReady関数は実行されないのか!なるほど、そしてこの関数が実行されないとプレーヤー オブジェクト(<iframe>部分)ができないんだな、50人分ということはIFrame Player API JavaScriptを50回呼び出さないといけないのか?と思ったのですが、職場の先輩の助言で目からウロコ!

IFrame Player API JavaScriptの読み込みは一回で良いようでした。onYouTubeIframeAPIReady()は定義しておく必要はありますが、この関数はあくまで<div>タグを<iframe>タグに置換するだけで、肝心の動画部分はnew YT.Playerで生成されています。

  • 1. IFrame Player API JavaScriptを読み込む。
  • 2.
    • 2-1. new YT.Playerで<iframe>タグを生成。
    • 2-2. onYouTubeIframeAPIReady()が実行され、<div>タグが<iframe>タグに置換される。

いまいち、最後まで仕組みはよく分からなかったのですが、

1度、IFrame Player API JavaScriptを読み込めば、あとはnew YT.PlayerをするたびにonYouTubeIframeAPIReady()が自動的に実行されるような気がします。

onYouTubeIframeAPIReady()が実行されるために、IFrame Player API JavaScriptが再読み込みされてるのだとしたら、そういう感じで説明して欲しいですね。

onYouTubeIframeAPIReady()が能動的に呼び出せないのなら、呼び出される条件をもう少ししっかり記述しておいて欲しいです。 

実際に使ってみた

今回は、<li>要素をクリックしたらポップアップする要素のなかに動画を表示するという仕様だったので、クリックされたらという感じでjQueryを使って実装しました。

⇩  WordPressのカスタム投稿については下記サイトへ

Wordpress カスタム投稿タイプの作り方 - Qiita

 

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>YouTube Iframe APIとWordPressのコラボ</title>
<link rel="stylesheet" href="<?php echo get_stylesheet_uri(); ?>">
</head>
<ul>
<!--WordPressの『カスタム投稿』の投稿を取得-->
<?php
$args = array(
'posts_per_page' => -1,
'post_type' => 'employee'
);
$custom_posts = new WP_Query($args);
?>
<!--WordPressの『カスタム投稿』をループ-->
<?php
if($custom_posts->have_posts()):
while($custom_posts->have_posts()): $custom_posts->the_post();
?>
<?php
// Advance Custom Fieldsで指定した値(youtubeの動画URL)を取得し
// 動画URL末尾のvideoIdを取得
$movie_url = end(implode("/", get_field('youtube_url')));
?>
<li class="youtube_area">
<a data-target="<?php echo $post->ID; ?>" data-video-id="<?php $movie_url; ?>" class="modal-open">
<span>
<?php
// 投稿にアイキャッチ画像が設定されているか
if(has_post_thumbnail()) {
the_post_thumbnail();
} else {
?>
<img src="<?php echo get_template_directory_uri(); ?>/img/dammy.png">
<?php
}
?>
</span>
</a>
</li>
<!--popup部分-->
<section id="popup-<?php echo $post->ID; ?>" class="modal-content">
<!--youtube置換部分-->
<div id="player_<?php echo $post->ID; ?>"></div>
<div class="profile">
<div class="profile-img">
<?php if(get_field('personal-photo')){ ?>
<img src="<?php the_field('personal-photo'); ?>">
<?php } else { ?>
<img src="<?php echo get_template_directory_uri(); ?>/img/dammy.png">
<?php } ?>
</div>
<div class="profile-content">
<div class="profile-text">
<?php the_title( '<h4>', '</h4>' ); ?>
<?php the_content(); ?>
</div>
</div>
</div>
</section>
<?php endwhile; ?>
<?php wp_reset_postdata(); ?>
<?php else: ?>
<!--『投稿』がない場合-->
<?php
for($post_count = 1; $post_count < 50; $post_count++){
?>
<li class="no-posts">
<img src="<?php echo get_template_directory_uri(); ?>/img/dammy.png">
</li>
<?php
}
?>
<?php endif; ?>
</ul>
<script>
var click_id;
var video_id;
var player_id;
var player;
var ytplayer = {};
$('youtube_area').click(function(){
click_id = $(this).find('a').data('target');
video_id = $(this).find('a').data('video_id');

player_id = 'player' + click_id;

if(click_id in ytplayer){
player = ytplayer[click_id];
} else {
player = new YT.Player(player_id, {
videoId: video_id,
playerVars: {
enablejsapi: 1,
origin: http://ts0818.com
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
ytplayer[click_id] = player;
player = player_id;
}
});

function onYouTubeIframeAPIReady(){
// iframeに置換してくれてる?ようです
}

// YT.Playerオブジェクトが存在するか
var youtube_ready = false;
function onPlayerReady(event){
youtube_ready = true;
}

// YT.Playerオブジェクトの状態(動画の状態)
var youtube_player_flag;
function onPlayerStateChange(event){
// event.dataで今現在の動画の状態を取得
switch(event.data){
// 動画が再生中の場合
case YT.PlayerState.PLAYING:
youtube_player_flag = 'playing';
break;
// 動画が終了中の場合
case YT.PlayerState.ENDED;
youtube_player_flag = 'ended';
break;
}
}

function pauseVideo(){
player.pauseVideo();
}

// ポップアップ処理
$(function(){
// 「.modal-open」をクリック
$('.modal-open').click(function(){
// オーバーレイ用の要素を追加
$('body').append('<div class="modal-overlay"></div>');
// オーバーレイをフェードイン
$('.modal-overlay').fadeIn('slow');

// モーダルコンテンツのIDを取得
var modal = '#' + $(this).attr('data-target');
// モーダルコンテンツの表示位置を設定
modalResize();
// モーダルコンテンツフェードイン
$(modal).fadeIn('slow');

// 「.modal-overlay」あるいは「.modal-close」をクリック
$('.modal-overlay, .modal-close').off().click(function(){
if(youtube_ready = true && youtube_player_flag = 'playing'){
pauseVideo();
}
// モーダルコンテンツとオーバーレイをフェードアウト
$(modal).fadeOut('slow');
$('.modal-overlay').fadeOut('slow',function(){
// オーバーレイを削除
$('.modal-overlay').remove();
});
});

// リサイズしたら表示位置を再取得
$(window).on('resize', function(){
modalResize();
});

// モーダルコンテンツの表示位置を設定する関数
function modalResize(){
// ウィンドウの横幅、高さを取得
var w = $(window).width();
var h = $(window).height();

// モーダルコンテンツの表示位置を取得
var x = (w - $(modal).outerWidth(true)) / 2;
var y = (h - $(modal).outerHeight(true)) / 2;

// モーダルコンテンツの表示位置を設定
$(modal).css({'left': x + 'px','top': y + 'px'});
}

});
});
</script>

cssはポップアップの部分以外は省略してます。

 
.modal-overlay {
  z-index:2;    /*header,footerをz-index:1にしたので それより上げています*/
  display:none; /*jsでフェードインされるまでdisplay:none*/
  position:fixed;
  top:0;
  left:0;
  width:100%;
  height:100vh; /*100vhでビューポートの高さいっぱいになります*/
  background-color:rgba(0,0,0,.5);
}
/*これは好きな色・透明度で*/
a.modal-open:hover {
  cursor: pointer; /*カーソルをポインタに*/
}
.modal-content {
    position: fixed;
left: 0; display: none; /*jsでフェードインされるまでdisplay:none*/ z-index: 120; /*オーバーレイより上に*/ margin: 10px; padding: 15px; border-radius: 20px; background: #fff; } .modal-content img { width: auto; max-height: 55vh; } .modal-content h1 { font-size: 120%; margin-bottom: 0.5em; } .modal-content p { text-align: left; font-size: 14px; font-weight: 500; } /*クローズボタンは何でも好きなスタイルでOK*/ a.modal-close { position: absolute; top: 0; right: 10px; color: #b29c33; font-size: 35px; line-height: 1; font-weight: bold; text-decoration: none; } a.modal-close:hover { cursor: pointer; /*カーソルをポインタに*/ }

今回は、複数動画とはいえ、ポップアップで実質1つずつしか動画が表示されない状況でしたが、最初から動画が10個ぐらい設置されているような場合は各動画を制御するのが激難な気がします。