HTTP Live Streaming

ちょっと調べる機会あったので、メモ。
ライブ配信ではなく、動画のオンデマンド配信についてが中心になります。

HLS(HTTP Live Streaming)はサーバの特別な設定なしでストリーミングが出来るようです。
ライブ配信する時はリアルタイムで変換する必要があるのでFFMPEGと組み合わせて使用します。
クライアント側でやりくりしている部分があるためAppleのデバイス限定となります。

Appleの公式ドキュメントや参考にしたサイトは下記。
https://developer.apple.com/jp/devcenter/ios/library/documentation/StreamingMediaGuide.pdf (PDF)
http://venture-blog.blogspot.jp/2012/05/http-live-streaming.html

オンデマンドで動画を配信する際もライブ配信も、MPEGのTSフォーマットで10秒区切りとか細切れでファイルを用意して、m3u8 のプレイリストで配信するのは共通です。

1
2
3
4
5
6
7
8
9
10
11
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10, no desc
fileSequence0.ts
#EXTINF:10, no desc
fileSequence1.ts
#EXTINF:10, no desc
fileSequence2.ts
...
#EXT-X-ENDLIST

こんな感じでTSファイルの場所をリストにして指定していきます。
.htaccess や Apache のMIME Type設定も忘れずに。

1
2
AddType application/x-mpegURL .m3u8
AddType video/MP2T .ts

あとは、配信用のページを用意すればひとまず配信可能です。

1
<a href="playlist.m3u8"><video src="playlist.m3u8" autoplay="true" controls="false" width="320" height="180"></video></a>

ライブ配信の場合はリアルタイムでTSファイルを生成しつつ、こちらのプレイリストを更新します。
iPhoneやiPadから見るとこのm3u8ファイルに何度もリクエストがきます。
m3u8ファイル最後の #EXT-X-ENDLIST は配信が終わるまでは出力しないことになります。

で、ここからが今回調べた内容です。前置き長かったですが…
要求としては、なるべく動画を見てもらいたいので早送りできない方法はないものか?というものでした。

通常のオンデマンド配信だと動画のスキップ(早送り)が出来るのですが、擬似的にライブ配信ということにすれば早送りはしにくくなるのではないかと。
という訳で、PHPでサーバ側で動的にプレイリストを出力するというのを試してみました。
クライアント側からリクエストが何度も来るので、セッションで管理して最初のアクセスからの時間経過でプレイリストを更新するようにしました。

ただ、拡張子が .php のままだと、iPhone/iPad で見た時に再生してくれないようです。
なので、.m3u8 で PHP を実行するように .htaccess なり mod_rewrite で設定する必要があります。

今回調べてて思ったより<video>タグは使えるレベルになってる気がしました。
相変わらずデバイスやブラウザ間の調整は必要ですが、あんまり変わったことしなければ何とかなりそうですね。

擬似ライブ配信サンプル ※iPhone / iPad から見てください

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
$video = array(
    'fileSequence0.ts',
    'fileSequence1.ts',
    'fileSequence2.ts',
    'fileSequence3.ts',
    'fileSequence4.ts',
);
$num = count($video);
$maxLength = 50; // sec, 動画の長さ
$length = 10; // sec, 分割動画の長さ
$now = time();

session_name('videoCount');
session_cache_limiter('nocache');
session_cache_expire(60);
session_start();
session_regenerate_id();

if (isset($_SESSION['count'])) {
    if ($now - $_SESSION['count'] > $maxLength) {
        $_SESSION['count'] = $now - 1;
    }
} else {
    $_SESSION['count'] = $now - 1;
}

$page = ceil(($now - $_SESSION['count']) / $length);
if ($page > $num) {
    $page = $num;
}

header("Content-type: application/x-mpegURL");
echo '#EXTM3U';
echo "n";
echo '#EXT-X-TARGETDURATION:10';
echo "n";
echo '#EXT-X-MEDIA-SEQUENCE:0';
echo "n";
echo '#EXT-X-ALLOW-CACHE:NO';
echo "n";

for ($i = 0; $i < $page; $i++) {
    echo '#EXTINF:10, no desc';
    echo "n";
    echo $video[$i];
    echo "n";
}
if ($page == $num) {
    echo '#EXT-X-ENDLIST';
}