Safariで「自動再生をしない」を設定した場合、どこまでブロックされるのか


はじめに

Safariの自動再生のポリシーの変更により、デフォルトではユーザーがクリックしないと音声付きの動画を自動再生を許可しなくなりました。

ところが、独自に構築したコントロールボタンをクリックしても再生が開始しないという問題に突き当たったため、どういう条件で「自動再生」とみなされるのか、整理してみました。

検証

環境

再生コンテンツの条件はシンプルに

  • MP4/H.264
  • 暗号化なし

検証環境は

  • Mac OSX El Capitan 10.11.6
  • Safari 11.0.3

です。

パターン1: videoタグの属性にautoplayを指定

一番シンプルにHTML5のvideoタグに対してautoplay属性を指定したパターンです。

<video id="id-video" src="sample.mp4" controls autoplay></video>

これはもちろんブロックされます。

パターン2: ページの読み込み時にJavaScriptから命令

次に、ページの読み込み完了時にJavaScriptでvideoタグの再生開始play()を呼び出すパターン。

<video id="id-video" src="sample.mp4" controls></video>
<script>
    window.onload = function() {
        document.getElementById('id-video').play();
    };
</script>

ユーザーが意図しない勝手な再生開始を防ぐごとが目的ですので、このパターンも当然、自動再生のブロック対象です。

パターン3: ユーザーがクリックしたら同期処理

次にボタンを用意してユーザーのクリックイベントの中で再生開始するパターン。

<video id="id-video" src="sample.mp4" controls></video>
<button id="id-button-play">Play</button>
<script>
    document.getElementById('id-button-play').onclick = function () {
        document.getElementById('id-video').play();
    };
</script>

これは想定通り、ブロックされずに再生が開始されます。

パターン4: ユーザーがクリック時に非同期処理(Callback)

ここからがハマったポイントです。まず、Callbackで非同期に再生開始するパターン。

<video id="id-video" src="sample.mp4" controls></video>
<button id="id-button-play">Play</button>
<script>
    document.getElementById('id-button-play').onclick = function () {
        setTimeout(function () {
            document.getElementById('id-video').play();
        ,10000};
    };
</script>

このサンプルだと、play()を呼んだ瞬間に下記のような エラーが発生して再生開始に失敗します

Unhandled Promise Rejection: NotAllowedError (DOM Exception 35): The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.

これが非常に厄介。この実装をしていると、ユーザーがクリックしているにもかかわらず、再生が開始できなくなってしまいます。
XMLHttpRequestのonloadなどのイベントハンドラでも同様に再生開始できず。

パターン5: ユーザーがクリック時に非同期処理(Promise)

気になるので、Promiseのthenの中で呼びだすパターンも試してみました。

<video id="id-video" src="sample.mp4" controls></video>
<button id="id-button-play">Play</button>
<script>
    document.getElementById('id-button-play').onclick = function () {
        createPromise2().then(function () {
            document.getElementById('id-video').play();
        }).catch(function (err) {
            console.log("promise catch:" + err);
        });
    };

    function createPromise() {
        return new Promise(function (resolve, reject) {
            // 何か処理を入れる
            resolve("resolved");
        });
    }
</script>

なんとこのパターンだとちゃんと再生が開始しました。同じ非同期でもなぜPromiseはOKなのか

まとめ

Safariの自動再生ポリシーは、Callbackに要注意ということになります。
例えば再生開始前に何かAPIを叩いて、成功したら再生開始・・・みたいなことをしたい場合、シーケンスをよく考える必要があります。

何か回避方法をご存じでしたら教えていただきたい・・・。