默认的128 * 1024 (128 kb)
发布时间:2025-06-24 19:35:10 作者:北方职教升学中心 阅读量:579
linux都可用,只跟浏览器有关系,对浏览器可能有点小要求(版本越新越好),IE浏览器实测也是可以用的
本文仅在局域网验证,如果是公网或跨网项目,需要自行解决摄像机拉流问题(可参考GB28181协议,设备主动注册服务器)
仅支持mpeg1格式视频、websocket-relay.js文件
2.1 使用介绍
2.1.1 引入与使用
引入
如果是npm安装方式:
import JSMpeg from 'jsmpeg'
如果是使用源码:
import JSMpeg from 'xx/jsmpeg.min.js'
使用
创建实例方式:
let player = new JSMpeg.Player('ws://xxxx',{ ... })
自动识别方式:
jsmpeg.js会自动识别docment中所有class包含jsmpeg的元素,并获取data-url属性后把该元素当做播放器容器,html如下
<div class="jsmpeg" data-url="ws://xxxx"></div>
2.1.2 参数与事件
点此官方介绍
options参数:
名称 | 类型 | 说明 | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
canvas | HTMLCanvasElement | 用于视频渲染的HTML Canvas元素。(可以理解为能接受的最大音画不同步时间) | ||||||||||||||||||||||||||||||||||||||||||||||||
videoBufferSize | number | 流媒体时,视频解码缓冲区的字节大小。默认=progressive | ||||||||||||||||||||||||||||||||||||||||||||||||
chunkSize | number | 使用时,以字节为单位加载的块大小。默认true | ||||||||||||||||||||||||||||||||||||||||||||||||
maxAudioLag | number | 流媒体时,以秒为单位的最大排队音频长度。mp2格式音频!!!仅支持mpeg1格式视频、前言
1 安装与介绍1.1 安装npm 安装(这样无法二次开发)
二次开发方案(本文章介绍) 把jsmpeg.min.js拖入项目目录下,建议将此js文件拆分为多个模块,如下图,当然你能在jsmpeg.min.js单文件上二次开发也是可以的 注:不能直接使用src文件夹下的代码,那是没经过编译的,jsmpeg.js有一部分使用的c语言,若要直接使用请自行学习如何编译jsmpeg,本文暂不介绍(详见build.sh) 1.2 介绍
| ||||||||||||||||||||||||||||||||||||||||||||||||
decodeFirstFrame | boolean | 是否解码并显示视频的第一帧,一般用于设置画布大小以及使用初始帧作为"poster"图像。
为方便测试,首先需要把jsmpeg的仓库拉取到本地,因为需要用到其中jsmpeg.min.js、 | ||||||||||||||||||||||||||||||||||||||||||||||||
pauseWhenHidden | boolean | 当页面处于非活动状态时是否暂停播放,默认=true(请注意,浏览器通常会在非活动选项卡中限制 JS) | ||||||||||||||||||||||||||||||||||||||||||||||||
disableGl | boolean | 是否禁用WebGL,始终使用Canvas2D渲染器,默认=false | ||||||||||||||||||||||||||||||||||||||||||||||||
disableWebAssembly | boolean | 是否禁用WebAssembly并始终使用JavaScript解码器,默认=false(不建议设置为true) | ||||||||||||||||||||||||||||||||||||||||||||||||
preserveDrawingBuffer | boolean | WebGL上下文是否创建必要的“截图” | ||||||||||||||||||||||||||||||||||||||||||||||||
progressive | boolean | 是否以块的形式加载数据(仅静态文件)。 | ||||||||||||||||||||||||||||||||||||||||||||||||
audioBufferSize | number | 流媒体时,音频解码缓冲区的字节大小。默认的512 * 1024 (512 kb)。当没有足够的数据时,返回成功 | ||||||||||||||||||||||||||||||||||||||||||||||||
destroy | none | 停止回放,断开源并清理WebGL和WebAudio状态。 首先贴上仓库中c语言的代码部分,如下图:
在根目录下有build.sh,用于编译出jsmpeg.min.js
在输出文件中,可以找到一段很长的字符串变量(WASM_BINARY_INLINED),这就是经过编译后的二进制c语言模块(经过base64加密压缩的)
这段内容在Player构造函数中被使用到,如下图:
至此,大致介绍完毕,相信看官们已经基本清楚jsmpeg的原理了 2 播放与使用在前面的介绍中已经说到,本方案需要使用到一个websocket server来中转,jsmpeg作者已经为我们提供了一个简易websocket server端,即websocket-relay.js。 |
事件:
名称 | 参数 | 说明 |
---|---|---|
onVideoDecode | decoder, time | 在每个解码和渲染的视频之后调用的回调 |
onAudioDecode | decoder, time | 在每个解码音频帧后调用的回调 |
onPlay | player | 当播放开始时调用的回调函数 |
onPause | player | 当播放暂停时调用的回调函数(例如当.pause()被调用或源结束时) |
onEnded | player | 当播放到达源的末尾时调用(只在loop=false调用) |
onStalled | player | 当没有足够的数据播放时调用的回调 |
onSourceEstablished | source | 当source第一次接收到数据时调用的回调 |
onSourceCompleted | source | 当源接收到所有数据时调用的回调 |
方法与属性:
名称 | 参数 | 说明 |
---|---|---|
play | none | 开始播放 |
pause | none | 暂停播放 |
stop | none | 停止播放,并跳到视频开头 |
nextFrame | none | 一个视频帧的高级回放。当启用时,回放可以在完整加载源之前开始 |
throttled | boolean | 当不需要回放时是否推迟加载块。github,欢迎各位一起优化和fork,拉取后可直接运行demo,同时发布到了npm中,可直接安装使用vue-jsmpeg-player |
volume | number | 获取或设置音频音量(0-1) |
currentTime | number | 获取或设置当前播放位置,以秒为单位 |
paused | boolean | 只读,无论回放是否暂停 |
2.2 简易播放测试
(1)确保你有可用的rtsp流,我是有摄像机可以测试的,如果没有摄像机,可以使用ffmpeg推视频文件流,或者去网上找找免费的电视台rtsp流,也可以用一些软件推你电脑的桌面流(不过一般的软件都只能推rtmp流)
- 提供一个推桌面流的方案:EasyDarwin(rtsp服务端)+ EasyScreenLive(捕获屏幕并推rtsp流)
- Windows上通过VLC播放器搭建rtsp流媒体测试地址操作步骤
- 通过ffmpeg直接拉取本地桌面视频流[new]
(2)运行websocket server,打开cmd,cd到websocket-relay.js所在目录(注:此步骤需要安装node.js环境,请百度)- 以下步骤推荐大家在VSCode中操作
运行命令:node ./websocket-relay.js test 8890 8891 (第一个参数为接流url子目录,第二个参数为接流端口,第三个参数为推流端口),作者在源码中的介绍如下:
若成功运行后,见下图:
(注:如果报错ws模块找不到,就运行下npm install ws)
(3)启用ffmpeg,拉取指定流转码后推给http server
转码海康摄像机流命令代码:
ffmpeg -rtsp_transport tcp -i rtsp://[用户名]:[密码]@[ip]:554/h264/ch1/main/av_stream -q 0 -f mpegts -codec:v mpeg1video -s 1280x720 -b:v 1500k -codec:a mp2 -ar 44100 -ac 1 -b:a 128k http://127.0.0.1:8890/test
其他流:
ffmpeg -rtsp_transport tcp -i rtsp流地址 -q 0 -f mpegts -codec:v mpeg1video -s 1280x720 -b:v 1500k -codec:a mp2 -ar 44100 -ac 1 -b:a 128k http://127.0.0.1:8890/test
本地桌面流:
ffmpeg -f gdigrab -i desktop -q 0 -f mpegts -codec:v mpeg1video -s 1280x720 -b:v 1500k -codec:a mp2 -ar 44100 -ac 1 -b:a 128k http://127.0.0.1:8890/jsmpeg
运行后,ffmpeg控制台如下图:
http server控制台中可以看到一个客户端连入,此后http server会把接受到的流数据转发给所有连入ws://127.0.0.1:8891的客户端:
(4)web中使用jsmpeg.js接收流并播放
- 为了方便测试,我这写了个简易html,可直接测试,不用搭建vue、mp2格式音频!!!,将视频流解码成图片并渲染到canvas上,并且可在源码基础上二次开发
1.2.1 jsmpeg方案架构
单链路即:rtsp流=>ffmpeg转码(mpeg1+mp2)=>http server接收=>websocket server转发=>websocket client
1.2.2 jsmpeg的c语言部分
- wasm技术简单来说就是:可以在Web 中运行除了JavaScript以外的语言编写的代码(例如C,C ++,Rust或其他)
- jsmpeg采用了这项技术来实现其解码器(mpeg1+mp2)
- 不过我猜测作者为防止在一些低版本浏览器上wasm技术无法使用,又用js写了一套解码器。对于非常高的比特率,您可能需要增加此值。github、
loop boolean 是否循环播放视频(仅静态文件),默认=true autoplay boolean 是否立即开始播放(仅限静态文件),默认=false audio boolean 是否解码音频,默认=true video boolean 是否解码视频,默认=true poster string 预览图像的URL,用来在视频播放之前作为海报显示。react框架测试了; - 在存放jsmpeg.min.js目录下新建一个html文件,拷贝下面的代码到放入其中,保存后运行即可;
<!DOCTYPE html><html> <head> <title>JSMpeg TEST</title> <meta name="viewport" content="width=device-width"> </head> <body> <div class="content"> <h1>JSMpeg 测试 (为了播放声音,请先点击页面一次)</div> <canvas id="jsmpeg-canvas"></canvas > </div> <script type="text/javascript" src="jsmpeg.min.js"></script> <script type="text/javascript"> document.addEventListener('click', () => { let player = new JSMpeg.Player("ws://127.0.0.1:8891",{ canvas: document.getElementById('jsmpeg-canvas'), // 要在用户点击过页面后,才可以播放声音 // audio: false, }) }, { once: true }) </script> </body></html>
目录结构:
双击运行到浏览器中,如下图:
2.3 延迟测试
测试1
测试2
测试3
录制了个gif,更加直观
延迟确实保持1s左右吧,整个延迟组合大概是:ffmpeg转码耗时+http server接流耗时+ws server转发耗时+client解码耗时,因为是本机测试,网络的延迟可忽略不计
3 优化与扩展
将jsmpeg使用到项目中的话,原生功能可能还是稍微不够的,需要对其二次开发
3.1 拆分模块
为了方便二次开发,建议各位将jsmpeg.min.js拆分为多个独立模块,在vscode中打开此文件,序列化之后,可以看到都已经分类好了,接下来将各个模块独立为js文件;
拆分后如下图:
各模块说明:
- modules
- audio-output:音频输出相关
- webaudio: 缓冲器模块
- decoder:解码相关
- decoder:解码器基类
- mp2-wasm:mp2音频解码模块(wasm技术,性能更好)
- mp2:mp2音频解码模块(原生js解码,性能较差)
- mpeg1-wasm:mp2音频解码模块(wasm技术,性能更好)
- mpeg1:mp2音频解码模块(原生js解码,性能较差)
- renderer:渲染器相关
- canvas2d:canvas2d渲染模块
- webgl:webgl渲染模块(性能更好)
- source:视频流接收相关
- ajax-progressive:使用ajax方案的稳定版本?没有深入了解
- ajax:使用XMLHttpRequest获取视频流
- fetch:使用原生的fetch方法获取视频流
- websocket:websocket客户端模块,用于接收websocet server转发的视频流(常用)
- buffer: 数据缓冲器模块
- jsmpeg:jsmpeg导出模块
- player: 播放器模块
- audio-output:音频输出相关
- utils:工具方法相关
3.2 功能扩展
画面旋转
/** * 旋转画布 * @param {number} angle 角度 * @param {boolean} append 是否为追加角度 * @returns */ rotate(angle, append = false) { if (!this.canvas || typeof angle !== 'number') return const canvas = this.canvas angle = append ? this.store.canvasAngle + angle : angle angle = angle >= 360 ? angle - 360 : angle <= -360 ? angle + 360 : angle if ((Math.abs(angle) / 90) % 2 === 1) { // 如果是90整数倍,表示为垂直状态 const containerBound = this.contianer.getBoundingClientRect(), canvasBound = canvas.getBoundingClientRect() if (canvas.width > canvas.height) { // 宽>高,取容器高度作为canvas最大宽度 canvas.style.width = containerBound.height + 'px' } else { // 宽<=高,取容器宽度作为canvas最大高度 canvas.style.height = containerBound.width + 'px' } } else { canvas.style.width = null canvas.style.height = null } canvas.style.transform = `rotate(${angle}deg)` this.store.canvasAngle = angle }
截图
这里的saveToLocal与savaAs方法相似/** * 截图 * @param {string} name */ snapshot(name = 'JSMPeg') { if (this.canvas) { const mime = 'image/png', url = this.canvas.toDataURL(mime) saveToLocal(url.replace(mime, 'image/octet-stream'), `${name}_截图_${new Date().toLocaleTimeString()}.png`, mime) } } // saveToLocal方法如下/** * * @param {object} param * @param {string|object|Array} param.data 数据,传入后url参数将被忽略 * @param {string} param.url 文件下载地址 * @param {string} param.name 文件名称 * @param {string} param.mimeType 文件mime类型 * @returns */export function saveToLocal(blob, name = 'JSMpeg_' + Date.now(), mimeType = '') { if (!blob) return const a = document.createElement('a') a.style.display = 'none' a.download = name if (typeof blob === 'string') { a.href = blob } else { blob = blob instanceof Blob ? blob : new Blob(blob instanceof Array ? blob : [blob], { type: mimeType }) a.href = URL.createObjectURL(blob) } setTimeout(() => { a.click() }, 0) setTimeout(() => { a.remove() }, 1) if (blob instanceof Blob) { setTimeout(() => { URL.revokeObjectURL(blob) }, 1000) }}
录制
这里需要注意的是MediaRecorder是新规范,有些浏览器不支持recorder = { /** 录制持续时间 */ duration: 0, timer: null, /** @type {'canvas'|'ws'} */ mode: '', running: false, saveName: '', /** * @type {MediaRecorder} * 媒体录制器 */ mediaRecorder: null, /** * @type {MediaStream} * 视频流 */ stream: null, /** * @type {ArrayBuffer[]} * 视频数据块 */ chunks: null, startTiming() { this.duration = 0 this.timer = setInterval(() => { this.duration += 1 }, 1000) }, pauseTiming() { clearInterval(this.timer) this.timer = null }, continueTiming() { this.timer = setInterval(() => { this.duration += 1 }, 1000) }, stopTiming() { this.pauseTiming() this.duration = 0 }, clear() { this.running = false this.mediaRecorder = null this.stream = null this.chunks = null } } /** * 视频录制 * @param {string} name * @param {'ws'|'canvas'} mode */ recording(name = 'JSMpeg', mode = 'ws') { try { if (!this.isPlaying) { return } if (this.isRecording || this.recorder.stream) { // 停止录制 this.recorder.stopTiming() // https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API if (this.recorder.mode === 'canvas' && this.recorder.mediaRecorder && this.recorder.stream instanceof MediaStream) { this.recorder.mediaRecorder.stop() saveToLocal(this.recorder.chunks, `${this.recorder.saveName}.webm`, 'video/webm;codecs=vp9') } else if (this.recorder.mode === 'ws' && this.recorder.chunks instanceof Array) { saveToLocal(this.recorder.chunks, `${this.recorder.saveName}.ts`, 'video/MP2T') this.source.recorder = undefined } this.recorder.clear() this.recorder.running = false } else { // 开始录制 if (mode === 'canvas') { // 此方法兼容性较差,captureStream、 官网地址
- jsmpeg为MIT开源协议,不用考虑版权问题
- 跨平台windows、