JavaScript實(shí)現(xiàn)網(wǎng)頁端播放攝像頭實(shí)時畫面
初衷
寫這篇博客已經(jīng)是項(xiàng)目過去很久了,之所以寫是因?yàn)楫?dāng)時被這個問題為難了很久。我原本是做后端的,涉及到前端的東西,當(dāng)時是兩眼一黑。好在最后還是解決了。當(dāng)相信這個內(nèi)容還是有價值的,所以今天整理出來,幫助未來可能需求的人。
應(yīng)對的場景
希望在自己的Web應(yīng)用中播放局域網(wǎng)(不能上云),或是廣域網(wǎng)的攝像頭實(shí)時畫面。
涉及到的范圍
- Nodejs 以及 Express
- WebSocket html頁面拉流
- ffmpeg 推流用
- node-rtsp-stream 主要依賴這個東西,將 rtsp 流推送到 Ws
- JSMpeg 主要用來播放 ws 流畫面
這個解決方案是全前端方案,所以后端的流處理都是用Node處理的。
解決問題的思路
- 首先要拿到攝像頭的播放Rtsp通道。(有些是帶密碼的,有些不帶密碼)。
- 使用ffmpeg將rtsp流轉(zhuǎn)成ws流。
- 當(dāng)客戶端請求播放攝像頭畫面的時候,Node接受請求,并將流地址返回給前端。
- 前端使用 JSMpeg 去播放ws流,畫面呈現(xiàn)。
- 閉關(guān)的時候,仍然請求后端,用Node處理。閉關(guān)推流進(jìn)程。
攝像頭的Rtsp地址
因?yàn)檫@里沒有攝像頭,所以我在網(wǎng)上搜索了一個流地址:
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
如果電腦中裝了 VCL,則可以使用VCL的流地址播放功能去播放,這里我就不做演示了。
ffmpeg 下載,安裝,配置環(huán)境變量
這里我在gitHub上找到了一個ffmepg下載地址 ,這個版本是windows版本的。
安裝就不用說了,下一步,下一步即可,最后就說配置環(huán)境變量。其作用就是在命令行狀態(tài)下可以直接通過 ffmpeg 訪問到文件。
nodejs 和 Express
nodejs 我就不詳細(xì)介紹了,express也一樣,這兩個東西要是不會,這篇文章也就不用看了。
然后就是寫代碼接受前端到http請求了??梢詤⒖疾┛蛨@里面的express介紹。稍后我會貼出代碼,建議看看文檔。
接受請求的代碼
var express = require('express'); const requestmanager = require('../lib/RequestManager') var router = express.Router(); router.get('/', function (req, res) { res.send('Carmeras Server is Runing...'); }); /* GET users listing. */ router.post('/', function (req, res) { var cfg = req.body let result = new requestmanager().Open(cfg) res.json(result) }); router.post('/close', function (req, res) { var cfg = req.body new requestmanager().Close(cfg) res.json({ state: 'close the rtsp stream success.' }) })
這里用到了 node-rtsp-stream 、express 、express.Router 還引用了一個 RequestManager,這是我自己寫的一個管理請求的包,代碼如下:
const Stream = require('node-rtsp-stream') const os = require('os'); ///獲取本機(jī)ip/// function getIPAdress() { var interfaces = os.networkInterfaces(); for (var devName in interfaces) { var iface = interfaces[devName]; for (var i = 0; i < iface.length; i++) { var alias = iface[i]; if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { return alias.address; } } } } const args = [] const requestManager = function () { } //這里是在原型上加上打開和關(guān)閉兩個方法 requestManager.prototype = { Open: function (arg) { let result = {} if (args.length == 0) { result = this._create(arg) result = this._openVideo(result) } else { args.forEach(a => { if (a.rtspUrl == arg.rtspUrl) { result = a } }) if (result.port === undefined || result.rtspUrl === undefined) { result = this._create(arg) result = this._openVideo(result) } } result = Object.assign(result,{url:`ws:\\${getIPAdress()}:${result.port}`}) return result; }, Close: function (arg) { let result = {} let idx = -1 idx = args.findIndex(a => a.rtspUrl == arg.rtspUrl) if (idx !== -1) { args[idx].stream.stop() result = args.splice(idx, 1) } else { } console.log(args) return result }, //這里是產(chǎn)生一個隨機(jī)端口號,用來推流使用。 _randomPort: function () { let port = Math.floor(Math.random() * (4001 - 3001) + 3001) return port }, //這里是核心推流代碼,其實(shí)很簡單。 _openVideo: function (arg) { arg.stream = new Stream({ name: 'name', //streamUrl: 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov', streamUrl: arg.rtspUrl, wsPort: arg.port, ffmpegOptions: { // options ffmpeg flags '-stats': '', // an option with no neccessary value uses a blank string '-r': 30, // options with required values specify the value after the key '-s': arg.size, '-codec:a': 'mp2', '-ar': 44100, '-ac': 1, '-b:a': '128k' } }) return arg }, //這里創(chuàng)建參數(shù)。 _create: function (arg) { let target = { rtspUrl: 'rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov', port: this._randomPort(), size: '1024*768', stream: null } let source = { rtspUrl: arg.rtspUrl, port: this._randomPort(), size: arg.size, stream: null } Object.assign(target, source) args.push(target) return target } } module.exports = requestManager
當(dāng) Open方法被調(diào)用的時候,node-rtsp-stream會調(diào)用 ffmepg 程序開始推流。其參數(shù)如下:
ffmpegOptions: { // options ffmpeg flags '-stats': '', // an option with no neccessary value uses a blank string '-r': 30, // options with required values specify the value after the key '-s': arg.size, '-codec:a': 'mp2', '-ar': 44100, '-ac': 1, '-b:a': '128k' }
這里關(guān)注 -s 它是設(shè)置畫幅大小的。所以我這里用到了參數(shù) arg.size。帶_(下劃線)的方法內(nèi)部使用。 關(guān)鍵位置都給了注視了,一般應(yīng)該是看的懂了。
JSMpeg 播放 和請求打開關(guān)閉
這是個第三方庫,在gitee和gitHub上都有,這里列出Gitee上的地址JSMpeg我用到的關(guān)鍵代碼就幾句
/*初始化并播放*/ let player = new JSMpeg.Player(url, opt); /*銷毀關(guān)閉*/ player.destroy()
至于請求,可以用axios 或是 jquery庫,我這里用的是 jquery
var player //關(guān)閉 function closeStream() { $.post("http://127.0.0.1:3000/cameras/close/", { rtspUrl: $('#rtsp').val() }, function (result) { player.destroy() }) } //打開 function start() { var rstp = $('#rtsp').val() var size = $('#size').val() $.post("http://127.0.0.1:3000/cameras/", { rtspUrl:rstp, size: size }, function (result) { var url = "ws://127.0.0.1:" + result.port; var canvas = document.getElementById('video-canvas'); let opt = { canvas: canvas, poster: "0.jpg", } player = new JSMpeg.Player(url, opt); }) }
完整的Html如下:
<!DOCTYPE html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>DEMO node-rtsp-stream-jsmpeg</title> <script src="https://jsmpeg.com/jsmpeg.min.js"></script> <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script> <style type="text/css"> html, body { text-align: center; } input[type='text'] { width: 450px; } </style> </head> <body> <div> <!-- <span>rtsp : <input type="text" name="rtsp" id="rtsp" value="rtsp://admin:xcs123456@192.168.3.11:554/h264/ch1/main/av_stream"></span><br /> --> <span>rtsp : <input type="text" name="rtsp" id="rtsp" value="rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov"></span><br /> <span>rtsp : <input type="text" name="size" id="size" value="1024*768"></span><br /> <canvas id="video-canvas"> </canvas><br /> <input type="button" value="Start Stream" onclick="start()"> <input type="button" value="Close Stream" onclick="closeStream()"> </div> <script type="text/javascript"> var player function closeStream() { $.post("http://127.0.0.1:3000/cameras/close/", { rtspUrl: $('#rtsp').val() }, function (result) { player.destroy() }) } function start() { var rstp = $('#rtsp').val() var size = $('#size').val() $.post("http://127.0.0.1:3000/cameras/", { rtspUrl:rstp, size: size }, function (result) { var url = "ws://127.0.0.1:" + result.port; var canvas = document.getElementById('video-canvas'); let opt = { canvas: canvas, poster: "0.jpg", } player = new JSMpeg.Player(url, opt); }) } </script> </body>
以上就是全部的內(nèi)容,完整代碼可以到gitee上下載。
以上就是JavaScript實(shí)現(xiàn)網(wǎng)頁端播放攝像頭實(shí)時畫面的詳細(xì)內(nèi)容,更多關(guān)于JavaScript攝像頭實(shí)時畫面的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript中forEach和map詳細(xì)講解
foreach和map都是JavaScript中數(shù)組的常用方法,它們都可以對數(shù)組中的每個元素執(zhí)行一個函數(shù),但是它們有一些區(qū)別,下面這篇文章主要給大家介紹了關(guān)于JavaScript中forEach和map詳細(xì)講解的相關(guān)資料,需要的朋友可以參考下2023-11-11JavaScript之Map和Set_動力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要為大家詳細(xì)介紹了JavaScript之Map和Set的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-06-06JS實(shí)現(xiàn)數(shù)組過濾從簡單到多條件篩選
一般情況下的單條件篩選,數(shù)組的filter方法就能夠滿足需求,本文討論的重點(diǎn)是多條件下的復(fù)合篩選,并列出了幾個相關(guān)知識點(diǎn),感興趣的可以了解一下2021-07-07學(xué)習(xí)JavaScript圖片預(yù)加載模塊
這篇文章主要介紹了js實(shí)現(xiàn)圖片預(yù)加載的方法,內(nèi)容很詳細(xì),帶領(lǐng)大家全面認(rèn)識js圖片預(yù)加載模塊,感興趣的小伙伴們可以參考一下2016-11-11關(guān)于innerHTML后丟失動態(tài)綁定的EVENT問題解決方法
用innerHTML取出一段內(nèi)容后再innerHTML回去,那么原來動態(tài)綁定的事件就會丟失,下面與大家分享下解決方法,感興趣的朋友可以參考下哈2013-05-05當(dāng)某個文本框成為焦點(diǎn)時即清除文本框內(nèi)容
這篇文章主要介紹了當(dāng)某個文本框成為焦點(diǎn)時如何清除文本框內(nèi)容,需要的朋友可以參考下2014-04-04