HTML内で動画を自動再生するためにしたこと

# はじめに

ボタンがクリックされたらAPIを叩き、返ってきた動画ファイルのパスを `<video>` に設定して自動再生する。
といった感じのページを作成しました。

これだけのことなんですがいろいろ気にすること、困ったことがあったので記載しておきます。

***

# `<video>`

HTML5 から追加された要素
以下は極シンプルな使い方

```html
<!-- 1, src 属性を指定 -->
<video src="smpleVideo.mp4"></video>

<!-- 2, <source> を使用 -->
<video>
  <source src="smpleVideo.mp4" type="video/mp4">
  <source src="smpleVideo.webm" type="video/webm">
  <p>どっちもブラウザが対応していなかったらこのテキストが出る</p>
</video>

<!-- 3, ただし上記だと本当にただ動画が読み込まれるだけのでシークバーとかを出す -->
<video controls src="smpleVideo.mp4"></video>
```

`controls` をつけるとシークバーとか再生ボタンとか音量調整とかが出てきます。

***

## `autoplay`
`<video>` の属性
設定しておくと動画が読み込まれ次第ユーザーの操作なしに自動で再生すればいいのに。

```html
<video autoplay src="smpleVideo.mp4"></video>
```

どういうことかというと、MDN Web Docs に以下のような記載たちがあります。

[自動再生の可用性](https://developer.mozilla.org/ja/docs/Web/Media/Autoplay_guide#autoplay_availability)
[メディア要素の自動再生](https://developer.mozilla.org/ja/docs/Web/Media/Autoplay_guide#autoplay_of_media_elements)

***

まぁ、自動再生が機能するための障害はことのほか多いということです。

勝手に動画が落ちてきたらギガを消費して(言ってみたいだけ)迷惑。
突然音が出たら電車の中とかで気まずい。
とか、ユーザーのことを考えた仕様でしょう。

[autoplay に関するメモ](https://developer.mozilla.org/ja/docs/Web/HTML/Element/video#attr-autoplay)


我々はどうしても動画を自動再生させたいならブラウザ様にお許しいただく必要があります。

***

## muted
仕方がないのでミュートにしましょう。

```html
<video autoplay muted src="smpleVideo.mp4"></video>
```

はい、できました。

これで自動再生される環境がぐっと増えたでしょう。

でもこれだと勝手に再生する無音の動画なので `controls` つけて音量調整できるようにしたり、javascript を使って `onClick` で `<video>` の  `muted` を `false` にするボタンなんかを配置すると良いでしょう。
あまり本筋とは関係ないのでそのあたりはここには書きません。

***

## iOS 用のいろいろ
ここまでで動画の自動再生はおおむね可能になったはずなのですが恐らく iOS ではまだ動作しないでしょう。
自動再生しないからと `controls` をつけて手動再生するとフルスクリーンで再生が始まるのではないでしょうか。

これはインライン再生が行えていないのが原因と考えられます。

***

### `playsinline`
論理属性で、映像を「インライン」で再生する、すなわち要素の再生領域内で再生するかを指定します。
少なくとも iOS safari 環境ではこれがないとインライン再生されないようです。

ただし `playsinline` は iOS10 からのサポート
(iOS9 以前は `webkit-playsinline` としてサポートされていたが有効なのは webview のみ。
さらに、webview アプリも `allowsInlineMediaPlayback` というオプションが有効でないとならない。
らしいのでこの属性をつけたところで実行環境をこちらでコントロールできない場合自動再生が機能する保証はない。
また、手元に再現環境がないため不確かな情報であり検証を推奨。
今回はとりあえずつけておく。) 

```html
<video autoplay muted playsinline webkit-playsinline src="smpleVideo.mp4"></video>
```

***

### 低電力モード
ここまでの設定を行っても iOS の場合、低電力モードがONだと自動再生が行われません。
`controls` がなくても再生ボタンが勝手に表示されるようになるので大きな問題はないとは思いますが一応気にしておきましょう。

***

### iOS safari の謎のバグ
ここまでの全設定を行っても一部の環境で自動再生が起こらないことがあると問題になりました。
不思議なことにページ初回表示時には自動再生が起こらず、ページを更新などして再表示すると動作するという挙動です。
ついでに、safari のキャッシュを削除すると上記状態が再発します。

また、この現象は社内の iOS 14.6 端末で主に確認されました。(2021年5月24日 配布、2021/07/07 時点での最新環境)

***

#### APIからファイルパスを取得
今回構築したページは、表示時点では空の `<video>` を用意しておいて、ボタンがクリックされたら API に問い合わせて取得した動画ファイルのパスをセットするようにしていました。

以下、本来のコードを乗せるわけにはいかないので適当にそれっぽく書いたもの。動作確認はしていないので動作は保証しない。

```html
<div class='video_area'>
  <video style="width: 300px; display: none;" autoplay muted playsinline webkit-playsinline src=""></video>
</div>

<button data="hoge">click me.</button>
```

```javascript
import $ from 'jquery';

const API_URL = 'something endpoint.'

$( async () => {
  $('button').on('click', async (event) => {
    const clickData = $(event.currentTarget).attr('data')

    const headers = {
      'Accept': 'application/json'
    };
  
    const body = JSON.stringify({param: clickData});
  
    try {
      const apiResponse = await fetch(API_URL, {
        method: "POST",
        headers: headers,
        body: body,
        mode: 'cors',
        credentials: 'include'
      });
  
      const res = await apiResponse.json();
    
      $('video').attr('src', res.videoPath);
      $('video').show();
    } catch (error) {
      console.log(error);
    }
  })
});
```

`autoplay` は動画の読み込みが行われた段階で自動で再生イベントを発火させるのでこれでも自動再生します。

先述の通り iOS 14.6 の safari でのみ、上記のようなコードで自動再生が行われませんでした。
開発者ツールで様子を見ると `<video>` の `src` に値はセットされているし Network タブを確認すると動画のダウンロードも起こっているようでした。
***

#### とりあえずの対処
いろいろと原因や対処法を探ってみたのですが、とりあえず現段階で下記のような対処になりました。
ちなみに原因は現段階でも皆目見当がついていません。

```javascript
import $ from 'jquery';

const API_URL = 'something endpoint.'
$( async () => {
  $('button').on('click', async (event) => {
    const clickData = $(event.currentTarget).attr('data')

    const headers = {
      'Accept': 'application/json'
    };
  
    const body = JSON.stringify({param: clickData});
  
    try {
      const apiResponse = await fetch(API_URL, {
        method: "POST",
        headers: headers,
        body: body,
        mode: 'cors',
        credentials: 'include'
      });
  
      const res = await apiResponse.json();
    
      // ちょっと1秒待ってみる。
      setTimeout(function() {
        $('video').attr('src', res.videoPath);
        $('video').show();
      },1000);
  
    } catch (error) {
      console.log(error);
    }
  })
});
```

なぜだか API 返却値の取得から1秒待ってから `<video>` に `src` を設定すると自動再生が起こるようになりました。
🤔🤔🤔🤔🤔

さっきまでも `src` への値のセットはできていたので API からの返却値が取れていなかったわけでもないのだが謎。
setTimeout の時間が短すぎても動作しない。

本来はAPIコールから `<video>` への `src` 設定までに他にもいくつか処理が挟まっていて、そのあたりが影響しているのかとコメントアウトしながら原因を探していたのだが、それらは一切効果がなくAPIから`$('video').attr('src', res.videoPath);`までの間に時間を空けることだけが効果があった。

どうもAPIコールから1秒程度待つことがポイントらしい。

![Dkt3fnqUcAAVaUN.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/315470/83f75129-bcbf-9fad-48de-cae65fd5ea2b.jpeg)

いや良くはないが。

***

# まとめ
iOS への好感度が5下がった。

`muted` や `playsinline` なんかはネットに情報があふれているので簡単に対処できたのだが、最後のバグめいた挙動に非常に困らされた。
同様の事象に苛まれた方への応急処置方策になれば幸いである。
原因に心当たりのある方も募集中。この挙動にピンときたら。

# 参考資料
[MDN Web Docs - \<video>: 動画埋め込み要素](https://developer.mozilla.org/ja/docs/Web/HTML/Element/video)
[MDN Web Docs - メディアおよびウェブオーディオ API の自動再生ガイド](https://developer.mozilla.org/ja/docs/Web/Media/Autoplay_guide)
[latest log - iOS 10 Safari から video の inline 再生が可能になります](https://uupaa.hatenablog.com/entry/2016/08/29/171809)
[Qiita - モバイルブラウザのビデオ再生がいろいろ変わるので確かめてみた](https://qiita.com/tomoyukilabs/items/cb9dd1d3e7eb0cc7f58a)