# websocket-video **Repository Path**: morris-mao/websocket-video ## Basic Information - **Project Name**: websocket-video - **Description**: ffmpeg 将视频转成fragment mp4 通过websocket 传给浏览器进行视频播放 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 3 - **Created**: 2023-06-08 - **Last Updated**: 2024-01-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ffmpeg转码流通过websocket传给浏览器播放(这里写自定义目录标题) # 背景 浏览器支持的视频格式有限。而ffmpeg有很强的格式转换功能。那我们能不能通过ffmpeg把不支持的视频转成浏览器可以支持的视频呢? # 方案 要实现以上方案要解决几个问题: 1. 如何实时获取ffmpeg的转换后的数据 2. 如何将数据实时传给浏览器 3. 浏览器收到数据后如何播放 # 如何实时获取ffmpeg的转换后的数据 正常我们通过 ffmpeg 是可以将一个视频文件转换成另一种视频文件。 如下是将一个其他编码的mp4文件编码格式进行转换 ```shell ffmpeg -i 1.mp4 -c:v libx264 -f mp4 mp4-264.mp4 ``` 但我们怎么通过程序调用ffmpeg来获取转换后的数据呢。 ffmpeg 提供了一个pipe的功能可以将数据传给标准输入输出 nodejs 可以通过spawn来调用ffmpeg获取输出流 ```javascript let ffmpeg = spawn('ffmpeg', //ffmpeg 自己从官网上载,改成自己路径,或配置成全局变量 [ '-i', 'html/1.mp4', '-c:v', 'libx264', // 如果原视频已经是264格式可以不转 '-movflags', 'frag_keyframe+empty_moov+default_base_moof', // 转成fragment mp4 '-f', 'mp4', 'pipe:1' // 输出流 ]); ffmpeg.stdout.on('data', chunk=>{ console.log("data") }) ``` # 如何将数据实时传给浏览器 现在转换后的视频数据拿到了,怎么将数据传给浏览器呢。 浏览器实时数据的传输一般想到的是用websocket. 我这边想到的是用两条websocket. 一条传输控制命令,一条传输视频数据。 websocket 支持二进制数据传输和文本数据传输。这里控制命令用文本类型。视频数据用的是二进制数据类型。 ```javascript ffmpeg.stdout.on('data', chunk=>{ console.log("data") //转码后的数据用二制制传输 ws.send(chunk, {binary: true, mask: false}); }) ``` # 浏览器收到数据后如何播放 视频流的播放这里用到了MediaSource. https://developer.mozilla.org/en-US/docs/Web/API/MediaSource ```javascript let video = document.querySelector('video'); let mediasource = new MediaSource(); video.src = URL.createObjectURL(mediasource); ``` 可将websocket接收到数据传给Mediasource ```javascript ws.onmessage = (event) => { sourceBuffer.appendBuffer(event.data); }; ``` 由于播放速度和转码的数据不一致,所以要根据播放进度来控制进度。这里用到刚提到的控制websocket, 通过监听播放进度和缓存的时间来判断是否要从后端拉取数据. ```javascript function getData() { controlWs.send("get") } // updateend会在往MediaSource放数据后调用, 可以读取到缓存时间 sourceBuffer.addEventListener('updateend', () => { let buffered = sourceBuffer.buffered for (let i = 0; i < buffered.length; i++) { bufferTime = buffered.end(i); // 缓存了多少时间的数据 } /** *readyState * 0 = HAVE_NOTHING - 没有关于音频/视频是否就绪的信息 1 = HAVE_METADATA - 关于音频/视频就绪的元数据 2 = HAVE_CURRENT_DATA - 关于当前播放位置的数据是可用的,但没有足够的数据来播放下一帧/毫秒 3 = HAVE_FUTURE_DATA - 当前及至少下一帧的数据是可用的 4 = HAVE_ENOUGH_DATA - 可用数据足以开始播放 * */ if (video.readyState !== 4) {// 数据不够拉数据 getData(); } }); video.addEventListener('timeupdate', (e)=>{ console.log("timeupdate: " , bufferTime , video.currentTime, video.readyState); // 缓存10秒 if (bufferTime - video.currentTime < 10) { getData() } }) ```