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/stream/patterns.md
1# Stream Patterns23Common workflows, full-stack flows, and best practices.45## React Stream Player67`npm install @cloudflare/stream-react`89```tsx10import { Stream } from '@cloudflare/stream-react';1112export function VideoPlayer({ videoId, token }: { videoId: string; token?: string }) {13return <Stream controls src={token ? `${videoId}?token=${token}` : videoId} responsive />;14}15```1617## Full-Stack Upload Flow1819**Backend API (Workers/Pages)**20```typescript21import Cloudflare from 'cloudflare';2223export default {24async fetch(request: Request, env: Env): Promise<Response> {25const { videoName } = await request.json();26const client = new Cloudflare({ apiToken: env.CF_API_TOKEN });27const { uploadURL, uid } = await client.stream.directUpload.create({28account_id: env.CF_ACCOUNT_ID,29maxDurationSeconds: 3600,30requireSignedURLs: true,31meta: { name: videoName }32});33return Response.json({ uploadURL, uid });34}35};36```3738**Frontend component**39```tsx40import { useState } from 'react';4142export function VideoUploader() {43const [uploading, setUploading] = useState(false);44const [progress, setProgress] = useState(0);4546async function handleUpload(file: File) {47setUploading(true);48const { uploadURL, uid } = await fetch('/api/upload-url', {49method: 'POST',50body: JSON.stringify({ videoName: file.name })51}).then(r => r.json());5253const xhr = new XMLHttpRequest();54xhr.upload.onprogress = (e) => setProgress((e.loaded / e.total) * 100);55xhr.onload = () => { setUploading(false); window.location.href = `/videos/${uid}`; };56xhr.open('POST', uploadURL);57const formData = new FormData();58formData.append('file', file);59xhr.send(formData);60}6162return (63<div>64<input type="file" accept="video/*" onChange={(e) => e.target.files?.[0] && handleUpload(e.target.files[0])} disabled={uploading} />65{uploading && <progress value={progress} max={100} />}66</div>67);68}69```7071## TUS Resumable Upload7273For large files (>500MB). `npm install tus-js-client`7475```typescript76import * as tus from 'tus-js-client';7778async function uploadWithTUS(file: File, uploadURL: string, onProgress?: (pct: number) => void) {79return new Promise<string>((resolve, reject) => {80const upload = new tus.Upload(file, {81endpoint: uploadURL,82retryDelays: [0, 3000, 5000, 10000, 20000],83chunkSize: 50 * 1024 * 1024,84metadata: { filename: file.name, filetype: file.type },85onError: reject,86onProgress: (up, total) => onProgress?.((up / total) * 100),87onSuccess: () => resolve(upload.url?.split('/').pop() || '')88});89upload.start();90});91}92```9394## Video State Polling9596```typescript97async function waitForVideoReady(client: Cloudflare, accountId: string, videoId: string) {98for (let i = 0; i < 60; i++) {99const video = await client.stream.videos.get(videoId, { account_id: accountId });100if (video.readyToStream || video.status.state === 'error') return video;101await new Promise(resolve => setTimeout(resolve, 5000));102}103throw new Error('Video processing timeout');104}105```106107## Webhook Handler108109```typescript110export default {111async fetch(request: Request, env: Env): Promise<Response> {112const signature = request.headers.get('Webhook-Signature');113const body = await request.text();114if (!signature || !await verifyWebhook(signature, body, env.WEBHOOK_SECRET)) {115return new Response('Unauthorized', { status: 401 });116}117const payload = JSON.parse(body);118if (payload.readyToStream) console.log(`Video ${payload.uid} ready`);119return new Response('OK');120}121};122123async function verifyWebhook(sig: string, body: string, secret: string): Promise<boolean> {124const parts = Object.fromEntries(sig.split(',').map(p => p.split('=')));125const timestamp = parseInt(parts.time || '0', 10);126if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;127128const key = await crypto.subtle.importKey(129'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']130);131const computed = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(`${timestamp}.${body}`));132const hex = Array.from(new Uint8Array(computed), b => b.toString(16).padStart(2, '0')).join('');133return hex === parts.sig1;134}135```136137## Self-Sign JWT (High Volume Tokens)138139For >1k tokens/day. Prerequisites: Create signing key (see configuration.md).140141```typescript142async function selfSignToken(keyId: string, jwkBase64: string, videoId: string, expiresIn = 3600) {143const key = await crypto.subtle.importKey(144'jwk', JSON.parse(atob(jwkBase64)), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign']145);146const now = Math.floor(Date.now() / 1000);147const header = btoa(JSON.stringify({ alg: 'RS256', kid: keyId })).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');148const payload = btoa(JSON.stringify({ sub: videoId, kid: keyId, exp: now + expiresIn, nbf: now }))149.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');150const message = `${header}.${payload}`;151const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, new TextEncoder().encode(message));152const b64Sig = btoa(String.fromCharCode(...new Uint8Array(sig))).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');153return `${message}.${b64Sig}`;154}155156// With access rules (geo-restriction)157const payloadWithRules = {158sub: videoId, kid: keyId, exp: now + 3600, nbf: now,159accessRules: [{ type: 'ip.geoip.country', action: 'allow', country: ['US'] }]160};161```162163## Best Practices164165- **Use Direct Creator Uploads** - Avoid proxying through servers166- **Enable requireSignedURLs** - Control private content access167- **Self-sign tokens at scale** - Use signing keys for >1k/day168- **Set allowedOrigins** - Prevent hotlinking169- **Use webhooks over polling** - Efficient status updates170- **Set maxDurationSeconds** - Prevent abuse171- **Enable live recordings** - Auto VOD after stream172173## In This Reference174175- [README.md](./README.md) - Overview and quick start176- [configuration.md](./configuration.md) - Setup and config177- [api.md](./api.md) - On-demand video APIs178- [api-live.md](./api-live.md) - Live streaming APIs179- [gotchas.md](./gotchas.md) - Error codes, troubleshooting180181## See Also182183- [workers](../workers/) - Deploy Stream APIs in Workers184- [pages](../pages/) - Integrate Stream with Pages185