Cloudflare StreamでVOD動画の署名付きURL配信をしてみた
はじめに
清水です。Cloudflareの動画配信プラットフォームのサービスCloudflare Streamを試しています。Cloudflare Streamでは動画コンテンツ保護の機能の1つとして署名付きURLがサポートされています。デフォルト状態ではビデオIDがわかれば誰もが動画を見ることのできるパブリックな状態ですが、署名付きURLを有効にすることで動画再生にトークンが必要になります。本エントリではVOD動画に対してこの署名付きURLを使った配信をしてみたのでまとめてみたいと思います。
署名付きURLを有効にする
まずは署名付きURL機能を有効にします。署名付きURL機能はビデオごとに設定していきます。ダッシュボードでビデオの詳細ページに進み、「設定」タブの「署名付きURLが必要」の項目のをチェックします。
なお署名付きURL機能を有効にした場合、ダッシュボード内のプレビュープレイヤーでの再生が無効になりますので注意しましょう。
APIからも署名付きURLの有効化が可能です。以下の形式でJSONデータをPOSTします。
curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ --data "{\"uid\":\"${VIDEO_UID}\", \"requireSignedURLs\":true}" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}"
以下が実際の実行例です。設定後、"requireSignedURLs": true
となっていることが確認できます。
##### 設定前の状態を確認 % curl -X GET -H "Authorization: Bearer ${TOKEN}" "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}" { "result": { "uid": "c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "creator": "user-odawara", "thumbnail": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/thumbnails/thumbnail.jpg", "thumbnailTimestampPct": 0, "readyToStream": true, "status": { "state": "ready", "pctComplete": "100.000000", "errorReasonCode": "", "errorReasonText": "" }, "meta": { "filename": "IMG_5908.MOV", "filetype": "video/quicktime", "name": "小田原の海", "relativePath": "null", "type": "video/quicktime" }, "created": "2022-04-30T09:57:30.832975Z", "modified": "2022-06-29T07:56:28.246831Z", "size": 382466674, "preview": "https://watch.videodelivery.net/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "allowedOrigins": [], "requireSignedURLs": true, "uploaded": "2022-04-30T09:57:30.832929Z", "uploadExpiry": "2022-05-01T09:57:30.832922Z", "maxSizeBytes": null, "maxDurationSeconds": null, "duration": 60.9, "input": { "width": 3840, "height": 2160 }, "playback": { "hls": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8", "dash": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.mpd" }, "watermark": null }, "success": true, "errors": [], "messages": [] } ##### 署名付きURL機能を有効に設定 % curl -X POST \ -H "Authorization: Bearer ${TOKEN}" \ --data "{\"uid\":\"${VIDEO_UID}\", \"requireSignedURLs\":true}" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}" { "result": { "uid": "c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "creator": "user-odawara", "thumbnail": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/thumbnails/thumbnail.jpg", "thumbnailTimestampPct": 0, "readyToStream": true, "status": { "state": "ready", "pctComplete": "100.000000", "errorReasonCode": "", "errorReasonText": "" }, "meta": { "filename": "IMG_5908.MOV", "filetype": "video/quicktime", "name": "小田原の海", "relativePath": "null", "type": "video/quicktime" }, "created": "2022-04-30T09:57:30.832975Z", "modified": "2022-06-29T08:04:21.104628Z", "size": 382466674, "preview": "https://watch.videodelivery.net/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "allowedOrigins": [], "requireSignedURLs": true, "uploaded": "2022-04-30T09:57:30.832929Z", "uploadExpiry": "2022-05-01T09:57:30.832922Z", "maxSizeBytes": null, "maxDurationSeconds": null, "duration": 60.9, "input": { "width": 3840, "height": 2160 }, "playback": { "hls": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8", "dash": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.mpd" }, "watermark": null }, "success": true, "errors": [], "messages": [] } ##### 設定後の状態を確認 % curl -X GET -H "Authorization: Bearer ${TOKEN}" "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}" { "result": { "uid": "c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "creator": "user-odawara", "thumbnail": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/thumbnails/thumbnail.jpg", "thumbnailTimestampPct": 0, "readyToStream": true, "status": { "state": "ready", "pctComplete": "100.000000", "errorReasonCode": "", "errorReasonText": "" }, "meta": { "filename": "IMG_5908.MOV", "filetype": "video/quicktime", "name": "小田原の海", "relativePath": "null", "type": "video/quicktime" }, "created": "2022-04-30T09:57:30.832975Z", "modified": "2022-06-29T08:04:21.104628Z", "size": 382466674, "preview": "https://watch.videodelivery.net/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "allowedOrigins": [], "requireSignedURLs": true, "uploaded": "2022-04-30T09:57:30.832929Z", "uploadExpiry": "2022-05-01T09:57:30.832922Z", "maxSizeBytes": null, "maxDurationSeconds": null, "duration": 60.9, "input": { "width": 3840, "height": 2160 }, "playback": { "hls": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8", "dash": "https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.mpd" }, "watermark": null }, "success": true, "errors": [], "messages": [] }
トークンを生成して動画を再生する
署名付きURLを有効にできました。この状態だと先ほどのダッシュボードのほか、iframe.videodelivery.net
で提供されるStream PlayerやマニフェストURLでも当然再生はできません。
以下のようにStream Playerでは「You don't have permission to view this video」と権限がない旨、表示されてしまいます。
またマニフェストURLにアクセスしようとした場合、以下のように401 unauthorized
が返ってきます。
% curl https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8 401 unauthorized
% curl -I https://cloudflarestream.com/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8 HTTP/2 401 date: Wed, 29 Jun 2022 08:09:33 GMT content-type: text/plain content-length: 16 access-control-allow-origin: * cache-control: no-cache, no-store, must-revalidate vary: origin expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" server: cloudflare cf-ray: 722d2e419c7d1ee9-NRT
再生するためにはトークンが必要です。トークンの作成方法はドキュメントに記載があります。
トークン作成時、デフォルトでは1時間有効なトークンが作成されます。オプションでトークンの有効期限のほか、再生できる地域や国、IPアドレスなどを指定することが可能です。
またトークンの作成方法についても、api.cloudflare.com
の/token
エンドポイントを使って作成する方法、自前でプログラムからトークンを作成する方法があります。前者についてはテスト目的、1日あたり10,000未満のトークンが目安ということですので、本格的な運用の場合には後者の方法を採るようにしましょう。
これらトークン作成方法については、詳細がドキュメントにCloudflare Workerを用いたサンプルコードとともに記載されています。今回はシンプルに/token
エンドポイントを利用した方法を確認してみます。api.cloudflare.com
にビデオIDを指定してデータをPOSTするかたちです。以下の形式となります。
curl -X POST \ -H "Authorization: Bearer $TOKEN" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}/token"
以下のようにexp
で有効期限を指定することも可能です。(有効期限を指定しない場合は1時間となります。)オプションは「
Create a signed URL token for a video - Cloudflare API v4 Documentation」なども参考にしましょう。
% curl -X POST \ -H "Authorization: Bearer $TOKEN" \ --data '{"exp": 1656491400}' \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}/token"
以下が実際の実行結果です。
% curl -X POST \ -H "Authorization: Bearer $TOKEN" \ "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/${VIDEO_UID}/token" { "result": { "token": "eyJhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" }, "success": true, "errors": [], "messages": [] }
Stream Playerで再生の際、ビデオIDの代わりにこのトークン文字列を使用します。
- 署名付きURLが無効の場合
https://iframe.videodelivery.net/c409xxxxxxxxxxxxxxxxxxxxxxxxxxxx
- 署名付きURLが有効な場合
https://iframe.videodelivery.net/eyJhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
マニフェストURLでも同様に、ビデオIDの代わりにトークン文字列を利用すればアクセスできるようになりました。以下、HLSマニフェストURLへアクセスしてみた例です。
% curl -I https://cloudflarestream.com/eyJhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8 HTTP/2 200 date: Wed, 29 Jun 2022 08:16:02 GMT content-type: application/x-mpegURL access-control-allow-origin: * cache-control: public, max-age=600 vary: origin, referer access-control-allow-headers: range access-control-expose-header: cf-ray stream-dw-version: 2022.6.2 expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct" server: cloudflare cf-ray: 722d37beb93a80f5-NRT
% curl https://cloudflarestream.com/eyJhxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/manifest/video.m3u8 #EXTM3U #EXT-X-VERSION:6 #EXT-X-INDEPENDENT-SEGMENTS #EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_audio",NAME="und",LANGUAGE="en",DEFAULT=YES,AUTOSELECT=YES,URI="stream_tcecxxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx553.m3u8" #EXT-X-STREAM-INF:RESOLUTION=854x480,CODECS="avc1.4d401f,mp4a.40.2",BANDWIDTH=2764131,AVERAGE-BANDWIDTH=2190192,FRAME-RATE=60.000,AUDIO="group_audio" stream_t63exxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx537.m3u8 #EXT-X-STREAM-INF:RESOLUTION=1920x1080,CODECS="avc1.4d402a,mp4a.40.2",BANDWIDTH=9212192,AVERAGE-BANDWIDTH=7422169,FRAME-RATE=60.000,AUDIO="group_audio" stream_t63exxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx589.m3u8 #EXT-X-STREAM-INF:RESOLUTION=1280x720,CODECS="avc1.4d4020,mp4a.40.2",BANDWIDTH=6762836,AVERAGE-BANDWIDTH=5211825,FRAME-RATE=60.000,AUDIO="group_audio" stream_t63exxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx535.m3u8 #EXT-X-STREAM-INF:RESOLUTION=640x360,CODECS="avc1.4d401f,mp4a.40.2",BANDWIDTH=1524991,AVERAGE-BANDWIDTH=1209509,FRAME-RATE=60.000,AUDIO="group_audio" stream_t63exxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx526.m3u8 #EXT-X-STREAM-INF:RESOLUTION=426x240,CODECS="avc1.42c01e,mp4a.40.2",BANDWIDTH=838965,AVERAGE-BANDWIDTH=624348,FRAME-RATE=60.000,AUDIO="group_audio" stream_t63exxxxxxxxxxxxxxxxxxxxxxxxxxxxx_r16xxxx464.m3u8
まとめ
Cloudflare Streamで署名付きURLを使った動画コンテンツの限定配信を試してみました。動画のURL自体がトークンを利用したものになるイメージですね。認証したユーザにだけ動画コンテンツを視聴させるといった場合のほか、動画にIP制限やジオロケーション制限を入れる場合も同様にこの署名付きURL機能を利用します。今回はCloudflareの/token
エンドポイントを利用してトークンを生成する方法でしたが、自前でプログラムからトークンを作成する方法についても試しておきたいなと思いました。