HTTP Live Streaming (HLS)の基本とngx_http_hls_moduleについて調べて試してみた

# この記事の対象

* ngx_http_hls_moduleを試してみたい方
* HLSに興味があるのでとりあえず試して見たい方
    * 自分なりに調べたまとめという内容ですので、ただシンプルにHLSの動作を確認してみたいという方は以下のページを参考にし、**本内容は読まなくて良い**です。
        * [HLS（HTTP Live Streaming）で動画を配信・再生してみよう | YAZ技術ブログ](https://www.yaz.co.jp/tec-blog/web%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9/212)
        * [動画をストリーミング配信する方法 | IT SKILL MAP](https://itskillmap.com/menu2_30/)
        * [ffmpeg を使い、HTTP Live Streaming(HLS)をファイルに保存し、保存したファイルからストリーミングを行う方法について - Qiita](https://qiita.com/uupaa/items/c76c76cb149470bf89f2)
        * [HTTP Live Streamingを試してみた - UUUM攻殻機動隊(エンジニアブログ)](http://system.blog.uuum.jp/entry/2017/10/20/110000)
        * [HTTP Live Streaming 動画配信のやり方 （実践） |  ビズバレーブログ](https://bizvalley.co.jp/blog/1090.html)
        * [ffmpeg で mp4 をiPhone用のストリーミング（HLS）に対応させる。 - それマグで！](http://takuya-1st.hatenablog.jp/entry/2016/04/06/034906)
        * [ffmpegでUSBカメラの映像をHLSでライブストリーミングする](https://signal-flag-z.blogspot.com/2018/03/ffmpeg-hls-streaming.html)



**動画配信技術、HLSについて詳しく知りたい方は以下のリンクなどを参考にし、本内容は読まなくて良いです**

* [HTTP Live Streaming - Wikipedia](https://en.wikipedia.org/wiki/HTTP_Live_Streaming)
* [フロントエンドエンジニアのための動画ストリーミング技術基礎 | ygoto3.com](https://ygoto3.com/posts/streaming-technology-basics-for-frontend-engineers/)
* [nginxでストリーミングサーバを立ててライブ配信する。 - Qiita](https://qiita.com/hagane5563/items/842afe6d6e7100db3a28)
* [HLSについて知っていることを話します](https://www.slideshare.net/moriyoshi/hls-46107808)
* [ライブ動画配信プロトコル（HTTP Live Streaming, HLS）の概要図解メモ（AbemaTV／FRESH!）](https://did2memo.net/2017/02/20/http-live-streaming/)
* [動画配信技術 その1 - HTTP Live Streaming(HLS) - Akamai Japan Blog](https://blogs.akamai.com/jp/2013/02/-1---http-live-streaminghls.html)
* [HLSとは - Qiita](https://qiita.com/STomohiko/items/eb223a9cb6325d7d42d9)
* [オンデマンド動画配信を支える技術 - わかる！HLS編 - Qiita](https://qiita.com/takeyuweb/items/331b80f2daad1527a99c)

※本記事で解説している内容もほとんど↑のリンクに書いてある内容から記載しています

# 本記事の要約

* HTTP Live Streaming(HLS)を使うと何が嬉しいか
    1. 「動画配信って動画全て読み込んでからの再生だと動きが遅いなあ」
    2. 「HTTP Live Streaming(HLS)を使えば読み込んだ部分から再生し、再生しながら次の部分を読み込むということが可能だよ」
    3. 「すごい！HLS便利！」
* ngx_http_hls_moduleを使うと何が嬉しいのか
    1. 「でもHLSで再生するには、`hoge.mp4`ファイルから`.m3u8`ファイルと複数`.ts`ファイルに変換する一手間があるな」
    2. 「ngx_http_hls_moduleを使えば`hoge.mp4`を変換しなくても、URLで`http://hogexxx.com/hoge.mp4.m3u8`とリクエストを送ったらサーバがよしなに解釈してくれるよ」
    3. 「すごい！まじかよ！超便利！！！」 


---

# ストリーミングとは

* [ストリーミング - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%9F%E3%83%B3%E3%82%B0)

> ストリーミング (英語: streaming) とは、主に音声や動画などのマルチメディアファイルを転送・再生するダウンロード方式の一種である。

> 通常、ファイルはダウンロード完了後に開く動作が行われるが、動画のようなサイズの大きいファイルを再生する際にはダウンロードに非常に時間がかかってしまい、特にライブ配信では大きな支障が出る。そこで、ファイルをダウンロードしながら、同時に再生をすることにより、ユーザーの待ち時間が大幅に短縮される。この方式を大まかに「ストリーミング」と称することが多い。


# HTTP Live Streaming (HLS)とは

* [ストリーミング - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%9F%E3%83%B3%E3%82%B0)

> アップルによるHTTPベースのストリーミング・プロトコル。IETF標準化を目指して仕様が公開されており、多くのサーバーやクライアントが対応している。また、いくらかのブラウザ(Safari、Androidなど)でも直接再生することができる。Flash Playerも再生に対応している。また、hls.jsなどのスクリプトを使うことで、[HTML5のMedia Source Extensions（英語版）](https://en.wikipedia.org/wiki/Media_Source_Extensions) APIに対応しているブラウザでも再生することができる。

---

* HTTPを使うので通常のwebサーバで通信できる
* プレイリスト(`.m3u8`ファイル)とセグメントファイル(動画を数秒ごとに分割したMPEG-2 TS形式のファイル)の2種類で構成されている
* `.m3u8`ファイルをまず読み込み、そこに記載されている動画ファイルへのパスを次々読み込んで行くという流れで動画配信を行う

##  プレイリスト(`.m3u8`ファイル)

### `.m3u8`ファイルとは

* [M3U - Wikipedia](https://ja.wikipedia.org/wiki/M3U)

> M3Uファイルは、一つ、または複数のメディアファイルのパスをプレーンテキスト（テキストファイル)で記述したものである。このファイルを、".m3u"または".m3u8"の拡張子で保存する。 M3UファイルのエンコードはWindows-1252の場合が多いものの、CP932に対応しているものも存在する。エンコードがUTF-8であることを明示するとき、拡張子M3U8を使用する。

> それぞれのエントリには以下のような記述をする。

> * 絶対パス（例: C:\My Music\Heavysets.mp3
> * 相対パス（例: Heavysets.mp3）
> * URL
> M3Uファイルにはコメントを含むことができ、#以降がコメントとみなされる。

> M3Uフォーマットの一般的な使用方法として、単一のエントリにURLを記述することがあげられる。このファイルのおかげで、ストリーミングへのアクセス、ウェブサイトからのダウンロード、インターネットラジオの視聴が容易になる。

> iOSのHTTP Live Streamingフォーマットは"M3U" and "M3U8" ファイルをもとにしている。

ただのテキストファイルで実際のメディアファイルへのパスが記載されている

あとで実際に作成しますが、自分が作ったのはこのような感じになりました

```:playlist.m3u8 
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:13
#EXTINF:10.500000,
segment-000.ts
#EXTINF:1.458333,
segment-001.ts
#EXTINF:3.875000,
segment-002.ts
#EXTINF:7.291667,
segment-003.ts
#EXTINF:10.416667,
(省略)
```

このplaylistを元に、`.ts`ファイルを順番に取得しつつ再生していきます

## セグメントファイル(分割したMPEG-2 TS形式のファイル)

* 拡張子`.ts`
* 一つの配信に対して`.ts`ファイルが大量にできる
* 映像コーデックはH.264、音声コーデックはAAC を使うのが一般的なようです



### コーデックとは

* [コーデック - Wikipedia](https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%BC%E3%83%87%E3%83%83%E3%82%AF)

> コーデック (Codec) は、符号化方式を使ってデータのエンコード（符号化）とデコード（復号）を双方向にできる装置やソフトウェアなどのこと。 また、そのためのアルゴリズムを指す用語としても使われている。

> コーデックには、データ圧縮機能を使ってデータを圧縮・伸張するソフトウェアや、音声や動画などのデータを別の形式に変換する装置およびソフトウェアが含まれる。

コーデックについて自分が分かりやすかったページ

* [コーデックとは？ 動画を扱うなら知っておきたい基礎知識｜ネットワークビデオレコーダー（NVR）](https://nvr.bz/topics/knowledge/what-codec.php)
* [動画形式の「コンテナ」と「コーデック」についての説明｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/column11.html)


## ブラウザ対応状況

https://caniuse.com/#search=hls

2019/01/02現在

![HTTP Live Streaming(HSL) ブラウザ対応状況](https://qiita-image-store.s3.amazonaws.com/0/142910/7f89d53b-b4cf-451f-04db-d8516857f68f.png)

chromeなどの主要なブラウザでは対応されていませんので、
Media Source Extensions([Media Source Extensions - Wikipedia](https://en.wikipedia.org/wiki/Media_Source_Extensions))を利用するとchromeなどでもHLS再生できるようです

https://caniuse.com/#search=mse

![Media Source Extensions(MSE) ブラウザ対応状況](https://qiita-image-store.s3.amazonaws.com/0/142910/7f469c9a-dc7a-28e0-5225-bae26e85b618.png)

MSEで再生するのに一番簡単にはhls.jsを使うと良いようです
https://github.com/video-dev/hls.js/

# HLSを試して見る

## 確認Version

VagrantでCentOSの環境を作成して確認しています

* macOS High Sierra
* VirtualBox 5.2.22
* Vagrant 2.2.2
* CentOS 7
* nginx/1.15.7 (nginx-plus-r17)
* ffmpeg version N-92867-g86555a2

## 使用する動画

[Big Buck Bunny ≫ Download](https://peach.blender.org/download/)

mp4の動画をダウンロードする


![Big Buck Bunny](https://qiita-image-store.s3.amazonaws.com/0/142910/96776313-5d88-3379-7c1f-855f0e36cdbe.png)

(c) copyright 2008, Blender Foundation / www.bigbuckbunny.org

## ffmpegで`mp4`を変換

* nginxのrootにmp4ファイルを置く

```bash
$ cd /usr/share/nginx/html
$ ls *.mp4
BigBuckBunny_320x180.mp4
```

* ffmpeg実行 ※バッククォート内の文字はコメントなのでなくてもOK

```bash
$ ffmpeg \
-i BigBuckBunny_320x180.mp4   `# 入力ファイル` \
-map 0 \
-f segment \
-vcodec libx264   `# 映像コーデックを指定` \
-acodec libfdk_aac   `# 音声コーデックを指定` \
-segment_list video/playlist.m3u8   `# プレイリスト名` \
-segment_time 5   `# 5秒ずつぐらいに分割される` \
video/segment-%03d.ts   `# .tsファイル名 segment-000.ts 〜　segment-999.ts になる`
```

※↑こちらが詳しいです。 https://www.slideshare.net/moriyoshi/hls-46107808/14

* プレイリストファイルとセグメントファイルが作成される

![プレイリストファイルとセグメントファイル](https://qiita-image-store.s3.amazonaws.com/0/142910/da18d59a-a4ec-8d1d-9270-4f480fa5cf63.png)

## 再生

* この状態でSafariから`video/playlist.m3u8`にアクセスしたら再生ができる！

![Safariから`video/playlist.m3u8`にアクセス](https://qiita-image-store.s3.amazonaws.com/0/142910/08b321a2-01c2-eed1-3cb3-722088dcafac.png)

### chromeから再生できるようにする

```bash
$ sudo touch /usr/share/nginx/html/live-stream.html
```

* https://github.com/video-dev/hls.js/ のREADMEのGetting Startedからコードをコピペし編集

```html:live-stream.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <!-- Or if you want a more recent canary version -->
    <!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
    <video id="video" controls preload="none"></video>
    <script>
    var video = document.getElementById('video');
    if(Hls.isSupported()) {
        var hls = new Hls();
        hls.loadSource('http://192.168.33.10/video/playlist.m3u8');
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED,function() {
            video.play();
        });
    }
    </script>
</body>
</html>
```

* chromeで再生でき、`.ts`ファイルを一定間隔で取得しているのも確認できました

![hls.jsを使ってchromeから再生](https://qiita-image-store.s3.amazonaws.com/0/142910/b1cd16f3-5850-a175-2905-ee8d115721d6.gif)



# ngx_http_hls_module

商用版のNGINX Plusで利用できます

## ngx_http_hls_moduleとは

* [Module ngx_http_hls_module](http://nginx.org/en/docs/http/ngx_http_hls_module.html)

> The ngx_http_hls_module module provides HTTP Live Streaming (HLS) server-side support for MP4 and MOV media files. Such files typically have the .mp4, .m4v, .m4a, .mov, or .qt filename extensions. The module supports H.264 video codec, AAC and MP3 audio codecs.

Google翻訳↓

> ngx_http_hls_moduleモジュールは、MP4およびMOVメディアファイルに対するHTTP Live Streaming（HLS）サーバー側のサポートを提供します。 このようなファイルは通常、.mp4、.m4v、.m4a、.mov、または.qtのファイル名拡張子を持ちます。 このモジュールは、H.264ビデオコーデック、AACおよびMP3オーディオコーデックをサポートしています。

先ほどHLSを試した時は以下のように実行しました

* `.mp4`ファイルを用意 -> ffmpegで変換 -> `.m3u8`ファイルを読み込んで再生

ngx_http_hls_moduleを使うことで

* `.mp4`ファイルを用意 -> URLで`hoge.mp4.m3u8`などとリクエストするとHLSとして読み込める

↑のように`.mp4`から変換することなくHLSで再生することが可能です

## ngx_http_hls_moduleの設定

```bash
sudo vi /etc/nginx/conf.d/default.conf
```

公式を参考にして設定追加

```diff
+    location /hls/ {
+        hls;
+        hls_fragment            5s;
+        hls_buffers             10 10m;
+        hls_mp4_buffer_size     1m;
+        hls_mp4_max_buffer_size 5m;
+        root /var/video/;
+    }
```

![default.conf](https://qiita-image-store.s3.amazonaws.com/0/142910/c6af7ccd-c6d7-a722-9a85-d4b4992f886d.png)

```bash
$ sudo systemctl restart nginx # nginx再起動
$ sudo mkdir -p /var/video/hls
$ sudo cp BigBuckBunny_320x180.mp4 /var/video/hls/BigBuckBunny_320x180.mp4 # confファイルのrootに設定したパスに動画をコピー
```

## 再生

```bash
sudo vi /usr/share/nginx/html/live-stream.html
```

動画へのパスを編集

```diff:/usr/share/nginx/html/live-stream.html
-       hls.loadSource('http://192.168.33.10/video/playlist.m3u8');
+       hls.loadSource('http://192.168.33.10/hls/BigBuckBunny_320x180.mp4.m3u8');
```

ブラウザでアクセスすると、先ほどと同じように再生されました

![ブラウザでngx_http_hls_moduleを使用してHLSで配信](https://qiita-image-store.s3.amazonaws.com/0/142910/d12b6857-79cd-25cd-bc3b-829a4b16fc45.png)

この時`http://192.168.33.10/hls/BigBuckBunny_320x180.mp4.m3u8`は以下のようなレスポンスを返します

```BigBuckBunny_320x180.mp4.m3u8
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:5
#EXT-X-PLAYLIST-TYPE:VOD

#EXTINF:5.000,
BigBuckBunny_320x180.mp4.ts?start=0.000&end=5.000
#EXTINF:5.000,
BigBuckBunny_320x180.mp4.ts?start=5.000&end=10.000
#EXTINF:5.000,
BigBuckBunny_320x180.mp4.ts?start=10.000&end=15.000
#EXTINF:5.000,
BigBuckBunny_320x180.mp4.ts?start=15.000&end=20.000
#EXTINF:5.000,
BigBuckBunny_320x180.mp4.ts?start=20.000&end=25.000
(省略)
```

クエリストリングの`start`と`end`が5秒おきに設定されます。これで5秒おきに動画を分割したのと同じように扱ってくれます。

confファイルの設定を変えれば5秒以外にも設定できます

```nginx
   　 location /hls/ {
      　  hls;
     　   hls_fragment            5s; # <- ここの設定を変える
    　    hls_buffers             10 10m;
   　     hls_mp4_buffer_size     1m;
  　      hls_mp4_max_buffer_size 5m;
 　       root /var/video/;
　    }
```

confファイルの設定を変えなくても、`http://192.168.33.10/hls/BigBuckBunny_320x180.mp4.m3u8?len=10`としても動画を分割する間隔の秒数を指定できる

---

その他パラメータ

* `start`

動画の開始位置を秒数で指定できる

* `end`

動画の終了位置を秒数で指定できる

* `offset`

動画の再生を指定秒数からにする

なので、以下のようなパラメータでリクエストを送ると

`http://192.168.33.10/hls/BigBuckBunny_320x180.mp4.m3u8?offset=5&start=3&end=20`

元の動画の3秒〜20秒までを切り取った17秒間の動画として扱い、
その17秒間の動画の5秒目から再生されるという動きになります

![パラメータ送信](https://qiita-image-store.s3.amazonaws.com/0/142910/d1249f59-56d7-4c51-d4f5-bb00bf3e746d.png)

---


詳しく知りたい方は公式を参照

* [Module ngx_http_hls_module](http://nginx.org/en/docs/http/ngx_http_hls_module.html)


最後まで読んでいただいてありがとうございましたm(_ _)m

---

参考

* [Instructions | NGINX](https://cs.nginx.com/repo_setup)
* [NGINX Plusでできることまとめ - Qiita](https://qiita.com/harukasan/items/5123f797a876696b343e)
* [html5 - Http Media Streaming Server - Stack Overflow](https://stackoverflow.com/questions/20369046/http-media-streaming-server)
* [CompilationGuide/Centos – FFmpeg](https://trac.ffmpeg.org/wiki/CompilationGuide/Centos)
* [CentOS7にffmpegのインストール - うま味がない](https://negima.mobi/2018/07/2925)
* [CentOS7にffmpegをyumインストール - Qiita](https://qiita.com/snoguchi/items/d12f0407075d7978925c)
* [[小ネタ] Video.jsのvideojs-contrib-hlsを使ってHLS形式のライブ配信を再生する最低限度の設定 ｜ DevelopersIO](https://dev.classmethod.jp/client-side/javascript/videojs-contrib-hls-livestreaming-minimum-setting/)
* [bash - How to put a line comment for a multi-line command - Stack Overflow](https://stackoverflow.com/questions/9522631/how-to-put-a-line-comment-for-a-multi-line-command)
* [javascript - How to handle "Uncaught (in promise) DOMException: play() failed because the user didn&#39;t interact with the document first." on Desktop with Chrome 66? - Stack Overflow](https://stackoverflow.com/questions/49930680/how-to-handle-uncaught-in-promise-domexception-play-failed-because-the-use)
* [Disable html5 video autoplay - Stack Overflow](https://stackoverflow.com/questions/19664622/disable-html5-video-autoplay)