Linode と FFmpeg を使って Akamai での HLS ライブストリーミングをテストしてみる

## はじめに
Akamai ではライブストリーミングで利用できるライブオリジンと CDN を提供しています。
Akamai の提供するライブオリジンである MSL4 (Media Service Live 4) では、ストリーミング形式は HLS/DASH/CMAF をサポートしていますが、どうやって CDN によるライブストリーミングのテストを行うかがよく課題にあがります。
なぜなら、HLS/DASH/CMAF でのインジェストをサポートしているエンコーダーはハードウェア・ソフトウェア含め有償のものしか無いからです。（無償のエンコーダーをご存知でしたらコメントいただけると嬉しいです！）
そのため、テストを行う環境を用意すること自体のハードルが高くなっています。
そこで、今回はエンコーダーとして FFmpeg を利用することで、誰でも簡単にテストできる環境を構築します。

本稿の内容には、Akamai の環境構築も含まれているので、FFmpeg の利用方法だけを見たい方は [こちら](https://qiita.com/tarooishi/items/d735f20b146abbf8e0f6#%E3%83%A9%E3%82%A4%E3%83%96%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%9F%E3%83%B3%E3%82%B0%E3%82%92%E3%83%86%E3%82%B9%E3%83%88%E3%81%99%E3%82%8B) をクリックしてください。

:::note info
MSL4 を利用せず、他のライブオリジンと Akamai の CDN でライブストリーミングをされる場合、上記のエンコーダの制限は当てはまらず、利用するライブオリジンの仕様に従います。
:::

## テスト環境作成手順
最終的には以下の構成でテストを行います。

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4-- HLS -->AMD-- HLS -->エンドユーザー;
```

:::note info
AMD (Adaptive Media Delivery) とはストリーミング配信に特化した CDN です。
詳細は以下で確認できます。
[Adaptive Media Delivery](https://www.akamai.com/ja/products/adaptive-media-delivery)
:::

Akamai の設定は API 経由で作成するので、事前設定をしていない場合、以下の手順を参考にしてみてください。
API が利用できない場合などは、もちろん GUI で作成しても構いません。

https://qiita.com/kabe-akam/items/499507d19220b44b4901


### MSL4 （ライブオリジン） の設定

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4-- HLS -->AMD-- HLS -->エンドユーザー
    subgraph ここ
    MSL4
    end
```

この API ガイドをもとに設定を進めます。

https://techdocs.akamai.com/msl/reference/api

#### 設定に必要なパラメーターを取得
設定の中で Akamai の契約 ID と CP コードが必要なため、それぞれ API 経由で情報を取得します。
後ほど利用するのでどこかにメモしておいてください。

##### 契約 ID
以下のエンドポイントから GET で契約 ID を取得します。
`https://{hostname}/config-media-live/v2/msl-origin/contracts` 

```JSON
[
    {
        "contractId": "Contract-123",
        "contractName": "Example Company",
        ︙
    }
]
```

##### CP コード
以下のエンドポイントから GET で CP コードを取得します。
`https://{hostname}/config-media-live/v2/msl-origin/cpcodes?type=INGEST` 

```JSON
[
    {
        "id": 1234567,
        "name": "msl_example_cpcode",
        "contracts": [
            {
                "contractId": "Contract-123"
            }
        ]
    }
]
```

#### オリジンを新規作成
以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/config-media-live/v2/msl-origin/origins`

Body に必要なパラメータを設定します。
`contractId` はさきほど取得したパラメータを入力します。
`hostName` は16文字以内で任意の文字列（英数字のみ）を入力します。
`cpcode` はさきほど取得したパラメータを入力します。


```JSON
{
     "emailIds": [
          "admin@example.com"
     ],
     "encoderZone": "JAPAN",
     "contractId": "Contract-123",
     "hostName": "mslexampleorigin",
     "cpcode": 1234567
}
```

Status Code が `202Accepted` であれば正常にリクエストが受け付けられています。
レスポンスボディは空ですが、これは正常な動作です。


オリジン作成の進捗を確認するために、以下のエンドポイントから GET で情報を取得します。

`https://{hostname}/config-media-live/v2/msl-origin/origins`

```JSON
[
    {
        "hostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
        "hostNameIdentifier": "mslexampleorigin",
        "cpcode": 1234567,
        "id": 2345678,
        ︙
        "status": "PROVISIONED"
    }
]
```

`status` が `PROVISIONED` になっていれば作成完了です。
完了まで約40-50分程度かかります。


#### ストリームを新規作成
以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/config-media-live/v2/msl-origin/streams`

Body に必要なパラメータを設定します。
`origin` の `hostName`、`cpcode` には前項で確認したパラメータをそのまま入力します。
`contractId` は取得したパラメータを入力します。
`encoderZone` は `JAPAN` を入力します。（もちろん JAPAN 以外の地域も指定可能です） 
`format` は今回 HLS を入力します。（DASH、CMAF も指定可能です） 
`name` は任意の文字列を入力します。
`cpcode` はさきほど取得したパラメータを入力します。

```JSON
{
     "origin": {
          "hostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
          "cpcode": 1234567
     },
     "contractId": "Contract-123",
     "encoderZone": "JAPAN",
     "format": "HLS",
     "name": "mslexamplestream",
     "cpcode": 1234567
}
```


Status Code が `202 Accepted` であれば正常にリクエストが受け付けられています。
こちらもレスポンスボディは空ですが、正常な動作です。

作成の進捗を確認するために、以下のエンドポイントから GET で情報を取得します。

`https://{hostname}/config-media-live/v2/msl-origin/origins`

```JSON
{
    "totalSize": 1,
    "page": 1,
    "pageSize": null,
    "streams": [
        {
            "id": 2345679,
            "name": "mslexamplestream",
            "format": "HLS",
            "type": "MSL4",
            "cpcode": 1234567,
            "originHostName": "014-dn001-mslexampleorigin.akamaiorigin.net",
            "modifiedDate": "2022-01-01T00:00:00.000Z",
            "primaryStorageCpcode": 3456789,
            "backupStorageCpcode": 0,
            "createdBy": "admin",
            "createdDate": "2022-01-01T00:00:00.000Z",
            "streamAuth": false,
            "dvrWindowInMin": 0,
            "provisionDetail": {
                "status": "PROVISIONED"
            },
            "encoderZone": "JAPAN"
        }
    ]
}
```



`provisionDetail` の `status` が `PROVISIONED` になっていれば作成完了です。
完了まで約2-3時間程度かかります。

これで MSL4 の設定は完了です。


### AMD （CDN） の設定

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4-- HLS -->AMD-- HLS -->エンドユーザー
    subgraph ここ
    AMD
    end
```

このガイドをもとに設定を進めます。

https://techdocs.akamai.com/property-mgr/reference/onboard-a-custom-cert-property

:::note info
この設定におけるすべてのリクエストには以下のヘッダを付与してください。
PAPI-Use-Prefixes: true
:::

#### Edge Hostname の作成

以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/papi/v1/edgehostnames?contractId=ctr_Contract-123`

Body に必要なパラメータを設定します。
`domainPrefix` にはお好きなサブドメイン名を入力します。
以下の例だと最終的な FQDN は `streaming-example.akamaized.net` に設定されます。
それ以外のパラメータは例と同様でOKです。

```JSON
{
     "useCases": [
          {
               "type": "GLOBAL",
               "useCase": "Segmented_Media_Mode",
               "option": "LIVE"
          }
     ],
     "secureNetwork": "SHARED_CERT",
     "productId": "prd_Adaptive_Media_Delivery",
     "domainPrefix": "streaming-example",
     "domainSuffix": "akamaized.net",
     "ipVersionBehavior": "IPV6_COMPLIANCE"
}
```

成功すると Status Code `201 Created` とともに以下の Body が返されます。

```JSON
{
    "edgeHostnameLink": "/papi/v1/edgehostnames/1234567?contractId=Contract-123"
}
```

返されたリンク内の数字は `edgehostnameId` として後ほど利用するのでメモしておいてください。 

#### CP Code 作成

以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/papi/v1/cpcodes?contractId=ctr_Contract-123`

Body に必要なパラメータを設定します。
`cpcodeName` にはさきほど設定した Edge Hostname を指定するとわかりやすいです。
以下の例だと最終的な FQDN は `streaming-example.akamaized.net` に設定されます。
それ以外のパラメータは例と同様でOKです。

```JSON
{
     "productId": "prd_Adaptive_Media_Delivery",
     "cpcodeName": "streaming-example.akamaized.net"
}
```

成功すると Status Code `201 Created` とともに以下の Body が返されます。

```JSON
{
    "cpcodeLink": "/papi/v1/cpcodes/1234567?contractId=Contract-123"
}
```

返されたリンク内の数字は CP Code の `id` として後ほど利用するのでメモしておいてください。 

#### Property の作成

Property では CDN 自体の設定をしていきます。

以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/papi/v1/properties?contractId=ctr_Contract-123`

Body に必要なパラメータを設定します。
`propertyName` にもさきほど設定した Edge Hostname を指定するとわかりやすいです。
それ以外のパラメータは例と同様でOKです。

```JSON
{
     "productId": "prd_Adaptive_Media_Delivery",
     "propertyName": "streaming-example.akamaized.net",
     "ruleFormat": "latest"
}
```

成功すると Status Code `201 Created` とともに以下の Body が返されます。

```JSON
{
    "propertyLink": "/papi/v1/properties/1234567?contractId=Contract-123"
}
```

#### Property の編集

##### Property バージョンの取得
Property を編集するためにバージョン情報を取得します。
Property の作成で返されたリンクのパスに `/versions` を付与したエンドポイントから GET で情報を取得します。

`https://{hostname}/papi/v1/properties/1234567/versions?contractId=ctr_Contract-123`

```JSON
{
    "propertyId": "1234567",
    "propertyName": "streaming-example.akamaized.net",
    "accountId": "Account-123",
    "contractId": "Contract-123",
    "groupId": "1234567",
    "assetId": "1234567",
    "versions": {
        "items": [
            {
                "propertyVersion": 1,
                "updatedByUser": "admin",
                "updatedDate": "2022-01-01T00:00:00.000Z",
                "productionStatus": "INACTIVE",
                "stagingStatus": "INACTIVE",
                "etag": "d8ccb79ff18c8de1352c8ae2e56aadafabec1794",
                "productId": "Adaptive_Media_Delivery",
                "ruleFormat": "latest"
            }
        ]
    }
}
```

上記の出力の `propertyId`、 `propertyVersion`、 `etag` を次の設定で利用します。

##### Property に作成した Edge Hostname を追加する

以下のエンドポイントに PUT でリクエストを送ります。

`https://{hostname}/papi/v1/properties/prp_1234567/versions/1/hostnames?contractId=ctr_Contract-123&validateHostnames=false&includeCertStatus=false`

Body に必要なパラメータを設定します。
`cnameFrom` と `cnameTo` にはさきほど設定した Edge Hostname を指定します。
`edgeHostnameId` の数字部分にはメモしておいた値を指定します。
それ以外のパラメータは例と同様でOKです。

:::note warn
また、この作業ではリクエストヘッダに `If-Match` を追加し、さきほど取得した `etag` の値を設定してリクエストする必要があるのでご注意ください。

`If-Match: "d8ccb79ff18c8de1352c8ae2e56aadafabec1794"`
:::

```JSON
[
    {
        "cnameType": "EDGE_HOSTNAME",
        "cnameFrom": "streaming-example.akamaized.net",
        "cnameTo": "streaming-example.akamaized.net",
        "edgeHostnameId": "ehn_1234567"
    }
]
```

:::note info
本稿執筆時の2022年11月30日において API 経由では Akamai の共有証明書が割り当てられません。
そのため、テストでは HTTPS は利用せず、HTTP のみで行います。
:::


##### Property の Rule を変更する

以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/papi/v1/properties/prp_1234567/versions/1/rules?contractId=ctr_Contract-123&validateMode=full&validateRules=true&dryRun=false`

Body に必要なパラメータを設定します。
以下の例の中でいくつか変更が必要な部分があるので、例のあとに解説します。

```JSON
{
   "rules": {
        "name": "default",
        "children": [
            {
                "name": "Default CORS Policy",
                "children": [],
                "behaviors": [
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "*",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_ORIGIN"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "GET,POST,OPTIONS",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_METHODS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "origin,range,hdntl,hdnts,CMCD-Request,CMCD-Object,CMCD-Status,CMCD-Session",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_HEADERS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "Server,range,hdntl,hdnts,Akamai-Mon-Iucid-Ing,Akamai-Mon-Iucid-Del,Akamai-Request-BC",
                            "standardModifyHeaderName": "ACCESS_CONTROL_EXPOSE_HEADERS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "true",
                            "standardModifyHeaderName": "ACCESS_CONTROL_ALLOW_CREDENTIALS"
                        }
                    },
                    {
                        "name": "modifyOutgoingResponseHeader",
                        "options": {
                            "action": "MODIFY",
                            "avoidDuplicateHeaders": false,
                            "newHeaderValue": "86400",
                            "standardModifyHeaderName": "ACCESS_CONTROL_MAX_AGE"
                        }
                    }
                ],
                "criteria": [],
                "criteriaMustSatisfy": "all",
                "comments": ""
            }
        ],
		"behaviors": [
            {
                "name": "origin",
                "options": {
                    "originType": "MEDIA_SERVICE_LIVE",
                    "mslorigin": "014-dn001-mslexampleorigin.akamaiorigin.net"
                }
            },
            {
                "name": "cpCode",
                "options": {
                    "value": {
                        "id": 1234567,
                        "description": "streaming-example.akamaized.net",
                        "products": [
                            "Adaptive_Media_Delivery"
                        ],
                        "name": "streaming-example.akamaized.net"
                    }
                }
            },
            {
                "name": "segmentedMediaOptimization",
                "options": {
                    "behavior": "LIVE",
                    "showAdvanced": false,
                    "enableUllStreaming": false
                }
            },
            {
                "name": "originCharacteristics",
                "options": {
                    "authenticationMethod": "AUTOMATIC",
                    "country": "JAPAN",
                    "authenticationMethodTitle": ""
                }
            },
            {
                "name": "contentCharacteristicsAMD",
                "options": {
                    "catalogSize": "UNKNOWN",
                    "contentType": "HD",
                    "dash": false,
                    "hds": false,
                    "hls": true,
                    "popularityDistribution": "UNKNOWN",
                    "segmentDurationHLS": "SEGMENT_DURATION_6S",
                    "segmentSizeHLS": "UNKNOWN",
                    "smooth": false
                }
            },
            {
                "name": "clientCharacteristics",
                "options": {
                    "country": "JAPAN"
                }
            },
            {
                "name": "cacheKeyQueryParams",
                "options": {
                    "behavior": "IGNORE_ALL"
                }
            },
            {
                "name": "segmentedContentProtection",
                "options": {
                    "enabled": false,
                    "hlsMediaEncryption": false,
                    "tokenAuthenticationTitle": "",
                    "mediaEncryptionTitle": "",
                    "dashMediaEncryption": false
                }
            },
            {
                "name": "dynamicThroughtputOptimization",
                "options": {
                    "enabled": true
                }
            }
        ],
        "options": {
            "is_secure": true
        } 
    }
}
```

オリジンサーバーのパラメータを変更します。
`mslorigin` に作成した MSL4 のオリジンホスト名を入力します。
それ以外のパラメータは下記の例のままでOKです。

```JSON
            {
                "name": "origin",
                "options": {
                    "originType": "MEDIA_SERVICE_LIVE",
                    "mslorigin": "014-dn001-mslexampleorigin.akamaiorigin.net"
                }
            }
```

CP Code のパラメータを変更します。
`id` はさきほどメモした値を指定します。
`description` と `name` には作成した CP Code の名前を指定します。

```JSON
            {
                "name": "cpCode",
                "options": {
                    "value": {
                        "id": 1234567,
                        "description": "streaming-example.akamaized.net",
                        "products": [
                            "Adaptive_Media_Delivery"
                        ],
                        "name": "streaming-example.akamaized.net"
                    }
                }
            }
```

上記を変更した内容を Body としてリクエストします。

成功すると Status Code `200 OK` と Property の内容が返されます。

```JSON
{
    "accountId": "act_Account-123",
    "contractId": "ctr_Contract-123",
    "groupId": "grp_12345",
    "propertyId": "prp_1234567",
    "propertyVersion": 1,
    ︙
```

#### Property を展開する

以下のエンドポイントに POST でリクエストを送ります。

`https://{hostname}/papi/v1/properties/prp_1234567/activations?contractId=ctr_Contract-123`

Body に必要なパラメータを設定します。
`notifyEmails`には管理者のアドレスを指定します。
それ以外のパラメータは例と同様でOKです。

```JSON
{
     "acknowledgeAllWarnings": true,
     "activationType": "ACTIVATE",
     "fastPush": true,
     "ignoreHttpErrors": true,
     "notifyEmails": [
          "admin@example.com"
     ],
     "useFastFallback": false,
     "network": "PRODUCTION",
     "note": "First activation",
     "propertyVersion": 1
}
```

成功すると Status Code `201 Created` とともに以下の Body が返されます。

```JSON
{
    "activationLink": "/papi/v1/properties/1234567/activations/1234567?contractId=Contract-123"
}
```

これで MSL4 と AMD の設定は完了です。

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4
    subgraph 完了したところ
    MSL4-- HLS -->AMD
    end
    AMD-- HLS -->エンドユーザー
```


## ライブストリーミングをテストする

今回は Linode 上から MSL4 に Ingest します。
無料のソフトウェアエンコーダーで HLS での Ingest をサポートしているものがないと思われるため、FFmpeg を利用します。

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4-- HLS -->AMD-- HLS -->エンドユーザー
    subgraph ここ
    エンコーダー(FFmpeg)
    end
```

### 仮想マシンを作成する
まずは Linode の仮想マシンを作成します。
以下の StackScripts を利用すると、私と同じ環境でお試しいただけます。

https://cloud.linode.com/stackscripts/1085603

もちろんご自身のお好きな環境にFFmpeg をインストールしても構いません。

### テスト用映像素材の準備
ここではテスト用の動画として有名な「Big Buck Bunny」を利用します。
以下のリンクからご自身の環境にダウンロードしてください。

https://peach.blender.org/download/

```bash
$ curl -O https://download.blender.org/demo/movies/BBB/bbb_sunflower_1080p_30fps_normal.mp4
```


### FFmpeg のパラメータを作成する

FFmpeg のパラメータ作成は非常に難解です。
そこで、Akamai 社員の有志が作成した便利なコマンド生成ツールを利用します。（とても便利です！！）

https://moctodemo.akamaized.net/tools/ffbuilder/

![ffmpeg_command_line_builder_default.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711428/5fe18c7c-2aa9-8f7b-2c3c-db90be8a190a.png)



設定できるところはたくさんありますが、最低限必要な部分だけ設定します。

`Input` は Loop file のまま、さきほどダウンロードしたファイル名を指定してください。（必要に応じてファイルが保管されている場所のパスも）
`Optimize` の `Segment size` は `6s` を指定します。AMDで設定した値と同じです。
`Outputs` では `HLS live stream` を指定します。

すると、追加入力項目が出てくるので、以下のように設定します。
`Packaging`: `HLS (ts) - discrete`
`EP type`: `Akamai`
`Stream ID`: Stream の ID
`Event name`: 任意の文字列

`Stream ID` は MSL4 の Stream の ID です。

以下のエンドポイントから GET で情報を取ると、`id` というキーがあるのでこの値を入力してください。
`https://{hostname}/config-media-live/v2/msl-origin/origins`

設定はこんな感じ。

![ffmpeg_command_builder_parameters_filled.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711428/d0aef852-9b6c-8b5f-4ccd-4617d59507a3.jpeg)


各パラメータを入力すると、以下のように Shell Script が出力されます。

```bash
#!/bin/bash

#Generated by command builder r0.3h

timestamp="$(date +%s)"

ffmpeg \
\
-re -fflags +genpts -stream_loop -1 -i testclip.mp4 \
\
-flags +global_header -r 30000/1001 \
\
-filter_complex "scale=1280x720" \
\
-pix_fmt yuv420p \
-c:v libx264 \
\
-b:v:0 3000K -maxrate:v:0 3000K -bufsize:v:0 3000K/2 \
\
-g:v 30 -keyint_min:v 30 -sc_threshold:v 0 \
\
-color_primaries bt709 -color_trc bt709 -colorspace bt709 \
\
-c:a aac -ar 48000 -b:a 96k \
\
-map 0:v:0 \
-map 0:a:0 \
\
-preset veryfast \
-tune zerolatency \
\
-hls_init_time 6.006 \
-hls_time 6.006 \
-hls_list_size 20 \
-hls_flags delete_segments \
-hls_base_url $timestamp/ \
-var_stream_map 'a:0,agroup:a0,default:0 v:0,agroup:a0' \
-hls_segment_filename 'http://p-ep2345678.i.akamaientrypoint.net/2345678/streamtest/'$timestamp/stream%v_%05d.ts \
-master_pl_name master.m3u8 \
-http_user_agent Akamai_Broadcaster_v1.0 \
-http_persistent 1 \
-f hls \
http://p-ep2345678.i.akamaientrypoint.net/2345678/streamtest/level_%v.m3u8
```

これを Linode の仮想マシン上で実行すると、Big Buck Bunny が延々とループ配信されます。

### Shell Script を配置する

生成されたスクリプトを仮想マシンに配置します。
ここでは `bbb_stream.sh` というファイル名で保存しています。

このままでは実行できないので、実行権限を付与します。

```bash
$ chmod +x bbb_stream.sh
```

実行権限が付与されているかどうか確認します。

```bash
$ ls -l bbb_stream.sh
-rwxrwxr-x 1 admini admin      986 Jan 01 00:00 bbb_stream.sh
```

`x` がついているのでOKです。

### ライブストリーミングを開始する
Shell Script の準備ができているので、あとは実行するだけです。

```bash
$ ./bbb_stream.sh
ffmpeg version 4.4.2-0ubuntu0.22.04.1 Copyright (c) 2000-2021 the FFmpeg developers
  built with gcc 11 (Ubuntu 11.2.0-19ubuntu1)
　︙
```

エラーが発生しなければOKです。

### ライブを視聴する

```mermaid
flowchart LR;
    エンコーダー(FFmpeg)-- HLS -->MSL4-- HLS -->AMD-- HLS -->エンドユーザー
    subgraph ここ
    エンドユーザー
    end
```

Akamai のデモプレイヤーを利用してライブ配信を視聴します。

http://players.akamai.com

ライブは URL は以下のフォーマットで配信されます。

`http://[hostname]/hls/live/[stream_id]/[event_name]/[filename].m3u8`

今回の構成だと以下のようになります。

`http://streaming-example.akamaized.net/hls/live/2345678/streamtest/master.m3u8`

こちらをプレイヤーに指定して再生します。

![bbb_on_akamai_player.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2711428/1fb8e8c3-6fc9-eb43-4f81-2fc13ab26355.jpeg)


問題なく再生されていればOKです。


## まとめ
今回は Akamai のライブ配信設定から FFmpeg を利用してライブストリーミングを行う手順までを解説しました。
最も難解な FFmpeg を簡単に使えるようになるので、テストのハードルは大きく下がると思います。
ただ、利用したスクリプトは例外処理等はまったくないため、あくまでも検証用として使ってください。
AMD（CDN）は API で作成しましたが、とても手間がかかるので 後日別の記事で Terraform での展開についても紹介したいと思います。

### （追記）Terraform での展開についてまとめました。

https://qiita.com/tarooishi/items/ae35397c557b8488afb7