ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Cloudflare Stream 技能指南:基于单一 API 的无服务器点播与直播视频平台实战

Cloudflare Stream 技能指南:基于单一 API 的无服务器点播与直播视频平台实战 Cloudflare Stream 技能指南基于单一 API 的无服务器点播与直播视频平台实战【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skillsCloudflare Stream 是 Cloudflare 提供的一体化视频服务通过单一 API 覆盖视频上传、存储、转码与分发全流程无需自行管理编码集群与源站基础设施。本文基于仓库中 Stream 参考文档 及其配套的 api.md、api-live.md、configuration.md、patterns.md、gotchas.md 五份子文档整理出可直接落地的上传、播放、直播、鉴权与排错方案。读完本文你将掌握直接创作者上传、TUS 断点续传、HLS/DASH 播放、签名 URL、WebRTC 直播与 Webhook 通知等完整实战能力。一、Cloudflare Stream 是什么Cloudflare Stream 是一个跑在 Cloudflare 全球网络上的无服务器直播与点播视频平台核心卖点是一个 API完成视频的上传upload、编码encode、存储store与分发delivery业务方无需维护任何视频基础设施。在 cloudflare-deploy 技能 的决策树中Stream 被归入Media Content板块用于需要视频流媒体与转码能力的场景stream/参考目录即对应此项。核心功能一览点播视频On-demand video上传、编码、存储、分发一条龙直播Live streaming支持 RTMPS/SRT 推流并自动输出 ABR自适应码率流直接创作者上传Direct creator uploads终端用户无需 API Key 即可直传签名 URLSigned URLs基于 Token 的访问控制分析Analytics通过 GraphQL 获取服务端指标Webhooks视频处理完成等事件通知字幕Captions支持上传字幕文件或由 AI 生成字幕水印Watermarks为视频叠加品牌标识下载Downloads允许用户下载 MP4 离线观看。二、核心概念2.1 三种视频上传方式方式说明适用场景API 上传TUS 协议服务端直传支持大文件断点续传服务端可控上传文件大于 500MB 时推荐配合 TUS从 URL 导入Upload from URL从外部公开地址拉取视频转存已有对象存储或其他源站的视频迁移直接创作者上传Direct Creator Uploads后端签发一次性 uploadURL前端直传UGC 场景官方推荐避免流量与密钥经过业务服务器其中直接创作者上传是官方推荐方案后端只负责生成带约束的上传地址文件本体由浏览器/客户端直传 Cloudflare既保护了 API Token 不泄露到前端又省去业务服务器中转带宽详见 patterns.md 中的全栈上传流程。2.2 播放选项Stream Playeriframe 嵌入官方内置播放器开箱即用、自动适配自定义播放器HLS/DASH通过 Manifest 地址对接 Video.js、HLS.js 等第三方播放器缩略图Thumbnails静态缩略图或 GIF 动图预览。2.3 访问控制Public无任何限制requireSignedURLs开启后必须携带签名 Token 才能播放allowedOrigins通过域名白名单限制可嵌入的站点防止盗链Access Rules在 Token 内嵌入地理/IP 级规则如仅允许美国访问。2.4 直播能力支持 OBS、FFmpeg 等工具以RTMPS/SRT推流直播自动录制并转成点播recording支持**同播Simulcast**到 YouTube、Twitch 等第三方平台支持WebRTCWHIP/WHEP浏览器可直接推流与播放。三、快速上手三条命令跑通核心流程3.1 从 URL 上传视频curl -X POST \ https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/copy \ -H Authorization: Bearer TOKEN \ -H Content-Type: application/json \ -d {url: https://example.com/video.mp4}注意TOKEN为 Cloudflare API Token需要具备 Stream 相关权限{account_id}可在 Cloudflare Dashboard 获取。3.2 嵌入播放器iframe srchttps://customer-CODE.cloudflarestream.com/VIDEO_ID/iframe styleborder: none; height720 width1280 allowaccelerometer; gyroscope; autoplay; encrypted-media; picture-in-picture; allowfullscreentrue /iframeCODE是账户专属的 customer code可从 Dashboard 获取VIDEO_ID即上传返回的uid。3.3 创建直播输入curl -X POST \ https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/live_inputs \ -H Authorization: Bearer TOKEN \ -H Content-Type: application/json \ -d {recording: {mode: automatic}}创建成功后返回uid、RTMPS/SRT 推流地址与推流密钥见 api-live.md。四、安装与环境配置4.1 依赖安装# 官方 Cloudflare SDK支持 Node.js、Workers、Pages npm install cloudflare # React 组件库 npm install cloudflare/stream-react # TUS 断点续传大文件上传 npm install tus-js-client4.2 环境变量# 必填 CF_ACCOUNT_IDyour-account-id CF_API_TOKENyour-api-token # 高并发签名 URL 场景自签 JWT 用 STREAM_KEY_IDyour-key-id STREAM_JWKbase64-encoded-jwk # Webhook 验签用 WEBHOOK_SECRETyour-webhook-secret # 账户专属 customer code从 Dashboard 获取 STREAM_CUSTOMER_CODEyour-customer-code4.3 Wrangler 配置{ name: stream-worker, main: src/index.ts, compatibility_date: 2025-01-01, // 新项目建议使用当前日期 vars: { CF_ACCOUNT_ID: your-account-id } // 敏感信息请用 secret 存储不要写进 vars // wrangler secret put CF_API_TOKEN // wrangler secret put STREAM_KEY_ID // wrangler secret put STREAM_JWK // wrangler secret put WEBHOOK_SECRET }五、点播视频 API 实战5.1 直接创作者上传推荐后端生成上传地址SDK 方式import Cloudflare from cloudflare; const client new Cloudflare({ apiToken: env.CF_API_TOKEN }); const uploadData await client.stream.directUpload.create({ account_id: env.CF_ACCOUNT_ID, maxDurationSeconds: 3600, requireSignedURLs: true, meta: { creator: user-123 } }); // 返回{ uploadURL: string, uid: string }前端直传文件async function uploadVideo(file: File, uploadURL: string) { const formData new FormData(); formData.append(file, file); return fetch(uploadURL, { method: POST, body: formData }).then(r r.json()); }关键约束maxDurationSeconds用于防止恶意超长视频占用资源详见 5.2 配置项meta可携带创作者、标题等业务元数据。5.2 上传/直播/水印配置项// 直接上传约束 const uploadConfig { maxDurationSeconds: 3600, // 最长时长秒 expiry: new Date(Date.now() 3600000).toISOString(), // uploadURL 过期时间 requireSignedURLs: true, // 播放是否需要签名 allowedOrigins: [https://yourdomain.com], // 播放域名白名单 meta: { creator: user-123 } }; // 直播输入配置 const liveConfig { recording: { mode: automatic, timeoutSeconds: 30 }, // 自动录制流结束 30s 后自动停止 deleteRecordingAfterDays: 30 // 录制文件保留天数 }; // 水印 const watermark { name: Logo, opacity: 0.7, padding: 20, position: lowerRight, scale: 0.15 };5.3 从 URL 导入const video await client.stream.copy.create({ account_id: env.CF_ACCOUNT_ID, url: https://example.com/video.mp4, meta: { name: My Video }, requireSignedURLs: false });5.4 播放相关 APIHLS/DASH Manifest 地址// HLS const hlsUrl https://customer-CODE.cloudflarestream.com/${videoId}/manifest/video.m3u8; // DASH const dashUrl https://customer-CODE.cloudflarestream.com/${videoId}/manifest/video.mpd;缩略图// 指定时间点秒 const thumb https://customer-CODE.cloudflarestream.com/${videoId}/thumbnails/thumbnail.jpg?time10s; // 按百分比 const thumbPct https://customer-CODE.cloudflarestream.com/${videoId}/thumbnails/thumbnail.jpg?time50%; // 动图 const gif https://customer-CODE.cloudflarestream.com/${videoId}/thumbnails/thumbnail.gif;5.5 签名 URLToken 访问控制低并发1k/天调用 API 签发async function getSignedToken(accountId: string, videoId: string, apiToken: string) { const response await fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/${videoId}/token, { method: POST, headers: { Authorization: Bearer ${apiToken}, Content-Type: application/json }, body: JSON.stringify({ exp: Math.floor(Date.now() / 1000) 3600, accessRules: [{ type: ip.geoip.country, action: allow, country: [US] }] }) } ); return (await response.json()).result.token; }高并发1k/天用 RS256 自签 JWT前置条件是先创建签名密钥见 configuration.md 的 Signing Keys 一节具体实现见 patterns.md 的 Self-Sign JWT 小节。5.6 字幕、AI 字幕与视频剪辑上传字幕文件PUT 到/{videoId}/captions/{language}async function uploadCaption( accountId: string, videoId: string, apiToken: string, language: string, captionFile: File ) { const formData new FormData(); formData.append(file, captionFile); return fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/${videoId}/captions/${language}, { method: PUT, headers: { Authorization: Bearer ${apiToken} }, body: formData } ).then(r r.json()); }AI 生成字幕依赖 Workers AI 集成仓库的 workers-ai 参考 有对应说明async function generateAICaptions(accountId: string, videoId: string, apiToken: string) { return fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/${videoId}/captions/generate, { method: POST, headers: { Authorization: Bearer ${apiToken}, Content-Type: application/json }, body: JSON.stringify({ language: en }) } ).then(r r.json()); }视频剪辑Clip——从已有视频截取片段生成新视频async function clipVideo( accountId: string, videoId: string, apiToken: string, startTime: number, endTime: number ) { return fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/clip, { method: POST, headers: { Authorization: Bearer ${apiToken}, Content-Type: application/json }, body: JSON.stringify({ clippedFromVideoUID: videoId, startTimeSeconds: startTime, endTimeSeconds: endTime }) } ).then(r r.json()); }5.7 视频管理列表/详情/更新/删除// 列表支持关键字搜索 const videos await client.stream.videos.list({ account_id: env.CF_ACCOUNT_ID, search: keyword // 可选 }); // 详情 const video await client.stream.videos.get(videoId, { account_id: env.CF_ACCOUNT_ID }); // 更新meta、requireSignedURLs 等 await client.stream.videos.update(videoId, { account_id: env.CF_ACCOUNT_ID, meta: { title: New Title }, requireSignedURLs: true }); // 删除 await client.stream.videos.delete(videoId, { account_id: env.CF_ACCOUNT_ID });六、直播 API 实战6.1 创建直播输入SDK 与原始 API 两种方式import Cloudflare from cloudflare; const client new Cloudflare({ apiToken: env.CF_API_TOKEN }); const liveInput await client.stream.liveInputs.create({ account_id: env.CF_ACCOUNT_ID, recording: { mode: automatic, timeoutSeconds: 30 }, deleteRecordingAfterDays: 30 }); // 返回{ uid, rtmps, srt, webRTC }原始 fetch 版本返回结构更直观async function createLiveInput(accountId: string, apiToken: string) { const response await fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/live_inputs, { method: POST, headers: { Authorization: Bearer ${apiToken}, Content-Type: application/json }, body: JSON.stringify({ recording: { mode: automatic, timeoutSeconds: 30 }, deleteRecordingAfterDays: 30 }) } ); const { result } await response.json(); return { uid: result.uid, rtmps: { url: result.rtmps.url, streamKey: result.rtmps.streamKey }, srt: { url: result.srt.url, streamId: result.srt.streamId, passphrase: result.srt.passphrase }, webRTC: result.webRTC }; }6.2 直播状态检查async function getLiveStatus(accountId: string, liveInputId: string, apiToken: string) { const response await fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/live_inputs/${liveInputId}, { headers: { Authorization: Bearer ${apiToken} } } ); const { result } await response.json(); return { isLive: result.status?.current?.state connected, recording: result.recording, status: result.status }; }6.3 同播Simulcast到 YouTube / Twitchasync function createLiveOutput( accountId: string, liveInputId: string, apiToken: string, outputUrl: string, streamKey: string ) { return fetch( https://api.cloudflare.com/client/v4/accounts/${accountId}/stream/live_inputs/${liveInputId}/outputs, { method: POST, headers: { Authorization: Bearer ${apiToken}, Content-Type: application/json }, body: JSON.stringify({ url: ${outputUrl}/${streamKey}, enabled: true, streamKey // 面向 YouTube、Twitch 等平台 }) } ).then(r r.json()); } // 示例同时推送到 YouTube 和 Twitch const liveInput await createLiveInput(accountId, apiToken); await createLiveOutput( accountId, liveInput.uid, apiToken, rtmp://a.rtmp.youtube.com/live2, your-youtube-stream-key ); await createLiveOutput( accountId, liveInput.uid, apiToken, rtmp://live.twitch.tv/app, your-twitch-stream-key );注意每个直播输入最多可配置5 个同播输出见 gotchas.md 的 Limits 表。6.4 WebRTC 直播WHIP/WHEP浏览器推流WHIPasync function startWebRTCBroadcast(liveInputId: string) { const pc new RTCPeerConnection(); const stream await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); stream.getTracks().forEach(track pc.addTrack(track, stream)); const offer await pc.createOffer(); await pc.setLocalDescription(offer); const response await fetch( https://customer-CODE.cloudflarestream.com/${liveInputId}/webRTC/publish, { method: POST, headers: { Content-Type: application/sdp }, body: offer.sdp } ); const answer await response.text(); await pc.setRemoteDescription({ type: answer, sdp: answer }); }浏览器拉流WHEPasync function playWebRTCStream(videoId: string) { const pc new RTCPeerConnection(); pc.addTransceiver(video, { direction: recvonly }); pc.addTransceiver(audio, { direction: recvonly }); const offer await pc.createOffer(); await pc.setLocalDescription(offer); const response await fetch( https://customer-CODE.cloudflarestream.com/${videoId}/webRTC/play, { method: POST, headers: { Content-Type: application/sdp }, body: offer.sdp } ); const answer await response.text(); await pc.setRemoteDescription({ type: answer, sdp: answer }); return pc; }6.5 录制模式说明模式行为automatic录制所有直播off不录制timeoutSeconds流结束 N 秒后自动停止录制const recordingConfig { mode: automatic, timeoutSeconds: 30, // 流结束 30s 后自动停止 requireSignedURLs: true, // 录制生成的 VOD 播放需 Token allowedOrigins: [https://yourdomain.com] };七、全栈模式与最佳实践7.1 React 播放器组件npm install cloudflare/stream-reactimport { Stream } from cloudflare/stream-react; export function VideoPlayer({ videoId, token }: { videoId: string; token?: string }) { return Stream controls src{token ? ${videoId}?token${token} : videoId} responsive /; }7.2 全栈上传流程Workers/Pages 后端 前端组件后端import Cloudflare from cloudflare; export default { async fetch(request: Request, env: Env): PromiseResponse { const { videoName } await request.json(); const client new Cloudflare({ apiToken: env.CF_API_TOKEN }); const { uploadURL, uid } await client.stream.directUpload.create({ account_id: env.CF_ACCOUNT_ID, maxDurationSeconds: 3600, requireSignedURLs: true, meta: { name: videoName } }); return Response.json({ uploadURL, uid }); } };前端组件含上传进度import { useState } from react; export function VideoUploader() { const [uploading, setUploading] useState(false); const [progress, setProgress] useState(0); async function handleUpload(file: File) { setUploading(true); const { uploadURL, uid } await fetch(/api/upload-url, { method: POST, body: JSON.stringify({ videoName: file.name }) }).then(r r.json()); const xhr new XMLHttpRequest(); xhr.upload.onprogress (e) setProgress((e.loaded / e.total) * 100); xhr.onload () { setUploading(false); window.location.href /videos/${uid}; }; xhr.open(POST, uploadURL); const formData new FormData(); formData.append(file, file); xhr.send(formData); } return ( div input typefile acceptvideo/* onChange{(e) e.target.files?.[0] handleUpload(e.target.files[0])} disabled{uploading} / {uploading progress value{progress} max{100} /} /div ); }7.3 TUS 断点续传大文件 500MBimport * as tus from tus-js-client; async function uploadWithTUS(file: File, uploadURL: string, onProgress?: (pct: number) void) { return new Promisestring((resolve, reject) { const upload new tus.Upload(file, { endpoint: uploadURL, retryDelays: [0, 3000, 5000, 10000, 20000], chunkSize: 50 * 1024 * 1024, metadata: { filename: file.name, filetype: file.type }, onError: reject, onProgress: (up, total) onProgress?.((up / total) * 100), onSuccess: () resolve(upload.url?.split(/).pop() || ) }); upload.start(); }); }7.4 视频处理状态轮询async function waitForVideoReady(client: Cloudflare, accountId: string, videoId: string) { for (let i 0; i 60; i) { const video await client.stream.videos.get(videoId, { account_id: accountId }); if (video.readyToStream || video.status.state error) return video; await new Promise(resolve setTimeout(resolve, 5000)); } throw new Error(Video processing timeout); }7.5 Webhook 处理器与签名校验export default { async fetch(request: Request, env: Env): PromiseResponse { const signature request.headers.get(Webhook-Signature); const body await request.text(); if (!signature || !await verifyWebhook(signature, body, env.WEBHOOK_SECRET)) { return new Response(Unauthorized, { status: 401 }); } const payload JSON.parse(body); if (payload.readyToStream) console.log(Video ${payload.uid} ready); return new Response(OK); } }; async function verifyWebhook(sig: string, body: string, secret: string): Promiseboolean { const parts Object.fromEntries(sig.split(,).map(p p.split())); const timestamp parseInt(parts.time || 0, 10); if (Math.abs(Date.now() / 1000 - timestamp) 300) return false; const key await crypto.subtle.importKey( raw, new TextEncoder().encode(secret), { name: HMAC, hash: SHA-256 }, false, [sign] ); const computed await crypto.subtle.sign(HMAC, key, new TextEncoder().encode(${timestamp}.${body})); const hex Array.from(new Uint8Array(computed), b b.toString(16).padStart(2, 0)).join(); return hex parts.sig1; }Webhook 验签要点时间戳允许5 分钟漂移验签失败几乎都源于 secret 不匹配或时钟偏差见 gotchas.md。7.6 自签 JWT高并发签名 Token适用于每日 Token 签发量超过 1k 的场景前置条件先在账户下创建签名密钥见 configuration.md。async function selfSignToken(keyId: string, jwkBase64: string, videoId: string, expiresIn 3600) { const key await crypto.subtle.importKey( jwk, JSON.parse(atob(jwkBase64)), { name: RSASSA-PKCS1-v1_5, hash: SHA-256 }, false, [sign] ); const now Math.floor(Date.now() / 1000); const header btoa(JSON.stringify({ alg: RS256, kid: keyId })).replace(//g, ).replace(/\/g, -).replace(/\//g, _); const payload btoa(JSON.stringify({ sub: videoId, kid: keyId, exp: now expiresIn, nbf: now })) .replace(//g, ).replace(/\/g, -).replace(/\//g, _); const message ${header}.${payload}; const sig await crypto.subtle.sign(RSASSA-PKCS1-v1_5, key, new TextEncoder().encode(message)); const b64Sig btoa(String.fromCharCode(...new Uint8Array(sig))).replace(//g, ).replace(/\/g, -).replace(/\//g, _); return ${message}.${b64Sig}; } // 携带地域访问规则的自签载荷 const payloadWithRules { sub: videoId, kid: keyId, exp: now 3600, nbf: now, accessRules: [{ type: ip.geoip.country, action: allow, country: [US] }] };签名密钥的创建与存储# 创建签名密钥保存响应中的 id 与 jwkbase64 curl -X POST \ https://api.cloudflare.com/client/v4/accounts/{account_id}/stream/keys \ -H Authorization: Bearer API_TOKEN # 存入 Worker 密钥 wrangler secret put STREAM_KEY_ID wrangler secret put STREAM_JWK7.7 最佳实践清单优先使用 Direct Creator Uploads——避免视频流经业务服务器中转开启 requireSignedURLs——私密内容必须走签名访问大规模时自签 Token——签名密钥支撑每日 1k 的签发量设置 allowedOrigins——防止跨站盗链用 Webhook 替代轮询——状态更新更高效设置 maxDurationSeconds——防止资源滥用开启直播录制——直播结束自动生成 VOD。八、限额与计费8.1 限额速查表资源限额单文件最大体积30 GB最大帧率60 fps推荐值单次直接上传最大时长由maxDurationSeconds配置Token 签发API 端点建议 1,000/天以内更高用量用签名密钥每个直播输入的同播输出5 个Webhook 重试次数5 次指数退避Webhook 超时30 秒字幕文件大小5 MB水印图片大小2 MB每个视频的元数据键不限搜索每页结果最多 1,000 条8.2 支持格式与计费支持格式MP4、MKV、MOV、AVI、FLV、MPEG-2 TS/PS、MXF、LXF、GXF、3GP、WebM、MPG、QuickTime计费存储 $5/1,000 分钟分发 $1/1,000 分钟。九、常见错误与排错9.1 常见错误码错误码原因解决办法ERR_NON_VIDEO上传的文件不是有效视频格式使用支持格式MP4、MKV、MOV 等ERR_DURATION_EXCEED_CONSTRAINT视频时长超过maxDurationSeconds限制调大该参数或先裁剪视频ERR_FETCH_ORIGIN_ERROR从 URL 拉取失败确认源地址公网可访问、HTTPS、文件存在ERR_MALFORMED_VIDEO文件损坏或编码异常用 FFmpeg 重新编码或检查文件完整性ERR_DURATION_TOO_SHORT视频时长不足 0.1 秒保证有效时长不能是单帧9.2 高频问题排查视频卡在inprogress状态大/复杂视频处理较慢最多等待 5 分钟建议用 Webhook 而非轮询签名 URL 返回 403Token 过期或签名无效检查过期时间戳、JWK 是否正确、服务器时钟是否同步直播无法连接RTMPS 地址或推流密钥不准确务必使用 API 返回的精确值并确认防火墙放行出站 443 端口Webhook 验签失败secret 不正确或超出 5 分钟时间窗视频上传成功但不可见requireSignedURLs开启但未携带 Token生成签名或对公开视频设false播放器无限加载allowedOrigins未包含你的域名将其加入白名单数组。9.3 性能优化上传慢大文件走 TUS 断点续传上传前压缩视频检查带宽播放缓冲使用 HLS/DASH 的 ABR 自适应码率降低最大码率处理时间长复杂编码/高分辨率导致预转码为 H.264效率最高、降低分辨率。9.4 类型安全与错误处理// 错误响应类型 interface StreamError { success: false; errors: Array{ code: number; message: string; }; } // 统一错误处理 async function uploadWithErrorHandling(url: string, file: File) { const formData new FormData(); formData.append(file, file); const response await fetch(url, { method: POST, body: formData }); const result await response.json(); if (!result.success) { throw new Error(result.errors[0]?.message || Upload failed); } return result; }9.5 安全红线前端绝不暴露 API Token——使用直接创作者上传务必校验 Webhook 签名——防止伪造通知Token 过期时间要短——缩短安全暴露窗口私密内容开启 requireSignedURLs——防止未授权访问白名单 allowedOrigins——防止在非授权站点被嵌入/盗链。十、在技能体系中的位置与延伸阅读本参考是cloudflare-deploy技能下references/stream/目录的入口文档README.md完整的阅读顺序如下顺序文件用途使用时机1configuration.md配置 SDK、环境变量、签名密钥新项目初始化2api.md点播视频 API实现上传/播放3api-live.md直播 API搭建直播功能4patterns.md全栈流程、TUS、JWT 签名实现业务工作流5gotchas.md错误码、限额、排错排查问题配套参考Stream API 可在 workers 中部署与 pages 集成可构建完整视频站点AI 字幕生成依赖 workers-ai 集成Wrangler 部署与 secret 管理细节见 wrangler。部署前记得先执行npx wrangler whoami确认认证状态再执行wrangler deploy。【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表