@xiboplayer/pwa 0.7.21 → 0.7.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/assets/__vite-browser-external-DeMPM02e.js +2 -0
  2. package/dist/assets/__vite-browser-external-DeMPM02e.js.map +1 -0
  3. package/dist/assets/chunk-DzMEjpoC.js +1 -0
  4. package/dist/assets/{html2canvas-BAfZNSwU.js → html2canvas-EikzC5d8.js} +2 -2
  5. package/dist/assets/{html2canvas-BAfZNSwU.js.map → html2canvas-EikzC5d8.js.map} +1 -1
  6. package/dist/assets/main-CRdq5ifQ.js +3 -0
  7. package/dist/assets/{main-vwJkNw4Y.js.map → main-CRdq5ifQ.js.map} +1 -1
  8. package/dist/assets/main-DTR2QDcF.js +108 -0
  9. package/dist/assets/main-DTR2QDcF.js.map +1 -0
  10. package/dist/assets/{pdf-Bxz9Nzto.js → pdf-CMz6puSt.js} +1 -1
  11. package/dist/assets/{pdf-Bxz9Nzto.js.map → pdf-CMz6puSt.js.map} +1 -1
  12. package/dist/assets/{setup-B4gZX38p.js → setup-Bw8T9Qq6.js} +2 -2
  13. package/dist/assets/{setup-B4gZX38p.js.map → setup-Bw8T9Qq6.js.map} +1 -1
  14. package/dist/assets/src-A5KHvitf.js +2 -0
  15. package/dist/assets/{src-CROvYSP8.js.map → src-A5KHvitf.js.map} +1 -1
  16. package/dist/assets/{src-DAB0dqGG.js → src-BHsN2u2P.js} +2 -2
  17. package/dist/assets/{src-DAB0dqGG.js.map → src-BHsN2u2P.js.map} +1 -1
  18. package/dist/assets/src-BLUMUwZR.js +1 -0
  19. package/dist/assets/src-BXXcWcHh.js +1 -0
  20. package/dist/assets/src-BxSOopk7.js +1 -0
  21. package/dist/assets/{src-WDu491CE.js → src-BxaX1gGg.js} +2 -2
  22. package/dist/assets/{src-WDu491CE.js.map → src-BxaX1gGg.js.map} +1 -1
  23. package/dist/assets/{src-BtVLiVYZ.js → src-CCAyzQUp.js} +3 -3
  24. package/dist/assets/{src-BtVLiVYZ.js.map → src-CCAyzQUp.js.map} +1 -1
  25. package/dist/assets/src-CWJcD3kA.js +1 -0
  26. package/dist/assets/{src-Cx3tXAAu.js → src-CZ1k5h23.js} +3 -3
  27. package/dist/assets/{src-Cx3tXAAu.js.map → src-CZ1k5h23.js.map} +1 -1
  28. package/dist/assets/src-ClrziKzV.js +16 -0
  29. package/dist/assets/src-ClrziKzV.js.map +1 -0
  30. package/dist/assets/{src-C_Lx4lXp.js → src-CtjjclS4.js} +2 -2
  31. package/dist/assets/{src-C_Lx4lXp.js.map → src-CtjjclS4.js.map} +1 -1
  32. package/dist/assets/src-CuVaZcMo.js +2 -0
  33. package/dist/assets/{src-B_BNICay.js.map → src-CuVaZcMo.js.map} +1 -1
  34. package/dist/assets/src-Cy5OUviT.js +1 -0
  35. package/dist/assets/src-DK5BYonP.js +630 -0
  36. package/dist/assets/src-DK5BYonP.js.map +1 -0
  37. package/dist/assets/src-Dk-W3N33.js +1 -0
  38. package/dist/assets/{src-cUopH0nN.js → src-xPTO7Ts6.js} +3 -3
  39. package/dist/assets/{src-cUopH0nN.js.map → src-xPTO7Ts6.js.map} +1 -1
  40. package/dist/assets/sync-manager-zf1tikPt.js +2 -0
  41. package/dist/assets/sync-manager-zf1tikPt.js.map +1 -0
  42. package/dist/index.html +1 -1
  43. package/dist/setup.html +3 -4
  44. package/dist/sw-pwa.js +2 -2
  45. package/dist/sw-pwa.js.map +1 -1
  46. package/package.json +15 -13
  47. package/dist/assets/chunk-7ZXdHUL4.js +0 -1
  48. package/dist/assets/main-oacre7st.js +0 -108
  49. package/dist/assets/main-oacre7st.js.map +0 -1
  50. package/dist/assets/main-vwJkNw4Y.js +0 -3
  51. package/dist/assets/src-B_BNICay.js +0 -2
  52. package/dist/assets/src-Bjt9ooXK.js +0 -16
  53. package/dist/assets/src-Bjt9ooXK.js.map +0 -1
  54. package/dist/assets/src-CKpVxGpH.js +0 -629
  55. package/dist/assets/src-CKpVxGpH.js.map +0 -1
  56. package/dist/assets/src-CROvYSP8.js +0 -2
  57. package/dist/assets/sync-manager-8Z-qwkod.js +0 -2
  58. package/dist/assets/sync-manager-8Z-qwkod.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"mappings":"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","names":["zstdDecompress","log","pkg"],"ignoreList":[2],"sources":["../../../expr/src/expr.js","../../../expr/src/state.js","../../../../node_modules/.pnpm/fzstd@0.1.1/node_modules/fzstd/esm/index.mjs","../../../expr/src/xp-state-init.js","../../../renderer/src/layout-pool.js","../../../renderer/src/renderer-lite.js","../../../renderer/src/layout.js","../../../renderer/src/index.js"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * SMIL State / XPath 1.0 subset expression evaluator.\n *\n * This is the Track B runtime counterpart of the Track A compile-time\n * folder in `xiboplayer-smil-tools/src/xlf-builder.js`. The grammar and\n * semantics are identical — the translator folds what it can at build\n * time against `xp:state-init`; anything referencing mutable state is\n * left as-is and re-evaluated here at runtime against an `XpStateStore`\n * (or any plain object exposing the same identifier lookups).\n *\n * Keeping the two implementations in lock-step is a round-trip safety\n * invariant: Track A and Track B MUST agree on what `expr` means, so\n * promoting an expression from \"out-of-scope\" → \"folded\" never changes\n * observable playback.\n *\n * Grammar (whitespace-insensitive, XPath 1.0 precedence from lowest to\n * highest):\n *\n * expr := orExpr\n * orExpr := andExpr (\"or\" andExpr)*\n * andExpr := eqExpr (\"and\" eqExpr)*\n * eqExpr := relExpr ((\"=\" | \"!=\" | \"==\") relExpr)*\n * relExpr := addExpr ((\"<=\" | \">=\" | \"<\" | \">\") addExpr)*\n * addExpr := mulExpr ((\"+\" | \"-\") mulExpr)*\n * mulExpr := unaryExpr ((\"*\" | \"/\" | \"mod\") unaryExpr)*\n * unaryExpr := \"-\" unaryExpr | primary\n * primary := NUMBER | STRING | \"not\" \"(\" expr \")\"\n * | \"smil-language\" \"(\" STRING \")\" | IDENT | \"(\" expr \")\"\n *\n * Strings are single- or double-quoted. IDENT resolves against the\n * `state` object (or store) via its `get(name)` / property lookup.\n * Built-in `smil-language('x')` resolves against `state.lang`.\n *\n * XPath 1.0 spells equality as `=`; we also accept JS-style `==` as\n * a convenience alias and emit an `XP_EXPR_JS_ALIAS` lint warning\n * (matches Track A).\n */\n\n/**\n * Named error thrown when an `expr=` attribute uses a construct the\n * evaluator cannot handle (unknown identifier, unknown built-in,\n * divide-by-zero, …). Callers typically catch this and fall back to a\n * safe default (e.g. treat an `xp:if=` guard as `false` so the element\n * hides rather than crashing the layout).\n */\nexport class ExprOutOfScope extends Error {\n constructor(message) {\n super(message);\n this.name = 'ExprOutOfScope';\n }\n}\n\n/**\n * Evaluate an expression against a state lookup.\n *\n * @param {string} expr - raw attribute value\n * @param {Record<string, unknown> | { get: (name: string) => unknown, has?: (name: string) => boolean, lang?: unknown }} state\n * Either a plain object or an object exposing `get(name)` /\n * optional `has(name)` (the `XpStateStore` contract).\n * @param {Array<{code: string, message: string}>} [warningsOut] - optional\n * collector for lint-style warnings.\n * @returns {boolean | number | string}\n */\nexport function evalExpr(expr, state, warningsOut) {\n const src = String(expr);\n const tokens = tokenize(src);\n if (tokens.length === 0) throw new ExprOutOfScope('empty expression');\n const p = { tokens, pos: 0, src, warnings: warningsOut };\n const result = parseOrExpr(p, state);\n if (p.pos < p.tokens.length) {\n const t = p.tokens[p.pos];\n throw new ExprOutOfScope(\n `unexpected token \"${t.value}\" at offset ${t.offset}`\n );\n }\n return result;\n}\n\n/**\n * Coerce a folded value to boolean using XPath 1.0 semantics. Exposed\n * for callers that evaluate `xp:if=` / `expr=` as a guard.\n */\nexport function asBool(v) {\n if (typeof v === 'boolean') return v;\n if (typeof v === 'number') return v !== 0 && !Number.isNaN(v);\n if (typeof v === 'string') return v.length > 0;\n return Boolean(v);\n}\n\n// ── Lexer ─────────────────────────────────────────────────────────────\n\nfunction tokenize(src) {\n const tokens = [];\n const len = src.length;\n let i = 0;\n while (i < len) {\n const c = src[i];\n if (c === ' ' || c === '\\t' || c === '\\n' || c === '\\r') {\n i++;\n continue;\n }\n const start = i;\n // String literals: single- or double-quoted.\n if (c === \"'\" || c === '\"') {\n const end = src.indexOf(c, i + 1);\n if (end < 0) throw new ExprOutOfScope(`unterminated string starting at ${i}`);\n tokens.push({ type: 'str', value: src.slice(i + 1, end), offset: start });\n i = end + 1;\n continue;\n }\n // Numbers: integer or decimal. Sign is handled by unary minus in\n // the parser — avoids ambiguity with subtraction.\n if (c >= '0' && c <= '9') {\n let j = i + 1;\n while (j < len && src[j] >= '0' && src[j] <= '9') j++;\n if (j < len && src[j] === '.') {\n j++;\n while (j < len && src[j] >= '0' && src[j] <= '9') j++;\n }\n tokens.push({ type: 'num', value: src.slice(i, j), offset: start });\n i = j;\n continue;\n }\n // Identifiers: letter (or underscore), then letters/digits/./_.\n // Hyphens are accepted inside the ident body only when the next\n // character after the hyphen is another ident-body char; this\n // lets `smil-language` tokenise as one word while `a - b` still\n // parses as subtraction.\n if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c === '_') {\n let j = i + 1;\n while (j < len) {\n const d = src[j];\n if (\n (d >= 'A' && d <= 'Z') ||\n (d >= 'a' && d <= 'z') ||\n (d >= '0' && d <= '9') ||\n d === '_' ||\n d === '.'\n ) {\n j++;\n continue;\n }\n if (d === '-' && j + 1 < len) {\n const e = src[j + 1];\n if (\n (e >= 'A' && e <= 'Z') ||\n (e >= 'a' && e <= 'z') ||\n (e >= '0' && e <= '9') ||\n e === '_'\n ) {\n j += 2;\n continue;\n }\n }\n break;\n }\n tokens.push({ type: 'ident', value: src.slice(i, j), offset: start });\n i = j;\n continue;\n }\n // Multi-char operators first (longest match).\n const two = src.slice(i, i + 2);\n if (two === '!=' || two === '<=' || two === '>=' || two === '==') {\n tokens.push({ type: 'op', value: two, offset: start });\n i += 2;\n continue;\n }\n if (c === '(' || c === ')') {\n tokens.push({ type: c === '(' ? 'lparen' : 'rparen', value: c, offset: start });\n i++;\n continue;\n }\n if (\n c === '+' || c === '-' || c === '*' || c === '/' ||\n c === '<' || c === '>' || c === '='\n ) {\n tokens.push({ type: 'op', value: c, offset: start });\n i++;\n continue;\n }\n throw new ExprOutOfScope(`unexpected character \"${c}\" at offset ${i}`);\n }\n return tokens;\n}\n\nfunction peekTok(p) {\n return p.tokens[p.pos];\n}\n\nfunction consumeIf(p, type, value) {\n const t = p.tokens[p.pos];\n if (!t) return null;\n if (t.type !== type) return null;\n if (value !== undefined && t.value !== value) return null;\n p.pos++;\n return t;\n}\n\n// ── Parser (lowest precedence first) ─────────────────────────────────\n\nfunction parseOrExpr(p, state) {\n let left = parseAndExpr(p, state);\n while (consumeIf(p, 'ident', 'or')) {\n const right = parseAndExpr(p, state);\n left = asBool(left) || asBool(right);\n }\n return left;\n}\n\nfunction parseAndExpr(p, state) {\n let left = parseEqExpr(p, state);\n while (consumeIf(p, 'ident', 'and')) {\n const right = parseEqExpr(p, state);\n left = asBool(left) && asBool(right);\n }\n return left;\n}\n\nfunction parseEqExpr(p, state) {\n let left = parseRelExpr(p, state);\n while (true) {\n const eq = consumeIf(p, 'op', '=');\n const js = !eq && consumeIf(p, 'op', '==');\n const neq = !eq && !js && consumeIf(p, 'op', '!=');\n if (!eq && !js && !neq) break;\n if (js && p.warnings) {\n p.warnings.push({\n code: 'XP_EXPR_JS_ALIAS',\n message: `expr= uses JS-style \"==\" — XPath 1.0 spells equality as \"=\"; evaluator accepts both.`\n });\n }\n const right = parseRelExpr(p, state);\n const op = neq ? '!=' : '=';\n left = applyCmp(left, op, right);\n }\n return left;\n}\n\nfunction parseRelExpr(p, state) {\n let left = parseAddExpr(p, state);\n while (true) {\n const t = peekTok(p);\n if (!t || t.type !== 'op') break;\n if (t.value !== '<' && t.value !== '>' && t.value !== '<=' && t.value !== '>=') break;\n p.pos++;\n const right = parseAddExpr(p, state);\n left = applyCmp(left, t.value, right);\n }\n return left;\n}\n\nfunction parseAddExpr(p, state) {\n let left = parseMulExpr(p, state);\n while (true) {\n const plus = consumeIf(p, 'op', '+');\n const minus = !plus && consumeIf(p, 'op', '-');\n if (!plus && !minus) break;\n const right = parseMulExpr(p, state);\n if (plus) left = applyAdd(left, right);\n else left = applySub(left, right);\n }\n return left;\n}\n\nfunction parseMulExpr(p, state) {\n let left = parseUnary(p, state);\n while (true) {\n const mul = consumeIf(p, 'op', '*');\n const div = !mul && consumeIf(p, 'op', '/');\n const mod = !mul && !div && consumeIf(p, 'ident', 'mod');\n if (!mul && !div && !mod) break;\n const right = parseUnary(p, state);\n if (mul) left = applyMul(left, right);\n else if (div) left = applyDiv(left, right);\n else left = applyMod(left, right);\n }\n return left;\n}\n\nfunction parseUnary(p, state) {\n if (consumeIf(p, 'op', '-')) {\n const inner = parseUnary(p, state);\n const n = Number(inner);\n if (!Number.isFinite(n)) {\n throw new ExprOutOfScope(\n `unary minus applied to non-numeric value \"${inner}\"`\n );\n }\n return -n;\n }\n return parsePrimary(p, state);\n}\n\nfunction parsePrimary(p, state) {\n const t = p.tokens[p.pos];\n if (!t) throw new ExprOutOfScope('unexpected end of expression');\n if (t.type === 'num') {\n p.pos++;\n return parseFloat(t.value);\n }\n if (t.type === 'str') {\n p.pos++;\n return t.value;\n }\n if (t.type === 'lparen') {\n p.pos++;\n const v = parseOrExpr(p, state);\n if (!consumeIf(p, 'rparen')) throw new ExprOutOfScope(`expected ')' at offset ${t.offset}`);\n return v;\n }\n if (t.type === 'ident') {\n // Reserved keywords that must not start a primary.\n if (t.value === 'and' || t.value === 'or' || t.value === 'mod') {\n throw new ExprOutOfScope(\n `unexpected keyword \"${t.value}\" at offset ${t.offset}`\n );\n }\n p.pos++;\n // Built-ins + not(): identifier followed by '(' is a function call.\n if (consumeIf(p, 'lparen')) {\n if (t.value === 'not') {\n const inner = parseOrExpr(p, state);\n if (!consumeIf(p, 'rparen')) throw new ExprOutOfScope(\"expected ')' closing not(\");\n return !asBool(inner);\n }\n if (t.value === 'smil-language') {\n const arg = p.tokens[p.pos];\n if (!arg || arg.type !== 'str') {\n throw new ExprOutOfScope('smil-language() expects a string literal argument');\n }\n p.pos++;\n if (!consumeIf(p, 'rparen')) throw new ExprOutOfScope(\"expected ')' closing smil-language(\");\n const lang = resolveIdent('lang', state, /*optional*/ true);\n if (lang === undefined || lang === null) {\n throw new ExprOutOfScope('smil-language() called but xp:state-init.lang is unset');\n }\n return String(lang) === arg.value;\n }\n throw new ExprOutOfScope(`unknown function: ${t.value}()`);\n }\n return resolveIdent(t.value, state, false);\n }\n throw new ExprOutOfScope(`unexpected token \"${t.value}\" at offset ${t.offset}`);\n}\n\n/**\n * Resolve an identifier against `state`. Supports two shapes:\n * 1. Plain object — `hasOwnProperty` + property access\n * 2. Store-like — duck-typed by presence of `.get` (Function)\n *\n * When `optional` is true and the identifier is missing, returns\n * `undefined` instead of throwing (used by the smil-language() built-in\n * to probe for `lang` without forcing callers to pre-seed it).\n */\nfunction resolveIdent(name, state, optional) {\n if (state && typeof state.get === 'function') {\n const has = typeof state.has === 'function' ? state.has(name) : state.get(name) !== undefined;\n if (!has) {\n if (optional) return undefined;\n throw new ExprOutOfScope(`unknown identifier: ${name}`);\n }\n return state.get(name);\n }\n if (state && Object.hasOwn(state, name)) {\n return state[name];\n }\n if (optional) return undefined;\n throw new ExprOutOfScope(`unknown identifier: ${name}`);\n}\n\n// ── Operator helpers ──────────────────────────────────────────────────\n\nfunction applyCmp(lhs, op, rhs) {\n // Number comparisons when both sides coerce to a finite number;\n // string comparisons otherwise. Matches XPath 1.0 loose typing.\n const ln = Number(lhs);\n const rn = Number(rhs);\n const bothNum = Number.isFinite(ln) && Number.isFinite(rn);\n const a = bothNum ? ln : String(lhs);\n const b = bothNum ? rn : String(rhs);\n switch (op) {\n case '=': return a === b;\n case '!=': return a !== b;\n case '<': return a < b;\n case '<=': return a <= b;\n case '>': return a > b;\n case '>=': return a >= b;\n }\n throw new ExprOutOfScope(`unknown comparator: ${op}`);\n}\n\nfunction applyAdd(lhs, rhs) {\n // Per W3C SMIL State, `+` on a string operand concatenates; on\n // numeric operands it adds. If either side is a string we take\n // the concatenation branch (mirrors JS semantics); otherwise both\n // must coerce to finite numbers.\n if (typeof lhs === 'string' || typeof rhs === 'string') {\n return String(lhs) + String(rhs);\n }\n const ln = Number(lhs);\n const rn = Number(rhs);\n if (!Number.isFinite(ln) || !Number.isFinite(rn)) {\n throw new ExprOutOfScope(\n `\"+\" applied to non-numeric operand (lhs=${lhs}, rhs=${rhs})`\n );\n }\n return ln + rn;\n}\n\nfunction applySub(lhs, rhs) {\n const ln = Number(lhs);\n const rn = Number(rhs);\n if (!Number.isFinite(ln) || !Number.isFinite(rn)) {\n throw new ExprOutOfScope(\n `\"-\" applied to non-numeric operand (lhs=${lhs}, rhs=${rhs})`\n );\n }\n return ln - rn;\n}\n\nfunction applyMul(lhs, rhs) {\n const ln = Number(lhs);\n const rn = Number(rhs);\n if (!Number.isFinite(ln) || !Number.isFinite(rn)) {\n throw new ExprOutOfScope(\n `\"*\" applied to non-numeric operand (lhs=${lhs}, rhs=${rhs})`\n );\n }\n return ln * rn;\n}\n\nfunction applyDiv(lhs, rhs) {\n const ln = Number(lhs);\n const rn = Number(rhs);\n if (!Number.isFinite(ln) || !Number.isFinite(rn)) {\n throw new ExprOutOfScope(\n `\"/\" applied to non-numeric operand (lhs=${lhs}, rhs=${rhs})`\n );\n }\n if (rn === 0) throw new ExprOutOfScope('division by zero');\n return ln / rn;\n}\n\nfunction applyMod(lhs, rhs) {\n const ln = Number(lhs);\n const rn = Number(rhs);\n if (!Number.isFinite(ln) || !Number.isFinite(rn)) {\n throw new ExprOutOfScope(\n `\"mod\" applied to non-numeric operand (lhs=${lhs}, rhs=${rhs})`\n );\n }\n if (rn === 0) throw new ExprOutOfScope('mod by zero');\n // XPath 1.0 mod is the remainder of truncating division — matches\n // the JS `%` operator for finite operands.\n return ln % rn;\n}\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * XpStateStore — runtime state store for SMIL State (Track B).\n *\n * Backs `<setvalue>`, `<newvalue>`, `<delvalue>`, and `xp:if=` /\n * `expr=` / `{AVT}` runtime evaluation in the PWA renderer. The store\n * mirrors the shape of `xp:state-init` but is mutable.\n *\n * Scope semantics (matches `xp:state-scope`, default `session`):\n * - \"document\": in-memory only; reset on layout change.\n * - \"session\": in-memory only; persists across layout changes until\n * the player restarts.\n * - \"display\": backed by `localStorage`; persists across restarts.\n *\n * The store is intentionally flat — keys are identifiers (dotted names\n * allowed as a nested convenience via `get('foo.bar')` / `set('foo.bar',\n * …)`). Authors that want structured state can model it however they\n * like; the runtime evaluator only cares that `state.get(ident)` works.\n *\n * Event model:\n * - `change` — fired after every set/delete, payload `{path, value, prev}`\n * - `change:<path>` — fired for a specific path (including parent paths on nested writes)\n *\n * Identifier-to-dependency mapping for `xp:if` re-evaluation is the\n * caller's responsibility (use `exprIdentifiers()` or `avtIdentifiers()`\n * from the companion modules to enumerate keys).\n */\n\nimport { evalExpr } from './expr.js';\n\nconst VALID_SCOPES = new Set(['document', 'session', 'display']);\n\n/**\n * @typedef {string | number | boolean | null} Scalar\n * @typedef {Scalar | Scalar[] | { [k: string]: Json }} Json\n */\n\nexport class XpStateStore {\n /**\n * @param {object} [opts]\n * @param {'document' | 'session' | 'display'} [opts.scope='session']\n * @param {Record<string, Json>} [opts.initialState] - `xp:state-init` object\n * @param {Storage} [opts.storage] - `localStorage`-compatible backend for\n * `display` scope. Defaults to `globalThis.localStorage` when present.\n * @param {string} [opts.storageKey='xp:state'] - localStorage key\n */\n constructor(opts = {}) {\n const scope = opts.scope ?? 'session';\n if (!VALID_SCOPES.has(scope)) {\n throw new Error(`XpStateStore: invalid scope \"${scope}\"`);\n }\n this.scope = scope;\n this._storageKey = opts.storageKey ?? 'xp:state';\n this._storage = scope === 'display'\n ? (opts.storage ?? (typeof globalThis !== 'undefined' ? globalThis.localStorage : null))\n : null;\n\n this._data = new Map();\n this._listeners = new Map(); // event → Set<handler>\n\n // Seed order: initialState first, then persisted display state overrides.\n if (opts.initialState && typeof opts.initialState === 'object') {\n for (const [k, v] of Object.entries(opts.initialState)) {\n this._data.set(k, clone(v));\n }\n }\n if (this._storage) {\n try {\n const raw = this._storage.getItem(this._storageKey);\n if (raw) {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') {\n for (const [k, v] of Object.entries(parsed)) {\n this._data.set(k, clone(v));\n }\n }\n }\n } catch (_err) {\n // Corrupt storage — fall back to initialState-only.\n }\n }\n }\n\n /**\n * Dot-path read. `get('foo.bar')` returns the nested value, or\n * `undefined` if any segment is missing. Flat `get('foo')` returns\n * the top-level value verbatim.\n *\n * @param {string} path\n * @returns {Json | undefined}\n */\n get(path) {\n const { key, rest } = splitPath(path);\n if (!this._data.has(key)) return undefined;\n let v = this._data.get(key);\n for (const seg of rest) {\n if (v == null || typeof v !== 'object') return undefined;\n v = v[seg];\n }\n return v;\n }\n\n /**\n * @param {string} path\n * @returns {boolean}\n */\n has(path) {\n return this.get(path) !== undefined;\n }\n\n /**\n * Dot-path write. Triggers `change` + `change:<path>` events (and\n * also `change:<ancestor>` for every parent path on nested writes).\n *\n * @param {string} path\n * @param {Json} value\n */\n set(path, value) {\n const prev = this.get(path);\n const { key, rest } = splitPath(path);\n if (rest.length === 0) {\n this._data.set(key, clone(value));\n } else {\n let obj = this._data.get(key);\n if (obj == null || typeof obj !== 'object') {\n obj = {};\n this._data.set(key, obj);\n }\n let cursor = obj;\n for (let i = 0; i < rest.length - 1; i++) {\n const seg = rest[i];\n if (cursor[seg] == null || typeof cursor[seg] !== 'object') {\n cursor[seg] = {};\n }\n cursor = cursor[seg];\n }\n cursor[rest[rest.length - 1]] = clone(value);\n }\n this._persist();\n this._emitChange(path, value, prev);\n }\n\n /**\n * Dot-path delete. Removes the leaf (for flat keys it removes the\n * whole top-level entry).\n *\n * @param {string} path\n */\n delete(path) {\n const prev = this.get(path);\n if (prev === undefined) return; // nothing to delete, no event\n\n const { key, rest } = splitPath(path);\n if (rest.length === 0) {\n this._data.delete(key);\n } else {\n let cursor = this._data.get(key);\n for (let i = 0; i < rest.length - 1; i++) {\n if (cursor == null || typeof cursor !== 'object') return;\n cursor = cursor[rest[i]];\n }\n if (cursor != null && typeof cursor === 'object') {\n delete cursor[rest[rest.length - 1]];\n }\n }\n this._persist();\n this._emitChange(path, undefined, prev);\n }\n\n /**\n * Evaluate an XPath 1.0 subset expression against the store.\n *\n * @param {string} expr\n * @param {Array<{code:string,message:string}>} [warningsOut]\n * @returns {boolean | number | string}\n */\n evaluate(expr, warningsOut) {\n return evalExpr(expr, this, warningsOut);\n }\n\n /**\n * Snapshot a plain-object copy of the store's top-level keys. Useful\n * for logging or passing to the Track A evaluator without exposing\n * the Map.\n */\n snapshot() {\n const out = {};\n for (const [k, v] of this._data) out[k] = clone(v);\n return out;\n }\n\n /**\n * Subscribe to change events. Returns an unsubscribe function.\n *\n * Events:\n * - `change` — every mutation\n * - `change:<path>` — mutations to a specific path (and ancestors)\n *\n * @param {string} event\n * @param {(payload: {path: string, value: Json|undefined, prev: Json|undefined}) => void} handler\n * @returns {() => void}\n */\n on(event, handler) {\n if (!this._listeners.has(event)) this._listeners.set(event, new Set());\n this._listeners.get(event).add(handler);\n return () => {\n const set = this._listeners.get(event);\n if (set) set.delete(handler);\n };\n }\n\n /**\n * Clear all keys and fire one `change` event with `path='*'`. Used\n * when a `document`-scoped store resets on layout change.\n */\n reset() {\n const prev = this.snapshot();\n this._data.clear();\n this._persist();\n this._emit('change', { path: '*', value: undefined, prev });\n }\n\n /**\n * lang getter — convenience for `smil-language()` built-in. The\n * evaluator resolves `lang` via the normal `get()` path, but\n * consumers (overlay, debug UI) can read it directly too.\n */\n get lang() {\n return this.get('lang');\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n _persist() {\n if (!this._storage) return;\n try {\n const obj = this.snapshot();\n this._storage.setItem(this._storageKey, JSON.stringify(obj));\n } catch (_err) {\n // Quota exceeded or serialisation error — persistence is\n // best-effort; in-memory state is still authoritative.\n }\n }\n\n _emitChange(path, value, prev) {\n this._emit('change', { path, value, prev });\n // Fire change:<path> and change:<ancestor> for each dotted parent.\n const segs = path.split('.');\n for (let i = segs.length; i >= 1; i--) {\n const p = segs.slice(0, i).join('.');\n this._emit(`change:${p}`, { path, value, prev });\n }\n }\n\n _emit(event, payload) {\n const set = this._listeners.get(event);\n if (!set || set.size === 0) return;\n // Copy so handlers can unsubscribe mid-emission without skipping.\n for (const h of Array.from(set)) {\n try {\n h(payload);\n } catch (err) {\n // Stay quiet by default — renderers shouldn't die if a stray\n // listener throws. A future logger hook could surface this.\n if (typeof console !== 'undefined' && console.error) {\n console.error('[XpStateStore] listener error on', event, err);\n }\n }\n }\n }\n}\n\nfunction clone(v) {\n if (v == null) return v;\n if (typeof v !== 'object') return v;\n if (Array.isArray(v)) return v.map(clone);\n const out = {};\n for (const [k, vv] of Object.entries(v)) out[k] = clone(vv);\n return out;\n}\n\nfunction splitPath(path) {\n const s = String(path);\n const i = s.indexOf('.');\n if (i < 0) return { key: s, rest: [] };\n return { key: s.slice(0, i), rest: s.slice(i + 1).split('.') };\n}\n","// Some numerical data is initialized as -1 even when it doesn't need initialization to help the JIT infer types\n// aliases for shorter compressed code (most minifers don't do this)\nvar ab = ArrayBuffer, u8 = Uint8Array, u16 = Uint16Array, i16 = Int16Array, u32 = Uint32Array, i32 = Int32Array;\nvar slc = function (v, s, e) {\n if (u8.prototype.slice)\n return u8.prototype.slice.call(v, s, e);\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n var n = new u8(e - s);\n n.set(v.subarray(s, e));\n return n;\n};\nvar fill = function (v, n, s, e) {\n if (u8.prototype.fill)\n return u8.prototype.fill.call(v, n, s, e);\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n for (; s < e; ++s)\n v[s] = n;\n return v;\n};\nvar cpw = function (v, t, s, e) {\n if (u8.prototype.copyWithin)\n return u8.prototype.copyWithin.call(v, t, s, e);\n if (s == null || s < 0)\n s = 0;\n if (e == null || e > v.length)\n e = v.length;\n while (s < e) {\n v[t++] = v[s++];\n }\n};\n/**\n * Codes for errors generated within this library\n */\nexport var ZstdErrorCode = {\n InvalidData: 0,\n WindowSizeTooLarge: 1,\n InvalidBlockType: 2,\n FSEAccuracyTooHigh: 3,\n DistanceTooFarBack: 4,\n UnexpectedEOF: 5\n};\n// error codes\nvar ec = [\n 'invalid zstd data',\n 'window size too large (>2046MB)',\n 'invalid block type',\n 'FSE accuracy too high',\n 'match distance too far back',\n 'unexpected EOF'\n];\nvar err = function (ind, msg, nt) {\n var e = new Error(msg || ec[ind]);\n e.code = ind;\n if (Error.captureStackTrace)\n Error.captureStackTrace(e, err);\n if (!nt)\n throw e;\n return e;\n};\nvar rb = function (d, b, n) {\n var i = 0, o = 0;\n for (; i < n; ++i)\n o |= d[b++] << (i << 3);\n return o;\n};\nvar b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };\n// read Zstandard frame header\nvar rzfh = function (dat, w) {\n var n3 = dat[0] | (dat[1] << 8) | (dat[2] << 16);\n if (n3 == 0x2FB528 && dat[3] == 253) {\n // Zstandard\n var flg = dat[4];\n // single segment checksum dict flag frame content flag\n var ss = (flg >> 5) & 1, cc = (flg >> 2) & 1, df = flg & 3, fcf = flg >> 6;\n if (flg & 8)\n err(0);\n // byte\n var bt = 6 - ss;\n // dict bytes\n var db = df == 3 ? 4 : df;\n // dictionary id\n var di = rb(dat, bt, db);\n bt += db;\n // frame size bytes\n var fsb = fcf ? (1 << fcf) : ss;\n // frame source size\n var fss = rb(dat, bt, fsb) + ((fcf == 1) && 256);\n // window size\n var ws = fss;\n if (!ss) {\n // window descriptor\n var wb = 1 << (10 + (dat[5] >> 3));\n ws = wb + (wb >> 3) * (dat[5] & 7);\n }\n if (ws > 2145386496)\n err(1);\n var buf = new u8((w == 1 ? (fss || ws) : w ? 0 : ws) + 12);\n buf[0] = 1, buf[4] = 4, buf[8] = 8;\n return {\n b: bt + fsb,\n y: 0,\n l: 0,\n d: di,\n w: (w && w != 1) ? w : buf.subarray(12),\n e: ws,\n o: new i32(buf.buffer, 0, 3),\n u: fss,\n c: cc,\n m: Math.min(131072, ws)\n };\n }\n else if (((n3 >> 4) | (dat[3] << 20)) == 0x184D2A5) {\n // skippable\n return b4(dat, 4) + 8;\n }\n err(0);\n};\n// most significant bit for nonzero\nvar msb = function (val) {\n var bits = 0;\n for (; (1 << bits) <= val; ++bits)\n ;\n return bits - 1;\n};\n// read finite state entropy\nvar rfse = function (dat, bt, mal) {\n // table pos\n var tpos = (bt << 3) + 4;\n // accuracy log\n var al = (dat[bt] & 15) + 5;\n if (al > mal)\n err(3);\n // size\n var sz = 1 << al;\n // probabilities symbols repeat index high threshold\n var probs = sz, sym = -1, re = -1, i = -1, ht = sz;\n // optimization: single allocation is much faster\n var buf = new ab(512 + (sz << 2));\n var freq = new i16(buf, 0, 256);\n // same view as freq\n var dstate = new u16(buf, 0, 256);\n var nstate = new u16(buf, 512, sz);\n var bb1 = 512 + (sz << 1);\n var syms = new u8(buf, bb1, sz);\n var nbits = new u8(buf, bb1 + sz);\n while (sym < 255 && probs > 0) {\n var bits = msb(probs + 1);\n var cbt = tpos >> 3;\n // mask\n var msk = (1 << (bits + 1)) - 1;\n var val = ((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (tpos & 7)) & msk;\n // mask (1 fewer bit)\n var msk1fb = (1 << bits) - 1;\n // max small value\n var msv = msk - probs - 1;\n // small value\n var sval = val & msk1fb;\n if (sval < msv)\n tpos += bits, val = sval;\n else {\n tpos += bits + 1;\n if (val > msk1fb)\n val -= msv;\n }\n freq[++sym] = --val;\n if (val == -1) {\n probs += val;\n syms[--ht] = sym;\n }\n else\n probs -= val;\n if (!val) {\n do {\n // repeat byte\n var rbt = tpos >> 3;\n re = ((dat[rbt] | (dat[rbt + 1] << 8)) >> (tpos & 7)) & 3;\n tpos += 2;\n sym += re;\n } while (re == 3);\n }\n }\n if (sym > 255 || probs)\n err(0);\n var sympos = 0;\n // sym step (coprime with sz - formula from zstd source)\n var sstep = (sz >> 1) + (sz >> 3) + 3;\n // sym mask\n var smask = sz - 1;\n for (var s = 0; s <= sym; ++s) {\n var sf = freq[s];\n if (sf < 1) {\n dstate[s] = -sf;\n continue;\n }\n // This is split into two loops in zstd to avoid branching, but as JS is higher-level that is unnecessary\n for (i = 0; i < sf; ++i) {\n syms[sympos] = s;\n do {\n sympos = (sympos + sstep) & smask;\n } while (sympos >= ht);\n }\n }\n // After spreading symbols, should be zero again\n if (sympos)\n err(0);\n for (i = 0; i < sz; ++i) {\n // next state\n var ns = dstate[syms[i]]++;\n // num bits\n var nb = nbits[i] = al - msb(ns);\n nstate[i] = (ns << nb) - sz;\n }\n return [(tpos + 7) >> 3, {\n b: al,\n s: syms,\n n: nbits,\n t: nstate\n }];\n};\n// read huffman\nvar rhu = function (dat, bt) {\n // index weight count\n var i = 0, wc = -1;\n // buffer header byte\n var buf = new u8(292), hb = dat[bt];\n // huffman weights\n var hw = buf.subarray(0, 256);\n // rank count\n var rc = buf.subarray(256, 268);\n // rank index\n var ri = new u16(buf.buffer, 268);\n // NOTE: at this point bt is 1 less than expected\n if (hb < 128) {\n // end byte, fse decode table\n var _a = rfse(dat, bt + 1, 6), ebt = _a[0], fdt = _a[1];\n bt += hb;\n var epos = ebt << 3;\n // last byte\n var lb = dat[bt];\n if (!lb)\n err(0);\n // state1 state2 state1 bits state2 bits\n var st1 = 0, st2 = 0, btr1 = fdt.b, btr2 = btr1;\n // fse pos\n // pre-increment to account for original deficit of 1\n var fpos = (++bt << 3) - 8 + msb(lb);\n for (;;) {\n fpos -= btr1;\n if (fpos < epos)\n break;\n var cbt = fpos >> 3;\n st1 += ((dat[cbt] | (dat[cbt + 1] << 8)) >> (fpos & 7)) & ((1 << btr1) - 1);\n hw[++wc] = fdt.s[st1];\n fpos -= btr2;\n if (fpos < epos)\n break;\n cbt = fpos >> 3;\n st2 += ((dat[cbt] | (dat[cbt + 1] << 8)) >> (fpos & 7)) & ((1 << btr2) - 1);\n hw[++wc] = fdt.s[st2];\n btr1 = fdt.n[st1];\n st1 = fdt.t[st1];\n btr2 = fdt.n[st2];\n st2 = fdt.t[st2];\n }\n if (++wc > 255)\n err(0);\n }\n else {\n wc = hb - 127;\n for (; i < wc; i += 2) {\n var byte = dat[++bt];\n hw[i] = byte >> 4;\n hw[i + 1] = byte & 15;\n }\n ++bt;\n }\n // weight exponential sum\n var wes = 0;\n for (i = 0; i < wc; ++i) {\n var wt = hw[i];\n // bits must be at most 11, same as weight\n if (wt > 11)\n err(0);\n wes += wt && (1 << (wt - 1));\n }\n // max bits\n var mb = msb(wes) + 1;\n // table size\n var ts = 1 << mb;\n // remaining sum\n var rem = ts - wes;\n // must be power of 2\n if (rem & (rem - 1))\n err(0);\n hw[wc++] = msb(rem) + 1;\n for (i = 0; i < wc; ++i) {\n var wt = hw[i];\n ++rc[hw[i] = wt && (mb + 1 - wt)];\n }\n // huf buf\n var hbuf = new u8(ts << 1);\n // symbols num bits\n var syms = hbuf.subarray(0, ts), nb = hbuf.subarray(ts);\n ri[mb] = 0;\n for (i = mb; i > 0; --i) {\n var pv = ri[i];\n fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << (mb - i)));\n }\n if (ri[0] != ts)\n err(0);\n for (i = 0; i < wc; ++i) {\n var bits = hw[i];\n if (bits) {\n var code = ri[bits];\n fill(syms, i, code, ri[bits] = code + (1 << (mb - bits)));\n }\n }\n return [bt, {\n n: nb,\n b: mb,\n s: syms\n }];\n};\n// Tables generated using this:\n// https://gist.github.com/101arrowz/a979452d4355992cbf8f257cbffc9edd\n// default literal length table\nvar dllt = /*#__PURE__*/ rfse(/*#__PURE__*/ new u8([\n 81, 16, 99, 140, 49, 198, 24, 99, 12, 33, 196, 24, 99, 102, 102, 134, 70, 146, 4\n]), 0, 6)[1];\n// default match length table\nvar dmlt = /*#__PURE__*/ rfse(/*#__PURE__*/ new u8([\n 33, 20, 196, 24, 99, 140, 33, 132, 16, 66, 8, 33, 132, 16, 66, 8, 33, 68, 68, 68, 68, 68, 68, 68, 68, 36, 9\n]), 0, 6)[1];\n// default offset code table\nvar doct = /*#__PURE__ */ rfse(/*#__PURE__*/ new u8([\n 32, 132, 16, 66, 102, 70, 68, 68, 68, 68, 36, 73, 2\n]), 0, 5)[1];\n// bits to baseline\nvar b2bl = function (b, s) {\n var len = b.length, bl = new i32(len);\n for (var i = 0; i < len; ++i) {\n bl[i] = s;\n s += 1 << b[i];\n }\n return bl;\n};\n// literal length bits\nvar llb = /*#__PURE__ */ new u8(( /*#__PURE__ */new i32([\n 0, 0, 0, 0, 16843009, 50528770, 134678020, 202050057, 269422093\n])).buffer, 0, 36);\n// literal length baseline\nvar llbl = /*#__PURE__ */ b2bl(llb, 0);\n// match length bits\nvar mlb = /*#__PURE__ */ new u8(( /*#__PURE__ */new i32([\n 0, 0, 0, 0, 0, 0, 0, 0, 16843009, 50528770, 117769220, 185207048, 252579084, 16\n])).buffer, 0, 53);\n// match length baseline\nvar mlbl = /*#__PURE__ */ b2bl(mlb, 3);\n// decode huffman stream\nvar dhu = function (dat, out, hu) {\n var len = dat.length, ss = out.length, lb = dat[len - 1], msk = (1 << hu.b) - 1, eb = -hu.b;\n if (!lb)\n err(0);\n var st = 0, btr = hu.b, pos = (len << 3) - 8 + msb(lb) - btr, i = -1;\n for (; pos > eb && i < ss;) {\n var cbt = pos >> 3;\n var val = (dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (pos & 7);\n st = ((st << btr) | val) & msk;\n out[++i] = hu.s[st];\n pos -= (btr = hu.n[st]);\n }\n if (pos != eb || i + 1 != ss)\n err(0);\n};\n// decode huffman stream 4x\n// TODO: use workers to parallelize\nvar dhu4 = function (dat, out, hu) {\n var bt = 6;\n var ss = out.length, sz1 = (ss + 3) >> 2, sz2 = sz1 << 1, sz3 = sz1 + sz2;\n dhu(dat.subarray(bt, bt += dat[0] | (dat[1] << 8)), out.subarray(0, sz1), hu);\n dhu(dat.subarray(bt, bt += dat[2] | (dat[3] << 8)), out.subarray(sz1, sz2), hu);\n dhu(dat.subarray(bt, bt += dat[4] | (dat[5] << 8)), out.subarray(sz2, sz3), hu);\n dhu(dat.subarray(bt), out.subarray(sz3), hu);\n};\n// read Zstandard block\nvar rzb = function (dat, st, out) {\n var _a;\n var bt = st.b;\n // byte 0 block type\n var b0 = dat[bt], btype = (b0 >> 1) & 3;\n st.l = b0 & 1;\n var sz = (b0 >> 3) | (dat[bt + 1] << 5) | (dat[bt + 2] << 13);\n // end byte for block\n var ebt = (bt += 3) + sz;\n if (btype == 1) {\n if (bt >= dat.length)\n return;\n st.b = bt + 1;\n if (out) {\n fill(out, dat[bt], st.y, st.y += sz);\n return out;\n }\n return fill(new u8(sz), dat[bt]);\n }\n if (ebt > dat.length)\n return;\n if (btype == 0) {\n st.b = ebt;\n if (out) {\n out.set(dat.subarray(bt, ebt), st.y);\n st.y += sz;\n return out;\n }\n return slc(dat, bt, ebt);\n }\n if (btype == 2) {\n // byte 3 lit btype size format\n var b3 = dat[bt], lbt = b3 & 3, sf = (b3 >> 2) & 3;\n // lit src size lit cmp sz 4 streams\n var lss = b3 >> 4, lcs = 0, s4 = 0;\n if (lbt < 2) {\n if (sf & 1)\n lss |= (dat[++bt] << 4) | ((sf & 2) && (dat[++bt] << 12));\n else\n lss = b3 >> 3;\n }\n else {\n s4 = sf;\n if (sf < 2)\n lss |= ((dat[++bt] & 63) << 4), lcs = (dat[bt] >> 6) | (dat[++bt] << 2);\n else if (sf == 2)\n lss |= (dat[++bt] << 4) | ((dat[++bt] & 3) << 12), lcs = (dat[bt] >> 2) | (dat[++bt] << 6);\n else\n lss |= (dat[++bt] << 4) | ((dat[++bt] & 63) << 12), lcs = (dat[bt] >> 6) | (dat[++bt] << 2) | (dat[++bt] << 10);\n }\n ++bt;\n // add literals to end - can never overlap with backreferences because unused literals always appended\n var buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m);\n // starting point for literals\n var spl = buf.length - lss;\n if (lbt == 0)\n buf.set(dat.subarray(bt, bt += lss), spl);\n else if (lbt == 1)\n fill(buf, dat[bt++], spl);\n else {\n // huffman table\n var hu = st.h;\n if (lbt == 2) {\n var hud = rhu(dat, bt);\n // subtract description length\n lcs += bt - (bt = hud[0]);\n st.h = hu = hud[1];\n }\n else if (!hu)\n err(0);\n (s4 ? dhu4 : dhu)(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu);\n }\n // num sequences\n var ns = dat[bt++];\n if (ns) {\n if (ns == 255)\n ns = (dat[bt++] | (dat[bt++] << 8)) + 0x7F00;\n else if (ns > 127)\n ns = ((ns - 128) << 8) | dat[bt++];\n // symbol compression modes\n var scm = dat[bt++];\n if (scm & 3)\n err(0);\n var dts = [dmlt, doct, dllt];\n for (var i = 2; i > -1; --i) {\n var md = (scm >> ((i << 1) + 2)) & 3;\n if (md == 1) {\n // rle buf\n var rbuf = new u8([0, 0, dat[bt++]]);\n dts[i] = {\n s: rbuf.subarray(2, 3),\n n: rbuf.subarray(0, 1),\n t: new u16(rbuf.buffer, 0, 1),\n b: 0\n };\n }\n else if (md == 2) {\n // accuracy log 8 for offsets, 9 for others\n _a = rfse(dat, bt, 9 - (i & 1)), bt = _a[0], dts[i] = _a[1];\n }\n else if (md == 3) {\n if (!st.t)\n err(0);\n dts[i] = st.t[i];\n }\n }\n var _b = st.t = dts, mlt = _b[0], oct = _b[1], llt = _b[2];\n var lb = dat[ebt - 1];\n if (!lb)\n err(0);\n var spos = (ebt << 3) - 8 + msb(lb) - llt.b, cbt = spos >> 3, oubt = 0;\n var lst = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << llt.b) - 1);\n cbt = (spos -= oct.b) >> 3;\n var ost = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << oct.b) - 1);\n cbt = (spos -= mlt.b) >> 3;\n var mst = ((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << mlt.b) - 1);\n for (++ns; --ns;) {\n var llc = llt.s[lst];\n var lbtr = llt.n[lst];\n var mlc = mlt.s[mst];\n var mbtr = mlt.n[mst];\n var ofc = oct.s[ost];\n var obtr = oct.n[ost];\n cbt = (spos -= ofc) >> 3;\n var ofp = 1 << ofc;\n var off = ofp + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16) | (dat[cbt + 3] << 24)) >>> (spos & 7)) & (ofp - 1));\n cbt = (spos -= mlb[mlc]) >> 3;\n var ml = mlbl[mlc] + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (spos & 7)) & ((1 << mlb[mlc]) - 1));\n cbt = (spos -= llb[llc]) >> 3;\n var ll = llbl[llc] + (((dat[cbt] | (dat[cbt + 1] << 8) | (dat[cbt + 2] << 16)) >> (spos & 7)) & ((1 << llb[llc]) - 1));\n cbt = (spos -= lbtr) >> 3;\n lst = llt.t[lst] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << lbtr) - 1));\n cbt = (spos -= mbtr) >> 3;\n mst = mlt.t[mst] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << mbtr) - 1));\n cbt = (spos -= obtr) >> 3;\n ost = oct.t[ost] + (((dat[cbt] | (dat[cbt + 1] << 8)) >> (spos & 7)) & ((1 << obtr) - 1));\n if (off > 3) {\n st.o[2] = st.o[1];\n st.o[1] = st.o[0];\n st.o[0] = off -= 3;\n }\n else {\n var idx = off - (ll != 0);\n if (idx) {\n off = idx == 3 ? st.o[0] - 1 : st.o[idx];\n if (idx > 1)\n st.o[2] = st.o[1];\n st.o[1] = st.o[0];\n st.o[0] = off;\n }\n else\n off = st.o[0];\n }\n for (var i = 0; i < ll; ++i) {\n buf[oubt + i] = buf[spl + i];\n }\n oubt += ll, spl += ll;\n var stin = oubt - off;\n if (stin < 0) {\n var len = -stin;\n var bs = st.e + stin;\n if (len > ml)\n len = ml;\n for (var i = 0; i < len; ++i) {\n buf[oubt + i] = st.w[bs + i];\n }\n oubt += len, ml -= len, stin = 0;\n }\n for (var i = 0; i < ml; ++i) {\n buf[oubt + i] = buf[stin + i];\n }\n oubt += ml;\n }\n if (oubt != spl) {\n while (spl < buf.length) {\n buf[oubt++] = buf[spl++];\n }\n }\n else\n oubt = buf.length;\n if (out)\n st.y += oubt;\n else\n buf = slc(buf, 0, oubt);\n }\n else if (out) {\n st.y += lss;\n if (spl) {\n for (var i = 0; i < lss; ++i) {\n buf[i] = buf[spl + i];\n }\n }\n }\n else if (spl)\n buf = slc(buf, spl);\n st.b = ebt;\n return buf;\n }\n err(2);\n};\n// concat\nvar cct = function (bufs, ol) {\n if (bufs.length == 1)\n return bufs[0];\n var buf = new u8(ol);\n for (var i = 0, b = 0; i < bufs.length; ++i) {\n var chk = bufs[i];\n buf.set(chk, b);\n b += chk.length;\n }\n return buf;\n};\n/**\n * Decompresses Zstandard data\n * @param dat The input data\n * @param buf The output buffer. If unspecified, the function will allocate\n * exactly enough memory to fit the decompressed data. If your\n * data has multiple frames and you know the output size, specifying\n * it will yield better performance.\n * @returns The decompressed data\n */\nexport function decompress(dat, buf) {\n var bufs = [], nb = +!buf;\n var bt = 0, ol = 0;\n for (; dat.length;) {\n var st = rzfh(dat, nb || buf);\n if (typeof st == 'object') {\n if (nb) {\n buf = null;\n if (st.w.length == st.u) {\n bufs.push(buf = st.w);\n ol += st.u;\n }\n }\n else {\n bufs.push(buf);\n st.e = 0;\n }\n for (; !st.l;) {\n var blk = rzb(dat, st, buf);\n if (!blk)\n err(5);\n if (buf)\n st.e = st.y;\n else {\n bufs.push(blk);\n ol += blk.length;\n cpw(st.w, 0, blk.length);\n st.w.set(blk, st.w.length - blk.length);\n }\n }\n bt = st.b + (st.c * 4);\n }\n else\n bt = st;\n dat = dat.subarray(bt);\n }\n return cct(bufs, ol);\n}\n/**\n * Decompressor for Zstandard streamed data\n */\nvar Decompress = /*#__PURE__*/ (function () {\n /**\n * Creates a Zstandard decompressor\n * @param ondata The handler for stream data\n */\n function Decompress(ondata) {\n this.ondata = ondata;\n this.c = [];\n this.l = 0;\n this.z = 0;\n }\n /**\n * Pushes data to be decompressed\n * @param chunk The chunk of data to push\n * @param final Whether or not this is the last chunk in the stream\n */\n Decompress.prototype.push = function (chunk, final) {\n if (typeof this.s == 'number') {\n var sub = Math.min(chunk.length, this.s);\n chunk = chunk.subarray(sub);\n this.s -= sub;\n }\n var sl = chunk.length;\n var ncs = sl + this.l;\n if (!this.s) {\n if (final) {\n if (!ncs) {\n this.ondata(new u8(0), true);\n return;\n }\n // min for frame + one block\n if (ncs < 5)\n err(5);\n }\n else if (ncs < 18) {\n this.c.push(chunk);\n this.l = ncs;\n return;\n }\n if (this.l) {\n this.c.push(chunk);\n chunk = cct(this.c, ncs);\n this.c = [];\n this.l = 0;\n }\n if (typeof (this.s = rzfh(chunk)) == 'number')\n return this.push(chunk, final);\n }\n if (typeof this.s != 'number') {\n if (ncs < (this.z || 3)) {\n if (final)\n err(5);\n this.c.push(chunk);\n this.l = ncs;\n return;\n }\n if (this.l) {\n this.c.push(chunk);\n chunk = cct(this.c, ncs);\n this.c = [];\n this.l = 0;\n }\n if (!this.z && ncs < (this.z = (chunk[this.s.b] & 2) ? 4 : 3 + ((chunk[this.s.b] >> 3) | (chunk[this.s.b + 1] << 5) | (chunk[this.s.b + 2] << 13)))) {\n if (final)\n err(5);\n this.c.push(chunk);\n this.l = ncs;\n return;\n }\n else\n this.z = 0;\n for (;;) {\n var blk = rzb(chunk, this.s);\n if (!blk) {\n if (final)\n err(5);\n var adc = chunk.subarray(this.s.b);\n this.s.b = 0;\n this.c.push(adc), this.l += adc.length;\n return;\n }\n else {\n this.ondata(blk, false);\n cpw(this.s.w, 0, blk.length);\n this.s.w.set(blk, this.s.w.length - blk.length);\n }\n if (this.s.l) {\n var rest = chunk.subarray(this.s.b);\n this.s = this.s.c * 4;\n this.push(rest, final);\n return;\n }\n }\n }\n else if (final)\n err(5);\n };\n return Decompress;\n}());\nexport { Decompress };\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * xp-state-init — parse `xp:state-init` widget option payloads.\n *\n * The CMS custom-module posts three forms of the state-init option for\n * the `xp-state-init` widget. This helper decodes any of them into a\n * plain JS object suitable for `new XpStateStore({ initialState: ... })`.\n *\n * 1. `zstd+b64:<b64>` — base64 → zstd decompress → JSON parse\n * 2. `gzip+b64:<b64>` — base64 → gunzip → JSON parse\n * 3. Plain JSON string (starts with `{`) — JSON parse directly\n *\n * gzip uses the Web standard `DecompressionStream('gzip')`, which is\n * available natively in Node ≥ 18 and all modern browsers — no bundled\n * dep required.\n *\n * zstd uses `fzstd` — a pure-JS decompressor (≈ 65 kB unpacked, tree-\n * shakes the decompress-only path; no wasm, no native module). We\n * evaluated `@bokuweb/zstd-wasm` but its wasm blob is ~200 kB gzipped\n * and forces host apps to serve a separate asset with the right MIME.\n * `fzstd` is small enough to inline, zero-config in jsdom tests, and\n * works identically in node + browser.\n *\n * On any failure this module throws an `Error` with a message citing\n * the path that failed (prefix detection / base64 decode / decompress /\n * JSON parse) — callers are expected to log + fall back to empty state.\n */\n\nimport { decompress as zstdDecompress } from 'fzstd';\n\n/**\n * Parse an `xp:state-init` widget-option value.\n *\n * @param {string} value - raw option text from the XLF\n * @returns {object} parsed state object (suitable for XpStateStore\n * `initialState`). Empty object when `value` decodes to `null` or\n * a non-object primitive — the store only accepts object seeds.\n * @throws {Error} with a descriptive message on any failure\n */\nexport function parseXpStateInit(value) {\n if (value == null || value === '') {\n throw new Error('parseXpStateInit: empty value');\n }\n if (typeof value !== 'string') {\n throw new Error(`parseXpStateInit: expected string, got ${typeof value}`);\n }\n\n const trimmed = value.trim();\n if (trimmed === '') {\n throw new Error('parseXpStateInit: empty value');\n }\n\n // Dispatch on prefix. The colon is required; anything else falls\n // through to the JSON branch below.\n const colon = trimmed.indexOf(':');\n if (colon > 0) {\n const prefix = trimmed.slice(0, colon);\n const payload = trimmed.slice(colon + 1);\n\n if (prefix === 'zstd+b64') {\n return _parseZstdB64(payload);\n }\n if (prefix === 'gzip+b64') {\n return _parseGzipB64(payload);\n }\n // Unknown prefix that still looks like \"word+b64:...\" — reject\n // loudly rather than silently treating the whole thing as JSON.\n // Heuristic: prefix contains \"+b64\" or is all-lowercase-word.\n if (/^[a-z][a-z0-9+]*$/i.test(prefix) && prefix.includes('+b64')) {\n throw new Error(\n `parseXpStateInit: unsupported prefix \"${prefix}\" — expected zstd+b64 or gzip+b64`\n );\n }\n }\n\n // Plain JSON path. Must start with { or [ to be remotely valid;\n // anything else almost certainly means a mis-typed prefix.\n if (!/^[\\[{]/.test(trimmed)) {\n throw new Error(\n 'parseXpStateInit: value is neither a known compression prefix ' +\n '(zstd+b64:/gzip+b64:) nor a JSON object/array literal'\n );\n }\n let parsed;\n try {\n parsed = JSON.parse(trimmed);\n } catch (err) {\n throw new Error(`parseXpStateInit: invalid JSON payload — ${err.message}`);\n }\n return _normalise(parsed);\n}\n\nfunction _parseZstdB64(b64) {\n const bytes = _b64ToBytes(b64, 'zstd+b64');\n let raw;\n try {\n raw = zstdDecompress(bytes);\n } catch (err) {\n throw new Error(`parseXpStateInit: zstd decompression failed — ${err.message}`);\n }\n return _parseJsonFromBytes(raw, 'zstd+b64');\n}\n\nfunction _parseGzipB64(b64) {\n const bytes = _b64ToBytes(b64, 'gzip+b64');\n let raw;\n try {\n raw = _gunzipSync(bytes);\n } catch (err) {\n throw new Error(`parseXpStateInit: gzip decompression failed — ${err.message}`);\n }\n return _parseJsonFromBytes(raw, 'gzip+b64');\n}\n\n/**\n * Synchronous gunzip.\n *\n * Node ≥ 18 exposes `zlib.gunzipSync`; the browser does not have a\n * synchronous gzip API (DecompressionStream is async-only). Rather\n * than forcing the public `parseXpStateInit` API to be async, we\n * detect the environment and provide gzip synchronously in Node only:\n *\n * - Node: imported from `node:zlib` via a top-level conditional\n * import. Bundlers (webpack, rollup, esbuild) treat `node:*` as\n * Node built-ins and either externalise or strip this import in\n * browser builds.\n * - Browser: throws with a message directing the caller to use\n * `zstd+b64:` or plain JSON. Production payloads from the CMS\n * custom-module default to `zstd+b64:`; `gzip+b64:` is a\n * debug / fallback path that browser-resident renderers don't\n * need today.\n */\nlet NODE_GUNZIP = null;\ntry {\n const proc = typeof globalThis !== 'undefined' ? globalThis.process : null;\n if (proc && proc.versions && proc.versions.node) {\n // Top-level await at module load. Safe in Node ≥ 14.8 and every\n // bundler that targets ESM (which is all of them in 2026).\n // eslint-disable-next-line no-restricted-syntax\n const zlib = await import('node:zlib');\n NODE_GUNZIP = zlib.gunzipSync;\n }\n} catch (_err) {\n // Import failed — non-Node environment, leave NODE_GUNZIP null.\n}\n\nfunction _gunzipSync(bytes) {\n if (NODE_GUNZIP) {\n return new Uint8Array(NODE_GUNZIP(Buffer.from(bytes)));\n }\n throw new Error(\n 'gzip decompression not supported in this runtime — browser builds ' +\n 'currently only support zstd+b64 and plain JSON xp:state-init payloads'\n );\n}\n\nfunction _b64ToBytes(b64, label) {\n const clean = String(b64).trim();\n if (clean === '') {\n throw new Error(`parseXpStateInit: empty base64 payload after ${label}:`);\n }\n try {\n if (typeof atob === 'function') {\n const bin = atob(clean);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n }\n if (typeof Buffer !== 'undefined') {\n return new Uint8Array(Buffer.from(clean, 'base64'));\n }\n throw new Error('no base64 decoder available (neither atob nor Buffer)');\n } catch (err) {\n throw new Error(\n `parseXpStateInit: base64 decode failed for ${label} — ${err.message}`\n );\n }\n}\n\nfunction _parseJsonFromBytes(bytes, label) {\n let text;\n try {\n text = new TextDecoder('utf-8', { fatal: true }).decode(bytes);\n } catch (err) {\n throw new Error(\n `parseXpStateInit: ${label} payload is not valid UTF-8 — ${err.message}`\n );\n }\n let parsed;\n try {\n parsed = JSON.parse(text);\n } catch (err) {\n throw new Error(\n `parseXpStateInit: ${label} payload is not valid JSON — ${err.message}`\n );\n }\n return _normalise(parsed);\n}\n\nfunction _normalise(parsed) {\n if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n throw new Error(\n 'parseXpStateInit: decoded payload must be a JSON object (got ' +\n (Array.isArray(parsed) ? 'array' : typeof parsed) +\n ')'\n );\n }\n return parsed;\n}\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2024-2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * LayoutPool - Maintains a pool of pre-built layout containers\n * for instant layout transitions.\n *\n * Instead of tearing down and rebuilding the DOM on every layout switch,\n * the pool keeps up to `maxSize` layout containers alive. The current\n * layout is marked 'hot' (visible); pre-loaded layouts are 'warm' (hidden).\n * When transitioning, visibility is swapped instantly - no DOM rebuild.\n *\n * Pool entries:\n * layoutId -> { container, layout, regions, blobUrls, mediaUrlCache, status, lastAccess }\n *\n * Status: 'hot' (currently visible) or 'warm' (preloaded, hidden)\n */\n\nimport { createLogger } from '@xiboplayer/utils';\n\nconst log = createLogger('LayoutPool');\n\nexport class LayoutPool {\n /**\n * @param {number} maxSize - Maximum number of layouts to keep in pool (default: 2)\n */\n constructor(maxSize = 2) {\n /** @type {Map<number, Object>} */\n this.layouts = new Map();\n this.maxSize = maxSize;\n /** @type {number|null} */\n this.hotLayoutId = null;\n }\n\n /**\n * Check if a layout is in the pool\n * @param {number} layoutId\n * @returns {boolean}\n */\n has(layoutId) {\n return this.layouts.has(layoutId);\n }\n\n /**\n * Get a pool entry\n * @param {number} layoutId\n * @returns {Object|undefined}\n */\n get(layoutId) {\n return this.layouts.get(layoutId);\n }\n\n /**\n * Add a layout entry to the pool.\n * If pool is full, evicts the least-recently-used warm entry.\n *\n * @param {number} layoutId\n * @param {Object} entry - Pool entry\n * @param {HTMLElement} entry.container - Layout container DOM element\n * @param {Object} entry.layout - Parsed layout object\n * @param {Map} entry.regions - Region map (regionId => region state)\n * @param {Set<string>} entry.blobUrls - Tracked blob URLs for this layout\n * @param {Map} [entry.mediaUrlCache] - Media URL cache (fileId => url)\n */\n add(layoutId, entry) {\n // If already in pool, update in place\n if (this.layouts.has(layoutId)) {\n const existing = this.layouts.get(layoutId);\n Object.assign(existing, entry);\n existing.lastAccess = Date.now();\n return;\n }\n\n // If pool is full, evict LRU warm entry\n if (this.layouts.size >= this.maxSize) {\n this.evictLRU();\n }\n\n entry.status = 'warm';\n entry.lastAccess = Date.now();\n this.layouts.set(layoutId, entry);\n log.info(`Added layout ${layoutId} to pool (size: ${this.layouts.size}/${this.maxSize})`);\n }\n\n /**\n * Mark a layout as active (visible).\n * The previous hot layout is demoted to warm.\n * @param {number} layoutId\n */\n setHot(layoutId) {\n // Demote previous hot layout to warm\n if (this.hotLayoutId !== null && this.layouts.has(this.hotLayoutId)) {\n this.layouts.get(this.hotLayoutId).status = 'warm';\n }\n\n if (this.layouts.has(layoutId)) {\n const entry = this.layouts.get(layoutId);\n entry.status = 'hot';\n entry.lastAccess = Date.now();\n }\n\n this.hotLayoutId = layoutId;\n }\n\n /**\n * Evict a specific layout from the pool.\n * Releases video/audio resources, revokes blob URLs, and removes the container from the DOM.\n * @param {number} layoutId\n */\n evict(layoutId) {\n const entry = this.layouts.get(layoutId);\n if (!entry) return;\n\n log.info(`Evicting layout ${layoutId} from pool`);\n\n // Stop any active region timers\n if (entry.regions) {\n for (const [regionId, region] of entry.regions) {\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n }\n }\n\n // Release all video/audio resources BEFORE removing from DOM.\n // Removing a <video> with an active src leaks decoded frame buffers.\n if (entry.container) {\n LayoutPool.releaseMediaElements(entry.container);\n }\n\n // Revoke blob URLs\n if (entry.blobUrls && entry.blobUrls.size > 0) {\n entry.blobUrls.forEach(url => {\n URL.revokeObjectURL(url);\n });\n log.info(`Revoked ${entry.blobUrls.size} blob URLs for layout ${layoutId}`);\n }\n\n // Revoke media URL cache blob URLs\n if (entry.mediaUrlCache) {\n for (const [fileId, blobUrl] of entry.mediaUrlCache) {\n if (blobUrl && typeof blobUrl === 'string' && blobUrl.startsWith('blob:')) {\n URL.revokeObjectURL(blobUrl);\n }\n }\n }\n\n // Remove container from DOM\n if (entry.container && entry.container.parentNode) {\n entry.container.remove();\n }\n\n this.layouts.delete(layoutId);\n\n // Clear hot reference if this was the hot layout\n if (this.hotLayoutId === layoutId) {\n this.hotLayoutId = null;\n }\n }\n\n /**\n * Release all video and audio elements inside a container.\n * Must be called BEFORE removing the container from the DOM —\n * browsers keep decoded frame buffers alive for detached <video> elements\n * that still have a src.\n *\n * @param {HTMLElement} container\n */\n static releaseMediaElements(container) {\n // Defer the actual release by one animation frame to give the GPU compositor\n // time to stop referencing textures from the old layout. Without this delay,\n // the compositor may still hold stale mailbox references when we destroy the\n // video backing, causing SharedImageManager::ProduceSkia \"non-existent mailbox\"\n // errors (Chrome bug: race in shared_image_manager.cc acknowledged in a TODO).\n requestAnimationFrame(() => LayoutPool._releaseMediaElementsSync(container));\n }\n\n static _releaseMediaElementsSync(container) {\n let videoCount = 0;\n let hlsCount = 0;\n\n container.querySelectorAll('video').forEach(v => {\n // Destroy hls.js instance if attached (stored by renderVideo)\n if (v._hlsInstance) {\n v._hlsInstance.destroy();\n v._hlsInstance = null;\n hlsCount++;\n }\n // Stop MediaStream tracks (webcam/mic)\n if (v._mediaStream) {\n v._mediaStream.getTracks().forEach(t => t.stop());\n v._mediaStream = null;\n v.srcObject = null;\n }\n v.pause();\n v.removeAttribute('src');\n v.load(); // Forces browser to release decoded buffers\n videoCount++;\n });\n\n container.querySelectorAll('audio').forEach(a => {\n a.pause();\n a.removeAttribute('src');\n a.load();\n });\n\n // Release media inside iframes (embedded widgets with HLS streams, webcams, etc.)\n // We can't querySelectorAll('video') across iframe boundaries, but we can:\n // 1. Try to access same-origin iframe contentDocument\n // 2. Force-remove the iframe src to stop all network activity\n let iframeCount = 0;\n container.querySelectorAll('iframe').forEach(iframe => {\n try {\n // Same-origin iframes: reach inside and release videos\n const doc = iframe.contentDocument || iframe.contentWindow?.document;\n if (doc) {\n doc.querySelectorAll('video').forEach(v => {\n v.pause();\n v.removeAttribute('src');\n v.load();\n videoCount++;\n });\n doc.querySelectorAll('audio').forEach(a => {\n a.pause();\n a.removeAttribute('src');\n a.load();\n });\n }\n } catch (_) {\n // Cross-origin: can't access contentDocument\n }\n // Force stop all iframe network activity (HLS segments, SSE, WebSocket, etc.)\n iframe.src = 'about:blank';\n iframeCount++;\n });\n\n // Destroy PDF documents and release GPU canvas backing stores\n container.querySelectorAll('.pdf-widget').forEach(el => {\n if (el._pdfDestroy) el._pdfDestroy();\n });\n\n if (videoCount > 0 || iframeCount > 0) {\n log.info(`Released ${videoCount} video(s)${hlsCount ? ` (${hlsCount} HLS)` : ''}${iframeCount ? `, ${iframeCount} iframe(s)` : ''}`);\n }\n }\n\n /**\n * Evict the least-recently-used warm entry.\n * Only warm entries are eligible for eviction (never the hot layout).\n */\n evictLRU() {\n let oldest = null;\n let oldestTime = Infinity;\n\n for (const [id, entry] of this.layouts) {\n if (entry.status === 'warm' && entry.lastAccess < oldestTime) {\n oldest = id;\n oldestTime = entry.lastAccess;\n }\n }\n\n if (oldest !== null) {\n this.evict(oldest);\n }\n }\n\n /**\n * Clear all warm (preloaded) entries, keeping the hot layout.\n * @returns {number} Number of entries cleared\n */\n clearWarm() {\n let count = 0;\n const warmIds = [];\n\n for (const [id, entry] of this.layouts) {\n if (entry.status === 'warm') {\n warmIds.push(id);\n }\n }\n\n for (const id of warmIds) {\n this.evict(id);\n count++;\n }\n\n if (count > 0) {\n log.info(`Cleared ${count} warm layout(s) from pool`);\n }\n\n return count;\n }\n\n /**\n * Clear warm entries NOT in the given set of layout IDs.\n * Keeps warm entries that are still scheduled.\n * @param {Set<number>} keepIds - Layout IDs to keep\n * @returns {number} Number of entries cleared\n */\n clearWarmNotIn(keepIds) {\n let count = 0;\n const evictIds = [];\n\n for (const [id, entry] of this.layouts) {\n if (entry.status === 'warm' && !keepIds.has(id)) {\n evictIds.push(id);\n }\n }\n\n for (const id of evictIds) {\n this.evict(id);\n count++;\n }\n\n if (count > 0) {\n log.info(`Cleared ${count} warm layout(s) no longer in schedule`);\n }\n\n return count;\n }\n\n /**\n * Get the most recently added layout ID.\n * @returns {number|undefined}\n */\n getLatest() {\n let latest;\n for (const id of this.layouts.keys()) {\n latest = id;\n }\n return latest;\n }\n\n /**\n * Clear all entries (both hot and warm).\n */\n clear() {\n const ids = Array.from(this.layouts.keys());\n for (const id of ids) {\n this.evict(id);\n }\n this.hotLayoutId = null;\n }\n\n /**\n * Get the number of entries in the pool.\n * @returns {number}\n */\n get size() {\n return this.layouts.size;\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2024-2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * RendererLite - Lightweight XLF Layout Renderer\n *\n * A standalone, reusable JavaScript library for rendering Xibo Layout Format (XLF) files.\n * Provides layout rendering without dependencies on XLR, suitable for any platform.\n *\n * Features:\n * - Parse XLF XML layout files\n * - Create region DOM elements with positioning\n * - Render widgets (text, image, video, audio, PDF, webpage)\n * - Handle widget duration timers\n * - Apply CSS transitions (fade, fly)\n * - Event emitter for lifecycle hooks\n * - Manage layout lifecycle\n *\n * Usage pattern (similar to xmr-wrapper.js):\n *\n * ```javascript\n * import { RendererLite } from './renderer-lite.js';\n *\n * const container = document.getElementById('player-container');\n * const renderer = new RendererLite({ cmsUrl: '...', hardwareKey: '...' }, container);\n *\n * // Listen to events\n * renderer.on('layoutStart', (layoutId) => console.log('Layout started:', layoutId));\n * renderer.on('layoutEnd', (layoutId) => console.log('Layout ended:', layoutId));\n * renderer.on('widgetStart', (widget) => console.log('Widget started:', widget));\n * renderer.on('widgetEnd', (widget) => console.log('Widget ended:', widget));\n * renderer.on('error', (error) => console.error('Error:', error));\n *\n * // Render a layout\n * await renderer.renderLayout(layoutXml, duration);\n *\n * // Stop current layout\n * renderer.stopCurrentLayout();\n *\n * // Cleanup\n * renderer.cleanup();\n * ```\n */\n\nimport { EventEmitter } from '@xiboplayer/utils';\nimport { createLogger, isDebug, PLAYER_API } from '@xiboplayer/utils';\nimport { parseLayoutDuration } from '@xiboplayer/schedule';\nimport { asBool, ExprOutOfScope, evalExpr, XpStateStore, parseXpStateInit } from '@xiboplayer/expr';\nimport { LayoutPool } from './layout-pool.js';\n\n/**\n * Transition utilities for widget animations\n */\nexport const Transitions = {\n /**\n * Apply fade in transition\n */\n fadeIn(element, duration) {\n const keyframes = [\n { opacity: 0 },\n { opacity: 1 }\n ];\n const timing = {\n duration: duration,\n easing: 'linear',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n /**\n * Apply fade out transition\n */\n fadeOut(element, duration) {\n const keyframes = [\n { opacity: 1 },\n { opacity: 0 }\n ];\n const timing = {\n duration: duration,\n easing: 'linear',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n /**\n * Get fly keyframes based on compass direction\n */\n getFlyKeyframes(direction, width, height, isIn) {\n const dirMap = {\n 'N': { x: 0, y: isIn ? -height : height },\n 'NE': { x: isIn ? width : -width, y: isIn ? -height : height },\n 'E': { x: isIn ? width : -width, y: 0 },\n 'SE': { x: isIn ? width : -width, y: isIn ? height : -height },\n 'S': { x: 0, y: isIn ? height : -height },\n 'SW': { x: isIn ? -width : width, y: isIn ? height : -height },\n 'W': { x: isIn ? -width : width, y: 0 },\n 'NW': { x: isIn ? -width : width, y: isIn ? -height : height }\n };\n\n const offset = dirMap[direction] || dirMap['N'];\n\n if (isIn) {\n return {\n from: {\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n opacity: 0\n },\n to: {\n transform: 'translate(0, 0)',\n opacity: 1\n }\n };\n } else {\n return {\n from: {\n transform: 'translate(0, 0)',\n opacity: 1\n },\n to: {\n transform: `translate(${offset.x}px, ${offset.y}px)`,\n opacity: 0\n }\n };\n }\n },\n\n /**\n * Apply fly in transition\n */\n flyIn(element, duration, direction, regionWidth, regionHeight) {\n const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, true);\n const timing = {\n duration: duration,\n easing: 'ease-out',\n fill: 'forwards'\n };\n return element.animate([keyframes.from, keyframes.to], timing);\n },\n\n /**\n * Apply fly out transition\n */\n flyOut(element, duration, direction, regionWidth, regionHeight) {\n const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, false);\n const timing = {\n duration: duration,\n easing: 'ease-in',\n fill: 'forwards'\n };\n return element.animate([keyframes.from, keyframes.to], timing);\n },\n\n /**\n * Apply slide-in transition (layout-level #337).\n *\n * Identical in shape to flyIn but keeps opacity at 1 throughout —\n * slides are pure positional animations for layout-to-layout\n * transitions where both layouts are fully rendered and the effect\n * is a carousel-style push/pull.\n */\n slideIn(element, duration, direction, width, height) {\n const dirMap = {\n N: { x: 0, y: -height },\n NE: { x: width, y: -height },\n E: { x: width, y: 0 },\n SE: { x: width, y: height },\n S: { x: 0, y: height },\n SW: { x: -width, y: height },\n W: { x: -width, y: 0 },\n NW: { x: -width, y: -height }\n };\n const offset = dirMap[direction] || dirMap.E;\n return element.animate(\n [\n { transform: `translate(${offset.x}px, ${offset.y}px)` },\n { transform: 'translate(0, 0)' }\n ],\n { duration, easing: 'ease-out', fill: 'forwards' }\n );\n },\n\n /**\n * Apply slide-out transition (layout-level #337).\n *\n * Pushes the outgoing layout off in the given direction.\n */\n slideOut(element, duration, direction, width, height) {\n const dirMap = {\n N: { x: 0, y: -height },\n NE: { x: width, y: -height },\n E: { x: width, y: 0 },\n SE: { x: width, y: height },\n S: { x: 0, y: height },\n SW: { x: -width, y: height },\n W: { x: -width, y: 0 },\n NW: { x: -width, y: -height }\n };\n const offset = dirMap[direction] || dirMap.W;\n return element.animate(\n [\n { transform: 'translate(0, 0)' },\n { transform: `translate(${offset.x}px, ${offset.y}px)` }\n ],\n { duration, easing: 'ease-in', fill: 'forwards' }\n );\n },\n\n /**\n * Apply wipe-in transition (layout-level #337).\n *\n * Reveals the incoming layout by progressively shrinking a\n * clip-path inset from one edge. The `direction` picks which edge\n * is the \"start\" of the reveal — E means \"wipe reveals starting\n * from the left edge moving east\", matching the barWipe convention.\n */\n wipeIn(element, duration, direction) {\n // inset(<top> <right> <bottom> <left>) — 100% on an edge hides\n // everything past that edge, 0% on an edge reveals everything.\n const insetByDirection = {\n E: { from: 'inset(0 100% 0 0)', to: 'inset(0 0 0 0)' },\n W: { from: 'inset(0 0 0 100%)', to: 'inset(0 0 0 0)' },\n S: { from: 'inset(0 0 100% 0)', to: 'inset(0 0 0 0)' },\n N: { from: 'inset(100% 0 0 0)', to: 'inset(0 0 0 0)' },\n // Diagonals: wipe from the named corner to its opposite\n SE: { from: 'inset(0 100% 100% 0)', to: 'inset(0 0 0 0)' },\n SW: { from: 'inset(0 0 100% 100%)', to: 'inset(0 0 0 0)' },\n NE: { from: 'inset(100% 100% 0 0)', to: 'inset(0 0 0 0)' },\n NW: { from: 'inset(100% 0 0 100%)', to: 'inset(0 0 0 0)' }\n };\n const clip = insetByDirection[direction] || insetByDirection.E;\n return element.animate(\n [{ clipPath: clip.from }, { clipPath: clip.to }],\n { duration, easing: 'ease-out', fill: 'forwards' }\n );\n },\n\n /**\n * Apply transition based on type\n */\n apply(element, transitionConfig, isIn, regionWidth, regionHeight) {\n if (!transitionConfig || !transitionConfig.type) {\n return null;\n }\n\n const type = transitionConfig.type.toLowerCase();\n const duration = transitionConfig.duration || 1000;\n const direction = transitionConfig.direction || 'N';\n\n switch (type) {\n case 'fade':\n return isIn ? this.fadeIn(element, duration) : this.fadeOut(element, duration);\n case 'fadein':\n return isIn ? this.fadeIn(element, duration) : null;\n case 'fadeout':\n return isIn ? null : this.fadeOut(element, duration);\n case 'fly':\n return isIn\n ? this.flyIn(element, duration, direction, regionWidth, regionHeight)\n : this.flyOut(element, duration, direction, regionWidth, regionHeight);\n case 'flyin':\n return isIn ? this.flyIn(element, duration, direction, regionWidth, regionHeight) : null;\n case 'flyout':\n return isIn ? null : this.flyOut(element, duration, direction, regionWidth, regionHeight);\n case 'slide':\n return isIn\n ? this.slideIn(element, duration, direction, regionWidth, regionHeight)\n : this.slideOut(element, duration, direction, regionWidth, regionHeight);\n case 'wipe':\n // Wipe is a reveal-only effect — the outgoing layout isn't\n // animated, the incoming one \"uncovers\" itself on top.\n return isIn ? this.wipeIn(element, duration, direction) : null;\n default:\n return null;\n }\n }\n};\n\n/**\n * RendererLite - Lightweight XLF renderer\n */\nexport class RendererLite {\n /**\n * @param {Object} config - Player configuration\n * @param {string} config.cmsUrl - CMS base URL\n * @param {string} config.hardwareKey - Display hardware key\n * @param {HTMLElement} container - DOM container for rendering\n * @param {Object} options - Renderer options\n * @param {Map<string,string>} [options.fileIdToSaveAs] - Map from numeric file ID to storedAs filename (for layout backgrounds)\n * @param {Function} options.getWidgetHtml - Function to get widget HTML (layoutId, regionId, widgetId) => html\n */\n constructor(config, container, options = {}) {\n this.config = config;\n this.container = container;\n this.options = options;\n\n // Logger with configurable level\n this.log = createLogger('RendererLite', options.logLevel);\n\n // Event emitter for lifecycle hooks\n this.emitter = new EventEmitter();\n\n // State\n this.currentLayout = null;\n this.currentLayoutId = null;\n this._preloadingLayoutId = null; // Set during preload for blob URL tracking\n this._preloadingPromise = null; // Promise for in-flight preload (await instead of skip)\n this.regions = new Map(); // regionId => { element, widgets, currentIndex, timer }\n this.layoutTimer = null;\n this.layoutEndEmitted = false; // Prevents double layoutEnd on stop after timer\n this._deferredTimerLayoutId = null; // Set when timer is deferred for dynamic layouts\n this._deferredTimerFallback = null; // Safety timeout: starts layout timer if metadata never arrives\n this._paused = false;\n this._layoutTimerStartedAt = null; // Date.now() when layout timer started\n this._layoutTimerDurationMs = null; // Total layout duration in ms\n this.layoutBlobUrls = new Map(); // layoutId => Set<blobUrl> (for lifecycle tracking)\n this.audioOverlays = new Map(); // widgetId => [HTMLAudioElement] (audio overlays for widgets)\n\n // Bound methods (avoid lambda allocation per call in startRegion/_advanceRegion)\n this._stopWidgetBound = (rid, idx) => this.stopWidget(rid, idx);\n this._renderWidgetBound = (rid, idx) => this.renderWidget(rid, idx);\n\n // Scale state (for fitting layout to screen)\n this.scaleFactor = 1;\n this.offsetX = 0;\n this.offsetY = 0;\n\n // Overlay state\n this.overlayContainer = null;\n this.activeOverlays = new Map(); // layoutId => { container, layout, timer, regions }\n\n // Interactive action state\n this._keydownHandler = null; // Document keydown listener (single, shared)\n this._keyboardActions = []; // Active keyboard actions for current layout\n\n // Sub-playlist cycle state (round-robin per parentWidgetId group)\n this._subPlaylistCycleIndex = new Map();\n\n // Widget lifecycle tracking — ensures symmetric start/stop\n this._startedWidgets = new Set(); // \"regionId:widgetIndex\" keys\n\n // SMIL State Track B — runtime xp:state store. Null until the host\n // application (PWA / Electron / etc.) injects a store via\n // setStateStore(). When present, widgets with xpIf= are evaluated\n // at _showWidget time and hidden on a false result.\n this._stateStore = null;\n this._stateUnsubscribe = null;\n\n // Layout preload pool (2-layout pool for instant transitions)\n this.layoutPool = new LayoutPool(2);\n this.preloadTimer = null;\n this._preloadRetryTimer = null;\n\n // Layout-to-layout transition default (#337). Applied when the\n // incoming layout has no per-layout layoutTransitionIn override.\n // Setting type to 'instant' preserves the pre-#337 hard-cut\n // behaviour byte-for-byte.\n this.layoutTransition = this._normalizeLayoutTransition(\n options.layoutTransition\n );\n\n // Setup container styles\n this.setupContainer();\n\n // Interactive Control (XIC) event handlers\n this.emitter.on('interactiveTrigger', (data) => this._handleInteractiveTrigger(data));\n this.emitter.on('widgetExpire', (data) => this._handleWidgetExpire(data));\n this.emitter.on('widgetExtendDuration', (data) => this._handleWidgetExtendDuration(data));\n this.emitter.on('widgetSetDuration', (data) => this._handleWidgetSetDuration(data));\n\n this.log.info('Initialized');\n }\n\n /**\n * Setup container element\n */\n setupContainer() {\n this.container.style.position = 'relative';\n this.container.style.width = '100%';\n this.container.style.height = '100vh'; // Use viewport height, not percentage\n this.container.style.overflow = 'hidden';\n\n // Watch for container resize to rescale layout (debounced to avoid spam)\n this._resizeSuppressed = false;\n if (typeof ResizeObserver !== 'undefined') {\n let resizeTimer = null;\n this.resizeObserver = new ResizeObserver(() => {\n if (this._resizeSuppressed) return;\n if (resizeTimer) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => this.rescaleRegions(), 150);\n });\n this.resizeObserver.observe(this.container);\n }\n\n // Create overlay container for overlay layouts (higher z-index than main content)\n this.overlayContainer = document.createElement('div');\n this.overlayContainer.id = 'overlay-container';\n this.overlayContainer.style.position = 'absolute';\n this.overlayContainer.style.top = '0';\n this.overlayContainer.style.left = '0';\n this.overlayContainer.style.width = '100%';\n this.overlayContainer.style.height = '100%';\n this.overlayContainer.style.zIndex = '1000'; // Above main layout (z-index 0-999)\n this.overlayContainer.style.pointerEvents = 'none'; // Don't block clicks on main layout\n this.container.appendChild(this.overlayContainer);\n }\n\n /**\n * Calculate scale factor to fit layout into container\n * Centers the layout and scales regions proportionally.\n * @param {Object} layout - Parsed layout with width/height\n */\n calculateScale(layout) {\n const screenWidth = this.container.clientWidth;\n const screenHeight = this.container.clientHeight;\n\n if (!screenWidth || !screenHeight) return;\n\n const scaleX = screenWidth / layout.width;\n const scaleY = screenHeight / layout.height;\n this.scaleFactor = Math.min(scaleX, scaleY);\n this.offsetX = (screenWidth - layout.width * this.scaleFactor) / 2;\n this.offsetY = (screenHeight - layout.height * this.scaleFactor) / 2;\n\n this.log.info(`Scale: ${this.scaleFactor.toFixed(3)} (${layout.width}x${layout.height} → ${screenWidth}x${screenHeight}, offset ${Math.round(this.offsetX)},${Math.round(this.offsetY)})`);\n }\n\n /**\n * Apply scale to a region element\n * @param {HTMLElement} regionEl - Region DOM element\n * @param {Object} regionConfig - Region config with left, top, width, height\n */\n applyRegionScale(regionEl, regionConfig) {\n const sf = this.scaleFactor;\n regionEl.style.left = `${regionConfig.left * sf + this.offsetX}px`;\n regionEl.style.top = `${regionConfig.top * sf + this.offsetY}px`;\n regionEl.style.width = `${regionConfig.width * sf}px`;\n regionEl.style.height = `${regionConfig.height * sf}px`;\n }\n\n /**\n * Reapply scale to all current regions (e.g., on window resize)\n */\n rescaleRegions() {\n if (!this.currentLayout) return;\n\n this.calculateScale(this.currentLayout);\n\n for (const [regionId, region] of this.regions) {\n this.applyRegionScale(region.element, region.config);\n // Update region dimensions for transition calculations\n region.width = region.config.width * this.scaleFactor;\n region.height = region.config.height * this.scaleFactor;\n }\n\n // Rescale active overlays too\n for (const [overlayId, overlay] of this.activeOverlays) {\n this.calculateScale(overlay.layout);\n for (const [regionId, region] of overlay.regions) {\n this.applyRegionScale(region.element, region.config);\n region.width = region.config.width * this.scaleFactor;\n region.height = region.config.height * this.scaleFactor;\n }\n }\n }\n\n /**\n * Event emitter interface (like XMR wrapper)\n */\n on(event, callback) {\n this.emitter.on(event, callback);\n }\n\n emit(event, ...args) {\n this.emitter.emit(event, ...args);\n }\n\n /**\n * Parse action elements from an XLF parent element (region or media)\n * @param {Element} parentEl - Parent XML element containing <action> children\n * @returns {Array} Parsed actions\n */\n parseActions(parentEl) {\n const actions = [];\n for (const actionEl of parentEl.children) {\n if (actionEl.tagName !== 'action') continue;\n actions.push({\n id: actionEl.getAttribute('id') || '',\n actionType: actionEl.getAttribute('actionType') || '',\n triggerType: actionEl.getAttribute('triggerType') || '',\n triggerCode: actionEl.getAttribute('triggerCode') || '',\n source: actionEl.getAttribute('source') || '',\n sourceId: actionEl.getAttribute('sourceId') || '',\n target: actionEl.getAttribute('target') || '',\n targetId: actionEl.getAttribute('targetId') || '',\n widgetId: actionEl.getAttribute('widgetId') || '',\n layoutCode: actionEl.getAttribute('layoutCode') || '',\n commandCode: actionEl.getAttribute('commandCode') || ''\n });\n }\n return actions;\n }\n\n /**\n * Normalize a layout transition spec from constructor options.\n *\n * Accepts either a partial object, null, or undefined, and returns a\n * canonical shape: {type, duration, direction}. Defaults to the\n * backwards-compatible `instant` type so existing callers see no\n * behavioural change.\n *\n * @param {Object|null|undefined} spec\n * @returns {{type: string, duration: number, direction: string|undefined}}\n */\n _normalizeLayoutTransition(spec) {\n const type = spec?.type || 'instant';\n const duration = Number.isFinite(spec?.duration) ? spec.duration : 500;\n const direction = spec?.direction || undefined;\n return { type, duration, direction };\n }\n\n /**\n * Resolve the effective layout transition spec for an incoming\n * layout. Per-layout overrides (from parseXlf) beat the\n * renderer-wide default; unspecified fields fall back to the\n * default's values.\n *\n * @param {Object} incomingLayout - parsed layout object\n * @returns {{type: string, duration: number, direction: string|undefined}}\n */\n _resolveLayoutTransition(incomingLayout) {\n const layoutOverride = incomingLayout?.layoutTransitionIn;\n if (!layoutOverride || !layoutOverride.type) {\n return this.layoutTransition;\n }\n return {\n type: layoutOverride.type,\n duration: Number.isFinite(layoutOverride.duration)\n ? layoutOverride.duration\n : this.layoutTransition.duration,\n direction: layoutOverride.direction || this.layoutTransition.direction,\n };\n }\n\n /**\n * Parse XLF XML to layout object\n * @param {string} xlfXml - XLF XML content\n * @returns {Object} Parsed layout\n */\n parseXlf(xlfXml) {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xlfXml, 'text/xml');\n\n const layoutEl = doc.querySelector('layout');\n if (!layoutEl) {\n throw new Error('Invalid XLF: no <layout> element');\n }\n\n const layoutDurationAttr = layoutEl.getAttribute('duration');\n\n // Layout-to-layout transitions (#337). When present, these attributes\n // describe the visual effect to apply when this layout becomes the\n // active one. The \"In\" suffix mirrors the transIn/transOut\n // convention on <media> widgets. Absent = use the renderer's\n // configured default (or \"instant\" if no default is set).\n //\n // Supported types: instant (default, hard cut), fade, slide, wipe.\n // Direction (slide + wipe) uses the same 8-way compass as widget fly:\n // N, NE, E, SE, S, SW, W, NW.\n const layoutTransitionInType = layoutEl.getAttribute('layoutTransitionIn');\n const layoutTransitionInDurationAttr = layoutEl.getAttribute(\n 'layoutTransitionInDuration'\n );\n const layoutTransitionInDirection = layoutEl.getAttribute(\n 'layoutTransitionInDirection'\n );\n\n // Parse layout-level <tags><tag>…</tag></tags>. Used by the sync\n // bridge (roadmap #236) to read markers such as\n // `xp-sync-group:NAME` that the xiboplayer-smil-tools translator\n // emits when the source SMIL carries `xp:sync-group=\"…\"`.\n // Only direct children of <layout> are considered — nested <tag>\n // elements inside <media><actions> etc. are ignored.\n const tags = [];\n for (const child of layoutEl.children) {\n if (child.tagName !== 'tags') continue;\n for (const tagEl of child.children) {\n if (tagEl.tagName !== 'tag') continue;\n const text = (tagEl.textContent || '').trim();\n if (text) tags.push(text);\n }\n }\n\n const layout = {\n schemaVersion: parseInt(layoutEl.getAttribute('schemaVersion') || '1'),\n width: parseInt(layoutEl.getAttribute('width') || '1920'),\n height: parseInt(layoutEl.getAttribute('height') || '1080'),\n duration: layoutDurationAttr ? parseInt(layoutDurationAttr) : 0, // 0 = calculate from widgets\n bgcolor: layoutEl.getAttribute('backgroundColor') || layoutEl.getAttribute('bgcolor') || '#000000',\n background: layoutEl.getAttribute('background') || null, // Background image fileId\n enableStat: layoutEl.getAttribute('enableStat') !== '0', // absent or \"1\" = enabled\n actions: this.parseActions(layoutEl),\n tags, // Layout-level tags (e.g. \"xp-sync-group:lobby-wall\")\n layoutTransitionIn: layoutTransitionInType\n ? {\n type: layoutTransitionInType,\n duration: layoutTransitionInDurationAttr\n ? parseInt(layoutTransitionInDurationAttr)\n : undefined,\n direction: layoutTransitionInDirection || undefined,\n }\n : null,\n regions: []\n };\n\n if (layout.schemaVersion > 1) {\n this.log.debug(`XLF schema version: ${layout.schemaVersion}`);\n }\n\n if (layoutDurationAttr) {\n this.log.info(`Layout duration from XLF: ${layout.duration}s`);\n } else {\n this.log.info(`Layout duration NOT in XLF, will calculate from widgets`);\n }\n\n // Parse regions and drawers (drawers are invisible regions for interactive actions)\n const regionAndDrawerEls = layoutEl.querySelectorAll(':scope > region, :scope > drawer');\n for (const regionEl of regionAndDrawerEls) {\n const isDrawer = regionEl.tagName === 'drawer';\n const regionType = regionEl.getAttribute('type') || null;\n const region = {\n id: regionEl.getAttribute('id'),\n width: parseInt(regionEl.getAttribute('width') || '0'),\n height: parseInt(regionEl.getAttribute('height') || '0'),\n top: parseInt(regionEl.getAttribute('top') || '0'),\n left: parseInt(regionEl.getAttribute('left') || '0'),\n zindex: parseInt(regionEl.getAttribute('zindex') || (isDrawer ? '2000' : '0')),\n enableStat: regionEl.getAttribute('enableStat') !== '0',\n actions: this.parseActions(regionEl),\n exitTransition: null,\n transitionType: null, // Region-level default widget transition type\n transitionDuration: null,\n transitionDirection: null,\n loop: true, // Default: cycle widgets. Spec: loop=0 means single media stays visible\n isDrawer,\n isCanvas: regionType === 'canvas', // Canvas regions render all widgets simultaneously\n widgets: []\n };\n\n // Parse region-level options (exit transitions, loop)\n // Use direct children only to avoid matching <options> inside <media>\n const regionOptionsEl = Array.from(regionEl.children).find(el => el.tagName === 'options');\n if (regionOptionsEl) {\n const exitTransType = regionOptionsEl.querySelector('exitTransType');\n if (exitTransType && exitTransType.textContent) {\n const exitTransDuration = regionOptionsEl.querySelector('exitTransDuration');\n const exitTransDirection = regionOptionsEl.querySelector('exitTransDirection');\n region.exitTransition = {\n type: exitTransType.textContent,\n duration: parseInt((exitTransDuration && exitTransDuration.textContent) || '1000'),\n direction: (exitTransDirection && exitTransDirection.textContent) || 'N'\n };\n }\n\n // Region loop option: 0 = single media stays on screen, 1 = cycles (default)\n const loopEl = regionOptionsEl.querySelector('loop');\n if (loopEl) {\n region.loop = loopEl.textContent !== '0';\n }\n\n // Region-level default transition for widgets (applied if widget has no own transition)\n const transType = regionOptionsEl.querySelector('transitionType');\n if (transType && transType.textContent) {\n region.transitionType = transType.textContent;\n const transDuration = regionOptionsEl.querySelector('transitionDuration');\n const transDirection = regionOptionsEl.querySelector('transitionDirection');\n region.transitionDuration = parseInt((transDuration && transDuration.textContent) || '1000');\n region.transitionDirection = (transDirection && transDirection.textContent) || 'N';\n }\n }\n\n // Parse media/widgets (use direct children to avoid nested matches)\n for (const child of regionEl.children) {\n if (child.tagName !== 'media') continue;\n const widget = this.parseWidget(child);\n // SMIL State Track B — xp-state-init widgets are metadata-only.\n // Their widget options declare the initial state the layout\n // expects (xpStateInit as a string carrier, xpStateScope,\n // xpLanguage, xpDefaultDatasource). They never produce a DOM\n // element: we collect the first one per layout and let the\n // renderLayout hook materialise the store. Any subsequent\n // xp-state-init on the same layout wins last (warning logged).\n if (widget.type === 'xp-state-init') {\n if (layout.xpStateInit) {\n this.log.warn(\n `Multiple xp-state-init widgets on layout — using widget ${widget.id} (last one wins)`\n );\n }\n layout.xpStateInit = {\n widgetId: widget.id,\n rawValue: widget.options.xpStateInit ?? '',\n scope: widget.options.xpStateScope ?? 'session',\n language: widget.options.xpLanguage ?? null,\n defaultDatasource: widget.options.xpDefaultDatasource ?? null\n };\n continue; // do NOT add to render queue\n }\n region.widgets.push(widget);\n }\n\n // Auto-detect canvas from CMS \"global\" widget (CMS bundles canvas sub-widgets\n // into a single type=\"global\" media element in the XLF)\n if (!region.isCanvas && region.widgets.some(w => w.type === 'global')) {\n region.isCanvas = true;\n }\n\n layout.regions.push(region);\n\n if (isDrawer) {\n this.log.info(`Parsed drawer: id=${region.id} with ${region.widgets.length} widgets`);\n }\n\n if (region.isCanvas) {\n this.log.info(`Parsed canvas region: id=${region.id} with ${region.widgets.length} widgets (all render simultaneously)`);\n }\n }\n\n // Calculate layout duration if not specified (duration=0)\n // Uses shared parseLayoutDuration() — single source of truth for XLF-based duration calc\n if (layout.duration === 0) {\n const { duration, isDynamic } = parseLayoutDuration(xlfXml);\n layout.duration = duration;\n layout.isDynamic = isDynamic;\n this.log.info(`Calculated layout duration: ${layout.duration}s (not specified in XLF)${isDynamic ? ' [dynamic — has useDuration=0 video]' : ''}`);\n }\n\n return layout;\n }\n\n /**\n * Parse widget from media element\n * @param {Element} mediaEl - Media XML element\n * @returns {Object} Widget config\n */\n parseWidget(mediaEl) {\n const type = mediaEl.getAttribute('type');\n const duration = parseInt(mediaEl.getAttribute('duration') || '10');\n const useDuration = parseInt(mediaEl.getAttribute('useDuration') || '1');\n const id = mediaEl.getAttribute('id');\n const fileId = mediaEl.getAttribute('fileId'); // Media library file ID\n\n // Parse options\n const options = {};\n const optionsEl = mediaEl.querySelector('options');\n if (optionsEl) {\n for (const child of optionsEl.children) {\n options[child.tagName] = child.textContent;\n }\n }\n\n // Parse raw content\n const rawEl = mediaEl.querySelector('raw');\n const raw = rawEl ? rawEl.textContent : '';\n\n // Parse transitions\n const transitions = {\n in: null,\n out: null\n };\n\n if (options.transIn) {\n transitions.in = {\n type: options.transIn,\n duration: parseInt(options.transInDuration || '1000'),\n direction: options.transInDirection || 'N'\n };\n }\n\n if (options.transOut) {\n transitions.out = {\n type: options.transOut,\n duration: parseInt(options.transOutDuration || '1000'),\n direction: options.transOutDirection || 'N'\n };\n }\n\n // Parse widget-level actions\n const actions = this.parseActions(mediaEl);\n\n // Parse audio overlay nodes (<audio> child elements on the widget)\n // Spec format: <audio><uri volume=\"\" loop=\"\" mediaId=\"\">filename.mp3</uri></audio>\n // Also supports flat format: <audio mediaId=\"\" uri=\"\" volume=\"\" loop=\"\">\n const audioNodes = [];\n for (const child of mediaEl.children) {\n if (child.tagName.toLowerCase() === 'audio') {\n const uriEl = child.querySelector('uri');\n if (uriEl) {\n // Spec format: attributes on <uri>, filename as text content\n audioNodes.push({\n mediaId: uriEl.getAttribute('mediaId') || null,\n uri: uriEl.textContent || '',\n volume: parseInt(uriEl.getAttribute('volume') || '100'),\n loop: uriEl.getAttribute('loop') === '1'\n });\n } else {\n // Flat format fallback: attributes directly on <audio>\n audioNodes.push({\n mediaId: child.getAttribute('mediaId') || null,\n uri: child.getAttribute('uri') || '',\n volume: parseInt(child.getAttribute('volume') || '100'),\n loop: child.getAttribute('loop') === '1'\n });\n }\n }\n }\n\n // Parse commands on media (shell/native commands triggered on widget start)\n // Spec: <commands><command commandCode=\"code\" commandString=\"args\"/></commands>\n const commands = [];\n const commandsEl = Array.from(mediaEl.children).find(el => el.tagName === 'commands');\n if (commandsEl) {\n for (const cmdEl of commandsEl.children) {\n if (cmdEl.tagName === 'command') {\n commands.push({\n commandCode: cmdEl.getAttribute('commandCode') || '',\n commandString: cmdEl.getAttribute('commandString') || ''\n });\n }\n }\n }\n\n // Sub-playlist attributes (widgets grouped by parentWidgetId)\n const parentWidgetId = mediaEl.getAttribute('parentWidgetId') || null;\n const displayOrder = parseInt(mediaEl.getAttribute('displayOrder') || '0');\n const cyclePlayback = mediaEl.getAttribute('cyclePlayback') === '1';\n const playCount = parseInt(mediaEl.getAttribute('playCount') || '0');\n const isRandom = mediaEl.getAttribute('isRandom') === '1';\n\n // Media expiry dates (per-widget time-gating within a layout)\n const fromDt = mediaEl.getAttribute('fromDt') || mediaEl.getAttribute('fromdt') || null;\n const toDt = mediaEl.getAttribute('toDt') || mediaEl.getAttribute('todt') || null;\n\n // Render mode: 'native' (player renders directly) or 'html' (use GetResource)\n const render = mediaEl.getAttribute('render') || null;\n\n // SMIL State Track B pass-through fields (plan 240/242).\n //\n // The CMS custom-module ships xp:* as widget *options* —\n // <options>\n // <option name=\"xpIf\">a = 1</option>\n // …\n // </options>\n // — which is how round-tripping through PUT /widget/:id stays\n // byte-identical (see xibo-players/xibo-cms-private#1). Earlier\n // prototypes put these on the <media> element as attributes;\n // we still fall back to attributes so hand-rolled test XLFs and\n // legacy translator output keep working.\n //\n // xpIf is the runtime guard expression — evaluated at show time\n // against the injected XpStateStore. xpDayPart / xpDatasource /\n // xpJsonpath / xpMatch / xpBegin / xpEnd are captured for\n // completeness (same-shape gates, handled by other subsystems).\n // See xp-translation-matrix.md.\n const xpIf = options.xpIf ?? mediaEl.getAttribute('xpIf') ?? null;\n const xpDayPart = options.xpDayPart ?? mediaEl.getAttribute('xpDayPart') ?? null;\n const xpDatasource = options.xpDatasource ?? mediaEl.getAttribute('xpDatasource') ?? null;\n const xpJsonpath = options.xpJsonpath ?? mediaEl.getAttribute('xpJsonpath') ?? null;\n const xpMatch = options.xpMatch ?? mediaEl.getAttribute('xpMatch') ?? null;\n const xpBegin = options.xpBegin ?? mediaEl.getAttribute('xpBegin') ?? null;\n const xpEnd = options.xpEnd ?? mediaEl.getAttribute('xpEnd') ?? null;\n\n return {\n type,\n duration,\n useDuration, // Whether to use specified duration (1) or media length (0)\n id,\n fileId, // Media library file ID for cache lookup\n render, // 'native' or 'html' — null means use type-based dispatch\n fromDt, // Widget valid-from date (Y-m-d H:i:s)\n toDt, // Widget valid-to date (Y-m-d H:i:s)\n enableStat: mediaEl.getAttribute('enableStat') !== '0', // absent or \"1\" = enabled\n webhookUrl: options.webhookUrl || null,\n options,\n raw,\n transitions,\n actions,\n audioNodes, // Audio overlays attached to this widget\n commands, // Shell commands triggered on widget start\n parentWidgetId,\n displayOrder,\n cyclePlayback,\n playCount,\n isRandom,\n // SMIL State Track B — runtime gating attributes (read from\n // widget <options> preferentially, with attribute fallback)\n xpIf,\n xpDayPart,\n xpDatasource,\n xpJsonpath,\n xpMatch,\n xpBegin,\n xpEnd\n };\n }\n\n /**\n * Track blob URL for lifecycle management\n * @param {string} blobUrl - Blob URL to track\n */\n trackBlobUrl(blobUrl) {\n const layoutId = this._preloadingLayoutId || this.currentLayoutId || 0;\n\n if (!layoutId) {\n this.log.warn('trackBlobUrl called without currentLayoutId, tracking under key 0');\n }\n\n if (!this.layoutBlobUrls.has(layoutId)) {\n this.layoutBlobUrls.set(layoutId, new Set());\n }\n\n this.layoutBlobUrls.get(layoutId).add(blobUrl);\n }\n\n /**\n * Revoke all blob URLs for a specific layout\n * @param {number} layoutId - Layout ID\n */\n revokeBlobUrlsForLayout(layoutId) {\n const blobUrls = this.layoutBlobUrls.get(layoutId);\n if (blobUrls) {\n blobUrls.forEach(url => {\n URL.revokeObjectURL(url);\n });\n this.layoutBlobUrls.delete(layoutId);\n this.log.info(`Revoked ${blobUrls.size} blob URLs for layout ${layoutId}`);\n }\n }\n\n /**\n * Update layout duration based on actual widget durations\n * Called when video metadata loads and we discover actual duration\n */\n updateLayoutDuration() {\n if (!this.currentLayout) return;\n\n // Calculate maximum region duration\n let maxRegionDuration = 0;\n\n for (const region of this.currentLayout.regions) {\n if (region.isDrawer) continue;\n let regionDuration = 0;\n\n for (const widget of region.widgets) {\n if (widget.duration > 0) {\n regionDuration += widget.duration;\n }\n }\n\n maxRegionDuration = Math.max(maxRegionDuration, regionDuration);\n }\n\n // Update layout duration if recalculated value differs.\n // Both upgrades (video metadata revealing longer duration) and downgrades\n // (DURATION comment correcting an overestimate) are legitimate.\n if (maxRegionDuration > 0 && maxRegionDuration !== this.currentLayout.duration) {\n const oldDuration = this.currentLayout.duration;\n this.currentLayout.duration = maxRegionDuration;\n this.currentLayout._durationFromMetadata = true;\n\n this.log.info(`Layout duration updated: ${oldDuration}s → ${maxRegionDuration}s (based on video metadata)`);\n const final_ = !this._hasUnprobedVideos();\n this.emit('layoutDurationUpdated', this.currentLayoutId, maxRegionDuration, final_);\n\n // Deferred timer: video metadata arrived, start the timer now\n if (this._deferredTimerLayoutId === this.currentLayoutId && !this.layoutTimer) {\n if (this._hasUnprobedVideos()) {\n this.log.info(`Layout duration updated to ${maxRegionDuration}s but still has unprobed videos — keeping timer deferred`);\n } else {\n // Cancel safety fallback — metadata arrived in time\n if (this._deferredTimerFallback) {\n clearTimeout(this._deferredTimerFallback);\n this._deferredTimerFallback = null;\n }\n const elapsed = Date.now() - (this._layoutTimerStartedAt || Date.now());\n const remainingMs = Math.max(1000, maxRegionDuration * 1000 - elapsed);\n this._deferredTimerLayoutId = null;\n this._layoutTimerDurationMs = remainingMs;\n this.layoutTimer = setTimeout(() => {\n this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`);\n if (this.currentLayoutId) {\n this.layoutEndEmitted = true;\n this.emit('layoutEnd', this.currentLayoutId);\n }\n }, remainingMs);\n this.log.info(`All video durations resolved — deferred timer started: ${(remainingMs / 1000).toFixed(1)}s remaining (waited ${(elapsed / 1000).toFixed(1)}s for metadata)`);\n }\n } else if (this.layoutTimer) {\n // Reset layout timer with REMAINING time — not full duration.\n clearTimeout(this.layoutTimer);\n\n const elapsed = Date.now() - (this._layoutTimerStartedAt || Date.now());\n const remainingMs = Math.max(1000, this.currentLayout.duration * 1000 - elapsed);\n this.layoutTimer = setTimeout(() => {\n this.log.info(`Layout ${this.currentLayoutId} duration expired (${this.currentLayout.duration}s)`);\n if (this.currentLayoutId) {\n this.layoutEndEmitted = true;\n this.emit('layoutEnd', this.currentLayoutId);\n }\n }, remainingMs);\n\n this.log.info(`Layout timer adjusted to ${(remainingMs / 1000).toFixed(1)}s remaining (elapsed ${(elapsed / 1000).toFixed(1)}s of ${this.currentLayout.duration}s)`);\n } else {\n this.log.info(`Layout duration updated to ${maxRegionDuration}s (timer not yet started, will use new value)`);\n }\n\n // Reschedule preload timer — the initial preload was based on the old\n // duration estimate (e.g. 45s for 60s default). With the real duration\n // (e.g. 375s), the preload should fire much later so that schedule\n // cooldowns (maxPlaysPerHour) have time to expire.\n this._scheduleNextLayoutPreload(this.currentLayout);\n }\n }\n\n // ── SMIL State Track B ──────────────────────────────────────────────\n\n /**\n * Inject an XpStateStore for runtime `xpIf=` evaluation. The store\n * backs `<setvalue>` / `<newvalue>` / `<delvalue>` in SMIL State and\n * persists per its configured scope (document/session/display).\n *\n * Wiring is idempotent — calling twice swaps stores and unsubscribes\n * the previous change listener. Pass `null` to disable runtime\n * evaluation (widgets with `xpIf=` then show unconditionally, matching\n * pre-Track-B behaviour).\n *\n * @param {object|null} store - instance of @xiboplayer/expr.XpStateStore\n * (or any duck-typed object exposing `get/has/on('change', …)`)\n */\n setStateStore(store) {\n if (this._stateUnsubscribe) {\n try { this._stateUnsubscribe(); } catch (_err) { /* best effort */ }\n this._stateUnsubscribe = null;\n }\n this._stateStore = store || null;\n if (this._stateStore && typeof this._stateStore.on === 'function') {\n // Re-evaluate xp:if on every state change. The handler is\n // deliberately broad — the renderer walks its current widgets\n // and toggles visibility. More fine-grained subscriptions\n // (change:<key>) can land in a future pass.\n this._stateUnsubscribe = this._stateStore.on('change', () => {\n this.reevaluateXpIf();\n });\n }\n }\n\n /**\n * Current state store, or null if none injected.\n * @returns {object|null}\n */\n getStateStore() {\n return this._stateStore;\n }\n\n /**\n * Evaluate a widget's `xpIf=` attribute against the current store.\n * Returns true when the widget should be shown. Absent xpIf is\n * treated as true (no runtime gating). An evaluation error\n * (ExprOutOfScope) hides the widget — the safe default prevents\n * broken expressions from leaking content that was meant to be\n * conditionally suppressed.\n *\n * @param {object} widget - parsed widget (must carry `.xpIf`)\n * @returns {boolean}\n */\n _evaluateXpIf(widget) {\n if (!widget || !widget.xpIf) return true;\n if (!this._stateStore) return true; // no store → no runtime gating\n try {\n const v = evalExpr(widget.xpIf, this._stateStore);\n return asBool(v);\n } catch (err) {\n if (err instanceof ExprOutOfScope) {\n this.log.warn(`xpIf evaluation failed (widget ${widget.id}): ${err.message} — hiding widget`);\n } else {\n this.log.error(`xpIf unexpected error (widget ${widget.id}):`, err);\n }\n return false;\n }\n }\n\n /**\n * Re-evaluate every live widget's xpIf against the current store and\n * toggle DOM visibility. Called automatically on store `change`\n * events; host code can also trigger it manually after batch updates.\n */\n reevaluateXpIf() {\n if (!this._stateStore) return;\n for (const region of this.regions.values()) {\n if (!region || !region.widgets) continue;\n for (const widget of region.widgets) {\n if (!widget.xpIf) continue;\n const el = region.widgetElements?.get(widget.id);\n if (!el) continue;\n const visible = this._evaluateXpIf(widget);\n // Use a data-attribute so tests + downstream code can observe\n // without parsing CSS. The inline style toggle is what actually\n // hides the widget; transitions are intentionally skipped for\n // re-evaluations (no animation spam on state churn).\n el.dataset.xpIf = visible ? 'true' : 'false';\n if (visible) {\n // Only un-hide the currently-active widget; do not resurrect\n // background widgets hidden by region cycling. We detect the\n // active widget by presence of `visibility: visible`.\n if (el.style.visibility !== 'hidden' || el.dataset.xpIfActive === '1') {\n el.style.visibility = 'visible';\n el.style.opacity = '1';\n }\n } else {\n el.style.visibility = 'hidden';\n el.style.opacity = '0';\n }\n }\n }\n this.emit('xpIfReevaluated');\n }\n\n /**\n * Materialise an XpStateStore from an xp-state-init widget (plan 242).\n *\n * xp-state-init widgets on a layout are metadata-only — they declare\n * the initial state, scope, language, and default datasource that\n * downstream xpIf / AVT / datasource bindings evaluate against. This\n * method:\n *\n * 1. Decodes the `xpStateInit` widget option via parseXpStateInit\n * (handles zstd+b64 / gzip+b64 / plain JSON carriers).\n * 2. Seeds `lang` from the `xpLanguage` option when present (so\n * `smil-language()` resolves correctly on the first render).\n * 3. Constructs an XpStateStore with the declared scope.\n * 4. Injects it via setStateStore() (which unsubscribes any prior\n * store and re-subscribes change listeners).\n *\n * If the layout has no xp-state-init widget or decoding fails, we\n * leave any previously-injected store in place — a host application\n * may have injected a store earlier and we must not clobber it.\n *\n * @param {object} layout - parsed layout (from parseXlf)\n * @returns {XpStateStore|null} the store that was injected, or null\n * if no xp-state-init was present / decoding failed\n */\n _applyXpStateInit(layout) {\n if (!layout || !layout.xpStateInit) return null;\n\n const init = layout.xpStateInit;\n if (!init.rawValue) {\n this.log.warn(\n `xp-state-init widget ${init.widgetId} has empty xpStateInit option — skipping`\n );\n return null;\n }\n\n let initialState;\n try {\n initialState = parseXpStateInit(init.rawValue);\n } catch (err) {\n this.log.error(\n `xp-state-init widget ${init.widgetId} decode failed: ${err.message} — keeping existing store`\n );\n return null;\n }\n\n // Language seed — xp:language ends up in the store under the\n // `lang` key so evalExpr's `smil-language()` built-in resolves\n // without a separate plumbing channel.\n if (init.language && typeof initialState === 'object' && !('lang' in initialState)) {\n initialState.lang = init.language;\n }\n\n const scope = init.scope || 'session';\n let store;\n try {\n store = new XpStateStore({ scope, initialState });\n } catch (err) {\n this.log.error(\n `xp-state-init widget ${init.widgetId}: XpStateStore construction failed: ${err.message}`\n );\n return null;\n }\n\n this.setStateStore(store);\n this.log.info(\n `xp-state-init applied: widget=${init.widgetId} scope=${scope} keys=${Object.keys(initialState).length}`\n );\n return store;\n }\n\n // ── Interactive Actions ──────────────────────────────────────────────\n\n /**\n * Attach interactive action event listeners for a layout.\n * Binds touch/click on region/widget elements and a single document keydown handler.\n */\n attachActionListeners(layout) {\n const allKeyboardActions = [];\n let touchActionCount = 0;\n\n // Layout-level actions (attached to the main container)\n for (const action of (layout.actions || [])) {\n if (action.triggerType === 'touch') {\n this.attachTouchAction(this.container, action, null, null);\n touchActionCount++;\n } else if (action.triggerType?.startsWith('keyboard:')) {\n allKeyboardActions.push(action);\n }\n }\n\n for (const regionConfig of layout.regions) {\n const region = this.regions.get(regionConfig.id);\n if (!region) continue;\n\n // Region-level actions\n for (const action of (regionConfig.actions || [])) {\n if (action.triggerType === 'touch') {\n this.attachTouchAction(region.element, action, regionConfig.id, null);\n touchActionCount++;\n } else if (action.triggerType.startsWith('keyboard:')) {\n allKeyboardActions.push(action);\n }\n }\n\n // Widget-level actions\n for (const widget of regionConfig.widgets) {\n if (!widget.actions || widget.actions.length === 0) continue;\n const widgetEl = region.widgetElements.get(widget.id);\n if (!widgetEl) continue;\n\n for (const action of widget.actions) {\n if (action.triggerType === 'touch') {\n this.attachTouchAction(widgetEl, action, regionConfig.id, widget.id);\n touchActionCount++;\n } else if (action.triggerType.startsWith('keyboard:')) {\n allKeyboardActions.push(action);\n }\n }\n }\n }\n\n this.setupKeyboardListener(allKeyboardActions);\n\n if (touchActionCount > 0 || allKeyboardActions.length > 0) {\n this.log.info(`Actions attached: ${touchActionCount} touch, ${allKeyboardActions.length} keyboard`);\n }\n }\n\n /**\n * Attach a click listener to an element for a touch-triggered action.\n */\n attachTouchAction(element, action, regionId, widgetId) {\n element.style.cursor = 'pointer';\n\n const handler = (event) => {\n event.stopPropagation();\n const source = widgetId ? `widget ${widgetId}` : `region ${regionId}`;\n this.log.info(`Touch action fired on ${source}: ${action.actionType}`);\n\n this.emit('action-trigger', {\n actionType: action.actionType,\n triggerType: 'touch',\n triggerCode: action.triggerCode,\n layoutCode: action.layoutCode,\n targetId: action.targetId,\n commandCode: action.commandCode,\n source: { regionId, widgetId }\n });\n };\n\n element.addEventListener('click', handler);\n if (!element._actionHandlers) element._actionHandlers = [];\n element._actionHandlers.push(handler);\n }\n\n /**\n * Setup document-level keyboard listener for keyboard-triggered actions.\n */\n setupKeyboardListener(keyboardActions) {\n this.removeKeyboardListener();\n this._keyboardActions = keyboardActions;\n if (keyboardActions.length === 0) return;\n\n this._keydownHandler = (event) => {\n const pressedKey = event.key;\n for (const action of this._keyboardActions) {\n const keycode = action.triggerType.substring('keyboard:'.length);\n if (pressedKey === keycode) {\n this.log.info(`Keyboard action (key: ${pressedKey}): ${action.actionType}`);\n this.emit('action-trigger', {\n actionType: action.actionType,\n triggerType: action.triggerType,\n triggerCode: action.triggerCode,\n layoutCode: action.layoutCode,\n targetId: action.targetId,\n commandCode: action.commandCode,\n source: { key: pressedKey }\n });\n break;\n }\n }\n };\n\n document.addEventListener('keydown', this._keydownHandler);\n }\n\n /** Remove the document-level keyboard listener */\n removeKeyboardListener() {\n if (this._keydownHandler) {\n document.removeEventListener('keydown', this._keydownHandler);\n this._keydownHandler = null;\n }\n this._keyboardActions = [];\n }\n\n /** Remove all action listeners (touch + keyboard) */\n removeActionListeners() {\n for (const [, region] of this.regions) {\n this._cleanElementActionHandlers(region.element);\n for (const [, widgetEl] of region.widgetElements) {\n this._cleanElementActionHandlers(widgetEl);\n }\n }\n this.removeKeyboardListener();\n }\n\n _cleanElementActionHandlers(element) {\n if (element._actionHandlers) {\n for (const handler of element._actionHandlers) {\n element.removeEventListener('click', handler);\n }\n delete element._actionHandlers;\n element.style.cursor = '';\n }\n }\n\n // ── Interactive Control (XIC) ─────────────────────────────────────\n\n /**\n * Find a region containing a widget by widget ID.\n * Searches main regions first, then overlay regions.\n * @param {string} widgetId\n * @returns {{ regionId: string, region: Object, widget: Object, widgetIndex: number, regionMap: Map }|null}\n */\n _findRegionByWidgetId(widgetId) {\n // Search main regions\n for (const [regionId, region] of this.regions) {\n const widgetIndex = region.widgets.findIndex(w => w.id === widgetId);\n if (widgetIndex !== -1) {\n return { regionId, region, widget: region.widgets[widgetIndex], widgetIndex, regionMap: this.regions };\n }\n }\n // Search overlay regions\n for (const overlay of this.activeOverlays.values()) {\n if (!overlay.regions) continue;\n for (const [regionId, region] of overlay.regions) {\n const widgetIndex = region.widgets.findIndex(w => w.id === widgetId);\n if (widgetIndex !== -1) {\n return { regionId, region, widget: region.widgets[widgetIndex], widgetIndex, regionMap: overlay.regions };\n }\n }\n }\n return null;\n }\n\n /**\n * Advance a region to its next widget using the standard cycle.\n * @param {string} regionId\n * @param {Map} regionMap - The Map containing this region (main or overlay)\n */\n _advanceRegion(regionId, regionMap) {\n const region = regionMap.get(regionId);\n if (!region) return;\n region.currentIndex = (region.currentIndex + 1) % region.widgets.length;\n const isMain = regionMap === this.regions;\n this._startRegionCycle(\n region, regionId,\n isMain ? this._renderWidgetBound : this._renderWidgetBound,\n isMain ? this._stopWidgetBound : this._stopWidgetBound,\n isMain ? () => this.checkLayoutComplete() : undefined\n );\n }\n\n /**\n * Handle interactiveTrigger XIC event — navigate to a target widget.\n * @param {{ targetId: string, triggerCode: string }} data\n */\n _handleInteractiveTrigger({ targetId, triggerCode }) {\n this.log.info(`XIC interactiveTrigger: target=${targetId} code=${triggerCode}`);\n const found = this._findRegionByWidgetId(targetId);\n if (found) {\n this.navigateToWidget(targetId);\n } else {\n this.log.warn(`XIC interactiveTrigger: widget ${targetId} not found`);\n }\n }\n\n /**\n * Handle widgetExpire XIC event — immediately expire a widget and advance.\n * @param {{ widgetId: string }} data\n */\n _handleWidgetExpire({ widgetId }) {\n const found = this._findRegionByWidgetId(widgetId);\n if (!found) {\n this.log.warn(`XIC widgetExpire: widget ${widgetId} not found`);\n return;\n }\n const { regionId, region, widgetIndex, regionMap } = found;\n this.log.info(`XIC widgetExpire: widget=${widgetId} region=${regionId}`);\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n this.stopWidget(regionId, widgetIndex);\n this._advanceRegion(regionId, regionMap);\n }\n\n /**\n * Handle widgetExtendDuration XIC event — extend the current widget timer.\n * @param {{ widgetId: string, duration: number }} data - duration in seconds (added to remaining)\n */\n _handleWidgetExtendDuration({ widgetId, duration }) {\n const found = this._findRegionByWidgetId(widgetId);\n if (!found) {\n this.log.warn(`XIC widgetExtendDuration: widget ${widgetId} not found`);\n return;\n }\n const { regionId, region } = found;\n this.log.info(`XIC widgetExtendDuration: widget=${widgetId} +${duration}s`);\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n // Re-arm timer with the extended duration\n region.timer = setTimeout(() => {\n this.stopWidget(regionId, region.currentIndex);\n this._advanceRegion(regionId, found.regionMap);\n }, duration * 1000);\n }\n\n /**\n * Handle widgetSetDuration XIC event — replace the widget timer with an absolute duration.\n * @param {{ widgetId: string, duration: number }} data - duration in seconds (absolute)\n */\n _handleWidgetSetDuration({ widgetId, duration }) {\n const found = this._findRegionByWidgetId(widgetId);\n if (!found) {\n this.log.warn(`XIC widgetSetDuration: widget ${widgetId} not found`);\n return;\n }\n const { regionId, region } = found;\n this.log.info(`XIC widgetSetDuration: widget=${widgetId} ${duration}s`);\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n // Set timer with the absolute duration\n region.timer = setTimeout(() => {\n this.stopWidget(regionId, region.currentIndex);\n this._advanceRegion(regionId, found.regionMap);\n }, duration * 1000);\n }\n\n /**\n * Navigate to a specific widget within a region (for navWidget actions)\n */\n navigateToWidget(targetWidgetId) {\n for (const [regionId, region] of this.regions) {\n const widgetIndex = region.widgets.findIndex(w => w.id === targetWidgetId);\n if (widgetIndex === -1) continue;\n\n this.log.info(`Navigating to widget ${targetWidgetId} in region ${regionId} (index ${widgetIndex})`);\n\n // Show drawer region if hidden (drawers start display:none)\n if (region.isDrawer && region.element.style.display === 'none') {\n region.element.style.display = '';\n this.log.info(`Drawer region ${regionId} revealed`);\n }\n\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n\n this.stopWidget(regionId, region.currentIndex);\n region.currentIndex = widgetIndex;\n this.renderWidget(regionId, widgetIndex);\n\n if (region.widgets.length > 1) {\n const widget = region.widgets[widgetIndex];\n const duration = widget.duration * 1000;\n region.timer = setTimeout(() => {\n this.stopWidget(regionId, widgetIndex);\n const nextIndex = (widgetIndex + 1) % region.widgets.length;\n region.currentIndex = nextIndex;\n // For drawers, hide again after last widget; for normal regions, continue cycling\n if (region.isDrawer && nextIndex === 0) {\n region.element.style.display = 'none';\n this.log.info(`Drawer region ${regionId} hidden (cycle complete)`);\n } else if (region.isDrawer) {\n // Continue cycling through remaining drawer widgets (will hide on wrap to 0)\n this.navigateToWidget(region.widgets[nextIndex].id);\n } else {\n this.startRegion(regionId);\n }\n }, duration);\n } else if (region.isDrawer) {\n // Single-widget drawer: hide after widget duration\n const widget = region.widgets[widgetIndex];\n const duration = widget.duration * 1000;\n region.timer = setTimeout(() => {\n this.stopWidget(regionId, widgetIndex);\n region.element.style.display = 'none';\n this.log.info(`Drawer region ${regionId} hidden (single widget done)`);\n }, duration);\n }\n return;\n }\n this.log.warn(`Target widget ${targetWidgetId} not found in any region`);\n }\n\n /**\n * Navigate to the next widget in a region (wraps around)\n * @param {string} [regionId] - Target region. If omitted, uses the first region.\n */\n nextWidget(regionId) {\n const region = regionId ? this.regions.get(regionId) : this.regions.values().next().value;\n if (!region || region.widgets.length <= 1) return;\n\n const nextIndex = (region.currentIndex + 1) % region.widgets.length;\n const targetWidget = region.widgets[nextIndex];\n this.log.info(`nextWidget → index ${nextIndex} (widget ${targetWidget.id})`);\n this.navigateToWidget(targetWidget.id);\n }\n\n /**\n * Navigate to the previous widget in a region (wraps around)\n * @param {string} [regionId] - Target region. If omitted, uses the first region.\n */\n previousWidget(regionId) {\n const region = regionId ? this.regions.get(regionId) : this.regions.values().next().value;\n if (!region || region.widgets.length <= 1) return;\n\n const prevIndex = (region.currentIndex - 1 + region.widgets.length) % region.widgets.length;\n const targetWidget = region.widgets[prevIndex];\n this.log.info(`previousWidget → index ${prevIndex} (widget ${targetWidget.id})`);\n this.navigateToWidget(targetWidget.id);\n }\n\n // ── Layout Helpers ───────────────────────────────────────────────\n\n /**\n * Get media file URL for storedAs filename.\n * @param {string} storedAs - The storedAs filename (e.g. \"42_abc123.jpg\")\n * @returns {string} Full URL for the media file\n */\n _mediaFileUrl(storedAs) {\n return `${window.location.origin}${PLAYER_API}/media/file/${storedAs}`;\n }\n\n /**\n * Position a widget element to fill its region (hidden by default).\n * @param {HTMLElement} element\n */\n _positionWidgetElement(element) {\n Object.assign(element.style, {\n position: 'absolute',\n top: '0',\n left: '0',\n width: '100%',\n height: '100%',\n visibility: 'hidden',\n opacity: '0',\n });\n }\n\n /**\n * Apply a background image with cover styling.\n * @param {HTMLElement} element\n * @param {string} url - Image URL\n */\n _applyBackgroundImage(element, url) {\n Object.assign(element.style, {\n backgroundImage: `url(${url})`,\n backgroundSize: 'cover',\n backgroundPosition: 'center',\n backgroundRepeat: 'no-repeat',\n });\n }\n\n /**\n * Clear all region timers in a region map.\n * @param {Map} regions - Region map (regionId → region)\n */\n _clearRegionTimers(regions) {\n for (const [, region] of regions) {\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n }\n }\n\n // ── Layout Rendering ──────────────────────────────────────────────\n\n /**\n * Render a layout\n * @param {string} xlfXml - XLF XML content\n * @param {number} layoutId - Layout ID\n * @returns {Promise<void>}\n */\n async renderLayout(xlfXml, layoutId) {\n try {\n this.log.info(`Rendering layout ${layoutId}`);\n\n // Check if we're replaying the same layout\n const isSameLayout = this.currentLayoutId === layoutId;\n\n if (isSameLayout) {\n // OPTIMIZATION: Reuse existing elements for same layout (Arexibo pattern)\n this.log.info(`Replaying layout ${layoutId} - reusing elements (no recreation!)`);\n\n // Stop all region timers and widgets, then reset to first widget\n this._clearRegionTimers(this.regions);\n this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);\n for (const [, region] of this.regions) {\n region.currentIndex = 0;\n region.complete = false;\n }\n\n // Clear layout timer\n if (this.layoutTimer) {\n clearTimeout(this.layoutTimer);\n this.layoutTimer = null;\n }\n\n this.layoutEndEmitted = false;\n this._deferredTimerLayoutId = null;\n if (this._deferredTimerFallback) {\n clearTimeout(this._deferredTimerFallback);\n this._deferredTimerFallback = null;\n }\n\n // DON'T call stopCurrentLayout() - keep elements alive!\n // DON'T recreate regions/elements - already exist!\n\n // Emit layout start event\n this.emit('layoutStart', layoutId, this.currentLayout);\n\n // Restart all regions from widget 0 (except drawers)\n for (const [regionId, region] of this.regions) {\n if (region.isDrawer) continue;\n this.startRegion(regionId);\n }\n\n // Wait for all initial widgets to be ready then start layout timer\n this.startLayoutTimerWhenReady(layoutId, this.currentLayout);\n\n this.log.info(`Layout ${layoutId} restarted (reused elements)`);\n\n // Schedule next layout preload for same-layout replay\n this._scheduleNextLayoutPreload(this.currentLayout);\n\n return; // EARLY RETURN - skip recreation below\n }\n\n // Check if this layout was preloaded in the pool\n if (this.layoutPool.has(layoutId)) {\n this.log.info(`Layout ${layoutId} found in preload pool - instant swap!`);\n await this._swapToPreloadedLayout(layoutId);\n return; // EARLY RETURN - preloaded layout swapped in\n }\n\n // Different layout - full teardown and rebuild\n this.log.info(`Switching to new layout ${layoutId}`);\n this.stopCurrentLayout();\n\n // Parse XLF\n const layout = this.parseXlf(xlfXml);\n this.currentLayout = layout;\n this.currentLayoutId = layoutId;\n\n // SMIL State Track B — if the layout carries an xp-state-init\n // widget (metadata-only, skipped from the render queue), decode\n // its payload and materialise an XpStateStore before any widget\n // is shown (so the first xpIf evaluation sees the seeded state).\n this._applyXpStateInit(layout);\n\n // Calculate scale factor to fit layout into screen\n this.calculateScale(layout);\n\n // Set container background\n this.container.style.backgroundColor = layout.bgcolor;\n this.container.style.backgroundImage = ''; // Reset previous\n\n // Apply background image if specified in XLF\n // With storedAs refactor, background may be a filename (e.g. \"43.png\") or a numeric fileId\n if (layout.background) {\n const saveAs = this.options.fileIdToSaveAs?.get(String(layout.background)) || layout.background;\n this._applyBackgroundImage(this.container, this._mediaFileUrl(saveAs));\n this.log.info(`Background image set: ${layout.background} → ${saveAs}`);\n }\n\n // Create regions\n for (const regionConfig of layout.regions) {\n await this.createRegion(regionConfig);\n }\n\n // PRE-CREATE: Build all widget elements upfront (Arexibo pattern)\n this.log.info('Pre-creating widget elements for instant transitions...');\n for (const [regionId, region] of this.regions) {\n for (let i = 0; i < region.widgets.length; i++) {\n const widget = region.widgets[i];\n widget.layoutId = this.currentLayoutId;\n widget.regionId = regionId;\n\n try {\n const element = await this.createWidgetElement(widget, region);\n this._positionWidgetElement(element);\n region.element.appendChild(element);\n region.widgetElements.set(widget.id, element);\n } catch (error) {\n this.log.error(`Failed to pre-create widget ${widget.id}:`, error);\n }\n }\n }\n this.log.info('All widget elements pre-created');\n\n // Attach interactive action listeners (touch/click and keyboard)\n this.attachActionListeners(layout);\n\n // Emit layout start event\n this.emit('layoutStart', layoutId, layout);\n\n // Report calculated duration so the schedule queue/timeline uses it\n // instead of the 60s default. For layouts with unprobed videos, this\n // is an estimate that will be corrected by updateLayoutDuration().\n if (layout.duration > 0) {\n const final_ = !this._hasUnprobedVideos();\n this.emit('layoutDurationUpdated', layoutId, layout.duration, final_);\n }\n\n // Start all regions (except drawers — they're action-triggered)\n for (const [regionId, region] of this.regions) {\n if (region.isDrawer) continue;\n this.startRegion(regionId);\n }\n\n // Wait for all initial widgets to be ready (videos playing, images loaded)\n // THEN start the layout timer — ensures videos play to their last frame\n this.startLayoutTimerWhenReady(layoutId, layout);\n\n // Schedule preloading of the next layout at 75% of current duration\n this._scheduleNextLayoutPreload(layout);\n\n this.log.info(`Layout ${layoutId} started`);\n\n } catch (error) {\n this.log.error('Error rendering layout:', error);\n this.emit('error', { type: 'layoutError', error, layoutId });\n throw error;\n }\n }\n\n /**\n * Build a region DOM element and state entry.\n * Shared by createRegion, preloadLayout, and renderOverlay.\n *\n * @param {Object} regionConfig - Region configuration from parsed XLF\n * @param {string} elementId - DOM element ID for the region div\n * @param {HTMLElement} parentEl - Parent element to append the region to\n * @param {Object} [extraState] - Additional properties merged into region state\n * @returns {Object} Region state object { element, config, widgets, ... }\n */\n _createRegionEntry(regionConfig, elementId, parentEl, extraState = {}) {\n const { className = 'renderer-lite-region', ...stateProps } = extraState;\n\n const regionEl = document.createElement('div');\n regionEl.id = elementId;\n regionEl.className = className;\n regionEl.style.position = 'absolute';\n regionEl.style.zIndex = String(regionConfig.zindex);\n regionEl.style.overflow = 'hidden';\n\n // Apply scaled positioning\n this.applyRegionScale(regionEl, regionConfig);\n\n parentEl.appendChild(regionEl);\n\n const sf = this.scaleFactor;\n return {\n element: regionEl,\n config: regionConfig,\n widgets: regionConfig.widgets,\n currentIndex: 0,\n timer: null,\n width: regionConfig.width * sf,\n height: regionConfig.height * sf,\n complete: false,\n widgetElements: new Map(),\n ...stateProps,\n };\n }\n\n /**\n * Create a region element\n * @param {Object} regionConfig - Region configuration\n */\n async createRegion(regionConfig) {\n const region = this._createRegionEntry(\n regionConfig,\n `region_${regionConfig.id}`,\n this.container,\n {\n isDrawer: regionConfig.isDrawer || false,\n isCanvas: regionConfig.isCanvas || false,\n }\n );\n\n // Drawer regions start fully hidden — shown only by navWidget actions\n if (regionConfig.isDrawer) {\n region.element.style.display = 'none';\n }\n\n // Filter expired widgets (fromDt/toDt time-gating within XLF)\n let widgets = regionConfig.widgets.filter(w => this._isWidgetActive(w));\n\n // For regions with sub-playlist cycle playback, select which widgets play this cycle\n if (widgets.some(w => w.cyclePlayback)) {\n widgets = this._applyCyclePlayback(widgets);\n }\n region.widgets = widgets;\n\n this.regions.set(regionConfig.id, region);\n }\n\n /**\n * Start playing a region's widgets\n * @param {string} regionId - Region ID\n */\n startRegion(regionId) {\n const region = this.regions.get(regionId);\n this._startRegionCycle(\n region, regionId,\n this._renderWidgetBound,\n this._stopWidgetBound,\n () => {\n this.log.info(`Region ${regionId} completed one full cycle`);\n this.checkLayoutComplete();\n }\n );\n }\n\n /**\n * Create a widget element (extracted for pre-creation)\n * @param {Object} widget - Widget config\n * @param {Object} region - Region state\n * @returns {Promise<HTMLElement>} Widget DOM element\n */\n async createWidgetElement(widget, region) {\n // render=\"html\" forces GetResource iframe regardless of native type,\n // EXCEPT for types we handle natively (PDF: CMS bundle can't work cross-origin)\n if (widget.render === 'html' && widget.type !== 'pdf') {\n return await this.renderGenericWidget(widget, region);\n }\n\n switch (widget.type) {\n case 'image':\n return await this.renderImage(widget, region);\n case 'video':\n return await this.renderVideo(widget, region);\n case 'audio':\n return await this.renderAudio(widget, region);\n case 'text':\n case 'ticker':\n return await this.renderTextWidget(widget, region);\n case 'pdf':\n return await this.renderPdf(widget, region);\n case 'webpage':\n return await this.renderWebpage(widget, region);\n case 'localvideo':\n return await this.renderVideo(widget, region);\n case 'videoin':\n return await this.renderVideoIn(widget, region);\n case 'powerpoint':\n case 'flash':\n // Legacy Windows-only types — show placeholder instead of failing silently\n this.log.warn(`Widget type '${widget.type}' is not supported on web players (widget ${widget.id})`);\n return this._renderUnsupportedPlaceholder(widget, region);\n default:\n // Generic widget (clock, calendar, weather, etc.)\n return await this.renderGenericWidget(widget, region);\n }\n }\n\n /**\n * Helper: Find media element within widget (works for both direct and wrapped elements)\n * @param {HTMLElement} element - Widget element (might BE the media element or contain it)\n * @param {string} tagName - Tag name to find ('VIDEO', 'AUDIO', 'IMG', 'IFRAME')\n * @returns {HTMLElement|null}\n */\n findMediaElement(element, tagName) {\n // Check if element IS the tag, or contains it as a descendant\n return element.tagName === tagName ? element : element.querySelector(tagName.toLowerCase());\n }\n\n /**\n * Update media element for dynamic content (videos/audio need restart)\n * @param {HTMLElement} element - Widget element\n * @param {Object} widget - Widget config\n */\n updateMediaElement(element, widget) {\n // Restart video or audio on widget show (even if looping)\n const mediaEl = this.findMediaElement(element, 'VIDEO') || this.findMediaElement(element, 'AUDIO');\n if (mediaEl) {\n // Re-acquire webcam stream if it was stopped during _hideWidget()\n if (mediaEl.tagName === 'VIDEO' && mediaEl._mediaConstraints && !mediaEl._mediaStream) {\n navigator.mediaDevices.getUserMedia(mediaEl._mediaConstraints).then(stream => {\n mediaEl.srcObject = stream;\n mediaEl._mediaStream = stream;\n this.log.info(`Webcam stream re-acquired for widget ${widget.id}`);\n }).catch(e => {\n this.log.warn('Failed to re-acquire webcam stream:', e.message);\n });\n return; // srcObject auto-plays, no need for _restartMediaElement\n }\n\n this._restartMediaElement(mediaEl);\n this.log.info(`${mediaEl.tagName === 'VIDEO' ? 'Video' : 'Audio'} restarted: ${widget.fileId || widget.id}`);\n }\n }\n\n /**\n * Restart a media element from the beginning.\n * Waits for seek to complete before playing — avoids DOMException\n * \"The play() request was interrupted\" when calling play() mid-seek.\n */\n _restartMediaElement(el) {\n el.currentTime = 0;\n const playAfterSeek = () => {\n el.removeEventListener('seeked', playAfterSeek);\n el.play().catch(() => {});\n };\n el.addEventListener('seeked', playAfterSeek);\n // Always call play() — for preloaded-then-paused videos, seeked may not\n // fire (currentTime already 0) and readyState may be < 2 (not buffered yet).\n // play() handles both cases: if not ready, it queues; if ready, it plays.\n el.play().catch(() => {});\n }\n\n /**\n * Wait for a widget's media to be ready for playback.\n * - Video: resolves when 'playing' fires (buffered enough to render frames)\n * - Image: resolves when 'load' fires (decoded and paintable)\n * - Text/embedded/clock: resolves immediately (inline content, no async load)\n * @param {HTMLElement} element - Widget DOM element\n * @param {Object} widget - Widget config\n * @returns {Promise<void>}\n */\n waitForWidgetReady(element, widget) {\n const READY_TIMEOUT = 10000; // 10s max wait — don't block forever on broken media\n\n // Video widgets: wait for actual playback\n const videoEl = this.findMediaElement(element, 'VIDEO');\n if (videoEl) {\n // Already playing (replay case where video was kept alive)\n if (!videoEl.paused && videoEl.readyState >= 3) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.log.warn(`Video ready timeout (${READY_TIMEOUT}ms) for widget ${widget.id}`);\n resolve();\n }, READY_TIMEOUT);\n const onPlaying = () => {\n videoEl.removeEventListener('playing', onPlaying);\n clearTimeout(timer);\n this.log.info(`Video widget ${widget.id} ready (playing)`);\n resolve();\n };\n videoEl.addEventListener('playing', onPlaying);\n });\n }\n\n // Audio widgets: wait for playback to start\n const audioEl = this.findMediaElement(element, 'AUDIO');\n if (audioEl) {\n if (!audioEl.paused && audioEl.readyState >= 3) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const timer = setTimeout(() => {\n this.log.warn(`Audio ready timeout (${READY_TIMEOUT}ms) for widget ${widget.id}`);\n resolve();\n }, READY_TIMEOUT);\n const onPlaying = () => {\n audioEl.removeEventListener('playing', onPlaying);\n clearTimeout(timer);\n this.log.info(`Audio widget ${widget.id} ready (playing)`);\n resolve();\n };\n audioEl.addEventListener('playing', onPlaying);\n });\n }\n\n // Image widgets: wait for image decode\n const imgEl = this.findMediaElement(element, 'IMG');\n if (imgEl) {\n if (imgEl.complete && imgEl.naturalWidth > 0) {\n return Promise.resolve();\n }\n return new Promise((resolve) => {\n const onLoad = () => {\n imgEl.removeEventListener('load', onLoad);\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n imgEl.removeEventListener('load', onLoad);\n this.log.warn(`Image ready timeout for widget ${widget.id}`);\n resolve();\n }, READY_TIMEOUT);\n imgEl.addEventListener('load', onLoad);\n });\n }\n\n // Text, embedded, clock, etc. — ready immediately\n return Promise.resolve();\n }\n\n /**\n * Start the layout timer only after all initial widgets are ready.\n * This ensures that the layout duration counts from when content is\n * actually visible, so videos play their full duration to the last frame.\n * @param {number|string} layoutId - Layout ID\n * @param {Object} layout - Layout config with .duration\n */\n async startLayoutTimerWhenReady(layoutId, layout) {\n if (!layout || layout.duration <= 0) return;\n\n // Collect readiness promises for each region's first (current) widget\n const readyPromises = [];\n for (const [regionId, region] of this.regions) {\n if (region.widgets.length === 0) continue;\n const widget = region.widgets[region.currentIndex || 0];\n const element = region.widgetElements.get(widget.id);\n if (element) {\n readyPromises.push(this.waitForWidgetReady(element, widget));\n }\n }\n\n if (readyPromises.length > 0) {\n this.log.info(`Waiting for ${readyPromises.length} widget(s) to be ready before starting layout timer...`);\n await Promise.all(readyPromises);\n this.log.info(`All widgets ready — starting layout timer`);\n }\n\n // Guard: layout may have changed while we were waiting\n if (this.currentLayoutId !== layoutId) {\n this.log.warn(`Layout changed while waiting for widgets — skipping timer for ${layoutId}`);\n return;\n }\n\n // Dynamic layouts (useDuration=0 videos): defer timer until video metadata\n // provides real durations. Safety timeout ensures corrupt/missing videos\n // don't freeze the display forever.\n // Skip deferral if updateLayoutDuration() already set the duration from\n // video metadata (e.g. during preload or a previous play of this layout).\n if (layout.isDynamic && !layout._durationFromMetadata && this._hasUnprobedVideos()) {\n this._deferredTimerLayoutId = layoutId;\n this._layoutTimerStartedAt = Date.now();\n this.log.info(`Layout ${layoutId} has unprobed videos — deferring timer until metadata loads`);\n\n // Safety: if metadata never arrives (corrupt file, codec error), start\n // the timer with the estimated duration after 30s so the display keeps cycling.\n this._deferredTimerFallback = setTimeout(() => {\n this._deferredTimerFallback = null;\n if (this._deferredTimerLayoutId === layoutId && !this.layoutTimer) {\n this.log.warn(`Layout ${layoutId}: metadata timeout after 30s — starting timer with ${layout.duration}s estimate`);\n this._deferredTimerLayoutId = null;\n this._startLayoutTimer(layoutId, layout);\n }\n }, 30000);\n\n return;\n }\n\n this._startLayoutTimer(layoutId, layout);\n }\n\n /**\n * Check if any region's longest-running video widget (useDuration=0) hasn't\n * been probed yet. Used to decide whether to defer the layout timer.\n *\n * Only checks widgets that have had <video> elements created (during preload\n * or show). Widgets that haven't been displayed yet can never be probed —\n * checking them would always force a 30s timeout on layouts with multiple\n * video widgets per region.\n *\n * Returns false if the layout duration has already been updated from video\n * metadata (meaning at least one probe succeeded and updateLayoutDuration\n * computed a real duration), since the timer can start with that value.\n */\n _hasUnprobedVideos() {\n // If any video was probed and updateLayoutDuration ran, the layout duration\n // is already based on real metadata — no need to defer further.\n for (const [, region] of this.regions) {\n for (const widget of region.widgets) {\n if (widget.type === 'video' && widget.useDuration === 0 && widget._probed) return false;\n }\n }\n // No videos probed at all — check if there are any that need probing\n for (const [, region] of this.regions) {\n for (const widget of region.widgets) {\n if (widget.type === 'video' && widget.useDuration === 0) return true;\n }\n }\n return false;\n }\n\n /**\n * Actually start the layout timer. Called directly or after deferred timer resolves.\n */\n _startLayoutTimer(layoutId, layout) {\n this._deferredTimerLayoutId = null;\n if (this._deferredTimerFallback) {\n clearTimeout(this._deferredTimerFallback);\n this._deferredTimerFallback = null;\n }\n const layoutDurationMs = layout.duration * 1000;\n this.log.info(`Layout ${layoutId} will end after ${layout.duration}s`);\n\n this._layoutTimerStartedAt = Date.now();\n this._layoutTimerDurationMs = layoutDurationMs;\n this.layoutTimer = setTimeout(() => {\n this.log.info(`Layout ${layoutId} duration expired (${layout.duration}s)`);\n if (this.currentLayoutId) {\n this.layoutEndEmitted = true;\n this.emit('layoutEnd', this.currentLayoutId);\n }\n }, layoutDurationMs);\n }\n\n /**\n * Render a widget in a region (using element reuse)\n * @param {string} regionId - Region ID\n * @param {number} widgetIndex - Widget index in region\n */\n /**\n * Core: show a widget in a region (shared by main layout + overlay)\n * Returns the widget object on success, null on failure.\n */\n async _showWidget(region, widgetIndex) {\n const widget = region.widgets[widgetIndex];\n if (!widget) return null;\n\n let element = region.widgetElements.get(widget.id);\n\n if (!element) {\n this.log.warn(`Widget ${widget.id} not pre-created, creating now`);\n element = await this.createWidgetElement(widget, region);\n element.style.position = 'absolute';\n element.style.top = '0';\n element.style.left = '0';\n element.style.width = '100%';\n element.style.height = '100%';\n region.widgetElements.set(widget.id, element);\n region.element.appendChild(element);\n }\n\n // Hide all other widgets in region (skip for canvas — all widgets stay visible)\n // Cancel fill:forwards animations first — they override inline styles\n if (!region.isCanvas) {\n for (const [widgetId, widgetEl] of region.widgetElements) {\n if (widgetId !== widget.id) {\n widgetEl.getAnimations?.().forEach(a => a.cancel());\n widgetEl.style.visibility = 'hidden';\n widgetEl.style.opacity = '0';\n // Clear the active marker on widgets we're hiding — otherwise\n // reevaluateXpIf() might resurrect them on the next state\n // change. Only the widget being shown this cycle is active.\n if (widgetEl.dataset) widgetEl.dataset.xpIfActive = '0';\n }\n }\n }\n\n // SMIL State Track B — evaluate xp:if before binding the widget to\n // the DOM timeline. When the guard is false the widget stays hidden\n // but the region timer continues (it will advance to the next\n // widget as if the expression had folded to false at build time).\n if (element.dataset) element.dataset.xpIfActive = '1';\n const xpIfVisible = this._evaluateXpIf(widget);\n if (!xpIfVisible) {\n this.updateMediaElement(element, widget);\n element.getAnimations?.().forEach(a => a.cancel());\n element.style.visibility = 'hidden';\n element.style.opacity = '0';\n element.dataset.xpIf = 'false';\n this.emit('xpIfHidden', { widgetId: widget.id, regionId: region.config?.id, expr: widget.xpIf });\n return widget;\n }\n if (element.dataset && widget.xpIf) element.dataset.xpIf = 'true';\n\n this.updateMediaElement(element, widget);\n element.getAnimations?.().forEach(a => a.cancel());\n element.style.visibility = 'visible';\n\n if (widget.transitions.in) {\n Transitions.apply(element, widget.transitions.in, true, region.width, region.height);\n } else {\n element.style.opacity = '1';\n }\n\n // Resume PDF page cycling if this widget was previously paused\n if (element._pdfResume) {\n element._pdfResume();\n }\n\n // Start audio overlays attached to this widget\n this._startAudioOverlays(widget);\n\n return widget;\n }\n\n /**\n * Start audio overlay elements for a widget.\n * Audio overlays are <audio> child nodes in the XLF that play alongside\n * the visual widget (e.g. background music for an image slideshow).\n * @param {Object} widget - Widget config with audioNodes array\n */\n _startAudioOverlays(widget) {\n if (!widget.audioNodes || widget.audioNodes.length === 0) return;\n\n // Stop any existing audio overlays for this widget first\n this._stopAudioOverlays(widget.id);\n\n const audioElements = [];\n for (const audioNode of widget.audioNodes) {\n if (!audioNode.uri) continue;\n\n const audio = document.createElement('audio');\n audio.autoplay = true;\n audio.loop = audioNode.loop;\n audio.volume = Math.max(0, Math.min(1, audioNode.volume / 100));\n\n // Direct URL from storedAs filename\n audio.src = audioNode.uri ? this._mediaFileUrl(audioNode.uri) : '';\n\n // Append to DOM to prevent garbage collection in some browsers\n audio.style.display = 'none';\n this.container.appendChild(audio);\n\n // Handle autoplay restrictions gracefully (play() may return undefined in some envs)\n const playPromise = audio.play();\n if (playPromise && playPromise.catch) playPromise.catch(() => {});\n\n audioElements.push(audio);\n this.log.info(`Audio overlay started for widget ${widget.id}: ${audioNode.uri} (loop=${audioNode.loop}, vol=${audioNode.volume})`);\n }\n\n if (audioElements.length > 0) {\n this.audioOverlays.set(widget.id, audioElements);\n }\n }\n\n /**\n * Stop and clean up audio overlay elements for a widget.\n * @param {string} widgetId - Widget ID\n */\n _stopAudioOverlays(widgetId) {\n const audioElements = this.audioOverlays.get(widgetId);\n if (!audioElements) return;\n\n for (const audio of audioElements) {\n audio.pause();\n audio.removeAttribute('src');\n audio.load(); // Release resources\n if (audio.parentNode) audio.parentNode.removeChild(audio); // Remove from DOM\n }\n\n this.audioOverlays.delete(widgetId);\n this.log.info(`Audio overlays stopped for widget ${widgetId}`);\n }\n\n /**\n * Core: hide a widget in a region (shared by main layout + overlay).\n * Returns { widget, animPromise } synchronously — callers await animPromise if needed.\n * NOT async, so callers that don't need the animation stay on the same microtask.\n */\n _hideWidget(region, widgetIndex) {\n const widget = region.widgets[widgetIndex];\n if (!widget) return { widget: null, animPromise: null };\n\n const widgetElement = region.widgetElements.get(widget.id);\n if (!widgetElement) return { widget: null, animPromise: null };\n\n let animPromise = null;\n if (widget.transitions.out) {\n const animation = Transitions.apply(\n widgetElement, widget.transitions.out, false, region.width, region.height\n );\n if (animation) {\n animPromise = new Promise(resolve => { animation.onfinish = resolve; });\n }\n }\n\n const videoEl = widgetElement.querySelector('video');\n if (videoEl) {\n videoEl.pause();\n\n // Stop MediaStream tracks (webcam/mic) to release the device\n if (videoEl._mediaStream) {\n videoEl._mediaStream.getTracks().forEach(t => t.stop());\n videoEl._mediaStream = null;\n videoEl.srcObject = null;\n }\n\n // Destroy HLS.js instance to free worker + buffers\n if (videoEl._hlsInstance) {\n videoEl._hlsInstance.destroy();\n videoEl._hlsInstance = null;\n }\n\n // Release decoded video buffers (GPU dmabufs) — without this, paused\n // videos hold texture memory until the layout is evicted from the pool.\n // removeAttribute('src') + load() forces the browser to drop the decoded\n // frame, releasing GPU dmabufs immediately instead of at pool eviction.\n videoEl.removeAttribute('src');\n videoEl.load();\n\n // Remove event listeners to prevent accumulation across widget cycles\n if (videoEl._eventCleanup) {\n for (const [event, handler] of videoEl._eventCleanup) {\n videoEl.removeEventListener(event, handler);\n }\n videoEl._eventCleanup = null;\n }\n }\n\n const audioEl = widgetElement.querySelector('audio');\n if (audioEl && widget.options.loop !== '1') audioEl.pause();\n\n // Remove audio event listeners\n if (audioEl?._eventCleanup) {\n for (const [event, handler] of audioEl._eventCleanup) {\n audioEl.removeEventListener(event, handler);\n }\n audioEl._eventCleanup = null;\n }\n\n // Stop audio overlays attached to this widget\n this._stopAudioOverlays(widget.id);\n\n // Stop PDF page cycling timers\n if (widgetElement._pdfCleanup) {\n widgetElement._pdfCleanup();\n }\n\n // Stop embedded widget iframes (HLS live streams, webcams, etc.)\n // Setting src=about:blank kills all network activity (HLS segment fetches,\n // WebSocket connections, SSE streams) and releases video decode buffers.\n const iframes = widgetElement.querySelectorAll('iframe');\n for (const iframe of iframes) {\n try {\n const doc = iframe.contentDocument || iframe.contentWindow?.document;\n if (doc) {\n doc.querySelectorAll('video').forEach(v => { v.pause(); v.removeAttribute('src'); v.load(); });\n doc.querySelectorAll('audio').forEach(a => { a.pause(); a.removeAttribute('src'); a.load(); });\n }\n } catch (_) {}\n iframe.src = 'about:blank';\n }\n\n return { widget, animPromise };\n }\n\n /**\n * Check if a widget is within its valid time window (fromDt/toDt).\n * Widgets without dates are always active.\n * @param {Object} widget - Widget config with optional fromDt/toDt\n * @returns {boolean}\n */\n _isWidgetActive(widget) {\n const now = new Date();\n if (widget.fromDt) {\n const from = new Date(widget.fromDt);\n if (now < from) return false;\n }\n if (widget.toDt) {\n const to = new Date(widget.toDt);\n if (now > to) return false;\n }\n return true;\n }\n\n /**\n * Parse NUMITEMS and DURATION HTML comments from GetResource responses.\n * CMS embeds these in widget HTML to override duration for dynamic content\n * (e.g. DataSet tickers, RSS feeds). Format: <!-- NUMITEMS=5 --> <!-- DURATION=30 -->\n * DURATION takes precedence; otherwise NUMITEMS × widget.duration is used.\n * @param {string} html - Widget HTML content\n * @param {Object} widget - Widget config (duration may be updated)\n */\n _parseDurationComments(html, widget) {\n const oldDuration = widget.duration;\n\n const durationMatch = html.match(/<!--\\s*DURATION=(\\d+)\\s*-->/);\n if (durationMatch) {\n const newDuration = parseInt(durationMatch[1], 10);\n if (newDuration > 0) {\n this.log.info(`Widget ${widget.id}: DURATION comment overrides duration ${widget.duration}→${newDuration}s`);\n widget.duration = newDuration;\n if (widget.duration !== oldDuration) this.updateLayoutDuration();\n return;\n }\n }\n\n const numItemsMatch = html.match(/<!--\\s*NUMITEMS=(\\d+)\\s*-->/);\n if (numItemsMatch) {\n const numItems = parseInt(numItemsMatch[1], 10);\n if (numItems > 0 && widget.duration > 0) {\n const newDuration = numItems * widget.duration;\n this.log.info(`Widget ${widget.id}: NUMITEMS=${numItems} × ${widget.duration}s = ${newDuration}s`);\n widget.duration = newDuration;\n }\n }\n\n if (widget.duration !== oldDuration) this.updateLayoutDuration();\n }\n\n /**\n * Apply sub-playlist cycle playback filtering.\n * Groups widgets by parentWidgetId, then selects one widget per group for this cycle.\n * Non-grouped widgets pass through unchanged.\n *\n * @param {Array} widgets - All widgets in the region\n * @returns {Array} Filtered widgets for this playback cycle\n */\n _applyCyclePlayback(widgets) {\n // Track cycle indices per group for deterministic round-robin\n if (!this._subPlaylistCycleIndex) {\n this._subPlaylistCycleIndex = new Map();\n }\n\n // Group widgets by parentWidgetId\n const groups = new Map(); // parentWidgetId → [widgets]\n const result = [];\n\n for (const widget of widgets) {\n if (widget.parentWidgetId && widget.cyclePlayback) {\n if (!groups.has(widget.parentWidgetId)) {\n groups.set(widget.parentWidgetId, []);\n }\n groups.get(widget.parentWidgetId).push(widget);\n } else {\n // Non-grouped widget: add a placeholder to preserve order\n result.push({ type: 'direct', widget });\n }\n }\n\n // For each group, select one widget for this cycle\n for (const [groupId, groupWidgets] of groups) {\n // Sort by displayOrder\n groupWidgets.sort((a, b) => a.displayOrder - b.displayOrder);\n\n let selectedWidget;\n if (groupWidgets.some(w => w.isRandom)) {\n // Random selection\n const idx = Math.floor(Math.random() * groupWidgets.length);\n selectedWidget = groupWidgets[idx];\n } else {\n // Round-robin based on cycle index, respecting playCount\n const state = this._subPlaylistCycleIndex.get(groupId) || { widgetIdx: 0, playsDone: 0 };\n selectedWidget = groupWidgets[state.widgetIdx % groupWidgets.length];\n const effectivePlayCount = selectedWidget.playCount || 1;\n\n state.playsDone++;\n if (state.playsDone >= effectivePlayCount) {\n state.widgetIdx++;\n state.playsDone = 0;\n }\n this._subPlaylistCycleIndex.set(groupId, state);\n }\n\n this.log.info(`Sub-playlist cycle: group ${groupId} selected widget ${selectedWidget.id} (${groupWidgets.length} in group)`);\n result.push({ type: 'direct', widget: selectedWidget });\n }\n\n return result.map(r => r.widget);\n }\n\n /**\n * Core: cycle through widgets in a region (shared by main layout + overlay)\n * @param {Object} region - Region state object\n * @param {string} regionId - Region ID\n * @param {Function} showFn - (regionId, widgetIndex) => show widget\n * @param {Function} hideFn - (regionId, widgetIndex) => hide widget\n * @param {Function} [onCycleComplete] - Called when region completes one full cycle\n */\n _startRegionCycle(region, regionId, showFn, hideFn, onCycleComplete) {\n if (!region || region.widgets.length === 0) return;\n\n // Canvas regions: render ALL widgets simultaneously (stacked), no cycling.\n // Duration = max widget duration; region completes when the longest widget expires.\n if (region.isCanvas) {\n this._startCanvasRegion(region, regionId, showFn, onCycleComplete);\n return;\n }\n\n // Non-looping region with a single widget: show it and stay (spec: loop=0)\n if (region.widgets.length === 1) {\n showFn(regionId, 0);\n return;\n }\n\n const playNext = () => {\n const widgetIndex = region.currentIndex;\n const widget = region.widgets[widgetIndex];\n\n showFn(regionId, widgetIndex);\n\n const duration = widget.duration * 1000;\n this.log.info(`Region ${regionId} widget ${widget.id} (${widget.type}) playing for ${widget.duration}s (useDuration=${widget.useDuration}, index ${widgetIndex}/${region.widgets.length})`);\n region.timer = setTimeout(() => {\n this._handleWidgetCycleEnd(widget, region, regionId, widgetIndex, showFn, hideFn, onCycleComplete, playNext);\n }, duration);\n };\n\n playNext();\n }\n\n /**\n * Start a canvas region — render all widgets simultaneously (stacked).\n * Canvas regions show every widget at once rather than cycling through them.\n * The region duration is the maximum widget duration.\n * @param {Object} region - Region state\n * @param {string} regionId - Region ID\n * @param {Function} showFn - Show widget function (regionId, widgetIndex)\n * @param {Function} onCycleComplete - Callback when region completes\n */\n _startCanvasRegion(region, regionId, showFn, onCycleComplete) {\n // Show all widgets at once\n for (let i = 0; i < region.widgets.length; i++) {\n showFn(regionId, i);\n }\n\n // Mark region as complete after max widget duration\n const maxDuration = Math.max(...region.widgets.map(w => w.duration)) * 1000;\n if (maxDuration > 0) {\n region.timer = setTimeout(() => {\n if (!region.complete) {\n region.complete = true;\n onCycleComplete?.();\n }\n }, maxDuration);\n } else {\n // No duration — immediately complete\n region.complete = true;\n onCycleComplete?.();\n }\n }\n\n /**\n * Handle widget cycle end — shared logic for timer-based and event-based cycling\n */\n _handleWidgetCycleEnd(widget, region, regionId, widgetIndex, showFn, hideFn, onCycleComplete, playNext) {\n // Emit widgetAction if widget has a webhook URL configured\n if (widget.webhookUrl) {\n this.emit('widgetAction', {\n type: 'durationEnd',\n widgetId: widget.id,\n layoutId: this.currentLayoutId,\n regionId,\n url: widget.webhookUrl\n });\n }\n\n hideFn(regionId, widgetIndex);\n\n const nextIndex = (region.currentIndex + 1) % region.widgets.length;\n if (nextIndex === 0 && !region.complete) {\n region.complete = true;\n onCycleComplete?.();\n }\n\n // Non-looping single-widget region (loop=0): don't replay.\n // Multi-widget regions (playlists) always cycle regardless of loop setting —\n // in Xibo, loop=0 only means \"don't repeat a single media item.\"\n if (nextIndex === 0 && region.config?.loop === false && region.widgets.length === 1) {\n showFn(regionId, 0);\n return;\n }\n\n // Don't start next widget if layout has already ended (race with layout timer)\n if (this.layoutEndEmitted) return;\n\n region.currentIndex = nextIndex;\n playNext();\n }\n\n async renderWidget(regionId, widgetIndex) {\n const region = this.regions.get(regionId);\n if (!region) return;\n\n try {\n const widget = await this._showWidget(region, widgetIndex);\n if (widget) {\n this.log.info(`Showing widget ${widget.type} (${widget.id}) in region ${regionId}`);\n this._startedWidgets.add(`${regionId}:${widgetIndex}`);\n this.emit('widgetStart', {\n widgetId: widget.id, regionId, layoutId: this.currentLayoutId,\n mediaId: parseInt(widget.fileId || widget.id) || null,\n type: widget.type, duration: widget.duration,\n enableStat: widget.enableStat\n });\n\n // Execute commands attached to this widget (shell/native commands)\n if (widget.commands && widget.commands.length > 0) {\n for (const cmd of widget.commands) {\n this.emit('widgetCommand', {\n commandCode: cmd.commandCode,\n commandString: cmd.commandString,\n widgetId: widget.id,\n regionId,\n layoutId: this.currentLayoutId\n });\n }\n }\n }\n } catch (error) {\n this.log.error(`Error rendering widget:`, error);\n this.emit('error', { type: 'widgetError', error, widgetId: region.widgets[widgetIndex]?.id, regionId });\n }\n }\n\n /**\n * Stop a widget (with element reuse - don't revoke blob URLs!)\n * @param {string} regionId - Region ID\n * @param {number} widgetIndex - Widget index\n */\n async stopWidget(regionId, widgetIndex) {\n const key = `${regionId}:${widgetIndex}`;\n if (!this._startedWidgets.delete(key)) return; // idempotent: already stopped\n\n const region = this.regions.get(regionId);\n if (!region) return;\n\n const { widget, animPromise } = this._hideWidget(region, widgetIndex);\n // Emit widgetEnd immediately — don't wait for exit animation.\n // If we await animPromise first, a pool eviction can remove the DOM element,\n // causing the animation's onfinish to never fire and widgetEnd to be lost.\n if (widget) {\n this.emit('widgetEnd', {\n widgetId: widget.id, regionId, layoutId: this.currentLayoutId,\n mediaId: parseInt(widget.fileId || widget.id) || null,\n type: widget.type,\n enableStat: widget.enableStat\n });\n }\n if (animPromise) await animPromise;\n }\n\n /**\n * Stop all started widgets across regions (symmetric counterpart to startRegion)\n * Canvas regions start ALL widgets; non-canvas regions have one active widget.\n * @param {Map} regions - Region map\n * @param {Function} stopFn - (regionId, widgetIndex) => void\n */\n _stopAllRegionWidgets(regions, stopFn) {\n for (const [regionId, region] of regions) {\n if (region.isCanvas) {\n for (let i = 0; i < region.widgets.length; i++) {\n stopFn(regionId, i);\n }\n } else if (region.widgets.length > 0) {\n stopFn(regionId, region.currentIndex);\n }\n }\n }\n\n /**\n * Render image widget\n */\n async renderImage(widget, region) {\n const img = document.createElement('img');\n img.className = 'renderer-lite-widget';\n img.style.width = '100%';\n img.style.height = '100%';\n // Scale type mapping (CMS image.xml):\n // center (default) → contain: scale proportionally to fit region, centered\n // stretch → fill: ignore aspect ratio, fill entire region\n // fit → cover: scale proportionally to fill region, crop excess\n const scaleType = widget.options.scaleType;\n const fitMap = { stretch: 'fill', center: 'contain', fit: 'cover' };\n img.style.objectFit = fitMap[scaleType] || 'contain';\n\n // Alignment: map alignId/valignId to CSS object-position\n // XLF tags are <alignId> and <valignId> (from CMS image.xml property ids)\n const alignMap = { left: 'left', center: 'center', right: 'right' };\n const valignMap = { top: 'top', middle: 'center', bottom: 'bottom' };\n const hPos = alignMap[widget.options.alignId] || 'center';\n const vPos = valignMap[widget.options.valignId] || 'center';\n img.style.objectPosition = `${hPos} ${vPos}`;\n\n img.style.opacity = '0';\n\n // Direct URL from storedAs filename — store key = widget reference = serve URL\n const src = widget.options.uri\n ? this._mediaFileUrl(widget.options.uri)\n : '';\n\n img.src = src;\n return img;\n }\n\n /**\n * Render video widget\n */\n async renderVideo(widget, region) {\n const video = document.createElement('video');\n video.className = 'renderer-lite-widget';\n video.style.width = '100%';\n video.style.height = '100%';\n const vScaleType = widget.options.scaleType;\n const vFitMap = { stretch: 'fill', center: 'none', fit: 'contain' };\n video.style.objectFit = vFitMap[vScaleType] || 'contain';\n video.style.opacity = '1'; // Immediately visible\n video.autoplay = true;\n video.preload = 'auto'; // Eagerly buffer - chunks are pre-warmed in SW BlobCache\n video.muted = widget.options.mute === '1';\n video.loop = false; // Don't use native loop - we handle it manually to avoid black frames\n video.controls = false; // Hidden by default — toggle with V key in PWA\n video.playsInline = true; // Prevent fullscreen on mobile\n\n // Direct URL from storedAs filename\n const storedAs = widget.options.uri || '';\n const fileId = widget.fileId || widget.id;\n\n // Handle video end - pause on last frame instead of showing black\n // Widget cycling will restart the video via updateMediaElement()\n const onEnded = () => {\n if (widget.options.loop === '1') {\n video.currentTime = 0;\n this.log.info(`Video ${storedAs} ended - reset to start, waiting for widget cycle to replay`);\n } else {\n this.log.info(`Video ${storedAs} ended - paused on last frame`);\n }\n };\n video.addEventListener('ended', onEnded);\n const videoSrc = storedAs ? this._mediaFileUrl(storedAs) : '';\n\n // HLS/DASH streaming support\n const isHlsStream = videoSrc.includes('.m3u8');\n if (isHlsStream) {\n // Try native HLS first (Safari, iOS, some Android)\n if (video.canPlayType('application/vnd.apple.mpegurl')) {\n this.log.info(`HLS stream (native): ${fileId}`);\n video.src = videoSrc;\n } else {\n // Dynamic import hls.js for Chrome/Firefox (code-split, not in main bundle)\n try {\n const { default: Hls } = await import('hls.js');\n if (Hls.isSupported()) {\n const hls = new Hls({ enableWorker: true, lowLatencyMode: true });\n hls.loadSource(videoSrc);\n hls.attachMedia(video);\n video._hlsInstance = hls; // Store for cleanup on eviction\n hls.on(Hls.Events.ERROR, (_event, data) => {\n if (data.fatal) {\n this.log.error(`HLS fatal error: ${data.type}`, data.details);\n hls.destroy();\n video._hlsInstance = null;\n }\n });\n this.log.info(`HLS stream (hls.js): ${fileId}`);\n } else {\n this.log.warn(`HLS not supported on this browser for ${fileId}`);\n video.src = videoSrc; // Fallback — may not work\n }\n } catch (e) {\n this.log.warn(`hls.js not available, falling back to native: ${e.message}`);\n video.src = videoSrc;\n }\n }\n } else {\n video.src = videoSrc;\n }\n\n // Detect video duration for dynamic layout timing (when useDuration=0)\n // Capture the layout ID at creation time — during preload, _preloadingLayoutId\n // is the target layout (currentLayoutId is still the playing layout).\n const createdForLayoutId = this._preloadingLayoutId || this.currentLayoutId;\n const onLoadedMetadata = () => {\n const videoDuration = video.duration;\n this.log.info(`Video ${storedAs} duration detected: ${videoDuration}s`);\n\n if (widget.duration === 0 || widget.useDuration === 0) {\n widget.duration = videoDuration;\n widget._probed = true;\n this.log.info(`Updated widget ${widget.id} duration to ${videoDuration}s (useDuration=0)`);\n\n if (this.currentLayoutId === createdForLayoutId) {\n this.updateLayoutDuration();\n } else {\n this.log.info(`Video ${storedAs} duration set but layout timer not updated (preloaded for layout ${createdForLayoutId}, current is ${this.currentLayoutId})`);\n }\n }\n };\n video.addEventListener('loadedmetadata', onLoadedMetadata);\n\n const onLoadedData = () => {\n this.log.info('Video loaded and ready:', storedAs);\n };\n video.addEventListener('loadeddata', onLoadedData);\n\n const onError = () => {\n const error = video.error;\n const errorCode = error?.code;\n const errorMessage = error?.message || 'Unknown error';\n this.log.warn(`Video error: ${storedAs}, code: ${errorCode}, time: ${video.currentTime.toFixed(1)}s, message: ${errorMessage}`);\n\n // Set fallback duration so the deferred timer can proceed.\n // Without this, a corrupt video leaves widget.duration=0 forever,\n // _hasUnprobedVideos() stays true, and the deferred timer never unblocks.\n if (widget.useDuration === 0 && widget.duration === 0) {\n widget.duration = 60;\n this.log.info(`Set fallback duration 60s for errored widget ${widget.id}`);\n if (this.currentLayoutId === createdForLayoutId) {\n this.updateLayoutDuration();\n }\n }\n\n this.emit('videoError', { storedAs, fileId, errorCode, errorMessage, currentTime: video.currentTime });\n };\n video.addEventListener('error', onError);\n\n const onPlaying = () => {\n this.log.info('Video playing:', storedAs);\n };\n video.addEventListener('playing', onPlaying);\n\n // Store listener references for cleanup in _hideWidget()\n video._eventCleanup = [\n ['ended', onEnded],\n ['loadedmetadata', onLoadedMetadata],\n ['loadeddata', onLoadedData],\n ['error', onError],\n ['playing', onPlaying],\n ];\n\n this.log.info('Video element created:', storedAs, video.src);\n\n return video;\n }\n\n /**\n * Render videoin (webcam/microphone) widget.\n * Uses getUserMedia() to capture live video from camera hardware.\n * @param {Object} widget - Widget config with options (sourceId, showFullScreen, mirror, mute, captureAudio)\n * @param {Object} region - Region dimensions (width, height)\n * @returns {HTMLVideoElement}\n */\n async renderVideoIn(widget, region) {\n const video = document.createElement('video');\n video.className = 'renderer-lite-widget';\n video.style.width = '100%';\n video.style.height = '100%';\n video.style.objectFit = widget.options.showFullScreen === '1' ? 'cover' : 'contain';\n video.autoplay = true;\n video.playsInline = true;\n video.controls = false;\n video.muted = widget.options.mute !== '0'; // Muted by default to prevent audio feedback\n\n // Mirror mode (front-facing camera)\n if (widget.options.mirror === '1') {\n video.style.transform = 'scaleX(-1)';\n }\n\n // Build getUserMedia constraints\n const videoConstraints = {\n width: { ideal: region.width },\n height: { ideal: region.height },\n };\n const deviceId = widget.options.sourceId || widget.options.deviceId;\n if (deviceId) {\n videoConstraints.deviceId = { exact: deviceId };\n } else {\n videoConstraints.facingMode = widget.options.facingMode || 'environment';\n }\n\n const constraints = {\n video: videoConstraints,\n audio: widget.options.captureAudio === '1',\n };\n\n // Store constraints for re-acquisition after layout transitions\n video._mediaConstraints = constraints;\n\n try {\n const stream = await navigator.mediaDevices.getUserMedia(constraints);\n video.srcObject = stream;\n video._mediaStream = stream;\n this.log.info(`Webcam stream acquired for widget ${widget.id} (tracks: ${stream.getTracks().length})`);\n } catch (e) {\n this.log.warn(`getUserMedia failed for widget ${widget.id}: ${e.message}`);\n return this._renderUnsupportedPlaceholder(\n { ...widget, type: 'Camera unavailable' },\n region\n );\n }\n\n return video;\n }\n\n /**\n * Render audio widget\n */\n async renderAudio(widget, region) {\n const container = document.createElement('div');\n container.className = 'renderer-lite-widget audio-widget';\n container.style.width = '100%';\n container.style.height = '100%';\n container.style.display = 'flex';\n container.style.flexDirection = 'column';\n container.style.alignItems = 'center';\n container.style.justifyContent = 'center';\n container.style.background = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';\n container.style.opacity = '0';\n\n // Audio element\n const audio = document.createElement('audio');\n audio.autoplay = true;\n audio.loop = widget.options.loop === '1';\n audio.volume = parseFloat(widget.options.volume || '100') / 100;\n\n // Direct URL from storedAs filename\n const storedAs = widget.options.uri || '';\n const fileId = widget.fileId || widget.id;\n audio.src = storedAs ? this._mediaFileUrl(storedAs) : '';\n\n // Handle audio end - similar to video ended handling\n const onAudioEnded = () => {\n if (widget.options.loop === '1') {\n audio.currentTime = 0;\n this.log.info(`Audio ${storedAs} ended - reset to start, waiting for widget cycle to replay`);\n } else {\n this.log.info(`Audio ${storedAs} ended - playback complete`);\n }\n };\n audio.addEventListener('ended', onAudioEnded);\n\n // Detect audio duration for dynamic layout timing (when useDuration=0)\n const audioCreatedForLayoutId = this._preloadingLayoutId || this.currentLayoutId;\n const onAudioLoadedMetadata = () => {\n const audioDuration = Math.floor(audio.duration);\n this.log.info(`Audio ${storedAs} duration detected: ${audioDuration}s`);\n\n if (widget.duration === 0 || widget.useDuration === 0) {\n widget.duration = audioDuration;\n this.log.info(`Updated widget ${widget.id} duration to ${audioDuration}s (useDuration=0)`);\n\n if (this.currentLayoutId === audioCreatedForLayoutId) {\n this.updateLayoutDuration();\n } else {\n this.log.info(`Audio ${storedAs} duration set but layout timer not updated (preloaded for layout ${audioCreatedForLayoutId}, current is ${this.currentLayoutId})`);\n }\n }\n };\n audio.addEventListener('loadedmetadata', onAudioLoadedMetadata);\n\n // Handle audio errors\n const onAudioError = () => {\n const error = audio.error;\n this.log.warn(`Audio error (non-fatal): ${storedAs}, code: ${error?.code}, message: ${error?.message || 'Unknown'}`);\n };\n audio.addEventListener('error', onAudioError);\n\n // Store listener references for cleanup in _hideWidget()\n audio._eventCleanup = [\n ['ended', onAudioEnded],\n ['loadedmetadata', onAudioLoadedMetadata],\n ['error', onAudioError],\n ];\n\n // Visual feedback\n const icon = document.createElement('div');\n icon.innerHTML = '♪';\n icon.style.fontSize = '120px';\n icon.style.color = 'white';\n icon.style.marginBottom = '20px';\n\n const info = document.createElement('div');\n info.style.color = 'white';\n info.style.fontSize = '24px';\n info.textContent = 'Playing Audio';\n\n const filename = document.createElement('div');\n filename.style.color = 'rgba(255,255,255,0.7)';\n filename.style.fontSize = '16px';\n filename.style.marginTop = '10px';\n filename.textContent = widget.options.uri;\n\n container.appendChild(audio);\n container.appendChild(icon);\n container.appendChild(info);\n container.appendChild(filename);\n\n return container;\n }\n\n /**\n * Render text/ticker widget\n */\n async renderTextWidget(widget, region) {\n return await this._renderIframeWidget(widget, region);\n }\n\n /**\n * Render PDF widget — single reusable canvas, page-by-page cycling.\n *\n * Memory strategy:\n * - One canvas is created and reused for all pages (no DOM churn)\n * - Each page is rendered sequentially (avoids concurrent render errors)\n * - page.cleanup() releases PDF.js internal page buffers after each render\n * - pdf.destroy() releases the entire document on widget teardown\n * - Active renderTask is cancelled on cleanup to prevent stale renders\n */\n async renderPdf(widget, region) {\n const container = document.createElement('div');\n container.className = 'renderer-lite-widget pdf-widget';\n container.style.width = '100%';\n container.style.height = '100%';\n container.style.backgroundColor = 'transparent';\n container.style.opacity = '0';\n container.style.position = 'relative';\n\n // Load PDF.js if available\n if (typeof window.pdfjsLib === 'undefined') {\n try {\n const pdfjsModule = await import('pdfjs-dist');\n window.pdfjsLib = pdfjsModule;\n // Derive worker path from current page location (works for /player/pwa/ and /player/)\n const basePath = window.location.pathname.replace(/\\/[^/]*$/, '/');\n window.pdfjsLib.GlobalWorkerOptions.workerSrc = `${window.location.origin}${basePath}pdf.worker.min.mjs`;\n } catch (error) {\n this.log.error('PDF.js not available:', error);\n container.innerHTML = '<div style=\"color:white;padding:20px;text-align:center;\">PDF viewer unavailable</div>';\n container.style.opacity = '1';\n return container;\n }\n }\n\n // Direct URL from storedAs filename\n const pdfUrl = widget.options.uri\n ? this._mediaFileUrl(widget.options.uri)\n : '';\n\n // Render PDF with multi-page cycling\n try {\n const loadingTask = window.pdfjsLib.getDocument(pdfUrl);\n const pdf = await loadingTask.promise;\n const totalPages = pdf.numPages;\n const duration = widget.duration || 60;\n const timePerPage = (duration * 1000) / totalPages;\n this.log.info(`[pdf] PDF loaded: ${totalPages} pages, ${duration}s duration, ${(timePerPage / 1000).toFixed(1)}s/page`);\n\n // Measure page size from first page to set up the single reusable canvas\n const page1 = await pdf.getPage(1);\n const viewport0 = page1.getViewport({ scale: 1 });\n const scale = Math.min(region.width / viewport0.width, region.height / viewport0.height);\n page1.cleanup();\n\n const canvas = document.createElement('canvas');\n canvas.className = 'pdf-page';\n canvas.width = Math.floor(viewport0.width * scale);\n canvas.height = Math.floor(viewport0.height * scale);\n canvas.style.cssText = 'display:block;margin:auto;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);';\n const ctx = canvas.getContext('2d');\n container.appendChild(canvas);\n\n // Page indicator (bottom-right, v1-style pill) — debug only\n const indicator = document.createElement('div');\n indicator.style.cssText = 'position:absolute;bottom:10px;right:10px;background:rgba(0,0,0,0.7);color:white;padding:8px 12px;border-radius:4px;font:14px system-ui;z-index:1;';\n if (!isDebug()) indicator.style.display = 'none';\n container.appendChild(indicator);\n\n let currentPage = 1;\n let cycleTimer = null;\n let activeRenderTask = null;\n let stopped = false;\n\n // Render one page at a time on the single canvas. Sequential scheduling\n // (setTimeout after render completes) avoids the \"Cannot use the same\n // canvas during multiple render() operations\" error from PDF.js.\n const cyclePage = async () => {\n if (stopped) return;\n indicator.textContent = `Page ${currentPage} / ${totalPages}`;\n\n const page = await pdf.getPage(currentPage);\n const scaledViewport = page.getViewport({ scale });\n\n // Clear and render on the reusable canvas\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n activeRenderTask = page.render({ canvasContext: ctx, viewport: scaledViewport });\n try {\n await activeRenderTask.promise;\n } catch (e) {\n // RenderingCancelledException is expected when stopped during render\n if (stopped) return;\n throw e;\n }\n activeRenderTask = null;\n page.cleanup(); // Release PDF.js internal page buffers\n\n // Schedule next page (only after current render completes)\n if (totalPages > 1 && !stopped) {\n cycleTimer = setTimeout(() => {\n currentPage = currentPage >= totalPages ? 1 : currentPage + 1;\n cyclePage();\n }, timePerPage);\n }\n };\n\n await cyclePage();\n\n // Pause: stop page cycling (called by _hideWidget during region cycling / replay)\n // Returns a promise that resolves when the active render is fully cancelled.\n let cancelPromise = null;\n container._pdfCleanup = () => {\n stopped = true;\n if (cycleTimer) clearTimeout(cycleTimer);\n cycleTimer = null;\n if (activeRenderTask) {\n const task = activeRenderTask;\n activeRenderTask = null;\n task.cancel();\n cancelPromise = task.promise.catch(() => {}); // wait for cancellation to propagate\n }\n };\n\n // Resume: restart page cycling from page 1 (called by _showWidget on reuse)\n // Always cleanup first — the PDF may still be rendering from preload\n // (pre-create starts cyclePage immediately, but the widget isn't \"shown\"\n // until the layout swap, so _pdfCleanup was never called).\n container._pdfResume = async () => {\n container._pdfCleanup(); // stop any in-flight render\n if (cancelPromise) { await cancelPromise; cancelPromise = null; }\n stopped = false;\n currentPage = 1;\n cyclePage();\n };\n\n // Destroy: release GPU + PDF resources (called on element removal / eviction)\n container._pdfDestroy = () => {\n container._pdfCleanup();\n canvas.width = 0;\n canvas.height = 0;\n pdf.destroy();\n };\n\n } catch (error) {\n this.log.error('PDF render failed:', error);\n container.innerHTML = '<div style=\"color:white;padding:20px;text-align:center;\">Failed to load PDF</div>';\n }\n\n container.style.opacity = '1';\n return container;\n }\n\n /**\n * Render webpage widget\n */\n async renderWebpage(widget, region) {\n // modeId=1 (or absent) = Open Natively (direct URL), modeId=0 = Manual/GetResource\n const modeId = parseInt(widget.options.modeId || '1');\n if (modeId === 0) {\n // GetResource mode: treat like a generic widget (fetch HTML from CMS)\n return await this.renderGenericWidget(widget, region);\n }\n\n const iframe = document.createElement('iframe');\n iframe.className = 'renderer-lite-widget';\n iframe.style.width = '100%';\n iframe.style.height = '100%';\n iframe.style.border = 'none';\n iframe.style.opacity = '0';\n // CMS may percent-encode the URI in XLF (e.g. https%3A%2F%2F → https://)\n const uri = decodeURIComponent(widget.options.uri || '');\n iframe.src = uri;\n\n return iframe;\n }\n\n /**\n * Render generic widget (clock, calendar, weather, etc.)\n */\n async renderGenericWidget(widget, region) {\n return await this._renderIframeWidget(widget, region);\n }\n\n /**\n * Shared iframe rendering for text/ticker and generic widgets.\n * Creates an iframe, resolves widget HTML via getWidgetHtml (cache URL or blob),\n * and parses NUMITEMS/DURATION comments for dynamic widget duration.\n */\n async _renderIframeWidget(widget, region) {\n const iframe = document.createElement('iframe');\n iframe.className = 'renderer-lite-widget';\n iframe.style.width = '100%';\n iframe.style.height = '100%';\n iframe.style.border = 'none';\n iframe.style.opacity = '0';\n\n // Get widget HTML (may return { url } for cache-path loading or string for blob)\n let html = widget.raw;\n if (this.options.getWidgetHtml) {\n const result = await this.options.getWidgetHtml(widget);\n if (result && typeof result === 'object' && result.url) {\n // Use cache URL — SW serves HTML and intercepts sub-resources\n iframe.src = result.url;\n\n // Parse NUMITEMS/DURATION from fallback HTML (cache path)\n if (result.fallback) {\n this._parseDurationComments(result.fallback, widget);\n }\n\n return iframe;\n }\n html = result;\n }\n\n if (html) {\n // Parse NUMITEMS/DURATION HTML comments for dynamic widget duration\n // Format: <!-- NUMITEMS=5 --> and <!-- DURATION=30 -->\n this._parseDurationComments(html, widget);\n\n const blob = new Blob([html], { type: 'text/html' });\n const blobUrl = URL.createObjectURL(blob);\n iframe.src = blobUrl;\n\n // Track blob URL for lifecycle management\n this.trackBlobUrl(blobUrl);\n } else {\n this.log.warn(`No HTML for widget ${widget.id}`);\n iframe.srcdoc = '<div style=\"padding:20px;\">Widget content unavailable</div>';\n }\n\n return iframe;\n }\n\n /**\n * Render a placeholder for unsupported widget types (powerpoint, flash)\n */\n _renderUnsupportedPlaceholder(widget, region) {\n const div = document.createElement('div');\n div.className = 'renderer-lite-widget';\n div.style.width = '100%';\n div.style.height = '100%';\n div.style.display = 'flex';\n div.style.alignItems = 'center';\n div.style.justifyContent = 'center';\n div.style.backgroundColor = '#111';\n div.style.color = '#666';\n div.style.fontSize = '14px';\n div.textContent = `Unsupported: ${widget.type}`;\n return div;\n }\n\n // ── Layout Preload Pool ─────────────────────────────────────────────\n\n /**\n * Schedule preloading of the next layout at 75% of current layout duration.\n * Emits 'request-next-layout-preload' so the platform layer can peek at the\n * schedule and call preloadLayout() with the next layout's XLF.\n * @param {Object} layout - Current layout object with .duration\n */\n _scheduleNextLayoutPreload(layout) {\n if (this.preloadTimer) {\n clearTimeout(this.preloadTimer);\n this.preloadTimer = null;\n }\n if (this._preloadRetryTimer) {\n clearTimeout(this._preloadRetryTimer);\n this._preloadRetryTimer = null;\n }\n\n const duration = layout.duration || 60; // seconds\n const preloadDelay = duration * 1000 * 0.75; // 75% through\n const retryDelay = duration * 1000 * 0.90; // 90% retry\n\n this.log.info(`Scheduling next layout preload in ${(preloadDelay / 1000).toFixed(1)}s (75% of ${duration}s)`);\n\n this.preloadTimer = setTimeout(() => {\n this.preloadTimer = null;\n this.emit('request-next-layout-preload');\n }, preloadDelay);\n\n // Retry at 90% if the 75% attempt couldn't find a layout (e.g. cooldowns\n // hadn't expired yet). The platform handler is idempotent — if a layout\n // is already in the pool it skips, so this is safe even if 75% succeeded.\n this._preloadRetryTimer = setTimeout(() => {\n this._preloadRetryTimer = null;\n this.emit('request-next-layout-preload');\n }, retryDelay);\n }\n\n /**\n * Preload a layout into the pool as a warm (hidden) entry.\n * Creates the full DOM hierarchy (regions + widgets) in a hidden container,\n * pre-fetches media, but does NOT start widget cycling or layout timer.\n *\n * This is called by the platform layer in response to 'request-next-layout-preload'.\n *\n * @param {string} xlfXml - XLF XML content for the layout\n * @param {number} layoutId - Layout ID\n * @returns {Promise<boolean>} true if preload succeeded, false on failure\n */\n hasPreloadedLayout(layoutId) {\n return this.layoutPool.has(layoutId);\n }\n\n async preloadLayout(xlfXml, layoutId) {\n // Don't preload if already in pool\n if (this.layoutPool.has(layoutId)) {\n this.log.info(`Layout ${layoutId} already in preload pool, skipping`);\n return true;\n }\n\n // Don't preload the currently playing layout\n if (this.currentLayoutId === layoutId) {\n this.log.info(`Layout ${layoutId} is current, skipping preload`);\n return true;\n }\n\n // If already in-flight, wait for it instead of skipping (prevents the race\n // where showLayout is called before the background preload finishes adding\n // the layout to the pool).\n if (this._preloadingLayoutId === layoutId && this._preloadingPromise) {\n this.log.info(`Layout ${layoutId} preload in-flight, waiting for it...`);\n return this._preloadingPromise;\n }\n\n // Store the preload promise so concurrent callers can await it\n this._preloadingPromise = this._doPreloadLayout(xlfXml, layoutId);\n return this._preloadingPromise;\n }\n\n async _doPreloadLayout(xlfXml, layoutId) {\n try {\n this.log.info(`Preloading layout ${layoutId} into pool...`);\n\n // Parse XLF\n const layout = this.parseXlf(xlfXml);\n\n // Calculate scale factor\n this.calculateScale(layout);\n\n // Create a hidden wrapper container for the preloaded layout\n const wrapper = document.createElement('div');\n wrapper.id = `preload_layout_${layoutId}`;\n wrapper.className = 'renderer-lite-preload-wrapper';\n wrapper.style.position = 'absolute';\n wrapper.style.top = '0';\n wrapper.style.left = '0';\n wrapper.style.width = '100%';\n wrapper.style.height = '100%';\n wrapper.style.visibility = 'hidden';\n wrapper.style.zIndex = '-1'; // Behind everything\n\n // Set background\n wrapper.style.backgroundColor = layout.bgcolor;\n\n // Apply background image if specified\n // With storedAs refactor, background may be a filename or a numeric fileId\n if (layout.background) {\n const saveAs = this.options.fileIdToSaveAs?.get(String(layout.background)) || layout.background;\n this._applyBackgroundImage(wrapper, this._mediaFileUrl(saveAs));\n }\n\n const savedCurrentLayoutId = this.currentLayoutId;\n\n // Create regions in the hidden wrapper\n const preloadRegions = new Map();\n for (const regionConfig of layout.regions) {\n const region = this._createRegionEntry(\n regionConfig,\n `preload_region_${layoutId}_${regionConfig.id}`,\n wrapper\n );\n preloadRegions.set(regionConfig.id, region);\n }\n\n // Track blob URLs for the preloaded layout separately\n const preloadBlobUrls = new Set();\n const savedLayoutBlobUrls = this.layoutBlobUrls;\n this.layoutBlobUrls = new Map();\n this.layoutBlobUrls.set(layoutId, preloadBlobUrls);\n\n // Set _preloadingLayoutId so trackBlobUrl routes to the correct layout\n // without corrupting currentLayoutId (which other code reads during awaits)\n this._preloadingLayoutId = layoutId;\n\n // Pre-create all widget elements\n for (const [regionId, region] of preloadRegions) {\n for (let i = 0; i < region.widgets.length; i++) {\n const widget = region.widgets[i];\n widget.layoutId = layoutId;\n widget.regionId = regionId;\n\n try {\n const element = await this.createWidgetElement(widget, region);\n this._positionWidgetElement(element);\n region.element.appendChild(element);\n region.widgetElements.set(widget.id, element);\n } catch (error) {\n this.log.error(`Preload: Failed to create widget ${widget.id}:`, error);\n }\n }\n }\n\n // Restore state\n this.currentLayoutId = savedCurrentLayoutId;\n\n // Pause all videos in preloaded layout (autoplay starts them even when hidden)\n wrapper.querySelectorAll('video').forEach(v => v.pause());\n\n // Collect any blob URLs tracked during preload\n const trackedBlobUrls = this.layoutBlobUrls.get(layoutId) || new Set();\n trackedBlobUrls.forEach(url => preloadBlobUrls.add(url));\n\n // Restore original layoutBlobUrls\n this.layoutBlobUrls = savedLayoutBlobUrls;\n\n // Add wrapper to main container (hidden)\n this.container.appendChild(wrapper);\n\n // Add to pool as warm\n this.layoutPool.add(layoutId, {\n container: wrapper,\n layout,\n regions: preloadRegions,\n blobUrls: preloadBlobUrls,\n });\n\n this.log.info(`Layout ${layoutId} preloaded into pool (${preloadRegions.size} regions)`);\n return true;\n\n } catch (error) {\n this.log.error(`Preload failed for layout ${layoutId}:`, error);\n return false;\n } finally {\n if (this._preloadingLayoutId === layoutId) {\n this._preloadingLayoutId = null;\n this._preloadingPromise = null;\n }\n }\n }\n\n /**\n * Swap to a preloaded layout from the pool.\n *\n * Dispatches on the resolved layout transition spec:\n * - `instant` (default) → hard cut via _swapToPreloadedLayoutInstant\n * - `fade|slide|wipe|...` → cross-fade overlap via\n * _swapToPreloadedLayoutWithTransition\n *\n * Per-layout overrides (from the XLF `layoutTransitionIn` attribute)\n * beat the renderer's configured default. See #337.\n *\n * @param {number} layoutId - Layout ID to swap to\n */\n async _swapToPreloadedLayout(layoutId) {\n const preloaded = this.layoutPool.get(layoutId);\n if (!preloaded) {\n this.log.error(`Cannot swap: layout ${layoutId} not in pool`);\n return;\n }\n\n const spec = this._resolveLayoutTransition(preloaded.layout);\n\n if (spec.type === 'instant') {\n return this._swapToPreloadedLayoutInstant(layoutId, preloaded);\n }\n return this._swapToPreloadedLayoutWithTransition(layoutId, preloaded, spec);\n }\n\n /**\n * Instant swap path — the pre-#337 fast swap that hard-cuts from\n * old to new with zero animation overhead. This is the default and\n * covers the common \"no transition configured\" case.\n *\n * Kept as a dedicated method (rather than a branch) so the\n * transition path can extract shared helpers without destabilising\n * the fast path's behaviour.\n *\n * @param {number} layoutId\n * @param {Object} preloaded - pool entry from layoutPool.get(layoutId)\n */\n async _swapToPreloadedLayoutInstant(layoutId, preloaded) {\n // ── Tear down old layout ──\n this.removeActionListeners();\n this._clearLayoutTimers();\n\n const oldLayoutId = this.currentLayoutId;\n const alreadyEmittedEnd = this.layoutEndEmitted;\n\n this.layoutEndEmitted = false;\n // Keep currentLayout/currentLayoutId until widgets are stopped,\n // so widgetEnd events carry the correct layoutId (not null).\n\n if (oldLayoutId && this.layoutPool.has(oldLayoutId)) {\n // Stop all widgets before evicting (symmetric widgetEnd events)\n this._clearRegionTimers(this.regions);\n this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);\n // Old layout was preloaded — evict from pool (safe: removes its wrapper div)\n this.layoutPool.evict(oldLayoutId);\n } else {\n // Old layout was rendered normally — manual cleanup.\n // Region elements live directly in this.container (not a wrapper),\n // so we must remove them individually.\n this._clearRegionTimers(this.regions);\n this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);\n for (const [, region] of this.regions) {\n // Release video/audio resources before removing from DOM\n LayoutPool.releaseMediaElements(region.element);\n // Apply region exit transition if configured, then remove\n if (region.config?.exitTransition) {\n const animation = Transitions.apply(\n region.element, region.config.exitTransition, false,\n region.width, region.height\n );\n if (animation) {\n const el = region.element;\n animation.onfinish = () => el.remove();\n } else {\n region.element.remove();\n }\n } else {\n region.element.remove();\n }\n }\n // Revoke blob URLs\n if (oldLayoutId) {\n this.revokeBlobUrlsForLayout(oldLayoutId);\n }\n }\n\n // Now safe to clear old layout state — widgets have been stopped with correct layoutId\n this.currentLayout = null;\n this.currentLayoutId = null;\n this.regions.clear();\n\n // ── Activate preloaded layout ──\n this._activatePreloadedLayout(layoutId, preloaded, oldLayoutId, alreadyEmittedEnd);\n\n this.log.info(`Swapped to preloaded layout ${layoutId} (instant transition)`);\n this._logResourceStats(layoutId);\n }\n\n /**\n * Transition swap path — cross-fade / slide / wipe between layouts\n * using the LayoutPool's overlap architecture (#337).\n *\n * The preloaded wrapper already lives inside `this.container` at\n * zIndex=-1 (hidden). For the transition we:\n *\n * 1. Stop old widgets with the OLD layoutId still set (so their\n * widgetEnd events carry the correct layoutId).\n * 2. Raise the new wrapper above the old (zIndex=1).\n * 3. Update renderer state to the new layout and start its\n * widgets — they play over the top of the still-visible old\n * content during the transition window.\n * 4. Kick off the incoming animation on the new wrapper and,\n * for fade/slide, a matching outgoing animation on the old\n * container. `wipe` is reveal-only — the old container\n * disappears instantly when the incoming wipe completes.\n * 5. On the incoming animation's onfinish, tear down the old\n * layout the same way the instant path would have done\n * synchronously.\n *\n * Notes:\n * - Audio from the old layout keeps playing during the overlap.\n * Authors who want silent transitions should use `instant` or\n * mute the last audio widget on the outgoing layout.\n * - `layoutEnd` for the old layout is emitted up-front (same\n * point as the instant path, right after currentLayoutId\n * updates) so stats accounting isn't gated on the animation\n * clock. The DOM/media cleanup still waits for onfinish.\n * - Multi-display sync (#337 DoD): no sync-manager changes are\n * needed. `onLayoutShow` fires on every display at the same\n * moment via the lead's `showAt` contract, each display\n * applies its choreography stagger, and the transition spec\n * comes from the layout XLF (same on every display) so all\n * displays start and finish the transition in lock-step.\n *\n * @param {number} layoutId\n * @param {Object} preloaded - pool entry from layoutPool.get(layoutId)\n * @param {{type:string,duration:number,direction?:string}} spec\n */\n async _swapToPreloadedLayoutWithTransition(layoutId, preloaded, spec) {\n this.removeActionListeners();\n this._clearLayoutTimers();\n\n const oldLayoutId = this.currentLayoutId;\n const alreadyEmittedEnd = this.layoutEndEmitted;\n this.layoutEndEmitted = false;\n\n // Capture old state before we mutate `this.regions` so the\n // deferred teardown in onfinish can still reach it.\n const oldRegions = this.regions;\n const oldIsPooled =\n oldLayoutId !== null && this.layoutPool.has(oldLayoutId);\n const oldContainer = oldIsPooled\n ? this.layoutPool.get(oldLayoutId).container\n : null;\n\n // Phase 1 — stop old widgets while currentLayoutId still points\n // at the old layout (widgetEnd events fire with the correct id).\n this._clearRegionTimers(oldRegions);\n this._stopAllRegionWidgets(oldRegions, this._stopWidgetBound);\n\n // Clear old state AFTER widgets have emitted. The DOM is still\n // alive and will be removed by _teardownOldLayoutAfterTransition\n // once the animation finishes.\n this.currentLayout = null;\n this.currentLayoutId = null;\n this.regions = new Map();\n\n // Phase 2 — raise the preloaded wrapper above the old content.\n // The preload path appends wrappers at zIndex=-1 hidden; the\n // instant path sets them to zIndex=0 on activation. For the\n // overlap transition we use zIndex=1 so the new layout visibly\n // paints on top, and restore it to 0 at the end of the animation\n // (matches the steady-state convention of the instant path).\n preloaded.container.style.visibility = 'visible';\n preloaded.container.style.zIndex = '1';\n // Start the incoming layout at the animation's \"from\" state:\n // opacity 0 for fade, translated off-screen for slide, fully\n // clipped for wipe. The Transitions.apply() call will drive the\n // animation from there.\n if (spec.type === 'fade') {\n preloaded.container.style.opacity = '0';\n }\n\n // Phase 3 — activate new layout state + start its widgets.\n this._activatePreloadedLayout(layoutId, preloaded, oldLayoutId, alreadyEmittedEnd);\n\n // Phase 4 — kick off the animations. The incoming animation drives\n // the teardown timing in its onfinish; the outgoing one runs in\n // parallel purely for visual effect.\n const layoutWidth = preloaded.layout.width;\n const layoutHeight = preloaded.layout.height;\n\n const incoming = Transitions.apply(\n preloaded.container,\n spec,\n true,\n layoutWidth,\n layoutHeight\n );\n let outgoing = null;\n if (oldContainer && (spec.type === 'fade' || spec.type === 'slide')) {\n outgoing = Transitions.apply(\n oldContainer,\n spec,\n false,\n layoutWidth,\n layoutHeight\n );\n }\n\n const finalizeTeardown = () => {\n // Restore the preloaded wrapper to the same zIndex the instant\n // path leaves it at, so any subsequent swap finds the DOM in a\n // consistent state.\n preloaded.container.style.zIndex = '0';\n preloaded.container.style.opacity = '';\n\n this._teardownOldLayoutAfterTransition(\n oldLayoutId,\n oldRegions,\n oldIsPooled\n );\n\n // Cancel any still-running outgoing animation in case the\n // incoming finished first (different durations) — prevents the\n // old container from becoming visible again mid-cleanup.\n if (outgoing) {\n try { outgoing.cancel(); } catch (_) { /* no-op */ }\n }\n\n this.log.info(\n `Swapped to preloaded layout ${layoutId} (${spec.type} transition, ${spec.duration}ms)`\n );\n this._logResourceStats(layoutId);\n };\n\n if (incoming) {\n incoming.onfinish = finalizeTeardown;\n // Safety net: if onfinish never fires (browser bug, tab\n // backgrounded, etc.), force cleanup after duration + 50%.\n // This matches the pre-#337 worst case where cleanup was\n // synchronous — we'd rather cut abruptly than leak DOM.\n setTimeout(finalizeTeardown, Math.ceil(spec.duration * 1.5));\n } else {\n // Browser doesn't support the requested transition type —\n // fall back to an instant cleanup so nothing leaks.\n finalizeTeardown();\n }\n }\n\n /**\n * Shared \"activate preloaded layout\" block. Extracted from the\n * instant path so both swap paths produce identical state after\n * activation (state updates, event emission, background copy,\n * scale, widget start). See _swapToPreloadedLayoutInstant for the\n * historical inline version — this is a straight cut-and-lift,\n * not a rewrite.\n *\n * Preconditions: the old layout's widgets have been stopped and\n * the old state has been cleared from this.currentLayout / this.regions.\n *\n * @param {number} layoutId\n * @param {Object} preloaded - pool entry\n * @param {number|null} oldLayoutId - id of the layout we're leaving\n * @param {boolean} alreadyEmittedEnd - whether layoutEnd was already emitted for the old layout\n */\n _activatePreloadedLayout(layoutId, preloaded, oldLayoutId, alreadyEmittedEnd) {\n preloaded.container.style.visibility = 'visible';\n // The transition path raises zIndex to 1 during the animation and\n // restores it to 0 in finalizeTeardown — don't clobber that here.\n if (preloaded.container.style.zIndex !== '1') {\n preloaded.container.style.zIndex = '0';\n }\n\n // Update renderer state to the preloaded layout\n this.layoutPool.setHot(layoutId);\n this.currentLayout = preloaded.layout;\n this.currentLayoutId = layoutId;\n this.regions = preloaded.regions;\n\n // SMIL State Track B — apply xp-state-init on layout activation\n // (parity with the non-preload path in renderLayout). Preloaded\n // layouts still carry the parsed xpStateInit metadata on\n // preloaded.layout, so we can materialise the store now.\n this._applyXpStateInit(preloaded.layout);\n\n // Emit layoutEnd for old layout AFTER setting new currentLayoutId —\n // the listener guard in main.ts sees the new layout already playing\n // and skips advance, while stats/tracking still run.\n // Skip if the layout timer already emitted layoutEnd (avoids double stats).\n if (oldLayoutId && !alreadyEmittedEnd) {\n this.emit('layoutEnd', oldLayoutId);\n }\n\n // Update container background to match preloaded layout\n this.container.style.backgroundColor = preloaded.layout.bgcolor;\n if (preloaded.container.style.backgroundImage) {\n // Copy background styles from preloaded wrapper to main container\n for (const prop of ['backgroundImage', 'backgroundSize', 'backgroundPosition', 'backgroundRepeat']) {\n this.container.style[prop] = preloaded.container.style[prop];\n }\n } else {\n this.container.style.backgroundImage = '';\n }\n\n // Recalculate scale for the preloaded layout\n this.calculateScale(preloaded.layout);\n\n // Attach interactive action listeners\n this.attachActionListeners(preloaded.layout);\n\n // Emit layout start event\n this.emit('layoutStart', layoutId, preloaded.layout);\n\n // Reset all regions and start widget cycling\n for (const [regionId, region] of this.regions) {\n region.currentIndex = 0;\n region.complete = false;\n this.startRegion(regionId);\n }\n\n // Recalculate layout duration from widget durations.\n // During preload, video loadedmetadata updated widget.duration but\n // updateLayoutDuration() updated this.currentLayout (the old layout),\n // so preloaded.layout.duration may still be the XLF default (e.g. 60s).\n this.updateLayoutDuration();\n\n // Wait for widgets to be ready then start layout timer\n this.startLayoutTimerWhenReady(layoutId, preloaded.layout);\n\n // Schedule next preload (unless updateLayoutDuration already did it)\n if (!this.preloadTimer) {\n this._scheduleNextLayoutPreload(preloaded.layout);\n }\n }\n\n /**\n * Tear down the old layout after a transition animation finishes.\n *\n * Mirrors the synchronous teardown in _swapToPreloadedLayoutInstant\n * but runs from an animation's onfinish callback (or the safety\n * timeout) so the DOM, videos, and blob URLs live long enough for\n * the visual transition to complete.\n *\n * @param {number|null} oldLayoutId\n * @param {Map} oldRegions - the this.regions captured before swap\n * @param {boolean} oldIsPooled - whether the old layout was in the pool\n */\n _teardownOldLayoutAfterTransition(oldLayoutId, oldRegions, oldIsPooled) {\n if (oldIsPooled && oldLayoutId !== null) {\n // Old layout was preloaded — evict from pool (removes wrapper).\n this.layoutPool.evict(oldLayoutId);\n return;\n }\n\n // Old layout was rendered normally — manual cleanup.\n for (const [, region] of oldRegions) {\n // Release video/audio resources before removing from DOM\n LayoutPool.releaseMediaElements(region.element);\n // Apply region exit transition if configured, then remove\n if (region.config?.exitTransition) {\n const animation = Transitions.apply(\n region.element, region.config.exitTransition, false,\n region.width, region.height\n );\n if (animation) {\n const el = region.element;\n animation.onfinish = () => el.remove();\n } else {\n region.element.remove();\n }\n } else {\n region.element.remove();\n }\n }\n if (oldLayoutId) {\n this.revokeBlobUrlsForLayout(oldLayoutId);\n }\n }\n\n /**\n * Log resource allocation stats for debugging memory/GPU leaks.\n * Called after every layout swap to track DOM node accumulation,\n * video element lifecycle, and pool state.\n */\n _logResourceStats(layoutId) {\n const domNodes = document.querySelectorAll('*').length;\n const videos = document.querySelectorAll('video').length;\n const videosSrc = document.querySelectorAll('video[src]').length;\n const canvases = document.querySelectorAll('canvas').length;\n const iframes = document.querySelectorAll('iframe').length;\n const images = document.querySelectorAll('img').length;\n const poolSize = this.layoutPool ? this.layoutPool.size : 0;\n const regionCount = this.regions ? this.regions.size : 0;\n const widgetElements = [...(this.regions?.values() || [])].reduce(\n (sum, r) => sum + (r.widgetElements?.size || 0), 0\n );\n const jsHeap = performance?.memory ? {\n used: Math.round(performance.memory.usedJSHeapSize / 1048576),\n total: Math.round(performance.memory.totalJSHeapSize / 1048576),\n limit: Math.round(performance.memory.jsHeapSizeLimit / 1048576),\n } : null;\n\n // Count blob URLs still tracked (potential leak indicator)\n const blobUrls = this._blobUrls ? [...this._blobUrls.values()].reduce((s, set) => s + set.size, 0) : 0;\n const blobLayouts = this._blobUrls ? this._blobUrls.size : 0;\n\n // Preload wrapper divs in DOM (should be 0-1 in normal operation)\n const preloadWrappers = document.querySelectorAll('.renderer-lite-preload-wrapper').length;\n\n // Audio overlay elements\n const audioEls = document.querySelectorAll('audio').length;\n\n const heapStr = jsHeap ? `heap=${jsHeap.used}/${jsHeap.total}MB (limit ${jsHeap.limit}MB)` : 'heap=N/A';\n this.log.info(\n `[Resources] layout=${layoutId} dom=${domNodes} videos=${videos}(src=${videosSrc}) ` +\n `canvas=${canvases} iframe=${iframes} img=${images} audio=${audioEls} ` +\n `pool=${poolSize} preloadWrappers=${preloadWrappers} ` +\n `regions=${regionCount} widgets=${widgetElements} ` +\n `blobs=${blobUrls}(${blobLayouts} layouts) ${heapStr}`\n );\n }\n\n /**\n * Get the currently showing layout ID.\n * @returns {number|null}\n */\n getCurrentLayoutId() {\n return this.currentLayoutId;\n }\n\n /**\n * Get the parsed <tags> array for the currently-showing layout.\n *\n * Layout-level tags are flat strings parsed from\n * `<layout><tags><tag>…</tag></tags></layout>`. The sync bridge\n * (roadmap #236) reads these to detect `xp-sync-group:NAME` markers\n * emitted by the xiboplayer-smil-tools translator.\n *\n * @returns {string[]} Tags on the current layout, or `[]` when no\n * layout is showing or the layout carries no tags.\n */\n getCurrentLayoutTags() {\n if (!this.currentLayout || !Array.isArray(this.currentLayout.tags)) return [];\n // Return a defensive copy so callers can't mutate renderer state.\n return this.currentLayout.tags.slice();\n }\n\n /**\n * Show a preloaded layout (swap from pool to visible).\n * If no layoutId, shows the most recently preloaded layout.\n * No-ops if the layout is not in the pool.\n * @param {number} [layoutId]\n */\n showLayout(layoutId) {\n if (layoutId === undefined) {\n layoutId = this.layoutPool.getLatest();\n if (layoutId === undefined) {\n this.log.warn('showLayout: no preloaded layout to show');\n return;\n }\n }\n // Same layout already showing — skip swap (self-swap would evict then fail).\n // Same-layout replay is handled by renderLayout's replay path instead.\n if (this.currentLayoutId === layoutId) {\n this.log.info(`showLayout: layout ${layoutId} already showing`);\n return;\n }\n if (!this.layoutPool.has(layoutId)) {\n this.log.warn(`showLayout: layout ${layoutId} not in preload pool`);\n return;\n }\n this._swapToPreloadedLayout(layoutId);\n }\n\n /**\n * Check if the layout timer is active (running or deferred waiting for metadata).\n * Used to detect stalled layouts that need timer restart.\n * @returns {boolean}\n */\n hasActiveLayoutTimer() {\n return this.layoutTimer !== null || this._deferredTimerLayoutId !== null;\n }\n\n /**\n * Check if all regions have completed one full cycle\n * This is informational only - layout timer is authoritative\n */\n checkLayoutComplete() {\n // Check if all regions with multiple widgets have completed one cycle\n let allComplete = true;\n for (const [regionId, region] of this.regions) {\n // Only check multi-widget regions\n if (region.widgets.length > 1 && !region.complete) {\n allComplete = false;\n break;\n }\n }\n\n if (allComplete && this.currentLayoutId) {\n this.log.info(`All multi-widget regions completed one cycle`);\n // NOTE: We DON'T emit layoutEnd here - layout timer is authoritative\n // This is just informational logging for debugging\n }\n }\n\n /**\n * Clear all layout-level timers (layout duration, preload, preload retry).\n */\n _clearLayoutTimers() {\n if (this.layoutTimer) {\n clearTimeout(this.layoutTimer);\n this.layoutTimer = null;\n }\n if (this.preloadTimer) {\n clearTimeout(this.preloadTimer);\n this.preloadTimer = null;\n }\n if (this._preloadRetryTimer) {\n clearTimeout(this._preloadRetryTimer);\n this._preloadRetryTimer = null;\n }\n }\n\n /**\n * Stop current layout\n */\n stopCurrentLayout() {\n if (!this.currentLayout) return;\n\n this.log.info(`Stopping layout ${this.currentLayoutId}`);\n\n const endedLayoutId = this.currentLayoutId;\n const shouldEmit = endedLayoutId && !this.layoutEndEmitted;\n\n this.layoutEndEmitted = false;\n this._deferredTimerLayoutId = null;\n if (this._deferredTimerFallback) {\n clearTimeout(this._deferredTimerFallback);\n this._deferredTimerFallback = null;\n }\n this.currentLayout = null;\n this.currentLayoutId = null;\n\n // Clear timers\n this._clearLayoutTimers();\n\n // Remove interactive action listeners before teardown\n this.removeActionListeners();\n\n // If layout was preloaded (has its own wrapper div in pool), evict safely.\n // Normally-rendered layouts are NOT in the pool, so we do manual cleanup.\n if (endedLayoutId && this.layoutPool.has(endedLayoutId)) {\n this.layoutPool.evict(endedLayoutId);\n } else {\n // Normally-rendered layout - manual cleanup (regions are in this.container)\n\n // Revoke all blob URLs for this layout (tracked lifecycle management)\n if (endedLayoutId) {\n this.revokeBlobUrlsForLayout(endedLayoutId);\n }\n\n // Stop all regions — use helper to stop ALL started widgets (canvas fix)\n this._clearRegionTimers(this.regions);\n this._stopAllRegionWidgets(this.regions, this._stopWidgetBound);\n for (const [, region] of this.regions) {\n // Release video/audio resources before removing from DOM\n LayoutPool.releaseMediaElements(region.element);\n\n // Apply region exit transition if configured, then remove\n if (region.config?.exitTransition) {\n const animation = Transitions.apply(\n region.element, region.config.exitTransition, false,\n region.width, region.height\n );\n if (animation) {\n const el = region.element;\n animation.onfinish = () => el.remove();\n } else {\n region.element.remove();\n }\n } else {\n region.element.remove();\n }\n }\n\n }\n\n this.regions.clear();\n\n // Emit LAST — re-entrant renderLayout() sees currentLayout=null,\n // so stopCurrentLayout() returns early. No cascade.\n if (shouldEmit) {\n this.emit('layoutEnd', endedLayoutId);\n }\n }\n\n /**\n * Render an overlay layout on top of the main layout\n * @param {string} xlfXml - XLF XML content for overlay\n * @param {number} layoutId - Overlay layout ID\n * @param {number} priority - Overlay priority (higher = on top)\n * @returns {Promise<void>}\n */\n async renderOverlay(xlfXml, layoutId, priority = 0) {\n try {\n this.log.info(`Rendering overlay ${layoutId} (priority ${priority})`);\n\n // Check if this overlay is already active\n if (this.activeOverlays.has(layoutId)) {\n this.log.warn(`Overlay ${layoutId} already active, skipping`);\n return;\n }\n\n // Parse XLF\n const layout = this.parseXlf(xlfXml);\n\n // Create overlay container\n const overlayDiv = document.createElement('div');\n overlayDiv.id = `overlay_${layoutId}`;\n overlayDiv.className = 'renderer-lite-overlay';\n overlayDiv.style.position = 'absolute';\n overlayDiv.style.top = '0';\n overlayDiv.style.left = '0';\n overlayDiv.style.width = '100%';\n overlayDiv.style.height = '100%';\n overlayDiv.style.zIndex = String(1000 + priority); // Higher priority = higher z-index\n overlayDiv.style.pointerEvents = 'auto'; // Enable clicks on overlay\n overlayDiv.style.backgroundColor = layout.bgcolor;\n\n // Calculate scale for overlay layout\n this.calculateScale(layout);\n\n // Create regions for overlay\n const overlayRegions = new Map();\n for (const regionConfig of layout.regions) {\n const region = this._createRegionEntry(\n regionConfig,\n `overlay_${layoutId}_region_${regionConfig.id}`,\n overlayDiv,\n {\n className: 'renderer-lite-region overlay-region',\n isCanvas: regionConfig.isCanvas || false,\n }\n );\n overlayRegions.set(regionConfig.id, region);\n }\n\n // Pre-create widget elements for overlay\n for (const [regionId, region] of overlayRegions) {\n for (const widget of region.widgets) {\n widget.layoutId = layoutId;\n widget.regionId = regionId;\n\n try {\n const element = await this.createWidgetElement(widget, region);\n this._positionWidgetElement(element);\n region.element.appendChild(element);\n region.widgetElements.set(widget.id, element);\n } catch (error) {\n this.log.error(`Failed to pre-create overlay widget ${widget.id}:`, error);\n }\n }\n }\n\n // Add overlay to container\n this.overlayContainer.appendChild(overlayDiv);\n\n // Store overlay state\n this.activeOverlays.set(layoutId, {\n container: overlayDiv,\n layout: layout,\n regions: overlayRegions,\n timer: null,\n priority: priority\n });\n\n // Emit overlay start event\n this.emit('overlayStart', layoutId, layout);\n\n // Start all overlay regions\n for (const [regionId, region] of overlayRegions) {\n this.startOverlayRegion(layoutId, regionId);\n }\n\n // Set overlay timer based on duration\n if (layout.duration > 0) {\n const durationMs = layout.duration * 1000;\n const overlayState = this.activeOverlays.get(layoutId);\n if (overlayState) {\n overlayState.timer = setTimeout(() => {\n this.log.info(`Overlay ${layoutId} duration expired (${layout.duration}s)`);\n this.emit('overlayEnd', layoutId);\n }, durationMs);\n }\n }\n\n this.log.info(`Overlay ${layoutId} started`);\n\n } catch (error) {\n this.log.error('Error rendering overlay:', error);\n this.emit('error', { type: 'overlayError', error, layoutId });\n throw error;\n }\n }\n\n /**\n * Start playing an overlay region's widgets\n * @param {number} overlayId - Overlay layout ID\n * @param {string} regionId - Region ID\n */\n startOverlayRegion(overlayId, regionId) {\n const overlayState = this.activeOverlays.get(overlayId);\n if (!overlayState) return;\n\n const region = overlayState.regions.get(regionId);\n this._startRegionCycle(\n region, regionId,\n (rid, idx) => this.renderOverlayWidget(overlayId, rid, idx),\n (rid, idx) => this.stopOverlayWidget(overlayId, rid, idx),\n () => this.log.info(`Overlay ${overlayId} region ${regionId} completed one full cycle`)\n );\n }\n\n /**\n * Render a widget in an overlay region\n * @param {number} overlayId - Overlay layout ID\n * @param {string} regionId - Region ID\n * @param {number} widgetIndex - Widget index in region\n */\n async renderOverlayWidget(overlayId, regionId, widgetIndex) {\n const overlayState = this.activeOverlays.get(overlayId);\n if (!overlayState) return;\n\n const region = overlayState.regions.get(regionId);\n if (!region) return;\n\n try {\n const widget = await this._showWidget(region, widgetIndex);\n if (widget) {\n this.log.info(`Showing overlay widget ${widget.type} (${widget.id}) in overlay ${overlayId} region ${regionId}`);\n this._startedWidgets.add(`overlay:${overlayId}:${regionId}:${widgetIndex}`);\n this.emit('overlayWidgetStart', {\n overlayId, widgetId: widget.id, regionId,\n type: widget.type, duration: widget.duration\n });\n }\n } catch (error) {\n this.log.error(`Error rendering overlay widget:`, error);\n this.emit('error', { type: 'overlayWidgetError', error, widgetId: region.widgets[widgetIndex]?.id, regionId, overlayId });\n }\n }\n\n /**\n * Stop an overlay widget\n * @param {number} overlayId - Overlay layout ID\n * @param {string} regionId - Region ID\n * @param {number} widgetIndex - Widget index\n */\n async stopOverlayWidget(overlayId, regionId, widgetIndex) {\n const key = `overlay:${overlayId}:${regionId}:${widgetIndex}`;\n if (!this._startedWidgets.delete(key)) return; // idempotent\n\n const overlayState = this.activeOverlays.get(overlayId);\n if (!overlayState) return;\n\n const region = overlayState.regions.get(regionId);\n if (!region) return;\n\n const { widget, animPromise } = this._hideWidget(region, widgetIndex);\n // Emit immediately — don't wait for exit animation (same fix as stopWidget)\n if (widget) {\n this.emit('overlayWidgetEnd', {\n overlayId, widgetId: widget.id, regionId, type: widget.type\n });\n }\n if (animPromise) await animPromise;\n }\n\n /**\n * Stop and remove an overlay layout\n * @param {number} layoutId - Overlay layout ID\n */\n stopOverlay(layoutId) {\n const overlayState = this.activeOverlays.get(layoutId);\n if (!overlayState) {\n this.log.warn(`Overlay ${layoutId} not active`);\n return;\n }\n\n this.log.info(`Stopping overlay ${layoutId}`);\n\n // Clear overlay timer\n if (overlayState.timer) {\n clearTimeout(overlayState.timer);\n overlayState.timer = null;\n }\n\n // Stop all overlay regions\n for (const [, region] of overlayState.regions) {\n if (region.timer) { clearTimeout(region.timer); region.timer = null; }\n }\n this._stopAllRegionWidgets(overlayState.regions,\n (rid, idx) => this.stopOverlayWidget(layoutId, rid, idx));\n\n // Remove overlay container from DOM\n if (overlayState.container) {\n overlayState.container.remove();\n }\n\n // Revoke blob URLs for this overlay\n this.revokeBlobUrlsForLayout(layoutId);\n\n // Remove from active overlays\n this.activeOverlays.delete(layoutId);\n\n // Emit overlay end event\n this.emit('overlayEnd', layoutId);\n\n this.log.info(`Overlay ${layoutId} stopped`);\n }\n\n /**\n * Stop all active overlays\n */\n stopAllOverlays() {\n const overlayIds = Array.from(this.activeOverlays.keys());\n for (const overlayId of overlayIds) {\n this.stopOverlay(overlayId);\n }\n this.log.info('All overlays stopped');\n }\n\n /**\n * Get active overlay IDs\n * @returns {Array<number>}\n */\n getActiveOverlays() {\n return Array.from(this.activeOverlays.keys());\n }\n\n /**\n * Pause playback: pause all media, stop widget cycling.\n * The layout timer keeps running — schedule is authoritative.\n */\n pause() {\n if (this._paused) return;\n this._paused = true;\n\n // Stop all region widget-cycling timers\n for (const [, region] of this.regions) {\n if (region.timer) {\n clearTimeout(region.timer);\n region.timer = null;\n }\n }\n\n // Pause all video/audio elements\n this._forEachMedia(el => el.pause());\n\n this.emit('paused');\n this.log.info('Playback paused (layout timer continues)');\n }\n\n /**\n * Check if playback is currently paused.\n */\n isPaused() {\n return this._paused;\n }\n\n /**\n * Resume playback: resume media and widget cycling.\n * Layout timer was never paused — no need to restore it.\n */\n resume() {\n if (!this._paused) return;\n this._paused = false;\n\n // Resume all video/audio\n this._forEachMedia(el => el.play().catch(() => {}));\n\n // Restart region widget cycling (re-enters cycle from current widget)\n for (const [regionId] of this.regions) {\n this.startRegion(regionId);\n }\n\n this.emit('resumed');\n this.log.info('Playback resumed');\n }\n\n /**\n * Apply a function to every video/audio element in all regions.\n */\n _forEachMedia(fn) {\n for (const [, region] of this.regions) {\n region.element?.querySelectorAll('video, audio').forEach(fn);\n }\n }\n\n /**\n * Cleanup renderer\n */\n cleanup() {\n this.stopAllOverlays();\n this.stopCurrentLayout();\n this._startedWidgets.clear();\n\n // Clean up any remaining audio overlays\n for (const widgetId of this.audioOverlays.keys()) {\n this._stopAudioOverlays(widgetId);\n }\n\n // Clear the layout preload pool\n this.layoutPool.clear();\n\n if (this.preloadTimer) {\n clearTimeout(this.preloadTimer);\n this.preloadTimer = null;\n }\n if (this._preloadRetryTimer) {\n clearTimeout(this._preloadRetryTimer);\n this._preloadRetryTimer = null;\n }\n\n if (this.resizeObserver) {\n this.resizeObserver.disconnect();\n this.resizeObserver = null;\n }\n\n // Release xp:state subscription so the store can be garbage-collected\n // independently of the renderer (Track B wiring; the store is owned\n // by the host app, the renderer only listens).\n if (this._stateUnsubscribe) {\n try { this._stateUnsubscribe(); } catch (_err) { /* best effort */ }\n this._stateUnsubscribe = null;\n }\n this._stateStore = null;\n\n this.container.innerHTML = '';\n this.log.info('Cleaned up');\n }\n}\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2024-2026 Pau Aliagas <linuxnow@gmail.com>\n/**\n * Layout translator - XLF to HTML\n * Based on arexibo layout.rs\n */\n\nimport { cacheWidgetHtml } from '@xiboplayer/cache';\nimport { createLogger, isDebug, PLAYER_API } from '@xiboplayer/utils';\n\nconst log = createLogger('Layout');\n\n// ── Safe interpolation helpers for HTML generation ─────────────────────────\n// Use these instead of raw ${value} in template literals to prevent XSS.\n// Our LayoutTranslator generates complete HTML documents as strings (unlike\n// upstream Xibo players that use DOM APIs). This gives us pre-rendering and\n// cross-context support (Node.js, arexibo, service worker) but requires\n// manual sanitization at every interpolation point.\nconst SAFE_CSS_COLOR = /^(#[0-9a-fA-F]{3,8}|rgba?\\(\\s*[\\d.,\\s%]+\\)|[a-zA-Z]{1,20}|transparent|inherit)$/;\nexport const safeCssColor = (v) => SAFE_CSS_COLOR.test(v) ? v : '#000000';\nexport const safeJsString = (v) => JSON.stringify(v);\nexport const safeHtmlAttr = (v) => String(v).replace(/[&\"'<>]/g, c =>\n ({ '&': '&amp;', '\"': '&quot;', \"'\": '&#39;', '<': '&lt;', '>': '&gt;' }[c]));\n\nexport class LayoutTranslator {\n constructor(xmds) {\n this.xmds = xmds;\n }\n\n /**\n * Translate XLF XML to playable HTML\n */\n async translateXLF(layoutId, xlfXml) {\n const parser = new DOMParser();\n const doc = parser.parseFromString(xlfXml, 'text/xml');\n\n const layoutEl = doc.querySelector('layout');\n if (!layoutEl) {\n throw new Error('Invalid XLF: no <layout> element');\n }\n\n const width = parseInt(layoutEl.getAttribute('width') || '1920');\n const height = parseInt(layoutEl.getAttribute('height') || '1080');\n const bgcolor = safeCssColor(layoutEl.getAttribute('bgcolor') || '#000000');\n\n const regions = [];\n for (const regionEl of doc.querySelectorAll('region')) {\n regions.push(await this.translateRegion(layoutId, regionEl));\n }\n\n return this.generateHTML(width, height, bgcolor, regions);\n }\n\n /**\n * Translate a single region\n */\n async translateRegion(layoutId, regionEl) {\n const id = regionEl.getAttribute('id');\n const width = parseInt(regionEl.getAttribute('width'));\n const height = parseInt(regionEl.getAttribute('height'));\n const top = parseInt(regionEl.getAttribute('top'));\n const left = parseInt(regionEl.getAttribute('left'));\n const zindex = parseInt(regionEl.getAttribute('zindex') || '0');\n\n const media = [];\n for (const mediaEl of regionEl.querySelectorAll('media')) {\n media.push(await this.translateMedia(layoutId, id, mediaEl));\n }\n\n return {\n id,\n width,\n height,\n top,\n left,\n zindex,\n media\n };\n }\n\n /**\n * Translate a single media item\n */\n async translateMedia(layoutId, regionId, mediaEl) {\n const type = mediaEl.getAttribute('type');\n const duration = parseInt(mediaEl.getAttribute('duration') || '10');\n const id = mediaEl.getAttribute('id');\n\n const optionsEl = mediaEl.querySelector('options');\n const rawEl = mediaEl.querySelector('raw');\n\n const options = {};\n if (optionsEl) {\n for (const child of optionsEl.children) {\n options[child.tagName] = child.textContent;\n }\n }\n\n // Parse transition information\n const transitions = {\n in: null,\n out: null\n };\n\n const transInEl = mediaEl.querySelector('options > transIn');\n const transOutEl = mediaEl.querySelector('options > transOut');\n const transInDurationEl = mediaEl.querySelector('options > transInDuration');\n const transOutDurationEl = mediaEl.querySelector('options > transOutDuration');\n const transInDirectionEl = mediaEl.querySelector('options > transInDirection');\n const transOutDirectionEl = mediaEl.querySelector('options > transOutDirection');\n\n if (transInEl && transInEl.textContent) {\n transitions.in = {\n type: transInEl.textContent,\n duration: parseInt(transInDurationEl?.textContent || '1000'),\n direction: transInDirectionEl?.textContent || 'N'\n };\n }\n\n if (transOutEl && transOutEl.textContent) {\n transitions.out = {\n type: transOutEl.textContent,\n duration: parseInt(transOutDurationEl?.textContent || '1000'),\n direction: transOutDirectionEl?.textContent || 'N'\n };\n }\n\n // All videos use cache URL pattern\n // Large videos download in background, small videos are already cached\n // Service Worker handles both cases appropriately\n\n let raw = rawEl ? rawEl.textContent : '';\n\n // For widgets (clock, calendar, etc.), fetch rendered HTML from CMS\n const widgetTypes = ['clock', 'clock-digital', 'clock-analogue', 'calendar', 'weather',\n 'currencies', 'stocks', 'twitter', 'global', 'embedded', 'text', 'ticker'];\n if (widgetTypes.some(w => type.includes(w))) {\n // Try to get widget HTML with retry logic for kiosk reliability\n const retries = 3;\n let lastError = null;\n\n for (let attempt = 1; attempt <= retries; attempt++) {\n try {\n log.info(`Fetching resource for ${type} widget (layout=${layoutId}, region=${regionId}, media=${id}) - attempt ${attempt}/${retries}`);\n raw = await this.xmds.getResource(layoutId, regionId, id);\n log.info(`Got resource HTML (${raw.length} chars)`);\n\n // Store widget HTML in cache and save cache key for iframe src generation\n const widgetCacheKey = await cacheWidgetHtml(layoutId, regionId, id, raw);\n options.widgetCacheKey = widgetCacheKey;\n\n // Success - break retry loop\n break;\n\n } catch (error) {\n lastError = error;\n log.warn(`Failed to get resource (attempt ${attempt}/${retries}):`, error.message);\n\n // If not last attempt, wait before retry\n if (attempt < retries) {\n const delay = attempt * 2000; // 2s, 4s backoff\n log.info(`Retrying in ${delay}ms...`);\n await new Promise(resolve => setTimeout(resolve, delay));\n }\n }\n }\n\n // If all retries failed, try to use cached version as fallback\n if (!raw && lastError) {\n log.warn('All retries failed, checking for cached widget HTML...');\n\n // Try to get cached widget HTML from ContentStore via proxy\n try {\n const resp = await fetch(`/store${PLAYER_API}/widgets/${layoutId}/${regionId}/${id}`);\n if (resp.ok) {\n raw = await resp.text();\n options.widgetCacheKey = `${PLAYER_API}/widgets/${layoutId}/${regionId}/${id}`;\n log.info(`Using stored widget HTML (${raw.length} chars) - CMS update pending`);\n } else {\n log.error(`No stored version available for widget ${id}`);\n raw = `<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;\">Content updating...</div>`;\n }\n } catch (storeError) {\n log.error('Store fallback failed:', storeError);\n raw = `<div style=\"display:flex;align-items:center;justify-content:center;height:100%;color:#999;font-size:18px;\">Content updating...</div>`;\n }\n }\n }\n\n return {\n type,\n duration,\n id,\n options,\n raw,\n transitions\n };\n }\n\n /**\n * Generate HTML from parsed layout\n */\n generateHTML(width, height, bgcolor, regions) {\n const regionHTML = regions.map(r => this.generateRegionHTML(r)).join('\\n');\n const regionJS = regions.map(r => this.generateRegionJS(r)).join(',\\n');\n\n return `<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=${width}, height=${height}\">\n <style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n html, body { width: 100%; height: 100%; overflow: hidden; }\n body { background-color: ${bgcolor}; }\n .region {\n position: absolute;\n overflow: hidden;\n }\n .media {\n width: 100%;\n height: 100%;\n object-fit: contain;\n }\n iframe {\n border: none;\n width: 100%;\n height: 100%;\n }\n </style>\n</head>\n<body>\n${regionHTML}\n<script>\n// Transition utilities\nwindow.Transitions = {\n fadeIn(element, duration) {\n const keyframes = [\n { opacity: 0 },\n { opacity: 1 }\n ];\n const timing = {\n duration: duration,\n easing: 'linear',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n fadeOut(element, duration) {\n const keyframes = [\n { opacity: 1 },\n { opacity: 0 }\n ];\n const timing = {\n duration: duration,\n easing: 'linear',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n getFlyKeyframes(direction, width, height, isIn) {\n const dirMap = {\n 'N': { x: 0, y: isIn ? -height : height },\n 'NE': { x: isIn ? width : -width, y: isIn ? -height : height },\n 'E': { x: isIn ? width : -width, y: 0 },\n 'SE': { x: isIn ? width : -width, y: isIn ? height : -height },\n 'S': { x: 0, y: isIn ? height : -height },\n 'SW': { x: isIn ? -width : width, y: isIn ? height : -height },\n 'W': { x: isIn ? -width : width, y: 0 },\n 'NW': { x: isIn ? -width : width, y: isIn ? -height : height }\n };\n\n const offset = dirMap[direction] || dirMap['N'];\n\n if (isIn) {\n return [\n { transform: \\`translate(\\${offset.x}px, \\${offset.y}px)\\`, opacity: 0 },\n { transform: 'translate(0, 0)', opacity: 1 }\n ];\n } else {\n return [\n { transform: 'translate(0, 0)', opacity: 1 },\n { transform: \\`translate(\\${offset.x}px, \\${offset.y}px)\\`, opacity: 0 }\n ];\n }\n },\n\n flyIn(element, duration, direction, regionWidth, regionHeight) {\n const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, true);\n const timing = {\n duration: duration,\n easing: 'ease-out',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n flyOut(element, duration, direction, regionWidth, regionHeight) {\n const keyframes = this.getFlyKeyframes(direction, regionWidth, regionHeight, false);\n const timing = {\n duration: duration,\n easing: 'ease-in',\n fill: 'forwards'\n };\n return element.animate(keyframes, timing);\n },\n\n apply(element, transitionConfig, isIn, regionWidth, regionHeight) {\n if (!transitionConfig || !transitionConfig.type) {\n return null;\n }\n\n const type = transitionConfig.type.toLowerCase();\n const duration = transitionConfig.duration || 1000;\n const direction = transitionConfig.direction || 'N';\n\n switch (type) {\n case 'fade':\n return isIn ? this.fadeIn(element, duration) : this.fadeOut(element, duration);\n case 'fadein':\n return isIn ? this.fadeIn(element, duration) : null;\n case 'fadeout':\n return isIn ? null : this.fadeOut(element, duration);\n case 'fly':\n return isIn\n ? this.flyIn(element, duration, direction, regionWidth, regionHeight)\n : this.flyOut(element, duration, direction, regionWidth, regionHeight);\n case 'flyin':\n return isIn ? this.flyIn(element, duration, direction, regionWidth, regionHeight) : null;\n case 'flyout':\n return isIn ? null : this.flyOut(element, duration, direction, regionWidth, regionHeight);\n default:\n return null;\n }\n }\n};\n\nconst regions = {\n${regionJS}\n};\n\n// Auto-start all regions\nObject.keys(regions).forEach(id => {\n playRegion(id);\n});\n\n// Track active timers per region so layout teardown can cancel them\nconst regionTimers = {};\n\nfunction playRegion(id) {\n const region = regions[id];\n if (!region || region.media.length === 0) return;\n\n regionTimers[id] = [];\n\n // If only one media item, just show it and don't cycle (arexibo behavior)\n if (region.media.length === 1) {\n const media = region.media[0];\n if (media.start) media.start();\n return; // Don't schedule stop/restart\n }\n\n // Multiple media items - cycle normally\n let currentIndex = 0;\n\n function playNext() {\n const media = region.media[currentIndex];\n if (media.start) media.start();\n\n const duration = media.duration || 10;\n const timerId = setTimeout(() => {\n if (media.stop) media.stop();\n currentIndex = (currentIndex + 1) % region.media.length;\n playNext();\n }, duration * 1000);\n regionTimers[id].push(timerId);\n }\n\n playNext();\n}\n\n// Cleanup function — called before layout teardown\nwindow._stopAllRegions = function() {\n Object.values(regionTimers).forEach(timers => timers.forEach(t => clearTimeout(t)));\n};\n</script>\n</body>\n</html>`;\n }\n\n /**\n * Generate HTML for a region container\n */\n generateRegionHTML(region) {\n return ` <div id=\"region_${region.id}\" class=\"region\" style=\"\n left: ${region.left}px;\n top: ${region.top}px;\n width: ${region.width}px;\n height: ${region.height}px;\n z-index: ${region.zindex};\n \"></div>`;\n }\n\n /**\n * Generate JavaScript for region media control\n */\n generateRegionJS(region) {\n const mediaJS = region.media.map(m => this.generateMediaJS(m, region.id)).join(',\\n ');\n\n return ` '${region.id}': {\n media: [\n${mediaJS}\n ]\n }`;\n }\n\n /**\n * Generate iframe widget JS for text/ticker and generic widget types.\n * Returns { startFn, stopFn } strings for the media item.\n */\n _generateIframeWidgetJS(regionId, mediaId, widgetUrl, transIn, transOut) {\n const iframeId = `widget_${regionId}_${mediaId}`;\n const startFn = `() => {\n const region = document.getElementById('region_${regionId}');\n let iframe = document.getElementById('${iframeId}');\n if (!iframe) {\n iframe = document.createElement('iframe');\n iframe.id = '${iframeId}';\n iframe.src = ${safeJsString(widgetUrl)};\n iframe.style.width = '100%';\n iframe.style.height = '100%';\n iframe.style.border = 'none';\n iframe.scrolling = 'no';\n iframe.style.opacity = '0';\n region.innerHTML = '';\n region.appendChild(iframe);\n\n // Apply transition after iframe loads\n iframe.onload = () => {\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(iframe, transIn, true, regionRect.width, regionRect.height);\n } else {\n iframe.style.opacity = '1';\n }\n };\n } else {\n iframe.style.display = 'block';\n iframe.style.opacity = '1';\n }\n }`;\n const stopFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const iframe = document.getElementById('${iframeId}');\n if (iframe) {\n const transOut = ${transOut};\n if (transOut && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n const animation = window.Transitions.apply(iframe, transOut, false, regionRect.width, regionRect.height);\n if (animation) {\n animation.onfinish = () => {\n iframe.style.display = 'none';\n };\n return;\n }\n }\n iframe.style.display = 'none';\n }\n }`;\n return { startFn, stopFn };\n }\n\n /**\n * Generate JavaScript for a single media item\n */\n generateMediaJS(media, regionId) {\n const duration = media.duration || 10;\n const transIn = media.transitions?.in ? JSON.stringify(media.transitions.in) : 'null';\n const transOut = media.transitions?.out ? JSON.stringify(media.transitions.out) : 'null';\n let startFn = 'null';\n let stopFn = 'null';\n\n switch (media.type) {\n case 'image': {\n // Use absolute URL within service worker scope\n const imageSrc = `${window.location.origin}${PLAYER_API}/media/${media.options.uri}`;\n startFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const img = document.createElement('img');\n img.className = 'media';\n img.src = ${safeJsString(imageSrc)};\n img.style.opacity = '0';\n region.innerHTML = '';\n region.appendChild(img);\n\n // Apply transition\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(img, transIn, true, regionRect.width, regionRect.height);\n } else {\n img.style.opacity = '1';\n }\n }`;\n break;\n }\n\n case 'video': {\n // All videos use cache URL pattern\n // Background-downloaded videos will auto-reload when cache completes\n const videoSrc = `${window.location.origin}${PLAYER_API}/media/${media.options.uri}`;\n const videoFilename = media.options.uri;\n\n startFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const video = document.createElement('video');\n video.className = 'media';\n video.src = ${safeJsString(videoSrc)};\n video.dataset.filename = ${safeJsString(videoFilename)};\n video.autoplay = true;\n video.muted = ${media.options.mute === '1' ? 'true' : 'false'};\n video.loop = false;\n video.style.width = '100%';\n video.style.height = '100%';\n video.style.objectFit = 'contain';\n video.style.opacity = '0';\n\n // Retry loading if cache completes while video is playing\n const retryOnCache = (event) => {\n if (event.detail.filename === ${safeJsString(videoFilename)} && video.error) {\n console.log('[Video] Cache complete, reloading:', '${videoFilename}');\n video.load();\n video.play();\n }\n };\n video._retryOnCache = retryOnCache;\n window.addEventListener('media-cached', retryOnCache);\n\n region.innerHTML = '';\n region.appendChild(video);\n\n // Apply transition\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(video, transIn, true, regionRect.width, regionRect.height);\n } else {\n video.style.opacity = '1';\n }\n\n console.log('[Video] Playing:', '${media.options.uri}');\n }`;\n stopFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const video = document.querySelector('#region_${regionId} video');\n if (video) {\n // Remove global media-cached listener to prevent leak\n if (video._retryOnCache) {\n window.removeEventListener('media-cached', video._retryOnCache);\n video._retryOnCache = null;\n }\n const transOut = ${transOut};\n if (transOut && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n const animation = window.Transitions.apply(video, transOut, false, regionRect.width, regionRect.height);\n if (animation) {\n animation.onfinish = () => {\n video.pause();\n video.remove();\n };\n return;\n }\n }\n video.pause();\n video.remove();\n }\n }`;\n break;\n }\n\n case 'text':\n case 'ticker':\n // Text/ticker widgets use the same iframe pattern as default widgets.\n // If no widgetCacheKey, fall through to the default case which handles unsupported types.\n if (media.options.widgetCacheKey) {\n const textUrl = `${window.location.origin}${media.options.widgetCacheKey}`;\n const iframe = this._generateIframeWidgetJS(regionId, media.id, textUrl, transIn, transOut);\n startFn = iframe.startFn;\n stopFn = iframe.stopFn;\n break;\n }\n // Fall through to default (handles missing widgetCacheKey as unsupported)\n\n case 'audio': {\n const audioSrc = `${window.location.origin}${PLAYER_API}/media/${media.options.uri}`;\n const audioId = `audio_${regionId}_${media.id}`;\n const audioLoop = media.options.loop === '1';\n const audioVolume = (parseInt(media.options.volume || '100') / 100).toFixed(2);\n\n startFn = `() => {\n const region = document.getElementById('region_${regionId}');\n\n // Create audio element\n const audio = document.createElement('audio');\n audio.id = '${audioId}';\n audio.className = 'media';\n audio.src = ${safeJsString(audioSrc)};\n audio.autoplay = true;\n audio.loop = ${audioLoop};\n audio.volume = ${audioVolume};\n\n // Create visual feedback container\n const visualContainer = document.createElement('div');\n visualContainer.className = 'audio-visual';\n visualContainer.style.cssText = \\`\n width: 100%;\n height: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n opacity: 0;\n \\`;\n\n // Audio icon\n const icon = document.createElement('div');\n icon.innerHTML = '♪';\n icon.style.cssText = \\`\n font-size: 120px;\n color: white;\n margin-bottom: 20px;\n animation: pulse 2s ease-in-out infinite;\n \\`;\n\n // Audio info\n const info = document.createElement('div');\n info.style.cssText = \\`\n color: white;\n font-size: 24px;\n text-align: center;\n padding: 0 20px;\n \\`;\n info.textContent = 'Playing Audio';\n\n // Filename\n const filename = document.createElement('div');\n filename.style.cssText = \\`\n color: rgba(255,255,255,0.7);\n font-size: 16px;\n margin-top: 10px;\n \\`;\n filename.textContent = '${media.options.uri}';\n\n visualContainer.appendChild(icon);\n visualContainer.appendChild(info);\n visualContainer.appendChild(filename);\n\n region.innerHTML = '';\n region.appendChild(audio);\n region.appendChild(visualContainer);\n\n // Add pulse animation\n const style = document.createElement('style');\n style.textContent = \\`\n @keyframes pulse {\n 0%, 100% { transform: scale(1); opacity: 1; }\n 50% { transform: scale(1.1); opacity: 0.8; }\n }\n \\`;\n document.head.appendChild(style);\n\n // Apply transition\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(visualContainer, transIn, true, regionRect.width, regionRect.height);\n } else {\n visualContainer.style.opacity = '1';\n }\n\n console.log('[Audio] Playing:', '${audioSrc}', 'Volume:', ${audioVolume}, 'Loop:', ${audioLoop});\n }`;\n\n stopFn = `() => {\n const audio = document.getElementById('${audioId}');\n if (audio) {\n audio.pause();\n audio.remove();\n }\n const region = document.getElementById('region_${regionId}');\n if (region) {\n const visualContainer = region.querySelector('.audio-visual');\n if (visualContainer) {\n const transOut = ${transOut};\n if (transOut && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n const animation = window.Transitions.apply(visualContainer, transOut, false, regionRect.width, regionRect.height);\n if (animation) {\n animation.onfinish = () => visualContainer.remove();\n return;\n }\n }\n visualContainer.remove();\n }\n }\n }`;\n break;\n }\n\n case 'pdf': {\n const pdfSrc = `${window.location.origin}${PLAYER_API}/media/${media.options.uri}`;\n const pdfContainerId = `pdf_${regionId}_${media.id}`;\n const pdfDuration = duration; // Total duration for entire PDF\n\n startFn = `async () => {\n const container = document.createElement('div');\n container.className = 'media pdf-container';\n container.id = '${pdfContainerId}';\n container.style.width = '100%';\n container.style.height = '100%';\n container.style.overflow = 'hidden';\n container.style.backgroundColor = '#525659';\n container.style.opacity = '0';\n container.style.position = 'relative';\n\n const region = document.getElementById('region_${regionId}');\n region.innerHTML = '';\n region.appendChild(container);\n\n // Load PDF.js if not already loaded\n if (typeof pdfjsLib === 'undefined') {\n try {\n const pdfjsModule = await import('pdfjs-dist');\n window.pdfjsLib = pdfjsModule;\n pdfjsLib.GlobalWorkerOptions.workerSrc = '${window.location.origin}/player/pdf.worker.min.mjs';\n } catch (error) {\n console.error('[PDF] Failed to load PDF.js:', error);\n container.innerHTML = '<div style=\"color:white;padding:20px;text-align:center;\">PDF viewer unavailable</div>';\n return;\n }\n }\n\n // Render PDF with multi-page support\n try {\n const loadingTask = pdfjsLib.getDocument(${safeJsString(pdfSrc)});\n const pdf = await loadingTask.promise;\n const totalPages = pdf.numPages;\n\n // Calculate time per page (distribute total duration across all pages)\n const timePerPage = (${pdfDuration} * 1000) / totalPages; // milliseconds per page\n\n console.log(\\`[PDF] Loading: \\${totalPages} pages, \\${timePerPage}ms per page\\`);\n\n const containerWidth = container.offsetWidth || ${width};\n const containerHeight = container.offsetHeight || ${height};\n\n // Create page indicator\n const pageIndicator = document.createElement('div');\n pageIndicator.className = 'pdf-page-indicator';\n pageIndicator.style.cssText = \\`\n position: absolute;\n bottom: 10px;\n right: 10px;\n background: rgba(0,0,0,0.7);\n color: white;\n padding: 8px 12px;\n border-radius: 4px;\n font-size: 14px;\n z-index: 10;\n display: ${isDebug() ? 'block' : 'none'};\n \\`;\n container.appendChild(pageIndicator);\n\n let currentPage = 1;\n let pageTimers = [];\n\n // Function to render a single page\n async function renderPage(pageNum) {\n const page = await pdf.getPage(pageNum);\n const viewport = page.getViewport({ scale: 1 });\n\n // Calculate scale to fit page within container\n const scaleX = containerWidth / viewport.width;\n const scaleY = containerHeight / viewport.height;\n const scale = Math.min(scaleX, scaleY);\n\n const scaledViewport = page.getViewport({ scale });\n\n const canvas = document.createElement('canvas');\n canvas.className = 'pdf-page';\n const context = canvas.getContext('2d');\n canvas.width = scaledViewport.width;\n canvas.height = scaledViewport.height;\n\n // Center canvas in container\n canvas.style.cssText = \\`\n display: block;\n margin: auto;\n margin-top: \\${Math.max(0, (containerHeight - scaledViewport.height) / 2)}px;\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%);\n opacity: 0;\n transition: opacity 0.5s ease-in-out;\n \\`;\n\n container.appendChild(canvas);\n\n await page.render({\n canvasContext: context,\n viewport: scaledViewport\n }).promise;\n\n // Fade in new page\n setTimeout(() => canvas.style.opacity = '1', 50);\n\n return canvas;\n }\n\n // Function to cycle through pages\n async function cyclePage() {\n // Update page indicator\n pageIndicator.textContent = \\`Page \\${currentPage} / \\${totalPages}\\`;\n\n // Remove old pages\n const oldPages = container.querySelectorAll('.pdf-page');\n oldPages.forEach(oldPage => {\n if (oldPage !== container.lastChild) {\n oldPage.style.opacity = '0';\n setTimeout(() => oldPage.remove(), 500);\n }\n });\n\n // Render current page\n await renderPage(currentPage);\n\n console.log(\\`[PDF] Showing page \\${currentPage}/\\${totalPages}\\`);\n\n // Schedule next page\n if (totalPages > 1) {\n const timer = setTimeout(() => {\n currentPage = currentPage >= totalPages ? 1 : currentPage + 1;\n cyclePage();\n }, timePerPage);\n pageTimers.push(timer);\n }\n }\n\n // Store live timer array on element for cleanup (not JSON — stays current)\n container._pageTimers = pageTimers;\n\n // Start cycling\n await cyclePage();\n\n // Apply transition to container\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(container, transIn, true, regionRect.width, regionRect.height);\n } else {\n container.style.opacity = '1';\n }\n\n } catch (error) {\n console.error('[PDF] Render failed:', error);\n container.innerHTML = '<div style=\"color:white;padding:20px;text-align:center;\">Failed to load PDF</div>';\n container.style.opacity = '1';\n }\n }`;\n\n stopFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const container = document.getElementById('${pdfContainerId}');\n if (container) {\n // Clear page cycling timers (live array, always current)\n if (container._pageTimers) {\n container._pageTimers.forEach(t => clearTimeout(t));\n container._pageTimers.length = 0;\n }\n\n const transOut = ${transOut};\n if (transOut && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n const animation = window.Transitions.apply(container, transOut, false, regionRect.width, regionRect.height);\n if (animation) {\n animation.onfinish = () => {\n container.remove();\n };\n return;\n }\n }\n container.remove();\n }\n }`;\n break;\n }\n\n case 'webpage': {\n const url = decodeURIComponent(media.options.uri || '');\n startFn = `() => {\n const region = document.getElementById('region_${regionId}');\n const iframe = document.createElement('iframe');\n iframe.src = ${safeJsString(url)};\n iframe.style.opacity = '0';\n region.innerHTML = '';\n region.appendChild(iframe);\n\n // Apply transition after iframe loads\n iframe.onload = () => {\n const transIn = ${transIn};\n if (transIn && window.Transitions) {\n const regionRect = region.getBoundingClientRect();\n window.Transitions.apply(iframe, transIn, true, regionRect.width, regionRect.height);\n } else {\n iframe.style.opacity = '1';\n }\n };\n }`;\n break;\n }\n\n default:\n // Widgets (clock, calendar, weather, etc.) - use cache URL pattern in /player/ scope for SW\n // Keep widget iframes alive across duration cycles (arexibo behavior)\n if (media.options.widgetCacheKey) {\n const widgetUrl = `${window.location.origin}${media.options.widgetCacheKey}`;\n const iframe = this._generateIframeWidgetJS(regionId, media.id, widgetUrl, transIn, transOut);\n startFn = iframe.startFn;\n stopFn = iframe.stopFn;\n } else {\n log.warn(`Unsupported media type: ${media.type}`);\n startFn = `() => console.log('Unsupported media type: ${media.type}')`;\n }\n }\n\n return ` {\n start: ${startFn},\n stop: ${stopFn},\n duration: ${duration}\n }`;\n }\n}\n\n","// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (c) 2024-2026 Pau Aliagas <linuxnow@gmail.com>\n// @xiboplayer/renderer - Layout rendering\nimport pkg from '../package.json' with { type: 'json' };\nexport const VERSION = pkg.version;\nexport { RendererLite } from './renderer-lite.js';\nexport { LayoutPool } from './layout-pool.js';\nexport { LayoutTranslator } from './layout.js';\n"],"file":"src-DK5BYonP.js"}