Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/realtime-sfu/patterns.md
1# Patterns & Use Cases23## Architecture45```6Client (WebRTC) <---> CF Edge <---> Backend (HTTP)7|8CF Backbone (310+ DCs)9|10Other Edges <---> Other Clients11```1213Anycast: Last-mile <50ms (95%), no region select, NACK shield, distributed consensus1415Cascading trees auto-scale to millions:16```17Publisher -> Edge A -> Edge B -> Sub118\-> Edge C -> Sub2,319```2021## Use Cases2223**1:1:** A creates session+publishes, B creates+subscribes to A+publishes, A subscribes to B24**N:N:** All create session+publish, backend broadcasts track IDs, all subscribe to others25**1:N:** Publisher creates+publishes, viewers each create+subscribe (no fan-out limit)26**Breakout:** Same PeerConnection! Backend closes/adds tracks, no recreation2728## PartyTracks (Recommended)2930Observable-based client with automatic device/network handling:3132```typescript33import {PartyTracks} from 'partytracks';3435// Create client36const pt = new PartyTracks({37apiUrl: '/api/calls',38sessionId: 'my-session',39onTrack: (track, peer) => {40const video = document.getElementById(`video-${peer.id}`) as HTMLVideoElement;41video.srcObject = new MediaStream([track]);42}43});4445// Publish camera (push API)46const camera = await pt.getCamera(); // Auto-requests permissions, handles device changes47await pt.publishTrack(camera, {trackName: 'my-camera'});4849// Subscribe to remote track (pull API)50await pt.subscribeToTrack({trackName: 'remote-camera', sessionId: 'other-session'});5152// React hook example53import {useObservableAsValue} from 'observable-hooks';5455function VideoCall() {56const localTracks = useObservableAsValue(pt.localTracks$);57const remoteTracks = useObservableAsValue(pt.remoteTracks$);5859return <div>{/* Render tracks */}</div>;60}6162// Screenshare63const screen = await pt.getScreenshare();64await pt.publishTrack(screen, {trackName: 'my-screen'});6566// Handle device changes (automatic)67// PartyTracks detects device changes (e.g., Bluetooth headset) and renegotiates68```6970## Backend7172Express:73```js74app.post('/api/new-session', async (req, res) => {75const r = await fetch(`${CALLS_API}/apps/${process.env.CALLS_APP_ID}/sessions/new`,76{method: 'POST', headers: {'Authorization': `Bearer ${process.env.CALLS_APP_SECRET}`}});77res.json(await r.json());78});79```8081Workers: Same pattern, use `env.CALLS_APP_ID` and `env.CALLS_APP_SECRET`8283DO Presence: See configuration.md for boilerplate8485## Audio Level Detection8687```typescript88// Attach analyzer to audio track89function attachAudioLevelDetector(track: MediaStreamTrack) {90const ctx = new AudioContext();91const analyzer = ctx.createAnalyser();92const src = ctx.createMediaStreamSource(new MediaStream([track]));93src.connect(analyzer);9495const data = new Uint8Array(analyzer.frequencyBinCount);96const checkLevel = () => {97analyzer.getByteFrequencyData(data);98const level = data.reduce((a, b) => a + b) / data.length;99if (level > 30) console.log('Speaking:', level); // Trigger UI update100requestAnimationFrame(checkLevel);101};102checkLevel();103}104```105106## Connection Quality Monitoring107108```typescript109pc.getStats().then(stats => {110stats.forEach(report => {111if (report.type === 'inbound-rtp' && report.kind === 'video') {112const {packetsLost, packetsReceived, jitter} = report;113const lossRate = packetsLost / (packetsLost + packetsReceived);114if (lossRate > 0.05) console.warn('High packet loss:', lossRate);115if (jitter > 100) console.warn('High jitter:', jitter);116}117});118});119```120121## Stage Management (Limit Visible Participants)122123```typescript124// Subscribe to top 6 active speakers only125let activeSubscriptions = new Set<string>();126127function updateStage(topSpeakers: string[]) {128const toAdd = topSpeakers.filter(id => !activeSubscriptions.has(id)).slice(0, 6);129const toRemove = [...activeSubscriptions].filter(id => !topSpeakers.includes(id));130131toRemove.forEach(id => {132pc.getSenders().find(s => s.track?.id === id)?.track?.stop();133activeSubscriptions.delete(id);134});135136toAdd.forEach(async id => {137await fetch(`/api/subscribe`, {method: 'POST', body: JSON.stringify({trackId: id})});138activeSubscriptions.add(id);139});140}141```142143## Advanced144145Bandwidth mgmt:146```ts147const s = pc.getSenders().find(s => s.track?.kind === 'video');148const p = s.getParameters();149if (!p.encodings) p.encodings = [{}];150p.encodings[0].maxBitrate = 1200000; p.encodings[0].maxFramerate = 24;151await s.setParameters(p);152```153154Simulcast (CF auto-forwards best layer):155```ts156pc.addTransceiver('video', {direction: 'sendonly', sendEncodings: [157{rid: 'high', maxBitrate: 1200000},158{rid: 'med', maxBitrate: 600000, scaleResolutionDownBy: 2},159{rid: 'low', maxBitrate: 200000, scaleResolutionDownBy: 4}160]});161```162163DataChannel:164```ts165const dc = pc.createDataChannel('chat', {ordered: true, maxRetransmits: 3});166dc.onopen = () => dc.send(JSON.stringify({type: 'chat', text: 'Hi'}));167dc.onmessage = (e) => console.log('RX:', JSON.parse(e.data));168```169170**WHIP/WHEP:** For streaming interop (OBS → SFU, SFU → video players), use WHIP (ingest) and WHEP (egress) protocols. See Cloudflare Stream integration docs.171172Integrations: R2 for recording `env.R2_BUCKET.put(...)`, Queues for analytics173174Perf: 100-250ms connect, ~50ms latency (95%), 200-400ms glass-to-glass, no participant limit (client: 10-50 tracks)175