@xiboplayer/pwa 0.7.11 → 0.7.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/assets/html2canvas-BdjGaL7S.js +6 -0
  2. package/dist/assets/html2canvas-BdjGaL7S.js.map +1 -0
  3. package/dist/assets/main-CT7JIMxf.js +735 -0
  4. package/dist/assets/main-CT7JIMxf.js.map +1 -0
  5. package/dist/assets/modulepreload-polyfill-wMinxHhO.js +1 -0
  6. package/dist/assets/pdf-ByOGXKA2.js +55 -0
  7. package/dist/assets/pdf-ByOGXKA2.js.map +1 -0
  8. package/dist/assets/setup-tmBa6Ubb.js +2 -0
  9. package/dist/assets/setup-tmBa6Ubb.js.map +1 -0
  10. package/dist/assets/src-B9Yas4SH.js +1 -0
  11. package/dist/assets/src-BfTQUSd6.js +2 -0
  12. package/dist/assets/src-BfTQUSd6.js.map +1 -0
  13. package/dist/assets/src-BvglFNkK.js +2 -0
  14. package/dist/assets/src-BvglFNkK.js.map +1 -0
  15. package/dist/assets/src-Ct_7vG0E.js +1 -0
  16. package/dist/assets/src-Cyk0X_mz.js +4 -0
  17. package/dist/assets/src-Cyk0X_mz.js.map +1 -0
  18. package/dist/assets/src-DGSLOm9k.js +3 -0
  19. package/dist/assets/src-DGSLOm9k.js.map +1 -0
  20. package/dist/assets/src-DNqc--R2.js +2 -0
  21. package/dist/assets/src-DNqc--R2.js.map +1 -0
  22. package/dist/assets/src-DgZHc2Af.js +2 -0
  23. package/dist/assets/src-DgZHc2Af.js.map +1 -0
  24. package/dist/assets/src-DmdLCQQf.js +1 -0
  25. package/dist/assets/src-Dr4QlWKq.js +16 -0
  26. package/dist/assets/src-Dr4QlWKq.js.map +1 -0
  27. package/dist/assets/src-DwSNRd1O.js +2 -0
  28. package/dist/assets/src-DwSNRd1O.js.map +1 -0
  29. package/dist/assets/sync-manager-zTULh3xK.js +2 -0
  30. package/dist/assets/sync-manager-zTULh3xK.js.map +1 -0
  31. package/dist/index.html +5 -4
  32. package/dist/pdf.worker.min.mjs +8 -1
  33. package/dist/setup.html +7 -4
  34. package/dist/sw-pwa.js +3 -2
  35. package/dist/sw-pwa.js.map +1 -1
  36. package/package.json +18 -18
  37. package/dist/assets/chunk-config-CHE1kw8L.js +0 -2
  38. package/dist/assets/chunk-config-CHE1kw8L.js.map +0 -1
  39. package/dist/assets/html2canvas.esm-CBrSDip1.js +0 -23
  40. package/dist/assets/html2canvas.esm-CBrSDip1.js.map +0 -1
  41. package/dist/assets/index-ArejExya.js +0 -2
  42. package/dist/assets/index-ArejExya.js.map +0 -1
  43. package/dist/assets/index-BvsjGl2t.js +0 -2
  44. package/dist/assets/index-BvsjGl2t.js.map +0 -1
  45. package/dist/assets/index-Byt59Jhj.js +0 -2
  46. package/dist/assets/index-Byt59Jhj.js.map +0 -1
  47. package/dist/assets/index-C8G3JQhb.js +0 -2
  48. package/dist/assets/index-C8G3JQhb.js.map +0 -1
  49. package/dist/assets/index-CHFWBM2e.js +0 -5
  50. package/dist/assets/index-CHFWBM2e.js.map +0 -1
  51. package/dist/assets/index-DgpOe3fJ.js +0 -2
  52. package/dist/assets/index-DgpOe3fJ.js.map +0 -1
  53. package/dist/assets/index-DvGNq4n8.js +0 -2
  54. package/dist/assets/index-DvGNq4n8.js.map +0 -1
  55. package/dist/assets/index-DxzaxlFM.js +0 -2
  56. package/dist/assets/index-DxzaxlFM.js.map +0 -1
  57. package/dist/assets/index-PVO8xOA2.js +0 -13
  58. package/dist/assets/index-PVO8xOA2.js.map +0 -1
  59. package/dist/assets/index-jk-E3cgt.js +0 -2
  60. package/dist/assets/index-jk-E3cgt.js.map +0 -1
  61. package/dist/assets/main-CcZ_mjpH.js +0 -736
  62. package/dist/assets/main-CcZ_mjpH.js.map +0 -1
  63. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js +0 -2
  64. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js.map +0 -1
  65. package/dist/assets/pdf-BnPRJEQ6.js +0 -13
  66. package/dist/assets/pdf-BnPRJEQ6.js.map +0 -1
  67. package/dist/assets/protocol-detector-z58JLhyD.js +0 -16
  68. package/dist/assets/protocol-detector-z58JLhyD.js.map +0 -1
  69. package/dist/assets/setup-BrexvCgF.js +0 -2
  70. package/dist/assets/setup-BrexvCgF.js.map +0 -1
  71. package/dist/assets/sync-manager-D0ajYwqd.js +0 -2
  72. package/dist/assets/sync-manager-D0ajYwqd.js.map +0 -1
  73. package/dist/assets/widget-html-DmPOk8hT.js +0 -2
  74. package/dist/assets/widget-html-DmPOk8hT.js.map +0 -1
@@ -0,0 +1,735 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./src-DgZHc2Af.js","./src-DGSLOm9k.js","./src-Ct_7vG0E.js","./src-BfTQUSd6.js","./src-Dr4QlWKq.js","./src-DmdLCQQf.js","./src-DNqc--R2.js","./src-B9Yas4SH.js","./src-BvglFNkK.js","./src-Cyk0X_mz.js","./src-DwSNRd1O.js","./sync-manager-zTULh3xK.js"])))=>i.map(i=>d[i]);
2
+ import"./modulepreload-polyfill-wMinxHhO.js";import{S as e,_ as t,b as n,h as r,l as i,o as a,p as o,t as s,v as c}from"./src-DGSLOm9k.js";import{c as l,l as u,s as d}from"./src-DNqc--R2.js";import{a as f,d as p,i as m,n as h,r as g}from"./src-BfTQUSd6.js";var _=Object.create,v=Object.defineProperty,y=Object.getOwnPropertyDescriptor,b=Object.getOwnPropertyNames,x=Object.getPrototypeOf,S=Object.prototype.hasOwnProperty,ee=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),C=(e,t)=>{let n={};for(var r in e)v(n,r,{get:e[r],enumerable:!0});return t||v(n,Symbol.toStringTag,{value:`Module`}),n},te=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var i=b(t),a=0,o=i.length,s;a<o;a++)s=i[a],!S.call(e,s)&&s!==n&&v(e,s,{get:(e=>t[e]).bind(null,s),enumerable:!(r=y(t,s))||r.enumerable});return e},w=(e,t,n)=>(n=e==null?{}:_(x(e)),te(t||!e||!e.__esModule?v(n,`default`,{value:e,enumerable:!0}):n,e)),ne={name:`@xiboplayer/renderer`,version:`0.7.13`,description:`RendererLite - Fast, efficient XLF layout rendering engine`,type:`module`,main:`./src/index.js`,types:`./src/index.d.ts`,exports:{".":`./src/index.js`,"./renderer-lite":`./src/renderer-lite.js`,"./layout":`./src/layout.js`},scripts:{test:`vitest run`,"test:watch":`vitest`,"test:coverage":`vitest run --coverage`},dependencies:{"@xiboplayer/cache":`workspace:*`,"@xiboplayer/schedule":`workspace:*`,"@xiboplayer/utils":`workspace:*`,"pdfjs-dist":`^5.6.205`},devDependencies:{jsdom:`^29.0.1`,vitest:`^4.1.2`},keywords:[`xibo`,`digital-signage`,`renderer`,`xlf`,`layout`],author:`Pau Aliagas <linuxnow@gmail.com>`,license:`AGPL-3.0-or-later`,repository:{type:`git`,url:`git+https://github.com/xibo-players/xiboplayer.git`,directory:`packages/renderer`},homepage:`https://xiboplayer.org`},T=c(`LayoutPool`),E=class e{constructor(e=2){this.layouts=new Map,this.maxSize=e,this.hotLayoutId=null}has(e){return this.layouts.has(e)}get(e){return this.layouts.get(e)}add(e,t){if(this.layouts.has(e)){let n=this.layouts.get(e);Object.assign(n,t),n.lastAccess=Date.now();return}this.layouts.size>=this.maxSize&&this.evictLRU(),t.status=`warm`,t.lastAccess=Date.now(),this.layouts.set(e,t),T.info(`Added layout ${e} to pool (size: ${this.layouts.size}/${this.maxSize})`)}setHot(e){if(this.hotLayoutId!==null&&this.layouts.has(this.hotLayoutId)&&(this.layouts.get(this.hotLayoutId).status=`warm`),this.layouts.has(e)){let t=this.layouts.get(e);t.status=`hot`,t.lastAccess=Date.now()}this.hotLayoutId=e}evict(t){let n=this.layouts.get(t);if(n){if(T.info(`Evicting layout ${t} from pool`),n.regions)for(let[e,t]of n.regions)t.timer&&=(clearTimeout(t.timer),null);if(n.container&&e.releaseMediaElements(n.container),n.blobUrls&&n.blobUrls.size>0&&(n.blobUrls.forEach(e=>{URL.revokeObjectURL(e)}),T.info(`Revoked ${n.blobUrls.size} blob URLs for layout ${t}`)),n.mediaUrlCache)for(let[e,t]of n.mediaUrlCache)t&&typeof t==`string`&&t.startsWith(`blob:`)&&URL.revokeObjectURL(t);n.container&&n.container.parentNode&&n.container.remove(),this.layouts.delete(t),this.hotLayoutId===t&&(this.hotLayoutId=null)}}static releaseMediaElements(t){requestAnimationFrame(()=>e._releaseMediaElementsSync(t))}static _releaseMediaElementsSync(e){let t=0,n=0;e.querySelectorAll(`video`).forEach(e=>{e._hlsInstance&&(e._hlsInstance.destroy(),e._hlsInstance=null,n++),e._mediaStream&&(e._mediaStream.getTracks().forEach(e=>e.stop()),e._mediaStream=null,e.srcObject=null),e.pause(),e.removeAttribute(`src`),e.load(),t++}),e.querySelectorAll(`audio`).forEach(e=>{e.pause(),e.removeAttribute(`src`),e.load()});let r=0;e.querySelectorAll(`iframe`).forEach(e=>{try{let n=e.contentDocument||e.contentWindow?.document;n&&(n.querySelectorAll(`video`).forEach(e=>{e.pause(),e.removeAttribute(`src`),e.load(),t++}),n.querySelectorAll(`audio`).forEach(e=>{e.pause(),e.removeAttribute(`src`),e.load()}))}catch{}e.src=`about:blank`,r++}),e.querySelectorAll(`.pdf-widget`).forEach(e=>{e._pdfDestroy&&e._pdfDestroy()}),(t>0||r>0)&&T.info(`Released ${t} video(s)${n?` (${n} HLS)`:``}${r?`, ${r} iframe(s)`:``}`)}evictLRU(){let e=null,t=1/0;for(let[n,r]of this.layouts)r.status===`warm`&&r.lastAccess<t&&(e=n,t=r.lastAccess);e!==null&&this.evict(e)}clearWarm(){let e=0,t=[];for(let[e,n]of this.layouts)n.status===`warm`&&t.push(e);for(let n of t)this.evict(n),e++;return e>0&&T.info(`Cleared ${e} warm layout(s) from pool`),e}clearWarmNotIn(e){let t=0,n=[];for(let[t,r]of this.layouts)r.status===`warm`&&!e.has(t)&&n.push(t);for(let e of n)this.evict(e),t++;return t>0&&T.info(`Cleared ${t} warm layout(s) no longer in schedule`),t}getLatest(){let e;for(let t of this.layouts.keys())e=t;return e}clear(){let e=Array.from(this.layouts.keys());for(let t of e)this.evict(t);this.hotLayoutId=null}get size(){return this.layouts.size}},re=`modulepreload`,ie=function(e,t){return new URL(e,t).href},D={},O=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}r=o(t.map(t=>{if(t=ie(t,n),t in D)return;D[t]=!0;let r=t.endsWith(`.css`),i=r?`[rel="stylesheet"]`:``;if(n)for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}else if(document.querySelector(`link[href="${t}"]${i}`))return;let o=document.createElement(`link`);if(o.rel=r?`stylesheet`:re,r||(o.as=`script`),o.crossOrigin=``,o.href=t,a&&o.setAttribute(`nonce`,a),document.head.appendChild(o),r)return new Promise((e,n)=>{o.addEventListener(`load`,e),o.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})},k={fadeIn(e,t){let n=[{opacity:0},{opacity:1}],r={duration:t,easing:`linear`,fill:`forwards`};return e.animate(n,r)},fadeOut(e,t){let n=[{opacity:1},{opacity:0}],r={duration:t,easing:`linear`,fill:`forwards`};return e.animate(n,r)},getFlyKeyframes(e,t,n,r){let i={N:{x:0,y:r?-n:n},NE:{x:r?t:-t,y:r?-n:n},E:{x:r?t:-t,y:0},SE:{x:r?t:-t,y:r?n:-n},S:{x:0,y:r?n:-n},SW:{x:r?-t:t,y:r?n:-n},W:{x:r?-t:t,y:0},NW:{x:r?-t:t,y:r?-n:n}},a=i[e]||i.N;return r?{from:{transform:`translate(${a.x}px, ${a.y}px)`,opacity:0},to:{transform:`translate(0, 0)`,opacity:1}}:{from:{transform:`translate(0, 0)`,opacity:1},to:{transform:`translate(${a.x}px, ${a.y}px)`,opacity:0}}},flyIn(e,t,n,r,i){let a=this.getFlyKeyframes(n,r,i,!0),o={duration:t,easing:`ease-out`,fill:`forwards`};return e.animate([a.from,a.to],o)},flyOut(e,t,n,r,i){let a=this.getFlyKeyframes(n,r,i,!1),o={duration:t,easing:`ease-in`,fill:`forwards`};return e.animate([a.from,a.to],o)},apply(e,t,n,r,i){if(!t||!t.type)return null;let a=t.type.toLowerCase(),o=t.duration||1e3,s=t.direction||`N`;switch(a){case`fade`:return n?this.fadeIn(e,o):this.fadeOut(e,o);case`fadein`:return n?this.fadeIn(e,o):null;case`fadeout`:return n?null:this.fadeOut(e,o);case`fly`:return n?this.flyIn(e,o,s,r,i):this.flyOut(e,o,s,r,i);case`flyin`:return n?this.flyIn(e,o,s,r,i):null;case`flyout`:return n?null:this.flyOut(e,o,s,r,i);default:return null}}},A=class{constructor(e,t,n={}){this.config=e,this.container=t,this.options=n,this.log=c(`RendererLite`,n.logLevel),this.emitter=new r,this.currentLayout=null,this.currentLayoutId=null,this._preloadingLayoutId=null,this._preloadingPromise=null,this.regions=new Map,this.layoutTimer=null,this.layoutEndEmitted=!1,this._deferredTimerLayoutId=null,this._deferredTimerFallback=null,this._paused=!1,this._layoutTimerStartedAt=null,this._layoutTimerDurationMs=null,this.layoutBlobUrls=new Map,this.audioOverlays=new Map,this._stopWidgetBound=(e,t)=>this.stopWidget(e,t),this._renderWidgetBound=(e,t)=>this.renderWidget(e,t),this.scaleFactor=1,this.offsetX=0,this.offsetY=0,this.overlayContainer=null,this.activeOverlays=new Map,this._keydownHandler=null,this._keyboardActions=[],this._subPlaylistCycleIndex=new Map,this._startedWidgets=new Set,this.layoutPool=new E(2),this.preloadTimer=null,this._preloadRetryTimer=null,this.setupContainer(),this.emitter.on(`interactiveTrigger`,e=>this._handleInteractiveTrigger(e)),this.emitter.on(`widgetExpire`,e=>this._handleWidgetExpire(e)),this.emitter.on(`widgetExtendDuration`,e=>this._handleWidgetExtendDuration(e)),this.emitter.on(`widgetSetDuration`,e=>this._handleWidgetSetDuration(e)),this.log.info(`Initialized`)}setupContainer(){if(this.container.style.position=`relative`,this.container.style.width=`100%`,this.container.style.height=`100vh`,this.container.style.overflow=`hidden`,this._resizeSuppressed=!1,typeof ResizeObserver<`u`){let e=null;this.resizeObserver=new ResizeObserver(()=>{this._resizeSuppressed||(e&&clearTimeout(e),e=setTimeout(()=>this.rescaleRegions(),150))}),this.resizeObserver.observe(this.container)}this.overlayContainer=document.createElement(`div`),this.overlayContainer.id=`overlay-container`,this.overlayContainer.style.position=`absolute`,this.overlayContainer.style.top=`0`,this.overlayContainer.style.left=`0`,this.overlayContainer.style.width=`100%`,this.overlayContainer.style.height=`100%`,this.overlayContainer.style.zIndex=`1000`,this.overlayContainer.style.pointerEvents=`none`,this.container.appendChild(this.overlayContainer)}calculateScale(e){let t=this.container.clientWidth,n=this.container.clientHeight;if(!t||!n)return;let r=t/e.width,i=n/e.height;this.scaleFactor=Math.min(r,i),this.offsetX=(t-e.width*this.scaleFactor)/2,this.offsetY=(n-e.height*this.scaleFactor)/2,this.log.info(`Scale: ${this.scaleFactor.toFixed(3)} (${e.width}x${e.height} → ${t}x${n}, offset ${Math.round(this.offsetX)},${Math.round(this.offsetY)})`)}applyRegionScale(e,t){let n=this.scaleFactor;e.style.left=`${t.left*n+this.offsetX}px`,e.style.top=`${t.top*n+this.offsetY}px`,e.style.width=`${t.width*n}px`,e.style.height=`${t.height*n}px`}rescaleRegions(){if(this.currentLayout){this.calculateScale(this.currentLayout);for(let[e,t]of this.regions)this.applyRegionScale(t.element,t.config),t.width=t.config.width*this.scaleFactor,t.height=t.config.height*this.scaleFactor;for(let[e,t]of this.activeOverlays){this.calculateScale(t.layout);for(let[e,n]of t.regions)this.applyRegionScale(n.element,n.config),n.width=n.config.width*this.scaleFactor,n.height=n.config.height*this.scaleFactor}}}on(e,t){this.emitter.on(e,t)}emit(e,...t){this.emitter.emit(e,...t)}parseActions(e){let t=[];for(let n of e.children)n.tagName===`action`&&t.push({id:n.getAttribute(`id`)||``,actionType:n.getAttribute(`actionType`)||``,triggerType:n.getAttribute(`triggerType`)||``,triggerCode:n.getAttribute(`triggerCode`)||``,source:n.getAttribute(`source`)||``,sourceId:n.getAttribute(`sourceId`)||``,target:n.getAttribute(`target`)||``,targetId:n.getAttribute(`targetId`)||``,widgetId:n.getAttribute(`widgetId`)||``,layoutCode:n.getAttribute(`layoutCode`)||``,commandCode:n.getAttribute(`commandCode`)||``});return t}parseXlf(e){let t=new DOMParser().parseFromString(e,`text/xml`).querySelector(`layout`);if(!t)throw Error(`Invalid XLF: no <layout> element`);let n=t.getAttribute(`duration`),r={schemaVersion:parseInt(t.getAttribute(`schemaVersion`)||`1`),width:parseInt(t.getAttribute(`width`)||`1920`),height:parseInt(t.getAttribute(`height`)||`1080`),duration:n?parseInt(n):0,bgcolor:t.getAttribute(`backgroundColor`)||t.getAttribute(`bgcolor`)||`#000000`,background:t.getAttribute(`background`)||null,enableStat:t.getAttribute(`enableStat`)!==`0`,actions:this.parseActions(t),regions:[]};r.schemaVersion>1&&this.log.debug(`XLF schema version: ${r.schemaVersion}`),n?this.log.info(`Layout duration from XLF: ${r.duration}s`):this.log.info(`Layout duration NOT in XLF, will calculate from widgets`);let i=t.querySelectorAll(`:scope > region, :scope > drawer`);for(let e of i){let t=e.tagName===`drawer`,n=e.getAttribute(`type`)||null,i={id:e.getAttribute(`id`),width:parseInt(e.getAttribute(`width`)||`0`),height:parseInt(e.getAttribute(`height`)||`0`),top:parseInt(e.getAttribute(`top`)||`0`),left:parseInt(e.getAttribute(`left`)||`0`),zindex:parseInt(e.getAttribute(`zindex`)||(t?`2000`:`0`)),enableStat:e.getAttribute(`enableStat`)!==`0`,actions:this.parseActions(e),exitTransition:null,transitionType:null,transitionDuration:null,transitionDirection:null,loop:!0,isDrawer:t,isCanvas:n===`canvas`,widgets:[]},a=Array.from(e.children).find(e=>e.tagName===`options`);if(a){let e=a.querySelector(`exitTransType`);if(e&&e.textContent){let t=a.querySelector(`exitTransDuration`),n=a.querySelector(`exitTransDirection`);i.exitTransition={type:e.textContent,duration:parseInt(t&&t.textContent||`1000`),direction:n&&n.textContent||`N`}}let t=a.querySelector(`loop`);t&&(i.loop=t.textContent!==`0`);let n=a.querySelector(`transitionType`);if(n&&n.textContent){i.transitionType=n.textContent;let e=a.querySelector(`transitionDuration`),t=a.querySelector(`transitionDirection`);i.transitionDuration=parseInt(e&&e.textContent||`1000`),i.transitionDirection=t&&t.textContent||`N`}}for(let t of e.children){if(t.tagName!==`media`)continue;let e=this.parseWidget(t);i.widgets.push(e)}!i.isCanvas&&i.widgets.some(e=>e.type===`global`)&&(i.isCanvas=!0),r.regions.push(i),t&&this.log.info(`Parsed drawer: id=${i.id} with ${i.widgets.length} widgets`),i.isCanvas&&this.log.info(`Parsed canvas region: id=${i.id} with ${i.widgets.length} widgets (all render simultaneously)`)}if(r.duration===0){let{duration:t,isDynamic:n}=l(e);r.duration=t,r.isDynamic=n,this.log.info(`Calculated layout duration: ${r.duration}s (not specified in XLF)${n?` [dynamic — has useDuration=0 video]`:``}`)}return r}parseWidget(e){let t=e.getAttribute(`type`),n=parseInt(e.getAttribute(`duration`)||`10`),r=parseInt(e.getAttribute(`useDuration`)||`1`),i=e.getAttribute(`id`),a=e.getAttribute(`fileId`),o={},s=e.querySelector(`options`);if(s)for(let e of s.children)o[e.tagName]=e.textContent;let c=e.querySelector(`raw`),l=c?c.textContent:``,u={in:null,out:null};o.transIn&&(u.in={type:o.transIn,duration:parseInt(o.transInDuration||`1000`),direction:o.transInDirection||`N`}),o.transOut&&(u.out={type:o.transOut,duration:parseInt(o.transOutDuration||`1000`),direction:o.transOutDirection||`N`});let d=this.parseActions(e),f=[];for(let t of e.children)if(t.tagName.toLowerCase()===`audio`){let e=t.querySelector(`uri`);e?f.push({mediaId:e.getAttribute(`mediaId`)||null,uri:e.textContent||``,volume:parseInt(e.getAttribute(`volume`)||`100`),loop:e.getAttribute(`loop`)===`1`}):f.push({mediaId:t.getAttribute(`mediaId`)||null,uri:t.getAttribute(`uri`)||``,volume:parseInt(t.getAttribute(`volume`)||`100`),loop:t.getAttribute(`loop`)===`1`})}let p=[],m=Array.from(e.children).find(e=>e.tagName===`commands`);if(m)for(let e of m.children)e.tagName===`command`&&p.push({commandCode:e.getAttribute(`commandCode`)||``,commandString:e.getAttribute(`commandString`)||``});let h=e.getAttribute(`parentWidgetId`)||null,g=parseInt(e.getAttribute(`displayOrder`)||`0`),_=e.getAttribute(`cyclePlayback`)===`1`,v=parseInt(e.getAttribute(`playCount`)||`0`),y=e.getAttribute(`isRandom`)===`1`,b=e.getAttribute(`fromDt`)||e.getAttribute(`fromdt`)||null,x=e.getAttribute(`toDt`)||e.getAttribute(`todt`)||null;return{type:t,duration:n,useDuration:r,id:i,fileId:a,render:e.getAttribute(`render`)||null,fromDt:b,toDt:x,enableStat:e.getAttribute(`enableStat`)!==`0`,webhookUrl:o.webhookUrl||null,options:o,raw:l,transitions:u,actions:d,audioNodes:f,commands:p,parentWidgetId:h,displayOrder:g,cyclePlayback:_,playCount:v,isRandom:y}}trackBlobUrl(e){let t=this._preloadingLayoutId||this.currentLayoutId||0;t||this.log.warn(`trackBlobUrl called without currentLayoutId, tracking under key 0`),this.layoutBlobUrls.has(t)||this.layoutBlobUrls.set(t,new Set),this.layoutBlobUrls.get(t).add(e)}revokeBlobUrlsForLayout(e){let t=this.layoutBlobUrls.get(e);t&&(t.forEach(e=>{URL.revokeObjectURL(e)}),this.layoutBlobUrls.delete(e),this.log.info(`Revoked ${t.size} blob URLs for layout ${e}`))}updateLayoutDuration(){if(!this.currentLayout)return;let e=0;for(let t of this.currentLayout.regions){if(t.isDrawer)continue;let n=0;for(let e of t.widgets)e.duration>0&&(n+=e.duration);e=Math.max(e,n)}if(e>0&&e!==this.currentLayout.duration){let t=this.currentLayout.duration;this.currentLayout.duration=e,this.currentLayout._durationFromMetadata=!0,this.log.info(`Layout duration updated: ${t}s → ${e}s (based on video metadata)`);let n=!this._hasUnprobedVideos();if(this.emit(`layoutDurationUpdated`,this.currentLayoutId,e,n),this._deferredTimerLayoutId===this.currentLayoutId&&!this.layoutTimer)if(this._hasUnprobedVideos())this.log.info(`Layout duration updated to ${e}s but still has unprobed videos — keeping timer deferred`);else{this._deferredTimerFallback&&=(clearTimeout(this._deferredTimerFallback),null);let t=Date.now()-(this._layoutTimerStartedAt||Date.now()),n=Math.max(1e3,e*1e3-t);this._deferredTimerLayoutId=null,this._layoutTimerDurationMs=n,this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit(`layoutEnd`,this.currentLayoutId))},n),this.log.info(`All video durations resolved — deferred timer started: ${(n/1e3).toFixed(1)}s remaining (waited ${(t/1e3).toFixed(1)}s for metadata)`)}else if(this.layoutTimer){clearTimeout(this.layoutTimer);let e=Date.now()-(this._layoutTimerStartedAt||Date.now()),t=Math.max(1e3,this.currentLayout.duration*1e3-e);this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit(`layoutEnd`,this.currentLayoutId))},t),this.log.info(`Layout timer adjusted to ${(t/1e3).toFixed(1)}s remaining (elapsed ${(e/1e3).toFixed(1)}s of ${this.currentLayout.duration}s)`)}else this.log.info(`Layout duration updated to ${e}s (timer not yet started, will use new value)`);this._scheduleNextLayoutPreload(this.currentLayout)}}attachActionListeners(e){let t=[],n=0;for(let r of e.actions||[])r.triggerType===`touch`?(this.attachTouchAction(this.container,r,null,null),n++):r.triggerType?.startsWith(`keyboard:`)&&t.push(r);for(let r of e.regions){let e=this.regions.get(r.id);if(e){for(let i of r.actions||[])i.triggerType===`touch`?(this.attachTouchAction(e.element,i,r.id,null),n++):i.triggerType.startsWith(`keyboard:`)&&t.push(i);for(let i of r.widgets){if(!i.actions||i.actions.length===0)continue;let a=e.widgetElements.get(i.id);if(a)for(let e of i.actions)e.triggerType===`touch`?(this.attachTouchAction(a,e,r.id,i.id),n++):e.triggerType.startsWith(`keyboard:`)&&t.push(e)}}}this.setupKeyboardListener(t),(n>0||t.length>0)&&this.log.info(`Actions attached: ${n} touch, ${t.length} keyboard`)}attachTouchAction(e,t,n,r){e.style.cursor=`pointer`;let i=e=>{e.stopPropagation();let i=r?`widget ${r}`:`region ${n}`;this.log.info(`Touch action fired on ${i}: ${t.actionType}`),this.emit(`action-trigger`,{actionType:t.actionType,triggerType:`touch`,triggerCode:t.triggerCode,layoutCode:t.layoutCode,targetId:t.targetId,commandCode:t.commandCode,source:{regionId:n,widgetId:r}})};e.addEventListener(`click`,i),e._actionHandlers||=[],e._actionHandlers.push(i)}setupKeyboardListener(e){this.removeKeyboardListener(),this._keyboardActions=e,e.length!==0&&(this._keydownHandler=e=>{let t=e.key;for(let e of this._keyboardActions)if(t===e.triggerType.substring(9)){this.log.info(`Keyboard action (key: ${t}): ${e.actionType}`),this.emit(`action-trigger`,{actionType:e.actionType,triggerType:e.triggerType,triggerCode:e.triggerCode,layoutCode:e.layoutCode,targetId:e.targetId,commandCode:e.commandCode,source:{key:t}});break}},document.addEventListener(`keydown`,this._keydownHandler))}removeKeyboardListener(){this._keydownHandler&&=(document.removeEventListener(`keydown`,this._keydownHandler),null),this._keyboardActions=[]}removeActionListeners(){for(let[,e]of this.regions){this._cleanElementActionHandlers(e.element);for(let[,t]of e.widgetElements)this._cleanElementActionHandlers(t)}this.removeKeyboardListener()}_cleanElementActionHandlers(e){if(e._actionHandlers){for(let t of e._actionHandlers)e.removeEventListener(`click`,t);delete e._actionHandlers,e.style.cursor=``}}_findRegionByWidgetId(e){for(let[t,n]of this.regions){let r=n.widgets.findIndex(t=>t.id===e);if(r!==-1)return{regionId:t,region:n,widget:n.widgets[r],widgetIndex:r,regionMap:this.regions}}for(let t of this.activeOverlays.values())if(t.regions)for(let[n,r]of t.regions){let i=r.widgets.findIndex(t=>t.id===e);if(i!==-1)return{regionId:n,region:r,widget:r.widgets[i],widgetIndex:i,regionMap:t.regions}}return null}_advanceRegion(e,t){let n=t.get(e);if(!n)return;n.currentIndex=(n.currentIndex+1)%n.widgets.length;let r=t===this.regions;this._startRegionCycle(n,e,this._renderWidgetBound,this._stopWidgetBound,r?()=>this.checkLayoutComplete():void 0)}_handleInteractiveTrigger({targetId:e,triggerCode:t}){this.log.info(`XIC interactiveTrigger: target=${e} code=${t}`),this._findRegionByWidgetId(e)?this.navigateToWidget(e):this.log.warn(`XIC interactiveTrigger: widget ${e} not found`)}_handleWidgetExpire({widgetId:e}){let t=this._findRegionByWidgetId(e);if(!t){this.log.warn(`XIC widgetExpire: widget ${e} not found`);return}let{regionId:n,region:r,widgetIndex:i,regionMap:a}=t;this.log.info(`XIC widgetExpire: widget=${e} region=${n}`),r.timer&&=(clearTimeout(r.timer),null),this.stopWidget(n,i),this._advanceRegion(n,a)}_handleWidgetExtendDuration({widgetId:e,duration:t}){let n=this._findRegionByWidgetId(e);if(!n){this.log.warn(`XIC widgetExtendDuration: widget ${e} not found`);return}let{regionId:r,region:i}=n;this.log.info(`XIC widgetExtendDuration: widget=${e} +${t}s`),i.timer&&=(clearTimeout(i.timer),null),i.timer=setTimeout(()=>{this.stopWidget(r,i.currentIndex),this._advanceRegion(r,n.regionMap)},t*1e3)}_handleWidgetSetDuration({widgetId:e,duration:t}){let n=this._findRegionByWidgetId(e);if(!n){this.log.warn(`XIC widgetSetDuration: widget ${e} not found`);return}let{regionId:r,region:i}=n;this.log.info(`XIC widgetSetDuration: widget=${e} ${t}s`),i.timer&&=(clearTimeout(i.timer),null),i.timer=setTimeout(()=>{this.stopWidget(r,i.currentIndex),this._advanceRegion(r,n.regionMap)},t*1e3)}navigateToWidget(e){for(let[t,n]of this.regions){let r=n.widgets.findIndex(t=>t.id===e);if(r!==-1){if(this.log.info(`Navigating to widget ${e} in region ${t} (index ${r})`),n.isDrawer&&n.element.style.display===`none`&&(n.element.style.display=``,this.log.info(`Drawer region ${t} revealed`)),n.timer&&=(clearTimeout(n.timer),null),this.stopWidget(t,n.currentIndex),n.currentIndex=r,this.renderWidget(t,r),n.widgets.length>1){let e=n.widgets[r].duration*1e3;n.timer=setTimeout(()=>{this.stopWidget(t,r);let e=(r+1)%n.widgets.length;n.currentIndex=e,n.isDrawer&&e===0?(n.element.style.display=`none`,this.log.info(`Drawer region ${t} hidden (cycle complete)`)):n.isDrawer?this.navigateToWidget(n.widgets[e].id):this.startRegion(t)},e)}else if(n.isDrawer){let e=n.widgets[r].duration*1e3;n.timer=setTimeout(()=>{this.stopWidget(t,r),n.element.style.display=`none`,this.log.info(`Drawer region ${t} hidden (single widget done)`)},e)}return}}this.log.warn(`Target widget ${e} not found in any region`)}nextWidget(e){let t=e?this.regions.get(e):this.regions.values().next().value;if(!t||t.widgets.length<=1)return;let n=(t.currentIndex+1)%t.widgets.length,r=t.widgets[n];this.log.info(`nextWidget → index ${n} (widget ${r.id})`),this.navigateToWidget(r.id)}previousWidget(e){let t=e?this.regions.get(e):this.regions.values().next().value;if(!t||t.widgets.length<=1)return;let n=(t.currentIndex-1+t.widgets.length)%t.widgets.length,r=t.widgets[n];this.log.info(`previousWidget → index ${n} (widget ${r.id})`),this.navigateToWidget(r.id)}_mediaFileUrl(e){return`${window.location.origin}${s}/media/file/${e}`}_positionWidgetElement(e){Object.assign(e.style,{position:`absolute`,top:`0`,left:`0`,width:`100%`,height:`100%`,visibility:`hidden`,opacity:`0`})}_applyBackgroundImage(e,t){Object.assign(e.style,{backgroundImage:`url(${t})`,backgroundSize:`cover`,backgroundPosition:`center`,backgroundRepeat:`no-repeat`})}_clearRegionTimers(e){for(let[,t]of e)t.timer&&=(clearTimeout(t.timer),null)}async renderLayout(e,t){try{if(this.log.info(`Rendering layout ${t}`),this.currentLayoutId===t){this.log.info(`Replaying layout ${t} - reusing elements (no recreation!)`),this._clearRegionTimers(this.regions),this._stopAllRegionWidgets(this.regions,this._stopWidgetBound);for(let[,e]of this.regions)e.currentIndex=0,e.complete=!1;this.layoutTimer&&=(clearTimeout(this.layoutTimer),null),this.layoutEndEmitted=!1,this._deferredTimerLayoutId=null,this._deferredTimerFallback&&=(clearTimeout(this._deferredTimerFallback),null),this.emit(`layoutStart`,t,this.currentLayout);for(let[e,t]of this.regions)t.isDrawer||this.startRegion(e);this.startLayoutTimerWhenReady(t,this.currentLayout),this.log.info(`Layout ${t} restarted (reused elements)`),this._scheduleNextLayoutPreload(this.currentLayout);return}if(this.layoutPool.has(t)){this.log.info(`Layout ${t} found in preload pool - instant swap!`),await this._swapToPreloadedLayout(t);return}this.log.info(`Switching to new layout ${t}`),this.stopCurrentLayout();let n=this.parseXlf(e);if(this.currentLayout=n,this.currentLayoutId=t,this.calculateScale(n),this.container.style.backgroundColor=n.bgcolor,this.container.style.backgroundImage=``,n.background){let e=this.options.fileIdToSaveAs?.get(String(n.background))||n.background;this._applyBackgroundImage(this.container,this._mediaFileUrl(e)),this.log.info(`Background image set: ${n.background} → ${e}`)}for(let e of n.regions)await this.createRegion(e);this.log.info(`Pre-creating widget elements for instant transitions...`);for(let[e,t]of this.regions)for(let n=0;n<t.widgets.length;n++){let r=t.widgets[n];r.layoutId=this.currentLayoutId,r.regionId=e;try{let e=await this.createWidgetElement(r,t);this._positionWidgetElement(e),t.element.appendChild(e),t.widgetElements.set(r.id,e)}catch(e){this.log.error(`Failed to pre-create widget ${r.id}:`,e)}}if(this.log.info(`All widget elements pre-created`),this.attachActionListeners(n),this.emit(`layoutStart`,t,n),n.duration>0){let e=!this._hasUnprobedVideos();this.emit(`layoutDurationUpdated`,t,n.duration,e)}for(let[e,t]of this.regions)t.isDrawer||this.startRegion(e);this.startLayoutTimerWhenReady(t,n),this._scheduleNextLayoutPreload(n),this.log.info(`Layout ${t} started`)}catch(e){throw this.log.error(`Error rendering layout:`,e),this.emit(`error`,{type:`layoutError`,error:e,layoutId:t}),e}}_createRegionEntry(e,t,n,r={}){let{className:i=`renderer-lite-region`,...a}=r,o=document.createElement(`div`);o.id=t,o.className=i,o.style.position=`absolute`,o.style.zIndex=String(e.zindex),o.style.overflow=`hidden`,this.applyRegionScale(o,e),n.appendChild(o);let s=this.scaleFactor;return{element:o,config:e,widgets:e.widgets,currentIndex:0,timer:null,width:e.width*s,height:e.height*s,complete:!1,widgetElements:new Map,...a}}async createRegion(e){let t=this._createRegionEntry(e,`region_${e.id}`,this.container,{isDrawer:e.isDrawer||!1,isCanvas:e.isCanvas||!1});e.isDrawer&&(t.element.style.display=`none`);let n=e.widgets.filter(e=>this._isWidgetActive(e));n.some(e=>e.cyclePlayback)&&(n=this._applyCyclePlayback(n)),t.widgets=n,this.regions.set(e.id,t)}startRegion(e){let t=this.regions.get(e);this._startRegionCycle(t,e,this._renderWidgetBound,this._stopWidgetBound,()=>{this.log.info(`Region ${e} completed one full cycle`),this.checkLayoutComplete()})}async createWidgetElement(e,t){if(e.render===`html`&&e.type!==`pdf`)return await this.renderGenericWidget(e,t);switch(e.type){case`image`:return await this.renderImage(e,t);case`video`:return await this.renderVideo(e,t);case`audio`:return await this.renderAudio(e,t);case`text`:case`ticker`:return await this.renderTextWidget(e,t);case`pdf`:return await this.renderPdf(e,t);case`webpage`:return await this.renderWebpage(e,t);case`localvideo`:return await this.renderVideo(e,t);case`videoin`:return await this.renderVideoIn(e,t);case`powerpoint`:case`flash`:return this.log.warn(`Widget type '${e.type}' is not supported on web players (widget ${e.id})`),this._renderUnsupportedPlaceholder(e,t);default:return await this.renderGenericWidget(e,t)}}findMediaElement(e,t){return e.tagName===t?e:e.querySelector(t.toLowerCase())}updateMediaElement(e,t){let n=this.findMediaElement(e,`VIDEO`)||this.findMediaElement(e,`AUDIO`);if(n){if(n.tagName===`VIDEO`&&n._mediaConstraints&&!n._mediaStream){navigator.mediaDevices.getUserMedia(n._mediaConstraints).then(e=>{n.srcObject=e,n._mediaStream=e,this.log.info(`Webcam stream re-acquired for widget ${t.id}`)}).catch(e=>{this.log.warn(`Failed to re-acquire webcam stream:`,e.message)});return}this._restartMediaElement(n),this.log.info(`${n.tagName===`VIDEO`?`Video`:`Audio`} restarted: ${t.fileId||t.id}`)}}_restartMediaElement(e){e.currentTime=0;let t=()=>{e.removeEventListener(`seeked`,t),e.play().catch(()=>{})};e.addEventListener(`seeked`,t),e.play().catch(()=>{})}waitForWidgetReady(e,t){let n=1e4,r=this.findMediaElement(e,`VIDEO`);if(r)return!r.paused&&r.readyState>=3?Promise.resolve():new Promise(e=>{let i=setTimeout(()=>{this.log.warn(`Video ready timeout (${n}ms) for widget ${t.id}`),e()},n),a=()=>{r.removeEventListener(`playing`,a),clearTimeout(i),this.log.info(`Video widget ${t.id} ready (playing)`),e()};r.addEventListener(`playing`,a)});let i=this.findMediaElement(e,`AUDIO`);if(i)return!i.paused&&i.readyState>=3?Promise.resolve():new Promise(e=>{let r=setTimeout(()=>{this.log.warn(`Audio ready timeout (${n}ms) for widget ${t.id}`),e()},n),a=()=>{i.removeEventListener(`playing`,a),clearTimeout(r),this.log.info(`Audio widget ${t.id} ready (playing)`),e()};i.addEventListener(`playing`,a)});let a=this.findMediaElement(e,`IMG`);return a?a.complete&&a.naturalWidth>0?Promise.resolve():new Promise(e=>{let r=()=>{a.removeEventListener(`load`,r),clearTimeout(i),e()},i=setTimeout(()=>{a.removeEventListener(`load`,r),this.log.warn(`Image ready timeout for widget ${t.id}`),e()},n);a.addEventListener(`load`,r)}):Promise.resolve()}async startLayoutTimerWhenReady(e,t){if(!t||t.duration<=0)return;let n=[];for(let[e,t]of this.regions){if(t.widgets.length===0)continue;let e=t.widgets[t.currentIndex||0],r=t.widgetElements.get(e.id);r&&n.push(this.waitForWidgetReady(r,e))}if(n.length>0&&(this.log.info(`Waiting for ${n.length} widget(s) to be ready before starting layout timer...`),await Promise.all(n),this.log.info(`All widgets ready — starting layout timer`)),this.currentLayoutId!==e){this.log.warn(`Layout changed while waiting for widgets — skipping timer for ${e}`);return}if(t.isDynamic&&!t._durationFromMetadata&&this._hasUnprobedVideos()){this._deferredTimerLayoutId=e,this._layoutTimerStartedAt=Date.now(),this.log.info(`Layout ${e} has unprobed videos — deferring timer until metadata loads`),this._deferredTimerFallback=setTimeout(()=>{this._deferredTimerFallback=null,this._deferredTimerLayoutId===e&&!this.layoutTimer&&(this.log.warn(`Layout ${e}: metadata timeout after 30s — starting timer with ${t.duration}s estimate`),this._deferredTimerLayoutId=null,this._startLayoutTimer(e,t))},3e4);return}this._startLayoutTimer(e,t)}_hasUnprobedVideos(){for(let[,e]of this.regions)for(let t of e.widgets)if(t.type===`video`&&t.useDuration===0&&t._probed)return!1;for(let[,e]of this.regions)for(let t of e.widgets)if(t.type===`video`&&t.useDuration===0)return!0;return!1}_startLayoutTimer(e,t){this._deferredTimerLayoutId=null,this._deferredTimerFallback&&=(clearTimeout(this._deferredTimerFallback),null);let n=t.duration*1e3;this.log.info(`Layout ${e} will end after ${t.duration}s`),this._layoutTimerStartedAt=Date.now(),this._layoutTimerDurationMs=n,this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${e} duration expired (${t.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit(`layoutEnd`,this.currentLayoutId))},n)}async _showWidget(e,t){let n=e.widgets[t];if(!n)return null;let r=e.widgetElements.get(n.id);if(r||(this.log.warn(`Widget ${n.id} not pre-created, creating now`),r=await this.createWidgetElement(n,e),r.style.position=`absolute`,r.style.top=`0`,r.style.left=`0`,r.style.width=`100%`,r.style.height=`100%`,e.widgetElements.set(n.id,r),e.element.appendChild(r)),!e.isCanvas)for(let[t,r]of e.widgetElements)t!==n.id&&(r.getAnimations?.().forEach(e=>e.cancel()),r.style.visibility=`hidden`,r.style.opacity=`0`);return this.updateMediaElement(r,n),r.getAnimations?.().forEach(e=>e.cancel()),r.style.visibility=`visible`,n.transitions.in?k.apply(r,n.transitions.in,!0,e.width,e.height):r.style.opacity=`1`,r._pdfResume&&r._pdfResume(),this._startAudioOverlays(n),n}_startAudioOverlays(e){if(!e.audioNodes||e.audioNodes.length===0)return;this._stopAudioOverlays(e.id);let t=[];for(let n of e.audioNodes){if(!n.uri)continue;let r=document.createElement(`audio`);r.autoplay=!0,r.loop=n.loop,r.volume=Math.max(0,Math.min(1,n.volume/100)),r.src=n.uri?this._mediaFileUrl(n.uri):``,r.style.display=`none`,this.container.appendChild(r);let i=r.play();i&&i.catch&&i.catch(()=>{}),t.push(r),this.log.info(`Audio overlay started for widget ${e.id}: ${n.uri} (loop=${n.loop}, vol=${n.volume})`)}t.length>0&&this.audioOverlays.set(e.id,t)}_stopAudioOverlays(e){let t=this.audioOverlays.get(e);if(t){for(let e of t)e.pause(),e.removeAttribute(`src`),e.load(),e.parentNode&&e.parentNode.removeChild(e);this.audioOverlays.delete(e),this.log.info(`Audio overlays stopped for widget ${e}`)}}_hideWidget(e,t){let n=e.widgets[t];if(!n)return{widget:null,animPromise:null};let r=e.widgetElements.get(n.id);if(!r)return{widget:null,animPromise:null};let i=null;if(n.transitions.out){let t=k.apply(r,n.transitions.out,!1,e.width,e.height);t&&(i=new Promise(e=>{t.onfinish=e}))}let a=r.querySelector(`video`);if(a&&(a.pause(),a._mediaStream&&(a._mediaStream.getTracks().forEach(e=>e.stop()),a._mediaStream=null,a.srcObject=null),a._hlsInstance&&=(a._hlsInstance.destroy(),null),a.removeAttribute(`src`),a.load(),a._eventCleanup)){for(let[e,t]of a._eventCleanup)a.removeEventListener(e,t);a._eventCleanup=null}let o=r.querySelector(`audio`);if(o&&n.options.loop!==`1`&&o.pause(),o?._eventCleanup){for(let[e,t]of o._eventCleanup)o.removeEventListener(e,t);o._eventCleanup=null}this._stopAudioOverlays(n.id),r._pdfCleanup&&r._pdfCleanup();let s=r.querySelectorAll(`iframe`);for(let e of s){try{let t=e.contentDocument||e.contentWindow?.document;t&&(t.querySelectorAll(`video`).forEach(e=>{e.pause(),e.removeAttribute(`src`),e.load()}),t.querySelectorAll(`audio`).forEach(e=>{e.pause(),e.removeAttribute(`src`),e.load()}))}catch{}e.src=`about:blank`}return{widget:n,animPromise:i}}_isWidgetActive(e){let t=new Date;return!(e.fromDt&&t<new Date(e.fromDt)||e.toDt&&t>new Date(e.toDt))}_parseDurationComments(e,t){let n=t.duration,r=e.match(/<!--\s*DURATION=(\d+)\s*-->/);if(r){let e=parseInt(r[1],10);if(e>0){this.log.info(`Widget ${t.id}: DURATION comment overrides duration ${t.duration}→${e}s`),t.duration=e,t.duration!==n&&this.updateLayoutDuration();return}}let i=e.match(/<!--\s*NUMITEMS=(\d+)\s*-->/);if(i){let e=parseInt(i[1],10);if(e>0&&t.duration>0){let n=e*t.duration;this.log.info(`Widget ${t.id}: NUMITEMS=${e} × ${t.duration}s = ${n}s`),t.duration=n}}t.duration!==n&&this.updateLayoutDuration()}_applyCyclePlayback(e){this._subPlaylistCycleIndex||=new Map;let t=new Map,n=[];for(let r of e)r.parentWidgetId&&r.cyclePlayback?(t.has(r.parentWidgetId)||t.set(r.parentWidgetId,[]),t.get(r.parentWidgetId).push(r)):n.push({type:`direct`,widget:r});for(let[e,r]of t){r.sort((e,t)=>e.displayOrder-t.displayOrder);let t;if(r.some(e=>e.isRandom))t=r[Math.floor(Math.random()*r.length)];else{let n=this._subPlaylistCycleIndex.get(e)||{widgetIdx:0,playsDone:0};t=r[n.widgetIdx%r.length];let i=t.playCount||1;n.playsDone++,n.playsDone>=i&&(n.widgetIdx++,n.playsDone=0),this._subPlaylistCycleIndex.set(e,n)}this.log.info(`Sub-playlist cycle: group ${e} selected widget ${t.id} (${r.length} in group)`),n.push({type:`direct`,widget:t})}return n.map(e=>e.widget)}_startRegionCycle(e,t,n,r,i){if(!e||e.widgets.length===0)return;if(e.isCanvas){this._startCanvasRegion(e,t,n,i);return}if(e.widgets.length===1){n(t,0);return}let a=()=>{let o=e.currentIndex,s=e.widgets[o];n(t,o);let c=s.duration*1e3;this.log.info(`Region ${t} widget ${s.id} (${s.type}) playing for ${s.duration}s (useDuration=${s.useDuration}, index ${o}/${e.widgets.length})`),e.timer=setTimeout(()=>{this._handleWidgetCycleEnd(s,e,t,o,n,r,i,a)},c)};a()}_startCanvasRegion(e,t,n,r){for(let r=0;r<e.widgets.length;r++)n(t,r);let i=Math.max(...e.widgets.map(e=>e.duration))*1e3;i>0?e.timer=setTimeout(()=>{e.complete||(e.complete=!0,r?.())},i):(e.complete=!0,r?.())}_handleWidgetCycleEnd(e,t,n,r,i,a,o,s){e.webhookUrl&&this.emit(`widgetAction`,{type:`durationEnd`,widgetId:e.id,layoutId:this.currentLayoutId,regionId:n,url:e.webhookUrl}),a(n,r);let c=(t.currentIndex+1)%t.widgets.length;if(c===0&&!t.complete&&(t.complete=!0,o?.()),c===0&&t.config?.loop===!1&&t.widgets.length===1){i(n,0);return}this.layoutEndEmitted||(t.currentIndex=c,s())}async renderWidget(e,t){let n=this.regions.get(e);if(n)try{let r=await this._showWidget(n,t);if(r&&(this.log.info(`Showing widget ${r.type} (${r.id}) in region ${e}`),this._startedWidgets.add(`${e}:${t}`),this.emit(`widgetStart`,{widgetId:r.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(r.fileId||r.id)||null,type:r.type,duration:r.duration,enableStat:r.enableStat}),r.commands&&r.commands.length>0))for(let t of r.commands)this.emit(`widgetCommand`,{commandCode:t.commandCode,commandString:t.commandString,widgetId:r.id,regionId:e,layoutId:this.currentLayoutId})}catch(r){this.log.error(`Error rendering widget:`,r),this.emit(`error`,{type:`widgetError`,error:r,widgetId:n.widgets[t]?.id,regionId:e})}}async stopWidget(e,t){let n=`${e}:${t}`;if(!this._startedWidgets.delete(n))return;let r=this.regions.get(e);if(!r)return;let{widget:i,animPromise:a}=this._hideWidget(r,t);i&&this.emit(`widgetEnd`,{widgetId:i.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(i.fileId||i.id)||null,type:i.type,enableStat:i.enableStat}),a&&await a}_stopAllRegionWidgets(e,t){for(let[n,r]of e)if(r.isCanvas)for(let e=0;e<r.widgets.length;e++)t(n,e);else r.widgets.length>0&&t(n,r.currentIndex)}async renderImage(e,t){let n=document.createElement(`img`);n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`;let r=e.options.scaleType,i={stretch:`fill`,center:`contain`,fit:`cover`};n.style.objectFit=i[r]||`contain`;let a={left:`left`,center:`center`,right:`right`},o={top:`top`,middle:`center`,bottom:`bottom`},s=a[e.options.alignId]||`center`,c=o[e.options.valignId]||`center`;return n.style.objectPosition=`${s} ${c}`,n.style.opacity=`0`,n.src=e.options.uri?this._mediaFileUrl(e.options.uri):``,n}async renderVideo(e,t){let n=document.createElement(`video`);n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`;let r=e.options.scaleType,i={stretch:`fill`,center:`none`,fit:`contain`};n.style.objectFit=i[r]||`contain`,n.style.opacity=`1`,n.autoplay=!0,n.preload=`auto`,n.muted=e.options.mute===`1`,n.loop=!1,n.controls=!1,n.playsInline=!0;let a=e.options.uri||``,o=e.fileId||e.id,s=()=>{e.options.loop===`1`?(n.currentTime=0,this.log.info(`Video ${a} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Video ${a} ended - paused on last frame`)};n.addEventListener(`ended`,s);let c=a?this._mediaFileUrl(a):``;if(c.includes(`.m3u8`))if(n.canPlayType(`application/vnd.apple.mpegurl`))this.log.info(`HLS stream (native): ${o}`),n.src=c;else try{let{default:e}=await O(async()=>{let{default:e}=await import(`hls.js`);return{default:e}},[],import.meta.url);if(e.isSupported()){let t=new e({enableWorker:!0,lowLatencyMode:!0});t.loadSource(c),t.attachMedia(n),n._hlsInstance=t,t.on(e.Events.ERROR,(e,r)=>{r.fatal&&(this.log.error(`HLS fatal error: ${r.type}`,r.details),t.destroy(),n._hlsInstance=null)}),this.log.info(`HLS stream (hls.js): ${o}`)}else this.log.warn(`HLS not supported on this browser for ${o}`),n.src=c}catch(e){this.log.warn(`hls.js not available, falling back to native: ${e.message}`),n.src=c}else n.src=c;let l=this._preloadingLayoutId||this.currentLayoutId,u=()=>{let t=n.duration;this.log.info(`Video ${a} duration detected: ${t}s`),(e.duration===0||e.useDuration===0)&&(e.duration=t,e._probed=!0,this.log.info(`Updated widget ${e.id} duration to ${t}s (useDuration=0)`),this.currentLayoutId===l?this.updateLayoutDuration():this.log.info(`Video ${a} duration set but layout timer not updated (preloaded for layout ${l}, current is ${this.currentLayoutId})`))};n.addEventListener(`loadedmetadata`,u);let d=()=>{this.log.info(`Video loaded and ready:`,a)};n.addEventListener(`loadeddata`,d);let f=()=>{let t=n.error,r=t?.code,i=t?.message||`Unknown error`;this.log.warn(`Video error: ${a}, code: ${r}, time: ${n.currentTime.toFixed(1)}s, message: ${i}`),e.useDuration===0&&e.duration===0&&(e.duration=60,this.log.info(`Set fallback duration 60s for errored widget ${e.id}`),this.currentLayoutId===l&&this.updateLayoutDuration()),this.emit(`videoError`,{storedAs:a,fileId:o,errorCode:r,errorMessage:i,currentTime:n.currentTime})};n.addEventListener(`error`,f);let p=()=>{this.log.info(`Video playing:`,a)};return n.addEventListener(`playing`,p),n._eventCleanup=[[`ended`,s],[`loadedmetadata`,u],[`loadeddata`,d],[`error`,f],[`playing`,p]],this.log.info(`Video element created:`,a,n.src),n}async renderVideoIn(e,t){let n=document.createElement(`video`);n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`,n.style.objectFit=e.options.showFullScreen===`1`?`cover`:`contain`,n.autoplay=!0,n.playsInline=!0,n.controls=!1,n.muted=e.options.mute!==`0`,e.options.mirror===`1`&&(n.style.transform=`scaleX(-1)`);let r={width:{ideal:t.width},height:{ideal:t.height}},i=e.options.sourceId||e.options.deviceId;i?r.deviceId={exact:i}:r.facingMode=e.options.facingMode||`environment`;let a={video:r,audio:e.options.captureAudio===`1`};n._mediaConstraints=a;try{let t=await navigator.mediaDevices.getUserMedia(a);n.srcObject=t,n._mediaStream=t,this.log.info(`Webcam stream acquired for widget ${e.id} (tracks: ${t.getTracks().length})`)}catch(n){return this.log.warn(`getUserMedia failed for widget ${e.id}: ${n.message}`),this._renderUnsupportedPlaceholder({...e,type:`Camera unavailable`},t)}return n}async renderAudio(e,t){let n=document.createElement(`div`);n.className=`renderer-lite-widget audio-widget`,n.style.width=`100%`,n.style.height=`100%`,n.style.display=`flex`,n.style.flexDirection=`column`,n.style.alignItems=`center`,n.style.justifyContent=`center`,n.style.background=`linear-gradient(135deg, #667eea 0%, #764ba2 100%)`,n.style.opacity=`0`;let r=document.createElement(`audio`);r.autoplay=!0,r.loop=e.options.loop===`1`,r.volume=parseFloat(e.options.volume||`100`)/100;let i=e.options.uri||``;e.fileId||e.id,r.src=i?this._mediaFileUrl(i):``;let a=()=>{e.options.loop===`1`?(r.currentTime=0,this.log.info(`Audio ${i} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Audio ${i} ended - playback complete`)};r.addEventListener(`ended`,a);let o=this._preloadingLayoutId||this.currentLayoutId,s=()=>{let t=Math.floor(r.duration);this.log.info(`Audio ${i} duration detected: ${t}s`),(e.duration===0||e.useDuration===0)&&(e.duration=t,this.log.info(`Updated widget ${e.id} duration to ${t}s (useDuration=0)`),this.currentLayoutId===o?this.updateLayoutDuration():this.log.info(`Audio ${i} duration set but layout timer not updated (preloaded for layout ${o}, current is ${this.currentLayoutId})`))};r.addEventListener(`loadedmetadata`,s);let c=()=>{let e=r.error;this.log.warn(`Audio error (non-fatal): ${i}, code: ${e?.code}, message: ${e?.message||`Unknown`}`)};r.addEventListener(`error`,c),r._eventCleanup=[[`ended`,a],[`loadedmetadata`,s],[`error`,c]];let l=document.createElement(`div`);l.innerHTML=`♪`,l.style.fontSize=`120px`,l.style.color=`white`,l.style.marginBottom=`20px`;let u=document.createElement(`div`);u.style.color=`white`,u.style.fontSize=`24px`,u.textContent=`Playing Audio`;let d=document.createElement(`div`);return d.style.color=`rgba(255,255,255,0.7)`,d.style.fontSize=`16px`,d.style.marginTop=`10px`,d.textContent=e.options.uri,n.appendChild(r),n.appendChild(l),n.appendChild(u),n.appendChild(d),n}async renderTextWidget(e,t){return await this._renderIframeWidget(e,t)}async renderPdf(e,t){let r=document.createElement(`div`);if(r.className=`renderer-lite-widget pdf-widget`,r.style.width=`100%`,r.style.height=`100%`,r.style.backgroundColor=`transparent`,r.style.opacity=`0`,r.style.position=`relative`,window.pdfjsLib===void 0)try{let e=await O(()=>import(`./pdf-ByOGXKA2.js`),[],import.meta.url);window.pdfjsLib=e;let t=window.location.pathname.replace(/\/[^/]*$/,`/`);window.pdfjsLib.GlobalWorkerOptions.workerSrc=`${window.location.origin}${t}pdf.worker.min.mjs`}catch(e){return this.log.error(`PDF.js not available:`,e),r.innerHTML=`<div style="color:white;padding:20px;text-align:center;">PDF viewer unavailable</div>`,r.style.opacity=`1`,r}let i=e.options.uri?this._mediaFileUrl(e.options.uri):``;try{let a=await window.pdfjsLib.getDocument(i).promise,o=a.numPages,s=e.duration||60,c=s*1e3/o;this.log.info(`[pdf] PDF loaded: ${o} pages, ${s}s duration, ${(c/1e3).toFixed(1)}s/page`);let l=await a.getPage(1),u=l.getViewport({scale:1}),d=Math.min(t.width/u.width,t.height/u.height);l.cleanup();let f=document.createElement(`canvas`);f.className=`pdf-page`,f.width=Math.floor(u.width*d),f.height=Math.floor(u.height*d),f.style.cssText=`display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);`;let p=f.getContext(`2d`);r.appendChild(f);let m=document.createElement(`div`);m.style.cssText=`position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,0.7);color:white;padding:8px 12px;border-radius:4px;font:14px system-ui;z-index:1;`,n()||(m.style.display=`none`),r.appendChild(m);let h=1,g=null,_=null,v=!1,y=async()=>{if(v)return;m.textContent=`Page ${h} / ${o}`;let e=await a.getPage(h),t=e.getViewport({scale:d});p.clearRect(0,0,f.width,f.height),_=e.render({canvasContext:p,viewport:t});try{await _.promise}catch(e){if(v)return;throw e}_=null,e.cleanup(),o>1&&!v&&(g=setTimeout(()=>{h=h>=o?1:h+1,y()},c))};await y();let b=null;r._pdfCleanup=()=>{if(v=!0,g&&clearTimeout(g),g=null,_){let e=_;_=null,e.cancel(),b=e.promise.catch(()=>{})}},r._pdfResume=async()=>{r._pdfCleanup(),b&&=(await b,null),v=!1,h=1,y()},r._pdfDestroy=()=>{r._pdfCleanup(),f.width=0,f.height=0,a.destroy()}}catch(e){this.log.error(`PDF render failed:`,e),r.innerHTML=`<div style="color:white;padding:20px;text-align:center;">Failed to load PDF</div>`}return r.style.opacity=`1`,r}async renderWebpage(e,t){if(parseInt(e.options.modeId||`1`)===0)return await this.renderGenericWidget(e,t);let n=document.createElement(`iframe`);return n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`,n.style.border=`none`,n.style.opacity=`0`,n.src=decodeURIComponent(e.options.uri||``),n}async renderGenericWidget(e,t){return await this._renderIframeWidget(e,t)}async _renderIframeWidget(e,t){let n=document.createElement(`iframe`);n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`,n.style.border=`none`,n.style.opacity=`0`;let r=e.raw;if(this.options.getWidgetHtml){let t=await this.options.getWidgetHtml(e);if(t&&typeof t==`object`&&t.url)return n.src=t.url,t.fallback&&this._parseDurationComments(t.fallback,e),n;r=t}if(r){this._parseDurationComments(r,e);let t=new Blob([r],{type:`text/html`}),i=URL.createObjectURL(t);n.src=i,this.trackBlobUrl(i)}else this.log.warn(`No HTML for widget ${e.id}`),n.srcdoc=`<div style="padding:20px;">Widget content unavailable</div>`;return n}_renderUnsupportedPlaceholder(e,t){let n=document.createElement(`div`);return n.className=`renderer-lite-widget`,n.style.width=`100%`,n.style.height=`100%`,n.style.display=`flex`,n.style.alignItems=`center`,n.style.justifyContent=`center`,n.style.backgroundColor=`#111`,n.style.color=`#666`,n.style.fontSize=`14px`,n.textContent=`Unsupported: ${e.type}`,n}_scheduleNextLayoutPreload(e){this.preloadTimer&&=(clearTimeout(this.preloadTimer),null),this._preloadRetryTimer&&=(clearTimeout(this._preloadRetryTimer),null);let t=e.duration||60,n=t*1e3*.75,r=t*1e3*.9;this.log.info(`Scheduling next layout preload in ${(n/1e3).toFixed(1)}s (75% of ${t}s)`),this.preloadTimer=setTimeout(()=>{this.preloadTimer=null,this.emit(`request-next-layout-preload`)},n),this._preloadRetryTimer=setTimeout(()=>{this._preloadRetryTimer=null,this.emit(`request-next-layout-preload`)},r)}hasPreloadedLayout(e){return this.layoutPool.has(e)}async preloadLayout(e,t){return this.layoutPool.has(t)?(this.log.info(`Layout ${t} already in preload pool, skipping`),!0):this.currentLayoutId===t?(this.log.info(`Layout ${t} is current, skipping preload`),!0):this._preloadingLayoutId===t&&this._preloadingPromise?(this.log.info(`Layout ${t} preload in-flight, waiting for it...`),this._preloadingPromise):(this._preloadingPromise=this._doPreloadLayout(e,t),this._preloadingPromise)}async _doPreloadLayout(e,t){try{this.log.info(`Preloading layout ${t} into pool...`);let n=this.parseXlf(e);this.calculateScale(n);let r=document.createElement(`div`);if(r.id=`preload_layout_${t}`,r.className=`renderer-lite-preload-wrapper`,r.style.position=`absolute`,r.style.top=`0`,r.style.left=`0`,r.style.width=`100%`,r.style.height=`100%`,r.style.visibility=`hidden`,r.style.zIndex=`-1`,r.style.backgroundColor=n.bgcolor,n.background){let e=this.options.fileIdToSaveAs?.get(String(n.background))||n.background;this._applyBackgroundImage(r,this._mediaFileUrl(e))}let i=this.currentLayoutId,a=new Map;for(let e of n.regions){let n=this._createRegionEntry(e,`preload_region_${t}_${e.id}`,r);a.set(e.id,n)}let o=new Set,s=this.layoutBlobUrls;this.layoutBlobUrls=new Map,this.layoutBlobUrls.set(t,o),this._preloadingLayoutId=t;for(let[e,n]of a)for(let r=0;r<n.widgets.length;r++){let i=n.widgets[r];i.layoutId=t,i.regionId=e;try{let e=await this.createWidgetElement(i,n);this._positionWidgetElement(e),n.element.appendChild(e),n.widgetElements.set(i.id,e)}catch(e){this.log.error(`Preload: Failed to create widget ${i.id}:`,e)}}return this.currentLayoutId=i,r.querySelectorAll(`video`).forEach(e=>e.pause()),(this.layoutBlobUrls.get(t)||new Set).forEach(e=>o.add(e)),this.layoutBlobUrls=s,this.container.appendChild(r),this.layoutPool.add(t,{container:r,layout:n,regions:a,blobUrls:o}),this.log.info(`Layout ${t} preloaded into pool (${a.size} regions)`),!0}catch(e){return this.log.error(`Preload failed for layout ${t}:`,e),!1}finally{this._preloadingLayoutId===t&&(this._preloadingLayoutId=null,this._preloadingPromise=null)}}async _swapToPreloadedLayout(e){let t=this.layoutPool.get(e);if(!t){this.log.error(`Cannot swap: layout ${e} not in pool`);return}this.removeActionListeners(),this._clearLayoutTimers();let n=this.currentLayoutId,r=this.layoutEndEmitted;if(this.layoutEndEmitted=!1,n&&this.layoutPool.has(n))this._clearRegionTimers(this.regions),this._stopAllRegionWidgets(this.regions,this._stopWidgetBound),this.layoutPool.evict(n);else{this._clearRegionTimers(this.regions),this._stopAllRegionWidgets(this.regions,this._stopWidgetBound);for(let[,e]of this.regions)if(E.releaseMediaElements(e.element),e.config?.exitTransition){let t=k.apply(e.element,e.config.exitTransition,!1,e.width,e.height);if(t){let n=e.element;t.onfinish=()=>n.remove()}else e.element.remove()}else e.element.remove();n&&this.revokeBlobUrlsForLayout(n)}if(this.currentLayout=null,this.currentLayoutId=null,this.regions.clear(),t.container.style.visibility=`visible`,t.container.style.zIndex=`0`,this.layoutPool.setHot(e),this.currentLayout=t.layout,this.currentLayoutId=e,this.regions=t.regions,n&&!r&&this.emit(`layoutEnd`,n),this.container.style.backgroundColor=t.layout.bgcolor,t.container.style.backgroundImage)for(let e of[`backgroundImage`,`backgroundSize`,`backgroundPosition`,`backgroundRepeat`])this.container.style[e]=t.container.style[e];else this.container.style.backgroundImage=``;this.calculateScale(t.layout),this.attachActionListeners(t.layout),this.emit(`layoutStart`,e,t.layout);for(let[e,t]of this.regions)t.currentIndex=0,t.complete=!1,this.startRegion(e);this.updateLayoutDuration(),this.startLayoutTimerWhenReady(e,t.layout),this.preloadTimer||this._scheduleNextLayoutPreload(t.layout),this.log.info(`Swapped to preloaded layout ${e} (instant transition)`),this._logResourceStats(e)}_logResourceStats(e){let t=document.querySelectorAll(`*`).length,n=document.querySelectorAll(`video`).length,r=document.querySelectorAll(`video[src]`).length,i=document.querySelectorAll(`canvas`).length,a=document.querySelectorAll(`iframe`).length,o=document.querySelectorAll(`img`).length,s=this.layoutPool?this.layoutPool.size:0,c=this.regions?this.regions.size:0,l=[...this.regions?.values()||[]].reduce((e,t)=>e+(t.widgetElements?.size||0),0),u=performance?.memory?{used:Math.round(performance.memory.usedJSHeapSize/1048576),total:Math.round(performance.memory.totalJSHeapSize/1048576),limit:Math.round(performance.memory.jsHeapSizeLimit/1048576)}:null,d=this._blobUrls?[...this._blobUrls.values()].reduce((e,t)=>e+t.size,0):0,f=this._blobUrls?this._blobUrls.size:0,p=document.querySelectorAll(`.renderer-lite-preload-wrapper`).length,m=document.querySelectorAll(`audio`).length,h=u?`heap=${u.used}/${u.total}MB (limit ${u.limit}MB)`:`heap=N/A`;this.log.info(`[Resources] layout=${e} dom=${t} videos=${n}(src=${r}) canvas=${i} iframe=${a} img=${o} audio=${m} pool=${s} preloadWrappers=${p} regions=${c} widgets=${l} blobs=${d}(${f} layouts) ${h}`)}getCurrentLayoutId(){return this.currentLayoutId}showLayout(e){if(e===void 0&&(e=this.layoutPool.getLatest(),e===void 0)){this.log.warn(`showLayout: no preloaded layout to show`);return}if(this.currentLayoutId===e){this.log.info(`showLayout: layout ${e} already showing`);return}if(!this.layoutPool.has(e)){this.log.warn(`showLayout: layout ${e} not in preload pool`);return}this._swapToPreloadedLayout(e)}hasActiveLayoutTimer(){return this.layoutTimer!==null||this._deferredTimerLayoutId!==null}checkLayoutComplete(){let e=!0;for(let[t,n]of this.regions)if(n.widgets.length>1&&!n.complete){e=!1;break}e&&this.currentLayoutId&&this.log.info(`All multi-widget regions completed one cycle`)}_clearLayoutTimers(){this.layoutTimer&&=(clearTimeout(this.layoutTimer),null),this.preloadTimer&&=(clearTimeout(this.preloadTimer),null),this._preloadRetryTimer&&=(clearTimeout(this._preloadRetryTimer),null)}stopCurrentLayout(){if(!this.currentLayout)return;this.log.info(`Stopping layout ${this.currentLayoutId}`);let e=this.currentLayoutId,t=e&&!this.layoutEndEmitted;if(this.layoutEndEmitted=!1,this._deferredTimerLayoutId=null,this._deferredTimerFallback&&=(clearTimeout(this._deferredTimerFallback),null),this.currentLayout=null,this.currentLayoutId=null,this._clearLayoutTimers(),this.removeActionListeners(),e&&this.layoutPool.has(e))this.layoutPool.evict(e);else{e&&this.revokeBlobUrlsForLayout(e),this._clearRegionTimers(this.regions),this._stopAllRegionWidgets(this.regions,this._stopWidgetBound);for(let[,e]of this.regions)if(E.releaseMediaElements(e.element),e.config?.exitTransition){let t=k.apply(e.element,e.config.exitTransition,!1,e.width,e.height);if(t){let n=e.element;t.onfinish=()=>n.remove()}else e.element.remove()}else e.element.remove()}this.regions.clear(),t&&this.emit(`layoutEnd`,e)}async renderOverlay(e,t,n=0){try{if(this.log.info(`Rendering overlay ${t} (priority ${n})`),this.activeOverlays.has(t)){this.log.warn(`Overlay ${t} already active, skipping`);return}let r=this.parseXlf(e),i=document.createElement(`div`);i.id=`overlay_${t}`,i.className=`renderer-lite-overlay`,i.style.position=`absolute`,i.style.top=`0`,i.style.left=`0`,i.style.width=`100%`,i.style.height=`100%`,i.style.zIndex=String(1e3+n),i.style.pointerEvents=`auto`,i.style.backgroundColor=r.bgcolor,this.calculateScale(r);let a=new Map;for(let e of r.regions){let n=this._createRegionEntry(e,`overlay_${t}_region_${e.id}`,i,{className:`renderer-lite-region overlay-region`,isCanvas:e.isCanvas||!1});a.set(e.id,n)}for(let[e,n]of a)for(let r of n.widgets){r.layoutId=t,r.regionId=e;try{let e=await this.createWidgetElement(r,n);this._positionWidgetElement(e),n.element.appendChild(e),n.widgetElements.set(r.id,e)}catch(e){this.log.error(`Failed to pre-create overlay widget ${r.id}:`,e)}}this.overlayContainer.appendChild(i),this.activeOverlays.set(t,{container:i,layout:r,regions:a,timer:null,priority:n}),this.emit(`overlayStart`,t,r);for(let[e,n]of a)this.startOverlayRegion(t,e);if(r.duration>0){let e=r.duration*1e3,n=this.activeOverlays.get(t);n&&(n.timer=setTimeout(()=>{this.log.info(`Overlay ${t} duration expired (${r.duration}s)`),this.emit(`overlayEnd`,t)},e))}this.log.info(`Overlay ${t} started`)}catch(e){throw this.log.error(`Error rendering overlay:`,e),this.emit(`error`,{type:`overlayError`,error:e,layoutId:t}),e}}startOverlayRegion(e,t){let n=this.activeOverlays.get(e);if(!n)return;let r=n.regions.get(t);this._startRegionCycle(r,t,(t,n)=>this.renderOverlayWidget(e,t,n),(t,n)=>this.stopOverlayWidget(e,t,n),()=>this.log.info(`Overlay ${e} region ${t} completed one full cycle`))}async renderOverlayWidget(e,t,n){let r=this.activeOverlays.get(e);if(!r)return;let i=r.regions.get(t);if(i)try{let r=await this._showWidget(i,n);r&&(this.log.info(`Showing overlay widget ${r.type} (${r.id}) in overlay ${e} region ${t}`),this._startedWidgets.add(`overlay:${e}:${t}:${n}`),this.emit(`overlayWidgetStart`,{overlayId:e,widgetId:r.id,regionId:t,type:r.type,duration:r.duration}))}catch(r){this.log.error(`Error rendering overlay widget:`,r),this.emit(`error`,{type:`overlayWidgetError`,error:r,widgetId:i.widgets[n]?.id,regionId:t,overlayId:e})}}async stopOverlayWidget(e,t,n){let r=`overlay:${e}:${t}:${n}`;if(!this._startedWidgets.delete(r))return;let i=this.activeOverlays.get(e);if(!i)return;let a=i.regions.get(t);if(!a)return;let{widget:o,animPromise:s}=this._hideWidget(a,n);o&&this.emit(`overlayWidgetEnd`,{overlayId:e,widgetId:o.id,regionId:t,type:o.type}),s&&await s}stopOverlay(e){let t=this.activeOverlays.get(e);if(!t){this.log.warn(`Overlay ${e} not active`);return}this.log.info(`Stopping overlay ${e}`),t.timer&&=(clearTimeout(t.timer),null);for(let[,e]of t.regions)e.timer&&=(clearTimeout(e.timer),null);this._stopAllRegionWidgets(t.regions,(t,n)=>this.stopOverlayWidget(e,t,n)),t.container&&t.container.remove(),this.revokeBlobUrlsForLayout(e),this.activeOverlays.delete(e),this.emit(`overlayEnd`,e),this.log.info(`Overlay ${e} stopped`)}stopAllOverlays(){let e=Array.from(this.activeOverlays.keys());for(let t of e)this.stopOverlay(t);this.log.info(`All overlays stopped`)}getActiveOverlays(){return Array.from(this.activeOverlays.keys())}pause(){if(!this._paused){this._paused=!0;for(let[,e]of this.regions)e.timer&&=(clearTimeout(e.timer),null);this._forEachMedia(e=>e.pause()),this.emit(`paused`),this.log.info(`Playback paused (layout timer continues)`)}}isPaused(){return this._paused}resume(){if(this._paused){this._paused=!1,this._forEachMedia(e=>e.play().catch(()=>{}));for(let[e]of this.regions)this.startRegion(e);this.emit(`resumed`),this.log.info(`Playback resumed`)}}_forEachMedia(e){for(let[,t]of this.regions)t.element?.querySelectorAll(`video, audio`).forEach(e)}cleanup(){this.stopAllOverlays(),this.stopCurrentLayout(),this._startedWidgets.clear();for(let e of this.audioOverlays.keys())this._stopAudioOverlays(e);this.layoutPool.clear(),this.preloadTimer&&=(clearTimeout(this.preloadTimer),null),this._preloadRetryTimer&&=(clearTimeout(this._preloadRetryTimer),null),this.resizeObserver&&=(this.resizeObserver.disconnect(),null),this.container.innerHTML=``,this.log.info(`Cleaned up`)}},j=c(`Layout`),ae=class{constructor(e){this.xmds=e}async translateXLF(e,t){let n=new DOMParser().parseFromString(t,`text/xml`),r=n.querySelector(`layout`);if(!r)throw Error(`Invalid XLF: no <layout> element`);let i=parseInt(r.getAttribute(`width`)||`1920`),a=parseInt(r.getAttribute(`height`)||`1080`),o=r.getAttribute(`bgcolor`)||`#000000`,s=[];for(let t of n.querySelectorAll(`region`))s.push(await this.translateRegion(e,t));return this.generateHTML(i,a,o,s)}async translateRegion(e,t){let n=t.getAttribute(`id`),r=parseInt(t.getAttribute(`width`)),i=parseInt(t.getAttribute(`height`)),a=parseInt(t.getAttribute(`top`)),o=parseInt(t.getAttribute(`left`)),s=parseInt(t.getAttribute(`zindex`)||`0`),c=[];for(let r of t.querySelectorAll(`media`))c.push(await this.translateMedia(e,n,r));return{id:n,width:r,height:i,top:a,left:o,zindex:s,media:c}}async translateMedia(e,t,n){let r=n.getAttribute(`type`),i=parseInt(n.getAttribute(`duration`)||`10`),a=n.getAttribute(`id`),o=n.querySelector(`options`),c=n.querySelector(`raw`),l={};if(o)for(let e of o.children)l[e.tagName]=e.textContent;let u={in:null,out:null},d=n.querySelector(`options > transIn`),f=n.querySelector(`options > transOut`),p=n.querySelector(`options > transInDuration`),m=n.querySelector(`options > transOutDuration`),g=n.querySelector(`options > transInDirection`),_=n.querySelector(`options > transOutDirection`);d&&d.textContent&&(u.in={type:d.textContent,duration:parseInt(p?.textContent||`1000`),direction:g?.textContent||`N`}),f&&f.textContent&&(u.out={type:f.textContent,duration:parseInt(m?.textContent||`1000`),direction:_?.textContent||`N`});let v=c?c.textContent:``;if([`clock`,`clock-digital`,`clock-analogue`,`calendar`,`weather`,`currencies`,`stocks`,`twitter`,`global`,`embedded`,`text`,`ticker`].some(e=>r.includes(e))){let n=null;for(let i=1;i<=3;i++)try{j.info(`Fetching resource for ${r} widget (layout=${e}, region=${t}, media=${a}) - attempt ${i}/3`),v=await this.xmds.getResource(e,t,a),j.info(`Got resource HTML (${v.length} chars)`),l.widgetCacheKey=await h(e,t,a,v);break}catch(e){if(n=e,j.warn(`Failed to get resource (attempt ${i}/3):`,e.message),i<3){let e=i*2e3;j.info(`Retrying in ${e}ms...`),await new Promise(t=>setTimeout(t,e))}}if(!v&&n){j.warn(`All retries failed, checking for cached widget HTML...`);try{let n=await fetch(`/store${s}/widgets/${e}/${t}/${a}`);n.ok?(v=await n.text(),l.widgetCacheKey=`${s}/widgets/${e}/${t}/${a}`,j.info(`Using stored widget HTML (${v.length} chars) - CMS update pending`)):(j.error(`No stored version available for widget ${a}`),v=`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>`)}catch(e){j.error(`Store fallback failed:`,e),v=`<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>`}}}return{type:r,duration:i,id:a,options:l,raw:v,transitions:u}}generateHTML(e,t,n,r){return`<!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=${e}, height=${t}">
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ html, body { width: 100%; height: 100%; overflow: hidden; }
10
+ body { background-color: ${n}; }
11
+ .region {
12
+ position: absolute;
13
+ overflow: hidden;
14
+ }
15
+ .media {
16
+ width: 100%;
17
+ height: 100%;
18
+ object-fit: contain;
19
+ }
20
+ iframe {
21
+ border: none;
22
+ width: 100%;
23
+ height: 100%;
24
+ }
25
+ </style>
26
+ </head>
27
+ <body>
28
+ ${r.map(e=>this.generateRegionHTML(e)).join(`
29
+ `)}
30
+ <script>
31
+ // Transition utilities
32
+ window.Transitions = {
33
+ fadeIn(element, duration) {
34
+ const keyframes = [
35
+ { opacity: 0 },
36
+ { opacity: 1 }
37
+ ];
38
+ const timing = {
39
+ duration: duration,
40
+ easing: 'linear',
41
+ fill: 'forwards'
42
+ };
43
+ return element.animate(keyframes, timing);
44
+ },
45
+
46
+ fadeOut(element, duration) {
47
+ const keyframes = [
48
+ { opacity: 1 },
49
+ { opacity: 0 }
50
+ ];
51
+ const timing = {
52
+ duration: duration,
53
+ easing: 'linear',
54
+ fill: 'forwards'
55
+ };
56
+ return element.animate(keyframes, timing);
57
+ },
58
+
59
+ getFlyKeyframes(direction, width, height, isIn) {
60
+ const dirMap = {
61
+ 'N': { x: 0, y: isIn ? -height : height },
62
+ 'NE': { x: isIn ? width : -width, y: isIn ? -height : height },
63
+ 'E': { x: isIn ? width : -width, y: 0 },
64
+ 'SE': { x: isIn ? width : -width, y: isIn ? height : -height },
65
+ 'S': { x: 0, y: isIn ? height : -height },
66
+ 'SW': { x: isIn ? -width : width, y: isIn ? height : -height },
67
+ 'W': { x: isIn ? -width : width, y: 0 },
68
+ 'NW': { x: isIn ? -width : width, y: isIn ? -height : height }
69
+ };
70
+
71
+ const offset = dirMap[direction] || dirMap['N'];
72
+
73
+ if (isIn) {
74
+ return [
75
+ { transform: \`translate(\${offset.x}px, \${offset.y}px)\`, opacity: 0 },
76
+ { transform: 'translate(0, 0)', opacity: 1 }
77
+ ];
78
+ } else {
79
+ return [
80
+ { transform: 'translate(0, 0)', opacity: 1 },
81
+ { transform: \`translate(\${offset.x}px, \${offset.y}px)\`, opacity: 0 }
82
+ ];
83
+ }
84
+ },
85
+
86
+ flyIn(element, duration, direction, regionWidth, regionHeight) {
87
+ const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, true);
88
+ const timing = {
89
+ duration: duration,
90
+ easing: 'ease-out',
91
+ fill: 'forwards'
92
+ };
93
+ return element.animate(keyframes, timing);
94
+ },
95
+
96
+ flyOut(element, duration, direction, regionWidth, regionHeight) {
97
+ const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, false);
98
+ const timing = {
99
+ duration: duration,
100
+ easing: 'ease-in',
101
+ fill: 'forwards'
102
+ };
103
+ return element.animate(keyframes, timing);
104
+ },
105
+
106
+ apply(element, transitionConfig, isIn, regionWidth, regionHeight) {
107
+ if (!transitionConfig || !transitionConfig.type) {
108
+ return null;
109
+ }
110
+
111
+ const type = transitionConfig.type.toLowerCase();
112
+ const duration = transitionConfig.duration || 1000;
113
+ const direction = transitionConfig.direction || 'N';
114
+
115
+ switch (type) {
116
+ case 'fade':
117
+ return isIn ? this.fadeIn(element, duration) : this.fadeOut(element, duration);
118
+ case 'fadein':
119
+ return isIn ? this.fadeIn(element, duration) : null;
120
+ case 'fadeout':
121
+ return isIn ? null : this.fadeOut(element, duration);
122
+ case 'fly':
123
+ return isIn
124
+ ? this.flyIn(element, duration, direction, regionWidth, regionHeight)
125
+ : this.flyOut(element, duration, direction, regionWidth, regionHeight);
126
+ case 'flyin':
127
+ return isIn ? this.flyIn(element, duration, direction, regionWidth, regionHeight) : null;
128
+ case 'flyout':
129
+ return isIn ? null : this.flyOut(element, duration, direction, regionWidth, regionHeight);
130
+ default:
131
+ return null;
132
+ }
133
+ }
134
+ };
135
+
136
+ const regions = {
137
+ ${r.map(e=>this.generateRegionJS(e)).join(`,
138
+ `)}
139
+ };
140
+
141
+ // Auto-start all regions
142
+ Object.keys(regions).forEach(id => {
143
+ playRegion(id);
144
+ });
145
+
146
+ // Track active timers per region so layout teardown can cancel them
147
+ const regionTimers = {};
148
+
149
+ function playRegion(id) {
150
+ const region = regions[id];
151
+ if (!region || region.media.length === 0) return;
152
+
153
+ regionTimers[id] = [];
154
+
155
+ // If only one media item, just show it and don't cycle (arexibo behavior)
156
+ if (region.media.length === 1) {
157
+ const media = region.media[0];
158
+ if (media.start) media.start();
159
+ return; // Don't schedule stop/restart
160
+ }
161
+
162
+ // Multiple media items - cycle normally
163
+ let currentIndex = 0;
164
+
165
+ function playNext() {
166
+ const media = region.media[currentIndex];
167
+ if (media.start) media.start();
168
+
169
+ const duration = media.duration || 10;
170
+ const timerId = setTimeout(() => {
171
+ if (media.stop) media.stop();
172
+ currentIndex = (currentIndex + 1) % region.media.length;
173
+ playNext();
174
+ }, duration * 1000);
175
+ regionTimers[id].push(timerId);
176
+ }
177
+
178
+ playNext();
179
+ }
180
+
181
+ // Cleanup function — called before layout teardown
182
+ window._stopAllRegions = function() {
183
+ Object.values(regionTimers).forEach(timers => timers.forEach(t => clearTimeout(t)));
184
+ };
185
+ <\/script>
186
+ </body>
187
+ </html>`}generateRegionHTML(e){return` <div id="region_${e.id}" class="region" style="
188
+ left: ${e.left}px;
189
+ top: ${e.top}px;
190
+ width: ${e.width}px;
191
+ height: ${e.height}px;
192
+ z-index: ${e.zindex};
193
+ "></div>`}generateRegionJS(e){let t=e.media.map(t=>this.generateMediaJS(t,e.id)).join(`,
194
+ `);return` '${e.id}': {
195
+ media: [
196
+ ${t}
197
+ ]
198
+ }`}_generateIframeWidgetJS(e,t,n,r,i){let a=`widget_${e}_${t}`;return{startFn:`() => {
199
+ const region = document.getElementById('region_${e}');
200
+ let iframe = document.getElementById('${a}');
201
+ if (!iframe) {
202
+ iframe = document.createElement('iframe');
203
+ iframe.id = '${a}';
204
+ iframe.src = '${n}';
205
+ iframe.style.width = '100%';
206
+ iframe.style.height = '100%';
207
+ iframe.style.border = 'none';
208
+ iframe.scrolling = 'no';
209
+ iframe.style.opacity = '0';
210
+ region.innerHTML = '';
211
+ region.appendChild(iframe);
212
+
213
+ // Apply transition after iframe loads
214
+ iframe.onload = () => {
215
+ const transIn = ${r};
216
+ if (transIn && window.Transitions) {
217
+ const regionRect = region.getBoundingClientRect();
218
+ window.Transitions.apply(iframe, transIn, true, regionRect.width, regionRect.height);
219
+ } else {
220
+ iframe.style.opacity = '1';
221
+ }
222
+ };
223
+ } else {
224
+ iframe.style.display = 'block';
225
+ iframe.style.opacity = '1';
226
+ }
227
+ }`,stopFn:`() => {
228
+ const region = document.getElementById('region_${e}');
229
+ const iframe = document.getElementById('${a}');
230
+ if (iframe) {
231
+ const transOut = ${i};
232
+ if (transOut && window.Transitions) {
233
+ const regionRect = region.getBoundingClientRect();
234
+ const animation = window.Transitions.apply(iframe, transOut, false, regionRect.width, regionRect.height);
235
+ if (animation) {
236
+ animation.onfinish = () => {
237
+ iframe.style.display = 'none';
238
+ };
239
+ return;
240
+ }
241
+ }
242
+ iframe.style.display = 'none';
243
+ }
244
+ }`}}generateMediaJS(e,t){let r=e.duration||10,i=e.transitions?.in?JSON.stringify(e.transitions.in):`null`,a=e.transitions?.out?JSON.stringify(e.transitions.out):`null`,o=`null`,c=`null`;switch(e.type){case`image`:o=`() => {
245
+ const region = document.getElementById('region_${t}');
246
+ const img = document.createElement('img');
247
+ img.className = 'media';
248
+ img.src = '${`${window.location.origin}${s}/media/${e.options.uri}`}';
249
+ img.style.opacity = '0';
250
+ region.innerHTML = '';
251
+ region.appendChild(img);
252
+
253
+ // Apply transition
254
+ const transIn = ${i};
255
+ if (transIn && window.Transitions) {
256
+ const regionRect = region.getBoundingClientRect();
257
+ window.Transitions.apply(img, transIn, true, regionRect.width, regionRect.height);
258
+ } else {
259
+ img.style.opacity = '1';
260
+ }
261
+ }`;break;case`video`:{let n=`${window.location.origin}${s}/media/${e.options.uri}`,r=e.options.uri;o=`() => {
262
+ const region = document.getElementById('region_${t}');
263
+ const video = document.createElement('video');
264
+ video.className = 'media';
265
+ video.src = '${n}';
266
+ video.dataset.filename = '${r}';
267
+ video.autoplay = true;
268
+ video.muted = ${e.options.mute===`1`?`true`:`false`};
269
+ video.loop = false;
270
+ video.style.width = '100%';
271
+ video.style.height = '100%';
272
+ video.style.objectFit = 'contain';
273
+ video.style.opacity = '0';
274
+
275
+ // Retry loading if cache completes while video is playing
276
+ const retryOnCache = (event) => {
277
+ if (event.detail.filename === '${r}' && video.error) {
278
+ console.log('[Video] Cache complete, reloading:', '${r}');
279
+ video.load();
280
+ video.play();
281
+ }
282
+ };
283
+ video._retryOnCache = retryOnCache;
284
+ window.addEventListener('media-cached', retryOnCache);
285
+
286
+ region.innerHTML = '';
287
+ region.appendChild(video);
288
+
289
+ // Apply transition
290
+ const transIn = ${i};
291
+ if (transIn && window.Transitions) {
292
+ const regionRect = region.getBoundingClientRect();
293
+ window.Transitions.apply(video, transIn, true, regionRect.width, regionRect.height);
294
+ } else {
295
+ video.style.opacity = '1';
296
+ }
297
+
298
+ console.log('[Video] Playing:', '${e.options.uri}');
299
+ }`,c=`() => {
300
+ const region = document.getElementById('region_${t}');
301
+ const video = document.querySelector('#region_${t} video');
302
+ if (video) {
303
+ // Remove global media-cached listener to prevent leak
304
+ if (video._retryOnCache) {
305
+ window.removeEventListener('media-cached', video._retryOnCache);
306
+ video._retryOnCache = null;
307
+ }
308
+ const transOut = ${a};
309
+ if (transOut && window.Transitions) {
310
+ const regionRect = region.getBoundingClientRect();
311
+ const animation = window.Transitions.apply(video, transOut, false, regionRect.width, regionRect.height);
312
+ if (animation) {
313
+ animation.onfinish = () => {
314
+ video.pause();
315
+ video.remove();
316
+ };
317
+ return;
318
+ }
319
+ }
320
+ video.pause();
321
+ video.remove();
322
+ }
323
+ }`;break}case`text`:case`ticker`:if(e.options.widgetCacheKey){let n=`${window.location.origin}${e.options.widgetCacheKey}`,r=this._generateIframeWidgetJS(t,e.id,n,i,a);o=r.startFn,c=r.stopFn;break}case`audio`:{let n=`${window.location.origin}${s}/media/${e.options.uri}`,r=`audio_${t}_${e.id}`,l=e.options.loop===`1`,u=(parseInt(e.options.volume||`100`)/100).toFixed(2);o=`() => {
324
+ const region = document.getElementById('region_${t}');
325
+
326
+ // Create audio element
327
+ const audio = document.createElement('audio');
328
+ audio.id = '${r}';
329
+ audio.className = 'media';
330
+ audio.src = '${n}';
331
+ audio.autoplay = true;
332
+ audio.loop = ${l};
333
+ audio.volume = ${u};
334
+
335
+ // Create visual feedback container
336
+ const visualContainer = document.createElement('div');
337
+ visualContainer.className = 'audio-visual';
338
+ visualContainer.style.cssText = \`
339
+ width: 100%;
340
+ height: 100%;
341
+ display: flex;
342
+ flex-direction: column;
343
+ align-items: center;
344
+ justify-content: center;
345
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
346
+ opacity: 0;
347
+ \`;
348
+
349
+ // Audio icon
350
+ const icon = document.createElement('div');
351
+ icon.innerHTML = '♪';
352
+ icon.style.cssText = \`
353
+ font-size: 120px;
354
+ color: white;
355
+ margin-bottom: 20px;
356
+ animation: pulse 2s ease-in-out infinite;
357
+ \`;
358
+
359
+ // Audio info
360
+ const info = document.createElement('div');
361
+ info.style.cssText = \`
362
+ color: white;
363
+ font-size: 24px;
364
+ text-align: center;
365
+ padding: 0 20px;
366
+ \`;
367
+ info.textContent = 'Playing Audio';
368
+
369
+ // Filename
370
+ const filename = document.createElement('div');
371
+ filename.style.cssText = \`
372
+ color: rgba(255,255,255,0.7);
373
+ font-size: 16px;
374
+ margin-top: 10px;
375
+ \`;
376
+ filename.textContent = '${e.options.uri}';
377
+
378
+ visualContainer.appendChild(icon);
379
+ visualContainer.appendChild(info);
380
+ visualContainer.appendChild(filename);
381
+
382
+ region.innerHTML = '';
383
+ region.appendChild(audio);
384
+ region.appendChild(visualContainer);
385
+
386
+ // Add pulse animation
387
+ const style = document.createElement('style');
388
+ style.textContent = \`
389
+ @keyframes pulse {
390
+ 0%, 100% { transform: scale(1); opacity: 1; }
391
+ 50% { transform: scale(1.1); opacity: 0.8; }
392
+ }
393
+ \`;
394
+ document.head.appendChild(style);
395
+
396
+ // Apply transition
397
+ const transIn = ${i};
398
+ if (transIn && window.Transitions) {
399
+ const regionRect = region.getBoundingClientRect();
400
+ window.Transitions.apply(visualContainer, transIn, true, regionRect.width, regionRect.height);
401
+ } else {
402
+ visualContainer.style.opacity = '1';
403
+ }
404
+
405
+ console.log('[Audio] Playing:', '${n}', 'Volume:', ${u}, 'Loop:', ${l});
406
+ }`,c=`() => {
407
+ const audio = document.getElementById('${r}');
408
+ if (audio) {
409
+ audio.pause();
410
+ audio.remove();
411
+ }
412
+ const region = document.getElementById('region_${t}');
413
+ if (region) {
414
+ const visualContainer = region.querySelector('.audio-visual');
415
+ if (visualContainer) {
416
+ const transOut = ${a};
417
+ if (transOut && window.Transitions) {
418
+ const regionRect = region.getBoundingClientRect();
419
+ const animation = window.Transitions.apply(visualContainer, transOut, false, regionRect.width, regionRect.height);
420
+ if (animation) {
421
+ animation.onfinish = () => visualContainer.remove();
422
+ return;
423
+ }
424
+ }
425
+ visualContainer.remove();
426
+ }
427
+ }
428
+ }`;break}case`pdf`:{let l=`${window.location.origin}${s}/media/${e.options.uri}`,u=`pdf_${t}_${e.id}`,d=r;o=`async () => {
429
+ const container = document.createElement('div');
430
+ container.className = 'media pdf-container';
431
+ container.id = '${u}';
432
+ container.style.width = '100%';
433
+ container.style.height = '100%';
434
+ container.style.overflow = 'hidden';
435
+ container.style.backgroundColor = '#525659';
436
+ container.style.opacity = '0';
437
+ container.style.position = 'relative';
438
+
439
+ const region = document.getElementById('region_${t}');
440
+ region.innerHTML = '';
441
+ region.appendChild(container);
442
+
443
+ // Load PDF.js if not already loaded
444
+ if (typeof pdfjsLib === 'undefined') {
445
+ try {
446
+ const pdfjsModule = await import('pdfjs-dist');
447
+ window.pdfjsLib = pdfjsModule;
448
+ pdfjsLib.GlobalWorkerOptions.workerSrc = '${window.location.origin}/player/pdf.worker.min.mjs';
449
+ } catch (error) {
450
+ console.error('[PDF] Failed to load PDF.js:', error);
451
+ container.innerHTML = '<div style="color:white;padding:20px;text-align:center;">PDF viewer unavailable</div>';
452
+ return;
453
+ }
454
+ }
455
+
456
+ // Render PDF with multi-page support
457
+ try {
458
+ const loadingTask = pdfjsLib.getDocument('${l}');
459
+ const pdf = await loadingTask.promise;
460
+ const totalPages = pdf.numPages;
461
+
462
+ // Calculate time per page (distribute total duration across all pages)
463
+ const timePerPage = (${d} * 1000) / totalPages; // milliseconds per page
464
+
465
+ console.log(\`[PDF] Loading: \${totalPages} pages, \${timePerPage}ms per page\`);
466
+
467
+ const containerWidth = container.offsetWidth || ${width};
468
+ const containerHeight = container.offsetHeight || ${height};
469
+
470
+ // Create page indicator
471
+ const pageIndicator = document.createElement('div');
472
+ pageIndicator.className = 'pdf-page-indicator';
473
+ pageIndicator.style.cssText = \`
474
+ position: absolute;
475
+ bottom: 10px;
476
+ right: 10px;
477
+ background: rgba(0,0,0,0.7);
478
+ color: white;
479
+ padding: 8px 12px;
480
+ border-radius: 4px;
481
+ font-size: 14px;
482
+ z-index: 10;
483
+ display: ${n()?`block`:`none`};
484
+ \`;
485
+ container.appendChild(pageIndicator);
486
+
487
+ let currentPage = 1;
488
+ let pageTimers = [];
489
+
490
+ // Function to render a single page
491
+ async function renderPage(pageNum) {
492
+ const page = await pdf.getPage(pageNum);
493
+ const viewport = page.getViewport({ scale: 1 });
494
+
495
+ // Calculate scale to fit page within container
496
+ const scaleX = containerWidth / viewport.width;
497
+ const scaleY = containerHeight / viewport.height;
498
+ const scale = Math.min(scaleX, scaleY);
499
+
500
+ const scaledViewport = page.getViewport({ scale });
501
+
502
+ const canvas = document.createElement('canvas');
503
+ canvas.className = 'pdf-page';
504
+ const context = canvas.getContext('2d');
505
+ canvas.width = scaledViewport.width;
506
+ canvas.height = scaledViewport.height;
507
+
508
+ // Center canvas in container
509
+ canvas.style.cssText = \`
510
+ display: block;
511
+ margin: auto;
512
+ margin-top: \${Math.max(0, (containerHeight - scaledViewport.height) / 2)}px;
513
+ position: absolute;
514
+ top: 0;
515
+ left: 50%;
516
+ transform: translateX(-50%);
517
+ opacity: 0;
518
+ transition: opacity 0.5s ease-in-out;
519
+ \`;
520
+
521
+ container.appendChild(canvas);
522
+
523
+ await page.render({
524
+ canvasContext: context,
525
+ viewport: scaledViewport
526
+ }).promise;
527
+
528
+ // Fade in new page
529
+ setTimeout(() => canvas.style.opacity = '1', 50);
530
+
531
+ return canvas;
532
+ }
533
+
534
+ // Function to cycle through pages
535
+ async function cyclePage() {
536
+ // Update page indicator
537
+ pageIndicator.textContent = \`Page \${currentPage} / \${totalPages}\`;
538
+
539
+ // Remove old pages
540
+ const oldPages = container.querySelectorAll('.pdf-page');
541
+ oldPages.forEach(oldPage => {
542
+ if (oldPage !== container.lastChild) {
543
+ oldPage.style.opacity = '0';
544
+ setTimeout(() => oldPage.remove(), 500);
545
+ }
546
+ });
547
+
548
+ // Render current page
549
+ await renderPage(currentPage);
550
+
551
+ console.log(\`[PDF] Showing page \${currentPage}/\${totalPages}\`);
552
+
553
+ // Schedule next page
554
+ if (totalPages > 1) {
555
+ const timer = setTimeout(() => {
556
+ currentPage = currentPage >= totalPages ? 1 : currentPage + 1;
557
+ cyclePage();
558
+ }, timePerPage);
559
+ pageTimers.push(timer);
560
+ }
561
+ }
562
+
563
+ // Store live timer array on element for cleanup (not JSON — stays current)
564
+ container._pageTimers = pageTimers;
565
+
566
+ // Start cycling
567
+ await cyclePage();
568
+
569
+ // Apply transition to container
570
+ const transIn = ${i};
571
+ if (transIn && window.Transitions) {
572
+ const regionRect = region.getBoundingClientRect();
573
+ window.Transitions.apply(container, transIn, true, regionRect.width, regionRect.height);
574
+ } else {
575
+ container.style.opacity = '1';
576
+ }
577
+
578
+ } catch (error) {
579
+ console.error('[PDF] Render failed:', error);
580
+ container.innerHTML = '<div style="color:white;padding:20px;text-align:center;">Failed to load PDF</div>';
581
+ container.style.opacity = '1';
582
+ }
583
+ }`,c=`() => {
584
+ const region = document.getElementById('region_${t}');
585
+ const container = document.getElementById('${u}');
586
+ if (container) {
587
+ // Clear page cycling timers (live array, always current)
588
+ if (container._pageTimers) {
589
+ container._pageTimers.forEach(t => clearTimeout(t));
590
+ container._pageTimers.length = 0;
591
+ }
592
+
593
+ const transOut = ${a};
594
+ if (transOut && window.Transitions) {
595
+ const regionRect = region.getBoundingClientRect();
596
+ const animation = window.Transitions.apply(container, transOut, false, regionRect.width, regionRect.height);
597
+ if (animation) {
598
+ animation.onfinish = () => {
599
+ container.remove();
600
+ };
601
+ return;
602
+ }
603
+ }
604
+ container.remove();
605
+ }
606
+ }`;break}case`webpage`:o=`() => {
607
+ const region = document.getElementById('region_${t}');
608
+ const iframe = document.createElement('iframe');
609
+ iframe.src = '${decodeURIComponent(e.options.uri||``)}';
610
+ iframe.style.opacity = '0';
611
+ region.innerHTML = '';
612
+ region.appendChild(iframe);
613
+
614
+ // Apply transition after iframe loads
615
+ iframe.onload = () => {
616
+ const transIn = ${i};
617
+ if (transIn && window.Transitions) {
618
+ const regionRect = region.getBoundingClientRect();
619
+ window.Transitions.apply(iframe, transIn, true, regionRect.width, regionRect.height);
620
+ } else {
621
+ iframe.style.opacity = '1';
622
+ }
623
+ };
624
+ }`;break;default:if(e.options.widgetCacheKey){let n=`${window.location.origin}${e.options.widgetCacheKey}`,r=this._generateIframeWidgetJS(t,e.id,n,i,a);o=r.startFn,c=r.stopFn}else j.warn(`Unsupported media type: ${e.type}`),o=`() => console.log('Unsupported media type: ${e.type}')`}return` {
625
+ start: ${o},
626
+ stop: ${c},
627
+ duration: ${r}
628
+ }`}},oe=C({LayoutPool:()=>E,LayoutTranslator:()=>ae,RendererLite:()=>A,VERSION:()=>se}),se=ne.version,ce={name:`@xiboplayer/core`,version:`0.7.13`,description:`Xibo Player core orchestration and lifecycle management`,type:`module`,main:`./src/index.js`,types:`./src/index.d.ts`,exports:{".":`./src/index.js`,"./player-core":`./src/player-core.js`},scripts:{dev:`vite`,build:`vite build`,preview:`vite preview`,test:`vitest run`,"test:watch":`vitest`,"test:ui":`vitest --ui`,"test:coverage":`vitest run --coverage`},dependencies:{"@xiboplayer/utils":`workspace:*`},peerDependencies:{"@xiboplayer/cache":`workspace:*`,"@xiboplayer/renderer":`workspace:*`,"@xiboplayer/schedule":`workspace:*`,"@xiboplayer/xmds":`workspace:*`},devDependencies:{"@vitest/coverage-v8":`^4.1.2`,"@vitest/ui":`^4.1.2`,jsdom:`^29.0.1`,vite:`^8.0.3`,vitest:`^4.1.2`},keywords:[`xibo`,`digital-signage`,`player`,`core`,`orchestration`],author:`Pau Aliagas <linuxnow@gmail.com>`,license:`AGPL-3.0-or-later`,repository:{type:`git`,url:`git+https://github.com/xibo-players/xiboplayer.git`,directory:`packages/core`},homepage:`https://xiboplayer.org`},M=c(`DataConnector`),le=3e5,ue=3,N=class extends r{constructor(){super(),this.connectors=new Map}setConnectors(e){if(this.stopPolling(),this.connectors.clear(),!e||e.length===0){M.debug(`No data connectors configured`);return}for(let t of e){if(!t.dataKey||!t.url){M.warn(`Skipping data connector with missing dataKey or url:`,t);continue}this.connectors.set(t.dataKey,{config:t,data:null,timer:null,lastFetch:null,failures:0}),M.info(`Registered data connector: ${t.dataKey} (interval: ${t.updateInterval}s)`)}M.info(`${this.connectors.size} data connector(s) configured`)}startPolling(){for(let[e,t]of this.connectors.entries()){let{config:n}=t,r=(n.updateInterval||300)*1e3;this.fetchData(t).catch(t=>{M.error(`Initial fetch failed for ${e}:`,t)}),t.timer=setInterval(()=>{this.fetchData(t).catch(t=>{M.error(`Polling fetch failed for ${e}:`,t)})},r),M.debug(`Started polling for ${e} every ${n.updateInterval}s`)}}stopPolling(){for(let[e,t]of this.connectors.entries())t.timer&&(clearInterval(t.timer),t.timer=null,M.debug(`Stopped polling for ${e}`))}getData(e){let t=this.connectors.get(e);return t?t.data:(M.debug(`No data connector found for key: ${e}`),null)}getAvailableKeys(){let e=[];for(let[t,n]of this.connectors.entries())n.data!==null&&e.push(t);return e}async fetchData(e){let{config:t}=e,{dataKey:n,url:r}=t;M.debug(`Fetching data for ${n}: ${r}`);try{let t=await a(r,{method:`GET`,headers:{Accept:`application/json`}},{maxRetries:2,baseDelayMs:2e3});if(!t.ok){M.warn(`Data connector ${n} returned ${t.status}: ${t.statusText}`);return}let i=t.headers.get(`Content-Type`)||``,o;o=i.includes(`application/json`)?await t.json():await t.text();let s=e.data;e.data=o,e.lastFetch=Date.now(),e.failures=0,M.debug(`Data updated for ${n} (fetched at ${new Date(e.lastFetch).toISOString()})`),this._ensureNormalPolling(e),this.emit(`data-updated`,n,o),JSON.stringify(s)!==JSON.stringify(o)&&this.emit(`data-changed`,n,o)}catch(r){if(e.failures=(e.failures||0)+1,M.error(`Failed to fetch data for ${n} (${e.failures}x):`,r),this.emit(`fetch-error`,n,r),e.failures>=ue&&e.timer){let r=(t.updateInterval||300)*1e3,i=Math.min(r*2**(e.failures-ue+1),le);clearInterval(e.timer),e.timer=setTimeout(()=>{this.fetchData(e).catch(()=>{}),e.timer=setInterval(()=>{this.fetchData(e).catch(()=>{})},i)},i),M.warn(`Circuit breaker: ${n} backing off to ${Math.round(i/1e3)}s`)}}}_ensureNormalPolling(e){if(e.failures===0&&e.timer){let t=(e.config.updateInterval||300)*1e3;clearInterval(e.timer),clearTimeout(e.timer),e.timer=setInterval(()=>{this.fetchData(e).catch(()=>{})},t)}}refreshAll(){this.connectors.size!==0&&(M.info(`Refreshing all ${this.connectors.size} data connector(s)`),this.stopPolling(),this.startPolling())}cleanup(){this.stopPolling(),this.connectors.clear(),this.removeAllListeners(),M.debug(`DataConnectorManager cleaned up`)}},P=c(`Blacklist`),de=class{constructor(e=3){this._entries=new Map,this._threshold=e}recordFailure(e,t){let n=Number(e),r=this._entries.get(n)||{failures:0,blacklisted:!1,reason:``};return r.failures++,r.reason=t,!r.blacklisted&&r.failures>=this._threshold?(r.blacklisted=!0,P.warn(`Layout ${n} blacklisted after ${r.failures} consecutive failures: ${t}`)):r.blacklisted||P.info(`Layout ${n} failure ${r.failures}/${this._threshold}: ${t}`),this._entries.set(n,r),{blacklisted:r.blacklisted,failures:r.failures}}recordSuccess(e){let t=Number(e);if(!this._entries.has(t))return!1;let n=this._entries.get(t);return this._entries.delete(t),n.blacklisted?(P.info(`Layout ${t} removed from blacklist (rendered successfully)`),!0):!1}isBlacklisted(e){return this._entries.get(Number(e))?.blacklisted===!0}getBlacklistedIds(){let e=[];for(let[t,n]of this._entries)n.blacklisted&&e.push(t);return e}reset(){let e=this._entries.size;return e>0&&(P.info(`Blacklist reset (${e} entries cleared)`),this._entries.clear()),e}get size(){return this._entries.size}},F=Object.freeze({COLLECTION_START:`collection-start`,COLLECTION_COMPLETE:`collection-complete`,COLLECTION_ERROR:`collection-error`,REGISTER_COMPLETE:`register-complete`,SCHEDULE_RECEIVED:`schedule-received`,LAYOUTS_SCHEDULED:`layouts-scheduled`,NO_LAYOUTS_SCHEDULED:`no-layouts-scheduled`,TIMELINE_UPDATED:`timeline-updated`,LAYOUT_PREPARE_REQUEST:`layout-prepare-request`,LAYOUT_EXPIRE_CURRENT:`layout-expire-current`,LAYOUT_ALREADY_PLAYING:`layout-already-playing`,CHECK_PENDING_LAYOUT:`check-pending-layout`,FILES_RECEIVED:`files-received`,DOWNLOAD_REQUEST:`download-request`,OVERLAY_LAYOUT_REQUEST:`overlay-layout-request`,REVERT_TO_SCHEDULE:`revert-to-schedule`,SYNC_CONFIG:`sync-config`,XMR_CONNECTED:`xmr-connected`,XMR_RECONNECTED:`xmr-reconnected`,XMR_MISCONFIGURED:`xmr-misconfigured`,NAVIGATE_TO_WIDGET:`navigate-to-widget`,EXECUTE_NATIVE_COMMAND:`execute-native-command`,SCHEDULED_COMMAND:`scheduled-command`,COMMAND_RESULT:`command-result`,SCREENSHOT_REQUEST:`screenshot-request`,SUBMIT_STATS_REQUEST:`submit-stats-request`,SUBMIT_LOGS_REQUEST:`submit-logs-request`,SUBMIT_FAULTS_REQUEST:`submit-faults-request`,CACHE_ANALYSIS:`cache-analysis`,COLLECTION_INTERVAL_SET:`collection-interval-set`,COLLECTION_INTERVAL_UPDATED:`collection-interval-updated`,LOG_LEVEL_CHANGED:`log-level-changed`,OFFLINE_MODE:`offline-mode`,PURGE_REQUEST:`purge-request`,PURGE_ALL_REQUEST:`purge-all-request`}),I=c(`PlayerCore`);async function fe(){if(typeof window<`u`&&window.electronAPI?.getLanIpAddress)try{return await window.electronAPI.getLanIpAddress()}catch{}try{let e=await(globalThis.__nativeFetch||globalThis.fetch)(`/system/lan-ip`);if(e.ok){let{ip:t}=await e.json();if(t)return t}}catch{}return``}var L=`xibo-offline-cache`,pe=1,R=`cache`;function z(e){return o(e?`${L}-${e}`:L,pe,R)}var B=class extends r{constructor(e){super(),this.config=e.config,this.xmds=e.xmds,this.cache=e.cache,this.schedule=e.schedule,this.renderer=e.renderer,this.XmrWrapper=e.xmrWrapper,this.statsCollector=e.statsCollector,this.displaySettings=e.displaySettings,this._cmsId=e.cmsId||null,this.dataConnectorManager=new N,fe().then(e=>{this._lanIpAddress=e,I.info(`LAN IP:`,e||`(not discovered)`)}),this.xmr=null,this.currentLayoutId=null,this.collecting=!1,this.collectionInterval=null,this.pendingLayouts=new Map,this._layoutMediaStatus=new Map,this.offlineMode=!1,this._normalCollectInterval=null,this._offlineRetrySeconds=0,this._lastCheckRf=null,this._lastCheckSchedule=null,this._lastTimelineFingerprint=null,this._lastTimeline=null,this._layoutOverride=null,this._lastRequiredFiles=[],this._executedCommands=new Set,this.displayCommands=null,this._faultReportingInterval=null,this._faultReportingSeconds=60,this._layoutBlacklist=new de(3),this._lastLayoutChangeTime=null,this._statusCode=2,this._dynamicLayouts=new Set,this.syncConfig=null,this.syncManager=null,this._layoutDurations=new Map,this._finalDurations=new Set,this._preparingLayoutId=null,this.cacheAnalyzer=this.cache?new g(this.cache):null,this._offlineCache={schedule:null,settings:null,requiredFiles:null},this._offlineDbReady=this._initOfflineCache()}get _queueOptions(){return{dynamicLayouts:this._dynamicLayouts}}_scheduleAutoRevert(e,t,n){t>0&&setTimeout(()=>{this._layoutOverride?.layoutId===e&&(I.info(`${n} duration expired (${t}s), reverting to schedule`),this.revertToSchedule())},t*1e3)}async _initOfflineCache(){try{let e=await z(this._cmsId),t=e.transaction(R,`readonly`).objectStore(R),[n,r,i,a,o,s]=await Promise.all([new Promise(e=>{let n=t.get(`schedule`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)}),new Promise(e=>{let n=t.get(`settings`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)}),new Promise(e=>{let n=t.get(`requiredFiles`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)}),new Promise(e=>{let n=t.get(`durations`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)}),new Promise(e=>{let n=t.get(`finalDurations`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)}),new Promise(e=>{let n=t.get(`durationsVersion`);n.onsuccess=()=>e(n.result??null),n.onerror=()=>e(null)})]);if(Array.isArray(a)&&a.length>0){for(let[e,t]of a)this._layoutDurations.set(e,t);I.info(`[Timeline] Restored ${a.length} cached durations from IDB`)}if(s>=2&&Array.isArray(o)&&o.length>0){for(let e of o)this._finalDurations.add(e);I.info(`[Timeline] Restored ${o.length} final duration keys from IDB`)}else Array.isArray(o)&&o.length>0&&I.info(`[Timeline] Discarded ${o.length} stale final duration keys (pre-v2)`);this._offlineCache={schedule:n,settings:r,requiredFiles:i},this._offlineDb=e,I.info(`Offline cache loaded from IndexedDB`,n?`(has schedule)`:`(empty)`)}catch(e){I.warn(`Failed to load offline cache from IndexedDB:`,e)}}async _offlineSave(e,t){this._offlineCache[e]=t;try{this._offlineDb||=await z(this._cmsId);let n=this._offlineDb.transaction(R,`readwrite`);n.objectStore(R).put(t,e),await new Promise((e,t)=>{n.oncomplete=e,n.onerror=()=>t(n.error)})}catch(t){this._offlineDb=null,I.warn(`Failed to save offline cache:`,e,t)}}hasCachedData(){return this._offlineCache.schedule!==null}isOffline(){return typeof navigator<`u`&&navigator.onLine===!1}isInOfflineMode(){return this.offlineMode}collectOffline(){if(I.warn(`Offline mode — using cached schedule`),this.offlineMode||(this.offlineMode=!0,this.emit(F.OFFLINE_MODE,!0)),this.collectionInterval&&(this._normalCollectInterval?this._offlineRetrySeconds=Math.min(this._offlineRetrySeconds*2,this._normalCollectInterval):(this._normalCollectInterval=this._currentCollectInterval,this._offlineRetrySeconds=30),this._setCollectionTimer(this._offlineRetrySeconds),I.info(`Offline: retry in ${this._offlineRetrySeconds}s`)),!this.collectionInterval){let e=this._offlineCache.settings;e?.settings&&(this.setupCollectionInterval(e.settings),this._normalCollectInterval=this._currentCollectInterval,this._offlineRetrySeconds=30,this._setCollectionTimer(this._offlineRetrySeconds),I.info(`Offline: retry in ${this._offlineRetrySeconds}s`))}let e=this._offlineCache.schedule;e&&(this.schedule.setSchedule(e),this.emit(F.SCHEDULE_RECEIVED,e));let t=this.schedule.getCurrentLayouts();I.info(`Offline layouts:`,t),this.emit(F.LAYOUTS_SCHEDULED,t),this._evaluateAndSwitchLayout(t,`Offline`),this.emit(F.COLLECTION_COMPLETE)}_evaluateAndSwitchLayout(e,t){let n=t?`${t}: `:``,{queue:r}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions);if(r.length>0)if(this.currentLayoutId)r.some(e=>u(e.layoutId)===this.currentLayoutId)?(I.info(`Layout ${this.currentLayoutId} playing — queue updated in background, playback continues`),this.emit(F.LAYOUT_ALREADY_PLAYING,this.currentLayoutId)):(I.info(`Layout ${this.currentLayoutId} no longer in queue — expiring`),this.currentLayoutId=null,this.emit(F.LAYOUT_EXPIRE_CURRENT));else if(this._preparingLayoutId)I.info(`${n}layout ${this._preparingLayoutId} already being prepared, skipping`);else{let e=this.getNextLayout();e&&(this._preparingLayoutId=e.layoutId,I.info(`${n}switching to layout ${e.layoutId}`),this.emit(F.LAYOUT_PREPARE_REQUEST,e.layoutId))}else I.info(`${t?`${t}: n`:`N`}o layouts${t?` in cached schedule`:` scheduled, falling back to default`}`),this.emit(F.NO_LAYOUTS_SCHEDULED);this.logUpcomingTimeline()}async collectNow(){return this._lastCheckRf=null,this._lastCheckSchedule=null,this.collect()}async collect(){if(this.collecting){I.debug(`Collection already in progress, skipping`);return}this.collecting=!0;try{if(await this._offlineDbReady,I.info(`Starting collection cycle...`),this.emit(F.COLLECTION_START),this.isOffline()){if(this.hasCachedData())return this.collecting=!1,this.collectOffline();throw Error(`Offline with no cached data — cannot start playback`)}this.config.ensureXmrKeyPair&&await this.config.ensureXmrKeyPair(),I.debug(`Collection step: registerDisplay`);let e=await this.xmds.registerDisplay();I.info(`Display registered: ${e.code}${e.tags?.length?`, tags: ${e.tags.join(`, `)}`:``}`),I.debug(`Register result:`,JSON.stringify(e)),this._processRegistration(e),I.debug(`Collection step: initializeXmr`),await this.initializeXmr(e);let t=e.checkRf||``,n=e.checkSchedule||``;if(!this._lastCheckRf||this._lastCheckRf!==t){this.resetBlacklist(),I.debug(`Collection step: requiredFiles`);let e=await this.xmds.requiredFiles(),r=e.files||e,i=e.purge||[];if(I.info(`Required files:`,r.length,i.length>0?`(+ ${i.length} purge)`:``),this._lastCheckRf=t,this.emit(F.FILES_RECEIVED,r),this._offlineSave(`requiredFiles`,e),i.length>0&&this.emit(F.PURGE_REQUEST,i),!this._lastCheckSchedule||this._lastCheckSchedule!==n){I.debug(`Collection step: schedule`);let e=await this.xmds.schedule();I.info(`Schedule received`),this._lastCheckSchedule=n,I.debug(`Collection step: processing schedule`),this._applyNewSchedule(e),this.logUpcomingTimeline()}I.debug(`Collection step: download-request + mediaInventory`),this.schedule.getCurrentLayouts();let{queue:a}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions),o=[...new Set(a.map(e=>u(e.layoutId)))];if(this._lastRequiredFiles=r,this.displaySettings?.isInDownloadWindow&&!this.displaySettings.isInDownloadWindow()){let e=this.displaySettings.getNextDownloadWindow?.();I.info(`Outside download window, skipping downloads${e?` (next: ${e.toLocaleTimeString()})`:``}`)}else this.emit(F.DOWNLOAD_REQUEST,{layoutOrder:o,files:r,layoutDependants:Object.fromEntries(this.schedule.getDependantsMap())});this.cacheAnalyzer&&this.cacheAnalyzer.analyze(r).then(e=>{this.emit(F.CACHE_ANALYSIS,e)}).catch(e=>I.warn(`Cache analysis failed:`,e)),this.submitMediaInventory(r)}else if(t&&I.info(`RequiredFiles CRC unchanged, skipping download check`),this._lastCheckSchedule!==n){let e=await this.xmds.schedule();I.info(`Schedule received (RF unchanged but schedule changed)`),this._lastCheckSchedule=n,this._applyNewSchedule(e)}else n&&I.info(`Schedule CRC unchanged, skipping`);await this._fetchWeatherData(),I.debug(`Collection step: evaluateSchedule`);let r=this.schedule.getCurrentLayouts();I.info(`Current layouts:`,r),this.emit(F.LAYOUTS_SCHEDULED,r),this._evaluateAndSwitchLayout(r,``),this._processScheduledCommands(),(e.settings?.statsEnabled===`On`||e.settings?.statsEnabled===`1`)&&(this.statsCollector?(I.info(`Stats enabled, submitting proof of play`),this.emit(F.SUBMIT_STATS_REQUEST)):I.warn(`Stats enabled but no StatsCollector provided`)),this.emit(F.SUBMIT_LOGS_REQUEST),this.emit(F.SUBMIT_FAULTS_REQUEST),!this.collectionInterval&&e.settings&&this.setupCollectionInterval(e.settings),this._faultReportingInterval||this._startFaultReportingAgent(),this.logUpcomingTimeline(),this.emit(F.COLLECTION_COMPLETE)}catch(e){if(this.hasCachedData())return I.warn(`Collection failed, falling back to cached data:`,e?.message||e),this.emit(F.COLLECTION_ERROR,e),this.collecting=!1,this.collectOffline();throw I.error(`Collection error:`,e),this.emit(F.COLLECTION_ERROR,e),e}finally{this.collecting=!1}}_processRegistration(e){if(this._offlineSave(`settings`,e),this.offlineMode&&(this.offlineMode=!1,I.info(`Back online — resuming normal collection`),this.emit(F.OFFLINE_MODE,!1),this._normalCollectInterval&&(this._setCollectionTimer(this._normalCollectInterval),this._normalCollectInterval=null,this._offlineRetrySeconds=0)),this.displaySettings&&e.settings){let n=this.displaySettings.applySettings(e.settings);n.changed.includes(`collectInterval`)&&this.updateCollectionInterval(n.settings.collectInterval),e.settings.logLevel&&t(e.settings.logLevel)&&(I.info(`Log level updated from CMS:`,e.settings.logLevel),this.emit(F.LOG_LEVEL_CHANGED,e.settings.logLevel))}if(this.schedule?.setDisplayProperties&&e.settings&&this.schedule.setDisplayProperties(e.settings),e.syncConfig){let t=JSON.stringify(e.syncConfig);t!==this._lastRawSyncConfig&&(this._lastRawSyncConfig=t,this.syncConfig=e.syncConfig,I.info(`Sync group:`,e.syncConfig.isLead?`LEAD`:`follower → ${e.syncConfig.syncGroup}`,`(switchDelay: ${e.syncConfig.syncSwitchDelay}ms, videoPauseDelay: ${e.syncConfig.syncVideoPauseDelay}ms)`),this.emit(F.SYNC_CONFIG,e.syncConfig))}if(this._applyTagConfig(e.tags),e.commands&&e.commands.length>0){this.displayCommands={};for(let t of e.commands)this.displayCommands[t.commandCode]=t;I.debug(`Display commands:`,Object.keys(this.displayCommands).join(`, `))}this.emit(F.REGISTER_COMPLETE,e)}_applyNewSchedule(e){this.emit(F.SCHEDULE_RECEIVED,e),this.schedule.setSchedule(e),this._executedCommands.clear(),this.updateDataConnectors(),this._offlineSave(`schedule`,e)}async initializeXmr(e){let t=e.settings?.xmrWebSocketAddress||e.settings?.xmrNetworkAddress;if(!t){I.warn(`XMR not configured: no xmrWebSocketAddress or xmrNetworkAddress in CMS settings`),this.emit(F.XMR_MISCONFIGURED,{reason:`missing`,message:`XMR address not configured in CMS. Go to CMS Admin → Settings → Configuration → XMR and set the WebSocket address.`});return}if(t.startsWith(`tcp://`)){I.warn(`XMR address uses tcp:// protocol which is not supported by PWA players: ${t}`),I.warn(`Configure XMR_WS_ADDRESS in CMS Admin → Settings → Configuration → XMR (e.g. wss://your-domain/xmr)`),this.emit(F.XMR_MISCONFIGURED,{reason:`wrong-protocol`,url:t,message:`XMR uses tcp:// protocol (not supported by PWA). Set XMR WebSocket Address to wss://your-domain/xmr in CMS Settings.`});return}if(/example\.(org|com|net)/i.test(t)){I.warn(`XMR address contains placeholder domain: ${t}`),I.warn(`Configure the real XMR address in CMS Admin → Settings → Configuration → XMR`),this.emit(F.XMR_MISCONFIGURED,{reason:`placeholder`,url:t,message:`XMR address is still the default placeholder (${t}). Update it in CMS Settings.`});return}let n=e.settings?.xmrCmsKey||e.settings?.serverKey||this.config.serverKey;I.debug(`XMR CMS Key:`,n?`present`:`missing`),this.xmr?this.xmr.isConnected()?I.debug(`XMR already connected`):(I.info(`XMR disconnected, attempting to reconnect...`),await this.xmr.start(t,n),this.emit(F.XMR_RECONNECTED,t)):(I.info(`Initializing XMR WebSocket:`,t),this.xmr=new this.XmrWrapper(this.config,this),await this.xmr.start(t,n),this.emit(F.XMR_CONNECTED,t))}setupCollectionInterval(e){let t=this.displaySettings?this.displaySettings.getCollectInterval():parseInt(e.collectInterval||`300`,10);this._setCollectionTimer(t),this.emit(F.COLLECTION_INTERVAL_SET,t)}updateCollectionInterval(e){this.collectionInterval&&(this._setCollectionTimer(e),this.emit(F.COLLECTION_INTERVAL_UPDATED,e))}_startFaultReportingAgent(){this._faultReportingInterval&&clearInterval(this._faultReportingInterval),I.info(`Fault reporting agent started (interval: ${this._faultReportingSeconds}s)`),this._faultReportingInterval=setInterval(()=>{this.emit(F.SUBMIT_FAULTS_REQUEST)},this._faultReportingSeconds*1e3)}_setCollectionTimer(e){this.collectionInterval&&clearInterval(this.collectionInterval),this._currentCollectInterval=e,I.info(`Collection interval: ${e}s`),this.collectionInterval=setInterval(()=>{I.debug(`Running scheduled collection cycle...`),this.collect().catch(e=>{I.error(`Collection error:`,e),this.emit(F.COLLECTION_ERROR,e)})},e*1e3)}async requestLayoutChange(e){I.info(`Layout change requested: ${e}`),this.currentLayoutId=null,this.emit(`layout-change-requested`,e)}clearPreparingLayout(){this._preparingLayoutId=null}setCurrentLayout(e){this.currentLayoutId=e,this._preparingLayoutId=null,this._lastLayoutChangeTime=new Date().toISOString(),this._statusCode=1,this.pendingLayouts.delete(e),this._layoutMediaStatus.delete(`${e}.xlf`),this.emit(`layout-current`,e),this._lastTimelineFingerprint=null,this.logUpcomingTimeline()}setPendingLayout(e,t){this.pendingLayouts.set(e,t),this.emit(`layout-pending`,e,t)}clearCurrentLayout(){this.currentLayoutId=null,this.emit(`layout-cleared`)}getNextLayout(){let e=this.schedule.popNextFromQueue(this._layoutDurations,this._queueOptions);if(!e){let e=this.schedule.schedule?.default;return e?{layoutId:u(e),layoutFile:e}:null}let t=u(e.layoutId);if(this.isLayoutBlacklisted(t)){let{queue:e}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions);for(let t=0;t<e.length-1;t++){let e=this.schedule.popNextFromQueue(this._layoutDurations,this._queueOptions);if(e){let t=u(e.layoutId);if(!this.isLayoutBlacklisted(t))return{layoutId:t,layoutFile:e.layoutId}}}I.warn(`All queued layouts are blacklisted, using current entry as fallback`)}return{layoutId:t,layoutFile:e.layoutId}}peekNextLayout(){let e=this.schedule.peekNextInQueue(this._layoutDurations,this._queueOptions);if(!e)return null;let t=u(e.layoutId);if(t===this.currentLayoutId){let e=this.schedule.peekAfterNext(this._layoutDurations,this._queueOptions);if(!e)return null;let t=u(e.layoutId);return t===this.currentLayoutId||this.isLayoutBlacklisted(t)?null:{layoutId:t,layoutFile:e.layoutId}}return this.isLayoutBlacklisted(t)?null:{layoutId:t,layoutFile:e.layoutId}}advanceToNextLayout(){if(this._layoutOverride){I.info(`Layout override active, not advancing schedule`);return}let e=this.getNextLayout();if(!e){if(this.currentLayoutId){I.info(`No layouts in queue, replaying ${this.currentLayoutId} to avoid blank screen`);let e=this.currentLayoutId;this.currentLayoutId=null,this._preparingLayoutId=e,this.emit(F.LAYOUT_PREPARE_REQUEST,e)}else I.info(`No layouts scheduled during advance`),this.emit(F.NO_LAYOUTS_SCHEDULED);return}let{layoutId:t,layoutFile:n}=e,r=this._layoutDurations.get(n)||`?`;if(this._lastTimeline&&this._lastTimeline.length>0){let e=this._lastTimeline.slice(0,2).map(e=>{let t=e.startTime.toLocaleTimeString(`en-GB`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`});return`${e.layoutFile}(${e.duration}s@${t})`});I.debug(`[Timeline] Layout transition: entering ${n} (${r}s), overlay top: [${e.join(`, `)}]`),this._lastTimeline[0].layoutFile!==n&&I.warn(`[Timeline] Mismatch: entering ${n} but overlay expects ${this._lastTimeline[0].layoutFile}`)}else I.debug(`[Timeline] Layout transition: entering ${n} (${r}s), no timeline data`);if(this.syncManager&&this.schedule.isSyncEvent(n))if(this.isSyncLead()){I.info(`[Sync] Lead requesting coordinated layout change: ${t}`),this._preparingLayoutId=t,this.emit(F.LAYOUT_PREPARE_REQUEST,t),this.syncManager.requestLayoutChange(t).catch(e=>{I.error(`[Sync] Layout change failed:`,e)});return}else if(this.syncManager.transport?.connected){I.info(`[Sync] Follower waiting for lead signal (not advancing independently)`);return}else I.warn(`[Sync] Follower: lead unreachable, advancing independently`);t===this.currentLayoutId&&(I.info(`Next layout ${t} is same as current, triggering replay`),this.currentLayoutId=null);let{queue:i}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions),a=this.schedule.getQueuePosition();I.info(`Advancing to layout ${t} (queue pos ${a}/${i.length})`),this._preparingLayoutId=t,this.emit(F.LAYOUT_PREPARE_REQUEST,t)}advanceToPreviousLayout(){if(this._layoutOverride){I.info(`Layout override active, not going back`);return}let{queue:e}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions);if(e.length<=1){I.info(`Single or empty queue, nothing to go back to`);return}let t=this.schedule.rewindQueue(2,this._layoutDurations,this._queueOptions);if(!t)return;let n=u(t.layoutId);if(n===this.currentLayoutId){I.info(`Previous layout is same as current, nothing to go back to`);return}I.info(`Going back to layout ${n}`),this.emit(F.LAYOUT_PREPARE_REQUEST,n)}notifyMediaReady(e,t=`media`){I.debug(`File ${e} ready (${t})`);for(let[n,r]of this.pendingLayouts.entries()){let i=t===`layout`&&n===parseInt(e),a=t===`media`&&r.includes(e);(i||a)&&(I.debug(`${t} ${e} was needed by pending layout ${n}, checking if ready...`),this.emit(F.CHECK_PENDING_LAYOUT,n,r))}}async notifyLayoutStatus(e){try{let t={currentLayoutId:e,deviceName:this.config?.displayName||``,displayName:this.config?.displayName||``,lastCommandSuccess:this._lastCommandSuccess??!0,code:this._statusCode,lastLayoutChangeTime:this._lastLayoutChangeTime||new Date().toISOString()};this.config?.latitude&&(t.latitude=this.config.latitude),this.config?.longitude&&(t.longitude=this.config.longitude),this._lanIpAddress&&(t.lanIpAddress=this._lanIpAddress),await this.xmds.notifyStatus(t),this.emit(`status-notified`,e)}catch(t){I.warn(`Failed to notify status:`,t),this.emit(`status-notify-failed`,e,t)}}reportGeoLocation(e){let t=parseFloat(e?.latitude),n=parseFloat(e?.longitude);if(isNaN(t)||isNaN(n)){I.warn(`reportGeoLocation: invalid coordinates`,e);return}I.info(`Geo location from CMS: ${t.toFixed(4)}, ${n.toFixed(4)}`),this.schedule?.setLocation&&this.schedule.setLocation(t,n),this.emit(`location-updated`,{latitude:t,longitude:n,source:`cms`}),this.checkSchedule()}async requestGeoLocation(){if(this._geoCache&&Date.now()-this._geoCache.ts<1800*1e3)return this._geoCache.location;if(!this._browserGeoFailed){let e=await this._tryBrowserGeolocation();if(e)return this._cacheGeo(this._applyLocation(e.latitude,e.longitude,`browser`));this._browserGeoFailed=!0}let e=this.config?.googleGeoApiKey;if(e){let t=await this._tryGoogleGeolocation(e);if(t)return this._cacheGeo(this._applyLocation(t.latitude,t.longitude,`google-api`))}let t=await this._tryIpGeolocation();return t?this._cacheGeo(this._applyLocation(t.latitude,t.longitude,`ip-geolocation`)):(I.warn(`All geolocation methods failed`),null)}_cacheGeo(e){return this._geoCache={location:e,ts:Date.now()},e}_applyTagConfig(e){if(!Array.isArray(e)||e.length===0)return;let t={geoApiKey:`googleGeoApiKey`};for(let n of e){let e=n.indexOf(`|`);if(e===-1)continue;let r=n.substring(0,e),i=n.substring(e+1),a=t[r];a&&i&&this.config&&(I.info(`Config from CMS tag: ${r} → ${a}`),this.config[a]=i)}}_applyLocation(e,t,n){return I.info(`Geolocation (${n}): ${e.toFixed(4)}, ${t.toFixed(4)}`),this.schedule?.setLocation&&this.schedule.setLocation(e,t),this.emit(`location-updated`,{latitude:e,longitude:t,source:n}),this.checkSchedule(),{latitude:e,longitude:t}}async _tryBrowserGeolocation(){if(typeof navigator>`u`||!navigator.geolocation)return null;try{let e=await new Promise((e,t)=>{navigator.geolocation.getCurrentPosition(e,t,{timeout:1e4,maximumAge:3e5,enableHighAccuracy:!1})});return{latitude:e.coords.latitude,longitude:e.coords.longitude}}catch(e){return I.warn(`Browser geolocation failed:`,e?.message||e),null}}async _tryGoogleGeolocation(e){try{let t=await fetch(`https://www.googleapis.com/geolocation/v1/geolocate?key=${e}`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({considerIp:!0}),signal:AbortSignal.timeout(5e3)});if(!t.ok)return I.warn(`Google Geolocation API returned ${t.status}`),null;let n=await t.json();return n.location?.lat!=null&&n.location?.lng!=null?{latitude:n.location.lat,longitude:n.location.lng}:null}catch(e){return I.warn(`Google Geolocation API failed:`,e?.message||e),null}}async _tryIpGeolocation(){let e=[{url:`https://ipapi.co/json/`,parse:e=>e.latitude!=null&&e.longitude!=null?{latitude:e.latitude,longitude:e.longitude}:null},{url:`https://freeipapi.com/api/json`,parse:e=>e.latitude!=null&&e.longitude!=null?{latitude:e.latitude,longitude:e.longitude}:null}];for(let t of e)try{let e=await fetch(t.url,{signal:AbortSignal.timeout(5e3)});if(!e.ok)continue;let n=await e.json(),r=t.parse(n);if(r)return r}catch(e){I.warn(`IP geolocation (${t.url}) failed:`,e?.message||e)}return null}checkSchedule(){let e=this.schedule.getCurrentLayouts();this.emit(F.LAYOUTS_SCHEDULED,e),this._evaluateAndSwitchLayout(e,``)}async captureScreenshot(){I.info(`Screenshot requested`),this.emit(F.SCREENSHOT_REQUEST)}async changeLayout(e,t){I.info(`Layout change requested via XMR:`,e);let n=parseInt(e,10),r=t?.duration||0;this._layoutOverride={layoutId:n,type:`change`,duration:r,changeMode:t?.changeMode||`replace`},this.currentLayoutId=null,this.emit(F.LAYOUT_PREPARE_REQUEST,n),this._scheduleAutoRevert(n,r,`Layout override`)}async overlayLayout(e,t){I.info(`Overlay layout requested via XMR:`,e);let n=parseInt(e,10),r=t?.duration||0;this._layoutOverride={layoutId:n,type:`overlay`,duration:r},this.emit(F.OVERLAY_LAYOUT_REQUEST,n),this._scheduleAutoRevert(n,r,`Overlay`)}async revertToSchedule(){I.info(`Reverting to scheduled content`),this._layoutOverride=null,this.currentLayoutId=null,this.emit(F.REVERT_TO_SCHEDULE);let e=this.schedule.getCurrentLayouts();if(e.length>0){let t=e[0],n=u(t);this.emit(F.LAYOUT_PREPARE_REQUEST,n)}else this.emit(F.NO_LAYOUTS_SCHEDULED)}async purgeAll(){return I.info(`Purge all cache requested via XMR`),this._lastCheckRf=null,this._lastCheckSchedule=null,this.emit(F.PURGE_ALL_REQUEST),this.collectNow()}async executeCommand(e,t){if(I.info(`Execute command requested:`,e),!t||!t[e]){I.warn(`Unknown command code:`,e),this._lastCommandSuccess=!1,this.emit(F.COMMAND_RESULT,{code:e,success:!1,reason:`Unknown command`});return}let n=t[e],r=n.commandString||n.value||``;if(r.startsWith(`http|`)){let t=r.split(`|`),n=t[1],i=t[2]||`application/json`;try{let t=await fetch(n,{method:`POST`,headers:{"Content-Type":i},signal:AbortSignal.timeout(1e4)}),r=t.ok;this._lastCommandSuccess=r,I.info(`HTTP command ${e} result: ${t.status}`),this.emit(F.COMMAND_RESULT,{code:e,success:r,status:t.status})}catch(t){this._lastCommandSuccess=!1,I.error(`HTTP command ${e} failed:`,t),this.emit(F.COMMAND_RESULT,{code:e,success:!1,reason:t.message})}}else I.info(`Delegating non-HTTP command to platform layer:`,e),this.emit(F.EXECUTE_NATIVE_COMMAND,{code:e,commandString:r})}triggerWebhook(e){I.info(`Webhook trigger from XMR:`,e),this.handleTrigger(e)}refreshDataConnectors(){I.info(`Data connector refresh requested via XMR`),this.dataConnectorManager.refreshAll(),this.emit(`data-connectors-refreshed`)}async submitMediaInventory(e){if(!(!e||e.length===0))try{let t=Math.floor(Date.now()/1e3),n=`<files>${e.filter(e=>[`media`,`layout`,`resource`,`dependency`,`widget`].includes(e.type)).map(e=>{let n=e.complete===void 0||e.complete?`1`:`0`,r=e.fileType?` fileType="${e.fileType}"`:``;return`<file type="${e.type}" id="${e.id}" complete="${n}" md5="${e.md5||``}" lastChecked="${t}"${r}/>`}).join(``)}</files>`;await this.xmds.mediaInventory(n),I.info(`Media inventory submitted: ${e.length} files`),this.emit(`media-inventory-submitted`,e.length)}catch(e){I.warn(`MediaInventory submission failed:`,e)}}async blackList(e,t,n){try{await this.xmds.blackList(e,t,n),this.emit(`media-blacklisted`,{mediaId:e,type:t,reason:n})}catch(e){I.warn(`BlackList failed:`,e)}}reportLayoutFailure(e,t){let n=Number(e);this._statusCode=3;let{blacklisted:r,failures:i}=this._layoutBlacklist.recordFailure(n,t);r&&i===3&&(this.emit(`layout-blacklisted`,{layoutId:n,reason:t,failures:i}),this.blackList(n,`layout`,t))}reportLayoutSuccess(e){this._layoutBlacklist.recordSuccess(Number(e))&&this.emit(`layout-unblacklisted`,{layoutId:Number(e)})}isLayoutBlacklisted(e){return this._layoutBlacklist.isBlacklisted(e)}getBlacklistedLayouts(){return this._layoutBlacklist.getBlacklistedIds()}resetBlacklist(){this._layoutBlacklist.reset()>0&&this.emit(`blacklist-reset`)}isLayoutOverridden(){return this._layoutOverride!==null}handleTrigger(e){let t=this.schedule.findActionByTrigger(e);if(!t){I.debug(`No scheduled action matches trigger:`,e);return}switch(I.info(`Action triggered: ${t.actionType} (trigger: ${e})`),t.actionType){case`navLayout`:case`navigateToLayout`:t.layoutCode&&this.changeLayout(t.layoutCode);break;case`navWidget`:case`navigateToWidget`:this.emit(F.NAVIGATE_TO_WIDGET,t);break;case`command`:this.emit(`execute-command`,t.commandCode);break;default:I.warn(`Unknown action type:`,t.actionType)}}updateDataConnectors(){let e=this.schedule.getDataConnectors();e.length>0&&I.info(`Configuring ${e.length} data connector(s)`),this.dataConnectorManager.setConnectors(e),e.length>0&&(this.dataConnectorManager.startPolling(),this.emit(`data-connectors-started`,e.length))}_processScheduledCommands(){if(!this.schedule?.getCommands)return;let e=this.schedule.getCommands();if(e.length===0)return;let t=new Date;for(let n of e){if(!n.code||!n.date)continue;let e=`${n.code}|${n.date}`;if(this._executedCommands.has(e))continue;let r=new Date(n.date);if(isNaN(r.getTime())){I.warn(`Scheduled command has invalid date:`,n.date);continue}t>=r&&(I.info(`Executing scheduled command: ${n.code} (scheduled: ${n.date})`),this._executedCommands.add(e),n.code===`collectNow`?setTimeout(()=>this.collectNow().catch(e=>I.error(`collectNow command failed:`,e)),0):this.emit(F.SCHEDULED_COMMAND,n))}}async _fetchWeatherData(){if(!(!this.xmds?.getWeather||!this.schedule?.setWeatherData))try{let e=await this.xmds.getWeather(),t=typeof e==`string`?JSON.parse(e):e;this.schedule.setWeatherData(t),I.info(`Weather data updated:`,Object.keys(t).join(`, `))}catch(e){I.warn(`GetWeather failed (non-critical):`,e?.message||e)}}getDataConnectorManager(){return this.dataConnectorManager}setSyncManager(e){this.syncManager=e,I.info(`SyncManager attached:`,e.isLead?`LEAD`:`FOLLOWER`)}isInSyncGroup(){return this.syncConfig!==null}isSyncLead(){return this.syncConfig?.isLead===!0}getSyncConfig(){return this.syncConfig}logUpcomingTimeline(){if(!this.schedule.getLayoutsAtTime)return;let e=[...this._layoutDurations.entries()].sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}:${t}`).join(`|`),t=[...this._layoutMediaStatus.entries()].sort(([e],[t])=>e.localeCompare(t)).map(([e,t])=>`${e}:${t.ready}:${t.missingKey}`).join(`|`),n=[...this.pendingLayouts.keys()].sort().join(`,`),r=this.schedule.getQueuePosition()||0,i=`${this._lastCheckSchedule}|${e}|${this.currentLayoutId}|${r}|${t}|${n}`;if(i===this._lastTimelineFingerprint&&this._lastTimeline){this.emit(F.TIMELINE_UPDATED,this._lastTimeline);return}let{queue:a}=this.schedule.getScheduleQueue(this._layoutDurations,this._queueOptions),o=d(a,this.schedule.getQueuePosition(),{currentLayoutStartedAt:this._lastLayoutChangeTime?new Date(this._lastLayoutChangeTime):null,defaultLayout:this.schedule.schedule?.default||null,durations:this._layoutDurations});if(o.length===0)return;for(let e of o){let t=parseInt(e.layoutFile.replace(`.xlf`,``),10),n=this.pendingLayouts.get(t);if(n&&n.length>0)e.missingMedia=n.map(String);else{let t=this._layoutMediaStatus.get(e.layoutFile);t&&!t.ready&&t.missing.length>0&&(e.missingMedia=t.missing.map(String))}}this._lastTimelineFingerprint=i,this._lastTimeline=o;let s=o.slice(0,20).map(e=>{let t=e.startTime.toLocaleTimeString(`en-GB`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}),n=e.endTime.toLocaleTimeString(`en-GB`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}),r=e.missingMedia?` [MISSING: ${e.missingMedia.length} files]`:``;return` ${t}-${n} Layout ${e.layoutFile} (${e.duration}s)${e.isDefault?` [default]`:``}${r}`});for(let e of o)e.missingMedia&&I.warn(`[Timeline] Layout ${e.layoutFile}: ${e.missingMedia.length} files missing`);I.info(`[Timeline] Next ${o.length} plays:\n${s.join(`
629
+ `)}`),this.emit(F.TIMELINE_UPDATED,o)}setLayoutMediaStatus(e,t,n=[]){let r=this._layoutMediaStatus.get(e),i=n.slice().sort().join(`,`);r&&r.ready===t&&r.missingKey===i||(this._layoutMediaStatus.set(e,{ready:t,missing:n,missingKey:i}),this._lastTimelineFingerprint=null)}recordLayoutDuration(e,t,n=!1){let r=String(e).replace(`.xlf`,``),i=r+`.xlf`;if(this._finalDurations.has(r))return;let a=this._layoutDurations.get(e);a===t&&!n||(this._layoutDurations.set(r,t),this._layoutDurations.set(i,t),n&&(this._finalDurations.add(r),this._finalDurations.add(i)),I.debug(`[Timeline] Duration corrected: layout ${e} ${a||`?`}s → ${t}s${n?` (final)`:``}`),this.schedule.invalidateQueue(),this._timelineRecalcTimer&&clearTimeout(this._timelineRecalcTimer),this._timelineRecalcTimer=setTimeout(()=>{this._timelineRecalcTimer=null,this.logUpcomingTimeline(),this._offlineSave(`durations`,[...this._layoutDurations.entries()]),this._offlineSave(`finalDurations`,[...this._finalDurations]),this._offlineSave(`durationsVersion`,2)},500))}cleanup(){this.collectionInterval&&=(clearInterval(this.collectionInterval),null),this._faultReportingInterval&&=(clearInterval(this._faultReportingInterval),null),this._timelineRecalcTimer&&=(clearTimeout(this._timelineRecalcTimer),null),this.xmr&&=(this.xmr.stop(),null),this.syncManager&&=(this.syncManager.stop(),null),this.dataConnectorManager.cleanup(),this.emit(`cleanup-complete`),this.removeAllListeners()}getCurrentLayoutId(){return this.currentLayoutId}getLayoutDuration(e){let t=String(e);return this._layoutDurations.get(`${t}.xlf`)||this._layoutDurations.get(t)}isCollecting(){return this.collecting}getPendingLayouts(){return Array.from(this.pendingLayouts.keys())}},me=C({CORE_EVENTS:()=>F,DataConnectorManager:()=>N,PlayerCore:()=>B,VERSION:()=>he}),he=ce.version,V=class{overlay=null;config;updateTimer=null;_visible=!1;_getProgress=null;constructor(e){this.config={updateInterval:1e3,autoHide:!0,...e},this.config.enabled&&(this.createOverlay(),this.overlay.style.display=`none`)}createOverlay(){this.overlay=document.createElement(`div`),this.overlay.id=`download-overlay`,this.overlay.style.cssText=`
630
+ position: fixed;
631
+ top: 1.5vh;
632
+ left: 1.5vw;
633
+ background: rgba(0, 0, 0, 0.88);
634
+ color: #fff;
635
+ font-family: system-ui, -apple-system, sans-serif;
636
+ font-size: 1.4vw;
637
+ padding: 1vh 1.2vw;
638
+ border-radius: 0.4vw;
639
+ border: 1px solid rgba(255, 255, 255, 0.25);
640
+ z-index: 999999;
641
+ max-width: 35vw;
642
+ box-shadow: 0 0.3vh 1.2vw rgba(0, 0, 0, 0.5);
643
+ `,document.body.appendChild(this.overlay)}setProgressCallback(e){this._getProgress=e}updateOverlay(){if(!this.overlay)return;let e=this._getProgress?this._getProgress():{},t=this.renderStatus(e);t?(this.overlay.innerHTML=t,this.overlay.style.display=`block`):this._visible?(this.overlay.innerHTML=`<div style="color: #6c6; font-size: 1.4vw;">✓ All downloads complete</div>`,this.overlay.style.display=`block`):(this.stopUpdating(),this.overlay.style.display=`none`)}renderStatus(e){let t=e||{};if(Object.keys(t).length===0)return this.config.autoHide?``:`<div style="color: #6c6;">✓ No downloads</div>`;let n=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw;">Downloads: ${Object.keys(t).length} active</div>`;for(let[e,r]of Object.entries(t)){let t=this.extractFilename(e),i=Math.round(r.percent||0),a=this.formatBytes(r.downloaded||0),o=this.formatBytes(r.total||0);n+=`
644
+ <div style="margin-bottom: 0.6vh; padding-bottom: 0.6vh; border-bottom: 1px solid rgba(255,255,255,0.1);">
645
+ <div style="font-size: 1.2vw; margin-bottom: 0.2vh;">${t}</div>
646
+ <div style="background: rgba(255,255,255,0.1); height: 0.4vh; border-radius: 0.2vw; overflow: hidden;">
647
+ <div style="width: ${i}%; height: 100%; background: #4a9eff; transition: width 0.3s;"></div>
648
+ </div>
649
+ <div style="color: #999; font-size: 1.1vw; margin-top: 0.2vh;">
650
+ ${i}% · ${a} / ${o}
651
+ </div>
652
+ </div>
653
+ `}return n}extractFilename(e){return e||`unknown`}formatBytes(e){if(e<1024)return`${e} B`;let t=e/1024;if(t<1024)return`${t.toFixed(1)} KB`;let n=t/1024;return n<1024?`${n.toFixed(1)} MB`:`${(n/1024).toFixed(1)} GB`}toggle(){this.overlay&&(this._visible=!this._visible,this._visible?(this.overlay.style.display=`block`,this.updateOverlay(),this.startUpdating()):(this.overlay.style.display=`none`,this.stopUpdating()))}startUpdating(){this.updateTimer||(this.updateTimer=window.setInterval(()=>{this.updateOverlay()},this.config.updateInterval),this.updateOverlay())}stopUpdating(){this.updateTimer&&=(clearInterval(this.updateTimer),null)}destroy(){this.stopUpdating(),this.overlay&&=(this.overlay.remove(),null)}setEnabled(e){this.config.enabled=e,e&&!this.overlay?this.createOverlay():!e&&this.overlay&&this.destroy()}};function ge(){let e=new URLSearchParams(window.location.search).get(`showDownloads`);if(e!==null)return{enabled:e!==`0`&&e!==`false`};let t=localStorage.getItem(`xibo_show_download_overlay`);return t===null?{enabled:!1}:{enabled:t===`true`}}var H=class{overlay=null;visible;timeline=[];currentLayoutId=null;layoutStartedAt=null;currentDuration=null;currentIsDefault=!1;previousLayout=null;offline=!1;onLayoutClick=null;refreshTimer=null;constructor(e=!1,t){this.visible=e,this.onLayoutClick=t||null,this.createOverlay(),this.visible||(this.overlay.style.display=`none`),this.refreshTimer=setInterval(()=>this.render(),5e3)}createOverlay(){this.overlay=document.createElement(`div`),this.overlay.id=`timeline-overlay`,this.overlay.style.cssText=`
654
+ position: fixed;
655
+ bottom: 1.5vh;
656
+ left: 1.5vw;
657
+ background: rgba(0, 0, 0, 0.88);
658
+ color: #fff;
659
+ font-family: system-ui, -apple-system, sans-serif;
660
+ font-size: 1.4vw;
661
+ padding: 1vh 1.2vw;
662
+ border-radius: 0.4vw;
663
+ border: 1px solid rgba(255, 255, 255, 0.25);
664
+ z-index: 999999;
665
+ max-width: 35vw;
666
+ box-shadow: 0 0.3vh 1.2vw rgba(0, 0, 0, 0.5);
667
+ pointer-events: auto;
668
+ `,this.overlay.addEventListener(`click`,e=>{let t=e.target.closest(`[data-layout-id]`);if(!t||!this.onLayoutClick)return;let n=parseInt(t.dataset.layoutId,10);isNaN(n)||n===this.currentLayoutId||this.onLayoutClick(n)}),document.body.appendChild(this.overlay)}toggle(){this.visible=!this.visible,this.overlay&&(this.overlay.style.display=this.visible?`block`:`none`),this.visible&&this.render(),localStorage.setItem(`xibo_show_timeline_overlay`,String(this.visible))}setOffline(e){this.offline=e,this.render()}update(e,t,n){t!==null&&(t!==this.currentLayoutId&&(this.currentLayoutId!==null&&this.currentDuration!==null&&this.layoutStartedAt!==null&&(this.previousLayout={id:this.currentLayoutId,duration:this.currentDuration,startedAt:this.layoutStartedAt}),this.currentLayoutId=t,this.currentIsDefault=!1),this.layoutStartedAt=Date.now(),n!==void 0&&(this.currentDuration=n)),e!==null&&(this.timeline=e),this.render()}render(){if(!this.overlay||!this.visible)return;if(this.timeline.length===0&&!this.previousLayout&&!this.currentLayoutId){this.overlay.innerHTML=`<div style="color: #999;">Timeline — no upcoming layouts</div>`;return}let e=Date.now(),t=this.onLayoutClick!==null,n=!1,r=[];for(let e of this.timeline){let t=u(e.layoutFile);if(!n&&t===this.currentLayoutId){n=!0;continue}r.push(e)}let i=(this.previousLayout?1:0)+(this.currentLayoutId?1:0)+r.length,a=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw; color: #ccc;">Timeline (${i} scheduled)${this.offline?` <span style="color: #ff4444; font-size: 1.1vw;">OFFLINE</span>`:``}</div>`,o=0;if(this.previousLayout&&o<8){let e=this.previousLayout,n=this.formatDuration(e.duration).padStart(7).replace(/ /g,`&nbsp;`),r=`#${e.id}`.padEnd(6).replace(/ /g,`&nbsp;`),i=new Date(e.startedAt),s=new Date(e.startedAt+e.duration*1e3),c=`${this.formatTime(i)}–${this.formatTime(s)} `,l=t?`cursor: pointer;`:``,u=t?`onmouseover="this.style.background='rgba(255,255,255,0.1)'" onmouseout="this.style.background='none'"`:``;a+=`<div data-layout-id="${e.id}" style="border-left: 0.25vw solid #555; padding-left: 0.6vw; color: #666; text-decoration: line-through; ${l} margin-bottom: 0.3vh; font-family: monospace; font-size: 1.3vw; line-height: 1.5; white-space: nowrap;" ${u}>`,a+=`${c}${r}${n}`,a+=`</div>`,o++}if(this.currentLayoutId!==null&&o<8){let t,n=``;if(this.currentDuration!==null&&this.layoutStartedAt!==null){let r=(e-this.layoutStartedAt)/1e3,i=Math.max(0,Math.round(this.currentDuration-r));t=this.formatDuration(i);let a=new Date(this.layoutStartedAt),o=new Date(this.layoutStartedAt+this.currentDuration*1e3);n=`${this.formatTime(a)}–${this.formatTime(o)} `}else t=`---`;let r=t.padStart(7).replace(/ /g,`&nbsp;`),i=`#${this.currentLayoutId}`.padEnd(6).replace(/ /g,`&nbsp;`);a+=`<div data-layout-id="${this.currentLayoutId}" style="border-left: 0.25vw solid #4a9eff; padding-left: 0.6vw; color: #fff; font-weight: 600; margin-bottom: 0.3vh; font-family: monospace; font-size: 1.3vw; line-height: 1.5; white-space: nowrap;">`,a+=`${n}${i}${r}`,this.currentIsDefault&&(a+=` <span style="color: #888;">[def]</span>`),a+=`</div>`,o++}let s=this.layoutStartedAt!==null&&this.currentDuration!==null?this.layoutStartedAt+this.currentDuration*1e3:e;for(let e of r){if(o>=8)break;let n=u(e.layoutFile),r=e.missingMedia&&e.missingMedia.length>0,i=this.formatDuration(e.duration),c=s+e.duration*1e3,l=this.formatTime(new Date(s)),d=this.formatTime(new Date(c)),f,p;r?(f=`border-left: 0.25vw solid #ff4444; padding-left: 0.6vw;`,p=`color: #ff6666;`):(f=`padding-left: 0.85vw;`,p=`color: #aaa;`),a+=`<div data-layout-id="${n}" style="${f} ${p} ${t?`cursor: pointer;`:``} margin-bottom: 0.3vh; font-family: monospace; font-size: 1.3vw; line-height: 1.5; white-space: nowrap;" ${t?`onmouseover="this.style.background='rgba(255,255,255,0.1)'" onmouseout="this.style.background='none'"`:``}>`;let m=`#${n}`.padEnd(6).replace(/ /g,`&nbsp;`),h=i.padStart(7).replace(/ /g,`&nbsp;`);if(a+=`${l}–${d} ${m}${h}`,e.isDefault&&(a+=` <span style="color: #888;">[def]</span>`),r){let t=e.missingMedia.join(`, `);a+=` <span style="color: #ff4444; font-size: 1.1vw;" title="Missing: ${t}">⚠ ${e.missingMedia.length}</span>`}if(e.hidden&&e.hidden.length>0){let t=e.hidden.map(e=>`#${e.file.replace(`.xlf`,``)} (p${e.priority})`).join(`, `);a+=` <span style="color: #8899aa; font-size: 1.1vw;" title="Also scheduled: ${t}">+${e.hidden.length}</span>`}a+=`</div>`,s=c,o++}i>8&&(a+=`<div style="padding-left: 0.85vw; color: #888; font-size: 1.1vw; margin-top: 0.3vh;">+${i-8} more</div>`),this.overlay.innerHTML=a}formatTime(e){return e.toLocaleTimeString(`en-GB`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`})}formatDuration(e){let t=Math.floor(e/60),n=Math.round(e%60);return t>0?`${t}m ${n.toString().padStart(2,`0`)}s`:`${n}s`}destroy(){this.refreshTimer&&=(clearInterval(this.refreshTimer),null),this.overlay&&=(this.overlay.remove(),null)}};function _e(){let e=new URLSearchParams(window.location.search).get(`showTimeline`);if(e!==null)return e!==`0`&&e!==`false`;let t=localStorage.getItem(`xibo_show_timeline_overlay`);return t===null?!1:t===`true`}var U=c(`SetupOverlay`),W=class{backdrop=null;gateCard=null;iframe=null;cancelBtn=null;visible=!1;show(){this.visible||(this.visible=!0,this.backdrop||this.create(),this.showGate(),this.backdrop.style.display=`flex`,U.info(`[SetupOverlay] Opened`))}hide(){this.visible&&(this.visible=!1,this.backdrop&&(this.backdrop.style.display=`none`),this.iframe&&(this.iframe.src=`about:blank`,this.iframe.style.display=`none`),U.info(`[SetupOverlay] Closed`))}toggle(){this.visible?this.hide():this.show()}isVisible(){return this.visible}showGate(){this.gateCard&&(this.gateCard.style.display=`block`),this.iframe&&(this.iframe.style.display=`none`),this.cancelBtn&&(this.cancelBtn.style.display=`none`);let e=this.gateCard?.querySelector(`#gate-key`);e&&(e.value=``,requestAnimationFrame(()=>e.focus()));let t=this.gateCard?.querySelector(`#gate-error`);t&&(t.style.display=`none`)}showSetup(){this.gateCard&&(this.gateCard.style.display=`none`),this.cancelBtn&&(this.cancelBtn.style.display=`block`),this.iframe&&(this.iframe.style.display=`block`,this.iframe.src=`./setup.html?unlocked=1`)}create(){this.backdrop=document.createElement(`div`),this.backdrop.id=`setup-overlay-backdrop`,this.backdrop.style.cssText=`
669
+ position: fixed;
670
+ inset: 0;
671
+ background: rgba(0, 0, 0, 0.85);
672
+ z-index: 1000000;
673
+ display: none;
674
+ align-items: center;
675
+ justify-content: center;
676
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
677
+ `,this.cancelBtn=document.createElement(`button`),this.cancelBtn.textContent=`Cancel`,this.cancelBtn.style.cssText=`
678
+ position: absolute;
679
+ top: 12px;
680
+ right: 16px;
681
+ background: transparent;
682
+ border: 1px solid rgba(255, 255, 255, 0.3);
683
+ color: #aaa;
684
+ font-size: 14px;
685
+ padding: 6px 18px;
686
+ border-radius: 6px;
687
+ cursor: pointer;
688
+ z-index: 1000001;
689
+ display: none;
690
+ transition: background 0.2s, color 0.2s;
691
+ `,this.cancelBtn.addEventListener(`mouseenter`,()=>{this.cancelBtn.style.background=`rgba(255,255,255,0.1)`,this.cancelBtn.style.color=`#fff`}),this.cancelBtn.addEventListener(`mouseleave`,()=>{this.cancelBtn.style.background=`transparent`,this.cancelBtn.style.color=`#aaa`}),this.cancelBtn.addEventListener(`click`,()=>this.hide()),this.gateCard=document.createElement(`div`),this.gateCard.style.cssText=`
692
+ background: #2A2A2A;
693
+ border-radius: 16px;
694
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
695
+ padding: 48px;
696
+ max-width: 480px;
697
+ width: 90vw;
698
+ color: #E0E0E0;
699
+ `,this.gateCard.innerHTML=`
700
+ <div style="text-align: center; margin-bottom: 32px;">
701
+ <div style="font-size: 36px; font-weight: 700; color: #fff; letter-spacing: -0.5px;">
702
+ <span style="color: #0097D8;">xibo</span> player
703
+ </div>
704
+ <div style="font-size: 14px; color: #888; margin-top: 4px;">PWA Digital Signage</div>
705
+ </div>
706
+ <div style="font-size: 18px; font-weight: 600; color: #fff; margin-bottom: 8px; text-align: center;">
707
+ Reconfigure Display
708
+ </div>
709
+ <div style="font-size: 13px; color: #888; margin-bottom: 20px; text-align: center; line-height: 1.5;">
710
+ Enter the current CMS Key to change settings.
711
+ </div>
712
+ <form id="gate-form">
713
+ <div style="margin-bottom: 20px;">
714
+ <label style="display: block; margin-bottom: 6px; color: #AAA; font-size: 13px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">
715
+ CMS Key
716
+ </label>
717
+ <input type="password" id="gate-key" placeholder="Current CMS key" required
718
+ style="width: 100%; padding: 12px 14px; background: #1D1D1D; border: 2px solid #3A3A3A; border-radius: 8px; font-size: 15px; color: #E0E0E0; transition: border-color 0.2s; box-sizing: border-box;">
719
+ </div>
720
+ <button type="submit" style="width: 100%; padding: 14px; background: #0097D8; color: white; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background 0.2s, transform 0.1s;">
721
+ Unlock
722
+ </button>
723
+ </form>
724
+ <div id="gate-error" style="margin-top: 16px; padding: 12px 14px; background: rgba(244, 67, 54, 0.15); border: 1px solid rgba(244, 67, 54, 0.3); border-radius: 8px; color: #EF9A9A; font-size: 14px; display: none;"></div>
725
+ <button id="gate-cancel" style="width: 100%; padding: 14px; background: transparent; border: 1px solid #3A3A3A; color: #AAA; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; margin-top: 8px; transition: background 0.2s;">
726
+ Cancel
727
+ </button>
728
+ `,this.iframe=document.createElement(`iframe`),this.iframe.style.cssText=`
729
+ width: 100%;
730
+ height: 100%;
731
+ border: none;
732
+ background: #1D1D1D;
733
+ display: none;
734
+ `,this.iframe.addEventListener(`load`,()=>{try{if((this.iframe.contentWindow?.location?.href||``).includes(`index.html`)){this.hide(),window.location.reload();return}let e=this.iframe.contentDocument;if(!e)return;e.addEventListener(`keydown`,e=>{e.key===`Escape`&&(e.preventDefault(),this.hide())})}catch{}}),this.backdrop.appendChild(this.cancelBtn),this.backdrop.appendChild(this.gateCard),this.backdrop.appendChild(this.iframe),document.body.appendChild(this.backdrop);let e=this.gateCard.querySelector(`#gate-form`),t=this.gateCard.querySelector(`#gate-key`),n=this.gateCard.querySelector(`#gate-error`),r=this.gateCard.querySelector(`#gate-cancel`);t.addEventListener(`focus`,()=>{t.style.borderColor=`#0097D8`}),t.addEventListener(`blur`,()=>{t.style.borderColor=`#3A3A3A`}),e.addEventListener(`submit`,e=>{e.preventDefault(),t.value.trim()===i.cmsKey?this.showSetup():(n.textContent=`Incorrect CMS key`,n.style.display=`block`,t.focus(),t.select())}),r.addEventListener(`click`,()=>this.hide()),this.backdrop.addEventListener(`keydown`,e=>{e.key===`Escape`&&(e.preventDefault(),this.hide()),e.stopPropagation()})}},G=c(`PWA`),K=s.slice(1),q=new URL(`./`,window.location.href).pathname.replace(/\/$/,``),ve,J,Y,ye,be,xe,Se,X,Z,Ce,we,Q,Te,Ee,De,Oe,$={},ke=class{renderer;core;xmds;downloadOverlay=null;timelineOverlay=null;setupOverlay=null;statsCollector=null;logReporter=null;displaySettings=null;currentScheduleId=-1;scheduledLayoutIds=new Set;preparingLayoutId=null;_pendingRetryLayoutId=null;_screenshotInterval=null;_screenshotMethod=null;_html2canvasMod=null;_screenshotInFlight=!1;_wakeLock=null;syncManager=null;_currentLayoutEnableStat=!0;_probeTimer=null;_mediaStatusTimer=null;_pendingFollowerStats=null;_pendingFollowerLogs=null;_iframeObserver=null;_swIcHandler=null;_chunkConfig=null;_fileIdToSaveAs=new Map;_cachedMediaKeys=new Set;protocolDetector=null;async init(){if(G.info(`Initializing player with RendererLite + PlayerCore...`),await this.loadCoreModules(),`serviceWorker`in navigator)try{let e=await navigator.serviceWorker.register(`${q}/sw-pwa.js?v=${Date.now()}`,{scope:`${q}/`,type:`module`,updateViaCache:`none`});G.info(`Service Worker registered for offline mode:`,e.scope),navigator.storage&&navigator.storage.persist&&(await navigator.storage.persist()?G.info(`Persistent storage granted - cache won't be evicted`):G.warn(`Persistent storage denied - cache may be evicted`))}catch(e){G.warn(`Service Worker registration failed:`,e)}G.info(`Initializing cache clients...`),X=new p;let{calculateChunkConfig:e}=await O(async()=>{let{calculateChunkConfig:e}=await import(`./src-DgZHc2Af.js`);return{calculateChunkConfig:e}},__vite__mapDeps([0,1]),import.meta.url);this._chunkConfig=e(G),Z=new f({concurrency:this._chunkConfig.concurrency,chunkSize:this._chunkConfig.chunkSize,chunksPerFile:2}),G.info(`Cache clients ready — StoreClient + DownloadManager`);let t=document.getElementById(`player-container`);if(!t)throw Error(`No #player-container found`);this.renderer=new A({cmsUrl:Y.cmsUrl,hardwareKey:Y.hardwareKey},t,{fileIdToSaveAs:this._fileIdToSaveAs,getWidgetHtml:async e=>{let t=`${s}/widgets/${e.layoutId}/${e.regionId}/${e.id}`;G.debug(`Looking for widget HTML at: ${t}`,e);try{if(await X.has(`${K}/widgets`,`${e.layoutId}/${e.regionId}/${e.id}`))return G.debug(`Widget HTML found in store, using mirror URL for iframe`),{url:t,fallback:e.raw||``};G.warn(`No widget HTML found in store: ${t}`)}catch(t){G.error(`Failed to check widget HTML for ${e.id}:`,t)}return G.warn(`Using widget.raw fallback for ${e.id}`),e.raw||``}}),this.core=new B({config:Y,xmds:this.xmds,cache:X,schedule:J,renderer:this.renderer,xmrWrapper:Se,statsCollector:this.statsCollector,displaySettings:this.displaySettings,cmsId:Y.activeCmsId}),this.setupCoreEventHandlers(),this.setupRendererEventHandlers(),this.setupInteractiveControl(),this.setupDataConnectorNotify(),this.setupRemoteControls(),this.updateConfigDisplay(),window.addEventListener(`online`,()=>{G.info(`Browser reports online — triggering immediate collection`),this.updateStatus(`Back online, syncing...`),this.removeOfflineIndicator(),this.core.collectNow().catch(e=>{G.error(`Failed to collect after coming online:`,e)})}),window.addEventListener(`offline`,()=>{G.warn(`Browser reports offline — continuing playback with cached data`),this.updateStatus(`Offline mode — using cached content`),this.showOfflineIndicator()});let n=(this.getControls().keyboard||{}).debugOverlays===!0,r=ge();r.enabled&&n&&(this.downloadOverlay=new V(r),this.downloadOverlay.setProgressCallback(()=>Z.getProgress()),G.info(`Download overlay enabled (hover bottom-right corner)`)),_e()&&n&&(this.timelineOverlay=new H(!0,e=>this.skipToLayout(e))),this.setupCertWarnings(),this.setupXmrWarning(),await this.requestWakeLock(),document.addEventListener(`visibilitychange`,()=>{document.visibilityState===`visible`&&this.requestWakeLock()}),await this.core.collect(),G.info(`Player initialized successfully`)}async requestWakeLock(){if(!(`wakeLock`in navigator)){G.debug(`Wake Lock API not supported`);return}try{this._wakeLock=await navigator.wakeLock.request(`screen`),G.info(`Screen Wake Lock acquired — display will stay on`),this._wakeLock.addEventListener(`release`,()=>{G.debug(`Screen Wake Lock released`),this._wakeLock=null})}catch(e){G.warn(`Wake Lock request failed:`,e?.message)}}setupCertWarnings(){let e=new Set;window.addEventListener(`cert-warning`,(t=>{let{host:n,error:r}=t.detail;if(e.has(n))return;e.add(n),G.warn(`Invalid SSL certificate accepted for stream: ${n} (${r})`);let i=document.getElementById(`overlay`),a=!1;if(!i){i=document.createElement(`div`),i.id=`overlay`;let e=document.createElement(`div`);e.id=`config-info`,i.appendChild(e);let t=document.createElement(`div`);t.id=`status`,i.appendChild(t),document.body.appendChild(i),a=!0}let o=document.getElementById(`cert-warnings`);if(!o){o=document.createElement(`span`),o.id=`cert-warnings`,o.style.cssText=`color: #ffaa33; flex: 0 0 auto;`;let e=document.getElementById(`status`);i.insertBefore(o,e)}let s=[...e].join(`, `);o.textContent=`\u26A0 SSL: ${s}`,a&&this.updateConfigDisplay()}))}setupXmrWarning(){this.core.on(`xmr-status`,({connected:e})=>{let t=document.getElementById(`overlay`);if(!t)return;let n=document.getElementById(`xmr-warning`);if(e)n?.remove();else{if(!n){n=document.createElement(`span`),n.id=`xmr-warning`,n.style.cssText=`color: #ff6666; flex: 0 0 auto;`;let e=document.getElementById(`cert-warnings`)||document.getElementById(`status`);t.insertBefore(n,e)}n.textContent=`⚠ XMR disconnected`}})}async loadCoreModules(){try{let[t,n,r,i,a,o,s,c,l,u]=await Promise.all([O(()=>import(`./src-Ct_7vG0E.js`),__vite__mapDeps([2,3,1]),import.meta.url),O(()=>import(`./src-Dr4QlWKq.js`).then(e=>e.t),__vite__mapDeps([4,1]),import.meta.url),O(()=>import(`./src-DmdLCQQf.js`),__vite__mapDeps([5,6,1]),import.meta.url),O(()=>import(`./src-B9Yas4SH.js`),__vite__mapDeps([7,1]),import.meta.url),O(()=>import(`./src-BvglFNkK.js`),__vite__mapDeps([8,1]),import.meta.url),O(()=>import(`./src-Cyk0X_mz.js`),__vite__mapDeps([9,1]),import.meta.url),O(()=>import(`./src-DwSNRd1O.js`),__vite__mapDeps([10,1]),import.meta.url),O(()=>Promise.resolve().then(()=>me),void 0,import.meta.url),O(()=>Promise.resolve().then(()=>oe),void 0,import.meta.url),O(()=>import(`./sync-manager-zTULh3xK.js`),__vite__mapDeps([11,1]),import.meta.url)]);if(ve=t.cacheWidgetHtml,De=u.SyncManager,Oe=u.computeStagger,J=r.scheduleManager,Y=i.config,ye=n.RestClient,be=n.XmdsClient,xe=n.ProtocolDetector,Se=a.XmrWrapper,Ce=o.StatsCollector,we=o.formatStats,Q=o.LogReporter,Te=o.formatLogs,Ee=s.DisplaySettings,$.core=c.VERSION||`?`,$.cache=t.VERSION||`?`,$.renderer=l.VERSION||`?`,$.schedule=r.VERSION||`?`,$.xmds=n.VERSION||`?`,$.xmr=a.VERSION||`?`,$.utils=i.VERSION||`?`,$.stats=o.VERSION||`?`,$.settings=s.VERSION||`?`,window.electronAPI?.getSystemInfo)try{let e=await window.electronAPI.getSystemInfo();e.macAddress&&(Y.macAddress=e.macAddress)}catch{}let d=Y.transport===`auto`?void 0:Y.transport,f=new URLSearchParams(window.location.search).get(`transport`)||(q.includes(`pwa-xmds`)?`xmds`:null)||d||`auto`;this.protocolDetector=new xe(Y.cmsUrl,ye,be);let p=f===`auto`?void 0:f,{client:m}=await this.protocolDetector.detect(Y,p);this.xmds=m;let h=Y.activeCmsId;this.statsCollector=new Ce(h),await this.statsCollector.init(),G.info(`Stats collector initialized${h?` (CMS: ${h})`:``}`),this.logReporter=new Q(h),await this.logReporter.init(),G.info(`Log reporter initialized${h?` (CMS: ${h})`:``}`);let g=e=>e.map(e=>typeof e==`string`?e:JSON.stringify(e)).join(` `);e(({level:e,name:t,args:n})=>{if(!this.logReporter)return;let r=g(n);this.logReporter.log(e,`[${t}] ${r}`,`PLAYER`).catch(()=>{})});let _=Y.debug;if(_?.consoleLogs){let t=(_.consoleLogsInterval||10)*1e3,n=[],r=null,i=()=>{if(n.length===0)return;let e=n;n=[],r=null,fetch(`/debug/log`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify(e)}).catch(()=>{})};e(({level:e,name:a,args:o})=>{let s=g(o);n.push({level:e,name:a,message:s,ts:new Date().toISOString()}),r||=setTimeout(i,t)}),G.info(`Console log forwarding to proxy enabled (flush every ${t/1e3}s)`)}this.displaySettings=new Ee,G.info(`Display settings manager initialized`);let v=`0.7.13`;G.info(`v${v} built 2026-04-02T12:51:15.949Z`);let y=Object.entries($).map(([e,t])=>`${e}=${t}`).join(` `);G.info(`SDK: ${y}`);let b=!!window.electronAPI,x=b?navigator.userAgent.match(/Electron\/([\d.]+)/)?.[1]||`?`:null,S=navigator.userAgent.match(/Chrome\/([\d.]+)/)?.[1]||`?`,ee=b?`Electron ${x} / Chrome ${S}`:`Chrome ${S}`;G.info(`Env: PWA v${v} | ${ee} | ${navigator.platform} | ${screen.width}x${screen.height}`),G.info(`Core modules loaded`)}catch(e){throw G.error(`Failed to load core modules:`,e),e}}setupCoreEventHandlers(){this.setupSyncEventHandlers(),this.setupDownloadEventHandlers(),this.setupCommandEventHandlers(),this.core.on(F.COLLECTION_START,()=>{this.updateStatus(`Collecting data from CMS...`)}),this.core.on(F.REGISTER_COMPLETE,e=>{let t=this.displaySettings?.getDisplayName()||e.displayName||Y.hardwareKey;this.updateStatus(`Registered: ${t}`),this.displaySettings&&(document.title=`Xibo Player - ${this.displaySettings.getDisplayName()}`);let n=parseFloat(e?.settings?.latitude),r=parseFloat(e?.settings?.longitude);n&&r&&!isNaN(n)&&!isNaN(r)?(G.info(`Display location from CMS: ${n.toFixed(4)}, ${r.toFixed(4)}`),J?.setLocation&&J.setLocation(n,r)):this.core.requestGeoLocation&&(G.info(`No CMS coordinates, requesting browser geolocation...`),this.core.requestGeoLocation()),!e.syncConfig&&Y.data?.sync&&(G.info(`[Sync] Using local sync config (CMS did not provide syncConfig)`),this.core.syncConfig=Y.data.sync,this.core.emit(F.SYNC_CONFIG,Y.data.sync))}),this.core.on(F.OFFLINE_MODE,e=>{e?(this.updateStatus(`Offline mode — using cached content`),this.showOfflineIndicator()):(this.updateStatus(`Back online`),this.removeOfflineIndicator())}),this.core.on(F.SCHEDULE_RECEIVED,e=>{if(this.updateStatus(`Processing schedule...`),e.layouts&&e.layouts.length>0?this.currentScheduleId=parseInt(e.layouts[0].scheduleid)||-1:e.campaigns&&e.campaigns.length>0&&(this.currentScheduleId=parseInt(e.campaigns[0].scheduleid)||-1),this.renderer?.layoutPool){let t=new Set;if(e.layouts)for(let n of e.layouts){let e=u(n.file||n.id||n);e&&t.add(e)}if(e.campaigns){for(let n of e.campaigns)if(n.layouts)for(let e of n.layouts){let n=u(e.file||e.id||e);n&&t.add(n)}}let n=this.renderer.layoutPool.clearWarmNotIn(t);n>0&&G.info(`Cleared ${n} preloaded layout(s) no longer in schedule`),this.scheduledLayoutIds=t}G.debug(`Current scheduleId for stats:`,this.currentScheduleId)}),this.core.on(F.LAYOUT_PREPARE_REQUEST,async e=>{await this.prepareLayout(e),(!this.syncManager||this.renderer.getCurrentLayoutId()===null)&&this.renderer.showLayout(e)}),this.core.on(F.LAYOUT_ALREADY_PLAYING,e=>{this.renderer.hasActiveLayoutTimer()||(G.warn(`Layout ${e} has no active timer — restarting layout`),this.renderer.stopCurrentLayout())}),this.core.on(F.LAYOUT_EXPIRE_CURRENT,()=>{G.info(`Schedule changed — expiring current layout`),this.renderer.stopCurrentLayout()}),this.core.on(F.NO_LAYOUTS_SCHEDULED,()=>{this.updateStatus(`No layouts scheduled`)}),this.core.on(F.COLLECTION_COMPLETE,()=>{let e=this.core.getCurrentLayoutId();e?this.updateStatus(`Playing layout ${e}`):this.preparingLayoutId&&this.updateStatus(`Downloading layout ${this.preparingLayoutId}...`)}),this.core.on(F.COLLECTION_ERROR,async e=>{this.updateStatus(`Collection error: ${e}`,`error`);let t=e?.message||String(e);if(t.includes(`403`)&&(t.includes(`Display not found`)||t.includes(`not authorized`))){G.warn(`Display not registered or not authorized — showing setup screen`),this.setupOverlay||=new W,this.setupOverlay.show();return}this.reportFault(`COLLECTION_FAILED`,`Collection cycle failed: ${e?.message||e}`)}),this.core.on(F.XMR_CONNECTED,e=>{G.info(`XMR connected:`,e)}),this.core.on(F.XMR_MISCONFIGURED,e=>{G.warn(`XMR misconfigured (${e.reason}): ${e.message}`)}),this.core.on(F.LOG_LEVEL_CHANGED,()=>{G.info(`Log level changed`)}),this.core.on(F.OVERLAY_LAYOUT_REQUEST,async e=>{G.info(`Overlay layout requested:`,e),await this.prepareLayout(e),this.renderer.showLayout(e)}),this.core.on(F.REVERT_TO_SCHEDULE,()=>{G.info(`Reverting to scheduled content`),this.updateStatus(`Reverting to schedule...`)}),this.displaySettings&&(this.displaySettings.on(`interval-changed`,e=>{G.info(`Collection interval changed to ${e}s`)}),this.displaySettings.on(`settings-applied`,(e,t)=>{t.length>0&&G.info(`Settings updated from CMS:`,t.join(`, `)),this._screenshotInterval||this.startScreenshotInterval()})),this.core.on(F.SUBMIT_STATS_REQUEST,async()=>{await this.submitStats()}),this.core.on(F.SUBMIT_LOGS_REQUEST,async()=>{await this.submitLogs()}),this.core.on(F.SCREENSHOT_REQUEST,async()=>{await this.captureAndSubmitScreenshot()}),this.core.on(F.CHECK_PENDING_LAYOUT,async e=>{await this.prepareLayout(e),this.renderer.showLayout(e)}),this.core.on(F.NAVIGATE_TO_WIDGET,e=>{e.targetId?this.renderer.navigateToWidget(e.targetId):G.warn(`navigate-to-widget action has no targetId:`,e)}),this.core.on(F.TIMELINE_UPDATED,e=>{let t=this.core.getCurrentLayoutId(),n=t?this.core.getLayoutDuration(t):void 0;this.timelineOverlay?.update(e,t,n)})}setupSyncEventHandlers(){this.core.on(F.OFFLINE_MODE,e=>{e&&!this.syncManager&&Y.data?.sync&&(G.info(`[Sync] Offline mode with local sync config — starting sync`),this.core.syncConfig=Y.data.sync,this.core.emit(F.SYNC_CONFIG,Y.data.sync))}),this.core.on(F.SYNC_CONFIG,async e=>{if(this.syncManager&&this.syncManager.stop(),e.syncPublisherPort)if(e.syncGroupId&&(e.syncGroup=String(e.syncGroupId)),e.isLead)e.relayUrl=`ws://localhost:${e.syncPublisherPort}/sync`,fetch(`/system/advertise-sync`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({syncGroupId:e.syncGroupId,port:e.syncPublisherPort,displayId:Y.hardwareKey})}).catch(()=>{});else{let t=e.syncGroup;try{let n=await fetch(`/system/discover-lead?syncGroupId=${e.syncGroupId}`);if(n.ok){let{host:e,port:r}=await n.json();t=e,G.info(`mDNS discovered lead at ${e}:${r}`)}}catch{G.warn(`mDNS discovery failed, using CMS-provided IP`)}e.relayUrl=`ws://${t}:${e.syncPublisherPort}/sync`}let{syncToken:t,...n}=e,r={...Y.data?.sync||{},...n};window.electronAPI?.setConfig?window.electronAPI.setConfig({sync:r}):fetch(`/config`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sync:r})}).catch(()=>{}),e.syncToken||=Y.cmsKey,this.syncManager=new De({displayId:Y.hardwareKey,syncConfig:e,onLayoutChange:async t=>{let n=(e.layoutMap||Y.sync?.layoutMap)?.[t]??t;n!==t&&G.info(`[Sync] Wall mode: lead layout ${t} → local layout ${n}`),G.info(`[Sync] Preparing layout ${n} (waiting for show signal)`),await this.prepareLayout(parseInt(String(n),10)),this.syncManager?.reportReady(t)},onLayoutShow:t=>{let n=(e.layoutMap||Y.sync?.layoutMap)?.[t]??t,r=parseInt(String(n),10),i=e.choreography||`simultaneous`,a={choreography:i,staggerMs:e.staggerMs??150};e.topology?(a.topology=e.topology,a.gridCols=e.gridCols??1,a.gridRows=e.gridRows??1):(a.position=e.position??0,a.totalDisplays=e.totalDisplays??1);let o=Oe(a);o>0?(G.info(`[Sync] Show layout ${r} with ${o}ms choreography delay (${i})`),setTimeout(()=>this.renderer.showLayout(r),o)):(G.info(`[Sync] Show layout ${r}`),this.renderer.showLayout(r))},onVideoStart:(e,t)=>{G.info(`[Sync] Video start: layout ${e} region ${t}`),this.renderer.resumeRegionMedia?.(t)},onStatsReport:async(e,t,n)=>{G.info(`[Sync] Submitting stats for follower ${e}`);try{await this.xmds.submitStats(t,e)&&n()}catch(t){G.warn(`[Sync] Stats submission failed for follower ${e}:`,t)}},onLogsReport:async(e,t,n)=>{G.info(`[Sync] Submitting logs for follower ${e}`);try{await this.xmds.submitLog(t,e)&&n()}catch(t){G.warn(`[Sync] Log submission failed for follower ${e}:`,t)}},onStatsAck:async e=>{G.info(`[Sync] Lead confirmed stats submission`),this._pendingFollowerStats&&this.statsCollector&&(await this.statsCollector.clearSubmittedStats(this._pendingFollowerStats),this._pendingFollowerStats=null)},onLogsAck:async e=>{G.info(`[Sync] Lead confirmed logs submission`),this._pendingFollowerLogs&&this.logReporter&&(await this.logReporter.clearSubmittedLogs(this._pendingFollowerLogs),this._pendingFollowerLogs=null)},onGroupUpdate:(t,n)=>{G.info(`[Sync] Group update: ${t} displays, topology: ${JSON.stringify(n)}`),e.totalDisplays=t}}),this.core.setSyncManager(this.syncManager),this.syncManager.start(),G.info(`[Sync] SyncManager started as ${e.isLead?`LEAD`:`FOLLOWER`}`),this.updateConfigDisplay()})}setupDownloadEventHandlers(){this.core.on(F.FILES_RECEIVED,e=>{this.updateStatus(`Downloading ${e.length} files...`)}),this.core.on(F.DOWNLOAD_REQUEST,async e=>{this.downloadOverlay?.startUpdating();try{let t=this.xmds?.getToken?.()||null;t&&await fetch(`/auth-token`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:t})}),await this.enqueueDownloads(e),G.info(`Download enqueue complete`)}catch(e){G.error(`Download request failed:`,e),this.updateStatus(`Download failed: `+e,`error`)}}),this.core.on(F.PURGE_REQUEST,async e=>{try{let t=await X.remove(e);G.info(`Purge complete: ${t.deleted}/${t.total} files deleted`)}catch(e){G.warn(`Purge failed:`,e)}}),this.core.on(F.PURGE_ALL_REQUEST,async()=>{G.info(`Purging all cached content...`),this.updateStatus(`Purging cache...`);try{let e=await X.list();if(e.length>0){let t=await X.remove(e);G.info(`Purged ${t.deleted} files from ContentStore`)}let t=await caches.keys();t.length>0&&(await Promise.all(t.map(e=>caches.delete(e))),G.info(`Purged ${t.length} legacy caches`))}catch(e){G.error(`Cache purge failed:`,e)}})}setupCommandEventHandlers(){this.core.on(F.EXECUTE_NATIVE_COMMAND,async e=>{let t;if(window.electronAPI?.executeShellCommand)t=await window.electronAPI.executeShellCommand({commandString:e.commandString});else try{t=await(await fetch(`/shell-command`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({commandString:e.commandString})})).json()}catch(e){t={success:!1,reason:e.message}}this.core.emit(F.COMMAND_RESULT,{code:e.code,...t})}),this.core.on(F.COMMAND_RESULT,e=>{G.info(`Command result:`,e),e.success||this.reportFault(`COMMAND_FAILED`,`Command ${e.code} failed: ${e.reason||`unknown`}`)}),this.core.on(F.SCHEDULED_COMMAND,e=>{G.info(`Scheduled command: ${e.code}`),this.core.executeCommand(e.code)})}setupInteractiveControl(){this._swIcHandler=e=>{if(e.data?.type!==`INTERACTIVE_CONTROL`)return;let{method:t,path:n,search:r,body:i}=e.data,a=e.ports?.[0];if(!a)return;let o=this.handleInteractiveControl(t,n,r,i);a.postMessage(o)},navigator.serviceWorker?.addEventListener(`message`,this._swIcHandler)}setupDataConnectorNotify(){this.core.getDataConnectorManager().on(`data-changed`,e=>{let t=document.querySelectorAll(`iframe`),n={ctrl:`rtNotifyData`,data:{dataKey:e}};for(let e of t)try{e.contentWindow?.postMessage(n,`*`)}catch{}})}setupRemoteControls(){window.addEventListener(`blur`,()=>{this.setupOverlay?.isVisible()||setTimeout(()=>window.focus(),200)});let e=e=>{let t=()=>{try{let t=e.contentDocument||e.contentWindow?.document;if(!t||e.__keyForwarderAttached)return;e.__keyForwarderAttached=!0,t.addEventListener(`keydown`,e=>{if(this.setupOverlay?.isVisible())return;let t=new KeyboardEvent(`keydown`,{key:e.key,code:e.code,keyCode:e.keyCode,ctrlKey:e.ctrlKey,shiftKey:e.shiftKey,altKey:e.altKey,metaKey:e.metaKey,bubbles:!0,cancelable:!0});document.dispatchEvent(t)||e.preventDefault()})}catch{}};e.addEventListener(`load`,t),t()};Array.from(document.querySelectorAll(`iframe`)).forEach(t=>e(t)),this._iframeObserver=new MutationObserver(t=>{for(let n of t)for(let t of n.addedNodes)t instanceof HTMLIFrameElement&&e(t),t instanceof HTMLElement&&t.querySelectorAll(`iframe`).forEach(t=>e(t))}),this._iframeObserver.observe(document.body,{childList:!0,subtree:!0});let{keyboard:t={}}=this.getControls(),n=t.debugOverlays===!0,r=t.setupKey===!0,i=t.playbackControl===!0,a=t.videoControls===!0;document.addEventListener(`keydown`,e=>{if(e.key===`q`&&(e.ctrlKey||e.metaKey)){e.preventDefault(),G.info(`[Remote] Quit requested (Ctrl+Q)`),fetch(`/quit`,{method:`POST`}).catch(()=>{});return}switch(e.key){case`t`:case`T`:if(!n)break;this.timelineOverlay||=new H(!0,e=>this.skipToLayout(e)),this.timelineOverlay.toggle();break;case`d`:case`D`:if(!n)break;this.downloadOverlay||(this.downloadOverlay=new V({enabled:!0,autoHide:!1}),this.downloadOverlay.setProgressCallback(()=>Z.getProgress())),this.downloadOverlay.toggle();break;case`v`:case`V`:{if(!a)break;let e=[...document.querySelectorAll(`video`)];document.querySelectorAll(`iframe`).forEach(t=>{try{e.push(...t.contentDocument.querySelectorAll(`video`))}catch{}});let t=e.length>0&&!e[0].controls;e.forEach(e=>e.controls=t);break}case`ArrowRight`:case`PageDown`:if(!i)break;G.info(`[Remote] Next layout (keyboard)`),this.core.advanceToNextLayout(),e.preventDefault();break;case`ArrowLeft`:case`PageUp`:if(!i)break;G.info(`[Remote] Previous layout (keyboard)`),this.core.advanceToPreviousLayout(),e.preventDefault();break;case` `:if(!i)break;G.info(`[Remote] Toggle pause (keyboard)`),this.renderer.isPaused()?this.renderer.resume():this.renderer.pause(),e.preventDefault();break;case`r`:case`R`:if(!i)break;this.core.isLayoutOverridden()&&(G.info(`[Remote] Revert to schedule (keyboard)`),this.core.revertToSchedule());break;case`s`:case`S`:if(!r)break;this.setupOverlay||=new W,this.setupOverlay.toggle(),e.preventDefault();break}}),i&&`mediaSession`in navigator&&(navigator.mediaSession.setActionHandler(`nexttrack`,()=>{G.info(`[Remote] Next layout (MediaSession)`),this.core.advanceToNextLayout()}),navigator.mediaSession.setActionHandler(`previoustrack`,()=>{G.info(`[Remote] Previous layout (MediaSession)`),this.core.advanceToPreviousLayout()}),navigator.mediaSession.setActionHandler(`pause`,()=>{G.info(`[Remote] Pause (MediaSession)`),this.renderer.pause()}),navigator.mediaSession.setActionHandler(`play`,()=>{G.info(`[Remote] Resume (MediaSession)`),this.renderer.resume()})),G.info(`Remote controls initialized (keyboard + MediaSession)`)}getControls(){return Y.controls}skipToLayout(e){G.info(`Skipping to layout ${e} (timeline click)`),this.core.changeLayout(e)}parseBody(e){try{return e?JSON.parse(e):{}}catch{return{}}}handleInteractiveControl(e,t,n,r){switch(G.debug(`IC request:`,e,t,n),t){case`/info`:return{status:200,body:JSON.stringify({hardwareKey:Y.hardwareKey,displayName:Y.displayName,playerType:`pwa`,currentLayoutId:this.core.getCurrentLayoutId()})};case`/trigger`:{let e=this.parseBody(r);return this.renderer.emit(`interactiveTrigger`,{targetId:e.id,triggerCode:e.trigger}),e.trigger&&this.core.handleTrigger(e.trigger),{status:200,body:`OK`}}case`/duration/expire`:{let e=this.parseBody(r);return G.info(`IC: Widget duration expire requested for`,e.id),this.renderer.emit(`widgetExpire`,{widgetId:e.id}),{status:200,body:`OK`}}case`/duration/extend`:{let e=this.parseBody(r);return G.info(`IC: Widget duration extend by`,e.duration,`for`,e.id),this.renderer.emit(`widgetExtendDuration`,{widgetId:e.id,duration:parseInt(e.duration)}),{status:200,body:`OK`}}case`/duration/set`:{let e=this.parseBody(r);return G.info(`IC: Widget duration set to`,e.duration,`for`,e.id),this.renderer.emit(`widgetSetDuration`,{widgetId:e.id,duration:parseInt(e.duration)}),{status:200,body:`OK`}}case`/fault`:{let e=this.parseBody(r);return this.reportFault(e.code||`WIDGET_FAULT`,e.reason||`Widget reported fault`,{layoutId:e.layoutId,regionId:e.regionId,widgetId:e.widgetId}),{status:200,body:`OK`}}case`/realtime`:{let e=new URLSearchParams(n).get(`dataKey`);if(G.debug(`IC: Realtime data request for key:`,e),!e)return{status:400,body:JSON.stringify({error:`Missing dataKey parameter`})};let t=this.core.getDataConnectorManager().getData(e);return t===null?{status:404,body:JSON.stringify({error:`No data available for key: ${e}`})}:{status:200,body:typeof t==`string`?t:JSON.stringify(t)}}case`/criteria`:return{status:200,body:JSON.stringify({displayId:Y.displayId,hardwareKey:Y.hardwareKey,displayName:Y.displayName,width:window.innerWidth,height:window.innerHeight,latitude:Y.latitude||null,longitude:Y.longitude||null,playerType:`pwa`})};default:return{status:404,body:JSON.stringify({error:`Unknown IC route`})}}}notifyFileCached(e,t){if(G.debug(`Download complete: ${t}/${e}`),t===`layout`)this.core.notifyMediaReady(parseInt(e),t);else if(t===`media`){let n=this._fileIdToSaveAs.get(e)||e;this._cachedMediaKeys.add(n),this.core.notifyMediaReady(n,t)}else this._cachedMediaKeys.add(e);this._probeTimer&&clearTimeout(this._probeTimer),this._probeTimer=setTimeout(()=>{this._probeTimer=null,this.probeLayoutDurations().catch(()=>{})},3e3),this._mediaStatusTimer&&clearTimeout(this._mediaStatusTimer),this._mediaStatusTimer=setTimeout(()=>{this._mediaStatusTimer=null,this.checkTimelineMediaStatus().catch(()=>{})},2e3)}async enqueueDownloads(e){let{extractMediaIdsFromXlf:t}=await O(async()=>{let{extractMediaIdsFromXlf:e}=await import(`./src-DgZHc2Af.js`);return{extractMediaIdsFromXlf:e}},__vite__mapDeps([0,1]),import.meta.url),{layoutOrder:n,files:r,layoutDependants:i}=e,a=e=>(e.path||``).split(`?`)[0].replace(/^\/+/,``)||`${e.type||`media`}/${e.id}`;for(let e of r)e.saveAs&&this._fileIdToSaveAs.set(String(e.id),e.saveAs);let o=new Map,s=[],c=new Map,l=new Map;for(let e of r)if(e.type===`layout`)o.set(parseInt(e.id),e);else if(e.type===`static`)s.push(e);else{let t=`${e.type}:${e.id}`;c.set(t,e);let n=String(e.id);l.has(n)||l.set(n,[]),l.get(n).push(t)}G.info(`Download: ${n.length} layouts, ${c.size} media, ${s.length} resources`);let u=new Map,d=[...n,...[...o.keys()].filter(e=>!n.includes(e))].map(async e=>{let n=o.get(e);if(!n?.path)return;let r;try{let t={};n.cmsDownloadUrl&&(t[`X-Cms-Download-Url`]=n.cmsDownloadUrl);let i=await fetch(n.path,Object.keys(t).length?{headers:t}:void 0);i.ok&&(r=await i.text(),G.info(`Fetched XLF ${e} (${r.length} bytes)`),await X.put(`${K}/layouts`,String(e),new Blob([r],{type:`text/xml`})),this.notifyFileCached(String(e),`layout`))}catch{}r&&u.set(e,t(r,G))});await Promise.allSettled(d),G.info(`Parsed ${u.size} XLFs`);let f=async(e,t)=>{if(!t.path||t.path===`null`||t.path===`undefined`)return!1;let n=a(t);try{if((await fetch(`/store/${n}`,{method:`HEAD`})).status===200)return!1}catch{}let r=`${t.type}/${t.id}`;if(Z.getTask(r))return!1;try{let e=await fetch(`/store/missing-chunks/${n}`);if(e.ok){let{missing:r,numChunks:i}=await e.json();if(i>0&&r.length<i){let e=new Set;for(let t=0;t<i;t++)r.includes(t)||e.add(t);t.skipChunks=e,G.info(`Resuming ${n}: ${e.size}/${i} chunks cached, ${r.length} to download`)}}}catch{}let i=e.addFile(t);return i.state===`pending`?(i.wait().then(e=>{let i=parseInt(t.size)||e.size;G.info(`Download complete:`,n,`(${i} bytes)`),i>this._chunkConfig.chunkSize&&fetch(`/store/mark-complete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({storeKey:n})}).catch(e=>G.warn(`mark-complete failed:`,n,e.message)),this.notifyFileCached(String(t.id),t.type),Z.removeCompleted(r)}).catch(e=>{G.error(`Download failed:`,t.id,e),Z.removeCompleted(r)}),!0):!1},p=Z.createTaskBuilder();await Promise.all(s.map(e=>f(p,e)));let h=await p.build();h.length>0&&(h.push(m),Z.enqueueOrderedTasks(h));let g=new Set,_=[...u.keys()].filter(e=>!n.includes(e)),v=new Map;for(let[e,t]of c)t.saveAs&&v.set(t.saveAs,e);let y=new Map;if(i)for(let[e,t]of Object.entries(i))y.set(parseInt(e,10),t);for(let e of n){let t=u.get(e);if(!t)continue;let n=new Set(t);for(let e of _){let t=u.get(e);if(t)for(let e of t)n.add(e)}let r=y.get(e)||[];for(let e of r){let t=v.get(e);t&&n.add(t)}let i=[];for(let e of n){if(c.has(e)&&!g.has(e)){i.push(c.get(e)),g.add(e);continue}let t=l.get(String(e))||[];for(let e of t)g.has(e)||(i.push(c.get(e)),g.add(e))}if(i.length===0)continue;G.info(`Layout ${e}: ${i.length} media`),i.sort((e,t)=>(e.size||0)-(t.size||0));let a=Z.createTaskBuilder();await Promise.all(i.map(e=>f(a,e)));let o=await a.build();o.length>0&&(o.push(m),Z.enqueueOrderedTasks(o))}let b=[...c.keys()].filter(e=>!g.has(e));if(b.length>0){G.info(`${b.length} media not in any XLF`);let e=Z.createTaskBuilder();await Promise.all(b.map(t=>{let n=c.get(t);return n?f(e,n):Promise.resolve(!1)}));let t=await e.build();t.length>0&&Z.enqueueOrderedTasks(t)}G.info(`Downloads active:`,Z.running,`, queued:`,Z.queued)}setupRendererEventHandlers(){this.renderer.on(`layoutStart`,(e,t)=>{G.info(`Layout started:`,e),this.updateStatus(`Playing layout ${e}`),this.core.setCurrentLayout(e),this._currentLayoutEnableStat=t?.enableStat!==!1;let n=this.core.getLayoutDuration(e)||t?.duration;this.timelineOverlay?.update(null,e,n),this.statsCollector&&this._currentLayoutEnableStat&&this.statsCollector.startLayout(e,this.currentScheduleId).catch(e=>{G.error(`Failed to start layout stat:`,e)})}),this.renderer.on(`layoutEnd`,e=>{if(G.info(`Layout ended:`,e),J?.recordPlay(e.toString()),this.statsCollector&&this._currentLayoutEnableStat&&this.statsCollector.endLayout(e,this.currentScheduleId).catch(e=>{G.error(`Failed to end layout stat:`,e)}),this.renderer.getCurrentLayoutId()&&this.renderer.getCurrentLayoutId()!==e){G.debug(`Layout ${e} ended but ${this.renderer.getCurrentLayoutId()} already playing, skipping advance`);return}if(this.preparingLayoutId&&this.preparingLayoutId!==e){G.debug(`Layout ${e} ended but ${this.preparingLayoutId} being prepared, skipping advance`);return}this.core.notifyLayoutStatus(e),this.core.clearCurrentLayout();let t=this.core.getPendingLayouts();if(t.length>0){G.info(`Layout ${t[0]} pending download, skipping advance`);return}G.info(`Layout cycle completed, advancing to next layout...`),this.core.advanceToNextLayout()}),this.renderer.on(`widgetStart`,e=>{let{widgetId:t,layoutId:n,mediaId:r}=e;G.debug(`Widget started:`,e.type,t,`media:`,r),this.statsCollector&&r&&e.enableStat!==!1&&this.statsCollector.startWidget(r,n,this.currentScheduleId).catch(e=>{G.error(`Failed to start widget stat:`,e)})}),this.renderer.on(`widgetEnd`,e=>{let{widgetId:t,layoutId:n,mediaId:r}=e;G.debug(`Widget ended:`,e.type,t,`media:`,r),this.statsCollector&&r&&e.enableStat!==!1&&this.statsCollector.endWidget(r,n,this.currentScheduleId).catch(e=>{G.error(`Failed to end widget stat:`,e)})}),this.renderer.on(`widgetCommand`,e=>{G.info(`Widget command:`,e.commandCode);let t={[e.commandCode]:{commandString:e.commandString}};this.core.executeCommand(e.commandCode,t)}),this.renderer.on(`error`,e=>{G.error(`Renderer error:`,e),this.updateStatus(`Error: ${e.type}`,`error`),this.reportFault(e.type||`RENDERER_ERROR`,`Renderer error: ${e.message||e.type}`,{layoutId:e.layoutId,regionId:e.regionId,widgetId:e.widgetId})}),this.renderer.on(`action-trigger`,e=>{let{actionType:t,triggerCode:n,layoutCode:r,targetId:i,commandCode:a}=e;switch(G.info(`Action trigger:`,t,e),t){case`navLayout`:case`navigateToLayout`:n?this.core.handleTrigger(n):r&&this.core.changeLayout(r);break;case`navWidget`:case`navigateToWidget`:n?this.core.handleTrigger(n):i&&this.renderer.navigateToWidget(i);break;case`previousWidget`:this.renderer.previousWidget(e.source?.regionId);break;case`nextWidget`:this.renderer.nextWidget(e.source?.regionId);break;case`command`:a&&this.core.executeCommand(a);break;default:G.warn(`Unknown action type:`,t)}this.statsCollector&&this.statsCollector.recordEvent(`touch`,this.core.getCurrentLayoutId(),e.targetId||null,this.currentScheduleId)}),this.renderer.on(`widgetAction`,e=>{e.type===`durationEnd`&&e.url&&(G.info(`Widget ${e.widgetId} duration ended, calling webhook: ${e.url}`),this.statsCollector&&this.statsCollector.recordEvent(`webhook`,e.layoutId,e.widgetId,this.currentScheduleId),fetch(e.url,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({widgetId:e.widgetId,layoutId:e.layoutId,regionId:e.regionId,event:`durationEnd`,timestamp:new Date().toISOString()})}).catch(e=>G.warn(`Webhook failed (non-critical):`,e)))}),this.renderer.on(`layoutDurationUpdated`,(e,t,n)=>{this.core.recordLayoutDuration(String(e),t,n)}),this.renderer.on(`request-next-layout-preload`,async()=>{try{let e=this.core.peekNextLayout();if(!e){G.debug(`No next layout to preload (single layout schedule or same layout)`);return}let t=e.layoutId;if(this.renderer.layoutPool.has(t)){G.debug(`Layout ${t} already in preload pool`);return}if(this.renderer._preloadingLayoutId===t){G.debug(`Layout ${t} preload already in-flight`);return}G.info(`Preloading next layout ${t}...`);let n=await X.get(`${K}/layouts`,t);if(!n){G.debug(`Layout ${t} XLF not cached, skipping preload`);return}let r=await n.text(),i=new DOMParser().parseFromString(r,`text/xml`),{allMedia:a}=this.getMediaIds(i);if(!await this.checkAllMediaCached(a)){G.debug(`Media not fully cached for layout ${t}, skipping preload`);return}await this.fetchWidgetHtml(i,t),await this.renderer.preloadLayout(r,t)?G.info(`Layout ${t} preloaded successfully`):G.warn(`Layout ${t} preload failed (will fall back to normal render)`)}catch(e){G.warn(`Layout preload failed (non-blocking):`,e)}}),this.renderer.on(`videoError`,async({storedAs:e})=>{if(!e)return;let t=`${s.slice(1)}/media/file/${e}`;try{let{missing:n}=await(await fetch(`/store/missing-chunks/${t}`)).json();if(n.length===0){G.warn(`Video ${e}: corrupt file (all chunks present), deleting for re-download`),await fetch(`/store/delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({files:[{key:t}]})});let n=this.core.getCurrentLayoutId();n&&this.core.setPendingLayout(n,[e]),this.core.collectNow().catch(t=>{G.error(`Failed to trigger re-download for ${e}:`,t.message)});return}G.warn(`Video ${e}: ${n.length} missing chunks (${n.join(`, `)}), re-downloading`),await fetch(`/store/unmark-complete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({storeKey:t})}),this.core.collectNow().catch(t=>{G.error(`Failed to trigger re-download for ${e}:`,t.message)})}catch(t){G.error(`Failed to check/re-download ${e}:`,t.message)}})}async prepareLayout(e){if(this.renderer.getCurrentLayoutId()===e){G.debug(`Layout ${e} replay`),this.core.clearPreparingLayout(),await this.renderer.renderLayout(``,e);return}if(this.preparingLayoutId===e){G.debug(`Layout ${e} preparation in progress, will retry after it completes`),this._pendingRetryLayoutId=e;return}this.preparingLayoutId=e;try{let t=await X.get(`${K}/layouts`,e);if(!t){G.info(`Layout not in cache yet, marking as pending:`,e),this.core.setPendingLayout(e,[String(e)]),this.updateStatus(`Downloading layout ${e}...`);return}let n=await t.text(),r=new DOMParser().parseFromString(n,`text/xml`),{allMedia:i}=this.getMediaIds(r);if(!await this.checkAllMediaCached(i)){Z.prioritizeLayoutFiles(i.map(String)),G.info(`Waiting for media to finish downloading for layout ${e}`),this.updateStatus(`Preparing layout ${e}...`),this.core.setPendingLayout(e,i);return}this.renderer.hasPreloadedLayout(e)||await this.fetchWidgetHtml(r,e),await this.renderer.preloadLayout(n,e),this.core.pendingLayouts.delete(e),G.info(`Layout ${e} ready`)}catch(t){G.error(`Failed to prepare layout:`,e,t),this.updateStatus(`Failed to load layout ${e}`,`error`),this.reportFault(`LAYOUT_LOAD_FAILED`,`Failed to prepare layout ${e}: ${t?.message||t}`,{layoutId:e})}finally{this.preparingLayoutId=null,this.core.clearPreparingLayout();let e=this._pendingRetryLayoutId;this._pendingRetryLayoutId=null,e!=null&&this.core.getCurrentLayoutId()!==e&&(G.debug(`Retrying preparation for layout ${e} after 500ms`),setTimeout(()=>this.prepareLayout(e),500))}}getMediaIds(e){let t=typeof e==`string`?new DOMParser().parseFromString(e,`text/xml`):e,n=[],r=[];t.querySelectorAll(`media[fileId]`).forEach(e=>{let t=e.getAttribute(`fileId`);if(t){let i=this._fileIdToSaveAs.get(t)||t;if(i.endsWith(`.xlf`))return;n.push(i),e.getAttribute(`type`)===`video`&&r.push(i)}});let i=t.querySelector(`layout`)?.getAttribute(`background`);if(i){let e=this._fileIdToSaveAs.get(i)||i;n.includes(e)||n.push(e)}return{allMedia:n,videoMedia:r}}async checkAllMediaCached(e){let t=e.filter(e=>!this._cachedMediaKeys.has(e));if(t.length===0)return!0;let n=t,r=await Promise.all(n.map(async e=>{try{let t=await X.has(K,`media/file/${e}`);return t&&this._cachedMediaKeys.add(e),t}catch{return G.warn(`Unable to verify media ${e}, assuming cached (offline mode)`),!0}})),i=n.filter((e,t)=>!r[t]);return i.length>0?(G.debug(`Media not yet cached: ${i.join(`, `)}`),!1):!0}async fetchWidgetHtml(e,t){let n=typeof e==`string`?new DOMParser().parseFromString(e,`text/xml`):e,r=[];for(let e of n.querySelectorAll(`region`)){let i=e.getAttribute(`id`);for(let a of e.querySelectorAll(`media`)){let e=a.getAttribute(`type`),o=a.getAttribute(`id`);a.getAttribute(`render`)===`html`&&r.push((async()=>{try{let r=`${t}/${i}/${o}`,s=null,c=await X.get(`${K}/widgets`,r);c&&(s=await c.text(),G.debug(`Found cached widget HTML for ${e} ${o}`)),s||(s=await this.xmds.getResource(t,i,o),G.debug(`Retrieved widget HTML for ${e} ${o} from CMS`)),s=(await ve(t,i,o,s)).html;let l=a.querySelector(`raw`);if(l)l.textContent=s;else{let e=n.createElement(`raw`);e.textContent=s,a.appendChild(e)}}catch(t){G.warn(`Failed to get widget HTML for ${e} ${o}:`,t)}})())}}r.length>0&&(G.info(`Fetching ${r.length} widget HTML resources in parallel...`),await Promise.all(r),G.debug(`All widget HTML fetched`))}async checkTimelineMediaStatus(){if(this.scheduledLayoutIds.size!==0){for(let e of this.scheduledLayoutIds){let t=`${e}.xlf`;try{let n=await X.get(`${K}/layouts`,e);if(!n)continue;let r=await n.text(),{allMedia:i}=this.getMediaIds(r);if(i.length===0){this.core.setLayoutMediaStatus(t,!0);continue}let a=[];for(let e of i){if(this._cachedMediaKeys.has(e))continue;let t=`${K}/media/file/${e}`;if(Z.getTask(t)){a.push(e);continue}try{await X.has(K,`media/file/${e}`)?this._cachedMediaKeys.add(e):a.push(e)}catch{}}this.core.setLayoutMediaStatus(t,a.length===0,a)}catch{}}this.core.logUpcomingTimeline()}}async probeLayoutDurations(){if(this.scheduledLayoutIds.size!==0)for(let e of this.scheduledLayoutIds)try{let t=await X.get(`${K}/layouts`,e);if(!t)continue;let n=await t.text(),{videoMedia:r}=this.getMediaIds(n);if(r.length===0)continue;let i=new DOMParser().parseFromString(n,`text/xml`),a=new Map,o=0;for(let e of i.querySelectorAll(`media[type="video"]`)){if(e.getAttribute(`useDuration`)===`1`)continue;let t=e.getAttribute(`fileId`);if(!t)continue;o++;let n=this._fileIdToSaveAs.get(t)||t;if(!await X.has(K,`media/file/${n}`))continue;let r=await this.probeVideoDuration(`${window.location.origin}${s}/media/file/${n}`);r>0&&a.set(t,r)}if(a.size===0)continue;let c=a.size>=o,{duration:u}=l(n,a);u>0&&this.core.recordLayoutDuration(String(e),u,c)}catch(t){G.debug(`Duration probe failed for layout ${e}:`,t)}}probeVideoDuration(e){return new Promise(t=>{let n=document.createElement(`video`);n.preload=`metadata`,n.muted=!0;let r=()=>{n.removeAttribute(`src`),n.load()};n.addEventListener(`loadedmetadata`,()=>{let e=n.duration;r(),t(e)},{once:!0}),n.addEventListener(`error`,()=>{r(),t(0)},{once:!0}),setTimeout(()=>{r(),t(0)},5e3),n.src=e})}updateConfigDisplay(){let e=document.getElementById(`config-info`);if(e){let t=`0.7.13`,n=`2026-04-02 12:51:15.949Z`.replace(/\.\d+Z$/,``),r=`${n?`v${t} (${n})`:`v${t}`} | CMS: ${Y.cmsUrl} | Display: ${Y.displayName||`Unknown`} | HW: ${Y.hardwareKey}`,i=this.core?.getSyncConfig?.();if(i){let e=i.relayUrl?new URL(i.relayUrl).host:``;r+=` | Sync: ${i.isLead?`LEAD`:`FOLLOWER → ${e}`} (group ${i.syncGroupId||i.syncGroup})`}e.textContent=r}}async submitCollectedData(e){let{name:t,pendingFlag:n,getItems:r,formatFn:i,delegateFn:a,submitFn:o,clearFn:s}=e;if(this[n]!==null){G.debug(`${t} delegation in-flight, skipping`);return}try{let e=await r();if(e.length===0){G.debug(`No ${t} to submit`);return}let c=i(e);if(this.syncManager&&!this.syncManager.isLead&&this._syncLeadAlive()){G.info(`[Sync] Delegating ${e.length} ${t} to lead`),this[n]=e,a(c);return}this.syncManager&&!this.syncManager.isLead&&G.warn(`[Sync] Lead not alive, submitting ${t} directly`),G.info(`Submitting ${e.length} ${t} to CMS...`),await o(c)?(G.info(`${t} submitted successfully`),await s(e)):G.warn(`${t} submission failed (CMS returned false)`)}catch(e){G.error(`Failed to submit ${t}:`,e)}}async submitStats(){if(!this.statsCollector){G.warn(`Stats collector not initialized`);return}let e=this.displaySettings?.getSetting(`aggregationLevel`)||`Individual`;await this.submitCollectedData({name:`stats`,pendingFlag:`_pendingFollowerStats`,getItems:async()=>e===`Aggregate`?this.statsCollector.getAggregatedStatsForSubmission(50):this.statsCollector.getStatsForSubmission(50),formatFn:we,delegateFn:e=>this.syncManager.reportStats(e),submitFn:e=>this.xmds.submitStats(e),clearFn:e=>this.statsCollector.clearSubmittedStats(e)})}async submitLogs(){this.logReporter&&await this.submitCollectedData({name:`logs`,pendingFlag:`_pendingFollowerLogs`,getItems:()=>this.logReporter.getLogsForSubmission(),formatFn:Te,delegateFn:e=>this.syncManager.reportLogs(e),submitFn:e=>this.xmds.submitLog(e),clearFn:e=>this.logReporter.clearSubmittedLogs(e)})}reportFault(e,t,n){this.logReporter?.reportFault(e,t),this.submitFault(e,t,n)}submitFault(e,t,n){if(!this.xmds)return;let r=JSON.stringify([{code:e,reason:t,date:new Date().toISOString().replace(`T`,` `).substring(0,19),...n}]);this.xmds.reportFaults(r).catch(e=>{G.debug(`reportFaults failed (non-critical):`,e)})}async captureAndSubmitScreenshot(){if(this._screenshotInFlight){G.debug(`Screenshot capture already in progress, skipping`);return}this._screenshotInFlight=!0;try{let e;if(this._screenshotMethod===`electron`||this._screenshotMethod===null&&window.electronAPI?.captureScreenshot){let t=await window.electronAPI.captureScreenshot();if(t)this._screenshotMethod=`electron`,e=t;else{G.debug(`Electron screenshot not ready yet, will retry next interval`);return}}else if(this._screenshotMethod===`displayMedia`||this._screenshotMethod===null&&typeof navigator.mediaDevices?.getDisplayMedia==`function`)try{e=await this.captureDisplayMedia(),this._screenshotMethod=`displayMedia`}catch(t){G.warn(`getDisplayMedia failed, falling back to html2canvas:`,t.message||t),this._screenshotMethod=null,e=await this.captureHtml2CanvasIsolated(),this._screenshotMethod=`html2canvas`}else this._screenshotMethod=`html2canvas`,e=await this.captureHtml2CanvasIsolated();await this.xmds.submitScreenShot(e)?G.info(`Screenshot submitted (${this._screenshotMethod})`):G.warn(`Screenshot submission failed`)}catch(e){G.error(`Failed to capture screenshot:`,e)}finally{this._screenshotInFlight=!1}}async captureDisplayMedia(){let e=await navigator.mediaDevices.getDisplayMedia({video:!0,audio:!1,preferCurrentTab:!0});try{let t=e.getVideoTracks()[0],n=await new window.ImageCapture(t).grabFrame(),r=document.createElement(`canvas`);return r.width=n.width,r.height=n.height,r.getContext(`2d`).drawImage(n,0,0),n.close(),r.toDataURL(`image/jpeg`,.8).split(`,`)[1]}finally{e.getTracks().forEach(e=>e.stop())}}async captureHtml2CanvasIsolated(){let e=document.createElement(`canvas`);e.width=window.innerWidth,e.height=window.innerHeight;let t=e.getContext(`2d`);t.fillStyle=`#000`,t.fillRect(0,0,e.width,e.height);let n=document.getElementById(`player-container`);if(!n)return e.toDataURL(`image/jpeg`,.8).split(`,`)[1];this._html2canvasMod||=(await O(async()=>{let{default:e}=await import(`./html2canvas-BdjGaL7S.js`).then(e=>w(e.default,1));return{default:e}},[],import.meta.url)).default,this.renderer&&(this.renderer._resizeSuppressed=!0);let r=n.style.contain||``;n.style.contain=`strict`;try{let r=n.getBoundingClientRect(),i=getComputedStyle(n),a=i.backgroundColor;a&&a!==`transparent`&&a!==`rgba(0, 0, 0, 0)`&&(t.fillStyle=a,t.fillRect(r.left,r.top,r.width,r.height));let o=i.backgroundImage;if(o&&o!==`none`){let e=o.match(/url\(["']?(.*?)["']?\)/);if(e)try{let n=new Image;n.crossOrigin=`anonymous`,await new Promise(t=>{n.onload=()=>t(),n.onerror=()=>t(),setTimeout(()=>t(),2e3),n.src=e[1]}),n.naturalWidth&&t.drawImage(n,r.left,r.top,r.width,r.height)}catch{}}let s=n.querySelectorAll(`img, video, iframe, canvas`),c=0;for(let e of s){let n=e;if(n.style.visibility===`hidden`||n.style.display===`none`)continue;let r=e.getBoundingClientRect();if(!(r.width===0||r.height===0))try{if(e instanceof HTMLImageElement){if(!e.complete||!e.naturalWidth)continue;if(getComputedStyle(e).objectFit===`contain`&&e.naturalWidth&&e.naturalHeight){let n=this.containedRect(e.naturalWidth,e.naturalHeight,r);t.drawImage(e,n.x,n.y,n.w,n.h)}else t.drawImage(e,r.left,r.top,r.width,r.height);c++}else if(e instanceof HTMLVideoElement){if(e.readyState<2)continue;if(getComputedStyle(e).objectFit===`contain`&&e.videoWidth&&e.videoHeight){let n=this.containedRect(e.videoWidth,e.videoHeight,r);t.drawImage(e,n.x,n.y,n.w,n.h)}else t.drawImage(e,r.left,r.top,r.width,r.height);c++}else if(e instanceof HTMLCanvasElement)t.drawImage(e,r.left,r.top,r.width,r.height),c++;else if(e instanceof HTMLIFrameElement){let n=e.contentDocument;if(!n?.body)continue;let i=document.createElement(`div`);i.style.cssText=`position:fixed;left:-9999px;top:0;width:${r.width}px;height:${r.height}px;overflow:hidden;`;let a=[];for(let e of n.querySelectorAll(`style`))i.appendChild(e.cloneNode(!0));for(let e of n.querySelectorAll(`link[rel="stylesheet"]`)){let t=document.createElement(`link`);t.rel=`stylesheet`,t.href=new URL(e.getAttribute(`href`)||``,n.baseURI).href,i.appendChild(t),a.push(new Promise(e=>{t.onload=()=>e(),t.onerror=()=>e()}))}let o=n.body.cloneNode(!0);for(let e of o.querySelectorAll(`img[src]`)){let t=e.getAttribute(`src`)||``;t&&!t.startsWith(`http`)&&!t.startsWith(`data:`)&&!t.startsWith(`blob:`)&&e.setAttribute(`src`,new URL(t,n.baseURI).href)}i.appendChild(o),document.body.appendChild(i);let s=n.querySelectorAll(`img`),l=new Map;s.forEach((e,t)=>{e.naturalWidth&&e.naturalHeight&&l.set(String(t),{nw:e.naturalWidth,nh:e.naturalHeight})}),a.length>0&&await Promise.race([Promise.all(a),new Promise(e=>setTimeout(e,500))]);let u=await this._html2canvasMod(i,{useCORS:!0,allowTaint:!0,logging:!1,backgroundColor:null,width:r.width,height:r.height,onclone:e=>{let t=e.createElement(`style`);t.textContent=`*, *::before, *::after { animation: none !important; transition: none !important; opacity: 1 !important; }`,e.head.appendChild(t),e.querySelectorAll(`img`).forEach((t,n)=>{let r=e.defaultView?.getComputedStyle(t);if(!r||r.objectFit!==`contain`)return;let i=l.get(String(n));if(!i)return;let a=t.clientWidth||parseFloat(r.width)||0,o=t.clientHeight||parseFloat(r.height)||0;if(!a||!o)return;let s=i.nw/i.nh,c=a/o,u,d;s>c?(u=a,d=a/s):(d=o,u=o*s);let f=e.createElement(`div`);f.style.cssText=`width:${a}px;height:${o}px;display:flex;align-items:center;justify-content:center;overflow:hidden;`,t.style.objectFit=`fill`,t.style.width=`${u}px`,t.style.height=`${d}px`,t.parentNode?.insertBefore(f,t),f.appendChild(t)})}});document.body.removeChild(i),t.drawImage(u,r.left,r.top,r.width,r.height);let d=e.getBoundingClientRect();for(let e of n.querySelectorAll(`video`)){if(e.readyState<2)continue;let r=e.getBoundingClientRect();if(!(r.width===0||r.height===0))try{if(n.defaultView?.getComputedStyle(e)?.objectFit===`contain`&&e.videoWidth&&e.videoHeight){let n=this.containedRect(e.videoWidth,e.videoHeight,new DOMRect(d.left+r.left,d.top+r.top,r.width,r.height));t.drawImage(e,n.x,n.y,n.w,n.h)}else t.drawImage(e,d.left+r.left,d.top+r.top,r.width,r.height)}catch{}}for(let e of n.querySelectorAll(`canvas`)){let n=e.getBoundingClientRect();if(!(n.width===0||n.height===0))try{t.drawImage(e,d.left+n.left,d.top+n.top,n.width,n.height)}catch{}}c++}}catch(t){G.warn(`Screenshot: failed to draw element`,e.tagName,t)}}return G.debug(`Screenshot: composed ${c}/${s.length} elements`),e.toDataURL(`image/jpeg`,.8).split(`,`)[1]}finally{n.style.contain=r,this.renderer&&(this.renderer._resizeSuppressed=!1)}}containedRect(e,t,n){let r=e/t,i=n.width/n.height,a,o;return r>i?(a=n.width,o=n.width/r):(o=n.height,a=n.height*r),{x:n.left+(n.width-a)/2,y:n.top+(n.height-o)/2,w:a,h:o}}startScreenshotInterval(){let e=this.displaySettings?.getSetting(`screenshotInterval`)||0;if(!e||e<=0)return;!this._html2canvasMod&&!window.electronAPI&&O(()=>import(`./html2canvas-BdjGaL7S.js`).then(e=>w(e.default,1)).then(e=>{this._html2canvasMod=e.default}),[],import.meta.url);let t=e*1e3;G.info(`Starting periodic screenshots every ${e}s`),this._screenshotInterval=setInterval(()=>{this.captureAndSubmitScreenshot()},t)}updateStatus(e,t=`info`){let n=document.getElementById(`status`);n&&(n.textContent=e,n.className=`status status-${t}`),t===`error`?G.error(`Status:`,e):G.info(`Status:`,e)}showOfflineIndicator(){this.timelineOverlay?.setOffline(!0)}removeOfflineIndicator(){this.timelineOverlay?.setOffline(!1)}_syncLeadAlive(){if(!this.syncManager)return!1;for(let[,e]of this.syncManager.followers)if(e.role===`lead`&&Date.now()-e.lastSeen<15e3)return!0;return!1}cleanup(){this.core.cleanup(),this.renderer.cleanup(),this._screenshotInterval&&=(clearInterval(this._screenshotInterval),null),this._wakeLock&&=(this._wakeLock.release(),null),this.downloadOverlay&&this.downloadOverlay.destroy(),this.timelineOverlay&&this.timelineOverlay.destroy(),this._iframeObserver&&=(this._iframeObserver.disconnect(),null),navigator.serviceWorker&&(this._swIcHandler&&=(navigator.serviceWorker.removeEventListener(`message`,this._swIcHandler),null)),Z?.clear(),this._probeTimer&&=(clearTimeout(this._probeTimer),null),this._mediaStatusTimer&&=(clearTimeout(this._mediaStatusTimer),null)}};function Ae(){let e=new ke;e.init().catch(e=>{G.error(`Failed to initialize:`,e),G.warn(`Redirecting to setup screen...`),window.location.href=`./setup.html`}),window.addEventListener(`beforeunload`,()=>{e.cleanup()})}document.readyState===`loading`?document.addEventListener(`DOMContentLoaded`,Ae):Ae();export{ee as n,C as r,O as t};
735
+ //# sourceMappingURL=main-CT7JIMxf.js.map