Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Cloudflare Realtime と Workers でつくるサーバーレス WebRTC

Cloudflare Realtime と Workers でつくるサーバーレス WebRTC

Avatar for Taiyu Yoshizawa

Taiyu Yoshizawa

June 03, 2025
Tweet

More Decks by Taiyu Yoshizawa

Other Decks in Programming

Transcript

  1. Who are you? g ɴᴇᴋᴏʏᴀsᴀ g Software Engineer @MIERUNE Inc1

    g Co-founder & Software Engineer @ ReMotivI g GitHub: @NEKOYASA2 g Web: nekoyasan.me
  2. Cloudflare Realtime とは „ Cloudflare が提供する WebRTC に関連するサービスの総Q „ 以前は

    Cloudflare Calls という名称でサービス提供がされて いたが、2025年4月に名称変s „ 名称変更と同タイミングで正式リリース & Realtime Kit の Private Beta が開始
  3. Cloudflare Realtime とは „ Cloudflare が提供する WebRTC に関連するサービスの総Q „ 以前は

    Cloudflare Calls という名称でサービス提供がされて いたが、2025年4月に名称変s „ 名称変更と同タイミングで正式リリース & Realtime Kit の Private Beta が開始
  4. WebRTCとは x Web RealTime Communication → Web RT7 x 双方向リアルタイム通信を実現するためのAPIやプロトコルのこ

    とを指すb x RFC8825 ~ 8839 あたりを中心に定義されているが、関連す る仕様なども合わせると非常に多岐にわたv x 現在ではブラウザに標準機能として基本的には搭載されており、 双方向リアルタイム通信の手法としては比較的利用しやすい
  5. WebRTCとは  Discord や Google Meet 、 Twitter Spaces など、音声通

    話やビデオ通話等に多く利用されているT  最近では WHIP や WHEP という規格によってOBSなどソフ トウェアを使った配信にも活用できるような基盤が整い始めてい †  WebRTCの詳しいことは専門の会社さんが記事などでまとめて くれているのでそちらへ...
  6. STUNとは u Session Traversal Utilities for NATs = STU% u

    NAT環境下に自身が居る場合にデバイスはプライベートIPしか わからず、そのままでは自身のグローバルIPがわからない → STUNに問い合わせることで知ることができる
  7. STUNとは クライアント STUN サーバー N A T Binding Request Binding

    Response 私は...どこのだれ...? あなたはこのIP / Port
  8. TURNとは n Traversal Using Relays around NAT = TURb n

    P2P による直接通信が出来ない(STUNだけではP2Pでの接続 が確立できない)or UDPによる接続が確立できない場合に利用 する中継サーバ・プロトコルのこi n サーバを経由するがただのバイパスしか役目がなく、バイパスし ているデータを読み取ることも、変化させることもなf n サーバを経由するため、P2Pに比べて遅延は大きくなってしまう
  9. SFUとは m Selective Forwarding Unit = SFv m P2P による通信ではメッシュ接続をする必要があるため参加者

    が増えるにつれて、1人が接続する接続数が増大する問題があf m SFUサーバとの接続のみを行い、SFUサーバーが必要に応じて 他のクライアントに転送すf m 転送しか出来ず加工をすることは出来な' m 対照的に合成・再エンコードを行うMCUも存在する
  10. Cloudflare Realtime では F Realtime KiD F TURN Serve' F

    Serverless SF3 F (STUN Server)
 で提供はされている stun.cloudflare.com:3478
  11. Cloudflare Realtime では F Realtime KiD F TURN Serve' F

    Serverless SF3 F (STUN Server)
 で提供はされている stun.cloudflare.com:3478
  12. Cloudflare Realtime では F Realtime KiD F TURN Serve' F

    Serverless SF3 F (STUN Server)
 で提供はされている stun.cloudflare.com:3478
  13. TURN Server f TURN サーバーの Cloudflare フルマネージドなもb f TURN Key

    IDと Token から TURN Username と Credentialを作り、それらとurlsをiceServersで設定すQ f GraphQL の Analytics API が用意されていて、どれだけ利用 されているか、不正利用の検知が出来Q f EdgeからのEgress のみが課金対u f 0.05 USD / GB で 初回1000GBまでは無’ f カスタムドメインを割り当てることも出来る
  14. Serverless SFU l SFU の Cloudflare フルマネージドなもs l PeerConnection で

    Session を作り、MediaStreamTrack (Audio / Video) をトラックとして持y l ルームという概念はなく、トラックそれぞれに一意のIDが付いて いて、それをグローバルなどこからでも取得することができ# l 取得していなければ課金もされないので、メディアストリームを 動的につなぎ替えることができるスイッチャーのような役目を 担っているとも言える
  15. Realtime Kit j まだPrivate Betaで全容がよくわからないものの.. j クライアントのSDK・UIが用意されていたV j RecordingやAnalytics、Workers AI

    との連) j Realtime AIと称し て Transcription や Agent、Voice AI、 ノイズキャンセルなどの連携が用意されているよ j ベース技術とし てはServerless SFU / TURN Serverなのは変 化がなく、ユーザー認証やルーム機能などが含まれていて、即使 える!みたいなもの?
  16. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 const new const await const return peerConnection RTCPeerConnection({ iceServers [ { urls , }, ], bundlePolicy , }); localStream navigator.mediaDevices. ({ video , audio , }); transceivers localStream. (). ((track) { peerConnection. (track, { direction , }); }); = : : : = : : = => : "stun:stun.cloudflare.com:3478" "max-bundle" "sendonly" getUserMedia getTracks map addTransceiver true true
  17. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 19 20 await await const await const await peerConnection. ( peerConnection. ()); response ( , { method , headers { , Authorization ${appSecret} , }, body . ({ sessionDescription { type , sdp peerConnection.localDescription.sdp, }, }), }, ); newSessionResult response. (); setLocalDescription createOffer fetch stringify json = : : : : : : : : = "https://rtc.live.cloudflare.com/v1/apps/${appId}/sessions/new" "POST" "application/json" `Bearer ` "offer" "Content-Type" JSON
  18. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 trackObjects transceivers. ((transceiver) { { location , mid transceiver.mid , trackName transceiver.sender.track.id, }; }); peerConnection. ( peerConnection. ()); newLocalTrackResult app. ( trackObjects, peerConnection.localDescription.sdp, ); peerConnection. ( RTCSessionDescription(newLocalTrackResult.sessionDescription), ); = => : : ?? : = map setLocalDescription createOffer newTracks setRemoteDescription return await await const await await new "local" "1" // POST /session/sessionid/tracks/new
  19. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 if switch case await new await await await break case throw new (newRemoteTracksResult.sessionDescription) { (newRemoteTracksResult.sessionDescription.type) { . ( ${sessionId} ); peerConnection. ( RTCSessionDescription(newRemoteTracksResult.sessionDescription), ); peerConnection. ( peerConnection. (), ); app. (peerConnection.localDescription.sdp); ; Error( ); } } "offer" `Renegotiating for session ` "answer" "An offer SDP was expected" : console : log setRemoteDescription setLocalDescription createAnswer sendAnswerSDP
  20. 1 2 3 4 5 6 7 8 9 10

    11 12 13 14 15 16 17 18 const new let if const await const new return remoteTrackPromise ((resolve) { tracks []; peerConnection. (event) { tracks. (event.track); . ( ${event.track.id} ${event.track.mid} ); (tracks.length ) { (tracks); } }; }); remoteTracks remoteTrackPromise; remoteStream MediaStream(); remoteTracks. ((track) { remoteStream. (track); }); remoteStream; = Promise => = = => console >= = = => ontrack push debug resolve forEach addTrack `Got track mid= ` 2