AWS S3+Media Converter+CloudFront 做 video file streaming CDN服務

雖然最後算算價格跟考慮使用情境應該不會採用這樣的 solution,但還是來記錄一下。

Main idea

S3 bucket 會因為 region 限制,所以如果使用者會遍部全球各地,那網路速度變成是服務最大的瓶頸。除非 sync 所有資料,但是這樣又會讓儲存費用倍增,對於少量不太會變動的檔案這樣弄還可以,但是如果是使用者產生的資料就會變得很可怕。
所以想法是開兩個 s3 bucket, 其中一個當作使用者上傳video的 storage,另一個當作轉檔後儲存的 storage。然後搭配cloudfront讀取資料做cdn,提供各地使用者作video file streaming。

開S3 bucket儲存檔案

Image 001

開bucket 的時候要決定檔案要放在哪個 region,所以還是有可能面臨到使用者距離這個bucket要花許多傳輸時間。所以簡單的解法是不要讓使用者直接對bucket寫入檔案,而是先傳到就近的EC2 instance,再由AWS內部水管傳比較快。

Image 002

然後關閉public access,避免安全漏洞。

Image 003

在網頁環境中要播放video file streaming,常使用 HLS.js ,而影片是放在aws 的 domain name底下,所以一定會有 cors 的問題。所以記得要設定 CORS policy,如果是allow * (如上圖例最後面)的話會變成任何網站都可以來 access 資料,這樣不會是好事,所以要乖乖寫好允許 CORS 的網站。
這樣 s3 bucket 建立大抵完成。

Create IAM role for Media Convert

在開始使用 Media Converter 之前,請先去建立 MediaConvert_Default_Role,要不然沒辦法使用Media Convert 服務。

Image 011

到IAM 頁面,點選 Roles

Image 012

Create Role

Image 013

直接看下面的 services,可以找到 MediaConvert

Image 014

預設會帶入S3fullaccess 跟Apigetwayinvokefullaccess 權限,直接下一步直到最後建立好為止。

使用 Media Convert

Image 005

上圖是 job list,可以看到之前的 job 的狀態。

Image 006

Create Job 後,要先選擇檔案來源。接著增加 output group (add output group)

Image 007

output group 有多種格式,在這邊我用apple hls

Image 008

然後要指定Convert結果要寫入到哪邊。

Image 009

output1 就是要指定output格式, bitrate等資料,可以在preset裏頭選擇,也可以手動填寫。在這邊我用16:9 1280*720 5.0Mbps。如果要多個 output也可以,繼續按add output group就好。

Image 010

Job setting裡面要記得指定給MediaConvert_Default_Role去執行,要不然果會類似下圖。

Image 016

Image 015

成功的話就沒有 error message

Image 017

切段的video file也可以在s3 bucket 裏頭看到,而.m3u8檔案就是streaming時的play list。

CloudFront CDN

Image 018

上圖為進到cloudfront的頁面,可以看到每個distribution狀態。要注意的是,因為CDN caching 的關係,所以做了任何修改都不會馬上看到修改後的樣子。

Image 027

修改後或者是新建立的 distribution都會看到狀態是 in progress,都要等一陣子

Image 019

建立新的distribution,用Web,而RTMP已經快要終止服務了。

Image 020

選擇資料來源,除了 s3 bucket 之外,也可以用Load balancer 等服務,反正就是後面真的有東西就是。

Image 021

然後讀取bucket 要有 read permission (特別是當 bucket 在建立的時候就已經關閉public access)。

Image 56

如果是建立新的 access identity,之後也可以查得到。

Image 024

然後要 whitelisting header,特別是 CORS 相關的,要不然跨網域甚麼東西都拿不到。

Image 025

建立之後可以看到 domain name,就可以透過這個 domain name 走 http/https 來拿資料了。

除此之外,還可以設定signed url/signed cookie來限制存取,當有需要讓某些資料只能被有登入的使用者瀏覽時就可以用到。

HLS.js streaming

最後就是寫網頁,最常用的應該就是 HLS.js。官網本身提供的範例也很清楚,改寫一下即可,類似下面這樣。

<html>

  <head>
    <title>Hls.js demo - basic usage</title>
  </head>

  <body>
      <script src="https://hls-js-latest.netlify.app/dist/hls.js"></script>

      <center>
          <h1>Hls.js demo - basic usage</h1>
          <video height="600" id="video" controls></video>
      </center>

      <script>
        if(Hls.isSupported()) {
          var video = document.getElementById('video');
          var hls = new Hls({
              debug: true
          });
          hls.loadSource('https://YOUR_CLOUDFRONT_DOMAIN_NAME/PATH/TO_PLAY_LIST.m3u8');
          hls.attachMedia(video);
          hls.on(Hls.Events.MEDIA_ATTACHED, function() {
//            video.muted = true;
            video.play();
        });
       }
       // hls.js is not supported on platforms that do not have Media Source Extensions (MSE) enabled.
       // When the browser has built-in HLS support (check using `canPlayType`), we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video element throught the `src` property.
       // This is using the built-in support of the plain video element, without using hls.js.
        else if (video.canPlayType('application/vnd.apple.mpegurl')) {
          video.src = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8';
          video.addEventListener('canplay',function() {
            video.play();
          });
        }
      </script>

  <!-- injected in netlify post processing step -->
<div style="position: absolute; top: 5px; right: 5px;">
  <a rel="noopener" href="https://www.netlify.com" target="_blank"><img src="https://www.netlify.com/img/global/badges/netlify-color-accent.svg" /></a>
</div></body>
</html>