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

news2024/11/20 10:40:18

心血历程

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

        var 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 = mediaStream
                    video.onloadedmetadata = function (e) {
                        video.play();
                    };
                  //采集视频
                    startRecord(mediaStream);
                })
                .catch(function (err) {
                    console.log(err.name + ': ' + err.message);
                }); // 总是在最后检查错误
        };

        var timer = null

        function 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.coloradmin.cn/o/851582.html

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

相关文章

百川智能发布首个530亿参数闭源大模型,今年追上GPT-3.5

4月官宣创业&#xff0c;6月15日发布第一款7B开源模型&#xff0c;7月11日发布第二款13B、130亿参数开源模型。 平均保持2个月一个版本发布速度&#xff0c;8月8日&#xff0c;百川智能发布了创业以来的首个530亿参数闭源大模型——Baichuan-53B&#xff08;以下简称“53B”&a…

运维作业5

一.基于 CS 7 构建 LVS-DR 群集。 1.lvs安装ipvsadm [rootnode ~]# yum install -y ipvsadm 2.配置lvs虚拟ip&#xff08;vip&#xff09; [rootnode ~]# ifconfig ens32:200 192.168.72.200 netmask 255.255.255.0 up 客户端测试&#xff1a; 3.在两台rs上安装httpd 4.两台rs建…

SpringBoot3之Web编程

标签&#xff1a;Rest.拦截器.swagger.测试; 一、简介 基于web包的依赖&#xff0c;SpringBoot可以快速启动一个web容器&#xff0c;简化项目的开发&#xff1b; 在web开发中又涉及如下几个功能点&#xff1a; 拦截器&#xff1a;可以让接口被访问之前&#xff0c;将请求拦截…

Oracle 聚合拼接的常用方式

Oracle常用函数&#xff1a;Oracle Database SQL Language Reference, 12c Release 2 (12.2) 1 listagg LISTAGG Syntax Description of the illustration listagg.eps (listagg_overflow_clause::, order_by_clause::, query_partition_clause::) listagg_overflow_claus…

【C++基础(九)】C++内存管理--new一个对象出来

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; C内存管理 1. 前言2. new2.1 new的使用方法2.2 …

RestTemplate发送请求携带文件

在工作上遇到这样一个需求&#xff0c;就是调用我们公司的AI平台&#xff0c;将图片文件发送给他们&#xff0c;他们进行解析然后返回解析结果。 首先用python进行调用一次&#xff0c;发送捕获的接口是这样的&#xff1a; 那么用java代码该如何组装这个请求发送过去呢&#xf…

MIT6.006 课程笔记P1 - 思考如何进行 PeakFinding

文章目录 寻找峰值 peak暴力算法分而治之从1D到2D朴素算法Attemp#2 寻找峰值 peak 给出一个数组 a b c d e f g h i 并给予数字 index 1 2 3 4 5 6 7 8 9 那么如果某个数字是 peak &#xff0c;那么他将 大于等于左边的数 且 大于等于右边的数 或者 a > b 这里的 a 也是峰值…

Pytest测试框架4

目录&#xff1a; pytest配置文件pytest插件pytest测试用例执行顺序自定义pytest-orderingpytest测试用例并行运行与分布式运行pytest内置插件hook体系pytest插件开发 1.pytest配置文件 pytest.ini 是什么&#xff1f; pytest.ini 是 pytest 的配置文件可以修改 pytest 的…

Sql server还原失败(数据库正在使用,无法获得对数据库的独占访问权)

一.Sql server还原失败(数据库正在使用,无法获得对数据库的独占访问权) 本次测试使用数据库实例SqlServer2008r2版 错误详细&#xff1a; 标题: Microsoft SQL Server Management Studio ------------------------------ 还原数据库“Mvc_HNHZ”时失败。 (Microsoft.SqlServer.…

Java笔记(三十一):MySQL(中)--查询DQL、单表查询、函数、多表查询、查询结果合并

六、查询DQL⭐⭐⭐⭐⭐&#xff08;SELECT&#xff09; 0、查询书写顺序&执行顺序 当selcet中有聚合函数时&#xff0c;看起来是 select 先执行&#xff0c;因为后面having可以用到selcet聚合函数后面的别名 但实际上还是select 后执行&#xff0c;如果不是聚合函数或者其…

C#,数值计算——基于模拟退火的极小化问题单纯形(下山)算法的计算方法与C#源程序

1 模拟退火 模拟退火算法其实是一个类似于仿生学的算法&#xff0c;模仿的就是物理退火的过程。 我们炼钢的时候&#xff0c;如果我们急速冷凝&#xff0c;这时候的状态是不稳定的&#xff0c;原子间杂乱无章的排序&#xff0c;能量很高。而如果我们让钢水慢慢冷凝&#xff0c…

PowerDesigner使用实践

PowerDesigner使用实践 一、前言 1.简介 PowerDesigner DataArchitect 是业界领先的数据建模工具。 它提供了一种模型驱动的方法来增强业务和 IT 的能力并使其保持一致。 PowerDesigner 使企业能够更轻松地可视化、分析和操作元数据&#xff0c;以实现有效的企业信息架构。 …

通向架构师的道路之weblogic的集群与配置

一、Weblogic的集群 还记得我们在第五天教程中讲到的关于Tomcat的集群吗? 两个tomcat做node即tomcat1, tomcat2&#xff0c;使用Apache HttpServer做请求派发。 现在看看WebLogic的集群吧&#xff0c;其实也差不多。 区别在于&#xff1a; Tomcat的集群的实现为两个物理上…

Kotin协程的基础

协程是什么&#xff1f; 就是同步方式去编写异步执行的代码。协程是依赖于线程&#xff0c;但是协程挂起的时候不需要阻塞线程。几乎没有任何代价。 协程的创建 一个线程可以创建多个协程。协程的创建是通过CoroutineScope创建&#xff0c;协程的启动方式有三种。 runBlockin…

湘大oj1088 N!:求阶乘 数据太大怎么处理 常规的递归求阶乘

一、链接 N&#xff01; 二、题目 Description 请求N&#xff01;&#xff08;N<10000&#xff09;&#xff0c;输出结果对10007取余 输入 每行一个整数n&#xff0c;遇到-1结束。 输出 每行一个整数&#xff0c;为对应n的运算结果。 Sample Input 1 2 -1 Sample Outp…

【数据结构与算法】左叶子之和

左叶子之和 递归三部曲 确定递归函数的参数和返回值 int sumOfLeftLeaves(TreeNode* root)确定终止条件 遍历遇到空节点 if (root NULL) return 0;单层的递归逻辑 遍历顺序&#xff1a;左右中&#xff08;后序遍历&#xff09; 选择后序遍历的原因&#xff1a;要通过递归函…

【Linux操作系统】深入了解系统编程gdb调试工具

在软件开发过程中&#xff0c;调试是一个非常重要的步骤。无论是在开发新的软件还是维护现有的代码&#xff0c;调试都是解决问题的关键。对于Linux开发者来说&#xff0c;GDB是一个非常有用的调试工具。在本文中&#xff0c;我们将探讨Linux中使用GDB进行调试的方法和技巧。 …

C# OpenCvSharp 读取rtsp流

效果 项目 代码 using OpenCvSharp; using OpenCvSharp.Extensions; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using Syste…

HTTPS安全通信

HTTPS,TLS/SSL Hyper Text Transfer Protocol over Secure Socket Layer,安全的超文本传输协议,网景公式设计了SSL(Secure Sockets Layer)协议用于对Http协议传输的数据进行加密,保证会话过程中的安全性。 使用TCP端口默认为443 TLS:(Transport Layer Security,传输层…

30、Flink SQL之SQL 客户端(通过kafka和filesystem的例子介绍了配置文件使用-表、视图等)

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…