HLSでサーバーレス動画ストリーミング配信をする

家にある動画を再生するのに、いちいちデスクトップパソコンを立ち上げたり、NASっぽいものを構築するのもめんどくさくて
URLを生成してiPhoneとかのブラウザでシュッと見られるようにしたかった。

長い動画を再生するためにストリーミング配信したくて
手持ちのMaciOS系デバイスで再生するのだと、どうやらHLSが向いてそうだという結論に落ち着いた。

自宅にサーバーを置きたくないので (でかいし、うるさいしで邪魔)
クラウド環境でいろいろやってみることにした。

結果できたのが以下のようなもの。

f:id:takanamito:20161022175237p:plain:w350

動画視聴までのワークフロー

  • 手元にある動画ファイルをS3にアップロード
  • S3にファイルが置かれたのを検知してLambdaを起動
  • LambdaでElasticTranscoderにエンコードジョブを作成してアップした動画をエンコード
  • エンコードが完了したらファイルをS3に出力
  • S3のファイルのURLをSafariで開いて再生

必要な作業は以下の通り

  • ElasticTranscoder側の設定 (Pipeline, Preset)
  • Lambda Functionの作成

S3のPUTイベントを検知してElasticTranscoderにジョブを投げるLambda Functionを作ります。
基本的には以下の記事を参考に進めました。

qiita.com

ElasticTranscoderの設定

Presetの作成

動画をエンコードする際の設定を決めます。
今回はさほど画質にこだわっていなかったのでデフォルトで用意されている設定を使うことにしました。
1M ~ 1.5MくらいのPresetがHD周辺の解像度でエンコードしてくれたので、そのまま採用しています。
この際、IDというカラムに表示されているPreset IDをメモしておきます。

f:id:takanamito:20161022181254p:plain

Pipelineの作成

ElasticTranscoderの作業フローを設定していきます。
S3上のどのBucketにある動画を、どのBucketにエンコードして出力するのかを決めます。

基本的には2つのBucketを用意し、仮にA, BというBucketがあるとすると

Aに動画ファイルをアップロード -> Lamba Functionがジョブを投げる -> ElasticTranscoderがエンコード -> Bにエンコード結果のファイルを保存

という流れになります。

こちらも生成後のPipeline IDをメモしておきましょう。

Lambda Functionの作成

先程のQiitaの記事を参考に、Lambda Functionを作っていきます。

メモしていた2つのIDを変数に埋めてください。

var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var ets = new aws.ElasticTranscoder({apiVersion: '2012-09-25', region: 'ap-northeast-1'});

exports.handler = function(event, context) {
   console.log('Received event:');
   console.log(JSON.stringify(event, null, '  '));
   
   var bucket = event.Records[0].s3.bucket.name;
   var key = event.Records[0].s3.object.key;
   
   var pipelineId = 'xxxxxxx';
   var presetId = 'xxxxxx';
  
   var fileName = key.split('.')[0];
   
   ets.createJob({
     PipelineId: pipelineId,
     OutputKeyPrefix: fileName + '/',
     Input: {
       Key: key,
       FrameRate: 'auto',
       Resolution: 'auto',
       AspectRatio: 'auto',
       Interlaced: 'auto',
       Container: 'auto',
     },
     Outputs: [
       {
         Key: 'ts-720p',
         PresetId: presetId,
         Rotate: 'auto',
         SegmentDuration: "10"
       },
     ],
     Playlists: [
       {
         Format: 'HLSv3',
         Name: 'master',
         OutputKeys: [
           'ts-720p',
         ],
       },
     ],
   }, function(error, data) {
        if(error) {
            console.log(error);
            context.done('error',error);
        } else {
            console.log('Job submitted');
            context.done(null,'');
        }
    });
};

またTriggersも設定します。
今回はS3上の.mp4というSuffixのファイルのObjectCreatedByPutObjectCreatedByCompleteMultipartUploadイベントを検知して起動するように設定しています。

これでS3に動画がアップされると自動でエンコードが開始されるようになります。

動画の再生

エンコードが完了すると.m3u8ファイルが2種類と.tsファイルが大量にbucket上に生成されます。

f:id:takanamito:20161022182541p:plain

.m3u8ファイルは動画のプレイリストとしてのテキストファイルです。
HLSはAppleが仕様を作っているプロトコルということもあり、Safariであればこの.m3u8ファイルのURLを開くだけで動画配信が始まります。

S3のPolicyを編集してIP絞るなど
インターネットに広く公開しないような設定をお忘れなく。

所感

ほぼプログラムを書かずにストリーミング配信を実現することができるので非常に楽。

ただElasticTranscoderの料金がバツグンに高いので、気になる方は手元のマシンでffmpeg使うなりしてHLS形式にするところまでやってS3に上げるのがよさそう。

HLSがHTTP/1.1の上で動作するため考えることも少なく、基本的にS3に動画ファイルとプレイリストを置いて静的に配信できれば
あとはプレーヤー側がうまくつなげて再生してくれるため、とても楽にストリーミング再生が実現できます。

またRTMPなどのプロトコルと違い、配信用サーバーを置かずにすんだり
CDNを使うことで容易に配信負荷分散ができるので、そのうち仕事で使う場面もでてきそう。

実際こないだのAbemaTVのカンファレンスでも現場のおもしろい話が聞けたので今後もHLS注目していこうと思います。

AbemaTV Developer Conference 2016