使用 WebRtcStreamer 实现实时视频流播放

WebRtcStreamer 是一个基于 WebRTC 协议的轻量级开源工具,可以在浏览器中直接播放 RTSP 视频流。它利用 WebRTC 的强大功能,提供低延迟的视频流播放体验,非常适合实时监控和其他视频流应用场景。

本文将介绍如何在Vue.js项目中使用 WebRtcStreamer 实现实时视频流播放,并分享相关的代码示例。

注意:只支持H264格式

流媒体方式文章
使用 Vue 和 flv.js 实现流媒体视频播放:完整教程
VUE项目中优雅使用EasyPlayer实时播放摄像头多种格式视频使用版本信息为5.xxxx

实现步骤

  • 安装和配置 WebRtcStreamer 服务端
    要使用 WebRtcStreamer,需要先在服务器上部署其服务端。以下是基本的安装步骤:

  • 从 WebRtcStreamer 官方仓库 下载代码。
    在这里插入图片描述
    启动命令
    在这里插入图片描述
    或者双击exe程序
    在这里插入图片描述
    服务启动后,默认会监听 8000 端口,访问 http://<server_ip>:8000 可查看状态。
    在这里插入图片描述
    更改默认端口命令:webrtc-streamer.exe -o -H 0.0.0.0:9527

2.集成到vue中
webRtcStreamer.js 不需要在html文件中引入webRtcStreamer相关代码

/*** @constructor* @param {string} videoElement -  dom ID* @param {string} srvurl -  WebRTC 流媒体服务器的 URL(默认为当前页面地址)*/
class WebRtcStreamer {constructor(videoElement, srvurl) {if (typeof videoElement === 'string') {this.videoElement = document.getElementById(videoElement);} else {this.videoElement = videoElement;}this.srvurl =srvurl || `${location.protocol}//${window.location.hostname}:${window.location.port}`;this.pc = null; // PeerConnection 实例// 媒体约束条件this.mediaConstraints = {offerToReceiveAudio: true,offerToReceiveVideo: true,};this.iceServers = null; // ICE 服务器配置this.earlyCandidates = []; // 提前收集的候选者}/*** HTTP 错误处理器* @param {Response} response - HTTP 响应* @throws {Error} 当响应不成功时抛出错误*/_handleHttpErrors(response) {if (!response.ok) {throw Error(response.statusText);}return response;}/*** 连接 WebRTC 视频流到指定的 videoElement* @param {string} videourl - 视频流 URL* @param {string} audiourl - 音频流 URL* @param {string} options - WebRTC 通话的选项* @param {MediaStream} localstream - 本地流* @param {string} prefmime - 优先的 MIME 类型*/connect(videourl, audiourl, options, localstream, prefmime) {this.disconnect();if (!this.iceServers) {console.log('获取 ICE 服务器配置...');fetch(`${this.srvurl}/api/getIceServers`).then(this._handleHttpErrors).then((response) => response.json()).then((response) =>this.onReceiveGetIceServers(response, videourl, audiourl, options, localstream, prefmime),).catch((error) => this.onError(`获取 ICE 服务器错误: ${error}`));} else {this.onReceiveGetIceServers(this.iceServers,videourl,audiourl,options,localstream,prefmime,);}}/*** 断开 WebRTC 视频流,并清空 videoElement 的视频源*/disconnect() {if (this.videoElement?.srcObject) {this.videoElement.srcObject.getTracks().forEach((track) => {track.stop();this.videoElement.srcObject.removeTrack(track);});}if (this.pc) {fetch(`${this.srvurl}/api/hangup?peerid=${this.pc.peerid}`).then(this._handleHttpErrors).catch((error) => this.onError(`hangup ${error}`));try {this.pc.close();} catch (e) {console.log(`Failure close peer connection: ${e}`);}this.pc = null;}}/*** 获取 ICE 服务器配置的回调* @param {Object} iceServers - ICE 服务器配置* @param {string} videourl - 视频流 URL* @param {string} audiourl - 音频流 URL* @param {string} options - WebRTC 通话的选项* @param {MediaStream} stream - 本地流* @param {string} prefmime - 优先的 MIME 类型*/onReceiveGetIceServers(iceServers, videourl, audiourl, options, stream, prefmime) {this.iceServers = iceServers;this.pcConfig = iceServers || { iceServers: [] };try {this.createPeerConnection();let callurl = `${this.srvurl}/api/call?peerid=${this.pc.peerid}&url=${encodeURIComponent(videourl,)}`;if (audiourl) {callurl += `&audiourl=${encodeURIComponent(audiourl)}`;}if (options) {callurl += `&options=${encodeURIComponent(options)}`;}if (stream) {this.pc.addStream(stream);}this.earlyCandidates.length = 0;this.pc.createOffer(this.mediaConstraints).then((sessionDescription) => {// console.log(`创建 Offer: ${JSON.stringify(sessionDescription)}`);if (prefmime !== undefined) {const [prefkind] = prefmime.split('/');const codecs = RTCRtpReceiver.getCapabilities(prefkind).codecs;const preferredCodecs = codecs.filter((codec) => codec.mimeType === prefmime);this.pc.getTransceivers().filter((transceiver) => transceiver.receiver.track.kind === prefkind).forEach((tcvr) => {if (tcvr.setCodecPreferences) {tcvr.setCodecPreferences(preferredCodecs);}});}this.pc.setLocalDescription(sessionDescription).then(() => {fetch(callurl, {method: 'POST',body: JSON.stringify(sessionDescription),}).then(this._handleHttpErrors).then((response) => response.json()).then((response) => this.onReceiveCall(response)).catch((error) => this.onError(`调用错误: ${error}`));}).catch((error) => console.log(`setLocalDescription error: ${JSON.stringify(error)}`));}).catch((error) => console.log(`创建 Offer 失败: ${JSON.stringify(error)}`));} catch (e) {this.disconnect();alert(`连接错误: ${e}`);}}/*** 创建 PeerConnection 实例*/createPeerConnection() {console.log('创建 PeerConnection...');this.pc = new RTCPeerConnection(this.pcConfig);this.pc.peerid = Math.random(); // 生成唯一的 peerid// 监听 ICE 候选者事件this.pc.onicecandidate = (evt) => this.onIceCandidate(evt);this.pc.onaddstream = (evt) => this.onAddStream(evt);this.pc.oniceconnectionstatechange = () => {if (this.videoElement) {if (this.pc.iceConnectionState === 'connected') {this.videoElement.style.opacity = '1.0';} else if (this.pc.iceConnectionState === 'disconnected') {this.videoElement.style.opacity = '0.25';} else if (['failed', 'closed'].includes(this.pc.iceConnectionState)) {this.videoElement.style.opacity = '0.5';} else if (this.pc.iceConnectionState === 'new') {this.getIceCandidate();}}};return this.pc;}onAddStream(event) {console.log(`Remote track added: ${JSON.stringify(event)}`);this.videoElement.srcObject = event.stream;const promise = this.videoElement.play();if (promise !== undefined) {promise.catch((error) => {console.warn(`error: ${error}`);this.videoElement.setAttribute('controls', true);});}}onIceCandidate(event) {if (event.candidate) {if (this.pc.currentRemoteDescription) {this.addIceCandidate(this.pc.peerid, event.candidate);} else {this.earlyCandidates.push(event.candidate);}} else {console.log('End of candidates.');}}/*** 添加 ICE 候选者到 PeerConnection* @param {RTCIceCandidate} candidate - ICE 候选者*/addIceCandidate(peerid, candidate) {fetch(`${this.srvurl}/api/addIceCandidate?peerid=${peerid}`, {method: 'POST',body: JSON.stringify(candidate),}).then(this._handleHttpErrors).catch((error) => this.onError(`addIceCandidate ${error}`));}/*** 处理 WebRTC 通话的响应* @param {Object} message - 来自服务器的响应消息*/onReceiveCall(dataJson) {const descr = new RTCSessionDescription(dataJson);this.pc.setRemoteDescription(descr).then(() => {while (this.earlyCandidates.length) {const candidate = this.earlyCandidates.shift();this.addIceCandidate(this.pc.peerid, candidate);}this.getIceCandidate();}).catch((error) => console.log(`设置描述文件失败: ${JSON.stringify(error)}`));}getIceCandidate() {fetch(`${this.srvurl}/api/getIceCandidate?peerid=${this.pc.peerid}`).then(this._handleHttpErrors).then((response) => response.json()).then((response) => this.onReceiveCandidate(response)).catch((error) => this.onError(`getIceCandidate ${error}`));}onReceiveCandidate(dataJson) {if (dataJson) {dataJson.forEach((candidateData) => {const candidate = new RTCIceCandidate(candidateData);this.pc.addIceCandidate(candidate).catch((error) => console.log(`addIceCandidate error: ${JSON.stringify(error)}`));});}}/*** 错误处理器* @param {string} message - 错误信息*/onError(status) {console.error(`WebRTC 错误: ${status}`);}
}export default WebRtcStreamer;

组件中使用

<template><div><video id="video" controls muted autoplay></video><button @click="startStream">开始播放</button><button @click="stopStream">停止播放</button></div>
</template><script>
import WebRtcStreamer from "@/utils/webRtcStreamer";export default {name: "VideoStreamer",data() {return {webRtcServer: null,};},methods: {startStream() {const srvurl = "127.0.0.1:9527"this.webRtcServer = new WebRtcStreamer("video",`${location.protocol}//${srvurl}`);const videoPath = "stream_name"; // 替换为你的流地址this.webRtcServer.connect(videoPath);},stopStream() {if (this.webRtcServer) {this.webRtcServer.disconnect(); // 销毁}},},
};
</script><style>
video {width: 100%;height: 100%;object-fit: fill;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/489174.html

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

相关文章

建立基于TCP的客户端和服务端

函数介绍&#xff1a; 1.socket() 作用&#xff1a;创建套接字 domain: AF_INET&#xff1a;IPv4 Internet 协议族。AF_INET6&#xff1a;IPv6 Internet 协议族。AF_UNIX&#xff1a;Unix 域协议族&#xff0c;用于在同一台主机上的进程间通信。 type: SOCK_STREAM&#xff1a…

SQL语句在MySQL中如何执行

MySQL的基础架构 首先就是客户端&#xff0c;其次Server服务层&#xff0c;大多数MySQL的核心服务都在这一层&#xff0c;包括连接、分析、优化、缓存以及所有的内置函数&#xff08;时间、日期、加密函数&#xff09;&#xff0c;所有跨存储引擎功能都在这一层实现&#xff1…

轻量化特征融合 | 一种基于增强层间特征相关性的轻量级特征融合网络 | 北理工新作

论文题目&#xff1a;A Lightweight Fusion Strategy With Enhanced Interlayer Feature Correlation for Small Object Detection 论文链接&#xff1a;https://ieeexplore.ieee.org/abstract/document/10671587 giuhub&#xff1a;https://github.com/nuliweixiao/EFC 关键词…

vue2+element-ui实现多行行内表格编辑

效果图展示 当在表格中点击编辑按钮时:点击的行变成文本框且数据回显可以点击确定按钮修改数据或者取消修改回退数据: 具体实现步骤 1. 行数据定义编辑标记 行数据定义编辑标记 当在组件中获取到用于表格展示数据的方法中,针对每一行数据添加一个编辑标记 this.list.f…

docker简单私有仓库的创建

1&#xff1a;下载Registry镜像 导入镜像到本地中 [rootlocalhost ~]# docker load -i registry.tag.gz 进行检查 2&#xff1a;开启Registry registry开启的端口号为5000 [rootlocalhost ~]# docker run -d -p 5000:5000 --restartalways registry [rootlocalhost ~]# dock…

使用 GD32F470ZGT6,手写 I2C 的实现

我的代码&#xff1a;https://gitee.com/a1422749310/gd32_-official_-code I2C 具体代码位置&#xff1a;https://gitee.com/a1422749310/gd32_-official_-code/blob/master/Hardware/i2c/i2c.c 黑马 - I2C原理 官方 - IIC 协议介绍 个人学习过程中的理解&#xff0c;有错误&…

VSCode,Anaconda,JupyterNotebook

文章目录 一. 下载VSCode并安装二. 下载Anaconda并安装1. anaconda介绍2. Anaconda的包管理功能3. Anaconda的虚拟环境管理4.Jupyter Notebook5. Jupyter Notebook使用简介6. Jupyter Notebook快捷键7.Jupyter notebook的功能扩展8. Jupyter notebook和Jupyter lab的区别 三. V…

【安全研究】某黑产网站后台滲透与逆向分析

文章目录 x01. 前言x02. 分析 【&#x1f3e0;作者主页】&#xff1a;吴秋霖 【&#x1f4bc;作者介绍】&#xff1a;擅长爬虫与JS加密逆向分析&#xff01;Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究与…

力扣 343. 整数拆分 (JAVA 记忆化搜索->动态规划)

给定一个正整数 n &#xff0c;将其拆分为 k 个 正整数 的和&#xff08; k > 2 &#xff09;&#xff0c;并使这些整数的乘积最大化。 返回 你可以获得的最大乘积 。 示例 1: 输入: n 2 输出: 1 解释: 2 1 1, 1 1 1。 示例 2: 输入: n 10 输出: 36 解释: 10 3 …

一次tomcat实战jvm线上问题定位排查和解决

问题分析&#xff1a; 问题来源是客户通过闲鱼找到了我这边进行问题的排查&#xff0c;给我发了一个报错日志&#xff0c;让我帮忙分析。 首先他的项目比较老&#xff0c;很多年以前开发的软件&#xff0c;但是具体做什么业务我不知道。下面来看日志的报错分析。 问题&#…

【Linux】软硬链接

文章目录 软链接硬链接软硬链接的使用场景&#xff1a;软链接的使用场景硬链接的应用场景 总结 软链接 软链接是一种指向文件或目录的快捷方式&#xff0c;是文件系统中非常重要的功能。它类似于Windows中的快捷方式&#xff0c;但更灵活&#xff0c;可以跨文件系统创建。 ln…

操作系统(5)进程

一、定义与特点 定义&#xff1a;进程是计算机中的程序关于某数据集合上的一次运行活动&#xff0c;是系统进行资源分配和调度的基本单位&#xff0c;是操作系统结构的基础。 特点&#xff1a; 动态性&#xff1a;进程是动态创建的&#xff0c;有它自身的生命周期&#xff0c;…

安宝特分享 | AR技术助力医院总院与分院间的远程面诊

随着科技的迅猛发展&#xff0c;增强现实&#xff08;AR&#xff09;技术在各行各业的应用愈发广泛&#xff0c;特别是在医疗领域&#xff0c;其潜力和价值正在被不断挖掘。在现代医疗环境中&#xff0c;患者常常面临“看病难、看病远、看病急”等诸多挑战&#xff0c;而安宝特…

CNCF云原生生态版图-分类指南(三)- 运行时

CNCF云原生生态版图-分类指南&#xff08;三&#xff09;- 运行时 CNCF云原生生态版图-分类指南三、运行时&#xff08;Runtime&#xff09;&#xff08;一&#xff09;云原生存储&#xff08;Cloud Native Storage&#xff09;1. 是什么&#xff1f;2. 解决什么问题&#xff1…

通俗易懂的 Nginx 反向代理 配置

通俗易懂的 Nginx 反向代理 配置 首先 root 与 alias 的区别 root 是直接拼接 root location location /i/ {root /data/w3; }当请求 /i/top.gif &#xff0c;/data/w3/i/top.gif 会被返回。 alias 是用 alias 替换 location location /i/ {alias /data/w3/images/; }当请…

HiveQL命令(一)- 数据库操作

文章目录 前言一、数据库操作1. 创建数据库1.1 语法及解释1.2 创建数据库示例 2. 查看数据库2.1 查看所有数据库2.2 查看数据库信息2.2.1 语法及解释2.2.2 查看数据库信息示例 3. 切换数据库3.1 语法3.2 示例 4. 修改数据库4.1 语法4.2 示例 5. 删除数据库5.1 语法及解释5.2 示…

Ubuntu22.04安装docker desktop遇到的bug

1. 确认已启用 KVM 虚拟化 如果加载了模块&#xff0c;输出应该如下图。说明 Intel CPU 的 KVM 模块已开启。 否则在VMware开启宿主机虚拟化功能&#xff1a; 2. 下一步操作&#xff1a; Ubuntu | Docker Docs 3. 启动Docker桌面后发现账户登陆不上去&#xff1a; Sign in | …

FPGA实现GTP光口数据回环传输,基于Aurora 8b/10b编解码架构,提供2套工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目我这里已有的 GT 高速接口解决方案 3、工程详细设计方案工程设计原理框图用户数据发送模块基于GTP高速接口的数据回环传输架构GTP IP 简介GTP 基本结构GTP 发送和接收…

(二)多智能体强化学习

目录 前言 一、多智能体强化学习的概念 二、多智能体面临的问题 三、现有算法简介 总结 前言 基于上一篇文章对于强化学习基础概念的介绍&#xff0c;本篇文章针对多智能体强化学习进行介绍和总结&#xff0c;帮助大家了解多智能体的基本概念以及算法&#xff0c;方便大家…

【Linux网络编程】传输协议UDP

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系…