既定では、Stream 上の動画は動画 ID だけで誰でも視聴できます。動画を既定で非公開にし、特定のユーザーだけにアクセスを許可したい場合は、署名付き URL(signed URL)機能を使います。動画に署名付き URL を必須にすると、動画 ID だけでは公開アクセスできなくなります。視聴またはダウンロードには、署名付き URL のトークンが必要です。
署名付き URL の一般的な用途は次のとおりです。
- ログイン済みのメンバーだけが特定の動画を視聴できるようにする
- 限られた期間(例: 24 時間)だけ視聴できるようにする
- 地理位置情報に基づいてアクセスを制限する
requireSignedURLs をオンにすると、署名付き URL で動画を保護できます。このオプションを有効にすると、customer-<CODE>.cloudflarestream.com/<VIDEO_ID>/watch や組み込みプレーヤーなど、公開リンクはすべて使えなくなります。
視聴の制限は、動画のメタデータを更新して行います。
curl "https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}" \
--header "Authorization: Bearer <API_TOKEN>" \
--header "Content-Type: application/json"
--data "{\"uid\": \"<VIDEO_UID>\", \"requireSignedURLs\": true }"レスポンス:
{
"result": {
"uid": "<VIDEO_UID>",
...
"requireSignedURLs": true
},
"success": true,
"errors": [],
"messages": []
}Worker の Stream バインディングでも、署名付き URL を必須にできます。セットアップ手順は Workers API にバインドする を参照してください。
export default {
async fetch(request, env) {
const video = await env.STREAM.video("VIDEO_ID").update({
requireSignedURLs: true,
});
return Response.json(video);
},
};export default {
async fetch(request, env) {
const video = await env.STREAM.video("VIDEO_ID").update({
requireSignedURLs: true,
});
return Response.json(video);
},
};アプリでトークンを生成する方法は 3 つあります。
-
少量またはテスト:
/tokenエンドポイントで短寿命の署名付きトークンを生成します。 テスト用途、または 1 日あたりのトークン生成が 1,000 件未満の場合に推奨します。トークンごとに Cloudflare へ API 呼び出しが必要で、レート制限 の対象です。既定の有効期間は 1 時間です。この方法は Live WebRTC に対応していません。 -
推奨: 署名鍵でトークンを作成します。 1 日あたり数千人のユーザーがいる場合や、Live WebRTC のように大量のトークンが必要な場合は、署名鍵を使って自分でトークンを作成できます。トークンを生成するたびに Stream API を呼ぶ必要がなく、レート制限の対象にもなりません。
-
Workers バインディング: Stream バインディングでトークンを生成します。 Stream バインディング付きの Cloudflare Workers を使っている場合は、別途 API を呼んだり署名鍵を管理したりせずに、直接トークンを生成できます。Workers 利用者にはいちばん簡単な方法です。アクセスルールや独自の有効期限など、高度なカスタマイズが必要な場合は、署名鍵を使ってください。
非公開にした任意の動画に対して /token エンドポイントを呼ぶと、1 時間で期限切れになる署名付き URL トークンを取得できます。この方法は Live WebRTC に対応していません。
curl --request POST \
https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}/token \
--header "Authorization: Bearer <API_TOKEN>"リクエストが成功すると、次のようなレスポンスが返ります。
{
"result": {
"token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ"
},
"success": true,
"errors": [],
"messages": []
}Stream バインディングで署名付きトークンを生成できます。
export default {
async fetch(request, env) {
const token = await env.STREAM.video("VIDEO_ID").generateToken();
return Response.json({ token });
},
};export default {
async fetch(request, env) {
const token = await env.STREAM.video("VIDEO_ID").generateToken();
return Response.json({ token });
},
};動画の表示や、マニフェスト、サムネイルなどのアセットを使うときは、動画 / 入力 ID の代わりに token の値を使います。たとえば Stream プレーヤーを使う場合は、cloudflarestream.com/ と /iframe のあいだの ID をトークンに置き換えます: https://customer-<CODE>.cloudflarestream.com/<TOKEN>/iframe。
<iframe
src="https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/iframe"
style="border: none;"
height="720"
width="1280"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowfullscreen="true"
></iframe>同様に、独自のプレーヤーを使う場合は、マニフェスト URL の動画 ID を token の値に置き換えて、HLS または DASH マニフェストを取得します。
https://customer-<CODE>.cloudflarestream.com/<TOKEN>/manifest/video.m3u8https://customer-<CODE>.cloudflarestream.com/<TOKEN>/manifest/video.mpd
本文なしで /token エンドポイントを呼ぶと、その他の制限もなく ダウンロード へのアクセスもない、1 時間で期限切れになるトークンが返ります。リクエストに追加のプロパティを渡すと、このトークンをカスタマイズできます。
const signed_url_restrictions = {
// Extend the lifetime of the token to 12 hours:
exp: Math.floor(Date.now() / 1000) + 12 * 60 * 60,
// Allow access to MP4 or Audio Download URLs:
downloadable: true,
// Geo or IP access restrictions:
accessRules: {
// ... see examples below
}
};
const init = {
method: "POST",
headers: {
Authorization: "Bearer <API_TOKEN>",
"content-type": "application/json;charset=UTF-8",
},
body: JSON.stringify(signed_url_restrictions),
};
const signedurl_service_response = await fetch(
"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid}/token",
init,
);
return new Response(
JSON.stringify(await signedurl_service_response.json()),
{ status: 200 },
);ただし、プログラムでトークンを生成する場合や、こうしたカスタマイズを加える場合は、署名鍵を使い、アプリケーション内だけでトークンを生成する方が速く、スケールしやすいです。
Worker で Stream バインディングを使っている場合は、/token エンドポイントへの別途 API 呼び出しや、署名鍵の自己管理なしに、署名付きトークンを生成できます。トークン生成はバインディングが内部で処理します。
セットアップ手順は Workers API にバインドする を参照してください。
export default {
async fetch(request, env) {
const token = await env.STREAM.video("VIDEO_ID").generateToken();
return Response.json({ token });
},
};export default {
async fetch(request, env) {
const token = await env.STREAM.video("VIDEO_ID").generateToken();
return Response.json({ token });
},
};バインディングが生成するトークンの有効期間は、既定で 1 時間です。有効期限、地理位置情報、ダウンロードアクセスなどの制限をカスタマイズする必要がある場合は、署名鍵 を使ってカスタムクレーム付きのトークンを作成します。
トークンを大量に生成する場合、Live WebRTC を使う場合、またはアクセスルールをカスタマイズする必要がある場合は、署名鍵で新しいトークンを生成します。毎回 Stream API を呼ぶ必要はありません。
curl --request POST \
"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/keys" \
--header "Authorization: Bearer <API_TOKEN>"レスポンスには pem と jwk の値が返ります。
{
"result": {
"id": "8f926b2b01f383510025a78a4dcbf6a",
"pem": "LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBemtHbXhCekFGMnBIMURiWmgyVGoyS3ZudlBVTkZmUWtNeXNCbzJlZzVqemRKTmRhCmtwMEphUHhoNkZxOTYveTBVd0lBNjdYeFdHb3kxcW1CRGhpdTVqekdtYW13NVgrYkR3TEdTVldGMEx3QnloMDYKN01Rb0xySHA3MDEycXBVNCtLODUyT1hMRVVlWVBrOHYzRlpTQ2VnMVdLRW5URC9oSmhVUTFsTmNKTWN3MXZUbQpHa2o0empBUTRBSFAvdHFERHFaZ3lMc1Vma2NsRDY3SVRkZktVZGtFU3lvVDVTcnFibHNFelBYcm9qaFlLWGk3CjFjak1yVDlFS0JCenhZSVEyOVRaZitnZU5ya0t4a2xMZTJzTUFML0VWZkFjdGkrc2ZqMkkyeEZKZmQ4aklmL2UKdHBCSVJZVDEza2FLdHUyYmk0R2IrV1BLK0toQjdTNnFGODlmTHdJREFRQUJBb0lCQUYzeXFuNytwNEtpM3ZmcgpTZmN4ZmRVV0xGYTEraEZyWk1mSHlaWEFJSnB1MDc0eHQ2ZzdqbXM3Tm0rTFVhSDV0N3R0bUxURTZacy91RXR0CjV3SmdQTjVUaFpTOXBmMUxPL3BBNWNmR2hFN1pMQ2wvV2ZVNXZpSFMyVDh1dGlRcUYwcXpLZkxCYk5kQW1MaWQKQWl4blJ6UUxDSzJIcmlvOW1KVHJtSUUvZENPdG80RUhYdHpZWjByOVordHRxMkZrd3pzZUdaK0tvd09JaWtvTgp2NWFOMVpmRGhEVG0wdG1Vd0tLbjBWcmZqalhRdFdjbFYxTWdRejhwM2xScWhISmJSK29PL1NMSXZqUE16dGxOCm5GV1ZEdTRmRHZsSjMyazJzSllNL2tRVUltT3V5alY3RTBBcm5vR2lBREdGZXFxK1UwajluNUFpNTJ6aTBmNloKdFdvwdju39xOFJWQkwxL2tvWFVmYk00S04ydVFadUdjaUdGNjlCRDJ1S3o1eGdvTwowVTBZNmlFNG9Cek5GUW5hWS9kayt5U1dsQWp2MkgraFBrTGpvZlRGSGlNTmUycUVNaUFaeTZ5cmRkSDY4VjdIClRNRllUQlZQaHIxT0dxZlRmc00vRktmZVhWY1FvMTI1RjBJQm5iWjNSYzRua1pNS0hzczUyWE1DZ1lFQTFQRVkKbGIybDU4blVianRZOFl6Uk1vQVo5aHJXMlhwM3JaZjE0Q0VUQ1dsVXFZdCtRN0NyN3dMQUVjbjdrbFk1RGF3QgpuTXJsZXl3S0crTUEvU0hlN3dQQkpNeDlVUGV4Q3YyRW8xT1loMTk3SGQzSk9zUythWWljemJsYmJqU0RqWXVjCkdSNzIrb1FlMzJjTXhjczJNRlBWcHVibjhjalBQbnZKd0k5aUpGVUNnWUVBMjM3UmNKSEdCTjVFM2FXLzd3ekcKbVBuUm1JSUczeW9UU0U3OFBtbHo2bXE5eTVvcSs5aFpaNE1Fdy9RbWFPMDF5U0xRdEY4QmY2TFN2RFh4QWtkdwpWMm5ra0svWWNhWDd3RHo0eWxwS0cxWTg3TzIwWWtkUXlxdjMybG1lN1JuVDhwcVBDQTRUWDloOWFVaXh6THNoCkplcGkvZFhRWFBWeFoxYXV4YldGL3VzQ2dZRUFxWnhVVWNsYVlYS2dzeUN3YXM0WVAxcEwwM3h6VDR5OTBOYXUKY05USFhnSzQvY2J2VHFsbGVaNCtNSzBxcGRmcDM5cjIrZFdlemVvNUx4YzBUV3Z5TDMxVkZhT1AyYk5CSUpqbwpVbE9ldFkwMitvWVM1NjJZWVdVQVNOandXNnFXY21NV2RlZjFIM3VuUDVqTVVxdlhRTTAxNjVnV2ZiN09YRjJyClNLYXNySFVDZ1lCYmRvL1orN1M3dEZSaDZlamJib2h3WGNDRVd4eXhXT2ZMcHdXNXdXT3dlWWZwWTh4cm5pNzQKdGRObHRoRXM4SHhTaTJudEh3TklLSEVlYmJ4eUh1UG5pQjhaWHBwNEJRNTYxczhjR1Z1ZSszbmVFUzBOTDcxZApQL1ZxUWpySFJrd3V5ckRFV2VCeEhUL0FvVEtEeSt3OTQ2SFM5V1dPTGJvbXQrd3g0NytNdWc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=",
"jwk": "eyJ1c2UiOiJzaWciLCJrdHkiOiJSU0EiLCJraWQiOiI4ZjkyNmIyYjAxZjM4MzUxNzAwMjVhNzhhNGRjYmY2YSIsImFsZyI6IlJTMjU2IiwibiI6InprR214QnpBRjJwSDFEYlpoMlRqMkt2bnZQVU5GZlFrTXlzQm8yZWc1anpkSk5kYWtwMEphUHhoNkZxOTZfeTBVd0lBNjdYeFdHb3kxcW1CRGhpdTVqekdtYW13NVgtYkR3TEdTVldGMEx3QnloMDY3TVFvTHJIcDcwMTJxcFU0LUs4NTJPWExFVWVZUGs4djNGWlNDZWcxV0tFblREX2hKaFVRMWxOY0pNY3cxdlRtR2tqNHpqQVE0QUhQX3RxRERxWmd5THNVZmtjbEQ2N0lUZGZLVWRrRVN5b1Q1U3JxYmxzRXpQWHJvamhZS1hpNzFjak1yVDlFS0JCenhZSVEyOVRaZi1nZU5ya0t4a2xMZTJzTUFMX0VWZkFjdGktc2ZqMkkyeEZKZmQ4aklmX2V0cEJJUllUMTNrYUt0dTJiaTRHYi1XUEstS2hCN1M2cUY4OWZMdyIsImUiOiJBUUFCIiwiZCI6IlhmS3FmdjZuZ3FMZTktdEo5ekY5MVJZc1ZyWDZFV3RreDhmSmxjQWdtbTdUdmpHM3FEdU9henMyYjR0Um9mbTN1MjJZdE1UcG16LTRTMjNuQW1BODNsT0ZsTDJsX1VzNy1rRGx4OGFFVHRrc0tYOVo5VG0tSWRMWlB5NjJKQ29YU3JNcDhzRnMxMENZdUowQ0xHZEhOQXNJcllldUtqMllsT3VZZ1Q5MEk2MmpnUWRlM05oblN2MW42MjJyWVdURE94NFpuNHFqQTRpS1NnMl9sbzNWbDhPRU5PYlMyWlRBb3FmUld0LU9OZEMxWnlWWFV5QkRQeW5lVkdxRWNsdEg2Zzc5SXNpLU04ek8yVTJjVlpVTzdoOE8tVW5mYVRhd2xnei1SQlFpWTY3S05Yc1RRQ3VlZ2FJQU1ZVjZxcjVUU1Ai2odx5iT0xSX3BtMWFpdktyUSIsInAiOiI5X1o5ZUpGTWI5X3E4UlZCTDFfa29YVWZiTTRLTjJ1UVp1R2NpR0Y2OUJEMnVLejV4Z29PMFUwWTZpRTRvQnpORlFuYVlfZGsteVNXbEFqdjJILWhQa0xqb2ZURkhpTU5lMnFFTWlBWnk2eXJkZEg2OFY3SFRNRllUQlZQaHIxT0dxZlRmc01fRktmZVhWY1FvMTI1RjBJQm5iWjNSYzRua1pNS0hzczUyWE0iLCJxIjoiMVBFWWxiMmw1OG5VYmp0WThZelJNb0FaOWhyVzJYcDNyWmYxNENFVENXbFVxWXQtUTdDcjd3TEFFY243a2xZNURhd0JuTXJsZXl3S0ctTUFfU0hlN3dQQkpNeDlVUGV4Q3YyRW8xT1loMTk3SGQzSk9zUy1hWWljemJsYmJqU0RqWXVjR1I3Mi1vUWUzMmNNeGNzMk1GUFZwdWJuOGNqUFBudkp3STlpSkZVIiwiZHAiOiIyMzdSY0pIR0JONUUzYVdfN3d6R21QblJtSUlHM3lvVFNFNzhQbWx6Nm1xOXk1b3EtOWhaWjRNRXdfUW1hTzAxeVNMUXRGOEJmNkxTdkRYeEFrZHdWMm5ra0tfWWNhWDd3RHo0eWxwS0cxWTg3TzIwWWtkUXlxdjMybG1lN1JuVDhwcVBDQTRUWDloOWFVaXh6THNoSmVwaV9kWFFYUFZ4WjFhdXhiV0ZfdXMiLCJkcSI6InFaeFVVY2xhWVhLZ3N5Q3dhczRZUDFwTDAzeHpUNHk5ME5hdWNOVEhYZ0s0X2NidlRxbGxlWjQtTUswcXBkZnAzOXIyLWRXZXplbzVMeGMwVFd2eUwzMVZGYU9QMmJOQklKam9VbE9ldFkwMi1vWVM1NjJZWVdVQVNOandXNnFXY21NV2RlZjFIM3VuUDVqTVVxdlhRTTAxNjVnV2ZiN09YRjJyU0thc3JIVSIsInFpIjoiVzNhUDJmdTB1N1JVWWVubzIyNkljRjNBaEZzY3NWam55NmNGdWNGanNIbUg2V1BNYTU0dS1MWFRaYllSTFBCOFVvdHA3UjhEU0NoeEhtMjhjaDdqNTRnZkdWNmFlQVVPZXRiUEhCbGJudnQ1M2hFdERTLTlYVF8xYWtJNngwWk1Mc3F3eEZuZ2NSMF93S0V5Zzh2c1BlT2gwdlZsamkyNkpyZnNNZU9fakxvIn0=",
"created": "2021-06-15T21:06:54.763937286Z"
},
"success": true,
"errors": [],
"messages": []
}これらの値は再表示されないため、すぐに安全な場所へ保存してください。Cloudflare Workers を使っている場合は Secrets に保存できます。別のプラットフォームを使っている場合は、安全な環境変数に保存します。
これらの値は、あとでトークンの生成に使います。pem と jwk のフィールドは base64 エンコードされているため、使う前にデコードする必要があります(手順 2 に例があります)。
手順 1 で鍵を生成したら、pem または jwk の値を使って、自分で自己署名 URL を生成できます。この方法では、新しいトークンを作るたびに Stream API を呼ぶ必要はありません。
次の例は、60 分で期限切れになり、英国からアクセスするユーザーだけに有効なトークンを生成する Cloudflare Worker スクリプトです。2 行目と 3 行目で、手順 1 の id と jwk の値を設定します。
// Global variables
const jwkKey = "{PRIVATE-KEY-IN-JWK-FORMAT}";
const keyID = "<KEY_ID>";
const videoUID = "<VIDEO_UID>";
// expiresTimeInS is the expired time in second of the video
const expiresTimeInS = 3600;
// Main function
async function streamSignedUrl() {
const encoder = new TextEncoder();
const expiresIn = Math.floor(Date.now() / 1000) + expiresTimeInS;
const headers = {
alg: "RS256",
kid: keyID,
};
const data = {
sub: videoUID,
kid: keyID,
exp: expiresIn,
// Add `downloadable` boolean for access to MP4 or Audio Downloads:
// downloadable: true,
accessRules: [
{
type: "ip.geoip.country",
action: "allow",
country: ["GB"],
},
{
type: "any",
action: "block",
},
],
};
const token = `${objectToBase64url(headers)}.${objectToBase64url(data)}`;
const jwk = JSON.parse(atob(jwkKey));
const key = await crypto.subtle.importKey(
"jwk",
jwk,
{
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
},
false,
["sign"],
);
const signature = await crypto.subtle.sign(
{ name: "RSASSA-PKCS1-v1_5" },
key,
encoder.encode(token),
);
const signedToken = `${token}.${arrayBufferToBase64Url(signature)}`;
return signedToken;
}
// Utilities functions
function arrayBufferToBase64Url(buffer) {
return btoa(String.fromCharCode(...new Uint8Array(buffer)))
.replace(/=/g, "")
.replace(/\+/g, "-")
.replace(/\//g, "_");
}
function objectToBase64url(payload) {
return arrayBufferToBase64Url(
new TextEncoder().encode(JSON.stringify(payload)),
);
}Stream Player を使う場合は、手順 2 の Worker が返した token の値を video id の代わりに入れます。cloudflarestream.com/ と /iframe のあいだの文字列全体を置き換えます。
<iframe
src="https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/iframe"
style="border: none;"
height="720"
width="1280"
allow="accelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture;"
allowfullscreen="true"
></iframe>独自のプレーヤーを使う場合は、マニフェスト URL の動画 ID を token の値に置き換えます。
https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJSUzI1NiIsImtpZCI6ImNkYzkzNTk4MmY4MDc1ZjJlZjk2MTA2ZDg1ZmNkODM4In0.eyJraWQiOiJjZGM5MzU5ODJmODA3NWYyZWY5NjEwNmQ4NWZjZDgzOCIsImV4cCI6IjE2MjE4ODk2NTciLCJuYmYiOiIxNjIxODgyNDU3In0.iHGMvwOh2-SuqUG7kp2GeLXyKvMavP-I2rYCni9odNwms7imW429bM2tKs3G9INms8gSc7fzm8hNEYWOhGHWRBaaCs3U9H4DRWaFOvn0sJWLBitGuF_YaZM5O6fqJPTAwhgFKdikyk9zVzHrIJ0PfBL0NsTgwDxLkJjEAEULQJpiQU1DNm0w5ctasdbw77YtDwdZ01g924Dm6jIsWolW0Ic0AevCLyVdg501Ki9hSF7kYST0egcll47jmoMMni7ujQCJI1XEAOas32DdjnMvU8vXrYbaHk1m1oXlm319rDYghOHed9kr293KM7ivtZNlhYceSzOpyAmqNFS7mearyQ/manifest/video.m3u8
MP4 または音声のダウンロード を許可するには、動画でダウンロードの種類がすでに有効になっていることを確認します。署名付き URL を生成するとき、上のコメントのとおりペイロードに downloadable: true を追加します。ダウンロード URL の動画 ID を token の値に置き換えます。
https://customer-<CODE>.cloudflarestream.com/eyJhbGciOiJ.../downloads/default.mp4
鍵は最大 1,000 個まで作成でき、都合に合わせてローテーションできます。 失効すると、その鍵で作成したトークンはすべて無効になります。
curl --request DELETE \
"https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/keys/{key_id}" \
--header "Authorization: Bearer <API_TOKEN>"
# Response:
{
"result": "Revoked",
"success": true,
"errors": [],
"messages": []
}| プロパティ名 | 説明 | |
|---|---|---|
| exp | 有効期限。この UNIX epoch タイムスタンプ以降、トークンは使えなくなります。トークン署名時点から 24 時間を超える未来にはできません | |
| nbf | Not Before 値。この UNIX epoch タイムスタンプより前は、トークンは使えません | |
| downloadable | true の場合、トークンで mp4 をダウンロードできます(動画でダウンロードが有効である必要があります) | |
| accessRules | IP および地域の制限を 1 つ以上指定する配列です。accessRules は先頭から順に評価されます。ルールに一致すると、対応する action が適用され、以降のルールは評価されません。トークンの accessRules 配列は最大 5 要素です。 |
各 accessRule には、必須プロパティが 2 つあります。
type: サポートされる値はany、ip.src、ip.geoip.countryですaction: サポートされる値はallowとblockです
ルールの種類に応じて、accessRules は次の追加プロパティを 2 つサポートします。
country: ISO 3166-1 Alpha 2 ↗ 形式の 2 文字の国コードの配列です。ip: IP レンジの配列です。可能なら、1 つのルールに IPv4 と IPv6 の両方を含めることを推奨します。一方のバリアントだけだと、もう一方は無視されます。たとえば IPv4 だけのルールは、IPv6 アドレスから接続する視聴者には適用されません。特定の IP アドレスより CIDR を優先してください。モバイルなど一部のデバイスは、視聴中に IP が変わることがあります。動画のアクセス制御は、視聴中も継続して評価されます。そのため、厳しすぎる IP ルールは再生を妨げることがあります。
例 1: 特定の国からの視聴をブロックする
...
"accessRules": [
{
"type": "ip.geoip.country",
"action": "block",
"country": ["US", "DE", "MX"],
},
]最初のルールは国で一致します。ここでは US、DE、MX です。一致すると block アクションにより、トークンは無効とみなされます。最初のルールに一致しない場合、評価する後続ルールはありません。この状況では、トークンは有効とみなされます。
例 2: 特定の国または IP からの視聴だけを許可する
...
"accessRules": [
{
"type": "ip.geoip.country",
"country": ["US", "MX"],
"action": "allow",
},
{
"type": "ip.src",
"ip": ["93.184.216.0/24", "2400:cb00::/32"],
"action": "allow",
},
{
"type": "any",
"action": "block",
},
]最初のルールは国で一致します。ここでは US と MX です。一致すると allow アクションにより、トークンは有効とみなされます。一致しない場合は、ルールの評価を続けます。
2 番目のルールは IP ルールで、CIDR の 93.184.216.0/24 と 2400:cb00::/32 に一致します。一致すると allow アクションにより、ルールは有効とみなされます。
最初の 2 つのルールに一致しない場合、最後の any ルールが残りのリクエストすべてに一致し、それらの視聴をブロックします。
既定では、Stream の埋め込みコードは任意のドメインで使えます。必要なら、Stream ダッシュボードから動画を埋め込めるドメインを制限できます。
ダッシュボードでは、各動画の横に Enter allowed origin domains separated by commas とラベルの付いたテキストボックスがあります。クリックすると、Stream 埋め込みコードを使えるドメインを列挙できます。
*.badtortilla.comはa.badtortilla.com、a.b.badtortilla.comを対象にし、badtortilla.comは対象にしませんexample.comは www.example.com ↗ や example.com のサブドメインを対象にしませんlocalhostは、HTTP のポート 80 または HTTPS のポート 443 で配信されていない場合、ポートの指定が必要です- パスの指定はできません。
example.comはexample.com/\*を対象にします
Stream API で、埋め込み制限をプログラムから制御することもできます。次の例の uid は動画 ID です。
curl https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/{video_uid} \
--header "Authorization: Bearer <API_TOKEN>" \
--data "{\"uid\": \"<VIDEO_UID>\", \"allowedOrigins\": [\"example.com\"]}"Stream バインディングで許可オリジンを設定することもできます。
export default {
async fetch(request, env) {
const video = await env.STREAM.video("VIDEO_ID").update({
allowedOrigins: ["example.com"],
});
return Response.json(video);
},
};export default {
async fetch(request, env) {
const video = await env.STREAM.video("VIDEO_ID").update({
allowedOrigins: ["example.com"],
});
return Response.json(video);
},
};Allowed Origins 機能では、再生を許可するオリジンを指定できます。独自の動画プレーヤーを使っている場合でも動作します。独自プレーヤーを使うときは、Allowed Origins により、HLS/DASH マニフェストと動画セグメントをリクエストできるドメインが制限されます。
署名付き URL と埋め込み制限を組み合わせると、動画の視聴方法を強く制御できます。信頼できるユーザーだけに配信しつつ、署名付き URL が未知のサイトに置かれるのを防げます。