mild refactor for embed live stream feature
parent
49b518d595
commit
3a7d30e362
|
@ -39,6 +39,8 @@ module.exports = function(s,config,lang,app){
|
|||
app.get([config.webPaths.apiPrefix+':auth/embed/:ke/:id',config.webPaths.apiPrefix+':auth/embed/:ke/:id/:addon'], function (req,res){
|
||||
req.params.protocol=req.protocol;
|
||||
s.auth(req.params,function(user){
|
||||
const authKey = req.params.auth
|
||||
const groupKey = req.params.ke
|
||||
const monitorId = req.params.id
|
||||
if(cantLiveStreamPermission(user,monitorId)){
|
||||
s.closeJsonResponse(res,{ok: false, msg: lang['Not Authorized']});
|
||||
|
@ -52,7 +54,21 @@ module.exports = function(s,config,lang,app){
|
|||
if(s.group[req.params.ke]&&s.group[req.params.ke].activeMonitors[req.params.id]){
|
||||
if(s.group[req.params.ke].activeMonitors[req.params.id].isStarted === true){
|
||||
req.params.uid=user.uid;
|
||||
s.renderPage(req,res,config.renderPaths.embed,{data:req.params,baseUrl:req.protocol+'://'+req.hostname,config: s.getConfigWithBranding(req.hostname),lang:user.lang,mon:Object.assign(s.group[req.params.ke].rawMonitorConfigurations[req.params.id],{}),originalURL:s.getOriginalUrl(req)});
|
||||
s.renderPage(req,res,config.renderPaths.embed,{
|
||||
data: req.params,
|
||||
baseUrl: req.protocol+'://'+req.hostname,
|
||||
config: s.getConfigWithBranding(req.hostname),
|
||||
define: s.getDefinitonFile(user.details ? user.details.lang : config.lang),
|
||||
$user: {
|
||||
auth_token: authKey,
|
||||
ke: groupKey,
|
||||
uid: user.uid,
|
||||
mail: user.mail,
|
||||
details: {},
|
||||
},
|
||||
mon: Object.assign(s.group[req.params.ke].rawMonitorConfigurations[req.params.id],{}),
|
||||
originalURL: s.getOriginalUrl(req)
|
||||
});
|
||||
res.end()
|
||||
}else{
|
||||
res.end(user.lang['Cannot watch a monitor that isn\'t running.'])
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
.progress-bar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
background-color: #1f80f9;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
|
||||
#video_preview .stream-objects {
|
||||
right: 0;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: auto
|
||||
}
|
||||
|
||||
.stream-block,
|
||||
.stream-objects {
|
||||
overflow: hidden !important
|
||||
}
|
||||
|
||||
.stream-objects {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 10
|
||||
}
|
||||
|
||||
.stream-objects .tag {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
background: #d9534f;
|
||||
color: #fff;
|
||||
font-family: monospace;
|
||||
font-size: 80%;
|
||||
border-radius: 15px;
|
||||
padding: 3px 5px;
|
||||
line-height: 1
|
||||
}
|
||||
|
||||
.stream-objects .stream-detected-object {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 3px dotted red;
|
||||
background: transparent;
|
||||
border-radius: 5px
|
||||
}
|
||||
|
||||
.stream-objects .stream-detected-point {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 3px solid yellow;
|
||||
background: transparent;
|
||||
border-radius: 5px
|
||||
}
|
||||
|
||||
.stream-objects .point {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border: 3px solid red;
|
||||
border-radius: 50%
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
body,
|
||||
html {
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.stream-element,
|
||||
.shinobi_stream {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.shinobi_stream video {
|
||||
object-fit: fill
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
body {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shinobi_stream {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.shinobi_hud {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
transition: 0.2s
|
||||
}
|
||||
|
||||
.shinobi_hud:hover {
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.shinobi_hud .shinobi_viewers {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.shinobi_hud .shinobi_viewers {
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
padding: 3px 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background-color: #777;
|
||||
border-radius: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
iframe.stream-element {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* All-CSS Toggle Switch (Checkbox Hack) by Marcus Burnette - https://codepen.io/mburnette/pen/LxNxNg */
|
||||
.shinobi_ws_http_toggle {
|
||||
width: 50px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle input[type=checkbox] {
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label {
|
||||
cursor: pointer;
|
||||
text-indent: -9999px;
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
background: grey;
|
||||
display: block;
|
||||
border-radius: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #fff;
|
||||
border-radius: 90px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle input:checked+label {
|
||||
background: #00118c;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle input:checked+label:after {
|
||||
left: calc(100% - 5px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label:active:after {
|
||||
width: 10px;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
$(window).resize(function(){
|
||||
$('.stream-element').attr('width',$('body').width())
|
||||
$('.stream-element').attr('height',$('body').height())
|
||||
})
|
|
@ -0,0 +1,722 @@
|
|||
var loadedLiveGrids = {}
|
||||
var monitorPops = {}
|
||||
var liveGridElements = {}
|
||||
var runningJpegStreams = {}
|
||||
var liveGrid = $('#monitors_live .stream-element-container')
|
||||
//
|
||||
var onLiveStreamInitiateExtensions = []
|
||||
function onLiveStreamInitiate(callback){
|
||||
onLiveStreamInitiateExtensions.push(callback)
|
||||
}
|
||||
var onLiveStreamCloseExtensions = []
|
||||
function onLiveStreamClose(callback){
|
||||
onLiveStreamCloseExtensions.push(callback)
|
||||
}
|
||||
var onSignalCheckLiveStreamExtensions = []
|
||||
function onSignalCheckLiveStream(callback){
|
||||
onSignalCheckLiveStreamExtensions.push(callback)
|
||||
}
|
||||
var onBuildStreamElementExtensions = []
|
||||
function onBuildStreamElement(callback){
|
||||
onBuildStreamElementExtensions.push(callback)
|
||||
}
|
||||
//
|
||||
function buildStreamElementHtml(streamType){
|
||||
var html = ''
|
||||
if(window.jpegModeOn === true){
|
||||
html = '<img class="stream-element">';
|
||||
}else{
|
||||
switch(streamType){
|
||||
case'hls':case'flv':case'mp4':
|
||||
html = `<video class="stream-element" playsinline muted autoplay></video>`;
|
||||
break;
|
||||
case'mjpeg':
|
||||
html = '<iframe class="stream-element"></iframe>';
|
||||
break;
|
||||
case'jpeg':
|
||||
html = '<img class="stream-element">';
|
||||
break;
|
||||
default://base64//h265
|
||||
html = '<canvas class="stream-element"></canvas>';
|
||||
break;
|
||||
}
|
||||
$.each(onBuildStreamElementExtensions,function(n,extender){
|
||||
var newHtml = extender(streamType)
|
||||
html = newHtml ? newHtml : html
|
||||
})
|
||||
}
|
||||
return html
|
||||
}
|
||||
function resetMonitorCanvas(monitorId,initiateAfter,subStreamChannel){
|
||||
var monitor = loadedMonitors[monitorId]
|
||||
var details = monitor.details
|
||||
var streamType = subStreamChannel ? details.substream ? details.substream.output.stream_type : 'hls' : details.stream_type
|
||||
if(!liveGridElements[monitorId])return;
|
||||
var streamBlock = liveGridElements[monitorId].monitorItem.find('.stream-block')
|
||||
closeLiveGridPlayer(monitorId,false)
|
||||
streamBlock.find('.stream-element').remove()
|
||||
streamBlock.append(buildStreamElementHtml(streamType))
|
||||
if(initiateAfter)initiateLiveGridPlayer(monitor,subStreamChannel)
|
||||
}
|
||||
function buildLiveGridBlock(monitor){
|
||||
if(monitor.mode === 'stop'){
|
||||
new PNotify({
|
||||
title: lang.sorryNo,
|
||||
text: lang[`Cannot watch a monitor that isn't running.`],
|
||||
type: 'danger'
|
||||
})
|
||||
return
|
||||
}
|
||||
var monitorId = monitor.mid
|
||||
var monitorDetails = safeJsonParse(monitor.details)
|
||||
var monitorLiveId = `monitor_live_${monitor.mid}`
|
||||
var subStreamChannel = monitor.subStreamChannel
|
||||
var streamType = subStreamChannel ? monitorDetails.substream ? monitorDetails.substream.output.stream_type : 'hls' : monitorDetails.stream_type
|
||||
var streamElement = buildStreamElementHtml(streamType)
|
||||
var streamBlockInfo = definitions['Monitor Stream Window']
|
||||
if(!loadedLiveGrids[monitor.mid])loadedLiveGrids[monitor.mid] = {}
|
||||
var quickLinkHtml = ''
|
||||
var baseHtml = `<div
|
||||
id="${monitorLiveId}"
|
||||
data-ke="${monitor.ke}"
|
||||
data-mid="${monitor.mid}"
|
||||
data-mode="${monitor.mode}"
|
||||
class="monitor_item glM${monitor.mid} ${streamBlockInfo.gridBlockClass || ''}"
|
||||
>
|
||||
<div class="stream-objects"></div>
|
||||
<div class="stream-hud">
|
||||
${streamBlockInfo.streamBlockHudHtml || ''}
|
||||
</div>
|
||||
${streamElement}
|
||||
${(streamBlockInfo.gridBlockAfterContentHtml || '').replace(`$QUICKLINKS`,quickLinkHtml)}
|
||||
</div>`
|
||||
return baseHtml
|
||||
}
|
||||
|
||||
function drawLiveGridBlock(monitorConfig,subStreamChannel){
|
||||
var monitorId = monitorConfig.mid
|
||||
if($('#monitor_live_' + monitorId).length === 0){
|
||||
var html = buildLiveGridBlock(monitorConfig)
|
||||
liveGrid.html(html);
|
||||
var theBlock = $('#monitor_live_' + monitorId);
|
||||
var streamElement = theBlock.find('.stream-element')
|
||||
liveGridElements[monitorId] = {
|
||||
monitorItem: theBlock,
|
||||
streamElement: streamElement,
|
||||
eventObjects: theBlock.find('.stream-objects'),
|
||||
motionMeter: theBlock.find('.indifference .progress-bar'),
|
||||
motionMeterText: theBlock.find('.indifference .progress-bar span'),
|
||||
width: streamElement.width(),
|
||||
height: streamElement.height(),
|
||||
}
|
||||
}
|
||||
initiateLiveGridPlayer(loadedMonitors[monitorId],subStreamChannel)
|
||||
}
|
||||
function initiateLiveGridPlayer(monitor,subStreamChannel){
|
||||
var livePlayerElement = loadedLiveGrids[monitor.mid]
|
||||
var details = monitor.details
|
||||
var groupKey = monitor.ke
|
||||
var monitorId = monitor.mid
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var loadedPlayer = loadedLiveGrids[monitor.mid]
|
||||
var websocketPath = checkCorrectPathEnding(location.pathname) + 'socket.io'
|
||||
var containerElement = $(`#monitor_live_${monitor.mid}`)
|
||||
var streamType = subStreamChannel ? details.substream ? details.substream.output.stream_type : 'hls' : details.stream_type
|
||||
if(location.search === '?p2p=1'){
|
||||
websocketPath = '/socket.io'
|
||||
// websocketQuery.machineId = machineId
|
||||
}
|
||||
switch(streamType){
|
||||
case'jpeg':
|
||||
startJpegStream(monitorId)
|
||||
break;
|
||||
case'b64':
|
||||
if(loadedPlayer.Base64 && loadedPlayer.Base64.connected){
|
||||
loadedPlayer.Base64.disconnect()
|
||||
}
|
||||
loadedPlayer.Base64 = io(location.origin,{ path: websocketPath, query: websocketQuery, transports: ['websocket'], forceNew: false})
|
||||
var ws = loadedPlayer.Base64
|
||||
var buffer
|
||||
ws.on('diconnect',function(){
|
||||
console.log('Base64 Stream Disconnected')
|
||||
})
|
||||
ws.on('connect',function(){
|
||||
ws.emit('Base64',{
|
||||
auth: $user.auth_token,
|
||||
uid: $user.uid,
|
||||
ke: monitor.ke,
|
||||
id: monitor.mid,
|
||||
channel: subStreamChannel
|
||||
})
|
||||
if(!loadedPlayer.ctx || loadedPlayer.ctx.length === 0){
|
||||
loadedPlayer.ctx = containerElement.find('canvas');
|
||||
}
|
||||
var ctx = loadedPlayer.ctx[0]
|
||||
var ctx2d = ctx.getContext("2d")
|
||||
loadedPlayer.image = new Image()
|
||||
var image = loadedPlayer.image
|
||||
image.onload = function() {
|
||||
loadedPlayer.imageLoading = false
|
||||
var x = 0
|
||||
var y = 0
|
||||
ctx.getContext("2d").drawImage(image,x,y,ctx.width,ctx.height)
|
||||
URL.revokeObjectURL(loadedPlayer.imageUrl)
|
||||
}
|
||||
ws.on('data',function(imageData){
|
||||
try{
|
||||
if(loadedPlayer.imageLoading === true)return console.log('drop');
|
||||
loadedPlayer.imageLoading = true
|
||||
var arrayBufferView = new Uint8Array(imageData);
|
||||
var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
|
||||
loadedPlayer.imageUrl = URL.createObjectURL( blob );
|
||||
loadedPlayer.image.src = loadedPlayer.imageUrl
|
||||
loadedPlayer.last_frame = 'data:image/jpeg;base64,'+base64ArrayBuffer(imageData)
|
||||
}catch(er){
|
||||
debugLog('base64 frame')
|
||||
}
|
||||
// $.ccio.init('signal',d);
|
||||
})
|
||||
})
|
||||
break;
|
||||
case'mp4':
|
||||
setTimeout(function(){
|
||||
var stream = containerElement.find('.stream-element');
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// mainSocket.f({f:'monitor',ff:'watch_on',id:monitor.mid})
|
||||
// },5000)
|
||||
}
|
||||
if(!loadedPlayer.PoseidonErrorCount)loadedPlayer.PoseidonErrorCount = 0
|
||||
if(loadedPlayer.PoseidonErrorCount >= 5)return
|
||||
if(subStreamChannel ? details.substream.output.stream_flv_type === 'ws' : monitor.details.stream_flv_type === 'ws'){
|
||||
if(loadedPlayer.Poseidon){
|
||||
loadedPlayer.Poseidon.stop()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
try{
|
||||
loadedPlayer.Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
query: websocketQuery,
|
||||
onError : onPoseidonError,
|
||||
channel : subStreamChannel
|
||||
})
|
||||
loadedPlayer.Poseidon.start();
|
||||
}catch(err){
|
||||
// onPoseidonError()
|
||||
console.log('onTryPoseidonError',err)
|
||||
}
|
||||
}else{
|
||||
stream.attr('src',getApiPrefix(`mp4`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.mp4?time=' + (new Date()).getTime())
|
||||
stream[0].onerror = function(err){
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
},1000)
|
||||
break;
|
||||
case'flv':
|
||||
if (flvjs.isSupported()) {
|
||||
if(loadedPlayer.flv){
|
||||
loadedPlayer.flv.destroy()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
var options = {};
|
||||
if(monitor.details.stream_flv_type==='ws'){
|
||||
if(monitor.details.stream_flv_maxLatency&&monitor.details.stream_flv_maxLatency!==''){
|
||||
monitor.details.stream_flv_maxLatency = parseInt(monitor.details.stream_flv_maxLatency)
|
||||
}else{
|
||||
monitor.details.stream_flv_maxLatency = 20000;
|
||||
}
|
||||
options = {
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
auth_token: $user.auth_token,
|
||||
ke: monitor.ke,
|
||||
uid: $user.uid,
|
||||
id: monitor.mid,
|
||||
maxLatency: monitor.details.stream_flv_maxLatency,
|
||||
hasAudio:false,
|
||||
url: location.origin,
|
||||
path: websocketPath,
|
||||
channel : subStreamChannel,
|
||||
query: websocketQuery
|
||||
}
|
||||
}else{
|
||||
options = {
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
url: getApiPrefix(`flv`)+'/'+monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '')+'/s.flv'
|
||||
}
|
||||
}
|
||||
loadedPlayer.flv = flvjs.createPlayer(options);
|
||||
loadedPlayer.flv.attachMediaElement(containerElement.find('.stream-element')[0]);
|
||||
loadedPlayer.flv.on('error',function(err){
|
||||
console.log(err)
|
||||
});
|
||||
loadedPlayer.flv.load();
|
||||
loadedPlayer.flv.play();
|
||||
}else{
|
||||
new PNotify({title:'Stream cannot be started',text:'FLV.js is not supported on this browser. Try another stream type.',type:'error'});
|
||||
}
|
||||
break;
|
||||
case'hls':
|
||||
function createSteamNow(){
|
||||
clearTimeout(loadedPlayer.m3uCheck)
|
||||
var url = getApiPrefix(`hls`) + '/' + monitor.mid + (subStreamChannel ? `/${subStreamChannel}` : '') + '/s.m3u8'
|
||||
$.get(url,function(m3u){
|
||||
if(m3u == 'File Not Found'){
|
||||
loadedPlayer.m3uCheck = setTimeout(function(){
|
||||
createSteamNow()
|
||||
},2000)
|
||||
}else{
|
||||
var video = containerElement.find('.stream-element')[0]
|
||||
if (isAppleDevice) {
|
||||
video.src = url;
|
||||
video.addEventListener('loadedmetadata', function() {
|
||||
setTimeout(function(){
|
||||
video.play();
|
||||
},3000)
|
||||
}, false);
|
||||
}else{
|
||||
var hlsOptions = safeJsonParse(dashboardOptions().hlsOptions) || {}
|
||||
if(hlsOptions instanceof String){
|
||||
hlsOptions = {}
|
||||
new PNotify({
|
||||
title: lang['Invalid JSON'],
|
||||
text: lang.hlsOptionsInvalid,
|
||||
type: `warning`,
|
||||
})
|
||||
}
|
||||
if(loadedPlayer.hls){
|
||||
loadedPlayer.hls.destroy()
|
||||
revokeVideoPlayerUrl(monitorId)
|
||||
}
|
||||
loadedPlayer.hls = new Hls(hlsOptions)
|
||||
loadedPlayer.hls.loadSource(url)
|
||||
loadedPlayer.hls.attachMedia(video)
|
||||
loadedPlayer.hls.on(Hls.Events.MANIFEST_PARSED,function() {
|
||||
if (video.paused) {
|
||||
video.play();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
createSteamNow()
|
||||
break;
|
||||
case'mjpeg':
|
||||
var liveStreamElement = containerElement.find('.stream-element')
|
||||
var setSource = function(){
|
||||
liveStreamElement.attr('src',getApiPrefix(`mjpeg`)+'/'+monitorId + (subStreamChannel ? `/${subStreamChannel}` : ''))
|
||||
liveStreamElement.unbind('ready')
|
||||
liveStreamElement.ready(function(){
|
||||
setTimeout(function(){
|
||||
liveStreamElement.contents().find("body").append('<style>img{width:100%;height:100%}</style>')
|
||||
},1000)
|
||||
})
|
||||
}
|
||||
setSource()
|
||||
liveStreamElement.on('error',function(err){
|
||||
setTimeout(function(){
|
||||
setSource()
|
||||
},4000)
|
||||
})
|
||||
break;
|
||||
}
|
||||
$.each(onLiveStreamInitiateExtensions,function(n,extender){
|
||||
extender(streamType,monitor,loadedPlayer,subStreamChannel)
|
||||
})
|
||||
var monitorMutes = dashboardOptions().monitorMutes || {}
|
||||
if(dashboardOptions().switches.monitorMuteAudio === 1){
|
||||
containerElement.find('video').each(function(n,el){
|
||||
el.muted = "muted"
|
||||
})
|
||||
}else{
|
||||
$.each(loadedMonitors,function(frontId,monitor){
|
||||
setTimeout(() => {
|
||||
var monitorId = monitor.mid
|
||||
var muted = monitorMutes[monitorId]
|
||||
try{
|
||||
var vidEl = $('.monitor_item[mid="' + monitorId + '"] video')[0]
|
||||
if(vidEl.length === 0)return;
|
||||
if(muted === 1){
|
||||
vidEl.muted = true
|
||||
}else{
|
||||
try{
|
||||
vidEl.muted = false
|
||||
}catch(err){
|
||||
console.error('User must have window active to unmute.')
|
||||
}
|
||||
}
|
||||
}catch(err){
|
||||
// console.log(err)
|
||||
}
|
||||
},2000)
|
||||
})
|
||||
}
|
||||
//initiate signal check
|
||||
if(streamType !== 'useSubstream'){
|
||||
var signalCheckInterval = (isNaN(loadedMonitor.details.signal_check) ? 10 : parseFloat(loadedMonitor.details.signal_check)) * 1000 * 60
|
||||
if(signalCheckInterval > 0){
|
||||
clearInterval(loadedPlayer.signal)
|
||||
loadedPlayer.signal = setInterval(function(){
|
||||
signalCheckLiveStream({
|
||||
mid: monitorId,
|
||||
checkSpeed: 1000,
|
||||
})
|
||||
},signalCheckInterval);
|
||||
}
|
||||
}
|
||||
}
|
||||
function revokeVideoPlayerUrl(monitorId){
|
||||
try{
|
||||
URL.revokeObjectURL(liveGridElements[monitorId].streamElement[0].src)
|
||||
}catch(err){
|
||||
debugLog(err)
|
||||
}
|
||||
}
|
||||
function closeLiveGridPlayer(monitorId,killElement){
|
||||
try{
|
||||
var livePlayerElement = loadedLiveGrids[monitorId]
|
||||
if(livePlayerElement){
|
||||
if(livePlayerElement.hls){livePlayerElement.hls.destroy()}
|
||||
if(livePlayerElement.Poseidon){livePlayerElement.Poseidon.stop()}
|
||||
if(livePlayerElement.Base64){livePlayerElement.Base64.disconnect()}
|
||||
if(livePlayerElement.dash){livePlayerElement.dash.reset()}
|
||||
if(livePlayerElement.jpegInterval){
|
||||
stopJpegStream(monitorId)
|
||||
}
|
||||
$.each(onLiveStreamCloseExtensions,function(n,extender){
|
||||
extender(livePlayerElement)
|
||||
})
|
||||
}
|
||||
if(liveGridElements[monitorId])revokeVideoPlayerUrl(monitorId)
|
||||
clearInterval(livePlayerElement.signal)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
}
|
||||
if(killElement){
|
||||
var theElement = $('#monitor_live_'+monitorId)
|
||||
if(theElement.length > 0){
|
||||
theElement.remove()
|
||||
delete(loadedLiveGrids[monitorId])
|
||||
delete(liveGridElements[monitorId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fullScreenLiveGridStream(monitorItem){
|
||||
var videoElement = monitorItem.find('.stream-element')
|
||||
monitorItem.addClass('fullscreen')
|
||||
if(videoElement.is('canvas')){
|
||||
var theBody = $('body')
|
||||
videoElement.attr('height',theBody.height())
|
||||
videoElement.attr('width',theBody.width())
|
||||
}
|
||||
fullScreenInit(videoElement[0])
|
||||
}
|
||||
function toggleJpegMode(){
|
||||
var sendData = {
|
||||
f: 'monitor',
|
||||
ff: 'jpeg_on'
|
||||
}
|
||||
if(window.jpegModeOn === true){
|
||||
sendData.ff = 'jpeg_off'
|
||||
}
|
||||
mainSocket.f(sendData)
|
||||
}
|
||||
function startJpegStream(monitorId){
|
||||
if(loadedLiveGrids[monitorId]){
|
||||
var monitor = loadedMonitors[monitorId]
|
||||
var loadedBlock = loadedLiveGrids[monitorId]
|
||||
var jpegInterval = !isNaN(monitor.details.jpegInterval) ? parseFloat(monitor.details.jpegInterval) : 1
|
||||
resetMonitorCanvas(monitorId,false)
|
||||
var streamElement = $('#monitor_live_' + monitorId + ' .stream-element');
|
||||
// stopJpegStream(monitorId)
|
||||
var jpegUrl = getApiPrefix('jpeg') + '/' + monitorId + '/s.jpg?time='
|
||||
function drawNewFrame(){
|
||||
streamElement.attr('src',jpegUrl + (new Date()).getTime())
|
||||
}
|
||||
streamElement.on('load',function(){
|
||||
loadedBlock.jpegInterval = setTimeout(drawNewFrame,1000/jpegInterval)
|
||||
}).on('error',function(){
|
||||
loadedBlock.jpegInterval = setTimeout(drawNewFrame,1000/jpegInterval)
|
||||
})
|
||||
drawNewFrame()
|
||||
}
|
||||
}
|
||||
function stopJpegStream(monitorId){
|
||||
var livePlayerElement = loadedLiveGrids[monitorId]
|
||||
if(!livePlayerElement)return;
|
||||
try{
|
||||
liveGridElements[monitorId].streamElement.off('load').off('error')
|
||||
clearTimeout(livePlayerElement.jpegInterval)
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
console.log(monitorId)
|
||||
}
|
||||
}
|
||||
function startAllJpegStreams(monitorId){
|
||||
$.each(loadedMonitors,function(n,monitor){
|
||||
startJpegStream(monitor.mid)
|
||||
})
|
||||
}
|
||||
function stopAllJpegStreams(monitorId){
|
||||
$.each(loadedMonitors,function(n,monitor){
|
||||
stopJpegStream(monitor.mid)
|
||||
})
|
||||
}
|
||||
function signalCheckLiveStream(options){
|
||||
try{
|
||||
var monitorId = options.mid
|
||||
var monitorConfig = loadedMonitors[monitorId]
|
||||
var liveGridData = liveGridElements[monitorId]
|
||||
var monitorItem = liveGridData.monitorItem
|
||||
var monitorDetails = monitorConfig.details
|
||||
var checkCount = 0
|
||||
var base64Data = null;
|
||||
var checkSpeed = options.checkSpeed || 1000
|
||||
var subStreamChannel = monitorConfig.subStreamChannel
|
||||
var streamType = subStreamChannel ? monitorDetails.substream ? monitorDetails.substream.output.stream_type : 'hls' : monitorDetails.stream_type
|
||||
function failedStreamCheck(){
|
||||
if(monitorConfig.signal_check_log == 1){
|
||||
logWriterDraw('[mid="'+monitorId+'"]',{
|
||||
log: {
|
||||
type: 'Stream Check',
|
||||
msg: lang.clientStreamFailedattemptingReconnect
|
||||
}
|
||||
})
|
||||
}
|
||||
mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId});
|
||||
}
|
||||
function succeededStreamCheck(){
|
||||
if(monitorConfig.signal_check_log == 1){
|
||||
logWriterDraw('[mid="'+monitorId+'"]',{
|
||||
log: {
|
||||
type: 'Stream Check',
|
||||
msg : lang.Success
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
function executeCheck(){
|
||||
switch(streamType){
|
||||
case'b64':
|
||||
monitorItem.resize()
|
||||
break;
|
||||
case'hls':case'flv':case'mp4':
|
||||
if(monitorItem.find('video')[0].paused){
|
||||
failedStreamCheck()
|
||||
}else{
|
||||
succeededStreamCheck()
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if(dashboardOptions().jpeg_on === true){return}
|
||||
getSnapshot({
|
||||
monitor: loadedMonitors[monitorId],
|
||||
},function(url){
|
||||
base64Data = url;
|
||||
setTimeout(function(){
|
||||
getSnapshot({
|
||||
monitor: loadedMonitors[monitorId],
|
||||
},function(url){
|
||||
if(base64Data === url){
|
||||
if(checkCount < 3){
|
||||
++checkCount;
|
||||
setTimeout(function(){
|
||||
executeCheck();
|
||||
},checkSpeed)
|
||||
}else{
|
||||
failedStreamCheck()
|
||||
}
|
||||
}else{
|
||||
succeededStreamCheck()
|
||||
}
|
||||
});
|
||||
},checkSpeed)
|
||||
});
|
||||
break;
|
||||
}
|
||||
$.each(onSignalCheckLiveStreamExtensions,function(n,extender){
|
||||
extender(streamType,monitorItem)
|
||||
})
|
||||
}
|
||||
executeCheck();
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
var errorStack = err.stack;
|
||||
function phraseFoundInErrorStack(x){return errorStack.indexOf(x) > -1}
|
||||
if(phraseFoundInErrorStack("The HTMLImageElement provided is in the 'broken' state.")){
|
||||
mainSocket.f({f:'monitor',ff:'watch_on',id:monitorId});
|
||||
}
|
||||
clearInterval(liveGridData.signal);
|
||||
delete(liveGridData.signal);
|
||||
}
|
||||
}
|
||||
function requestMonitorInit(){
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'watch_on',
|
||||
id: monitorId
|
||||
});
|
||||
}
|
||||
$(document).ready(function(e){
|
||||
$('body')
|
||||
.on('dblclick','.stream-block',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]');
|
||||
fullScreenLiveGridStream(monitorItem)
|
||||
})
|
||||
.on('click','.launch-live-grid-monitor',function(){
|
||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
// if(isMobile){
|
||||
// createLivePlayerTab(loadedMonitors[monitorId])
|
||||
// }else{
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'watch_on',
|
||||
id: monitorId
|
||||
})
|
||||
openLiveGrid()
|
||||
// }
|
||||
})
|
||||
.on('click','.reconnect-live-grid-monitor',function(){
|
||||
var monitorId = $(this).parents('[data-mid]').attr('data-mid')
|
||||
mainSocket.f({
|
||||
f: 'monitor',
|
||||
ff: 'watch_on',
|
||||
id: monitorId
|
||||
})
|
||||
})
|
||||
.on('click','.toggle-live-grid-monitor-fullscreen',function(){
|
||||
var monitorItem = $(this).parents('[data-mid]')
|
||||
fullScreenLiveGridStream(monitorItem)
|
||||
});
|
||||
onWebSocketEvent(function (d){
|
||||
switch(d.f){
|
||||
case'init_success':
|
||||
// loadPreviouslyOpenedLiveGridBlocks()
|
||||
break;
|
||||
case'video_build_success':
|
||||
d.status = 1
|
||||
d.mid = d.id || d.mid
|
||||
var monitorId = d.mid
|
||||
var videoTime = d.time
|
||||
loadedVideosInMemory[`${monitorId}${videoTime}`] = d
|
||||
break;
|
||||
case'monitor_watch_off':case'monitor_stopping':
|
||||
var monitorId = d.mid || d.id
|
||||
closeLiveGridPlayer(monitorId,(d.f === 'monitor_watch_off'))
|
||||
break;
|
||||
case'monitor_status':
|
||||
if(
|
||||
tabTree.name === 'liveGrid' &&
|
||||
(
|
||||
d.code === 2 ||
|
||||
d.code === 3
|
||||
)
|
||||
){
|
||||
var monitorId = d.mid || d.id
|
||||
setTimeout(function(){
|
||||
callMonitorToLiveGrid(loadedMonitors[monitorId])
|
||||
},2000)
|
||||
}
|
||||
break;
|
||||
case'substream_start':
|
||||
loadedMonitors[d.mid].subStreamChannel = d.channel
|
||||
setTimeout(() => {
|
||||
resetMonitorCanvas(d.mid,true,d.channel)
|
||||
},3000)
|
||||
break;
|
||||
case'substream_end':
|
||||
loadedMonitors[d.mid].subStreamChannel = null
|
||||
resetMonitorCanvas(d.mid,true,null)
|
||||
break;
|
||||
case'monitor_watch_on':
|
||||
var monitorId = d.mid || d.id
|
||||
var loadedMonitor = loadedMonitors[monitorId]
|
||||
var subStreamChannel = d.subStreamChannel
|
||||
if(!loadedMonitor.subStreamChannel && loadedMonitor.details.stream_type === 'useSubstream'){
|
||||
toggleSubStream(monitorId,function(){
|
||||
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||
})
|
||||
}else{
|
||||
drawLiveGridBlock(loadedMonitors[monitorId],subStreamChannel)
|
||||
}
|
||||
break;
|
||||
case'mode_jpeg_off':
|
||||
window.jpegModeOn = false
|
||||
$.each(loadedMonitors,function(n,v){
|
||||
stopJpegStream(v.mid)
|
||||
resetMonitorCanvas(v.mid)
|
||||
initiateLiveGridPlayer(v)
|
||||
})
|
||||
$('body').removeClass('jpegMode')
|
||||
break;
|
||||
case'mode_jpeg_on':
|
||||
window.jpegModeOn = true
|
||||
startAllJpegStreams()
|
||||
$('body').addClass('jpegMode')
|
||||
break;
|
||||
case'detector_trigger':
|
||||
var monitorId = d.id
|
||||
var liveGridElement = liveGridElements[monitorId]
|
||||
if(!window.dontShowDetection && liveGridElement){
|
||||
var monitorElement = liveGridElement.monitorItem
|
||||
var livePlayerElement = loadedLiveGrids[monitorId]
|
||||
if(d.doObjectDetection === true){
|
||||
monitorElement.addClass('doObjectDetection')
|
||||
clearTimeout(livePlayerElement.detector_trigger_doObjectDetection_timeout)
|
||||
livePlayerElement.detector_trigger_doObjectDetection_timeout = setTimeout(function(){
|
||||
monitorElement.removeClass('doObjectDetection')
|
||||
},3000)
|
||||
}else{
|
||||
monitorElement.removeClass('doObjectDetection')
|
||||
}
|
||||
if(d.details.matrices&&d.details.matrices.length>0){
|
||||
drawMatrices(d,{
|
||||
theContainer: liveGridElement.eventObjects,
|
||||
height: liveGridElement.height,
|
||||
width: liveGridElement.width,
|
||||
})
|
||||
}
|
||||
if(d.details.confidence){
|
||||
var eventConfidence = d.details.confidence
|
||||
if(eventConfidence > 100)eventConfidence = 100
|
||||
liveGridElement.motionMeter.css('width',eventConfidence + '%');
|
||||
liveGridElement.motionMeterText[0].innerHtml = d.details.confidence+'% change in <b>'+d.details.name+'</b>'
|
||||
}
|
||||
monitorElement.addClass('detector_triggered')
|
||||
clearTimeout(livePlayerElement.detector_trigger_timeout);
|
||||
livePlayerElement.detector_trigger_timeout = setTimeout(function(){
|
||||
monitorElement.removeClass('detector_triggered');
|
||||
liveGridElement.eventObjects.find('.stream-detected-object,.stream-detected-point').remove()
|
||||
},800);
|
||||
playAudioAlert()
|
||||
var monitorPop = monitorPops[monitorId]
|
||||
if($user.details.event_mon_pop === '1' && (!monitorPop || monitorPop.closed === true)){
|
||||
popOutMonitor(monitorId)
|
||||
}
|
||||
// console.log({
|
||||
// ke: d.ke,
|
||||
// mid: monitorId,
|
||||
// log: {
|
||||
// type: lang['Event Occurred'],
|
||||
// msg: d.details,
|
||||
// }
|
||||
// })
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
createWebsocket(`ws://${location.host}`,'/socket.io');
|
||||
onInitWebsocket(function(){
|
||||
requestMonitorInit();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
var isAppleDevice = navigator.userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome'));
|
||||
var isMobile = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent)
|
||||
|| /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substr(0,4))
|
||||
|
||||
function safeJsonParse(string){
|
||||
if(string instanceof Object){
|
||||
return string
|
||||
}else{
|
||||
|
||||
}
|
||||
var newObject = {}
|
||||
try{
|
||||
newObject = JSON.parse(string)
|
||||
}catch(err){
|
||||
|
||||
}
|
||||
return newObject
|
||||
}
|
||||
function checkCorrectPathEnding(x){
|
||||
var length=x.length
|
||||
if(x.charAt(length-1)!=='/'){
|
||||
x=x+'/'
|
||||
}
|
||||
return x
|
||||
}
|
||||
function getLocation(d){
|
||||
var url
|
||||
if(d && d.info && d.info.URL){
|
||||
url = d.info.URL
|
||||
if(url.charAt(url.length-1) !== '/'){
|
||||
url = url+'/'
|
||||
}
|
||||
}else{
|
||||
url = urlPrefix
|
||||
}
|
||||
return url
|
||||
}
|
||||
function getApiPrefix(path){
|
||||
var mainPart = getLocation() + $user.auth_token
|
||||
return path ? mainPart + '/' + path + '/' + $user.ke : mainPart
|
||||
}
|
||||
function dashboardOptions(r,rr,rrr){
|
||||
if(!rrr){rrr={};};if(typeof rrr === 'string'){rrr={n:rrr}};if(!rrr.n){rrr.n='ShinobiOptions_'+location.host}
|
||||
ii={o:localStorage.getItem(rrr.n)};try{ii.o=JSON.parse(ii.o)}catch(e){ii.o={}}
|
||||
if(!ii.o){ii.o={}}
|
||||
if(r&&rr&&!rrr.x){
|
||||
ii.o[r]=rr;
|
||||
}
|
||||
switch(rrr.x){
|
||||
case 0:
|
||||
delete(ii.o[r])
|
||||
break;
|
||||
case 1:
|
||||
delete(ii.o[r][rr])
|
||||
break;
|
||||
}
|
||||
localStorage.setItem(rrr.n,JSON.stringify(ii.o))
|
||||
return ii.o
|
||||
}
|
|
@ -5,4 +5,7 @@ onWebSocketEvent(function (d){
|
|||
setInterfaceCounts()
|
||||
break;
|
||||
}
|
||||
})
|
||||
});
|
||||
$(document).ready(function(){
|
||||
createWebsocket();
|
||||
});
|
||||
|
|
|
@ -15,9 +15,9 @@ function onWebSocketEvent(theAction){
|
|||
onWebSocketEventFunctions.push(theAction)
|
||||
}
|
||||
var queuedCallbacks = {}
|
||||
$(document).ready(function(){
|
||||
mainSocket = io(location.origin,{
|
||||
path: websocketPath,
|
||||
function createWebsocket(theURL,thePath){
|
||||
mainSocket = io(theURL || location.origin,{
|
||||
path: thePath || websocketPath,
|
||||
query: websocketQuery
|
||||
})
|
||||
mainSocket.f = function(data,callback){
|
||||
|
@ -73,4 +73,4 @@ $(document).ready(function(){
|
|||
theAction(d)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,424 +1,67 @@
|
|||
<script>var io=null;</script>
|
||||
<%
|
||||
data.name='SHINOBI_'+data.ke+'_'+data.id;
|
||||
if(config.ssl&&config.ssl.port&&data.protocol==='https'){
|
||||
data.port=config.ssl.port
|
||||
}else{
|
||||
data.port=config.port
|
||||
}
|
||||
if(!data.port||data.port===''||data.port==80||data.port==443){data.url=baseUrl}else{data.url=baseUrl+':'+data.port}
|
||||
if(data.addon || data.addon.indexOf('relative')>-1){
|
||||
data.url = ''
|
||||
}else if(config.baseURL){
|
||||
data.url = config.baseURL
|
||||
}
|
||||
if(data.url.charAt(data.url.length - 1) !== '/'){
|
||||
data.url += '/'
|
||||
}
|
||||
var urlPrefix = ``
|
||||
var targetPort = config.ssl && config.ssl.port && data.protocol === 'https' ? config.ssl.port : config.port
|
||||
var addonsEnabled = {}
|
||||
var rawAddonList = decodeURI(data.addon || '').split('|');
|
||||
rawAddonList.forEach(function(piece){
|
||||
var pieceParts = piece.split('=');
|
||||
var key = pieceParts[0];
|
||||
var value = pieceParts[1] || true;
|
||||
addonsEnabled[key] = value;
|
||||
});
|
||||
function getAddon(addonTag){
|
||||
return addonsEnabled[addonTag];
|
||||
}
|
||||
var streamWidth = parseInt(getAddon('width')) || 640
|
||||
var streamHeight = parseInt(getAddon('height')) || 480
|
||||
var hasGUI = getAddon('gui')
|
||||
var isFullscreen = getAddon('fullscreen')
|
||||
var isRelativeUrl = getAddon('relative')
|
||||
if(isRelativeUrl){
|
||||
urlPrefix = ''
|
||||
}else if(config.baseURL){
|
||||
urlPrefix = config.baseURL
|
||||
}else if(!targetPort || targetPort === '' || targetPort == 80 || targetPort == 443){
|
||||
urlPrefix = baseUrl
|
||||
}else{
|
||||
urlPrefix = `${baseUrl}:${targetPort}`
|
||||
}
|
||||
if(urlPrefix.endsWith('/') === false){
|
||||
urlPrefix += '/'
|
||||
}
|
||||
%>
|
||||
<script src="<%=data.url%>assets/vendor/js/socket.io.min.js"></script>
|
||||
<script src="<%=data.url%>assets/vendor/js/poseidon.js"></script>
|
||||
<script src="<%=data.url%>assets/vendor/js/hls.min.js"></script>
|
||||
<script src="<%=data.url%>assets/vendor/js/flv.min.js"></script>
|
||||
<% if(data.addon){
|
||||
var ar={}
|
||||
decodeURI(data.addon).split('|').forEach(function(v){
|
||||
if(v.indexOf('=')>-1){
|
||||
v=v.split('=');
|
||||
ar[v[0]]=v[1];
|
||||
}
|
||||
})
|
||||
if(!ar.width){ar.width=640;}
|
||||
if(!ar.height){ar.height=480;}
|
||||
if(data.addon.indexOf('jquery')>-1){ %>
|
||||
<script src="<%=data.url%>assets/vendor/js/jquery.min.js"></script>
|
||||
<% };
|
||||
if(data.addon.indexOf('gui')>-1){ %>
|
||||
<style>
|
||||
body {position:relative;}
|
||||
.shinobi_stream{position:absolute;width:100%;height:100%;top:0;left:0;}
|
||||
.shinobi_hud{position:absolute;width:100%;height:100%;top:0;left:0;opacity:0;transition:0.2s}
|
||||
.shinobi_hud:hover{opacity:1}
|
||||
.shinobi_hud .shinobi_viewers{position:absolute;top:5px;left:5px;}
|
||||
.shinobi_hud .shinobi_viewers{
|
||||
display: inline-block;
|
||||
min-width: 10px;
|
||||
padding: 3px 7px;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: middle;
|
||||
background-color: #777;
|
||||
border-radius: 10px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
iframe.stream-element{border:0;}
|
||||
/* All-CSS Toggle Switch (Checkbox Hack) by Marcus Burnette - https://codepen.io/mburnette/pen/LxNxNg */
|
||||
.shinobi_ws_http_toggle {
|
||||
width: 50px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
.shinobi_ws_http_toggle input[type=checkbox]{
|
||||
height: 0;
|
||||
width: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label {
|
||||
cursor: pointer;
|
||||
text-indent: -9999px;
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
background: grey;
|
||||
display: block;
|
||||
border-radius: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: #fff;
|
||||
border-radius: 90px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle input:checked + label {
|
||||
background: #00118c;
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle input:checked + label:after {
|
||||
left: calc(100% - 5px);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.shinobi_ws_http_toggle label:active:after {
|
||||
width: 10px;
|
||||
}
|
||||
</style>
|
||||
<% };
|
||||
if(data.addon.indexOf('fullscreen')>-1){ %>
|
||||
<style>
|
||||
body,html{overflow: hidden;height:100%}
|
||||
*{margin:0;padding:0;border:0}
|
||||
.stream-element,.shinobi_stream{position:absolute;top:0;left:0;height:100%}
|
||||
.shinobi_stream video{object-fit: fill}
|
||||
</style>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/socket.io.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/poseidon.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/hls.min.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/flv.min.js"></script>
|
||||
<script>
|
||||
$(window).resize(function(){
|
||||
$('.stream-element').attr('width',$('body').width())
|
||||
$('.stream-element').attr('height',$('body').height())
|
||||
})
|
||||
var urlPrefix = `<%- urlPrefix %>`;
|
||||
var $user = <%- JSON.stringify($user) %>;
|
||||
var definitions = <%- JSON.stringify(define) %>;
|
||||
var lang = $user.lang;
|
||||
var hasGUI = <%- hasGUI || false %>;
|
||||
var streamWidth = <%- streamWidth %>;
|
||||
var streamHeight = <%- streamHeight %>;
|
||||
var monitorId = `<%- data.id %>`;
|
||||
var monitorConfig = <%- JSON.stringify(mon) %>;
|
||||
var loadedMonitors = {}
|
||||
loadedMonitors[monitorId] = monitorConfig;
|
||||
</script>
|
||||
<% if(getAddon('jquery')){ %>
|
||||
<script src="<%- urlPrefix %>assets/vendor/js/jquery.min.js"></script>
|
||||
<% };
|
||||
if(hasGUI){ %>
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/bs5.embed.gui.css">
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/bs5.embed.detections.css">
|
||||
<% };
|
||||
if(isFullscreen){ %>
|
||||
<link rel="stylesheet" href="<%- urlPrefix %>assets/css/bs5.embed.fullscreen.css">
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.embed.fullscreen.js"></script>
|
||||
<% } %>
|
||||
<% }else{
|
||||
//no addon set, do defaults
|
||||
var ar={};
|
||||
ar.width=640;
|
||||
ar.height=480;
|
||||
} %>
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$('#<%= data.name %> canvas').attr('width',<%=ar.width%>).attr('height',<%=ar.height%>)
|
||||
})
|
||||
</script>
|
||||
<div class="shinobi_stream" id="<%= data.name %>">
|
||||
|
||||
<% switch(mon.details.stream_type){
|
||||
case'jpeg':
|
||||
%><img class="stream-element"><%
|
||||
break;
|
||||
case'flv':case'hls':case'mp4':
|
||||
%><video class="stream-element" autoplay></video><%
|
||||
break;
|
||||
case'mjpeg':
|
||||
%><iframe class="stream-element"></iframe><%
|
||||
break;
|
||||
default:
|
||||
%><canvas class="stream-element"></canvas><%
|
||||
break;
|
||||
} %>
|
||||
|
||||
|
||||
<% if(data.addon&&data.addon.indexOf('gui')>-1){ %>
|
||||
<div class="shinobi_hud">
|
||||
<div class="shinobi_viewers" title="Current number of viewers"></div>
|
||||
<div class="shinobi_ws_http_toggle" style="display:none">
|
||||
<input type="checkbox" id="shinobi_ws_http_toggle" <% if(mon.details.stream_flv_type === 'ws'){ %>checked<% } %> /><label for="shinobi_ws_http_toggle">WebSocket</label>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="shinobi_stream" id="monitors_live">
|
||||
<div class="stream-element-container"></div>
|
||||
</div>
|
||||
<script>
|
||||
var SHINOBI_TIMER=setInterval(function(){
|
||||
if(io){
|
||||
clearInterval(SHINOBI_TIMER);delete(SHINOBI_TIMER);
|
||||
if(!$.shinobi){
|
||||
$.shinobi={}
|
||||
};
|
||||
if(!$.shinobi.mon){
|
||||
$.shinobi.mon={}
|
||||
};
|
||||
$.shinobi.init=function(d){
|
||||
if($.shinobi.mon[d.id].Base64 && $.shinobi.mon[d.id].Base64.connected){
|
||||
$.shinobi.mon[d.id].Base64.disconnect()
|
||||
}
|
||||
if($.shinobi.mon[d.id].Poseidon){
|
||||
$.shinobi.mon[d.id].Poseidon.stop()
|
||||
}
|
||||
if($.shinobi.mon[d.id].flv){
|
||||
$.shinobi.mon[d.id].flv.destroy()
|
||||
}
|
||||
if($.shinobi.mon[d.id].hls){
|
||||
$.shinobi.mon[d.id].hls.destroy()
|
||||
}
|
||||
clearInterval($.shinobi.mon[d.id].jpegInterval);
|
||||
switch($.shinobi.mon[d.id].details.stream_type){
|
||||
case'b64':
|
||||
$.shinobi.mon[d.id].Base64 = io('<%=data.url%>',{transports: ['websocket'], forceNew: false})
|
||||
var ws = $.shinobi.mon[d.id].Base64
|
||||
ws.on('diconnect',function(){
|
||||
console.log('Base64 Stream Disconnected')
|
||||
})
|
||||
ws.on('connect',function(){
|
||||
ws.emit('Base64',{
|
||||
auth:'<%=data.auth%>',
|
||||
ke:d.ke,
|
||||
uid:'<%=data.uid%>',
|
||||
id:d.id,
|
||||
url: '<%=data.url%>'
|
||||
})
|
||||
if(!$.shinobi.mon[d.id].ctx||$.shinobi.mon[d.id].ctx.length===0){
|
||||
$.shinobi.mon[d.id].ctx = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')
|
||||
}
|
||||
var ctx = $.shinobi.mon[d.id].ctx[0]
|
||||
$.shinobi.mon[d.id].image = new Image()
|
||||
var image = $.shinobi.mon[d.id].image
|
||||
image.onload = function() {
|
||||
$.shinobi.mon[d.id].imageLoading = false
|
||||
d.x = 0
|
||||
d.y = 0
|
||||
// d.ratio = Math.min(ctx.width/image.width,ctx.height/image.height)
|
||||
// d.height = image.height * d.ratio
|
||||
// d.width = image.width * d.ratio
|
||||
// if(d.width < ctx.width){
|
||||
// d.x = (ctx.width / 2) - (d.width / 2)
|
||||
// }
|
||||
// if(d.height < ctx.height){
|
||||
// d.y = (ctx.height / 2) - (d.height / 2)
|
||||
// }
|
||||
// ctx.getContext("2d").drawImage(image,d.x,d.y,d.width,d.height)
|
||||
ctx.getContext("2d").drawImage(image,d.x,d.y,ctx.width,ctx.height)
|
||||
URL.revokeObjectURL($.shinobi.mon[d.id].imageUrl)
|
||||
}
|
||||
ws.on('data',function(imageData){
|
||||
try{
|
||||
if($.shinobi.mon[d.id].imageLoading === true)return console.log('drop');
|
||||
// var base64Frame = 'data:image/jpeg;base64,'+$.ccio.base64ArrayBuffer(imageData)
|
||||
$.shinobi.mon[d.id].imageLoading = true
|
||||
// $.shinobi.mon[d.id].image.src = base64Frame
|
||||
var arrayBufferView = new Uint8Array(imageData);
|
||||
var blob = new Blob( [ arrayBufferView ], { type: "image/jpeg" } );
|
||||
$.shinobi.mon[d.id].imageUrl = URL.createObjectURL( blob );
|
||||
$.shinobi.mon[d.id].image.src = $.shinobi.mon[d.id].imageUrl
|
||||
$.shinobi.mon[d.id].last_frame = 'data:image/jpeg;base64,'+$.ccio.base64ArrayBuffer(imageData)
|
||||
}catch(er){
|
||||
console.log(er)
|
||||
}
|
||||
})
|
||||
})
|
||||
break;
|
||||
case'mp4':
|
||||
var stream = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element');
|
||||
if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){
|
||||
var createPoseidon = function(){
|
||||
var onPoseidonError = function(){
|
||||
// setTimeout(function(){
|
||||
// $.ccio.cx({f:'monitor',ff:'watch_on',id:d.id},user)
|
||||
// },2000)
|
||||
}
|
||||
$.shinobi.mon[d.id].Poseidon = new Poseidon({
|
||||
video: stream[0],
|
||||
auth_token:'<%=data.auth%>',
|
||||
ke:d.ke,
|
||||
uid:'<%=data.uid%>',
|
||||
id:d.id,
|
||||
url: '<%=data.url%>',
|
||||
onError : onPoseidonError
|
||||
});
|
||||
$.shinobi.mon[d.id].Poseidon.start();
|
||||
$.shinobi.mon[d.id].Poseidon._socket.on('data',function(res){
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
try{
|
||||
createPoseidon()
|
||||
}catch(err){
|
||||
console.log(err)
|
||||
setTimeout(function(){
|
||||
createPoseidon()
|
||||
},3000)
|
||||
}
|
||||
}else{
|
||||
stream.attr('src','<%=data.url%><%=data.auth%>/mp4/'+d.ke+'/'+d.id+'/s.mp4')
|
||||
}
|
||||
break;
|
||||
case'flv':
|
||||
if (flvjs.isSupported()) {
|
||||
var options = {};
|
||||
// if($.shinobi.mon[d.id].details.stream_flv_type==='ws'){
|
||||
// if($.shinobi.mon[d.id].details.stream_flv_maxLatency&&$.shinobi.mon[d.id].details.stream_flv_maxLatency!==''){
|
||||
// $.shinobi.mon[d.id].details.stream_flv_maxLatency = parseInt($.shinobi.mon[d.id].details.stream_flv_maxLatency)
|
||||
// }else{
|
||||
// $.shinobi.mon[d.id].details.stream_flv_maxLatency = 20000;
|
||||
// }
|
||||
// options = {
|
||||
// type: 'flv',
|
||||
// isLive: true,
|
||||
// auth_token:'<%=data.auth%>',
|
||||
// ke:d.ke,
|
||||
// uid:'<%=data.uid%>',
|
||||
// id:d.id,
|
||||
// maxLatency:$.shinobi.mon[d.id].details.stream_flv_maxLatency,
|
||||
// hasAudio:false,
|
||||
// url: '<%=data.url%>'
|
||||
// }
|
||||
// }else{
|
||||
options = {
|
||||
type: 'flv',
|
||||
isLive: true,
|
||||
url: '<%=data.url%><%=data.auth%>/flv/'+d.ke+'/'+d.id+'/s.flv'
|
||||
}
|
||||
// }
|
||||
$.shinobi.mon[d.id].flv = flvjs.createPlayer(options);
|
||||
$.shinobi.mon[d.id].flv.attachMediaElement($('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')[0]);
|
||||
$.shinobi.mon[d.id].flv.on('error',function(err){
|
||||
console.log(err)
|
||||
});
|
||||
$.shinobi.mon[d.id].flv.load();
|
||||
$.shinobi.mon[d.id].flv.play();
|
||||
}else{
|
||||
alert({title:'Stream cannot be started',text:'FLV.js is not supported on this browser. Try another stream type.',type:'error'})
|
||||
}
|
||||
break;
|
||||
case'jpeg':
|
||||
d.mon=$.shinobi.mon[d.id]
|
||||
k=d.mon.details;
|
||||
k.jpegInterval=parseFloat(k.jpegInterval);
|
||||
if(!k.jpegInterval||k.jpegInterval===''||isNaN(k.jpegInterval)){k.jpegInterval=1}
|
||||
if(!$.shinobi.mon[d.id].jpegInterval){
|
||||
clearInterval($.shinobi.mon[d.id].jpegInterval);
|
||||
$.shinobi.mon[d.id].jpegInterval=setInterval(function(){
|
||||
$('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element').attr('src','<%=data.url%><%=data.auth%>/jpeg/'+d.mon.ke+'/'+d.mon.mid+'/s.jpg?time='+(new Date()).getTime())
|
||||
},1000/k.jpegInterval);
|
||||
}
|
||||
break;
|
||||
case'hls':
|
||||
var video = $('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element')[0];
|
||||
d.url='<%=data.url%><%=data.auth%>/hls/'+d.ke+'/'+d.id+'/s.m3u8';
|
||||
if (navigator.userAgent.match(/(iPod|iPhone|iPad)/)||(navigator.userAgent.match(/(Safari)/)&&!navigator.userAgent.match('Chrome'))) {
|
||||
video.src=d.url;
|
||||
video.play();
|
||||
}else{
|
||||
$.shinobi.mon[d.id].hls = new Hls();
|
||||
$.shinobi.mon[d.id].hls.loadSource(d.url);
|
||||
$.shinobi.mon[d.id].hls.attachMedia(video);
|
||||
$.shinobi.mon[d.id].hls.on(Hls.Events.MANIFEST_PARSED,function() {
|
||||
setTimeout(function(){
|
||||
video.play();
|
||||
},1000)
|
||||
});
|
||||
}
|
||||
break;
|
||||
case'mjpeg':
|
||||
$('#SHINOBI_'+d.ke+'_'+d.id+' .stream-element').attr('src','<%=data.url%><%=data.auth%>/mjpeg/'+d.ke+'/'+d.id+'?full=true')
|
||||
break;
|
||||
}
|
||||
}
|
||||
$.shinobi.mon['<%=data.id%>']=<%- JSON.stringify(mon) %>;
|
||||
if(!$.shinobi.callback){$.shinobi.callback=function(){}}
|
||||
if(!$.shinobi.ws||$.shinobi.ws.connected===false){
|
||||
$.shinobi.ws=io('<%=data.url%>');
|
||||
$.shinobi.ws.on('f',function (d){
|
||||
if(d.viewers){
|
||||
$('#SHINOBI_'+d.ke+'_'+d.id+' .shinobi_viewers').html(d.viewers);
|
||||
}
|
||||
switch(d.f){
|
||||
case'monitor_frame':
|
||||
var image = new Image();
|
||||
var ctx = $('#SHINOBI_'+d.ke+'_'+d.id+' canvas')[0];
|
||||
image.onload = function() {
|
||||
ctx.getContext("2d").drawImage(image,0,0,ctx.width,ctx.height);
|
||||
delete(d.frame);
|
||||
delete(image);
|
||||
};
|
||||
image.src='data:image/jpeg;base64,'+d.frame
|
||||
break;
|
||||
case'monitor_watch_off':case'monitor_watch_on':
|
||||
$('#SHINOBI_'+d.ke+'_'+d.id+' .shinobi_viewers').html(d.viewers)
|
||||
$.shinobi.init(d)
|
||||
break;
|
||||
case'monitor_edit':
|
||||
if(!d.id){d.id=d.mon.mid;}
|
||||
if($.shinobi.mon[d.id]){
|
||||
clearInterval($.shinobi.mon[d.id].jpegInterval);
|
||||
}
|
||||
d.e=$('#SHINOBI_'+d.ke+'_'+d.id+'');
|
||||
d.e.find('.stream-element').remove();
|
||||
d.tmp='';
|
||||
switch(d.mon.details.stream_type){
|
||||
case'hls':
|
||||
d.tmp+='<video class="stream-element" controls autoplay></video>';
|
||||
break;
|
||||
case'mjpeg':
|
||||
d.tmp+='<iframe class="stream-element"></iframe>';
|
||||
break;
|
||||
case'jpeg'://base64
|
||||
d.tmp+='<img class="stream-element">';
|
||||
break;
|
||||
default://base64
|
||||
d.tmp+='<canvas class="stream-element"></canvas>';
|
||||
break;
|
||||
}
|
||||
d.e.append(d.tmp).find('.stream-element').resize();
|
||||
$(window).resize();
|
||||
// d.mon.details=JSON.stringify(d.mon.details);
|
||||
d.mon.id = d.mon.mid
|
||||
$.shinobi.mon[d.id] = d.mon;
|
||||
$.shinobi.init(d.mon);
|
||||
break;
|
||||
}
|
||||
$.shinobi.callback()
|
||||
});
|
||||
};
|
||||
$.shinobi.ws.emit('e',{f:'init',auth:'<%=data.auth%>',id:'<%=data.id%>',ke:'<%=data.ke%>'})
|
||||
$(window).resize();
|
||||
}
|
||||
},1000);
|
||||
//websocket / http toggle
|
||||
$('#shinobi_ws_http_toggle').change(function(){
|
||||
var monitor = $.shinobi.mon['<%=data.id%>'];
|
||||
var parent = $(this).parents('.shinobi_ws_http_toggle')
|
||||
var label = parent.find('label')
|
||||
if(monitor.details.stream_flv_type !== 'ws'){
|
||||
label.text('WebSocket')
|
||||
monitor.details.stream_flv_type = 'ws'
|
||||
}else{
|
||||
label.text('HTTP')
|
||||
monitor.details.stream_flv_type = 'http'
|
||||
}
|
||||
monitor.id = monitor.mid
|
||||
$.shinobi.init(monitor);
|
||||
})
|
||||
$('.shinobi_ws_http_toggle').show()
|
||||
</script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.embed.utils.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.websocket.js"></script>
|
||||
<script src="<%- urlPrefix %>assets/js/bs5.embed.js"></script>
|
||||
|
|
Loading…
Reference in New Issue