You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

经Deno+Oak代理后的HLS视频无法在hls.js与Art Player中播放的问题排查咨询

经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
  });
};

我试过的操作

  1. 验证片段数据有效性:把代理返回的HTML文件内容保存为.ts,VLC能正常播放
  2. 修改片段响应头:在代理中把HTML文件的Content-Type设为video/mp2t
  3. 重写m3u8中的所有URL:确保master和子播放列表里的地址都指向代理接口

针对你的问题的排查思路与解决方案

嗨,我仔细看了你的问题描述,这种“不报错也不播放”的情况在HLS代理场景里挺常见的,咱们一步步来捋:

关于“把代理路径改成.ts后缀”的思路:完全可行,甚至是关键步骤之一

hls.js会优先通过URL后缀识别资源类型,即使你设置了正确的Content-Type,但当前片段URL是/m3u8-proxy?url=xxx.html这种带.html后缀和查询参数的形式,hls.js或浏览器的媒体解析器还是可能对资源类型产生混淆,导致加载后无法正确解析。

你可以给代理加一个专门处理片段的路由,比如/segment/:filename.ts,逻辑如下:

  1. 解析出对应的原HTML片段地址(比如把page-0.ts映射到原地址https://example.com/page-0.html
  2. 拉取原地址的原始二进制数据
  3. 设置响应头Content-Type: video/mp2t,同时补全CORS相关头
  4. 直接转发二进制数据

这样hls.js看到.ts后缀的URL,会直接当作TS片段处理,能很大概率解决识别问题。

其他需要排查的关键点

  1. 补全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头,浏览器发送的范围请求会被拦截,导致片段加载失败但无明显报错。

  2. 检查m3u8重写的正确性
    你贴出的master.m3u8里,子播放列表的URL是/m3u8-proxy?url=https://example.com/index.m3u8,要注意:

    • 确保URL是绝对路径或与前端同域名的相对路径,避免跨域或路径解析错误
    • 原URL必须做encodeURIComponent编码,否则特殊字符会导致代理解析失败
  3. 开启hls.js调试日志
    Art Player集成了hls.js,你可以开启调试模式查看内部加载细节:

    const player = new Artplayer({
      container: containerRef.current,
      url: proxyUrl,
      hls: {
        debug: true, // 开启调试日志
        enableWorker: true,
      },
    });
    

    在浏览器控制台过滤hls.js的日志,就能看到它是否成功获取播放列表、是否尝试加载片段、片段加载状态等信息,这是定位问题的关键。

  4. 临时禁用缓存中间件
    你的缓存中间件给所有响应加了长时间缓存,虽然VOD场景影响不大,但可能导致浏览器加载旧的、错误的m3u8内容。可以先注释掉缓存中间件,测试是否能播放,排除缓存干扰。

  5. 确保片段响应是原始二进制数据
    在代理拉取原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的调试日志贴出来,就能更精准定位啦!

火山引擎 最新活动