ふっちーのやんのかやんねーのかブログ

ITの技術的なこともそうでないことも。

【javascript】iPhone/iOSのSafariで動画をインラインで再生させる方法

こんにちは、ふっちーです。

前回に引き続き、HTML5 videoの話題に触れていきます。

iPhoneSafariで動画を再生すると画面いっぱいに表示されちゃいますよね。方やAndroidのブラウザではそのまま再生される。こんな挙動の違いに悩まされ、iPhoneでもインライン再生をやりたいよ!と思った方も少なくないのではないでしょうか。僕もその一人です。

ということで、今回はiPhone/iOSSafariブラウザにおける動画のインライン再生を試してみたので、その方法について紹介したいと思います。

■いきなりまとめ

・videoのcurrentTimeを操作し、フレーム都度の表示をcanvasに描画することでインライン再生を実現する。

・自動再生させることも可能。

・ただし、この方法だと音声の再生は行われないため音声の再生についてはaudioタグを別途設置し再生させる機能実装する必要がある。

・iOS10ではplaysinlineプロパティ(iOS9のWebViewで利用できていたwebkit-playsinlineから変更されるようです)によるインライン再生、また、mutedプロパティ設定時の自動再生が可能となるためcanvasを使う必要がなくなる。iOS10たのしみ。

■まずはサンプルコード

iOSSafariブラウザでは、videoタグの再生を実行するとビデオプレーヤーによるフルスクリーン再生となります。

そのため、今回の実装ではvideoタグの再生を実行せず、videoのcurrentTimeをfpsに合わせ加算していき、都度のフレームをcanvasに描画することで、再生しているように見せかけています。

See the Pen iOS Safari HTML5 video inline play with canvas by junya fuchinoue (@fuchinoue_j) on CodePen.

■実装方法

1. videoタグの表示をcanvasに描画する

まずはvideoの表示をcanvasに描画します。

See the Pen iOS Safari HTML5 video inline play with canvas | work 1 by junya fuchinoue (@fuchinoue_j) on CodePen.

canvasに描画を行うには、drawImage()を利用します。

描画領域の設定はちょっとくせがあるのですが、今回はあまり意識しなくて良いようにcanvasの大きさをvideoの大きさに合わせています。

2. videoタグのcurrentTimeを変更する

videoをcanvasに描画することが出来たので、fpsに合わせcurrentTimeを加算し再生されているようにみせていきます。

See the Pen iOS Safari HTML5 video inline play with canvas | work 2.1 by junya fuchinoue (@fuchinoue_j) on CodePen.

setIntervalを利用し、currentTimeを加算しています。

canvasの描画はcurrentTimeが変更された際に発火するtimeupdateイベントにハンドルし実行させます。

また、currentTimeの変更後、リソースの読み込みが行われtimeupdateイベントが発火する前に次のcurrentTimeが設定されないよう、フラグで制御を行っています。

リソースの読み込みが遅い場合カクついてしまいます。。。ここは要ブラッシュアップ。

3. videoタグを非表示にする

最後に、videoタグを非表示にしてcanvasのみの表示にします。

See the Pen iOS Safari HTML5 video inline play with canvas by junya fuchinoue (@fuchinoue_j) on CodePen.

これでiPhone/iOSSafariブラウザ上で動画のインライン再生が行えました。やったぜ。

ただ、上記サンプルの状態だと、音声はありません。

音声を再生させる場合は、動画から音声データのみを切り出したものを用意しaudioタグを設置、currentTimeの同期を取りながら平行して再生させる必要があるでしょう。

■さいごに

CodePenを初めて使ったけど、これは良いものだ。

サンプルコード中の動画はDropboxにアップロードしたものを使っています。素材は拾い物です。

Googleドライブでも静的URLを発行できなくなったので、しばらくはDropboxで行こうかと思います。

iOS10たのしみ。

【Javascript】HTML5 Videoを使った動画プレイヤーの実装方法いろいろ

こんにちは、ふっちーです。

近日のアドテク業界は動画が非常にアツイ感じなので、 スマートフォンにおけるブラウザ/WebViewのHTML5 video(以下video)を使った動画プレイヤー事情を、インライン再生自動再生の観点で検証してみました。

■まとめ

実際どうなのよ、ていうのが知りたいと思うので、まず結果からお伝えしたいと思います。

iOS/WebView、Android/ブラウザ、Android/WebViewはvideoを直接利用するのがよさそう

iOS/Safariはvideoとcanvasを合わせて利用するのがよさそう

・標準的にインライン再生の実装を行い、フルスクリーン再生の実装を独自に行なったほうが共通化できる

Android/ブラウザにおける自動再生が課題

検証を行ったバリエーションと動作状況は以下の通りでした。

◎:インライン再生可+自動再生可
◯:インライン再生可
△:インライン再生不可
×:再生不可

  iOS/Safari iOS/WebView Android/ブラウザ Android/WebView Android/WebView
OSバージョン 8以上 8以上   4.1以下 4.2以上
video
video×canvas ×
jpeg×canvas
ネイティブ - -

参考までに。

■はじめに

今回は、HTML5 videoを使った動画プレイヤーの実装と検証を行った、

HTML5 video

HTML5 video × HTML5 canvas(以下canvas

の2つの手法ついて、ソースコードとそれぞれの再生を実現する方法と問題点についてまとめてみました。

jpeg × HTML5 canvas

は動画のフレーム毎にキャプチャした画像を順番にcanvasに描画するパラパラマンガ的な手法なのですが、こちらについては次の機会にまとめようと思います。

(1. )videoを利用した動画プレイヤー

videoを利用し、動画のインライン再生と自動再生を行う検証です。

この手法はvideoを利用した最もシンプルな方法になるのですが、

iOS/Safariでインライン再生が実現できない

iOS/SafariAndroid/ブラウザで自動再生が実現できない

といった問題点が残りました。

ちなみに、iOS/Safariでインライン再生についてはiOS10になることで解消される予定のようです。

ソースコード

<video id="js-video" preload="none" webkit-playsinline="webkit-playsinline">
    <source src="video.mp4" type="video/mp4">
</video>

<script>
  var elemVideo = document.getElementById('js-video');

  // 再生実行用の関数
  var videoPlay = function () {
    elemVideo.play();
  }
</script>

■インライン再生設定

Android/ブラウザ、Android/WebViewでは標準でインライン再生となるので特に問題なしです。

iOS/WebViewでインライン再生が行えるように、videoタグにwebkit-playsinline属性を設定します。

先述したとおり、残念ながら現状iOS/Safariではvideoを利用したインライン再生は行えず、ビデオプレーヤーによるフルスクリーン再生となります

<video id="js-video" preload="none" webkit-playsinline="webkit-playsinline">

■自動再生設定

iOS/SafariAndroid/ブラウザの場合

iOS/SafariAndroid/ブラウザでは、自動再生は行えません

これは、videoのplay、loadなどのイベントの発火にユーザアクションが必須となっているブラウザ仕様に起因しているためどうしようもなさそうです。

Javascriptでvideoのplayイベントの発火させても再生は開始されずautoplay属性による自動再生ついても同様に行えないようです。

PCブラウザ等では以下のスクリプトで自動再生を開始できます。

<script>
  // windowのonload発火時に再生実行用の関数をコール
  window.addEventListener('load', videoPlay);
</script>

iOS/WebView・Android/WebViewの場合

アプリ内からvideoのplayイベントを発火させることで自動再生を開始することが出来ます。

今回のソースコードで言うと、アプリからvideoPlay()を実行<することで自動再生を実現します。

この際、自動再生を開始するタイミングなどはアプリ側の実装で行うことになります。

(2.)video×canvasを利用した動画プレイヤー

videoとcanvasを利用し、動画のインライン再生と自動再生を行う検証です。

要約すると、videoのcurrentTimeをsetInterval()でカウントアップしていき、都度のフレームをcanvasに描画する仕組みになります。

この手法を取ることで、iOS/Safariでインライン再生を実現できます。

問題点としては、

・音声の再生に対応していない(実際に再生を行っているわけではないので)

Androidの低いバージョンの端末では、canvasへの描画が正しく行えない場合がある

canvasを利用するため端末負荷が高い

といったところです。

音声再生については、HTML5 audioでcurrentTimeの同期をとった音声ファイルを再生することで、擬似的に再現することが出来ます。ジャストアイデアですが。

ソースコード

<video id="js-video" preload="none">
    <source src="video.mp4" type="video/mp4">
</video>

<canvas id="js-canvas"></canvas>

<script>
  var elemVideo = document.getElementById('js-video');
  var elemCanvas = document.getElementById('js-canvas');

  var canvasDrawTimer;
  var lastTime;

  var videoPlay = function () {
    canvasDrawTimer = setInterval(function() {
      // 前回の描画時間都の差分を取り、currentTimeを設定
      var nowTime = Date.now();
      var diffTime = (nowTime - lastTime) / 1000;
      lastTime = nowTime;
      elemVideo.currentTime = elemVideo.currentTime + diffTime;

      // canvasへの描画を実行
      canvasDraw();
    }, 1000 / 24); // 24fpsで描画する
  }

  function canvasDraw() {
    // 動画の縦横サイズ・アスペクト比を取得しcanvasの縦横サイズを変更
    var aspectRatio = elemVideo.videoHeight / elemVideo.videoWidth;
    var canvasWidth = elemCanvas.clientWidth;
    var canvasHeight = Math.floor(canvasWidth * aspectRatio);
    elemCanvas.setAttribute("width", canvasWidth.toString());
    elemCanvas.setAttribute("height", canvasHeight.toString());

    // canvasに描画
    var ctx = elemCanvas.getContext('2d');
    ctx.drawImage(elemVideo, 0, 0, elemVideo.videoWidth,
        elemVideo.videoHeight, 0, 0, canvasWidth, canvasHeight);

    // 総再生時間と現在の再生時間を比較し再生を終了
    var currentTime = (Math.round(parseFloat(elemVideo.currentTime) * 10000) / 10000);
    var duration = (Math.round(parseFloat(elemVideo.duration) * 10000) / 10000);
    if (duration >= 1 && currentTime >= duration) {
      clearInterval(canvasDrawTimer);
    }
  }
}
</script>

■インライン再生設定

こちらの方法では、標準がインライン再生の対応となります。

逆に、フルスクリーン再生を行う際には、フルスクリーンを行う実装を別途行う必要があります

繰り返しになりますが、iOS/Safariでインライン再生を実現できる点においては胸熱です。

■自動再生設定

windowのonload時にvideoPlay()を発火させてあげることで自動再生を実現します。

<script>
  window.addEventListener('load', videoPlay);
</script>

■最後に

ブラウザにおける自動再生については、ユーザーの通信量を意識したブラウザ成約だとかがいろいろ絡んでいるようで、一筋縄には行かないようです。

ただ、現状においても、対象となるブラウザ毎にそれぞれの動作パターンを組み合わせることで、スマートフォン向けの最適な動画プレイヤーが作成できそうな予感はしますね。

プレイヤーだけでなく、プレイヤーに対応した動画素材の作成も大きな課題になりそうですが。。。

今回はこんなところで。

【Javascript】jQueryのプラグインを作成する

こんにちわ。ふっちーです。

Javasriptを使っていると、近年においてもjQueryの利用に行き着く方が多いのではないかと思う。

よりjQueryを楽しむため「jQueryプラグインを作成する」方法について、実際にプラグインを作成しながらあらためてまとめてみる。

新しい関数のプロパティを追加する

はじめに、jQuery.fnオブジェクトに新しい関数のプロパティを追加する。 追加したプロパティ名がプラグインの名前となる。

// 即時関数で囲む
;(function( $ ) {
    // 新しい関数のプロパティを追加する
    $.fn.myPlugin = function( ) {

        // プラグインのコードはここに作成する
    
    };
})( jQuery );

1. 即時関数で囲む

JavaScriptは関数スコープであるため、スコープを限定する際は関数で囲む必要がある。 即時関数で囲まれた内部で定義された変数はローカル変数となり、外部のグローバルな変数を上書きして汚染することが無いため、安全に変数定義を行うことができる。

jQueryプラグインを作成するにあたり、他のプラグインでも利用されているドルマークが衝突しないよう、即時関数の引数にjQueryを渡し、$という変数に格納しこれを利用する。

2. jQueryプラグインの関数を定義する

即時関数内で「$.fn.関数名」とプロパティを定義することで、jQueryプラグインの関数として認識される。 ここで定義した関数名がプラグイン名となる。

プラグインの機能を作成する

関数のプロパティ内にプラグインの機能を作成する。 今回は、対象の要素群の高さを、最大の高さに合わせるプラグインを作成してみる。

;(function( $ ) {
    $.fn.flatHeight = function() {
        // height() を使って対象の要素群の最大の高さを取得する
        var maxHeight = 0;
        this.each(function() {
            maxHeight = Math.max( maxHeight, $( this ).height() );
        });

        // 要素群に取得した高さを設定する
        this.css( "height" , maxHeight );

        // thisを返す
        return this;
    };
})( jQuery );

// div要素群に作成したプラグインを適用する
$( "div" ).flatHeight();

1. コンテキスト

対象となるDOM要素のプロパティを利用したい場合はthisから取得する。

プラグインの関数のスコープが有効な範囲内では、thisは呼び出されたプラグインjQueryオブジェクトを参照しているため、jQuery関数でのラッピングは不要となる。

2.メソッドチェーンを持続させる

jQueryプラグインは上記のサンプルのように、いくつかの方法で要素の集合をシンプルに書き換えていくというものが多い。

プラグインにおいてメソッドチェーンを持続させるために、プラグインはthisを返し、続いている次のメソッドに対象の要素群を引き渡すようにしておく必要がある。

作成したプラグインを利用する

実際にdiv要素が3つ横に並ぶページを作成し、ここまでで作成したプラグインを適用させてみる。

<html>

<head>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>

    <style type="text/css">
    div {
        float: left;
        background-color: gray;
        padding: 5px;
        margin: 5px;
    }
    </style>
</head>

<body>
    <div>
        DIV1<br />
    </div>

    <div>
        DIV2<br />
        DIV2<br />
    </div>
    
    <div>
        DIV3<br />
        DIV3<br />
        DIV3<br />
    </div>

    <script type="text/javascript">
        ;(function( $ ) {
            $.fn.flatHeight = function() {
                // height() を使って対象の要素群の最大の高さを取得する
                var maxHeight = 0;
                this.each(function() {
                    maxHeight = Math.max( maxHeight, $( this ).height() );
                });

                // 要素群に取得した高さを設定する
                this.css( "height", maxHeight );

                // thisを返す
                return this;
            };
        })( jQuery );

        $( "div" ).flatHeight();
    </script>
</body>

</html>

Before

f:id:fuchinoue_j:20160228133856p:plain

After

f:id:fuchinoue_j:20160228133524p:plain

なんということでしょう。

デフォルトとオプション

次に、オプションを受け取り、プラグインの動作を変更させる構成を用意する。 これは、プラグインが呼び出された時に拡張可能なデフォルトのセッティングを持っておくとよい。 デフォルトのオプションによる拡張は$.extendを利用する。

それでは、先ほど作ったプラグインに、最小の高さを設定できるオプションを用意してみよう。

;(function( $ ) {
    $.fn.flatHeight = function( options ) {
        // 渡された任意のオプションで拡張されるデフォルトを作成する
        var settings = $.extend( {
          'min-height'         : 0 // 最小の高さのデフォルトを設定した
        }, options);

       // height() を使って対象の要素群の最大の高さを取得する
        var maxHeight = settings['min-height']; // 最小の高さ設定を適用させるように拡張した
        this.each(function() {
            maxHeight = Math.max( maxHeight, $( this ).height() );
        });

        // 要素群に取得した高さを設定する
        this.css( "height", maxHeight );

        // thisを返す
        return this;
    };
})( jQuery );

オプションを指定した呼び出しは以下のとおりとなる。

$( 'div' ).flatHeight({
    'min-height' : 100 // 最小の高さのオプションを設定した
});

After

f:id:fuchinoue_j:20160228222407p:plain

プラグインメソッド

プラグインを作成する際、1つのプラグインjQuery.fnオブジェクトに対して複数のネームスペースを作成する状況を作ってはいけない。

プラグイン内で複数メソッドをもたせる場合、オブジェクトリテラルプラグインメソッド全てを集約し、利用するプラグインメソッドの文字列を渡すことで呼び出す必要がある。

では、例として先ほど作ったプラグインに、対象の要素群の表示・非表示を行うメソッドを追加してみる。

;(function( $ ) {
    var settings = {
        'min-height': 0 // 最小の高さのデフォルトを設定した
    }

    var methods = {
        init : function( options ) {
            // 渡された任意のオプションで拡張されるデフォルトを作成する
            settings = $.extend( settings, options );

           // height() を使って対象の要素群の最大の高さを取得する
            var maxHeight = settings['min-height']; // 最小の高さ設定を適用させるように拡張した
            this.each(function() {
                maxHeight = Math.max( maxHeight, $( this ).height() );
            });

            // 要素群に取得した高さを設定する
            this.css( "height", maxHeight );

            // thisを返す
            return this;
        },
        show : function () {
            this.show();

            return this;
        },
        hide : function () {
            this.hide();

            return this;
        },
        html : function (content) {
            this.html(content);

            return this;

        }
    };

    $.fn.flatHeight = function( method ) {
        // メソッド呼び出し部分
        if ( methods[method] ) {
            // 呼び出すメソッドを指定した場合
            return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
        } else if ( typeof method === 'object' || ! method ) {
            // 引数がオブジェクトである場合、また、呼び出すメソッドを指定しない場合
            // init() を呼び出す
            return methods.init.apply( this, arguments );
        } else {
            $.error( 'Method ' +  method + ' does not exist on jQuery.flatHeight' );
        }
    };
})( jQuery );

// 初期化メソッドの呼び出し
$('div').flatHeight();

// オプションを設定した初期化メソッドの呼び出し
$('div').flatHeight({
    "min-height":100
});
// hideメソッドの呼び出し
$('div').flatHeight("hide");
// htmlメソッドの(引数を渡す)呼び出し
$('div').flatHeight("html", "TEST");

一般的なアーキテクチャとなるが、全てのメソッドプラグインの親クロージャカプセル化し、渡された最初の文字列によってメソッドを呼び出す形式を取る。メソッドがパラメータを必要とする場合、追加のパラメータを渡すことができるようになっている。

最後に

不足も多いと思うが、以上がjQueryプラグインを作成する基本的な構成となる。

jQueryの処理をプラグイン化することで、構造をコンパクトにし、コードの再利用とメンテナンスをシンプルにしてくれると思う。 プラグインの作成を躊躇している方は、是非プラグインの作成を行いjQueryを楽しんでいただきたい。