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

打印 上一主题 下一主题

主题 1733|帖子 1733|积分 5199

1.创建wss协议视频

1.1必备包

   npm install ws @ffmpeg-installer/ffmpeg fluent-ffmpeg
  阐明:安装以下三个包。

1.2代码实现

阐明:创建WebSocket服务器,端口为8080
  1. import { WebSocket, WebSocketServer } from 'ws'; // 导入 WebSocket 和 WebSocketServer 模块
  2. import ffmpeg from 'fluent-ffmpeg'; // 导入 fluent-ffmpeg 模块,用于处理视频流
  3. import ffmpegInstaller from '@ffmpeg-installer/ffmpeg'; // 导入 ffmpeg-installer 模块,用于获取 FFmpeg 的路径
  4. // 设置 FFmpeg 路径
  5. ffmpeg.setFfmpegPath(ffmpegInstaller.path); // 将 FFmpeg 的路径设置为安装路径
  6. // 创建一个 WebSocket 服务器,监听端口 8080
  7. const wss = new WebSocketServer({ port: 8080 });
  8. wss.on('connection', (ws) => { // 当有新的客户端连接时触发
  9.     console.log('新客户端连接'); // 输出连接信息
  10.     // 使用 FFmpeg 转换视频为 MPEG-TS 格式
  11.     const command = ffmpeg('./ElephantsDream.mp4') // 指定要转换的视频文件
  12.         .format('mpegts') // 设置输出格式为 MPEG-TS
  13.         .videoCodec('mpeg1video') // 设置视频编码为 MPEG-1
  14.         .videoBitrate('1000k') // 设置视频比特率为 1000k
  15.         .size('640x480') // 设置视频分辨率为 640x480
  16.         .audioCodec('mp2')  // 添加音频编码为 MP2
  17.         .audioBitrate('128k')  // 设置音频比特率为 128k
  18.         .on('error', (err) => { // 处理 FFmpeg 错误
  19.             console.error('FFmpeg 错误:', err); // 输出错误信息
  20.             ws.close(); // 关闭 WebSocket 连接
  21.         });
  22.     // 将转换后的数据流通过 WebSocket 发送
  23.     const stream = command.pipe(); // 获取转换后的数据流
  24.     stream.on('data', (chunk) => { // 当有数据块时触发
  25.         if (ws.readyState === WebSocket.OPEN) { // 检查 WebSocket 是否处于打开状态
  26.             ws.send(chunk); // 发送数据块给客户端
  27.         }
  28.     });
  29.     // 处理断开连接
  30.     ws.on('close', () => { // 当客户端断开连接时触发
  31.         console.log('客户端断开连接'); // 输出断开连接信息
  32.         stream.destroy(); // 销毁数据流以释放资源
  33.     });
  34. });
  35. console.log('WebSocket 服务器启动在端口 8080'); // 输出服务器启动信息
复制代码
1.3文件启动

   node server.js 
  阐明:文件启动,打印日志。

2. 接收播放wss协议视频

2.1api测试

阐明:服务器不绝推送二进制的数据。

2.2代码实现

阐明:安装jsmpeg-players播放器库,对通过websocket接收到的二进制数据进行播放。
  1. <template>
  2.   <div class="video-container">
  3.     <canvas ref="videoCanvas"></canvas> <!-- 用于显示视频的画布 -->
  4.     <div v-if="errorInfo.show" class="status error-status">
  5.       <!-- 显示错误信息 -->
  6.       <div>时间: {{ formatTime(errorInfo.time) }}</div>
  7.       <div>错误次数: {{ errorInfo.count }}</div>
  8.       <div>{{ errorMessage }}</div>
  9.       <button @click="retryConnection" class="retry-btn">重试连接</button> <!-- 重试连接按钮 -->
  10.     </div>
  11.     <div v-else-if="status" class="status">{{ status }}</div> <!-- 显示当前状态 -->
  12.   </div>
  13. </template>
  14. <script setup>
  15. import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; // 导入 Vue 的组合式 API
  16. import JSMpeg from 'jsmpeg-player'; // 导入 JSMpeg 播放器库
  17. const videoCanvas = ref(null); // 用于引用画布元素
  18. const status = ref(''); // 用于存储当前状态信息
  19. const errorMessage = ref('WebSocket连接错误'); // 用于存储错误信息
  20. const errorInfo = reactive({
  21.   show: false, // 是否显示错误信息
  22.   time: '', // 错误发生时间
  23.   count: 0 // 错误次数
  24. });
  25. let player = null; // JSMpeg 播放器实例
  26. let ws = null; // WebSocket 实例
  27. let connectionTimeout = null; // 连接超时计时器
  28. // 格式化时间
  29. const formatTime = (timeStr) => {
  30.   if (!timeStr || timeStr === '不适用') return '--'; // 如果时间不可用,返回占位符
  31.   return timeStr.split('.')[0]; // 去掉毫秒部分
  32. };
  33. // 记录错误信息
  34. const recordError = (message = 'WebSocket连接错误') => {
  35.   errorInfo.show = true; // 显示错误信息
  36.   errorInfo.time = new Date().toLocaleTimeString(); // 记录当前时间
  37.   errorInfo.count++; // 错误次数加一
  38.   errorMessage.value = message; // 更新错误信息
  39. };
  40. // 重试连接
  41. const retryConnection = () => {
  42.   if (connectionTimeout) {
  43.     clearTimeout(connectionTimeout); // 清除连接超时计时器
  44.   }
  45.   if (ws) {
  46.     ws.close(); // 关闭现有 WebSocket 连接
  47.   }
  48.   if (player) {
  49.     player.destroy(); // 销毁播放器实例
  50.     player = null; // 重置播放器实例
  51.   }
  52.   initVideo(); // 重新初始化视频连接
  53. };
  54. // 初始化视频连接
  55. const initVideo = () => {
  56.   try {
  57.     const streamUrl = 'ws://localhost:8080'; // WebSocket 服务器地址
  58.     status.value = '正在连接...'; // 更新状态信息
  59.     // 设置连接超时
  60.     connectionTimeout = setTimeout(() => {
  61.       if (ws && ws.readyState !== WebSocket.OPEN) {
  62.         ws.close(); // 如果连接未打开,关闭连接
  63.         recordError('连接超时'); // 记录连接超时错误
  64.       }
  65.     }, 5000);
  66.     // 创建WebSocket连接
  67.     ws = new WebSocket(streamUrl);
  68.     ws.binaryType = 'arraybuffer'; // 设置数据类型为二进制数组
  69.     let dataReceived = false; // 标记是否收到数据
  70.     let dataTimer = null; // 数据接收超时计时器
  71.     ws.onopen = () => {
  72.       clearTimeout(connectionTimeout); // 清除连接超时计时器
  73.       status.value = '连接成功,等待数据...'; // 更新状态信息
  74.       // 设置数据接收超时
  75.       dataTimer = setTimeout(() => {
  76.         if (!dataReceived) {
  77.           recordError('连接成功但未收到有效数据'); // 记录未收到数据错误
  78.           ws.close(); // 关闭连接
  79.         }
  80.       }, 8000);
  81.     };
  82.     ws.onmessage = (event) => {
  83.       // 检查数据包是否有效
  84.       if (event.data instanceof ArrayBuffer) {
  85.         dataReceived = true; // 标记已收到数据
  86.         clearTimeout(dataTimer); // 清除数据接收超时计时器
  87.         if (event.data.byteLength > 0) {
  88.           console.log('收到数据包:', event.data.byteLength, new Date().toLocaleTimeString());
  89.           // 如果播放器未初始化,初始化播放器
  90.           if (!player) {
  91.             status.value = '收到数据,初始化播放器...'; // 更新状态信息
  92.             startPlayer(streamUrl); // 初始化播放器
  93.           }
  94.           // 尝试手动处理数据
  95.           try {
  96.             if (player && player.source) {
  97.               const data = new Uint8Array(event.data);
  98.               // 检查数据是否为MPEG-TS格式
  99.               if (data[0] === 0x47) { // MPEG-TS同步字节
  100.                 player.source.write(data); // 写入数据到播放器
  101.               } else {
  102.                 console.warn('收到非MPEG-TS格式数据'); // 警告非预期格式数据
  103.               }
  104.             }
  105.           } catch (e) {
  106.             console.error('数据处理错误:', e); // 输出数据处理错误信息
  107.           }
  108.         }
  109.       }
  110.     };
  111.     // 其他事件处理保持不变...
  112.   } catch (error) {
  113.     // 错误处理保持不变...
  114.   }
  115. };
  116. // 初始化播放器
  117. const startPlayer = (streamUrl) => {
  118.   try {
  119.     player = new JSMpeg.Player(streamUrl, {
  120.       canvas: videoCanvas.value, // 指定画布元素
  121.       autoplay: true, // 自动播放
  122.       audio: true,  // 启用音频
  123.       audioBufferSize: 512 * 1024,  // 音频缓冲区大小
  124.       loop: true, // 循环播放
  125.       videoBufferSize: 1024 * 1024 * 2, // 视频缓冲区大小
  126.       onSourceEstablished: () => {
  127.         console.log('视频源已建立'); // 输出视频源建立信息
  128.         status.value = '视频播放中'; // 更新状态信息
  129.       },
  130.       onSourceCompleted: () => {
  131.         console.log('视频源已完成'); // 输出视频源完成信息
  132.       },
  133.       onStalled: () => {
  134.         console.log('播放停滞'); // 输出播放停滞信息
  135.       }
  136.     });
  137.   } catch (error) {
  138.     recordError(`播放器初始化失败: ${error.message}`); // 记录播放器初始化错误
  139.     console.error('播放器错误:', error); // 输出播放器错误信息
  140.   }
  141. };
  142. // 组件挂载时初始化视频连接
  143. onMounted(() => {
  144.   initVideo();
  145. });
  146. // 组件卸载前清理资源
  147. onBeforeUnmount(() => {
  148.   if (connectionTimeout) {
  149.     clearTimeout(connectionTimeout); // 清除连接超时计时器
  150.   }
  151.   ws?.close(); // 关闭 WebSocket 连接
  152.   player?.destroy(); // 销毁播放器实例
  153. });
  154. </script>
  155. <style scoped>
  156. .video-container {
  157.   width: 100%;
  158.   height: 100%;
  159.   position: relative; /* 设置容器为相对定位 */
  160. }
  161. canvas {
  162.   width: 100%;
  163.   height: 100%;
  164.   background: #000; /* 设置画布背景为黑色 */
  165. }
  166. .status {
  167.   position: absolute;
  168.   top: 50%;
  169.   left: 50%;
  170.   transform: translate(-50%, -50%); /* 居中显示状态信息 */
  171.   background: rgba(0,0,0,0.7); /* 半透明背景 */
  172.   color: #fff; /* 白色文字 */
  173.   padding: 12px 20px; /* 内边距 */
  174.   border-radius: 4px; /* 圆角边框 */
  175.   text-align: center; /* 文本居中 */
  176. }
  177. .error-status {
  178.   color: #ff6b6b; /* 错误状态文字颜色 */
  179. }
  180. .retry-btn {
  181.   margin-top: 10px; /* 按钮顶部外边距 */
  182.   padding: 5px 10px; /* 按钮内边距 */
  183.   background: #3498db; /* 按钮背景颜色 */
  184.   border: none; /* 无边框 */
  185.   border-radius: 4px; /* 圆角边框 */
  186.   color: white; /* 按钮文字颜色 */
  187.   cursor: pointer; /* 鼠标指针样式 */
  188. }
  189. .retry-btn:hover {
  190.   background: #2980b9; /* 按钮悬停背景颜色 */
  191. }
  192. </style>
复制代码
2.3播放

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

3.附件

 3.1视频链接

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

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天空闲话

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表