Merge branch 'ZoneMinder:master' into patch-141

pull/4068/head
IgorA100 2024-06-14 19:23:35 +03:00 committed by GitHub
commit 576dc6a652
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 454 additions and 275 deletions

View File

@ -1384,14 +1384,24 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) {
}
pkt->dts = last_dts[stream->index];
} else {
if ((last_dts[stream->index] != AV_NOPTS_VALUE) and (pkt->dts <= last_dts[stream->index])) {
Warning("non increasing dts, fixing. our dts %" PRId64 " stream %d last_dts %" PRId64 ". reorder_queue_size=%zu",
pkt->dts, stream->index, last_dts[stream->index], reorder_queue_size);
// dts MUST monotonically increase, so add 1 which should be a small enough time difference to not matter.
pkt->dts = last_dts[stream->index]+1;
if (last_dts[stream->index] != AV_NOPTS_VALUE) {
if (pkt->dts < last_dts[stream->index]) {
Warning("non increasing dts, fixing. our dts %" PRId64 " stream %d last_dts %" PRId64 ". reorder_queue_size=%zu",
pkt->dts, stream->index, last_dts[stream->index], reorder_queue_size);
pkt->dts = last_dts[stream->index]+last_duration[stream->index];
if (pkt->dts > pkt->pts) pkt->pts = pkt->dts; // Do it here to avoid warning below
} else if (pkt->dts == last_dts[stream->index]) {
// Commonly seen
Debug(1, "non increasing dts, fixing. our dts %" PRId64 " stream %d last_dts %" PRId64 ". reorder_queue_size=%zu",
pkt->dts, stream->index, last_dts[stream->index], reorder_queue_size);
// dts MUST monotonically increase, so add 1 which should be a small enough time difference to not matter.
pkt->dts = last_dts[stream->index]+last_duration[stream->index];
if (pkt->dts > pkt->pts) pkt->pts = pkt->dts; // Do it here to avoid warning below
}
}
next_dts[stream->index] = pkt->dts + pkt->duration;
last_dts[stream->index] = pkt->dts;
last_duration[stream->index] = pkt->duration;
}
if (pkt->pts == AV_NOPTS_VALUE) {

View File

@ -86,6 +86,7 @@ class VideoStore {
// These are for out, should start at zero. We assume they do not wrap because we just aren't going to save files that big.
int64_t *next_dts;
std::map<int, int64_t> last_dts;
std::map<int, int64_t> last_duration;
int64_t audio_next_pts;
int max_stream_index;

View File

@ -440,6 +440,11 @@ switch ( $_REQUEST['layout'] ) {
case 'json' :
{
$response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data );
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
$response['auth'] = $auth_hash;
$response['auth_relay'] = get_auth_relay();
}
if ( isset($_REQUEST['loopback']) )
$response['loopback'] = validJsStr($_REQUEST['loopback']);
#ZM\Warning(print_r($response, true));

View File

@ -495,7 +495,7 @@ public static function getStatuses() {
unset($args['zones']);
$streamSrc .= '?'.http_build_query($args, '', $querySep);
$this->streamSrc = $streamSrc;
return $streamSrc;
} // end function getStreamSrc

View File

@ -243,7 +243,7 @@ function MonitorStream(monitorData) {
}});
}
attachVideo(parseInt(this.id), this.janusPin);
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), delay);
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
return;
}
if (this.RTSP2WebEnabled) {
@ -289,7 +289,7 @@ function MonitorStream(monitorData) {
webrtcUrl.pathname = "/stream/" + this.id + "/channel/0/webrtc";
startRTSP2WebPlay(videoEl, webrtcUrl.href);
}
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), delay);
this.statusCmdTimer = setInterval(this.statusCmdQuery.bind(this), statusRefreshTimeout);
return;
} else {
console.log("ZM_RTSP2WEB_PATH is empty. Go to Options->System and set ZM_RTSP2WEB_PATH accordingly.");
@ -753,6 +753,15 @@ function MonitorStream(monitorData) {
} // end if canEdit.Monitors
this.setAlarmState(monitorStatus);
if (respObj.auth_hash) {
if (this.auth_hash != respObj.auth_hash) {
// Don't reload the stream because it causes annoying flickering. Wait until the stream breaks.
console.log("Changed auth from " + this.auth_hash + " to " + respObj.auth_hash);
this.streamCmdParms.auth = this.auth_hash = respObj.auth_hash;
this.auth_relay = respObj.auth_relay;
}
} // end if have a new auth hash
} else {
checkStreamForErrors('getStatusCmdResponse', respObj);
}

View File

@ -63,6 +63,10 @@ var zmPanZoom = {
_this.setTriggerChangedMonitors(id);
});
} else if (action == "disable") { //Disable a specific object
if (!this.panZoom[param['id']]) {
console.log(`PanZoom for monitor "${param['id']}" was not initialized.`);
return;
}
$j(document).off('keyup.panzoom keydown.panzoom');
$j('.btn-zoom-in').addClass('hidden');
$j('.btn-zoom-out').addClass('hidden');
@ -162,7 +166,9 @@ var zmPanZoom = {
const point = {clientX: event.clientX, clientY: event.clientY};
this.panZoom[id].zoomToPoint(scale, point, {focal: {x: event.clientX, y: event.clientY}});
}
this.setTriggerChangedMonitors(id);
if (this.ctrled || this.shifted) {
this.setTriggerChangedMonitors(id);
}
},
setTriggerChangedMonitors: function(id) {

View File

@ -469,18 +469,6 @@ body.sticky #content {
* Generic useful classes, especially with mootools
*/
.hidden {
display: none;
}
.hidden-shift {
position: absolute !important; left: -999em !important;
}
.invisible {
visibility: hidden;
}
.nowrap {
white-space: nowrap;
}
@ -680,7 +668,7 @@ input[type=button]:disabled,
input[type=submit]:disabled,
a.disabled,
a.btn-primary.disabled,
.btn-primary:disabled {
.btn-primary.disabled, .btn-primary:disabled, .btn-secondary.disabled, .btn-secondary:disabled {
background-color: #aaaaaa;
border-color: #bbbbbb;
}
@ -938,6 +926,11 @@ a.flip {
clear: both;
}
.monitor .monitorStatus {
position: relative;
background-color: #FFFFFF;
}
.monitor .monitorStatus.bottom {
color: #dddddd;
}
@ -1124,3 +1117,18 @@ html::-webkit-scrollbar-thumb, div::-webkit-scrollbar-thumb, nav::-webkit-scroll
width: 100%;
}
}
/* +++ This block should always be located at the end! */
.hidden {
display: none;
}
.hidden-shift {
position: absolute !important; left: -999em !important;
}
.invisible {
visibility: hidden;
}
/* --- This block should always be located at the end! */

View File

@ -167,7 +167,7 @@ button:disabled,
input[disabled],
input[type=button]:disabled,
input[type=submit]:disabled,
.btn-primary:disabled {
.btn-primary.disabled, .btn-primary:disabled, .btn-secondary.disabled, .btn-secondary:disabled {
color: #888888;
background-color: #666666;
border-color: #666666;
@ -290,6 +290,10 @@ ul.nav.nav-pills.flex-column {
background:#444444;
}
.monitor .monitorStatus {
background-color: #222222;
}
/* Change scrollbar style */
::-webkit-scrollbar, div::-webkit-scrollbar, nav::-webkit-scrollbar, .chosen-results::-webkit-scrollbar {
width: 11px;

View File

@ -683,13 +683,13 @@ function endOfResize(e) {
* */
function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl, container, panZoomScale = 1) {
$j(window).on('resize', endOfResize); //set delayed scaling when Scale to Fit is selected
const ratio = baseWidth / baseHeight;
if (!container) container = $j('#content');
if (!container) {
console.error("No container found");
return;
}
const ratio = baseWidth / baseHeight;
const viewPort = $j(window);
// jquery does not provide a bottom offset, and offset does not include margins. outerHeight true minus false gives total vertical margins.
var bottomLoc = 0;
@ -701,22 +701,13 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl, container, panZoom
console.log("bottomLoc: " + bottomEl.offset().top + " + (" + bottomEl.outerHeight(true) + ' - ' + bottomEl.outerHeight() +') + '+bottomEl.outerHeight(true) + '='+bottomLoc);
}
let newHeight = viewPort.height() - (bottomLoc - scaleEl.outerHeight(true));
console.log("newHeight = " + viewPort.height() +" - " + bottomLoc + ' - ' + scaleEl.outerHeight(true)+'='+newHeight);
let newWidth = ratio * newHeight;
// Let's recalculate everything and reduce the height a little. Necessary if "padding" is specified for "wrapperEventVideo"
padding = parseInt(container.css("padding-left")) + parseInt(container.css("padding-right"));
newWidth -= padding;
newHeight = newWidth / ratio;
console.log("newWidth = ", newWidth, "container width:", container.innerWidth()-padding);
if (newHeight < 0 || newWidth > container.innerWidth()-padding) {
if (newHeight < 0 || newWidth > container.width()) {
// Doesn't fit on screen anyways?
newWidth = container.innerWidth()-padding;
newWidth = container.width();
newHeight = newWidth / ratio;
}
console.log("newWidth = " + newWidth);
let autoScale = Math.round(newWidth / baseWidth * SCALE_BASE * panZoomScale);
/* IgorA100 not required due to new "Scale" algorithm & new PanZoom (may 2024)
const scales = $j('#scale option').map(function() {
@ -735,6 +726,7 @@ function scaleToFit(baseWidth, baseHeight, scaleEl, bottomEl, container, panZoom
}
*/
if (autoScale < 10) autoScale = 10;
console.log(`container.height=${container.height()}, newWidth=${newWidth}, newHeight=${newHeight}, container width=${container.width()}, autoScale=${autoScale}`);
return {width: Math.floor(newWidth), height: Math.floor(newHeight), autoScale: autoScale};
}
@ -1162,9 +1154,13 @@ function stringToNumber(str) {
return parseInt(str.replace(/\D/g, ''));
}
const font = new FontFaceObserver('Material Icons', {weight: 400});
font.load().then(function() {
$j('.material-icons').css('display', 'inline-block');
}, function() {
$j('.material-icons').css('display', 'inline-block');
});
function loadFontFaceObserver() {
const font = new FontFaceObserver('Material Icons', {weight: 400});
font.load().then(function() {
$j('.material-icons').css('display', 'inline-block');
}, function() {
$j('.material-icons').css('display', 'inline-block');
});
}
loadFontFaceObserver();

View File

@ -192,7 +192,7 @@ echo 'var rangeTimeSecs='.($maxTimeSecs - $minTimeSecs + 1).";\n";
if ( isset($defaultCurrentTimeSecs) )
echo 'var currentTimeSecs=parseInt('.$defaultCurrentTimeSecs.");\n";
else
echo 'var currentTimeSecs=parseInt('.(($minTimeSecs + $maxTimeSecs)/2).");\n";
echo 'var currentTimeSecs=parseInt('.$minTimeSecs.");\n";
echo 'var speeds=[';
for ( $i=0; $i < count($speeds); $i++ )

View File

@ -8,6 +8,8 @@ var sidebarControls = $j('#ptzControls');
var wrapperMonitor = $j('#wrapperMonitor');
var filterQuery = '&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId;
var idle = 0;
var monitorStream = false; /* Stream is not started */
var currentMonitor;
var classSidebarL = 'col-sm-3'; /* id="sidebar" */
var classSidebarR = 'col-sm-2'; /* id="ptzControls" */
@ -132,36 +134,11 @@ function showPtzControls() {
showMode = 'control';
}
function changeSize() {
var width = $j('#width').val();
var height = $j('#height').val();
//monitorStream.setScale('0', width, height);
monitorsSetScale(monitorId);
//$j('#scale').val('0');
$j('#sidebar ul').height($j('#wrapperMonitor').height()-$j('#cycleButtons').height());
//setCookie('zmWatchScale', '0');
setCookie('zmWatchWidth', width);
setCookie('zmWatchHeight', height);
} // end function changeSize()
function changeScale() {
const scale = $j('#scale').val();
setCookie('zmWatchScaleNew'+monitorId, scale);
setCookie('zmCycleScale', scale);
monitorsSetScale(monitorId);
/*
const scale = $j('#scale').val();
setCookie('zmWatchScale'+monitorId, scale);
$j('#width').val('auto');
$j('#height').val('auto');
setCookie('zmCycleScale', scale);
setCookie('zmWatchWidth', 'auto');
setCookie('zmWatchHeight', 'auto');
setScale();
*/
}
function changeStreamQuality() {
@ -170,22 +147,6 @@ function changeStreamQuality() {
monitorsSetScale(monitorId);
}
// Implement current scale, as opposed to changing it
function setScale() {
/*
const scale = $j('#scale').val();
//monitorStream.setScale(scale, $j('#width').val(), $j('#height').val());
monitorsSetScale(monitorId);
// Always turn it off, we will re-add it below. I don't know if you can add a callback multiple
// times and what the consequences would be
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
if (scale == '0') {
$j(window).on('resize', endOfResize); //remove resize handler when Scale to Fit is not active
changeSize();
}
*/
} // end function changeScale
function getStreamCmdResponse(respObj, respText) {
watchdogOk('stream');
streamCmdTimer = clearTimeout(streamCmdTimer);
@ -335,10 +296,14 @@ function streamCmdPause(action) {
}
function onPlay() {
setButtonState('pauseBtn', 'inactive');
setButtonState('playBtn', 'active');
//monitorStream.setup_onplay(onPlay); //IgorA100 Added for testing, but probably not required
//setButtonState('pauseBtn', 'inactive');
//setButtonState('playBtn', 'active');
setButtonStateWatch('pauseBtn', 'inactive');
setButtonStateWatch('stopBtn', 'inactive');
setButtonStateWatch('playBtn', 'unavail');
if (monitorStream.status.delayed == true) {
setButtonState('stopBtn', 'inactive');
//setButtonState('stopBtn', 'inactive');
if (monitorStreamReplayBuffer) {
setButtonState('fastFwdBtn', 'inactive');
setButtonState('slowFwdBtn', 'inactive');
@ -346,7 +311,7 @@ function onPlay() {
setButtonState('fastRevBtn', 'inactive');
}
} else {
setButtonState('stopBtn', 'unavail');
//setButtonState('stopBtn', 'unavail');
if (monitorStreamReplayBuffer) {
setButtonState('fastFwdBtn', 'unavail');
setButtonState('slowFwdBtn', 'unavail');
@ -359,14 +324,21 @@ function onPlay() {
function streamCmdPlay(action) {
onPlay();
if (action) {
monitorStream.streamCommand(CMD_PLAY);
if (monitorStream.started) {
//Stream was on pause
monitorStream.streamCommand(CMD_PLAY);
} else {
//Stream has been stopped
monitorStream.start();
}
}
}
function streamCmdStop(action) {
setButtonState('pauseBtn', 'inactive');
setButtonState('playBtn', 'unavail');
setButtonState('stopBtn', 'active');
monitorStream.onplay = false; //Without this line, "onPlay" is triggered immediately due to "if (this.onplay) this.onplay();" in MonitorStream.js
//setButtonState('pauseBtn', 'inactive');
//setButtonState('playBtn', 'unavail');
//setButtonState('stopBtn', 'active');
if (monitorStreamReplayBuffer) {
setButtonState('fastFwdBtn', 'unavail');
setButtonState('slowFwdBtn', 'unavail');
@ -374,10 +346,14 @@ function streamCmdStop(action) {
setButtonState('fastRevBtn', 'unavail');
}
if (action) {
monitorStream.streamCommand(CMD_STOP);
//monitorStream.streamCommand(CMD_STOP);
monitorStream.kill();
}
setButtonState('stopBtn', 'unavail');
setButtonState('playBtn', 'active');
//setButtonState('stopBtn', 'unavail');
//setButtonState('playBtn', 'active');
setButtonStateWatch('playBtn', 'inactive');
setButtonStateWatch('stopBtn', 'unavail');
setButtonStateWatch('pauseBtn', 'unavail');
}
function streamCmdFastFwd(action) {
@ -620,13 +596,25 @@ function fetchImage(streamImage) {
}
function handleClick(event) {
const targetId = event.target.id;
if (targetId.indexOf("nav-link") >= 0) { //Navigation through monitors
cycleStop(event.target);
const oldId = stringToNumber(document.querySelector('[id ^= "liveStream"]').id);
const newId = stringToNumber(targetId);
streamReStart(oldId, newId);
} else if (event.target.closest('#dvrControls')) { //Controls DVR
cyclePause();
} else if (!event.target.closest('#wrapperMonitor')) {
return;
}
if (panZoomEnabled) {
//event.preventDefault();
if (event.target.id) {
//We are looking for an object with an ID, because there may be another element in the button.
var obj = event.target;
} else {
var obj = event.target.parentElement;
const obj = targetId ? event.target : event.target.parentElement;
if (!obj) {
console.log("No obj found", targetId, event.target, event.target.parentElement);
return;
}
if (obj.className.includes('btn-zoom-out') || obj.className.includes('btn-zoom-in')) return;
@ -639,8 +627,12 @@ function handleClick(event) {
}
}
if (obj.getAttribute('id').indexOf("liveStream") >= 0) {
zmPanZoom.click(monitorId);
const obj_id = obj.getAttribute('id');
if (obj_id) {
if (obj_id.indexOf("liveStream") >= 0)
zmPanZoom.click(monitorId);
} else {
console.log("obj does not have an id", obj);
}
} else {
// +++ Old ZoomPan algorithm.
@ -909,92 +901,7 @@ function controlSetClicked() {
}
}
function streamStart() {
monitorStream = new MonitorStream(monitorData[monIdx]);
monitorStream.setBottomElement(document.getElementById('dvrControls'));
// Start the fps and status updates. give a random delay so that we don't assault the server
//monitorStream.setScale($j('#scale').val(), $j('#width').val(), $j('#height').val());
monitorsSetScale(monitorId);
monitorStream.start();
if (streamMode == 'single') {
monitorStream.setup_onclick(fetchImage);
} else {
monitorStream.setup_onclick(handleClick);
monitorStream.setup_onmove(handleMove);
}
monitorStream.setup_onpause(onPause);
monitorStream.setup_onplay(onPlay);
monitorStream.setup_onalarm(refresh_events_table);
monitorStream.setButton('enableAlarmButton', enableAlmBtn);
monitorStream.setButton('forceAlarmButton', forceAlmBtn);
monitorStream.setButton('zoomOutButton', $j('zoomOutBtn'));
if (canEdit.Monitors) {
// Will be enabled by streamStatus ajax
enableAlmBtn.on('click', cmdAlarm);
forceAlmBtn.on('click', cmdForce);
} else {
forceAlmBtn.prop('title', forceAlmBtn.prop('title') + ': disabled because cannot edit Monitors');
enableAlmBtn.prop('title', enableAlmBtn.prop('title') + ': disabled because cannot edit Monitors');
}
/*
if (streamMode == 'single') {
statusCmdTimer = setTimeout(statusCmdQuery, 200);
setInterval(watchdogCheck, statusRefreshTimeout*2, 'status');
} else {
streamCmdTimer = setTimeout(streamCmdQuery, 200);
setInterval(watchdogCheck, statusRefreshTimeout*2, 'stream');
}
if (canStream || (streamMode == 'single')) {
var streamImg = $j('#imageFeed img');
if (!streamImg) streamImg = $j('#imageFeed object');
if (!streamImg) {
console.error('No streamImg found for imageFeed');
} else {
if (streamMode == 'single') {
streamImg.click(streamImg, fetchImage);
setInterval(fetchImage, imageRefreshTimeout, $j('#imageFeed img'));
} else {
streamImg.click(function(event) {
handleClick(event);
});
streamImg.on("error", function(thing) {
console.log("Error loading image");
console.log(thing);
setInterval(fetchImage, 100, $j('#imageFeed img'));
});
}
} // end if have streamImg
} // streamMode native or single
*/
}
function initPage() {
// +++ Support of old ZoomPan algorithm
var useOldZoomPan = getCookie('zmUseOldZoomPan');
const btnZoomOutBtn = document.getElementById('zoomOutBtn'); //Zoom out button below Frame. She may not
if (useOldZoomPan) {
panZoomEnabled = false;
if (btnZoomOutBtn) {
btnZoomOutBtn.classList.remove("hidden");
}
} else {
if (btnZoomOutBtn) {
btnZoomOutBtn.classList.add("hidden");
}
}
$j("#use-old-zoom-pan").click(function() {
useOldZoomPan = this.checked;
setCookie('zmUseOldZoomPan', this.checked);
location.reload();
});
document.getElementById('use-old-zoom-pan').checked = useOldZoomPan;
// --- Support of old ZoomPan algorithm
zmPanZoom.init();
function streamPrepareStart(monitor=null) {
if (canView.Control) {
// Load the settings modal into the DOM
if (monitorType == 'Local') getSettingsModal();
@ -1011,7 +918,7 @@ function initPage() {
}
if ((monitorType != 'WebSite') && monitorData.length) {
streamStart();
streamStart(monitor);
if (window.history.length == 1) {
$j('#closeControl').html('');
}
@ -1052,6 +959,172 @@ function initPage() {
setInterval(reloadWebSite, monitorRefresh*1000);
}
// Manage the generate Edit button
bindButton('#editBtn', 'click', null, function onEditClick(evt) {
evt.preventDefault();
window.location.assign("?view=monitor&mid="+monitorId);
});
const el = document.querySelector('.imageFeed');
el.addEventListener('mouseenter', handleMouseEnter);
el.addEventListener('mouseleave', handleMouseLeave);
const i = setInterval(function() {
if (document.querySelector('[id ^= "liveStream"]').offsetHeight > 20) {
//You need to wait until the image appears.
clearInterval(i);
document.getElementById('monitor').classList.remove('hidden-shift');
monitorsSetScale(monitorId);
}
}, 100);
setButtonStateWatch('stopBtn', 'active');
setTimeout(dataOnClick, 100);
}
function handleMouseEnter(event) {
//Displaying "Scale" and other buttons at the top of the monitor image
const id = stringToNumber(this.id);
$j('#button_zoom' + id).stop(true, true).slideDown('fast');
}
function handleMouseLeave(event) {
const id = stringToNumber(this.id);
$j('#button_zoom' + id).stop(true, true).slideUp('fast');
}
function streamStart(monitor = null) {
if (monitor) {
monitorStream = new MonitorStream(monitor);
} else {
monitorStream = new MonitorStream(monitorData[monIdx]);
}
monitorStream.setBottomElement(document.getElementById('dvrControls'));
// Start the fps and status updates. give a random delay so that we don't assault the server
//monitorStream.setScale($j('#scale').val(), $j('#width').val(), $j('#height').val());
//monitorsSetScale(monitorId);
monitorStream.start();
if (streamMode == 'single') {
monitorStream.setup_onclick(fetchImage);
} else {
monitorStream.setup_onclick(handleClick);
monitorStream.setup_onmove(handleMove);
}
monitorStream.setup_onpause(onPause);
monitorStream.setup_onplay(onPlay);
monitorStream.setup_onalarm(refresh_events_table);
monitorStream.setButton('enableAlarmButton', enableAlmBtn);
monitorStream.setButton('forceAlarmButton', forceAlmBtn);
monitorStream.setButton('zoomOutButton', $j('zoomOutBtn'));
if (canEdit.Monitors) {
// Will be enabled by streamStatus ajax
enableAlmBtn.on('click', cmdAlarm);
forceAlmBtn.on('click', cmdForce);
} else {
forceAlmBtn.prop('title', forceAlmBtn.prop('title') + ': disabled because cannot edit Monitors');
enableAlmBtn.prop('title', enableAlmBtn.prop('title') + ': disabled because cannot edit Monitors');
}
}
function streamReStart(oldId, newId) {
document.getElementById('monitor').classList.add('hidden-shift');
const el = document.querySelector('.imageFeed');
const newMonitorName = document.getElementById('nav-item-cycle'+newId).querySelector('a').textContent;
currentMonitor = monitorData.find((o) => {
return parseInt(o["id"]) === newId;
});
const url = new URL(document.location.href);
monitorId = newId;
filterQuery = '&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId;
document.querySelector('title').textContent = newMonitorName;
url.searchParams.set('mid', monitorId);
history.pushState(null, "", url);
zmPanZoom.action('disable', {id: oldId});
if (monitorStream) {
monitorStream.kill();
}
el.removeEventListener('mouseenter', handleMouseEnter);
el.removeEventListener('mouseleave', handleMouseLeave);
//Change main monitor block
document.getElementById('monitor').innerHTML = currentMonitor.streamHTML;
//Change active element of the navigation menu
document.getElementById('nav-item-cycle'+oldId).querySelector('a').classList.remove("active");
document.getElementById('nav-item-cycle'+newId).querySelector('a').classList.add("active");
//Set global variables from the current monitor
monitorWidth = currentMonitor.monitorWidth;
monitorHeight = currentMonitor.monitorHeight;
monitorType = currentMonitor.monitorType;
monitorRefresh = currentMonitor.monitorRefresh;
monitorStreamReplayBuffer = currentMonitor.monitorStreamReplayBuffer;
monitorControllable = currentMonitor.monitorControllable;
streamMode = currentMonitor.streamMode;
table.bootstrapTable('destroy');
applyMonitorControllable();
streamPrepareStart(currentMonitor);
zmPanZoom.init();
loadFontFaceObserver();
//document.getElementById('monitor').classList.remove('hidden-shift');
}
function applyMonitorControllable() {
const ptzToggle = document.getElementById('ptzToggle');
if (!ptzToggle) {
console.log('ptz toggle is not present. Likely OPT_CONTROL is off');
return;
}
if (currentMonitor.monitorControllable) {
const ptzShow = getCookie('ptzShow');
ptzToggle.classList.remove("disabled");
ptzToggle.disabled=false;
sidebarControls.html(currentMonitor.ptzControls);
if (ptzShow) {
sidebarControls.show();
ptzToggle.classList.remove("btn-secondary");
ptzToggle.classList.add("btn-primary");
} else {
sidebarControls.hide();
ptzToggle.classList.remove("btn-primary");
ptzToggle.classList.add("btn-secondary");
}
} else {
ptzToggle.classList.add("disabled");
ptzToggle.disabled=true;
sidebarControls.html('');
sidebarControls.hide();
}
changeObjectClass();
}
function initPage() {
// +++ Support of old ZoomPan algorithm
var useOldZoomPan = getCookie('zmUseOldZoomPan');
const btnZoomOutBtn = document.getElementById('zoomOutBtn'); //Zoom out button below Frame. She may not
if (useOldZoomPan) {
panZoomEnabled = false;
if (btnZoomOutBtn) {
btnZoomOutBtn.classList.remove("hidden");
}
} else {
if (btnZoomOutBtn) {
btnZoomOutBtn.classList.add("hidden");
}
}
$j("#use-old-zoom-pan").click(function() {
useOldZoomPan = this.checked;
setCookie('zmUseOldZoomPan', this.checked);
location.reload();
});
document.getElementById('use-old-zoom-pan').checked = useOldZoomPan;
// --- Support of old ZoomPan algorithm
zmPanZoom.init();
// Manage the BACK button
bindButton('#backBtn', 'click', null, function onBackClick(evt) {
evt.preventDefault();
@ -1073,12 +1146,6 @@ function initPage() {
$j('#settingsModal').modal('show');
});
// Manage the generate Edit button
bindButton('#editBtn', 'click', null, function onEditClick(evt) {
evt.preventDefault();
window.location.assign("?view=monitor&mid="+monitorId);
});
bindButton('#cyclePlayBtn', 'click', null, cycleStart);
bindButton('#cyclePauseBtn', 'click', null, cyclePause);
bindButton('#cycleNextBtn', 'click', null, cycleNext);
@ -1119,17 +1186,6 @@ function initPage() {
}
}, 10*1000);
}
$j(".imageFeed").hover(
//Displaying "Scale" and other buttons at the top of the monitor image
function() {
const id = stringToNumber(this.id);
$j('#button_zoom' + id).stop(true, true).slideDown('fast');
},
function() {
const id = stringToNumber(this.id);
$j('#button_zoom' + id).stop(true, true).slideUp('fast');
}
);
setInterval(() => {
//Updating Scale. When quickly scrolling the mouse wheel or quickly pressing Zoom In/Out, you should not set Scale very often.
@ -1137,11 +1193,22 @@ function initPage() {
monitorsSetScale(monitorId);
updateScale = false;
}
}, 500);
}, 300);
document.getElementById('monitor').classList.remove('hidden-shift');
document.addEventListener('click', function(event) {
handleClick(event);
});
//document.getElementById('monitor').classList.remove('hidden-shift');
changeObjectClass();
changeSize();
currentMonitor = monitorData.find((o) => {
return parseInt(o["id"]) === monitorId;
});
if (currentMonitor) {
applyMonitorControllable();
}
streamPrepareStart();
} // initPage
function watchFullscreen() {
@ -1159,7 +1226,7 @@ function watchFullscreen() {
}
function watchAllEvents() {
window.location.replace(document.getElementById('allEventsBtn').getAttribute('data-url'));
window.location.replace(currentMonitor.urlForAllEvents);
}
var intervalId;
@ -1181,13 +1248,20 @@ function cyclePause() {
}
function cycleStart() {
secondsToCycle = $j('#cyclePeriod').val();
if (secondsToCycle == 0) secondsToCycle = $j('#cyclePeriod').val();
intervalId = setInterval(nextCycleView, 1000);
cycle = true;
$j('#cyclePauseBtn').show();
$j('#cyclePlayBtn').hide();
}
function cycleStop(target) {
secondsToCycle = 0;
monIdx = target.getAttribute('data-monIdx');
$j('#secondsToCycle').text('');
cyclePause();
}
function cycleNext() {
monIdx ++;
if (monIdx >= monitorData.length) {
@ -1198,7 +1272,19 @@ function cycleNext() {
}
clearInterval(intervalId);
monitorStream.kill();
window.location.replace('?view=watch&cycle='+cycle+'&mid='+monitorData[monIdx].id+'&mode='+mode);
// +++ Start next monitor
let oldId;
if (monIdx == 0) {
oldId = monitorData[monitorData.length-1].id;
} else {
oldId = monitorData[monIdx-1].id;
}
const newId = monitorData[monIdx].id;
streamReStart(oldId, newId);
cycleStart();
// --- Start next monitor
//window.location.replace('?view=watch&cycle='+cycle+'&mid='+monitorData[monIdx].id+'&mode='+mode);
}
function cyclePrev() {
@ -1210,8 +1296,21 @@ function cyclePrev() {
console.log('No monitorData for ' + monIdx);
}
clearInterval(intervalId);
monitorStream.stop();
window.location.replace('?view=watch&cycle='+cycle+'&mid='+monitorData[monIdx].id+'&mode='+mode);
//monitorStream.stop();
monitorStream.kill();
// +++ Start previous monitor
let oldId;
if (monIdx == monitorData.length - 1) {
oldId = monitorData[0].id;
} else {
oldId = monitorData[monIdx+1].id;
}
const newId = monitorData[monIdx].id;
streamReStart(oldId, newId);
cycleStart();
// --- Start previous monitors
//window.location.replace('?view=watch&cycle='+cycle+'&mid='+monitorData[monIdx].id+'&mode='+mode);
}
function cyclePeriodChange() {
@ -1230,7 +1329,7 @@ function cycleToggle(e) {
button.toggleClass('btn-secondary');
button.toggleClass('btn-primary');
changeObjectClass();
changeSize();
monitorsSetScale(monitorId);
}
function ptzToggle(e) {
@ -1245,7 +1344,7 @@ function ptzToggle(e) {
button.toggleClass('btn-secondary');
button.toggleClass('btn-primary');
changeObjectClass();
changeSize();
monitorsSetScale(monitorId);
}
function changeRate(e) {
@ -1310,16 +1409,20 @@ function panZoomOut(el) {
function monitorsSetScale(id=null) {
//This function will probably need to be moved to the main JS file, because now used on Watch & Montage pages
if (id || typeof monitorStream !== 'undefined') {
//monitorStream used on Watch page.
if (monitorStream) {
if (monitorStream !== false) {
//monitorStream used on Watch page.
var curentMonitor = monitorStream;
} else {
} else if (typeof monitors !== 'undefined') {
//used on Montage, Watch & Event page.
var curentMonitor = monitors.find((o) => {
return parseInt(o["id"]) === id;
});
} else {
//Stream is missing
return;
}
//const el = document.getElementById('liveStream'+id);
if (panZoomEnabled) {
if (panZoomEnabled && zmPanZoom.panZoom[id]) {
var panZoomScale = zmPanZoom.panZoom[id].getScale();
} else {
var panZoomScale = 1;
@ -1450,12 +1553,30 @@ document.onvisibilitychange = () => {
TimerHideShow = clearTimeout(TimerHideShow);
TimerHideShow = setTimeout(function() {
//Stop monitor when closing or hiding page
monitorStream.kill();
if (monitorStream) {
monitorStream.kill();
}
}, 15*1000);
} else {
//Start monitor when show page
if (!monitorStream.started) {
if (monitorStream && !monitorStream.started) {
monitorStream.start();
}
}
};
function setButtonStateWatch(element_id, btnClass) {
//Temporary function so as not to break anything else, because analysis of the setButtonState function in skin.js is required,
//and also review the logic of the buttons and more (if (this.onplay) this.onplay() in MonitorStream.js) var element = document.getElementById(element_id);
var element = document.getElementById(element_id);
if ( element ) {
element.className = btnClass;
if (btnClass == 'unavail') {
element.disabled = true;
} else {
element.disabled = false;
}
} else {
console.log('Element was null or not found in setButtonState. id:'+element_id);
}
}

View File

@ -3,6 +3,7 @@
global $nextMid;
global $options;
global $monitors;
global $monitorsExtraData;
global $streamMode;
global $showPtzControls;
global $monitor;
@ -53,7 +54,17 @@ monitorData[monitorData.length] = {
'onclick': function(){window.location.assign( '?view=watch&mid=<?php echo $m->Id() ?>' );},
'type': '<?php echo $m->Type() ?>',
'refresh': '<?php echo $m->Refresh() ?>',
'janus_pin': '<?php echo $m->Janus_Pin() ?>'
'janus_pin': '<?php echo $m->Janus_Pin() ?>',
'streamHTML': '<?php echo str_replace(array("\r\n", "\r", "\n"), '', $monitorsExtraData[$m->Id()]['StreamHTML']) ?>',
'urlForAllEvents': '<?php echo $monitorsExtraData[$m->Id()]['urlForAllEvents'] ?>',
'ptzControls': '<?php echo str_replace(array("\r\n", "\r", "\n"), '', $monitorsExtraData[$m->Id()]['ptzControls']) ?>',
'monitorWidth': parseInt('<?php echo $m->ViewWidth() ?>'),
'monitorHeight': parseInt('<?php echo $m->ViewHeight() ?>'),
'monitorType': '<?php echo $m->Type() ?>',
'monitorRefresh': '<?php echo $m->Refresh() ?>',
'monitorStreamReplayBuffer': parseInt('<?php echo $m->StreamReplayBuffer() ?>'),
'monitorControllable': <?php echo $m->Controllable()?'true':'false' ?>,
'streamMode': '<?php echo getStreamModeMonitor($m) ?>'
};
<?php
} // end foreach monitor
@ -61,11 +72,11 @@ monitorData[monitorData.length] = {
var scale = '<?php echo $scale ?>';
var statusRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>;
var eventsRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_EVENTS ?>;
var imageRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_IMAGE ?>;
const statusRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_STATUS ?>;
const eventsRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_EVENTS ?>;
const imageRefreshTimeout = <?php echo 1000*ZM_WEB_REFRESH_IMAGE ?>;
var canStream = <?php echo canStream()?'true':'false' ?>;
const canStream = <?php echo canStream()?'true':'false' ?>;
var imageControlMode = '<?php
$control = $monitor->Control();

View File

@ -103,7 +103,15 @@ if (!$cycle and isset($_COOKIE['zmCycleShow'])) {
$showCycle = $_COOKIE['zmCycleShow'] == 'true';
}
#Whether to show the controls button
$hasPtzControls = ( ZM_OPT_CONTROL && $monitor->Controllable() && canView('Control') && $monitor->Type() != 'WebSite' );
$hasPtzControls = false;
foreach ($monitors as $m) {
if (( ZM_OPT_CONTROL && $m->Controllable() && canView('Control') && $m->Type() != 'WebSite' )) {
//If there is control for at least one camera, then we display the block.
$hasPtzControls = true;
break;
}
}
$showPtzControls = false;
if ($hasPtzControls) {
$showPtzControls = true;
@ -126,10 +134,10 @@ if (!empty($_REQUEST['mode']) and ($_REQUEST['mode']=='still' or $_REQUEST['mode
}
$options['mode'] = 'single';
if (!empty($_REQUEST['maxfps']) and validFloat($_REQUEST['maxfps']) and ($_REQUEST['maxfps']>0)) {
$options['maxfps'] = validHtmlStr($_REQUEST['maxfps']);
if (!empty($_REQUEST['maxfps']) and validNum($_REQUEST['maxfps']) and ($_REQUEST['maxfps']>0)) {
$options['maxfps'] = validNum($_REQUEST['maxfps']);
} else if (isset($_COOKIE['zmWatchRate'])) {
$options['maxfps'] = validHtmlStr($_COOKIE['zmWatchRate']);
$options['maxfps'] = validNum($_COOKIE['zmWatchRate']);
} else {
$options['maxfps'] = ''; // unlimited
}
@ -166,6 +174,7 @@ if ( !isset($scales[$scale])) {
$options['scale'] = 0; //Somewhere something is spoiled because of this...
$streamQualitySelected = '0';
# TODO input validation on streamquality
if (isset($_REQUEST['streamQuality'])) {
$streamQualitySelected = $_REQUEST['streamQuality'];
} else if (isset($_COOKIE['zmStreamQuality'])) {
@ -198,14 +207,19 @@ if (
) {
$options['scale'] = 0;
}
if ($monitor->JanusEnabled()) {
$streamMode = 'janus';
} else if ($monitor->RTSP2WebEnabled()) {
$streamMode = $monitor->RTSP2WebType();
} else {
$streamMode = getStreamMode();
function getStreamModeMonitor($monitor) {
if ($monitor->JanusEnabled()) {
$streamMode = 'janus';
} else if ($monitor->RTSP2WebEnabled()) {
$streamMode = $monitor->RTSP2WebType();
} else {
$streamMode = getStreamMode();
}
return $streamMode;
}
$streamMode = getStreamModeMonitor($monitor);
noCacheHeaders();
xhtmlHeaders(__FILE__, $monitor->Name().' - '.translate('Feed'));
getBodyTopHTML();
@ -254,6 +268,8 @@ echo getNavBarHTML() ?>
<span class="material-icons md-18">open_with</span>
</button>
<?php
} else {
echo 'No ptz';
}
?>
<span id="rateControl">
@ -332,8 +348,34 @@ echo htmlSelect('cyclePeriod', $cyclePeriodOptions, $period, array('id'=>'cycleP
</div>
<ul class="nav nav-pills flex-column">
<?php
if ($monitor->Type() != 'WebSite') {
$options['state'] = true;
}
$monitorsExtraData = [];
$monitorJanusUsed = false;
$dataMonIdx=0;
if ($hasPtzControls) {
foreach ( getSkinIncludes('includes/control_functions.php') as $includeFile )
require_once $includeFile;
}
foreach ($monitors as $m) {
echo '<li class="nav-item"><a class="nav-link'.( $m->Id() == $monitor->Id() ? ' active' : '' ).'" href="?view=watch&amp;mid='.$m->Id().'">'.$m->Name().'</a></li>';
$monitorsExtraData[$m->Id()]['StreamHTML'] = $m->getStreamHTML($options);
$monitorsExtraData[$m->Id()]['urlForAllEvents'] = "?view=events&page=1&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=Monitor&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D=%3D&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=".$m->Id()."&filter%5BQuery%5D%5Bsort_asc%5D=1&filter%5BQuery%5D%5Bsort_field%5D=StartDateTime&filter%5BQuery%5D%5Bskip_locked%5D=&filter%5BQuery%5D%5Blimit%5D=0";
if ($m->JanusEnabled()) {
$monitorJanusUsed = true;
}
$monitorsExtraData[$m->Id()]['ptzControls'] = '';
if ($hasPtzControls) {
$ptzControls = ptzControls($m);
$monitorsExtraData[$m->Id()]['ptzControls'] = $ptzControls;
}
echo '<li id="nav-item-cycle'.$m->Id().'" class="nav-item"><a id="nav-link'.$m->Id().'" class="nav-link'.( $m->Id() == $monitor->Id() ? ' active' : '' ).'" data-monIdx='.$dataMonIdx++.' href="#">'.$m->Name().'</a></li>';
}
if ($monitorJanusUsed) {
?>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="/javascript/janus/janus.js"></script>
<?php
}
?>
</ul>
@ -342,66 +384,43 @@ echo htmlSelect('cyclePeriod', $cyclePeriodOptions, $period, array('id'=>'cycleP
<div id="monitor" class="monitor hidden-shift"
>
<?php
if ($monitor->Type() != 'WebSite') {
$options['state'] = true;
}
echo $monitor->getStreamHTML($options);
?>
</div><!-- id="Monitor" -->
<?php
if ($monitor->Type() != 'WebSite') {
?>
<div class="buttons" id="dvrControls">
<!--
<button type="button" id="getImageBtn" title="<?php echo translate('Download Image') ?>"/>
-->
<?php
if ($streamMode == 'jpeg') {
if ($monitor->StreamReplayBuffer() != 0) {
?>
<button type="button" id="fastRevBtn" title="<?php echo translate('Rewind') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdFastRev">
<i class="material-icons md-18">fast_rewind</i>
</button>
<button type="button" id="slowRevBtn" title="<?php echo translate('StepBack') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdSlowRev">
<i class="material-icons md-18">chevron_right</i>
<i class="material-icons md-18">chevron_left</i>
</button>
<?php
}
?>
<button type="button" id="pauseBtn" title="<?php echo translate('Pause') ?>" class="inactive" data-on-click-true="streamCmdPause">
<i class="material-icons md-18">pause</i>
</button>
<button type="button" id="stopBtn" title="<?php echo translate('Stop') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdStop" style="display:none;">
<button type="button" id="stopBtn" title="<?php echo translate('Stop') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdStop">
<i class="material-icons md-18">stop</i>
</button>
<button type="button" id="playBtn" title="<?php echo translate('Play') ?>" class="active" disabled="disabled" data-on-click-true="streamCmdPlay">
<i class="material-icons md-18">play_arrow</i>
</button>
<?php
if ($monitor->StreamReplayBuffer() != 0) {
?>
<button type="button" id="slowFwdBtn" title="<?php echo translate('StepForward') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdSlowFwd">
<i class="material-icons md-18">chevron_right</i>
</button>
<button type="button" id="fastFwdBtn" title="<?php echo translate('FastForward') ?>" class="unavail" disabled="disabled" data-on-click-true="streamCmdFastFwd">
<i class="material-icons md-18">fast_forward</i>
</button>
<?php
}
?>
<button type="button" id="zoomOutBtn" title="<?php echo translate('ZoomOut') ?>" class="avail" data-on-click="zoomOutClick">
<i class="material-icons md-18">zoom_out</i>
</button>
<?php
} // end if streamMode==jpeg
?>
</div><!--dvrControls-->
<?php } // end if $monitor->Type() != 'WebSite' ?>
<div class="buttons" id="extButton">
<button type="button" id="fullscreenBtn" title="<?php echo translate('Fullscreen') ?>" class="avail" data-on-click="watchFullscreen">
<i class="material-icons md-18">fullscreen</i>
</button>
<button type="button" id="allEventsBtn" title="<?php echo translate('All Events') ?>" class="avail" data-on-click="watchAllEvents" data-url="?view=events&page=1&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Battr%5D=Monitor&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bop%5D=%3D&filter%5BQuery%5D%5Bterms%5D%5B0%5D%5Bval%5D=<?php echo $monitor->Id()?>&filter%5BQuery%5D%5Bsort_asc%5D=1&filter%5BQuery%5D%5Bsort_field%5D=StartDateTime&filter%5BQuery%5D%5Bskip_locked%5D=&filter%5BQuery%5D%5Blimit%5D=0"><?php echo translate('All Events') ?>
<button type="button" id="allEventsBtn" title="<?php echo translate('All Events') ?>" class="avail" data-on-click="watchAllEvents"><?php echo translate('All Events') ?>
</button>
</div>
</div><!-- id="wrapperMonitor" -->
@ -409,12 +428,9 @@ if ($streamMode == 'jpeg') {
<!-- START Control -->
<?php
if ( $hasPtzControls ) {
foreach ( getSkinIncludes('includes/control_functions.php') as $includeFile )
require_once $includeFile;
?>
<div id="ptzControls" class="col-sm-2 ptzControls"<?php echo $showPtzControls ? '' : ' style="display:none;"'?>>
<?php echo ptzControls($monitor) ?>
</div>
<div id="ptzControls" class="col-sm-2 ptzControls"<?php echo $showPtzControls ? '' : ' style="display:none;"'?>>
</div>
<?php
}
?>
@ -479,14 +495,6 @@ if ( canView('Events') && ($monitor->Type() != 'WebSite') ) {
</div>
</div>
<?php
if ( $monitor->JanusEnabled() ) {
?>
<script src="<?php echo cache_bust('js/adapter.min.js') ?>"></script>
<script src="/javascript/janus/janus.js"></script>
<?php
}
?>
<?php
if ( $monitor->RTSP2WebEnabled() and $monitor->RTSP2WebType == "HLS") {
?>
<script src="<?php echo cache_bust('js/hls.js') ?>"></script>