コントローラUIの設計

<video>要素を使用した場合、表示されるコントローラはWebブラウザによって異なる。

Webブラウザ別のネイティブなビデオコントローラ - Figure 1: Native browser video controls across different browsersより引用

再生、一時停止、シークなどあらゆる機能/ボタンはMedia elements APIをとおして、JavaScriptでアクセス/操作することが可能だ。これらのコントローラUIは、HTML/CSS/SVGなどで自由に組みたてられる。

コントローラのマークアップ

まず最初にこれらコントローラをマークアップする必要がある。さきほどのWebブラウザ別ネイティブビデオコントローラを見て分かるとおり、プレーヤには次の機能が求められる。

  • 再生/一時停止ボタン
  • シークバー
  • タイマー
  • ボリューム調節ボタン/スライダ

これらを構成するマークアップは次のとおり。

<div class="ghinda-video-controls">
    <a class="ghinda-video-play" title="Play/Pause"></a>
    <div class="ghinda-video-seek"></div>
    <div class="ghinda-video-timer">00:00</div>
    <div class="ghinda-volume-box">
        <div class="ghinda-volume-slider"></div>
        <a class="ghinda-volume-button" title="Mute/Unmute"></a>
    </div>
</div>

それぞれのクラス名は次の機能に対応している。

  • ghinda-video-controls: コントローラ全体
  • ghinda-video-play: 再生/一時停止
  • ghinda-video-seek: シーク
  • ghinda-video-timer: タイマー
  • ghinda-volume-box: ボリューム調節のボタン。オンマウスでスライダとミュート切替を表示
  • ghinda-volume-slider: ボリューム調節スライダ
  • ghinda-volume-button: ミュート切替

JavaScriptを用いてこのマークアップ(controls wraper)を<video>要素の後に挿入、<div class="hinda-video-player">(main wrapper)で<video>要素をラップする。

作成したプレーヤをjQueryプラグインとしてパッケージ

JavaScriptによるマークアップが完了したら、これらをjQueryプラグインとしてパッケージする。jQueryプラグインにしておくことで、簡単に再利用することが可能になる。AUTHOR'S NOTEによると、jQueryプラグインを作成してみたい場合はSitePointのHow To Develop a jQuery PluginやOpera Developer Communityの39: Programming - the real basics!もチェックしてほしいとのことだ。

次に紹介するコードは、同氏の成果物である「jquery.ghindaVideoPlayer.js」の一部。実装を追いながら確認したい方は、さきに記事最下部の「download the source code (8.5mb, ZIP file)」からソースコードをダウンロードしておくことをおすすめする。

JavaScriptでおこなっている処理の順番は次のとおり。

  1. デフォルトのオプション定義
  2. <video>の前後にmain/controlsそれぞれの要素を挿入
  3. 再生/一時停止のコントローラを作成
  4. シークの作成
  5. タイマーの作成
  6. ボリュームコントローラを作成
  7. <video>からcontrols属性を削除

JavaScriptを無効にしている場合でもコントロールが無効になることがないように、スクリプトロードが完了し、コントローラの描画に成功した時点でコントロール属性を削除。JavaScriptが無効の環境や、なんらかの理由で1~6が実行できなかった場合は、Webブラウザデフォルトのコントローラがそのまま表示されるというわけだ。

$.fn.gVideo = function(options) {
    // build main options before element iteration
    var defaults = {
        theme: 'simpledark',
        childtheme: ''
    };
    var options = $.extend(defaults, options);
    // iterate and reformat each matched element
    return this.each(function() {
        var $gVideo = $(this);

        //create html structure
        //main wrapper
        var $video_wrap = $('<div></div>').addClass('ghinda-video-player').addClass(options.theme).addClass(options.childtheme);
        //controls wraper
        var $video_controls = $('<div class="ghinda-video-controls"><a class="ghinda-video-play" title="Play/Pause"></a><div class="ghinda-video-seek"></div><div class="ghinda-video-timer">00:00</div><div class="ghinda-volume-box"><div class="ghinda-volume-slider"></div><a class="ghinda-volume-button" title="Mute/Unmute"></a></div></div>'); 
        $gVideo.wrap($video_wrap);
        $gVideo.after($video_controls);

さきほどのマークアップを<video>要素の前後に挿入する。

続いて、コントローラUIを構築する各要素を紐づける。

//get newly created elements
var $video_container = $gVideo.parent('.ghinda-video-player');
var $video_controls = $('.ghinda-video-controls', $video_container);
var $ghinda_play_btn = $('.ghinda-video-play', $video_container);
var $ghinda_video_seek = $('.ghinda-video-seek', $video_container);
var $ghinda_video_timer = $('.ghinda-video-timer', $video_container);
var $ghinda_volume = $('.ghinda-volume-slider', $video_container);
var $ghinda_volume_btn = $('.ghinda-volume-button', $video_container);

$video_controls.hide(); // keep the controls hidden

$video_controls.hide();でいったんコントローラを非表示にしている。これは動作を再生する準備ができるまで、コントローラを非表示にしておきたいため。

続いて、再生/一時停止のコントロールを作成する。

var gPlay = function() {
    if($gVideo.attr('paused') == false) {
        $gVideo[0].pause();                    
    } else {                    
        $gVideo[0].play();                
    }
};

$ghinda_play_btn.click(gPlay);
$gVideo.click(gPlay);

$gVideo.bind('play', function() {
    $ghinda_play_btn.addClass('ghinda-paused-button');
});

$gVideo.bind('pause', function() {
    $ghinda_play_btn.removeClass('ghinda-paused-button');
});

$gVideo.bind('ended', function() {
    $ghinda_play_btn.removeClass('ghinda-paused-button');
});

<video>をサポートするほとんどのWebブラウザは、プレーヤを右クリックするとコンテキストメニューに再生や一時停止といったコントロールのためのメニューを表示する。コンテキストメニューから操作されても自前で用意したコントローラUIが連動して動作するように、ここでは「ボタンに機能を紐づける」のとは別に、「<video>の各種イベント(play, pause, ended)時に、ボタンのクラスを操作」している。

再生/一時停止のコントロールの後、シークの作成にうつる。

 var createSeek = function() {
    if($gVideo.attr('readyState')) {
        var video_duration = $gVideo.attr('duration');
        $ghinda_video_seek.slider({
            value: 0,
            step: 0.01,
            orientation: "horizontal",
            range: "min",
            max: video_duration,
            animate: true,                    
            slide: function(){                            
                seeksliding = true;
            },
            stop:function(e,ui){
                seeksliding = false;                        
                $gVideo.attr("currentTime",ui.value);
            }
        });
        $video_controls.show();                    
    } else {
        setTimeout(createSeek, 150);
    }
};

createSeek();

createSeek()は再帰関数。readyState属性の有無をチェックし、再生準備ができたらjQuery UI Sliderを使ってスライダを描画。スライダ描画後、非表示にしていたコントロールを再表示する。

続いてタイマーを作成し、timeupdateイベントリスナにアタッチする。

var gTimeFormat=function(seconds){
    var m=Math.floor(seconds/60)<10?"0"+Math.floor(seconds/60):Math.floor(seconds/60);
    var s=Math.floor(seconds-(m*60))<10?"0"+Math.floor(seconds-(m*60)):Math.floor(seconds-(m*60));
    return m+":"+s;
};

var seekUpdate = function() {
    var currenttime = $gVideo.attr('currentTime');
    if(!seeksliding) $ghinda_video_seek.slider('value', currenttime);
    $ghinda_video_timer.text(gTimeFormat(currenttime));                            
};

$gVideo.bind('timeupdate', seekUpdate);

gTimeFormatは時間の書式を整形するための関数。seekUpdateはさきほど作成したスライダの値を取得し、現在再生している動画の時間を得るための関数。timeupdateイベントリスナにseekUpdateをアタッチし、スライダを移動させてcurrentTimeの値が変動したときに再生時間を取得。gTimeFormatで時間書式を変換し、タイマー部分に表示する。

ここまできたらコントローラ最後のUI、ボリュームコントローラを作成する。

$ghinda_volume.slider({
    value: 1,
    orientation: "vertical",
    range: "min",
    max: 1,
    step: 0.05,
    animate: true,
    slide:function(e,ui){
        $gVideo.attr('muted',false);
        video_volume = ui.value;
        $gVideo.attr('volume',ui.value);
    }
});

var muteVolume = function() {
    if($gVideo.attr('muted')==true) {
        $gVideo.attr('muted', false);
        $ghinda_volume.slider('value', video_volume);

        $ghinda_volume_btn.removeClass('ghinda-volume-mute');                    
    } else {
        $gVideo.attr('muted', true);
        $ghinda_volume.slider('value', '0');

        $ghinda_volume_btn.addClass('ghinda-volume-mute');
    };
};

$ghinda_volume_btn.click(muteVolume);

シーク同様、jQuery UI Sliderでボリュームコントローラを実装している。スライダが動いた場合はvolumeをリアルタイムに変更。またmuteVolumeでミュートのOn/Offを切り替えられるようになっている。

最後にWebブラウザのデフォルトコントローラの代わりに、カスタマイズしたコントローラが使用されるように、<video>からcontrols属性を削除する。

$gVideo.removeAttr('controls');

プラグインの実装はここまで。あとはカスタマイズしたプレーヤを使用したい場面で

$('video').gVideo();

の1行を追加すればOKだ。

なおJavaScriptからプレーヤのコントローラを操作するコードを書いてみたい場合は、「【特集】詳解! HTML 5と関連APIの最新動向 - 新タグ&API編 (3) Video/Audio要素とそのAPI」においても詳細なサンプルが掲載されているので、こちらも参考にされたい。