@xiboplayer/pwa 0.3.1 → 0.3.2

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 (40) hide show
  1. package/dist/assets/cache-proxy-CrlayfSe.js +2 -0
  2. package/dist/assets/cache-proxy-CrlayfSe.js.map +1 -0
  3. package/dist/assets/cms-api-Ce7EVg5h.js +2 -0
  4. package/dist/assets/cms-api-Ce7EVg5h.js.map +1 -0
  5. package/dist/assets/html2canvas.esm-CBrSDip1.js +23 -0
  6. package/dist/assets/html2canvas.esm-CBrSDip1.js.map +1 -0
  7. package/dist/assets/index-BF8qB-pu.js +2 -0
  8. package/dist/assets/index-BF8qB-pu.js.map +1 -0
  9. package/dist/assets/index-Baows0WY.js +2 -0
  10. package/dist/assets/index-Baows0WY.js.map +1 -0
  11. package/dist/assets/index-Be_IxwIZ.js +2 -0
  12. package/dist/assets/index-Be_IxwIZ.js.map +1 -0
  13. package/dist/assets/index-BhHwWvzx.js +2 -0
  14. package/dist/assets/index-BhHwWvzx.js.map +1 -0
  15. package/dist/assets/index-C77HSi9N.js +8 -0
  16. package/dist/assets/index-C77HSi9N.js.map +1 -0
  17. package/dist/assets/index-ChPoQ8Bt.js +607 -0
  18. package/dist/assets/index-ChPoQ8Bt.js.map +1 -0
  19. package/dist/assets/index-DPR3fBRV.js +2 -0
  20. package/dist/assets/index-DPR3fBRV.js.map +1 -0
  21. package/dist/assets/index-b1tfCACR.js +2 -0
  22. package/dist/assets/index-b1tfCACR.js.map +1 -0
  23. package/dist/assets/index-es8y3c70.js +2 -0
  24. package/dist/assets/index-es8y3c70.js.map +1 -0
  25. package/dist/assets/main-BUvkpHsV.js +44 -0
  26. package/dist/assets/main-BUvkpHsV.js.map +1 -0
  27. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js +2 -0
  28. package/dist/assets/modulepreload-polyfill-B5Qt9EMX.js.map +1 -0
  29. package/dist/assets/pdf-BnPRJEQ6.js +13 -0
  30. package/dist/assets/pdf-BnPRJEQ6.js.map +1 -0
  31. package/dist/assets/setup-B9GCkQRS.js +2 -0
  32. package/dist/assets/setup-B9GCkQRS.js.map +1 -0
  33. package/dist/assets/xmds-client-MaDHqpeL.js +16 -0
  34. package/dist/assets/xmds-client-MaDHqpeL.js.map +1 -0
  35. package/dist/index.html +130 -0
  36. package/dist/setup.html +371 -0
  37. package/dist/sw-pwa.js +2 -0
  38. package/dist/sw-pwa.js.map +1 -0
  39. package/dist/sw.test.js +271 -0
  40. package/package.json +9 -8
@@ -0,0 +1,2 @@
1
+ import{D as y,P as m}from"./main-BUvkpHsV.js";import"./cms-api-Ce7EVg5h.js";import{E as i}from"./cache-proxy-CrlayfSe.js";import"./modulepreload-polyfill-B5Qt9EMX.js";const r="0.3.1",a={version:r};class c extends i{constructor(){super(),this.currentLayoutId=null,this.currentScheduleId=null,this.displayName="",this.hardwareKey="",this.playerType="pwa",this.displayStatus="idle",this.screenWidth=0,this.screenHeight=0,this.lastCollectionTime=null,this.lastHeartbeat=null,this.isRegistered=!1}set(t,e){if(this[t]===e)return;const s=this[t];this[t]=e,this.emit("change",t,e,s)}toJSON(){return{currentLayoutId:this.currentLayoutId,currentScheduleId:this.currentScheduleId,displayName:this.displayName,hardwareKey:this.hardwareKey,playerType:this.playerType,displayStatus:this.displayStatus,screenWidth:this.screenWidth,screenHeight:this.screenHeight,lastCollectionTime:this.lastCollectionTime,lastHeartbeat:this.lastHeartbeat,isRegistered:this.isRegistered}}}const d=a.version;export{y as DataConnectorManager,m as PlayerCore,c as PlayerState,d as VERSION};
2
+ //# sourceMappingURL=index-es8y3c70.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-es8y3c70.js","sources":["../../../xiboplayer/packages/core/src/state.js","../../../xiboplayer/packages/core/src/index.js"],"sourcesContent":["/**\n * Centralized player state\n *\n * Single source of truth for player status, dimensions, and display info.\n * Avoids scattered state across PlayerCore, renderer, and platform layer.\n */\n\nimport { EventEmitter } from '@xiboplayer/utils';\n\nexport class PlayerState extends EventEmitter {\n constructor() {\n super();\n this.currentLayoutId = null;\n this.currentScheduleId = null;\n this.displayName = '';\n this.hardwareKey = '';\n this.playerType = 'pwa';\n this.displayStatus = 'idle'; // idle | collecting | rendering | error\n this.screenWidth = 0;\n this.screenHeight = 0;\n this.lastCollectionTime = null;\n this.lastHeartbeat = null;\n this.isRegistered = false;\n }\n\n /**\n * Update a state property and emit change event\n */\n set(key, value) {\n if (this[key] === value) return;\n const old = this[key];\n this[key] = value;\n this.emit('change', key, value, old);\n }\n\n /**\n * Get snapshot of current state (for status reporting)\n */\n toJSON() {\n return {\n currentLayoutId: this.currentLayoutId,\n currentScheduleId: this.currentScheduleId,\n displayName: this.displayName,\n hardwareKey: this.hardwareKey,\n playerType: this.playerType,\n displayStatus: this.displayStatus,\n screenWidth: this.screenWidth,\n screenHeight: this.screenHeight,\n lastCollectionTime: this.lastCollectionTime,\n lastHeartbeat: this.lastHeartbeat,\n isRegistered: this.isRegistered\n };\n }\n}\n","// @xiboplayer/core - Player core orchestration\nimport pkg from '../package.json' with { type: 'json' };\nexport const VERSION = pkg.version;\nexport { PlayerCore } from './player-core.js';\nexport { PlayerState } from './state.js';\nexport { DataConnectorManager } from './data-connectors.js';\n"],"names":["PlayerState","EventEmitter","key","value","old","VERSION","pkg"],"mappings":"qMASO,MAAMA,UAAoBC,CAAa,CAC5C,aAAc,CACZ,MAAK,EACL,KAAK,gBAAkB,KACvB,KAAK,kBAAoB,KACzB,KAAK,YAAc,GACnB,KAAK,YAAc,GACnB,KAAK,WAAa,MAClB,KAAK,cAAgB,OACrB,KAAK,YAAc,EACnB,KAAK,aAAe,EACpB,KAAK,mBAAqB,KAC1B,KAAK,cAAgB,KACrB,KAAK,aAAe,EACtB,CAKA,IAAIC,EAAKC,EAAO,CACd,GAAI,KAAKD,CAAG,IAAMC,EAAO,OACzB,MAAMC,EAAM,KAAKF,CAAG,EACpB,KAAKA,CAAG,EAAIC,EACZ,KAAK,KAAK,SAAUD,EAAKC,EAAOC,CAAG,CACrC,CAKA,QAAS,CACP,MAAO,CACL,gBAAiB,KAAK,gBACtB,kBAAmB,KAAK,kBACxB,YAAa,KAAK,YAClB,YAAa,KAAK,YAClB,WAAY,KAAK,WACjB,cAAe,KAAK,cACpB,YAAa,KAAK,YAClB,aAAc,KAAK,aACnB,mBAAoB,KAAK,mBACzB,cAAe,KAAK,cACpB,aAAc,KAAK,YACzB,CACE,CACF,CCnDY,MAACC,EAAUC,EAAI"}
@@ -0,0 +1,44 @@
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-Baows0WY.js","./cache-proxy-CrlayfSe.js","./cms-api-Ce7EVg5h.js","./index-Be_IxwIZ.js","./xmds-client-MaDHqpeL.js","./index-BF8qB-pu.js","./modulepreload-polyfill-B5Qt9EMX.js","./index-BhHwWvzx.js","./index-b1tfCACR.js","./index-C77HSi9N.js","./index-DPR3fBRV.js","./index-es8y3c70.js","./index-ChPoQ8Bt.js"])))=>i.map(i=>d[i]);
2
+ var ve=Object.defineProperty;var be=(h,e,t)=>e in h?ve(h,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):h[e]=t;var v=(h,e,t)=>be(h,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";import{c as O,i as me,f as Le,a as Se,r as $e}from"./cms-api-Ce7EVg5h.js";import{E as pe,C as Ce}from"./cache-proxy-CrlayfSe.js";const Ie="modulepreload",xe=function(h,e){return new URL(h,e).href},te={},T=function(e,t,i){let r=Promise.resolve();if(t&&t.length>0){const s=document.getElementsByTagName("link"),n=document.querySelector("meta[property=csp-nonce]"),a=(n==null?void 0:n.nonce)||(n==null?void 0:n.getAttribute("nonce"));r=Promise.allSettled(t.map(l=>{if(l=xe(l,i),l in te)return;te[l]=!0;const c=l.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!i)for(let g=s.length-1;g>=0;g--){const p=s[g];if(p.href===l&&(!c||p.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${l}"]${u}`))return;const m=document.createElement("link");if(m.rel=c?"stylesheet":Ie,c||(m.as="script"),m.crossOrigin="",m.href=l,a&&m.setAttribute("nonce",a),document.head.appendChild(m),c)return new Promise((g,p)=>{m.addEventListener("load",g),m.addEventListener("error",()=>p(new Error(`Unable to preload CSS for ${l}`)))})}))}function o(s){const n=new Event("vite:preloadError",{cancelable:!0});if(n.payload=s,window.dispatchEvent(n),!n.defaultPrevented)throw s}return r.then(s=>{for(const n of s||[])n.status==="rejected"&&o(n.reason);return e().catch(o)})};let Te=()=>({emit(h,...e){for(let t=this.events[h]||[],i=0,r=t.length;i<r;i++)t[i](...e)},events:{},on(h,e){var t;return((t=this.events)[h]||(t[h]=[])).push(e),()=>{var i;this.events[h]=(i=this.events[h])==null?void 0:i.filter(r=>e!==r)}}});const z=O("LayoutPool");class ke{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)){const i=this.layouts.get(e);Object.assign(i,t),i.lastAccess=Date.now();return}this.layouts.size>=this.maxSize&&this.evictLRU(),t.status="warm",t.lastAccess=Date.now(),this.layouts.set(e,t),z.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)){const t=this.layouts.get(e);t.status="hot",t.lastAccess=Date.now()}this.hotLayoutId=e}evict(e){const t=this.layouts.get(e);if(t){if(z.info(`Evicting layout ${e} from pool`),t.blobUrls&&t.blobUrls.size>0&&(t.blobUrls.forEach(i=>{URL.revokeObjectURL(i)}),z.info(`Revoked ${t.blobUrls.size} blob URLs for layout ${e}`)),t.mediaUrlCache)for(const[i,r]of t.mediaUrlCache)r&&typeof r=="string"&&r.startsWith("blob:")&&URL.revokeObjectURL(r);if(t.regions)for(const[i,r]of t.regions)r.timer&&(clearTimeout(r.timer),r.timer=null);t.container&&t.container.parentNode&&t.container.remove(),this.layouts.delete(e),this.hotLayoutId===e&&(this.hotLayoutId=null)}}evictLRU(){let e=null,t=1/0;for(const[i,r]of this.layouts)r.status==="warm"&&r.lastAccess<t&&(e=i,t=r.lastAccess);e!==null&&this.evict(e)}clearWarm(){let e=0;const t=[];for(const[i,r]of this.layouts)r.status==="warm"&&t.push(i);for(const i of t)this.evict(i),e++;return e>0&&z.info(`Cleared ${e} warm layout(s) from pool`),e}clearWarmNotIn(e){let t=0;const i=[];for(const[r,o]of this.layouts)o.status==="warm"&&!e.has(r)&&i.push(r);for(const r of i)this.evict(r),t++;return t>0&&z.info(`Cleared ${t} warm layout(s) no longer in schedule`),t}clear(){const e=Array.from(this.layouts.keys());for(const t of e)this.evict(t);this.hotLayoutId=null}get size(){return this.layouts.size}}const ie={fadeIn(h,e){const t=[{opacity:0},{opacity:1}],i={duration:e,easing:"linear",fill:"forwards"};return h.animate(t,i)},fadeOut(h,e){const t=[{opacity:1},{opacity:0}],i={duration:e,easing:"linear",fill:"forwards"};return h.animate(t,i)},getFlyKeyframes(h,e,t,i){const r={N:{x:0,y:i?-t:t},NE:{x:i?e:-e,y:i?-t:t},E:{x:i?e:-e,y:0},SE:{x:i?e:-e,y:i?t:-t},S:{x:0,y:i?t:-t},SW:{x:i?-e:e,y:i?t:-t},W:{x:i?-e:e,y:0},NW:{x:i?-e:e,y:i?-t:t}},o=r[h]||r.N;return i?{from:{transform:`translate(${o.x}px, ${o.y}px)`,opacity:0},to:{transform:"translate(0, 0)",opacity:1}}:{from:{transform:"translate(0, 0)",opacity:1},to:{transform:`translate(${o.x}px, ${o.y}px)`,opacity:0}}},flyIn(h,e,t,i,r){const o=this.getFlyKeyframes(t,i,r,!0),s={duration:e,easing:"ease-out",fill:"forwards"};return h.animate([o.from,o.to],s)},flyOut(h,e,t,i,r){const o=this.getFlyKeyframes(t,i,r,!1),s={duration:e,easing:"ease-in",fill:"forwards"};return h.animate([o.from,o.to],s)},apply(h,e,t,i,r){if(!e||!e.type)return null;const o=e.type.toLowerCase(),s=e.duration||1e3,n=e.direction||"N";switch(o){case"fadein":return t?this.fadeIn(h,s):null;case"fadeout":return t?null:this.fadeOut(h,s);case"flyin":return t?this.flyIn(h,s,n,i,r):null;case"flyout":return t?null:this.flyOut(h,s,n,i,r);default:return null}}};class _e{constructor(e,t,i={}){this.config=e,this.container=t,this.options=i,this.log=O("RendererLite",i.logLevel),this.emitter=Te(),this.currentLayout=null,this.currentLayoutId=null,this.regions=new Map,this.layoutTimer=null,this.layoutEndEmitted=!1,this._paused=!1,this._layoutTimerStartedAt=null,this._layoutTimerDurationMs=null,this._layoutTimerRemaining=null,this.widgetTimers=new Map,this.mediaUrlCache=new Map,this.layoutBlobUrls=new Map,this.scaleFactor=1,this.offsetX=0,this.offsetY=0,this.overlayContainer=null,this.activeOverlays=new Map,this._keydownHandler=null,this._keyboardActions=[],this.layoutPool=new ke(2),this.preloadTimer=null,this._preloadRetryTimer=null,this.setupContainer(),this.log.info("Initialized")}setupContainer(){this.container.style.position="relative",this.container.style.width="100%",this.container.style.height="100vh",this.container.style.overflow="hidden",typeof ResizeObserver<"u"&&(this.resizeObserver=new ResizeObserver(()=>{this.rescaleRegions()}),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){const t=this.container.clientWidth,i=this.container.clientHeight;if(!t||!i)return;const r=t/e.width,o=i/e.height;this.scaleFactor=Math.min(r,o),this.offsetX=(t-e.width*this.scaleFactor)/2,this.offsetY=(i-e.height*this.scaleFactor)/2,this.log.info(`Scale: ${this.scaleFactor.toFixed(3)} (${e.width}x${e.height} → ${t}x${i}, offset ${Math.round(this.offsetX)},${Math.round(this.offsetY)})`)}applyRegionScale(e,t){const i=this.scaleFactor;e.style.left=`${t.left*i+this.offsetX}px`,e.style.top=`${t.top*i+this.offsetY}px`,e.style.width=`${t.width*i}px`,e.style.height=`${t.height*i}px`}rescaleRegions(){if(this.currentLayout){this.calculateScale(this.currentLayout);for(const[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(const[e,t]of this.activeOverlays){this.calculateScale(t.layout);for(const[i,r]of t.regions)this.applyRegionScale(r.element,r.config),r.width=r.config.width*this.scaleFactor,r.height=r.config.height*this.scaleFactor}}}on(e,t){return this.emitter.on(e,t)}emit(e,...t){this.emitter.emit(e,...t)}parseActions(e){const t=[];for(const i of e.children)i.tagName==="action"&&t.push({actionType:i.getAttribute("actionType")||"",triggerType:i.getAttribute("triggerType")||"",triggerCode:i.getAttribute("triggerCode")||"",layoutCode:i.getAttribute("layoutCode")||"",targetId:i.getAttribute("targetId")||"",commandCode:i.getAttribute("commandCode")||""});return t}parseXlf(e){const i=new DOMParser().parseFromString(e,"text/xml"),r=i.querySelector("layout");if(!r)throw new Error("Invalid XLF: no <layout> element");const o=r.getAttribute("duration"),s={width:parseInt(r.getAttribute("width")||"1920"),height:parseInt(r.getAttribute("height")||"1080"),duration:o?parseInt(o):0,bgcolor:r.getAttribute("bgcolor")||"#000000",background:r.getAttribute("background")||null,regions:[]};o?this.log.info(`Layout duration from XLF: ${s.duration}s`):this.log.info("Layout duration NOT in XLF, will calculate from widgets");for(const n of i.querySelectorAll("region")){const a={id:n.getAttribute("id"),width:parseInt(n.getAttribute("width")),height:parseInt(n.getAttribute("height")),top:parseInt(n.getAttribute("top")),left:parseInt(n.getAttribute("left")),zindex:parseInt(n.getAttribute("zindex")||"0"),actions:this.parseActions(n),widgets:[]};for(const l of n.querySelectorAll("media")){const c=this.parseWidget(l);a.widgets.push(c)}s.regions.push(a)}if(s.duration===0){let n=0;for(const a of s.regions){let l=0;for(const c of a.widgets)if(c.duration>0)l+=c.duration;else{l=60;break}n=Math.max(n,l)}s.duration=n>0?n:60,this.log.info(`Calculated layout duration: ${s.duration}s (not specified in XLF)`)}return s}parseWidget(e){const t=e.getAttribute("type"),i=parseInt(e.getAttribute("duration")||"10"),r=parseInt(e.getAttribute("useDuration")||"1"),o=e.getAttribute("id"),s=e.getAttribute("fileId"),n={},a=e.querySelector("options");if(a)for(const m of a.children)n[m.tagName]=m.textContent;const l=e.querySelector("raw"),c=l?l.textContent:"",u={in:null,out:null};n.transIn&&(u.in={type:n.transIn,duration:parseInt(n.transInDuration||"1000"),direction:n.transInDirection||"N"}),n.transOut&&(u.out={type:n.transOut,duration:parseInt(n.transOutDuration||"1000"),direction:n.transOutDirection||"N"});const y=this.parseActions(e);return{type:t,duration:i,useDuration:r,id:o,fileId:s,options:n,raw:c,transitions:u,actions:y}}trackBlobUrl(e){this.currentLayoutId&&(this.layoutBlobUrls.has(this.currentLayoutId)||this.layoutBlobUrls.set(this.currentLayoutId,new Set),this.layoutBlobUrls.get(this.currentLayoutId).add(e))}revokeBlobUrlsForLayout(e){const t=this.layoutBlobUrls.get(e);t&&(t.forEach(i=>{URL.revokeObjectURL(i)}),this.layoutBlobUrls.delete(e),this.log.info(`Revoked ${t.size} blob URLs for layout ${e}`))}updateLayoutDuration(){if(!this.currentLayout)return;let e=0;for(const t of this.currentLayout.regions){let i=0;for(const r of t.widgets)r.duration>0&&(i+=r.duration);e=Math.max(e,i)}if(e>0&&e!==this.currentLayout.duration){const t=this.currentLayout.duration;if(this.currentLayout.duration=e,this.log.info(`Layout duration updated: ${t}s → ${e}s (based on video metadata)`),this.emit("layoutDurationUpdated",this.currentLayoutId,e),this.layoutTimer){clearTimeout(this.layoutTimer);const i=this.currentLayout.duration*1e3;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))},i),this.log.info(`Layout timer reset to ${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){const t=[];let i=0;for(const r of e.regions){const o=this.regions.get(r.id);if(o){for(const s of r.actions||[])s.triggerType==="touch"?(this.attachTouchAction(o.element,s,r.id,null),i++):s.triggerType.startsWith("keyboard:")&&t.push(s);for(const s of r.widgets){if(!s.actions||s.actions.length===0)continue;const n=o.widgetElements.get(s.id);if(n)for(const a of s.actions)a.triggerType==="touch"?(this.attachTouchAction(n,a,r.id,s.id),i++):a.triggerType.startsWith("keyboard:")&&t.push(a)}}}this.setupKeyboardListener(t),(i>0||t.length>0)&&this.log.info(`Actions attached: ${i} touch, ${t.length} keyboard`)}attachTouchAction(e,t,i,r){e.style.cursor="pointer";const o=s=>{s.stopPropagation();const n=r?`widget ${r}`:`region ${i}`;this.log.info(`Touch action fired on ${n}: ${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:i,widgetId:r}})};e.addEventListener("click",o),e._actionHandlers||(e._actionHandlers=[]),e._actionHandlers.push(o)}setupKeyboardListener(e){this.removeKeyboardListener(),this._keyboardActions=e,e.length!==0&&(this._keydownHandler=t=>{const i=t.key;for(const r of this._keyboardActions){const o=r.triggerType.substring(9);if(i===o){this.log.info(`Keyboard action (key: ${i}): ${r.actionType}`),this.emit("action-trigger",{actionType:r.actionType,triggerType:r.triggerType,triggerCode:r.triggerCode,layoutCode:r.layoutCode,targetId:r.targetId,commandCode:r.commandCode,source:{key:i}});break}}},document.addEventListener("keydown",this._keydownHandler))}removeKeyboardListener(){this._keydownHandler&&(document.removeEventListener("keydown",this._keydownHandler),this._keydownHandler=null),this._keyboardActions=[]}removeActionListeners(){for(const[,e]of this.regions){this._cleanElementActionHandlers(e.element);for(const[,t]of e.widgetElements)this._cleanElementActionHandlers(t)}this.removeKeyboardListener()}_cleanElementActionHandlers(e){if(e._actionHandlers){for(const t of e._actionHandlers)e.removeEventListener("click",t);delete e._actionHandlers,e.style.cursor=""}}navigateToWidget(e){for(const[t,i]of this.regions){const r=i.widgets.findIndex(o=>o.id===e);if(r!==-1){if(this.log.info(`Navigating to widget ${e} in region ${t} (index ${r})`),i.timer&&(clearTimeout(i.timer),i.timer=null),this.stopWidget(t,i.currentIndex),i.currentIndex=r,this.renderWidget(t,r),i.widgets.length>1){const s=i.widgets[r].duration*1e3;i.timer=setTimeout(()=>{this.stopWidget(t,r);const n=(r+1)%i.widgets.length;i.currentIndex=n,this.startRegion(t)},s)}return}}this.log.warn(`Target widget ${e} not found in any region`)}nextWidget(e){const t=e?this.regions.get(e):this.regions.values().next().value;if(!t||t.widgets.length<=1)return;const i=(t.currentIndex+1)%t.widgets.length,r=t.widgets[i];this.log.info(`nextWidget → index ${i} (widget ${r.id})`),this.navigateToWidget(r.id)}previousWidget(e){const t=e?this.regions.get(e):this.regions.values().next().value;if(!t||t.widgets.length<=1)return;const i=(t.currentIndex-1+t.widgets.length)%t.widgets.length,r=t.widgets[i];this.log.info(`previousWidget → index ${i} (widget ${r.id})`),this.navigateToWidget(r.id)}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!)`);for(const[o,s]of this.regions)s.timer&&(clearTimeout(s.timer),s.timer=null),s.currentIndex=0;this.layoutTimer&&(clearTimeout(this.layoutTimer),this.layoutTimer=null),this.layoutEndEmitted=!1,this.emit("layoutStart",t,this.currentLayout);for(const[o,s]of this.regions)this.startRegion(o);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();const r=this.parseXlf(e);if(this.currentLayout=r,this.currentLayoutId=t,this.calculateScale(r),this.container.style.backgroundColor=r.bgcolor,this.container.style.backgroundImage="",r.background&&this.options.getMediaUrl)try{const o=await this.options.getMediaUrl(parseInt(r.background));o&&(this.container.style.backgroundImage=`url(${o})`,this.container.style.backgroundSize="cover",this.container.style.backgroundPosition="center",this.container.style.backgroundRepeat="no-repeat",this.log.info(`Background image set: ${r.background}`))}catch(o){this.log.warn("Failed to load background image:",o)}if(this.options.getMediaUrl){const o=[];this.mediaUrlCache.clear();for(const s of r.regions)for(const n of s.widgets)if(n.fileId){const a=parseInt(n.fileId||n.id);this.mediaUrlCache.has(a)||o.push(this.options.getMediaUrl(a).then(l=>{this.mediaUrlCache.set(a,l)}).catch(l=>{this.log.warn(`Failed to fetch media ${a}:`,l)}))}o.length>0&&(this.log.info(`Pre-fetching ${o.length} media URLs in parallel...`),await Promise.all(o),this.log.info("All media URLs pre-fetched"))}for(const o of r.regions)await this.createRegion(o);this.log.info("Pre-creating widget elements for instant transitions...");for(const[o,s]of this.regions)for(let n=0;n<s.widgets.length;n++){const a=s.widgets[n];a.layoutId=this.currentLayoutId,a.regionId=o;try{const l=await this.createWidgetElement(a,s);l.style.visibility="hidden",l.style.opacity="0",s.element.appendChild(l),s.widgetElements.set(a.id,l)}catch(l){this.log.error(`Failed to pre-create widget ${a.id}:`,l)}}this.log.info("All widget elements pre-created"),this.attachActionListeners(r),this.emit("layoutStart",t,r);for(const[o,s]of this.regions)this.startRegion(o);this.startLayoutTimerWhenReady(t,r),this._scheduleNextLayoutPreload(r),this.log.info(`Layout ${t} started`)}catch(i){throw this.log.error("Error rendering layout:",i),this.emit("error",{type:"layoutError",error:i,layoutId:t}),i}}async createRegion(e){const t=document.createElement("div");t.id=`region_${e.id}`,t.className="renderer-lite-region",t.style.position="absolute",t.style.zIndex=e.zindex,t.style.overflow="hidden",this.applyRegionScale(t,e),this.container.appendChild(t);const i=this.scaleFactor;this.regions.set(e.id,{element:t,config:e,widgets:e.widgets,currentIndex:0,timer:null,width:e.width*i,height:e.height*i,complete:!1,widgetElements:new Map})}startRegion(e){const t=this.regions.get(e);this._startRegionCycle(t,e,(i,r)=>this.renderWidget(i,r),(i,r)=>this.stopWidget(i,r),()=>{this.log.info(`Region ${e} completed one full cycle`),this.checkLayoutComplete()})}async createWidgetElement(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);default:return await this.renderGenericWidget(e,t)}}findMediaElement(e,t){return e.tagName===t?e:e.querySelector(t.toLowerCase())}updateMediaElement(e,t){const i=this.findMediaElement(e,"VIDEO")||this.findMediaElement(e,"AUDIO");i&&(this._restartMediaElement(i),this.log.info(`${i.tagName==="VIDEO"?"Video":"Audio"} restarted: ${t.fileId||t.id}`))}_restartMediaElement(e){e.currentTime=0;const t=()=>{e.removeEventListener("seeked",t),e.play().catch(()=>{})};e.addEventListener("seeked",t),e.currentTime===0&&e.readyState>=2&&(e.removeEventListener("seeked",t),e.play().catch(()=>{}))}waitForWidgetReady(e,t){const r=this.findMediaElement(e,"VIDEO");if(r)return!r.paused&&r.readyState>=3?Promise.resolve():new Promise(s=>{const n=setTimeout(()=>{this.log.warn(`Video ready timeout (10000ms) for widget ${t.id}`),s()},1e4),a=()=>{r.removeEventListener("playing",a),clearTimeout(n),this.log.info(`Video widget ${t.id} ready (playing)`),s()};r.addEventListener("playing",a)});const o=this.findMediaElement(e,"IMG");return o?o.complete&&o.naturalWidth>0?Promise.resolve():new Promise(s=>{const n=setTimeout(()=>{this.log.warn(`Image ready timeout for widget ${t.id}`),s()},1e4),a=()=>{o.removeEventListener("load",a),clearTimeout(n),s()};o.addEventListener("load",a)}):Promise.resolve()}async startLayoutTimerWhenReady(e,t){if(!t||t.duration<=0)return;const i=[];for(const[o,s]of this.regions){if(s.widgets.length===0)continue;const n=s.widgets[s.currentIndex||0],a=s.widgetElements.get(n.id);a&&i.push(this.waitForWidgetReady(a,n))}if(i.length>0&&(this.log.info(`Waiting for ${i.length} widget(s) to be ready before starting layout timer...`),await Promise.all(i),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}const r=t.duration*1e3;this.log.info(`Layout ${e} will end after ${t.duration}s`),this._layoutTimerStartedAt=Date.now(),this._layoutTimerDurationMs=r,this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${e} duration expired (${t.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},r)}async _showWidget(e,t){const i=e.widgets[t];if(!i)return null;let r=e.widgetElements.get(i.id);r||(this.log.warn(`Widget ${i.id} not pre-created, creating now`),r=await this.createWidgetElement(i,e),e.widgetElements.set(i.id,r),e.element.appendChild(r));for(const[o,s]of e.widgetElements)o!==i.id&&(s.style.visibility="hidden",s.style.opacity="0");return this.updateMediaElement(r,i),r.style.visibility="visible",i.transitions.in?ie.apply(r,i.transitions.in,!0,e.width,e.height):r.style.opacity="1",i}_hideWidget(e,t){const i=e.widgets[t];if(!i)return{widget:null,animPromise:null};const r=e.widgetElements.get(i.id);if(!r)return{widget:null,animPromise:null};let o=null;if(i.transitions.out){const a=ie.apply(r,i.transitions.out,!1,e.width,e.height);a&&(o=new Promise(l=>{a.onfinish=l}))}const s=r.querySelector("video");s&&i.options.loop!=="1"&&s.pause();const n=r.querySelector("audio");return n&&i.options.loop!=="1"&&n.pause(),{widget:i,animPromise:o}}_startRegionCycle(e,t,i,r,o){if(!e||e.widgets.length===0)return;if(e.widgets.length===1){i(t,0);return}const s=()=>{const n=e.currentIndex,a=e.widgets[n];i(t,n);const l=a.duration*1e3;e.timer=setTimeout(()=>{r(t,n);const c=(e.currentIndex+1)%e.widgets.length;c===0&&!e.complete&&(e.complete=!0,o==null||o()),e.currentIndex=c,s()},l)};s()}async renderWidget(e,t){var r;const i=this.regions.get(e);if(i)try{const o=await this._showWidget(i,t);o&&(this.log.info(`Showing widget ${o.type} (${o.id}) in region ${e}`),this.emit("widgetStart",{widgetId:o.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(o.fileId||o.id)||null,type:o.type,duration:o.duration}))}catch(o){this.log.error("Error rendering widget:",o),this.emit("error",{type:"widgetError",error:o,widgetId:(r=i.widgets[t])==null?void 0:r.id,regionId:e})}}async stopWidget(e,t){const i=this.regions.get(e);if(!i)return;const{widget:r,animPromise:o}=this._hideWidget(i,t);o&&await o,r&&this.emit("widgetEnd",{widgetId:r.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(r.fileId||r.id)||null,type:r.type})}async renderImage(e,t){const i=document.createElement("img");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.objectFit="contain",i.style.opacity="0";const r=parseInt(e.fileId||e.id);let o=this.mediaUrlCache.get(r);return!o&&this.options.getMediaUrl?o=await this.options.getMediaUrl(r):o||(o=`${window.location.origin}/player/cache/media/${e.options.uri}`),i.src=o,i}async renderVideo(e,t){const i=document.createElement("video");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.objectFit="contain",i.style.opacity="1",i.autoplay=!0,i.preload="auto",i.muted=e.options.mute==="1",i.loop=!1,i.controls=me(),i.playsInline=!0,i.addEventListener("ended",()=>{e.options.loop==="1"?(i.currentTime=0,this.log.info(`Video ${r} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Video ${r} ended - paused on last frame`)});const r=parseInt(e.fileId||e.id);let o=this.mediaUrlCache.get(r);if(!o&&this.options.getMediaUrl?o=await this.options.getMediaUrl(r):o||(o=`${window.location.origin}/player/cache/media/${r}`),o.includes(".m3u8"))if(i.canPlayType("application/vnd.apple.mpegurl"))this.log.info(`HLS stream (native): ${r}`),i.src=o;else try{const{default:n}=await T(async()=>{const{default:a}=await import("hls.js");return{default:a}},[],import.meta.url);if(n.isSupported()){const a=new n({enableWorker:!0,lowLatencyMode:!0});a.loadSource(o),a.attachMedia(i),a.on(n.Events.ERROR,(l,c)=>{c.fatal&&(this.log.error(`HLS fatal error: ${c.type}`,c.details),a.destroy())}),this.log.info(`HLS stream (hls.js): ${r}`)}else this.log.warn(`HLS not supported on this browser for ${r}`),i.src=o}catch(n){this.log.warn(`hls.js not available, falling back to native: ${n.message}`),i.src=o}else i.src=o;return i.addEventListener("loadedmetadata",()=>{const n=Math.floor(i.duration);this.log.info(`Video ${r} duration detected: ${n}s`),(e.duration===0||e.useDuration===0)&&(e.duration=n,this.log.info(`Updated widget ${e.id} duration to ${n}s (useDuration=0)`),this.updateLayoutDuration())}),i.addEventListener("loadeddata",()=>{this.log.info("Video loaded and ready:",r)}),i.addEventListener("error",n=>{const a=i.error,l=a==null?void 0:a.code,c=(a==null?void 0:a.message)||"Unknown error";this.log.warn(`Video error (non-fatal, logged only): ${r}, code: ${l}, time: ${i.currentTime.toFixed(1)}s, message: ${c}`)}),i.addEventListener("playing",()=>{this.log.info("Video playing:",r)}),this.log.info("Video element created:",r,i.src),i}async renderAudio(e,t){const i=document.createElement("div");i.className="renderer-lite-widget audio-widget",i.style.width="100%",i.style.height="100%",i.style.display="flex",i.style.flexDirection="column",i.style.alignItems="center",i.style.justifyContent="center",i.style.background="linear-gradient(135deg, #667eea 0%, #764ba2 100%)",i.style.opacity="0";const r=document.createElement("audio");r.autoplay=!0,r.loop=e.options.loop==="1",r.volume=parseFloat(e.options.volume||"100")/100;const o=parseInt(e.fileId||e.id);let s=this.mediaUrlCache.get(o);!s&&this.options.getMediaUrl?s=await this.options.getMediaUrl(o):s||(s=`${window.location.origin}/player/cache/media/${o}`),r.src=s;const n=document.createElement("div");n.innerHTML="♪",n.style.fontSize="120px",n.style.color="white",n.style.marginBottom="20px";const a=document.createElement("div");a.style.color="white",a.style.fontSize="24px",a.textContent="Playing Audio";const l=document.createElement("div");return l.style.color="rgba(255,255,255,0.7)",l.style.fontSize="16px",l.style.marginTop="10px",l.textContent=e.options.uri,i.appendChild(r),i.appendChild(n),i.appendChild(a),i.appendChild(l),i}async renderTextWidget(e,t){const i=document.createElement("iframe");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.border="none",i.style.opacity="0";let r=e.raw;if(this.options.getWidgetHtml){const n=await this.options.getWidgetHtml(e);if(n&&typeof n=="object"&&n.url){if(i.src=n.url,n.fallback){const a=this;i.addEventListener("load",function(){var l;try{if(!((l=i.contentDocument)!=null&&l.querySelector("base"))){console.warn("[RendererLite] Cache URL failed (hard reload?), using original CMS URLs");const c=new Blob([n.fallback],{type:"text/html"}),u=URL.createObjectURL(c);a.trackBlobUrl(u),i.src=u}}catch{}},{once:!0})}return i}r=n}const o=new Blob([r],{type:"text/html"}),s=URL.createObjectURL(o);return i.src=s,this.trackBlobUrl(s),i}async renderPdf(e,t){const i=document.createElement("div");if(i.className="renderer-lite-widget pdf-widget",i.style.width="100%",i.style.height="100%",i.style.backgroundColor="#525659",i.style.opacity="0",i.style.position="relative",typeof window.pdfjsLib>"u")try{const s=await T(()=>import("./pdf-BnPRJEQ6.js"),[],import.meta.url);window.pdfjsLib=s,window.pdfjsLib.GlobalWorkerOptions.workerSrc=`${window.location.origin}/player/pdf.worker.min.mjs`}catch(s){return this.log.error("PDF.js not available:",s),i.innerHTML='<div style="color:white;padding:20px;text-align:center;">PDF viewer unavailable</div>',i.style.opacity="1",i}const r=parseInt(e.fileId||e.id);let o=this.mediaUrlCache.get(r);!o&&this.options.getMediaUrl?o=await this.options.getMediaUrl(r):o||(o=`${window.location.origin}/player/cache/media/${e.options.uri}`);try{const a=await(await window.pdfjsLib.getDocument(o).promise).getPage(1),l=a.getViewport({scale:1}),c=Math.min(t.width/l.width,t.height/l.height),u=a.getViewport({scale:c}),y=document.createElement("canvas");y.width=u.width,y.height=u.height,y.style.display="block",y.style.margin="auto";const m=y.getContext("2d");await a.render({canvasContext:m,viewport:u}).promise,i.appendChild(y)}catch(s){this.log.error("PDF render failed:",s),i.innerHTML='<div style="color:white;padding:20px;text-align:center;">Failed to load PDF</div>'}return i.style.opacity="1",i}async renderWebpage(e,t){const i=document.createElement("iframe");return i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.border="none",i.style.opacity="0",i.src=e.options.uri,i}async renderGenericWidget(e,t){const i=document.createElement("iframe");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.border="none",i.style.opacity="0";let r=e.raw;if(this.options.getWidgetHtml){const o=await this.options.getWidgetHtml(e);if(o&&typeof o=="object"&&o.url){if(i.src=o.url,o.fallback){const s=this;i.addEventListener("load",function(){var n;try{if(!((n=i.contentDocument)!=null&&n.querySelector("base"))){console.warn("[RendererLite] Cache URL failed (hard reload?), using original CMS URLs");const a=new Blob([o.fallback],{type:"text/html"}),l=URL.createObjectURL(a);s.trackBlobUrl(l),i.src=l}}catch{}},{once:!0})}return i}r=o}if(r){const o=new Blob([r],{type:"text/html"}),s=URL.createObjectURL(o);i.src=s,this.trackBlobUrl(s)}else this.log.warn(`No HTML for widget ${e.id}`),i.srcdoc='<div style="padding:20px;">Widget content unavailable</div>';return i}_scheduleNextLayoutPreload(e){this.preloadTimer&&(clearTimeout(this.preloadTimer),this.preloadTimer=null),this._preloadRetryTimer&&(clearTimeout(this._preloadRetryTimer),this._preloadRetryTimer=null);const t=e.duration||60,i=t*1e3*.75,r=t*1e3*.9;this.log.info(`Scheduling next layout preload in ${(i/1e3).toFixed(1)}s (75% of ${t}s)`),this.preloadTimer=setTimeout(()=>{this.preloadTimer=null,this.emit("request-next-layout-preload")},i),this._preloadRetryTimer=setTimeout(()=>{this._preloadRetryTimer=null,this.emit("request-next-layout-preload")},r)}async preloadLayout(e,t){if(this.layoutPool.has(t))return this.log.info(`Layout ${t} already in preload pool, skipping`),!0;if(this.currentLayoutId===t)return this.log.info(`Layout ${t} is current, skipping preload`),!0;try{this.log.info(`Preloading layout ${t} into pool...`);const i=this.parseXlf(e);this.calculateScale(i);const 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=i.bgcolor,i.background&&this.options.getMediaUrl)try{const m=await this.options.getMediaUrl(parseInt(i.background));m&&(r.style.backgroundImage=`url(${m})`,r.style.backgroundSize="cover",r.style.backgroundPosition="center",r.style.backgroundRepeat="no-repeat")}catch(m){this.log.warn("Preload: Failed to load background image:",m)}const o=new Map;if(this.options.getMediaUrl){const m=[];for(const g of i.regions)for(const p of g.widgets)if(p.fileId){const w=parseInt(p.fileId||p.id);o.has(w)||m.push(this.options.getMediaUrl(w).then(b=>{o.set(w,b)}).catch(b=>{this.log.warn(`Preload: Failed to fetch media ${w}:`,b)}))}m.length>0&&(this.log.info(`Preload: fetching ${m.length} media URLs...`),await Promise.all(m))}const s=this.mediaUrlCache,n=this.currentLayoutId;this.mediaUrlCache=o;const a=new Map,l=this.scaleFactor;for(const m of i.regions){const g=document.createElement("div");g.id=`preload_region_${t}_${m.id}`,g.className="renderer-lite-region",g.style.position="absolute",g.style.zIndex=m.zindex,g.style.overflow="hidden",this.applyRegionScale(g,m),r.appendChild(g);const p={element:g,config:m,widgets:m.widgets,currentIndex:0,timer:null,width:m.width*l,height:m.height*l,complete:!1,widgetElements:new Map};a.set(m.id,p)}const c=new Set,u=this.layoutBlobUrls;this.layoutBlobUrls=new Map,this.layoutBlobUrls.set(t,c),this.currentLayoutId=t;for(const[m,g]of a)for(let p=0;p<g.widgets.length;p++){const w=g.widgets[p];w.layoutId=t,w.regionId=m;try{const b=await this.createWidgetElement(w,g);b.style.visibility="hidden",b.style.opacity="0",g.element.appendChild(b),g.widgetElements.set(w.id,b)}catch(b){this.log.error(`Preload: Failed to create widget ${w.id}:`,b)}}return this.mediaUrlCache=s,this.currentLayoutId=n,r.querySelectorAll("video").forEach(m=>m.pause()),(this.layoutBlobUrls.get(t)||new Set).forEach(m=>c.add(m)),this.layoutBlobUrls=u,this.container.appendChild(r),this.layoutPool.add(t,{container:r,layout:i,regions:a,blobUrls:c,mediaUrlCache:o}),this.log.info(`Layout ${t} preloaded into pool (${a.size} regions, ${o.size} media)`),!0}catch(i){return this.log.error(`Preload failed for layout ${t}:`,i),!1}}async _swapToPreloadedLayout(e){const t=this.layoutPool.get(e);if(!t){this.log.error(`Cannot swap: layout ${e} not in pool`);return}this.removeActionListeners(),this.layoutTimer&&(clearTimeout(this.layoutTimer),this.layoutTimer=null),this.preloadTimer&&(clearTimeout(this.preloadTimer),this.preloadTimer=null),this._preloadRetryTimer&&(clearTimeout(this._preloadRetryTimer),this._preloadRetryTimer=null);const i=this.currentLayoutId;if(i&&this.layoutPool.has(i))this.layoutPool.evict(i);else{for(const[r,o]of this.regions)o.timer&&(clearTimeout(o.timer),o.timer=null),o.element.querySelectorAll("video").forEach(s=>{s.pause(),s.removeAttribute("src"),s.load()}),o.element.remove();i&&this.revokeBlobUrlsForLayout(i);for(const[r,o]of this.mediaUrlCache)o&&typeof o=="string"&&o.startsWith("blob:")&&URL.revokeObjectURL(o)}i&&!this.layoutEndEmitted&&this.emit("layoutEnd",i),this.regions.clear(),this.mediaUrlCache.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,this.mediaUrlCache=t.mediaUrlCache||new Map,this.layoutEndEmitted=!1,this.container.style.backgroundColor=t.layout.bgcolor,t.container.style.backgroundImage?(this.container.style.backgroundImage=t.container.style.backgroundImage,this.container.style.backgroundSize=t.container.style.backgroundSize,this.container.style.backgroundPosition=t.container.style.backgroundPosition,this.container.style.backgroundRepeat=t.container.style.backgroundRepeat):this.container.style.backgroundImage="",this.calculateScale(t.layout),this.attachActionListeners(t.layout),this.emit("layoutStart",e,t.layout);for(const[r,o]of this.regions)o.currentIndex=0,o.complete=!1,this.startRegion(r);this.updateLayoutDuration(),this.startLayoutTimerWhenReady(e,t.layout),this._scheduleNextLayoutPreload(t.layout),this.log.info(`Swapped to preloaded layout ${e} (instant transition)`)}checkLayoutComplete(){let e=!0;for(const[t,i]of this.regions)if(i.widgets.length>1&&!i.complete){e=!1;break}e&&this.currentLayoutId&&this.log.info("All multi-widget regions completed one cycle")}stopCurrentLayout(){if(this.currentLayout){if(this.log.info(`Stopping layout ${this.currentLayoutId}`),this.removeActionListeners(),this.layoutTimer&&(clearTimeout(this.layoutTimer),this.layoutTimer=null),this.preloadTimer&&(clearTimeout(this.preloadTimer),this.preloadTimer=null),this._preloadRetryTimer&&(clearTimeout(this._preloadRetryTimer),this._preloadRetryTimer=null),this.currentLayoutId&&this.layoutPool.has(this.currentLayoutId))this.layoutPool.evict(this.currentLayoutId);else{this.currentLayoutId&&this.revokeBlobUrlsForLayout(this.currentLayoutId);for(const[e,t]of this.regions)t.timer&&(clearTimeout(t.timer),t.timer=null),t.widgets.length>0&&this.stopWidget(e,t.currentIndex),t.element.remove();for(const[e,t]of this.mediaUrlCache)t&&t.startsWith("blob:")&&URL.revokeObjectURL(t)}this.regions.clear(),this.mediaUrlCache.clear(),this.currentLayoutId&&!this.layoutEndEmitted&&this.emit("layoutEnd",this.currentLayoutId),this.layoutEndEmitted=!1,this.currentLayout=null,this.currentLayoutId=null}}async renderOverlay(e,t,i=0){try{if(this.log.info(`Rendering overlay ${t} (priority ${i})`),this.activeOverlays.has(t)){this.log.warn(`Overlay ${t} already active, skipping`);return}const r=this.parseXlf(e),o=document.createElement("div");if(o.id=`overlay_${t}`,o.className="renderer-lite-overlay",o.style.position="absolute",o.style.top="0",o.style.left="0",o.style.width="100%",o.style.height="100%",o.style.zIndex=String(1e3+i),o.style.pointerEvents="auto",o.style.backgroundColor=r.bgcolor,this.options.getMediaUrl){const a=[];for(const l of r.regions)for(const c of l.widgets)if(c.fileId){const u=parseInt(c.fileId||c.id);this.mediaUrlCache.has(u)||a.push(this.options.getMediaUrl(u).then(y=>{this.mediaUrlCache.set(u,y)}).catch(y=>{this.log.warn(`Failed to fetch overlay media ${u}:`,y)}))}a.length>0&&(this.log.info(`Pre-fetching ${a.length} overlay media URLs...`),await Promise.all(a))}this.calculateScale(r);const s=new Map,n=this.scaleFactor;for(const a of r.regions){const l=document.createElement("div");l.id=`overlay_${t}_region_${a.id}`,l.className="renderer-lite-region overlay-region",l.style.position="absolute",l.style.zIndex=String(a.zindex),l.style.overflow="hidden",this.applyRegionScale(l,a),o.appendChild(l),s.set(a.id,{element:l,config:a,widgets:a.widgets,currentIndex:0,timer:null,width:a.width*n,height:a.height*n,complete:!1,widgetElements:new Map})}for(const[a,l]of s)for(const c of l.widgets){c.layoutId=t,c.regionId=a;try{const u=await this.createWidgetElement(c,l);u.style.visibility="hidden",u.style.opacity="0",l.element.appendChild(u),l.widgetElements.set(c.id,u)}catch(u){this.log.error(`Failed to pre-create overlay widget ${c.id}:`,u)}}this.overlayContainer.appendChild(o),this.activeOverlays.set(t,{container:o,layout:r,regions:s,timer:null,priority:i}),this.emit("overlayStart",t,r);for(const[a,l]of s)this.startOverlayRegion(t,a);if(r.duration>0){const a=r.duration*1e3,l=this.activeOverlays.get(t);l&&(l.timer=setTimeout(()=>{this.log.info(`Overlay ${t} duration expired (${r.duration}s)`),this.emit("overlayEnd",t)},a))}this.log.info(`Overlay ${t} started`)}catch(r){throw this.log.error("Error rendering overlay:",r),this.emit("error",{type:"overlayError",error:r,layoutId:t}),r}}startOverlayRegion(e,t){const i=this.activeOverlays.get(e);if(!i)return;const r=i.regions.get(t);this._startRegionCycle(r,t,(o,s)=>this.renderOverlayWidget(e,o,s),(o,s)=>this.stopOverlayWidget(e,o,s),()=>this.log.info(`Overlay ${e} region ${t} completed one full cycle`))}async renderOverlayWidget(e,t,i){var s;const r=this.activeOverlays.get(e);if(!r)return;const o=r.regions.get(t);if(o)try{const n=await this._showWidget(o,i);n&&(this.log.info(`Showing overlay widget ${n.type} (${n.id}) in overlay ${e} region ${t}`),this.emit("overlayWidgetStart",{overlayId:e,widgetId:n.id,regionId:t,type:n.type,duration:n.duration}))}catch(n){this.log.error("Error rendering overlay widget:",n),this.emit("error",{type:"overlayWidgetError",error:n,widgetId:(s=o.widgets[i])==null?void 0:s.id,regionId:t,overlayId:e})}}async stopOverlayWidget(e,t,i){const r=this.activeOverlays.get(e);if(!r)return;const o=r.regions.get(t);if(!o)return;const{widget:s,animPromise:n}=this._hideWidget(o,i);n&&await n,s&&this.emit("overlayWidgetEnd",{overlayId:e,widgetId:s.id,regionId:t,type:s.type})}stopOverlay(e){const 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),t.timer=null);for(const[i,r]of t.regions)r.timer&&(clearTimeout(r.timer),r.timer=null),r.widgets.length>0&&this.stopOverlayWidget(e,i,r.currentIndex);t.container&&t.container.remove(),this.revokeBlobUrlsForLayout(e),this.activeOverlays.delete(e),this.emit("overlayEnd",e),this.log.info(`Overlay ${e} stopped`)}stopAllOverlays(){const e=Array.from(this.activeOverlays.keys());for(const t of e)this.stopOverlay(t);this.log.info("All overlays stopped")}getActiveOverlays(){return Array.from(this.activeOverlays.keys())}pause(){if(!this._paused){if(this._paused=!0,this.layoutTimer&&this._layoutTimerStartedAt){const e=Date.now()-this._layoutTimerStartedAt;this._layoutTimerRemaining=Math.max(0,this._layoutTimerDurationMs-e),clearTimeout(this.layoutTimer),this.layoutTimer=null}for(const[,e]of this.regions)e.timer&&(clearTimeout(e.timer),e.timer=null);this._forEachMedia(e=>e.pause()),this.emit("paused"),this.log.info("Playback paused")}}resume(){if(this._paused){if(this._paused=!1,this._layoutTimerRemaining!=null&&this._layoutTimerRemaining>0){this._layoutTimerStartedAt=Date.now(),this._layoutTimerDurationMs=this._layoutTimerRemaining;const e=this.currentLayoutId;this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${e} duration expired (resumed)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},this._layoutTimerRemaining),this._layoutTimerRemaining=null}this._forEachMedia(e=>e.play().catch(()=>{}));for(const[e]of this.regions)this.startRegion(e);this.emit("resumed"),this.log.info("Playback resumed")}}_forEachMedia(e){var t;for(const[,i]of this.regions)(t=i.element)==null||t.querySelectorAll("video, audio").forEach(e)}cleanup(){this.stopAllOverlays(),this.stopCurrentLayout(),this.layoutPool.clear(),this.preloadTimer&&(clearTimeout(this.preloadTimer),this.preloadTimer=null),this._preloadRetryTimer&&(clearTimeout(this._preloadRetryTimer),this._preloadRetryTimer=null),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.container.innerHTML="",this.log.info("Cleaned up")}}const Z=O("schedule:criteria"),Me=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];function Ee(h,e,t={}){switch(h){case"dayOfWeek":return Me[e.getDay()];case"dayOfMonth":return String(e.getDate());case"month":return String(e.getMonth()+1);case"hour":return String(e.getHours());case"isoDay":return String(e.getDay()===0?7:e.getDay());default:return t[h]!==void 0?String(t[h]):(Z.debug(`Unknown metric: ${h}`),null)}}function Re(h,e,t,i){if(h===null)return!1;if(i==="number"){const s=parseFloat(h),n=parseFloat(t);if(isNaN(s)||isNaN(n))return!1;switch(e){case"equals":return s===n;case"notEquals":return s!==n;case"greaterThan":return s>n;case"greaterThanOrEquals":return s>=n;case"lessThan":return s<n;case"lessThanOrEquals":return s<=n;default:return!1}}const r=h.toLowerCase(),o=t.toLowerCase();switch(e){case"equals":return r===o;case"notEquals":return r!==o;case"contains":return r.includes(o);case"notContains":return!r.includes(o);case"startsWith":return r.startsWith(o);case"endsWith":return r.endsWith(o);case"in":return o.split(",").map(s=>s.trim().toLowerCase()).includes(r);case"greaterThan":return r>o;case"lessThan":return r<o;default:return Z.debug(`Unknown condition: ${e}`),!1}}function J(h,e={}){if(!h||h.length===0)return!0;const t=e.now||new Date,i=e.displayProperties||{};for(const r of h){const o=Ee(r.metric,t,i);if(!Re(o,r.condition,r.value,r.type))return Z.debug(`Criteria failed: ${r.metric} ${r.condition} "${r.value}" (actual: "${o}")`),!1}return!0}const A=O("Schedule");class De{constructor(e={}){this.schedule=null,this.playHistory=new Map,this.interruptScheduler=e.interruptScheduler||null,this.displayProperties=e.displayProperties||{},this.playerLocation=null,this._layoutMetadata=new Map}setSchedule(e){this.schedule=e}getDataConnectors(){var e;return((e=this.schedule)==null?void 0:e.dataConnectors)||[]}isRecurringScheduleActive(e,t){if(!e.recurrenceType||e.recurrenceType!=="Week")return!0;if(e.recurrenceRepeatsOn){const i=this.getIsoDayOfWeek(t);if(!e.recurrenceRepeatsOn.split(",").map(o=>parseInt(o.trim())).includes(i))return!1}if(e.recurrenceRange){const i=new Date(e.recurrenceRange);if(t>i)return!1}return!0}getIsoDayOfWeek(e){const t=e.getDay();return t===0?7:t}isTimeActive(e,t){const i=e.fromdt?new Date(e.fromdt):null,r=e.todt?new Date(e.todt):null;if(e.recurrenceType==="Week"){if(i&&r){const o=t.getHours()*3600+t.getMinutes()*60+t.getSeconds(),s=i.getHours()*3600+i.getMinutes()*60+i.getSeconds(),n=r.getHours()*3600+r.getMinutes()*60+r.getSeconds();return s<=n?o>=s&&o<=n:o>=s||o<=n}return!0}return!(i&&t<i||r&&t>r)}getCurrentLayouts(){return this._getLayoutsAt(new Date)}getLayoutsAtTime(e){return this._getLayoutsAt(e,{skipRateLimiting:!0,skipInterrupts:!0,quiet:!0})}getAllLayoutsAtTime(e){if(!this.schedule)return[];const t=e,i=[];if(this.schedule.layouts)for(const r of this.schedule.layouts)this.isRecurringScheduleActive(r,t)&&this.isTimeActive(r,t)&&(r.criteria&&r.criteria.length>0&&!J(r.criteria,{now:t,displayProperties:this.displayProperties})||r.isGeoAware&&r.geoLocation&&!this.isWithinGeoFence(r.geoLocation)||i.push({file:r.file,priority:r.priority||0,maxPlaysPerHour:r.maxPlaysPerHour||0}));if(this.schedule.campaigns){for(const r of this.schedule.campaigns)if(this.isRecurringScheduleActive(r,t)&&this.isTimeActive(r,t))for(const o of r.layouts)i.push({file:o.file,priority:r.priority||0,maxPlaysPerHour:o.maxPlaysPerHour||0})}return i}_getLayoutsAt(e,t={}){if(!this.schedule)return[];const{skipRateLimiting:i=!1,skipInterrupts:r=!1,quiet:o=!1}=t,s=o?()=>{}:(...u)=>A.info(...u),n=[];if(this._maxActivePriority=0,this.schedule.campaigns)for(const u of this.schedule.campaigns)this.isRecurringScheduleActive(u,e)&&this.isTimeActive(u,e)&&(this._maxActivePriority=Math.max(this._maxActivePriority,u.priority||0),n.push({type:"campaign",priority:u.priority,layouts:u.layouts,campaignId:u.id}));if(this.schedule.layouts){for(const u of this.schedule.layouts)if(this.isRecurringScheduleActive(u,e)&&this.isTimeActive(u,e)){if(u.criteria&&u.criteria.length>0&&!J(u.criteria,{now:e,displayProperties:this.displayProperties})){s("[Schedule] Layout",u.id,"filtered by criteria");continue}if(u.isGeoAware&&u.geoLocation&&!this.isWithinGeoFence(u.geoLocation)){s("[Schedule] Layout",u.id,"filtered by geofence");continue}if(this._maxActivePriority=Math.max(this._maxActivePriority,u.priority||0),!i&&!this.canPlayLayout(u.id,u.maxPlaysPerHour)){s("[Schedule] Layout",u.id,"filtered by maxPlaysPerHour (limit:",u.maxPlaysPerHour,")");continue}n.push({type:"layout",priority:u.priority||0,layouts:[u],layoutId:u.id})}}if(n.length===0)return this.schedule.default?[this.schedule.default]:[];let a=Math.max(...n.map(u=>u.priority));s("[Schedule] Max priority:",a,"from",n.length,"active items");let l=[];for(const u of n)u.priority===a?(s("[Schedule] Including priority",u.priority,"layouts:",u.layouts.map(y=>y.file)),l.push(...u.layouts)):s("[Schedule] Skipping priority",u.priority,"< max",a);this._layoutMetadata.clear();for(const u of l)this._layoutMetadata.set(u.file,{syncEvent:u.syncEvent||!1,shareOfVoice:u.shareOfVoice||0,scheduleid:u.scheduleid,priority:u.priority||0});if(!r&&this.interruptScheduler){const{normalLayouts:u,interruptLayouts:y}=this.interruptScheduler.separateLayouts(l);if(y.length>0){s("[Schedule] Found",y.length,"interrupt layouts with shareOfVoice");const g=this.interruptScheduler.processInterrupts(u,y).map(p=>p.file);return s("[Schedule] Final layouts (with interrupts):",g),g}}const c=l.map(u=>u.file);return s("[Schedule] Final layouts:",c),c}shouldCheckSchedule(e){return e?Date.now()-e>=6e4:!0}canPlayLayout(e,t){if(!t||t===0)return!0;const i=Date.now(),r=i-60*60*1e3,s=(this.playHistory.get(e)||[]).filter(n=>n>r);if(s.length>=t)return A.info(`Layout ${e} has reached max plays per hour (${s.length}/${t})`),!1;if(s.length>0){const n=36e5/t,a=Math.max(...s),l=i-a;if(l<n){const c=((n-l)/6e4).toFixed(1);return A.info(`Layout ${e} spacing: next play in ${c} min (${s.length}/${t} plays, ${Math.round(n/6e4)} min gap)`),!1}}return!0}recordPlay(e){this.playHistory.has(e)||this.playHistory.set(e,[]);const t=this.playHistory.get(e);t.push(Date.now());const i=Date.now()-60*60*1e3,r=t.filter(o=>o>i);this.playHistory.set(e,r),A.info(`Recorded play for layout ${e} (${r.length} plays in last hour)`)}getMaxActivePriority(){return this._maxActivePriority||0}isSyncEvent(e){const t=this._layoutMetadata.get(e);return(t==null?void 0:t.syncEvent)===!0}getLayoutMetadata(e){return this._layoutMetadata.get(e)||null}hasSyncEvents(){for(const e of this._layoutMetadata.values())if(e.syncEvent)return!0;return!1}getActiveActions(){var t;if(!((t=this.schedule)!=null&&t.actions))return[];const e=new Date;return this.schedule.actions.filter(i=>this.isTimeActive(i,e))}getCommands(){var e;return((e=this.schedule)==null?void 0:e.commands)||[]}findActionByTrigger(e){return this.getActiveActions().find(i=>i.triggerCode===e)||null}clearPlayHistory(){this.playHistory.clear(),A.info("Play history cleared")}setLocation(e,t){this.playerLocation={latitude:e,longitude:t},A.info(`Location set: ${e}, ${t}`)}setDisplayProperties(e){this.displayProperties=e||{}}isWithinGeoFence(e,t=500){if(!this.playerLocation)return A.debug("No player location, skipping geofence check"),!0;if(!e)return!0;const i=e.split(",").map(l=>parseFloat(l.trim()));if(i.length<2||isNaN(i[0])||isNaN(i[1]))return A.warn("Invalid geoLocation format:",e),!0;const r=i[0],o=i[1],s=i[2]||t,n=this.haversineDistance(this.playerLocation.latitude,this.playerLocation.longitude,r,o),a=n<=s;return A.info(`Geofence: ${n.toFixed(0)}m from (${r},${o}), radius ${s}m → ${a?"WITHIN":"OUTSIDE"}`),a}haversineDistance(e,t,i,r){const s=c=>c*Math.PI/180,n=s(i-e),a=s(r-t),l=Math.sin(n/2)**2+Math.cos(s(e))*Math.cos(s(i))*Math.sin(a/2)**2;return 6371e3*2*Math.atan2(Math.sqrt(l),Math.sqrt(1-l))}}const Ye=new De,E=O("schedule:interrupts");class Je{constructor(){this.interruptCommittedDurations=new Map}isInterrupt(e){return!!(e.shareOfVoice&&e.shareOfVoice>0)}resetCommittedDurations(){this.interruptCommittedDurations.clear(),E.debug("Reset interrupt committed durations")}getCommittedDuration(e){return this.interruptCommittedDurations.get(e)||0}addCommittedDuration(e,t){const i=this.getCommittedDuration(e);this.interruptCommittedDurations.set(e,i+t)}isInterruptDurationSatisfied(e){if(!e.shareOfVoice)return!0;const t=e.id||e.file,i=e.shareOfVoice/100*3600;return this.getCommittedDuration(t)>=i}getRequiredSeconds(e){return e.shareOfVoice?e.shareOfVoice/100*3600:0}processInterrupts(e,t){if(!t||t.length===0)return E.debug("No interrupt layouts, returning normal layouts"),e;if(!e||e.length===0)return E.warn("No normal layouts available, interrupts will fill entire hour"),this.fillHourWithInterrupts(t);E.info(`Processing ${t.length} interrupt layouts with ${e.length} normal layouts`);for(const c of t){const u=c.id||c.file;this.interruptCommittedDurations.set(u,0)}const i=[];let r=0,o=0,s=!1;for(;!s;){if(o>=t.length){o=0;let u=!0;for(const y of t)if(!this.isInterruptDurationSatisfied(y)){u=!1;break}if(u){s=!0;break}}const c=t[o];if(!this.isInterruptDurationSatisfied(c)){const u=c.id||c.file;this.addCommittedDuration(u,c.duration),r+=c.duration,i.push(c)}o++}if(E.debug(`Resolved ${i.length} interrupt plays (${r}s total)`),r>=3600)return E.info("Interrupts fill entire hour (>= 3600s), no room for normal layouts"),i;const n=3600-r,a=this.fillTimeWithLayouts(e,n);E.debug(`Resolved ${a.length} normal plays (${n}s target)`);const l=this.interleaveLayouts(a,i);return E.info(`Final loop: ${l.length} layouts (${a.length} normal + ${i.length} interrupts)`),l}fillTimeWithLayouts(e,t){const i=[];let r=t,o=0;for(;r>0;){o>=e.length&&(o=0);const s=e[o];i.push(s),r-=s.duration,o++}return i}fillHourWithInterrupts(e){return this.fillTimeWithLayouts(e,3600)}interleaveLayouts(e,t){const i=[],r=Math.max(e.length,t.length),o=Math.ceil(1*r/e.length),s=Math.floor(1*r/t.length);E.debug(`Interleaving: pickCount=${r}, normalPick=${o}, interruptPick=${s}`);let n=0,a=0,l=0;for(let c=0;c<r;c++)c%o===0&&(n>=e.length&&(n=0),i.push(e[n]),l+=e[n].duration,n++),c%s===0&&a<t.length&&(i.push(t[a]),l+=t[a].duration,a++);for(;l<3600;)n>=e.length&&(n=0),i.push(e[n]),l+=e[n].duration,n++;return E.debug(`Interleaved ${i.length} layouts, total duration: ${l}s`),i}separateLayouts(e){const t=[],i=[];for(const r of e)this.isInterrupt(r)?i.push(r):t.push(r);return{normalLayouts:t,interruptLayouts:i}}}const U=O("schedule:overlays");class Ae{constructor(){this.overlays=[],this.displayProperties={},this.scheduleManager=null,U.debug("OverlayScheduler initialized")}setScheduleManager(e){this.scheduleManager=e}setDisplayProperties(e){this.displayProperties=e||{}}setOverlays(e){this.overlays=e||[],U.info(`Loaded ${this.overlays.length} overlay(s)`)}getCurrentOverlays(){if(!this.overlays||this.overlays.length===0)return[];const e=new Date,t=[];for(const i of this.overlays){if(!this.isTimeActive(i,e)){U.debug(`Overlay ${i.file} not in time window`);continue}if(i.isGeoAware&&i.geoLocation&&this.scheduleManager&&!this.scheduleManager.isWithinGeoFence(i.geoLocation)){U.debug(`Overlay ${i.file} filtered by geofence`);continue}if(i.criteria&&i.criteria.length>0&&!J(i.criteria,{now:e,displayProperties:this.displayProperties})){U.debug(`Overlay ${i.file} filtered by criteria`);continue}t.push(i)}return t.sort((i,r)=>{const o=i.priority||0;return(r.priority||0)-o}),t.length>0&&U.info(`Active overlays: ${t.length}`),t}isTimeActive(e,t){const i=e.fromDt?new Date(e.fromDt):null,r=e.toDt?new Date(e.toDt):null;return!(i&&t<i||r&&t>r)}shouldCheckOverlays(e){return e?Date.now()-e>=6e4:!0}getOverlayByFile(e){return this.overlays.find(t=>t.file===e)||null}clear(){this.overlays=[],U.debug("Cleared all overlays")}processOverlays(e,t){return this.setOverlays(t),e}}new Ae;function Pe(h){const t=new DOMParser().parseFromString(h,"text/xml").querySelector("layout");if(!t)return 60;const i=parseInt(t.getAttribute("duration")||"0",10);if(i>0)return i;let r=0;for(const o of t.querySelectorAll("region")){let s=0;for(const n of o.querySelectorAll("media")){const a=parseInt(n.getAttribute("duration")||"0",10),l=parseInt(n.getAttribute("useDuration")||"1",10);a>0&&l!==0?s+=a:s+=60}r=Math.max(r,s)}return r>0?r:60}function re(h,e){if(h.length!==e.length)return!1;for(let t=0;t<h.length;t++)if(h[t]!==e[t])return!1;return!0}function Oe(h,e,t){if(!e||e===0)return!0;const i=t-36e5,r=h.filter(o=>o>i);if(r.length>=e)return!1;if(r.length>0){const o=36e5/e,s=Math.max(...r);if(t-s<o)return!1}return!0}function Fe(h){const e=new Map;if(!h)return e;for(const[t,i]of h){const r=`${t}.xlf`;e.set(r,[...i])}return e}function oe(h,e,t){const i=h.filter(o=>{if(!o.maxPlaysPerHour||o.maxPlaysPerHour===0)return!0;const s=e.get(o.file)||[];return Oe(s,o.maxPlaysPerHour,t)});if(i.length===0)return[];const r=Math.max(...i.map(o=>o.priority));return i.filter(o=>o.priority===r).map(o=>o.file)}function We(h,e,t={}){var y;const i=t.from||new Date,r=t.hours||2,o=new Date(i.getTime()+r*36e5),s=t.defaultDuration||60,n=[];let a=new Date(i);const l=typeof h.getAllLayoutsAtTime=="function",c=Fe(h.playHistory),u=500;for(;a<o&&n.length<u;){const m=a.getTime();let g;if(l){const p=h.getAllLayoutsAtTime(a);g=p.length>0?oe(p,c,m):[]}else g=h.getLayoutsAtTime(a);if(g.length===0){const p=(y=h.schedule)==null?void 0:y.default;if(p){const w=e.get(p)||s;n.push({layoutFile:p,startTime:new Date(a),endTime:new Date(m+w*1e3),duration:w,isDefault:!0}),a=new Date(m+w*1e3)}else a=new Date(m+6e4);continue}for(let p=0;p<g.length&&a<o&&n.length<u;p++){const w=g[p],b=e.get(w)||s,k=a.getTime()+b*1e3;if(n.push({layoutFile:w,startTime:new Date(a),endTime:new Date(k),duration:b,isDefault:!1}),l&&(c.has(w)||c.set(w,[]),c.get(w).push(a.getTime())),a=new Date(k),l){const L=h.getAllLayoutsAtTime(a),$=L.length>0?oe(L,c,a.getTime()):[];if(!re(g,$))break}else{const L=h.getLayoutsAtTime(a);if(!re(g,L))break}}}return n}const C=O("DataConnector");class Ue extends pe{constructor(){super(),this.connectors=new Map}setConnectors(e){if(this.stopPolling(),this.connectors.clear(),!e||e.length===0){C.debug("No data connectors configured");return}for(const t of e){if(!t.dataKey||!t.url){C.warn("Skipping data connector with missing dataKey or url:",t);continue}this.connectors.set(t.dataKey,{config:t,data:null,timer:null,lastFetch:null}),C.info(`Registered data connector: ${t.dataKey} (interval: ${t.updateInterval}s)`)}C.info(`${this.connectors.size} data connector(s) configured`)}startPolling(){for(const[e,t]of this.connectors.entries()){const{config:i}=t,r=(i.updateInterval||300)*1e3;this.fetchData(t).catch(o=>{C.error(`Initial fetch failed for ${e}:`,o)}),t.timer=setInterval(()=>{this.fetchData(t).catch(o=>{C.error(`Polling fetch failed for ${e}:`,o)})},r),C.debug(`Started polling for ${e} every ${i.updateInterval}s`)}}stopPolling(){for(const[e,t]of this.connectors.entries())t.timer&&(clearInterval(t.timer),t.timer=null,C.debug(`Stopped polling for ${e}`))}getData(e){const t=this.connectors.get(e);return t?t.data:(C.debug(`No data connector found for key: ${e}`),null)}getAvailableKeys(){const e=[];for(const[t,i]of this.connectors.entries())i.data!==null&&e.push(t);return e}async fetchData(e){const{config:t}=e,{dataKey:i,url:r}=t;C.debug(`Fetching data for ${i}: ${r}`);try{const o=await Le(r,{method:"GET",headers:{Accept:"application/json"}},{maxRetries:2,baseDelayMs:2e3});if(!o.ok){C.warn(`Data connector ${i} returned ${o.status}: ${o.statusText}`);return}const s=o.headers.get("Content-Type")||"";let n;s.includes("application/json")?n=await o.json():n=await o.text();const a=e.data;e.data=n,e.lastFetch=Date.now(),C.debug(`Data updated for ${i} (fetched at ${new Date(e.lastFetch).toISOString()})`),this.emit("data-updated",i,n),JSON.stringify(a)!==JSON.stringify(n)&&this.emit("data-changed",i,n)}catch(o){C.error(`Failed to fetch data for ${i}:`,o),this.emit("fetch-error",i,o)}}cleanup(){this.stopPolling(),this.connectors.clear(),this.removeAllListeners(),C.debug("DataConnectorManager cleaned up")}}const f=O("PlayerCore"),Ne="xibo-offline-cache",He=1,H="cache";function R(h){return parseInt(String(h).replace(".xlf",""),10)}function se(){return new Promise((h,e)=>{const t=indexedDB.open(Ne,He);t.onupgradeneeded=()=>{const i=t.result;i.objectStoreNames.contains(H)||i.createObjectStore(H)},t.onsuccess=()=>h(t.result),t.onerror=()=>e(t.error)})}class qe extends pe{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.dataConnectorManager=new Ue,this.xmr=null,this.currentLayoutId=null,this.collecting=!1,this.collectionInterval=null,this.pendingLayouts=new Map,this.offlineMode=!1,this._normalCollectInterval=null,this._offlineRetrySeconds=0,this._lastCheckRf=null,this._lastCheckSchedule=null,this._layoutOverride=null,this._lastRequiredFiles=[],this._currentLayoutIndex=0,this.syncConfig=null,this.syncManager=null,this._layoutDurations=new Map,this._offlineCache={schedule:null,settings:null,requiredFiles:null},this._offlineDbReady=this._initOfflineCache()}async _initOfflineCache(){try{const e=await se(),i=e.transaction(H,"readonly").objectStore(H),[r,o,s]=await Promise.all([new Promise(n=>{const a=i.get("schedule");a.onsuccess=()=>n(a.result??null),a.onerror=()=>n(null)}),new Promise(n=>{const a=i.get("settings");a.onsuccess=()=>n(a.result??null),a.onerror=()=>n(null)}),new Promise(n=>{const a=i.get("requiredFiles");a.onsuccess=()=>n(a.result??null),a.onerror=()=>n(null)})]);this._offlineCache={schedule:r,settings:o,requiredFiles:s},e.close(),console.log("[PlayerCore] Offline cache loaded from IndexedDB",r?"(has schedule)":"(empty)")}catch(e){console.warn("[PlayerCore] Failed to load offline cache from IndexedDB:",e)}}async _offlineSave(e,t){this._offlineCache[e]=t;try{const i=await se(),r=i.transaction(H,"readwrite");r.objectStore(H).put(t,e),await new Promise((o,s)=>{r.oncomplete=o,r.onerror=()=>s(r.error)}),i.close()}catch(i){console.warn("[PlayerCore] Failed to save offline cache:",e,i)}}hasCachedData(){return this._offlineCache.schedule!==null}isOffline(){return typeof navigator<"u"&&navigator.onLine===!1}isInOfflineMode(){return this.offlineMode}collectOffline(){if(console.warn("[PlayerCore] Offline mode — using cached schedule"),this.offlineMode||(this.offlineMode=!0,this.emit("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),f.info(`Offline: retry in ${this._offlineRetrySeconds}s`)),!this.collectionInterval){const i=this._offlineCache.settings;i!=null&&i.settings&&(this.setupCollectionInterval(i.settings),this._normalCollectInterval=this._currentCollectInterval,this._offlineRetrySeconds=30,this._setCollectionTimer(this._offlineRetrySeconds),f.info(`Offline: retry in ${this._offlineRetrySeconds}s`))}const e=this._offlineCache.schedule;e&&(this.schedule.setSchedule(e),this.emit("schedule-received",e));const t=this.schedule.getCurrentLayouts();f.info("Offline layouts:",t),this.emit("layouts-scheduled",t),this._evaluateAndSwitchLayout(t,"Offline"),this.emit("collection-complete")}async _evaluateAndSwitchLayout(e,t){const i=t?`${t}: `:"";if(e.length>0)if(this.currentLayoutId)if(e.some(o=>R(o)===this.currentLayoutId)){const o=e.findIndex(s=>R(s)===this.currentLayoutId);o>=0&&(this._currentLayoutIndex=o),f.debug(`Layout ${this.currentLayoutId} still in schedule${t?` (${t.toLowerCase()})`:""}, continuing playback`),this.emit("layout-already-playing",this.currentLayoutId)}else{this._currentLayoutIndex=0;const o=this.getNextLayout();o&&(f.info(`${i}switching to layout ${o.layoutId}${t?"":` (from ${this.currentLayoutId})`}`),this.emit("layout-prepare-request",o.layoutId))}else{this._currentLayoutIndex=0;const r=this.getNextLayout();r&&(f.info(`${i}switching to layout ${r.layoutId}`),this.emit("layout-prepare-request",r.layoutId))}else f.info(`${t?`${t}: n`:"N"}o layouts${t?" in cached schedule":" scheduled, falling back to default"}`),this.emit("no-layouts-scheduled");await this._buildLayoutDurations(),this.logUpcomingTimeline()}async collectNow(){return this._lastCheckRf=null,this._lastCheckSchedule=null,this.collect()}async collect(){var e,t,i;if(this.collecting){f.debug("Collection already in progress, skipping");return}this.collecting=!0;try{if(await this._offlineDbReady,f.info("Starting collection cycle..."),this.emit("collection-start"),this.isOffline()){if(this.hasCachedData())return this.collectOffline();throw new Error("Offline with no cached data — cannot start playback")}f.debug("Collection step: registerDisplay");const r=await this.xmds.registerDisplay();if(f.info("Display registered:",r),this._offlineSave("settings",r),this.offlineMode&&(this.offlineMode=!1,console.log("[PlayerCore] Back online — resuming normal collection"),this.emit("offline-mode",!1),this._normalCollectInterval&&(this._setCollectionTimer(this._normalCollectInterval),this._normalCollectInterval=null,this._offlineRetrySeconds=0)),this.displaySettings&&r.settings){const a=this.displaySettings.applySettings(r.settings);a.changed.includes("collectInterval")&&this.updateCollectionInterval(a.settings.collectInterval),r.settings.logLevel&&Se(r.settings.logLevel)&&(f.info("Log level updated from CMS:",r.settings.logLevel),this.emit("log-level-changed",r.settings.logLevel))}r.syncConfig&&(this.syncConfig=r.syncConfig,f.info("Sync group:",r.syncConfig.isLead?"LEAD":`follower → ${r.syncConfig.syncGroup}`,`(switchDelay: ${r.syncConfig.syncSwitchDelay}ms, videoPauseDelay: ${r.syncConfig.syncVideoPauseDelay}ms)`),this.emit("sync-config",r.syncConfig)),this.emit("register-complete",r),f.debug("Collection step: initializeXmr"),await this.initializeXmr(r);const o=r.checkRf||"",s=r.checkSchedule||"";if(!this._lastCheckRf||this._lastCheckRf!==o){f.debug("Collection step: requiredFiles");const a=await this.xmds.requiredFiles(),l=a.filter(g=>g.type==="purge"),c=a.filter(g=>g.type!=="purge");if(f.info("Required files:",c.length,l.length>0?`(+ ${l.length} purge)`:""),this._lastCheckRf=o,this.emit("files-received",c),this._offlineSave("requiredFiles",a),l.length>0&&this.emit("purge-request",l),!this._lastCheckSchedule||this._lastCheckSchedule!==s){f.debug("Collection step: schedule");const g=await this.xmds.schedule();f.info("Schedule received"),this._lastCheckSchedule=s,f.debug("Collection step: processing schedule"),this.emit("schedule-received",g),this.schedule.setSchedule(g),this.updateDataConnectors(),this._offlineSave("schedule",g),this.logUpcomingTimeline()}f.debug("Collection step: download-request + mediaInventory");const y=this.schedule.getCurrentLayouts().map(g=>R(g)),m=[];for(let g=0;g<y.length;g++){const p=(this._currentLayoutIndex+g)%y.length;m.push(y[p])}this._lastRequiredFiles=c,this.emit("download-request",{layoutOrder:m,files:c}),this.submitMediaInventory(c)}else if(o&&f.info("RequiredFiles CRC unchanged, skipping download check"),this._lastCheckSchedule!==s){const a=await this.xmds.schedule();f.info("Schedule received (RF unchanged but schedule changed)"),this._lastCheckSchedule=s,this.emit("schedule-received",a),this.schedule.setSchedule(a),this.updateDataConnectors(),this._offlineSave("schedule",a),this.logUpcomingTimeline()}else s&&f.info("Schedule CRC unchanged, skipping");f.debug("Collection step: evaluateSchedule");const n=this.schedule.getCurrentLayouts();if(f.info("Current layouts:",n),this.emit("layouts-scheduled",n),this._evaluateAndSwitchLayout(n,""),n.length===0&&this.currentLayoutId&&((e=this.schedule.schedule)!=null&&e.default)){const a=R(this.schedule.schedule.default);f.info(`Current layout filtered by schedule, switching to default layout ${a}`),this.currentLayoutId=null,this.emit("layout-prepare-request",a)}(((t=r.settings)==null?void 0:t.statsEnabled)==="On"||((i=r.settings)==null?void 0:i.statsEnabled)==="1")&&(this.statsCollector?(f.info("Stats enabled, submitting proof of play"),this.emit("submit-stats-request")):f.warn("Stats enabled but no StatsCollector provided")),this.emit("submit-logs-request"),!this.collectionInterval&&r.settings&&this.setupCollectionInterval(r.settings),this.emit("collection-complete")}catch(r){if(this.hasCachedData())return console.warn("[PlayerCore] Collection failed, falling back to cached data:",(r==null?void 0:r.message)||r),this.emit("collection-error",r),this.collectOffline();throw f.error("Collection error:",r),this.emit("collection-error",r),r}finally{this.collecting=!1}}async initializeXmr(e){var r,o,s,n;const t=((r=e.settings)==null?void 0:r.xmrWebSocketAddress)||((o=e.settings)==null?void 0:o.xmrNetworkAddress);if(!t){f.warn("XMR not configured: no xmrWebSocketAddress or xmrNetworkAddress in CMS settings"),this.emit("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://")){f.warn(`XMR address uses tcp:// protocol which is not supported by PWA players: ${t}`),f.warn("Configure XMR_WS_ADDRESS in CMS Admin → Settings → Configuration → XMR (e.g. wss://your-domain/xmr)"),this.emit("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)){f.warn(`XMR address contains placeholder domain: ${t}`),f.warn("Configure the real XMR address in CMS Admin → Settings → Configuration → XMR"),this.emit("xmr-misconfigured",{reason:"placeholder",url:t,message:`XMR address is still the default placeholder (${t}). Update it in CMS Settings.`});return}const i=((s=e.settings)==null?void 0:s.xmrCmsKey)||((n=e.settings)==null?void 0:n.serverKey)||this.config.serverKey;f.debug("XMR CMS Key:",i?"present":"missing"),this.xmr?this.xmr.isConnected()?f.debug("XMR already connected"):(f.info("XMR disconnected, attempting to reconnect..."),this.xmr.reconnectAttempts=0,await this.xmr.start(t,i),this.emit("xmr-reconnected",t)):(f.info("Initializing XMR WebSocket:",t),this.xmr=new this.XmrWrapper(this.config,this),await this.xmr.start(t,i),this.emit("xmr-connected",t))}setupCollectionInterval(e){const t=this.displaySettings?this.displaySettings.getCollectInterval():parseInt(e.collectInterval||"300",10);this._setCollectionTimer(t),this.emit("collection-interval-set",t)}updateCollectionInterval(e){this.collectionInterval&&(this._setCollectionTimer(e),this.emit("collection-interval-updated",e))}_setCollectionTimer(e){this.collectionInterval&&clearInterval(this.collectionInterval),this._currentCollectInterval=e,f.info(`Collection interval: ${e}s`),this.collectionInterval=setInterval(()=>{f.debug("Running scheduled collection cycle..."),this.collect().catch(t=>{f.error("Collection error:",t),this.emit("collection-error",t)})},e*1e3)}async requestLayoutChange(e){f.info(`Layout change requested: ${e}`),this.currentLayoutId=null,this.emit("layout-change-requested",e)}setCurrentLayout(e){this.currentLayoutId=e,this.pendingLayouts.delete(e),this.emit("layout-current",e),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(){const e=this.schedule.getCurrentLayouts();if(e.length===0)return null;this._currentLayoutIndex>=e.length&&(this._currentLayoutIndex=0);const t=e[this._currentLayoutIndex];return{layoutId:R(t),layoutFile:t}}peekNextLayout(){const e=this.schedule.getCurrentLayouts();if(e.length<=1)return null;const t=(this._currentLayoutIndex+1)%e.length,i=e[t],r=R(i);return r===this.currentLayoutId?null:{layoutId:r,layoutFile:i}}advanceToNextLayout(){if(this._layoutOverride){f.info("Layout override active, not advancing schedule");return}const e=this.schedule.getCurrentLayouts();if(f.info(`Advancing schedule: ${e.length} layout(s) available, current index ${this._currentLayoutIndex}`),e.length===0){if(this.currentLayoutId){f.info(`No layouts available (all rate-limited), replaying ${this.currentLayoutId} to avoid blank screen`);const r=this.currentLayoutId;this.currentLayoutId=null,this.emit("layout-prepare-request",r)}else f.info("No layouts scheduled during advance"),this.emit("no-layouts-scheduled");return}this._currentLayoutIndex=(this._currentLayoutIndex+1)%e.length;const t=e[this._currentLayoutIndex],i=R(t);if(this.syncManager&&this.schedule.isSyncEvent(t))if(this.isSyncLead()){f.info(`[Sync] Lead requesting coordinated layout change: ${i}`),this.syncManager.requestLayoutChange(i).catch(r=>{f.error("[Sync] Layout change failed:",r),this.emit("layout-prepare-request",i)});return}else{f.info("[Sync] Follower waiting for lead signal (not advancing independently)");return}i===this.currentLayoutId&&(f.info(`Next layout ${i} is same as current, triggering replay`),this.currentLayoutId=null),f.info(`Advancing to layout ${i} (index ${this._currentLayoutIndex}/${e.length})`),this.emit("layout-prepare-request",i)}advanceToPreviousLayout(){if(this._layoutOverride){f.info("Layout override active, not going back");return}const e=this.schedule.getCurrentLayouts();if(e.length===0)return;const t=(this._currentLayoutIndex-1+e.length)%e.length,i=e[t],r=R(i);if(r===this.currentLayoutId){f.info("Only one layout in schedule, nothing to go back to");return}this._currentLayoutIndex=t,f.info(`Going back to layout ${r} (index ${this._currentLayoutIndex}/${e.length})`),this.emit("layout-prepare-request",r)}notifyMediaReady(e,t="media"){f.debug(`File ${e} ready (${t})`);for(const[i,r]of this.pendingLayouts.entries()){const o=t==="layout"&&i===parseInt(e),s=t==="media"&&r.includes(parseInt(e));(o||s)&&(f.debug(`${t} ${e} was needed by pending layout ${i}, checking if ready...`),this.emit("check-pending-layout",i,r))}}async notifyLayoutStatus(e){try{await this.xmds.notifyStatus({currentLayoutId:e}),this.emit("status-notified",e)}catch(t){f.warn("Failed to notify status:",t),this.emit("status-notify-failed",e,t)}}async captureScreenshot(){f.info("Screenshot requested"),this.emit("screenshot-request")}async changeLayout(e){f.info("Layout change requested via XMR:",e),this._layoutOverride={layoutId:parseInt(e,10),type:"change"},this.currentLayoutId=null,this.emit("layout-prepare-request",parseInt(e,10))}async overlayLayout(e){f.info("Overlay layout requested via XMR:",e),this._layoutOverride={layoutId:parseInt(e,10),type:"overlay"},this.emit("overlay-layout-request",parseInt(e,10))}async revertToSchedule(){f.info("Reverting to scheduled content"),this._layoutOverride=null,this.currentLayoutId=null,this.emit("revert-to-schedule");const e=this.schedule.getCurrentLayouts();if(e.length>0){const t=e[0],i=R(t);this.emit("layout-prepare-request",i)}else this.emit("no-layouts-scheduled")}async purgeAll(){return f.info("Purge all cache requested via XMR"),this._lastCheckRf=null,this._lastCheckSchedule=null,this.emit("purge-all-request"),this.collectNow()}async executeCommand(e,t){if(f.info("Execute command requested:",e),!t||!t[e]){f.warn("Unknown command code:",e),this.emit("command-result",{code:e,success:!1,reason:"Unknown command"});return}const i=t[e],r=i.commandString||i.value||"";if(r.startsWith("http|")){const o=r.split("|"),s=o[1],n=o[2]||"application/json";try{const a=await fetch(s,{method:"POST",headers:{"Content-Type":n}}),l=a.ok;f.info(`HTTP command ${e} result: ${a.status}`),this.emit("command-result",{code:e,success:l,status:a.status})}catch(a){f.error(`HTTP command ${e} failed:`,a),this.emit("command-result",{code:e,success:!1,reason:a.message})}}else f.warn("Non-HTTP commands not supported in browser:",e),this.emit("command-result",{code:e,success:!1,reason:"Only HTTP commands supported in browser"})}triggerWebhook(e){f.info("Webhook trigger from XMR:",e),this.handleTrigger(e)}refreshDataConnectors(){f.info("Data connector refresh requested via XMR"),this.dataConnectorManager.refreshAll(),this.emit("data-connectors-refreshed")}async submitMediaInventory(e){if(!(!e||e.length===0))try{const t=Math.floor(Date.now()/1e3),r=`<files>${e.filter(o=>o.type==="media"||o.type==="layout").map(o=>`<file type="${o.type}" id="${o.id}" complete="1" md5="${o.md5||""}" lastChecked="${t}"/>`).join("")}</files>`;await this.xmds.mediaInventory(r),f.info(`Media inventory submitted: ${e.length} files`),this.emit("media-inventory-submitted",e.length)}catch(t){f.warn("MediaInventory submission failed:",t)}}async blackList(e,t,i){try{await this.xmds.blackList(e,t,i),this.emit("media-blacklisted",{mediaId:e,type:t,reason:i})}catch(r){f.warn("BlackList failed:",r)}}isLayoutOverridden(){return this._layoutOverride!==null}handleTrigger(e){const t=this.schedule.findActionByTrigger(e);if(!t){f.debug("No scheduled action matches trigger:",e);return}switch(f.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("navigate-to-widget",t);break;case"command":this.emit("execute-command",t.commandCode);break;default:f.warn("Unknown action type:",t.actionType)}}updateDataConnectors(){const e=this.schedule.getDataConnectors();e.length>0&&f.info(`Configuring ${e.length} data connector(s)`),this.dataConnectorManager.setConnectors(e),e.length>0&&(this.dataConnectorManager.startPolling(),this.emit("data-connectors-started",e.length))}getDataConnectorManager(){return this.dataConnectorManager}setSyncManager(e){this.syncManager=e,f.info("SyncManager attached:",e.isLead?"LEAD":"FOLLOWER")}isInSyncGroup(){return this.syncConfig!==null}isSyncLead(){var e;return((e=this.syncConfig)==null?void 0:e.isLead)===!0}getSyncConfig(){return this.syncConfig}async _buildLayoutDurations(){var o,s;if(!((o=this.cache)!=null&&o.getFile))return;const e=this.schedule.getCurrentLayouts(),t=(s=this.schedule.schedule)==null?void 0:s.default,i=[...new Set([...e,...t?[t]:[]])];let r=0;for(const n of i){const a=R(n);try{const l=await this.cache.getFile("layout",a);if(l){const c=Pe(l);this._layoutDurations.set(n,c),this._layoutDurations.set(String(a),c),r++}}catch(l){f.debug(`Could not parse duration for layout ${a}:`,l.message)}}r>0&&f.info(`[Timeline] Parsed durations for ${r} layouts`)}logUpcomingTimeline(){if(this._layoutDurations.size===0||!this.schedule.getLayoutsAtTime)return;const e=We(this.schedule,this._layoutDurations);if(e.length===0)return;const t=e.slice(0,20).map(i=>{const r=i.startTime.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"}),o=i.endTime.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"2-digit"});return` ${r}-${o} Layout ${i.layoutFile} (${i.duration}s)${i.isDefault?" [default]":""}`});f.info(`[Timeline] Next ${e.length} plays:
3
+ ${t.join(`
4
+ `)}`),this.emit("timeline-updated",e)}recordLayoutDuration(e,t){const i=this._layoutDurations.get(e);i!==t&&(this._layoutDurations.set(e,t),f.debug(`[Timeline] Duration corrected: layout ${e} ${i||"?"}s → ${t}s`),this.logUpcomingTimeline())}cleanup(){this.collectionInterval&&(clearInterval(this.collectionInterval),this.collectionInterval=null),this.xmr&&(this.xmr.stop(),this.xmr=null),this.syncManager&&(this.syncManager.stop(),this.syncManager=null),this.dataConnectorManager.cleanup(),this.emit("cleanup-complete"),this.removeAllListeners()}getCurrentLayoutId(){return this.currentLayoutId}isCollecting(){return this.collecting}getPendingLayouts(){return Array.from(this.pendingLayouts.keys())}}class ne{constructor(e){v(this,"overlay",null);v(this,"config");v(this,"updateTimer",null);v(this,"_visible",!1);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=`
5
+ position: fixed;
6
+ top: 1.5vh;
7
+ left: 1.5vw;
8
+ background: rgba(0, 0, 0, 0.88);
9
+ color: #fff;
10
+ font-family: system-ui, -apple-system, sans-serif;
11
+ font-size: 1.4vw;
12
+ padding: 1vh 1.2vw;
13
+ border-radius: 0.4vw;
14
+ border: 1px solid rgba(255, 255, 255, 0.25);
15
+ z-index: 999999;
16
+ max-width: 35vw;
17
+ box-shadow: 0 0.3vh 1.2vw rgba(0, 0, 0, 0.5);
18
+ `,document.body.appendChild(this.overlay)}async updateOverlay(){var e;if(this.overlay)try{if(!((e=navigator.serviceWorker)!=null&&e.controller))throw new Error("No SW controller");const t=new MessageChannel,i=new Promise(o=>{t.port1.onmessage=s=>o(s.data),setTimeout(()=>o({success:!1}),500)});navigator.serviceWorker.controller.postMessage({type:"GET_DOWNLOAD_PROGRESS"},[t.port2]);const r=await i;if(r.success){const o=this.renderStatus(r.progress);!!o?(this.overlay.innerHTML=o,this._visible&&(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")}else throw new Error("Progress request failed")}catch{this._visible&&this.overlay?this.overlay.innerHTML='<div style="color: #999; font-size: 1.4vw;">⋯ Waiting for service worker</div>':(this.stopUpdating(),this.overlay&&(this.overlay.style.display="none"))}}renderStatus(e){const t=e||{};if(Object.keys(t).length===0)return this.config.autoHide?"":'<div style="color: #6c6;">✓ No downloads</div>';let r=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw;">Downloads: ${Object.keys(t).length} active</div>`;for(const[o,s]of Object.entries(t)){const n=this.extractFilename(o),a=Math.round(s.percent||0),l=this.formatBytes(s.downloaded||0),c=this.formatBytes(s.total||0);r+=`
19
+ <div style="margin-bottom: 0.6vh; padding-bottom: 0.6vh; border-bottom: 1px solid rgba(255,255,255,0.1);">
20
+ <div style="font-size: 1.2vw; margin-bottom: 0.2vh;">${n}</div>
21
+ <div style="background: rgba(255,255,255,0.1); height: 0.4vh; border-radius: 0.2vw; overflow: hidden;">
22
+ <div style="width: ${a}%; height: 100%; background: #4a9eff; transition: width 0.3s;"></div>
23
+ </div>
24
+ <div style="color: #999; font-size: 1.1vw; margin-top: 0.2vh;">
25
+ ${a}% · ${l} / ${c}
26
+ </div>
27
+ </div>
28
+ `}return r}extractFilename(e){return e||"unknown"}formatBytes(e){if(e<1024)return`${e} B`;const t=e/1024;if(t<1024)return`${t.toFixed(1)} KB`;const i=t/1024;return i<1024?`${i.toFixed(1)} MB`:`${(i/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._visible=!0,!this.updateTimer&&(this.updateTimer=window.setInterval(()=>{this.updateOverlay()},this.config.updateInterval))}stopUpdating(){this.updateTimer&&(clearInterval(this.updateTimer),this.updateTimer=null)}destroy(){this.stopUpdating(),this.overlay&&(this.overlay.remove(),this.overlay=null)}setEnabled(e){this.config.enabled=e,e&&!this.overlay?this.createOverlay():!e&&this.overlay&&this.destroy()}}function ae(){const e=new URLSearchParams(window.location.search).get("showDownloads");if(e!==null)return{enabled:e!=="0"&&e!=="false"};const t=localStorage.getItem("xibo_show_download_overlay");return t!==null?{enabled:t==="true"}:{enabled:!0,autoHide:!0}}class Be{constructor(e=!1){v(this,"overlay",null);v(this,"visible");v(this,"timeline",[]);v(this,"currentLayoutId",null);v(this,"offline",!1);this.visible=e,this.createOverlay(),this.visible||(this.overlay.style.display="none")}createOverlay(){this.overlay=document.createElement("div"),this.overlay.id="timeline-overlay",this.overlay.style.cssText=`
29
+ position: fixed;
30
+ bottom: 1.5vh;
31
+ left: 1.5vw;
32
+ background: rgba(0, 0, 0, 0.88);
33
+ color: #fff;
34
+ font-family: system-ui, -apple-system, sans-serif;
35
+ font-size: 1.4vw;
36
+ padding: 1vh 1.2vw;
37
+ border-radius: 0.4vw;
38
+ border: 1px solid rgba(255, 255, 255, 0.25);
39
+ z-index: 999999;
40
+ max-width: 35vw;
41
+ box-shadow: 0 0.3vh 1.2vw rgba(0, 0, 0, 0.5);
42
+ pointer-events: auto;
43
+ `,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){e!==null&&(this.timeline=e),t!==null&&(this.currentLayoutId=t),this.render()}render(){if(!this.overlay||!this.visible)return;const e=new Date,t=this.timeline.filter(a=>a.endTime>e);if(t.length===0){this.overlay.innerHTML='<div style="color: #999;">Timeline — no upcoming layouts</div>';return}const i=8,r=t.length,o=t.slice(0,i),s=this.offline?' <span style="color: #ff4444; font-size: 1.1vw;">OFFLINE</span>':"";let n=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw; color: #ccc;">Timeline (${r} upcoming)${s}</div>`;for(const a of o){const l=parseInt(a.layoutFile.replace(".xlf",""),10),c=l===this.currentLayoutId&&a.startTime<=e&&a.endTime>e,u=this.formatTime(a.startTime),y=this.formatTime(a.endTime),m=this.formatDuration(a.duration),g=c?"▶ ":" ";n+=`<div style="${c?"border-left: 0.25vw solid #4a9eff; padding-left: 0.6vw;":"padding-left: 0.85vw;"} ${c?"color: #fff;":"color: #ccc;"} margin-bottom: 0.3vh; font-family: monospace; font-size: 1.3vw; line-height: 1.5; white-space: nowrap;">`,n+=`${g}${u}–${y} #${l} ${m}`,a.isDefault&&(n+=' <span style="color: #888;">[def]</span>'),n+="</div>"}r>i&&(n+=`<div style="padding-left: 0.85vw; color: #888; font-size: 1.1vw; margin-top: 0.3vh;">+${r-i} more</div>`),this.overlay.innerHTML=n}formatTime(e){return e.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit"})}formatDuration(e){const t=Math.floor(e/60),i=Math.round(e%60);return t>0?`${t}m ${i.toString().padStart(2,"0")}s`:`${i}s`}destroy(){this.overlay&&(this.overlay.remove(),this.overlay=null)}}function ze(){const e=new URLSearchParams(window.location.search).get("showTimeline");if(e!==null)return e!=="0"&&e!=="false";const t=localStorage.getItem("xibo_show_timeline_overlay");return t!==null?t==="true":!1}const d=O("PWA"),_=new URL("./",window.location.href).pathname.replace(/\/$/,"");let F,P,I,le,Y,ce,M,de,ue,he,fe,ge;const D={};class Ve{constructor(){v(this,"renderer");v(this,"core");v(this,"xmds");v(this,"downloadOverlay",null);v(this,"timelineOverlay",null);v(this,"statsCollector",null);v(this,"logReporter",null);v(this,"displaySettings",null);v(this,"currentScheduleId",-1);v(this,"scheduledLayoutIds",new Set);v(this,"preparingLayoutId",null);v(this,"_screenshotInterval",null);v(this,"_screenshotMethod",null);v(this,"_screenshotInFlight",!1);v(this,"_html2canvasMod",null);v(this,"_wakeLock",null);v(this,"_probeTimer",null)}async init(){if(d.info("Initializing player with RendererLite + PlayerCore..."),await this.loadCoreModules(),"serviceWorker"in navigator)try{const i=await navigator.serviceWorker.register(`${_}/sw-pwa.js?v=${Date.now()}`,{scope:`${_}/`,type:"module",updateViaCache:"none"});d.info("Service Worker registered for offline mode:",i.scope),navigator.storage&&navigator.storage.persist&&(await navigator.storage.persist()?d.info("Persistent storage granted - cache won't be evicted"):d.warn("Persistent storage denied - cache may be evicted"))}catch(i){d.warn("Service Worker registration failed:",i)}d.info("Initializing cache..."),await F.init(),d.info("Initializing CacheProxy..."),M=new Ce,await M.init(),d.info("CacheProxy ready - using Service Worker backend");const e=document.getElementById("player-container");if(!e)throw new Error("No #player-container found");this.renderer=new _e({cmsUrl:I.cmsAddress,hardwareKey:I.hardwareKey},e,{getMediaUrl:async i=>{if(d.debug(`getMediaUrl called for media ${i}`),!await M.hasFile("media",String(i)))return d.warn(`Media ${i} not in cache`),"";const o=`${_}/cache/media/${i}`;return d.debug(`Using streaming URL for media ${i}: ${o}`),o},getWidgetHtml:async i=>{const r=`${_}/cache/widget/${i.layoutId}/${i.regionId}/${i.id}`;d.debug(`Looking for widget HTML at: ${r}`,i);try{if(await(await caches.open("xibo-media-v1")).match(r))return d.debug(`Widget HTML cached at ${r}, using cache URL for iframe`),{url:r,fallback:i.raw||""};d.warn(`No cached HTML found at ${r}`)}catch(o){d.error(`Failed to get cached widget HTML for ${i.id}:`,o)}return d.warn(`Using widget.raw fallback for ${i.id}`),i.raw||""}}),this.core=new qe({config:I,xmds:this.xmds,cache:M,schedule:P,renderer:this.renderer,xmrWrapper:ce,statsCollector:this.statsCollector,displaySettings:this.displaySettings}),this.setupCoreEventHandlers(),this.setupRendererEventHandlers(),this.setupServiceWorkerEventHandlers(),this.setupInteractiveControl(),this.setupRemoteControls(),this.core.on("register-complete",i=>{var s,n;const r=parseFloat((s=i==null?void 0:i.settings)==null?void 0:s.latitude),o=parseFloat((n=i==null?void 0:i.settings)==null?void 0:n.longitude);r&&o&&!isNaN(r)&&!isNaN(o)&&(d.info(`Display location from CMS: ${r.toFixed(4)}, ${o.toFixed(4)}`),P!=null&&P.setLocation&&P.setLocation(r,o))}),this.updateConfigDisplay(),window.addEventListener("online",()=>{console.log("[PWA] Browser reports online — triggering immediate collection"),this.updateStatus("Back online, syncing..."),this.removeOfflineIndicator(),this.core.collectNow().catch(i=>{d.error("Failed to collect after coming online:",i)})}),window.addEventListener("offline",()=>{console.warn("[PWA] Browser reports offline — continuing playback with cached data"),this.updateStatus("Offline mode — using cached content"),this.showOfflineIndicator()});const t=ae();t.enabled&&(this.downloadOverlay=new ne(t),d.info("Download overlay enabled (hover bottom-right corner)")),this.timelineOverlay=new Be(ze()),await this.requestWakeLock(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&this.requestWakeLock()}),await this.core.collect(),d.info("Player initialized successfully")}async requestWakeLock(){if(!("wakeLock"in navigator)){d.debug("Wake Lock API not supported");return}try{this._wakeLock=await navigator.wakeLock.request("screen"),d.info("Screen Wake Lock acquired — display will stay on"),this._wakeLock.addEventListener("release",()=>{d.debug("Screen Wake Lock released"),this._wakeLock=null})}catch(e){d.warn("Wake Lock request failed:",e==null?void 0:e.message)}}async loadCoreModules(){var e,t,i;try{const r=await T(()=>import("./index-Baows0WY.js").then(S=>S.i),__vite__mapDeps([0,1,2]),import.meta.url),o=await T(()=>import("./index-Be_IxwIZ.js"),__vite__mapDeps([3,4,2]),import.meta.url),s=await T(()=>import("./index-BF8qB-pu.js"),__vite__mapDeps([5,6,2,1]),import.meta.url),n=await T(()=>import("./index-BhHwWvzx.js"),__vite__mapDeps([7,2,1]),import.meta.url),a=await T(()=>import("./index-b1tfCACR.js"),__vite__mapDeps([8,2]),import.meta.url),l=await T(()=>import("./index-C77HSi9N.js"),__vite__mapDeps([9,2]),import.meta.url),c=await T(()=>import("./index-DPR3fBRV.js"),__vite__mapDeps([10,2,1]),import.meta.url),u=await T(()=>import("./index-es8y3c70.js"),__vite__mapDeps([11,2,1,6]),import.meta.url),y=await T(()=>import("./index-ChPoQ8Bt.js"),__vite__mapDeps([12,6,2,1]),import.meta.url);if(F=r.cacheManager,P=s.scheduleManager,I=n.config,le=o.RestClient,Y=o.XmdsClient,ce=a.XmrWrapper,de=l.StatsCollector,ue=l.formatStats,he=l.LogReporter,fe=l.formatLogs,ge=c.DisplaySettings,D.core=u.VERSION||"?",D.cache=r.VERSION||"?",D.renderer=y.VERSION||"?",D.schedule=s.VERSION||"?",D.xmds=o.VERSION||"?",D.xmr=a.VERSION||"?",D.utils=n.VERSION||"?",D.stats=l.VERSION||"?",D.settings=c.VERSION||"?",(e=window.electronAPI)!=null&&e.getSystemInfo)try{const S=await window.electronAPI.getSystemInfo();S.macAddress&&(I.macAddress=S.macAddress)}catch{}if(_.includes("pwa-xmds")||new URLSearchParams(window.location.search).get("transport")==="xmds")d.info("Using XMDS/SOAP transport (forced)"),this.xmds=new Y(I);else{this.xmds=new le(I);try{await this.xmds.registerDisplay(),d.info("Using REST transport")}catch(S){d.warn("REST unavailable, falling back to XMDS/SOAP:",S.message),this.xmds=new Y(I)}}this.statsCollector=new de,await this.statsCollector.init(),d.info("Stats collector initialized"),this.logReporter=new he,await this.logReporter.init(),d.info("Log reporter initialized"),$e(({level:S,name:x,args:V})=>{if(!this.logReporter)return;const N=V.map(W=>typeof W=="string"?W:JSON.stringify(W)).join(" ");this.logReporter.log(S,`[${x}] ${N}`,"PLAYER").catch(()=>{})}),this.displaySettings=new ge,d.info("Display settings manager initialized");const g="2026-02-20T13:39:47.662Z",p="0.3.1";d.info(`v${p} built ${g}`);const w=Object.entries(D).map(([S,x])=>`${S}=${x}`).join(" ");d.info(`SDK: ${w}`);const b=!!window.electronAPI,k=b?((t=navigator.userAgent.match(/Electron\/([\d.]+)/))==null?void 0:t[1])||"?":null,L=((i=navigator.userAgent.match(/Chrome\/([\d.]+)/))==null?void 0:i[1])||"?",$=b?`Electron ${k} / Chrome ${L}`:`Chrome ${L}`;d.info(`Env: PWA v${p} | ${$} | ${navigator.platform} | ${screen.width}x${screen.height}`),d.info("Core modules loaded")}catch(r){throw d.error("Failed to load core modules:",r),r}}setupCoreEventHandlers(){this.core.on("collection-start",()=>{this.updateStatus("Collecting data from CMS...")}),this.core.on("register-complete",e=>{var i;const t=((i=this.displaySettings)==null?void 0:i.getDisplayName())||e.displayName||I.hardwareKey;this.updateStatus(`Registered: ${t}`),this.displaySettings&&(document.title=`Xibo Player - ${this.displaySettings.getDisplayName()}`)}),this.core.on("files-received",e=>{this.updateStatus(`Downloading ${e.length} files...`)}),this.core.on("offline-mode",e=>{e?(this.updateStatus("Offline mode — using cached content"),this.showOfflineIndicator()):(this.updateStatus("Back online"),this.removeOfflineIndicator())}),this.core.on("purge-request",async e=>{try{const t=await M.deleteFiles(e);d.info(`Purge complete: ${t.deleted}/${t.total} files deleted`)}catch(t){d.warn("Purge failed:",t)}}),this.core.on("download-request",async e=>{var t;(t=this.downloadOverlay)==null||t.startUpdating();try{await M.requestDownload(e),d.info("Download request complete")}catch(i){d.error("Download request failed:",i),this.updateStatus("Download failed: "+i,"error")}}),this.core.on("schedule-received",e=>{var t;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),(t=this.renderer)!=null&&t.layoutPool){const i=new Set;if(e.layouts)for(const o of e.layouts){const s=parseInt(String(o.file||o.id||o).replace(".xlf",""),10);s&&i.add(s)}if(e.campaigns){for(const o of e.campaigns)if(o.layouts)for(const s of o.layouts){const n=parseInt(String(s.file||s.id||s).replace(".xlf",""),10);n&&i.add(n)}}const r=this.renderer.layoutPool.clearWarmNotIn(i);r>0&&d.info(`Cleared ${r} preloaded layout(s) no longer in schedule`),this.scheduledLayoutIds=i}d.debug("Current scheduleId for stats:",this.currentScheduleId)}),this.core.on("layout-prepare-request",async e=>{await this.prepareAndRenderLayout(e)}),this.core.on("no-layouts-scheduled",()=>{this.updateStatus("No layouts scheduled")}),this.core.on("collection-complete",()=>{const e=this.core.getCurrentLayoutId();e?this.updateStatus(`Playing layout ${e}`):this.preparingLayoutId&&this.updateStatus(`Downloading layout ${this.preparingLayoutId}...`),this.probeLayoutDurations().catch(t=>{d.debug("Duration probe failed (non-blocking):",t)})}),this.core.on("collection-error",e=>{var t;this.updateStatus(`Collection error: ${e}`,"error"),(t=this.logReporter)==null||t.reportFault("COLLECTION_FAILED",`Collection cycle failed: ${(e==null?void 0:e.message)||e}`)}),this.core.on("xmr-connected",e=>{d.info("XMR connected:",e)}),this.core.on("xmr-misconfigured",e=>{d.warn(`XMR misconfigured (${e.reason}): ${e.message}`),console.warn(`%c[XMR] ${e.message}`,"background: #ff9800; color: #000; padding: 4px 8px; font-weight: bold;")}),this.core.on("log-level-changed",()=>{const e=me();d.info(`Log level changed, debug=${e}`),e&&!this.downloadOverlay?(this.downloadOverlay=new ne(ae()),d.info("Download overlay enabled (log level → DEBUG)")):!e&&this.downloadOverlay&&(this.downloadOverlay.destroy(),this.downloadOverlay=null,d.info("Download overlay disabled (log level above DEBUG)"))}),this.core.on("overlay-layout-request",async e=>{d.info("Overlay layout requested:",e),await this.prepareAndRenderLayout(e)}),this.core.on("revert-to-schedule",()=>{d.info("Reverting to scheduled content"),this.updateStatus("Reverting to schedule...")}),this.core.on("purge-all-request",async()=>{d.info("Purging all cached content..."),this.updateStatus("Purging cache...");try{const e=await caches.keys();await Promise.all(e.map(t=>caches.delete(t))),d.info(`Purged ${e.length} caches`),await F.init()}catch(e){d.error("Cache purge failed:",e)}}),this.core.on("command-result",e=>{var t;d.info("Command result:",e),e.success||(t=this.logReporter)==null||t.reportFault("COMMAND_FAILED",`Command ${e.code} failed: ${e.reason||"unknown"}`)}),this.displaySettings&&(this.displaySettings.on("interval-changed",e=>{d.info(`Collection interval changed to ${e}s`)}),this.displaySettings.on("settings-applied",(e,t)=>{t.length>0&&d.info("Settings updated from CMS:",t.join(", ")),this._screenshotInterval||this.startScreenshotInterval()})),this.core.on("submit-stats-request",async()=>{await this.submitStats()}),this.core.on("submit-logs-request",async()=>{await this.submitLogs()}),this.core.on("screenshot-request",async()=>{await this.captureAndSubmitScreenshot()}),this.core.on("check-pending-layout",async e=>{await this.prepareAndRenderLayout(e)}),this.core.on("timeline-updated",e=>{var t;(t=this.timelineOverlay)==null||t.update(e,this.core.getCurrentLayoutId())})}setupInteractiveControl(){var e;(e=navigator.serviceWorker)==null||e.addEventListener("message",t=>{var l,c;if(((l=t.data)==null?void 0:l.type)!=="INTERACTIVE_CONTROL")return;const{method:i,path:r,search:o,body:s}=t.data,n=(c=t.ports)==null?void 0:c[0];if(!n)return;const a=this.handleInteractiveControl(i,r,o,s);n.postMessage(a)})}setupRemoteControls(){window.addEventListener("blur",()=>{setTimeout(()=>window.focus(),200)}),document.addEventListener("keydown",e=>{var t,i;switch(e.key){case"ArrowRight":case"PageDown":case"MediaTrackNext":this.core.peekNextLayout()&&(d.info("[Remote] Next layout"),this.core.advanceToNextLayout());break;case"ArrowLeft":case"PageUp":case"MediaTrackPrevious":d.info("[Remote] Previous layout"),this.core.advanceToPreviousLayout();break;case" ":e.preventDefault(),this.renderer._paused?(d.info("[Remote] Resume"),this.renderer.resume()):(d.info("[Remote] Pause"),this.renderer.pause());break;case"MediaPlayPause":this.renderer._paused?(d.info("[Remote] Resume (MediaPlayPause)"),this.renderer.resume()):(d.info("[Remote] Pause (MediaPlayPause)"),this.renderer.pause());break;case"t":case"T":(t=this.timelineOverlay)==null||t.toggle();break;case"d":case"D":(i=this.downloadOverlay)==null||i.toggle();break}}),"mediaSession"in navigator&&(navigator.mediaSession.setActionHandler("nexttrack",()=>{d.info("[Remote] Next layout (MediaSession)"),this.core.advanceToNextLayout()}),navigator.mediaSession.setActionHandler("previoustrack",()=>{d.info("[Remote] Previous layout (MediaSession)"),this.core.advanceToPreviousLayout()}),navigator.mediaSession.setActionHandler("pause",()=>{d.info("[Remote] Pause (MediaSession)"),this.renderer.pause()}),navigator.mediaSession.setActionHandler("play",()=>{d.info("[Remote] Resume (MediaSession)"),this.renderer.resume()})),d.info("Remote controls initialized (keyboard + MediaSession)")}parseBody(e){try{return e?JSON.parse(e):{}}catch{return{}}}handleInteractiveControl(e,t,i,r){var o;switch(d.debug("IC request:",e,t,i),t){case"/info":return{status:200,body:JSON.stringify({hardwareKey:I.hardwareKey,displayName:I.displayName,playerType:"pwa",currentLayoutId:this.core.getCurrentLayoutId()})};case"/trigger":{const s=this.parseBody(r);return this.renderer.emit("interactiveTrigger",{targetId:s.id,triggerCode:s.trigger}),s.trigger&&this.core.handleTrigger(s.trigger),{status:200,body:"OK"}}case"/duration/expire":{const s=this.parseBody(r);return d.info("IC: Widget duration expire requested for",s.id),this.renderer.emit("widgetExpire",{widgetId:s.id}),{status:200,body:"OK"}}case"/duration/extend":{const s=this.parseBody(r);return d.info("IC: Widget duration extend by",s.duration,"for",s.id),this.renderer.emit("widgetExtendDuration",{widgetId:s.id,duration:parseInt(s.duration)}),{status:200,body:"OK"}}case"/duration/set":{const s=this.parseBody(r);return d.info("IC: Widget duration set to",s.duration,"for",s.id),this.renderer.emit("widgetSetDuration",{widgetId:s.id,duration:parseInt(s.duration)}),{status:200,body:"OK"}}case"/fault":{const s=this.parseBody(r);return(o=this.logReporter)==null||o.reportFault(s.code||"WIDGET_FAULT",s.reason||"Widget reported fault"),{status:200,body:"OK"}}case"/realtime":{const n=new URLSearchParams(i).get("dataKey");if(d.debug("IC: Realtime data request for key:",n),!n)return{status:400,body:JSON.stringify({error:"Missing dataKey parameter"})};const l=this.core.getDataConnectorManager().getData(n);return l===null?{status:404,body:JSON.stringify({error:`No data available for key: ${n}`})}:{status:200,body:typeof l=="string"?l:JSON.stringify(l)}}default:return{status:404,body:JSON.stringify({error:"Unknown IC route"})}}}setupServiceWorkerEventHandlers(){navigator.serviceWorker&&navigator.serviceWorker.addEventListener("message",e=>{const{type:t,fileId:i,fileType:r}=e.data;t==="FILE_CACHED"&&(d.debug(`Service Worker cached ${r}/${i}`),(r==="media"||r==="layout")&&this.core.notifyMediaReady(parseInt(i),r),this._probeTimer&&clearTimeout(this._probeTimer),this._probeTimer=setTimeout(()=>{this._probeTimer=null,this.probeLayoutDurations().catch(()=>{})},3e3))})}setupRendererEventHandlers(){this.renderer.on("layoutStart",(e,t)=>{var i;d.info("Layout started:",e),this.updateStatus(`Playing layout ${e}`),this.core.setCurrentLayout(e),(i=this.timelineOverlay)==null||i.update(null,e),t!=null&&t.duration&&this.core.recordLayoutDuration(String(e),t.duration),this.statsCollector&&this.statsCollector.startLayout(e,this.currentScheduleId).catch(r=>{d.error("Failed to start layout stat:",r)})}),this.renderer.on("layoutEnd",e=>{d.info("Layout ended:",e),P==null||P.recordPlay(e.toString()),this.statsCollector&&this.statsCollector.endLayout(e,this.currentScheduleId).catch(i=>{d.error("Failed to end layout stat:",i)}),this.core.notifyLayoutStatus(e),this.core.clearCurrentLayout();const t=this.core.getPendingLayouts();if(t.length>0){d.info(`Layout ${t[0]} pending download, skipping advance`);return}d.info("Layout cycle completed, advancing to next layout..."),this.core.advanceToNextLayout()}),this.renderer.on("widgetStart",e=>{const{widgetId:t,layoutId:i,mediaId:r}=e;d.debug("Widget started:",e.type,t,"media:",r),this.statsCollector&&r&&this.statsCollector.startWidget(r,i,this.currentScheduleId).catch(o=>{d.error("Failed to start widget stat:",o)})}),this.renderer.on("widgetEnd",e=>{const{widgetId:t,layoutId:i,mediaId:r}=e;d.debug("Widget ended:",e.type,t,"media:",r),this.statsCollector&&r&&this.statsCollector.endWidget(r,i,this.currentScheduleId).catch(o=>{d.error("Failed to end widget stat:",o)})}),this.renderer.on("error",e=>{var t;d.error("Renderer error:",e),this.updateStatus(`Error: ${e.type}`,"error"),(t=this.logReporter)==null||t.reportFault(e.type||"RENDERER_ERROR",`Renderer error: ${e.message||e.type} (layout ${e.layoutId||"unknown"})`)}),this.renderer.on("action-trigger",e=>{var n,a;const{actionType:t,triggerCode:i,layoutCode:r,targetId:o,commandCode:s}=e;switch(d.info("Action trigger:",t,e),t){case"navLayout":case"navigateToLayout":i?this.core.handleTrigger(i):r&&this.core.changeLayout(r);break;case"navWidget":case"navigateToWidget":i?this.core.handleTrigger(i):o&&this.renderer.navigateToWidget(o);break;case"previousWidget":this.renderer.previousWidget((n=e.source)==null?void 0:n.regionId);break;case"nextWidget":this.renderer.nextWidget((a=e.source)==null?void 0:a.regionId);break;case"command":s&&this.core.executeCommand(s);break;default:d.warn("Unknown action type:",t)}}),this.renderer.on("layoutDurationUpdated",(e,t)=>{this.core.recordLayoutDuration(String(e),t)}),this.renderer.on("request-next-layout-preload",async()=>{try{const e=this.core.peekNextLayout();if(!e){d.debug("No next layout to preload (single layout schedule or same layout)");return}const t=e.layoutId;if(this.renderer.layoutPool.has(t)){d.debug(`Layout ${t} already in preload pool`);return}d.info(`Preloading next layout ${t}...`);const i=await F.getCachedFile("layout",t);if(!i){d.debug(`Layout ${t} XLF not cached, skipping preload`);return}const r=await i.text(),{allMedia:o,videoMedia:s}=this.getMediaIds(r);if(!await this.checkAllMediaCached(o,s)){d.debug(`Media not fully cached for layout ${t}, skipping preload`);return}await this.fetchWidgetHtml(r,t),s.length>0&&await M.prewarmVideoChunks(s),await this.renderer.preloadLayout(r,t)?d.info(`Layout ${t} preloaded successfully`):d.warn(`Layout ${t} preload failed (will fall back to normal render)`)}catch(e){d.warn("Layout preload failed (non-blocking):",e)}})}async prepareAndRenderLayout(e){var t;if(this.core.getCurrentLayoutId()===e){d.debug(`Layout ${e} already playing, skipping duplicate prepare`);return}if(this.preparingLayoutId===e){d.debug(`Layout ${e} preparation already in progress, skipping`);return}this.preparingLayoutId=e;try{const i=await F.getCachedFile("layout",e);if(!i){d.info("Layout not in cache yet, marking as pending:",e),this.core.setPendingLayout(e,[e]),this.updateStatus(`Downloading layout ${e}...`);return}const r=await i.text(),{allMedia:o,videoMedia:s}=this.getMediaIds(r);if(!await this.checkAllMediaCached(o,s)){M.prioritizeLayoutFiles(o.map(String)),d.info(`Waiting for media to finish downloading for layout ${e}`),this.updateStatus(`Preparing layout ${e}...`),this.core.setPendingLayout(e,o);return}await this.fetchWidgetHtml(r,e),s.length>0&&(d.info(`Pre-warming ${s.length} video file(s) for layout ${e}`),await M.prewarmVideoChunks(s)),await this.renderer.renderLayout(r,e),this.updateStatus(`Playing layout ${e}`)}catch(i){d.error("Failed to prepare layout:",e,i),this.updateStatus(`Failed to load layout ${e}`,"error"),(t=this.logReporter)==null||t.reportFault("LAYOUT_LOAD_FAILED",`Failed to prepare layout ${e}: ${(i==null?void 0:i.message)||i}`)}finally{this.preparingLayoutId=null}}getMediaIds(e){var n;const i=new DOMParser().parseFromString(e,"text/xml"),r=[],o=[];i.querySelectorAll("media[fileId]").forEach(a=>{const l=a.getAttribute("fileId");if(l){const c=parseInt(l,10);r.push(c),a.getAttribute("type")==="video"&&o.push(c)}});const s=(n=i.querySelector("layout"))==null?void 0:n.getAttribute("background");if(s){const a=parseInt(s,10);!isNaN(a)&&!r.includes(a)&&r.push(a)}return{allMedia:r,videoMedia:o}}async checkAllMediaCached(e,t=[]){for(const i of e)try{if(!await M.hasFile("media",String(i)))return d.debug(`Media ${i} not yet cached`),!1;const o=await F.getCachedResponse("media",i);if(!o){const u=await caches.open("xibo-media-v1"),y=await u.match(`${_}/cache/media/${i}/metadata`);if(y){const m=await y.text(),g=JSON.parse(m),p=(g.totalSize/1024/1024).toFixed(1);if(t.includes(i)){if(!await u.match(`${_}/cache/media/${i}/chunk-0`))return d.debug(`Media ${i} video: chunk 0 not yet available`),!1;const k=g.numChunks-1;if(k>0&&!await u.match(`${_}/cache/media/${i}/chunk-${k}`))return d.debug(`Media ${i} video: last chunk (${k}) not yet available`),!1;d.info(`Media ${i} video ready for early playback (chunk 0 + ${k} of ${g.numChunks}, ${p} MB total)`)}else{const b=`${_}/cache/media/${i}/chunk-${g.numChunks-1}`;if(!await u.match(b))return d.debug(`Media ${i} chunked but still downloading (chunk ${g.numChunks-1} missing)`),!1;d.debug(`Media ${i} cached as chunks (${g.numChunks} x ${(g.chunkSize/1024/1024).toFixed(0)} MB = ${p} MB total)`)}continue}}const s=o.headers.get("Content-Type")||"",n=await o.blob();if(s==="text/plain"||n.size<100){d.warn(`Media ${i} corrupted (${s}, ${n.size} bytes) - will re-download`);const u=await caches.open("xibo-media-v1"),y=`${_}/cache/media/${i}`;return await u.delete(y),!1}const a=n.size/1024,l=a/1024,c=l>=1?`${l.toFixed(1)} MB`:`${a.toFixed(1)} KB`;d.debug(`Media ${i} cached and valid (${c})`)}catch{d.warn(`Unable to verify media ${i}, assuming cached (offline mode)`)}return!0}async fetchWidgetHtml(e,t){const r=new DOMParser().parseFromString(e,"text/xml"),o=["clock","calendar","weather","currencies","stocks","twitter","global","embedded","text","ticker"],s=[];for(const n of r.querySelectorAll("region")){const a=n.getAttribute("id");for(const l of n.querySelectorAll("media")){const c=l.getAttribute("type"),u=l.getAttribute("id");if(o.some(y=>c==null?void 0:c.includes(y))){const y=`${_}/cache/widget/${t}/${a}/${u}`;s.push((async()=>{try{const g=await(await caches.open("xibo-media-v1")).match(y);let p;g?(p=await g.text(),d.debug(`Using cached widget HTML for ${c} ${u}`)):(p=await this.xmds.getResource(t,a,u),await F.cacheWidgetHtml(t,a,u,p),d.debug(`Retrieved widget HTML for ${c} ${u}`));const w=l.querySelector("raw");if(w)w.textContent=p;else{const b=r.createElement("raw");b.textContent=p,l.appendChild(b)}}catch(m){d.warn(`Failed to get widget HTML for ${c} ${u}:`,m)}})())}}}s.length>0&&(d.info(`Fetching ${s.length} widget HTML resources in parallel...`),await Promise.all(s),d.debug("All widget HTML fetched"))}async probeLayoutDurations(){if(this.scheduledLayoutIds.size!==0)for(const e of this.scheduledLayoutIds)try{const t=await F.getCachedFile("layout",e);if(!t)continue;const i=await t.text(),{videoMedia:r}=this.getMediaIds(i);if(r.length===0)continue;const s=new DOMParser().parseFromString(i,"text/xml");let n=0;for(const a of s.querySelectorAll('media[type="video"]')){if(a.getAttribute("useDuration")==="1")continue;const c=a.getAttribute("fileId");if(!c||!await M.hasFile("media",c))continue;const y=await this.probeVideoDuration(`${_}/cache/media/${c}`);y>0&&(n=Math.max(n,y))}n>0&&this.core.recordLayoutDuration(String(e),n)}catch(t){d.debug(`Duration probe failed for layout ${e}:`,t)}}probeVideoDuration(e){return new Promise(t=>{const i=document.createElement("video");i.preload="metadata",i.muted=!0;const r=()=>{i.removeAttribute("src"),i.load()};i.addEventListener("loadedmetadata",()=>{const o=Math.floor(i.duration);r(),t(o)},{once:!0}),i.addEventListener("error",()=>{r(),t(0)},{once:!0}),setTimeout(()=>{r(),t(0)},5e3),i.src=e})}updateConfigDisplay(){const e=document.getElementById("config-info");if(e){const t="0.3.1",i="2026-02-20T13:39:47.662Z".replace("T"," ").replace(/\.\d+Z$/,""),r=i?`v${t} (${i})`:`v${t}`;e.textContent=`${r} | CMS: ${I.cmsAddress} | Display: ${I.displayName||"Unknown"} | HW: ${I.hardwareKey}`}}async submitStats(){var e;if(!this.statsCollector){d.warn("Stats collector not initialized");return}try{const i=(((e=this.displaySettings)==null?void 0:e.getSetting("aggregationLevel"))||"Individual")==="Aggregate"?await this.statsCollector.getAggregatedStatsForSubmission(50):await this.statsCollector.getStatsForSubmission(50);if(i.length===0){d.debug("No stats to submit");return}d.info(`Submitting ${i.length} proof of play stats...`);const r=ue(i);await this.xmds.submitStats(r)?(d.info("Stats submitted successfully"),await this.statsCollector.clearSubmittedStats(i),d.debug(`Cleared ${i.length} submitted stats from database`)):d.warn("Stats submission failed (CMS returned false)")}catch(t){d.error("Failed to submit stats:",t)}}async submitLogs(){if(this.logReporter)try{const e=await this.logReporter.getLogsForSubmission(100);if(e.length===0){d.debug("No logs to submit");return}d.info(`Submitting ${e.length} logs to CMS...`);const t=fe(e);await this.xmds.submitLog(t)?(d.info("Logs submitted successfully"),await this.logReporter.clearSubmittedLogs(e)):d.warn("Log submission failed (CMS returned false)")}catch(e){d.error("Failed to submit logs:",e)}}async captureAndSubmitScreenshot(){var e;if(this._screenshotInFlight){d.debug("Screenshot capture already in progress, skipping");return}this._screenshotInFlight=!0;try{let t;if(this._screenshotMethod==="electron"||this._screenshotMethod===null&&((e=window.electronAPI)!=null&&e.captureScreenshot)){const r=await window.electronAPI.captureScreenshot();if(r)this._screenshotMethod="electron",t=r;else{d.debug("Electron screenshot not ready yet, will retry next interval");return}}else t=await this.captureWithBrowserMethods();await this.xmds.submitScreenShot(t)?d.info(`Screenshot submitted (${this._screenshotMethod})`):d.warn("Screenshot submission failed")}catch(t){d.error("Failed to capture screenshot:",t)}finally{this._screenshotInFlight=!1}}async captureWithBrowserMethods(){return this._screenshotMethod="html2canvas",this.captureHtml2Canvas()}async captureHtml2Canvas(){const e=document.createElement("canvas");e.width=window.innerWidth,e.height=window.innerHeight;const t=e.getContext("2d");t.fillStyle="#000",t.fillRect(0,0,e.width,e.height);const i=document.getElementById("player-container");if(!i)return e.toDataURL("image/jpeg",.8).split(",")[1];const r=i.getBoundingClientRect(),o=getComputedStyle(i),s=o.backgroundColor;s&&s!=="transparent"&&s!=="rgba(0, 0, 0, 0)"&&(t.fillStyle=s,t.fillRect(r.left,r.top,r.width,r.height));const n=o.backgroundImage;if(n&&n!=="none"){const c=n.match(/url\(["']?(.*?)["']?\)/);if(c)try{const u=new Image;u.crossOrigin="anonymous",await new Promise(y=>{u.onload=()=>y(),u.onerror=()=>y(),setTimeout(()=>y(),2e3),u.src=c[1]}),u.naturalWidth&&t.drawImage(u,r.left,r.top,r.width,r.height)}catch{}}this._html2canvasMod||(this._html2canvasMod=(await T(async()=>{const{default:c}=await import("./html2canvas.esm-CBrSDip1.js");return{default:c}},[],import.meta.url)).default);const a=i.querySelectorAll("img, video, iframe, canvas");let l=0;for(const c of a){const u=c;if(u.style.visibility==="hidden"||u.style.display==="none")continue;const y=c.getBoundingClientRect();if(!(y.width===0||y.height===0))try{if(c instanceof HTMLImageElement){if(!c.complete||!c.naturalWidth)continue;if(getComputedStyle(c).objectFit==="contain"&&c.naturalWidth&&c.naturalHeight){const g=this.containedRect(c.naturalWidth,c.naturalHeight,y);t.drawImage(c,g.x,g.y,g.w,g.h)}else t.drawImage(c,y.left,y.top,y.width,y.height);l++}else if(c instanceof HTMLVideoElement){if(c.readyState<2)continue;if(getComputedStyle(c).objectFit==="contain"&&c.videoWidth&&c.videoHeight){const g=this.containedRect(c.videoWidth,c.videoHeight,y);t.drawImage(c,g.x,g.y,g.w,g.h)}else t.drawImage(c,y.left,y.top,y.width,y.height);l++}else if(c instanceof HTMLCanvasElement)t.drawImage(c,y.left,y.top,y.width,y.height),l++;else if(c instanceof HTMLIFrameElement){const m=c.contentDocument;if(!(m!=null&&m.body))continue;const g=document.createElement("div");g.style.cssText=`position:fixed;left:-9999px;top:0;width:${y.width}px;height:${y.height}px;overflow:hidden;`;const p=[];for(const L of m.querySelectorAll("style"))g.appendChild(L.cloneNode(!0));for(const L of m.querySelectorAll('link[rel="stylesheet"]')){const $=document.createElement("link");$.rel="stylesheet",$.href=new URL(L.getAttribute("href")||"",m.baseURI).href,g.appendChild($),p.push(new Promise(S=>{$.onload=()=>S(),$.onerror=()=>S()}))}g.appendChild(m.body.cloneNode(!0)),document.body.appendChild(g);const w=m.querySelectorAll("img"),b=new Map;w.forEach((L,$)=>{L.naturalWidth&&L.naturalHeight&&b.set(String($),{nw:L.naturalWidth,nh:L.naturalHeight})}),p.length>0&&await Promise.race([Promise.all(p),new Promise(L=>setTimeout(L,500))]);const k=await this._html2canvasMod(g,{useCORS:!0,allowTaint:!0,logging:!1,backgroundColor:null,width:y.width,height:y.height,onclone:L=>{const $=L.createElement("style");$.textContent="*, *::before, *::after { animation: none !important; transition: none !important; opacity: 1 !important; }",L.head.appendChild($),L.querySelectorAll("img").forEach((x,V)=>{var Q,ee;const N=(Q=L.defaultView)==null?void 0:Q.getComputedStyle(x);if(!N||N.objectFit!=="contain")return;const W=b.get(String(V));if(!W)return;const q=x.clientWidth||parseFloat(N.width)||0,B=x.clientHeight||parseFloat(N.height)||0;if(!q||!B)return;const X=W.nw/W.nh,we=q/B;let j,K;X>we?(j=q,K=q/X):(K=B,j=B*X);const G=L.createElement("div");G.style.cssText=`width:${q}px;height:${B}px;display:flex;align-items:center;justify-content:center;overflow:hidden;`,x.style.objectFit="fill",x.style.width=`${j}px`,x.style.height=`${K}px`,(ee=x.parentNode)==null||ee.insertBefore(G,x),G.appendChild(x)})}});document.body.removeChild(g),t.drawImage(k,y.left,y.top,y.width,y.height),l++}}catch(m){d.warn("Screenshot: failed to draw element",c.tagName,m)}}return d.debug(`Screenshot: composed ${l}/${a.length} elements`),e.toDataURL("image/jpeg",.8).split(",")[1]}containedRect(e,t,i){const r=e/t,o=i.width/i.height;let s,n;return r>o?(s=i.width,n=i.width/r):(n=i.height,s=i.height*r),{x:i.left+(i.width-s)/2,y:i.top+(i.height-n)/2,w:s,h:n}}startScreenshotInterval(){var i;const e=((i=this.displaySettings)==null?void 0:i.getSetting("screenshotInterval"))||0;if(!e||e<=0)return;this._html2canvasMod||T(()=>import("./html2canvas.esm-CBrSDip1.js"),[],import.meta.url).then(r=>{this._html2canvasMod=r.default});const t=e*1e3;d.info(`Starting periodic screenshots every ${e}s`),this._screenshotInterval=setInterval(()=>{this.captureAndSubmitScreenshot()},t)}updateStatus(e,t="info"){const i=document.getElementById("status");i&&(i.textContent=e,i.className=`status status-${t}`),t==="error"?d.error("Status:",e):d.info("Status:",e)}showOfflineIndicator(){var e;(e=this.timelineOverlay)==null||e.setOffline(!0)}removeOfflineIndicator(){var e;(e=this.timelineOverlay)==null||e.setOffline(!1)}cleanup(){this.core.cleanup(),this.renderer.cleanup(),this._screenshotInterval&&(clearInterval(this._screenshotInterval),this._screenshotInterval=null),this._wakeLock&&(this._wakeLock.release(),this._wakeLock=null),this.downloadOverlay&&this.downloadOverlay.destroy(),this.timelineOverlay&&this.timelineOverlay.destroy()}}function ye(){const h=new Ve;h.init().catch(e=>{d.error("Failed to initialize:",e)}),window.addEventListener("beforeunload",()=>{h.cleanup()})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",ye):ye();export{Ue as D,Je as I,ke as L,Ae as O,qe as P,_e as R,De as S,We as c,Pe as p,Ye as s};
44
+ //# sourceMappingURL=main-BUvkpHsV.js.map