You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何检测浏览器播放HLS视频时的TS文件请求,精准定位直播进度?

兼容Safari与第三方HLS库的直播观看进度获取方案

针对你遇到的无法通过fetch/XMLHttpRequest拦截HLS媒体请求的问题,核心原因是Safari原生HLS播放依赖浏览器媒体栈发起请求,完全绕开了常规的XHR/fetch接口;部分第三方HLS库也会使用底层网络请求机制而非标准API。以下是几个通用可行的解决方案:

方案一:利用HLS播放器的原生事件监听

不管是第三方HLS库(如hls.js)还是Safari原生<video>,都提供了直接关联播放进度的事件,无需拦截网络请求:

第三方HLS库(以hls.js为例)

通过监听片段加载、切换事件,结合播放列表信息精准定位进度:

const hls = new Hls();
hls.loadSource('your-stream.m3u8');
hls.attachMedia(videoElement);

// 监听片段切换事件,获取当前播放的TS片段信息
hls.on(Hls.Events.FRAG_CHANGED, (event, data) => {
  const currentFrag = data.frag;
  // 当前片段的起始时间、时长可直接从frag对象获取
  const currentProgress = currentFrag.start + videoElement.currentTime - currentFrag.start;
  console.log('当前观看进度:', currentProgress.toFixed(2) + 's');
});

// 配合timeupdate事件实时更新
videoElement.addEventListener('timeupdate', () => {
  console.log('实时播放位置:', videoElement.currentTime.toFixed(2) + 's');
});

Safari原生HLS播放

Safari原生支持HLS的<video>元素,可通过timeupdate事件结合解析m3u8播放列表来匹配进度:

// 先获取并解析m3u8播放列表,得到每个TS片段的时间范围
async function parseM3U8(url) {
  const response = await fetch(url);
  const text = await response.text();
  const segments = [];
  let currentStart = 0;
  text.split('\n').forEach(line => {
    if (line.startsWith('#EXTINF:')) {
      const duration = parseFloat(line.split(':')[1]);
      segments.push({ start: currentStart, duration });
      currentStart += duration;
    }
  });
  return segments;
}

// 监听播放位置变化
const video = document.querySelector('video');
const segments = await parseM3U8('your-stream.m3u8');

video.addEventListener('timeupdate', () => {
  const currentTime = video.currentTime;
  // 匹配当前时间对应的TS片段
  const currentSegment = segments.find(seg => currentTime >= seg.start && currentTime < seg.start + seg.duration);
  if (currentSegment) {
    console.log('当前观看片段起始时间:', currentSegment.start.toFixed(2) + 's');
    console.log('片段内进度:', (currentTime - currentSegment.start).toFixed(2) + 's');
  }
});

方案二:使用Service Worker拦截媒体请求

Service Worker可以拦截所有页面发起的网络请求(包括浏览器媒体栈的请求),是唯一能统一捕获m3u8/TS请求的方式:

1. 注册Service Worker

在主页面中注册:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', async () => {
    await navigator.serviceWorker.register('/sw.js');
  });
}

2. 在Service Worker中拦截请求并解析

sw.js文件内容:

self.addEventListener('fetch', (event) => {
  const requestUrl = new URL(event.request.url);
  // 拦截m3u8或TS文件请求
  if (requestUrl.pathname.endsWith('.m3u8') || requestUrl.pathname.endsWith('.ts')) {
    event.respondWith(fetch(event.request).then(response => {
      // 处理m3u8请求,解析播放列表
      if (requestUrl.pathname.endsWith('.m3u8')) {
        response.clone().text().then(text => {
          // 解析m3u8内容,存储片段时间信息
          console.log('捕获m3u8请求,内容:', text);
          // 这里可以将解析后的片段信息发送给主页面
          self.clients.matchAll().then(clients => {
            clients.forEach(client => {
              client.postMessage({ type: 'M3U8_DATA', data: text });
            });
          });
        });
      } else if (requestUrl.pathname.endsWith('.ts')) {
        // 捕获TS请求,结合之前解析的m3u8信息匹配进度
        console.log('捕获TS请求:', requestUrl.href);
        self.clients.matchAll().then(clients => {
          clients.forEach(client => {
            client.postMessage({ type: 'TS_REQUESTED', url: requestUrl.href });
          });
        });
      }
      return response;
    }));
  }
});

3. 主页面接收Service Worker的消息

navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data.type === 'M3U8_DATA') {
    // 解析m3u8数据,存储片段时间范围
    const segments = parseM3U8Content(event.data.data);
    // 后续结合video.currentTime匹配进度
  } else if (event.data.type === 'TS_REQUESTED') {
    // 根据TS文件名或URL匹配对应的片段起始时间
    console.log('当前加载的TS片段:', event.data.url);
  }
});

function parseM3U8Content(text) {
  const segments = [];
  let currentStart = 0;
  text.split('\n').forEach(line => {
    if (line.startsWith('#EXTINF:')) {
      const duration = parseFloat(line.split(':')[1]);
      segments.push({ start: currentStart, duration });
      currentStart += duration;
    }
  });
  return segments;
}

方案三:结合媒体元素的buffered属性跟踪进度

通过<video>的buffered属性可以获取已加载的媒体时间范围,结合currentTime计算观看进度,无需拦截请求:

videoElement.addEventListener('timeupdate', () => {
  const currentTime = videoElement.currentTime;
  const buffered = videoElement.buffered;
  
  // 找到当前时间所在的已缓冲区间
  for (let i = 0; i < buffered.length; i++) {
    if (buffered.start(i) <= currentTime && buffered.end(i) >= currentTime) {
      console.log('已缓冲到:', buffered.end(i).toFixed(2) + 's');
      console.log('当前观看进度:', currentTime.toFixed(2) + 's');
      break;
    }
  }
});

关键注意事项

  • Safari原生HLS的直播流可能会动态更新m3u8,需要定期重新获取播放列表来更新片段信息;
  • Service Worker需要在HTTPS环境下运行(本地开发可用localhost);
  • 第三方HLS库的事件名称可能略有差异,需参考对应库的官方文档调整。

内容的提问来源于stack exchange,提问作者Aditya Joshi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 05:05:19