Cloudflare StreamのライブストリーミングをAPIでやってみた

Cloudflare StreamではGUIを使わずAPI経由でライブストリーミングが可能です。APIトークンの作成からはじまり、ライブストリーミングに必要なリソースの作成、映像打ち上げと視聴に必要な情報取得までをAPIを使ってやってみました。
2022.03.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。Cloudflareの動画配信プラットフォームのサービスCloudflare Streamを試しています。以前のエントリではCloudflareのアカウントの作成から、ダッシュボードを使ってGUIでポチポチしながらライブストリーミングを行ってみました。

CloudflareはAPI経由での操作も可能です。本エントリではAPIを使ってライブストリーミングを行う手順を確認してみたのでまとめてみます。

APIトークンの取得

まずはCloudflareをAPI操作するために必要なAPIトークンの取得からはじめます。(CloudflareのアカウントやCloudflare Streamのサブスクリプションは完了している前提となります。これらの詳細はこちらのエントリを参照ください。)

APIトークンの取得は以下ドキュメントを参考に行いました。

なおAPIトークンと同様に認証スキームの1つとして、APIキーというものも存在しているようです。

上記ドキュメントにあるように現在はlegacy扱い、可能な限りAPIトークンを利用するよう記載があります。またAPIトークンと比べてAPIキーにはアクセス制御などいくつかの制約があるようです。

ということで、APIトークンを作成していきます。Cloudflareのダッシュボード、右上の「マイプロフィール」からAPIトークンの画面に進み、[トークンを作成する]ボタンを押下します。

APIトークンの作成画面に進みます。APIトークンテンプレートという、事前構成された権限を選択することもできるのですが、Cloudfare Stream用のものが見つからなかったため、カスタムトークンで作成することとしました。「カスタムトークンを作成する」の[始める]ボタンで進みます。

カスタムトークン作成画面に遷移します。トーク名を入力し、アクセス許可を設定します。今回アクセス許可については、Cloudfare Streamサービスの編集権限のみとしました。アカウントStream編集をそれぞれ選択します。その他、アカウントリソースやクライアントIPアドレス、トークンのTTLが設定可能なようです。今回はいったんデフォルトのままとしています。

[概要に進む]ボタンで遷移先のページで作成するトークンの概要を確認し、[トークンを作成する]ボタンで作成します。

トークンが作成できました。

記載されているcurlコマンドを実行して動作を確認してみます。

% curl -X GET "https://api.cloudflare.com/client/v4/user/tokens/verify" \
     -H "Authorization: Bearer XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
     -H "Content-Type:application/json"
{"result":{"id":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","status":"active"},"success":true,"errors":[],"messages":[{"code":10000,"message":"This API Token is valid and active","type":null}]}

返ってきたjsonを整形すると以下のようになりました。APIトークンの使用は問題なさそうですね。

{
  "result": {
    "id": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "status": "active"
  },
  "success": true,
  "errors": [],
  "messages": [
    {
      "code": 10000,
      "message": "This API Token is valid and active",
      "type": null
    }
  ]
}

なお、実行したcurlコマンドより、認証としてはBearer認証を使用していることになりますね。

Bearer認証のトークンについては、一般的に有効期限を設けておいたほうが良いとされているかと思います。さきほどのトークン作成時にはトークンのTTLはデフォルトのまま、特に設定していませんでしたが実際に作成するときは適切な値を設定するようにしましょう。

ライブストリーミングをAPIでやってみた

APIトークンが作成できたので、続いて本題のCloudflareのライブストリーミングをAPIを使ってやってみたいと思います。以下ドキュメントの「Using the API」を参考に進めていきます。

API実行には、さきほどのトークン作成時に確認したようにcurlコマンドを用いて行います。またAPIトークン、ならびにアカウントIDをcurlコマンドを実行するシェルの変数に設定しておきます。

% TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
% ACCOUNT=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

アカウントIDはダッシュボードの以下の箇所で確認ができます。

Live InputをAPIで作成

ライブストリーミングを行う手順はダッシュボードから行った場合と変わりありません。まずLive Input(ライブ入力)を作成し、接続情報を確認してStreaming Softwareで映像を打上げるぐあいです。

まずはLive Inputの作成です。以下のコマンドを実行します。POSTリクエスト実行時に--dataで指定する送信データはドキュメントを参考に、実行例からnameを適切に設定、またallowedOrigins要素を省いたものにしています。

curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs" \
    --data '{"meta": {"name":"api-live-input"},"recording": { "mode": "automatic", "timeoutSeconds": 10, "requireSignedURLs": false }}'

送信データのJSONを整形すると以下となります。

{
  "meta": {
    "name": "api-live-input"
  },
  "recording": {
    "mode": "automatic",
    "timeoutSeconds": 10,
    "requireSignedURLs": false
  }
}

以下が実行結果です。

% curl -X POST \
     -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs" \
    --data '{"meta": {"name":"api-live-input"},"recording": { "mode": "automatic", "timeoutSeconds": 10, "requireSignedURLs": false }}'

{
  "result": {
    "uid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "rtmps": {
      "url": "rtmps://live.cloudflare.com:443/live/",
      "streamKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "srt": {
      "url": "srt://live.cloudflare.com:778",
      "streamId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "passphrase": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "created": "2022-03-31T12:05:53.674649Z",
    "modified": "2022-03-31T12:05:53.674649Z",
    "meta": {
      "name": "api-live-input"
    },
    "status": null,
    "recording": {
      "mode": "automatic",
      "timeoutSeconds": 10,
      "requireSignedURLs": false,
      "allowedOrigins": null
    }
  },
  "success": true,
  "errors": [],
  "messages": []
}

ダッシュボードでも確認してみましょう。Live Inputが作成されていますね!

Streaming Softwareからの接続先の確認

続いてStreaming SoftwareからRTMPSでの接続先となるRTMPS URLとRTMPSキーですが、さきほどのLive Input作成時のレスポンスに含まれていますね。ここから確認することもできますが、このLive Input作成時の情報がない状態からの確認方法も抑えておきましょう。

さきほどの/live_inputsエンドポイントへのリクエストをGETで行います。以下のように作成済みのLive Inputの概要情報が返ります。

% curl -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs"

{
  "result": [
    {
      "uid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "created": "2022-03-31T12:05:53.674649Z",
      "modified": "2022-03-31T12:05:53.674649Z",
      "meta": {
        "name": "api-live-input"
      }
    },
    {
      "uid": "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY",
      "created": "2022-03-31T10:16:15.140567Z",
      "modified": "2022-03-31T10:16:15.140567Z",
      "meta": {
        "name": "test-stream-1"
      }
    },
    {
      "uid": "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ",
      "created": "2022-03-23T02:12:28.624844Z",
      "modified": "2022-03-23T02:12:28.624844Z",
      "meta": {
        "name": "cloudflare-stream-live-input"
      }
    }
  ],
  "success": true,
  "errors": [],
  "messages": []
}

ここで重要なのがそれぞれのLive Inputに対応したuidです。これをシェル変数LIVE_INPUT_UIDに格納しておきます。

% LIVE_INPUT_UID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

/live_inputs/${LIVE_INPUT_UID}にGETリクエストを行うと、Live Input作成時と同じ情報が返ってきます。

% curl -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs/${LIVE_INPUT_UID}"
{
  "result": {
    "uid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "rtmps": {
      "url": "rtmps://live.cloudflare.com:443/live/",
      "streamKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "srt": {
      "url": "srt://live.cloudflare.com:778",
      "streamId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
      "passphrase": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    "created": "2022-03-31T12:05:53.674649Z",
    "modified": "2022-03-31T12:05:53.674649Z",
    "meta": {
      "name": "api-live-input"
    },
    "status": null,
    "recording": {
      "mode": "automatic",
      "timeoutSeconds": 10,
      "requireSignedURLs": false,
      "allowedOrigins": null
    }
  },
  "success": true,
  "errors": [],
  "messages": []
}

jq .result.rtmpsなどとするとそこだけの表示となりますね。

% curl -s -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs/${LIVE_INPUT_UID}" \
| jq .result.rtmps

{
  "url": "rtmps://live.cloudflare.com:443/live/",
  "streamKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
}

このRTMPS URLとRTMPSキーをStreaming Softwareに設定し、映像を打ち上げます。Streaming Softwareにはダッシュボードでライブストリーミングしたときと同様、iPhone XS上のZixi ONAIRを使用します。プロトコルはRTMP、その他設定は以前と同様で行いました。

ライブストリーミング視聴URLの確認

ライブストリーミング視聴用のURLの確認についても、APIで行ってみます。

live_inputs/$LIVE_INPUT_UID/videosにGETリクエストを行います。ライブストリーミング開始前は以下のように、特に情報がありません。

% curl -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT/stream/live_inputs/${LIVE_INPUT_UID}/videos"

{
  "result": [],
  "success": true,
  "errors": [],
  "messages": []
}

映像を打ち上げてライブストリーミングを開始すると、以下のようにライブビデオが追加されていきます。

% curl -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs/${LIVE_INPUT_UID}/videos"
{
  "result": [
    {
      "uid": "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
      "thumbnail": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/thumbnails/thumbnail.jpg",
      "thumbnailTimestampPct": 0,
      "readyToStream": false,
      "status": {
        "state": "live-inprogress",
        "errorReasonCode": "",
        "errorReasonText": ""
      },
      "meta": {
        "name": "api-live-input 31 Mar 22 12:53 UTC"
      },
      "created": "2022-03-31T12:53:09.256953Z",
      "modified": "2022-03-31T12:53:09.256953Z",
      "size": 0,
      "preview": "https://watch.videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
      "allowedOrigins": [],
      "requireSignedURLs": false,
      "uploaded": "2022-03-31T12:53:09.256935Z",
      "uploadExpiry": null,
      "maxSizeBytes": null,
      "maxDurationSeconds": null,
      "duration": -1,
      "input": {
        "width": -1,
        "height": -1
      },
      "playback": {
        "hls": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/manifest/video.m3u8",
        "dash": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/manifest/video.mpd"
      },
      "watermark": null,
      "liveInput": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  ],
  "success": true,
  "errors": [],
  "messages": []
}

result内のuidの部分がビデオIDとなります。previewにあるように、https://watch.videodelivery.net/のあとにこのビデオIDを指定したり、埋め込み用のhttps://iframe.videodelivery.net/のあとにビデオIDを指定することでライブストリーミングの視聴が可能となります。

  • https://watch.videodelivery.net/$VIDEOID
  • https://iframe.videodelivery.net/$VIDEOID

このビデオID部分ですが、ライブストリーミングを中断するなどした場合は別のものになり、またレコーディングの設定などにもりますがresultが複数になる点に注意しましょう。実際にいちどライブストリーミングを中断し、再開した場合の結果が以下となります。

 % curl -X GET \
    -H "Authorization: Bearer ${TOKEN}" \
    "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT}/stream/live_inputs/${LIVE_INPUT_UID}/videos"
{
  "result": [
    {
      "uid": "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW",
      "thumbnail": "https://videodelivery.net/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW/thumbnails/thumbnail.jpg",
      "thumbnailTimestampPct": 0,
      "readyToStream": false,
      "status": {
        "state": "live-inprogress",
        "errorReasonCode": "",
        "errorReasonText": ""
      },
      "meta": {
        "name": "api-live-input 31 Mar 22 13:08 UTC"
      },
      "created": "2022-03-31T13:08:41.620302Z",
      "modified": "2022-03-31T13:08:41.620302Z",
      "size": 0,
      "preview": "https://watch.videodelivery.net/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW",
      "allowedOrigins": [],
      "requireSignedURLs": false,
      "uploaded": "2022-03-31T13:08:41.620282Z",
      "uploadExpiry": null,
      "maxSizeBytes": null,
      "maxDurationSeconds": null,
      "duration": -1,
      "input": {
        "width": -1,
        "height": -1
      },
      "playback": {
        "hls": "https://videodelivery.net/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW/manifest/video.m3u8",
        "dash": "https://videodelivery.net/WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW/manifest/video.mpd"
      },
      "watermark": null,
      "liveInput": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    },
    {
      "uid": "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
      "thumbnail": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/thumbnails/thumbnail.jpg",
      "thumbnailTimestampPct": 0,
      "readyToStream": true,
      "status": {
        "state": "ready",
        "pctComplete": "100.000000",
        "errorReasonCode": "",
        "errorReasonText": ""
      },
      "meta": {
        "name": "api-live-input 31 Mar 22 12:53 UTC"
      },
      "created": "2022-03-31T12:53:09.256953Z",
      "modified": "2022-03-31T13:07:36.452998Z",
      "size": 0,
      "preview": "https://watch.videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
      "allowedOrigins": [],
      "requireSignedURLs": false,
      "uploaded": "2022-03-31T12:53:09.256935Z",
      "uploadExpiry": null,
      "maxSizeBytes": null,
      "maxDurationSeconds": null,
      "duration": 855.75,
      "input": {
        "width": 1920,
        "height": 1080
      },
      "playback": {
        "hls": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/manifest/video.m3u8",
        "dash": "https://videodelivery.net/VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV/manifest/video.mpd"
      },
      "watermark": null,
      "liveInput": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
    }
  ],
  "success": true,
  "errors": [],
  "messages": []
}

現在ライブストリーミングしているものと、中断前にライブストリーミングしていたものでビデオIDが変わり、PlayerにそれぞれのビデオIDを指定することで視聴可能になります。

このビデオIDについては、別の取得方法もあります。videodelivery.net$LIVE_INPUT_UID/lifecycleにGETリクエストを投げます。この際に認証情報はいらないようでした。

こちらも、ライブストリーミングを行っていない状態では以下のように情報がありません。

% curl -X GET \
    "https://videodelivery.net/${LIVE_INPUT_UID}/lifecycle"

{"isInput":true,"videoUID":null,"live":false}

ライブストリーミング開始後の実行結果が下記になります。

 % curl -X GET \
    "https://videodelivery.net/${LIVE_INPUT_UID}/lifecycle"

{"isInput":true,"videoUID":"VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV","live":true}

いちどライブストリーミングを中断し再度再開したあとは、返るビデオIDは1つのみですがさきほどと異なるものとなりました。実際にライブストリーミングを行っているビデオIDがかえるというわけですね。

% curl -X GET \
    "https://videodelivery.net/${LIVE_INPUT_UID}/lifecycle"

{"isInput":true,"videoUID":"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW","live":true}

ライブストリーミング終了後は以下のように、ビデオIDが含まれないレスポンスとなりました。

 % curl -X GET \
    "https://videodelivery.net/${LIVE_INPUT_UID}/lifecycle"

{"isInput":true,"videoUID":null,"live":false}

まとめ

Cloudflare Streamでライブ動画配信をAPIを使ってやってみました。CloudflareをAPI経由で利用する際に必要なAPIトークンを作成し、このトークンを使ってライブストリーミングに必要なリソースLive Inputを作成しています。またStreaming Softwareから映像の打ち上げ先、ライブストリーミング視聴のURLについても、API経由で情報を取得しました。

Live Inputやビデオが少数の場合はダッシュボードのGUI経由での操作でも問題ないかとは思いますが、複数のLive Inputを同時に作成する、利用するような場合にはAPI経由で操作するとよさそうですよね。なによりプログラマブルに操作できることがうれしいポイントだと思います!