通过 WebSocket 接收和播放 WSS 协议视频流

1.创建wss协议视频

1.1必备包

npm install ws @ffmpeg-installer/ffmpeg fluent-ffmpeg

说明:安装以下三个包。

1.2代码实现

说明:创建WebSocket服务器,端口为8080

import { WebSocket, WebSocketServer } from 'ws'; // 导入 WebSocket 和 WebSocketServer 模块
import ffmpeg from 'fluent-ffmpeg'; // 导入 fluent-ffmpeg 模块,用于处理视频流
import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; // 导入 ffmpeg-installer 模块,用于获取 FFmpeg 的路径
// 设置 FFmpeg 路径
ffmpeg.setFfmpegPath(ffmpegInstaller.path); // 将 FFmpeg 的路径设置为安装路径
// 创建一个 WebSocket 服务器,监听端口 8080
const wss = new WebSocketServer({ port: 8080 });
wss.on('connection', (ws) => { // 当有新的客户端连接时触发
    console.log('新客户端连接'); // 输出连接信息

    // 使用 FFmpeg 转换视频为 MPEG-TS 格式
    const command = ffmpeg('./ElephantsDream.mp4') // 指定要转换的视频文件
        .format('mpegts') // 设置输出格式为 MPEG-TS
        .videoCodec('mpeg1video') // 设置视频编码为 MPEG-1
        .videoBitrate('1000k') // 设置视频比特率为 1000k
        .size('640x480') // 设置视频分辨率为 640x480
        .audioCodec('mp2')  // 添加音频编码为 MP2
        .audioBitrate('128k')  // 设置音频比特率为 128k
        .on('error', (err) => { // 处理 FFmpeg 错误
            console.error('FFmpeg 错误:', err); // 输出错误信息
            ws.close(); // 关闭 WebSocket 连接
        });

    // 将转换后的数据流通过 WebSocket 发送
    const stream = command.pipe(); // 获取转换后的数据流
    stream.on('data', (chunk) => { // 当有数据块时触发
        if (ws.readyState === WebSocket.OPEN) { // 检查 WebSocket 是否处于打开状态
            ws.send(chunk); // 发送数据块给客户端
        }
    });

    // 处理断开连接
    ws.on('close', () => { // 当客户端断开连接时触发
        console.log('客户端断开连接'); // 输出断开连接信息
        stream.destroy(); // 销毁数据流以释放资源
    });
});

console.log('WebSocket 服务器启动在端口 8080'); // 输出服务器启动信息

1.3文件启动

node server.js 

说明:文件启动,打印日志。

2. 接收播放wss协议视频

2.1api测试

说明:服务器不停推送二进制的数据。

2.2代码实现

说明:安装jsmpeg-players播放器库,对通过websocket接收到的二进制数据进行播放。

<template>
  <div class="video-container">
    <canvas ref="videoCanvas"></canvas> <!-- 用于显示视频的画布 -->
    <div v-if="errorInfo.show" class="status error-status">
      <!-- 显示错误信息 -->
      <div>时间: {{ formatTime(errorInfo.time) }}</div>
      <div>错误次数: {{ errorInfo.count }}</div>
      <div>{{ errorMessage }}</div>
      <button @click="retryConnection" class="retry-btn">重试连接</button> <!-- 重试连接按钮 -->
    </div>
    <div v-else-if="status" class="status">{{ status }}</div> <!-- 显示当前状态 -->
  </div>
</template>

<script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; // 导入 Vue 的组合式 API
import JSMpeg from 'jsmpeg-player'; // 导入 JSMpeg 播放器库

const videoCanvas = ref(null); // 用于引用画布元素
const status = ref(''); // 用于存储当前状态信息
const errorMessage = ref('WebSocket连接错误'); // 用于存储错误信息
const errorInfo = reactive({
  show: false, // 是否显示错误信息
  time: '', // 错误发生时间
  count: 0 // 错误次数
});
let player = null; // JSMpeg 播放器实例
let ws = null; // WebSocket 实例
let connectionTimeout = null; // 连接超时计时器

// 格式化时间
const formatTime = (timeStr) => {
  if (!timeStr || timeStr === '不适用') return '--'; // 如果时间不可用,返回占位符
  return timeStr.split('.')[0]; // 去掉毫秒部分
};

// 记录错误信息
const recordError = (message = 'WebSocket连接错误') => {
  errorInfo.show = true; // 显示错误信息
  errorInfo.time = new Date().toLocaleTimeString(); // 记录当前时间
  errorInfo.count++; // 错误次数加一
  errorMessage.value = message; // 更新错误信息
};

// 重试连接
const retryConnection = () => {
  if (connectionTimeout) {
    clearTimeout(connectionTimeout); // 清除连接超时计时器
  }
  if (ws) {
    ws.close(); // 关闭现有 WebSocket 连接
  }
  if (player) {
    player.destroy(); // 销毁播放器实例
    player = null; // 重置播放器实例
  }
  initVideo(); // 重新初始化视频连接
};

// 初始化视频连接
const initVideo = () => {
  try {
    const streamUrl = 'ws://localhost:8080'; // WebSocket 服务器地址
    status.value = '正在连接...'; // 更新状态信息

    // 设置连接超时
    connectionTimeout = setTimeout(() => {
      if (ws && ws.readyState !== WebSocket.OPEN) {
        ws.close(); // 如果连接未打开,关闭连接
        recordError('连接超时'); // 记录连接超时错误
      }
    }, 5000);

    // 创建WebSocket连接
    ws = new WebSocket(streamUrl);
    ws.binaryType = 'arraybuffer'; // 设置数据类型为二进制数组

    let dataReceived = false; // 标记是否收到数据
    let dataTimer = null; // 数据接收超时计时器

    ws.onopen = () => {
      clearTimeout(connectionTimeout); // 清除连接超时计时器
      status.value = '连接成功,等待数据...'; // 更新状态信息

      // 设置数据接收超时
      dataTimer = setTimeout(() => {
        if (!dataReceived) {
          recordError('连接成功但未收到有效数据'); // 记录未收到数据错误
          ws.close(); // 关闭连接
        }
      }, 8000);
    };

    ws.onmessage = (event) => {
      // 检查数据包是否有效
      if (event.data instanceof ArrayBuffer) {
        dataReceived = true; // 标记已收到数据
        clearTimeout(dataTimer); // 清除数据接收超时计时器

        if (event.data.byteLength > 0) {
          console.log('收到数据包:', event.data.byteLength, new Date().toLocaleTimeString());

          // 如果播放器未初始化,初始化播放器
          if (!player) {
            status.value = '收到数据,初始化播放器...'; // 更新状态信息
            startPlayer(streamUrl); // 初始化播放器
          }

          // 尝试手动处理数据
          try {
            if (player && player.source) {
              const data = new Uint8Array(event.data);
              // 检查数据是否为MPEG-TS格式
              if (data[0] === 0x47) { // MPEG-TS同步字节
                player.source.write(data); // 写入数据到播放器
              } else {
                console.warn('收到非MPEG-TS格式数据'); // 警告非预期格式数据
              }
            }
          } catch (e) {
            console.error('数据处理错误:', e); // 输出数据处理错误信息
          }
        }
      }
    };

    // 其他事件处理保持不变...
  } catch (error) {
    // 错误处理保持不变...
  }
};

// 初始化播放器
const startPlayer = (streamUrl) => {
  try {
    player = new JSMpeg.Player(streamUrl, {
      canvas: videoCanvas.value, // 指定画布元素
      autoplay: true, // 自动播放
      audio: true,  // 启用音频
      audioBufferSize: 512 * 1024,  // 音频缓冲区大小
      loop: true, // 循环播放
      videoBufferSize: 1024 * 1024 * 2, // 视频缓冲区大小
      onSourceEstablished: () => {
        console.log('视频源已建立'); // 输出视频源建立信息
        status.value = '视频播放中'; // 更新状态信息
      },
      onSourceCompleted: () => {
        console.log('视频源已完成'); // 输出视频源完成信息
      },
      onStalled: () => {
        console.log('播放停滞'); // 输出播放停滞信息
      }
    });
  } catch (error) {
    recordError(`播放器初始化失败: ${error.message}`); // 记录播放器初始化错误
    console.error('播放器错误:', error); // 输出播放器错误信息
  }
};

// 组件挂载时初始化视频连接
onMounted(() => {
  initVideo();
});

// 组件卸载前清理资源
onBeforeUnmount(() => {
  if (connectionTimeout) {
    clearTimeout(connectionTimeout); // 清除连接超时计时器
  }
  ws?.close(); // 关闭 WebSocket 连接
  player?.destroy(); // 销毁播放器实例
});
</script>

<style scoped>
.video-container {
  width: 100%;
  height: 100%;
  position: relative; /* 设置容器为相对定位 */
}

canvas {
  width: 100%;
  height: 100%;
  background: #000; /* 设置画布背景为黑色 */
}

.status {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%); /* 居中显示状态信息 */
  background: rgba(0,0,0,0.7); /* 半透明背景 */
  color: #fff; /* 白色文字 */
  padding: 12px 20px; /* 内边距 */
  border-radius: 4px; /* 圆角边框 */
  text-align: center; /* 文本居中 */
}

.error-status {
  color: #ff6b6b; /* 错误状态文字颜色 */
}

.retry-btn {
  margin-top: 10px; /* 按钮顶部外边距 */
  padding: 5px 10px; /* 按钮内边距 */
  background: #3498db; /* 按钮背景颜色 */
  border: none; /* 无边框 */
  border-radius: 4px; /* 圆角边框 */
  color: white; /* 按钮文字颜色 */
  cursor: pointer; /* 鼠标指针样式 */
}

.retry-btn:hover {
  background: #2980b9; /* 按钮悬停背景颜色 */
}
</style>

2.3播放

说明:打开对应文件的路由,播放视频。

3.附件

 3.1视频链接

说明:https://live.csdn.net/v/474456 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FOREVER-Q

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值