@xibosignage/xibo-layout-renderer 1.0.27 → 1.0.29

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.
@@ -20,30 +20,6 @@ function _isNativeReflectConstruct() {
20
20
  return !!t;
21
21
  })();
22
22
  }
23
- function _iterableToArrayLimit(r, l) {
24
- var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
25
- if (null != t) {
26
- var e,
27
- n,
28
- i,
29
- u,
30
- a = [],
31
- f = !0,
32
- o = !1;
33
- try {
34
- if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
35
- } catch (r) {
36
- o = !0, n = r;
37
- } finally {
38
- try {
39
- if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
40
- } finally {
41
- if (o) throw n;
42
- }
43
- }
44
- return a;
45
- }
46
- }
47
23
  function ownKeys(e, r) {
48
24
  var t = Object.keys(e);
49
25
  if (Object.getOwnPropertySymbols) {
@@ -522,18 +498,12 @@ function _possibleConstructorReturn(self, call) {
522
498
  }
523
499
  return _assertThisInitialized(self);
524
500
  }
525
- function _slicedToArray(arr, i) {
526
- return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest();
527
- }
528
501
  function _toConsumableArray(arr) {
529
502
  return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray$1(arr) || _nonIterableSpread();
530
503
  }
531
504
  function _arrayWithoutHoles(arr) {
532
505
  if (Array.isArray(arr)) return _arrayLikeToArray$1(arr);
533
506
  }
534
- function _arrayWithHoles(arr) {
535
- if (Array.isArray(arr)) return arr;
536
- }
537
507
  function _iterableToArray(iter) {
538
508
  if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
539
509
  }
@@ -553,9 +523,6 @@ function _arrayLikeToArray$1(arr, len) {
553
523
  function _nonIterableSpread() {
554
524
  throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
555
525
  }
556
- function _nonIterableRest() {
557
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
558
- }
559
526
  function _createForOfIteratorHelper(o, allowArrayLike) {
560
527
  var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
561
528
  if (!it) {
@@ -1062,6 +1029,13 @@ var initialXlr = {
1062
1029
  },
1063
1030
  gotoNextLayout: function gotoNextLayout() {},
1064
1031
  gotoPrevLayout: function gotoPrevLayout() {},
1032
+ gotoLayoutByCode: function gotoLayoutByCode(_layoutCode) {
1033
+ return Promise.resolve();
1034
+ },
1035
+ playInterruptLayout: function playInterruptLayout(_inputLayout) {
1036
+ return Promise.resolve();
1037
+ },
1038
+ triggerAction: function triggerAction(_triggerCode, _widgetId) {},
1065
1039
  init: function init() {
1066
1040
  return Promise.resolve({});
1067
1041
  },
@@ -1100,7 +1074,7 @@ var initialXlr = {
1100
1074
  renderOverlayLayouts: function renderOverlayLayouts() {
1101
1075
  return Promise.resolve();
1102
1076
  },
1103
- uniqueLayouts: {},
1077
+ uniqueLayouts: new Map(),
1104
1078
  updateInputLayout: function updateInputLayout(layoutIndex, layout) {},
1105
1079
  updateLayouts: function updateLayouts(inputLayouts) {},
1106
1080
  updateLoop: function updateLoop(inputLayouts) {
@@ -14020,11 +13994,11 @@ function normalizeLineEndings(input) {
14020
13994
  * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMParser
14021
13995
  * @see https://html.spec.whatwg.org/multipage/dynamic-markup-insertion.html#dom-parsing-and-serialization
14022
13996
  */
14023
- function DOMParser$1(options){
13997
+ function DOMParser$2(options){
14024
13998
  this.options = options ||{locator:{}};
14025
13999
  }
14026
14000
 
14027
- DOMParser$1.prototype.parseFromString = function(source,mimeType){
14001
+ DOMParser$2.prototype.parseFromString = function(source,mimeType){
14028
14002
  var options = this.options;
14029
14003
  var sax = new XMLReader();
14030
14004
  var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler
@@ -14271,9 +14245,9 @@ function appendElement (hander,node) {
14271
14245
 
14272
14246
  domParser.__DOMHandler = DOMHandler;
14273
14247
  domParser.normalizeLineEndings = normalizeLineEndings;
14274
- domParser.DOMParser = DOMParser$1;
14248
+ domParser.DOMParser = DOMParser$2;
14275
14249
 
14276
- var DOMParser = domParser.DOMParser;
14250
+ var DOMParser$1 = domParser.DOMParser;
14277
14251
 
14278
14252
  /*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */
14279
14253
 
@@ -16897,7 +16871,7 @@ const stringToMpdXml = manifestString => {
16897
16871
  throw new Error(errors.DASH_EMPTY_MANIFEST);
16898
16872
  }
16899
16873
 
16900
- const parser = new DOMParser();
16874
+ const parser = new DOMParser$1();
16901
16875
  let xml;
16902
16876
  let mpd;
16903
16877
 
@@ -73602,7 +73576,7 @@ function createMediaElement(mediaObject) {
73602
73576
  cssText += "\n visibility: hidden;\n opacity: 0;\n z-index: 0;\n ";
73603
73577
  }
73604
73578
  $media.style.cssText = cssText;
73605
- if (self.render === 'html' || self.mediaType === 'ticker' || self.mediaType === 'webpage') {
73579
+ if (self.mediaType !== 'spacer' && (self.render === 'html' || self.mediaType === 'ticker' || self.mediaType === 'webpage')) {
73606
73580
  self.checkIframeStatus = true;
73607
73581
  self.iframe = prepareIframe(self);
73608
73582
  } else if (self.mediaType === "image") {
@@ -73750,8 +73724,6 @@ function prepareAudioMedia(media, region) {
73750
73724
  region.html.appendChild(media.html);
73751
73725
  }
73752
73726
  function prepareHtmlMedia(media, region) {
73753
- // Set state as false ( for now )
73754
- media.ready = false;
73755
73727
  if (media.html) {
73756
73728
  var mediaId = getMediaId(media);
73757
73729
  // Clean up old copy of the media
@@ -73763,14 +73735,16 @@ function prepareHtmlMedia(media, region) {
73763
73735
  mediaId: mediaId,
73764
73736
  mediaInRegion: mediaInRegion
73765
73737
  });
73766
- // Append iframe
73767
- media.html.innerHTML = '';
73768
- media.html.appendChild(media.iframe);
73769
73738
  if (!mediaInRegion) {
73770
- // Add fresh copy of the media into the region using the direct reference
73739
+ // Append iframe and insert into region only when not already in the DOM.
73740
+ // Calling innerHTML = '' when the element is already present detaches the
73741
+ // iframe, causing the browser to reload its src unnecessarily (e.g. when
73742
+ // a media was preloaded then skipped by a navigation action).
73743
+ media.html.innerHTML = '';
73744
+ media.html.appendChild(media.iframe);
73771
73745
  region.html.appendChild(media.html);
73772
- media.ready = true;
73773
73746
  }
73747
+ media.ready = true;
73774
73748
  }
73775
73749
  }
73776
73750
  exports.FaultCodes = void 0;
@@ -74026,14 +74000,13 @@ var Media = /*#__PURE__*/function () {
74026
74000
  _this.startMediaTimer(media);
74027
74001
  }
74028
74002
  } else if (media.mediaType === 'shellcommand') {
74029
- if (_this.hasCommandExecuted && !_this.loop) {
74030
- return;
74031
- }
74032
- _this.hasCommandExecuted = true;
74033
- _this.emitCommand(media);
74034
- if (media.duration > 0) {
74035
- _this.startMediaTimer(media);
74003
+ // Fire once per cycle unless loop is enabled, in which case re-fire every play.
74004
+ if (!_this.hasCommandExecuted || _this.loop) {
74005
+ _this.hasCommandExecuted = true;
74006
+ _this.emitCommand(media);
74036
74007
  }
74008
+ // Always run the timer so the layout advances and stats are recorded correctly.
74009
+ _this.startMediaTimer(media);
74037
74010
  } else {
74038
74011
  _this.startMediaTimer(media);
74039
74012
  }
@@ -74079,7 +74052,12 @@ var Media = /*#__PURE__*/function () {
74079
74052
  var _this$sspImpressionUr, _this$sspErrorUrls;
74080
74053
  _this.xlr.emitter.emit('sspWidgetEnd', (_this$sspImpressionUr = _this.sspImpressionUrls) !== null && _this$sspImpressionUr !== void 0 ? _this$sspImpressionUr : [], (_this$sspErrorUrls = _this.sspErrorUrls) !== null && _this$sspErrorUrls !== void 0 ? _this$sspErrorUrls : [], _this.sspImpressionUrls ? _this.duration : 0);
74081
74054
  }
74082
- media.region.playNextMedia();
74055
+ // Only advance the region if this media is still the active one.
74056
+ // A user-triggered next/prev action may have already moved currMedia
74057
+ // on, in which case the timer firing here would cause a double-advance.
74058
+ if (media === media.region.currMedia) {
74059
+ media.region.playNextMedia();
74060
+ }
74083
74061
  });
74084
74062
  this.on('cancelled', function (media) {
74085
74063
  if (media.state === MediaState.CANCELLED) return;
@@ -74109,7 +74087,12 @@ var Media = /*#__PURE__*/function () {
74109
74087
  var _this$sspImpressionUr2, _this$sspErrorUrls2;
74110
74088
  _this.xlr.emitter.emit('sspWidgetEnd', (_this$sspImpressionUr2 = _this.sspImpressionUrls) !== null && _this$sspImpressionUr2 !== void 0 ? _this$sspImpressionUr2 : [], (_this$sspErrorUrls2 = _this.sspErrorUrls) !== null && _this$sspErrorUrls2 !== void 0 ? _this$sspErrorUrls2 : [], _this.sspImpressionUrls ? _this.duration : 0);
74111
74089
  }
74112
- media.region.playNextMedia();
74090
+ // Only advance the region if this media is still the active one.
74091
+ // A user-triggered next/prev action may have already moved currMedia
74092
+ // on, in which case the timer firing here would cause a double-advance.
74093
+ if (media === media.region.currMedia) {
74094
+ media.region.playNextMedia();
74095
+ }
74113
74096
  });
74114
74097
  // Initialize Media object
74115
74098
  this.init();
@@ -74118,6 +74101,10 @@ var Media = /*#__PURE__*/function () {
74118
74101
  key: "startMediaTimer",
74119
74102
  value: function startMediaTimer(media) {
74120
74103
  var _this2 = this;
74104
+ // Always reset the counter so a media replayed after cancellation runs
74105
+ // for its full duration rather than the residual time left from the
74106
+ // previous play.
74107
+ this.mediaTimeCount = 0;
74121
74108
  var preloadTimeMs = 2000;
74122
74109
  var preloadTimeBufferMs = media.duration * 1000 / 2 - preloadTimeMs;
74123
74110
  var isPreparingNextMedia = false;
@@ -74342,11 +74329,11 @@ var Media = /*#__PURE__*/function () {
74342
74329
  };
74343
74330
  var getNewMedia = function getNewMedia() {
74344
74331
  var $region = _this3.region.html;
74345
- // This function is for checking whether
74346
- // the region still has to show a media item
74347
- // when another region is not finished yet
74348
- if (_this3.region.complete && !_this3.region.layout.allEnded) {
74349
- // Add currentMedia to the region
74332
+ // Re-insert the element whenever it is missing from the DOM. This covers:
74333
+ // 1. Region completed early but the layout is still running (freeze last frame).
74334
+ // 2. Backward navigation the previous media's element was removed by the
74335
+ // removeOldMedia setTimeout in transitionNodes.
74336
+ if (_this3.html) {
74350
74337
  $region.insertBefore(_this3.html, $region.lastElementChild);
74351
74338
  return _this3.html;
74352
74339
  }
@@ -74612,15 +74599,21 @@ var Region = /*#__PURE__*/function () {
74612
74599
  });
74613
74600
  // Add media to region for targeted actions
74614
74601
  (_this$layout$actionCo = this.layout.actionController) === null || _this$layout$actionCo === void 0 || _this$layout$actionCo.actions.forEach(function (action) {
74602
+ var _ref2, _attributes$actionTyp, _ref3, _attributes$targetId, _ref4, _attributes$widgetId, _attributes$target;
74615
74603
  var attributes = getAllAttributes(action.xml);
74616
- if (attributes.target.value === 'region' && attributes.actionType.value === 'navWidget' && attributes.targetId.value == _this.id) {
74604
+ // getAllAttributes preserves the XML attribute case. Xibo CMS may export
74605
+ // attributes in camelCase (actionType, targetId, widgetId) or lowercase.
74606
+ // Read both variants so we work with either XLF format.
74607
+ var actionType = (_ref2 = (_attributes$actionTyp = attributes['actionType']) !== null && _attributes$actionTyp !== void 0 ? _attributes$actionTyp : attributes['actiontype']) === null || _ref2 === void 0 ? void 0 : _ref2.value;
74608
+ var targetId = (_ref3 = (_attributes$targetId = attributes['targetId']) !== null && _attributes$targetId !== void 0 ? _attributes$targetId : attributes['targetid']) === null || _ref3 === void 0 ? void 0 : _ref3.value;
74609
+ var widgetId = (_ref4 = (_attributes$widgetId = attributes['widgetId']) !== null && _attributes$widgetId !== void 0 ? _attributes$widgetId : attributes['widgetid']) === null || _ref4 === void 0 ? void 0 : _ref4.value;
74610
+ if (((_attributes$target = attributes['target']) === null || _attributes$target === void 0 ? void 0 : _attributes$target.value) === 'region' && actionType === 'navWidget' && targetId == _this.id) {
74617
74611
  var _this$layout$drawer;
74618
74612
  var drawerMediaItems = Array.from(((_this$layout$drawer = _this.layout.drawer) === null || _this$layout$drawer === void 0 ? void 0 : _this$layout$drawer.getElementsByTagName('media')) || []);
74619
74613
  drawerMediaItems.forEach(function (drawerMedia) {
74620
- var _attributes$widgetId;
74621
- if (drawerMedia.id === ((_attributes$widgetId = attributes.widgetId) === null || _attributes$widgetId === void 0 ? void 0 : _attributes$widgetId.value)) {
74614
+ if (drawerMedia.getAttribute('id') === widgetId) {
74622
74615
  // Add drawer media to the region
74623
- _this.mediaObjectsActions.push(new Media(_this, (drawerMedia === null || drawerMedia === void 0 ? void 0 : drawerMedia.getAttribute('id')) || '', drawerMedia, _this.options, _this.xlr));
74616
+ _this.mediaObjectsActions.push(new Media(_this, drawerMedia.getAttribute('id') || '', drawerMedia, _this.options, _this.xlr));
74624
74617
  }
74625
74618
  });
74626
74619
  }
@@ -74652,6 +74645,11 @@ var Region = /*#__PURE__*/function () {
74652
74645
  prepareAudioMedia(media, this);
74653
74646
  } else if ((media.render === 'html' || media.mediaType === 'webpage') && media.iframe && media.checkIframeStatus) {
74654
74647
  prepareHtmlMedia(media, this);
74648
+ } else if (media.mediaType === 'shellcommand') {
74649
+ // Shell command widgets are invisible but must be in the DOM to trigger playback.
74650
+ if (media.html) {
74651
+ this.html.appendChild(media.html);
74652
+ }
74655
74653
  }
74656
74654
  }
74657
74655
  }, {
@@ -74875,7 +74873,17 @@ var Region = /*#__PURE__*/function () {
74875
74873
  }, {
74876
74874
  key: "playNextMedia",
74877
74875
  value: function playNextMedia() {
74878
- var _this$oldMedia3, _this$currMedia2, _this$nxtMedia, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia4, _this$currMedia7, _this$nxtMedia2;
74876
+ var _this$oldMedia3,
74877
+ _this$currMedia2,
74878
+ _this$nxtMedia,
74879
+ _this$currMedia3,
74880
+ _this$currMedia4,
74881
+ _this$currMedia5,
74882
+ _this$currMedia6,
74883
+ _this3 = this,
74884
+ _this$oldMedia4,
74885
+ _this$currMedia7,
74886
+ _this$nxtMedia2;
74879
74887
  console.debug('??? XLR.debug Region playing next media', {
74880
74888
  regionId: this.id,
74881
74889
  currentMediaIndex: this.currentMediaIndex,
@@ -74895,8 +74903,10 @@ var Region = /*#__PURE__*/function () {
74895
74903
  // cycle completion after the skip loop runs.
74896
74904
  var origIndex = this.currentMediaIndex;
74897
74905
  // When the region has completed and when currentMedia is html
74898
- // Then, preserve the currentMedia state
74899
- if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html') {
74906
+ // and there is only one media object, preserve the currentMedia state.
74907
+ // Guard is limited to single-media regions so navWidget injections
74908
+ // (which splice a second media in) are not blocked.
74909
+ if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html' && this.totalMediaObjects === 1) {
74900
74910
  return;
74901
74911
  }
74902
74912
  // When the region has completed and mediaObjects.length = 1
@@ -74942,6 +74952,25 @@ var Region = /*#__PURE__*/function () {
74942
74952
  }
74943
74953
  });
74944
74954
  }
74955
+ // Remove single-play media injected via navWidget actions at the end of each cycle
74956
+ if (crossedEnd && this.mediaObjects.some(function (m) {
74957
+ return m.singlePlay;
74958
+ })) {
74959
+ this.mediaObjects = this.mediaObjects.filter(function (m) {
74960
+ return !m.singlePlay;
74961
+ });
74962
+ this.totalMediaObjects = this.mediaObjects.length;
74963
+ if (this.totalMediaObjects === 0) {
74964
+ this.finished();
74965
+ return;
74966
+ }
74967
+ var newIndex = this.mediaObjects.findIndex(function (m) {
74968
+ return m === _this3.currMedia;
74969
+ });
74970
+ this.currentMediaIndex = newIndex >= 0 ? newIndex : 0;
74971
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
74972
+ this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
74973
+ }
74945
74974
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74946
74975
  regionId: this.id,
74947
74976
  currentMediaIndex: this.currentMediaIndex,
@@ -74951,25 +74980,41 @@ var Region = /*#__PURE__*/function () {
74951
74980
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74952
74981
  });
74953
74982
  if (!this.layout.isOverlay && crossedEnd) {
74983
+ var _this$currMedia8;
74954
74984
  this.finished();
74955
74985
  if (this.layout.allEnded) {
74956
74986
  console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74957
74987
  return;
74958
74988
  }
74989
+ // Freeze single-media HTML at its last state while waiting for other
74990
+ // regions to complete. The guard at the top only catches the second
74991
+ // call; this one catches the first completion when complete was just
74992
+ // set by finished() above.
74993
+ if (((_this$currMedia8 = this.currMedia) === null || _this$currMedia8 === void 0 ? void 0 : _this$currMedia8.render) === 'html' && this.totalMediaObjects === 1 && this.oldMedia === this.currMedia) {
74994
+ return;
74995
+ }
74959
74996
  }
74960
74997
  this.transitionNodes(this.oldMedia, this.currMedia);
74961
74998
  }
74962
74999
  }, {
74963
75000
  key: "playPreviousMedia",
74964
75001
  value: function playPreviousMedia() {
74965
- this.currentMediaIndex = this.currentMediaIndex - 1;
74966
- if (this.currentMediaIndex < 0 || this.ended) {
74967
- this.currentMediaIndex = 0;
75002
+ if (this.currentMediaIndex <= 0 || this.ended) {
74968
75003
  return;
74969
75004
  }
74970
- this.prepareMediaObjects();
75005
+ var interruptedMedia = this.currMedia;
75006
+ this.oldMedia = this.currMedia;
75007
+ this.currentMediaIndex -= 1;
75008
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
75009
+ this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
75010
+ this.complete = false;
75011
+ // Cancel the interrupted media after advancing currMedia, using the same
75012
+ // pattern as gotoMediaInRegion — emitting after the update ensures the
75013
+ // handler's (media === currMedia) guard correctly skips playNextMedia.
75014
+ if ((interruptedMedia === null || interruptedMedia === void 0 ? void 0 : interruptedMedia.state) === MediaState.PLAYING) {
75015
+ interruptedMedia.emitter.emit('cancelled', interruptedMedia);
75016
+ }
74971
75017
  console.debug('region::playPreviousMedia', this);
74972
- /* Do the transition */
74973
75018
  this.transitionNodes(this.oldMedia, this.currMedia);
74974
75019
  }
74975
75020
  }, {
@@ -75046,6 +75091,7 @@ var ActionController = /*#__PURE__*/function () {
75046
75091
  _defineProperty(this, "$actionControllerTitle", void 0);
75047
75092
  _defineProperty(this, "$actionsContainer", void 0);
75048
75093
  _defineProperty(this, "translations", {});
75094
+ _defineProperty(this, "keyboardHandler", null);
75049
75095
  this.parent = parent;
75050
75096
  this.actions = actions;
75051
75097
  this.options = options;
@@ -75168,6 +75214,10 @@ var ActionController = /*#__PURE__*/function () {
75168
75214
  key: "openLayoutInNewTab",
75169
75215
  value: function openLayoutInNewTab(layoutCode, options) {
75170
75216
  var url = options.layoutPreviewUrl.replace('[layoutCode]', layoutCode) + '?findByCode=1';
75217
+ console.debug('[ActionController::openLayoutInNewTab] Navigating to layout in new tab with code', {
75218
+ layoutCode: layoutCode,
75219
+ url: url
75220
+ });
75171
75221
  // Send a postMessage to the parent frame so the CMS can handle the confirmation
75172
75222
  // and navigation (confirm() is blocked in sandboxed iframes without allow-modals).
75173
75223
  window.parent.postMessage({
@@ -75180,41 +75230,78 @@ var ActionController = /*#__PURE__*/function () {
75180
75230
  }
75181
75231
  }, {
75182
75232
  key: "openLayoutInPlayer",
75183
- value: function openLayoutInPlayer(layoutCode, options) {
75184
- // this.parent.xlr.updateLoop([]);
75233
+ value: function openLayoutInPlayer(layoutCode, _options) {
75234
+ console.debug('[ActionController::openLayoutInPlayer] Navigating to layout in player with code', {
75235
+ layoutCode: layoutCode,
75236
+ options: _options
75237
+ });
75238
+ this.parent.xlr.emitter.emit('navLayout', layoutCode, '');
75185
75239
  }
75186
75240
  }, {
75187
75241
  key: "prevOrNextLayout",
75188
75242
  value: function prevOrNextLayout(targetId, actionType) {
75189
75243
  var _this$parent$xlr$curr;
75190
- // Check if currentLayout is the targetId
75191
- if (((_this$parent$xlr$curr = this.parent.xlr.currentLayout) === null || _this$parent$xlr$curr === void 0 ? void 0 : _this$parent$xlr$curr.layoutId) === parseInt(targetId)) {
75192
- if (actionType === 'next') {
75193
- this.parent.xlr.gotoNextLayout();
75194
- } else if (actionType === 'previous') {
75195
- this.parent.xlr.gotoPrevLayout();
75196
- }
75244
+ console.debug('[ActionController::prevOrNextLayout] Changing layout with data', {
75245
+ targetId: targetId,
75246
+ actionType: actionType
75247
+ });
75248
+ // For screen-level actions targetId may be "0" (the screen has no numeric ID).
75249
+ // Guard using this.parent.layoutId instead so the check always works.
75250
+ if (((_this$parent$xlr$curr = this.parent.xlr.currentLayout) === null || _this$parent$xlr$curr === void 0 ? void 0 : _this$parent$xlr$curr.layoutId) !== this.parent.layoutId) {
75251
+ return;
75252
+ }
75253
+ if (actionType === 'next') {
75254
+ this.parent.xlr.gotoNextLayout();
75255
+ } else if (actionType === 'previous') {
75256
+ this.parent.xlr.gotoPrevLayout();
75197
75257
  }
75198
75258
  }
75199
- /** Change media in region (next/previous) */
75259
+ /** Change media in region (next/previous) with wrap-around at boundaries. */
75200
75260
  }, {
75201
- key: "nextMediaInRegion",
75202
- value: function nextMediaInRegion(regionId, actionType) {
75203
- // Find target region
75261
+ key: "gotoMediaInRegion",
75262
+ value: function gotoMediaInRegion(regionId, actionType) {
75263
+ console.debug('[ActionController::gotoMediaInRegion] Changing media in region with data', {
75264
+ regionId: regionId,
75265
+ actionType: actionType
75266
+ });
75204
75267
  this.parent.regions.forEach(function (regionObj) {
75205
- if (regionObj.id === regionId) {
75206
- if (actionType === 'next') {
75207
- regionObj.playNextMedia();
75208
- } else {
75209
- regionObj.playPreviousMedia();
75210
- }
75268
+ if (regionObj.id !== regionId || regionObj.ended) return;
75269
+ var total = regionObj.totalMediaObjects;
75270
+ if (total === 0) return;
75271
+ // Snapshot the currently-playing media before updating currMedia so
75272
+ // we can cancel it cleanly after the region state is advanced.
75273
+ var interruptedMedia = regionObj.currMedia;
75274
+ // Compute new index with wrap-around. We do NOT delegate to
75275
+ // playNextMedia() / playPreviousMedia() here because those carry
75276
+ // normal playlist-cycle semantics (finished(), regionExpired()) that
75277
+ // must not fire during user-driven navigation.
75278
+ var newIndex = actionType === 'next' ? (regionObj.currentMediaIndex + 1) % total : (regionObj.currentMediaIndex - 1 + total) % total;
75279
+ regionObj.oldMedia = regionObj.currMedia;
75280
+ regionObj.currentMediaIndex = newIndex;
75281
+ regionObj.currMedia = regionObj.mediaObjects[newIndex];
75282
+ regionObj.nxtMedia = regionObj.mediaObjects[(newIndex + 1) % total];
75283
+ regionObj.complete = false;
75284
+ // Properly cancel the interrupted media AFTER updating currMedia.
75285
+ // Using 'cancelled' rather than bare clearInterval ensures state is
75286
+ // reset from PLAYING and mediaTimeCount is zeroed — without this,
75287
+ // returning to a cancelled media causes run() → 'start' to bail on
75288
+ // the state === PLAYING guard, leaving the region stuck indefinitely.
75289
+ // currMedia is already updated so the handler's guard
75290
+ // (media === media.region.currMedia) correctly skips playNextMedia.
75291
+ if ((interruptedMedia === null || interruptedMedia === void 0 ? void 0 : interruptedMedia.state) === MediaState.PLAYING) {
75292
+ interruptedMedia.emitter.emit('cancelled', interruptedMedia);
75211
75293
  }
75294
+ regionObj.transitionNodes(regionObj.oldMedia, regionObj.currMedia);
75212
75295
  });
75213
75296
  }
75214
75297
  }, {
75215
75298
  key: "loadMediaInRegion",
75216
75299
  value: function loadMediaInRegion(regionId, widgetId) {
75217
- var _targetRegion, _targetRegion2, _targetRegion3;
75300
+ var _targetRegion, _targetRegion2, _targetRegion3, _targetRegion4;
75301
+ console.debug('[ActionController::loadMediaInRegion] Loading media in region with data', {
75302
+ regionId: regionId,
75303
+ widgetId: widgetId
75304
+ });
75218
75305
  var self = this;
75219
75306
  // Find target region
75220
75307
  var targetRegion;
@@ -75237,27 +75324,84 @@ var ActionController = /*#__PURE__*/function () {
75237
75324
  targetMedia.singlePlay = true;
75238
75325
  }
75239
75326
  // If region is empty, remove the background color and empty message
75240
- if (((_targetRegion = targetRegion) === null || _targetRegion === void 0 ? void 0 : _targetRegion.mediaObjects.length) === 0) {
75327
+ if (((_targetRegion = targetRegion) === null || _targetRegion === void 0 ? void 0 : _targetRegion.totalMediaObjects) === 0) {
75328
+ targetRegion.complete = false;
75329
+ }
75330
+ // Bail out early when the target widget was not found in the drawer
75331
+ if (!targetMedia) {
75332
+ console.debug('[ActionController::loadMediaInRegion] Target media not found in mediaObjectsActions', {
75333
+ regionId: regionId,
75334
+ widgetId: widgetId
75335
+ });
75336
+ return;
75337
+ }
75338
+ // Guard against duplicate insertion if the action fires multiple times before the widget plays
75339
+ if (targetRegion && targetRegion.mediaObjects.some(function (m) {
75340
+ return m.id === targetMedia.id;
75341
+ })) {
75342
+ console.debug('[ActionController::loadMediaInRegion] Target media already queued, skipping duplicate insertion');
75343
+ return;
75344
+ }
75345
+ // Cancel the interrupted media so it doesn't double-advance when the playlist
75346
+ // returns to it. currMedia has not been advanced yet at this point (playNextMedia
75347
+ // does that below), so we cannot use emitter.emit('cancelled') — the handler's
75348
+ // currMedia guard would fire and call playNextMedia a second time. Instead we
75349
+ // cancel directly: clear the timer and reset state so the 'start' handler does
75350
+ // not bail on the state === PLAYING guard when this media is replayed.
75351
+ if (((_targetRegion2 = targetRegion) === null || _targetRegion2 === void 0 || (_targetRegion2 = _targetRegion2.currMedia) === null || _targetRegion2 === void 0 ? void 0 : _targetRegion2.state) === MediaState.PLAYING) {
75352
+ var interruptedMedia = targetRegion.currMedia;
75353
+ if (interruptedMedia.mediaTimer) {
75354
+ clearInterval(interruptedMedia.mediaTimer);
75355
+ interruptedMedia.mediaTimer = undefined;
75356
+ }
75357
+ interruptedMedia.state = MediaState.CANCELLED;
75358
+ }
75359
+ // Reset complete so the HTML-media guard in playNextMedia() doesn't block
75360
+ // the transition (that guard is for single-media loops, not navWidget injections).
75361
+ if (targetRegion) {
75241
75362
  targetRegion.complete = false;
75242
75363
  }
75243
75364
  // Create media in region and play it next
75244
- (_targetRegion2 = targetRegion) === null || _targetRegion2 === void 0 || _targetRegion2.mediaObjects.splice(targetRegion.currentMediaIndex + 1, 0, targetMedia);
75245
- (_targetRegion3 = targetRegion) === null || _targetRegion3 === void 0 || _targetRegion3.playNextMedia();
75365
+ (_targetRegion3 = targetRegion) === null || _targetRegion3 === void 0 || _targetRegion3.mediaObjects.splice(targetRegion.currentMediaIndex + 1, 0, targetMedia);
75366
+ // Keep totalMediaObjects in sync with the actual array length
75367
+ if (targetRegion) {
75368
+ targetRegion.totalMediaObjects = targetRegion.mediaObjects.length;
75369
+ }
75370
+ // Drawer media items are never run through the normal prepareMedia pipeline,
75371
+ // so their DOM element has no background-image / src set and is not yet in the
75372
+ // region DOM. Prepare it now so Media.run() finds a ready element to show.
75373
+ if (targetRegion) {
75374
+ targetRegion.prepareMedia(targetMedia);
75375
+ }
75376
+ console.debug('[ActionController::loadMediaInRegion] Target media loaded, playing next', {
75377
+ regionId: regionId,
75378
+ widgetId: widgetId
75379
+ });
75380
+ (_targetRegion4 = targetRegion) === null || _targetRegion4 === void 0 || _targetRegion4.playNextMedia();
75246
75381
  }
75247
75382
  /** Run action based on action data */
75248
75383
  }, {
75249
75384
  key: "runAction",
75250
75385
  value: function runAction(actionData, options) {
75386
+ // If this layout is no longer active (being cancelled or navigated away from),
75387
+ // discard the action so it doesn't interfere with the outgoing transition.
75388
+ // inLoop is set to false synchronously before finishAllRegions() in all nav paths.
75389
+ if (!this.parent.inLoop) {
75390
+ return;
75391
+ }
75392
+ console.debug('[ActionController::runAction] Triggering action', {
75393
+ actionData: actionData
75394
+ });
75251
75395
  if (actionData.actiontype == 'navLayout') {
75252
75396
  if (this.parent.xlr.config.platform === exports.ConsumerPlatform.CMS) {
75253
- // Open layout preview in a new tab
75397
+ // Open layout preview in a new tab (CMS preview only)
75254
75398
  this.openLayoutInNewTab(actionData.layoutcode, options);
75255
- } else if (this.parent.xlr.config.platform === exports.ConsumerPlatform.CHROMEOS) {
75256
- // Set target layout as active layout
75399
+ } else {
75400
+ // All player platforms (Electron, ChromeOS, Android, etc.)
75257
75401
  this.openLayoutInPlayer(actionData.layoutcode, options);
75258
75402
  }
75259
75403
  } else if ((actionData.actiontype == 'previous' || actionData.actiontype == 'next') && actionData.target == 'region') {
75260
- this.nextMediaInRegion(actionData.targetid, actionData.actiontype);
75404
+ this.gotoMediaInRegion(actionData.targetid, actionData.actiontype);
75261
75405
  } else if (actionData.actiontype == 'navWidget' && actionData.target == 'region') {
75262
75406
  this.loadMediaInRegion(actionData.targetid, actionData.widgetid);
75263
75407
  } else if (actionData.target === 'screen') {
@@ -75316,35 +75460,53 @@ var ActionController = /*#__PURE__*/function () {
75316
75460
  }
75317
75461
  });
75318
75462
  }
75463
+ /** Dispatch an incoming webhook trigger to any matching actions on this layout. */
75464
+ }, {
75465
+ key: "handleWebhookTrigger",
75466
+ value: function handleWebhookTrigger(triggerCode, widgetId) {
75467
+ var _this3 = this;
75468
+ this.$actionController.querySelectorAll('.action[triggertype="webhook"]').forEach(function ($el) {
75469
+ var ds = $el.dataset;
75470
+ if (ds.triggercode !== triggerCode) return;
75471
+ if (widgetId && ds.sourceid !== widgetId) return;
75472
+ _this3.runAction(ds, _this3.options);
75473
+ });
75474
+ }
75319
75475
  }, {
75320
75476
  key: "initKeyboardActions",
75321
75477
  value: function initKeyboardActions() {
75322
75478
  var self = this;
75323
- // Store actions in a map
75324
75479
  var keyActions = new Map();
75325
- this.$actionController.querySelectorAll('.action[triggerType="keyPress"]').forEach(function ($el) {
75480
+ this.$actionController.querySelectorAll('.action[triggertype="keyPress"]').forEach(function ($el) {
75326
75481
  var dataset = $el.dataset;
75327
75482
  var code = dataset.triggercode;
75328
75483
  if (code) {
75329
- // Create an empty array, if not yet set
75330
75484
  if (!keyActions.get(code)) {
75331
75485
  keyActions.set(code, []);
75332
75486
  }
75333
- // Add new action to array
75334
75487
  keyActions.get(code).push(dataset);
75335
75488
  }
75336
75489
  });
75337
- // Keyboard listener
75338
- document.addEventListener('keydown', function (ev) {
75490
+ // Nothing to do if this layout has no keyboard-triggered actions.
75491
+ if (keyActions.size === 0) return;
75492
+ this.keyboardHandler = function (ev) {
75339
75493
  var actions = keyActions.get(ev.code);
75340
- // Are there action for this key code?
75341
75494
  if (actions) {
75342
- // Run all actions associated with it
75343
75495
  actions.forEach(function (dataset) {
75344
75496
  self.runAction(dataset, self.options);
75345
75497
  });
75346
75498
  }
75347
- });
75499
+ };
75500
+ document.addEventListener('keydown', this.keyboardHandler);
75501
+ }
75502
+ /** Remove the keydown listener registered by initKeyboardActions. Call when the layout ends or is cancelled. */
75503
+ }, {
75504
+ key: "removeKeyboardActions",
75505
+ value: function removeKeyboardActions() {
75506
+ if (this.keyboardHandler) {
75507
+ document.removeEventListener('keydown', this.keyboardHandler);
75508
+ this.keyboardHandler = null;
75509
+ }
75348
75510
  }
75349
75511
  }]);
75350
75512
  }();
@@ -75579,6 +75741,7 @@ var Layout = /*#__PURE__*/function () {
75579
75741
  });
75580
75742
  this.on('end', /*#__PURE__*/function () {
75581
75743
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(layout) {
75744
+ var _layout$actionControl;
75582
75745
  var $layout, _$layout$parentElemen;
75583
75746
  return _regeneratorRuntime().wrap(function _callee2$(_context2) {
75584
75747
  while (1) switch (_context2.prev = _context2.next) {
@@ -75637,9 +75800,10 @@ var Layout = /*#__PURE__*/function () {
75637
75800
  }
75638
75801
  // Emit layout end event
75639
75802
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75640
- _context2.next = 13;
75803
+ (_layout$actionControl = layout.actionController) === null || _layout$actionControl === void 0 || _layout$actionControl.removeKeyboardActions();
75804
+ _context2.next = 14;
75641
75805
  return layout.xlr.emitSync('layoutEnd', layout);
75642
- case 13:
75806
+ case 14:
75643
75807
  if (_this.xlr.config.platform !== exports.ConsumerPlatform.CMS && layout.inLoop) {
75644
75808
  // Transition next layout to current layout and prepare next layout if exist
75645
75809
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75669,7 +75833,7 @@ var Layout = /*#__PURE__*/function () {
75669
75833
  };
75670
75834
  }());
75671
75835
  }
75672
- case 14:
75836
+ case 15:
75673
75837
  case "end":
75674
75838
  return _context2.stop();
75675
75839
  }
@@ -75680,9 +75844,11 @@ var Layout = /*#__PURE__*/function () {
75680
75844
  };
75681
75845
  }());
75682
75846
  this.on('cancelled', function (layout) {
75847
+ var _layout$actionControl2;
75683
75848
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75684
75849
  layout.state = exports.ELayoutState.CANCELLED;
75685
75850
  layout.inLoop = false;
75851
+ (_layout$actionControl2 = layout.actionController) === null || _layout$actionControl2 === void 0 || _layout$actionControl2.removeKeyboardActions();
75686
75852
  // Dispose video handlers immediately so their stall watchdogs and error
75687
75853
  // callbacks can't fire against a layout whose DOM is about to be removed.
75688
75854
  var _iterator = _createForOfIteratorHelper(layout.regions),
@@ -75841,7 +76007,8 @@ var Layout = /*#__PURE__*/function () {
75841
76007
  _this2.regions.push(regionObj);
75842
76008
  });
75843
76009
  this.actionController.initTouchActions();
75844
- this.actionController.initKeyboardActions();
76010
+ // Keyboard actions are registered in run() so the global document listener
76011
+ // is only active while the layout is actually playing, not during background preparation.
75845
76012
  }
75846
76013
  }, {
75847
76014
  key: "run",
@@ -75860,6 +76027,7 @@ var Layout = /*#__PURE__*/function () {
75860
76027
  shouldParse: false
75861
76028
  });
75862
76029
  if ($layoutContainer) {
76030
+ var _this$actionControlle;
75863
76031
  $layoutContainer.style.setProperty('visibility', 'visible');
75864
76032
  $layoutContainer.style.setProperty('opacity', '1');
75865
76033
  $layoutContainer.style.setProperty('z-index', this.zIndex !== null ? "".concat(this.zIndex) : '1');
@@ -75868,6 +76036,9 @@ var Layout = /*#__PURE__*/function () {
75868
76036
  // Also set the background color of the player window > body
75869
76037
  document.body.style.setProperty('background-color', "".concat(this.bgColor));
75870
76038
  }
76039
+ // Register keyboard actions now that the layout is active.
76040
+ // Done here (not in parseXlf) so the global listener is scoped to playback time.
76041
+ (_this$actionControlle = this.actionController) === null || _this$actionControlle === void 0 || _this$actionControlle.initKeyboardActions();
75871
76042
  // Emit start event
75872
76043
  this.emitter.emit('start', this);
75873
76044
  // Play regions
@@ -76365,6 +76536,13 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76365
76536
  return _ref4.apply(this, arguments);
76366
76537
  };
76367
76538
  }());
76539
+ xlrObject.on('navLayout', function (layoutCode) {
76540
+ // Non-CMS platforms handle navLayout in their renderer via playInterruptLayout.
76541
+ // CMS navLayout is handled by ActionController (opens layout in a new tab).
76542
+ console.debug('[navLayout] XLR::on("navLayout") - received', {
76543
+ layoutCode: layoutCode
76544
+ });
76545
+ });
76368
76546
  xlrObject.emitSync = /*#__PURE__*/function () {
76369
76547
  var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(eventName) {
76370
76548
  var _xlrObject$emitter$ev;
@@ -76544,35 +76722,28 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76544
76722
  }));
76545
76723
  xlrObject.updateScheduleLayouts = /*#__PURE__*/function () {
76546
76724
  var _ref9 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee9(scheduleLayouts) {
76547
- var inputLayoutIds, _iterator, _step, _step$value, layoutIndex, _layout, uniqueLayout, _i, _Object$keys, layoutId;
76725
+ var next;
76548
76726
  return _regeneratorRuntime().wrap(function _callee9$(_context9) {
76549
76727
  while (1) switch (_context9.prev = _context9.next) {
76550
76728
  case 0:
76551
- console.debug('XLR::updateScheduleLayouts > Updating schedule layouts . . .');
76552
- inputLayoutIds = [];
76553
- _iterator = _createForOfIteratorHelper(scheduleLayouts.entries());
76554
- try {
76555
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
76556
- _step$value = _slicedToArray(_step.value, 2), layoutIndex = _step$value[0], _layout = _step$value[1];
76557
- uniqueLayout = _layout;
76558
- uniqueLayout.index = layoutIndex;
76559
- uniqueLayout.id = _layout.layoutId;
76560
- this.uniqueLayouts[_layout.layoutId] = uniqueLayout;
76561
- inputLayoutIds.push(_layout.layoutId);
76562
- }
76563
- // Cross-check if we need to remove non-existing layouts based on inputLayouts
76564
- } catch (err) {
76565
- _iterator.e(err);
76566
- } finally {
76567
- _iterator.f();
76568
- }
76569
- for (_i = 0, _Object$keys = Object.keys(this.uniqueLayouts); _i < _Object$keys.length; _i++) {
76570
- layoutId = _Object$keys[_i];
76571
- if (!inputLayoutIds.includes(parseInt(layoutId))) {
76572
- delete this.uniqueLayouts[layoutId];
76573
- }
76729
+ console.debug('XLR::updateScheduleLayouts > Updating schedule layouts . . .', scheduleLayouts);
76730
+ next = new Map();
76731
+ if (!(scheduleLayouts.length === 0)) {
76732
+ _context9.next = 5;
76733
+ break;
76574
76734
  }
76735
+ this.uniqueLayouts = next;
76736
+ return _context9.abrupt("return");
76575
76737
  case 5:
76738
+ scheduleLayouts.forEach(function (_layout, layoutIndex) {
76739
+ next.set(String(_layout.layoutId), _objectSpread2(_objectSpread2({}, _layout), {}, {
76740
+ index: layoutIndex,
76741
+ id: _layout.layoutId
76742
+ }));
76743
+ });
76744
+ console.debug('XLR::updateScheduleLayouts > next unique layouts', Array.from(next).values());
76745
+ this.uniqueLayouts = next;
76746
+ case 8:
76576
76747
  case "end":
76577
76748
  return _context9.stop();
76578
76749
  }
@@ -76621,6 +76792,36 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76621
76792
  case 0:
76622
76793
  console.debug('>>>> XLR.debug XLR::updateLoop > Updating schedule loop . . .');
76623
76794
  this.inputLayouts = inputLayouts;
76795
+ // Guard against a splash-only update: uniqueLayouts has no entry for layoutId 0,
76796
+ // so parseLayouts() would return undefined current/next and prepareLayoutXlf()
76797
+ // would be called with undefined. Clean up any playing layouts and show splash directly.
76798
+ if (!(inputLayouts.length === 1 && inputLayouts[0].layoutId === 0)) {
76799
+ _context11.next = 14;
76800
+ break;
76801
+ }
76802
+ if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76803
+ _context11.next = 8;
76804
+ break;
76805
+ }
76806
+ // Force all regions to complete immediately
76807
+ this.currentLayout.inLoop = false;
76808
+ _context11.next = 7;
76809
+ return this.currentLayout.finishAllRegions();
76810
+ case 7:
76811
+ this.currentLayout.removeLayout();
76812
+ case 8:
76813
+ if (this.nextLayout) {
76814
+ // Discard regardless of DOM presence: nextLayout may be preloaded with
76815
+ // DOM elements and video.js players but not yet attached to the screen container.
76816
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76817
+ }
76818
+ this.currentLayout = undefined;
76819
+ this.nextLayout = undefined;
76820
+ _context11.next = 13;
76821
+ return this.playSchedules(this);
76822
+ case 13:
76823
+ return _context11.abrupt("return");
76824
+ case 14:
76624
76825
  playback = this.parseLayouts(true);
76625
76826
  isCurrentLayoutValid = isLayoutValid(this.inputLayouts, (_this$currentLayout = this.currentLayout) === null || _this$currentLayout === void 0 ? void 0 : _this$currentLayout.layoutId);
76626
76827
  if (this.isSspEnabled && this.currentLayoutId === -1) {
@@ -76629,7 +76830,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76629
76830
  if (!isCurrentLayoutValid && this.currentLayout) {
76630
76831
  this.currentLayout.emitter.emit('cancelled', this.currentLayout);
76631
76832
  }
76632
- console.debug('>>>>> XLR.debug XLR::updateLoop > uniqueLayouts', this.uniqueLayouts);
76833
+ console.debug('>>>>> XLR.debug XLR::updateLoop > uniqueLayouts', Array.from(this.uniqueLayouts.values()));
76633
76834
  console.debug('>>>>> XLR.debug XLR::updateLoop > inputLayouts', this.inputLayouts);
76634
76835
  console.debug('>>>>> XLR.debug XLR::updateLoop > isCurrentLayoutValid', isCurrentLayoutValid);
76635
76836
  console.debug('>>>>> XLR.debug XLR::updateLoop > currentLayout', this.currentLayout);
@@ -76657,86 +76858,86 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76657
76858
  };
76658
76859
  }();
76659
76860
  if (isCurrentLayoutValid) {
76660
- _context11.next = 55;
76861
+ _context11.next = 67;
76661
76862
  break;
76662
76863
  }
76663
76864
  if (!playback.hasDefaultOnly) {
76664
- _context11.next = 34;
76865
+ _context11.next = 46;
76665
76866
  break;
76666
76867
  }
76667
76868
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
76668
- _context11.next = 20;
76869
+ _context11.next = 32;
76669
76870
  break;
76670
76871
  }
76671
76872
  this.currentLayout.inLoop = false;
76672
- _context11.next = 19;
76873
+ _context11.next = 31;
76673
76874
  return this.currentLayout.finishAllRegions();
76674
- case 19:
76875
+ case 31:
76675
76876
  this.currentLayout.removeLayout();
76676
- case 20:
76877
+ case 32:
76677
76878
  // Discard old nextLayout before replacing it — same as the
76678
76879
  // other two branches do, otherwise the prepared DOM element
76679
76880
  // and any video.js players are orphaned.
76680
76881
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76681
76882
  this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76682
76883
  }
76683
- _context11.next = 23;
76884
+ _context11.next = 35;
76684
76885
  return this.prepareLayoutXlf(playback.currentLayout);
76685
- case 23:
76886
+ case 35:
76686
76887
  this.currentLayout = _context11.sent;
76687
76888
  this.currentLayoutId = this.currentLayout.layoutId;
76688
76889
  _context11.t0 = this;
76689
- _context11.next = 28;
76890
+ _context11.next = 40;
76690
76891
  return this.prepareLayoutXlf(playback.nextLayout);
76691
- case 28:
76892
+ case 40:
76692
76893
  _context11.t1 = _context11.sent;
76693
- _context11.next = 31;
76894
+ _context11.next = 43;
76694
76895
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76695
- case 31:
76896
+ case 43:
76696
76897
  this.nextLayout = _context11.sent;
76697
- _context11.next = 51;
76898
+ _context11.next = 63;
76698
76899
  break;
76699
- case 34:
76900
+ case 46:
76700
76901
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76701
- _context11.next = 39;
76902
+ _context11.next = 51;
76702
76903
  break;
76703
76904
  }
76704
76905
  this.currentLayout.inLoop = false;
76705
- _context11.next = 38;
76906
+ _context11.next = 50;
76706
76907
  return this.currentLayout.finishAllRegions();
76707
- case 38:
76908
+ case 50:
76708
76909
  this.currentLayout.removeLayout();
76709
- case 39:
76910
+ case 51:
76710
76911
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76711
76912
  this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76712
76913
  }
76713
76914
  if (!playback.currentLayout) {
76714
- _context11.next = 43;
76915
+ _context11.next = 55;
76715
76916
  break;
76716
76917
  }
76717
- _context11.next = 43;
76918
+ _context11.next = 55;
76718
76919
  return prepareNewCurrentLayout();
76719
- case 43:
76920
+ case 55:
76720
76921
  if (!playback.nextLayout) {
76721
- _context11.next = 51;
76922
+ _context11.next = 63;
76722
76923
  break;
76723
76924
  }
76724
76925
  _context11.t2 = this;
76725
- _context11.next = 47;
76926
+ _context11.next = 59;
76726
76927
  return this.prepareLayoutXlf(playback.nextLayout);
76727
- case 47:
76928
+ case 59:
76728
76929
  _context11.t3 = _context11.sent;
76729
- _context11.next = 50;
76930
+ _context11.next = 62;
76730
76931
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76731
- case 50:
76932
+ case 62:
76732
76933
  this.nextLayout = _context11.sent;
76733
- case 51:
76734
- _context11.next = 53;
76934
+ case 63:
76935
+ _context11.next = 65;
76735
76936
  return this.playSchedules(this);
76736
- case 53:
76737
- _context11.next = 67;
76937
+ case 65:
76938
+ _context11.next = 79;
76738
76939
  break;
76739
- case 55:
76940
+ case 67:
76740
76941
  // Remove next layout if it is in the DOM
76741
76942
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76742
76943
  this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
@@ -76763,22 +76964,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76763
76964
  // nextLayout keeps the cycle in order; the slot after that will be
76764
76965
  // prepared by the normal prepareLayouts() call at transition time.
76765
76966
  if (!playback.currentLayout) {
76766
- _context11.next = 66;
76967
+ _context11.next = 78;
76767
76968
  break;
76768
76969
  }
76769
76970
  this.currentLayoutIndex = playback.currentLayoutIndex;
76770
76971
  _context11.t4 = this;
76771
- _context11.next = 62;
76972
+ _context11.next = 74;
76772
76973
  return this.prepareLayoutXlf(playback.currentLayout);
76773
- case 62:
76974
+ case 74:
76774
76975
  _context11.t5 = _context11.sent;
76775
- _context11.next = 65;
76976
+ _context11.next = 77;
76776
76977
  return _context11.t4.prepareForSsp.call(_context11.t4, _context11.t5);
76777
- case 65:
76978
+ case 77:
76778
76979
  this.nextLayout = _context11.sent;
76779
- case 66:
76980
+ case 78:
76780
76981
  console.debug('>>>> XLR.debug XLR::updateLoop > updated nextLayout', this.nextLayout);
76781
- case 67:
76982
+ case 79:
76782
76983
  case "end":
76783
76984
  return _context11.stop();
76784
76985
  }
@@ -76808,7 +77009,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76808
77009
  };
76809
77010
  }();
76810
77011
  xlrObject.parseLayouts = function (hasChanged) {
76811
- var _this$currentLayout2, _this$currentLayout3;
77012
+ var _this$currentLayout2, _this$currentLayout3, _this$currentLayout4, _this$nextLayout, _currentLayout2, _nextLayout2;
76812
77013
  var _currentLayout;
76813
77014
  var _nextLayout;
76814
77015
  var _hasDefaultOnly = hasDefaultOnly(this.inputLayouts);
@@ -76821,17 +77022,28 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76821
77022
  if (this.isSspEnabled && ((_this$currentLayout3 = this.currentLayout) === null || _this$currentLayout3 === void 0 ? void 0 : _this$currentLayout3.layoutId) === -1) {
76822
77023
  isCurrentLayoutValid = true;
76823
77024
  }
77025
+ console.debug('XLR::parseLayouts', {
77026
+ currentLayoutId: (_this$currentLayout4 = this.currentLayout) === null || _this$currentLayout4 === void 0 ? void 0 : _this$currentLayout4.layoutId,
77027
+ currentLayoutIndex: this.currentLayoutIndex,
77028
+ nextLayoutId: (_this$nextLayout = this.nextLayout) === null || _this$nextLayout === void 0 ? void 0 : _this$nextLayout.layoutId,
77029
+ isCurrentLayoutValid: isCurrentLayoutValid,
77030
+ hasChanged: !!hasChanged,
77031
+ inputLayoutsCount: this.inputLayouts.length,
77032
+ inputLayoutIds: this.inputLayouts.map(function (l) {
77033
+ return l.layoutId;
77034
+ }).join(', ')
77035
+ });
76824
77036
  _currentLayout = this.currentLayout;
76825
77037
  if (this.currentLayout && this.nextLayout) {
76826
77038
  // Both currentLayout and nextLayout has values
76827
77039
  if (hasLayout) {
76828
77040
  if (!isCurrentLayoutValid) {
76829
- var _this$nextLayout;
77041
+ var _this$nextLayout2;
76830
77042
  // Check if currentLayout.state is PLAYED,
76831
77043
  // then, validate nextLayout and if valid,
76832
77044
  // proceed to nextLayout as new currentLayout
76833
77045
  // Else, go back to first layout in the loop
76834
- if (this.currentLayout.state === exports.ELayoutState.PLAYED && isLayoutValid(this.inputLayouts, (_this$nextLayout = this.nextLayout) === null || _this$nextLayout === void 0 ? void 0 : _this$nextLayout.layoutId)) {
77046
+ if (this.currentLayout.state === exports.ELayoutState.PLAYED && isLayoutValid(this.inputLayouts, (_this$nextLayout2 = this.nextLayout) === null || _this$nextLayout2 === void 0 ? void 0 : _this$nextLayout2.layoutId)) {
76835
77047
  // Get nextLayout from updated loop
76836
77048
  var tempNextLayoutIndex = getLayoutIndexByLayoutId(this.inputLayouts, this.nextLayout.layoutId);
76837
77049
  _currentLayoutIndex = tempNextLayoutIndex !== null && tempNextLayoutIndex !== void 0 ? tempNextLayoutIndex : 0;
@@ -76875,7 +77087,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76875
77087
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76876
77088
  }
76877
77089
  } else {
76878
- var _this$currentLayout4, _this$currentLayout5;
77090
+ var _this$currentLayout5, _this$currentLayout6;
76879
77091
  _currentLayout = this.nextLayout;
76880
77092
  _currentLayoutIndex = _currentLayout.index;
76881
77093
  // updateLoop can re-queue the same index that is currently
@@ -76885,7 +77097,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76885
77097
  // whether the queued next-to-current is at the same index as the
76886
77098
  // layout that just finished, and advance past it so the following
76887
77099
  // slot (e.g. an SSP that now has an ad) becomes current instead.
76888
- if (this.inputLayouts.length > 1 && (_this$currentLayout4 = this.currentLayout) !== null && _this$currentLayout4 !== void 0 && _this$currentLayout4.done && _currentLayoutIndex === ((_this$currentLayout5 = this.currentLayout) === null || _this$currentLayout5 === void 0 ? void 0 : _this$currentLayout5.index)) {
77100
+ if (this.inputLayouts.length > 1 && (_this$currentLayout5 = this.currentLayout) !== null && _this$currentLayout5 !== void 0 && _this$currentLayout5.done && _currentLayoutIndex === ((_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.index)) {
76889
77101
  _currentLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76890
77102
  _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76891
77103
  _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
@@ -76922,6 +77134,12 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76922
77134
  _currentLayout.xlr = this;
76923
77135
  _nextLayout.xlr = this;
76924
77136
  }
77137
+ console.debug('XLR::parseLayouts result', {
77138
+ currentLayoutId: (_currentLayout2 = _currentLayout) === null || _currentLayout2 === void 0 ? void 0 : _currentLayout2.layoutId,
77139
+ currentLayoutIndex: _currentLayoutIndex,
77140
+ nextLayoutId: (_nextLayout2 = _nextLayout) === null || _nextLayout2 === void 0 ? void 0 : _nextLayout2.layoutId,
77141
+ nextLayoutIndex: _nextLayoutIndex
77142
+ });
76925
77143
  return {
76926
77144
  currentLayout: _currentLayout,
76927
77145
  nextLayout: _nextLayout,
@@ -76933,7 +77151,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76933
77151
  };
76934
77152
  xlrObject.getLayout = function (inputLayout) {
76935
77153
  var isCMS = this.config.platform === exports.ConsumerPlatform.CMS;
76936
- if (!isCMS && Object.keys(this.uniqueLayouts).length === 0) {
77154
+ if (!isCMS && this.uniqueLayouts.size === 0) {
76937
77155
  return;
76938
77156
  }
76939
77157
  var _layout = {};
@@ -76950,7 +77168,13 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76950
77168
  activeLayout.id = activeLayout.layoutId;
76951
77169
  }
76952
77170
  } else {
76953
- activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
77171
+ var layoutFromUniqueLayouts = this.uniqueLayouts.get(String(inputLayout.layoutId));
77172
+ console.debug('XLR::getLayout > layoutFromUniqueLayouts', {
77173
+ layoutFromUniqueLayouts: layoutFromUniqueLayouts,
77174
+ inputLayout: inputLayout,
77175
+ uniqueLayouts: this.uniqueLayouts
77176
+ });
77177
+ activeLayout = layoutFromUniqueLayouts ? _objectSpread2({}, layoutFromUniqueLayouts) : _objectSpread2({}, inputLayout);
76954
77178
  }
76955
77179
  _layout = _objectSpread2(_objectSpread2({}, _layout), activeLayout);
76956
77180
  console.debug('XLR::getLayout > activeLayout from uniqueLayouts', {
@@ -76967,10 +77191,10 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76967
77191
  return iLayout;
76968
77192
  };
76969
77193
  xlrObject.getLayoutById = function (layoutId, layoutIndex) {
76970
- if (!layoutId || Object.keys(this.uniqueLayouts).length === 0) {
77194
+ if (!layoutId || this.uniqueLayouts.size === 0 || !this.uniqueLayouts.has(String(layoutId))) {
76971
77195
  return undefined;
76972
77196
  }
76973
- var _layout = _objectSpread2(_objectSpread2({}, initialLayout), this.uniqueLayouts[layoutId]);
77197
+ var _layout = _objectSpread2(_objectSpread2({}, initialLayout), this.uniqueLayouts.get(String(layoutId)));
76974
77198
  // Set layout index if available
76975
77199
  if (layoutIndex) {
76976
77200
  _layout.index = layoutIndex;
@@ -76978,71 +77202,79 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76978
77202
  return _layout;
76979
77203
  };
76980
77204
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76981
- var _layoutPlayback$curre, _layoutPlayback$curre2;
77205
+ var _layoutPlayback$curre, _layoutPlayback$nextL, _layoutPlayback$curre2, _layoutPlayback$curre3, _layouts$, _layouts$2, _layouts$3, _layouts$4;
76982
77206
  var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76983
77207
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76984
77208
  while (1) switch (_context14.prev = _context14.next) {
76985
77209
  case 0:
76986
77210
  self = xlrObject;
76987
77211
  if (!this.isUpdatingLoop) {
76988
- _context14.next = 3;
77212
+ _context14.next = 4;
76989
77213
  break;
76990
77214
  }
77215
+ console.debug('XLR::prepareLayouts - skipped (isUpdatingLoop)');
76991
77216
  return _context14.abrupt("return", Promise.resolve(self));
76992
- case 3:
77217
+ case 4:
76993
77218
  layoutPlayback = self.parseLayouts(); // Don't prepare layout if it's just the splash screen
76994
77219
  if (!(self.inputLayouts.length === 1 && self.inputLayouts[0].layoutId === 0)) {
76995
- _context14.next = 6;
77220
+ _context14.next = 8;
76996
77221
  break;
76997
77222
  }
77223
+ console.debug('XLR::prepareLayouts - skipped (splash screen only)');
76998
77224
  return _context14.abrupt("return", Promise.resolve(self));
76999
- case 6:
77000
- console.debug('??? XLR.debug prepareLayouts::playback', {
77001
- layoutPlayback: layoutPlayback,
77002
- shouldParse: false
77225
+ case 8:
77226
+ console.debug('XLR::prepareLayouts', {
77227
+ currentLayoutId: (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId,
77228
+ currentLayoutIndex: layoutPlayback.currentLayoutIndex,
77229
+ nextLayoutId: (_layoutPlayback$nextL = layoutPlayback.nextLayout) === null || _layoutPlayback$nextL === void 0 ? void 0 : _layoutPlayback$nextL.layoutId,
77230
+ nextLayoutIndex: layoutPlayback.nextLayoutIndex
77003
77231
  });
77004
- self.currentLayoutId = (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId;
77232
+ self.currentLayoutId = (_layoutPlayback$curre2 = layoutPlayback.currentLayout) === null || _layoutPlayback$curre2 === void 0 ? void 0 : _layoutPlayback$curre2.layoutId;
77005
77233
  // Only reuse the existing Layout instance if it is fully healthy —
77006
77234
  // a done=true instance was removed from the DOM (e.g. an SSP slot that
77007
77235
  // had no ad), and an empty-XLF instance has no regions so it can never
77008
77236
  // advance the cycle. In either case re-prepare from scratch so we get
77009
77237
  // a fresh request (which may now have a valid ad / XLF).
77010
- if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
77011
- _context14.next = 12;
77238
+ if (!((_layoutPlayback$curre3 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre3 !== void 0 && _layoutPlayback$curre3.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
77239
+ _context14.next = 14;
77012
77240
  break;
77013
77241
  }
77014
77242
  _context14.t0 = layoutPlayback.currentLayout;
77015
- _context14.next = 15;
77243
+ _context14.next = 17;
77016
77244
  break;
77017
- case 12:
77018
- _context14.next = 14;
77019
- return self.prepareLayoutXlf(layoutPlayback.currentLayout);
77020
77245
  case 14:
77246
+ _context14.next = 16;
77247
+ return self.prepareLayoutXlf(layoutPlayback.currentLayout);
77248
+ case 16:
77021
77249
  _context14.t0 = _context14.sent;
77022
- case 15:
77250
+ case 17:
77023
77251
  currentLayoutXlf = _context14.t0;
77024
77252
  // True when the same object was returned (reused); false when a fresh
77025
77253
  // Layout was constructed by prepareLayoutXlf above.
77026
77254
  wasCurrentReused = currentLayoutXlf === layoutPlayback.currentLayout;
77027
- _context14.next = 19;
77255
+ _context14.next = 21;
77028
77256
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
77029
- case 19:
77257
+ case 21:
77030
77258
  nextLayoutXlf = _context14.sent;
77031
77259
  _context14.t1 = Promise;
77032
77260
  _context14.t2 = currentLayoutXlf;
77033
- _context14.next = 24;
77261
+ _context14.next = 26;
77034
77262
  return self.prepareForSsp(nextLayoutXlf);
77035
- case 24:
77263
+ case 26:
77036
77264
  _context14.t3 = _context14.sent;
77037
77265
  _context14.t4 = [_context14.t2, _context14.t3];
77038
- _context14.next = 28;
77266
+ _context14.next = 30;
77039
77267
  return _context14.t1.all.call(_context14.t1, _context14.t4);
77040
- case 28:
77268
+ case 30:
77041
77269
  layouts = _context14.sent;
77042
77270
  if (!(self.isUpdatingLoop || layouts[0].done)) {
77043
- _context14.next = 33;
77271
+ _context14.next = 36;
77044
77272
  break;
77045
77273
  }
77274
+ console.debug('XLR::prepareLayouts - aborted (concurrent updateLoop)', {
77275
+ isUpdatingLoop: self.isUpdatingLoop,
77276
+ currentLayoutDone: layouts[0].done
77277
+ });
77046
77278
  // If currentLayout was freshly prepared (not reused from nextLayout),
77047
77279
  // its DOM element was just appended — discard it now so it does not
77048
77280
  // accumulate in screen_container. Also disposes any video.js players
@@ -77054,8 +77286,14 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
77054
77286
  nextLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
77055
77287
  }
77056
77288
  return _context14.abrupt("return", Promise.resolve(self));
77057
- case 33:
77058
- console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
77289
+ case 36:
77290
+ console.debug('XLR::prepareLayouts - layouts prepared', {
77291
+ currentLayoutId: (_layouts$ = layouts[0]) === null || _layouts$ === void 0 ? void 0 : _layouts$.layoutId,
77292
+ currentLayoutIndex: (_layouts$2 = layouts[0]) === null || _layouts$2 === void 0 ? void 0 : _layouts$2.index,
77293
+ nextLayoutId: (_layouts$3 = layouts[1]) === null || _layouts$3 === void 0 ? void 0 : _layouts$3.layoutId,
77294
+ nextLayoutIndex: (_layouts$4 = layouts[1]) === null || _layouts$4 === void 0 ? void 0 : _layouts$4.index,
77295
+ currentReused: wasCurrentReused
77296
+ });
77059
77297
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
77060
77298
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
77061
77299
  return _regeneratorRuntime().wrap(function _callee13$(_context13) {
@@ -77089,7 +77327,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
77089
77327
  return _ref14.apply(this, arguments);
77090
77328
  };
77091
77329
  }()));
77092
- case 35:
77330
+ case 38:
77093
77331
  case "end":
77094
77332
  return _context14.stop();
77095
77333
  }
@@ -77252,33 +77490,48 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
77252
77490
  return _ref16.apply(this, arguments);
77253
77491
  };
77254
77492
  }();
77493
+ // Shared re-entry guard for all layout navigation methods.
77494
+ // Prevents a double-tap from advancing two layouts at once.
77495
+ var isNavigatingLayout = false;
77255
77496
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
77256
77497
  var _this4 = this;
77257
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
77498
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout7;
77258
77499
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
77259
77500
  while (1) switch (_context18.prev = _context18.next) {
77260
77501
  case 0:
77502
+ if (!isNavigatingLayout) {
77503
+ _context18.next = 2;
77504
+ break;
77505
+ }
77506
+ return _context18.abrupt("return");
77507
+ case 2:
77508
+ isNavigatingLayout = true;
77509
+ _context18.prev = 3;
77261
77510
  _currentLayoutIndex = this.currentLayoutIndex;
77262
77511
  _assumedPrevIndex = _currentLayoutIndex - 1; // If previous layout is same as current layout or
77263
77512
  // if there's only one layout, do nothing
77264
77513
  if (!(_assumedPrevIndex < 0)) {
77265
- _context18.next = 4;
77514
+ _context18.next = 8;
77266
77515
  break;
77267
77516
  }
77268
77517
  return _context18.abrupt("return");
77269
- case 4:
77518
+ case 8:
77270
77519
  console.debug('XLR::gotoPrevLayout', {
77271
- previousLayoutIndex: _assumedPrevIndex,
77272
- method: 'XLR::gotoPrevLayout',
77273
- shouldParse: false
77520
+ previousLayoutIndex: _assumedPrevIndex
77274
77521
  });
77275
77522
  if (!Boolean(this.inputLayouts[_assumedPrevIndex])) {
77276
- _context18.next = 10;
77523
+ _context18.next = 15;
77277
77524
  break;
77278
77525
  }
77279
- _context18.next = 8;
77280
- return (_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.finishAllRegions();
77281
- case 8:
77526
+ // Prevent the natural layout-end handler from also calling
77527
+ // prepareLayouts() when finishAllRegions() causes the layout
77528
+ // 'end' event to fire.
77529
+ if (this.currentLayout) {
77530
+ this.currentLayout.inLoop = false;
77531
+ }
77532
+ _context18.next = 13;
77533
+ return (_this$currentLayout7 = this.currentLayout) === null || _this$currentLayout7 === void 0 ? void 0 : _this$currentLayout7.finishAllRegions();
77534
+ case 13:
77282
77535
  // and set the previous layout as current layout
77283
77536
  this.currentLayoutIndex = _assumedPrevIndex;
77284
77537
  this.prepareLayouts().then( /*#__PURE__*/function () {
@@ -77298,30 +77551,366 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
77298
77551
  return _ref18.apply(this, arguments);
77299
77552
  };
77300
77553
  }());
77301
- case 10:
77554
+ case 15:
77555
+ _context18.prev = 15;
77556
+ isNavigatingLayout = false;
77557
+ return _context18.finish(15);
77558
+ case 18:
77302
77559
  case "end":
77303
77560
  return _context18.stop();
77304
77561
  }
77305
- }, _callee18, this);
77562
+ }, _callee18, this, [[3,, 15, 18]]);
77306
77563
  }));
77307
- xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
77308
- var _xlrObject$currentLay2;
77309
- return _regeneratorRuntime().wrap(function _callee19$(_context19) {
77310
- while (1) switch (_context19.prev = _context19.next) {
77564
+ xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee20() {
77565
+ var _this5 = this;
77566
+ var _this$currentLayout8, nextIndex;
77567
+ return _regeneratorRuntime().wrap(function _callee20$(_context20) {
77568
+ while (1) switch (_context20.prev = _context20.next) {
77311
77569
  case 0:
77570
+ if (!isNavigatingLayout) {
77571
+ _context20.next = 2;
77572
+ break;
77573
+ }
77574
+ return _context20.abrupt("return");
77575
+ case 2:
77576
+ isNavigatingLayout = true;
77577
+ _context20.prev = 3;
77578
+ nextIndex = this.currentLayoutIndex + 1;
77579
+ if (Boolean(this.inputLayouts[nextIndex])) {
77580
+ _context20.next = 7;
77581
+ break;
77582
+ }
77583
+ return _context20.abrupt("return");
77584
+ case 7:
77312
77585
  console.debug('XLR::gotoNextLayout', {
77313
- nextLayoutIndex: this.currentLayoutIndex + 1,
77314
- method: 'XLR::gotoNextLayout',
77315
- shouldParse: false
77586
+ nextLayoutIndex: nextIndex
77316
77587
  });
77317
- _context19.next = 3;
77318
- return (_xlrObject$currentLay2 = xlrObject.currentLayout) === null || _xlrObject$currentLay2 === void 0 ? void 0 : _xlrObject$currentLay2.finishAllRegions();
77319
- case 3:
77588
+ if (this.currentLayout) {
77589
+ this.currentLayout.inLoop = false;
77590
+ }
77591
+ _context20.next = 11;
77592
+ return (_this$currentLayout8 = this.currentLayout) === null || _this$currentLayout8 === void 0 ? void 0 : _this$currentLayout8.finishAllRegions();
77593
+ case 11:
77594
+ this.currentLayoutIndex = nextIndex;
77595
+ this.prepareLayouts().then( /*#__PURE__*/function () {
77596
+ var _ref20 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19(xlr) {
77597
+ return _regeneratorRuntime().wrap(function _callee19$(_context19) {
77598
+ while (1) switch (_context19.prev = _context19.next) {
77599
+ case 0:
77600
+ _context19.next = 2;
77601
+ return _this5.playSchedules(xlr);
77602
+ case 2:
77603
+ case "end":
77604
+ return _context19.stop();
77605
+ }
77606
+ }, _callee19);
77607
+ }));
77608
+ return function (_x14) {
77609
+ return _ref20.apply(this, arguments);
77610
+ };
77611
+ }());
77612
+ case 13:
77613
+ _context20.prev = 13;
77614
+ isNavigatingLayout = false;
77615
+ return _context20.finish(13);
77616
+ case 16:
77320
77617
  case "end":
77321
- return _context19.stop();
77618
+ return _context20.stop();
77322
77619
  }
77323
- }, _callee19, this);
77620
+ }, _callee20, this, [[3,, 13, 16]]);
77324
77621
  }));
77622
+ xlrObject.gotoLayoutByCode = /*#__PURE__*/function () {
77623
+ var _ref21 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee22(layoutCode) {
77624
+ var _this6 = this;
77625
+ var _this$currentLayout9, targetIndex, parsedLayouts, _loop, _ret, _i, _parsedLayouts, parser, i, _inputLayout$getXlf, _doc$documentElement, inputLayout, xlfString, url, res, doc, foundCode;
77626
+ return _regeneratorRuntime().wrap(function _callee22$(_context23) {
77627
+ while (1) switch (_context23.prev = _context23.next) {
77628
+ case 0:
77629
+ if (!isNavigatingLayout) {
77630
+ _context23.next = 2;
77631
+ break;
77632
+ }
77633
+ return _context23.abrupt("return");
77634
+ case 2:
77635
+ isNavigatingLayout = true;
77636
+ _context23.prev = 3;
77637
+ targetIndex = -1; // 1. Check the two already-parsed layouts first (zero fetch cost)
77638
+ parsedLayouts = [this.layouts['current'], this.layouts['next']];
77639
+ _loop = /*#__PURE__*/_regeneratorRuntime().mark(function _loop() {
77640
+ var _layout$layoutNode;
77641
+ var layout, code;
77642
+ return _regeneratorRuntime().wrap(function _loop$(_context21) {
77643
+ while (1) switch (_context21.prev = _context21.next) {
77644
+ case 0:
77645
+ layout = _parsedLayouts[_i];
77646
+ if (layout) {
77647
+ _context21.next = 3;
77648
+ break;
77649
+ }
77650
+ return _context21.abrupt("return", 0);
77651
+ case 3:
77652
+ code = (_layout$layoutNode = layout.layoutNode) === null || _layout$layoutNode === void 0 || (_layout$layoutNode = _layout$layoutNode.documentElement) === null || _layout$layoutNode === void 0 ? void 0 : _layout$layoutNode.getAttribute('code');
77653
+ if (!(code === layoutCode)) {
77654
+ _context21.next = 7;
77655
+ break;
77656
+ }
77657
+ targetIndex = _this6.inputLayouts.findIndex(function (i) {
77658
+ return i.layoutId === layout.layoutId;
77659
+ });
77660
+ return _context21.abrupt("return", 1);
77661
+ case 7:
77662
+ case "end":
77663
+ return _context21.stop();
77664
+ }
77665
+ }, _loop);
77666
+ });
77667
+ _i = 0, _parsedLayouts = parsedLayouts;
77668
+ case 8:
77669
+ if (!(_i < _parsedLayouts.length)) {
77670
+ _context23.next = 18;
77671
+ break;
77672
+ }
77673
+ return _context23.delegateYield(_loop(), "t0", 10);
77674
+ case 10:
77675
+ _ret = _context23.t0;
77676
+ if (!(_ret === 0)) {
77677
+ _context23.next = 13;
77678
+ break;
77679
+ }
77680
+ return _context23.abrupt("continue", 15);
77681
+ case 13:
77682
+ if (!(_ret === 1)) {
77683
+ _context23.next = 15;
77684
+ break;
77685
+ }
77686
+ return _context23.abrupt("break", 18);
77687
+ case 15:
77688
+ _i++;
77689
+ _context23.next = 8;
77690
+ break;
77691
+ case 18:
77692
+ if (!(targetIndex === -1)) {
77693
+ _context23.next = 60;
77694
+ break;
77695
+ }
77696
+ parser = new DOMParser();
77697
+ i = 0;
77698
+ case 21:
77699
+ if (!(i < this.inputLayouts.length)) {
77700
+ _context23.next = 60;
77701
+ break;
77702
+ }
77703
+ inputLayout = this.inputLayouts[i]; // Fast check: code pre-populated by the player (no fetch needed)
77704
+ if (!(inputLayout.code !== undefined)) {
77705
+ _context23.next = 28;
77706
+ break;
77707
+ }
77708
+ if (!(inputLayout.code === layoutCode)) {
77709
+ _context23.next = 27;
77710
+ break;
77711
+ }
77712
+ targetIndex = i;
77713
+ return _context23.abrupt("break", 60);
77714
+ case 27:
77715
+ return _context23.abrupt("continue", 57);
77716
+ case 28:
77717
+ xlfString = void 0; // Prefer getXlf() when available (e.g. CMS platform)
77718
+ xlfString = (_inputLayout$getXlf = inputLayout.getXlf) === null || _inputLayout$getXlf === void 0 ? void 0 : _inputLayout$getXlf.call(inputLayout);
77719
+ // Otherwise fetch from the local file server (Electron / ChromeOS)
77720
+ if (!(!xlfString && this.config.appHost && inputLayout.path)) {
77721
+ _context23.next = 49;
77722
+ break;
77723
+ }
77724
+ url = this.config.appHost + inputLayout.path;
77725
+ console.debug('[gotoLayoutByCode] Fetching XLF for layoutId', inputLayout.layoutId, url);
77726
+ _context23.prev = 33;
77727
+ _context23.next = 36;
77728
+ return fetch(url);
77729
+ case 36:
77730
+ res = _context23.sent;
77731
+ if (res.ok) {
77732
+ _context23.next = 40;
77733
+ break;
77734
+ }
77735
+ console.debug('[gotoLayoutByCode] Fetch non-OK', res.status, url);
77736
+ return _context23.abrupt("continue", 57);
77737
+ case 40:
77738
+ _context23.next = 42;
77739
+ return res.text();
77740
+ case 42:
77741
+ xlfString = _context23.sent;
77742
+ _context23.next = 49;
77743
+ break;
77744
+ case 45:
77745
+ _context23.prev = 45;
77746
+ _context23.t1 = _context23["catch"](33);
77747
+ console.debug('[gotoLayoutByCode] Fetch error for', url, _context23.t1);
77748
+ return _context23.abrupt("continue", 57);
77749
+ case 49:
77750
+ if (xlfString) {
77751
+ _context23.next = 52;
77752
+ break;
77753
+ }
77754
+ console.debug('[gotoLayoutByCode] No XLF for layoutId', inputLayout.layoutId, 'path:', inputLayout.path);
77755
+ return _context23.abrupt("continue", 57);
77756
+ case 52:
77757
+ doc = parser.parseFromString(xlfString, 'text/xml');
77758
+ foundCode = (_doc$documentElement = doc.documentElement) === null || _doc$documentElement === void 0 ? void 0 : _doc$documentElement.getAttribute('code');
77759
+ if (!(foundCode === layoutCode)) {
77760
+ _context23.next = 57;
77761
+ break;
77762
+ }
77763
+ targetIndex = i;
77764
+ return _context23.abrupt("break", 60);
77765
+ case 57:
77766
+ i++;
77767
+ _context23.next = 21;
77768
+ break;
77769
+ case 60:
77770
+ if (!(targetIndex === -1)) {
77771
+ _context23.next = 63;
77772
+ break;
77773
+ }
77774
+ console.warn('XLR::gotoLayoutByCode - layout not found for code:', layoutCode);
77775
+ return _context23.abrupt("return");
77776
+ case 63:
77777
+ console.debug('XLR::gotoLayoutByCode', {
77778
+ layoutCode: layoutCode,
77779
+ targetIndex: targetIndex
77780
+ });
77781
+ // Prevent the natural layout-end handler from racing with our own
77782
+ // prepareLayouts() call (same pattern as gotoPrevLayout/gotoNextLayout).
77783
+ if (this.currentLayout) {
77784
+ this.currentLayout.inLoop = false;
77785
+ }
77786
+ _context23.next = 67;
77787
+ return (_this$currentLayout9 = this.currentLayout) === null || _this$currentLayout9 === void 0 ? void 0 : _this$currentLayout9.finishAllRegions();
77788
+ case 67:
77789
+ this.currentLayoutIndex = targetIndex;
77790
+ this.prepareLayouts().then( /*#__PURE__*/function () {
77791
+ var _ref22 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee21(xlr) {
77792
+ return _regeneratorRuntime().wrap(function _callee21$(_context22) {
77793
+ while (1) switch (_context22.prev = _context22.next) {
77794
+ case 0:
77795
+ _context22.next = 2;
77796
+ return _this6.playSchedules(xlr);
77797
+ case 2:
77798
+ case "end":
77799
+ return _context22.stop();
77800
+ }
77801
+ }, _callee21);
77802
+ }));
77803
+ return function (_x16) {
77804
+ return _ref22.apply(this, arguments);
77805
+ };
77806
+ }());
77807
+ case 69:
77808
+ _context23.prev = 69;
77809
+ isNavigatingLayout = false;
77810
+ return _context23.finish(69);
77811
+ case 72:
77812
+ case "end":
77813
+ return _context23.stop();
77814
+ }
77815
+ }, _callee22, this, [[3,, 69, 72], [33, 45]]);
77816
+ }));
77817
+ return function (_x15) {
77818
+ return _ref21.apply(this, arguments);
77819
+ };
77820
+ }();
77821
+ xlrObject.playInterruptLayout = /*#__PURE__*/function () {
77822
+ var _ref23 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee23(inputLayout) {
77823
+ var _this7 = this;
77824
+ var _this$currentLayout10, _this$currentLayout11, resumeIndex, savedNextLayout, interruptKey, wasInUniqueLayouts, interruptILayout, cleanup;
77825
+ return _regeneratorRuntime().wrap(function _callee23$(_context24) {
77826
+ while (1) switch (_context24.prev = _context24.next) {
77827
+ case 0:
77828
+ if (!isNavigatingLayout) {
77829
+ _context24.next = 2;
77830
+ break;
77831
+ }
77832
+ return _context24.abrupt("return");
77833
+ case 2:
77834
+ isNavigatingLayout = true;
77835
+ _context24.prev = 3;
77836
+ resumeIndex = this.currentLayoutIndex; // Save B (the layout that was queued to play after A) before stopping A.
77837
+ // After the interrupt ends, parseLayouts() will use this to resume the loop.
77838
+ savedNextLayout = this.nextLayout;
77839
+ console.debug('[navLayout] XLR::playInterruptLayout - Starting interrupt', {
77840
+ interruptLayoutId: inputLayout.layoutId,
77841
+ resumeIndex: resumeIndex,
77842
+ currentLayoutId: (_this$currentLayout10 = this.currentLayout) === null || _this$currentLayout10 === void 0 ? void 0 : _this$currentLayout10.layoutId,
77843
+ resumeNextLayoutId: savedNextLayout === null || savedNextLayout === void 0 ? void 0 : savedNextLayout.layoutId
77844
+ });
77845
+ // Prevent A's end handler from calling prepareLayouts (we take over).
77846
+ if (this.currentLayout) {
77847
+ this.currentLayout.inLoop = false;
77848
+ }
77849
+ _context24.next = 10;
77850
+ return (_this$currentLayout11 = this.currentLayout) === null || _this$currentLayout11 === void 0 ? void 0 : _this$currentLayout11.finishAllRegions();
77851
+ case 10:
77852
+ // Register interrupt in uniqueLayouts so getLayout()/prepareLayoutXlf() resolve it.
77853
+ // Do NOT splice into inputLayouts — keeping the original loop intact means
77854
+ // parseLayouts() will see the interrupt as "not in loop" (isCurrentLayoutValid=false)
77855
+ // after it ends, and will correctly advance to savedNextLayout (B).
77856
+ interruptKey = String(inputLayout.layoutId);
77857
+ wasInUniqueLayouts = this.uniqueLayouts.has(interruptKey);
77858
+ if (!wasInUniqueLayouts) {
77859
+ this.uniqueLayouts.set(interruptKey, _objectSpread2(_objectSpread2({}, inputLayout), {}, {
77860
+ index: resumeIndex,
77861
+ id: inputLayout.layoutId
77862
+ }));
77863
+ }
77864
+ // Prepare the interrupt ILayout (fetches XLF, builds regions).
77865
+ _context24.next = 15;
77866
+ return this.prepareLayoutXlf(this.getLayout(inputLayout));
77867
+ case 15:
77868
+ interruptILayout = _context24.sent;
77869
+ // Wire into XLR so playLayouts picks up the interrupt as current.
77870
+ // inLoop=true lets the interrupt's own end handler call prepareLayouts normally.
77871
+ interruptILayout.inLoop = true;
77872
+ this.layouts.current = interruptILayout;
77873
+ this.currentLayout = interruptILayout;
77874
+ this.currentLayoutId = interruptILayout.layoutId;
77875
+ // Restore nextLayout to B so after the interrupt ends, parseLayouts() resumes
77876
+ // the original loop from B (since interrupt.layoutId is not in inputLayouts,
77877
+ // parseLayouts sees it as invalid and advances to nextLayout).
77878
+ if (savedNextLayout) {
77879
+ this.layouts.next = savedNextLayout;
77880
+ this.nextLayout = savedNextLayout;
77881
+ }
77882
+ // Remove interrupt from uniqueLayouts once it ends.
77883
+ cleanup = this.emitter.on('layoutEnd', function (endedLayout) {
77884
+ if (endedLayout !== interruptILayout) return;
77885
+ cleanup();
77886
+ if (!wasInUniqueLayouts) {
77887
+ _this7.uniqueLayouts["delete"](interruptKey);
77888
+ }
77889
+ console.debug('[navLayout] XLR::playInterruptLayout - Interrupt ended, resuming loop', {
77890
+ interruptLayoutId: inputLayout.layoutId,
77891
+ resumeNextLayoutId: savedNextLayout === null || savedNextLayout === void 0 ? void 0 : savedNextLayout.layoutId
77892
+ });
77893
+ });
77894
+ _context24.next = 24;
77895
+ return this.playSchedules(xlrObject);
77896
+ case 24:
77897
+ _context24.prev = 24;
77898
+ isNavigatingLayout = false;
77899
+ return _context24.finish(24);
77900
+ case 27:
77901
+ case "end":
77902
+ return _context24.stop();
77903
+ }
77904
+ }, _callee23, this, [[3,, 24, 27]]);
77905
+ }));
77906
+ return function (_x17) {
77907
+ return _ref23.apply(this, arguments);
77908
+ };
77909
+ }();
77910
+ xlrObject.triggerAction = function (triggerCode, widgetId) {
77911
+ var _this$currentLayout12;
77912
+ (_this$currentLayout12 = this.currentLayout) === null || _this$currentLayout12 === void 0 || (_this$currentLayout12 = _this$currentLayout12.actionController) === null || _this$currentLayout12 === void 0 || _this$currentLayout12.handleWebhookTrigger(triggerCode, widgetId);
77913
+ };
77325
77914
  xlrObject.updateInputLayout = function (layoutIndex, layout) {
77326
77915
  var xlrInputLayout = this.inputLayouts[layoutIndex];
77327
77916
  if (layout !== null) {