はじめに
HLSとは?
HLSの基本概要
HLS(HTTP Live Streaming、以下HLS)は、Appleが開発したストリーミングプロトコルで、インターネット上で音声や映像データを効率的に配信する技術です。
HLSの仕組みと特徴
-
セグメントファイルとインデックスファイル
数秒ごとに分割した「セグメントファイル」と、それをリスト化した「インデックスファイル」で構成されています。 -
再生の仕組み
プレイヤーは最初にインデックスファイルを取得し、そこから必要なセグメントファイルを順次取得して再生します。 -
アダプティブビットレート
解像度ごとにセグメントファイルを用意することで、ネットワーク環境やデバイス性能に応じて異なるビットレートのストリーミングに動的に切り替え可能です。これにより、映像の遅延が少なく動画を再生できます。
HLSのメリット
- ネットワーク環境が不安定でも、適切なビットレートに自動調整されるため、MP4ファイルと比較して再生が途切れにくい。
- 動的なビットレート切り替えにより、さまざまなデバイスや帯域幅に対応できる柔軟性。
- 分割されたセグメント形式のため、再生の開始が迅速でスムーズ。
AWSを活用した動画配信基盤
AWSのサービス群を組み合わせることで、高品質かつ効率的な動画配信基盤を構築できます。このセクションでは、S3によるストレージ管理、MediaConvertを活用した動画処理、Lambdaを使った自動化など、各サービスの役割と実装方法を解説します。
構成は下記のようなイメージになります。
S3によるストレージ管理
動画配信基盤では、MediaConvertの処理前後のデータを保存するために、S3をストレージとして活用します。ここでは、インプット用とアウトプット用のバケットを作成します。
-
インプット用バケットの作成
変換前の動画ファイルをアップロードするためのS3バケットを作成します。このバケットは公開設定にせず、セキュリティを強化するために署名付きURLを使用します。これにより、特定のケースでのみファイルのアップロードを許可できます。 -
アウトプット用バケットの作成
動画変換後のHLSファイル群を格納するために、S3バケットを作成します。このバケットは直接公開設定せず、CloudFrontを利用して配信する設定が推奨されます。これにより、安全かつ効率的にHLSファイルを配信できます。
MediaConvertでの動画処理
MediaConvertは、動画のエンコードを中心とした変換処理を行うAWSのサービスです。このセクションでは、効率的な処理を実現するために必要なキューの作成とジョブテンプレートの設定手順を解説します。
-
MediaConvertのキュー作成
後述で設定するジョブで画像処理を行いますが、ジョブ自体は特定のキューに追加してキューの中で各ジョブを実行していきます。
デフォルトのキューがあるので、そのまま何もせず利用できますが、もし同じAWSアカウントの中で複数サービス(例:検証環境、本番環境など)を並行で出す場合は、サービスごとにキューを作成します。 -
MediaConvertジョブテンプレートの作成
動画のビットレートやコーデックなどの変換内容を指定したジョブテンプレートを作成します。これにより、特定の動画形式への変換処理を簡単に実行できるようになります。
下記に設定例としてコンソール画面を添付します。詳細についてはこちらをご参照ください。
【一般設定】
【出力グループ】
送信先はジョブ作成時に指定するため空欄で大丈夫です。
【出力設定(480p)】
・動画
・オーディオ
【出力設定(720p)】
・動画・オーディオの設定は720pと同じにしたので割愛します。
Lambdaを使った自動化
AWS Lambdaを利用することで、MediaConvertを活用した動画処理の自動化が可能です。
-
ジョブ作成用Lambda関数の実装
Lambda関数を実行してジョブテンプレートから動画変換用のジョブを作成します(こちらを参照)。
Lambda関数はS3のインプット用のバケットにオブジェクトをアップロードしたタイミングで実行します(こちらを参照)。Lambda関数はJavascriptで実装しています。
import AWS from 'aws-sdk'; import path from 'path'; const mediaconvert = new AWS.MediaConvert({ region: 'ap-northeast-1' }); export async function handler(event) { // S3イベントからバケット名とオブジェクトキーを取得 const s3InputBucket = event.Records[0].s3.bucket.name; const s3Key = event.Records[0].s3.object.key; // 出力用のS3バケットと関連設定を環境変数から取得 const outputBucket = process.env.OUTPUT_BUCKET; const mediaConvertJobTemplateArn = process.env.MEDIA_CONVERT_JOB_TEMPLATE_ARN; const mediaConvertRoleArn = process.env.MEDIA_CONVERT_ROLE_ARN; const mediaConvertQue = process.env.MEDIA_CONVERT_QUE; // 入力ファイルと出力先パスを生成 const inputFile = `s3://${s3InputBucket}/${s3Key}`; const videoId = path.basename(s3Key, path.extname(s3Key)); const outputFile = `s3://${outputBucket}/${videoId}/output`; try { // MediaConvert用の設定オブジェクトを作成 const setting = { "OutputGroups": [ { "OutputGroupSettings": { "Type": "HLS_GROUP_SETTINGS", "HlsGroupSettings": { "Destination": outputFile // 出力先を指定 } } } ], "Inputs": [ { "AudioSelectors": { "Audio Selector 1": { "DefaultSelection": "DEFAULT" // デフォルト音声選択を設定 } }, "FileInput": inputFile // 入力ファイルを指定 } ] }; // MediaConvertジョブを作成 const response = await mediaconvert.createJob({ JobTemplate: mediaConvertJobTemplateArn, // 使用するジョブテンプレート Queue: mediaConvertQue, // ジョブを送信するキュー Role: mediaConvertRoleArn, // 実行ロール Settings: setting // 作成した設定オブジェクトを指定 }).promise(); console.log("MediaConvert job created:", response); // 成功時のログ出力 } catch (error) { // エラーログと例外スロー console.error(`Error creating MediaConvert job for ${s3Key} from bucket ${s3InputBucket}.`, error); throw error; } };
-
ジョブ完了後のLambda関数の実装
変換後に特定の処理を行う必要があればLambda関数などを実装します。
こちらを参考にジョブ完了後にEvent Bridgeで検出してトリガーを起動します。
HLSプレイヤー
HLSは、通常iPhoneなどのApple製の端末でのみネイティブサポートされています。そのため、Androidや一部のHLS非対応デバイスで再生する場合、HLS.jsを使用してHLS Playerを実装することで対応可能です。この実装により、幅広いデバイスでHLS形式の動画を再生できるようになります。
実装方法
Next.jsでの実装例を記載します。
"use client";
import React, { useEffect, useRef } from "react";
import Hls from "hls.js";
interface HlsPlayerProps {
videoID: string;
}
const HlsPlayer = ({ videoID }: HlsPlayerProps) => {
const videoRef = useRef(null);
useEffect(() => {
const video = videoRef.current; // 動画プレーヤーの参照を取得
let hls: Hls; // HLS.jsインスタンス
// HLS.jsがサポートされている場合
if (video && Hls.isSupported()) {
hls = new Hls({
maxBufferLength: 60, // バッファの長さを最大60秒に設定
maxMaxBufferLength: 600, // バッファが最大600秒まで成長することを許可
maxBufferSize: 60 * 1000 * 1000, // バッファサイズを最大60MBに設定
});
// サンプルURL
hls.loadSource(`https://example.com/hls/${videoID}/output.m3u8`);
hls.attachMedia(video); // 動画プレーヤーにHLSをアタッチ
// マニフェスト(動画の再生リスト)が解析された後のイベント
hls.on(Hls.Events.MANIFEST_PARSED, () => video.pause());
}
// Safariなど、HLSをネイティブでサポートするブラウザの場合
else if (video && video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = `https://example.com/hls/${videoID}/output.m3u8`; // サンプルURLに置き換え
video.addEventListener("loadedmetadata", () => video.pause()); // メタデータ読み込み時に再生を一時停止
video.load(); // 動画をロード
}
// コンポーネントがアンマウントされたときにHLSインスタンスをクリーンアップ
return () => {
if (hls) {
hls.destroy();
}
};
}, [videoID]); // videoIDが変更されたときにエフェクトを再実行
return (
<div>
{/* 動画プレーヤーの要素 */}
<video ref={videoRef} controls width='100%' height='100%'></video>
</div>
);
};
export default HlsPlayer;
最後に
最後までご覧いただきありがとうございました。
動画配信基盤の構築にあたり、私自身で調べた内容をまとめてご紹介しました。
この記事が、少しでも皆様のお役に立てれば幸いです。
今井 陽介 Yosuke Imai
新規サービス開発統括部サービス支援部エンジニアリンググループ
2024年10月にパーソルキャリア株式会社へ中途入社
※2024年12月現在の情報です。