JavaScript實(shí)現(xiàn)網(wǎng)頁端播放攝像頭實(shí)時(shí)畫面
初衷
寫這篇博客已經(jīng)是項(xiàng)目過去很久了,之所以寫是因?yàn)楫?dāng)時(shí)被這個(gè)問題為難了很久。我原本是做后端的,涉及到前端的東西,當(dāng)時(shí)是兩眼一黑。好在最后還是解決了。當(dāng)相信這個(gè)內(nèi)容還是有價(jià)值的,所以今天整理出來,幫助未來可能需求的人。
應(yīng)對的場景
希望在自己的Web應(yīng)用中播放局域網(wǎng)(不能上云),或是廣域網(wǎng)的攝像頭實(shí)時(shí)畫面。
涉及到的范圍
- Nodejs 以及 Express
- WebSocket html頁面拉流
- ffmpeg 推流用
- node-rtsp-stream 主要依賴這個(gè)東西,將 rtsp 流推送到 Ws
- JSMpeg 主要用來播放 ws 流畫面
這個(gè)解決方案是全前端方案,所以后端的流處理都是用Node處理的。
解決問題的思路
- 首先要拿到攝像頭的播放Rtsp通道。(有些是帶密碼的,有些不帶密碼)。
- 使用ffmpeg將rtsp流轉(zhuǎn)成ws流。
- 當(dāng)客戶端請求播放攝像頭畫面的時(shí)候,Node接受請求,并將流地址返回給前端。
- 前端使用 JSMpeg 去播放ws流,畫面呈現(xiàn)。
- 閉關(guān)的時(shí)候,仍然請求后端,用Node處理。閉關(guān)推流進(jìn)程。
攝像頭的Rtsp地址
因?yàn)檫@里沒有攝像頭,所以我在網(wǎng)上搜索了一個(gè)流地址:
rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov'
如果電腦中裝了 VCL,則可以使用VCL的流地址播放功能去播放,這里我就不做演示了。
ffmpeg 下載,安裝,配置環(huán)境變量
這里我在gitHub上找到了一個(gè)ffmepg下載地址 ,這個(gè)版本是windows版本的。
安裝就不用說了,下一步,下一步即可,最后就說配置環(huán)境變量。其作用就是在命令行狀態(tài)下可以直接通過 ffmpeg 訪問到文件。
nodejs 和 Express
nodejs 我就不詳細(xì)介紹了,express也一樣,這兩個(gè)東西要是不會(huì),這篇文章也就不用看了。
然后就是寫代碼接受前端到http請求了??梢詤⒖疾┛蛨@里面的express介紹。稍后我會(huì)貼出代碼,建議看看文檔。
接受請求的代碼
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 還引用了一個(gè) RequestManager,這是我自己寫的一個(gè)管理請求的包,代碼如下:
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)閉兩個(gè)方法
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)生一個(gè)隨機(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)用的時(shí)候,node-rtsp-stream會(huì)調(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)閉
這是個(gè)第三方庫,在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í)時(shí)畫面的詳細(xì)內(nèi)容,更多關(guān)于JavaScript攝像頭實(shí)時(shí)畫面的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
ko knockoutjs動(dòng)態(tài)屬性綁定技巧應(yīng)用
ko的動(dòng)態(tài)屬性是指,ViewModel不確定的屬性,而后期卻需要的屬性,本文將詳細(xì)介紹,需要的朋友參考下2012-11-11
JavaScript簡單生成 N~M 之間隨機(jī)數(shù)的方法
這篇文章主要介紹了JavaScript簡單生成 N~M 之間隨機(jī)數(shù)的方法,結(jié)合自定義函數(shù)分析了JS生成固定區(qū)間內(nèi)隨機(jī)數(shù)的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
Electron 使? electron-builder 打包應(yīng)用過程詳解
Electron應(yīng)用開發(fā)中,electron-builder是一個(gè)常用的打包工具,提供了多種自定義配置,不過,使用npm安裝electron-builder時(shí)可能會(huì)遇到下載依賴慢或失敗的問題,本文給大家介紹Electron 使? electron-builder 打包應(yīng)用的相關(guān)操作,感興趣的朋友一起看看吧2024-10-10
JavaScript實(shí)現(xiàn)獲得所有兄弟節(jié)點(diǎn)的方法
這篇文章主要介紹了JavaScript實(shí)現(xiàn)獲得所有兄弟節(jié)點(diǎn)的方法,實(shí)例分析了javascript節(jié)點(diǎn)遍歷的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07
使用javascript實(shí)現(xiàn)一個(gè)在線RGB顏色轉(zhuǎn)換器
目前已經(jīng)有很多網(wǎng)頁版在線小工具,之前很多窗體化的工具也逐漸網(wǎng)頁化,比如:PS畫圖軟件,也都能直接網(wǎng)頁化進(jìn)行設(shè)計(jì),由于自己實(shí)際項(xiàng)目經(jīng)常會(huì)用到顏色轉(zhuǎn)換,所以直接自己開發(fā)個(gè)簡單版的在線顏色轉(zhuǎn)換小工具,需要的朋友可以參考下2024-01-01
微信小程序?qū)崿F(xiàn)簡單倒計(jì)時(shí)功能
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)簡單倒計(jì)時(shí)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Javascript中JSON數(shù)據(jù)分組優(yōu)化實(shí)踐及JS操作JSON總結(jié)
這篇文章主要介紹了Javascript中JSON數(shù)據(jù)分組優(yōu)化實(shí)踐,文中還對JS操作JSON的要領(lǐng)做了總結(jié),需要的朋友可以參考下2017-12-12

