websocket+node实现直播(弱鸡版)

news/2025/2/2 3:43:34/

心血历程

这部分主要是写在写这些的时候遇到的问题以及换思路的过程,可以之间看正文
在之前我也写过直播功能,并且与websocket相结合实现了直播弹幕。只不过直播是使用的腾讯云的,而不是手写的直播推流拉流,这次又有一个新的项目,和直播息息相关(直播自习平台),所以就想着使用原生的写直播推流、拉流,搞了两天,实现了一个基础版本的直播。
这两天搞直播也挺难受的,原本我和后端想的和之前写项目一样,一个拉流地址,一个推流地址,不过后端写好之后也用软件测试了一下可行,兴高采烈的给我说写好了,既然后端接口写好了,剩下的就是前端去交互了,经过一段时间的交互,拉流报错,推流也报错,经过一番百度原来是浏览器不支持rtmp协议,只能另寻它法,最后想到使用websocket去连接,我把采集到的音视频流数据传输给后端,后端在传输给我,我再统一处理,写好之后发现了一个小问题就是每次推流与拉流之间都会闪一下,原本想着使用mediaSource视频流去搞一下,不过最后发现视频格式对于不同浏览器的要求是不一样的,而且浏览器对于视频格式要求也比较严,最后就不搞了。经过与后端的商量,最终决定项目中写两版直播,一版是原生写的,一版是使用腾讯云直播写的。下面就进入正文吧。

直播常见协议

  1. RTMP
    RTMP(Real-Time Messaging Protocol)是一种用于音频、视频和数据传输的协议。它最初由Adobe开发,用于在Flash播放器和媒体服务器之间进行实时通信。RTMP通过建立持久的连接来传输流媒体数据,支持实时的流媒体传输和即时通信。
  2. HTTP-FLV
    HTTP-FLV(HTTP-Flash Video)是一种基于HTTP协议的流媒体传输协议。它是由阿里巴巴开发的,用于在Web浏览器中播放Flash视频。HTTP-FLV通过HTTP协议传输FLV(Flash Video)格式的视频,可以实现低延迟的视频直播和点播。
  3. HLS
    HLS(HTTP Live Streaming)是一种由苹果公司开发的流媒体传输协议。它通过将视频和音频切片成小的TS(Transport Stream)文件,并使用HTTP协议传输这些文件,实现了在不同网络环境下的自适应流媒体传输。HLS支持多种编码格式和分辨率的视频,可以在iOS设备和Web浏览器上播放。
    总的来说RTMP是一种实时的流媒体传输协议,适用于实时通信和互动性较高的应用;HTTP-FLV是一种基于HTTP协议的低延迟流媒体传输协议,适用于Web浏览器中的Flash视频播放;HLS是一种适用于不同网络环境下的自适应流媒体传输协议,适用于iOS设备和Web浏览器的播放。
    直播基本流程
    在这里插入图片描述

看上图可知直播分为以下三个步骤:

  1. 视频/音频采集(推流)
  2. 流媒体服务器(用于推流/拉流的中转服务器)
  3. 播放(拉流)
    对于前端来说推流以及拉流都是我们所要操心的事,而后端要做的是中转服务器作用

直播代码

经过以上的说明,大家对直播有了一个简单的了解,那么我们之间上代码
node端(需要下载ws第三方库)

const WebSocket = require("ws");const wss = new WebSocket.Server({port: 3000,
});wss.on("connection", (ws) => {ws.on("message", (msg) => {wss.clients.forEach((ws) => {ws.send(msg);});});
});

node端我们做的很简洁,就是把前端发给我们的数据再发给前端
那么就显得数据很重要了,既然是直播那么我们的数据就是我们直播的数据(及视频,音频),另外需要补充两点就是我们传输的数据一般比较大,websocket的传输虽然理论上是无限,但是为了不明显的卡顿,我们前端传输数据采用分段上传。另外就是为了保证数据的准确,我们传输的是二进制数据,这样就能保证了观看与直播的相对无误差。
在写前端代码之前,我来解释一下为什么这个直播是弱鸡版,我们实现的是推流是前端录视频一段时间之后发给后端,后端再转接给前端,循环往复,由此就实现了直播功能,这也是推流拉流时屏幕闪的原因。不过更多的是让大家了解一下大致的流程,及一个可行的方法。
前端推流代码

<!--用以预览--><video></video><button onclick="onStartRecord()">开始共享</button><button onclick="onDownload()">手动推流</button><button onclick="close()">关闭</button><script>var socket = new WebSocket("ws://127.0.0.1:3000");socket.onopen = () => {console.log("WebSocket 连接成功");};socket.onclose = (event) => {console.log("WebSocket 连接关闭", event);};socket.onerror = (error) => {console.error("发生错误", error);};socket.onmessage = (event) => {console.log("收到信息", event.data);};// 想要获取一个最接近 1280x720 的相机分辨率var recordedChunks = [];var streamvar onStartRecord = function () {navigator.mediaDevices.getDisplayMedia({video: {mediaSource: "screen",},audio: true}).then(async function (mediaStream) {const audioTrack = await navigator.mediaDevices.getUserMedia({ audio: true });// 添加声音轨道await mediaStream.addTrack(audioTrack.getAudioTracks()[0]);var video = document.querySelector('video');//本地预览视频video.srcObject = mediaStream;stream = mediaStreamvideo.onloadedmetadata = function (e) {video.play();};//采集视频startRecord(mediaStream);}).catch(function (err) {console.log(err.name + ': ' + err.message);}); // 总是在最后检查错误};var timer = nullfunction startRecord(stream) {recordedChunks = []var options = { mimeType: 'video/webm; codecs=vp9' };mediaRecorder = new MediaRecorder(stream, options);mediaRecorder.ondataavailable = handleDataAvailable;mediaRecorder.start();clearInterval(timer)//每隔5秒自动推流timer = setInterval(async () => {await onDownload()await startRecord(stream)}, 5000)}//onDownloadClick,调用stop会停止并会触发ondataavailable,相应下载逻辑在回调中完成var onDownload = function () {mediaRecorder.stop();};function handleDataAvailable(event) {if (event.data.size > 0) {recordedChunks.push(event.data);download();} else {// ...}}const CHUNK_SIZE = 1024 * 5; // 每个数据块的大小为 16KB//分片上传function sliceBlob(blob) {const chunks = [];let offset = 0;while (offset < blob.size) {const chunk = blob.slice(offset, offset + CHUNK_SIZE);chunks.push(chunk);offset += CHUNK_SIZE;}return chunks;}async function download() {var blob = new Blob(recordedChunks, {type: 'video/webm',});console.log(blob);const binaryChunks = sliceBlob(blob);for (const chunk of binaryChunks) {console.log(chunk);await socket.send(chunk);}}//关闭直播function close() {stream.getTracks().map((track) => track.stop());}</script>

前端拉流代码

 <video src="" id="mv" controls></video><script>const mv = document.getElementById('mv')var socket = new WebSocket("ws://127.0.0.1:3000");socket.onopen = () => {console.log("WebSocket 连接成功");};socket.onclose = (event) => {console.log("WebSocket 连接关闭", event);};socket.onerror = (error) => {console.error("WebSocket Error:", error);};let videoData = []socket.onmessage = async (event) => {if (event.data.size < 1024 * 5) {//合并数据进行渲染const blob = new Blob(videoData, { type: 'video/webm' })mv.src = URL.createObjectURL(blob);videoData = []mv.play()} else {// 接收数据videoData.push(event.data)}}</script>

注意事项:
我们使用blob时要指定格式,以此方便我们播放的时候浏览器解码

总结

之后遇到项目的问题再继续分享吧。之后也正式开始写项目了。我们一同进步。


http://www.ppmy.cn/news/1008737.html

相关文章

SQL-每日一题【1164. 指定日期的产品价格】

题目 产品数据表: Products 写一段 SQL来查找在 2019-08-16 时全部产品的价格&#xff0c;假设所有产品在修改前的价格都是 10 。 以 任意顺序 返回结果表。 查询结果格式如下例所示。 示例 1: 解题思路 1.题目要求我们查找在 2019-08-16 时全部产品的价格&#xff0c;假设所…

导出LLaMA等LLM模型为onnx

通过onnx模型可以在支持onnx推理的推理引擎上进行推理&#xff0c;从而可以将LLM部署在更加广泛的平台上面。此外还可以具有避免pytorch依赖&#xff0c;获得更好的性能等优势。 这篇博客&#xff08;大模型LLaMa及周边项目&#xff08;二&#xff09; - 知乎&#xff09;进行…

Pytohn将matplotlib嵌入到tkinter中

文章目录 matplotlib窗口组成tkinter布局嵌入图像 matplotlib窗口组成 tkinter是Python标准库中自带的GUI工具&#xff0c;使用十分方便&#xff0c;如能将matplotlib嵌入到tkinter中&#xff0c;就可以做出相对专业的数据展示系统&#xff0c;很有竞争力。 在具体实现之前&a…

使用PHP和Redis实现简单秒杀功能

安装Redis 首先&#xff0c;需要在服务器上安装Redis。如果使用Linux系统&#xff0c;可以使用命令行安装。如果使用Windows系统&#xff0c;可以下载并安装Redis二进制文件。 创建Redis连接 在PHP中&#xff0c;可以使用Redis扩展来连接Redis服务器。需要在PHP文件中包含Re…

自问自答----WEB篇

目录 1、https和http协议的端口 2、http协议的版本 3、linux中查看报文的方法 3.1 curl www.baidu.com -v 3.2 wget --debug www.baidu.com 4、http有哪些请求方法 5、http的请求头 6、http响应头​编辑 7、状态码有哪些 8、uri和url 9、静态&#xff1f;动态&#x…

频繁设置CGroup触发linux内核bug导致CGroup running task不调度

2. 问题 2.1 触发bug code(code较长&#xff0c;请展开代码) 2.1.1 code View Code 2.1.2 编译 g -stdc11 -lpthread trigger_cgroup_timer_inactive.cpp -o inactive_timer 2.1.3 在CentOS7.0~7.5的系统上执行程序 ./inactive_timer 100000 10000 2.1.4 上述代码主要干了2…

跨cpu架构部署容器技术点:怎么将容器启动时的1号进程挂载到systemctl

在某些时候&#xff0c;我们除去容器内本身的打包好的程序&#xff0c;我们还会需要一些依赖服务&#xff0c;这些服务通常是挂载在一号进程上通过 systemctl这个控制器服务来维护。 但因为docker的最小工作原则&#xff0c;info进程&#xff0c;或者说 systemctl控制程序&…

1、二分搜索法

二分搜索法 易混点&#xff1a; 1.left是小于right还是小于等于right 2.更新的时候是更新到mildle还是midle-1 3.区间是 [left,right] 还是 [left,right)&#xff0c;对区间的定义不同会影响到边界的处理 左闭右闭写法 伪代码 left 0; right numsize - 1; while(left &l…