nodejs+express+ffmpeg+jsmpeg 实现rtsp转码网页播放
1、版本说明
nodejs版本
ffmpeg
2.js 安装
1 2 3 | npm install ws -g npm install express --save |
3.ffmpeg管理脚本 控制ffmpeg的启停和查询
#!/bin/bash # 1. list all pushing streams or a specific dst pattern # --list [--dst_pattern=''] # # return json is like: # {"ret": 0, "streams": [ # "http://127.0.0.1:8081/supersecret/live1" # ], "msg": "success"} # 2. stop all streams or a specific pattern # --stop [--dst_pattern=''] # # return json is like: # {"ret":0, "msg":"stop pids: 4428 4441"} # 3. start new forward with params # --start_new [--src_addr='your rtsp addr' --dst_addr='your push dst addr' --video_w='video push width' --video_h='video push height'] # # return json is like: # {"ret":0, "msg":"start new: http://127.0.0.1:8081/supersecret/live1"} cd `dirname $0` ret_common() { local ret=$1 local msg=$2 echo {\"ret\":$ret, \"msg\":\"$msg\"} } list_forward() { local dst_pattern= local x= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --dst_pattern=*) dst_pattern=${p#--dst_pattern=} ;; *) ret_common -1 "unknown param $p, usage $0 --list [--dst_pattern=xxx]" return ;; esac done local list= if [ -n "$dst_pattern" ]; then list=$(ps -A x | grep forward_stream.sh | grep -v grep | grep -o "\-\-dst_addr=.*" | grep "$dst_pattern" | awk '{print $1}' | sed 's/--dst_addr=//') else list=$(ps -A x | grep forward_stream.sh | grep -v grep | grep -o "\-\-dst_addr=.*" | awk '{print $1}' | sed 's/--dst_addr=//') fi # generate json ret msg local list_a=($list) echo {\"ret\": 0, \"streams\": [ for ((x=0; x<${#list_a[*]}; x++)); do if (( $x > 0 )); then echo ,; fi echo \"${list_a[$x]}\" done echo ], \"msg\": \"success\"} } stop_forward() { local dst_pattern= local x= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --dst_pattern=*) dst_pattern=${p#--dst_pattern=} ;; *) ret_common -1 "unknown param $p, usage $0 --list [--dst_pattern=xxx]" return ;; esac done local stop_pids= if [ -n "$dst_pattern" ]; then #stop specific stop_pids=$(ps -A x | grep forward_stream.sh | grep -v grep | grep "\-\-dst_addr=.*${dst_pattern}.*" | awk '{print $1}') else # stop all stop_pids=$(ps -A x | grep forward_stream.sh | grep -v grep | awk '{print $1}') fi local child= for x in $stop_pids; do child=$(pgrep -P $x) kill $x $child &>/dev/null done ret_common 0 "stop pids: $stop_pids" } start_new() { local src_addr= local dst_addr= local video_w= local video_h= for ((x=1; x<=$#; x++)); do p=$(eval echo \$$x) case "$p" in --src_addr=*) src_addr=${p#--src_addr=} ;; --dst_addr=*) dst_addr=${p#--dst_addr=} ;; --video_w=*) video_w=${p#--video_w=} ;; --video_h=*) video_h=${p#--video_h=} ;; *) ret_common -1 "unknown param $p" return ;; esac done bash ./forward_stream.sh --src_addr="$src_addr" --video_w=$video_w --video_h=$video_h --dst_addr="$dst_addr" &>/dev/null & ret_common 0 "start new: $dst_addr" } # parse cmd ret=-1 if (( $# > 0 )); then case "$1" in --list) shift list_forward $@ ;; --stop) shift stop_forward $@ ;; --start_new) shift start_new $@ ;; *) ret_common -1 "unknown param $1" ;; esac else ret_common -2 "need more param" fi
4、express-real-time-video.js express服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | var express = require( 'express' ); var app = express(); const exec = require( 'child_process' ).exec; var serverIp = '127.0.0.1' //服务器地址,自行修改 var localIp = 'localhost' var websocketUri = 'http://' + localIp var secret = 'live' var websocketPort = 8081 var pushPort = 8082 var expressPort = 8085 var rtspWidth = 1280 var rtspHeight = 720 app.all( '*' , function(req, res, next) { //设为指定的域 res.header( 'Access-Control-Allow-Origin' , "*" ); res.header( "Access-Control-Allow-Headers" , "X-Requested-With" ); res.header( 'Access-Control-Allow-Headers' , 'Content-Type' ); res.header( "Access-Control-Allow-Methods" , "PUT,POST,GET,DELETE,OPTIONS" ); res.header( 'Access-Control-Allow-Credentials' , true ); res.header( "X-Powered-By" , ' 3.2.1' ); next(); }); // POST 请求 app.post( '/' , function (req, res) { var rtspId = req.query.rtspId; var rtspUrl = req.query.rtspUrl; var pushUrl = websocketUri + ':' + websocketPort + '/' + secret + '/' + rtspId; //开启ffmpeg execQuery(rtspUrl, pushUrl, rtspWidth, rtspHeight); var wsUrl = 'ws://' + serverIp + ':' + pushPort + '/' + rtspId; res.send(wsUrl) }) var server = app.listen(expressPort, function () { console.log( "应用实例,访问地址为 http://" + localIp + ':' + expressPort) exeWebsocket() }) //开启websocket function exeWebsocket(){ const startWebsocket = 'node realtime-video-websocket.js ' + secret + ' ' + websocketPort + ' ' + pushPort; console.log( '[api] start websocket server:' + startWebsocket); exec(startWebsocket, function(err,stdout,stderr){ if (err) { console.log( 'get weather api error:' +stderr); } else { console.log(stdout); } }); } //开启推流 function execFFmpeg(rtspUrl, pushUrl, rtspWidth, rtspHeight){ const pushFFmpeg = 'bash ./forward_mgr.sh --start_new --src_addr=\'' + rtspUrl + '\' --dst_addr=\'' + pushUrl + '\' --video_w=' + rtspWidth + ' --video_h=' + rtspHeight; console.log( '[api] start push rtsp sh:' + pushFFmpeg); exec(pushFFmpeg, function(err,stdout,stderr){ if (err) { console.log( 'get weather api error:' +stderr); } else { console.log(stdout); } }); } //查询rtsp流是否存在 function execQuery(rtspUrl, pushUrl, rtspWidth, rtspHeight){ const queryShell = 'bash ./forward_mgr.sh --list --dst_pattern=\'' + pushUrl + '\'' ; console.log( '[api] query rtsp sh:' + queryShell); exec(queryShell, function(error,stdout,stderr){ if (error) { console.log( 'get weather api error:' +stderr); } else { var data = JSON.parse(stdout); if (pushUrl == data.streams[0]){ console.log( '[api]rtsp push alive' ) } else { execFFmpeg(rtspUrl, pushUrl, rtspWidth, rtspHeight) } } } ); } |
5.realtime-video-websocket.js 实时websocket转发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | var fs = require( 'fs' ), http = require( 'http' ), WebSocket = require( 'ws' ); const exec = require( 'child_process' ).exec; if (process.argv.length < 3) { console.log( '输入正确参数' ); process.exit(); } var stream_secret = process.argv[2]; //密码 var stream_port = process.argv[3] || 8081; //ffpeng推送端口 var websocket_port = process.argv[4] || 8082; //前端websocket端口 ,比如:8082 var record_stream = false ; var totalSize = 0; function initWebSocket(websocket_port) { var clientMap = new Map(); //缓存,实现多个视频流同时播放的问题 var socketServer = new WebSocket.Server({ port : websocket_port, perMessageDeflate : false }); socketServer. on ( 'connection' , function(socket, upgradeReq) { var url = upgradeReq.socket.remoteAddress + upgradeReq.url; var params = url.substr(1).split( '/' ); printData( 'initWebSocket' , params ) var key = params [1]; //key就是通过url传递过来的标识比如:(ws://127.0.0.1:8082/live3)其中live3就是这个标识,其他的流可随机生成其他的字符串 var ips = params [0].split( ':' ); console.log( 'IP:' + ips[ips.length-1] + '已连接' ) var clients = clientMap. get (key); if (!clients){ clients = new Set(); clientMap. set (key,clients); } clients.add(socket); totalSize++; process.stdout.write( "[INFO]:a new connection, the current number of connections: " + totalSize + ".\r" ); socket. on ( 'close' , function(code, message) { var clientSet = clientMap. get (key); if (clientSet){ clientSet.delete(socket); totalSize--; if (clientSet.size == 0){ clientMap.delete(key); //关闭ffmpeg closeFFmpeg(key); } } process.stdout.write( "[INFO]:close a connection, the current number of connections:" + totalSize + ".\r" ); }); socket. on ( "error" ,function(err){ // 出错触发 // console.log( "header err" ) console.log(err) }) }); //广播 socketServer.broadcast = function(data, theme) { var clients = clientMap. get (theme); if (clients) { clients.forEach(function (client, set ) { if (client.readyState === WebSocket.OPEN){ client.send(data); } }); } }; return socketServer; } function initHttp(stream_port, stream_secret, record_stream, socketServer) { var streamServer = http.createServer( function(request, response) { var params = request.url.substr(1).split( '/' ); console.log( "params--->" + params ); console.log( "stream_secret--->" + stream_secret); printData( 'initHttp' , params ) if ( params .length != 2) { process.stdout.write( "\n[ERROR]:Incorrect parameters, enter password and push theme" ); response.end(); } if ( params [0] !== stream_secret) { process.stdout.write( "\n[ERROR]:Password error: " +request.socket.remoteAddress+ ":" +request.socket.remotePort+ "" ); response.end(); } response.connection.setTimeout(0); request. on ( 'data' , function(data) { socketServer.broadcast(data, params [1]); if (request.socket.recording) { request.socket.recording.write(data); } }); request. on ( 'end' , function() { process.stdout.write( "\n[INFO]:close request" ); if (request.socket.recording) { request.socket.recording.close(); } }); if (record_stream) { var path = 'recordings/' + Date.now() + '.ts' ; request.socket.recording = fs.createWriteStream(path); } }).listen(stream_port); console.log( 'started rtsp WebSocket service in secret is [%s], service port is [%s], ws port is [%s].' ,stream_secret,stream_port,websocket_port); } function closeFFmpeg(key){ var pushUrl = 'http://localhost:8081/live/' + key; const closeShell = 'bash ./forward_mgr.sh --stop --dst_pattern=\'' + pushUrl + '\'' ; console.log( '[socket] close push' ); console.log( '[socket] close push sh:' + closeShell); exec(closeShell, function(error,stdout,stderr){ if (error) { console.log( 'get weather api error:' +stderr); } else { console.log( '[socket]close push result:' + stdout); } } ); } function printData(mark, params ){ console.log( '============' + mark + '================ \n' ) for ( var i =0;i< params .length;i++){ console.log(mark + '--------->' + params [i]) } } initHttp(stream_port, stream_secret, record_stream, initWebSocket(websocket_port)); console.log( "start success\n" ) |
6.页面video.html
<!DOCTYPE html> <html> <head> <title>JSMpeg Stream Client</title> <style type="text/css"> html, body { background-color: #111; text-align: center; } </style> </head> <body> <canvas id="video-canvas" ></canvas> <script src="./lib/jsmpeg.min.js"></script> <script src="./jquery-1.11.1.min.js"></script> <script type="text/javascript"> $(function(){ $.ajax({ type: "POST", url: "http://192.168.200.112:8085/?rtspUrl=rtsp地址&rtspId=rtsp编号", dataType: "text", success: function(data){ openVideo(data); } }); }); function openVideo(url){ var canvas = document.getElementById('video-canvas'); var player = new JSMpeg.Player(url, {canvas: canvas}); } </script> </body> </html>
7、启动express服务
1 | node express-real-time-video.js |
8、页面放到tomcat下,访问页面
9、管理express 脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | #!/bin/bash cd `dirname $0` express_port=8085 push_port=8081 websocket_port=8082 start() { echo "begin start express and websocket" node express-real-time-video.js & echo "start express and websocket success!" exit 0; } stop() { local express_pid=$(netstat -nlp | grep :$express_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) local push_pid=$(netstat -nlp | grep :$push_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) local websocket_pid=$(netstat -nlp | grep :$websocket_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) if [ -n "$express_pid" ]; then echo "stop express $express_pid" kill -9 $express_pid || echo "failed kill express" echo "kill express success!" fi if [ -n "$websocket_pid" ]; then echo "stop websocket $websocket_pid" kill -9 $websocket_pid || echo "failed kill websocket" echo "kill websocket success!" fi echo "stop success!" exit 0; } restart() { echo "begin restart server" echo "begin stop server" local express_pid=$(netstat -nlp | grep :$express_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) local push_pid=$(netstat -nlp | grep :$push_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) local websocket_pid=$(netstat -nlp | grep :$websocket_port | awk '{print $7}' | awk -F "/" '{ print $1 }' ) if [ -n "$express_pid" ]; then echo "stop express $express_pid" kill -9 $express_pid || echo "failed kill express" echo "kill express success!" fi if [ -n "$websocket_pid" ]; then echo "stop websocket $websocket_pid" kill -9 $websocket_pid || echo "failed kill websocket" echo "kill websocket success!" fi echo "stop server success, begin start" node express-real-time-video.js & >logFile.log echo "restart server success!" exit 0; } if (( $# > 0 )); then case "$1" in -start) shift start $@ ;; -stop) shift stop $@ ;; -restart) shift restart $@ ;; *) echo "unknown command $1" ;; esac else echo "need more command" fi |
1 2 3 4 5 6 7 8 | # 开启 bash express-ffmpeg.sh -start # 关闭 bash express-ffmpeg.sh -stop # 重启 bash express-ffmpeg.sh -restart |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了