云顶玩家必备!用 React+TS 打造多路直播监控,同时看 N 个主播学阵容

421 阅读5分钟

前言:为什么要做这个项目?

你是否也有过这种困扰?玩云顶之弈想学习不同主播的阵容,打开 5 个浏览器标签页,切来切去错过关键操作😑;想看比赛多路解说,来回切换直播间心态爆炸…🙄与其忍受标签页地狱,不如自己动手!🛠️今天用 React+TypeScript+Node.js,花 1 小时搭建一个「多路直播监控工具」,支持虎牙 / 斗鱼平台,同屏观看、自由静音、灵活布局,彻底解决多直播同时看的痛点~👍️

这个项目不仅能解决了实际需求,还成为了学习React的绝佳实践。今天就来分享这个项目的完整开发过程。

ominiviewer2.gif

你能学到什么

✅️ 虎牙/斗鱼直播源解析
✅️ 多布局切换
✅️ 本地快速部署
✅️ 完整源码

ominiviewer6.gif

核心功能实现

1. 直播流卡片组件设计

// StreamTile.tsx - 直播流卡片组件
interface StreamTileProps {
  stream: StreamSource;
  onRemove: (id: string) => void;
  onToggleMute: (id: string) => void;
}

const StreamTile: React.FC<StreamTileProps> = ({ 
  stream, 
  onRemove, 
  onToggleMute 
}) => {
  const [isLoading, setIsLoading] = useState(true);
  const [hasError, setHasError] = useState(false);
  
  return (
    <div className="stream-tile">
      {/* 视频播放区域 */}
      <video 
        src={stream.url}
        muted={stream.isMuted}
        onLoadedData={() => setIsLoading(false)}
        onError={() => setHasError(true)}
      />
      
      {/* 控制工具栏 */}
      <div className="controls">
        <button onClick={() => onToggleMute(stream.id)}>
          {stream.isMuted ? '🔇' : '🔊'}
        </button>
        <button onClick={() => onRemove(stream.id)}>❌</button>
      </div>
    </div>
  );
};

2. 多布局网格系统

// 布局枚举定义
export enum GridLayout {
  Single = '1x1',
  Dual = '2x1', 
  Triple = '3x1',
  Quad = '2x2',
  Hex = '3x2'
}

// 动态网格类名生成
const getGridClass = (layout: GridLayout): string => {
  const gridMap = {
    [GridLayout.Single]: 'grid-cols-1 grid-rows-1',
    [GridLayout.Dual]: 'grid-cols-2 grid-rows-1',
    [GridLayout.Triple]: 'grid-cols-3 grid-rows-1',
    [GridLayout.Quad]: 'grid-cols-2 grid-rows-2',
    [GridLayout.Hex]: 'grid-cols-3 grid-rows-2'
  };
  return gridMap[layout];
};

3. 多平台直播源智能处理

❓ 问题:如何将用户输入的直播平台URL(如虎牙、斗鱼直播间链接)转换为可直接播放的流媒体地址?

  • 不同平台(虎牙、斗鱼)有不同的解析规则和API接口
  • 需要处理多种流媒体格式(FLV、HLS/m3u8)
  • 需要提供多种链接选项以应对不同网络环境

💡 思路:分层解析与优先级策略

🔧 核心代码:

智能识别与分流

// 第一步:判断是否为可直接播放的链接
const isDirectStream = customUrl.includes('.m3u8') ||
                       customUrl.includes('.flv') ||
                       customUrl.includes('flvjs');

如果是直接可播放的链接(包含.m3u8、.flv等),直接使用;否则调用后端API解析。

后端 API 解析
// 调用本地解析服务
const response = await fetch('http://localhost:3001/api/parse-url', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({ url: customUrl }),
});

将复杂的解析逻辑放在后端,前端只负责展示和选择。

多层级链接优先级策略

按优先级从高到低收集候选链接:

第一优先级:虎牙格式链接

  • FLV格式链接(高清、稳定)
  • HLS格式链接(兼容性好)

第二优先级:斗鱼格式链接

  • PC端链接
  • 移动端链接

第三优先级:CDN链接

  • m3u8 CDN链接
  • FLV CDN链接

第四优先级:其他链接

  // 链接优先级收集器
const urlCandidates: string[] = [];

// 虎牙FLV链接(最高优先级)
if (parsedData.links?.flv && typeof parsedData.links.flv === 'object') {
  Object.values(parsedData.links.flv).forEach(url => {
    if (typeof url === 'string') urlCandidates.push(url);
  });
}

// 虎牙HLS链接
if (parsedData.links?.hls && typeof parsedData.links.hls === 'object') {
  Object.values(parsedData.links.hls).forEach(url => {
    if (typeof url === 'string') urlCandidates.push(url);
  });
}

// 斗鱼PC链接
if (parsedData.links?.pc && typeof parsedData.links.pc === 'string') {
  urlCandidates.push(parsedData.links.pc);
}

// CDN链接(网络优化)
if (parsedData.links?.cdnLinks?.m3u8 && Array.isArray(parsedData.links.cdnLinks.m3u8)) {
  parsedData.links.cdnLinks.m3u8.forEach(url => {
    if (typeof url === 'string') urlCandidates.push(url);
  });
}      

按优先级顺序选择第一个可用链接,确保最佳播放体验。

技术难点与解决方案

难点:FLV格式直播流播放

核心难点:HTML5不支持FLV格式,怎么播放直播流?

❓ 问题:虎牙/斗鱼等平台的直播流多为FLV格式,而原生video标签只支持MP4/HLV,直接播放会报错。
💡 思路:使用flv.js库解析FLV流,将其转成video标签可识别的格式,同时处理加载失败、卡顿等异常场景。
🔧 核心代码:

import flvjs from 'flv.js';

const StreamPlayer = ({ url }) => {
  const videoRef = useRef<HTMLVideoElement>(null);
  const playerRef = useRef<flvjs.Player | null>(null);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    // 1. 检查浏览器是否支持flv.js
    if (!flvjs.isSupported() || !videoRef.current) return;

    // 2. 创建播放器实例(关键配置:标记为直播流,启用懒加载)
    playerRef.current = flvjs.createPlayer(
      { type: 'flv', url, isLive: true },
      { lazyLoad: true, seekType: 'range' }
    );

    // 3. 绑定DOM+加载播放
    playerRef.current.attachMediaElement(videoRef.current);
    playerRef.current.load();
    playerRef.current.play().catch(err => console.log('播放失败:', err));

    // 4. 监听状态,更新UI
    playerRef.current.on(flvjs.Events.LOADING_COMPLETE, () => setIsLoading(false));

    // 5. 组件卸载时销毁播放器(避免内存泄漏)
    return () => playerRef.current?.destroy();
  }, [url]);

  return (
    <div className="stream-player">
      {isLoading && <div className="loading">加载中...</div>}
      <video ref={videoRef} controls={false} />
    </div>
  );
。

⚠️ 避坑点:

  1. 开发环境跨域:后端需要配置CORS,允许前端请求;
  2. 部分浏览器不支持Web Worker:设置enableWorker: false提高兼容性;
  3. 直播流中断处理:可添加重连逻辑(setTimeout重试load())

快速上手

1. 克隆源码

git clone https://github.com/Schuyler2025/ominiview-monitor.git
cd ominiview-monitor

2. 启动前端(React)

npm install  # 安装依赖
npm run build
npm run preview

3. 启动后端(Node.js)

cd server
npm install
npm start  # 启动服务

使用方法:

  1. 打开http://localhost:4173;
  2. 复制虎牙/斗鱼主播直播间链接(比如www.huya.com/xxx);
  3. 粘贴到输入框,点击“添加直播”;
  4. 右上角切换布局,点击直播卡片的🔇/❌控制静音/关闭。

总结与展望

通过这个项目,不仅掌握了React的核心概念(组件化、状态管理、生命周期),还深入理解了现代前端工程化实践。

为什么这个项目值得学?

解决真实痛点:不是为了学 React 而写 Demo,而是真的能替代 “多标签页切换”;

技术栈实用:React Hooks+TypeScript+Node.js,都是前端面试高频技术;

技术收获:

  • React Hooks的熟练使用
  • TypeScript类型系统设计
  • 工程化构建配置

未来规划:

  • 支持更多直播平台(B站、抖音等)
  • 添加录制功能
  • 开发浏览器插件版本

给新手的建议

  1. 从实际需求出发:解决真实问题能让学习更有动力
  2. 循序渐进:先实现核心功能,再逐步优化
  3. 善用工具:gemini3、豆包等工具能提升开发效率
  4. 代码规范:从一开始就养成良好的编码习惯

源码地址

项目已开源,欢迎Star和贡献: 👉 GitHub仓库地址

如果文章对你有帮助,欢迎点赞收藏~有任何问题可以在评论区交流!