最近项目中需要对接摄像头监控,海康摄像头为rtsp流格式

有一个软件VLC media player,可以在线进行rtsp或者rtmp流播放,可用来测试流地址是否可用

功能实现思路为后台通过fmpeg把rtsp流进行转码,然后通过ws方式进行一帧一帧推送。(还有一种时后台通过fmpeg转为hls格式,但是这样会在项目中生产一张张图片,所以我们没有考虑)

首先后台我先使用node进行了一个测试dme,网上找了一个测试的rtmp地址:

rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid  //好像是个游戏直播

const { spawn } = require('child_process');
const WebSocket = require('ws');const wss = new WebSocket.Server({ port: 9999 });wss.on('connection', (ws) => {console.log('WebSocket 客户端连接');// 关键修改1:使用完整FFmpeg路径(避免环境变量问题)const ffmpegPath = 'D:\\rj\\ffmpeg\\bin\\ffmpeg.exe'; // 自己电脑的ffmpeg地址,自行更换确保路径存在// 关键修改2:简化参数(移除可能冲突的选项)const args = ['-i', 'rtmp://liteavapp.qcloud.com/live/liteavdemoplayerstreamid', //网上的游戏直播地址'-f', 'mpegts','-codec:v', 'mpeg1video','-'];//   // rtsp的配置,和rtp略有区别
//  const args= [
//     '-rtsp_transport', 'tcp',       // 强制TCP传输
//     '-timeout', '5000000',          // 5秒超时
//     '-re',                          // 按原始速率读取
//     '-i', 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4',
//  '-f', 'mpegts',
//   '-codec:v', 'mpeg1video',       // JSMpeg兼容编码
//   '-b:v', '800k',                 // 视频比特率
//   '-r', '25',                     // 帧率
//   '-vf', 'scale=640:480',         // 分辨率缩放
//   '-preset', 'ultrafast',         // 最快编码预设
//   '-fflags', 'nobuffer',          // 减少缓冲
//   '-'
//   ]// 关键修改3:显式传递环境变量const env = { ...process.env, PATH: process.env.PATH }; // 继承当前环境const ffmpeg = spawn(ffmpegPath, args, { env: env,stdio: ['ignore', 'pipe', 'pipe'] // 忽略stdin,捕获stdout/stderr});// 打印FFmpeg日志(调试用)ffmpeg.stderr.on('data', (data) => {console.log('[FFmpeg]', data.toString());});// 转发数据到WebSocketffmpeg.stdout.on('data', (data) => {if (ws.readyState === ws.OPEN) {ws.send(data, { binary: true });}});ws.on('close', () => {ffmpeg.kill('SIGTERM');});
});

代码写完后通过node运行

前端在vue中采用   @cycjimmy/jsmpeg-player 或者 vue-jsmpeg-player,关于这点,因为vue-jsmpeg-player必须要求vue 2.6.12 以上,版本地低的话有问题,我这边采用@cycjimmy/jsmpeg-player

1.使用@cycjimmy/jsmpeg-player

npm install @cycjimmy/jsmpeg-player

npm下载安装后新建一个播放直播流的vue组件,代码如下

<template><div class="video-player"><!-- 视频画布容器 --><div class="video-container" ref="videoContainer"><canvas ref="videoCanvas" class="video-canvas"></canvas><!-- 加载状态 --><div v-if="isLoading" class="loading-overlay"><div class="spinner"></div><p class="loading-text">加载中...</p></div><!-- 错误提示 --><div v-if="hasError" class="error-overlay"><p class="error-text">无法加载视频流,请检查连接</p><button @click="reloadPlayer" class="reload-btn">重新加载</button></div></div><!-- 控制栏 --><div class="controls-bar"><div class="controls-group"><!-- 播放/暂停按钮 --><button class="control-btn" @click="togglePlay":title="isPlaying ? '暂停' : '播放'"><i class="fas" :class="isPlaying ? 'fa-pause' : 'fa-play'"></i></button><!-- 音量控制 --><div class="volume-control"><button class="control-btn volume-btn"@click="toggleMute":title="isMuted ? '取消静音' : '静音'"><i class="fas" :class="isMuted ? 'fa-volume-mute' : 'fa-volume-up'"></i></button><inputtype="range"min="0"max="100"v-model="volume"@input="setVolume"class="volume-slider":title="`音量: ${volume}%`"></div></div><div class="controls-group"><!-- 全屏按钮 --><button class="control-btn" @click="toggleFullscreen":title="isFullscreen ? '退出全屏' : '进入全屏'"><i class="fas" :class="isFullscreen ? 'fa-compress' : 'fa-expand'"></i></button></div></div></div>
</template><script>
import JSMpeg from '@cycjimmy/jsmpeg-player';export default {name: 'VideoPlayer',props: {// 视频流地址streamUrl: {type: String,required: true,default: 'ws://localhost:9999' //node后台1的ws地址},// 封面图poster: {type: String,default: ''}},data() {return {player: null,isPlaying: false,volume: 80,isMuted: false,isFullscreen: false,isLoading: true,hasError: false};},mounted() {this.initPlayer();this.addEventListeners();},beforeUnmount() {this.destroyPlayer();this.removeEventListeners();},methods: {// 初始化播放器initPlayer() {this.isLoading = true;this.hasError = false;try {this.player = new JSMpeg.Player(this.streamUrl, {canvas: this.$refs.videoCanvas,autoplay: false,loop: false,controls: false,poster: this.poster,decodeFirstFrame: true,volume: this.volume / 100,// 事件回调onPlay: () => {this.isPlaying = true;this.isLoading = false;},onPause: () => {this.isPlaying = false;},onEnded: () => {this.isPlaying = false;},onError: () => {this.hasError = true;this.isLoading = false;this.isPlaying = false;}});} catch (error) {console.error('播放器初始化失败:', error);this.hasError = true;this.isLoading = false;}},// 销毁播放器destroyPlayer() {if (this.player) {this.player.destroy();this.player = null;}},// 重新加载播放器reloadPlayer() {this.destroyPlayer();this.initPlayer();},// 切换播放/暂停togglePlay() {if (!this.player) return;if (this.isPlaying) {this.player.pause();} else {// 处理浏览器自动播放限制this.player.play().catch(err => {console.warn('自动播放失败,需要用户交互:', err);this.showPlayPrompt();});}},// 显示播放提示(用于自动播放限制)showPlayPrompt() {const container = this.$refs.videoContainer;const prompt = document.createElement('div');prompt.className = 'play-prompt';prompt.innerHTML = '<i class="fas fa-play"></i><p>点击播放</p>';container.appendChild(prompt);prompt.addEventListener('click', () => {this.player.play();container.removeChild(prompt);}, { once: true });},// 切换静音toggleMute() {if (!this.player) return;this.isMuted = !this.isMuted;this.player.volume = this.isMuted ? 0 : this.volume / 100;},// 设置音量setVolume() {if (!this.player) return;this.isMuted = this.volume === 0;this.player.volume = this.volume / 100;},// 切换全屏toggleFullscreen() {const container = this.$refs.videoContainer;if (!document.fullscreenElement) {container.requestFullscreen().catch(err => {console.error(`全屏错误: ${err.message}`);});this.isFullscreen = true;} else {if (document.exitFullscreen) {document.exitFullscreen();this.isFullscreen = false;}}},// 监听全屏状态变化handleFullscreenChange() {this.isFullscreen = !!document.fullscreenElement;},// 添加事件监听器addEventListeners() {document.addEventListener('fullscreenchange', this.handleFullscreenChange);},// 移除事件监听器removeEventListeners() {document.removeEventListener('fullscreenchange', this.handleFullscreenChange);}}
};
</script><style scoped>
.video-player {position: relative;width: 100%;max-width: 1200px;margin: 0 auto;border-radius: 8px;overflow: hidden;box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}.video-container {position: relative;width: 100%;background-color: #000;aspect-ratio: 16 / 9; /* 保持视频比例 */
}.video-canvas {width: 100%;height: 100%;object-fit: contain;
}/* 加载状态 */
.loading-overlay {position: absolute;top: 0;left: 0;width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;align-items: center;background-color: rgba(0, 0, 0, 0.5);color: white;z-index: 10;
}.spinner {width: 40px;height: 40px;border: 4px solid rgba(255, 255, 255, 0.3);border-radius: 50%;border-top: 4px solid white;animation: spin 1s linear infinite;margin-bottom: 16px;
}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }
}.loading-text {font-size: 16px;font-weight: 500;
}/* 错误提示 */
.error-overlay {position: absolute;top: 0;left: 0;width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;align-items: center;background-color: rgba(0, 0, 0, 0.7);color: white;z-index: 10;padding: 20px;text-align: center;
}.error-text {font-size: 16px;margin-bottom: 20px;max-width: 400px;
}.reload-btn {background-color: #42b983;color: white;border: none;padding: 8px 16px;border-radius: 4px;cursor: pointer;font-size: 14px;transition: background-color 0.2s;
}.reload-btn:hover {background-color: #359e75;
}/* 播放提示 */
::v-deep .play-prompt {position: absolute;top: 0;left: 0;width: 100%;height: 100%;display: flex;flex-direction: column;justify-content: center;align-items: center;background-color: rgba(0, 0, 0, 0.5);color: white;z-index: 9;cursor: pointer;
}::v-deep .play-prompt i {font-size: 48px;margin-bottom: 16px;transition: transform 0.2s;
}::v-deep .play-prompt:hover i {transform: scale(1.1);
}/* 控制栏 */
.controls-bar {display: flex;justify-content: space-between;align-items: center;padding: 12px 20px;background-color: #1a1a1a;color: white;
}.controls-group {display: flex;align-items: center;gap: 16px;
}.control-btn {background: none;border: none;color: white;font-size: 18px;cursor: pointer;width: 36px;height: 36px;border-radius: 50%;display: flex;align-items: center;justify-content: center;transition: background-color 0.2s;
}.control-btn:hover {background-color: rgba(255, 255, 255, 0.15);
}/* 音量控制 */
.volume-control {display: flex;align-items: center;gap: 8px;
}.volume-slider {width: 100px;height: 4px;-webkit-appearance: none;background: rgba(255, 255, 255, 0.2);border-radius: 2px;outline: none;
}.volume-slider::-webkit-slider-thumb {-webkit-appearance: none;width: 14px;height: 14px;border-radius: 50%;background: white;cursor: pointer;transition: transform 0.1s;
}.volume-slider::-webkit-slider-thumb:hover {transform: scale(1.2);
}/* 响应式调整 */
@media (max-width: 768px) {.controls-bar {padding: 8px 12px;}.controls-group {gap: 8px;}.control-btn {font-size: 16px;width: 32px;height: 32px;}.volume-slider {width: 80px;}
}
</style>

然后在父组件中引入使用就可以了。

2.使用 vue-jsmpeg-player

npm i vue-jsmpeg-player@1.1.0-beta

安装后在main.js全局引入:


import JSMpegPlayer from 'vue-jsmpeg-player';
import 'vue-jsmpeg-player/dist/jsmpeg-player.css';Vue.use(JSMpegPlayer)

然后新建vue组件:

<template><div><jsmpeg-player :url="url" /></div>
</template><script>
export default {components: {},data() {return {//后台转发的ws地址url: "ws://10.10.10.113:9999", //后台ws地址};},computed: {},mounted() {// jsmpeg-player组件的使用说明//     url	string	视频流地址(推荐websocket,实际上jsmpeg.js原生也支持http方式,但没有经过测试)// title	string	播放器标题// no-signal-text	string	无信号时的显示文本// options	object	jsmpeg原生选项,直接透传,详见下表// closeable	boolean	是否可关闭(单击关闭按钮,仅抛出事件)// in-background	boolean	是否处于后台,如el-tabs的切换,路由的切换等,支持.sync修饰符// show-duration	boolean	是否现实持续播放时间// default-mute	boolean	默认静音// with-toolbar	boolean	是否需要工具栏// loading-text	boolean	加载时的文本,默认为:拼命加载中},beforeDestroy() {},methods: {},
};
</script><style lang="scss">
</style>

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

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

相关文章

Docker容器强制删除及文件系统修复完整指南

Docker容器强制删除及文件系统修复完整指南 故障现象与原因分析 ​故障表现​&#xff1a; ERROR: for c9ca40be974d_OpIsosMD_OB unable to remove filesystem unlinkat /data/docker/storage/containers/c9ca40be974d...: structure needs cleaning​根本原因​&#xff1a;…

Matplotlib 知识点总结

1. 基础绘图&#xff08;plot函数&#xff09;基本语法&#xff1a;plot([x], y, [fmt], [x2], y2, [fmt2], ..., **kwargs)功能特点&#xff1a;可绘制点、线和组合图形自动生成x轴&#xff08;0-N-1&#xff09;当x未指定时示例&#xff1a;绘制两点连线、多点不规则线等代码…

高可用微服务架构实战:Nacos集群+Nginx负载均衡,Spring Cloud无缝对接

"当你的注册中心挂了&#xff0c;整个微服务就变成了无头苍蝇。" 这是我在生产环境踩坑后最痛的领悟。今天&#xff0c;我将分享如何用Nacos集群Nginx搭建坚如磐石的注册中心&#xff0c;让你的微服务永不迷路&#xff01; 在 Windows 环境下配置 Nacos 集群&#x…

Spark大数据处理实战指南

Spark 简介 Apache Spark 是一个开源的分布式计算框架,专为大规模数据处理而设计。它通过内存计算和优化的执行引擎显著提升了数据处理速度,适用于批处理、实时流处理、机器学习和图计算等场景。 核心特性 高性能:利用内存计算(In-Memory Processing)减少磁盘 I/O,比传…

浏览器缓存机制全解析:强缓存与协商缓存

浏览器缓存是浏览器为提升页面加载速度、减少服务器压力和节省网络带宽&#xff0c;在本地存储资源&#xff08;如 HTML、CSS、JS、图片等&#xff09;的机制。其核心分为强缓存和协商缓存&#xff0c;并涉及多种 HTTP 头字段和存储位置。以下是详细解析&#xff1a;⚙️ 一、缓…

知识随记-----Qt 实用技巧:自定义倒计时按钮防止用户频繁点击

Qt 技巧&#xff1a;实现自定义倒计时按钮防止用户频繁点击注册 项目场景 在一个基于 Qt 开发的聊天应用中&#xff0c;用户注册时需要获取验证码。为防止用户频繁点击获取验证码按钮&#xff0c;需要实现一个倒计时功能&#xff0c;用户点击后按钮进入倒计时状态&#xff0c;倒…

Linux与Windows应急响应

本人首先进行了linux的应急响应&#xff0c;windows之后再进行 Linux与Windows应急响应初体验1 linux应急响应1.1 账户&#xff1a;1.1.1 使用cat /etc/passwd命令查看passwd文件2.1.2 使用cat /etc/shadow命令查找shadow文件&#xff0c;该文件为密码文件的存储项1.2 入侵排查…

计算机网络1-4:计算机网络的定义和分类

目录 计算机网络的定义 计算机网络的分类 计算机网络的定义 计算机网络的分类 按交换技术分类&#xff1a;电路交换网络、报文交换网络、分组交换网络 按使用者分类&#xff1a;公用网、专用网 按传输介质分类&#xff1a;有线网络、无线网络 按覆盖范围分类&#xff1a;…

在QT中动态添加/删除控件,伸缩因子该怎么处理

开发中遇到的问题[TOC](开发中遇到的问题)处理方式在我们的界面开发过程中&#xff0c;通常需要开发一些可以动态添加or删除控件的容器&#xff0c;类似Tab页一样&#xff0c;为了美观的话&#xff0c;我们通常使用伸缩因子将容器中的控件往一个方向挤&#xff0c;类似下面的控…

【设计模式精解】什么是代理模式?彻底理解静态代理和动态代理

目录 静态代理 动态代理 JDK动态代理 CGLIB代理 JDK动态代理和CGLIB代理的区别 总结 代理模式简单来说就是 我们使用代理对象来代替对真实对象(real object)的访问&#xff0c;这样就可以在不修改原目标对象的前提下&#xff0c;扩展目标对象的功能。 代理模式有静态代理…

MCU AI/ML - 弥合智能和嵌入式系统之间的差距

作者&#xff1a;芯科科技产品营销高级经理Gopinath Krishniah 人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;是使系统能够从数据中学习、进行推理并随着时间的推移提高性能的关键技术。这些技术通常用于大型数据中心和功能强大的GPU&#xff0c;但…

Redis中的sdshdr的len和alloc那块的知识点详解

文章目录核心比喻&#xff1a;一个可以伸缩的水瓶场景一&#xff1a;创建一个新字符串场景二&#xff1a;追加字符串&#xff08;触发“空间预分配”&#xff09;场景三&#xff1a;再次追加字符串&#xff08;利用空闲空间&#xff09;场景四&#xff1a;缩短字符串&#xff0…

在Linux下访问MS SQL Server数据库

Linux作为一个免费的Unix类操作系统&#xff0c;以其开放性源代码、多任务、X window等特点为众多的用户所采用&#xff0c;并有很多企业采用Linux来作为其内部网的全功能服务器(WWW&#xff0c;FTP&#xff0c;Email、DNS)。企业的内部网不仅要提供文本信息的访问&#xff0c;…

计算机视觉-OpenCV

一下载第三方库opencv-python3.4.18.65opencv-contrib-python3.4.18.65import cv2 # 读取的格式是BGR numpy import numpy as np# 读取图片 a cv2.imread(generated_image.jpg) # 读取图片 print(a) # NumPy数组&#xff0c;其中存储了读取的图像文件的像素值。cv2.imshow…

解决GitHub无法打开

找到下图文件&#xff0c;用记事本打开 在最下方粘贴如下代码140.82.113.4 github.com 20.205.243.166 github.com 140.82.112.4 github.com 151.101.1.6 github.global.ssl.fastly.net 185.199.108.153 assets-cdn.github.com 185.199.109.153 assets-cdn.github.com 185.199.…

AWS VPC Transit Gateway 可观测最佳实践

AWS VPC Transit Gateway 介绍 Amazon VPC Transit Gateway 是一个网络传输中心&#xff0c;用于互连虚拟私有云 (VPCs) 和本地网络。随着您的云基础设施在全球扩展&#xff0c;区域间对等互连使用 AWS 全球基础设施将中转网关连接在一起。 AWS 数据中心之间的所有网络流量都在…

WeakRef的作用和使用

文章目录WeakRef的作用和使用使用 WeakRef 避免强引用&#xff1a;原理与实践一、WeakRef 的核心特性二、WeakRef 与强引用的对比三、WeakRef 的使用场景与示例1. 非关键数据缓存&#xff08;避免缓存导致内存泄漏&#xff09;2. 跟踪对象生命周期&#xff08;不干扰回收&#…

【华为机试】332. 重新安排行程

文章目录332. 重新安排行程题目描述示例 1&#xff1a;示例 2&#xff1a;提示&#xff1a;解题思路核心思路算法流程图欧拉路径原理DFS回溯机制字典序优化策略复杂度分析算法实现要点完整题解代码332. 重新安排行程 题目描述 给你一份航线列表 tickets &#xff0c;其中 tic…

通信算法之300:CRC表生成方式-CRC8、CRC16、CRC32-输入字节

"CRC表的MATLAB生成代码"生成的查找表可以用于快速计算 CRC 值&#xff0c;通过查表法可以显著提高 CRC 计算效率&#xff0c;尤其适用于需要处理大量数据的场景。下面是一个生成 CRC 查找表&#xff08;CRC Table&#xff09;的 MATLAB 代码&#xff0c;该代码可以根…

国内使用 npm 时配置镜像源

在国内使用 npm 时&#xff0c;由于网络限制可能会遇到下载速度慢或连接超时的问题。通过设置国内镜像源&#xff0c;可以显著提升下载速度和稳定性。以下是常用的国内 npm 镜像源及其配置方法。 查询当前使用的镜像源 npm get registry 设置为淘宝镜像源 npm config set reg…