会員制ライブ動画配信サイトの骨組をAWSで作ろう

<font color="red">20200708: サンプルHLS動画をAppleさんのものに差し替えました。</font>

こんにちは😃
今日は、タイトルのことを↓のような方法で実現してみます。

----

* S3にHLS動画を置く。パブリックアクセスは許可しない。
* CloudFront経由でのみ、その動画を再生できるようにする。<br />ただし再生にはCloudFrontの暗号鍵で署名したCookieが必要となるよう設定する。
* CloudFrontにドメインを割り当てる。
* AWS SDKのCloudFrontクライアントが使えて署名Cookieを操作できる言語（本記事ではPHP）で、動画配信WEBアプリケーションを作る。
* WEBサーバーに、CloudFrontとセカンドレベルドメインまで同じドメインを割り当てる。<br />またCloudFrontの暗号鍵を持たせる。（これでCloudFrontとWEBサーバーで署名Cookieを共有できる）
* あとは署名Cookie発行をWEBアプリケーションの業務ロジックでコントロールすれば、会員制動画配信が実現可能！（これ以降は本記事では割愛します。）

ついでにCORS対応を施して、
Safari以外のブラウザ（本記事ではChrome）でHLS動画再生も実現します。

----

正味作業時間はそれほどかかりませんでしたが、

* つまづきポイントが多い
* 回避不能な待ち時間が発生する

関係でブヒブヒ言うハメになったので、スクリーンショット付きで詳しく説明していきます。
結構長いですが、お付き合いくださいませ...。

# おことわり

本記事中のAWSリソースなどは執筆のために一時的に作成したものです。
流用はできませんので、あしからずご了承くださいませ。

# 必要なもの

* AWSアカウント。<br />ドメイン取得などを実施するので若干料金が発生します。2,000円くらいあれば十分。<br />またドメイン名を何にするか決めておくとスムーズです。
（本記事の例では`yagrush.net`にしています。）

* シェルが使えてHLS動画が再生できる端末（Macおすすめ。）

* Docker & docker-compose（Docker Desktopならdocker-composeも付いてくるので、おすすめ。）
* Chrome 

# HLS（HTTP Live Streaming）とは

Appleさんが作った動画配信技術。
AbemaTVとかでも使われてるそうです。
本記事ではこのHLSを使っていきます。

昨今の事情に合ったメリットがいっぱいあります。

* HTTPと相性が良く、一般的なWEBサーバーで動画配信できる。
* 再生品質をリアルタイム変更できるので安定して配信しやすい。
* 暗号や認証対応が充実。<br />etc...

Safariが一番相性良いですが、他ブラウザでも再生方法はあります。
（後のほうで説明します。）

## HLSの動画ファイル形式

`.ts`と`.m3u8`という拡張子の２種類のファイルで構成されます。
（.mp4 から変換してくれるソフトやオンラインサービスがいっぱいあります。）

### `.ts`ファイル

動画データファイル。
１本の動画を数秒単位で細切れにしたもの。
HLS動画はこの .ts ファイル何百何千個で構成されており、しかもそれが再生品質パターン分あったりする。

### `.m3u8`ファイル

HLS動画ファイルのメイン。
山ほどある .ts のファイルパスや再生する順番が記録されている。

ちなみに他の.m3u8ファイルを入れ子にもできる。
例えば、低品質用 .m3u8 にサッと挿げ替えれば、動画再生を止めずに再生品質を下げれたり。

本物のライブ配信システムは .ts をリアルタイムでガシガシ作成していき .m3u8 をガンガン書き換えることで実現しているそうです。

# 作業開始

## .m3u8ファイルと.tsファイルを用意する

いきなり言われても大変ですよね。

そこで、Appleさんが公開しているサンプルHLS動画ファイルをお借りします。

> [Deploying a Basic HTTP Live Stream](https://developer.apple.com/documentation/http_live_streaming/deploying_a_basic_http_live_stream)

全編だと.tsファイルが100個以上あります。
迷惑をかけるので、無理なダウンロードはやめましょう！

今回は、0～24までを使わせて頂きましょう。

### .m3u8
http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8
http://devimages.apple.com/iphone/samples/bipbop/gear1/plog_index.m3u8
http://devimages.apple.com/iphone/samples/bipbop/gear2/plog_index.m3u8
http://devimages.apple.com/iphone/samples/bipbop/gear3/plog_index.m3u8
http://devimages.apple.com/iphone/samples/bipbop/gear4/plog_index.m3u8

### .ts(/gear1～/gear4 各サブディレクトリごとに25個ずつ)
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence0.ts
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence1.ts
～
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence23.ts
http://devimages.apple.com/iphone/samples/bipbop/gear1/fileSequence24.ts

```.tsファイルを一発ダウンロードするシェルの例.悪用しないでね！
URLBASE=http://devimages.apple.com/iphone/samples/bipbop

SUBDIR=$1
mkdir -p $SUBDIR
cd $SUBDIR
wget $URLBASE/$SUBDIR/plog_index.m3u8
for i in `seq 0 24`
do
  fileno=`echo $i`
  wget $URLBASE/$SUBDIR/fileSequence$fileno.ts
  if [ $? -gt 0 ]; then
    exit 99
  fi
done

cd ..

```

## S3作業

S3は動画ファイル置き場です。
CloudFront経由かつ特定ドメインからの再生 のみアクセスできるよう、設定を施します。

### まずはバケットを作る

<img width="555" alt="スクリーンショット 2020-07-03 16.24.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/ff9d8d65-35bf-2990-a289-745557e43584.png">

<img width="1108" alt="スクリーンショット 2020-07-03 16.26.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/f6fd92e4-18da-1de8-b25a-9c252ca6409f.png">

<img width="1112" alt="スクリーンショット 2020-07-03 16.28.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/b3ede355-9349-be1c-785a-6d89b3e74439.png">

CloudFront経由でアクセスするので、パブリックアクセス許可は不要です。
<img width="1107" alt="スクリーンショット 2020-07-04 0.44.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/cc4bade5-44e4-e776-3382-f4028be97ab3.png">


### HLS動画ファイルをS3にアップロード

サンプル動画の.m3u8に合わせたディレクトリ構造でアップロードしてください。

```
S3バケット/
  bipbopall.m3u8
  gear1/
     plog_index.m3u8
     fileSequence0.ts
      ...
     fileSequence24.ts
  gear2/
     plog_index.m3u8
     fileSequence0.ts
      ...
     fileSequence24.ts
  gear3/
     plog_index.m3u8
     fileSequence0.ts
      ...
     fileSequence24.ts
  gear4/
     plog_index.m3u8
     fileSequence0.ts
      ...
     fileSequence24.ts
```

## CloudFront作業

ここで一旦、CloudFrontの作業に移ります。

CloudFrontは、S3の門番役です。
特定アクセスのみ動画データを転送するよう、色々設定します。

### CloudFrontディストリビューション作成

<img width="611" alt="スクリーンショット 2020-07-03 17.03.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/4855a592-311e-b397-8d89-e475c63b0829.png">

<img width="591" alt="スクリーンショット 2020-07-03 17.03.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/dce7e476-24cf-818c-e223-8d775a2f9d75.png">

以下の設定だけを変更して、

|設定項目名|値|
|----|----|
|Origin Domain Name|クリックするとドロップリストが出るので、先ほど作成したS3を選んでください。|
|Origin ID|ドロップリストを選択すれば自動的にセットされると思います。|
|Ristrict Buchet Access|YESを選んでください。|
|Whitelist Headers|左のリストからOriginを選択して【Add >>】ボタンを押し、右のリストにOriginが移動するようにしてください。|
|Ristrict Viewer Access|YESを選んでください。|

それ以外は触らないでOKです。
最後に【Create Distribution】ボタンを押してください。

<img width="828" alt="スクリーンショット 2020-07-03 17.04.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/97492eb6-fa6f-5695-6cc0-826ea1832c3c.png">

<img width="978" alt="スクリーンショット 2020-07-03 17.06.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/7f2d4567-e6f8-4436-e7ab-ed56f3922804.png">

<img width="754" alt="スクリーンショット 2020-07-03 17.08.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/f9543c1c-4a37-b195-468e-c1f7ec8e14fe.png">

<img width="1096" alt="スクリーンショット 2020-07-03 17.08.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/210dcfd2-b46f-3a18-8931-971eabbf7ad8.png">

CloudFrontディストリビューションが作成されます。
ちなみにID（本例では`ERDBXHEEWQCAE`）をクリックすると詳細画面に遷移できます。

<img width="870" alt="スクリーンショット 2020-07-03 17.08.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/af04b14d-fe1b-50ef-269e-7eabf180f817.png">

### S3がCloudFrontからのアクセスを許可するのに必要な情報を調べる

Origin Access Identityという値を調べます。
https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html

これはCloudFrontがS3にアクセスするための特別なユーザーで、ディストリビューション作成したときに自動的に作成されているはずです。

これのIDを控えておいてください。
欄がせまくて途中で見切れていますが、ダブルクリックで全選択→クリップボードなどで確実に控えましょう。

<img width="987" alt="スクリーンショット 2020-07-03 17.21.37.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/c15ffb17-c3e7-ad83-49be-e8a23ac06e7e.png">


## 再びS3作業

### バケットポリシーを設定する

S3のバケットポリシー設定画面を開きます。

<img width="1207" alt="スクリーンショット 2020-07-03 17.36.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/6c14b552-9924-c833-1b92-6d7fffe0793c.png">

テキストボックスに↓のJSONコードをコピペしてください。
ただし２箇所、あなたの環境に合わせて変更する必要があります。

```JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Grant a CloudFront Origin Identity access",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E1DCCPJY6X9NYO"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::yagrush-hls/*"
        }
    ]
}
```

* `"AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity` のあとに、さきほどコピーしたCFディストリビューションのOrigin Access IdentityのIDを貼り付けてください。
* `"Resource": "arn:aws:s3:::` のあとに、あなたが作成したS3の`バケット名/*`を貼り付けてください。

最後に【保存】ボタンを押してください。
エラーが表示されなければOK。

### CORS構成を設定する

#### CORSとは

異なるサーバー間でリソース（動画とか）を共有できるようにする仕組みです。
これをちゃんと設定しないと、Safari以外のブラウザではS3に置かれたHLS動画を再生できません。
ある意味クロスサイトスクリプティングみたいなことになってしまい、エラーになります。

ちなみにSafariだけは（HLSを開発したAppleさんだけあって）ダイレクトにプラグインが起動してあっけなく再生できてしまいます。
#### 設定する

S3バケットの【アクセス権限】タブから【CORSの設定】画面を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/99657eec-5cf0-cff8-cfd7-d63082d4811e.png)

↓のXMLをコピペして下さい。
ただ1ヶ所、変更して頂く必要があります。

```XML
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>http://web.yagrush.net:8000</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <AllowedMethod>HEAD</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
```

* `<AllowedOrigin>`の中の`http://web.yagrush.net:8000`は、みなさんの環境に合わせて書き換えてください。（トップレベルとセカンドレベルドメイン（本例では`yagrush.net`）の部分は、予め決めたものから変えないで下さいね。）

またこの値は、後ほど構築するWEBサーバーのURLに使用するので控えておいてください。

最後に【保存】ボタンを押して下さい。
エラーにならなければOK。

※ XMLのタグを小文字にしたり、`<AllowedOrigin>`の値の末尾に`/`が付いてたりするだけですぐエラーになります。かなりセンシティブなので、コピペや編集の際はご注意を。

## Route53作業

### ドメインを取得する

ちょっと料金がかかります。

また、<font color="red">**ここ、ウェイトポイントです！<br />購入したドメインが有効になるまで数時間かかる場合があります。**</font>

では、AWSのRoute53にアクセスしましょう。
そして、ドメインを作成する画面まで進んでください。

<img width="385" alt="スクリーンショット 2020-07-03 17.42.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/3aed6e92-08b1-926a-29eb-f9b8240baa9d.png">

<img width="483" alt="スクリーンショット 2020-07-03 17.43.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/1e66aa50-298f-9864-eb96-3043453ef6e5.png">

<img width="836" alt="スクリーンショット 2020-07-03 17.43.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/f682f84a-eab5-2b68-02d0-8576b0e69f46.png">

指定したドメインが世界でまだ使われていなければOK。
カートに入れて購入します。

<img width="1324" alt="スクリーンショット 2020-07-03 17.44.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/2d9c894b-c7a8-7abd-58de-b0a89f5f1639.png">

ドメイン購入は結構ウルサイです。
ちゃんと個人情報を入力しないといけません。
（※ 一度買ったことがあれば、この手順は発生しないと思います。）

<img width="1050" alt="スクリーンショット 2020-07-03 17.51.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/b24825ab-d3e6-924e-849a-0e84aeaf77ee.png">

その後も「規約に同意しろ」とか色々質問が出ますが、ここでは説明は省略します。
無事購入すると、世界中に浸透させる処理が開始されます。

<font color="red">**これがまたリアル数時間かかります。**</font>
完了するとAWSからメールが来ると思うので、それまで休憩するのも良いでしょう。


<img width="1110" alt="スクリーンショット 2020-07-03 17.53.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/3b1bc69e-2bd2-cfea-0f21-8045cf0e8be2.png">

ドメインが有効になったら続きをやりましょう。

### CloudFrontに振るサブドメインを決める

本例では`cf.`を付けて`cf.yagrush.net`にしています。

### ドメインとCloudFrontを結びつける

Route53でCNAMEという設定を追加すると、そういうことができます。
別名を登録する感じです。

その前にまずCloudFrontの画面で、CloudFrontディストリビューションのデフォルトアドレスを調べておきましょう。
↓このへんに表示されているやつですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/b450f07e-d81b-e7bc-d294-5a55ab14e7b8.png)

本例では`d14br4vlcj23gd.cloudfront.net`です。
**※ またもや欄がせまくて途中で見切れているので、クリップボードコピーするときはご注意ください。**

### Route53にCNAMEレコードを追加する

まずはRoute53の画面を辿って「レコードセットの作成」まで進んでください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/722d35eb-e0a9-140e-d3ec-6c24a0c9b71a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/0fc994a8-f810-d7a2-469c-e6ac4d3999d5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/ba1185e1-2a14-cc36-acc6-23f0852e4397.png)

「レコードセットの作成」ボタンを押すと、画面右側に設定小窓が出るので、先ほど調べておいた情報を設定して【作成】ボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/c3e3de0f-1971-263f-19e6-6c26bb843834.png)

これでOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/c302c1a8-cad4-6e1c-26cd-c73c12171b03.png)

## 再びCloudFront作業へ

### CloudFront用サーバー証明書を取得する

CloudFrontディストリビューションの詳細設定画面を開いてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/21def1ba-13c5-ba99-afeb-7ec65062a208.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/a211d5bb-407b-28d5-9483-6896f720dff6.png)

中段あたりにある【Request or Import a Certificate with ACM】ボタンを押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/da4c6658-acef-e036-e6ac-8bd67a03f57f.png)

「証明書のリクエスト」画面が開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/ab49984e-822f-2ada-8986-6c8293dc721b.png)
<font color="red">まずこのとき、画面右上のリージョン表示が「東京」などではなく「バージニア北部」になっていることを確認してください。</font>（CloudFrontのエンドポイントはバージニア北部にあるため、バージニア北部で取得した証明書じゃないと使えないそうです。罠ポイント。）

それでは、先ほどのCloudFront用サブドメインを入力して【次へ】を押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/e81a62d8-7492-2ad5-759c-8f54d57e9929.png)

「DNSの検証」を選択して【次へ】。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/896407a6-87cf-c30c-16d9-74e08bc3c7fe.png)

タグは、必要でしたらお好みで設定してください。
【確認】ボタンを押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/921a7e7f-e418-76af-f1f2-1604a69e62cb.png)

内容に誤りがなければ【確定とリクエスト】を押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/12c427fc-adda-50e5-a3b8-166e8a56fbc8.png)

なんだか不安にさせられる画面になりました...
でも大丈夫、【続行】を押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/45974a7b-8e5a-2703-99ad-679ce0957ac4.png)

こんな画面に遷移しました。
これまた不穏な...

もちろんこのままではダメです。
画面中段あたり、CloudFront用ドメインの左にある ▼ を押して下さい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/8c7a2328-e237-05da-5913-cc1acca22156.png)

小窓が展開されます。
その中にあるボタン【Route53でのレコードの作成】を押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/29f869f3-5b1e-ab1f-564f-ae42e424e131.png)

【作成】を押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/cea33c72-a66e-94b7-645b-7c6509447e84.png)

これで処理が進みます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/9d02b394-b440-1c5d-747f-75e6739d2e82.png)

はい、来ました！<font color="red">**ここもウェイトポイントです！**</font>
証明書が使えるようになるまで数時間かかる場合があります...。
（わりとすぐの場合もあります。）

完了すると↓に変わるはずです。
時間をおいてチェックしてみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/da6066d8-f252-4901-3ebe-a2b8b10259b8.png)

OKになったら作業の続きを。

### CloudFrontに証明書をセットする

CloudFrontの詳細画面をまた開いてください。

中段あたりの`SSL Certificate`で、2番目の選択肢`Custom SSL Certificate(example.com)`がアクティブになり選択できるようになっているはずです。
それを選択しましょう。

**※ たまに、証明書OKなのに選択肢`Custom SSL Certificate(example.com)`がアクティブになるまで時間がかかる場合があるようです...。**

そしてその下にあるボックスをクリックするとドロップリストが開き、先ほどの証明書が選択できます。

更にそれらより上の方、`Alternate Domain Names`にCloudFront用サブドメインを入力します。

全部終わったら、一番右下の【Yes, Edit】ボタンを押しましょう。
問題なければ、前の画面にスッと戻るはずです。
（もしエラーだと、赤字で下の方に表示されます。）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/4e29fe41-516f-a4d5-ea7c-a50527db9355.png)

### CloudFrontキーペアを作成する

今作業をしているAWSアカウントがルートアカウントでない場合、ログアウトしてルートアカウントでAWSコンソールにログインしなおしてください。（ルートアカウントじゃないとできない作業なのです。）

ルートアカウントで画面右上のユーザーメニューから「マイセキュリティ資格情報」を開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/6e44d85c-7837-a06d-95a1-e711b665d351.png)

ルートアカウントのみ【CloudFrontのキーペア】というタブが出現します。
それを開くと【新しいキーペアの作成】ボタンがあるので押して下さい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/4d90acab-5bf0-2d54-ca4e-b1edd15eeeea.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553715/28b066b7-1535-ded4-e354-2a80f0720ffa.png)

こうなればOK。

* 【プライベートキーファイルのダウンロード】
* 【パブリックキーファイルのダウンロード】

は、ダウンロードしておいてください。
後で使います。

後はもうルートアカウントじゃなくてOK。

## WEBアプリケーションを作る

以上でAWS作業からは離れます。

次にDockerとPHPを使ってWEBアプリケーションを作りましょう。
動画配信サイトにあたる部分です。

本記事ではローカルに構築して、hostsファイルを書き換えて対応します。
本格的にリモートに構築すると面倒なので...。

### hostsを編集する

WEBサーバーのURL（本例では`web.yagrush.net`）をローカル（127.0.0.1）に向けます。

```shell
sudo vim /etc/hosts
```

```/etc/hosts.
127.0.0.1	localhost web.yagrush.net
```

これで`web.yagrush.net`のURLでアクセスするかぎり、HLS動画を再生できます。
CloudFrontをだます ...って言い方もアレですけどね。

さすがに記事が辛くなってきた:sob:
あと少し...！

### WEBアプリケーション一式セットをダウンロードする

今回、動作確認済のWEBアプリケーション一式セットを予めご用意しました。
https://github.com/yagrush/hls-web

`git clone`でもいいですが、ZIPダウンロード&解凍でもOK。
wgetコマンドが使える方は↓の一発コマンドをどうぞ。

```shell
wget https://github.com/yagrush/hls-web/archive/master.zip -O hls-web.zip; unzip hls-web.zip; rm -f hls-web.zip

#ついでにディレクトリを移動しておく
cd hls-web-master
```

### CloudFrontのプライベートキーファイルを配備する

前の方の手順でCloudFrontキーペアを作成してプライベートキーファイルをダウンロードしたと思います。
（こんな名前のファイル `pk-XXXXXXXXXXXXXXXXXXX.pem`）
それを `hls-web-master/backend/storage/app` に置いてください。

### Laravelのstorageディレクトリのアクセス権限を設定する

以下のコマンドを打ってください。

```shell
chmod -R 777 backend/storage
```

### 設定をカスタマイズする

数か所、みなさんの環境に合わせて変更して頂く必要があります。
（一応目印として`**hls**`というコメントを付けてあります。）

#### `hls-web-master/backend/resources/views/play_video.blade.php`, `hls-web-master/backend/app/Http/Controllers/PlayVideoController.php`
`cf.yagrush.net`や`yagrush.net`の部分を、みなさんのケースに合わせて書き換えて下さい。

#### `hls-web-master/backend/app/Http/Controllers/PlayVideoController.php`
みなさんのCloudFrontキーペアのプライベートキーファイル名に合わせて`XXXXXXXXXXXXXXXXXXX`の部分を書き換えて下さい。
ちなみにキーペアIDも、プライベートキーファイル名の`XXXXXXXXXXXXXXXXXXX`と同じです。

```hls-web-master/backend/resources/views/play_video.blade.php
...
...
    var videoSrc = 'https://cf.yagrush.net/bipbopall.m3u8'; //**hls**
...
...
```

```hls-web-master/backend/app/Http/Controllers/PlayVideoController.php
...
...
        $resourceKey = "https://cf.yagrush.net/*"; //**hls**
...
...
            'private_key' => storage_path() . '/app/pk-XXXXXXXXXXXXXXXXXXX.pem', //**hls**
            'key_pair_id' => 'XXXXXXXXXXXXXXXXXXX' //**hls**
...
...
        $domain = 'yagrush.net';  //**hls**
...
...
```

### 起動

`hls-web-master/`で以下のコマンドを打ってください。（数分かかるかも。）

```shell
make init
```

問題なければ、

* ポート8000でnginxコンテナがWEBサーバーのフロントとして起動します。
* PHPを処理するためのバックエンドとしてphp-fpmコンテナが起動します。
* Laravelフレームワークのためだけにポート3306でmysqlコンテナが起動しますが、気にしないでください。

もしポート被りで起動に失敗したら、`hls-web-master/docker-compose.yml`を編集してポートを変更するなどしてくださいね。

### 動画再生してみる

動画再生ページにChromeでアクセスしてみて下さい。
（本例だと http://web.yagrush.net:8000/play_video ）

再生されましたでしょうか？

今回は動画データの一部だけしか配備してないので、途中で停止します。

お時間あればSafariでもアクセスしてみて下さい。
ちなみにSafari以外での再生のために [hls.js](https://github.com/video-dev/hls.js/) を採用しました。

### 動画に直アクセスできないのを確認してみる

以下のコマンドでcookieを使わずにアクセスしてみます。
（例によって`yagrush.net`はみなさんの環境に合わせて書き換えて下さいね。）

まずはOriginヘッダを付けて送信してみます。

```shell
curl -X GET -H 'Origin: http://web.yagrush.net:8000' -i -v -f https://cf.yagrush.net/bipbopall.m3u8
```

403エラー！
拒否されちゃいました。

```shell
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying xxx.xxx.xxx.xxx...
* TCP_NODELAY set
* Connected to cf.yagrush.net (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=cf.yagrush.net
*  start date: Jul  3 00:00:00 2020 GMT
*  expire date: Aug  3 12:00:00 2021 GMT
*  subjectAltName: host "cf.yagrush.net" matched certs "cf.yagrush.net"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0xxxxxxxxxxxxx)
> GET /bipbopall.m3u8 HTTP/2
> Host: cf.yagrush.net
> User-Agent: curl/7.64.1
> Accept: */*
> Origin: http://web.yagrush.net:8000
> 
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
* The requested URL returned error: 403 
* stopped the pause stream!
* Connection #0 to host cf.yagrush.net left intact
curl: (22) The requested URL returned error: 403 
* Closing connection 0
```

今度はプライベートキーファイルまで付けて送信してみます。

```shell
curl -X GET -H 'Origin: http://web.yagrush.net:8000' -i -v -f -E backend/storage/app/pk-XXXXXXXXXXXXXXXXXX.pem https://cf.yagrush.net/bipbopall.m3u8
```

やはり怒られました。

```shell
Note: Unnecessary use of -X or --request, GET is already inferred.
*   Trying xxx.xxx.xxx.xxx...
* TCP_NODELAY set
* Connected to sample.flhls.net (xxx.xxx.xxx.xxx) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* could not load PEM client certificate, LibreSSL error error:09FFF06C:PEM routines:CRYPTO_internal:no start line, (no key found, wrong pass phrase, or wrong file format?)
* Closing connection 0
curl: (58) could not load PEM client certificate, LibreSSL error error:09FFF06C:PEM routines:CRYPTO_internal:no start line, (no key found, wrong pass phrase, or wrong file format?)
```

### 終了

以下のコマンドでWEBアプリケーションを終了＆破棄します。

```shell
make destroy
```

おつかれさまでしたぁーー！！！

### WEBアプリケーション変更点 まとめ

今回は、定番のこちら -> [最強のLaravel開発環境をDockerを使って構築する【新編集版】](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4) をベースに以下の手を加えました。

#### ★ [PHP用AWS SDK](https://docs.aws.amazon.com/ja_jp/sdk-for-php/v3/developer-guide/getting-started_installation.html)インストールをビルド工程（Makefile @ create-project）に追加。

```shell
docker-compose exec app composer require aws/aws-sdk-php
```

#### ★ CloudFrontキーペアのプライベートキーファイルを`backend/storage/app`に配備。

#### ★ `backend/routes/web.php`にルーティング設定追記。

```php
Route::get('/play_video', 'PlayVideoController@show');
```

#### ★ ソースコード２つ作成。

* backend/app/Http/Controllers/PlayVideoController.php
* backend/resources/views/play_video.blade.php

[CloudFront+S3+Video.js+ 署名付きCookieでクローズドな動画配信](https://www.datastadium.co.jp/engineer/e-reports/4557) を参考に書きました。
↑お時間あれば是非ご覧になってみて下さい。


# 参考文献

今回はこれらのサイトにお世話になりました。
ありがとうございます。

* [CloudFront+S3+Video.js+ 署名付きCookieでクローズドな動画配信](https://www.datastadium.co.jp/engineer/e-reports/4557)
* [AWS CloudFront+S3で署名付きCookieでプライベートコンテンツを配信する方法](https://confrage.jp/aws-cloudfronts3%E3%81%A7%E7%BD%B2%E5%90%8D%E4%BB%98%E3%81%8Dcookie%E3%81%A7%E3%83%97%E3%83%A9%E3%82%A4%E3%83%99%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%92%E9%85%8D/)
* [Adaptive Streamingサンプル](https://tech.jstream.jp/blog/streaming/adaptive-sample-content/)
* [ウェブディストリビューションでの Amazon S3 オリジン、MediaPackage チャネル、およびカスタムオリジンの使用](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html)