diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ec8ea..04a71c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. +## 2.8.5 - 2026-08-17 + +### Fixed + +- Fixed thumbnail capture timing so video assets are ready before the preview canvas is captured. +- Replaced unavailable webpage thumbnails with a subdued placeholder while keeping live webpage previews intact. + ## 2.8.4 - 2026-08-17 ### Added diff --git a/build/package.player.json b/build/package.player.json index b7616d9..92f94ea 100644 --- a/build/package.player.json +++ b/build/package.player.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-player", - "version": "2.8.4", + "version": "2.8.5", "private": false, "description": "Pulse Signage player application bundle", "engines": { diff --git a/build/package.web.json b/build/package.web.json index 8a1176c..606b9a5 100644 --- a/build/package.web.json +++ b/build/package.web.json @@ -1,6 +1,6 @@ { "name": "pulse-signage-web", - "version": "2.8.4", + "version": "2.8.5", "private": false, "description": "Pulse Signage web and bridge application bundle", "engines": { diff --git a/package-lock.json b/package-lock.json index 9dbbb84..798fb10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pulse-signage", - "version": "2.8.4", + "version": "2.8.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "pulse-signage", - "version": "2.8.4", + "version": "2.8.5", "dependencies": { "@sparticuz/chromium": "^149.0.0", "animate.css": "^4.1.1", diff --git a/package.json b/package.json index 9c620d0..400d503 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pulse-signage", - "version": "2.8.4", + "version": "2.8.5", "private": false, "description": "Pulse Signage application with MySQL and media storage", "engines": { diff --git a/src/player/regions/html.js b/src/player/regions/html.js index c40c4ba..a54efb4 100644 --- a/src/player/regions/html.js +++ b/src/player/regions/html.js @@ -32,7 +32,7 @@ function buildHtmlDocument(html) { return raw; } - return '' + raw + ''; + return '' + raw + ''; } function renderHtmlRegionContent(value) { @@ -40,7 +40,10 @@ function renderHtmlRegionContent(value) { if (!html) { return ''; } - return ''; + if (/^'; + } + return '
' + html + '
'; } function renderHtmlRegion(region, regionContent) { diff --git a/src/web/lib/media/slide-thumbnail-preview.js b/src/web/lib/media/slide-thumbnail-preview.js index 9a103d4..b4637ac 100644 --- a/src/web/lib/media/slide-thumbnail-preview.js +++ b/src/web/lib/media/slide-thumbnail-preview.js @@ -142,9 +142,13 @@ function buildRegionInnerHtml(region, regionContent, baseUrl) { if (regionType === 'html') { const html = String(rawValue || '').trim(); - return html - ? '
' - : ''; + if (!html) { + return ''; + } + if (/^'; + } + return '
' + html + '
'; } if (regionType === 'rtmp') { diff --git a/src/web/lib/media/slide-thumbnails.js b/src/web/lib/media/slide-thumbnails.js index c57673f..327d69c 100644 --- a/src/web/lib/media/slide-thumbnails.js +++ b/src/web/lib/media/slide-thumbnails.js @@ -203,10 +203,7 @@ function buildRegionInnerHtml(region, regionContent, baseUrl, options) { } if (regionType === 'webpage') { - const src = resolveAssetUrl(baseUrl, rawValue); - return src - ? '
' - : ''; + return '
Webpage preview unavailable
'; } if (regionType === 'qr-code') { @@ -220,9 +217,13 @@ function buildRegionInnerHtml(region, regionContent, baseUrl, options) { if (regionType === 'html') { const html = String(rawValue || '').trim(); - return html - ? '
' - : ''; + if (!html) { + return ''; + } + if (/^'; + } + return '
' + html + '
'; } if (regionType === 'rtmp') { @@ -301,8 +302,7 @@ async function launchBrowser() { args: [ '--no-sandbox', '--disable-setuid-sandbox', - '--disable-dev-shm-usage', - '--disable-gpu' + '--disable-dev-shm-usage' ], defaultViewport: { width: 1920, height: 1080, deviceScaleFactor: 1 }, executablePath: executablePath, @@ -401,14 +401,9 @@ async function captureSlideThumbnail(options) { }, { timeout: 30000 }); await page.waitForFunction(function () { - var canvas = document.querySelector('#popup-preview-canvas'); - if (!canvas) { - return false; - } - - var images = Array.prototype.slice.call(canvas.querySelectorAll('img')); - return images.every(function (image) { - return image.complete && typeof image.naturalWidth === 'number'; + var videos = Array.prototype.slice.call(document.querySelectorAll('#popup-preview-canvas video')); + return videos.every(function (video) { + return video.readyState >= 2; }); }, { timeout: 30000 }); @@ -417,17 +412,13 @@ async function captureSlideThumbnail(options) { try { await document.fonts.ready; } catch (_error) { - // Ignore font readiness failures and fall back to the rendered frame. + return null; } } }); - await page.evaluate(function () { - return new Promise(function (resolve) { - window.requestAnimationFrame(function () { - window.requestAnimationFrame(resolve); - }); - }); + await new Promise(function (resolve) { + setTimeout(resolve, 1000); }); } @@ -435,19 +426,14 @@ async function captureSlideThumbnail(options) { try { const page = await browser.newPage(); try { - const previewPayload = buildThumbnailPreviewPayload(slide, { - baseUrl: baseUrl, - fontStylesheetHref: options && options.fontStylesheetHref ? options.fontStylesheetHref : '', - getItem: getThumbnailItem, - getCachedImagePath: getCachedImagePath + const previewPath = '/api/internal/slide-thumbnails/' + encodeURIComponent(String(slide.id)) + '/popup-preview'; + const previewUrl = baseUrl + previewPath; + const canvasSize = getThumbnailCanvasSize(slide); + await page.setViewport({ + width: Math.max(1, Number(canvasSize.width || PLAYER_VIEWPORT.width)), + height: Math.max(1, Number(canvasSize.height || PLAYER_VIEWPORT.height)), + deviceScaleFactor: 1 }); - const previewPath = '/slides/popup-preview'; - const previewUrl = baseUrl + previewPath + '#' + encodeURIComponent(JSON.stringify(previewPayload)); - await page.setViewport({ - width: Math.max(1, Number(previewPayload.canvasWidth || PLAYER_VIEWPORT.width)), - height: Math.max(1, Number(previewPayload.canvasHeight || PLAYER_VIEWPORT.height)), - deviceScaleFactor: 1 - }); await page.setExtraHTTPHeaders(Object.assign({}, createRequestAuthHeaders({ method: 'GET', pathname: previewPath @@ -463,7 +449,26 @@ async function captureSlideThumbnail(options) { if (!canvas) { throw new Error('Popup preview did not produce a slide canvas.'); } - await canvas.screenshot({ path: fullSizePath }); + await page.evaluate(function () { + var canvasElement = document.querySelector('#popup-preview-canvas'); + if (canvasElement) { + canvasElement.style.transform = 'none'; + canvasElement.style.transformOrigin = 'top left'; + } + }); + const canvasBounds = await canvas.boundingBox(); + if (!canvasBounds) { + throw new Error('Popup preview canvas has no screenshot bounds.'); + } + await page.screenshot({ + path: fullSizePath, + clip: { + x: Math.max(0, canvasBounds.x), + y: Math.max(0, canvasBounds.y), + width: Math.max(1, canvasBounds.width), + height: Math.max(1, canvasBounds.height) + } + }); } finally { await page.close().catch(function () { return null; diff --git a/src/web/public/js/slides/slide-form.js b/src/web/public/js/slides/slide-form.js index 04a0851..cf49ffa 100644 --- a/src/web/public/js/slides/slide-form.js +++ b/src/web/public/js/slides/slide-form.js @@ -305,31 +305,12 @@ import { createSlideFormPreviewHelpers } from '/assets/js/slides/slide-form-prev previewBox.innerHTML = value ? '
' + (isVideo - ? '' + ? '' : '' + (isQrImage ? 'Current QR image preview' : 'Current image preview') + '') + '' + '
' : '
No ' + (isVideo ? 'video' : 'image') + '
'; - if (isVideo) { - window.requestAnimationFrame(function () { - var video = previewBox.querySelector('video.slide-image-region-preview'); - if (!video) { - return; - } - try { - video.load(); - } catch (_error) { - // Ignore load failures; play() will retry if the browser allows it. - } - var playPromise = video.play && video.play(); - if (playPromise && typeof playPromise.catch === 'function') { - playPromise.catch(function () { - return null; - }); - } - }); - } } function loadVideoDuration(mediaPath) { diff --git a/test/player-render-helpers.test.js b/test/player-render-helpers.test.js index c15b11b..df695ad 100644 --- a/test/player-render-helpers.test.js +++ b/test/player-render-helpers.test.js @@ -99,9 +99,9 @@ test('shared iframe renderers size preview content explicitly', () => { assert.ok(playerHtmlRegionSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"')); assert.ok(playerWebpageRegionSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"')); assert.ok(playerRenderHelpersSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"')); - assert.ok(slideThumbnailPreviewSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"')); + assert.ok(slideThumbnailPreviewSource.includes('slide-preview-webpage-region')); assert.ok(slideThumbnailPreviewSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"')); - assert.ok(slideThumbnailsSource.includes('style="width:100%;height:100%;border:0;display:block;background:#fff;overflow:hidden;"')); + assert.ok(slideThumbnailsSource.includes('slide-preview-webpage-placeholder')); assert.ok(slideThumbnailsSource.includes('style="width:100%;height:100%;border:0;display:block;background:transparent;overflow:hidden;"')); });