AWS MediaConvert と hls.js で動画配信サービスを構築しました
こんにちは、第一開発グループの矢野です。ジョブメドレー開発エンジニアとして、主にバックエンドを担当しています。
直近では、ジョブメドレーが先月リリースした 「動画選考」 機能の開発プロジェクトに携わっており、動画ファイルのアップロード/配信環境の設計・実装を行っていました。
今回のブログでは、この「動画選考」機能の開発に利用した AWS Elemental MediaConvert サービスと、hls.js という OSS ライブラリについて紹介したいと思います。
ジョブメドレーの「動画選考」機能
はじめに、今回リリースした「動画選考」機能について概要を紹介します。
新型コロナウイルス感染拡大によって、対面での面接に不安を感じたり、公共交通機関の利用が難しくなったりすることにより、満足な転職活動ができなくなっている方もいらっしゃるかと思います。 このような課題を解決するために、ジョブメドレーではリアルタイムにオンラインで面接を行う「WEB 面接」と、事業者があらかじめ設定した質問に対して応募者が動画で回答を送る「動画選考」の 2 つの機能を提供開始いたしました。
動画選考(動画面接)は、近年増加傾向にあるオンライン選考の一種です。一般的に、求職者 / 就活生が PC ・スマートフォン等のカメラで、予め用意された設問に応じて動画を撮影し、企業に送ることで選考を行います。
私たちジョブメドレーの動画選考では、事業所があらかじめ設定した質問に対して、求職者が回答動画を提出することができます。事業所も求職者も、動画で質問・回答を送ることで、書類だけでは伝わらない雰囲気や強みを相手に伝えることができます。
動画配信サービスの設計ポイント
Web アプリでこのような動画配信サービスを開発する場合、「ユーザによる動画アップロード環境」と「ユーザへの動画の配信・再生環境」を提供する必要があります。
ジョブメドレーで扱う動画は一般公開されるものではなく、公開条件も複雑です。
よって今回は、この「動画アップロード/配信環境」を自サービス内に構築する方針をとり、以下のような動画まわりの設計ポイントについて検討・技術選定を行うことにしました。
(もちろん、要件によっては YouTube や、法人向け動画配信プラットフォームを契約した方が手軽な場合もあるかと思います)
- 動画の録画・撮影
- サポートしたい動画ファイルのフォーマットをどうするか
- Web アプリ内に録画機能を設けるか
- 動画のアップロード(ストレージ)
- 動画ファイルのバリデーションで「動画ファイルの解析」を行うか
- 動画ファイルのアップロード先(ストレージ)をどこにするか
- 動画のエンコード
- 動画ファイルのエンコード形式(H.264、HLS 等)をどうするか
- 非同期エンコードの場合、ステータス検知・エラーハンドリングをどうするか
- 動画の配信(ダウンロード)
- 配信形式(ダウンロード/ストリーミング)をどうするか
- 暗号化をする場合、復号をどのように行うか
- 動画ファイルの公開方法(アクセス制限)をどうするか
- 動画の再生
- Web ページ上で再生させるのか、その場合の表示・再生制御をどうするか
- ブラウザサポートをどこまでにするか、非対応・エラー時の制御をどうするか
今回は、上記の太字で記載した 「動画のエンコード」に MediaConvert を、「動画の再生」に hls.js をそれぞれ採用しています。
各項の詳細は省きますが、全体を通して大まかに、以下のフローで「動画アップロード → エンコード(変換)→ 配信・再生」を実現することにしました。
- ブラウザから Ajax で動画を S3 へアップロードする
- MediaConvert が動画を HLS 形式にエンコード(変換)する
- ブラウザで hls.js を使い動画を CloudFront からストリーミング形式で受信、再生する
今回はこの「動画アップロード → エンコード(変換)→ 配信・再生」に焦点を絞り、MediaConvert と hls.js をどのように使ったのかを紹介します。
MediaConvert による HLS エンコード
AWS Elemental MediaConvert は、S3 との親和性が高いファイルベースの動画変換サービスです。自前で ffmpeg などを使って動画エンコードサーバを構築・管理することなく、スケーラブルな動画変換処理を手軽にシステムに組み込むことができます。
料金は出力する動画の再生時間に応じた従量課金です。AWS コンソールから GUI ベースでエンコード設定を作成したり、ジョブ(エンコード処理)を登録することができます。
また、他 AWS サービス同様に API が提供されており、AWS CLI や各言語の SDK を使ってプログラムからエンコード処理を登録することができ、システム連携も容易です。
# CLI でエンコードジョブを登録する例
$ aws --endpoint-url https://abcd1234.mediaconvert.region-name-1.amazonaws.com --region region-name-1 mediaconvert create-job --cli-input-json file://~/job.json
上記 CLI コマンドで下のようなエンコード設定を記載した JSON を使いジョブを作成すると、S3 上の動画ファイルをサクッとエンコードしてくれます。ジョブはキューイングされ、内部で並列処理されるため、大量のエンコード要求にも簡単に応じることができます。
{
...
"Settings": {
"Inputs": [
{
# 入力元の S3 バケット上の動画ファイル key を指定
"FileInput": "s3://testcontent/720/example_input_720p.mov"
}
],
"OutputGroups": [
{
"OutputGroupSettings": {
"FileGroupSettings": {
# 出力先の S3 バケット key を指定
"Destination": "s3://testbucket/output"
}
},
# 動画・音声のエンコード設定を指定
# ここで品質レベル毎に振り分けた複数のファイルを出力したり
# サムネイル jpg を作成したりすることも可能
"Outputs": [
{
"VideoDescription": { … },
"AudioDescriptions": { … }
}
]
}
]
}
}
エンコードが完了したジョブは、cron + SDK などで API を介して定期チェックする他に、CloudWatch Events によるイベント監視 → Lambda で処理するようなこともできます。
なぜ動画を再エンコードするのか
通常、ユーザからアップロードされる動画ファイルは、既に何らかのコーデックで圧縮され .mp4
や .mov
などのコンテナフォーマットに変換されていることが殆どです。
しかし Web ページで <video>
タグを使いこれら動画ファイルを再生しようとした場合、 「動画フォーマットにブラウザが非対応だと再生できない」 という環境依存問題があります。
ブラウザと動画フォーマットのサポート表
この問題に対応するため、多くの動画配信サービスでは、ユーザの動画を多くの環境で再生可能な MP4 コンテナフォーマット(H.264 + AAC コーデック)などの形式へ「再エンコード」しています。
ジョブメドレーの動画選考では上記目的に加えて、動画閲覧時の回線・端末負荷を抑える 「HTTP ストリーミング形式」 で動画を配信するために、アップロードされた動画を全て HLS 形式 にエンコードしています。
HLS - HTTP Live Streaming 形式
HLS は HTTP Live Streaming の略で、Apple 社の開発した規格です。HTTP ベースのストリーミング通信プロトコルで、細切れにした MP4 動画ファイルを分割ダウンロードさせることで動画のストリーミング配信を実現しています。
HLS 形式にエンコードされた動画は .ts
という分割されたメディアファイル群と、 .m3u8
という、メディアファイルの取得先や秒数などを記載したテキストファイルで構成されます。
.m3u8 ファイルの例(マニフェストファイル、プレイリストファイルとも)
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.97663,
media-0.ts
#EXTINF:9.97663,
media-1.ts
#EXTINF:7.10710,
media-2.ts
#EXT-X-ENDLIST
HLS は他のストリーミング形式と比較して、ライブ配信 / VOD どちらにも対応可能なこと、対応ブラウザが多いこと、専用の配信サーバを使わずに配信可能なことなどから、近年の動画配信サービスで広く利用されています。
Web エンジニアの視点から見ても、 HTTP ベースなためキャッシュや HTTPS 暗号化など、既存 Web 技術と掛け合わせることが想像しやすく、扱いやすい印象でした。
MediaConvert の HLS エンコードジョブ設定
実際にプログラムから API 経由で HLS エンコードジョブを登録する際の設定 JSON は、以下のように GUI でジョブテンプレートを作成して確認することができます。
この「 JSON を表示」で、前述した CLI コマンド mediaconvert create-job --cli-input-json
に渡せる JSON が表示されます。実装の際にはこちらを参考にしながら、ユーザーガイド を参照して利用したい機能にあわせた設定を追加していくことをおすすめします。
注意点・つまづいたポイント
- 利用前に IAM で MediaConvert 用ロールの設定が必要です
- AWS コンソールの Service Quotas > AWS サービス > AWS Elemental MediaConvert から確認できますが、エンコード並行処理の同時実行数上限は 20 になっています
- AWS ルートアカウント 1 つにつき 1 サービスが割当てられるので、これを増やしたい場合は申請が必要です
- エンコードジョブをキューイングする「キュー」を作成して、ジョブの登録時に選べるのですが、上記した「並行処理の同時実行数上限」はこの「キュー」毎に均等に振り分けられます
- 例えば「本番キュー」と「検証キュー」の 2 つのキューを作成した場合、それぞれの並行処理の同時実行数上限は 10 ずつになるので注意してください
- マニフェスト期間形式(Manifest duration format)に整数(INTEGER)を指定していると、iOS Safari で「動画の実際の再生時間と、再生プレイヤーのシークバーに表示される合計時間にズレが生じる」問題がありました
- 浮動小数点(FLOATING POINT)に変更することで対応しました、マニフェストファイルに出力される各
.ts
ファイルの長さが、浮動小数点 → 整数に変換され切り上げられることでズレが生じているようでした
- 浮動小数点(FLOATING POINT)に変更することで対応しました、マニフェストファイルに出力される各
hls.js による HLS 動画の再生制御
MediaConvert により HLS 形式にエンコードされた動画を、Web ブラウザで再生するために必要なのが、hls.js です。
ref. video-dev/hls.js
実は HLS によるストリーミング配信は、現状 Safari など限られたブラウザでしかネイティブでサポートされていません。
この HLS 動画を Safari 以外の Google Chrome や IE11 などの主要ブラウザで再生可能にするため、hls.js が利用されています。内部的には、非対応ブラウザ環境において、ブラウザの MediaSource 拡張 を使って HLS 動画を再生する仕様になっています。
Video.js との比較
似たようなライブラリに Video.js というものもあり、導入を迷ったのですが …
- Video.js は UI もセットになった「 HLS に対応した再生プレイヤー」ライブラリ
- HLS 対応以外にも、字幕や章分けなど機能が豊富
- hls.js はブラウザ標準の
<video>
タグで HLS に対応することだけを目的にした「 HLS クライアント」ライブラリ- UI などはなく、動画再生プレイヤーはブラウザ標準のまま
…と、上記のように hls.js の方がシンプルにやりたいことを実現できるため、今回は hls.js を採用しました。
GitHub のスター数は先発の Video.js の方が多いのですが、hls.js も開発は活発で、日本では グノシー さん、世界的には TED や Twitter でも採用されており、十分実績があるかと思います。
hls.js による実装
基本的には README の Getting Started の通りで実装できます。一部 README のサンプルコードから抜粋して解説すると...
var video = document.getElementById("video");
var videoSrc = "https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8";
if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
}
上記 Hls.isSupported()
の分岐で、HLS をネイティブサポートしていないブラウザの処理を実装しています。
本来 <video>
の src
属性にセットするべき .m3u8
ファイルの URL へ hls.loadSource()
でアクセスさせ、クライアントから XHR リクエストを飛ばします。その後 hls.attachMedia()
でインスタンスを DOM 上の <video>
タグに紐づけています。
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
video.addEventListener('loadedmetadata', function() {
video.play();
});
}
上記の分岐が iOS Safari など、HLS 動画をネイティブサポートしているブラウザ向けの処理です。単純に .m3u8
への URL を <video>
タグの src
へ付与しているだけですね。
(サンプルコードでは、マニフェストファイルのロード後に自動再生させるようになっているようです)
注意点・つまづいたポイント
- hls.js クライアントが取得する HLS 動画ファイル群は、CORS ヘッダで GET リクエストを許可された環境に設置する必要があります
.m3u8
マニフェストファイルをアプリの API などから返却する場合、Content-Type をapplication/x-mpegURL
にして渡す必要があります- iOS Safari などの hls.js 非対応ブラウザ向けの実装を意識する必要があります
- hls.js による制御が複雑になるケースでは、同じような制御を hls.js 非対応ブラウザ向けに実装できるか?をイメージできないと手戻りが発生しそうです
この他、フロントエンドでは <video>
タグのブラウザ毎の挙動や、表示の違いに時間がかかりました。(ある程度予想はしていましたが、やはりメディアの取り扱いは難しい…)
hls.js 自体は導入も手軽で、サクッと HLS 動画のマルチブラウザ対応が実現でき、とても使いやすかったです。@types も存在するので、TypeScript 環境でも難なく実装できました。
SSR や HLS + AES-128 の再生にも対応しているので、興味のある方は一度 公式ドキュメント を確認してみてください。
おわりに
従来、動画配信サービスを構築する場合、ffmpeg を載せたエンコードサーバや、ストリーミング配信サーバを別建てして、負荷に応じてスケールさせて…のような設計が必要だったかと思います。
今回、MediaConvert をはじめとした AWS サービスと hls.js を利用することで、手軽に、スケーラブルな動画エンコード/HTTP ストリーミング配信環境を構築することができました。
ジョブメドレーの動画選考はまだリリースしたばかりですので、今後反響を見ながら、さらなる改善を重ねていけたらと思います。最後までお読みいただきありがとうございました。