用于web播放海康威视rtsp://admin:ca123456@192.168.64.82:554//Streaming/Channels/1 格式的视频。
注意:前2个目前都在windows上使用,服务器安装部署多多少少有些问题,如果急需解决则直接看第3个方法。
1、WebRtcStreamer
github:https://github.com/mpromonet/webrtc-streamer/releases 但是经常打不开 ,如果有需要私信我,因为太忙了没时间放网盘,见谅
里面有windows版也有linux版的
在本地使用,进入exe目录
启动,默认是8000
打开测试
默认是index.html,我们可以再html里面新建一个test.html修改一下
test.html
<html> <head> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="libs/adapter.min.js"></script> <script src="webrtcconfig.js"></script> <script src="webrtcstreamer.js"></script> <script> let options = webrtcConfig.options; let codec = webrtcConfig.codec; console.log(codec); window.onload = function () { this.webRtcServer = new WebRtcStreamer("video", webrtcConfig.url); webRtcServer.connect( "rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream", "", options, undefined, codec ); }; window.onbeforeunload = function () { this.webRtcServer.disconnect(); }; </script> </head> <body> <video id="video" muted playsinline controls></video> <!-- <iframe src="http://127.0.0.1:8000/webrtcstreamer.html?video=rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream"></iframe> --> </body> </html>
其中我们测试的rtsp是 rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream
如果不知道网页上的rtsp是否可用,可以下载vlc-3.0.20-win64 播放器打开网络串流测试
可以播放即能使用。
打开test页面, 播放成功。
这个使用起来还是比较简单,但是linux服务器安装比较麻烦,目前还在实践中。
2、node websockt+ffmpeg转码成flv
node服务端调用ffmpeg转码然后前端使用
服务端
serve.js
const WebSocket = require('ws') const webSocketStream = require('websocket-stream/stream') // const ffmpeg = require('fluent-ffmpeg') const ffmpegInstaller = require('@ffmpeg-installer/ffmpeg'); const ffmpeg = require('fluent-ffmpeg'); ffmpeg.setFfmpegPath(ffmpegInstaller.path); // 建立WebSocket服务 const wss = new WebSocket.Server({ port: 8888, perMessageDeflate: false }) // 监听连接 wss.on('connection', handleConnection) // 连接时触发事件 function handleConnection (ws, req) { // 获取前端请求的流地址(前端websocket连接时后面带上流地址) const url = req.url.slice(1) // 传入连接的ws客户端 实例化一个流 const stream = webSocketStream(ws, { binary: true }) // 通过ffmpeg命令 对实时流进行格式转换 输出flv格式 const ffmpegCommand = ffmpeg(url) .addInputOption('-analyzeduration', '100000', '-max_delay', '1000000') .on('start', function () { console.log('Stream started.') }) .on('codecData', function () { console.log('Stream codecData.') }) .on('error', function (err) { console.log('An error occured: ', err.message) stream.end() }) .on('end', function () { console.log('Stream end!') stream.end() }) .outputFormat('flv').videoCodec('copy') // .outputFormat('flv').videoCodec('copy').noAudio() // 取消音频输出 stream.on('close', function () { ffmpegCommand.kill('SIGKILL') }) try { // 执行命令 传输到实例流中返回给客户端 ffmpegCommand.pipe(stream) } catch (error) { console.log(error) } }
创建一个package.json
{ "name": "rtsp-vue-server", "version": "1.0.0", "scripts": { "start": "node server.js" }, "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", "express-ws": "^5.0.2", "fluent-ffmpeg": "^2.1.2", "node-media-server": "^2.6.2", "node-rtsp-stream": "^0.0.9", "websocket-stream": "^5.5.2" } }
安装完必须的包后可以通过npm start启动。
启动完成。
然后下载一个windows版本ffmpeg进行转码
ffmpeg-N-103197-gbff7d662d7-win64-gpl 官网:https://www.ffmpeg.org/download.html#build-windows
解压完成后通过环境变量加入path,否则会找不到
测试 ffmpeg -v
然后前端使用
npm install flv.js --save
需要安装flv
import flvjs from 'flv.js';
<video muted="muted" controls width="600" height="600" style="width:100%; height:100%" ref="video"></video>
url: 'rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream',
createVideo() { if (flvjs.isSupported()) { const videoElement = this.$refs.video; this.flvPlayer = flvjs.createPlayer( { type: "flv", // isLive: false, // hasAudio: false, url: "ws://localhost:8888/" + this.url, }, { cors: true, // 是否跨域 // enableWorker: true, // 是否多线程工作 enableStashBuffer: false, // 是否启用缓存 // stashInitialSize: 128, // 缓存大小(kb) 默认384kb autoCleanupSourceBuffer: true, // 是否自动清理缓存 fixAudioTimestampGap: false, //false才会音视频同步 } ); this.flvPlayer.attachMediaElement(videoElement); this.flvPlayer.load(); this.flvPlayer.play(); // 报错重连 this.flvPlayer.on(flvjs.Events.ERROR, (errType, errDetail) => { console.log("errorType:", errType); console.log("errorDetail:", errDetail); this.play(); }); } }, // 销毁video destoryVideo() { if (this.flvPlayer) { this.flvPlayer.pause(); this.flvPlayer.unload(); this.flvPlayer.detachMediaElement(); this.flvPlayer.destroy(); this.flvPlayer = null; } }, // 重播/播放 play() { this.destoryVideo(); this.createVideo(); },
加载片刻后即可播放
目前也还在实践linux部署。
3.海康威视无插件开发包
前提需要确保摄像头可以支持websocket,可以登录 海康威视的平台查看网络配置。
需要注意的是,海康威视会有一个大的ip以及子ip摄像头,需要登录包含所有的摄像头ip去查看。
ex:
ip1: 192.168.64.214 登录之后可以查看所有通道下的摄像头视频
ip2: 192.168.64.xx 登录之后只能查看一个摄像头的视频 与之对应的视频地址:rtsp://admin:ca123456@192.168.64.xx:554//Streaming/Channels/1
这是需要注意的,我当时就是使用了ip2测试,但是一直播放不了,但使用ip1即可。
首先下载开发包
https://open.hikvision.com/download/5cda567cf47ae80dd41a54b3?type=20&id=4c945d18fa5f49638ce517ec32e24e24
下载完成解压如下:
其中webs里面会有测试demo nginx是用于起服务的以及连接websocket ,其中nginx的配置需要拷贝到服务器上的nginx里面。
doc里面还会有一些问题。如下:
因为看到了不支持本地配置 所以我并没有在本地测试,可以直接在服务器上测试demo,首先确保服务器可以连到摄像头对应的ip。
服务器测试:
配置nginx 只需要配置指向web以及sdk和websocket的配置
location / {
root "usr/wen/webs";
index index.html index.htm;
}
location ~ /ISAPI|SDK/ { if ($http_cookie ~ "webVideoCtrlProxy=(.+)") { proxy_pass http://$cookie_webVideoCtrlProxy; break; } } location ^~ /webSocketVideoCtrlProxy { #web socket proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; if ($http_cookie ~ "webVideoCtrlProxyWs=(.+)") { proxy_pass http://$cookie_webVideoCtrlProxyWs/$cookie_webVideoCtrlProxyWsChannel?$args; break; } if ($http_cookie ~ "webVideoCtrlProxyWss=(.+)") { proxy_pass http://$cookie_webVideoCtrlProxyWss/$cookie_webVideoCtrlProxyWsChannel?$args; break; } }
然后再服务器上打开对应的ip进行测试。
测试的流程可以根据开发包里面的pdf来操作
如果没有什么问题的话,就可以打开,其中也可以根据里面的操作信息提示判断一些问题。
点击登录,如果可以登录成功表明以及连上了。
然后点开始预览就会去获取通道的一些信息,有零通道 模拟通道 数字通道等,就可以看到摄像头到底使用的哪一个通道,倒是后集成到前端时我们就可以去掉一些通道。
如果获取通道都失败可能表明 nginx没配置好 或者 摄像头是否开启了或者是否支持websocket等原因。
测试结果因为当时在内网没有截图,所以无法展现。
如果测试通过了,即可集成到vue项目中了。
按照demo的引入方式,我们需要引入这些内容
<script type="text/javascript" src="/jquery-1.7.1.min.js"></script> <script type="text/javascript" src="/codebase/encryption/AES.js"></script> <script type="text/javascript" src="/codebase/encryption/cryptico.min.js"></script> <script type="text/javascript" src="/codebase/encryption/crypto-3.1.2.min.js"></script> <script type="text/javascript" id="videonode" src="/codebase/webVideoCtrl.js"></script>
在video页面使用
新建一个webVideo.js 配置初始化连接海康的文件 // 初始化插件
export function WebVideo() { this.g_iWndIndex = 0; this.szDeviceIdentify = ""; this.deviceport = "8000"; this.rtspPort = "554"; this.channels = []; this.ip = "192.168.64.214"; this.port = "80"; this.username = "admin"; this.password = "ca123456"; this.idchannel = 1; //默认的通道id this.init = function (ip, username, password, idchannel) { this.ip = ip; this.username = username; this.password = password; this.idchannel = idchannel; //传进来的通道 // var self = this // 检查插件是否已经安装过 // var iRet = WebVideoCtrl.I_CheckPluginInstall(); // if (-1 == iRet) { // alert("您还未安装过插件,双击开发包目录里的WebComponentsKit.exe安装!"); // return; // } // 初始化插件参数及插入插件 WebVideoCtrl.I_InitPlugin(454, 315, { szColorProperty: "plugin-background:#102749; sub-background:#102749; sub-border:#18293c; sub-border-select:red", bWndFull: true, // 全屏 // iPackageType: 2, iWndowType: 1, //分屏 bNoPlugin: true, // 支持无插件 cbInitPluginComplete: function () { WebVideoCtrl.I_InsertOBJECTPlugin("divPlugin"); }, }); }; // 登录 this.clickLogin = function () { var self = this; if ("" == self.ip || "" == self.port) { return; } self.szDeviceIdentify = self.ip + "_" + self.port; console.log(self.idchannel); WebVideoCtrl.I_Login(self.ip, 1, self.port, self.username, self.password, { success: function (xmlDoc) { setTimeout(function () { console.log("登录成功");
//登录完成之后需要获取该ip下所有通道,可以通过demo测试查看使用的哪一个通道 我这里用的是数字通道 有的可能是模拟通道
// 由于我已经知晓了所有通道信息 我就没有去吊数字通道 // self.getChannelInfoData(); // self.getDevicePort(); }, 10); setTimeout(function () { self.clickStartRealPlay(); }, 5000); }, error: function (status, xmlDoc) { console.log("登录失败"); }, }); }; // 退出 this.clickLogout = function () { var self = this; self.channels = []; var szDeviceIdentify = $("#ip").val(), szInfo = ""; if (null == szDeviceIdentify) { return; } // if (null == self.szDeviceIdentify) { // return; // } // var iRet = WebVideoCtrl.I_Logout(self.szDeviceIdentify); var iRet = WebVideoCtrl.I_Logout(szDeviceIdentify); if (0 == iRet) { console.log("退出成功"); // self.getChannelInfoData(); // self.getDevicePort(); } }; // 获取通道 this.getChannelInfo = function () { var self = this; self.channels = []; if (null == self.szDeviceIdentify) { return; } // 模拟通道 WebVideoCtrl.I_GetAnalogChannelInfo(self.szDeviceIdentify, { async: false, success: function (xmlDoc) { var oChannels = $(xmlDoc).find("VideoInputChannel"); console.log(oChannels); $.each(oChannels, function (i) { var id = $(this).find("id").eq(0).text(), name = $(this).find("name").eq(0).text(); if ("" == name) { name = "Camera " + (i < 9 ? "0" + (i + 1) : i + 1); } self.channels.push({ id: id, name: name, }); }); console.log("获取模拟通道号成功"); }, error: function (status, xmlDoc) { console.log("获取模拟通道号失败"); }, }); }; //数字通道 this.getChannelInfoData = function () { var self = this; self.channels = []; if (null == self.szDeviceIdentify) { return; } WebVideoCtrl.I_GetDigitalChannelInfo(self.szDeviceIdentify, { async: false, success: function (xmlDoc) { var oChannels = $(xmlDoc).find("InputProxyChannelStatus"); console.log(oChannels); $.each(oChannels, function (i) { var id = $(this).find("id").eq(0).text(), name = $(this).find("name").eq(0).text(); if ("" == name) { name = "Camera " + (i < 9 ? "0" + (i + 1) : i + 1); } self.channels.push({ id: id, name: name, }); }); console.log("获取模拟通道号成功"); }, error: function (status, xmlDoc) { console.log("获取模拟通道号失败"); }, }); }; // 显示回调信息 this.showCBInfo = (szInfo) => { szInfo = "<div>" + szInfo +"</div>"; $("#title-video").html(szInfo); }; // 获取端口 this.getDevicePort = function () { var self = this; if (null == self.szDeviceIdentify) { return; } var oPort = WebVideoCtrl.I_GetDevicePort(self.szDeviceIdentify); if (oPort != null) { self.deviceport = oPort.iDevicePort; self.rtspPort = oPort.iRtspPort; } console.log("获取端口号成功"); }; // 开始预览 this.clickStartRealPlay = function () { var self = this; var oWndInfo = WebVideoCtrl.I_GetWindowStatus(self.g_iWndIndex), // iChannelID = self.channels[0].id iChannelID = self.idchannel; console.log(self.channels); if (null == self.szDeviceIdentify) { return; } var startRealPlay = function () { WebVideoCtrl.I_StartRealPlay(self.szDeviceIdentify, { iChannelID: iChannelID, bZeroChannel: false, iStreamType: 2, success: function () { console.log("预览成功"); self.showCBInfo("") }, error: function (status, xmlDoc) { if (403 === status) { console.log("设备不支持Websocket取流"); } else { console.log("预览失败"); } }, }); }; if (oWndInfo != null) { // 已经在播放了,先停止 WebVideoCtrl.I_Stop({ success: function () { startRealPlay(); }, }); } else { startRealPlay(); } }; // 停止预览 this.clickStopRealPlay = function (id = 0) { var self = this; var oWndInfo = WebVideoCtrl.I_GetWindowStatus(self.g_iWndIndex); if (oWndInfo != null) { WebVideoCtrl.I_Stop({ success: function () { // console.log("停止预览成功"); // alert("停止预览成功"); // console.log("开始登出"); self.showCBInfo("停止预览中,请稍后...") // self.clickLogout(); if (id > 0) { self.idchannel = id; self.clickStartRealPlay(); } }, error: function () { console.log("停止预览失败"); }, }); } }; }
在页面中使用:
正常播放视频的逻辑是,先初始化->登录->获取通道->选择通道播放
其中通道我们可以在demo测试那里知道到底用的哪一个通道,甚至我们可以直接把通道复制下来写死,正如下方所示:
通道格式是xml,如下:
我们配置数据如下:
{ id: 1, name: "CALY涂装排气塔4" }, { id: 2, name: "CALY储运车场H库" }, { id: 3, name: "CALY涂装RTO4" }, { id: 4, name: "CALY涂装VOC内4" }, { id: 5, name: "CALY涂装VOC外4" }, { id: 6, name: "CALY废水站槽体A4" }, { id: 7, name: "CALY涂装危废间4" }, { id: 8, name: "CALY废水站槽体B4" }, { id: 9, name: "CALY排气塔东侧3" }, { id: 10, name: "CALY一厂污水处理站排水槽" }, { id: 11, name: "CALY一厂污水处理过滤池" },
这就是该ip下所有的通道,我们只需要通道id用于播放。
如果你知晓了通道,就可以不用再获取通道信息。正如我上面的js一样,我注释调了获取通道的内容,我直接将通道传入。
我默认就播放第一个通道,如果你需要真实的不写死的话就可以直接去获取通道,然后选择其中的来播放。
<template> <Backdrop :title="'轨道巡查视频'"> <template #Assembly> <div class="on-line-monitoring-video"> <div class="title" id="title-video"></div> <el-icon class="iconswitch" @click="showChannelList = !showChannelList" title="切换视频" ><Switch /></el-icon> <div class="srollList" ref="srollListRef" v-show="showChannelList"> <div v-for="(item, key) in channelList" :ref="'srollDiv' + key + 'ref'" :title="item.name" :key="key" @click="selectVideo(item.id)" > <span :class="activeIndex == item.id ? 'active' : ''">{{ item.name }}</span> </div> </div> <div id="divPlugin" class="divPlugin" style="width: 100%; height: 100%; overflow-y: auto" /> </div> </template> </Backdrop> </template> <script> import { ref } from "vue"; import { WebVideo } from "@/utils/webVideo.js"; export default { name: "OnLineMonitoringVideo", data() { return { activeIndex: 1, webVideoPlayer: null, hkvInfo: { ip: "192.168.64.214", //ip username: "admin", //用户名 password: "ca123456", //密码 }, showChannelList: false,
//所有的通道list channelList: [ { id: 1, name: "CALY涂装排气塔4" }, { id: 2, name: "CALY储运车场H库" }, { id: 3, name: "CALY涂装RTO4" }, { id: 4, name: "CALY涂装VOC内4" }, { id: 5, name: "CALY涂装VOC外4" }, { id: 6, name: "CALY废水站槽体A4" }, { id: 7, name: "CALY涂装危废间4" }, { id: 8, name: "CALY废水站槽体B4" }, { id: 9, name: "CALY排气塔东侧3" }, { id: 10, name: "CALY一厂污水处理站排水槽" }, { id: 11, name: "CALY一厂污水处理过滤池" }, ], }; }, mounted() { this.initVideoPlay(); }, beforeDestroy() { // this.stopVideoPlay(); this.webVideoPlayer.clickStopRealPlay(); }, methods: { selectVideo(key) {
//切换视频 先停止再播放下一个 this.activeIndex = key; this.stopVideoPlay(key); // this.initVideoPlay(); }, initVideoPlay() { if (this.webVideoPlayer == null) this.webVideoPlayer = new WebVideo(); this.$nextTick(() => { this.webVideoPlayer.init( this.hkvInfo.ip, this.hkvInfo.username, this.hkvInfo.password, this.activeIndex ); this.webVideoPlayer.clickLogin(); }); }, stopVideoPlay(key) { this.webVideoPlayer.clickStopRealPlay(key); }, }, }; </script> <style lang="less" scoped> .iconswitch { position: absolute; left: 0px; top: 50%; font-weight: bold; cursor: pointer; } #title-video { color: red; height: 20px; position: absolute; top: 0px; text-align: center; width: 100%; padding: 10px; } </style>
然后部署到服务器上就可以正常播放第一个视频了,但是目前切换视频很慢还没有解决。
其中关闭上一个视频的操作很慢,主要是WebVideoCtrl.I_Stop 这个方法特别慢,内置的无法知晓。