videoタグがipadでうまく再生できない考察

## はじめに
動画ファイルがたくさん溜まってきたのでwebで検索し再生するソフトを作成した。html5からvideoタグが使えるようになり、新しいwebの作り方を勉強しつつ作成したものです。しかし、パソコン・Androidスマホでwebを操作し再生することはできたのだが、ipadで再生することができなかった。はじめてのvideoでもあり、使い方に間違いがあるのかもしれませんが、結局原因がわかりませんでした。

今回、調査した内容をまとめるとともに、当面の運用として対処法を示す。

## 使用したプログラム
原因調査をするため、プログラムをシンプルにした。プログラムはhtmlファイルとビデオを提供するphpファイルです。
対象動画ファイル(003.mp4)：サイズは1280x720、長さは2:04:33、ファイルサイズは1.33GiB
大きいファイルサイズの動画が対象のため、一度に全てを送信するにはサーバの負担が大きすぎるため、最大1MBに制限しクライアントから逐次複数回要求により全体を提供する方法とした。

``` html:index.html
<html lang='ja'>
	<head>
	<meta http-equiv='content-type' content='application/html; charset=EUC-JP' >
	</head>
	<body>
	<div id='video'>
		<video controls width="640" autoplay playsinline>
		    <source src="movie_mp4t.php">
		</video>
	</div>
	</body>
</html>
```

``` php:movie_mp4t.php
<?php
	function write_log($text) {
		umask(0);
		$log = date("Y-m-d H:i:s") . " {$_SERVER['REQUEST_URI']}	{$text}\n";
		error_log($log, 3, "access.log");
	}

	$file = "003.mp4";														// 動画ファイルへのパス
	$size = filesize($file);
	$fp = fopen($file, "rb");
	$step = 1000000;														// 最大伝送サイズ
	if (@$_SERVER["HTTP_RANGE"]){											// ブラウザがHTTP_RANGEを要求してきた場合
		list($start, $end) = sscanf($_SERVER["HTTP_RANGE"], "bytes=%d-%d");	// 要求された開始位置と終了位置を取得
		$s = $end - $start + 1;
		write_log('$_SERVER["HTTP_RANGE"]:'.$_SERVER["HTTP_RANGE"]);
		if (empty($end)) $end = $start + $step - 1;							// 終了位置が指定されていない場合$step bytes出す
		fseek($fp, $start);
	} else {
		$start = 0;
		$end = $step - 1;
	}
	if ($end - $start >= $step) $end = $start + $step - 1;					// 要求が$stepより多い場合$stepに制限
	if ($end >= $size - 1) $end = $size - 1;								// 要求が動画の終了を超えている場合制限
	$c_size = $end - $start + 1;											// 提供サイズ
	$etag = md5($_SERVER["REQUEST_URI"]).$size;								// コンテンツの識別子
	$header_http = "HTTP/1.1 206 Partial Content";
	$header_content = "Content-Type: video/mp4";
	$header_accept = "Accept-Ranges: bytes";
	$header_range = "Content-Range: bytes {$start}-{$end}/{$size}";
	$header_length = "Content-Length: {$c_size}";
	$header_etag = "Etag: \"{$etag}\"";
	header($header_http);
	write_log($header_http);
	header($header_content);
	write_log($header_content);
	header($header_accept);													// HTTP_RANGE(部分リクエスト)に対応
	write_log($header_accept);
	header($header_range);
	write_log($header_range);
	header($header_length);
	write_log($header_length);
	header($header_etag);
	write_log($header_etag);
	if ($c_size) echo fread($fp, $c_size);									// ファイルポインタの開始位置からコンテンツ長だけ出力
	fclose($fp);
?>
```

## 状況
以下のログのとおり、複数回のやり取り後止まってしまいます。
ログが長いので固定メッセージを省略し示す。

``` access.log
2020-01-15 08:22:06 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=0-1
2020-01-15 08:22:06 /movie4/movie_mp4t.php	HTTP/1.1 206 Partial Content
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Type: video/mp4
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Accept-Ranges: bytes
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Range: bytes 0-1/1432153772
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Length: 2
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Etag: "c7ad0632263096871e3aaf236ccf7e301432153772"
2020-01-15 08:22:06 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=0-1432153771
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Range: bytes 0-999999/1432153772
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:06 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1423966208-1432153771
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Range: bytes 1423966208-1424966207/1432153772
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:06 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1427505152-1432153771
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Range: bytes 1427505152-1428505151/1432153772
2020-01-15 08:22:06 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1432092672-1432153771
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1432092672-1432153771/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 61100
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1424228320-1427505151
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1424228320-1425228319/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1427800032-1432092671
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1427800032-1428800031/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1425228320-1427505151
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1425228320-1426228319/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1426228320-1427505151
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1426228320-1427228319/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1427228320-1427505151
2020-01-15 08:22:07 /movie4/movie_mp4t.php	HTTP/1.1 206 Partial Content
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 276832
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1428800032-1432092671
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Range: bytes 1428800032-1429800031/1432153772
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:07 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1429800032-1432092671
2020-01-15 08:22:07 /movie4/movie_mp4t.php	HTTP/1.1 206 Partial Content
2020-01-15 08:22:07 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:08 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1430800032-1432092671
2020-01-15 08:22:08 /movie4/movie_mp4t.php	Content-Range: bytes 1430800032-1431800031/1432153772
2020-01-15 08:22:08 /movie4/movie_mp4t.php	Content-Length: 1000000
2020-01-15 08:22:08 /movie4/movie_mp4t.php	$_SERVER["HTTP_RANGE"]:bytes=1431800032-1432092671
2020-01-15 08:22:08 /movie4/movie_mp4t.php	Content-Range: bytes 1431800032-1432092671/1432153772
2020-01-15 08:22:08 /movie4/movie_mp4t.php	Content-Length: 292640
```

## 考察
ログを以下のように整理する。
14回の要求で動画のindexを読み終えた段階で停止している。

```sh:最大サイズ1MB
            要 求                  応 答          転 送 
    ---------- ---------- ---------- ---------- ------- ----------------------------------
 1:          0-         1          0-         1       2 ファイルの識別子？２バイト読み込み
 2:          0-1432153771          0-    999999 1000000 ファイル全体を要求されるが1MB送信
 3: 1423966208-1432153771 1423966208-1424966207 1000000 動画のindex全体を要求されるが1MB送信
 4: 1427505152-1432153771 1427505152-1428505151 1000000 動画のindex
 5: 1432092672-1432153771 1432092672-1432153771 1000000 動画のindex
 6: 1424228320-1427505151 1424228320-1425228319 1000000 動画のindex
 7: 1427800032-1432092671 1427800032-1428800031 1000000 動画のindex
 8: 1425228320-1427505151 1425228320-1426228319 1000000 動画のindex
 9: 1426228320-1427505151 1426228320-1427228319 1000000 動画のindex
10: 1427228320-1427505151 1427228320-1427505151  276832 動画のindex
11: 1428800032-1432092671 1428800032-1429800031 1000000 動画のindex
12: 1429800032-1432092671 1429800032-1430800031 1000000 動画のindex
13: 1430800032-1432092671 1430800032-1431800031 1000000 動画のindex
14: 1431800032-1432092671 1431800032-1432092671  292640 動画のindex
```

転送最大サイズを変更して実施してみる。

```sh:最大サイズ2MB
            要 求                 応 答          転 送 
    ---------- ---------- ---------- ---------- ------- ----------------------------------
 1:          0-         1          0-         1       2 ファイルの識別子？２バイト読み込み
 2:          0-1432153771          0-   1999999 2000000 ファイル全体を要求されるが2MB送信
 3: 1423966208-1432153771 1423966208-1425966207 2000000 動画のindex全体を要求されるが2MB送信
 4: 1427505152-1432153771 1427505152-1429505151 2000000 動画のindex
 5: 1432092672-1432153771 1432092672-1432153771   61100 動画のindex
 6: 1424203752-1427505151 1424203752-1426203751 2000000 動画のindex
 7: 1427726312-1432092671 1427726312-1429726311 2000000 動画のindex
 8: 1426203752-1427505151 1426203752-1427505151 1301400 動画のindex
 9: 1429726312-1432092671 1429726312-1431726311 2000000 動画のindex
10: 1431726312-1432092671 1431726312-1432092671  366360 動画のindex
```

```sh:最大サイズ3MB
            要 求                 応 答          転 送 
    ---------- ---------- ---------- ---------- ------- ----------------------------------
 1:          0-         1          0-         1       2 ファイルの識別子？２バイト読み込み
 2:          0-1432153771          0-   2999999 3000000 ファイル全体を要求されるが3MB送信
 3: 1423966208-1432153771 1423966208-1426966207 3000000 動画のindex全体を要求されるが3MB送信
 4: 1427505152-1432153771 1427505152-1430505151 3000000 動画のindex
 5: 1432092672-1432153771 1432092672-1432153771   61100 動画のindex
 6: 1424179176-1427505151 1424179176-1427179175 3000000 動画のindex
 7: 1427677168-1432092671 1427677168-1430677167 3000000 動画のindex
 8: 1427179176-1427505151 1427179176-1427505151  325976 動画のindex
 9: 1430677168-1432092671 1430677168-1432092671 1415504 動画のindex
```

```sh:最大サイズ4MB
            要 求                 応 答          転 送 
    ---------- ---------- ---------- ---------- ------- ----------------------------------
 1:          0-         1          0-         1       2 ファイルの識別子？２バイト読み込み
 2:          0-1432153771          0-   3999999 4000000 ファイル全体を要求されるが4MB送信
 3: 1423966208-1432153771 1423966208-1427966207 4000000 動画のindex全体を要求されるが4MB送信
 4: 1427505152-1432153771 1427505152-1431505151 4000000 動画のindex
 5: 1432092672-1432153771 1432092672-1432153771   61100 動画のindex
 6: 1424277472-1427505151 1424277472-1427505151 3227680 動画のindex
 7: 1427767264-1432092671 1427767264-1431767263 4000000 動画のindex
 8: 1431767264-1432092671 1431767264-1432092671  325408 動画のindex
 9:     196584-1423966207     196584-   4196583 4000000 動画
10:    4196584-1423966207    4196584-   8196583 4000000 動画
```

最後の最大サイズ4MBにすることで正常に動画が再生された。
何れの最大サイズにおいても3:～5:の要求が全く同じで応答レンジとは全く関係がない。
正常に再生されない最大サイズでは、3:と4:の隙間を6:以降で補完しているが、4:と5:の補完が完全に行えていなく再生に失敗していると思われる。最大サイズ4MBの4:と5:には偶然（？）隙間がなく、再生できた。更に、サイズの大きな動画ファイルでは最大サイズ4MBでも再生に失敗することもわかっている。
ここでは、紹介しなかったがプログレッシブダウンロード対応のMP4ファイル（indexをファイルの前方に配置する）を作成し実験したが変化はなかった。

## 結論
原因については、ipad-osのバグと考えたほうが自然では有るが、これまでに対応されていないところを見るとなにか理由がありそうである。結局原因は全く分からず対処法として以下で対応している。
動画サイズによって、変動することから最大サイズを動画サイズの1/300とすることで取り敢えず再生できている。

```
-	$step = 1000000;														// 最大伝送サイズ
+	$step = round($size / 300);												// 最大伝送サイズ
```

因みに、PCで再生した場合を以下に示す。若干クセがありますが、順次読み取っていくシンプルな読み方となっています。

```sh:最大サイズ1MB(PC)
            要 求                 応 答          転 送 
    ---------- ---------- ---------- ---------- ------- ----------------------------------
 1:          0-                    0-    999999 1000000 全体要求
 2: 1423966208-1432153771 1423966208-1424966207 1000000 動画のindex全体を要求されるが1MB送信
 3: 1424966208-           1424966208-1425966207 1000000 動画のindex
 4: 1425966208-1432153771 1425966208-1426966207 1000000 動画のindex
 5: 1426966208-           1426966208-1427966207 1000000 動画のindex
 6: 1427966208-1432153771 1427966208-1428966207 1000000 動画のindex
 7: 1428966208-           1428966208-1429966207 1000000 動画のindex
 8: 1429966208-1432153771 1429966208-1430966207 1000000 動画のindex
 9: 1430966208-           1430966208-1431966207 1000000 動画のindex
10: 1431966208-1432153771 1431966208-1432153771  187564 動画のindex
11:          0-1430966271          0-    999999 1000000 動画（？）
12:          0-1432153771          0-    999999 1000000 動画
13:    1000000-              1000000-   1999999 1000000 動画
14:    2000000-1432153771    2000000-   2999999 1000000 動画
15:    3000000-              3000000-   3999999 1000000 動画
```

## 参考

* [phpを経由して動画(mp4)を出力する方法](https://www.systemexpress.co.jp/php/mp4.html)
* [RFC 7233, Range Requests（日本語訳）](https://triple-underscore.github.io/RFC7233-ja.html)
* [プログレッシブダウンロード対応の MP4ファイル を作成する :Tips & FAQ](https://aruo.net/arbk/blog/article/techclip_how2make_mp4_video_file_for_progressive_download_with_ffmpeg_or_mp4box#MAKESMP4FORPRGRSVDLBYFFMPEG)