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