Merge branch 'master' of github.com:ZoneMinder/zoneminder

pull/4707/merge
Isaac Connor 2026-03-15 17:25:17 -04:00
commit dcb12e6f2a
3 changed files with 157 additions and 1 deletions

View File

@ -83,12 +83,14 @@ function MonitorStream(monitorData) {
this.img_onerror = function() {
console.log('Image stream has been stopped! stopping streamCmd');
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
this.writeTextInfoBlock("Error", {showImg: false});
};
this.img_onload = function() {
if (!this.streamCmdTimer) {
console.log('Image stream has loaded! starting streamCmd for monitor ID='+this.id+' connKey='+this.connKey+' in '+statusRefreshTimeout + 'ms');
this.streamCmdQuery(); // This is to get an instant status update
this.streamCmdTimer = setInterval(this.streamCmdQuery.bind(this), statusRefreshTimeout);
this.writeTextInfoBlock("");
}
};
@ -404,18 +406,21 @@ function MonitorStream(monitorData) {
}
this.handlerEventListener['playStream'] = manageEventListener.addEventListener(stream, 'play',
(e) => {
this.writeTextInfoBlock("");
this.createVolumeSlider();
getTracksFromStream(this); //Go2rtc
getTracksFromStream(this);
}
);
this.handlerEventListener['pauseStream'] = manageEventListener.addEventListener(stream, 'pause',
(e) => {
this.writeTextInfoBlock("Paused", {showImg: false});
manageEventListener.removeEventListener(this.handlerEventListener['volumechange']);
}
);
};
this.start = function(streamChannel = 'default') {
this.writeTextInfoBlock("Loading...");
if (streamChannel === null || streamChannel === '' || currentView == 'montage') streamChannel = 'default';
// Normalize channel name for internal tracking
if (streamChannel == 'default') {
@ -632,6 +637,98 @@ function MonitorStream(monitorData) {
this.updateStreamInfo('ZMS MJPEG');
}; // this.start
this.setSrcInfoBlock = function() {
const imgInfoBlock = document.getElementById('img-stream-info-block' + this.id);
if (!imgInfoBlock) return null;
let src = this.url_to_zms.replace(/mode=jpeg/i, 'mode=single');
if (-1 == src.search('auth')) {
src += '&'+auth_relay;
} else {
src = src.replace(/auth=\w+/i, 'auth='+auth_hash);
}
if (-1 == src.search('scale=')) {
src += '&scale='+this.scale;
}
if (-1 == src.search('mode=')) {
src += '&mode=single';
}
imgInfoBlock.src = '';
imgInfoBlock.src = src;
return imgInfoBlock;
};
this.writeTextInfoBlock = function(text, params = {}) {
const infoBlock = document.getElementById('stream-info-block' + this.id) || this.createInfoBlock();
if (infoBlock) {
if (params.color) infoBlock.style.color = params.color;
const normalizedText = (text == null) ? '' : text;
infoBlock.textContent = normalizedText;
if (normalizedText === "") {
infoBlock.style.zIndex = 0;
this.hideImgForInfoBlock();
} else {
setTextSizeOnInfoBlock(infoBlock);
infoBlock.style.zIndex = 10001;
if (params.showImg === false) {
this.hideImgForInfoBlock();
} else {
this.createImgForInfoBlock();
this.showImgForInfoBlock();
}
}
}
};
this.hideImgForInfoBlock = function() {
const imgInfoBlock = document.getElementById('img-stream-info-block' + this.id);
if (imgInfoBlock) imgInfoBlock.classList.add('hidden-shift');
};
this.showImgForInfoBlock = function() {
const imgInfoBlock = document.getElementById('img-stream-info-block' + this.id);
if (imgInfoBlock) imgInfoBlock.classList.remove('hidden-shift');
};
this.createImgForInfoBlock = function() {
let currentImg = document.getElementById('img-stream-info-block' + this.id);
if (!currentImg) {
const imgInfoBlock = document.createElement('img');
imgInfoBlock.classList.add('img-stream-info-block');
imgInfoBlock.id = 'img-stream-info-block' + this.id;
imgInfoBlock.style.position = 'absolute';
imgInfoBlock.style.top = 0;
imgInfoBlock.style.left = 0;
imgInfoBlock.style.width = '100%';
imgInfoBlock.style.height = '100%';
imgInfoBlock.style.zIndex = 10000;
imgInfoBlock.style.pointerEvents = 'none';
this.getElement().parentNode.appendChild(imgInfoBlock);
currentImg = imgInfoBlock;
}
this.setSrcInfoBlock();
return currentImg;
};
this.createInfoBlock = function() {
let currentInfoBlock = document.getElementById('stream-info-block' + this.id);
if (!currentInfoBlock) {
const infoBlock = document.createElement('div');
infoBlock.classList.add('stream-info-block');
infoBlock.id = 'stream-info-block' + this.id;
infoBlock.style.position = 'absolute';
infoBlock.style.width = '100%';
infoBlock.style.height = 'auto';
infoBlock.style.top = '50%';
infoBlock.style.left = '50%';
infoBlock.style.transform = 'translate(-50%, -50%)';
infoBlock.style.pointerEvents = 'none';
this.getElement().parentNode.appendChild(infoBlock);
currentInfoBlock = infoBlock;
}
return currentInfoBlock;
};
this.stop = function() {
manageEventListener.removeEventListener(this.handlerEventListener['killStream']);
manageEventListener.removeEventListener(this.handlerEventListener['playStream']);
@ -647,6 +744,11 @@ function MonitorStream(monitorData) {
console.warn(`! ${dateTimeToISOLocal(new Date())} Stream for ID=${this.id} has already stopped.`);
return;
}
if (-1 !== this.activePlayer.indexOf('zms')) {
this.writeTextInfoBlock("Stopped", {showImg: false});
} else {
this.writeTextInfoBlock("Stopped");
}
console.debug(`! ${dateTimeToISOLocal(new Date())} Stream for ID=${this.id} STOPPING`);
this.statusCmdTimer = clearInterval(this.statusCmdTimer);
this.streamCmdTimer = clearInterval(this.streamCmdTimer);
@ -1630,6 +1732,13 @@ function MonitorStream(monitorData) {
$j.ajaxSetup({timeout: AJAX_TIMEOUT});
this.streamCmdReq = function(streamCmdParms) {
if (-1 !== this.activePlayer.indexOf('zms')) {
if (streamCmdParms.command == CMD_PAUSE) {
this.writeTextInfoBlock("Paused", {showImg: false});
} else if (streamCmdParms.command == CMD_PLAY) {
this.writeTextInfoBlock("");
}
}
if (!(streamCmdParms.command == CMD_STOP && ((-1 !== this.activePlayer.indexOf('go2rtc')) || (-1 !== this.activePlayer.indexOf('rtsp2web'))))) {
//Otherwise, there will be errors in the console "Socket ... does not exist" when quickly switching stop->start and we also do not need to replace SRC in getStreamCmdResponse
this.ajaxQueue = jQuery.ajaxQueue({

View File

@ -1621,6 +1621,28 @@ video-stream[id^='liveStream'] video{
border-radius: 4px;
}
.text-3d {
color: transparent;
text-shadow: -4px 4px hsla(0, 0%, 70%, .4),
-3px 3px hsla(0, 0%, 65%, .2),
-2px 2px hsla(0, 0%, 60%, .2),
-1px 1px hsla(0, 0%, 55%, .2),
0px 0px hsla(0, 0%, 50%, .5),
1px -1px hsla(0, 0%, 40%, .6),
2px -2px hsla(0, 0%, 38%, .7),
3px -3px hsla(0, 0%, 37%, .8),
4px -4px hsla(0, 0%, 36%, .9),
5px -5px hsla(0, 0%, 35%, 1.0);
}
.text-3d-mini {
color: transparent;
text-shadow: -2px 2px hsla(0, 0%, 65%, .2),
-1px 1px hsla(0, 0%, 55%, .2),
0px 0px hsla(0, 0%, 50%, .5),
1px -1px hsla(0, 0%, 40%, .6),
2px -2px hsla(0, 0%, 30%, .7);
}
/* +++ This block should always be located at the end! */
.hidden {
display: none;

View File

@ -1854,6 +1854,30 @@ function setButtonSizeOnStream() {
});
}
function calcTextSizeOnInfoBlock(el) {
const w = el.offsetWidth;
const textLength = el.innerText.length;
if (textLength === 0) return false;
const d = (w/400 > 1) ? 1 : w/400/0.8; // If the block width is less than 400px, the text will take up more than 40% of the width, otherwise it will be difficult to read.
return parseInt((w/textLength) * 0.6 / d); // ~40% of the block width
}
function setTextSizeOnInfoBlocks() {
const block = document.querySelectorAll('[id ^= "stream-info-block"]');
Array.prototype.forEach.call(block, (el) => {
setTextSizeOnInfoBlock(el);
});
}
function setTextSizeOnInfoBlock(el) {
if (el.innerText.length == 0) return;
const fontSize = calcTextSizeOnInfoBlock(el);
el.style.fontSize = fontSize + "px";
el.classList.remove("text-3d-mini", "text-3d");
const blockClass = (fontSize !== fontSize || fontSize < 50) ? 'text-3d-mini' : 'text-3d';
el.classList.add(blockClass);
}
/*
* date - object type Date()
* shift.offset - number (can be negative)
@ -2768,6 +2792,7 @@ function monitorsSetScale(id=null) {
}
} // End function _setScale
setButtonSizeOnStream();
setTextSizeOnInfoBlocks();
} // End function monitorsSetScale
/*IMPORTANT DO NOT CALL WITHOUT CONSCIOUS NEED!!!*/