@xiboplayer/pwa 0.6.0 → 0.6.1
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.
- package/dist/assets/{chunk-config-yjpxlWy6.js → chunk-config-oTovBPVw.js} +2 -2
- package/dist/assets/{chunk-config-yjpxlWy6.js.map → chunk-config-oTovBPVw.js.map} +1 -1
- package/dist/assets/{index-BKKe321E.js → index-B6MdC-Qx.js} +2 -2
- package/dist/assets/{index-BKKe321E.js.map → index-B6MdC-Qx.js.map} +1 -1
- package/dist/assets/{index-BnKrYvmo.js → index-BVIXBw9z.js} +2 -2
- package/dist/assets/{index-BnKrYvmo.js.map → index-BVIXBw9z.js.map} +1 -1
- package/dist/assets/{index-DblGx8T7.js → index-BWvWWyDc.js} +2 -2
- package/dist/assets/{index-DblGx8T7.js.map → index-BWvWWyDc.js.map} +1 -1
- package/dist/assets/{index-B-_xo7Jd.js → index-C3Orblel.js} +2 -2
- package/dist/assets/{index-B-_xo7Jd.js.map → index-C3Orblel.js.map} +1 -1
- package/dist/assets/{index-D8pNyRvb.js → index-CleHw0Tc.js} +2 -2
- package/dist/assets/{index-D8pNyRvb.js.map → index-CleHw0Tc.js.map} +1 -1
- package/dist/assets/index-Cq9aOTTR.js +2 -0
- package/dist/assets/{index-DcGeUWV6.js.map → index-Cq9aOTTR.js.map} +1 -1
- package/dist/assets/index-D81Qhc3r.js +2 -0
- package/dist/assets/{index-C0V6Yuf7.js.map → index-D81Qhc3r.js.map} +1 -1
- package/dist/assets/{index-CjVBCq85.js → index-D_aTOqNE.js} +2 -2
- package/dist/assets/{index-CjVBCq85.js.map → index-D_aTOqNE.js.map} +1 -1
- package/dist/assets/index-Dj2ND9Mx.js +2 -0
- package/dist/assets/{index-ChxdOFuh.js.map → index-Dj2ND9Mx.js.map} +1 -1
- package/dist/assets/{index-BgzyAcAx.js → index-leM889oV.js} +2 -2
- package/dist/assets/{index-BgzyAcAx.js.map → index-leM889oV.js.map} +1 -1
- package/dist/assets/{main-BuxLonCL.js → main-DvteKhS8.js} +12 -17
- package/dist/assets/main-DvteKhS8.js.map +1 -0
- package/dist/assets/{setup-BKQoSJHa.js → setup-KpkKDvsj.js} +2 -2
- package/dist/assets/{setup-BKQoSJHa.js.map → setup-KpkKDvsj.js.map} +1 -1
- package/dist/assets/{sync-manager-Bxee4l4n.js → sync-manager-DEght5gK.js} +2 -2
- package/dist/assets/{sync-manager-Bxee4l4n.js.map → sync-manager-DEght5gK.js.map} +1 -1
- package/dist/assets/{widget-html-wxCcPbXd.js → widget-html-BAV4ZZd8.js} +2 -2
- package/dist/assets/{widget-html-wxCcPbXd.js.map → widget-html-BAV4ZZd8.js.map} +1 -1
- package/dist/assets/xmds-client-Dyy8ex-B.js +16 -0
- package/dist/assets/xmds-client-Dyy8ex-B.js.map +1 -0
- package/dist/index.html +3 -3
- package/dist/setup.html +3 -3
- package/dist/sw-pwa.js +1 -1
- package/package.json +13 -13
- package/dist/assets/index-C0V6Yuf7.js +0 -2
- package/dist/assets/index-ChxdOFuh.js +0 -2
- package/dist/assets/index-DcGeUWV6.js +0 -2
- package/dist/assets/main-BuxLonCL.js.map +0 -1
- package/dist/assets/xmds-client-xqEtcrNj.js +0 -16
- package/dist/assets/xmds-client-xqEtcrNj.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-
|
|
2
|
-
var _e=Object.defineProperty;var ke=(p,e,t)=>e in p?_e(p,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):p[e]=t;var $=(p,e,t)=>ke(p,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";import{createLogger as q,PLAYER_API as O,EventEmitter as xe,fetchWithRetry as Te,applyCmsLogLevel as De,config as Me,registerLogSink as Ae}from"./index-BgzyAcAx.js";import{c as Ee,C as Re,D as Pe,L as ne,B as de,S as Oe}from"./widget-html-wxCcPbXd.js";const Fe="modulepreload",We=function(p,e){return new URL(p,e).href},ue={},P=function(e,t,i){let n=Promise.resolve();if(t&&t.length>0){const s=document.getElementsByTagName("link"),r=document.querySelector("meta[property=csp-nonce]"),a=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));n=Promise.allSettled(t.map(l=>{if(l=We(l,i),l in ue)return;ue[l]=!0;const c=l.endsWith(".css"),d=c?'[rel="stylesheet"]':"";if(!!i)for(let f=s.length-1;f>=0;f--){const m=s[f];if(m.href===l&&(!c||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${l}"]${d}`))return;const g=document.createElement("link");if(g.rel=c?"stylesheet":Fe,c||(g.as="script"),g.crossOrigin="",g.href=l,a&&g.setAttribute("nonce",a),document.head.appendChild(g),c)return new Promise((f,m)=>{g.addEventListener("load",f),g.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${l}`)))})}))}function o(s){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=s,window.dispatchEvent(r),!r.defaultPrevented)throw s}return n.then(s=>{for(const r of s||[])r.status==="rejected"&&o(r.reason);return e().catch(o)})};let Ne=()=>({emit(p,...e){for(let t=this.events[p]||[],i=0,n=t.length;i<n;i++)t[i](...e)},events:{},on(p,e){var t;return((t=this.events)[p]||(t[p]=[])).push(e),()=>{var i;this.events[p]=(i=this.events[p])==null?void 0:i.filter(n=>e!==n)}}});const J=q("LayoutPool");class Y{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),J.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(J.info(`Evicting layout ${e} from pool`),t.regions)for(const[i,n]of t.regions)n.timer&&(clearTimeout(n.timer),n.timer=null);if(t.container&&Y.releaseMediaElements(t.container),t.blobUrls&&t.blobUrls.size>0&&(t.blobUrls.forEach(i=>{URL.revokeObjectURL(i)}),J.info(`Revoked ${t.blobUrls.size} blob URLs for layout ${e}`)),t.mediaUrlCache)for(const[i,n]of t.mediaUrlCache)n&&typeof n=="string"&&n.startsWith("blob:")&&URL.revokeObjectURL(n);t.container&&t.container.parentNode&&t.container.remove(),this.layouts.delete(e),this.hotLayoutId===e&&(this.hotLayoutId=null)}}static releaseMediaElements(e){let t=0,i=0;e.querySelectorAll("video").forEach(n=>{n._hlsInstance&&(n._hlsInstance.destroy(),n._hlsInstance=null,i++),n._mediaStream&&(n._mediaStream.getTracks().forEach(o=>o.stop()),n._mediaStream=null,n.srcObject=null),n.pause(),n.removeAttribute("src"),n.load(),t++}),e.querySelectorAll("audio").forEach(n=>{n.pause(),n.removeAttribute("src"),n.load()}),t>0&&J.info(`Released ${t} video(s)${i?` (${i} HLS)`:""}`)}evictLRU(){let e=null,t=1/0;for(const[i,n]of this.layouts)n.status==="warm"&&n.lastAccess<t&&(e=i,t=n.lastAccess);e!==null&&this.evict(e)}clearWarm(){let e=0;const t=[];for(const[i,n]of this.layouts)n.status==="warm"&&t.push(i);for(const i of t)this.evict(i),e++;return e>0&&J.info(`Cleared ${e} warm layout(s) from pool`),e}clearWarmNotIn(e){let t=0;const i=[];for(const[n,o]of this.layouts)o.status==="warm"&&!e.has(n)&&i.push(n);for(const n of i)this.evict(n),t++;return t>0&&J.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 Z={fadeIn(p,e){const t=[{opacity:0},{opacity:1}],i={duration:e,easing:"linear",fill:"forwards"};return p.animate(t,i)},fadeOut(p,e){const t=[{opacity:1},{opacity:0}],i={duration:e,easing:"linear",fill:"forwards"};return p.animate(t,i)},getFlyKeyframes(p,e,t,i){const n={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=n[p]||n.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(p,e,t,i,n){const o=this.getFlyKeyframes(t,i,n,!0),s={duration:e,easing:"ease-out",fill:"forwards"};return p.animate([o.from,o.to],s)},flyOut(p,e,t,i,n){const o=this.getFlyKeyframes(t,i,n,!1),s={duration:e,easing:"ease-in",fill:"forwards"};return p.animate([o.from,o.to],s)},apply(p,e,t,i,n){if(!e||!e.type)return null;const o=e.type.toLowerCase(),s=e.duration||1e3,r=e.direction||"N";switch(o){case"fade":case"fadein":return t?this.fadeIn(p,s):null;case"fadeout":return t?null:this.fadeOut(p,s);case"fly":case"flyin":return t?this.flyIn(p,s,r,i,n):null;case"flyout":return t?null:this.flyOut(p,s,r,i,n);default:return null}}};class Ue{constructor(e,t,i={}){this.config=e,this.container=t,this.options=i,this.log=q("RendererLite",i.logLevel),this.emitter=Ne(),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.widgetTimers=new Map,this.mediaUrlCache=new Map,this.layoutBlobUrls=new Map,this.audioOverlays=new Map,this.scaleFactor=1,this.offsetX=0,this.offsetY=0,this.overlayContainer=null,this.activeOverlays=new Map,this._keydownHandler=null,this._keyboardActions=[],this._subPlaylistCycleIndex=new Map,this.layoutPool=new Y(2),this.preloadTimer=null,this._preloadRetryTimer=null,this.setupContainer(),this.log.info("Initialized")}setupContainer(){if(this.container.style.position="relative",this.container.style.width="100%",this.container.style.height="100vh",this.container.style.overflow="hidden",typeof ResizeObserver<"u"){let e=null;this.resizeObserver=new ResizeObserver(()=>{e&&clearTimeout(e),e=setTimeout(()=>this.rescaleRegions(),150)}),this.resizeObserver.observe(this.container)}this.overlayContainer=document.createElement("div"),this.overlayContainer.id="overlay-container",this.overlayContainer.style.position="absolute",this.overlayContainer.style.top="0",this.overlayContainer.style.left="0",this.overlayContainer.style.width="100%",this.overlayContainer.style.height="100%",this.overlayContainer.style.zIndex="1000",this.overlayContainer.style.pointerEvents="none",this.container.appendChild(this.overlayContainer)}calculateScale(e){const t=this.container.clientWidth,i=this.container.clientHeight;if(!t||!i)return;const n=t/e.width,o=i/e.height;this.scaleFactor=Math.min(n,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,n]of t.regions)this.applyRegionScale(n.element,n.config),n.width=n.config.width*this.scaleFactor,n.height=n.config.height*this.scaleFactor}}}on(e,t){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({id:i.getAttribute("id")||"",actionType:i.getAttribute("actionType")||"",triggerType:i.getAttribute("triggerType")||"",triggerCode:i.getAttribute("triggerCode")||"",source:i.getAttribute("source")||"",sourceId:i.getAttribute("sourceId")||"",target:i.getAttribute("target")||"",targetId:i.getAttribute("targetId")||"",widgetId:i.getAttribute("widgetId")||"",layoutCode:i.getAttribute("layoutCode")||"",commandCode:i.getAttribute("commandCode")||""});return t}parseXlf(e){const n=new DOMParser().parseFromString(e,"text/xml").querySelector("layout");if(!n)throw new Error("Invalid XLF: no <layout> element");const o=n.getAttribute("duration"),s={schemaVersion:parseInt(n.getAttribute("schemaVersion")||"1"),width:parseInt(n.getAttribute("width")||"1920"),height:parseInt(n.getAttribute("height")||"1080"),duration:o?parseInt(o):0,bgcolor:n.getAttribute("backgroundColor")||n.getAttribute("bgcolor")||"#000000",background:n.getAttribute("background")||null,enableStat:n.getAttribute("enableStat")!=="0",actions:this.parseActions(n),regions:[]};s.schemaVersion>1&&this.log.debug(`XLF schema version: ${s.schemaVersion}`),o?this.log.info(`Layout duration from XLF: ${s.duration}s`):this.log.info("Layout duration NOT in XLF, will calculate from widgets");const r=n.querySelectorAll(":scope > region, :scope > drawer");for(const a of r){const l=a.tagName==="drawer",c={id:a.getAttribute("id"),width:parseInt(a.getAttribute("width")||"0"),height:parseInt(a.getAttribute("height")||"0"),top:parseInt(a.getAttribute("top")||"0"),left:parseInt(a.getAttribute("left")||"0"),zindex:parseInt(a.getAttribute("zindex")||(l?"2000":"0")),enableStat:a.getAttribute("enableStat")!=="0",actions:this.parseActions(a),exitTransition:null,transitionType:null,transitionDuration:null,transitionDirection:null,loop:!0,isDrawer:l,widgets:[]},d=Array.from(a.children).find(h=>h.tagName==="options");if(d){const h=d.querySelector("exitTransType");if(h&&h.textContent){const m=d.querySelector("exitTransDuration"),w=d.querySelector("exitTransDirection");c.exitTransition={type:h.textContent,duration:parseInt(m&&m.textContent||"1000"),direction:w&&w.textContent||"N"}}const g=d.querySelector("loop");g&&(c.loop=g.textContent!=="0");const f=d.querySelector("transitionType");if(f&&f.textContent){c.transitionType=f.textContent;const m=d.querySelector("transitionDuration"),w=d.querySelector("transitionDirection");c.transitionDuration=parseInt(m&&m.textContent||"1000"),c.transitionDirection=w&&w.textContent||"N"}}for(const h of a.children){if(h.tagName!=="media")continue;const g=this.parseWidget(h);c.widgets.push(g)}s.regions.push(c),l&&this.log.info(`Parsed drawer: id=${c.id} with ${c.widgets.length} widgets`)}if(s.duration===0){let a=0;for(const l of s.regions){if(l.isDrawer)continue;let c=0;for(const d of l.widgets)if(d.duration>0)c+=d.duration;else{c=60;break}a=Math.max(a,c)}s.duration=a>0?a: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"),n=parseInt(e.getAttribute("useDuration")||"1"),o=e.getAttribute("id"),s=e.getAttribute("fileId"),r={},a=e.querySelector("options");if(a)for(const b of a.children)r[b.tagName]=b.textContent;const l=e.querySelector("raw"),c=l?l.textContent:"",d={in:null,out:null};r.transIn&&(d.in={type:r.transIn,duration:parseInt(r.transInDuration||"1000"),direction:r.transInDirection||"N"}),r.transOut&&(d.out={type:r.transOut,duration:parseInt(r.transOutDuration||"1000"),direction:r.transOutDirection||"N"});const h=this.parseActions(e),g=[];for(const b of e.children)if(b.tagName.toLowerCase()==="audio"){const I=b.querySelector("uri");I?g.push({mediaId:I.getAttribute("mediaId")||null,uri:I.textContent||"",volume:parseInt(I.getAttribute("volume")||"100"),loop:I.getAttribute("loop")==="1"}):g.push({mediaId:b.getAttribute("mediaId")||null,uri:b.getAttribute("uri")||"",volume:parseInt(b.getAttribute("volume")||"100"),loop:b.getAttribute("loop")==="1"})}const f=[],m=Array.from(e.children).find(b=>b.tagName==="commands");if(m)for(const b of m.children)b.tagName==="command"&&f.push({commandCode:b.getAttribute("commandCode")||"",commandString:b.getAttribute("commandString")||""});const w=e.getAttribute("parentWidgetId")||null,L=parseInt(e.getAttribute("displayOrder")||"0"),C=e.getAttribute("cyclePlayback")==="1",v=parseInt(e.getAttribute("playCount")||"0"),_=e.getAttribute("isRandom")==="1",k=e.getAttribute("fromDt")||e.getAttribute("fromdt")||null,x=e.getAttribute("toDt")||e.getAttribute("todt")||null,S=e.getAttribute("render")||null;return{type:t,duration:i,useDuration:n,id:o,fileId:s,render:S,fromDt:k,toDt:x,enableStat:e.getAttribute("enableStat")!=="0",webhookUrl:r.webhookUrl||null,options:r,raw:c,transitions:d,actions:h,audioNodes:g,commands:f,parentWidgetId:w,displayOrder:L,cyclePlayback:C,playCount:v,isRandom:_}}trackBlobUrl(e){const t=this.currentLayoutId||0;t||this.log.warn("trackBlobUrl called without currentLayoutId, tracking under key 0"),this.layoutBlobUrls.has(t)||this.layoutBlobUrls.set(t,new Set),this.layoutBlobUrls.get(t).add(e)}revokeBlobUrlsForLayout(e){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){if(t.isDrawer)continue;let i=0;for(const n of t.widgets)n.duration>0&&(i+=n.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=Date.now()-(this._layoutTimerStartedAt||Date.now()),n=Math.max(1e3,this.currentLayout.duration*1e3-i);this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},n),this.log.info(`Layout timer adjusted to ${(n/1e3).toFixed(1)}s remaining (elapsed ${(i/1e3).toFixed(1)}s of ${this.currentLayout.duration}s)`)}else this.log.info(`Layout duration updated to ${e}s (timer not yet started, will use new value)`);this._scheduleNextLayoutPreload(this.currentLayout)}}attachActionListeners(e){var n;const t=[];let i=0;for(const o of e.actions||[])o.triggerType==="touch"?(this.attachTouchAction(this.container,o,null,null),i++):(n=o.triggerType)!=null&&n.startsWith("keyboard:")&&t.push(o);for(const o of e.regions){const s=this.regions.get(o.id);if(s){for(const r of o.actions||[])r.triggerType==="touch"?(this.attachTouchAction(s.element,r,o.id,null),i++):r.triggerType.startsWith("keyboard:")&&t.push(r);for(const r of o.widgets){if(!r.actions||r.actions.length===0)continue;const a=s.widgetElements.get(r.id);if(a)for(const l of r.actions)l.triggerType==="touch"?(this.attachTouchAction(a,l,o.id,r.id),i++):l.triggerType.startsWith("keyboard:")&&t.push(l)}}}this.setupKeyboardListener(t),(i>0||t.length>0)&&this.log.info(`Actions attached: ${i} touch, ${t.length} keyboard`)}attachTouchAction(e,t,i,n){e.style.cursor="pointer";const o=s=>{s.stopPropagation();const r=n?`widget ${n}`:`region ${i}`;this.log.info(`Touch action fired on ${r}: ${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:n}})};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 n of this._keyboardActions){const o=n.triggerType.substring(9);if(i===o){this.log.info(`Keyboard action (key: ${i}): ${n.actionType}`),this.emit("action-trigger",{actionType:n.actionType,triggerType:n.triggerType,triggerCode:n.triggerCode,layoutCode:n.layoutCode,targetId:n.targetId,commandCode:n.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 n=i.widgets.findIndex(o=>o.id===e);if(n!==-1){if(this.log.info(`Navigating to widget ${e} in region ${t} (index ${n})`),i.isDrawer&&i.element.style.display==="none"&&(i.element.style.display="",this.log.info(`Drawer region ${t} revealed`)),i.timer&&(clearTimeout(i.timer),i.timer=null),this.stopWidget(t,i.currentIndex),i.currentIndex=n,this.renderWidget(t,n),i.widgets.length>1){const s=i.widgets[n].duration*1e3;i.timer=setTimeout(()=>{this.stopWidget(t,n);const r=(n+1)%i.widgets.length;i.currentIndex=r,i.isDrawer&&r===0?(i.element.style.display="none",this.log.info(`Drawer region ${t} hidden (cycle complete)`)):this.startRegion(t)},s)}else if(i.isDrawer){const s=i.widgets[n].duration*1e3;i.timer=setTimeout(()=>{this.stopWidget(t,n),i.element.style.display="none",this.log.info(`Drawer region ${t} hidden (single widget done)`)},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,n=t.widgets[i];this.log.info(`nextWidget → index ${i} (widget ${n.id})`),this.navigateToWidget(n.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,n=t.widgets[i];this.log.info(`previousWidget → index ${i} (widget ${n.id})`),this.navigateToWidget(n.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)s.isDrawer||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 n=this.parseXlf(e);if(this.currentLayout=n,this.currentLayoutId=t,this.calculateScale(n),this.container.style.backgroundColor=n.bgcolor,this.container.style.backgroundImage="",n.background&&this.options.getMediaUrl)try{const o=await this.options.getMediaUrl(parseInt(n.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: ${n.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 n.regions)for(const r of s.widgets)if(r.fileId){const a=parseInt(r.fileId||r.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 n.regions)await this.createRegion(o);this.log.info("Pre-creating widget elements for instant transitions...");for(const[o,s]of this.regions)for(let r=0;r<s.widgets.length;r++){const a=s.widgets[r];a.layoutId=this.currentLayoutId,a.regionId=o;try{const l=await this.createWidgetElement(a,s);l.style.position="absolute",l.style.top="0",l.style.left="0",l.style.width="100%",l.style.height="100%",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(n),this.emit("layoutStart",t,n);for(const[o,s]of this.regions)s.isDrawer||this.startRegion(o);this.startLayoutTimerWhenReady(t,n),this._scheduleNextLayoutPreload(n),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",e.isDrawer&&(t.style.display="none"),this.applyRegionScale(t,e),this.container.appendChild(t);let i=e.widgets.filter(o=>this._isWidgetActive(o));i.some(o=>o.cyclePlayback)&&(i=this._applyCyclePlayback(i));const n=this.scaleFactor;this.regions.set(e.id,{element:t,config:e,widgets:i,currentIndex:0,timer:null,width:e.width*n,height:e.height*n,complete:!1,isDrawer:e.isDrawer||!1,widgetElements:new Map})}startRegion(e){const t=this.regions.get(e);this._startRegionCycle(t,e,(i,n)=>this.renderWidget(i,n),(i,n)=>this.stopWidget(i,n),()=>{this.log.info(`Region ${e} completed one full cycle`),this.checkLayoutComplete()})}async createWidgetElement(e,t){if(e.render==="html"&&e.type!=="pdf")return await this.renderGenericWidget(e,t);switch(e.type){case"image":return await this.renderImage(e,t);case"video":return await this.renderVideo(e,t);case"audio":return await this.renderAudio(e,t);case"text":case"ticker":return await this.renderTextWidget(e,t);case"pdf":return await this.renderPdf(e,t);case"webpage":return await this.renderWebpage(e,t);case"localvideo":return await this.renderVideo(e,t);case"videoin":return await this.renderVideoIn(e,t);case"powerpoint":case"flash":return this.log.warn(`Widget type '${e.type}' is not supported on web players (widget ${e.id})`),this._renderUnsupportedPlaceholder(e,t);default:return await this.renderGenericWidget(e,t)}}findMediaElement(e,t){return e.tagName===t?e:e.querySelector(t.toLowerCase())}updateMediaElement(e,t){const i=this.findMediaElement(e,"VIDEO")||this.findMediaElement(e,"AUDIO");if(i){if(i.tagName==="VIDEO"&&i._mediaConstraints&&!i._mediaStream){navigator.mediaDevices.getUserMedia(i._mediaConstraints).then(n=>{i.srcObject=n,i._mediaStream=n,this.log.info(`Webcam stream re-acquired for widget ${t.id}`)}).catch(n=>{this.log.warn("Failed to re-acquire webcam stream:",n.message)});return}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 n=this.findMediaElement(e,"VIDEO");if(n)return!n.paused&&n.readyState>=3?Promise.resolve():new Promise(r=>{const a=setTimeout(()=>{this.log.warn(`Video ready timeout (10000ms) for widget ${t.id}`),r()},1e4),l=()=>{n.removeEventListener("playing",l),clearTimeout(a),this.log.info(`Video widget ${t.id} ready (playing)`),r()};n.addEventListener("playing",l)});const o=this.findMediaElement(e,"AUDIO");if(o)return!o.paused&&o.readyState>=3?Promise.resolve():new Promise(r=>{const a=setTimeout(()=>{this.log.warn(`Audio ready timeout (10000ms) for widget ${t.id}`),r()},1e4),l=()=>{o.removeEventListener("playing",l),clearTimeout(a),this.log.info(`Audio widget ${t.id} ready (playing)`),r()};o.addEventListener("playing",l)});const s=this.findMediaElement(e,"IMG");return s?s.complete&&s.naturalWidth>0?Promise.resolve():new Promise(r=>{const a=()=>{s.removeEventListener("load",a),clearTimeout(l),r()},l=setTimeout(()=>{s.removeEventListener("load",a),this.log.warn(`Image ready timeout for widget ${t.id}`),r()},1e4);s.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 r=s.widgets[s.currentIndex||0],a=s.widgetElements.get(r.id);a&&i.push(this.waitForWidgetReady(a,r))}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 n=t.duration*1e3;this.log.info(`Layout ${e} will end after ${t.duration}s`),this._layoutTimerStartedAt=Date.now(),this._layoutTimerDurationMs=n,this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${e} duration expired (${t.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},n)}async _showWidget(e,t){var o,s;const i=e.widgets[t];if(!i)return null;let n=e.widgetElements.get(i.id);n||(this.log.warn(`Widget ${i.id} not pre-created, creating now`),n=await this.createWidgetElement(i,e),n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",n.style.height="100%",e.widgetElements.set(i.id,n),e.element.appendChild(n));for(const[r,a]of e.widgetElements)r!==i.id&&((o=a.getAnimations)==null||o.call(a).forEach(l=>l.cancel()),a.style.visibility="hidden",a.style.opacity="0");return this.updateMediaElement(n,i),(s=n.getAnimations)==null||s.call(n).forEach(r=>r.cancel()),n.style.visibility="visible",i.transitions.in?Z.apply(n,i.transitions.in,!0,e.width,e.height):n.style.opacity="1",this._startAudioOverlays(i),i}_startAudioOverlays(e){if(!e.audioNodes||e.audioNodes.length===0)return;this._stopAudioOverlays(e.id);const t=[];for(const i of e.audioNodes){if(!i.uri)continue;const n=document.createElement("audio");n.autoplay=!0,n.loop=i.loop,n.volume=Math.max(0,Math.min(1,i.volume/100));const o=parseInt(i.mediaId);let s=o?this.mediaUrlCache.get(o):null;!s&&o&&this.options.getMediaUrl?this.options.getMediaUrl(o).then(a=>{n.src=a}).catch(()=>{n.src=`${window.location.origin}${O}/media/${i.uri}`}):s?n.src=s:n.src=`${window.location.origin}${O}/media/${i.uri}`,n.style.display="none",this.container.appendChild(n);const r=n.play();r&&r.catch&&r.catch(()=>{}),t.push(n),this.log.info(`Audio overlay started for widget ${e.id}: ${i.uri} (loop=${i.loop}, vol=${i.volume})`)}t.length>0&&this.audioOverlays.set(e.id,t)}_stopAudioOverlays(e){const t=this.audioOverlays.get(e);if(t){for(const i of t)i.pause(),i.removeAttribute("src"),i.load(),i.parentNode&&i.parentNode.removeChild(i);this.audioOverlays.delete(e),this.log.info(`Audio overlays stopped for widget ${e}`)}}_hideWidget(e,t){const i=e.widgets[t];if(!i)return{widget:null,animPromise:null};const n=e.widgetElements.get(i.id);if(!n)return{widget:null,animPromise:null};let o=null;if(i.transitions.out){const a=Z.apply(n,i.transitions.out,!1,e.width,e.height);a&&(o=new Promise(l=>{a.onfinish=l}))}const s=n.querySelector("video");if(s&&i.options.loop!=="1"&&s.pause(),s!=null&&s._mediaStream&&(s._mediaStream.getTracks().forEach(a=>a.stop()),s._mediaStream=null,s.srcObject=null),s!=null&&s._hlsInstance&&(s._hlsInstance.destroy(),s._hlsInstance=null),s!=null&&s._eventCleanup){for(const[a,l]of s._eventCleanup)s.removeEventListener(a,l);s._eventCleanup=null}const r=n.querySelector("audio");if(r&&i.options.loop!=="1"&&r.pause(),r!=null&&r._eventCleanup){for(const[a,l]of r._eventCleanup)r.removeEventListener(a,l);r._eventCleanup=null}return this._stopAudioOverlays(i.id),n._pdfCleanup&&n._pdfCleanup(),{widget:i,animPromise:o}}_isWidgetActive(e){const t=new Date;if(e.fromDt){const i=new Date(e.fromDt);if(t<i)return!1}if(e.toDt){const i=new Date(e.toDt);if(t>i)return!1}return!0}_parseDurationComments(e,t){const i=e.match(/<!--\s*DURATION=(\d+)\s*-->/);if(i){const o=parseInt(i[1],10);if(o>0){this.log.info(`Widget ${t.id}: DURATION comment overrides duration ${t.duration}→${o}s`),t.duration=o;return}}const n=e.match(/<!--\s*NUMITEMS=(\d+)\s*-->/);if(n){const o=parseInt(n[1],10);if(o>0&&t.duration>0){const s=o*t.duration;this.log.info(`Widget ${t.id}: NUMITEMS=${o} × ${t.duration}s = ${s}s`),t.duration=s}}}_applyCyclePlayback(e){this._subPlaylistCycleIndex||(this._subPlaylistCycleIndex=new Map);const t=new Map,i=[];for(const n of e)n.parentWidgetId&&n.cyclePlayback?(t.has(n.parentWidgetId)||t.set(n.parentWidgetId,[]),t.get(n.parentWidgetId).push(n)):i.push({type:"direct",widget:n});for(const[n,o]of t){o.sort((r,a)=>r.displayOrder-a.displayOrder);let s;if(o.some(r=>r.isRandom)){const r=Math.floor(Math.random()*o.length);s=o[r]}else{const r=this._subPlaylistCycleIndex.get(n)||0;s=o[r%o.length],this._subPlaylistCycleIndex.set(n,r+1)}this.log.info(`Sub-playlist cycle: group ${n} selected widget ${s.id} (${o.length} in group)`),i.push({type:"direct",widget:s})}return i.map(n=>n.widget)}_startRegionCycle(e,t,i,n,o){if(!e||e.widgets.length===0)return;if(e.widgets.length===1){i(t,0);return}const s=()=>{const r=e.currentIndex,a=e.widgets[r];i(t,r);const l=a.duration*1e3;e.timer=setTimeout(()=>{this._handleWidgetCycleEnd(a,e,t,r,i,n,o,s)},l)};s()}_handleWidgetCycleEnd(e,t,i,n,o,s,r,a){var c;e.webhookUrl&&this.emit("widgetAction",{type:"durationEnd",widgetId:e.id,layoutId:this.currentLayoutId,regionId:i,url:e.webhookUrl}),s(i,n);const l=(t.currentIndex+1)%t.widgets.length;if(l===0&&!t.complete&&(t.complete=!0,r==null||r()),l===0&&((c=t.config)==null?void 0:c.loop)===!1){o(i,t.widgets.length-1);return}this.layoutEndEmitted||(t.currentIndex=l,a())}async renderWidget(e,t){var n;const i=this.regions.get(e);if(i)try{const o=await this._showWidget(i,t);if(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,enableStat:o.enableStat}),o.commands&&o.commands.length>0))for(const s of o.commands)this.emit("widgetCommand",{commandCode:s.commandCode,commandString:s.commandString,widgetId:o.id,regionId:e,layoutId:this.currentLayoutId})}catch(o){this.log.error("Error rendering widget:",o),this.emit("error",{type:"widgetError",error:o,widgetId:(n=i.widgets[t])==null?void 0:n.id,regionId:e})}}async stopWidget(e,t){const i=this.regions.get(e);if(!i)return;const{widget:n,animPromise:o}=this._hideWidget(i,t);o&&await o,n&&this.emit("widgetEnd",{widgetId:n.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(n.fileId||n.id)||null,type:n.type,enableStat:n.enableStat})}async renderImage(e,t){const i=document.createElement("img");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%";const n=e.options.scaleType,o={stretch:"fill",center:"contain",fit:"cover"};i.style.objectFit=o[n]||"contain";const s={left:"left",center:"center",right:"right"},r={top:"top",middle:"center",bottom:"bottom"},a=s[e.options.alignId]||"center",l=r[e.options.valignId]||"center";i.style.objectPosition=`${a} ${l}`,i.style.opacity="0";const c=parseInt(e.fileId||e.id);let d=this.mediaUrlCache.get(c);return!d&&this.options.getMediaUrl?d=await this.options.getMediaUrl(c):d||(d=`${window.location.origin}${O}/media/${e.options.uri}`),i.src=d,i}async renderVideo(e,t){const i=document.createElement("video");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%";const n=e.options.scaleType,o={stretch:"fill",center:"none",fit:"contain"};i.style.objectFit=o[n]||"contain",i.style.opacity="1",i.autoplay=!0,i.preload="auto",i.muted=e.options.mute==="1",i.loop=!1,i.controls=!1,i.playsInline=!0;const s=parseInt(e.fileId||e.id),r=()=>{e.options.loop==="1"?(i.currentTime=0,this.log.info(`Video ${s} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Video ${s} ended - paused on last frame`)};i.addEventListener("ended",r);let a=this.mediaUrlCache.get(s);if(!a&&this.options.getMediaUrl?a=await this.options.getMediaUrl(s):a||(a=`${window.location.origin}${O}/media/${s}`),a.includes(".m3u8"))if(i.canPlayType("application/vnd.apple.mpegurl"))this.log.info(`HLS stream (native): ${s}`),i.src=a;else try{const{default:m}=await P(async()=>{const{default:w}=await import("hls.js");return{default:w}},[],import.meta.url);if(m.isSupported()){const w=new m({enableWorker:!0,lowLatencyMode:!0});w.loadSource(a),w.attachMedia(i),i._hlsInstance=w,w.on(m.Events.ERROR,(L,C)=>{C.fatal&&(this.log.error(`HLS fatal error: ${C.type}`,C.details),w.destroy(),i._hlsInstance=null)}),this.log.info(`HLS stream (hls.js): ${s}`)}else this.log.warn(`HLS not supported on this browser for ${s}`),i.src=a}catch(m){this.log.warn(`hls.js not available, falling back to native: ${m.message}`),i.src=a}else i.src=a;const c=this.currentLayoutId,d=()=>{const m=Math.floor(i.duration);this.log.info(`Video ${s} duration detected: ${m}s`),(e.duration===0||e.useDuration===0)&&(e.duration=m,this.log.info(`Updated widget ${e.id} duration to ${m}s (useDuration=0)`),this.currentLayoutId===c?this.updateLayoutDuration():this.log.info(`Video ${s} duration set but layout timer not updated (preloaded for layout ${c}, current is ${this.currentLayoutId})`))};i.addEventListener("loadedmetadata",d);const h=()=>{this.log.info("Video loaded and ready:",s)};i.addEventListener("loadeddata",h);const g=()=>{const m=i.error,w=m==null?void 0:m.code,L=(m==null?void 0:m.message)||"Unknown error";this.log.warn(`Video error: ${s}, code: ${w}, time: ${i.currentTime.toFixed(1)}s, message: ${L}`),this.emit("videoError",{fileId:s,errorCode:w,errorMessage:L,currentTime:i.currentTime})};i.addEventListener("error",g);const f=()=>{this.log.info("Video playing:",s)};return i.addEventListener("playing",f),i._eventCleanup=[["ended",r],["loadedmetadata",d],["loadeddata",h],["error",g],["playing",f]],this.log.info("Video element created:",s,i.src),i}async renderVideoIn(e,t){const i=document.createElement("video");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.objectFit=e.options.showFullScreen==="1"?"cover":"contain",i.autoplay=!0,i.playsInline=!0,i.controls=!1,i.muted=e.options.mute!=="0",e.options.mirror==="1"&&(i.style.transform="scaleX(-1)");const n={width:{ideal:t.width},height:{ideal:t.height}},o=e.options.sourceId||e.options.deviceId;o?n.deviceId={exact:o}:n.facingMode=e.options.facingMode||"environment";const s={video:n,audio:e.options.captureAudio==="1"};i._mediaConstraints=s;try{const r=await navigator.mediaDevices.getUserMedia(s);i.srcObject=r,i._mediaStream=r,this.log.info(`Webcam stream acquired for widget ${e.id} (tracks: ${r.getTracks().length})`)}catch(r){return this.log.warn(`getUserMedia failed for widget ${e.id}: ${r.message}`),this._renderUnsupportedPlaceholder({...e,type:"Camera unavailable"},t)}return 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 n=document.createElement("audio");n.autoplay=!0,n.loop=e.options.loop==="1",n.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}${O}/media/${o}`),n.src=s;const r=()=>{e.options.loop==="1"?(n.currentTime=0,this.log.info(`Audio ${o} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Audio ${o} ended - playback complete`)};n.addEventListener("ended",r);const a=this.currentLayoutId,l=()=>{const f=Math.floor(n.duration);this.log.info(`Audio ${o} duration detected: ${f}s`),(e.duration===0||e.useDuration===0)&&(e.duration=f,this.log.info(`Updated widget ${e.id} duration to ${f}s (useDuration=0)`),this.currentLayoutId===a?this.updateLayoutDuration():this.log.info(`Audio ${o} duration set but layout timer not updated (preloaded for layout ${a}, current is ${this.currentLayoutId})`))};n.addEventListener("loadedmetadata",l);const c=()=>{const f=n.error;this.log.warn(`Audio error (non-fatal): ${o}, code: ${f==null?void 0:f.code}, message: ${(f==null?void 0:f.message)||"Unknown"}`)};n.addEventListener("error",c),n._eventCleanup=[["ended",r],["loadedmetadata",l],["error",c]];const d=document.createElement("div");d.innerHTML="♪",d.style.fontSize="120px",d.style.color="white",d.style.marginBottom="20px";const h=document.createElement("div");h.style.color="white",h.style.fontSize="24px",h.textContent="Playing Audio";const g=document.createElement("div");return g.style.color="rgba(255,255,255,0.7)",g.style.fontSize="16px",g.style.marginTop="10px",g.textContent=e.options.uri,i.appendChild(n),i.appendChild(d),i.appendChild(h),i.appendChild(g),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 n=e.raw;if(this.options.getWidgetHtml){const r=await this.options.getWidgetHtml(e);if(r&&typeof r=="object"&&r.url){if(i.src=r.url,r.fallback){const a=this;i.addEventListener("load",function(){var l;try{if(!((l=i.contentDocument)!=null&&l.querySelector("base"))){a.log.warn("Cache URL failed (hard reload?), using original CMS URLs");const c=new Blob([r.fallback],{type:"text/html"}),d=URL.createObjectURL(c);a.trackBlobUrl(d),i.src=d}}catch{}},{once:!0})}return r.fallback&&this._parseDurationComments(r.fallback,e),i}n=r}n&&this._parseDurationComments(n,e);const o=new Blob([n],{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 P(()=>import("./pdf-BnPRJEQ6.js"),[],import.meta.url);window.pdfjsLib=s;const r=window.location.pathname.replace(/\/[^/]*$/,"/");window.pdfjsLib.GlobalWorkerOptions.workerSrc=`${window.location.origin}${r}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 n=parseInt(e.fileId||e.id);let o=this.mediaUrlCache.get(n);!o&&this.options.getMediaUrl?o=await this.options.getMediaUrl(n):o||(o=`${window.location.origin}${O}/media/${e.options.uri}`);try{const r=await window.pdfjsLib.getDocument(o).promise,a=r.numPages,l=e.duration||60,c=l*1e3/a;this.log.info(`[pdf] PDF loaded: ${a} pages, ${l}s duration, ${(c/1e3).toFixed(1)}s/page`);const d=await r.getPage(1),h=d.getViewport({scale:1}),g=Math.min(t.width/h.width,t.height/h.height);d.cleanup();const f=document.createElement("canvas");f.className="pdf-page",f.width=Math.floor(h.width*g),f.height=Math.floor(h.height*g),f.style.cssText="display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);";const m=f.getContext("2d");i.appendChild(f);const w=document.createElement("div");w.style.cssText="position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,0.7);color:white;padding:8px 12px;border-radius:4px;font:14px system-ui;z-index:1;",i.appendChild(w);let L=1,C=null,v=null,_=!1;const k=async()=>{if(_)return;w.textContent=`Page ${L} / ${a}`;const x=await r.getPage(L),S=x.getViewport({scale:g});m.clearRect(0,0,f.width,f.height),v=x.render({canvasContext:m,viewport:S});try{await v.promise}catch(b){if(_)return;throw b}v=null,x.cleanup(),a>1&&!_&&(C=setTimeout(()=>{L=L>=a?1:L+1,k()},c))};await k(),i._pdfCleanup=()=>{_=!0,C&&clearTimeout(C),C=null,v&&(v.cancel(),v=null),f.width=0,f.height=0,r.destroy()}}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){if(parseInt(e.options.modeId||"1")===0)return await this.renderGenericWidget(e,t);const n=document.createElement("iframe");n.className="renderer-lite-widget",n.style.width="100%",n.style.height="100%",n.style.border="none",n.style.opacity="0";const o=decodeURIComponent(e.options.uri||"");return n.src=o,n}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 n=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 r;try{if(!((r=i.contentDocument)!=null&&r.querySelector("base"))){s.log.warn("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 o.fallback&&this._parseDurationComments(o.fallback,e),i}n=o}if(n){this._parseDurationComments(n,e);const o=new Blob([n],{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}_renderUnsupportedPlaceholder(e,t){const i=document.createElement("div");return i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.display="flex",i.style.alignItems="center",i.style.justifyContent="center",i.style.backgroundColor="#111",i.style.color="#666",i.style.fontSize="14px",i.textContent=`Unsupported: ${e.type}`,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,n=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")},n)}hasPreloadedLayout(e){return this.layoutPool.has(e)}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 n=document.createElement("div");if(n.id=`preload_layout_${t}`,n.className="renderer-lite-preload-wrapper",n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",n.style.height="100%",n.style.visibility="hidden",n.style.zIndex="-1",n.style.backgroundColor=i.bgcolor,i.background&&this.options.getMediaUrl)try{const g=await this.options.getMediaUrl(parseInt(i.background));g&&(n.style.backgroundImage=`url(${g})`,n.style.backgroundSize="cover",n.style.backgroundPosition="center",n.style.backgroundRepeat="no-repeat")}catch(g){this.log.warn("Preload: Failed to load background image:",g)}const o=new Map;if(this.options.getMediaUrl){const g=[];for(const f of i.regions)for(const m of f.widgets)if(m.fileId){const w=parseInt(m.fileId||m.id);o.has(w)||g.push(this.options.getMediaUrl(w).then(L=>{o.set(w,L)}).catch(L=>{this.log.warn(`Preload: Failed to fetch media ${w}:`,L)}))}g.length>0&&(this.log.info(`Preload: fetching ${g.length} media URLs...`),await Promise.all(g))}const s=this.mediaUrlCache,r=this.currentLayoutId;this.mediaUrlCache=o;const a=new Map,l=this.scaleFactor;for(const g of i.regions){const f=document.createElement("div");f.id=`preload_region_${t}_${g.id}`,f.className="renderer-lite-region",f.style.position="absolute",f.style.zIndex=g.zindex,f.style.overflow="hidden",this.applyRegionScale(f,g),n.appendChild(f);const m={element:f,config:g,widgets:g.widgets,currentIndex:0,timer:null,width:g.width*l,height:g.height*l,complete:!1,widgetElements:new Map};a.set(g.id,m)}const c=new Set,d=this.layoutBlobUrls;this.layoutBlobUrls=new Map,this.layoutBlobUrls.set(t,c),this.currentLayoutId=t;for(const[g,f]of a)for(let m=0;m<f.widgets.length;m++){const w=f.widgets[m];w.layoutId=t,w.regionId=g;try{const L=await this.createWidgetElement(w,f);L.style.position="absolute",L.style.top="0",L.style.left="0",L.style.width="100%",L.style.height="100%",L.style.visibility="hidden",L.style.opacity="0",f.element.appendChild(L),f.widgetElements.set(w.id,L)}catch(L){this.log.error(`Preload: Failed to create widget ${w.id}:`,L)}}return this.mediaUrlCache=s,this.currentLayoutId=r,n.querySelectorAll("video").forEach(g=>g.pause()),(this.layoutBlobUrls.get(t)||new Set).forEach(g=>c.add(g)),this.layoutBlobUrls=d,this.container.appendChild(n),this.layoutPool.add(t,{container:n,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[n,o]of this.regions)if(o.timer&&(clearTimeout(o.timer),o.timer=null),Y.releaseMediaElements(o.element),o.config&&o.config.exitTransition){const s=Z.apply(o.element,o.config.exitTransition,!1,o.width,o.height);if(s){const r=o.element;s.onfinish=()=>r.remove()}else o.element.remove()}else o.element.remove();i&&this.revokeBlobUrlsForLayout(i);for(const[n,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[n,o]of this.regions)o.currentIndex=0,o.complete=!1,this.startRegion(n);this.updateLayoutDuration(),this.startLayoutTimerWhenReady(e,t.layout),this.preloadTimer||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)if(t.timer&&(clearTimeout(t.timer),t.timer=null),t.widgets.length>0&&this.stopWidget(e,t.currentIndex),Y.releaseMediaElements(t.element),t.config&&t.config.exitTransition){const i=Z.apply(t.element,t.config.exitTransition,!1,t.width,t.height);if(i){const n=t.element;i.onfinish=()=>n.remove()}else t.element.remove()}else 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 n=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=n.bgcolor,this.options.getMediaUrl){const a=[];for(const l of n.regions)for(const c of l.widgets)if(c.fileId){const d=parseInt(c.fileId||c.id);this.mediaUrlCache.has(d)||a.push(this.options.getMediaUrl(d).then(h=>{this.mediaUrlCache.set(d,h)}).catch(h=>{this.log.warn(`Failed to fetch overlay media ${d}:`,h)}))}a.length>0&&(this.log.info(`Pre-fetching ${a.length} overlay media URLs...`),await Promise.all(a))}this.calculateScale(n);const s=new Map,r=this.scaleFactor;for(const a of n.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*r,height:a.height*r,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 d=await this.createWidgetElement(c,l);d.style.visibility="hidden",d.style.opacity="0",l.element.appendChild(d),l.widgetElements.set(c.id,d)}catch(d){this.log.error(`Failed to pre-create overlay widget ${c.id}:`,d)}}this.overlayContainer.appendChild(o),this.activeOverlays.set(t,{container:o,layout:n,regions:s,timer:null,priority:i}),this.emit("overlayStart",t,n);for(const[a,l]of s)this.startOverlayRegion(t,a);if(n.duration>0){const a=n.duration*1e3,l=this.activeOverlays.get(t);l&&(l.timer=setTimeout(()=>{this.log.info(`Overlay ${t} duration expired (${n.duration}s)`),this.emit("overlayEnd",t)},a))}this.log.info(`Overlay ${t} started`)}catch(n){throw this.log.error("Error rendering overlay:",n),this.emit("error",{type:"overlayError",error:n,layoutId:t}),n}}startOverlayRegion(e,t){const i=this.activeOverlays.get(e);if(!i)return;const n=i.regions.get(t);this._startRegionCycle(n,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 n=this.activeOverlays.get(e);if(!n)return;const o=n.regions.get(t);if(o)try{const r=await this._showWidget(o,i);r&&(this.log.info(`Showing overlay widget ${r.type} (${r.id}) in overlay ${e} region ${t}`),this.emit("overlayWidgetStart",{overlayId:e,widgetId:r.id,regionId:t,type:r.type,duration:r.duration}))}catch(r){this.log.error("Error rendering overlay widget:",r),this.emit("error",{type:"overlayWidgetError",error:r,widgetId:(s=o.widgets[i])==null?void 0:s.id,regionId:t,overlayId:e})}}async stopOverlayWidget(e,t,i){const n=this.activeOverlays.get(e);if(!n)return;const o=n.regions.get(t);if(!o)return;const{widget:s,animPromise:r}=this._hideWidget(o,i);r&&await r,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,n]of t.regions)n.timer&&(clearTimeout(n.timer),n.timer=null),n.widgets.length>0&&this.stopOverlayWidget(e,i,n.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){this._paused=!0;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 (layout timer continues)")}}isPaused(){return this._paused}resume(){if(this._paused){this._paused=!1,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();for(const e of this.audioOverlays.keys())this._stopAudioOverlays(e);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 V=q("Layout");class ht{constructor(e){this.xmds=e}async translateXLF(e,t){const n=new DOMParser().parseFromString(t,"text/xml"),o=n.querySelector("layout");if(!o)throw new Error("Invalid XLF: no <layout> element");const s=parseInt(o.getAttribute("width")||"1920"),r=parseInt(o.getAttribute("height")||"1080"),a=o.getAttribute("bgcolor")||"#000000",l=[];for(const c of n.querySelectorAll("region"))l.push(await this.translateRegion(e,c));return this.generateHTML(s,r,a,l)}async translateRegion(e,t){const i=t.getAttribute("id"),n=parseInt(t.getAttribute("width")),o=parseInt(t.getAttribute("height")),s=parseInt(t.getAttribute("top")),r=parseInt(t.getAttribute("left")),a=parseInt(t.getAttribute("zindex")||"0"),l=[];for(const c of t.querySelectorAll("media"))l.push(await this.translateMedia(e,i,c));return{id:i,width:n,height:o,top:s,left:r,zindex:a,media:l}}async translateMedia(e,t,i){const n=i.getAttribute("type"),o=parseInt(i.getAttribute("duration")||"10"),s=i.getAttribute("id"),r=i.querySelector("options"),a=i.querySelector("raw"),l={};if(r)for(const v of r.children)l[v.tagName]=v.textContent;const c={in:null,out:null},d=i.querySelector("options > transIn"),h=i.querySelector("options > transOut"),g=i.querySelector("options > transInDuration"),f=i.querySelector("options > transOutDuration"),m=i.querySelector("options > transInDirection"),w=i.querySelector("options > transOutDirection");d&&d.textContent&&(c.in={type:d.textContent,duration:parseInt((g==null?void 0:g.textContent)||"1000"),direction:(m==null?void 0:m.textContent)||"N"}),h&&h.textContent&&(c.out={type:h.textContent,duration:parseInt((f==null?void 0:f.textContent)||"1000"),direction:(w==null?void 0:w.textContent)||"N"});let L=a?a.textContent:"";if(["clock","clock-digital","clock-analogue","calendar","weather","currencies","stocks","twitter","global","embedded","text","ticker"].some(v=>n.includes(v))){let v=3,_=null;for(let k=1;k<=v;k++)try{V.info(`Fetching resource for ${n} widget (layout=${e}, region=${t}, media=${s}) - attempt ${k}/${v}`),L=await this.xmds.getResource(e,t,s),V.info(`Got resource HTML (${L.length} chars)`);const x=await Ee(e,t,s,L);l.widgetCacheKey=x;break}catch(x){if(_=x,V.warn(`Failed to get resource (attempt ${k}/${v}):`,x.message),k<v){const S=k*2e3;V.info(`Retrying in ${S}ms...`),await new Promise(b=>setTimeout(b,S))}}if(!L&&_){V.warn("All retries failed, checking for cached widget HTML...");try{const k=await fetch(`/store${O}/widgets/${e}/${t}/${s}`);k.ok?(L=await k.text(),l.widgetCacheKey=`${O}/widgets/${e}/${t}/${s}`,V.info(`Using stored widget HTML (${L.length} chars) - CMS update pending`)):(V.error(`No stored version available for widget ${s}`),L='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>')}catch(k){V.error("Store fallback failed:",k),L='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>'}}}return{type:n,duration:o,id:s,options:l,raw:L,transitions:c}}generateHTML(e,t,i,n){const o=n.map(r=>this.generateRegionHTML(r)).join(`
|
|
1
|
+
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./index-B6MdC-Qx.js","./chunk-config-oTovBPVw.js","./index-leM889oV.js","./index-C3Orblel.js","./widget-html-BAV4ZZd8.js","./index-D81Qhc3r.js","./xmds-client-Dyy8ex-B.js","./index-Dj2ND9Mx.js","./modulepreload-polyfill-B5Qt9EMX.js","./index-BWvWWyDc.js","./index-BVIXBw9z.js","./index-CleHw0Tc.js","./index-D_aTOqNE.js","./index-Cq9aOTTR.js","./sync-manager-DEght5gK.js"])))=>i.map(i=>d[i]);
|
|
2
|
+
var xe=Object.defineProperty;var ke=(p,e,t)=>e in p?xe(p,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):p[e]=t;var $=(p,e,t)=>ke(p,typeof e!="symbol"?e+"":e,t);import"./modulepreload-polyfill-B5Qt9EMX.js";import{createLogger as q,PLAYER_API as O,EventEmitter as _e,fetchWithRetry as Te,applyCmsLogLevel as De,config as Me,registerLogSink as Ae}from"./index-leM889oV.js";import{c as Ee,C as Re,D as Pe,L as ne,B as de,S as Oe}from"./widget-html-BAV4ZZd8.js";const Fe="modulepreload",We=function(p,e){return new URL(p,e).href},ue={},P=function(e,t,i){let n=Promise.resolve();if(t&&t.length>0){const s=document.getElementsByTagName("link"),r=document.querySelector("meta[property=csp-nonce]"),a=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));n=Promise.allSettled(t.map(l=>{if(l=We(l,i),l in ue)return;ue[l]=!0;const c=l.endsWith(".css"),u=c?'[rel="stylesheet"]':"";if(!!i)for(let f=s.length-1;f>=0;f--){const m=s[f];if(m.href===l&&(!c||m.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${l}"]${u}`))return;const g=document.createElement("link");if(g.rel=c?"stylesheet":Fe,c||(g.as="script"),g.crossOrigin="",g.href=l,a&&g.setAttribute("nonce",a),document.head.appendChild(g),c)return new Promise((f,m)=>{g.addEventListener("load",f),g.addEventListener("error",()=>m(new Error(`Unable to preload CSS for ${l}`)))})}))}function o(s){const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=s,window.dispatchEvent(r),!r.defaultPrevented)throw s}return n.then(s=>{for(const r of s||[])r.status==="rejected"&&o(r.reason);return e().catch(o)})};let Ne=()=>({emit(p,...e){for(let t=this.events[p]||[],i=0,n=t.length;i<n;i++)t[i](...e)},events:{},on(p,e){var t;return((t=this.events)[p]||(t[p]=[])).push(e),()=>{var i;this.events[p]=(i=this.events[p])==null?void 0:i.filter(n=>e!==n)}}});const J=q("LayoutPool");class Y{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),J.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(J.info(`Evicting layout ${e} from pool`),t.regions)for(const[i,n]of t.regions)n.timer&&(clearTimeout(n.timer),n.timer=null);if(t.container&&Y.releaseMediaElements(t.container),t.blobUrls&&t.blobUrls.size>0&&(t.blobUrls.forEach(i=>{URL.revokeObjectURL(i)}),J.info(`Revoked ${t.blobUrls.size} blob URLs for layout ${e}`)),t.mediaUrlCache)for(const[i,n]of t.mediaUrlCache)n&&typeof n=="string"&&n.startsWith("blob:")&&URL.revokeObjectURL(n);t.container&&t.container.parentNode&&t.container.remove(),this.layouts.delete(e),this.hotLayoutId===e&&(this.hotLayoutId=null)}}static releaseMediaElements(e){let t=0,i=0;e.querySelectorAll("video").forEach(n=>{n._hlsInstance&&(n._hlsInstance.destroy(),n._hlsInstance=null,i++),n._mediaStream&&(n._mediaStream.getTracks().forEach(o=>o.stop()),n._mediaStream=null,n.srcObject=null),n.pause(),n.removeAttribute("src"),n.load(),t++}),e.querySelectorAll("audio").forEach(n=>{n.pause(),n.removeAttribute("src"),n.load()}),t>0&&J.info(`Released ${t} video(s)${i?` (${i} HLS)`:""}`)}evictLRU(){let e=null,t=1/0;for(const[i,n]of this.layouts)n.status==="warm"&&n.lastAccess<t&&(e=i,t=n.lastAccess);e!==null&&this.evict(e)}clearWarm(){let e=0;const t=[];for(const[i,n]of this.layouts)n.status==="warm"&&t.push(i);for(const i of t)this.evict(i),e++;return e>0&&J.info(`Cleared ${e} warm layout(s) from pool`),e}clearWarmNotIn(e){let t=0;const i=[];for(const[n,o]of this.layouts)o.status==="warm"&&!e.has(n)&&i.push(n);for(const n of i)this.evict(n),t++;return t>0&&J.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 Z={fadeIn(p,e){const t=[{opacity:0},{opacity:1}],i={duration:e,easing:"linear",fill:"forwards"};return p.animate(t,i)},fadeOut(p,e){const t=[{opacity:1},{opacity:0}],i={duration:e,easing:"linear",fill:"forwards"};return p.animate(t,i)},getFlyKeyframes(p,e,t,i){const n={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=n[p]||n.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(p,e,t,i,n){const o=this.getFlyKeyframes(t,i,n,!0),s={duration:e,easing:"ease-out",fill:"forwards"};return p.animate([o.from,o.to],s)},flyOut(p,e,t,i,n){const o=this.getFlyKeyframes(t,i,n,!1),s={duration:e,easing:"ease-in",fill:"forwards"};return p.animate([o.from,o.to],s)},apply(p,e,t,i,n){if(!e||!e.type)return null;const o=e.type.toLowerCase(),s=e.duration||1e3,r=e.direction||"N";switch(o){case"fade":case"fadein":return t?this.fadeIn(p,s):null;case"fadeout":return t?null:this.fadeOut(p,s);case"fly":case"flyin":return t?this.flyIn(p,s,r,i,n):null;case"flyout":return t?null:this.flyOut(p,s,r,i,n);default:return null}}};class Ue{constructor(e,t,i={}){this.config=e,this.container=t,this.options=i,this.log=q("RendererLite",i.logLevel),this.emitter=Ne(),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.widgetTimers=new Map,this.mediaUrlCache=new Map,this.layoutBlobUrls=new Map,this.audioOverlays=new Map,this.scaleFactor=1,this.offsetX=0,this.offsetY=0,this.overlayContainer=null,this.activeOverlays=new Map,this._keydownHandler=null,this._keyboardActions=[],this._subPlaylistCycleIndex=new Map,this.layoutPool=new Y(2),this.preloadTimer=null,this._preloadRetryTimer=null,this.setupContainer(),this.log.info("Initialized")}setupContainer(){if(this.container.style.position="relative",this.container.style.width="100%",this.container.style.height="100vh",this.container.style.overflow="hidden",typeof ResizeObserver<"u"){let e=null;this.resizeObserver=new ResizeObserver(()=>{e&&clearTimeout(e),e=setTimeout(()=>this.rescaleRegions(),150)}),this.resizeObserver.observe(this.container)}this.overlayContainer=document.createElement("div"),this.overlayContainer.id="overlay-container",this.overlayContainer.style.position="absolute",this.overlayContainer.style.top="0",this.overlayContainer.style.left="0",this.overlayContainer.style.width="100%",this.overlayContainer.style.height="100%",this.overlayContainer.style.zIndex="1000",this.overlayContainer.style.pointerEvents="none",this.container.appendChild(this.overlayContainer)}calculateScale(e){const t=this.container.clientWidth,i=this.container.clientHeight;if(!t||!i)return;const n=t/e.width,o=i/e.height;this.scaleFactor=Math.min(n,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,n]of t.regions)this.applyRegionScale(n.element,n.config),n.width=n.config.width*this.scaleFactor,n.height=n.config.height*this.scaleFactor}}}on(e,t){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({id:i.getAttribute("id")||"",actionType:i.getAttribute("actionType")||"",triggerType:i.getAttribute("triggerType")||"",triggerCode:i.getAttribute("triggerCode")||"",source:i.getAttribute("source")||"",sourceId:i.getAttribute("sourceId")||"",target:i.getAttribute("target")||"",targetId:i.getAttribute("targetId")||"",widgetId:i.getAttribute("widgetId")||"",layoutCode:i.getAttribute("layoutCode")||"",commandCode:i.getAttribute("commandCode")||""});return t}parseXlf(e){const n=new DOMParser().parseFromString(e,"text/xml").querySelector("layout");if(!n)throw new Error("Invalid XLF: no <layout> element");const o=n.getAttribute("duration"),s={schemaVersion:parseInt(n.getAttribute("schemaVersion")||"1"),width:parseInt(n.getAttribute("width")||"1920"),height:parseInt(n.getAttribute("height")||"1080"),duration:o?parseInt(o):0,bgcolor:n.getAttribute("backgroundColor")||n.getAttribute("bgcolor")||"#000000",background:n.getAttribute("background")||null,enableStat:n.getAttribute("enableStat")!=="0",actions:this.parseActions(n),regions:[]};s.schemaVersion>1&&this.log.debug(`XLF schema version: ${s.schemaVersion}`),o?this.log.info(`Layout duration from XLF: ${s.duration}s`):this.log.info("Layout duration NOT in XLF, will calculate from widgets");const r=n.querySelectorAll(":scope > region, :scope > drawer");for(const a of r){const l=a.tagName==="drawer",c={id:a.getAttribute("id"),width:parseInt(a.getAttribute("width")||"0"),height:parseInt(a.getAttribute("height")||"0"),top:parseInt(a.getAttribute("top")||"0"),left:parseInt(a.getAttribute("left")||"0"),zindex:parseInt(a.getAttribute("zindex")||(l?"2000":"0")),enableStat:a.getAttribute("enableStat")!=="0",actions:this.parseActions(a),exitTransition:null,transitionType:null,transitionDuration:null,transitionDirection:null,loop:!0,isDrawer:l,widgets:[]},u=Array.from(a.children).find(h=>h.tagName==="options");if(u){const h=u.querySelector("exitTransType");if(h&&h.textContent){const m=u.querySelector("exitTransDuration"),w=u.querySelector("exitTransDirection");c.exitTransition={type:h.textContent,duration:parseInt(m&&m.textContent||"1000"),direction:w&&w.textContent||"N"}}const g=u.querySelector("loop");g&&(c.loop=g.textContent!=="0");const f=u.querySelector("transitionType");if(f&&f.textContent){c.transitionType=f.textContent;const m=u.querySelector("transitionDuration"),w=u.querySelector("transitionDirection");c.transitionDuration=parseInt(m&&m.textContent||"1000"),c.transitionDirection=w&&w.textContent||"N"}}for(const h of a.children){if(h.tagName!=="media")continue;const g=this.parseWidget(h);c.widgets.push(g)}s.regions.push(c),l&&this.log.info(`Parsed drawer: id=${c.id} with ${c.widgets.length} widgets`)}if(s.duration===0){let a=0;for(const l of s.regions){if(l.isDrawer)continue;let c=0;for(const u of l.widgets)if(u.duration>0)c+=u.duration;else{c=60;break}a=Math.max(a,c)}s.duration=a>0?a: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"),n=parseInt(e.getAttribute("useDuration")||"1"),o=e.getAttribute("id"),s=e.getAttribute("fileId"),r={},a=e.querySelector("options");if(a)for(const b of a.children)r[b.tagName]=b.textContent;const l=e.querySelector("raw"),c=l?l.textContent:"",u={in:null,out:null};r.transIn&&(u.in={type:r.transIn,duration:parseInt(r.transInDuration||"1000"),direction:r.transInDirection||"N"}),r.transOut&&(u.out={type:r.transOut,duration:parseInt(r.transOutDuration||"1000"),direction:r.transOutDirection||"N"});const h=this.parseActions(e),g=[];for(const b of e.children)if(b.tagName.toLowerCase()==="audio"){const I=b.querySelector("uri");I?g.push({mediaId:I.getAttribute("mediaId")||null,uri:I.textContent||"",volume:parseInt(I.getAttribute("volume")||"100"),loop:I.getAttribute("loop")==="1"}):g.push({mediaId:b.getAttribute("mediaId")||null,uri:b.getAttribute("uri")||"",volume:parseInt(b.getAttribute("volume")||"100"),loop:b.getAttribute("loop")==="1"})}const f=[],m=Array.from(e.children).find(b=>b.tagName==="commands");if(m)for(const b of m.children)b.tagName==="command"&&f.push({commandCode:b.getAttribute("commandCode")||"",commandString:b.getAttribute("commandString")||""});const w=e.getAttribute("parentWidgetId")||null,L=parseInt(e.getAttribute("displayOrder")||"0"),C=e.getAttribute("cyclePlayback")==="1",v=parseInt(e.getAttribute("playCount")||"0"),x=e.getAttribute("isRandom")==="1",k=e.getAttribute("fromDt")||e.getAttribute("fromdt")||null,_=e.getAttribute("toDt")||e.getAttribute("todt")||null,S=e.getAttribute("render")||null;return{type:t,duration:i,useDuration:n,id:o,fileId:s,render:S,fromDt:k,toDt:_,enableStat:e.getAttribute("enableStat")!=="0",webhookUrl:r.webhookUrl||null,options:r,raw:c,transitions:u,actions:h,audioNodes:g,commands:f,parentWidgetId:w,displayOrder:L,cyclePlayback:C,playCount:v,isRandom:x}}trackBlobUrl(e){const t=this.currentLayoutId||0;t||this.log.warn("trackBlobUrl called without currentLayoutId, tracking under key 0"),this.layoutBlobUrls.has(t)||this.layoutBlobUrls.set(t,new Set),this.layoutBlobUrls.get(t).add(e)}revokeBlobUrlsForLayout(e){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){if(t.isDrawer)continue;let i=0;for(const n of t.widgets)n.duration>0&&(i+=n.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=Date.now()-(this._layoutTimerStartedAt||Date.now()),n=Math.max(1e3,this.currentLayout.duration*1e3-i);this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},n),this.log.info(`Layout timer adjusted to ${(n/1e3).toFixed(1)}s remaining (elapsed ${(i/1e3).toFixed(1)}s of ${this.currentLayout.duration}s)`)}else this.log.info(`Layout duration updated to ${e}s (timer not yet started, will use new value)`);this._scheduleNextLayoutPreload(this.currentLayout)}}attachActionListeners(e){var n;const t=[];let i=0;for(const o of e.actions||[])o.triggerType==="touch"?(this.attachTouchAction(this.container,o,null,null),i++):(n=o.triggerType)!=null&&n.startsWith("keyboard:")&&t.push(o);for(const o of e.regions){const s=this.regions.get(o.id);if(s){for(const r of o.actions||[])r.triggerType==="touch"?(this.attachTouchAction(s.element,r,o.id,null),i++):r.triggerType.startsWith("keyboard:")&&t.push(r);for(const r of o.widgets){if(!r.actions||r.actions.length===0)continue;const a=s.widgetElements.get(r.id);if(a)for(const l of r.actions)l.triggerType==="touch"?(this.attachTouchAction(a,l,o.id,r.id),i++):l.triggerType.startsWith("keyboard:")&&t.push(l)}}}this.setupKeyboardListener(t),(i>0||t.length>0)&&this.log.info(`Actions attached: ${i} touch, ${t.length} keyboard`)}attachTouchAction(e,t,i,n){e.style.cursor="pointer";const o=s=>{s.stopPropagation();const r=n?`widget ${n}`:`region ${i}`;this.log.info(`Touch action fired on ${r}: ${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:n}})};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 n of this._keyboardActions){const o=n.triggerType.substring(9);if(i===o){this.log.info(`Keyboard action (key: ${i}): ${n.actionType}`),this.emit("action-trigger",{actionType:n.actionType,triggerType:n.triggerType,triggerCode:n.triggerCode,layoutCode:n.layoutCode,targetId:n.targetId,commandCode:n.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 n=i.widgets.findIndex(o=>o.id===e);if(n!==-1){if(this.log.info(`Navigating to widget ${e} in region ${t} (index ${n})`),i.isDrawer&&i.element.style.display==="none"&&(i.element.style.display="",this.log.info(`Drawer region ${t} revealed`)),i.timer&&(clearTimeout(i.timer),i.timer=null),this.stopWidget(t,i.currentIndex),i.currentIndex=n,this.renderWidget(t,n),i.widgets.length>1){const s=i.widgets[n].duration*1e3;i.timer=setTimeout(()=>{this.stopWidget(t,n);const r=(n+1)%i.widgets.length;i.currentIndex=r,i.isDrawer&&r===0?(i.element.style.display="none",this.log.info(`Drawer region ${t} hidden (cycle complete)`)):this.startRegion(t)},s)}else if(i.isDrawer){const s=i.widgets[n].duration*1e3;i.timer=setTimeout(()=>{this.stopWidget(t,n),i.element.style.display="none",this.log.info(`Drawer region ${t} hidden (single widget done)`)},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,n=t.widgets[i];this.log.info(`nextWidget → index ${i} (widget ${n.id})`),this.navigateToWidget(n.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,n=t.widgets[i];this.log.info(`previousWidget → index ${i} (widget ${n.id})`),this.navigateToWidget(n.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)s.isDrawer||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 n=this.parseXlf(e);if(this.currentLayout=n,this.currentLayoutId=t,this.calculateScale(n),this.container.style.backgroundColor=n.bgcolor,this.container.style.backgroundImage="",n.background&&this.options.getMediaUrl)try{const o=await this.options.getMediaUrl(parseInt(n.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: ${n.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 n.regions)for(const r of s.widgets)if(r.fileId){const a=parseInt(r.fileId||r.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 n.regions)await this.createRegion(o);this.log.info("Pre-creating widget elements for instant transitions...");for(const[o,s]of this.regions)for(let r=0;r<s.widgets.length;r++){const a=s.widgets[r];a.layoutId=this.currentLayoutId,a.regionId=o;try{const l=await this.createWidgetElement(a,s);l.style.position="absolute",l.style.top="0",l.style.left="0",l.style.width="100%",l.style.height="100%",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(n),this.emit("layoutStart",t,n);for(const[o,s]of this.regions)s.isDrawer||this.startRegion(o);this.startLayoutTimerWhenReady(t,n),this._scheduleNextLayoutPreload(n),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",e.isDrawer&&(t.style.display="none"),this.applyRegionScale(t,e),this.container.appendChild(t);let i=e.widgets.filter(o=>this._isWidgetActive(o));i.some(o=>o.cyclePlayback)&&(i=this._applyCyclePlayback(i));const n=this.scaleFactor;this.regions.set(e.id,{element:t,config:e,widgets:i,currentIndex:0,timer:null,width:e.width*n,height:e.height*n,complete:!1,isDrawer:e.isDrawer||!1,widgetElements:new Map})}startRegion(e){const t=this.regions.get(e);this._startRegionCycle(t,e,(i,n)=>this.renderWidget(i,n),(i,n)=>this.stopWidget(i,n),()=>{this.log.info(`Region ${e} completed one full cycle`),this.checkLayoutComplete()})}async createWidgetElement(e,t){if(e.render==="html"&&e.type!=="pdf")return await this.renderGenericWidget(e,t);switch(e.type){case"image":return await this.renderImage(e,t);case"video":return await this.renderVideo(e,t);case"audio":return await this.renderAudio(e,t);case"text":case"ticker":return await this.renderTextWidget(e,t);case"pdf":return await this.renderPdf(e,t);case"webpage":return await this.renderWebpage(e,t);case"localvideo":return await this.renderVideo(e,t);case"videoin":return await this.renderVideoIn(e,t);case"powerpoint":case"flash":return this.log.warn(`Widget type '${e.type}' is not supported on web players (widget ${e.id})`),this._renderUnsupportedPlaceholder(e,t);default:return await this.renderGenericWidget(e,t)}}findMediaElement(e,t){return e.tagName===t?e:e.querySelector(t.toLowerCase())}updateMediaElement(e,t){const i=this.findMediaElement(e,"VIDEO")||this.findMediaElement(e,"AUDIO");if(i){if(i.tagName==="VIDEO"&&i._mediaConstraints&&!i._mediaStream){navigator.mediaDevices.getUserMedia(i._mediaConstraints).then(n=>{i.srcObject=n,i._mediaStream=n,this.log.info(`Webcam stream re-acquired for widget ${t.id}`)}).catch(n=>{this.log.warn("Failed to re-acquire webcam stream:",n.message)});return}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 n=this.findMediaElement(e,"VIDEO");if(n)return!n.paused&&n.readyState>=3?Promise.resolve():new Promise(r=>{const a=setTimeout(()=>{this.log.warn(`Video ready timeout (10000ms) for widget ${t.id}`),r()},1e4),l=()=>{n.removeEventListener("playing",l),clearTimeout(a),this.log.info(`Video widget ${t.id} ready (playing)`),r()};n.addEventListener("playing",l)});const o=this.findMediaElement(e,"AUDIO");if(o)return!o.paused&&o.readyState>=3?Promise.resolve():new Promise(r=>{const a=setTimeout(()=>{this.log.warn(`Audio ready timeout (10000ms) for widget ${t.id}`),r()},1e4),l=()=>{o.removeEventListener("playing",l),clearTimeout(a),this.log.info(`Audio widget ${t.id} ready (playing)`),r()};o.addEventListener("playing",l)});const s=this.findMediaElement(e,"IMG");return s?s.complete&&s.naturalWidth>0?Promise.resolve():new Promise(r=>{const a=()=>{s.removeEventListener("load",a),clearTimeout(l),r()},l=setTimeout(()=>{s.removeEventListener("load",a),this.log.warn(`Image ready timeout for widget ${t.id}`),r()},1e4);s.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 r=s.widgets[s.currentIndex||0],a=s.widgetElements.get(r.id);a&&i.push(this.waitForWidgetReady(a,r))}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 n=t.duration*1e3;this.log.info(`Layout ${e} will end after ${t.duration}s`),this._layoutTimerStartedAt=Date.now(),this._layoutTimerDurationMs=n,this.layoutTimer=setTimeout(()=>{this.log.info(`Layout ${e} duration expired (${t.duration}s)`),this.currentLayoutId&&(this.layoutEndEmitted=!0,this.emit("layoutEnd",this.currentLayoutId))},n)}async _showWidget(e,t){var o,s;const i=e.widgets[t];if(!i)return null;let n=e.widgetElements.get(i.id);n||(this.log.warn(`Widget ${i.id} not pre-created, creating now`),n=await this.createWidgetElement(i,e),n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",n.style.height="100%",e.widgetElements.set(i.id,n),e.element.appendChild(n));for(const[r,a]of e.widgetElements)r!==i.id&&((o=a.getAnimations)==null||o.call(a).forEach(l=>l.cancel()),a.style.visibility="hidden",a.style.opacity="0");return this.updateMediaElement(n,i),(s=n.getAnimations)==null||s.call(n).forEach(r=>r.cancel()),n.style.visibility="visible",i.transitions.in?Z.apply(n,i.transitions.in,!0,e.width,e.height):n.style.opacity="1",this._startAudioOverlays(i),i}_startAudioOverlays(e){if(!e.audioNodes||e.audioNodes.length===0)return;this._stopAudioOverlays(e.id);const t=[];for(const i of e.audioNodes){if(!i.uri)continue;const n=document.createElement("audio");n.autoplay=!0,n.loop=i.loop,n.volume=Math.max(0,Math.min(1,i.volume/100));const o=parseInt(i.mediaId);let s=o?this.mediaUrlCache.get(o):null;!s&&o&&this.options.getMediaUrl?this.options.getMediaUrl(o).then(a=>{n.src=a}).catch(()=>{n.src=`${window.location.origin}${O}/media/${i.uri}`}):s?n.src=s:n.src=`${window.location.origin}${O}/media/${i.uri}`,n.style.display="none",this.container.appendChild(n);const r=n.play();r&&r.catch&&r.catch(()=>{}),t.push(n),this.log.info(`Audio overlay started for widget ${e.id}: ${i.uri} (loop=${i.loop}, vol=${i.volume})`)}t.length>0&&this.audioOverlays.set(e.id,t)}_stopAudioOverlays(e){const t=this.audioOverlays.get(e);if(t){for(const i of t)i.pause(),i.removeAttribute("src"),i.load(),i.parentNode&&i.parentNode.removeChild(i);this.audioOverlays.delete(e),this.log.info(`Audio overlays stopped for widget ${e}`)}}_hideWidget(e,t){const i=e.widgets[t];if(!i)return{widget:null,animPromise:null};const n=e.widgetElements.get(i.id);if(!n)return{widget:null,animPromise:null};let o=null;if(i.transitions.out){const a=Z.apply(n,i.transitions.out,!1,e.width,e.height);a&&(o=new Promise(l=>{a.onfinish=l}))}const s=n.querySelector("video");if(s&&i.options.loop!=="1"&&s.pause(),s!=null&&s._mediaStream&&(s._mediaStream.getTracks().forEach(a=>a.stop()),s._mediaStream=null,s.srcObject=null),s!=null&&s._hlsInstance&&(s._hlsInstance.destroy(),s._hlsInstance=null),s!=null&&s._eventCleanup){for(const[a,l]of s._eventCleanup)s.removeEventListener(a,l);s._eventCleanup=null}const r=n.querySelector("audio");if(r&&i.options.loop!=="1"&&r.pause(),r!=null&&r._eventCleanup){for(const[a,l]of r._eventCleanup)r.removeEventListener(a,l);r._eventCleanup=null}return this._stopAudioOverlays(i.id),n._pdfCleanup&&n._pdfCleanup(),{widget:i,animPromise:o}}_isWidgetActive(e){const t=new Date;if(e.fromDt){const i=new Date(e.fromDt);if(t<i)return!1}if(e.toDt){const i=new Date(e.toDt);if(t>i)return!1}return!0}_parseDurationComments(e,t){const i=e.match(/<!--\s*DURATION=(\d+)\s*-->/);if(i){const o=parseInt(i[1],10);if(o>0){this.log.info(`Widget ${t.id}: DURATION comment overrides duration ${t.duration}→${o}s`),t.duration=o;return}}const n=e.match(/<!--\s*NUMITEMS=(\d+)\s*-->/);if(n){const o=parseInt(n[1],10);if(o>0&&t.duration>0){const s=o*t.duration;this.log.info(`Widget ${t.id}: NUMITEMS=${o} × ${t.duration}s = ${s}s`),t.duration=s}}}_applyCyclePlayback(e){this._subPlaylistCycleIndex||(this._subPlaylistCycleIndex=new Map);const t=new Map,i=[];for(const n of e)n.parentWidgetId&&n.cyclePlayback?(t.has(n.parentWidgetId)||t.set(n.parentWidgetId,[]),t.get(n.parentWidgetId).push(n)):i.push({type:"direct",widget:n});for(const[n,o]of t){o.sort((r,a)=>r.displayOrder-a.displayOrder);let s;if(o.some(r=>r.isRandom)){const r=Math.floor(Math.random()*o.length);s=o[r]}else{const r=this._subPlaylistCycleIndex.get(n)||0;s=o[r%o.length],this._subPlaylistCycleIndex.set(n,r+1)}this.log.info(`Sub-playlist cycle: group ${n} selected widget ${s.id} (${o.length} in group)`),i.push({type:"direct",widget:s})}return i.map(n=>n.widget)}_startRegionCycle(e,t,i,n,o){if(!e||e.widgets.length===0)return;if(e.widgets.length===1){i(t,0);return}const s=()=>{const r=e.currentIndex,a=e.widgets[r];i(t,r);const l=a.duration*1e3;e.timer=setTimeout(()=>{this._handleWidgetCycleEnd(a,e,t,r,i,n,o,s)},l)};s()}_handleWidgetCycleEnd(e,t,i,n,o,s,r,a){var c;e.webhookUrl&&this.emit("widgetAction",{type:"durationEnd",widgetId:e.id,layoutId:this.currentLayoutId,regionId:i,url:e.webhookUrl}),s(i,n);const l=(t.currentIndex+1)%t.widgets.length;if(l===0&&!t.complete&&(t.complete=!0,r==null||r()),l===0&&((c=t.config)==null?void 0:c.loop)===!1){o(i,t.widgets.length-1);return}this.layoutEndEmitted||(t.currentIndex=l,a())}async renderWidget(e,t){var n;const i=this.regions.get(e);if(i)try{const o=await this._showWidget(i,t);if(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,enableStat:o.enableStat}),o.commands&&o.commands.length>0))for(const s of o.commands)this.emit("widgetCommand",{commandCode:s.commandCode,commandString:s.commandString,widgetId:o.id,regionId:e,layoutId:this.currentLayoutId})}catch(o){this.log.error("Error rendering widget:",o),this.emit("error",{type:"widgetError",error:o,widgetId:(n=i.widgets[t])==null?void 0:n.id,regionId:e})}}async stopWidget(e,t){const i=this.regions.get(e);if(!i)return;const{widget:n,animPromise:o}=this._hideWidget(i,t);o&&await o,n&&this.emit("widgetEnd",{widgetId:n.id,regionId:e,layoutId:this.currentLayoutId,mediaId:parseInt(n.fileId||n.id)||null,type:n.type,enableStat:n.enableStat})}async renderImage(e,t){const i=document.createElement("img");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%";const n=e.options.scaleType,o={stretch:"fill",center:"contain",fit:"cover"};i.style.objectFit=o[n]||"contain";const s={left:"left",center:"center",right:"right"},r={top:"top",middle:"center",bottom:"bottom"},a=s[e.options.alignId]||"center",l=r[e.options.valignId]||"center";i.style.objectPosition=`${a} ${l}`,i.style.opacity="0";const c=parseInt(e.fileId||e.id);let u=this.mediaUrlCache.get(c);return!u&&this.options.getMediaUrl?u=await this.options.getMediaUrl(c):u||(u=`${window.location.origin}${O}/media/${e.options.uri}`),i.src=u,i}async renderVideo(e,t){const i=document.createElement("video");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%";const n=e.options.scaleType,o={stretch:"fill",center:"none",fit:"contain"};i.style.objectFit=o[n]||"contain",i.style.opacity="1",i.autoplay=!0,i.preload="auto",i.muted=e.options.mute==="1",i.loop=!1,i.controls=!1,i.playsInline=!0;const s=parseInt(e.fileId||e.id),r=()=>{e.options.loop==="1"?(i.currentTime=0,this.log.info(`Video ${s} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Video ${s} ended - paused on last frame`)};i.addEventListener("ended",r);let a=this.mediaUrlCache.get(s);if(!a&&this.options.getMediaUrl?a=await this.options.getMediaUrl(s):a||(a=`${window.location.origin}${O}/media/${s}`),a.includes(".m3u8"))if(i.canPlayType("application/vnd.apple.mpegurl"))this.log.info(`HLS stream (native): ${s}`),i.src=a;else try{const{default:m}=await P(async()=>{const{default:w}=await import("hls.js");return{default:w}},[],import.meta.url);if(m.isSupported()){const w=new m({enableWorker:!0,lowLatencyMode:!0});w.loadSource(a),w.attachMedia(i),i._hlsInstance=w,w.on(m.Events.ERROR,(L,C)=>{C.fatal&&(this.log.error(`HLS fatal error: ${C.type}`,C.details),w.destroy(),i._hlsInstance=null)}),this.log.info(`HLS stream (hls.js): ${s}`)}else this.log.warn(`HLS not supported on this browser for ${s}`),i.src=a}catch(m){this.log.warn(`hls.js not available, falling back to native: ${m.message}`),i.src=a}else i.src=a;const c=this.currentLayoutId,u=()=>{const m=Math.floor(i.duration);this.log.info(`Video ${s} duration detected: ${m}s`),(e.duration===0||e.useDuration===0)&&(e.duration=m,this.log.info(`Updated widget ${e.id} duration to ${m}s (useDuration=0)`),this.currentLayoutId===c?this.updateLayoutDuration():this.log.info(`Video ${s} duration set but layout timer not updated (preloaded for layout ${c}, current is ${this.currentLayoutId})`))};i.addEventListener("loadedmetadata",u);const h=()=>{this.log.info("Video loaded and ready:",s)};i.addEventListener("loadeddata",h);const g=()=>{const m=i.error,w=m==null?void 0:m.code,L=(m==null?void 0:m.message)||"Unknown error";this.log.warn(`Video error: ${s}, code: ${w}, time: ${i.currentTime.toFixed(1)}s, message: ${L}`),this.emit("videoError",{fileId:s,errorCode:w,errorMessage:L,currentTime:i.currentTime})};i.addEventListener("error",g);const f=()=>{this.log.info("Video playing:",s)};return i.addEventListener("playing",f),i._eventCleanup=[["ended",r],["loadedmetadata",u],["loadeddata",h],["error",g],["playing",f]],this.log.info("Video element created:",s,i.src),i}async renderVideoIn(e,t){const i=document.createElement("video");i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.objectFit=e.options.showFullScreen==="1"?"cover":"contain",i.autoplay=!0,i.playsInline=!0,i.controls=!1,i.muted=e.options.mute!=="0",e.options.mirror==="1"&&(i.style.transform="scaleX(-1)");const n={width:{ideal:t.width},height:{ideal:t.height}},o=e.options.sourceId||e.options.deviceId;o?n.deviceId={exact:o}:n.facingMode=e.options.facingMode||"environment";const s={video:n,audio:e.options.captureAudio==="1"};i._mediaConstraints=s;try{const r=await navigator.mediaDevices.getUserMedia(s);i.srcObject=r,i._mediaStream=r,this.log.info(`Webcam stream acquired for widget ${e.id} (tracks: ${r.getTracks().length})`)}catch(r){return this.log.warn(`getUserMedia failed for widget ${e.id}: ${r.message}`),this._renderUnsupportedPlaceholder({...e,type:"Camera unavailable"},t)}return 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 n=document.createElement("audio");n.autoplay=!0,n.loop=e.options.loop==="1",n.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}${O}/media/${o}`),n.src=s;const r=()=>{e.options.loop==="1"?(n.currentTime=0,this.log.info(`Audio ${o} ended - reset to start, waiting for widget cycle to replay`)):this.log.info(`Audio ${o} ended - playback complete`)};n.addEventListener("ended",r);const a=this.currentLayoutId,l=()=>{const f=Math.floor(n.duration);this.log.info(`Audio ${o} duration detected: ${f}s`),(e.duration===0||e.useDuration===0)&&(e.duration=f,this.log.info(`Updated widget ${e.id} duration to ${f}s (useDuration=0)`),this.currentLayoutId===a?this.updateLayoutDuration():this.log.info(`Audio ${o} duration set but layout timer not updated (preloaded for layout ${a}, current is ${this.currentLayoutId})`))};n.addEventListener("loadedmetadata",l);const c=()=>{const f=n.error;this.log.warn(`Audio error (non-fatal): ${o}, code: ${f==null?void 0:f.code}, message: ${(f==null?void 0:f.message)||"Unknown"}`)};n.addEventListener("error",c),n._eventCleanup=[["ended",r],["loadedmetadata",l],["error",c]];const u=document.createElement("div");u.innerHTML="♪",u.style.fontSize="120px",u.style.color="white",u.style.marginBottom="20px";const h=document.createElement("div");h.style.color="white",h.style.fontSize="24px",h.textContent="Playing Audio";const g=document.createElement("div");return g.style.color="rgba(255,255,255,0.7)",g.style.fontSize="16px",g.style.marginTop="10px",g.textContent=e.options.uri,i.appendChild(n),i.appendChild(u),i.appendChild(h),i.appendChild(g),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 n=e.raw;if(this.options.getWidgetHtml){const r=await this.options.getWidgetHtml(e);if(r&&typeof r=="object"&&r.url){if(i.src=r.url,r.fallback){const a=this;i.addEventListener("load",function(){var l;try{if(!((l=i.contentDocument)!=null&&l.querySelector("base"))){a.log.warn("Cache URL failed (hard reload?), using original CMS URLs");const c=new Blob([r.fallback],{type:"text/html"}),u=URL.createObjectURL(c);a.trackBlobUrl(u),i.src=u}}catch{}},{once:!0})}return r.fallback&&this._parseDurationComments(r.fallback,e),i}n=r}n&&this._parseDurationComments(n,e);const o=new Blob([n],{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 P(()=>import("./pdf-BnPRJEQ6.js"),[],import.meta.url);window.pdfjsLib=s;const r=window.location.pathname.replace(/\/[^/]*$/,"/");window.pdfjsLib.GlobalWorkerOptions.workerSrc=`${window.location.origin}${r}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 n=parseInt(e.fileId||e.id);let o=this.mediaUrlCache.get(n);!o&&this.options.getMediaUrl?o=await this.options.getMediaUrl(n):o||(o=`${window.location.origin}${O}/media/${e.options.uri}`);try{const r=await window.pdfjsLib.getDocument(o).promise,a=r.numPages,l=e.duration||60,c=l*1e3/a;this.log.info(`[pdf] PDF loaded: ${a} pages, ${l}s duration, ${(c/1e3).toFixed(1)}s/page`);const u=await r.getPage(1),h=u.getViewport({scale:1}),g=Math.min(t.width/h.width,t.height/h.height);u.cleanup();const f=document.createElement("canvas");f.className="pdf-page",f.width=Math.floor(h.width*g),f.height=Math.floor(h.height*g),f.style.cssText="display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);";const m=f.getContext("2d");i.appendChild(f);const w=document.createElement("div");w.style.cssText="position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,0.7);color:white;padding:8px 12px;border-radius:4px;font:14px system-ui;z-index:1;",i.appendChild(w);let L=1,C=null,v=null,x=!1;const k=async()=>{if(x)return;w.textContent=`Page ${L} / ${a}`;const _=await r.getPage(L),S=_.getViewport({scale:g});m.clearRect(0,0,f.width,f.height),v=_.render({canvasContext:m,viewport:S});try{await v.promise}catch(b){if(x)return;throw b}v=null,_.cleanup(),a>1&&!x&&(C=setTimeout(()=>{L=L>=a?1:L+1,k()},c))};await k(),i._pdfCleanup=()=>{x=!0,C&&clearTimeout(C),C=null,v&&(v.cancel(),v=null),f.width=0,f.height=0,r.destroy()}}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){if(parseInt(e.options.modeId||"1")===0)return await this.renderGenericWidget(e,t);const n=document.createElement("iframe");n.className="renderer-lite-widget",n.style.width="100%",n.style.height="100%",n.style.border="none",n.style.opacity="0";const o=decodeURIComponent(e.options.uri||"");return n.src=o,n}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 n=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 r;try{if(!((r=i.contentDocument)!=null&&r.querySelector("base"))){s.log.warn("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 o.fallback&&this._parseDurationComments(o.fallback,e),i}n=o}if(n){this._parseDurationComments(n,e);const o=new Blob([n],{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}_renderUnsupportedPlaceholder(e,t){const i=document.createElement("div");return i.className="renderer-lite-widget",i.style.width="100%",i.style.height="100%",i.style.display="flex",i.style.alignItems="center",i.style.justifyContent="center",i.style.backgroundColor="#111",i.style.color="#666",i.style.fontSize="14px",i.textContent=`Unsupported: ${e.type}`,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,n=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")},n)}hasPreloadedLayout(e){return this.layoutPool.has(e)}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 n=document.createElement("div");if(n.id=`preload_layout_${t}`,n.className="renderer-lite-preload-wrapper",n.style.position="absolute",n.style.top="0",n.style.left="0",n.style.width="100%",n.style.height="100%",n.style.visibility="hidden",n.style.zIndex="-1",n.style.backgroundColor=i.bgcolor,i.background&&this.options.getMediaUrl)try{const g=await this.options.getMediaUrl(parseInt(i.background));g&&(n.style.backgroundImage=`url(${g})`,n.style.backgroundSize="cover",n.style.backgroundPosition="center",n.style.backgroundRepeat="no-repeat")}catch(g){this.log.warn("Preload: Failed to load background image:",g)}const o=new Map;if(this.options.getMediaUrl){const g=[];for(const f of i.regions)for(const m of f.widgets)if(m.fileId){const w=parseInt(m.fileId||m.id);o.has(w)||g.push(this.options.getMediaUrl(w).then(L=>{o.set(w,L)}).catch(L=>{this.log.warn(`Preload: Failed to fetch media ${w}:`,L)}))}g.length>0&&(this.log.info(`Preload: fetching ${g.length} media URLs...`),await Promise.all(g))}const s=this.mediaUrlCache,r=this.currentLayoutId;this.mediaUrlCache=o;const a=new Map,l=this.scaleFactor;for(const g of i.regions){const f=document.createElement("div");f.id=`preload_region_${t}_${g.id}`,f.className="renderer-lite-region",f.style.position="absolute",f.style.zIndex=g.zindex,f.style.overflow="hidden",this.applyRegionScale(f,g),n.appendChild(f);const m={element:f,config:g,widgets:g.widgets,currentIndex:0,timer:null,width:g.width*l,height:g.height*l,complete:!1,widgetElements:new Map};a.set(g.id,m)}const c=new Set,u=this.layoutBlobUrls;this.layoutBlobUrls=new Map,this.layoutBlobUrls.set(t,c),this.currentLayoutId=t;for(const[g,f]of a)for(let m=0;m<f.widgets.length;m++){const w=f.widgets[m];w.layoutId=t,w.regionId=g;try{const L=await this.createWidgetElement(w,f);L.style.position="absolute",L.style.top="0",L.style.left="0",L.style.width="100%",L.style.height="100%",L.style.visibility="hidden",L.style.opacity="0",f.element.appendChild(L),f.widgetElements.set(w.id,L)}catch(L){this.log.error(`Preload: Failed to create widget ${w.id}:`,L)}}return this.mediaUrlCache=s,this.currentLayoutId=r,n.querySelectorAll("video").forEach(g=>g.pause()),(this.layoutBlobUrls.get(t)||new Set).forEach(g=>c.add(g)),this.layoutBlobUrls=u,this.container.appendChild(n),this.layoutPool.add(t,{container:n,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[n,o]of this.regions)if(o.timer&&(clearTimeout(o.timer),o.timer=null),Y.releaseMediaElements(o.element),o.config&&o.config.exitTransition){const s=Z.apply(o.element,o.config.exitTransition,!1,o.width,o.height);if(s){const r=o.element;s.onfinish=()=>r.remove()}else o.element.remove()}else o.element.remove();i&&this.revokeBlobUrlsForLayout(i);for(const[n,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[n,o]of this.regions)o.currentIndex=0,o.complete=!1,this.startRegion(n);this.updateLayoutDuration(),this.startLayoutTimerWhenReady(e,t.layout),this.preloadTimer||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)if(t.timer&&(clearTimeout(t.timer),t.timer=null),t.widgets.length>0&&this.stopWidget(e,t.currentIndex),Y.releaseMediaElements(t.element),t.config&&t.config.exitTransition){const i=Z.apply(t.element,t.config.exitTransition,!1,t.width,t.height);if(i){const n=t.element;i.onfinish=()=>n.remove()}else t.element.remove()}else 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 n=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=n.bgcolor,this.options.getMediaUrl){const a=[];for(const l of n.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(h=>{this.mediaUrlCache.set(u,h)}).catch(h=>{this.log.warn(`Failed to fetch overlay media ${u}:`,h)}))}a.length>0&&(this.log.info(`Pre-fetching ${a.length} overlay media URLs...`),await Promise.all(a))}this.calculateScale(n);const s=new Map,r=this.scaleFactor;for(const a of n.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*r,height:a.height*r,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:n,regions:s,timer:null,priority:i}),this.emit("overlayStart",t,n);for(const[a,l]of s)this.startOverlayRegion(t,a);if(n.duration>0){const a=n.duration*1e3,l=this.activeOverlays.get(t);l&&(l.timer=setTimeout(()=>{this.log.info(`Overlay ${t} duration expired (${n.duration}s)`),this.emit("overlayEnd",t)},a))}this.log.info(`Overlay ${t} started`)}catch(n){throw this.log.error("Error rendering overlay:",n),this.emit("error",{type:"overlayError",error:n,layoutId:t}),n}}startOverlayRegion(e,t){const i=this.activeOverlays.get(e);if(!i)return;const n=i.regions.get(t);this._startRegionCycle(n,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 n=this.activeOverlays.get(e);if(!n)return;const o=n.regions.get(t);if(o)try{const r=await this._showWidget(o,i);r&&(this.log.info(`Showing overlay widget ${r.type} (${r.id}) in overlay ${e} region ${t}`),this.emit("overlayWidgetStart",{overlayId:e,widgetId:r.id,regionId:t,type:r.type,duration:r.duration}))}catch(r){this.log.error("Error rendering overlay widget:",r),this.emit("error",{type:"overlayWidgetError",error:r,widgetId:(s=o.widgets[i])==null?void 0:s.id,regionId:t,overlayId:e})}}async stopOverlayWidget(e,t,i){const n=this.activeOverlays.get(e);if(!n)return;const o=n.regions.get(t);if(!o)return;const{widget:s,animPromise:r}=this._hideWidget(o,i);r&&await r,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,n]of t.regions)n.timer&&(clearTimeout(n.timer),n.timer=null),n.widgets.length>0&&this.stopOverlayWidget(e,i,n.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){this._paused=!0;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 (layout timer continues)")}}isPaused(){return this._paused}resume(){if(this._paused){this._paused=!1,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();for(const e of this.audioOverlays.keys())this._stopAudioOverlays(e);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 V=q("Layout");class ht{constructor(e){this.xmds=e}async translateXLF(e,t){const n=new DOMParser().parseFromString(t,"text/xml"),o=n.querySelector("layout");if(!o)throw new Error("Invalid XLF: no <layout> element");const s=parseInt(o.getAttribute("width")||"1920"),r=parseInt(o.getAttribute("height")||"1080"),a=o.getAttribute("bgcolor")||"#000000",l=[];for(const c of n.querySelectorAll("region"))l.push(await this.translateRegion(e,c));return this.generateHTML(s,r,a,l)}async translateRegion(e,t){const i=t.getAttribute("id"),n=parseInt(t.getAttribute("width")),o=parseInt(t.getAttribute("height")),s=parseInt(t.getAttribute("top")),r=parseInt(t.getAttribute("left")),a=parseInt(t.getAttribute("zindex")||"0"),l=[];for(const c of t.querySelectorAll("media"))l.push(await this.translateMedia(e,i,c));return{id:i,width:n,height:o,top:s,left:r,zindex:a,media:l}}async translateMedia(e,t,i){const n=i.getAttribute("type"),o=parseInt(i.getAttribute("duration")||"10"),s=i.getAttribute("id"),r=i.querySelector("options"),a=i.querySelector("raw"),l={};if(r)for(const v of r.children)l[v.tagName]=v.textContent;const c={in:null,out:null},u=i.querySelector("options > transIn"),h=i.querySelector("options > transOut"),g=i.querySelector("options > transInDuration"),f=i.querySelector("options > transOutDuration"),m=i.querySelector("options > transInDirection"),w=i.querySelector("options > transOutDirection");u&&u.textContent&&(c.in={type:u.textContent,duration:parseInt((g==null?void 0:g.textContent)||"1000"),direction:(m==null?void 0:m.textContent)||"N"}),h&&h.textContent&&(c.out={type:h.textContent,duration:parseInt((f==null?void 0:f.textContent)||"1000"),direction:(w==null?void 0:w.textContent)||"N"});let L=a?a.textContent:"";if(["clock","clock-digital","clock-analogue","calendar","weather","currencies","stocks","twitter","global","embedded","text","ticker"].some(v=>n.includes(v))){let v=3,x=null;for(let k=1;k<=v;k++)try{V.info(`Fetching resource for ${n} widget (layout=${e}, region=${t}, media=${s}) - attempt ${k}/${v}`),L=await this.xmds.getResource(e,t,s),V.info(`Got resource HTML (${L.length} chars)`);const _=await Ee(e,t,s,L);l.widgetCacheKey=_;break}catch(_){if(x=_,V.warn(`Failed to get resource (attempt ${k}/${v}):`,_.message),k<v){const S=k*2e3;V.info(`Retrying in ${S}ms...`),await new Promise(b=>setTimeout(b,S))}}if(!L&&x){V.warn("All retries failed, checking for cached widget HTML...");try{const k=await fetch(`/store${O}/widgets/${e}/${t}/${s}`);k.ok?(L=await k.text(),l.widgetCacheKey=`${O}/widgets/${e}/${t}/${s}`,V.info(`Using stored widget HTML (${L.length} chars) - CMS update pending`)):(V.error(`No stored version available for widget ${s}`),L='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>')}catch(k){V.error("Store fallback failed:",k),L='<div style="display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;">Content updating...</div>'}}}return{type:n,duration:o,id:s,options:l,raw:L,transitions:c}}generateHTML(e,t,i,n){const o=n.map(r=>this.generateRegionHTML(r)).join(`
|
|
3
3
|
`),s=n.map(r=>this.generateRegionJS(r)).join(`,
|
|
4
4
|
`);return`<!DOCTYPE html>
|
|
5
5
|
<html>
|
|
@@ -252,11 +252,11 @@ ${t}
|
|
|
252
252
|
} else {
|
|
253
253
|
img.style.opacity = '1';
|
|
254
254
|
}
|
|
255
|
-
}`;break;case"video":const
|
|
255
|
+
}`;break;case"video":const u=`${window.location.origin}${O}/media/${e.options.uri}`,h=e.options.uri;s=`() => {
|
|
256
256
|
const region = document.getElementById('region_${t}');
|
|
257
257
|
const video = document.createElement('video');
|
|
258
258
|
video.className = 'media';
|
|
259
|
-
video.src = '${
|
|
259
|
+
video.src = '${u}';
|
|
260
260
|
video.dataset.filename = '${h}';
|
|
261
261
|
video.autoplay = true;
|
|
262
262
|
video.muted = ${e.options.mute==="1"?"true":"false"};
|
|
@@ -314,7 +314,7 @@ ${t}
|
|
|
314
314
|
video.pause();
|
|
315
315
|
video.remove();
|
|
316
316
|
}
|
|
317
|
-
}`;break;case"text":case"ticker":if(e.options.widgetCacheKey){const k=`${window.location.origin}${e.options.widgetCacheKey}`,
|
|
317
|
+
}`;break;case"text":case"ticker":if(e.options.widgetCacheKey){const k=`${window.location.origin}${e.options.widgetCacheKey}`,_=this._generateIframeWidgetJS(t,e.id,k,n,o);s=_.startFn,r=_.stopFn;break}case"audio":const g=`${window.location.origin}${O}/media/${e.options.uri}`,f=`audio_${t}_${e.id}`,m=e.options.loop==="1",w=(parseInt(e.options.volume||"100")/100).toFixed(2);s=`() => {
|
|
318
318
|
const region = document.getElementById('region_${t}');
|
|
319
319
|
|
|
320
320
|
// Create audio element
|
|
@@ -596,10 +596,10 @@ ${t}
|
|
|
596
596
|
}
|
|
597
597
|
container.remove();
|
|
598
598
|
}
|
|
599
|
-
}`;break;case"webpage":const
|
|
599
|
+
}`;break;case"webpage":const x=decodeURIComponent(e.options.uri||"");s=`() => {
|
|
600
600
|
const region = document.getElementById('region_${t}');
|
|
601
601
|
const iframe = document.createElement('iframe');
|
|
602
|
-
iframe.src = '${
|
|
602
|
+
iframe.src = '${x}';
|
|
603
603
|
iframe.style.opacity = '0';
|
|
604
604
|
region.innerHTML = '';
|
|
605
605
|
region.appendChild(iframe);
|
|
@@ -614,11 +614,11 @@ ${t}
|
|
|
614
614
|
iframe.style.opacity = '1';
|
|
615
615
|
}
|
|
616
616
|
};
|
|
617
|
-
}`;break;default:if(e.options.widgetCacheKey){const k=`${window.location.origin}${e.options.widgetCacheKey}`,
|
|
617
|
+
}`;break;default:if(e.options.widgetCacheKey){const k=`${window.location.origin}${e.options.widgetCacheKey}`,_=this._generateIframeWidgetJS(t,e.id,k,n,o);s=_.startFn,r=_.stopFn}else V.warn(`Unsupported media type: ${e.type}`),s=`() => console.log('Unsupported media type: ${e.type}')`}return` {
|
|
618
618
|
start: ${s},
|
|
619
619
|
stop: ${r},
|
|
620
620
|
duration: ${i}
|
|
621
|
-
}`}}const te=q("schedule:criteria"),qe=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],he={weatherTemp:"temperature",weatherHumidity:"humidity",weatherWindSpeed:"windSpeed",weatherCondition:"condition",weatherCloudCover:"cloudCover"};function He(p,e,t={},i={}){switch(p){case"dayOfWeek":return qe[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:if(he[p]){const n=he[p];return i[n]!==void 0?String(i[n]):(te.debug(`Weather metric "${p}" requested but no weather data available`),null)}return t[p]!==void 0?String(t[p]):(te.debug(`Unknown metric: ${p}`),null)}}function Be(p,e,t,i){if(p===null)return!1;if(i==="number"){const s=parseFloat(p),r=parseFloat(t);if(isNaN(s)||isNaN(r))return!1;switch(e){case"equals":return s===r;case"notEquals":return s!==r;case"greaterThan":return s>r;case"greaterThanOrEquals":return s>=r;case"lessThan":return s<r;case"lessThanOrEquals":return s<=r;default:return!1}}const n=p.toLowerCase(),o=t.toLowerCase();switch(e){case"equals":return n===o;case"notEquals":return n!==o;case"contains":return n.includes(o);case"notContains":return!n.includes(o);case"startsWith":return n.startsWith(o);case"endsWith":return n.endsWith(o);case"in":return o.split(",").map(s=>s.trim().toLowerCase()).includes(n);case"greaterThan":return n>o;case"lessThan":return n<o;default:return te.debug(`Unknown condition: ${e}`),!1}}function re(p,e={}){if(!p||p.length===0)return!0;const t=e.now||new Date,i=e.displayProperties||{},n=e.weatherData||{};for(const o of p){const s=He(o.metric,t,i,n);if(!Be(s,o.condition,o.value,o.type))return te.debug(`Criteria failed: ${o.metric} ${o.condition} "${o.value}" (actual: "${s}")`),!1}return!0}function ze(p){const t=new DOMParser().parseFromString(p,"text/xml").querySelector("layout");if(!t)return{duration:60,isDynamic:!1};const i=parseInt(t.getAttribute("duration")||"0",10);if(i>0)return{duration:i,isDynamic:!1};let n=0,o=!1;for(const r of t.querySelectorAll("region")){let a=0;for(const l of r.querySelectorAll("media")){const c=parseInt(l.getAttribute("duration")||"0",10),d=parseInt(l.getAttribute("useDuration")||"1",10);c>0&&d!==0?a+=c:(a+=60,o=!0)}n=Math.max(n,a)}return{duration:n>0?n:60,isDynamic:o}}function ae(p,e){if(p.length!==e.length)return!1;for(let t=0;t<p.length;t++)if(p[t]!==e[t])return!1;return!0}function je(p,e,t){if(!e||e===0)return!0;const i=t-36e5,n=p.filter(o=>o>i);if(n.length>=e)return!1;if(n.length>0){const o=36e5/e,s=Math.max(...n);if(t-s<o)return!1}return!0}function Ve(p){const e=new Map;if(!p)return e;for(const[t,i]of p){const n=`${t}.xlf`;e.set(n,[...i])}return e}function ie(p,e,t){const i=p.filter(o=>{if(!o.maxPlaysPerHour||o.maxPlaysPerHour===0)return!0;const s=e.get(o.file)||[];return je(s,o.maxPlaysPerHour,t)});if(i.length===0)return[];const n=Math.max(...i.map(o=>o.priority));return i.filter(o=>o.priority===n).map(o=>o.file)}function Ke(p,e,t={}){var f;const i=t.from||new Date,n=t.hours||2,o=new Date(i.getTime()+n*36e5),s=t.defaultDuration||60,r=t.currentLayoutStartedAt||null,a=[];let l=new Date(i),c=!0;const d=typeof p.getAllLayoutsAtTime=="function",h=Ve(p.playHistory),g=500;for(;l<o&&a.length<g;){const m=l.getTime();let w,L=null;if(d){const C=p.getAllLayoutsAtTime(l);w=C.length>0?ie(C,h,m):[],C.length>w.length&&(L=C.filter(v=>!w.includes(v.file)).map(v=>({file:v.file,priority:v.priority})))}else w=p.getLayoutsAtTime(l);if(w.length===0){const C=(f=p.schedule)==null?void 0:f.default;if(C){const v=e.get(C)||s;a.push({layoutFile:C,startTime:new Date(l),endTime:new Date(m+v*1e3),duration:v,isDefault:!0}),l=new Date(m+v*1e3)}else l=new Date(m+6e4);continue}for(let C=0;C<w.length&&l<o&&a.length<g;C++){const v=w[C];let _=e.get(v)||s;if(c&&r){const S=(i.getTime()-r.getTime())/1e3;_=Math.max(1,Math.round(_-S)),c=!1}const k=l.getTime()+_*1e3,x={layoutFile:v,startTime:new Date(l),endTime:new Date(k),duration:_,isDefault:!1};if(L&&L.length>0&&(x.hidden=L),a.push(x),d&&(h.has(v)||h.set(v,[]),h.get(v).push(l.getTime())),l=new Date(k),d){const S=p.getAllLayoutsAtTime(l),b=S.length>0?ie(S,h,l.getTime()):[];if(!ae(w,b))break}else{const S=p.getLayoutsAtTime(l);if(!ae(w,S))break}}}return a}function Xe(p,e){for(p=Math.abs(Math.round(p)),e=Math.abs(Math.round(e));e;)[p,e]=[e,p%e];return p}function Ge(p,e){return p===0||e===0?0:Math.abs(Math.round(p)*Math.round(e))/Xe(p,e)}function Je(p){return p.reduce((e,t)=>Ge(e,t),1)}function Qe(p,e,t={}){const{defaultLayout:i=null,defaultDuration:n=60}=t;if(p.length===0&&!i)return{queue:[],periodSeconds:0};const o=p.filter(h=>h.maxPlaysPerHour>0);let s;if(o.length>0){const h=o.map(g=>Math.round(3600/g.maxPlaysPerHour));s=Je(h),s>7200&&(s=7200)}else s=p.reduce((g,f)=>g+(e.get(f.file)||n),0)+(i&&!p.some(g=>g.file===i)?e.get(i)||n:0)||n;const r=[],a=new Map;let l=0;const c=s*1e3,d=500;for(;l<c&&r.length<d;){const h=ie(p,a,l);if(h.length===0){if(i){const g=e.get(i)||n;r.push({layoutId:i,duration:g}),l+=g*1e3}else l+=6e4;continue}for(let g=0;g<h.length&&l<c&&r.length<d;g++){const f=h[g],m=e.get(f)||n;r.push({layoutId:f,duration:m}),a.has(f)||a.set(f,[]),a.get(f).push(l),l+=m*1e3;const w=ie(p,a,l);if(!ae(h,w))break}}if(r.length===0&&i){const h=e.get(i)||n;r.push({layoutId:i,duration:h})}return{queue:r,periodSeconds:s}}const U=q("Schedule");class Ye{constructor(e={}){this.schedule=null,this.playHistory=new Map,this.interruptScheduler=e.interruptScheduler||null,this.displayProperties=e.displayProperties||{},this.weatherData={},this.playerLocation=null,this._layoutMetadata=new Map,this._scheduleQueue=null,this._queuePosition=0,this._queueLayoutSet=null}setSchedule(e){this.schedule=e,this._invalidateQueue()}setWeatherData(e){this.weatherData=e||{}}getDataConnectors(){var e;return((e=this.schedule)==null?void 0:e.dataConnectors)||[]}getDependantsMap(){const e=new Map;if(!this.schedule)return e;const t=this.schedule.dependants||[],i=n=>{const o=parseInt(String(n.file||n.id).replace(".xlf",""),10),s=[...t,...n.dependants||[]];s.length>0&&e.set(o,s)};if(this.schedule.layouts)for(const n of this.schedule.layouts)i(n);if(this.schedule.campaigns)for(const n of this.schedule.campaigns)for(const o of n.layouts)i(o);return e}isRecurringScheduleActive(e,t){if(!e.recurrenceType)return!0;if(e.recurrenceRange){const i=new Date(e.recurrenceRange);if(t>i)return!1}switch(e.recurrenceType){case"Week":{if(e.recurrenceRepeatsOn){const i=this.getIsoDayOfWeek(t);if(!e.recurrenceRepeatsOn.split(",").map(o=>parseInt(o.trim())).includes(i))return!1}return!0}case"Day":{const i=e.recurrenceDetail||1;if(i>1&&e.fromdt){const n=new Date(e.fromdt),o=t.getTime()-n.getTime(),s=Math.floor(o/864e5);if(s<0||s%i!==0)return!1}return!0}case"Month":{if(e.recurrenceRepeatsOn){const n=e.recurrenceRepeatsOn.split(",").map(s=>parseInt(s.trim())),o=t.getDate();if(!n.includes(o))return!1}const i=e.recurrenceDetail||1;if(i>1&&e.fromdt){const n=new Date(e.fromdt),o=(t.getFullYear()-n.getFullYear())*12+t.getMonth()-n.getMonth();if(o<0||o%i!==0)return!1}return!0}default:return U.debug(`Unsupported recurrence type: ${e.recurrenceType}`),!0}}getIsoDayOfWeek(e){const t=e.getDay();return t===0?7:t}isTimeActive(e,t){const i=e.fromdt?new Date(e.fromdt):null,n=e.todt?new Date(e.todt):null;if(e.recurrenceType==="Week"||e.recurrenceType==="Day"||e.recurrenceType==="Month"){if(i&&n){const o=t.getHours()*3600+t.getMinutes()*60+t.getSeconds(),s=i.getHours()*3600+i.getMinutes()*60+i.getSeconds(),r=n.getHours()*3600+n.getMinutes()*60+n.getSeconds();return s<=r?o>=s&&o<=r:o>=s||o<=r}return!0}return!(i&&t<i||n&&t>n)}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 n of this.schedule.layouts)this.isRecurringScheduleActive(n,t)&&this.isTimeActive(n,t)&&(n.criteria&&n.criteria.length>0&&!re(n.criteria,{now:t,displayProperties:this.displayProperties,weatherData:this.weatherData})||n.isGeoAware&&n.geoLocation&&!this.isWithinGeoFence(n.geoLocation)||i.push({file:n.file,priority:n.priority||0,maxPlaysPerHour:n.maxPlaysPerHour||0}));if(this.schedule.campaigns){for(const n of this.schedule.campaigns)if(this.isRecurringScheduleActive(n,t)&&this.isTimeActive(n,t))for(const o of n.layouts)i.push({file:o.file,priority:n.priority||0,maxPlaysPerHour:o.maxPlaysPerHour||0})}return i}detectConflicts(e={}){const t=e.from||new Date,i=e.hours||24,n=new Date(t.getTime()+i*36e5),o=6e4,s=[];let r=null;for(let a=t.getTime();a<n.getTime();a+=o){const l=new Date(a),c=this.getAllLayoutsAtTime(l);if(c.length===0){r&&(s.push(r),r=null);continue}const d=Math.max(...c.map(w=>w.priority)),h=c.filter(w=>w.priority<d);if(h.length===0){r&&(s.push(r),r=null);continue}const g=c.filter(w=>w.priority===d),f=g.map(w=>w.file).sort().join(","),m=h.map(w=>`${w.file}:${w.priority}`).sort().join(",");r&&r._winnerKey===f&&r._hiddenKey===m?r.endTime=new Date(a+o):(r&&s.push(r),r={startTime:new Date(a),endTime:new Date(a+o),winner:{file:g[0].file,priority:d},hidden:h.map(w=>({file:w.file,priority:w.priority})),_winnerKey:f,_hiddenKey:m})}r&&s.push(r);for(const a of s)delete a._winnerKey,delete a._hiddenKey;return s}_getLayoutsAt(e,t={}){if(!this.schedule)return[];const{skipRateLimiting:i=!1,skipInterrupts:n=!1,quiet:o=!1}=t,s=o?()=>{}:(...d)=>U.info(...d),r=[];if(this._maxActivePriority=0,this.schedule.campaigns)for(const d of this.schedule.campaigns)this.isRecurringScheduleActive(d,e)&&this.isTimeActive(d,e)&&(this._maxActivePriority=Math.max(this._maxActivePriority,d.priority||0),r.push({type:"campaign",priority:d.priority,layouts:d.layouts,campaignId:d.id}));if(this.schedule.layouts){for(const d of this.schedule.layouts)if(this.isRecurringScheduleActive(d,e)&&this.isTimeActive(d,e)){if(d.criteria&&d.criteria.length>0&&!re(d.criteria,{now:e,displayProperties:this.displayProperties,weatherData:this.weatherData})){s("[Schedule] Layout",d.id,"filtered by criteria");continue}if(d.isGeoAware&&d.geoLocation&&!this.isWithinGeoFence(d.geoLocation)){s("[Schedule] Layout",d.id,"filtered by geofence");continue}if(this._maxActivePriority=Math.max(this._maxActivePriority,d.priority||0),!i&&!this.canPlayLayout(d.id,d.maxPlaysPerHour)){s("[Schedule] Layout",d.id,"filtered by maxPlaysPerHour (limit:",d.maxPlaysPerHour,")");continue}r.push({type:"layout",priority:d.priority||0,layouts:[d],layoutId:d.id})}}if(r.length===0)return this.schedule.default?[this.schedule.default]:[];let a=Math.max(...r.map(d=>d.priority));s("[Schedule] Max priority:",a,"from",r.length,"active items");let l=[];for(const d of r)d.priority===a?(s("[Schedule] Including priority",d.priority,"layouts:",d.layouts.map(h=>h.file)),l.push(...d.layouts)):s("[Schedule] Skipping priority",d.priority,"< max",a);this._layoutMetadata.clear();for(const d of l)this._layoutMetadata.set(d.file,{syncEvent:d.syncEvent||!1,shareOfVoice:d.shareOfVoice||0,scheduleid:d.scheduleid,priority:d.priority||0});if(!n&&this.interruptScheduler){const{normalLayouts:d,interruptLayouts:h}=this.interruptScheduler.separateLayouts(l);if(h.length>0){s("[Schedule] Found",h.length,"interrupt layouts with shareOfVoice");const f=this.interruptScheduler.processInterrupts(d,h).map(m=>m.file);return s("[Schedule] Final layouts (with interrupts):",f),f}}const c=l.map(d=>d.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(),n=i-60*60*1e3,s=(this.playHistory.get(e)||[]).filter(r=>r>n);if(s.length>=t)return U.info(`Layout ${e} has reached max plays per hour (${s.length}/${t})`),!1;if(s.length>0){const r=36e5/t,a=Math.max(...s),l=i-a;if(l<r){const c=((r-l)/6e4).toFixed(1);return U.info(`Layout ${e} spacing: next play in ${c} min (${s.length}/${t} plays, ${Math.round(r/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,n=t.filter(o=>o>i);this.playHistory.set(e,n),U.info(`Recorded play for layout ${e} (${n.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}getScheduleQueue(e,t={}){var r;const i=this.getAllLayoutsAtTime(new Date),n=i.map(a=>`${a.file}:${a.priority}:${a.maxPlaysPerHour}`).sort().join("|");if(this._scheduleQueue&&this._queueLayoutSet===n)return this._scheduleQueue;const o=Qe(i,e,{defaultLayout:((r=this.schedule)==null?void 0:r.default)||null,defaultDuration:60,dynamicLayouts:t.dynamicLayouts||new Set}),s=this._queueLayoutSet;return this._scheduleQueue=o,this._queueLayoutSet=n,s!==n&&(this._queuePosition=0),o.queue.length>0&&(U.info(`[Schedule] Built queue: ${o.queue.length} entries, period ${o.periodSeconds}s (pos ${this._queuePosition})`),U.info(`[Schedule] Queue: ${o.queue.map(a=>`${a.layoutId}(${a.duration}s)`).join(" → ")}`)),o}popNextFromQueue(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);if(i.length===0)return null;const n=i[this._queuePosition%i.length];return this._queuePosition=(this._queuePosition+1)%i.length,n}peekNextInQueue(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);return i.length===0?null:i[this._queuePosition%i.length]}peekAfterNext(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);return i.length<=1?null:i[(this._queuePosition+1)%i.length]}_invalidateQueue(){this._scheduleQueue=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(),U.info("Play history cleared")}setLocation(e,t){this.playerLocation={latitude:e,longitude:t},U.info(`Location set: ${e}, ${t}`)}setDisplayProperties(e){this.displayProperties=e||{}}isWithinGeoFence(e,t=500){if(!this.playerLocation)return U.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 U.warn("Invalid geoLocation format:",e),!0;const n=i[0],o=i[1],s=i[2]||t,r=this.haversineDistance(this.playerLocation.latitude,this.playerLocation.longitude,n,o),a=r<=s;return U.info(`Geofence: ${r.toFixed(0)}m from (${n},${o}), radius ${s}m → ${a?"WITHIN":"OUTSIDE"}`),a}haversineDistance(e,t,i,n){const s=c=>c*Math.PI/180,r=s(i-e),a=s(n-t),l=Math.sin(r/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 gt=new Ye,H=q("schedule:interrupts");class ft{constructor(){this.interruptCommittedDurations=new Map}isInterrupt(e){return!!(e.shareOfVoice&&e.shareOfVoice>0)}resetCommittedDurations(){this.interruptCommittedDurations.clear(),H.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 H.debug("No interrupt layouts, returning normal layouts"),e;if(!e||e.length===0)return H.warn("No normal layouts available, interrupts will fill entire hour"),this.fillHourWithInterrupts(t);H.info(`Processing ${t.length} interrupt layouts with ${e.length} normal layouts`);for(const c of t){const d=c.id||c.file;this.interruptCommittedDurations.set(d,0)}const i=[];let n=0,o=0,s=!1;for(;!s;){if(o>=t.length){o=0;let d=!0;for(const h of t)if(!this.isInterruptDurationSatisfied(h)){d=!1;break}if(d){s=!0;break}}const c=t[o];if(!this.isInterruptDurationSatisfied(c)){const d=c.id||c.file;this.addCommittedDuration(d,c.duration),n+=c.duration,i.push(c)}o++}if(H.debug(`Resolved ${i.length} interrupt plays (${n}s total)`),n>=3600)return H.info("Interrupts fill entire hour (>= 3600s), no room for normal layouts"),i;const r=3600-n,a=this.fillTimeWithLayouts(e,r);H.debug(`Resolved ${a.length} normal plays (${r}s target)`);const l=this.interleaveLayouts(a,i);return H.info(`Final loop: ${l.length} layouts (${a.length} normal + ${i.length} interrupts)`),l}fillTimeWithLayouts(e,t){const i=[];let n=t,o=0;for(;n>0;){o>=e.length&&(o=0);const s=e[o];i.push(s),n-=s.duration,o++}return i}fillHourWithInterrupts(e){return this.fillTimeWithLayouts(e,3600)}interleaveLayouts(e,t){const i=[],n=Math.max(e.length,t.length),o=Math.ceil(1*n/e.length),s=Math.floor(1*n/t.length);H.debug(`Interleaving: pickCount=${n}, normalPick=${o}, interruptPick=${s}`);let r=0,a=0,l=0;for(let c=0;c<n;c++)c%o===0&&(r>=e.length&&(r=0),i.push(e[r]),l+=e[r].duration,r++),c%s===0&&a<t.length&&(i.push(t[a]),l+=t[a].duration,a++);for(;l<3600;)r>=e.length&&(r=0),i.push(e[r]),l+=e[r].duration,r++;return H.debug(`Interleaved ${i.length} layouts, total duration: ${l}s`),i}separateLayouts(e){const t=[],i=[];for(const n of e)this.isInterrupt(n)?i.push(n):t.push(n);return{normalLayouts:t,interruptLayouts:i}}}const G=q("schedule:overlays");class Ze{constructor(){this.overlays=[],this.displayProperties={},this.scheduleManager=null,G.debug("OverlayScheduler initialized")}setScheduleManager(e){this.scheduleManager=e}setDisplayProperties(e){this.displayProperties=e||{}}setOverlays(e){this.overlays=e||[],G.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)){G.debug(`Overlay ${i.file} not in time window`);continue}if(i.isGeoAware&&i.geoLocation&&this.scheduleManager&&!this.scheduleManager.isWithinGeoFence(i.geoLocation)){G.debug(`Overlay ${i.file} filtered by geofence`);continue}if(i.criteria&&i.criteria.length>0&&!re(i.criteria,{now:e,displayProperties:this.displayProperties})){G.debug(`Overlay ${i.file} filtered by criteria`);continue}t.push(i)}return t.sort((i,n)=>{const o=i.priority||0;return(n.priority||0)-o}),t.length>0&&G.info(`Active overlays: ${t.length}`),t}isTimeActive(e,t){const i=e.fromdt||e.fromDt?new Date(e.fromdt||e.fromDt):null,n=e.todt||e.toDt?new Date(e.todt||e.toDt):null;return!(i&&t<i||n&&t>n)}shouldCheckOverlays(e){return e?Date.now()-e>=6e4:!0}getOverlayByFile(e){return this.overlays.find(t=>t.file===e)||null}clear(){this.overlays=[],G.debug("Cleared all overlays")}processOverlays(e,t){return this.setOverlays(t),e}}new Ze;const F=q("DataConnector");class et extends xe{constructor(){super(),this.connectors=new Map}setConnectors(e){if(this.stopPolling(),this.connectors.clear(),!e||e.length===0){F.debug("No data connectors configured");return}for(const t of e){if(!t.dataKey||!t.url){F.warn("Skipping data connector with missing dataKey or url:",t);continue}this.connectors.set(t.dataKey,{config:t,data:null,timer:null,lastFetch:null}),F.info(`Registered data connector: ${t.dataKey} (interval: ${t.updateInterval}s)`)}F.info(`${this.connectors.size} data connector(s) configured`)}startPolling(){for(const[e,t]of this.connectors.entries()){const{config:i}=t,n=(i.updateInterval||300)*1e3;this.fetchData(t).catch(o=>{F.error(`Initial fetch failed for ${e}:`,o)}),t.timer=setInterval(()=>{this.fetchData(t).catch(o=>{F.error(`Polling fetch failed for ${e}:`,o)})},n),F.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,F.debug(`Stopped polling for ${e}`))}getData(e){const t=this.connectors.get(e);return t?t.data:(F.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:n}=t;F.debug(`Fetching data for ${i}: ${n}`);try{const o=await Te(n,{method:"GET",headers:{Accept:"application/json"}},{maxRetries:2,baseDelayMs:2e3});if(!o.ok){F.warn(`Data connector ${i} returned ${o.status}: ${o.statusText}`);return}const s=o.headers.get("Content-Type")||"";let r;s.includes("application/json")?r=await o.json():r=await o.text();const a=e.data;e.data=r,e.lastFetch=Date.now(),F.debug(`Data updated for ${i} (fetched at ${new Date(e.lastFetch).toISOString()})`),this.emit("data-updated",i,r),JSON.stringify(a)!==JSON.stringify(r)&&this.emit("data-changed",i,r)}catch(o){F.error(`Failed to fetch data for ${i}:`,o),this.emit("fetch-error",i,o)}}cleanup(){this.stopPolling(),this.connectors.clear(),this.removeAllListeners(),F.debug("DataConnectorManager cleaned up")}}const y=q("PlayerCore"),tt="xibo-offline-cache",it=1,Q="cache";function B(p){return parseInt(String(p).replace(".xlf",""),10)}function ge(){return new Promise((p,e)=>{const t=indexedDB.open(tt,it);t.onupgradeneeded=()=>{const i=t.result;i.objectStoreNames.contains(Q)||i.createObjectStore(Q)},t.onsuccess=()=>p(t.result),t.onerror=()=>e(t.error)})}class nt extends xe{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 et,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._executedCommands=new Set,this.displayCommands=null,this._faultReportingInterval=null,this._faultReportingSeconds=60,this._layoutBlacklist=new Map,this._blacklistThreshold=3,this._lastLayoutChangeTime=null,this._statusCode=2,this._dynamicLayouts=new Set,this.syncConfig=null,this.syncManager=null,this._layoutDurations=new Map,this.cacheAnalyzer=this.cache?new Re(this.cache):null,this._offlineCache={schedule:null,settings:null,requiredFiles:null},this._offlineDbReady=this._initOfflineCache()}async _initOfflineCache(){try{const e=await ge(),i=e.transaction(Q,"readonly").objectStore(Q),[n,o,s]=await Promise.all([new Promise(r=>{const a=i.get("schedule");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)}),new Promise(r=>{const a=i.get("settings");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)}),new Promise(r=>{const a=i.get("requiredFiles");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)})]);this._offlineCache={schedule:n,settings:o,requiredFiles:s},e.close(),y.info("Offline cache loaded from IndexedDB",n?"(has schedule)":"(empty)")}catch(e){y.warn("Failed to load offline cache from IndexedDB:",e)}}async _offlineSave(e,t){this._offlineCache[e]=t;try{const i=await ge(),n=i.transaction(Q,"readwrite");n.objectStore(Q).put(t,e),await new Promise((o,s)=>{n.oncomplete=o,n.onerror=()=>s(n.error)}),i.close()}catch(i){y.warn("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(y.warn("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),y.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),y.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();y.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)y.info(`Layout ${this.currentLayoutId} playing — queue updated in background, playback continues`),this.emit("layout-already-playing",this.currentLayoutId);else{const n=this.getNextLayout();n&&(y.info(`${i}switching to layout ${n.layoutId}`),this.emit("layout-prepare-request",n.layoutId))}else y.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,n,o,s,r,a;if(this.collecting){y.debug("Collection already in progress, skipping");return}this.collecting=!0;try{if(await this._offlineDbReady,y.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")}this.config.ensureXmrKeyPair&&await this.config.ensureXmrKeyPair(),y.debug("Collection step: registerDisplay");const l=await this.xmds.registerDisplay();if(y.info(`Display registered: ${l.code}${(e=l.tags)!=null&&e.length?`, tags: ${l.tags.join(", ")}`:""}`),y.debug("Register result:",JSON.stringify(l)),this._offlineSave("settings",l),this.offlineMode&&(this.offlineMode=!1,y.info("Back online — resuming normal collection"),this.emit("offline-mode",!1),this._normalCollectInterval&&(this._setCollectionTimer(this._normalCollectInterval),this._normalCollectInterval=null,this._offlineRetrySeconds=0)),this.displaySettings&&l.settings){const g=this.displaySettings.applySettings(l.settings);g.changed.includes("collectInterval")&&this.updateCollectionInterval(g.settings.collectInterval),l.settings.logLevel&&De(l.settings.logLevel)&&(y.info("Log level updated from CMS:",l.settings.logLevel),this.emit("log-level-changed",l.settings.logLevel))}if((t=this.schedule)!=null&&t.setDisplayProperties&&l.settings&&this.schedule.setDisplayProperties(l.settings),l.syncConfig&&(this.syncConfig=l.syncConfig,y.info("Sync group:",l.syncConfig.isLead?"LEAD":`follower → ${l.syncConfig.syncGroup}`,`(switchDelay: ${l.syncConfig.syncSwitchDelay}ms, videoPauseDelay: ${l.syncConfig.syncVideoPauseDelay}ms)`),this.emit("sync-config",l.syncConfig)),this._applyTagConfig(l.tags),l.commands&&l.commands.length>0){this.displayCommands={};for(const g of l.commands)this.displayCommands[g.commandCode]=g;y.debug("Display commands:",Object.keys(this.displayCommands).join(", "))}this.emit("register-complete",l),y.debug("Collection step: initializeXmr"),await this.initializeXmr(l);const c=l.checkRf||"",d=l.checkSchedule||"";if(!this._lastCheckRf||this._lastCheckRf!==c){this.resetBlacklist(),y.debug("Collection step: requiredFiles");const g=await this.xmds.requiredFiles(),f=g.files||g,m=g.purge||[];if(y.info("Required files:",f.length,m.length>0?`(+ ${m.length} purge)`:""),this._lastCheckRf=c,this.emit("files-received",f),this._offlineSave("requiredFiles",g),m.length>0&&this.emit("purge-request",m),!this._lastCheckSchedule||this._lastCheckSchedule!==d){y.debug("Collection step: schedule");const v=await this.xmds.schedule();y.info("Schedule received"),this._lastCheckSchedule=d,y.debug("Collection step: processing schedule"),this.emit("schedule-received",v),this.schedule.setSchedule(v),this._executedCommands.clear(),this.updateDataConnectors(),this._offlineSave("schedule",v),this.logUpcomingTimeline()}y.debug("Collection step: download-request + mediaInventory");const w=this.schedule.getCurrentLayouts(),{queue:L}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts}),C=[...new Set(L.map(v=>B(v.layoutId)))];if(this._lastRequiredFiles=f,(i=this.displaySettings)!=null&&i.isInDownloadWindow&&!this.displaySettings.isInDownloadWindow()){const v=(o=(n=this.displaySettings).getNextDownloadWindow)==null?void 0:o.call(n);y.info(`Outside download window, skipping downloads${v?` (next: ${v.toLocaleTimeString()})`:""}`)}else this.emit("download-request",{layoutOrder:C,files:f,layoutDependants:Object.fromEntries(this.schedule.getDependantsMap())});this.cacheAnalyzer&&this.cacheAnalyzer.analyze(f).then(v=>{this.emit("cache-analysis",v)}).catch(v=>y.warn("Cache analysis failed:",v)),this.submitMediaInventory(f)}else if(c&&y.info("RequiredFiles CRC unchanged, skipping download check"),this._lastCheckSchedule!==d){const g=await this.xmds.schedule();y.info("Schedule received (RF unchanged but schedule changed)"),this._lastCheckSchedule=d,this.emit("schedule-received",g),this.schedule.setSchedule(g),this._executedCommands.clear(),this.updateDataConnectors(),this._offlineSave("schedule",g)}else d&&y.info("Schedule CRC unchanged, skipping");await this._fetchWeatherData(),y.debug("Collection step: evaluateSchedule");const h=this.schedule.getCurrentLayouts();if(y.info("Current layouts:",h),this.emit("layouts-scheduled",h),this._evaluateAndSwitchLayout(h,""),this._processScheduledCommands(),h.length===0&&this.currentLayoutId&&((s=this.schedule.schedule)!=null&&s.default)){const g=B(this.schedule.schedule.default);y.info(`Current layout filtered by schedule, switching to default layout ${g}`),this.currentLayoutId=null,this.emit("layout-prepare-request",g)}(((r=l.settings)==null?void 0:r.statsEnabled)==="On"||((a=l.settings)==null?void 0:a.statsEnabled)==="1")&&(this.statsCollector?(y.info("Stats enabled, submitting proof of play"),this.emit("submit-stats-request")):y.warn("Stats enabled but no StatsCollector provided")),this.emit("submit-logs-request"),this.emit("submit-faults-request"),!this.collectionInterval&&l.settings&&this.setupCollectionInterval(l.settings),this._faultReportingInterval||this._startFaultReportingAgent(),this.logUpcomingTimeline(),this.emit("collection-complete")}catch(l){if(this.hasCachedData())return y.warn("Collection failed, falling back to cached data:",(l==null?void 0:l.message)||l),this.emit("collection-error",l),this.collectOffline();throw y.error("Collection error:",l),this.emit("collection-error",l),l}finally{this.collecting=!1}}async initializeXmr(e){var n,o,s,r;const t=((n=e.settings)==null?void 0:n.xmrWebSocketAddress)||((o=e.settings)==null?void 0:o.xmrNetworkAddress);if(!t){y.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://")){y.warn(`XMR address uses tcp:// protocol which is not supported by PWA players: ${t}`),y.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)){y.warn(`XMR address contains placeholder domain: ${t}`),y.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)||((r=e.settings)==null?void 0:r.serverKey)||this.config.serverKey;y.debug("XMR CMS Key:",i?"present":"missing"),this.xmr?this.xmr.isConnected()?y.debug("XMR already connected"):(y.info("XMR disconnected, attempting to reconnect..."),this.xmr.reconnectAttempts=0,await this.xmr.start(t,i),this.emit("xmr-reconnected",t)):(y.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))}_startFaultReportingAgent(){this._faultReportingInterval&&clearInterval(this._faultReportingInterval),y.info(`Fault reporting agent started (interval: ${this._faultReportingSeconds}s)`),this._faultReportingInterval=setInterval(()=>{this.emit("submit-faults-request")},this._faultReportingSeconds*1e3)}_setCollectionTimer(e){this.collectionInterval&&clearInterval(this.collectionInterval),this._currentCollectInterval=e,y.info(`Collection interval: ${e}s`),this.collectionInterval=setInterval(()=>{y.debug("Running scheduled collection cycle..."),this.collect().catch(t=>{y.error("Collection error:",t),this.emit("collection-error",t)})},e*1e3)}async requestLayoutChange(e){y.info(`Layout change requested: ${e}`),this.currentLayoutId=null,this.emit("layout-change-requested",e)}setCurrentLayout(e){this.currentLayoutId=e,this._lastLayoutChangeTime=new Date().toISOString(),this._statusCode=1,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(){var i;const e=this.schedule.popNextFromQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!e){const n=(i=this.schedule.schedule)==null?void 0:i.default;return n?{layoutId:B(n),layoutFile:n}:null}const t=B(e.layoutId);if(this.isLayoutBlacklisted(t)){const{queue:n}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});for(let o=0;o<n.length-1;o++){const s=this.schedule.popNextFromQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(s){const r=B(s.layoutId);if(!this.isLayoutBlacklisted(r))return{layoutId:r,layoutFile:s.layoutId}}}y.warn("All queued layouts are blacklisted, using current entry as fallback")}return{layoutId:t,layoutFile:e.layoutId}}peekNextLayout(){const e=this.schedule.peekNextInQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!e)return null;const t=B(e.layoutId);if(t===this.currentLayoutId){const i=this.schedule.peekAfterNext(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!i)return null;const n=B(i.layoutId);return n===this.currentLayoutId||this.isLayoutBlacklisted(n)?null:{layoutId:n,layoutFile:i.layoutId}}return this.isLayoutBlacklisted(t)?null:{layoutId:t,layoutFile:e.layoutId}}advanceToNextLayout(){if(this._layoutOverride){y.info("Layout override active, not advancing schedule");return}const e=this.getNextLayout();if(!e){if(this.currentLayoutId){y.info(`No layouts in queue, replaying ${this.currentLayoutId} to avoid blank screen`);const s=this.currentLayoutId;this.currentLayoutId=null,this.emit("layout-prepare-request",s)}else y.info("No layouts scheduled during advance"),this.emit("no-layouts-scheduled");return}const{layoutId:t,layoutFile:i}=e;if(this.syncManager&&this.schedule.isSyncEvent(i))if(this.isSyncLead()){y.info(`[Sync] Lead requesting coordinated layout change: ${t}`),this.syncManager.requestLayoutChange(t).catch(s=>{y.error("[Sync] Layout change failed:",s),this.emit("layout-prepare-request",t)});return}else{y.info("[Sync] Follower waiting for lead signal (not advancing independently)");return}t===this.currentLayoutId&&(y.info(`Next layout ${t} is same as current, triggering replay`),this.currentLayoutId=null);const{queue:n}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts}),o=this.schedule._queuePosition;y.info(`Advancing to layout ${t} (queue pos ${o}/${n.length})`),this.emit("layout-prepare-request",t)}advanceToPreviousLayout(){if(this._layoutOverride){y.info("Layout override active, not going back");return}const{queue:e}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(e.length<=1){y.info("Single or empty queue, nothing to go back to");return}this.schedule._queuePosition=(this.schedule._queuePosition-2+e.length)%e.length;const t=e[this.schedule._queuePosition];this.schedule._queuePosition=(this.schedule._queuePosition+1)%e.length;const i=B(t.layoutId);if(i===this.currentLayoutId){y.info("Previous layout is same as current, nothing to go back to");return}y.info(`Going back to layout ${i}`),this.emit("layout-prepare-request",i)}notifyMediaReady(e,t="media"){y.debug(`File ${e} ready (${t})`);for(const[i,n]of this.pendingLayouts.entries()){const o=t==="layout"&&i===parseInt(e),s=t==="media"&&n.includes(parseInt(e));(o||s)&&(y.debug(`${t} ${e} was needed by pending layout ${i}, checking if ready...`),this.emit("check-pending-layout",i,n))}}async notifyLayoutStatus(e){var t,i,n,o;try{const s={currentLayoutId:e,deviceName:((t=this.config)==null?void 0:t.displayName)||"",displayName:((i=this.config)==null?void 0:i.displayName)||"",lastCommandSuccess:this._lastCommandSuccess??!0,code:this._statusCode,lastLayoutChangeTime:this._lastLayoutChangeTime||new Date().toISOString()};(n=this.config)!=null&&n.latitude&&(s.latitude=this.config.latitude),(o=this.config)!=null&&o.longitude&&(s.longitude=this.config.longitude),await this.xmds.notifyStatus(s),this.emit("status-notified",e)}catch(s){y.warn("Failed to notify status:",s),this.emit("status-notify-failed",e,s)}}reportGeoLocation(e){var n;const t=parseFloat(e==null?void 0:e.latitude),i=parseFloat(e==null?void 0:e.longitude);if(isNaN(t)||isNaN(i)){y.warn("reportGeoLocation: invalid coordinates",e);return}y.info(`Geo location from CMS: ${t.toFixed(4)}, ${i.toFixed(4)}`),(n=this.schedule)!=null&&n.setLocation&&this.schedule.setLocation(t,i),this.emit("location-updated",{latitude:t,longitude:i,source:"cms"}),this.checkSchedule()}async requestGeoLocation(){var n;const e=await this._tryBrowserGeolocation();if(e)return this._applyLocation(e.latitude,e.longitude,"browser");const t=(n=this.config)==null?void 0:n.googleGeoApiKey;if(t){const o=await this._tryGoogleGeolocation(t);if(o)return this._applyLocation(o.latitude,o.longitude,"google-api")}const i=await this._tryIpGeolocation();return i?this._applyLocation(i.latitude,i.longitude,"ip-geolocation"):(y.warn("All geolocation methods failed"),null)}_applyTagConfig(e){if(!Array.isArray(e)||e.length===0)return;const t={geoApiKey:"googleGeoApiKey"};for(const i of e){const n=i.indexOf("|");if(n===-1)continue;const o=i.substring(0,n),s=i.substring(n+1),r=t[o];r&&s&&this.config&&(y.info(`Config from CMS tag: ${o} → ${r}`),this.config[r]=s)}}_applyLocation(e,t,i){var n;return y.info(`Geolocation (${i}): ${e.toFixed(4)}, ${t.toFixed(4)}`),(n=this.schedule)!=null&&n.setLocation&&this.schedule.setLocation(e,t),this.emit("location-updated",{latitude:e,longitude:t,source:i}),this.checkSchedule(),{latitude:e,longitude:t}}async _tryBrowserGeolocation(){if(typeof navigator>"u"||!navigator.geolocation)return null;try{const e=await new Promise((t,i)=>{navigator.geolocation.getCurrentPosition(t,i,{timeout:1e4,maximumAge:3e5,enableHighAccuracy:!1})});return{latitude:e.coords.latitude,longitude:e.coords.longitude}}catch(e){return y.warn("Browser geolocation failed:",(e==null?void 0:e.message)||e),null}}async _tryGoogleGeolocation(e){var t,i;try{const n=await fetch(`https://www.googleapis.com/geolocation/v1/geolocate?key=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({considerIp:!0}),signal:AbortSignal.timeout(5e3)});if(!n.ok)return y.warn(`Google Geolocation API returned ${n.status}`),null;const o=await n.json();return((t=o.location)==null?void 0:t.lat)!=null&&((i=o.location)==null?void 0:i.lng)!=null?{latitude:o.location.lat,longitude:o.location.lng}:null}catch(n){return y.warn("Google Geolocation API failed:",(n==null?void 0:n.message)||n),null}}async _tryIpGeolocation(){const e=[{url:"https://ipapi.co/json/",parse:t=>t.latitude!=null&&t.longitude!=null?{latitude:t.latitude,longitude:t.longitude}:null},{url:"https://freeipapi.com/api/json",parse:t=>t.latitude!=null&&t.longitude!=null?{latitude:t.latitude,longitude:t.longitude}:null}];for(const t of e)try{const i=await fetch(t.url,{signal:AbortSignal.timeout(5e3)});if(!i.ok)continue;const n=await i.json(),o=t.parse(n);if(o)return o}catch(i){y.warn(`IP geolocation (${t.url}) failed:`,(i==null?void 0:i.message)||i)}return null}checkSchedule(){const e=this.schedule.getCurrentLayouts();this.emit("layouts-scheduled",e),this._evaluateAndSwitchLayout(e,"")}async captureScreenshot(){y.info("Screenshot requested"),this.emit("screenshot-request")}async changeLayout(e,t){y.info("Layout change requested via XMR:",e);const i=parseInt(e,10),n=(t==null?void 0:t.duration)||0,o=(t==null?void 0:t.changeMode)||"replace";this._layoutOverride={layoutId:i,type:"change",duration:n,changeMode:o},this.currentLayoutId=null,this.emit("layout-prepare-request",i),n>0&&setTimeout(()=>{var s;((s=this._layoutOverride)==null?void 0:s.layoutId)===i&&(y.info(`Layout override duration expired (${n}s), reverting to schedule`),this.revertToSchedule())},n*1e3)}async overlayLayout(e,t){y.info("Overlay layout requested via XMR:",e);const i=parseInt(e,10),n=(t==null?void 0:t.duration)||0;this._layoutOverride={layoutId:i,type:"overlay",duration:n},this.emit("overlay-layout-request",i),n>0&&setTimeout(()=>{var o;((o=this._layoutOverride)==null?void 0:o.layoutId)===i&&(y.info(`Overlay duration expired (${n}s), reverting to schedule`),this.revertToSchedule())},n*1e3)}async revertToSchedule(){y.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=B(t);this.emit("layout-prepare-request",i)}else this.emit("no-layouts-scheduled")}async purgeAll(){return y.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(y.info("Execute command requested:",e),!t||!t[e]){y.warn("Unknown command code:",e),this._lastCommandSuccess=!1,this.emit("command-result",{code:e,success:!1,reason:"Unknown command"});return}const i=t[e],n=i.commandString||i.value||"";if(n.startsWith("http|")){const o=n.split("|"),s=o[1],r=o[2]||"application/json";try{const a=await fetch(s,{method:"POST",headers:{"Content-Type":r}}),l=a.ok;this._lastCommandSuccess=l,y.info(`HTTP command ${e} result: ${a.status}`),this.emit("command-result",{code:e,success:l,status:a.status})}catch(a){this._lastCommandSuccess=!1,y.error(`HTTP command ${e} failed:`,a),this.emit("command-result",{code:e,success:!1,reason:a.message})}}else y.info("Delegating non-HTTP command to platform layer:",e),this.emit("execute-native-command",{code:e,commandString:n})}triggerWebhook(e){y.info("Webhook trigger from XMR:",e),this.handleTrigger(e)}refreshDataConnectors(){y.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),n=`<files>${e.filter(o=>["media","layout","resource","dependency","widget"].includes(o.type)).map(o=>{const s=o.complete!==void 0?o.complete?"1":"0":"1",r=o.fileType?` fileType="${o.fileType}"`:"";return`<file type="${o.type}" id="${o.id}" complete="${s}" md5="${o.md5||""}" lastChecked="${t}"${r}/>`}).join("")}</files>`;await this.xmds.mediaInventory(n),y.info(`Media inventory submitted: ${e.length} files`),this.emit("media-inventory-submitted",e.length)}catch(t){y.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(n){y.warn("BlackList failed:",n)}}reportLayoutFailure(e,t){const i=Number(e),n=this._layoutBlacklist.get(i)||{failures:0,blacklisted:!1,reason:""};n.failures++,n.reason=t,!n.blacklisted&&n.failures>=this._blacklistThreshold?(n.blacklisted=!0,y.warn(`Layout ${i} blacklisted after ${n.failures} consecutive failures: ${t}`),this.emit("layout-blacklisted",{layoutId:i,reason:t,failures:n.failures}),this.blackList(i,"layout",t)):n.blacklisted||y.info(`Layout ${i} failure ${n.failures}/${this._blacklistThreshold}: ${t}`),this._layoutBlacklist.set(i,n)}reportLayoutSuccess(e){const t=Number(e);if(this._layoutBlacklist.has(t)){const i=this._layoutBlacklist.get(t);this._layoutBlacklist.delete(t),i.blacklisted&&(y.info(`Layout ${t} removed from blacklist (rendered successfully)`),this.emit("layout-unblacklisted",{layoutId:t}))}}isLayoutBlacklisted(e){const t=this._layoutBlacklist.get(Number(e));return(t==null?void 0:t.blacklisted)===!0}getBlacklistedLayouts(){const e=[];for(const[t,i]of this._layoutBlacklist)i.blacklisted&&e.push(t);return e}resetBlacklist(){this._layoutBlacklist.size>0&&(y.info(`Blacklist reset (${this._layoutBlacklist.size} entries cleared)`),this._layoutBlacklist.clear(),this.emit("blacklist-reset"))}isLayoutOverridden(){return this._layoutOverride!==null}handleTrigger(e){const t=this.schedule.findActionByTrigger(e);if(!t){y.debug("No scheduled action matches trigger:",e);return}switch(y.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:y.warn("Unknown action type:",t.actionType)}}updateDataConnectors(){const e=this.schedule.getDataConnectors();e.length>0&&y.info(`Configuring ${e.length} data connector(s)`),this.dataConnectorManager.setConnectors(e),e.length>0&&(this.dataConnectorManager.startPolling(),this.emit("data-connectors-started",e.length))}_processScheduledCommands(){var i;if(!((i=this.schedule)!=null&&i.getCommands))return;const e=this.schedule.getCommands();if(e.length===0)return;const t=new Date;for(const n of e){if(!n.code||!n.date)continue;const o=`${n.code}|${n.date}`;if(this._executedCommands.has(o))continue;const s=new Date(n.date);if(isNaN(s.getTime())){y.warn("Scheduled command has invalid date:",n.date);continue}t>=s&&(y.info(`Executing scheduled command: ${n.code} (scheduled: ${n.date})`),this._executedCommands.add(o),n.code==="collectNow"?setTimeout(()=>this.collectNow().catch(r=>y.error("collectNow command failed:",r)),0):this.emit("scheduled-command",n))}}async _fetchWeatherData(){var e,t;if(!(!((e=this.xmds)!=null&&e.getWeather)||!((t=this.schedule)!=null&&t.setWeatherData)))try{const i=await this.xmds.getWeather(),n=typeof i=="string"?JSON.parse(i):i;this.schedule.setWeatherData(n),y.info("Weather data updated:",Object.keys(n).join(", "))}catch(i){y.warn("GetWeather failed (non-critical):",(i==null?void 0:i.message)||i)}}getDataConnectorManager(){return this.dataConnectorManager}setSyncManager(e){this.syncManager=e,y.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 n=0;for(const r of i){const a=B(r);try{const l=await this.cache.get("layout",a);if(l){const{duration:c,isDynamic:d}=ze(l);this._layoutDurations.has(r)||this._layoutDurations.set(r,c),this._layoutDurations.has(String(a))||this._layoutDurations.set(String(a),c),d&&this._dynamicLayouts.add(r),n++}}catch(l){y.debug(`Could not parse duration for layout ${a}:`,l.message)}}n>0&&y.info(`[Timeline] Parsed durations for ${n} layouts`)}logUpcomingTimeline(){if(this._layoutDurations.size===0||!this.schedule.getLayoutsAtTime)return;const e=Ke(this.schedule,this._layoutDurations,{currentLayoutStartedAt:this._lastLayoutChangeTime?new Date(this._lastLayoutChangeTime):null});if(e.length===0)return;const t=e.slice(0,20).map(i=>{const n=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` ${n}-${o} Layout ${i.layoutFile} (${i.duration}s)${i.isDefault?" [default]":""}`});y.info(`[Timeline] Next ${e.length} plays:
|
|
621
|
+
}`}}const te=q("schedule:criteria"),qe=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],he={weatherTemp:"temperature",weatherHumidity:"humidity",weatherWindSpeed:"windSpeed",weatherCondition:"condition",weatherCloudCover:"cloudCover"};function He(p,e,t={},i={}){switch(p){case"dayOfWeek":return qe[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:if(he[p]){const n=he[p];return i[n]!==void 0?String(i[n]):(te.debug(`Weather metric "${p}" requested but no weather data available`),null)}return t[p]!==void 0?String(t[p]):(te.debug(`Unknown metric: ${p}`),null)}}function Be(p,e,t,i){if(p===null)return!1;if(i==="number"){const s=parseFloat(p),r=parseFloat(t);if(isNaN(s)||isNaN(r))return!1;switch(e){case"equals":return s===r;case"notEquals":return s!==r;case"greaterThan":return s>r;case"greaterThanOrEquals":return s>=r;case"lessThan":return s<r;case"lessThanOrEquals":return s<=r;default:return!1}}const n=p.toLowerCase(),o=t.toLowerCase();switch(e){case"equals":return n===o;case"notEquals":return n!==o;case"contains":return n.includes(o);case"notContains":return!n.includes(o);case"startsWith":return n.startsWith(o);case"endsWith":return n.endsWith(o);case"in":return o.split(",").map(s=>s.trim().toLowerCase()).includes(n);case"greaterThan":return n>o;case"lessThan":return n<o;default:return te.debug(`Unknown condition: ${e}`),!1}}function re(p,e={}){if(!p||p.length===0)return!0;const t=e.now||new Date,i=e.displayProperties||{},n=e.weatherData||{};for(const o of p){const s=He(o.metric,t,i,n);if(!Be(s,o.condition,o.value,o.type))return te.debug(`Criteria failed: ${o.metric} ${o.condition} "${o.value}" (actual: "${s}")`),!1}return!0}function ze(p){const t=new DOMParser().parseFromString(p,"text/xml").querySelector("layout");if(!t)return{duration:60,isDynamic:!1};const i=parseInt(t.getAttribute("duration")||"0",10);if(i>0)return{duration:i,isDynamic:!1};let n=0,o=!1;for(const r of t.querySelectorAll("region")){let a=0;for(const l of r.querySelectorAll("media")){const c=parseInt(l.getAttribute("duration")||"0",10),u=parseInt(l.getAttribute("useDuration")||"1",10);c>0&&u!==0?a+=c:(a+=60,o=!0)}n=Math.max(n,a)}return{duration:n>0?n:60,isDynamic:o}}function ae(p,e){if(p.length!==e.length)return!1;for(let t=0;t<p.length;t++)if(p[t]!==e[t])return!1;return!0}function je(p,e,t){if(!e||e===0)return!0;const i=t-36e5,n=p.filter(o=>o>i);if(n.length>=e)return!1;if(n.length>0){const o=36e5/e,s=Math.max(...n);if(t-s<o)return!1}return!0}function Ve(p){const e=new Map;if(!p)return e;for(const[t,i]of p){const n=`${t}.xlf`;e.set(n,[...i])}return e}function ie(p,e,t){const i=p.filter(o=>{if(!o.maxPlaysPerHour||o.maxPlaysPerHour===0)return!0;const s=e.get(o.file)||[];return je(s,o.maxPlaysPerHour,t)});if(i.length===0)return[];const n=Math.max(...i.map(o=>o.priority));return i.filter(o=>o.priority===n).map(o=>o.file)}function Ke(p,e,t={}){var f;const i=t.from||new Date,n=t.hours||2,o=new Date(i.getTime()+n*36e5),s=t.defaultDuration||60,r=t.currentLayoutStartedAt||null,a=[];let l=new Date(i),c=!0;const u=typeof p.getAllLayoutsAtTime=="function",h=Ve(p.playHistory),g=500;for(;l<o&&a.length<g;){const m=l.getTime();let w,L=null;if(u){const C=p.getAllLayoutsAtTime(l);w=C.length>0?ie(C,h,m):[],C.length>w.length&&(L=C.filter(v=>!w.includes(v.file)).map(v=>({file:v.file,priority:v.priority})))}else w=p.getLayoutsAtTime(l);if(w.length===0){const C=(f=p.schedule)==null?void 0:f.default;if(C){const v=e.get(C)||s;a.push({layoutFile:C,startTime:new Date(l),endTime:new Date(m+v*1e3),duration:v,isDefault:!0}),l=new Date(m+v*1e3)}else l=new Date(m+6e4);continue}for(let C=0;C<w.length&&l<o&&a.length<g;C++){const v=w[C];let x=e.get(v)||s;if(c&&r){const S=(i.getTime()-r.getTime())/1e3;x=Math.max(1,Math.round(x-S)),c=!1}const k=l.getTime()+x*1e3,_={layoutFile:v,startTime:new Date(l),endTime:new Date(k),duration:x,isDefault:!1};if(L&&L.length>0&&(_.hidden=L),a.push(_),u&&(h.has(v)||h.set(v,[]),h.get(v).push(l.getTime())),l=new Date(k),u){const S=p.getAllLayoutsAtTime(l),b=S.length>0?ie(S,h,l.getTime()):[];if(!ae(w,b))break}else{const S=p.getLayoutsAtTime(l);if(!ae(w,S))break}}}return a}function Xe(p,e){for(p=Math.abs(Math.round(p)),e=Math.abs(Math.round(e));e;)[p,e]=[e,p%e];return p}function Ge(p,e){return p===0||e===0?0:Math.abs(Math.round(p)*Math.round(e))/Xe(p,e)}function Je(p){return p.reduce((e,t)=>Ge(e,t),1)}function Qe(p,e,t={}){const{defaultLayout:i=null,defaultDuration:n=60}=t;if(p.length===0&&!i)return{queue:[],periodSeconds:0};const o=p.filter(h=>h.maxPlaysPerHour>0);let s;if(o.length>0){const h=o.map(g=>Math.round(3600/g.maxPlaysPerHour));s=Je(h),s>7200&&(s=7200)}else s=p.reduce((g,f)=>g+(e.get(f.file)||n),0)+(i&&!p.some(g=>g.file===i)?e.get(i)||n:0)||n;const r=[],a=new Map;let l=0;const c=s*1e3,u=500;for(;l<c&&r.length<u;){const h=ie(p,a,l);if(h.length===0){if(i){const g=e.get(i)||n;r.push({layoutId:i,duration:g}),l+=g*1e3}else l+=6e4;continue}for(let g=0;g<h.length&&l<c&&r.length<u;g++){const f=h[g],m=e.get(f)||n;r.push({layoutId:f,duration:m}),a.has(f)||a.set(f,[]),a.get(f).push(l),l+=m*1e3;const w=ie(p,a,l);if(!ae(h,w))break}}if(r.length===0&&i){const h=e.get(i)||n;r.push({layoutId:i,duration:h})}return{queue:r,periodSeconds:s}}const U=q("Schedule");class Ye{constructor(e={}){this.schedule=null,this.playHistory=new Map,this.interruptScheduler=e.interruptScheduler||null,this.displayProperties=e.displayProperties||{},this.weatherData={},this.playerLocation=null,this._layoutMetadata=new Map,this._scheduleQueue=null,this._queuePosition=0,this._queueLayoutSet=null}setSchedule(e){this.schedule=e,this._invalidateQueue()}setWeatherData(e){this.weatherData=e||{}}getDataConnectors(){var e;return((e=this.schedule)==null?void 0:e.dataConnectors)||[]}getDependantsMap(){const e=new Map;if(!this.schedule)return e;const t=this.schedule.dependants||[],i=n=>{const o=parseInt(String(n.file||n.id).replace(".xlf",""),10),s=[...t,...n.dependants||[]];s.length>0&&e.set(o,s)};if(this.schedule.layouts)for(const n of this.schedule.layouts)i(n);if(this.schedule.campaigns)for(const n of this.schedule.campaigns)for(const o of n.layouts)i(o);return e}isRecurringScheduleActive(e,t){if(!e.recurrenceType)return!0;if(e.recurrenceRange){const i=new Date(e.recurrenceRange);if(t>i)return!1}switch(e.recurrenceType){case"Week":{if(e.recurrenceRepeatsOn){const i=this.getIsoDayOfWeek(t);if(!e.recurrenceRepeatsOn.split(",").map(o=>parseInt(o.trim())).includes(i))return!1}return!0}case"Day":{const i=e.recurrenceDetail||1;if(i>1&&e.fromdt){const n=new Date(e.fromdt),o=t.getTime()-n.getTime(),s=Math.floor(o/864e5);if(s<0||s%i!==0)return!1}return!0}case"Month":{if(e.recurrenceRepeatsOn){const n=e.recurrenceRepeatsOn.split(",").map(s=>parseInt(s.trim())),o=t.getDate();if(!n.includes(o))return!1}const i=e.recurrenceDetail||1;if(i>1&&e.fromdt){const n=new Date(e.fromdt),o=(t.getFullYear()-n.getFullYear())*12+t.getMonth()-n.getMonth();if(o<0||o%i!==0)return!1}return!0}default:return U.debug(`Unsupported recurrence type: ${e.recurrenceType}`),!0}}getIsoDayOfWeek(e){const t=e.getDay();return t===0?7:t}isTimeActive(e,t){const i=e.fromdt?new Date(e.fromdt):null,n=e.todt?new Date(e.todt):null;if(e.recurrenceType==="Week"||e.recurrenceType==="Day"||e.recurrenceType==="Month"){if(i&&n){const o=t.getHours()*3600+t.getMinutes()*60+t.getSeconds(),s=i.getHours()*3600+i.getMinutes()*60+i.getSeconds(),r=n.getHours()*3600+n.getMinutes()*60+n.getSeconds();return s<=r?o>=s&&o<=r:o>=s||o<=r}return!0}return!(i&&t<i||n&&t>n)}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 n of this.schedule.layouts)this.isRecurringScheduleActive(n,t)&&this.isTimeActive(n,t)&&(n.criteria&&n.criteria.length>0&&!re(n.criteria,{now:t,displayProperties:this.displayProperties,weatherData:this.weatherData})||n.isGeoAware&&n.geoLocation&&!this.isWithinGeoFence(n.geoLocation)||i.push({file:n.file,priority:n.priority||0,maxPlaysPerHour:n.maxPlaysPerHour||0}));if(this.schedule.campaigns){for(const n of this.schedule.campaigns)if(this.isRecurringScheduleActive(n,t)&&this.isTimeActive(n,t))for(const o of n.layouts)i.push({file:o.file,priority:n.priority||0,maxPlaysPerHour:o.maxPlaysPerHour||0})}return i}detectConflicts(e={}){const t=e.from||new Date,i=e.hours||24,n=new Date(t.getTime()+i*36e5),o=6e4,s=[];let r=null;for(let a=t.getTime();a<n.getTime();a+=o){const l=new Date(a),c=this.getAllLayoutsAtTime(l);if(c.length===0){r&&(s.push(r),r=null);continue}const u=Math.max(...c.map(w=>w.priority)),h=c.filter(w=>w.priority<u);if(h.length===0){r&&(s.push(r),r=null);continue}const g=c.filter(w=>w.priority===u),f=g.map(w=>w.file).sort().join(","),m=h.map(w=>`${w.file}:${w.priority}`).sort().join(",");r&&r._winnerKey===f&&r._hiddenKey===m?r.endTime=new Date(a+o):(r&&s.push(r),r={startTime:new Date(a),endTime:new Date(a+o),winner:{file:g[0].file,priority:u},hidden:h.map(w=>({file:w.file,priority:w.priority})),_winnerKey:f,_hiddenKey:m})}r&&s.push(r);for(const a of s)delete a._winnerKey,delete a._hiddenKey;return s}_getLayoutsAt(e,t={}){if(!this.schedule)return[];const{skipRateLimiting:i=!1,skipInterrupts:n=!1,quiet:o=!1}=t,s=o?()=>{}:(...u)=>U.info(...u),r=[];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),r.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&&!re(u.criteria,{now:e,displayProperties:this.displayProperties,weatherData:this.weatherData})){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}r.push({type:"layout",priority:u.priority||0,layouts:[u],layoutId:u.id})}}if(r.length===0)return this.schedule.default?[this.schedule.default]:[];let a=Math.max(...r.map(u=>u.priority));s("[Schedule] Max priority:",a,"from",r.length,"active items");let l=[];for(const u of r)u.priority===a?(s("[Schedule] Including priority",u.priority,"layouts:",u.layouts.map(h=>h.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(!n&&this.interruptScheduler){const{normalLayouts:u,interruptLayouts:h}=this.interruptScheduler.separateLayouts(l);if(h.length>0){s("[Schedule] Found",h.length,"interrupt layouts with shareOfVoice");const f=this.interruptScheduler.processInterrupts(u,h).map(m=>m.file);return s("[Schedule] Final layouts (with interrupts):",f),f}}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(),n=i-60*60*1e3,s=(this.playHistory.get(e)||[]).filter(r=>r>n);if(s.length>=t)return U.info(`Layout ${e} has reached max plays per hour (${s.length}/${t})`),!1;if(s.length>0){const r=36e5/t,a=Math.max(...s),l=i-a;if(l<r){const c=((r-l)/6e4).toFixed(1);return U.info(`Layout ${e} spacing: next play in ${c} min (${s.length}/${t} plays, ${Math.round(r/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,n=t.filter(o=>o>i);this.playHistory.set(e,n),U.info(`Recorded play for layout ${e} (${n.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}getScheduleQueue(e,t={}){var r;const i=this.getAllLayoutsAtTime(new Date),n=i.map(a=>`${a.file}:${a.priority}:${a.maxPlaysPerHour}`).sort().join("|");if(this._scheduleQueue&&this._queueLayoutSet===n)return this._scheduleQueue;const o=Qe(i,e,{defaultLayout:((r=this.schedule)==null?void 0:r.default)||null,defaultDuration:60,dynamicLayouts:t.dynamicLayouts||new Set}),s=this._queueLayoutSet;return this._scheduleQueue=o,this._queueLayoutSet=n,s!==n&&(this._queuePosition=0),o.queue.length>0&&(U.info(`[Schedule] Built queue: ${o.queue.length} entries, period ${o.periodSeconds}s (pos ${this._queuePosition})`),U.info(`[Schedule] Queue: ${o.queue.map(a=>`${a.layoutId}(${a.duration}s)`).join(" → ")}`)),o}popNextFromQueue(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);if(i.length===0)return null;const n=i[this._queuePosition%i.length];return this._queuePosition=(this._queuePosition+1)%i.length,n}peekNextInQueue(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);return i.length===0?null:i[this._queuePosition%i.length]}peekAfterNext(e,t={}){const{queue:i}=this.getScheduleQueue(e,t);return i.length<=1?null:i[(this._queuePosition+1)%i.length]}_invalidateQueue(){this._scheduleQueue=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(),U.info("Play history cleared")}setLocation(e,t){this.playerLocation={latitude:e,longitude:t},U.info(`Location set: ${e}, ${t}`)}setDisplayProperties(e){this.displayProperties=e||{}}isWithinGeoFence(e,t=500){if(!this.playerLocation)return U.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 U.warn("Invalid geoLocation format:",e),!0;const n=i[0],o=i[1],s=i[2]||t,r=this.haversineDistance(this.playerLocation.latitude,this.playerLocation.longitude,n,o),a=r<=s;return U.info(`Geofence: ${r.toFixed(0)}m from (${n},${o}), radius ${s}m → ${a?"WITHIN":"OUTSIDE"}`),a}haversineDistance(e,t,i,n){const s=c=>c*Math.PI/180,r=s(i-e),a=s(n-t),l=Math.sin(r/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 gt=new Ye,H=q("schedule:interrupts");class ft{constructor(){this.interruptCommittedDurations=new Map}isInterrupt(e){return!!(e.shareOfVoice&&e.shareOfVoice>0)}resetCommittedDurations(){this.interruptCommittedDurations.clear(),H.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 H.debug("No interrupt layouts, returning normal layouts"),e;if(!e||e.length===0)return H.warn("No normal layouts available, interrupts will fill entire hour"),this.fillHourWithInterrupts(t);H.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 n=0,o=0,s=!1;for(;!s;){if(o>=t.length){o=0;let u=!0;for(const h of t)if(!this.isInterruptDurationSatisfied(h)){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),n+=c.duration,i.push(c)}o++}if(H.debug(`Resolved ${i.length} interrupt plays (${n}s total)`),n>=3600)return H.info("Interrupts fill entire hour (>= 3600s), no room for normal layouts"),i;const r=3600-n,a=this.fillTimeWithLayouts(e,r);H.debug(`Resolved ${a.length} normal plays (${r}s target)`);const l=this.interleaveLayouts(a,i);return H.info(`Final loop: ${l.length} layouts (${a.length} normal + ${i.length} interrupts)`),l}fillTimeWithLayouts(e,t){const i=[];let n=t,o=0;for(;n>0;){o>=e.length&&(o=0);const s=e[o];i.push(s),n-=s.duration,o++}return i}fillHourWithInterrupts(e){return this.fillTimeWithLayouts(e,3600)}interleaveLayouts(e,t){const i=[],n=Math.max(e.length,t.length),o=Math.ceil(1*n/e.length),s=Math.floor(1*n/t.length);H.debug(`Interleaving: pickCount=${n}, normalPick=${o}, interruptPick=${s}`);let r=0,a=0,l=0;for(let c=0;c<n;c++)c%o===0&&(r>=e.length&&(r=0),i.push(e[r]),l+=e[r].duration,r++),c%s===0&&a<t.length&&(i.push(t[a]),l+=t[a].duration,a++);for(;l<3600;)r>=e.length&&(r=0),i.push(e[r]),l+=e[r].duration,r++;return H.debug(`Interleaved ${i.length} layouts, total duration: ${l}s`),i}separateLayouts(e){const t=[],i=[];for(const n of e)this.isInterrupt(n)?i.push(n):t.push(n);return{normalLayouts:t,interruptLayouts:i}}}const G=q("schedule:overlays");class Ze{constructor(){this.overlays=[],this.displayProperties={},this.scheduleManager=null,G.debug("OverlayScheduler initialized")}setScheduleManager(e){this.scheduleManager=e}setDisplayProperties(e){this.displayProperties=e||{}}setOverlays(e){this.overlays=e||[],G.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)){G.debug(`Overlay ${i.file} not in time window`);continue}if(i.isGeoAware&&i.geoLocation&&this.scheduleManager&&!this.scheduleManager.isWithinGeoFence(i.geoLocation)){G.debug(`Overlay ${i.file} filtered by geofence`);continue}if(i.criteria&&i.criteria.length>0&&!re(i.criteria,{now:e,displayProperties:this.displayProperties})){G.debug(`Overlay ${i.file} filtered by criteria`);continue}t.push(i)}return t.sort((i,n)=>{const o=i.priority||0;return(n.priority||0)-o}),t.length>0&&G.info(`Active overlays: ${t.length}`),t}isTimeActive(e,t){const i=e.fromdt||e.fromDt?new Date(e.fromdt||e.fromDt):null,n=e.todt||e.toDt?new Date(e.todt||e.toDt):null;return!(i&&t<i||n&&t>n)}shouldCheckOverlays(e){return e?Date.now()-e>=6e4:!0}getOverlayByFile(e){return this.overlays.find(t=>t.file===e)||null}clear(){this.overlays=[],G.debug("Cleared all overlays")}processOverlays(e,t){return this.setOverlays(t),e}}new Ze;const F=q("DataConnector");class et extends _e{constructor(){super(),this.connectors=new Map}setConnectors(e){if(this.stopPolling(),this.connectors.clear(),!e||e.length===0){F.debug("No data connectors configured");return}for(const t of e){if(!t.dataKey||!t.url){F.warn("Skipping data connector with missing dataKey or url:",t);continue}this.connectors.set(t.dataKey,{config:t,data:null,timer:null,lastFetch:null}),F.info(`Registered data connector: ${t.dataKey} (interval: ${t.updateInterval}s)`)}F.info(`${this.connectors.size} data connector(s) configured`)}startPolling(){for(const[e,t]of this.connectors.entries()){const{config:i}=t,n=(i.updateInterval||300)*1e3;this.fetchData(t).catch(o=>{F.error(`Initial fetch failed for ${e}:`,o)}),t.timer=setInterval(()=>{this.fetchData(t).catch(o=>{F.error(`Polling fetch failed for ${e}:`,o)})},n),F.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,F.debug(`Stopped polling for ${e}`))}getData(e){const t=this.connectors.get(e);return t?t.data:(F.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:n}=t;F.debug(`Fetching data for ${i}: ${n}`);try{const o=await Te(n,{method:"GET",headers:{Accept:"application/json"}},{maxRetries:2,baseDelayMs:2e3});if(!o.ok){F.warn(`Data connector ${i} returned ${o.status}: ${o.statusText}`);return}const s=o.headers.get("Content-Type")||"";let r;s.includes("application/json")?r=await o.json():r=await o.text();const a=e.data;e.data=r,e.lastFetch=Date.now(),F.debug(`Data updated for ${i} (fetched at ${new Date(e.lastFetch).toISOString()})`),this.emit("data-updated",i,r),JSON.stringify(a)!==JSON.stringify(r)&&this.emit("data-changed",i,r)}catch(o){F.error(`Failed to fetch data for ${i}:`,o),this.emit("fetch-error",i,o)}}cleanup(){this.stopPolling(),this.connectors.clear(),this.removeAllListeners(),F.debug("DataConnectorManager cleaned up")}}const y=q("PlayerCore"),tt="xibo-offline-cache",it=1,Q="cache";function B(p){return parseInt(String(p).replace(".xlf",""),10)}function ge(){return new Promise((p,e)=>{const t=indexedDB.open(tt,it);t.onupgradeneeded=()=>{const i=t.result;i.objectStoreNames.contains(Q)||i.createObjectStore(Q)},t.onsuccess=()=>p(t.result),t.onerror=()=>e(t.error)})}class nt extends _e{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 et,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._executedCommands=new Set,this.displayCommands=null,this._faultReportingInterval=null,this._faultReportingSeconds=60,this._layoutBlacklist=new Map,this._blacklistThreshold=3,this._lastLayoutChangeTime=null,this._statusCode=2,this._dynamicLayouts=new Set,this.syncConfig=null,this.syncManager=null,this._layoutDurations=new Map,this.cacheAnalyzer=this.cache?new Re(this.cache):null,this._offlineCache={schedule:null,settings:null,requiredFiles:null},this._offlineDbReady=this._initOfflineCache()}async _initOfflineCache(){try{const e=await ge(),i=e.transaction(Q,"readonly").objectStore(Q),[n,o,s]=await Promise.all([new Promise(r=>{const a=i.get("schedule");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)}),new Promise(r=>{const a=i.get("settings");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)}),new Promise(r=>{const a=i.get("requiredFiles");a.onsuccess=()=>r(a.result??null),a.onerror=()=>r(null)})]);this._offlineCache={schedule:n,settings:o,requiredFiles:s},e.close(),y.info("Offline cache loaded from IndexedDB",n?"(has schedule)":"(empty)")}catch(e){y.warn("Failed to load offline cache from IndexedDB:",e)}}async _offlineSave(e,t){this._offlineCache[e]=t;try{const i=await ge(),n=i.transaction(Q,"readwrite");n.objectStore(Q).put(t,e),await new Promise((o,s)=>{n.oncomplete=o,n.onerror=()=>s(n.error)}),i.close()}catch(i){y.warn("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(y.warn("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),y.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),y.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();y.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)y.info(`Layout ${this.currentLayoutId} playing — queue updated in background, playback continues`),this.emit("layout-already-playing",this.currentLayoutId);else{const n=this.getNextLayout();n&&(y.info(`${i}switching to layout ${n.layoutId}`),this.emit("layout-prepare-request",n.layoutId))}else y.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,n,o,s,r,a;if(this.collecting){y.debug("Collection already in progress, skipping");return}this.collecting=!0;try{if(await this._offlineDbReady,y.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")}this.config.ensureXmrKeyPair&&await this.config.ensureXmrKeyPair(),y.debug("Collection step: registerDisplay");const l=await this.xmds.registerDisplay();if(y.info(`Display registered: ${l.code}${(e=l.tags)!=null&&e.length?`, tags: ${l.tags.join(", ")}`:""}`),y.debug("Register result:",JSON.stringify(l)),this._offlineSave("settings",l),this.offlineMode&&(this.offlineMode=!1,y.info("Back online — resuming normal collection"),this.emit("offline-mode",!1),this._normalCollectInterval&&(this._setCollectionTimer(this._normalCollectInterval),this._normalCollectInterval=null,this._offlineRetrySeconds=0)),this.displaySettings&&l.settings){const g=this.displaySettings.applySettings(l.settings);g.changed.includes("collectInterval")&&this.updateCollectionInterval(g.settings.collectInterval),l.settings.logLevel&&De(l.settings.logLevel)&&(y.info("Log level updated from CMS:",l.settings.logLevel),this.emit("log-level-changed",l.settings.logLevel))}if((t=this.schedule)!=null&&t.setDisplayProperties&&l.settings&&this.schedule.setDisplayProperties(l.settings),l.syncConfig&&(this.syncConfig=l.syncConfig,y.info("Sync group:",l.syncConfig.isLead?"LEAD":`follower → ${l.syncConfig.syncGroup}`,`(switchDelay: ${l.syncConfig.syncSwitchDelay}ms, videoPauseDelay: ${l.syncConfig.syncVideoPauseDelay}ms)`),this.emit("sync-config",l.syncConfig)),this._applyTagConfig(l.tags),l.commands&&l.commands.length>0){this.displayCommands={};for(const g of l.commands)this.displayCommands[g.commandCode]=g;y.debug("Display commands:",Object.keys(this.displayCommands).join(", "))}this.emit("register-complete",l),y.debug("Collection step: initializeXmr"),await this.initializeXmr(l);const c=l.checkRf||"",u=l.checkSchedule||"";if(!this._lastCheckRf||this._lastCheckRf!==c){this.resetBlacklist(),y.debug("Collection step: requiredFiles");const g=await this.xmds.requiredFiles(),f=g.files||g,m=g.purge||[];if(y.info("Required files:",f.length,m.length>0?`(+ ${m.length} purge)`:""),this._lastCheckRf=c,this.emit("files-received",f),this._offlineSave("requiredFiles",g),m.length>0&&this.emit("purge-request",m),!this._lastCheckSchedule||this._lastCheckSchedule!==u){y.debug("Collection step: schedule");const v=await this.xmds.schedule();y.info("Schedule received"),this._lastCheckSchedule=u,y.debug("Collection step: processing schedule"),this.emit("schedule-received",v),this.schedule.setSchedule(v),this._executedCommands.clear(),this.updateDataConnectors(),this._offlineSave("schedule",v),this.logUpcomingTimeline()}y.debug("Collection step: download-request + mediaInventory");const w=this.schedule.getCurrentLayouts(),{queue:L}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts}),C=[...new Set(L.map(v=>B(v.layoutId)))];if(this._lastRequiredFiles=f,(i=this.displaySettings)!=null&&i.isInDownloadWindow&&!this.displaySettings.isInDownloadWindow()){const v=(o=(n=this.displaySettings).getNextDownloadWindow)==null?void 0:o.call(n);y.info(`Outside download window, skipping downloads${v?` (next: ${v.toLocaleTimeString()})`:""}`)}else this.emit("download-request",{layoutOrder:C,files:f,layoutDependants:Object.fromEntries(this.schedule.getDependantsMap())});this.cacheAnalyzer&&this.cacheAnalyzer.analyze(f).then(v=>{this.emit("cache-analysis",v)}).catch(v=>y.warn("Cache analysis failed:",v)),this.submitMediaInventory(f)}else if(c&&y.info("RequiredFiles CRC unchanged, skipping download check"),this._lastCheckSchedule!==u){const g=await this.xmds.schedule();y.info("Schedule received (RF unchanged but schedule changed)"),this._lastCheckSchedule=u,this.emit("schedule-received",g),this.schedule.setSchedule(g),this._executedCommands.clear(),this.updateDataConnectors(),this._offlineSave("schedule",g)}else u&&y.info("Schedule CRC unchanged, skipping");await this._fetchWeatherData(),y.debug("Collection step: evaluateSchedule");const h=this.schedule.getCurrentLayouts();if(y.info("Current layouts:",h),this.emit("layouts-scheduled",h),this._evaluateAndSwitchLayout(h,""),this._processScheduledCommands(),h.length===0&&this.currentLayoutId&&((s=this.schedule.schedule)!=null&&s.default)){const g=B(this.schedule.schedule.default);y.info(`Current layout filtered by schedule, switching to default layout ${g}`),this.currentLayoutId=null,this.emit("layout-prepare-request",g)}(((r=l.settings)==null?void 0:r.statsEnabled)==="On"||((a=l.settings)==null?void 0:a.statsEnabled)==="1")&&(this.statsCollector?(y.info("Stats enabled, submitting proof of play"),this.emit("submit-stats-request")):y.warn("Stats enabled but no StatsCollector provided")),this.emit("submit-logs-request"),this.emit("submit-faults-request"),!this.collectionInterval&&l.settings&&this.setupCollectionInterval(l.settings),this._faultReportingInterval||this._startFaultReportingAgent(),this.logUpcomingTimeline(),this.emit("collection-complete")}catch(l){if(this.hasCachedData())return y.warn("Collection failed, falling back to cached data:",(l==null?void 0:l.message)||l),this.emit("collection-error",l),this.collectOffline();throw y.error("Collection error:",l),this.emit("collection-error",l),l}finally{this.collecting=!1}}async initializeXmr(e){var n,o,s,r;const t=((n=e.settings)==null?void 0:n.xmrWebSocketAddress)||((o=e.settings)==null?void 0:o.xmrNetworkAddress);if(!t){y.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://")){y.warn(`XMR address uses tcp:// protocol which is not supported by PWA players: ${t}`),y.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)){y.warn(`XMR address contains placeholder domain: ${t}`),y.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)||((r=e.settings)==null?void 0:r.serverKey)||this.config.serverKey;y.debug("XMR CMS Key:",i?"present":"missing"),this.xmr?this.xmr.isConnected()?y.debug("XMR already connected"):(y.info("XMR disconnected, attempting to reconnect..."),this.xmr.reconnectAttempts=0,await this.xmr.start(t,i),this.emit("xmr-reconnected",t)):(y.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))}_startFaultReportingAgent(){this._faultReportingInterval&&clearInterval(this._faultReportingInterval),y.info(`Fault reporting agent started (interval: ${this._faultReportingSeconds}s)`),this._faultReportingInterval=setInterval(()=>{this.emit("submit-faults-request")},this._faultReportingSeconds*1e3)}_setCollectionTimer(e){this.collectionInterval&&clearInterval(this.collectionInterval),this._currentCollectInterval=e,y.info(`Collection interval: ${e}s`),this.collectionInterval=setInterval(()=>{y.debug("Running scheduled collection cycle..."),this.collect().catch(t=>{y.error("Collection error:",t),this.emit("collection-error",t)})},e*1e3)}async requestLayoutChange(e){y.info(`Layout change requested: ${e}`),this.currentLayoutId=null,this.emit("layout-change-requested",e)}setCurrentLayout(e){this.currentLayoutId=e,this._lastLayoutChangeTime=new Date().toISOString(),this._statusCode=1,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(){var i;const e=this.schedule.popNextFromQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!e){const n=(i=this.schedule.schedule)==null?void 0:i.default;return n?{layoutId:B(n),layoutFile:n}:null}const t=B(e.layoutId);if(this.isLayoutBlacklisted(t)){const{queue:n}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});for(let o=0;o<n.length-1;o++){const s=this.schedule.popNextFromQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(s){const r=B(s.layoutId);if(!this.isLayoutBlacklisted(r))return{layoutId:r,layoutFile:s.layoutId}}}y.warn("All queued layouts are blacklisted, using current entry as fallback")}return{layoutId:t,layoutFile:e.layoutId}}peekNextLayout(){const e=this.schedule.peekNextInQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!e)return null;const t=B(e.layoutId);if(t===this.currentLayoutId){const i=this.schedule.peekAfterNext(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(!i)return null;const n=B(i.layoutId);return n===this.currentLayoutId||this.isLayoutBlacklisted(n)?null:{layoutId:n,layoutFile:i.layoutId}}return this.isLayoutBlacklisted(t)?null:{layoutId:t,layoutFile:e.layoutId}}advanceToNextLayout(){if(this._layoutOverride){y.info("Layout override active, not advancing schedule");return}const e=this.getNextLayout();if(!e){if(this.currentLayoutId){y.info(`No layouts in queue, replaying ${this.currentLayoutId} to avoid blank screen`);const s=this.currentLayoutId;this.currentLayoutId=null,this.emit("layout-prepare-request",s)}else y.info("No layouts scheduled during advance"),this.emit("no-layouts-scheduled");return}const{layoutId:t,layoutFile:i}=e;if(this.syncManager&&this.schedule.isSyncEvent(i))if(this.isSyncLead()){y.info(`[Sync] Lead requesting coordinated layout change: ${t}`),this.syncManager.requestLayoutChange(t).catch(s=>{y.error("[Sync] Layout change failed:",s),this.emit("layout-prepare-request",t)});return}else{y.info("[Sync] Follower waiting for lead signal (not advancing independently)");return}t===this.currentLayoutId&&(y.info(`Next layout ${t} is same as current, triggering replay`),this.currentLayoutId=null);const{queue:n}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts}),o=this.schedule._queuePosition;y.info(`Advancing to layout ${t} (queue pos ${o}/${n.length})`),this.emit("layout-prepare-request",t)}advanceToPreviousLayout(){if(this._layoutOverride){y.info("Layout override active, not going back");return}const{queue:e}=this.schedule.getScheduleQueue(this._layoutDurations,{dynamicLayouts:this._dynamicLayouts});if(e.length<=1){y.info("Single or empty queue, nothing to go back to");return}this.schedule._queuePosition=(this.schedule._queuePosition-2+e.length)%e.length;const t=e[this.schedule._queuePosition];this.schedule._queuePosition=(this.schedule._queuePosition+1)%e.length;const i=B(t.layoutId);if(i===this.currentLayoutId){y.info("Previous layout is same as current, nothing to go back to");return}y.info(`Going back to layout ${i}`),this.emit("layout-prepare-request",i)}notifyMediaReady(e,t="media"){y.debug(`File ${e} ready (${t})`);for(const[i,n]of this.pendingLayouts.entries()){const o=t==="layout"&&i===parseInt(e),s=t==="media"&&n.includes(parseInt(e));(o||s)&&(y.debug(`${t} ${e} was needed by pending layout ${i}, checking if ready...`),this.emit("check-pending-layout",i,n))}}async notifyLayoutStatus(e){var t,i,n,o;try{const s={currentLayoutId:e,deviceName:((t=this.config)==null?void 0:t.displayName)||"",displayName:((i=this.config)==null?void 0:i.displayName)||"",lastCommandSuccess:this._lastCommandSuccess??!0,code:this._statusCode,lastLayoutChangeTime:this._lastLayoutChangeTime||new Date().toISOString()};(n=this.config)!=null&&n.latitude&&(s.latitude=this.config.latitude),(o=this.config)!=null&&o.longitude&&(s.longitude=this.config.longitude),await this.xmds.notifyStatus(s),this.emit("status-notified",e)}catch(s){y.warn("Failed to notify status:",s),this.emit("status-notify-failed",e,s)}}reportGeoLocation(e){var n;const t=parseFloat(e==null?void 0:e.latitude),i=parseFloat(e==null?void 0:e.longitude);if(isNaN(t)||isNaN(i)){y.warn("reportGeoLocation: invalid coordinates",e);return}y.info(`Geo location from CMS: ${t.toFixed(4)}, ${i.toFixed(4)}`),(n=this.schedule)!=null&&n.setLocation&&this.schedule.setLocation(t,i),this.emit("location-updated",{latitude:t,longitude:i,source:"cms"}),this.checkSchedule()}async requestGeoLocation(){var n;const e=await this._tryBrowserGeolocation();if(e)return this._applyLocation(e.latitude,e.longitude,"browser");const t=(n=this.config)==null?void 0:n.googleGeoApiKey;if(t){const o=await this._tryGoogleGeolocation(t);if(o)return this._applyLocation(o.latitude,o.longitude,"google-api")}const i=await this._tryIpGeolocation();return i?this._applyLocation(i.latitude,i.longitude,"ip-geolocation"):(y.warn("All geolocation methods failed"),null)}_applyTagConfig(e){if(!Array.isArray(e)||e.length===0)return;const t={geoApiKey:"googleGeoApiKey"};for(const i of e){const n=i.indexOf("|");if(n===-1)continue;const o=i.substring(0,n),s=i.substring(n+1),r=t[o];r&&s&&this.config&&(y.info(`Config from CMS tag: ${o} → ${r}`),this.config[r]=s)}}_applyLocation(e,t,i){var n;return y.info(`Geolocation (${i}): ${e.toFixed(4)}, ${t.toFixed(4)}`),(n=this.schedule)!=null&&n.setLocation&&this.schedule.setLocation(e,t),this.emit("location-updated",{latitude:e,longitude:t,source:i}),this.checkSchedule(),{latitude:e,longitude:t}}async _tryBrowserGeolocation(){if(typeof navigator>"u"||!navigator.geolocation)return null;try{const e=await new Promise((t,i)=>{navigator.geolocation.getCurrentPosition(t,i,{timeout:1e4,maximumAge:3e5,enableHighAccuracy:!1})});return{latitude:e.coords.latitude,longitude:e.coords.longitude}}catch(e){return y.warn("Browser geolocation failed:",(e==null?void 0:e.message)||e),null}}async _tryGoogleGeolocation(e){var t,i;try{const n=await fetch(`https://www.googleapis.com/geolocation/v1/geolocate?key=${e}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({considerIp:!0}),signal:AbortSignal.timeout(5e3)});if(!n.ok)return y.warn(`Google Geolocation API returned ${n.status}`),null;const o=await n.json();return((t=o.location)==null?void 0:t.lat)!=null&&((i=o.location)==null?void 0:i.lng)!=null?{latitude:o.location.lat,longitude:o.location.lng}:null}catch(n){return y.warn("Google Geolocation API failed:",(n==null?void 0:n.message)||n),null}}async _tryIpGeolocation(){const e=[{url:"https://ipapi.co/json/",parse:t=>t.latitude!=null&&t.longitude!=null?{latitude:t.latitude,longitude:t.longitude}:null},{url:"https://freeipapi.com/api/json",parse:t=>t.latitude!=null&&t.longitude!=null?{latitude:t.latitude,longitude:t.longitude}:null}];for(const t of e)try{const i=await fetch(t.url,{signal:AbortSignal.timeout(5e3)});if(!i.ok)continue;const n=await i.json(),o=t.parse(n);if(o)return o}catch(i){y.warn(`IP geolocation (${t.url}) failed:`,(i==null?void 0:i.message)||i)}return null}checkSchedule(){const e=this.schedule.getCurrentLayouts();this.emit("layouts-scheduled",e),this._evaluateAndSwitchLayout(e,"")}async captureScreenshot(){y.info("Screenshot requested"),this.emit("screenshot-request")}async changeLayout(e,t){y.info("Layout change requested via XMR:",e);const i=parseInt(e,10),n=(t==null?void 0:t.duration)||0,o=(t==null?void 0:t.changeMode)||"replace";this._layoutOverride={layoutId:i,type:"change",duration:n,changeMode:o},this.currentLayoutId=null,this.emit("layout-prepare-request",i),n>0&&setTimeout(()=>{var s;((s=this._layoutOverride)==null?void 0:s.layoutId)===i&&(y.info(`Layout override duration expired (${n}s), reverting to schedule`),this.revertToSchedule())},n*1e3)}async overlayLayout(e,t){y.info("Overlay layout requested via XMR:",e);const i=parseInt(e,10),n=(t==null?void 0:t.duration)||0;this._layoutOverride={layoutId:i,type:"overlay",duration:n},this.emit("overlay-layout-request",i),n>0&&setTimeout(()=>{var o;((o=this._layoutOverride)==null?void 0:o.layoutId)===i&&(y.info(`Overlay duration expired (${n}s), reverting to schedule`),this.revertToSchedule())},n*1e3)}async revertToSchedule(){y.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=B(t);this.emit("layout-prepare-request",i)}else this.emit("no-layouts-scheduled")}async purgeAll(){return y.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(y.info("Execute command requested:",e),!t||!t[e]){y.warn("Unknown command code:",e),this._lastCommandSuccess=!1,this.emit("command-result",{code:e,success:!1,reason:"Unknown command"});return}const i=t[e],n=i.commandString||i.value||"";if(n.startsWith("http|")){const o=n.split("|"),s=o[1],r=o[2]||"application/json";try{const a=await fetch(s,{method:"POST",headers:{"Content-Type":r}}),l=a.ok;this._lastCommandSuccess=l,y.info(`HTTP command ${e} result: ${a.status}`),this.emit("command-result",{code:e,success:l,status:a.status})}catch(a){this._lastCommandSuccess=!1,y.error(`HTTP command ${e} failed:`,a),this.emit("command-result",{code:e,success:!1,reason:a.message})}}else y.info("Delegating non-HTTP command to platform layer:",e),this.emit("execute-native-command",{code:e,commandString:n})}triggerWebhook(e){y.info("Webhook trigger from XMR:",e),this.handleTrigger(e)}refreshDataConnectors(){y.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),n=`<files>${e.filter(o=>["media","layout","resource","dependency","widget"].includes(o.type)).map(o=>{const s=o.complete!==void 0?o.complete?"1":"0":"1",r=o.fileType?` fileType="${o.fileType}"`:"";return`<file type="${o.type}" id="${o.id}" complete="${s}" md5="${o.md5||""}" lastChecked="${t}"${r}/>`}).join("")}</files>`;await this.xmds.mediaInventory(n),y.info(`Media inventory submitted: ${e.length} files`),this.emit("media-inventory-submitted",e.length)}catch(t){y.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(n){y.warn("BlackList failed:",n)}}reportLayoutFailure(e,t){const i=Number(e),n=this._layoutBlacklist.get(i)||{failures:0,blacklisted:!1,reason:""};n.failures++,n.reason=t,!n.blacklisted&&n.failures>=this._blacklistThreshold?(n.blacklisted=!0,y.warn(`Layout ${i} blacklisted after ${n.failures} consecutive failures: ${t}`),this.emit("layout-blacklisted",{layoutId:i,reason:t,failures:n.failures}),this.blackList(i,"layout",t)):n.blacklisted||y.info(`Layout ${i} failure ${n.failures}/${this._blacklistThreshold}: ${t}`),this._layoutBlacklist.set(i,n)}reportLayoutSuccess(e){const t=Number(e);if(this._layoutBlacklist.has(t)){const i=this._layoutBlacklist.get(t);this._layoutBlacklist.delete(t),i.blacklisted&&(y.info(`Layout ${t} removed from blacklist (rendered successfully)`),this.emit("layout-unblacklisted",{layoutId:t}))}}isLayoutBlacklisted(e){const t=this._layoutBlacklist.get(Number(e));return(t==null?void 0:t.blacklisted)===!0}getBlacklistedLayouts(){const e=[];for(const[t,i]of this._layoutBlacklist)i.blacklisted&&e.push(t);return e}resetBlacklist(){this._layoutBlacklist.size>0&&(y.info(`Blacklist reset (${this._layoutBlacklist.size} entries cleared)`),this._layoutBlacklist.clear(),this.emit("blacklist-reset"))}isLayoutOverridden(){return this._layoutOverride!==null}handleTrigger(e){const t=this.schedule.findActionByTrigger(e);if(!t){y.debug("No scheduled action matches trigger:",e);return}switch(y.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:y.warn("Unknown action type:",t.actionType)}}updateDataConnectors(){const e=this.schedule.getDataConnectors();e.length>0&&y.info(`Configuring ${e.length} data connector(s)`),this.dataConnectorManager.setConnectors(e),e.length>0&&(this.dataConnectorManager.startPolling(),this.emit("data-connectors-started",e.length))}_processScheduledCommands(){var i;if(!((i=this.schedule)!=null&&i.getCommands))return;const e=this.schedule.getCommands();if(e.length===0)return;const t=new Date;for(const n of e){if(!n.code||!n.date)continue;const o=`${n.code}|${n.date}`;if(this._executedCommands.has(o))continue;const s=new Date(n.date);if(isNaN(s.getTime())){y.warn("Scheduled command has invalid date:",n.date);continue}t>=s&&(y.info(`Executing scheduled command: ${n.code} (scheduled: ${n.date})`),this._executedCommands.add(o),n.code==="collectNow"?setTimeout(()=>this.collectNow().catch(r=>y.error("collectNow command failed:",r)),0):this.emit("scheduled-command",n))}}async _fetchWeatherData(){var e,t;if(!(!((e=this.xmds)!=null&&e.getWeather)||!((t=this.schedule)!=null&&t.setWeatherData)))try{const i=await this.xmds.getWeather(),n=typeof i=="string"?JSON.parse(i):i;this.schedule.setWeatherData(n),y.info("Weather data updated:",Object.keys(n).join(", "))}catch(i){y.warn("GetWeather failed (non-critical):",(i==null?void 0:i.message)||i)}}getDataConnectorManager(){return this.dataConnectorManager}setSyncManager(e){this.syncManager=e,y.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 n=0;for(const r of i){const a=B(r);try{const l=await this.cache.get("layout",a);if(l){const{duration:c,isDynamic:u}=ze(l);this._layoutDurations.has(r)||this._layoutDurations.set(r,c),this._layoutDurations.has(String(a))||this._layoutDurations.set(String(a),c),u&&this._dynamicLayouts.add(r),n++}}catch(l){y.debug(`Could not parse duration for layout ${a}:`,l.message)}}n>0&&y.info(`[Timeline] Parsed durations for ${n} layouts`)}logUpcomingTimeline(){if(this._layoutDurations.size===0||!this.schedule.getLayoutsAtTime)return;const e=Ke(this.schedule,this._layoutDurations,{currentLayoutStartedAt:this._lastLayoutChangeTime?new Date(this._lastLayoutChangeTime):null});if(e.length===0)return;const t=e.slice(0,20).map(i=>{const n=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` ${n}-${o} Layout ${i.layoutFile} (${i.duration}s)${i.isDefault?" [default]":""}`});y.info(`[Timeline] Next ${e.length} plays:
|
|
622
622
|
${t.join(`
|
|
623
623
|
`)}`),this.emit("timeline-updated",e)}recordLayoutDuration(e,t){const i=this._layoutDurations.get(e);i!==t&&(i&&i>60&&t<i||(this._layoutDurations.set(e,t),y.debug(`[Timeline] Duration corrected: layout ${e} ${i||"?"}s → ${t}s`),this._timelineRecalcTimer&&clearTimeout(this._timelineRecalcTimer),this._timelineRecalcTimer=setTimeout(()=>{this._timelineRecalcTimer=null,this.logUpcomingTimeline()},500)))}cleanup(){this.collectionInterval&&(clearInterval(this.collectionInterval),this.collectionInterval=null),this._faultReportingInterval&&(clearInterval(this._faultReportingInterval),this._faultReportingInterval=null),this._timelineRecalcTimer&&(clearTimeout(this._timelineRecalcTimer),this._timelineRecalcTimer=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 fe{constructor(e){$(this,"overlay",null);$(this,"config");$(this,"updateTimer",null);$(this,"_visible",!1);$(this,"_getProgress",null);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=`
|
|
624
624
|
position: fixed;
|
|
@@ -659,7 +659,7 @@ ${t.join(`
|
|
|
659
659
|
max-width: 35vw;
|
|
660
660
|
box-shadow: 0 0.3vh 1.2vw rgba(0, 0, 0, 0.5);
|
|
661
661
|
pointer-events: auto;
|
|
662
|
-
`,this.overlay.addEventListener("click",e=>{const t=e.target.closest("[data-layout-id]");if(!t||!this.onLayoutClick)return;const i=parseInt(t.dataset.layoutId,10);isNaN(i)||i===this.currentLayoutId||this.onLayoutClick(i)}),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.currentLayoutId!==null?this.timeline.find(h=>parseInt(h.layoutFile.replace(".xlf",""),10)===this.currentLayoutId&&h.startTime<=e&&h.endTime>e):null,i=this.timeline.filter(h=>h===t?!1:h.startTime>e),n=[];let o=t??null;if(t?n.push(t):this.currentLayoutId!==null&&i.length>0&&(o={layoutFile:`${this.currentLayoutId}.xlf`,startTime:e,endTime:i[0].startTime,duration:(i[0].startTime.getTime()-e.getTime())/1e3,isDefault:!0},n.push(o)),n.push(...i),n.length===0){this.overlay.innerHTML='<div style="color: #999;">Timeline — no upcoming layouts</div>';return}const s=8,r=n.length,a=n.slice(0,s),l=this.offline?' <span style="color: #ff4444; font-size: 1.1vw;">OFFLINE</span>':"";let c=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw; color: #ccc;">Timeline (${r} upcoming)${l}</div>`;const
|
|
662
|
+
`,this.overlay.addEventListener("click",e=>{const t=e.target.closest("[data-layout-id]");if(!t||!this.onLayoutClick)return;const i=parseInt(t.dataset.layoutId,10);isNaN(i)||i===this.currentLayoutId||this.onLayoutClick(i)}),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.currentLayoutId!==null?this.timeline.find(h=>parseInt(h.layoutFile.replace(".xlf",""),10)===this.currentLayoutId&&h.startTime<=e&&h.endTime>e):null,i=this.timeline.filter(h=>h===t?!1:h.startTime>e),n=[];let o=t??null;if(t?n.push(t):this.currentLayoutId!==null&&i.length>0&&(o={layoutFile:`${this.currentLayoutId}.xlf`,startTime:e,endTime:i[0].startTime,duration:(i[0].startTime.getTime()-e.getTime())/1e3,isDefault:!0},n.push(o)),n.push(...i),n.length===0){this.overlay.innerHTML='<div style="color: #999;">Timeline — no upcoming layouts</div>';return}const s=8,r=n.length,a=n.slice(0,s),l=this.offline?' <span style="color: #ff4444; font-size: 1.1vw;">OFFLINE</span>':"";let c=`<div style="font-weight: 600; margin-bottom: 0.8vh; font-size: 1.4vw; color: #ccc;">Timeline (${r} upcoming)${l}</div>`;const u=this.onLayoutClick!==null;for(const h of a){const g=parseInt(h.layoutFile.replace(".xlf",""),10),f=h===o,m=this.formatTime(h.startTime),w=this.formatTime(h.endTime),L=this.formatDuration(h.duration);c+=`<div data-layout-id="${g}" style="${f?"border-left: 0.25vw solid #4a9eff; padding-left: 0.6vw;":"padding-left: 0.85vw;"} ${f?"color: #fff; font-weight: 600;":"color: #aaa;"} ${u&&!f?"cursor: pointer;":""} margin-bottom: 0.3vh; font-family: monospace; font-size: 1.3vw; line-height: 1.5; white-space: nowrap;" ${u&&!f?`onmouseover="this.style.background='rgba(255,255,255,0.1)'" onmouseout="this.style.background='none'"`:""}>`;const _=`#${g}`.padEnd(4).replace(/ /g," "),S=L.padStart(7).replace(/ /g," ");if(c+=`${m}–${w} ${_}${S}`,h.isDefault&&(c+=' <span style="color: #888;">[def]</span>'),h.hidden&&h.hidden.length>0){const b=h.hidden.map(I=>`#${I.file.replace(".xlf","")} (p${I.priority})`).join(", ");c+=` <span style="color: #8899aa; font-size: 1.1vw;" title="Also scheduled: ${b}">+${h.hidden.length}</span>`}c+="</div>"}r>s&&(c+=`<div style="padding-left: 0.85vw; color: #888; font-size: 1.1vw; margin-top: 0.3vh;">+${r-s} more</div>`),this.overlay.innerHTML=c}formatTime(e){return e.toLocaleTimeString("en-GB",{hour:"2-digit",minute:"2-digit",second:"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.refreshTimer&&(clearInterval(this.refreshTimer),this.refreshTimer=null),this.overlay&&(this.overlay.remove(),this.overlay=null)}}function st(){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 pe=q("SetupOverlay");class rt{constructor(){$(this,"backdrop",null);$(this,"gateCard",null);$(this,"iframe",null);$(this,"cancelBtn",null);$(this,"visible",!1)}show(){this.visible||(this.visible=!0,this.backdrop||this.create(),this.showGate(),this.backdrop.style.display="flex",pe.info("[SetupOverlay] Opened"))}hide(){this.visible&&(this.visible=!1,this.backdrop&&(this.backdrop.style.display="none"),this.iframe&&(this.iframe.src="about:blank",this.iframe.style.display="none"),pe.info("[SetupOverlay] Closed"))}toggle(){this.visible?this.hide():this.show()}isVisible(){return this.visible}showGate(){var i,n;this.gateCard&&(this.gateCard.style.display="block"),this.iframe&&(this.iframe.style.display="none"),this.cancelBtn&&(this.cancelBtn.style.display="none");const e=(i=this.gateCard)==null?void 0:i.querySelector("#gate-key");e&&(e.value="",requestAnimationFrame(()=>e.focus()));const t=(n=this.gateCard)==null?void 0:n.querySelector("#gate-error");t&&(t.style.display="none")}showSetup(){this.gateCard&&(this.gateCard.style.display="none"),this.cancelBtn&&(this.cancelBtn.style.display="block"),this.iframe&&(this.iframe.style.display="block",this.iframe.src="./setup.html?unlocked=1")}create(){this.backdrop=document.createElement("div"),this.backdrop.id="setup-overlay-backdrop",this.backdrop.style.cssText=`
|
|
663
663
|
position: fixed;
|
|
664
664
|
inset: 0;
|
|
665
665
|
background: rgba(0, 0, 0, 0.85);
|
|
@@ -725,10 +725,5 @@ ${t.join(`
|
|
|
725
725
|
border: none;
|
|
726
726
|
background: #1D1D1D;
|
|
727
727
|
display: none;
|
|
728
|
-
`,this.iframe.addEventListener("load",()=>{var o,s;try{if((((s=(o=this.iframe.contentWindow)==null?void 0:o.location)==null?void 0:s.href)||"").includes("index.html")){this.hide(),window.location.reload();return}const a=this.iframe.contentDocument;if(!a)return;a.addEventListener("keydown",l=>{l.key==="Escape"&&(l.preventDefault(),this.hide())})}catch{}}),this.backdrop.appendChild(this.cancelBtn),this.backdrop.appendChild(this.gateCard),this.backdrop.appendChild(this.iframe),document.body.appendChild(this.backdrop);const e=this.gateCard.querySelector("#gate-form"),t=this.gateCard.querySelector("#gate-key"),i=this.gateCard.querySelector("#gate-error"),n=this.gateCard.querySelector("#gate-cancel");t.addEventListener("focus",()=>{t.style.borderColor="#0097D8"}),t.addEventListener("blur",()=>{t.style.borderColor="#3A3A3A"}),e.addEventListener("submit",o=>{o.preventDefault(),t.value.trim()===Me.cmsKey?this.showSetup():(i.textContent="Incorrect CMS key",i.style.display="block",t.focus(),t.select())}),n.addEventListener("click",()=>this.hide()),this.backdrop.addEventListener("keydown",o=>{o.key==="Escape"&&(o.preventDefault(),this.hide()),o.stopPropagation()})}}const u=q("PWA"),oe=new URL("./",window.location.href).pathname.replace(/\/$/,"");let me,K,D,ee,se,we,W,X,ve,be,Se,Le,$e,Ce;const z={};class at{constructor(){$(this,"renderer");$(this,"core");$(this,"xmds");$(this,"downloadOverlay",null);$(this,"timelineOverlay",null);$(this,"setupOverlay",null);$(this,"statsCollector",null);$(this,"logReporter",null);$(this,"displaySettings",null);$(this,"currentScheduleId",-1);$(this,"scheduledLayoutIds",new Set);$(this,"preparingLayoutId",null);$(this,"_pendingRetryLayoutId",null);$(this,"_screenshotInterval",null);$(this,"_screenshotMethod",null);$(this,"_screenshotInFlight",!1);$(this,"_html2canvasMod",null);$(this,"_wakeLock",null);$(this,"syncManager",null);$(this,"_currentLayoutEnableStat",!0);$(this,"_probeTimer",null);$(this,"_pendingFollowerStats",null);$(this,"_pendingFollowerLogs",null);$(this,"_iframeObserver",null);$(this,"_swIcHandler",null);$(this,"_chunkConfig",null)}async init(){if(u.info("Initializing player with RendererLite + PlayerCore..."),await this.loadCoreModules(),"serviceWorker"in navigator)try{const s=await navigator.serviceWorker.register(`${oe}/sw-pwa.js?v=${Date.now()}`,{scope:`${oe}/`,type:"module",updateViaCache:"none"});u.info("Service Worker registered for offline mode:",s.scope),navigator.storage&&navigator.storage.persist&&(await navigator.storage.persist()?u.info("Persistent storage granted - cache won't be evicted"):u.warn("Persistent storage denied - cache may be evicted"))}catch(s){u.warn("Service Worker registration failed:",s)}u.info("Initializing cache clients..."),W=new Oe;const{calculateChunkConfig:e}=await P(async()=>{const{calculateChunkConfig:s}=await import("./index-BKKe321E.js");return{calculateChunkConfig:s}},__vite__mapDeps([0,1,2]),import.meta.url);this._chunkConfig=e(u),X=new Pe({concurrency:this._chunkConfig.concurrency,chunkSize:this._chunkConfig.chunkSize,chunksPerFile:2}),u.info("Cache clients ready — StoreClient + DownloadManager");const t=document.getElementById("player-container");if(!t)throw new Error("No #player-container found");this.renderer=new Ue({cmsUrl:D.cmsUrl,hardwareKey:D.hardwareKey},t,{getMediaUrl:async s=>{if(u.debug(`getMediaUrl called for media ${s}`),!await W.has("api/v2/player",`media/${s}`))return u.warn(`Media ${s} not in cache`),"";const a=`${window.location.origin}${O}/media/${s}`;return u.debug(`Using streaming URL for media ${s}: ${a}`),a},getWidgetHtml:async s=>{const r=`${O}/widgets/${s.layoutId}/${s.regionId}/${s.id}`;u.debug(`Looking for widget HTML at: ${r}`,s);try{if(await W.has("api/v2/player/widgets",`${s.layoutId}/${s.regionId}/${s.id}`))return u.debug("Widget HTML found in store, using mirror URL for iframe"),{url:r,fallback:s.raw||""};u.warn(`No widget HTML found in store: ${r}`)}catch(a){u.error(`Failed to check widget HTML for ${s.id}:`,a)}return u.warn(`Using widget.raw fallback for ${s.id}`),s.raw||""}}),this.core=new nt({config:D,xmds:this.xmds,cache:W,schedule:K,renderer:this.renderer,xmrWrapper:we,statsCollector:this.statsCollector,displaySettings:this.displaySettings}),this.setupCoreEventHandlers(),this.setupRendererEventHandlers(),this.setupInteractiveControl(),this.setupRemoteControls(),this.core.on("register-complete",s=>{var l,c;const r=parseFloat((l=s==null?void 0:s.settings)==null?void 0:l.latitude),a=parseFloat((c=s==null?void 0:s.settings)==null?void 0:c.longitude);r&&a&&!isNaN(r)&&!isNaN(a)?(u.info(`Display location from CMS: ${r.toFixed(4)}, ${a.toFixed(4)}`),K!=null&&K.setLocation&&K.setLocation(r,a)):this.core.requestGeoLocation&&(u.info("No CMS coordinates, requesting browser geolocation..."),this.core.requestGeoLocation())}),this.updateConfigDisplay(),window.addEventListener("online",()=>{u.info("Browser reports online — triggering immediate collection"),this.updateStatus("Back online, syncing..."),this.removeOfflineIndicator(),this.core.collectNow().catch(s=>{u.error("Failed to collect after coming online:",s)})}),window.addEventListener("offline",()=>{u.warn("Browser reports offline — continuing playback with cached data"),this.updateStatus("Offline mode — using cached content"),this.showOfflineIndicator()});const n=(this.getControls().keyboard||{}).debugOverlays===!0,o=ot();o.enabled&&n&&(this.downloadOverlay=new fe(o),this.downloadOverlay.setProgressCallback(()=>X.getProgress()),u.info("Download overlay enabled (hover bottom-right corner)")),st()&&n&&(this.timelineOverlay=new ye(!0,s=>this.skipToLayout(s))),this.setupCertWarnings(),await this.requestWakeLock(),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&this.requestWakeLock()}),await this.core.collect(),u.info("Player initialized successfully")}async requestWakeLock(){if(!("wakeLock"in navigator)){u.debug("Wake Lock API not supported");return}try{this._wakeLock=await navigator.wakeLock.request("screen"),u.info("Screen Wake Lock acquired — display will stay on"),this._wakeLock.addEventListener("release",()=>{u.debug("Screen Wake Lock released"),this._wakeLock=null})}catch(e){u.warn("Wake Lock request failed:",e==null?void 0:e.message)}}setupCertWarnings(){const e=new Set;window.addEventListener("cert-warning",t=>{const{host:i,error:n}=t.detail;if(e.has(i))return;e.add(i),u.warn(`Invalid SSL certificate accepted for stream: ${i} (${n})`);let o=document.getElementById("overlay");if(!o){o=document.createElement("div"),o.id="overlay",o.style.cssText=`
|
|
729
|
-
|
|
730
|
-
background: rgba(0, 0, 0, 0.7); padding: 10px 20px;
|
|
731
|
-
display: flex; justify-content: space-between; align-items: center;
|
|
732
|
-
font-size: 12px; z-index: 9999;
|
|
733
|
-
`;const a=document.createElement("div");a.id="config-info",a.style.color="#4CAF50",o.appendChild(a);const l=document.createElement("div");l.id="status",l.style.color="#fff",o.appendChild(l),document.body.appendChild(o)}let s=document.getElementById("cert-warnings");if(!s){s=document.createElement("span"),s.id="cert-warnings",s.style.cssText="color: #ffaa33; flex: 0 0 auto;";const a=document.getElementById("status");o.insertBefore(s,a)}const r=[...e].join(", ");s.textContent=`⚠ SSL: ${r}`,o.classList.add("has-warnings")})}async loadCoreModules(){var e,t,i;try{const n=await P(()=>import("./index-B-_xo7Jd.js"),__vite__mapDeps([3,4,2]),import.meta.url),o=await P(()=>import("./index-C0V6Yuf7.js"),__vite__mapDeps([5,6,2]),import.meta.url),s=await P(()=>import("./index-ChxdOFuh.js"),__vite__mapDeps([7,8,2,4]),import.meta.url),r=await P(()=>import("./index-BgzyAcAx.js"),[],import.meta.url),a=await P(()=>import("./index-DblGx8T7.js"),__vite__mapDeps([9,2]),import.meta.url),l=await P(()=>import("./index-BnKrYvmo.js"),__vite__mapDeps([10,2]),import.meta.url),c=await P(()=>import("./index-D8pNyRvb.js"),__vite__mapDeps([11,2]),import.meta.url),d=await P(()=>import("./index-CjVBCq85.js"),__vite__mapDeps([12,2,8,4]),import.meta.url),h=await P(()=>import("./index-DcGeUWV6.js"),__vite__mapDeps([13,8,2,4]),import.meta.url),g=await P(()=>import("./sync-manager-Bxee4l4n.js"),__vite__mapDeps([14,2]),import.meta.url);if(me=n.cacheWidgetHtml,Ce=g.SyncManager,K=s.scheduleManager,D=r.config,ee=o.RestClient,se=o.XmdsClient,we=a.XmrWrapper,ve=l.StatsCollector,be=l.formatStats,Se=l.LogReporter,Le=l.formatLogs,$e=c.DisplaySettings,z.core=d.VERSION||"?",z.cache=n.VERSION||"?",z.renderer=h.VERSION||"?",z.schedule=s.VERSION||"?",z.xmds=o.VERSION||"?",z.xmr=a.VERSION||"?",z.utils=r.VERSION||"?",z.stats=l.VERSION||"?",z.settings=c.VERSION||"?",(e=window.electronAPI)!=null&&e.getSystemInfo)try{const b=await window.electronAPI.getSystemInfo();b.macAddress&&(D.macAddress=b.macAddress)}catch{}const f=(()=>{try{return JSON.parse(localStorage.getItem("xibo_config")||"{}").transport}catch{return}})(),w=new URLSearchParams(window.location.search).get("transport")||(oe.includes("pwa-xmds")?"xmds":null)||f||"auto";w==="xmds"?(u.info("Using XMDS/SOAP transport (forced)"),this.xmds=new se(D)):w==="rest"?(u.info("Using REST transport (forced)"),this.xmds=new ee(D)):await ee.isAvailable(D.cmsUrl,{maxRetries:0})?(this.xmds=new ee(D),u.info("Using REST transport (auto-detected)")):(u.warn("REST unavailable, falling back to XMDS/SOAP"),this.xmds=new se(D)),this.statsCollector=new ve,await this.statsCollector.init(),u.info("Stats collector initialized"),this.logReporter=new Se,await this.logReporter.init(),u.info("Log reporter initialized"),Ae(({level:b,name:I,args:A})=>{if(!this.logReporter)return;const T=A.map(R=>typeof R=="string"?R:JSON.stringify(R)).join(" ");this.logReporter.log(b,`[${I}] ${T}`,"PLAYER").catch(()=>{})}),this.displaySettings=new $e,u.info("Display settings manager initialized");const L="2026-03-02T20:56:57.798Z",C="0.6.0";u.info(`v${C} built ${L}`);const v=Object.entries(z).map(([b,I])=>`${b}=${I}`).join(" ");u.info(`SDK: ${v}`);const _=!!window.electronAPI,k=_?((t=navigator.userAgent.match(/Electron\/([\d.]+)/))==null?void 0:t[1])||"?":null,x=((i=navigator.userAgent.match(/Chrome\/([\d.]+)/))==null?void 0:i[1])||"?",S=_?`Electron ${k} / Chrome ${x}`:`Chrome ${x}`;u.info(`Env: PWA v${C} | ${S} | ${navigator.platform} | ${screen.width}x${screen.height}`),u.info("Core modules loaded")}catch(n){throw u.error("Failed to load core modules:",n),n}}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||D.hardwareKey;this.updateStatus(`Registered: ${t}`),this.displaySettings&&(document.title=`Xibo Player - ${this.displaySettings.getDisplayName()}`)}),this.core.on("sync-config",e=>{this.syncManager&&this.syncManager.stop(),this.syncManager=new Ce({displayId:D.hardwareKey,syncConfig:e,onLayoutChange:async t=>{var i;u.info(`[Sync] Loading layout ${t} (waiting for show signal)`),await this.prepareAndRenderLayout(parseInt(t,10)),(i=this.syncManager)==null||i.reportReady(t)},onLayoutShow:t=>{u.info(`[Sync] Show signal for layout ${t}`)},onVideoStart:(t,i)=>{var n,o;u.info(`[Sync] Video start: layout ${t} region ${i}`),(o=(n=this.renderer).resumeRegionMedia)==null||o.call(n,i)},onStatsReport:async(t,i,n)=>{u.info(`[Sync] Submitting stats for follower ${t}`);try{await this.xmds.submitStats(i,t)&&n()}catch(o){u.warn(`[Sync] Stats submission failed for follower ${t}:`,o)}},onLogsReport:async(t,i,n)=>{u.info(`[Sync] Submitting logs for follower ${t}`);try{await this.xmds.submitLog(i,t)&&n()}catch(o){u.warn(`[Sync] Log submission failed for follower ${t}:`,o)}},onStatsAck:async t=>{u.info("[Sync] Lead confirmed stats submission"),this._pendingFollowerStats&&this.statsCollector&&(await this.statsCollector.clearSubmittedStats(this._pendingFollowerStats),this._pendingFollowerStats=null)},onLogsAck:async t=>{u.info("[Sync] Lead confirmed logs submission"),this._pendingFollowerLogs&&this.logReporter&&(await this.logReporter.clearSubmittedLogs(this._pendingFollowerLogs),this._pendingFollowerLogs=null)}}),this.core.setSyncManager(this.syncManager),this.syncManager.start(),u.info(`[Sync] SyncManager started as ${e.isLead?"LEAD":"FOLLOWER"}`)}),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 W.remove(e);u.info(`Purge complete: ${t.deleted}/${t.total} files deleted`)}catch(t){u.warn("Purge failed:",t)}}),this.core.on("download-request",async e=>{var t,i;(t=this.downloadOverlay)==null||t.startUpdating();try{const n=((i=this.xmds)==null?void 0:i._token)||null;n&&await fetch("/auth-token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:n})}),await this.enqueueDownloads(e),u.info("Download enqueue complete")}catch(n){u.error("Download request failed:",n),this.updateStatus("Download failed: "+n,"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 r=parseInt(String(s.file||s.id||s).replace(".xlf",""),10);r&&i.add(r)}}const n=this.renderer.layoutPool.clearWarmNotIn(i);n>0&&u.info(`Cleared ${n} preloaded layout(s) no longer in schedule`),this.scheduledLayoutIds=i}u.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.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.submitFault("COLLECTION_FAILED",`Collection cycle failed: ${(e==null?void 0:e.message)||e}`)}),this.core.on("xmr-connected",e=>{u.info("XMR connected:",e)}),this.core.on("xmr-misconfigured",e=>{u.warn(`XMR misconfigured (${e.reason}): ${e.message}`),u.warn(`XMR misconfigured: ${e.message}`)}),this.core.on("log-level-changed",()=>{u.info("Log level changed")}),this.core.on("overlay-layout-request",async e=>{u.info("Overlay layout requested:",e),await this.prepareAndRenderLayout(e)}),this.core.on("revert-to-schedule",()=>{u.info("Reverting to scheduled content"),this.updateStatus("Reverting to schedule...")}),this.core.on("purge-all-request",async()=>{u.info("Purging all cached content..."),this.updateStatus("Purging cache...");try{const e=await W.list();if(e.length>0){const i=await W.remove(e);u.info(`Purged ${i.deleted} files from ContentStore`)}const t=await caches.keys();t.length>0&&(await Promise.all(t.map(i=>caches.delete(i))),u.info(`Purged ${t.length} legacy caches`))}catch(e){u.error("Cache purge failed:",e)}}),this.core.on("command-result",e=>{var t;u.info("Command result:",e),e.success||((t=this.logReporter)==null||t.reportFault("COMMAND_FAILED",`Command ${e.code} failed: ${e.reason||"unknown"}`),this.submitFault("COMMAND_FAILED",`Command ${e.code} failed: ${e.reason||"unknown"}`))}),this.core.on("scheduled-command",e=>{u.info(`Scheduled command: ${e.code}`),this.core.executeCommand(e.code)}),this.displaySettings&&(this.displaySettings.on("interval-changed",e=>{u.info(`Collection interval changed to ${e}s`)}),this.displaySettings.on("settings-applied",(e,t)=>{t.length>0&&u.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;this._swIcHandler=t=>{var l,c;if(((l=t.data)==null?void 0:l.type)!=="INTERACTIVE_CONTROL")return;const{method:i,path:n,search:o,body:s}=t.data,r=(c=t.ports)==null?void 0:c[0];if(!r)return;const a=this.handleInteractiveControl(i,n,o,s);r.postMessage(a)},(e=navigator.serviceWorker)==null||e.addEventListener("message",this._swIcHandler)}setupRemoteControls(){window.addEventListener("blur",()=>{setTimeout(()=>window.focus(),200)});const e=a=>{const l=()=>{var c;try{const d=a.contentDocument||((c=a.contentWindow)==null?void 0:c.document);if(!d||a.__keyForwarderAttached)return;a.__keyForwarderAttached=!0,d.addEventListener("keydown",h=>{const g=new KeyboardEvent("keydown",{key:h.key,code:h.code,keyCode:h.keyCode,ctrlKey:h.ctrlKey,shiftKey:h.shiftKey,altKey:h.altKey,metaKey:h.metaKey,bubbles:!0,cancelable:!0});document.dispatchEvent(g)||h.preventDefault()})}catch{}};a.addEventListener("load",l),l()};Array.from(document.querySelectorAll("iframe")).forEach(a=>e(a)),this._iframeObserver=new MutationObserver(a=>{for(const l of a)for(const c of l.addedNodes)c instanceof HTMLIFrameElement&&e(c),c instanceof HTMLElement&&c.querySelectorAll("iframe").forEach(d=>e(d))}),this._iframeObserver.observe(document.body,{childList:!0,subtree:!0});const t=this.getControls(),{keyboard:i={}}=t,n=i.debugOverlays===!0,o=i.setupKey===!0,s=i.playbackControl===!0,r=i.videoControls===!0;document.addEventListener("keydown",a=>{switch(a.key){case"t":case"T":if(!n)break;this.timelineOverlay||(this.timelineOverlay=new ye(!0,l=>this.skipToLayout(l))),this.timelineOverlay.toggle();break;case"d":case"D":if(!n)break;this.downloadOverlay||(this.downloadOverlay=new fe({enabled:!0,autoHide:!1}),this.downloadOverlay.setProgressCallback(()=>X.getProgress())),this.downloadOverlay.toggle();break;case"v":case"V":{if(!r)break;const l=document.querySelectorAll("video"),c=l.length>0&&!l[0].controls;l.forEach(d=>d.controls=c);break}case"ArrowRight":case"PageDown":if(!s)break;u.info("[Remote] Next layout (keyboard)"),this.core.advanceToNextLayout(),a.preventDefault();break;case"ArrowLeft":case"PageUp":if(!s)break;u.info("[Remote] Previous layout (keyboard)"),this.core.advanceToPreviousLayout(),a.preventDefault();break;case" ":if(!s)break;u.info("[Remote] Toggle pause (keyboard)"),this.renderer.isPaused()?this.renderer.resume():this.renderer.pause(),a.preventDefault();break;case"r":case"R":if(!s)break;this.core.isLayoutOverridden()&&(u.info("[Remote] Revert to schedule (keyboard)"),this.core.revertToSchedule());break;case"s":case"S":if(!o)break;this.setupOverlay||(this.setupOverlay=new rt),this.setupOverlay.toggle(),a.preventDefault();break}}),s&&"mediaSession"in navigator&&(navigator.mediaSession.setActionHandler("nexttrack",()=>{u.info("[Remote] Next layout (MediaSession)"),this.core.advanceToNextLayout()}),navigator.mediaSession.setActionHandler("previoustrack",()=>{u.info("[Remote] Previous layout (MediaSession)"),this.core.advanceToPreviousLayout()}),navigator.mediaSession.setActionHandler("pause",()=>{u.info("[Remote] Pause (MediaSession)"),this.renderer.pause()}),navigator.mediaSession.setActionHandler("play",()=>{u.info("[Remote] Resume (MediaSession)"),this.renderer.resume()})),u.info("Remote controls initialized (keyboard + MediaSession)")}getControls(){try{return JSON.parse(localStorage.getItem("xibo_config")||"{}").controls||{}}catch{return{}}}skipToLayout(e){u.info(`Skipping to layout ${e} (timeline click)`),this.core.changeLayout(e)}parseBody(e){try{return e?JSON.parse(e):{}}catch{return{}}}handleInteractiveControl(e,t,i,n){var o;switch(u.debug("IC request:",e,t,i),t){case"/info":return{status:200,body:JSON.stringify({hardwareKey:D.hardwareKey,displayName:D.displayName,playerType:"pwa",currentLayoutId:this.core.getCurrentLayoutId()})};case"/trigger":{const s=this.parseBody(n);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(n);return u.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(n);return u.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(n);return u.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(n);return(o=this.logReporter)==null||o.reportFault(s.code||"WIDGET_FAULT",s.reason||"Widget reported fault"),this.submitFault(s.code||"WIDGET_FAULT",s.reason||"Widget reported fault",{layoutId:s.layoutId,regionId:s.regionId,widgetId:s.widgetId}),{status:200,body:"OK"}}case"/realtime":{const r=new URLSearchParams(i).get("dataKey");if(u.debug("IC: Realtime data request for key:",r),!r)return{status:400,body:JSON.stringify({error:"Missing dataKey parameter"})};const l=this.core.getDataConnectorManager().getData(r);return l===null?{status:404,body:JSON.stringify({error:`No data available for key: ${r}`})}:{status:200,body:typeof l=="string"?l:JSON.stringify(l)}}case"/criteria":return{status:200,body:JSON.stringify({displayId:D.displayId,hardwareKey:D.hardwareKey,displayName:D.displayName,width:window.innerWidth,height:window.innerHeight,latitude:D.latitude||null,longitude:D.longitude||null,playerType:"pwa"})};default:return{status:404,body:JSON.stringify({error:"Unknown IC route"})}}}notifyFileCached(e,t){u.debug(`Download complete: ${t}/${e}`),(t==="media"||t==="layout")&&this.core.notifyMediaReady(parseInt(e),t),this._probeTimer&&clearTimeout(this._probeTimer),this._probeTimer=setTimeout(()=>{this._probeTimer=null,this.probeLayoutDurations().catch(()=>{})},3e3)}async enqueueDownloads(e){const{extractMediaIdsFromXlf:t}=await P(async()=>{const{extractMediaIdsFromXlf:S}=await import("./index-BKKe321E.js");return{extractMediaIdsFromXlf:S}},__vite__mapDeps([0,1,2]),import.meta.url),{layoutOrder:i,files:n,layoutDependants:o}=e,s=X.queue,r=S=>(S.path||"").split("?")[0].replace(/^\/+/,"")||`${S.type||"media"}/${S.id}`,a=new Map,l=[],c=new Map,d=new Map;for(const S of n)if(S.type==="layout")a.set(parseInt(S.id),S);else if(S.type==="static")l.push(S);else{const b=`${S.type}:${S.id}`;c.set(b,S);const I=String(S.id);d.has(I)||d.set(I,[]),d.get(I).push(b)}u.info(`Download: ${i.length} layouts, ${c.size} media, ${l.length} resources`);const h=new Map,f=[...i,...[...a.keys()].filter(S=>!i.includes(S))].map(async S=>{const b=a.get(S);if(!(b!=null&&b.path))return;let I;try{const A=await fetch(b.path);A.ok&&(I=await A.text(),u.info(`Fetched XLF ${S} (${I.length} bytes)`),this.notifyFileCached(String(S),"layout"))}catch{}I&&h.set(S,t(I,u))});await Promise.allSettled(f),u.info(`Parsed ${h.size} XLFs`);const m=async(S,b)=>{if(!b.path||b.path==="null"||b.path==="undefined")return!1;const I=r(b);try{if((await fetch(`/store/${I}`,{method:"HEAD"})).ok)return!1}catch{}if(X.getTask(I))return!1;try{const T=await fetch(`/store/missing-chunks/${I}`);if(T.ok){const{missing:R,numChunks:N}=await T.json();if(N>0&&R.length<N){const M=new Set;for(let E=0;E<N;E++)R.includes(E)||M.add(E);b.skipChunks=M,u.info(`Resuming ${I}: ${M.size}/${N} chunks cached, ${R.length} to download`)}}}catch{}const A=S.addFile(b);return A.state!=="pending"?!1:(A.wait().then(T=>{const R=parseInt(b.size)||T.size;u.info("Download complete:",I,`(${R} bytes)`),R>this._chunkConfig.chunkSize&&fetch("/store/mark-complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({storeKey:I})}).catch(N=>u.warn("mark-complete failed:",I,N.message)),this.notifyFileCached(String(b.id),b.type),s.removeCompleted(I)}).catch(T=>{u.error("Download failed:",b.id,T),s.removeCompleted(r(b))}),!0)},w=new ne(s);for(const S of l)await m(w,S);const L=await w.build();L.length>0&&(L.push(de),s.enqueueOrderedTasks(L));const C=new Set,v=[...h.keys()].filter(S=>!i.includes(S)),_=new Map;for(const[S,b]of c)b.saveAs&&_.set(b.saveAs,S);const k=new Map;if(o)for(const[S,b]of Object.entries(o))k.set(parseInt(S,10),b);for(const S of i){const b=h.get(S);if(!b)continue;const I=new Set(b);for(const M of v){const E=h.get(M);if(E)for(const j of E)I.add(j)}const A=k.get(S)||[];for(const M of A){const E=_.get(M);E&&I.add(E)}const T=[];for(const M of I){if(c.has(M)&&!C.has(M)){T.push(c.get(M)),C.add(M);continue}const E=d.get(String(M))||[];for(const j of E)C.has(j)||(T.push(c.get(j)),C.add(j))}if(T.length===0)continue;u.info(`Layout ${S}: ${T.length} media`),T.sort((M,E)=>(M.size||0)-(E.size||0));const R=new ne(s);for(const M of T)await m(R,M);const N=await R.build();N.length>0&&(N.push(de),s.enqueueOrderedTasks(N))}const x=[...c.keys()].filter(S=>!C.has(S));if(x.length>0){u.info(`${x.length} media not in any XLF`);const S=new ne(s);for(const I of x){const A=c.get(I);A&&await m(S,A)}const b=await S.build();b.length>0&&s.enqueueOrderedTasks(b)}u.info("Downloads active:",s.running,", queued:",s.queue.length)}setupRendererEventHandlers(){this.renderer.on("layoutStart",(e,t)=>{var i;u.info("Layout started:",e),this.updateStatus(`Playing layout ${e}`),this.core.setCurrentLayout(e),this._currentLayoutEnableStat=(t==null?void 0:t.enableStat)!==!1,(i=this.timelineOverlay)==null||i.update(null,e),this.statsCollector&&this._currentLayoutEnableStat&&this.statsCollector.startLayout(e,this.currentScheduleId).catch(n=>{u.error("Failed to start layout stat:",n)})}),this.renderer.on("layoutEnd",e=>{u.info("Layout ended:",e),K==null||K.recordPlay(e.toString()),this.statsCollector&&this._currentLayoutEnableStat&&this.statsCollector.endLayout(e,this.currentScheduleId).catch(i=>{u.error("Failed to end layout stat:",i)}),this.core.notifyLayoutStatus(e),this.core.clearCurrentLayout();const t=this.core.getPendingLayouts();if(t.length>0){u.info(`Layout ${t[0]} pending download, skipping advance`);return}u.info("Layout cycle completed, advancing to next layout..."),this.core.advanceToNextLayout()}),this.renderer.on("widgetStart",e=>{const{widgetId:t,layoutId:i,mediaId:n}=e;u.debug("Widget started:",e.type,t,"media:",n),this.statsCollector&&n&&e.enableStat!==!1&&this.statsCollector.startWidget(n,i,this.currentScheduleId).catch(o=>{u.error("Failed to start widget stat:",o)})}),this.renderer.on("widgetEnd",e=>{const{widgetId:t,layoutId:i,mediaId:n}=e;u.debug("Widget ended:",e.type,t,"media:",n),this.statsCollector&&n&&e.enableStat!==!1&&this.statsCollector.endWidget(n,i,this.currentScheduleId).catch(o=>{u.error("Failed to end widget stat:",o)})}),this.renderer.on("error",e=>{var t;u.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.submitFault(e.type||"RENDERER_ERROR",`Renderer error: ${e.message||e.type}`,{layoutId:e.layoutId,regionId:e.regionId,widgetId:e.widgetId})}),this.renderer.on("action-trigger",e=>{var r,a;const{actionType:t,triggerCode:i,layoutCode:n,targetId:o,commandCode:s}=e;switch(u.info("Action trigger:",t,e),t){case"navLayout":case"navigateToLayout":i?this.core.handleTrigger(i):n&&this.core.changeLayout(n);break;case"navWidget":case"navigateToWidget":i?this.core.handleTrigger(i):o&&this.renderer.navigateToWidget(o);break;case"previousWidget":this.renderer.previousWidget((r=e.source)==null?void 0:r.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:u.warn("Unknown action type:",t)}this.statsCollector&&this.statsCollector.recordEvent("touch",this.core.getCurrentLayoutId(),e.targetId||null,this.currentScheduleId)}),this.renderer.on("widgetAction",e=>{e.type==="durationEnd"&&e.url&&(u.info(`Widget ${e.widgetId} duration ended, calling webhook: ${e.url}`),this.statsCollector&&this.statsCollector.recordEvent("webhook",e.layoutId,e.widgetId,this.currentScheduleId),fetch(e.url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({widgetId:e.widgetId,layoutId:e.layoutId,regionId:e.regionId,event:"durationEnd",timestamp:new Date().toISOString()})}).catch(t=>u.warn("Webhook failed (non-critical):",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){u.debug("No next layout to preload (single layout schedule or same layout)");return}const t=e.layoutId;if(this.renderer.layoutPool.has(t)){u.debug(`Layout ${t} already in preload pool`);return}u.info(`Preloading next layout ${t}...`);const i=await W.get("api/v2/player/layouts",t);if(!i){u.debug(`Layout ${t} XLF not cached, skipping preload`);return}const n=await i.text(),{allMedia:o}=this.getMediaIds(n);if(!await this.checkAllMediaCached(o)){u.debug(`Media not fully cached for layout ${t}, skipping preload`);return}await this.fetchWidgetHtml(n,t),await this.renderer.preloadLayout(n,t)?u.info(`Layout ${t} preloaded successfully`):u.warn(`Layout ${t} preload failed (will fall back to normal render)`)}catch(e){u.warn("Layout preload failed (non-blocking):",e)}}),this.renderer.on("videoError",async({fileId:e})=>{const t=`${O.slice(1)}/media/${e}`;try{const i=await fetch(`/store/missing-chunks/${t}`),{missing:n}=await i.json();if(n.length===0){u.warn(`Video error for media ${e} but no missing chunks — possible decode error`);return}u.warn(`Video ${e}: ${n.length} missing chunks (${n.join(", ")}), re-downloading`),await fetch("/store/unmark-complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({storeKey:t})}),this.core.collectNow().catch(o=>{u.error(`Failed to trigger re-download for media ${e}:`,o.message)})}catch(i){u.error(`Failed to check/re-download media ${e}:`,i.message)}})}async prepareAndRenderLayout(e){var t;if(this.core.getCurrentLayoutId()===e){u.debug(`Layout ${e} already playing, skipping duplicate prepare`);return}if(this.preparingLayoutId===e){u.debug(`Layout ${e} preparation in progress, will retry after it completes`),this._pendingRetryLayoutId=e;return}this.preparingLayoutId=e;try{const i=await W.get("api/v2/player/layouts",e);if(!i){u.info("Layout not in cache yet, marking as pending:",e),this.core.setPendingLayout(e,[e]),this.updateStatus(`Downloading layout ${e}...`);return}const n=await i.text(),{allMedia:o}=this.getMediaIds(n);if(!await this.checkAllMediaCached(o)){X.prioritizeLayoutFiles(o.map(String)),u.info(`Waiting for media to finish downloading for layout ${e}`),this.updateStatus(`Preparing layout ${e}...`),this.core.setPendingLayout(e,o);return}this.renderer.hasPreloadedLayout(e)||await this.fetchWidgetHtml(n,e),await this.renderer.renderLayout(n,e),this.updateStatus(`Playing layout ${e}`)}catch(i){u.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}`),this.submitFault("LAYOUT_LOAD_FAILED",`Failed to prepare layout ${e}: ${(i==null?void 0:i.message)||i}`,{layoutId:e})}finally{this.preparingLayoutId=null;const i=this._pendingRetryLayoutId;this._pendingRetryLayoutId=null,i!=null&&this.core.getCurrentLayoutId()!==i&&(u.debug(`Retrying preparation for layout ${i} after 500ms`),setTimeout(()=>this.prepareAndRenderLayout(i),500))}}getMediaIds(e){var r;const i=new DOMParser().parseFromString(e,"text/xml"),n=[],o=[];i.querySelectorAll("media[fileId]").forEach(a=>{const l=a.getAttribute("fileId");if(l){const c=parseInt(l,10);n.push(c),a.getAttribute("type")==="video"&&o.push(c)}});const s=(r=i.querySelector("layout"))==null?void 0:r.getAttribute("background");if(s){const a=parseInt(s,10);!isNaN(a)&&!n.includes(a)&&n.push(a)}return{allMedia:n,videoMedia:o}}async checkAllMediaCached(e){for(const t of e)try{if(!await W.has("api/v2/player",`media/${t}`))return u.debug(`Media ${t} not yet cached`),!1;u.debug(`Media ${t} cached`)}catch{u.warn(`Unable to verify media ${t}, assuming cached (offline mode)`)}return!0}async fetchWidgetHtml(e,t){const n=new DOMParser().parseFromString(e,"text/xml"),o=[];for(const s of n.querySelectorAll("region")){const r=s.getAttribute("id");for(const a of s.querySelectorAll("media")){const l=a.getAttribute("type"),c=a.getAttribute("id");a.getAttribute("render")==="html"&&o.push((async()=>{try{const h=`${t}/${r}/${c}`;let g=null;const f=await W.get("api/v2/player/widgets",h);f&&(g=await f.text(),u.debug(`Found cached widget HTML for ${l} ${c}`)),g||(g=await this.xmds.getResource(t,r,c),u.debug(`Retrieved widget HTML for ${l} ${c} from CMS`)),await me(t,r,c,g);const m=await W.get("api/v2/player/widgets",h);m&&(g=await m.text());const w=a.querySelector("raw");if(w)w.textContent=g;else{const L=n.createElement("raw");L.textContent=g,a.appendChild(L)}}catch(h){u.warn(`Failed to get widget HTML for ${l} ${c}:`,h)}})())}}o.length>0&&(u.info(`Fetching ${o.length} widget HTML resources in parallel...`),await Promise.all(o),u.debug("All widget HTML fetched"))}async probeLayoutDurations(){if(this.scheduledLayoutIds.size!==0)for(const e of this.scheduledLayoutIds)try{const t=await W.get("api/v2/player/layouts",e);if(!t)continue;const i=await t.text(),{videoMedia:n}=this.getMediaIds(i);if(n.length===0)continue;const s=new DOMParser().parseFromString(i,"text/xml"),r=new Map;for(const l of s.querySelectorAll('media[type="video"]')){if(l.getAttribute("useDuration")==="1")continue;const d=l.getAttribute("fileId");if(!d||!await W.has("api/v2/player",`media/${d}`))continue;const g=await this.probeVideoDuration(`${window.location.origin}${O}/media/${d}`);g>0&&r.set(d,g)}if(r.size===0)continue;let a=0;for(const l of s.querySelectorAll("region")){if(l.getAttribute("type")==="drawer")continue;let c=0;for(const d of l.querySelectorAll("media")){const h=parseInt(d.getAttribute("duration")||"0",10),g=parseInt(d.getAttribute("useDuration")||"1",10),f=d.getAttribute("fileId")||"",m=r.get(f);m!==void 0?c+=m:h>0&&g!==0&&(c+=h)}a=Math.max(a,c)}a>0&&this.core.recordLayoutDuration(String(e),a)}catch(t){u.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 n=()=>{i.removeAttribute("src"),i.load()};i.addEventListener("loadedmetadata",()=>{const o=Math.floor(i.duration);n(),t(o)},{once:!0}),i.addEventListener("error",()=>{n(),t(0)},{once:!0}),setTimeout(()=>{n(),t(0)},5e3),i.src=e})}updateConfigDisplay(){const e=document.getElementById("config-info");if(e){const t="0.6.0",i="2026-03-02T20:56:57.798Z".replace("T"," ").replace(/\.\d+Z$/,""),n=i?`v${t} (${i})`:`v${t}`;e.textContent=`${n} | CMS: ${D.cmsUrl} | Display: ${D.displayName||"Unknown"} | HW: ${D.hardwareKey}`}}async submitStats(){var e;if(!this.statsCollector){u.warn("Stats collector not initialized");return}if(this._pendingFollowerStats!==null){u.debug("Stats delegation in-flight, skipping");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){u.debug("No stats to submit");return}const n=be(i);if(this.syncManager&&!this.syncManager.isLead&&this._syncLeadAlive()){u.info(`[Sync] Delegating ${i.length} stats to lead`),this._pendingFollowerStats=i,this.syncManager.reportStats(n);return}this.syncManager&&!this.syncManager.isLead&&u.warn("[Sync] Lead not alive, submitting stats directly"),u.info(`Submitting ${i.length} proof of play stats...`),await this.xmds.submitStats(n)?(u.info("Stats submitted successfully"),await this.statsCollector.clearSubmittedStats(i),u.debug(`Cleared ${i.length} submitted stats from database`)):u.warn("Stats submission failed (CMS returned false)")}catch(t){u.error("Failed to submit stats:",t)}}async submitLogs(){if(this.logReporter){if(this._pendingFollowerLogs!==null){u.debug("Logs delegation in-flight, skipping");return}try{const e=await this.logReporter.getLogsForSubmission();if(e.length===0){u.debug("No logs to submit");return}const t=Le(e);if(this.syncManager&&!this.syncManager.isLead&&this._syncLeadAlive()){u.info(`[Sync] Delegating ${e.length} logs to lead`),this._pendingFollowerLogs=e,this.syncManager.reportLogs(t);return}this.syncManager&&!this.syncManager.isLead&&u.warn("[Sync] Lead not alive, submitting logs directly"),u.info(`Submitting ${e.length} logs to CMS...`),await this.xmds.submitLog(t)?(u.info("Logs submitted successfully"),await this.logReporter.clearSubmittedLogs(e)):u.warn("Log submission failed (CMS returned false)")}catch(e){u.error("Failed to submit logs:",e)}}}submitFault(e,t,i){if(!this.xmds)return;const n=JSON.stringify([{code:e,reason:t,date:new Date().toISOString().replace("T"," ").substring(0,19),...i}]);this.xmds.reportFaults(n).catch(o=>{u.debug("reportFaults failed (non-critical):",o)})}async captureAndSubmitScreenshot(){var e;if(this._screenshotInFlight){u.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 n=await window.electronAPI.captureScreenshot();if(n)this._screenshotMethod="electron",t=n;else{u.debug("Electron screenshot not ready yet, will retry next interval");return}}else t=await this.captureWithBrowserMethods();await this.xmds.submitScreenShot(t)?u.info(`Screenshot submitted (${this._screenshotMethod})`):u.warn("Screenshot submission failed")}catch(t){u.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 n=i.getBoundingClientRect(),o=getComputedStyle(i),s=o.backgroundColor;s&&s!=="transparent"&&s!=="rgba(0, 0, 0, 0)"&&(t.fillStyle=s,t.fillRect(n.left,n.top,n.width,n.height));const r=o.backgroundImage;if(r&&r!=="none"){const c=r.match(/url\(["']?(.*?)["']?\)/);if(c)try{const d=new Image;d.crossOrigin="anonymous",await new Promise(h=>{d.onload=()=>h(),d.onerror=()=>h(),setTimeout(()=>h(),2e3),d.src=c[1]}),d.naturalWidth&&t.drawImage(d,n.left,n.top,n.width,n.height)}catch{}}this._html2canvasMod||(this._html2canvasMod=(await P(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 d=c;if(d.style.visibility==="hidden"||d.style.display==="none")continue;const h=c.getBoundingClientRect();if(!(h.width===0||h.height===0))try{if(c instanceof HTMLImageElement){if(!c.complete||!c.naturalWidth)continue;if(getComputedStyle(c).objectFit==="contain"&&c.naturalWidth&&c.naturalHeight){const f=this.containedRect(c.naturalWidth,c.naturalHeight,h);t.drawImage(c,f.x,f.y,f.w,f.h)}else t.drawImage(c,h.left,h.top,h.width,h.height);l++}else if(c instanceof HTMLVideoElement){if(c.readyState<2)continue;if(getComputedStyle(c).objectFit==="contain"&&c.videoWidth&&c.videoHeight){const f=this.containedRect(c.videoWidth,c.videoHeight,h);t.drawImage(c,f.x,f.y,f.w,f.h)}else t.drawImage(c,h.left,h.top,h.width,h.height);l++}else if(c instanceof HTMLCanvasElement)t.drawImage(c,h.left,h.top,h.width,h.height),l++;else if(c instanceof HTMLIFrameElement){const g=c.contentDocument;if(!(g!=null&&g.body))continue;const f=document.createElement("div");f.style.cssText=`position:fixed;left:-9999px;top:0;width:${h.width}px;height:${h.height}px;overflow:hidden;`;const m=[];for(const v of g.querySelectorAll("style"))f.appendChild(v.cloneNode(!0));for(const v of g.querySelectorAll('link[rel="stylesheet"]')){const _=document.createElement("link");_.rel="stylesheet",_.href=new URL(v.getAttribute("href")||"",g.baseURI).href,f.appendChild(_),m.push(new Promise(k=>{_.onload=()=>k(),_.onerror=()=>k()}))}f.appendChild(g.body.cloneNode(!0)),document.body.appendChild(f);const w=g.querySelectorAll("img"),L=new Map;w.forEach((v,_)=>{v.naturalWidth&&v.naturalHeight&&L.set(String(_),{nw:v.naturalWidth,nh:v.naturalHeight})}),m.length>0&&await Promise.race([Promise.all(m),new Promise(v=>setTimeout(v,500))]);const C=await this._html2canvasMod(f,{useCORS:!0,allowTaint:!0,logging:!1,backgroundColor:null,width:h.width,height:h.height,onclone:v=>{const _=v.createElement("style");_.textContent="*, *::before, *::after { animation: none !important; transition: none !important; opacity: 1 !important; }",v.head.appendChild(_),v.querySelectorAll("img").forEach((x,S)=>{var le,ce;const b=(le=v.defaultView)==null?void 0:le.getComputedStyle(x);if(!b||b.objectFit!=="contain")return;const I=L.get(String(S));if(!I)return;const A=x.clientWidth||parseFloat(b.width)||0,T=x.clientHeight||parseFloat(b.height)||0;if(!A||!T)return;const R=I.nw/I.nh,N=A/T;let M,E;R>N?(M=A,E=A/R):(E=T,M=T*R);const j=v.createElement("div");j.style.cssText=`width:${A}px;height:${T}px;display:flex;align-items:center;justify-content:center;overflow:hidden;`,x.style.objectFit="fill",x.style.width=`${M}px`,x.style.height=`${E}px`,(ce=x.parentNode)==null||ce.insertBefore(j,x),j.appendChild(x)})}});document.body.removeChild(f),t.drawImage(C,h.left,h.top,h.width,h.height),l++}}catch(g){u.warn("Screenshot: failed to draw element",c.tagName,g)}}return u.debug(`Screenshot: composed ${l}/${a.length} elements`),e.toDataURL("image/jpeg",.8).split(",")[1]}containedRect(e,t,i){const n=e/t,o=i.width/i.height;let s,r;return n>o?(s=i.width,r=i.width/n):(r=i.height,s=i.height*n),{x:i.left+(i.width-s)/2,y:i.top+(i.height-r)/2,w:s,h:r}}startScreenshotInterval(){var i;const e=((i=this.displaySettings)==null?void 0:i.getSetting("screenshotInterval"))||0;if(!e||e<=0)return;this._html2canvasMod||P(()=>import("./html2canvas.esm-CBrSDip1.js"),[],import.meta.url).then(n=>{this._html2canvasMod=n.default});const t=e*1e3;u.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"?u.error("Status:",e):u.info("Status:",e)}showOfflineIndicator(){var e;(e=this.timelineOverlay)==null||e.setOffline(!0)}removeOfflineIndicator(){var e;(e=this.timelineOverlay)==null||e.setOffline(!1)}_syncLeadAlive(){if(!this.syncManager)return!1;for(const[,e]of this.syncManager.followers)if(e.role==="lead"&&Date.now()-e.lastSeen<15e3)return!0;return!1}cleanup(){this.core.cleanup(),this.renderer.cleanup(),this._screenshotInterval&&(clearInterval(this._screenshotInterval),this._screenshotInterval=null),this._wakeLock&&(this._wakeLock.release(),this._wakeLock=null),this.downloadOverlay&&this.downloadOverlay.destroy(),this.timelineOverlay&&this.timelineOverlay.destroy(),this._iframeObserver&&(this._iframeObserver.disconnect(),this._iframeObserver=null),navigator.serviceWorker&&this._swIcHandler&&(navigator.serviceWorker.removeEventListener("message",this._swIcHandler),this._swIcHandler=null),X==null||X.clear(),this._probeTimer&&(clearTimeout(this._probeTimer),this._probeTimer=null)}}function Ie(){const p=new at;p.init().catch(e=>{u.error("Failed to initialize:",e),u.warn("Redirecting to setup screen..."),window.location.href="./setup.html"}),window.addEventListener("beforeunload",()=>{p.cleanup()})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ie):Ie();export{et as D,ft as I,Y as L,Ze as O,nt as P,Ue as R,Ye as S,ht as a,Qe as b,Ke as c,ze as p,gt as s};
|
|
734
|
-
//# sourceMappingURL=main-BuxLonCL.js.map
|
|
728
|
+
`,this.iframe.addEventListener("load",()=>{var o,s;try{if((((s=(o=this.iframe.contentWindow)==null?void 0:o.location)==null?void 0:s.href)||"").includes("index.html")){this.hide(),window.location.reload();return}const a=this.iframe.contentDocument;if(!a)return;a.addEventListener("keydown",l=>{l.key==="Escape"&&(l.preventDefault(),this.hide())})}catch{}}),this.backdrop.appendChild(this.cancelBtn),this.backdrop.appendChild(this.gateCard),this.backdrop.appendChild(this.iframe),document.body.appendChild(this.backdrop);const e=this.gateCard.querySelector("#gate-form"),t=this.gateCard.querySelector("#gate-key"),i=this.gateCard.querySelector("#gate-error"),n=this.gateCard.querySelector("#gate-cancel");t.addEventListener("focus",()=>{t.style.borderColor="#0097D8"}),t.addEventListener("blur",()=>{t.style.borderColor="#3A3A3A"}),e.addEventListener("submit",o=>{o.preventDefault(),t.value.trim()===Me.cmsKey?this.showSetup():(i.textContent="Incorrect CMS key",i.style.display="block",t.focus(),t.select())}),n.addEventListener("click",()=>this.hide()),this.backdrop.addEventListener("keydown",o=>{o.key==="Escape"&&(o.preventDefault(),this.hide()),o.stopPropagation()})}}const d=q("PWA"),oe=new URL("./",window.location.href).pathname.replace(/\/$/,"");let me,K,D,ee,se,we,W,X,ve,be,Se,Le,$e,Ce;const z={};class at{constructor(){$(this,"renderer");$(this,"core");$(this,"xmds");$(this,"downloadOverlay",null);$(this,"timelineOverlay",null);$(this,"setupOverlay",null);$(this,"statsCollector",null);$(this,"logReporter",null);$(this,"displaySettings",null);$(this,"currentScheduleId",-1);$(this,"scheduledLayoutIds",new Set);$(this,"preparingLayoutId",null);$(this,"_pendingRetryLayoutId",null);$(this,"_screenshotInterval",null);$(this,"_screenshotMethod",null);$(this,"_screenshotInFlight",!1);$(this,"_html2canvasMod",null);$(this,"_wakeLock",null);$(this,"syncManager",null);$(this,"_currentLayoutEnableStat",!0);$(this,"_probeTimer",null);$(this,"_pendingFollowerStats",null);$(this,"_pendingFollowerLogs",null);$(this,"_iframeObserver",null);$(this,"_swIcHandler",null);$(this,"_chunkConfig",null)}async init(){if(d.info("Initializing player with RendererLite + PlayerCore..."),await this.loadCoreModules(),"serviceWorker"in navigator)try{const s=await navigator.serviceWorker.register(`${oe}/sw-pwa.js?v=${Date.now()}`,{scope:`${oe}/`,type:"module",updateViaCache:"none"});d.info("Service Worker registered for offline mode:",s.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(s){d.warn("Service Worker registration failed:",s)}d.info("Initializing cache clients..."),W=new Oe;const{calculateChunkConfig:e}=await P(async()=>{const{calculateChunkConfig:s}=await import("./index-B6MdC-Qx.js");return{calculateChunkConfig:s}},__vite__mapDeps([0,1,2]),import.meta.url);this._chunkConfig=e(d),X=new Pe({concurrency:this._chunkConfig.concurrency,chunkSize:this._chunkConfig.chunkSize,chunksPerFile:2}),d.info("Cache clients ready — StoreClient + DownloadManager");const t=document.getElementById("player-container");if(!t)throw new Error("No #player-container found");this.renderer=new Ue({cmsUrl:D.cmsUrl,hardwareKey:D.hardwareKey},t,{getMediaUrl:async s=>{if(d.debug(`getMediaUrl called for media ${s}`),!await W.has("api/v2/player",`media/${s}`))return d.warn(`Media ${s} not in cache`),"";const a=`${window.location.origin}${O}/media/${s}`;return d.debug(`Using streaming URL for media ${s}: ${a}`),a},getWidgetHtml:async s=>{const r=`${O}/widgets/${s.layoutId}/${s.regionId}/${s.id}`;d.debug(`Looking for widget HTML at: ${r}`,s);try{if(await W.has("api/v2/player/widgets",`${s.layoutId}/${s.regionId}/${s.id}`))return d.debug("Widget HTML found in store, using mirror URL for iframe"),{url:r,fallback:s.raw||""};d.warn(`No widget HTML found in store: ${r}`)}catch(a){d.error(`Failed to check widget HTML for ${s.id}:`,a)}return d.warn(`Using widget.raw fallback for ${s.id}`),s.raw||""}}),this.core=new nt({config:D,xmds:this.xmds,cache:W,schedule:K,renderer:this.renderer,xmrWrapper:we,statsCollector:this.statsCollector,displaySettings:this.displaySettings}),this.setupCoreEventHandlers(),this.setupRendererEventHandlers(),this.setupInteractiveControl(),this.setupRemoteControls(),this.core.on("register-complete",s=>{var l,c;const r=parseFloat((l=s==null?void 0:s.settings)==null?void 0:l.latitude),a=parseFloat((c=s==null?void 0:s.settings)==null?void 0:c.longitude);r&&a&&!isNaN(r)&&!isNaN(a)?(d.info(`Display location from CMS: ${r.toFixed(4)}, ${a.toFixed(4)}`),K!=null&&K.setLocation&&K.setLocation(r,a)):this.core.requestGeoLocation&&(d.info("No CMS coordinates, requesting browser geolocation..."),this.core.requestGeoLocation())}),this.updateConfigDisplay(),window.addEventListener("online",()=>{d.info("Browser reports online — triggering immediate collection"),this.updateStatus("Back online, syncing..."),this.removeOfflineIndicator(),this.core.collectNow().catch(s=>{d.error("Failed to collect after coming online:",s)})}),window.addEventListener("offline",()=>{d.warn("Browser reports offline — continuing playback with cached data"),this.updateStatus("Offline mode — using cached content"),this.showOfflineIndicator()});const n=(this.getControls().keyboard||{}).debugOverlays===!0,o=ot();o.enabled&&n&&(this.downloadOverlay=new fe(o),this.downloadOverlay.setProgressCallback(()=>X.getProgress()),d.info("Download overlay enabled (hover bottom-right corner)")),st()&&n&&(this.timelineOverlay=new ye(!0,s=>this.skipToLayout(s))),this.setupCertWarnings(),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)}}setupCertWarnings(){const e=new Set;window.addEventListener("cert-warning",t=>{const{host:i,error:n}=t.detail;if(e.has(i))return;e.add(i),d.warn(`Invalid SSL certificate accepted for stream: ${i} (${n})`);let o=document.getElementById("overlay"),s=!1;if(!o){o=document.createElement("div"),o.id="overlay";const l=document.createElement("div");l.id="config-info",o.appendChild(l);const c=document.createElement("div");c.id="status",o.appendChild(c),document.body.appendChild(o),s=!0}let r=document.getElementById("cert-warnings");if(!r){r=document.createElement("span"),r.id="cert-warnings",r.style.cssText="color: #ffaa33; flex: 0 0 auto;";const l=document.getElementById("status");o.insertBefore(r,l)}const a=[...e].join(", ");r.textContent=`⚠ SSL: ${a}`,s&&this.updateConfigDisplay()})}async loadCoreModules(){var e,t,i;try{const n=await P(()=>import("./index-C3Orblel.js"),__vite__mapDeps([3,4,2]),import.meta.url),o=await P(()=>import("./index-D81Qhc3r.js"),__vite__mapDeps([5,6,2]),import.meta.url),s=await P(()=>import("./index-Dj2ND9Mx.js"),__vite__mapDeps([7,8,2,4]),import.meta.url),r=await P(()=>import("./index-leM889oV.js"),[],import.meta.url),a=await P(()=>import("./index-BWvWWyDc.js"),__vite__mapDeps([9,2]),import.meta.url),l=await P(()=>import("./index-BVIXBw9z.js"),__vite__mapDeps([10,2]),import.meta.url),c=await P(()=>import("./index-CleHw0Tc.js"),__vite__mapDeps([11,2]),import.meta.url),u=await P(()=>import("./index-D_aTOqNE.js"),__vite__mapDeps([12,2,8,4]),import.meta.url),h=await P(()=>import("./index-Cq9aOTTR.js"),__vite__mapDeps([13,8,2,4]),import.meta.url),g=await P(()=>import("./sync-manager-DEght5gK.js"),__vite__mapDeps([14,2]),import.meta.url);if(me=n.cacheWidgetHtml,Ce=g.SyncManager,K=s.scheduleManager,D=r.config,ee=o.RestClient,se=o.XmdsClient,we=a.XmrWrapper,ve=l.StatsCollector,be=l.formatStats,Se=l.LogReporter,Le=l.formatLogs,$e=c.DisplaySettings,z.core=u.VERSION||"?",z.cache=n.VERSION||"?",z.renderer=h.VERSION||"?",z.schedule=s.VERSION||"?",z.xmds=o.VERSION||"?",z.xmr=a.VERSION||"?",z.utils=r.VERSION||"?",z.stats=l.VERSION||"?",z.settings=c.VERSION||"?",(e=window.electronAPI)!=null&&e.getSystemInfo)try{const b=await window.electronAPI.getSystemInfo();b.macAddress&&(D.macAddress=b.macAddress)}catch{}const f=(()=>{try{return JSON.parse(localStorage.getItem("xibo_config")||"{}").transport}catch{return}})(),w=new URLSearchParams(window.location.search).get("transport")||(oe.includes("pwa-xmds")?"xmds":null)||f||"auto";w==="xmds"?(d.info("Using XMDS/SOAP transport (forced)"),this.xmds=new se(D)):w==="rest"?(d.info("Using REST transport (forced)"),this.xmds=new ee(D)):await ee.isAvailable(D.cmsUrl,{maxRetries:0})?(this.xmds=new ee(D),d.info("Using REST transport (auto-detected)")):(d.warn("REST unavailable, falling back to XMDS/SOAP"),this.xmds=new se(D)),this.statsCollector=new ve,await this.statsCollector.init(),d.info("Stats collector initialized"),this.logReporter=new Se,await this.logReporter.init(),d.info("Log reporter initialized"),Ae(({level:b,name:I,args:A})=>{if(!this.logReporter)return;const T=A.map(R=>typeof R=="string"?R:JSON.stringify(R)).join(" ");this.logReporter.log(b,`[${I}] ${T}`,"PLAYER").catch(()=>{})}),this.displaySettings=new $e,d.info("Display settings manager initialized");const L="2026-03-03T03:55:17.568Z",C="0.6.1";d.info(`v${C} built ${L}`);const v=Object.entries(z).map(([b,I])=>`${b}=${I}`).join(" ");d.info(`SDK: ${v}`);const x=!!window.electronAPI,k=x?((t=navigator.userAgent.match(/Electron\/([\d.]+)/))==null?void 0:t[1])||"?":null,_=((i=navigator.userAgent.match(/Chrome\/([\d.]+)/))==null?void 0:i[1])||"?",S=x?`Electron ${k} / Chrome ${_}`:`Chrome ${_}`;d.info(`Env: PWA v${C} | ${S} | ${navigator.platform} | ${screen.width}x${screen.height}`),d.info("Core modules loaded")}catch(n){throw d.error("Failed to load core modules:",n),n}}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||D.hardwareKey;this.updateStatus(`Registered: ${t}`),this.displaySettings&&(document.title=`Xibo Player - ${this.displaySettings.getDisplayName()}`)}),this.core.on("sync-config",e=>{this.syncManager&&this.syncManager.stop(),this.syncManager=new Ce({displayId:D.hardwareKey,syncConfig:e,onLayoutChange:async t=>{var i;d.info(`[Sync] Loading layout ${t} (waiting for show signal)`),await this.prepareAndRenderLayout(parseInt(t,10)),(i=this.syncManager)==null||i.reportReady(t)},onLayoutShow:t=>{d.info(`[Sync] Show signal for layout ${t}`)},onVideoStart:(t,i)=>{var n,o;d.info(`[Sync] Video start: layout ${t} region ${i}`),(o=(n=this.renderer).resumeRegionMedia)==null||o.call(n,i)},onStatsReport:async(t,i,n)=>{d.info(`[Sync] Submitting stats for follower ${t}`);try{await this.xmds.submitStats(i,t)&&n()}catch(o){d.warn(`[Sync] Stats submission failed for follower ${t}:`,o)}},onLogsReport:async(t,i,n)=>{d.info(`[Sync] Submitting logs for follower ${t}`);try{await this.xmds.submitLog(i,t)&&n()}catch(o){d.warn(`[Sync] Log submission failed for follower ${t}:`,o)}},onStatsAck:async t=>{d.info("[Sync] Lead confirmed stats submission"),this._pendingFollowerStats&&this.statsCollector&&(await this.statsCollector.clearSubmittedStats(this._pendingFollowerStats),this._pendingFollowerStats=null)},onLogsAck:async t=>{d.info("[Sync] Lead confirmed logs submission"),this._pendingFollowerLogs&&this.logReporter&&(await this.logReporter.clearSubmittedLogs(this._pendingFollowerLogs),this._pendingFollowerLogs=null)}}),this.core.setSyncManager(this.syncManager),this.syncManager.start(),d.info(`[Sync] SyncManager started as ${e.isLead?"LEAD":"FOLLOWER"}`)}),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 W.remove(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,i;(t=this.downloadOverlay)==null||t.startUpdating();try{const n=((i=this.xmds)==null?void 0:i._token)||null;n&&await fetch("/auth-token",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token:n})}),await this.enqueueDownloads(e),d.info("Download enqueue complete")}catch(n){d.error("Download request failed:",n),this.updateStatus("Download failed: "+n,"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 r=parseInt(String(s.file||s.id||s).replace(".xlf",""),10);r&&i.add(r)}}const n=this.renderer.layoutPool.clearWarmNotIn(i);n>0&&d.info(`Cleared ${n} 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.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.submitFault("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}`),d.warn(`XMR misconfigured: ${e.message}`)}),this.core.on("log-level-changed",()=>{d.info("Log level changed")}),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 W.list();if(e.length>0){const i=await W.remove(e);d.info(`Purged ${i.deleted} files from ContentStore`)}const t=await caches.keys();t.length>0&&(await Promise.all(t.map(i=>caches.delete(i))),d.info(`Purged ${t.length} legacy caches`))}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.submitFault("COMMAND_FAILED",`Command ${e.code} failed: ${e.reason||"unknown"}`))}),this.core.on("scheduled-command",e=>{d.info(`Scheduled command: ${e.code}`),this.core.executeCommand(e.code)}),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;this._swIcHandler=t=>{var l,c;if(((l=t.data)==null?void 0:l.type)!=="INTERACTIVE_CONTROL")return;const{method:i,path:n,search:o,body:s}=t.data,r=(c=t.ports)==null?void 0:c[0];if(!r)return;const a=this.handleInteractiveControl(i,n,o,s);r.postMessage(a)},(e=navigator.serviceWorker)==null||e.addEventListener("message",this._swIcHandler)}setupRemoteControls(){window.addEventListener("blur",()=>{setTimeout(()=>window.focus(),200)});const e=a=>{const l=()=>{var c;try{const u=a.contentDocument||((c=a.contentWindow)==null?void 0:c.document);if(!u||a.__keyForwarderAttached)return;a.__keyForwarderAttached=!0,u.addEventListener("keydown",h=>{const g=new KeyboardEvent("keydown",{key:h.key,code:h.code,keyCode:h.keyCode,ctrlKey:h.ctrlKey,shiftKey:h.shiftKey,altKey:h.altKey,metaKey:h.metaKey,bubbles:!0,cancelable:!0});document.dispatchEvent(g)||h.preventDefault()})}catch{}};a.addEventListener("load",l),l()};Array.from(document.querySelectorAll("iframe")).forEach(a=>e(a)),this._iframeObserver=new MutationObserver(a=>{for(const l of a)for(const c of l.addedNodes)c instanceof HTMLIFrameElement&&e(c),c instanceof HTMLElement&&c.querySelectorAll("iframe").forEach(u=>e(u))}),this._iframeObserver.observe(document.body,{childList:!0,subtree:!0});const t=this.getControls(),{keyboard:i={}}=t,n=i.debugOverlays===!0,o=i.setupKey===!0,s=i.playbackControl===!0,r=i.videoControls===!0;document.addEventListener("keydown",a=>{if(a.key==="q"&&(a.ctrlKey||a.metaKey)){a.preventDefault(),d.info("[Remote] Quit requested (Ctrl+Q)"),fetch("/quit",{method:"POST"}).catch(()=>{});return}switch(a.key){case"t":case"T":if(!n)break;this.timelineOverlay||(this.timelineOverlay=new ye(!0,l=>this.skipToLayout(l))),this.timelineOverlay.toggle();break;case"d":case"D":if(!n)break;this.downloadOverlay||(this.downloadOverlay=new fe({enabled:!0,autoHide:!1}),this.downloadOverlay.setProgressCallback(()=>X.getProgress())),this.downloadOverlay.toggle();break;case"v":case"V":{if(!r)break;const l=document.querySelectorAll("video"),c=l.length>0&&!l[0].controls;l.forEach(u=>u.controls=c);break}case"ArrowRight":case"PageDown":if(!s)break;d.info("[Remote] Next layout (keyboard)"),this.core.advanceToNextLayout(),a.preventDefault();break;case"ArrowLeft":case"PageUp":if(!s)break;d.info("[Remote] Previous layout (keyboard)"),this.core.advanceToPreviousLayout(),a.preventDefault();break;case" ":if(!s)break;d.info("[Remote] Toggle pause (keyboard)"),this.renderer.isPaused()?this.renderer.resume():this.renderer.pause(),a.preventDefault();break;case"r":case"R":if(!s)break;this.core.isLayoutOverridden()&&(d.info("[Remote] Revert to schedule (keyboard)"),this.core.revertToSchedule());break;case"s":case"S":if(!o)break;this.setupOverlay||(this.setupOverlay=new rt),this.setupOverlay.toggle(),a.preventDefault();break}}),s&&"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)")}getControls(){try{return JSON.parse(localStorage.getItem("xibo_config")||"{}").controls||{}}catch{return{}}}skipToLayout(e){d.info(`Skipping to layout ${e} (timeline click)`),this.core.changeLayout(e)}parseBody(e){try{return e?JSON.parse(e):{}}catch{return{}}}handleInteractiveControl(e,t,i,n){var o;switch(d.debug("IC request:",e,t,i),t){case"/info":return{status:200,body:JSON.stringify({hardwareKey:D.hardwareKey,displayName:D.displayName,playerType:"pwa",currentLayoutId:this.core.getCurrentLayoutId()})};case"/trigger":{const s=this.parseBody(n);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(n);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(n);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(n);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(n);return(o=this.logReporter)==null||o.reportFault(s.code||"WIDGET_FAULT",s.reason||"Widget reported fault"),this.submitFault(s.code||"WIDGET_FAULT",s.reason||"Widget reported fault",{layoutId:s.layoutId,regionId:s.regionId,widgetId:s.widgetId}),{status:200,body:"OK"}}case"/realtime":{const r=new URLSearchParams(i).get("dataKey");if(d.debug("IC: Realtime data request for key:",r),!r)return{status:400,body:JSON.stringify({error:"Missing dataKey parameter"})};const l=this.core.getDataConnectorManager().getData(r);return l===null?{status:404,body:JSON.stringify({error:`No data available for key: ${r}`})}:{status:200,body:typeof l=="string"?l:JSON.stringify(l)}}case"/criteria":return{status:200,body:JSON.stringify({displayId:D.displayId,hardwareKey:D.hardwareKey,displayName:D.displayName,width:window.innerWidth,height:window.innerHeight,latitude:D.latitude||null,longitude:D.longitude||null,playerType:"pwa"})};default:return{status:404,body:JSON.stringify({error:"Unknown IC route"})}}}notifyFileCached(e,t){d.debug(`Download complete: ${t}/${e}`),(t==="media"||t==="layout")&&this.core.notifyMediaReady(parseInt(e),t),this._probeTimer&&clearTimeout(this._probeTimer),this._probeTimer=setTimeout(()=>{this._probeTimer=null,this.probeLayoutDurations().catch(()=>{})},3e3)}async enqueueDownloads(e){const{extractMediaIdsFromXlf:t}=await P(async()=>{const{extractMediaIdsFromXlf:S}=await import("./index-B6MdC-Qx.js");return{extractMediaIdsFromXlf:S}},__vite__mapDeps([0,1,2]),import.meta.url),{layoutOrder:i,files:n,layoutDependants:o}=e,s=X.queue,r=S=>(S.path||"").split("?")[0].replace(/^\/+/,"")||`${S.type||"media"}/${S.id}`,a=new Map,l=[],c=new Map,u=new Map;for(const S of n)if(S.type==="layout")a.set(parseInt(S.id),S);else if(S.type==="static")l.push(S);else{const b=`${S.type}:${S.id}`;c.set(b,S);const I=String(S.id);u.has(I)||u.set(I,[]),u.get(I).push(b)}d.info(`Download: ${i.length} layouts, ${c.size} media, ${l.length} resources`);const h=new Map,f=[...i,...[...a.keys()].filter(S=>!i.includes(S))].map(async S=>{const b=a.get(S);if(!(b!=null&&b.path))return;let I;try{const A=await fetch(b.path);A.ok&&(I=await A.text(),d.info(`Fetched XLF ${S} (${I.length} bytes)`),this.notifyFileCached(String(S),"layout"))}catch{}I&&h.set(S,t(I,d))});await Promise.allSettled(f),d.info(`Parsed ${h.size} XLFs`);const m=async(S,b)=>{if(!b.path||b.path==="null"||b.path==="undefined")return!1;const I=r(b);try{if((await fetch(`/store/${I}`,{method:"HEAD"})).ok)return!1}catch{}if(X.getTask(I))return!1;try{const T=await fetch(`/store/missing-chunks/${I}`);if(T.ok){const{missing:R,numChunks:N}=await T.json();if(N>0&&R.length<N){const M=new Set;for(let E=0;E<N;E++)R.includes(E)||M.add(E);b.skipChunks=M,d.info(`Resuming ${I}: ${M.size}/${N} chunks cached, ${R.length} to download`)}}}catch{}const A=S.addFile(b);return A.state!=="pending"?!1:(A.wait().then(T=>{const R=parseInt(b.size)||T.size;d.info("Download complete:",I,`(${R} bytes)`),R>this._chunkConfig.chunkSize&&fetch("/store/mark-complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({storeKey:I})}).catch(N=>d.warn("mark-complete failed:",I,N.message)),this.notifyFileCached(String(b.id),b.type),s.removeCompleted(I)}).catch(T=>{d.error("Download failed:",b.id,T),s.removeCompleted(r(b))}),!0)},w=new ne(s);for(const S of l)await m(w,S);const L=await w.build();L.length>0&&(L.push(de),s.enqueueOrderedTasks(L));const C=new Set,v=[...h.keys()].filter(S=>!i.includes(S)),x=new Map;for(const[S,b]of c)b.saveAs&&x.set(b.saveAs,S);const k=new Map;if(o)for(const[S,b]of Object.entries(o))k.set(parseInt(S,10),b);for(const S of i){const b=h.get(S);if(!b)continue;const I=new Set(b);for(const M of v){const E=h.get(M);if(E)for(const j of E)I.add(j)}const A=k.get(S)||[];for(const M of A){const E=x.get(M);E&&I.add(E)}const T=[];for(const M of I){if(c.has(M)&&!C.has(M)){T.push(c.get(M)),C.add(M);continue}const E=u.get(String(M))||[];for(const j of E)C.has(j)||(T.push(c.get(j)),C.add(j))}if(T.length===0)continue;d.info(`Layout ${S}: ${T.length} media`),T.sort((M,E)=>(M.size||0)-(E.size||0));const R=new ne(s);for(const M of T)await m(R,M);const N=await R.build();N.length>0&&(N.push(de),s.enqueueOrderedTasks(N))}const _=[...c.keys()].filter(S=>!C.has(S));if(_.length>0){d.info(`${_.length} media not in any XLF`);const S=new ne(s);for(const I of _){const A=c.get(I);A&&await m(S,A)}const b=await S.build();b.length>0&&s.enqueueOrderedTasks(b)}d.info("Downloads active:",s.running,", queued:",s.queue.length)}setupRendererEventHandlers(){this.renderer.on("layoutStart",(e,t)=>{var i;d.info("Layout started:",e),this.updateStatus(`Playing layout ${e}`),this.core.setCurrentLayout(e),this._currentLayoutEnableStat=(t==null?void 0:t.enableStat)!==!1,(i=this.timelineOverlay)==null||i.update(null,e),this.statsCollector&&this._currentLayoutEnableStat&&this.statsCollector.startLayout(e,this.currentScheduleId).catch(n=>{d.error("Failed to start layout stat:",n)})}),this.renderer.on("layoutEnd",e=>{d.info("Layout ended:",e),K==null||K.recordPlay(e.toString()),this.statsCollector&&this._currentLayoutEnableStat&&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:n}=e;d.debug("Widget started:",e.type,t,"media:",n),this.statsCollector&&n&&e.enableStat!==!1&&this.statsCollector.startWidget(n,i,this.currentScheduleId).catch(o=>{d.error("Failed to start widget stat:",o)})}),this.renderer.on("widgetEnd",e=>{const{widgetId:t,layoutId:i,mediaId:n}=e;d.debug("Widget ended:",e.type,t,"media:",n),this.statsCollector&&n&&e.enableStat!==!1&&this.statsCollector.endWidget(n,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.submitFault(e.type||"RENDERER_ERROR",`Renderer error: ${e.message||e.type}`,{layoutId:e.layoutId,regionId:e.regionId,widgetId:e.widgetId})}),this.renderer.on("action-trigger",e=>{var r,a;const{actionType:t,triggerCode:i,layoutCode:n,targetId:o,commandCode:s}=e;switch(d.info("Action trigger:",t,e),t){case"navLayout":case"navigateToLayout":i?this.core.handleTrigger(i):n&&this.core.changeLayout(n);break;case"navWidget":case"navigateToWidget":i?this.core.handleTrigger(i):o&&this.renderer.navigateToWidget(o);break;case"previousWidget":this.renderer.previousWidget((r=e.source)==null?void 0:r.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.statsCollector&&this.statsCollector.recordEvent("touch",this.core.getCurrentLayoutId(),e.targetId||null,this.currentScheduleId)}),this.renderer.on("widgetAction",e=>{e.type==="durationEnd"&&e.url&&(d.info(`Widget ${e.widgetId} duration ended, calling webhook: ${e.url}`),this.statsCollector&&this.statsCollector.recordEvent("webhook",e.layoutId,e.widgetId,this.currentScheduleId),fetch(e.url,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({widgetId:e.widgetId,layoutId:e.layoutId,regionId:e.regionId,event:"durationEnd",timestamp:new Date().toISOString()})}).catch(t=>d.warn("Webhook failed (non-critical):",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 W.get("api/v2/player/layouts",t);if(!i){d.debug(`Layout ${t} XLF not cached, skipping preload`);return}const n=await i.text(),{allMedia:o}=this.getMediaIds(n);if(!await this.checkAllMediaCached(o)){d.debug(`Media not fully cached for layout ${t}, skipping preload`);return}await this.fetchWidgetHtml(n,t),await this.renderer.preloadLayout(n,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)}}),this.renderer.on("videoError",async({fileId:e})=>{const t=`${O.slice(1)}/media/${e}`;try{const i=await fetch(`/store/missing-chunks/${t}`),{missing:n}=await i.json();if(n.length===0){d.warn(`Video error for media ${e} but no missing chunks — possible decode error`);return}d.warn(`Video ${e}: ${n.length} missing chunks (${n.join(", ")}), re-downloading`),await fetch("/store/unmark-complete",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({storeKey:t})}),this.core.collectNow().catch(o=>{d.error(`Failed to trigger re-download for media ${e}:`,o.message)})}catch(i){d.error(`Failed to check/re-download media ${e}:`,i.message)}})}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 in progress, will retry after it completes`),this._pendingRetryLayoutId=e;return}this.preparingLayoutId=e;try{const i=await W.get("api/v2/player/layouts",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 n=await i.text(),{allMedia:o}=this.getMediaIds(n);if(!await this.checkAllMediaCached(o)){X.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}this.renderer.hasPreloadedLayout(e)||await this.fetchWidgetHtml(n,e),await this.renderer.renderLayout(n,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}`),this.submitFault("LAYOUT_LOAD_FAILED",`Failed to prepare layout ${e}: ${(i==null?void 0:i.message)||i}`,{layoutId:e})}finally{this.preparingLayoutId=null;const i=this._pendingRetryLayoutId;this._pendingRetryLayoutId=null,i!=null&&this.core.getCurrentLayoutId()!==i&&(d.debug(`Retrying preparation for layout ${i} after 500ms`),setTimeout(()=>this.prepareAndRenderLayout(i),500))}}getMediaIds(e){var r;const i=new DOMParser().parseFromString(e,"text/xml"),n=[],o=[];i.querySelectorAll("media[fileId]").forEach(a=>{const l=a.getAttribute("fileId");if(l){const c=parseInt(l,10);n.push(c),a.getAttribute("type")==="video"&&o.push(c)}});const s=(r=i.querySelector("layout"))==null?void 0:r.getAttribute("background");if(s){const a=parseInt(s,10);!isNaN(a)&&!n.includes(a)&&n.push(a)}return{allMedia:n,videoMedia:o}}async checkAllMediaCached(e){for(const t of e)try{if(!await W.has("api/v2/player",`media/${t}`))return d.debug(`Media ${t} not yet cached`),!1;d.debug(`Media ${t} cached`)}catch{d.warn(`Unable to verify media ${t}, assuming cached (offline mode)`)}return!0}async fetchWidgetHtml(e,t){const n=new DOMParser().parseFromString(e,"text/xml"),o=[];for(const s of n.querySelectorAll("region")){const r=s.getAttribute("id");for(const a of s.querySelectorAll("media")){const l=a.getAttribute("type"),c=a.getAttribute("id");a.getAttribute("render")==="html"&&o.push((async()=>{try{const h=`${t}/${r}/${c}`;let g=null;const f=await W.get("api/v2/player/widgets",h);f&&(g=await f.text(),d.debug(`Found cached widget HTML for ${l} ${c}`)),g||(g=await this.xmds.getResource(t,r,c),d.debug(`Retrieved widget HTML for ${l} ${c} from CMS`)),await me(t,r,c,g);const m=await W.get("api/v2/player/widgets",h);m&&(g=await m.text());const w=a.querySelector("raw");if(w)w.textContent=g;else{const L=n.createElement("raw");L.textContent=g,a.appendChild(L)}}catch(h){d.warn(`Failed to get widget HTML for ${l} ${c}:`,h)}})())}}o.length>0&&(d.info(`Fetching ${o.length} widget HTML resources in parallel...`),await Promise.all(o),d.debug("All widget HTML fetched"))}async probeLayoutDurations(){if(this.scheduledLayoutIds.size!==0)for(const e of this.scheduledLayoutIds)try{const t=await W.get("api/v2/player/layouts",e);if(!t)continue;const i=await t.text(),{videoMedia:n}=this.getMediaIds(i);if(n.length===0)continue;const s=new DOMParser().parseFromString(i,"text/xml"),r=new Map;for(const l of s.querySelectorAll('media[type="video"]')){if(l.getAttribute("useDuration")==="1")continue;const u=l.getAttribute("fileId");if(!u||!await W.has("api/v2/player",`media/${u}`))continue;const g=await this.probeVideoDuration(`${window.location.origin}${O}/media/${u}`);g>0&&r.set(u,g)}if(r.size===0)continue;let a=0;for(const l of s.querySelectorAll("region")){if(l.getAttribute("type")==="drawer")continue;let c=0;for(const u of l.querySelectorAll("media")){const h=parseInt(u.getAttribute("duration")||"0",10),g=parseInt(u.getAttribute("useDuration")||"1",10),f=u.getAttribute("fileId")||"",m=r.get(f);m!==void 0?c+=m:h>0&&g!==0&&(c+=h)}a=Math.max(a,c)}a>0&&this.core.recordLayoutDuration(String(e),a)}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 n=()=>{i.removeAttribute("src"),i.load()};i.addEventListener("loadedmetadata",()=>{const o=Math.floor(i.duration);n(),t(o)},{once:!0}),i.addEventListener("error",()=>{n(),t(0)},{once:!0}),setTimeout(()=>{n(),t(0)},5e3),i.src=e})}updateConfigDisplay(){const e=document.getElementById("config-info");if(e){const t="0.6.1",i="2026-03-03T03:55:17.568Z".replace("T"," ").replace(/\.\d+Z$/,""),n=i?`v${t} (${i})`:`v${t}`;e.textContent=`${n} | CMS: ${D.cmsUrl} | Display: ${D.displayName||"Unknown"} | HW: ${D.hardwareKey}`}}async submitStats(){var e;if(!this.statsCollector){d.warn("Stats collector not initialized");return}if(this._pendingFollowerStats!==null){d.debug("Stats delegation in-flight, skipping");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}const n=be(i);if(this.syncManager&&!this.syncManager.isLead&&this._syncLeadAlive()){d.info(`[Sync] Delegating ${i.length} stats to lead`),this._pendingFollowerStats=i,this.syncManager.reportStats(n);return}this.syncManager&&!this.syncManager.isLead&&d.warn("[Sync] Lead not alive, submitting stats directly"),d.info(`Submitting ${i.length} proof of play stats...`),await this.xmds.submitStats(n)?(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){if(this._pendingFollowerLogs!==null){d.debug("Logs delegation in-flight, skipping");return}try{const e=await this.logReporter.getLogsForSubmission();if(e.length===0){d.debug("No logs to submit");return}const t=Le(e);if(this.syncManager&&!this.syncManager.isLead&&this._syncLeadAlive()){d.info(`[Sync] Delegating ${e.length} logs to lead`),this._pendingFollowerLogs=e,this.syncManager.reportLogs(t);return}this.syncManager&&!this.syncManager.isLead&&d.warn("[Sync] Lead not alive, submitting logs directly"),d.info(`Submitting ${e.length} logs to CMS...`),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)}}}submitFault(e,t,i){if(!this.xmds)return;const n=JSON.stringify([{code:e,reason:t,date:new Date().toISOString().replace("T"," ").substring(0,19),...i}]);this.xmds.reportFaults(n).catch(o=>{d.debug("reportFaults failed (non-critical):",o)})}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 n=await window.electronAPI.captureScreenshot();if(n)this._screenshotMethod="electron",t=n;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 n=i.getBoundingClientRect(),o=getComputedStyle(i),s=o.backgroundColor;s&&s!=="transparent"&&s!=="rgba(0, 0, 0, 0)"&&(t.fillStyle=s,t.fillRect(n.left,n.top,n.width,n.height));const r=o.backgroundImage;if(r&&r!=="none"){const c=r.match(/url\(["']?(.*?)["']?\)/);if(c)try{const u=new Image;u.crossOrigin="anonymous",await new Promise(h=>{u.onload=()=>h(),u.onerror=()=>h(),setTimeout(()=>h(),2e3),u.src=c[1]}),u.naturalWidth&&t.drawImage(u,n.left,n.top,n.width,n.height)}catch{}}this._html2canvasMod||(this._html2canvasMod=(await P(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 h=c.getBoundingClientRect();if(!(h.width===0||h.height===0))try{if(c instanceof HTMLImageElement){if(!c.complete||!c.naturalWidth)continue;if(getComputedStyle(c).objectFit==="contain"&&c.naturalWidth&&c.naturalHeight){const f=this.containedRect(c.naturalWidth,c.naturalHeight,h);t.drawImage(c,f.x,f.y,f.w,f.h)}else t.drawImage(c,h.left,h.top,h.width,h.height);l++}else if(c instanceof HTMLVideoElement){if(c.readyState<2)continue;if(getComputedStyle(c).objectFit==="contain"&&c.videoWidth&&c.videoHeight){const f=this.containedRect(c.videoWidth,c.videoHeight,h);t.drawImage(c,f.x,f.y,f.w,f.h)}else t.drawImage(c,h.left,h.top,h.width,h.height);l++}else if(c instanceof HTMLCanvasElement)t.drawImage(c,h.left,h.top,h.width,h.height),l++;else if(c instanceof HTMLIFrameElement){const g=c.contentDocument;if(!(g!=null&&g.body))continue;const f=document.createElement("div");f.style.cssText=`position:fixed;left:-9999px;top:0;width:${h.width}px;height:${h.height}px;overflow:hidden;`;const m=[];for(const v of g.querySelectorAll("style"))f.appendChild(v.cloneNode(!0));for(const v of g.querySelectorAll('link[rel="stylesheet"]')){const x=document.createElement("link");x.rel="stylesheet",x.href=new URL(v.getAttribute("href")||"",g.baseURI).href,f.appendChild(x),m.push(new Promise(k=>{x.onload=()=>k(),x.onerror=()=>k()}))}f.appendChild(g.body.cloneNode(!0)),document.body.appendChild(f);const w=g.querySelectorAll("img"),L=new Map;w.forEach((v,x)=>{v.naturalWidth&&v.naturalHeight&&L.set(String(x),{nw:v.naturalWidth,nh:v.naturalHeight})}),m.length>0&&await Promise.race([Promise.all(m),new Promise(v=>setTimeout(v,500))]);const C=await this._html2canvasMod(f,{useCORS:!0,allowTaint:!0,logging:!1,backgroundColor:null,width:h.width,height:h.height,onclone:v=>{const x=v.createElement("style");x.textContent="*, *::before, *::after { animation: none !important; transition: none !important; opacity: 1 !important; }",v.head.appendChild(x),v.querySelectorAll("img").forEach((_,S)=>{var le,ce;const b=(le=v.defaultView)==null?void 0:le.getComputedStyle(_);if(!b||b.objectFit!=="contain")return;const I=L.get(String(S));if(!I)return;const A=_.clientWidth||parseFloat(b.width)||0,T=_.clientHeight||parseFloat(b.height)||0;if(!A||!T)return;const R=I.nw/I.nh,N=A/T;let M,E;R>N?(M=A,E=A/R):(E=T,M=T*R);const j=v.createElement("div");j.style.cssText=`width:${A}px;height:${T}px;display:flex;align-items:center;justify-content:center;overflow:hidden;`,_.style.objectFit="fill",_.style.width=`${M}px`,_.style.height=`${E}px`,(ce=_.parentNode)==null||ce.insertBefore(j,_),j.appendChild(_)})}});document.body.removeChild(f),t.drawImage(C,h.left,h.top,h.width,h.height),l++}}catch(g){d.warn("Screenshot: failed to draw element",c.tagName,g)}}return d.debug(`Screenshot: composed ${l}/${a.length} elements`),e.toDataURL("image/jpeg",.8).split(",")[1]}containedRect(e,t,i){const n=e/t,o=i.width/i.height;let s,r;return n>o?(s=i.width,r=i.width/n):(r=i.height,s=i.height*n),{x:i.left+(i.width-s)/2,y:i.top+(i.height-r)/2,w:s,h:r}}startScreenshotInterval(){var i;const e=((i=this.displaySettings)==null?void 0:i.getSetting("screenshotInterval"))||0;if(!e||e<=0)return;this._html2canvasMod||P(()=>import("./html2canvas.esm-CBrSDip1.js"),[],import.meta.url).then(n=>{this._html2canvasMod=n.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)}_syncLeadAlive(){if(!this.syncManager)return!1;for(const[,e]of this.syncManager.followers)if(e.role==="lead"&&Date.now()-e.lastSeen<15e3)return!0;return!1}cleanup(){this.core.cleanup(),this.renderer.cleanup(),this._screenshotInterval&&(clearInterval(this._screenshotInterval),this._screenshotInterval=null),this._wakeLock&&(this._wakeLock.release(),this._wakeLock=null),this.downloadOverlay&&this.downloadOverlay.destroy(),this.timelineOverlay&&this.timelineOverlay.destroy(),this._iframeObserver&&(this._iframeObserver.disconnect(),this._iframeObserver=null),navigator.serviceWorker&&this._swIcHandler&&(navigator.serviceWorker.removeEventListener("message",this._swIcHandler),this._swIcHandler=null),X==null||X.clear(),this._probeTimer&&(clearTimeout(this._probeTimer),this._probeTimer=null)}}function Ie(){const p=new at;p.init().catch(e=>{d.error("Failed to initialize:",e),d.warn("Redirecting to setup screen..."),window.location.href="./setup.html"}),window.addEventListener("beforeunload",()=>{p.cleanup()})}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Ie):Ie();export{et as D,ft as I,Y as L,Ze as O,nt as P,Ue as R,Ye as S,ht as a,Qe as b,Ke as c,ze as p,gt as s};
|
|
729
|
+
//# sourceMappingURL=main-DvteKhS8.js.map
|