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

心血历程

这部分主要是写在写这些的时候遇到的问题以及换思路的过程,可以之间看正文
在之前我也写过直播功能,并且与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.mzph.cn/news/30179.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【D3S】集成smart-doc并同步配置到Torna

目录 一、引言二、maven插件三、smart-doc.json配置四、smart-doc-maven-plugin相关命令五、推送文档到Torna六、通过Maven Profile简化构建 一、引言 D3S&#xff08;DDD with SpringBoot&#xff09;为本作者使用DDD过程中开发的框架&#xff0c;目前已可公开查看源码&#…

自从学了C++之后,小雅兰就有对象了!!!(类与对象)(中)——“C++”

各位CSDN的uu们好呀&#xff0c;好久没有更新小雅兰的C专栏啦&#xff0c;话不多说&#xff0c;让我们进入类和对象的世界吧&#xff01;&#xff01;&#xff01; 类的6个默认成员函数 构造函数 析构函数 拷贝构造函数 类的6个默认成员函数 如果一个类中什么成员都没有&am…

el-select与el-tree结合使用,实现select框下拉使用树形结构选择数据

使用el-select与el-tree&#xff0c;实现如下效果&#xff0c; 代码如下&#xff1a; 注意点&#xff1a;搜索input框的代码一点放在option上面&#xff0c;不要放在option里面&#xff0c;否则一点击搜索框&#xff0c;下拉框就会收起来&#xff0c;不能使用。 <el-select…

【深度学习注意力机制系列】—— SKNet注意力机制(附pytorch实现)

SKNet&#xff08;Selective Kernel Network&#xff09;是一种用于图像分类和目标检测任务的深度神经网络架构&#xff0c;其核心创新是引入了选择性的多尺度卷积核&#xff08;Selective Kernel&#xff09;以及一种新颖的注意力机制&#xff0c;从而在不增加网络复杂性的情况…

工业无线技术应用-无线控制斗轮机启停、故障等开关信号

斗轮堆取料机是一种对散料进行连续堆取作业的高效装卸大型机械,被广泛使用于火力发电厂和炼焦厂的输煤系统中。目前对斗轮机的技改主要为将斗轮机的部分程控信号改为无线传输&#xff0c;取代卷筒电机和电缆的应用。 多数情况下都是利用无线通讯做媒介&#xff0c;让工作人员通…

第48节:cesium 面交集计算(含源码+视频)

结果示例: 完整源码: <template><div class="viewer"><vc-viewer @ready="ready" :logo="false"><vc-navigation

docker版jxTMS使用指南:使用jxTMS采集数据之一

本文讲解了如何jxTMS的数据采集与处理框架并介绍了如何用来采集数据&#xff0c;整个系列的文章请查看&#xff1a;docker版jxTMS使用指南&#xff1a;4.4版升级内容 docker版本的使用&#xff0c;请查看&#xff1a;docker版jxTMS使用指南 4.0版jxTMS的说明&#xff0c;请查…

Unity之ShaderGraph 节点介绍 Utility节点

Utility 逻辑All&#xff08;所有分量都不为零&#xff0c;返回 true&#xff09;Any&#xff08;任何分量不为零&#xff0c;返回 true&#xff09;And&#xff08;A 和 B 均为 true&#xff09;Branch&#xff08;动态分支&#xff09;Comparison&#xff08;两个输入值 A 和…

15 款最佳建筑渲染软件,适用于 Windows、macOS,免费和付费版本

3D 建模和渲染在建筑行业的各种项目的推广和营销中发挥着非常重要的作用。建筑公司使用 3D 建模和渲染、3D 建筑动画和演练来展示他们的设计。房地产效果图帮助代理商让客户清楚地了解建筑设计、纹理、灯光效果和环境情况。这是非常有价值的&#xff0c;并且在销售设计时提供了…

解决MAC M1处理器运行Android protoc时出现的错误

Protobuf是Google开发的一种新的结构化数据存储格式&#xff0c;一般用于结构化数据的序列化&#xff0c;也就是我们常说的数据序列化。这个序列化协议非常轻量级和高效&#xff0c;并且是跨平台的。目前&#xff0c;它支持多种主流语言&#xff0c;比传统的XML、JSON等方法更具…

python采集淘宝整店商品 json格式

竞争优势&#xff1a;通过采集淘宝整店商品&#xff0c;可以获取到同一行业或同一类别的竞争对手的商品信息。这使得你可以更好地了解市场上的产品&#xff0c;了解竞争对手的定价、销售策略和产品特点&#xff0c;从而更好地制定自己的营销策略和定价策略。在竞争激烈的市场中…

类和对象的学习

类和对象说明 类的属性和方法 类的入门案例 //类名 public class school {//属性String name; //名称int jsNumber; //教室数目int jfNumber;//机房数目//方法public void show(){System.out.println("名称: " name "教室数目" jsNumber " , 机房数…

设计模式——设计模式以及六大原则概述

设计模式代表有经验的面向对象软件开发人员使用的最佳实践。 设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。 这些解决方案是由许多软件开发人员在相当长的时间内通过试错获得的。 什么是 GOF&#xff08;四人帮&#xff0c;全拼 Gang of Four&#xff09…

Android 面试重点之Framework (Handler篇)

近期在网上看到不少Android 开发分享的面试经验&#xff0c;我发现基本每个面经中多多少少都有Framework 底层原理的影子。它也是Android 开发中最重要的一个部分&#xff0c;面试官一般会通过 Framework底层中的一些逻辑原理由浅入深进行提问&#xff0c;来评估应聘者的真实水…

[每周一更]-(第57期):用Docker、Docker-compose部署一个完整的前后端go+vue分离项目

文章目录 1.参考项目2.技能点3.GO的Dockerfile配置后端的结构如图Dockerfile先手动docker调试服务是否可以启动报错 4.Vue的Dockerfile配置前端的结构如图nginx_docker.confDockerfile构建 5.docker-compose 整合前后端docker-compose.yml错误记录&#xff08;1&#xff09;ip端…

Android复习(Android基础-四大组件)——Service与Activity通信

我们前面学会了启动和停止服务的方法&#xff0c;但是服务虽然是在活动里启动的&#xff0c;但是启动服务之后&#xff0c;活动与服务之间基本没什么关系了。正常情况&#xff0c;我们在Activity里调用startService()方法启动MyService这个服务&#xff0c;然后MyService的onCr…

Games101学习笔记 - MVP矩阵

MV矩阵&#xff08;模型视图变换&#xff09; 目的&#xff0c;把摄像机通过变换移动的世界坐标远点&#xff0c;并且朝向与Z轴的负方向相同。这个变换就是模型试图变换。 因为移动了相机&#xff0c;如果想保持正确的渲染的话&#xff0c;那么对应的物体需要要和相机保持相对…

day23-113. 路径总和ii

113. 路径总和ii 力扣题目链接(opens new window) 给定一个二叉树和一个目标和&#xff0c;找到所有从根节点到叶子节点路径总和等于给定目标和的路径。 说明: 叶子节点是指没有子节点的节点。 示例: 给定如下二叉树&#xff0c;以及目标和 sum 22&#xff0c; 思路 利用…

机器学习(十八):Bagging和随机森林

全文共10000余字&#xff0c;预计阅读时间约30~40分钟 | 满满干货(附数据及代码)&#xff0c;建议收藏&#xff01; 本文目标&#xff1a;理解什么是集成学习&#xff0c;明确Bagging算法的过程&#xff0c;熟悉随机森林算法的原理及其在Sklearn中的各参数定义和使用方法 代码…

【Spring】Spring中的设计模式

文章目录 责任链模式工厂模式适配器模式代理模式模版方法观察者模式构造器模式 责任链模式 Spring中的Aop的通知调用会使用责任链模式责任链模式介绍 角色&#xff1a;抽象处理者&#xff08;Handler&#xff09;具体处理者&#xff08;ConcreteHandler1&#xff09;客户类角…