Bootstrap

mp4视频流推送的学习

一、依赖引入:

①使用 CDN 的播放器代码

<!-- 引入 xgplayer 核心 -->
<script src="https://unpkg.byted-static.com/xgplayer/3.0.10/dist/index.min.js" charset="utf-8"></script>

<!-- 引入 xgplayer mp4 插件 -->
<script src="https://unpkg.byted-static.com/xgplayer-mp4/3.0.10/dist/index.min.js" charset="utf-8"></script>

<!-- 引入 xgplayer 样式 -->
<link rel="stylesheet" href="https://unpkg.byted-static.com/xgplayer/3.0.10/dist/index.min.css" />
<!-- 支持 danmu 的插件 -->
<script src="https://unpkg.byted-static.com/xgplayer/3.0.10/dist/index.min.js"></script>
<script src="https://unpkg.com/xgplayer-danmu/2.1.0/dist/index.min.js"></script>

②命令行安装 xgplayer

npm install xgplayer 
npm install xgplayer-mp4
npm install xgplayer-danmu

二、组件封装(弹幕功能暂不支持)

<template>
  <div class="player-container">
    <div id="mse"></div>
  </div>
</template>

<script setup>
import { onMounted, onBeforeUnmount, ref, nextTick } from 'vue';

// 引入 Player 和 Mp4Plugin (假设通过 CDN 引入,挂载在 window 上)
import Player from 'xgplayer';
// import 'xgplayer-danmu';
import Mp4Plugin from 'xgplayer-mp4';

const player = ref(null);
// 弹幕数据
const danmuComments = [
  {
    duration: 5000, // 弹幕持续时间
    id: '1', // 弹幕唯一标识
    start: 0, // 视频开始时出现
    txt: '这是第一条弹幕', // 弹幕内容
    style: {
      color: '#fff', // 弹幕文字颜色
      fontSize: '20px', // 弹幕文字大小
      border: 'solid 1px #ff9500', // 弹幕边框
      borderRadius: '50px', // 弹幕圆角
      padding: '5px 11px', // 弹幕内边距
      backgroundColor: 'rgba(255, 255, 255, 0.1)', // 弹幕背景颜色
    },
  },
  {
    duration: 3000, // 弹幕持续时间
    id: '2',
    start: 2, // 视频播放到 2 秒时出现
    txt: '第二条弹幕,展示更多内容',
    style: {
      color: '#00ff00',
      fontSize: '18px',
      backgroundColor: 'rgba(0, 0, 0, 0.5)',
    },
  },
];
let previousFrame = null; // 用于存储上一帧数据

// 插值函数:生成中间帧
const interpolateFrames = (frame1, frame2) => {
  if (!frame1 || !frame2) return frame2; // 如果没有上一帧,直接返回当前帧

  const width = frame1.width;
  const height = frame1.height;
  const interpolated = new ImageData(width, height); // 创建空白帧

  for (let i = 0; i < frame1.data.length; i++) {
    interpolated.data[i] = (frame1.data[i] + frame2.data[i]) / 2; // 简单线性插值
  }

  return interpolated;
};

// 初始化播放器配置
const playerConfig = {
  id: 'mse',// 容器
  autoplay: true, //  自动播放
  volume: 0.3,// 音量
  url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4', // 视频地址
  poster: '//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/poster.jpg',// 封面
  playsinline: true,// 小窗播放
  thumbnail: {
    pic_num: 44,// 每张图片的帧数
    width: 160, // 每张图片的宽度
    height: 90, // 每张图片的高度
    col: 10,// 每行显示的图片数
    row: 10,// 每列显示的图片数
    urls: ['//lf3-static.bytednsdoc.com/obj/eden-cn/nupenuvpxnuvo/xgplayer_doc/xgplayer-demo-thumbnail.jpg'], // 图片地址
  },
  // 弹幕配置
  danmu: {
    comments: danmuComments,
    area: {
      start: 0, // 开始时间
      end: 1, // 范围从 0 到 1,表示全视频范围内展示
    },
  },
  // plugins: [window.Mp4Plugin], // cnd引入使用
  plugins: [Mp4Plugin],// import 导入使用 Mp4Plugin 插件
  mp4plugin: {
    maxBufferLength: 50, // 缓冲区最大长度
    minBufferLength: 10, // 缓冲区最小长度
  },
  height: 500,// 播放器高度
  width: 800, // 播放器宽度
  controls: true,// 启用控制栏
};

// 初始化播放器
const initPlayer = () => {
  try {
    console.log('Mp4Plugin:', Mp4Plugin);
    if (!Mp4Plugin) {
      throw new Error('Mp4Plugin is not loaded. Please check the CDN or script reference.');
    }
    // 手动加载弹幕数据
    player.value = new Player(playerConfig);
    console.log('Player danmu:', Player.danmu);
    // 检查 danmu 插件是否可用
    // 检查 danmu 插件是否可用
    if (player.value.danmu) {
      console.log('danmu initialized:', player.value.danmu);
      player.value.danmu.load(danmuComments);
    } else {
      console.warn('danmu 功能未初始化');
    }

    // 监听事件
    player.value.on('play', () => {
      if (player.value.danmu) {
        player.value.danmu.start();
      }
    });

    player.value.on('pause', () => {
      if (player.value.danmu) {
        player.value.danmu.pause();
      }
    });
    // 动态加载资源
    player.value.emit('resourceReady', [
      { name: '超清', definition: '1080p', url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-720p.mp4' },
      { name: '高清', definition: '720p', url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-480p.mp4' },
      { name: '标清', definition: '480p', url: '//sf1-cdn-tos.huoshanstatic.com/obj/media-fe/xgplayer_doc_video/mp4/xgplayer-demo-360p.mp4' },
    ]);

    // 插帧逻辑:监听帧渲染事件
    player.value.on('frameRender', () => {
      const videoElement = document.querySelector('video');
      if (!videoElement || !videoElement.videoWidth || !videoElement.videoHeight) {
        console.warn('视频容器未加载完成,无法获取视频尺寸。');
        return;
      }
      try {
        // 创建 canvas 和上下文
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        canvas.width = videoElement.videoWidth;
        canvas.height = videoElement.videoHeight;

        // 获取当前帧
        ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
        const currentFrame = ctx.getImageData(0, 0, canvas.width, canvas.height);

        // 生成插值帧
        const interpolatedFrame = interpolateFrames(previousFrame, currentFrame);
        previousFrame = currentFrame; // 更新上一帧

        // 渲染到 canvas(如需可视化)
        ctx.putImageData(interpolatedFrame, 0, 0);
      } catch (error) {
        console.error('错误动画帧渲染:', error);
      }
    });
    // 监听播放器错误
    player.value.on('error', (error) => {
      console.error('播放连接失败:', error);
      alert('视频加载失败,请检查网络或视频资源路径');
    });
  } catch (error) {
    console.error('初始化容器失败:', error.message, '\nStack:', error.stack, '\nConfig:', playerConfig);
    alert('播放器初始化失败,请联系技术支持');
  }
};

// 调整播放器大小
const resizePlayer = () => {
  const resizeObserver = new ResizeObserver(() => {
    if (player.value) {
      player.value.resize(window.innerWidth, window.innerHeight);
    }
  });
  resizeObserver.observe(document.body);
};

// 生命周期钩子
onMounted(() => {
  nextTick(() => {
    initPlayer();
    resizePlayer();
  })

});

onBeforeUnmount(() => {
  if (player.value) {
    player.value.destroy(); // 销毁播放器
  }
});
</script>

<style scoped>
.player-container {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #000;
  /* 确保背景色为黑色 */
}

#MSE {
  width: 100%;
  height: 100%;
}
</style>

三、应用:

<template>
  <VideoPlayer />
</template>

<script setup>
import VideoPlayer from './components/VideoPlayer.vue'
</script>

四、效果:

五、学习啦:

MP4 是一种广泛使用的数字多媒体容器格式,用于存储视频、音频、字幕以及其他数据(例如元数据)

扩展名:通常为 .mp4

用途

  • 存储和分发音视频数据。
  • 支持流媒体(渐进式下载和适应性流)。
  • 广泛应用于互联网、移动设备和流媒体服务。
特点
  • 高度灵活:支持多种视频和音频编码格式(如 H.264、AAC)。
  • 容量小:相较于其他格式,MP4 提供更高的压缩率。
  • 兼容性强:在大多数平台和设备上可以直接播放。
;