HLSとは?AWS MediaConvertで始めるシンプルな動画配信基盤の構築 #Developer&Designer Advent Calendar 2024

alt

はじめに

こんにちは、新規サービス開発統括部 エンジニアの今井です。

2024年10月にパーソルキャリアに中途入社し、現在はサービス支援部で新規サービスのフロントエンド開発に携わっています。

今回は、プライベートで動画配信基盤を構築した経験をもとに、AWSのMediaConvertとS3を活用した動画配信基盤の仕組みをご紹介します。

本基盤では、HTTP Live Streaming(HLS)技術を採用しています。

 

 

HLSとは?

HLSの基本概要

HLS(HTTP Live Streaming、以下HLS)は、Appleが開発したストリーミングプロトコルで、インターネット上で音声や映像データを効率的に配信する技術です。

HLSの仕組みと特徴

  • セグメントファイルとインデックスファイル
    数秒ごとに分割した「セグメントファイル」と、それをリスト化した「インデックスファイル」で構成されています。

  • 再生の仕組み
    プレイヤーは最初にインデックスファイルを取得し、そこから必要なセグメントファイルを順次取得して再生します。

  • アダプティブビットレート
    解像度ごとにセグメントファイルを用意することで、ネットワーク環境やデバイス性能に応じて異なるビットレートのストリーミングに動的に切り替え可能です。これにより、映像の遅延が少なく動画を再生できます。

HLSのメリット

  • ネットワーク環境が不安定でも、適切なビットレートに自動調整されるため、MP4ファイルと比較して再生が途切れにくい。
  • 動的なビットレート切り替えにより、さまざまなデバイスや帯域幅に対応できる柔軟性。
  • 分割されたセグメント形式のため、再生の開始が迅速でスムーズ。

AWSを活用した動画配信基盤

AWSのサービス群を組み合わせることで、高品質かつ効率的な動画配信基盤を構築できます。このセクションでは、S3によるストレージ管理、MediaConvertを活用した動画処理、Lambdaを使った自動化など、各サービスの役割と実装方法を解説します。

構成は下記のようなイメージになります。

S3によるストレージ管理

動画配信基盤では、MediaConvertの処理前後のデータを保存するために、S3をストレージとして活用します。ここでは、インプット用とアウトプット用のバケットを作成します。

  1. インプット用バケットの作成
    変換前の動画ファイルをアップロードするためのS3バケットを作成します。このバケットは公開設定にせず、セキュリティを強化するために署名付きURLを使用します。これにより、特定のケースでのみファイルのアップロードを許可できます。

  2. アウトプット用バケットの作成
    動画変換後のHLSファイル群を格納するために、S3バケットを作成します。このバケットは直接公開設定せず、CloudFrontを利用して配信する設定が推奨されます。これにより、安全かつ効率的にHLSファイルを配信できます。

MediaConvertでの動画処理

MediaConvertは、動画のエンコードを中心とした変換処理を行うAWSのサービスです。このセクションでは、効率的な処理を実現するために必要なキューの作成とジョブテンプレートの設定手順を解説します。

  1. MediaConvertのキュー作成
    後述で設定するジョブで画像処理を行いますが、ジョブ自体は特定のキューに追加してキューの中で各ジョブを実行していきます。
    デフォルトのキューがあるので、そのまま何もせず利用できますが、もし同じAWSアカウントの中で複数サービス(例:検証環境、本番環境など)を並行で出す場合は、サービスごとにキューを作成します。

  2. MediaConvertジョブテンプレートの作成
    動画のビットレートやコーデックなどの変換内容を指定したジョブテンプレートを作成します。これにより、特定の動画形式への変換処理を簡単に実行できるようになります。
    下記に設定例としてコンソール画面を添付します。詳細についてはこちらをご参照ください。

    【一般設定】


    【出力グループ】
    送信先はジョブ作成時に指定するため空欄で大丈夫です。


    【出力設定(480p)】
    ・動画

    ・オーディオ


    【出力設定(720p)】
    ・動画



    ・オーディオの設定は720pと同じにしたので割愛します。

Lambdaを使った自動化

AWS Lambdaを利用することで、MediaConvertを活用した動画処理の自動化が可能です。

  1. ジョブ作成用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;
        }
    };
    
  2. ジョブ完了後の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;

 

最後に

最後までご覧いただきありがとうございました。
動画配信基盤の構築にあたり、私自身で調べた内容をまとめてご紹介しました。
この記事が、少しでも皆様のお役に立てれば幸いです。

 

alt

今井 陽介 Yosuke Imai

新規サービス開発統括部サービス支援部エンジニアリンググループ

2024年10月にパーソルキャリア株式会社へ中途入社

※2024年12月現在の情報です。