经Deno+Oak代理后的HLS视频无法在hls.js与Art Player中播放的问题排查咨询
问题背景
我用Playwright抓取了一个HLS流的master.m3u8地址,但原地址存在CORS问题,于是用Deno+Oak搭建了一个代理服务:
- 代理会重写
master.m3u8里的子播放列表URL,指向我的代理接口/m3u8-proxy?url=xxx - 子播放列表(比如
index.m3u8)里的片段是HTML格式的文件(实际是TS二进制数据),我已经在代理里把这些文件的响应头Content-Type改成了video/mp2t - 直接把HTML文件的内容保存成
.ts后用VLC能正常播放,但在前端用Art Player(基于hls.js)加载代理后的流时,既不报错也不播放
目前我考虑过一种方案:把代理的片段路径改成类似/segment/page-0.ts的形式,让hls.js识别出是TS片段,但还没实现,想问问这个思路是否可行?还有其他可能的问题点和解决办法吗?
我的代理与前端关键代码
代理路由(Deno+Oak)
// routes.js import { Router } from "oak/mod.ts"; import { m3u8Proxy } from "./m3u8-proxy.js"; const router = new Router(); router.get("/m3u8-proxy", m3u8Proxy); export { router };
代理服务启动
// server.js import { Application } from "oak/mod.ts"; import { cacheRoutes } from "./cache-routes.js"; import { router } from "./routes.js"; const app1 = new Application(); app1.use(cacheRoutes(3600)); app1.use(router.routes()); app1.use(router.allowedMethods()); await app1.listen({ port: "YOUR_PORT" });
缓存中间件
// cache-routes.js export function cacheRoutes(maxAgeSeconds = 3600) { return async (ctx, next) => { await next(); if (!ctx.response.headers.has("Cache-Control")) { ctx.response.headers.set( "Cache-Control", `public, max-age=${maxAgeSeconds}, must-revalidate` ); } }; }
前端Art Player初始化
const initializePlayerWithData = async (streamData, activeServerDomain) => { let attempts = 0; while (!containerRef.current && attempts < 10) { await new Promise((resolve) => setTimeout(resolve, 100)); attempts++; } if (!containerRef.current) { console.error("Container ref not available"); setPlayerLoading(false); return; } const originalStreamUrl = streamData[activeServerDomain]?.hls_url; if (!originalStreamUrl) { console.error("No stream URL available"); setPlayerLoading(false); return; } const proxyUrl = `/m3u8-proxy?url=${encodeURIComponent(originalStreamUrl)}`; const player = new Artplayer({ container: containerRef.current, url: proxyUrl, hls: true, // 启用hls.js }); };
我试过的操作
- 验证片段数据有效性:把代理返回的HTML文件内容保存为
.ts,VLC能正常播放 - 修改片段响应头:在代理中把HTML文件的
Content-Type设为video/mp2t - 重写m3u8中的所有URL:确保master和子播放列表里的地址都指向代理接口
针对你的问题的排查思路与解决方案
嗨,我仔细看了你的问题描述,这种“不报错也不播放”的情况在HLS代理场景里挺常见的,咱们一步步来捋:
关于“把代理路径改成.ts后缀”的思路:完全可行,甚至是关键步骤之一
hls.js会优先通过URL后缀识别资源类型,即使你设置了正确的Content-Type,但当前片段URL是/m3u8-proxy?url=xxx.html这种带.html后缀和查询参数的形式,hls.js或浏览器的媒体解析器还是可能对资源类型产生混淆,导致加载后无法正确解析。
你可以给代理加一个专门处理片段的路由,比如/segment/:filename.ts,逻辑如下:
- 解析出对应的原HTML片段地址(比如把
page-0.ts映射到原地址https://example.com/page-0.html) - 拉取原地址的原始二进制数据
- 设置响应头
Content-Type: video/mp2t,同时补全CORS相关头 - 直接转发二进制数据
这样hls.js看到.ts后缀的URL,会直接当作TS片段处理,能很大概率解决识别问题。
其他需要排查的关键点
补全CORS响应头
你只处理了Content-Type,但代理返回的所有资源(master.m3u8、子m3u8、TS片段)都需要完整的CORS头,尤其是HLS播放依赖的Range请求头:ctx.response.headers.set("Access-Control-Allow-Origin", "*"); // 或者你的前端域名 ctx.response.headers.set("Access-Control-Allow-Methods", "GET, OPTIONS"); ctx.response.headers.set("Access-Control-Allow-Headers", "Range");如果没允许
Range头,浏览器发送的范围请求会被拦截,导致片段加载失败但无明显报错。检查m3u8重写的正确性
你贴出的master.m3u8里,子播放列表的URL是/m3u8-proxy?url=https://example.com/index.m3u8,要注意:- 确保URL是绝对路径或与前端同域名的相对路径,避免跨域或路径解析错误
- 原URL必须做
encodeURIComponent编码,否则特殊字符会导致代理解析失败
开启hls.js调试日志
Art Player集成了hls.js,你可以开启调试模式查看内部加载细节:const player = new Artplayer({ container: containerRef.current, url: proxyUrl, hls: { debug: true, // 开启调试日志 enableWorker: true, }, });在浏览器控制台过滤
hls.js的日志,就能看到它是否成功获取播放列表、是否尝试加载片段、片段加载状态等信息,这是定位问题的关键。临时禁用缓存中间件
你的缓存中间件给所有响应加了长时间缓存,虽然VOD场景影响不大,但可能导致浏览器加载旧的、错误的m3u8内容。可以先注释掉缓存中间件,测试是否能播放,排除缓存干扰。确保片段响应是原始二进制数据
在代理拉取原HTML片段时,一定要获取原始二进制数据,不要转成字符串再返回(比如用await response.arrayBuffer()或直接转发response.body,而非await response.text()),否则会破坏TS片段的二进制结构,导致无法播放。
片段代理路由示例(Deno+Oak)
// routes.js router.get("/segment/:filename.ts", async (ctx) => { const filename = ctx.params.filename; const originalUrl = `https://example.com/${filename}.html`; // 映射原地址 try { const response = await fetch(originalUrl); if (!response.ok) { ctx.response.status = response.status; return; } // 设置CORS和Content-Type ctx.response.headers.set("Access-Control-Allow-Origin", "*"); ctx.response.headers.set("Access-Control-Allow-Methods", "GET, OPTIONS"); ctx.response.headers.set("Access-Control-Allow-Headers", "Range"); ctx.response.headers.set("Content-Type", "video/mp2t"); // 转发原始二进制数据 ctx.response.body = response.body; } catch (err) { console.error(err); ctx.response.status = 500; } });
之后重写子m3u8时,把片段URL改成/segment/page-0.ts这种形式即可。
总结
优先尝试把片段URL改成带.ts后缀的形式,同时补全CORS头、检查m3u8重写正确性、开启hls.js调试日志,这几个点应该能解决你的问题。如果还有疑问,把hls.js的调试日志贴出来,就能更精准定位啦!




