fix: calculate optimal thumbnail overlay scale from monitor dimensions

Add data-monitor-width and data-monitor-height attributes to thumbnail
img elements in console.php, events.php, watch.php, and monitor.php.
Use these to calculate the scale parameter as a ratio of overlay size to
monitor native resolution, clamped to 5-100%, instead of hardcoding
scale=75 (overlay) and scale=32 (fallback).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
pull/4653/head
Isaac Connor 2026-02-26 12:16:56 -05:00
parent 90d641f317
commit 61b025b85f
5 changed files with 16 additions and 3 deletions

View File

@ -518,6 +518,7 @@ function queryRequest() {
$row['Thumbnail'] = '<div class="colThumbnail" style="'.$thmbHeight.'"><a href="?view=watch&amp;mid='.$monitor['Id'].'">'.
'<img id="thumbnail'.$Monitor->Id().'" src="'.$stillSrc.'" style="'.$thmbWidth.$thmbHeight.
'" stream_src="'.$streamSrc.'" still_src="'.$stillSrc.'"'.$videoAttr.$go2rtcAttr.$liveStreamAttr.$debugAttr.
' data-monitor-width="'.$Monitor->ViewWidth().'" data-monitor-height="'.$Monitor->ViewHeight().'"'.
($options['width'] ? ' width="'.$options['width'].'"' : '').
($options['height'] ? ' height="'.$options['height'].'"' : '').
' loading="lazy" /></a></div>';

View File

@ -332,7 +332,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
}
// Modify the row data as needed
$row['imgHtml'] = '<img id="thumbnail' .$event->Id(). '" src="' .$imgSrc. '" alt="Event '.$event->Id().'" width="' .validInt($event->ThumbnailWidth()). '" height="' .validInt($event->ThumbnailHeight()).'" stream_src="' .$streamSrc. '" still_src="' .$imgSrc. '"' .$videoAttr. ' loading="lazy" />';
$row['imgHtml'] = '<img id="thumbnail' .$event->Id(). '" src="' .$imgSrc. '" alt="Event '.$event->Id().'" width="' .validInt($event->ThumbnailWidth()). '" height="' .validInt($event->ThumbnailHeight()).'" stream_src="' .$streamSrc. '" still_src="' .$imgSrc. '"' .$videoAttr. ' data-monitor-width="'.$event->Width().'" data-monitor-height="'.$event->Height().'" loading="lazy" />';
$row['imgWidth'] = validInt($event->ThumbnailWidth());
$row['imgHeight'] = validInt($event->ThumbnailHeight());

View File

@ -94,7 +94,7 @@ if ($rows) {
}
// Modify the row data as needed
$row['imgHtml'] = '<img id="thumbnail' .$event->Id(). '" src="' .$imgSrc. '" alt="Event '.$event->Id().'" width="' .validInt($event->ThumbnailWidth()). '" height="' .validInt($event->ThumbnailHeight()).'" stream_src="' .$streamSrc. '" still_src="' .$imgSrc. '"' .$videoAttr. ' loading="lazy" />';
$row['imgHtml'] = '<img id="thumbnail' .$event->Id(). '" src="' .$imgSrc. '" alt="Event '.$event->Id().'" width="' .validInt($event->ThumbnailWidth()). '" height="' .validInt($event->ThumbnailHeight()).'" stream_src="' .$streamSrc. '" still_src="' .$imgSrc. '"' .$videoAttr. ' data-monitor-width="'.$event->Width().'" data-monitor-height="'.$event->Height().'" loading="lazy" />';
$row['Name'] = validHtmlStr($row['Name']);
$row['Length'] = gmdate('H:i:s', intval($row['Length']));

View File

@ -1339,6 +1339,13 @@ function calculateOverlayDimensions(img) {
return {width: Math.round(width), height: Math.round(height)};
}
function calculateOverlayScale(img, overlayWidth) {
const monitorWidth = parseInt(img.dataset.monitorWidth);
if (!monitorWidth || monitorWidth <= 0) return 100;
const scale = Math.round(100 * overlayWidth / monitorWidth);
return Math.max(5, Math.min(100, scale));
}
function createThumbnailOverlay(img, overlaySrc, dimensions, streamType, monitorId, go2rtcSrc, go2rtcMid, useGo2rtc) {
const existing = document.getElementById('thumb-overlay');
if (existing) existing.remove();
@ -1359,10 +1366,12 @@ function createThumbnailOverlay(img, overlaySrc, dimensions, streamType, monitor
container.style.backgroundImage = 'url("' + img.src + '")';
const fallbackToMjpeg = function() {
console.log('fallback');
const streamSrc = img.getAttribute('stream_src');
if (streamSrc) {
const fallbackImg = document.createElement('img');
fallbackImg.src = streamSrc.replace(/scale=\d+/, 'scale=32');
const scale = calculateOverlayScale(img, dimensions.width);
fallbackImg.src = streamSrc.replace(/scale=\d+/, 'scale=' + scale);
container.appendChild(fallbackImg);
}
};
@ -1398,6 +1407,8 @@ function createThumbnailOverlay(img, overlaySrc, dimensions, streamType, monitor
createVideoElement(container, overlaySrc, eventStart, statusBar);
} else {
const overlayImg = document.createElement('img');
const scale = calculateOverlayScale(img, dimensions.width);
overlaySrc = overlaySrc.replace(/scale=\d+/, 'scale=' + scale);
overlayImg.src = overlaySrc;
container.appendChild(overlayImg);
}

View File

@ -1396,6 +1396,7 @@ $codecs = array(
$imgHTML .= $stream_available ? ' href="?view=watch&amp;mid='.$monitor->Id().'">' : '>';
$imgHTML .= '<img id="thumbnail' .$monitor->Id(). '" src="' .$stillSrc. '" style="'
.$thmbWidth.$thmbHeight. '" stream_src="' .$streamSrc. '" still_src="' .$stillSrc. '"'.
' data-monitor-width="'.$monitor->ViewWidth().'" data-monitor-height="'.$monitor->ViewHeight().'"'.
($options['width'] ? ' width="'.$options['width'].'"' : '' ).
($options['height'] ? ' height="'.$options['height'].'"' : '' ).
' loading="lazy" /></a></div>';