AWS MediaLiveを使った擬似リニア配信方法 ~実践編~

# 🎦 擬似リニア配信(Virtual Linear OTT)とは？

擬似リニア配信とは、24時間/365日、番組表などのスケジュールに従って時間通りに配信する仕組みになります。事前に収録済み動画ファイル(mp4など)を使ってあたかも生配信しているかのようにライブ映像として流します。いわゆるケーブルTVみたいなものです。本資料ではMediaLiveのスケジュールアクション機能使った擬似リニア配信の実現とより実践で使える設定・設計について説明します。

注: MediaLiveをMLと省略して説明します。


## MLで実現するための流れ ~基礎編~

1. 動画ファイルをs3にアップロードする
1. MLチャンネルを作成する
1. スケジュールアクションを登録する

### 1.動画ファイルをS3にアップロードする

配信したい動画を事前にs3にアップロードします。
| ファイルパス | 用途 |
|:-----------|:------------|
| s3://demo-bucket/CM.mp4 | 本編動画を流す前の動画、今回は仮でCM(宣伝)動画 |
| s3://demo-bucket/収録映像.mp4 | 事前に収録された動画 |

### 2.ML入力(input)を作成する

ML入力(input)を二つ作成します。それぞれ入力Typeは`MP4`にします。Input class は`SINGLE_INPUT` にします。一つの目の入力は、`s3://demo-bucket/CM.mp4`にして、二つ目の入力は `$urlPath$` とセットします。

おや、二つ目の入力は変数なものがセットされてますね :thinking: 

もちろん、`静的入力(Static Input)`として固定の`s3://demo-bucket/収録映像.mp4`をセットすることも可能です。ただ、使用する映像の数だけ入力を作成する必要が出てくるので、場合によっては 入力数が枯渇したり、管理が煩雑になります。`動的入力(Dynamic Input)`は、スケジュールアクションにて動的に動画へのs3パスをセットする方法になります。なので、動画の数分スケジュールアクションを作成することになります。
https://docs.aws.amazon.com/ja_jp/medialive/latest/ug/mp4-pull-input.html

:::note info
<b>動的入力のメリット</b>
- 動的に動画のパスが変えられる
- 動画のs３パスは日本語を含むマルチバイト文字も対応
:::


詳細は下記の AWS Black Belt資料参考にしてみてください。
<iframe src="//www.slideshare.net/slideshow/embed_code/key/epZaOKR4yaNV16?startSlide=72" width="595" height="485" frameborder="0" marginwidth="0" marginheight="0" scrolling="no" style="border:1px solid #CCC; border-width:1px; margin-bottom:5px; max-width: 100%;" allowfullscreen> </iframe> <div style="margin-bottom:5px"> <strong> <a href="//www.slideshare.net/AmazonWebServicesJapan/20191112-aws-black-belt-online-seminar-aws-media-services" title="20191112 AWS Black Belt Online Seminar AWS Media Services で始めるライブ動画配信" target="_blank">20191112 AWS Black Belt Online Seminar AWS Media Services で始めるライブ動画配信</a> </strong> from <strong><a href="//www.slideshare.net/AmazonWebServicesJapan" target="_blank">Amazon Web Services Japan</a></strong> </div>

:::note info
<b>映像のループをさせたい場合</b>
対象の入力の`ソース終了時の動作`という項目にて、`LOOP` と設定します
:::

### 3.MLチャンネルを作成する

MLチャンネルを作成します。先ほど作った二つの入力を`入力アタッチメント`項目にて紐づけます。`出力グループ`項目は配信したい出力先をそれぞれ設定します。独自に配信する場合は、MediaStoreやMediaPackageを設定します。YouTubeなどの外部プラットフォームに配信することも可能です。その際は、RTMPを選択し、YouTubeへの出力先のRTMP情報をセットします。

### 4.MLスケジュールアクションを登録する

スケジュールアクション機能にて下記の添付のように登録します。開始タイプを `Fixed` を選択し、配信時間を入力します。UTCでの登録になりますので、日本時間で配信したい場合は、`-9時間`で登録します。アクションのタイプを`input-switch`を選択することで、s3パスが表示されます。ここに配信したい動画のs3パス($UrlPath)をセットします。![スクリーンショット 2022-11-11 9.53.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/232803/6fb9d809-3f86-f0f7-9d98-9b035ee39c08.png)

### 4.MLチャンネルを起動する

最後に、MLチャンネルを起動します。RUNNINGが確認できたら、最初に登録したCM(宣伝).mp4の動画が流れます。先ほど登録したスケジュールアクションの時間になったら、映像の切り替わりが確認できると思います。これを複数動画を設定し続けることで、擬似リニア配信が実現できます。

:::note info
MediaLive起動中でも、新規のアクションの追加し、別映像の配信に切り替えることもできます。
:::

# 実践編

## 事前準備

- 素材動画(mp4)ファイルを準備し、s3 にアップロードしておく
- スケジュールを作成する
- cronなどを使ってスケジュールをチェックするスクリプト

### 今回使用する配信時間と動画の表

|配信時間(hh:mm) | ファイルパス | 用途 |
|:-----------:|:-----------|:------------|
| 12:30 | s3://demo-bucket/CM.mp4 | CM(宣伝)動画 |
| 13:00 | s3://demo-bucket/収録映像.mp4 | 事前に収録された動画 |
| 14:45 | s3://demo-bucket/フィラー映像.mp4 | 映像と映像の繋ぎ目のフィラー動画(5分) |
| 14:50 | s3://demo-bucket/終了映像.mp4 | 事前に収録された終了用の動画 |

:::note warn
フィラー映像は、配信と配信の繋ぎとなる代替映像のことになります。スケジュールと動画の尺が合わない際を考慮して、参考程度であえて入れてます。
:::

## スケジュール

| 時間(hh:mm) | アクション内容 | MLスケジュールアクション | s3ファイルパス |
|:-----------:|:------------|:------------:|:------------|
| 11:59 | pauseアクション実施 | pause(pause) | |
| 12:00 | MLチャンネルの起動 | |
| 12:30 | unpauseアクションの実施 | pause(unpause) ||
| 12:30 | 配信開始(CM宣伝映像) | input-switch | s3://demo-bucket/CM.mp4　|
| 13:00 | 配信開始(収録映像) | input-switch | s3://demo-bucket/収録映像.mp4　|
| 14:45 | 配信開始(フィラー映像) | input-switch | s3://demo-bucket/フィラー.mp4　|
| 14:50 | 配信開始(終了映像) | input-switch | s3://demo-bucket/終了映像.mp4　|
| 15:00 | MLチャンネルの停止 | | |
| 15:00 | Lambdaなどでスケジュールアクションの削除 | | |

pauseアクションはスケジュールアクションの一つです。pauseを追加することで映像が流れなくなります。つまりHLSなどのマニフェストにアクセスしても取得できません。たとえML起動しても映像は流れず、12:30 の pause(unpause) が適用後にCM宣伝映像が流れます。13:00になったら収録映像が流れ出します。途中追加も可能です。15:00 になったらMLチャンネルを停止し配信が完全終了となります。

:::note info
<b>ポイント1</b>
pause/unpause のスケジュールアクションを組み込むことで、指定時間の12:30になったらすぐ配信が可能になります。pause はML起動前にスケジュールアクションに組み込む必要があります。MLチャンネルの起動には数分ほどかかります。
:::

:::note info
<b>ポイント2</b>
スムーズな映像切り替えのために、フィラー映像や終了映像を準備しておきましょう。そうしないと、映像の途中できれたり、黒映像が流れて視聴者は配信トラブルと勘違いしてしまいます。
:::

:::note info
<b>ポイント3</b>
ML停止後に Lambda によってスケジュールアクションを掃除しましょう。次回配信する際に悪影響なくスムーズな配信が可能になります。
:::


:::note info
<b>遅延を考慮したトリッキーな方法</b>
リニア配信は映像開始してから数秒から数十秒の遅延があります。スケジュールアクションは秒単位で指定可能ですので、遅延を考慮し12:29:50と10秒前を指定することで、12:30ちょうど開始により近づけるための調整も可能です。

注: マニフェストのセグメントサイズや視聴環境にて遅延時間が異なりますのであしからず :sweat: 
:::

## スクリプト設計について

単発なリニア配信(スケジュール配信)では、MLスケジュールアクションに登録するだけで完結するかと思いますが、システムとして組み込む場合はそのようにうまくいかないケースが出てきます。おそらく番組などのスケジュールはデータベースやファイルなどに保持されており、読み込んで cron や scheduler などで自動でチェックすることになるかと思います。つまり番組スケジュールとスケジュールアクションの同期の課題があります。

### MLスケジュールアクションをいつ登録するかの課題

最初の設計では、スケジュールが組み込まれたらMLスケジュールアクションに即時に登録することを考えてました。しかし、スケジュールが変わったり、他の配信で急遽割り込みが発生した場合、再登録(リプレイス)し直さなければなりません。誤って、他の配信でスケジュールを登録したらそれが登録されてしまい、配信に影響あるリスクも考えられます。不必要になった過去スケジュールアクションの削除も必要です。うまくデータベースとスケジュールアクションの時間の同期が取れてないと不具合の原因になります。

そのようなリスクを考慮して、配信直前(1-2分前)になったらスケジュールアクション関連を登録するというのも選択肢の一つです。未来のスケジュールアクションを登録する場合は、`最低15秒以上後の時間`を指定する必要があります。結果的には直前に登録する方が、スケジュールアクションの管理がシンプルになると考えてます。

## AWS MediaLive SDK for Goを使った実装

AWS MediaLive SDK for Goを使ったブログや記事はネット上に多くはありません。またAPI仕様書を見ても理解に時間がかかります。参考までに、MedisLive Go SDK を使った、スケジュールアクションの登録するためのサンプルコードです。ニッチなコードですが、参考になる人がいれば幸いです。

```golang
package main

import (
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/medialive"
)

func main() {

	var region string = "ap-northeast-1"
	sess := session.Must(session.NewSession(&aws.Config{Region: aws.String(region)}))
	client := medialive.New(sess)

	// スケジュール時間: 2022/11/10 13:00:00 JST
	scheduledTimeStr := time.Date(2022, 11, 10, 13, 0, 0, 0, time.Local).Format("2006-01-02T15:04:05.000Z")
	channelId := "1234567890"        // MediaLiveチャンネルID
	actionName := "配信開始(収録映像)"  // スケジュール名
	mlChannelInput := "demo-input"    // 入力名
	s3Path := "s3://demo-bucket/収録映像.mp4"
	urlPath := []*string{
		aws.String(s3Path),
	}

    // スケジュールアクション(input-switch用)の組み立て
	batchUpdateScheduleInput := medialive.BatchUpdateScheduleInput{
		ChannelId: aws.String(channelId),
		Creates: &medialive.BatchScheduleActionCreateRequest{
			ScheduleActions: []*medialive.ScheduleAction{
				{
					ActionName: aws.String(actionName),
					ScheduleActionSettings: &medialive.ScheduleActionSettings{
						InputSwitchSettings: &medialive.InputSwitchScheduleActionSettings{
							InputAttachmentNameReference: aws.String(mlChannelInput),
							UrlPath:                      urlPath,
						},
					},
					ScheduleActionStartSettings: &medialive.ScheduleActionStartSettings{
						FixedModeScheduleActionStartSettings: &medialive.FixedModeScheduleActionStartSettings{
							Time: aws.String(scheduledTimeStr),
						},
					},
				},
			},
		},
	}

    // 実行
	_, err := client.BatchUpdateSchedule(&batchUpdateScheduleInput)
	if err != nil {
		fmt.Println(err)
		return
	}
}
```

# まとめ
MLスケジュールアクションを活用した擬似リニア配信方法を説明しました。スムーズに配信を行うための配信前、配信中、配信後のポイントを記載しました。基本リニア配信は24時間/365日、配信は止まらない前提ではありますがご了承ください:bow: 

最近、MediaTailorを使った同様な`仮想リニアOTT`という方法あるようなので、別途試して見たいと思います。
https://aws.amazon.com/jp/blogs/news/jpmne-deploying-virtual-linear-ott-channels-using-aws-media-services/