适合场景
私有化部署到客户电脑 或者大屏 能够显示rtsp
视频流到网页上
注意本地电脑需要安装 ffmpeg
能够在终端访问 ffmpeg
命令 因为本质上server
端就是调用命令来实现
client:
<script type="text/javascript" src="Decoder.js"></script>
<script type="text/javascript" src="YUVCanvas.js"></script>
<script type="text/javascript" src="Player.js"></script>
let playBoard = function () {
let rurl = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
// let rurl = "rtsp://221.178.114.229:554/openUrl/Uza4ToQ";
let url = `ws://${netlocalvideohost}:8888/rtsp?url=${rurl}`;
var player = new Player({
size: {
width: 640,
height: 320
},
useWorker: true,
});
document.body.appendChild(player.canvas);
const ws = new WebSocket(url)
ws.binaryType = 'arraybuffer'
ws.onmessage = function (evt) {
console.log('ws message receive');
var data = evt.data
if (typeof data !== 'string') {
console.log(data);
player.decode(new Uint8Array(data))
} else {
console.log('get command from server: ', data)
}
}
}
playBoard();
server:
import express from 'express';
import expressWebSocket from 'express-ws';
import ffmpeg from '@ffmpeg-installer/ffmpeg';
import { spawn } from 'child_process';
const ffmpegPath = ffmpeg.path;
let wsInstance;
function localServer() {
let app = express();
// extend express app with app.ws()
if (!wsInstance)
wsInstance = expressWebSocket(app, null, {
perMessageDeflate: true,
});
app.ws('/rtsp', rtspRequestHandle);
app.listen(8888);
console.log('express listened');
}
function rtspRequestHandle(ws, req) {
let url = req.query.url;
if (!url) throw new Error('URL to rtsp stream is required');
console.log('rtsp url:', url);
// const wsServer = wsInstance.getWss();
// console.log(wsServer.clients);
// these should be detected from the source stream
const [width, height] = [0, 0];
const streamHeader = Buffer.alloc(8);
streamHeader.write('jsmp');
streamHeader.writeUInt16BE(width, 4);
streamHeader.writeUInt16BE(height, 6);
ws.send(streamHeader, { binary: true });
// ffmpeg转码
//适用于 jsmpeg.js
// let shell = spawn(
// ffmpegPath,
// [
// '-rtsp_transport',
// 'tcp',
// '-i',
// url,
// '-f',
// 'mpegts',
// '-codec:v',
// 'mpeg1video',
// '-vf',
// 'scale=1366:-2', // scale video and keep aspect ratio
// '-r',
// '30', // 30 fps. any lower and the client can't decode it
// '-q:v',
// '1',
// '-fflags',
// 'nobuffer',
// '-',
// ],
// { detached: false }
// );
//适用于 Broadway.js
let shell = spawn(
ffmpegPath,
[
'-i',
url,
'-r',
'30000/1001',
'-f',
'h264', //board需要输出的是h264的data 所以这里一定要写h264
'-vcodec',
'libx264',
'-b:a',
'2M',
'-bt',
'4M',
'-pass',
'1',
'-coder',
'0',
'-bf',
'0',
'-flags',
'-loop',
'-wpredp',
'0',
'-',//最后加一个短横线表示直接输出不用保存文件 这样才能让后面的 shell.stdout.on 监听到
],
{ detached: false }
);
shell.stdout.on('data', (data, opts) => {
// console.log(data);
if (ws.readyState === 1) ws.send(data, opts);
});
shell.stderr.on('error', (e) => console.log('err:error', e));
shell.stdout.on('error', (e) => console.log('out:error', e));
shell.on('error', (err) => {
console.warn(`[rtsp-relay] Internal Error: ${err.message}`);
});
shell.on('exit', (_code, signal) => {
if (signal !== 'SIGTERM') {
console.warn('[rtsp-relay] Stream died - will recreate when the next client connects');
}
});
ws.on('close', () => {
shell.kill('SIGKILL');
});
}
localServer();