@xibosignage/xibo-layout-renderer 1.0.24 → 1.0.26

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.
@@ -707,6 +707,7 @@ var initialLayout = {
707
707
  return Promise.resolve([]);
708
708
  },
709
709
  removeLayout: function removeLayout() {},
710
+ discardLayout: function discardLayout() {},
710
711
  getXlf: function getXlf() {
711
712
  return '';
712
713
  },
@@ -717,6 +718,13 @@ var initialLayout = {
717
718
  html: null
718
719
  };
719
720
 
721
+ var MediaState = {
722
+ IDLE: 'idle',
723
+ PLAYING: 'playing',
724
+ ENDED: 'ended',
725
+ CANCELLED: 'cancelled'
726
+ };
727
+
720
728
  var initialRegion = {
721
729
  complete: false,
722
730
  containerName: '',
@@ -765,56 +773,6 @@ var initialRegion = {
765
773
  xlr: {}
766
774
  };
767
775
 
768
- var MediaState = {
769
- IDLE: 'idle',
770
- PLAYING: 'playing',
771
- ENDED: 'ended',
772
- CANCELLED: 'cancelled'
773
- };
774
- var initialMedia = {
775
- attachedAudio: false,
776
- checkIframeStatus: false,
777
- containerName: '',
778
- divHeight: 0,
779
- divWidth: 0,
780
- duration: 0,
781
- emitter: {},
782
- enableStat: false,
783
- fileId: '',
784
- finished: false,
785
- html: null,
786
- id: '',
787
- idCounter: 0,
788
- iframe: null,
789
- iframeName: '',
790
- index: 0,
791
- loadIframeOnRun: false,
792
- loop: false,
793
- mediaId: '',
794
- mediaType: '',
795
- muted: false,
796
- options: {},
797
- player: undefined,
798
- ready: true,
799
- region: initialRegion,
800
- render: 'html',
801
- run: function run() {},
802
- schemaVersion: '1',
803
- singlePlay: false,
804
- state: MediaState.IDLE,
805
- stop: function stop() {
806
- return Promise.resolve();
807
- },
808
- tempSrc: '',
809
- timeoutId: setTimeout(function () {}, 0),
810
- type: '',
811
- uri: '',
812
- url: null,
813
- useDuration: Boolean(0),
814
- xml: null,
815
- mediaTimer: undefined
816
- };
817
-
818
776
  var OverlayLayoutManager = /*#__PURE__*/function () {
819
777
  function OverlayLayoutManager() {
820
778
  _classCallCheck(this, OverlayLayoutManager);
@@ -835,31 +793,37 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
835
793
  case 0:
836
794
  _context2.next = 2;
837
795
  return Promise.all(list.map( /*#__PURE__*/function () {
838
- var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(item) {
839
- var inputOverlay, overlayLayout, $overlay;
796
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(item, index) {
797
+ var _item$index;
798
+ var inputOverlay, overlayLayout, $overlay, _overlayLayout$zIndex;
840
799
  return _regeneratorRuntime().wrap(function _callee$(_context) {
841
800
  while (1) switch (_context.prev = _context.next) {
842
801
  case 0:
843
802
  inputOverlay = {};
844
803
  inputOverlay = _objectSpread2(_objectSpread2({}, inputOverlay), item);
845
- inputOverlay.index = item.index;
804
+ inputOverlay.index = (_item$index = item.index) !== null && _item$index !== void 0 ? _item$index : index;
846
805
  _context.next = 5;
847
806
  return _this.parent.prepareLayoutXlf(_objectSpread2(_objectSpread2({}, initialLayout), inputOverlay));
848
807
  case 5:
849
808
  overlayLayout = _context.sent;
809
+ console.debug('<> XLR.debug OverlayLayoutManager::parseOverlays prepared overlay layout', {
810
+ overlayLayout: overlayLayout,
811
+ inputOverlay: inputOverlay
812
+ });
850
813
  // Hide all overlays first
851
814
  $overlay = document.querySelector("#".concat(overlayLayout.containerName, "[data-sequence=\"").concat(overlayLayout.index, "\"]"));
852
815
  if ($overlay !== null) {
853
- $overlay.style.setProperty('display', 'none');
816
+ $overlay.style.setProperty('visibility', 'hidden');
817
+ $overlay.style.setProperty('z-index', "".concat((_overlayLayout$zIndex = overlayLayout.zIndex) !== null && _overlayLayout$zIndex !== void 0 ? _overlayLayout$zIndex : -999));
854
818
  }
855
819
  return _context.abrupt("return", overlayLayout);
856
- case 9:
820
+ case 10:
857
821
  case "end":
858
822
  return _context.stop();
859
823
  }
860
824
  }, _callee);
861
825
  }));
862
- return function (_x2) {
826
+ return function (_x2, _x3) {
863
827
  return _ref.apply(this, arguments);
864
828
  };
865
829
  }()));
@@ -988,7 +952,7 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
988
952
  }
989
953
  }, _callee3, this, [[12, 21, 24, 27]]);
990
954
  }));
991
- function prepareOverlayLayouts(_x3, _x4) {
955
+ function prepareOverlayLayouts(_x4, _x5) {
992
956
  return _prepareOverlayLayouts.apply(this, arguments);
993
957
  }
994
958
  return prepareOverlayLayouts;
@@ -999,7 +963,8 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
999
963
  var _this$parent$currentL;
1000
964
  if (this.overlays.length === 0) return;
1001
965
  if (this.parent && (_this$parent$currentL = this.parent.currentLayout) !== null && _this$parent$currentL !== void 0 && _this$parent$currentL.isInterrupt()) {
1002
- this.container.style.setProperty('display', 'none');
966
+ this.container.style.setProperty('visibility', 'hidden');
967
+ this.container.style.setProperty('z-index', '-999');
1003
968
  return;
1004
969
  }
1005
970
  this.overlays.forEach(function (overlay) {
@@ -1017,21 +982,25 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
1017
982
  while (1) switch (_context5.prev = _context5.next) {
1018
983
  case 0:
1019
984
  overlayHtml = document.querySelector("#".concat(overlay.containerName, "[data-sequence=\"").concat(overlay.index, "\"]"));
985
+ console.debug('<> XLR.debug OverlayLayoutManager::stopOverlays', {
986
+ overlay: overlay,
987
+ overlayHtml: overlayHtml
988
+ });
1020
989
  if (!(overlayHtml !== null)) {
1021
- _context5.next = 5;
990
+ _context5.next = 6;
1022
991
  break;
1023
992
  }
1024
- _context5.next = 4;
993
+ _context5.next = 5;
1025
994
  return overlay.finishAllRegions();
1026
- case 4:
1027
- overlay.emitter.emit('end', overlay);
1028
995
  case 5:
996
+ overlay.emitter.emit('end', overlay);
997
+ case 6:
1029
998
  case "end":
1030
999
  return _context5.stop();
1031
1000
  }
1032
1001
  }, _callee4);
1033
1002
  }));
1034
- return function (_x5) {
1003
+ return function (_x6) {
1035
1004
  return _ref2.apply(this, arguments);
1036
1005
  };
1037
1006
  }());
@@ -1100,6 +1069,7 @@ var initialXlr = {
1100
1069
  isLayoutInDOM: function isLayoutInDOM(containerName, layoutId) {
1101
1070
  return false;
1102
1071
  },
1072
+ cleanupOrphanedLayouts: function cleanupOrphanedLayouts(_keepCurrent, _keepNext) {},
1103
1073
  isSspEnabled: false,
1104
1074
  isUpdatingLoop: false,
1105
1075
  isUpdatingOverlays: false,
@@ -72520,6 +72490,10 @@ if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
72520
72490
  function composeVideoSource($media, media) {
72521
72491
  // const videoSrc = await preloadMediaBlob(media.url as string, media.mediaType as MediaTypes);
72522
72492
  var vidType = videoFileType(getFileExt(media.uri));
72493
+ if (!vidType) {
72494
+ console.warn("XLR >> VideoMedia: Unsupported video type for media ".concat(media.id, " with uri ").concat(media.uri));
72495
+ return $media;
72496
+ }
72523
72497
  // Only add one source per type
72524
72498
  if ($media.querySelectorAll("source[type=\"".concat(vidType, "\"]")).length === 0) {
72525
72499
  var $videoSource = document.createElement('source');
@@ -72546,8 +72520,38 @@ var vjsDefaultOptions = function vjsDefaultOptions(opts) {
72546
72520
  };
72547
72521
  var reportToPlayerPlatform = [exports.ConsumerPlatform.CHROMEOS, exports.ConsumerPlatform.ELECTRON];
72548
72522
  function VideoMedia(media, xlr) {
72523
+ var stopped = false;
72549
72524
  var mediaId = getMediaId(media);
72525
+ // ── Stall watchdog (closure-level so stop() can cancel it) ───────────────
72526
+ // 'waiting' and 'stalled' fire when the browser stops receiving data.
72527
+ // Unlike codec or source errors they do NOT fire the 'error' event, so
72528
+ // without a watchdog the video silently freezes for its entire duration.
72529
+ var stallWatchdog;
72530
+ var STALL_TIMEOUT_MS = 10000;
72531
+ var clearStallWatchdog = function clearStallWatchdog() {
72532
+ if (stallWatchdog !== undefined) {
72533
+ clearTimeout(stallWatchdog);
72534
+ stallWatchdog = undefined;
72535
+ }
72536
+ };
72537
+ // ─────────────────────────────────────────────────────────────────────────
72538
+ // ── Unified error → report → stop helper (closure-level) ─────────────────
72539
+ // Used by both the 'error' event and the play Promise catch.
72540
+ // playerReportFault only fires for platforms that report faults (Electron,
72541
+ // ChromeOS). All other platforms just advance to the next media via stop().
72542
+ var reportAndStop = function reportAndStop(reason, code) {
72543
+ if (stopped) return;
72544
+ if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72545
+ playerReportFault(reason, media, code).then(function () {
72546
+ return videoPlayer.stop();
72547
+ });
72548
+ } else {
72549
+ videoPlayer.stop();
72550
+ }
72551
+ };
72552
+ // ─────────────────────────────────────────────────────────────────────────
72550
72553
  var videoPlayer = {
72554
+ player: undefined,
72551
72555
  duration: 0,
72552
72556
  init: function init() {
72553
72557
  var _this = this;
@@ -72555,9 +72559,37 @@ function VideoMedia(media, xlr) {
72555
72559
  videoPlayer.duration = media.duration;
72556
72560
  var vjsPlayer = videojs(mediaId);
72557
72561
  if (vjsPlayer) {
72558
- vjsPlayer.on('loadstart', function () {
72559
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72560
- });
72562
+ videoPlayer.player = vjsPlayer;
72563
+ // ── Early source check ────────────────────────────────────────────────
72564
+ // Two-step check before video.js tries to load anything:
72565
+ // 1. Is the file extension one we map to a MIME type?
72566
+ // 2. Can the browser actually play that MIME type?
72567
+ // Failing either step skips the media immediately so video.js
72568
+ // never renders its "No compatible source" error overlay.
72569
+ var vidType = videoFileType(getFileExt(media.uri));
72570
+ if (!vidType) {
72571
+ console.warn("XLR >> VideoMedia: unrecognised file extension for media ".concat(media.id, " (uri: ").concat(media.uri, ")"));
72572
+ reportAndStop("Unsupported video file extension for media ".concat(media.id), exports.FaultCodes.FaultVideoSource);
72573
+ return;
72574
+ }
72575
+ if (document.createElement('video').canPlayType(vidType) === '') {
72576
+ console.warn("XLR >> VideoMedia: browser cannot play type \"".concat(vidType, "\" for media ").concat(media.id));
72577
+ reportAndStop("Browser cannot play video type \"".concat(vidType, "\" for media ").concat(media.id), exports.FaultCodes.FaultVideoSource);
72578
+ return;
72579
+ }
72580
+ // ─────────────────────────────────────────────────────────────────────
72581
+ var armStallWatchdog = function armStallWatchdog() {
72582
+ clearStallWatchdog();
72583
+ stallWatchdog = setTimeout(function () {
72584
+ if (stopped) return;
72585
+ console.warn("XLR >> VideoMedia: stall timeout on media ".concat(media.id));
72586
+ reportAndStop('Video stall timeout', exports.FaultCodes.FaultVideoUnexpected);
72587
+ }, STALL_TIMEOUT_MS);
72588
+ };
72589
+ vjsPlayer.on('waiting', armStallWatchdog);
72590
+ vjsPlayer.on('stalled', armStallWatchdog);
72591
+ vjsPlayer.on('playing', clearStallWatchdog);
72592
+ vjsPlayer.on('ended', clearStallWatchdog);
72561
72593
  vjsPlayer.on('loadedmetadata', function () {
72562
72594
  if (media.duration === 0) {
72563
72595
  videoPlayer.duration = vjsPlayer.duration();
@@ -72628,34 +72660,20 @@ function VideoMedia(media, xlr) {
72628
72660
  }, 5000);
72629
72661
  })]).then(function () {
72630
72662
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay started"));
72631
- })["catch"]( /*#__PURE__*/function () {
72632
- var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72633
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72634
- while (1) switch (_context2.prev = _context2.next) {
72635
- case 0:
72636
- if (error === 'Timeout') {
72637
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72638
- _this.stop();
72639
- } else {
72640
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72641
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72642
- playerReportFault('Media autoplay error', media).then(function () {
72643
- _this.stop();
72644
- });
72645
- }
72646
- }
72647
- case 1:
72648
- case "end":
72649
- return _context2.stop();
72650
- }
72651
- }, _callee2);
72652
- }));
72653
- return function (_x) {
72654
- return _ref2.apply(this, arguments);
72655
- };
72656
- }());
72663
+ })["catch"](function (error) {
72664
+ if (stopped) return;
72665
+ if (error === 'Timeout') {
72666
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72667
+ // Timeout is a scheduling issue, not a media fault — just advance
72668
+ videoPlayer.stop();
72669
+ } else {
72670
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72671
+ reportAndStop('Media autoplay error', exports.FaultCodes.FaultVideoUnexpected);
72672
+ }
72673
+ });
72657
72674
  // Optional: Reset the flag automatically when a new video loads or the source changes
72658
72675
  vjsPlayer.on('loadstart', function () {
72676
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72659
72677
  triggerTimeUpdate = false;
72660
72678
  });
72661
72679
  if (media.duration === 0) {
@@ -72668,13 +72686,9 @@ function VideoMedia(media, xlr) {
72668
72686
  if (mediaDuration !== undefined && currentTime !== undefined) {
72669
72687
  remainingTimeMs = (mediaDuration - currentTime) * 1000;
72670
72688
  }
72671
- if (regionHasMultipleMedia && remainingTimeMs === 0 && !triggerTimeUpdate) {
72672
- // We don't have data yet and we must immediately prepare next media
72673
- media.region.prepareNextMedia();
72674
- } else if (regionHasMultipleMedia && remainingTimeMs <= preloadBufferTimeMs && !triggerTimeUpdate) {
72689
+ if (regionHasMultipleMedia && !triggerTimeUpdate && (remainingTimeMs === 0 || remainingTimeMs <= preloadBufferTimeMs)) {
72675
72690
  // Check if remaining time is less than preloadBufferTimeMs and the action hasn't been triggered yet
72676
72691
  console.log('Less than preloadBufferTimeMs remaining! Do something now.');
72677
- // Prepare next media in region
72678
72692
  media.region.prepareNextMedia();
72679
72693
  triggerTimeUpdate = true; // Set the flag to prevent re-triggering
72680
72694
  }
@@ -72686,33 +72700,16 @@ function VideoMedia(media, xlr) {
72686
72700
  }
72687
72701
  }
72688
72702
  });
72689
- vjsPlayer.on('error', /*#__PURE__*/function () {
72690
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(err) {
72691
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
72692
- while (1) switch (_context3.prev = _context3.next) {
72693
- case 0:
72694
- console.debug("??? XLR.debug >> VideoMedia: Media Error: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id));
72695
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72696
- playerReportFault('Video file source not supported', media).then(function () {
72697
- _this.stop();
72698
- });
72699
- } else {
72700
- // End media after 5 seconds
72701
- setTimeout(function () {
72702
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended . . ."));
72703
- _this.stop();
72704
- }, 5000);
72705
- }
72706
- case 2:
72707
- case "end":
72708
- return _context3.stop();
72709
- }
72710
- }, _callee3);
72711
- }));
72712
- return function (_x2) {
72713
- return _ref3.apply(this, arguments);
72714
- };
72715
- }());
72703
+ vjsPlayer.on('error', function () {
72704
+ if (stopped) return;
72705
+ clearStallWatchdog();
72706
+ // Extract the actual MediaError so the fault message is
72707
+ // meaningful: code 2 = network, 3 = decode, 4 = not supported.
72708
+ var vjsError = vjsPlayer.error();
72709
+ var reason = vjsError ? "Video error (code ".concat(vjsError.code, "): ").concat(vjsError.message) : 'Unknown video error';
72710
+ console.warn("XLR >> VideoMedia: error on media ".concat(media.id), vjsError);
72711
+ reportAndStop(reason, exports.FaultCodes.FaultVideoUnexpected);
72712
+ });
72716
72713
  if (media.duration === 0) {
72717
72714
  vjsPlayer.on('ended', function () {
72718
72715
  console.debug("??? XLR.debug >> VideoMedia: onended: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
@@ -72722,26 +72719,37 @@ function VideoMedia(media, xlr) {
72722
72719
  }
72723
72720
  },
72724
72721
  stop: function stop() {
72722
+ var _videoPlayer$player;
72725
72723
  var disposeOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
72726
- var vjsPlayer = media.player;
72724
+ clearStallWatchdog();
72725
+ // videoPlayer.player is where init() stores the vjs instance;
72726
+ // media.player is a legacy path kept for backward compat but is
72727
+ // no longer set by init(), so always prefer videoPlayer.player.
72728
+ var vjsPlayer = (_videoPlayer$player = videoPlayer.player) !== null && _videoPlayer$player !== void 0 ? _videoPlayer$player : media.player;
72727
72729
  console.debug('??? XLR.debug >> VideoMedia::stop', {
72728
72730
  vjsPlayer: vjsPlayer,
72729
- isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed_,
72730
- el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el_
72731
+ isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed(),
72732
+ el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el()
72731
72733
  });
72732
72734
  // Expire the media and dispose the video
72733
- if (vjsPlayer !== undefined && !vjsPlayer.isDisposed_) {
72735
+ if (vjsPlayer !== undefined && !vjsPlayer.isDisposed()) {
72734
72736
  if (!disposeOnly) {
72735
72737
  media.emitter.emit('end', media);
72736
72738
  }
72737
72739
  vjsPlayer.dispose();
72738
72740
  // Clear up media player
72741
+ videoPlayer.player = undefined;
72739
72742
  media.player = undefined;
72743
+ media.html = null;
72740
72744
  } else {
72745
+ videoPlayer.player = undefined;
72741
72746
  media.player = undefined;
72742
72747
  media.html = null;
72743
- media.emitter.emit('end', media);
72748
+ if (!disposeOnly) {
72749
+ media.emitter.emit('end', media);
72750
+ }
72744
72751
  }
72752
+ stopped = true;
72745
72753
  },
72746
72754
  play: function play() {
72747
72755
  var _this2 = this;
@@ -72749,9 +72757,9 @@ function VideoMedia(media, xlr) {
72749
72757
  if (vjsPlayer !== undefined) {
72750
72758
  var _vjsPlayer$play;
72751
72759
  (_vjsPlayer$play = vjsPlayer.play()) === null || _vjsPlayer$play === void 0 || _vjsPlayer$play["catch"]( /*#__PURE__*/function () {
72752
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(error) {
72753
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
72754
- while (1) switch (_context4.prev = _context4.next) {
72760
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72761
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72762
+ while (1) switch (_context2.prev = _context2.next) {
72755
72763
  case 0:
72756
72764
  if (error === 'Timeout') {
72757
72765
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
@@ -72766,12 +72774,12 @@ function VideoMedia(media, xlr) {
72766
72774
  }
72767
72775
  case 1:
72768
72776
  case "end":
72769
- return _context4.stop();
72777
+ return _context2.stop();
72770
72778
  }
72771
- }, _callee4);
72779
+ }, _callee2);
72772
72780
  }));
72773
- return function (_x3) {
72774
- return _ref4.apply(this, arguments);
72781
+ return function (_x) {
72782
+ return _ref2.apply(this, arguments);
72775
72783
  };
72776
72784
  }());
72777
72785
  }
@@ -73183,11 +73191,11 @@ function getDataBlob(_x, _x2) {
73183
73191
  return _getDataBlob.apply(this, arguments);
73184
73192
  }
73185
73193
  function _getDataBlob() {
73186
- _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, jwtToken) {
73187
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73188
- while (1) switch (_context3.prev = _context3.next) {
73194
+ _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(src, jwtToken) {
73195
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73196
+ while (1) switch (_context2.prev = _context2.next) {
73189
73197
  case 0:
73190
- return _context3.abrupt("return", fetch(src, {
73198
+ return _context2.abrupt("return", fetch(src, {
73191
73199
  method: 'GET',
73192
73200
  headers: {
73193
73201
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73206,9 +73214,9 @@ function _getDataBlob() {
73206
73214
  }));
73207
73215
  case 1:
73208
73216
  case "end":
73209
- return _context3.stop();
73217
+ return _context2.stop();
73210
73218
  }
73211
- }, _callee3);
73219
+ }, _callee2);
73212
73220
  }));
73213
73221
  return _getDataBlob.apply(this, arguments);
73214
73222
  }
@@ -73216,12 +73224,12 @@ function preloadMediaBlob(_x3, _x4, _x5) {
73216
73224
  return _preloadMediaBlob.apply(this, arguments);
73217
73225
  }
73218
73226
  function _preloadMediaBlob() {
73219
- _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(src, type, jwtToken) {
73227
+ _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, type, jwtToken) {
73220
73228
  var res, blob, data;
73221
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73222
- while (1) switch (_context4.prev = _context4.next) {
73229
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73230
+ while (1) switch (_context3.prev = _context3.next) {
73223
73231
  case 0:
73224
- _context4.next = 2;
73232
+ _context3.next = 2;
73225
73233
  return fetch(src, {
73226
73234
  method: 'GET',
73227
73235
  headers: {
@@ -73229,45 +73237,45 @@ function _preloadMediaBlob() {
73229
73237
  }
73230
73238
  });
73231
73239
  case 2:
73232
- res = _context4.sent;
73240
+ res = _context3.sent;
73233
73241
  blob = new Blob();
73234
73242
  if (!(type === 'image')) {
73235
- _context4.next = 8;
73243
+ _context3.next = 8;
73236
73244
  break;
73237
73245
  }
73238
73246
  blob = new Blob();
73239
- _context4.next = 19;
73247
+ _context3.next = 19;
73240
73248
  break;
73241
73249
  case 8:
73242
73250
  if (!(type === 'video')) {
73243
- _context4.next = 14;
73251
+ _context3.next = 14;
73244
73252
  break;
73245
73253
  }
73246
- _context4.next = 11;
73254
+ _context3.next = 11;
73247
73255
  return res.blob();
73248
73256
  case 11:
73249
- blob = _context4.sent;
73250
- _context4.next = 19;
73257
+ blob = _context3.sent;
73258
+ _context3.next = 19;
73251
73259
  break;
73252
73260
  case 14:
73253
73261
  if (!(type === 'audio')) {
73254
- _context4.next = 19;
73262
+ _context3.next = 19;
73255
73263
  break;
73256
73264
  }
73257
- _context4.next = 17;
73265
+ _context3.next = 17;
73258
73266
  return res.arrayBuffer();
73259
73267
  case 17:
73260
- data = _context4.sent;
73268
+ data = _context3.sent;
73261
73269
  blob = new Blob([data], {
73262
73270
  type: audioFileType(getFileExt(src))
73263
73271
  });
73264
73272
  case 19:
73265
- return _context4.abrupt("return", URL.createObjectURL(blob));
73273
+ return _context3.abrupt("return", URL.createObjectURL(blob));
73266
73274
  case 20:
73267
73275
  case "end":
73268
- return _context4.stop();
73276
+ return _context3.stop();
73269
73277
  }
73270
- }, _callee4);
73278
+ }, _callee3);
73271
73279
  }));
73272
73280
  return _preloadMediaBlob.apply(this, arguments);
73273
73281
  }
@@ -73275,11 +73283,11 @@ function fetchJSON(_x6, _x7) {
73275
73283
  return _fetchJSON.apply(this, arguments);
73276
73284
  }
73277
73285
  function _fetchJSON() {
73278
- _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73279
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73280
- while (1) switch (_context5.prev = _context5.next) {
73286
+ _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(url, jwtToken) {
73287
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73288
+ while (1) switch (_context4.prev = _context4.next) {
73281
73289
  case 0:
73282
- return _context5.abrupt("return", fetch(url, {
73290
+ return _context4.abrupt("return", fetch(url, {
73283
73291
  method: 'GET',
73284
73292
  headers: {
73285
73293
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73291,9 +73299,9 @@ function _fetchJSON() {
73291
73299
  }));
73292
73300
  case 1:
73293
73301
  case "end":
73294
- return _context5.stop();
73302
+ return _context4.stop();
73295
73303
  }
73296
- }, _callee5);
73304
+ }, _callee4);
73297
73305
  }));
73298
73306
  return _fetchJSON.apply(this, arguments);
73299
73307
  }
@@ -73301,11 +73309,11 @@ function fetchText(_x8, _x9) {
73301
73309
  return _fetchText.apply(this, arguments);
73302
73310
  }
73303
73311
  function _fetchText() {
73304
- _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(url, jwtToken) {
73305
- return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73306
- while (1) switch (_context6.prev = _context6.next) {
73312
+ _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73313
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73314
+ while (1) switch (_context5.prev = _context5.next) {
73307
73315
  case 0:
73308
- return _context6.abrupt("return", fetch(url, {
73316
+ return _context5.abrupt("return", fetch(url, {
73309
73317
  method: 'GET',
73310
73318
  headers: {
73311
73319
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73324,9 +73332,9 @@ function _fetchText() {
73324
73332
  }));
73325
73333
  case 1:
73326
73334
  case "end":
73327
- return _context6.stop();
73335
+ return _context5.stop();
73328
73336
  }
73329
- }, _callee6);
73337
+ }, _callee5);
73330
73338
  }));
73331
73339
  return _fetchText.apply(this, arguments);
73332
73340
  }
@@ -73443,6 +73451,32 @@ function setExpiry(numDays) {
73443
73451
  var today = new Date();
73444
73452
  return new Date(today.setHours(24 * numDays || 1)).toJSON();
73445
73453
  }
73454
+ /**
73455
+ * Check whether a media item is currently within its valid date window.
73456
+ * Returns true when the media should be shown, false when it should be skipped.
73457
+ *
73458
+ * Rules:
73459
+ * - Empty / invalid fromDt → treat as "no start restriction"
73460
+ * - Empty / invalid toDt → treat as "no expiry"
73461
+ * - now < fromDt → not yet active → skip
73462
+ * - now > toDt → expired → skip
73463
+ */
73464
+ function isMediaActive(fromDt, toDt) {
73465
+ var now = Date.now();
73466
+ if (fromDt) {
73467
+ var from = new Date(fromDt).getTime();
73468
+ if (!isNaN(from) && now < from) {
73469
+ return false;
73470
+ }
73471
+ }
73472
+ if (toDt) {
73473
+ var to = new Date(toDt).getTime();
73474
+ if (!isNaN(to) && now > to) {
73475
+ return false;
73476
+ }
73477
+ }
73478
+ return true;
73479
+ }
73446
73480
  /**
73447
73481
  * Check if given layout exists in the loop using layoutId
73448
73482
  * @param layouts Schedule loop unique layouts (uniqueLayouts)
@@ -73651,7 +73685,7 @@ function prepareVideoMedia(media, region) {
73651
73685
  var $layout = region.layout.html;
73652
73686
  var layoutSelector = '#' + region.layout.containerName + '[data-sequence="' + region.layout.index + '"]';
73653
73687
  var $layoutWithIndex = document.querySelector(layoutSelector);
73654
- var $region = document.querySelector('#' + region.containerName);
73688
+ var $region = region.html;
73655
73689
  var mediaInRegion = $region === null || $region === void 0 ? void 0 : $region.querySelector('.' + mediaId);
73656
73690
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73657
73691
  layoutSelector: layoutSelector,
@@ -73670,7 +73704,7 @@ function prepareVideoMedia(media, region) {
73670
73704
  media.html = createMediaElement(media);
73671
73705
  }
73672
73706
  // Append fresh copy of the media into the region
73673
- $region !== null && $region.appendChild(media.html);
73707
+ region.html.appendChild(media.html);
73674
73708
  var isMediaInDOM = document.body.contains(media.html);
73675
73709
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73676
73710
  isMediaInDOM: isMediaInDOM,
@@ -73679,29 +73713,9 @@ function prepareVideoMedia(media, region) {
73679
73713
  });
73680
73714
  // Initialize video.js
73681
73715
  media.player = videojs(mediaId, _objectSpread2(_objectSpread2({}, defaultVjsOpts), {}, {
73682
- errorDisplay: region.xlr.config.platform !== exports.ConsumerPlatform.CHROMEOS,
73716
+ errorDisplay: !reportToPlayerPlatform.includes(region.xlr.config.platform),
73683
73717
  loop: media.loop
73684
73718
  }));
73685
- media.player.on('error', /*#__PURE__*/function () {
73686
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(err) {
73687
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73688
- while (1) switch (_context2.prev = _context2.next) {
73689
- case 0:
73690
- if (media.region.xlr.config.platform === exports.ConsumerPlatform.CHROMEOS) {
73691
- playerReportFault('Video file not supported', media).then(function () {
73692
- media.emitter.emit('end', media);
73693
- });
73694
- }
73695
- case 1:
73696
- case "end":
73697
- return _context2.stop();
73698
- }
73699
- }, _callee2);
73700
- }));
73701
- return function (_x10) {
73702
- return _ref3.apply(this, arguments);
73703
- };
73704
- }());
73705
73719
  media.player.el().style.setProperty('visibility', 'hidden');
73706
73720
  media.player.el().style.setProperty('opacity', '0');
73707
73721
  media.player.el().style.setProperty('z-index', '-99');
@@ -73716,9 +73730,9 @@ function prepareImageMedia(media, region) {
73716
73730
  if (mediaInRegion) {
73717
73731
  mediaInRegion.remove();
73718
73732
  }
73719
- // Append media to its region
73720
- var $region = document.querySelector('#' + region.containerName);
73721
- $region !== null && $region.appendChild(media.html);
73733
+ // Append media to its region using the direct reference to avoid
73734
+ // global querySelector finding a same-named region in another layout
73735
+ region.html.appendChild(media.html);
73722
73736
  }
73723
73737
  function prepareAudioMedia(media, region) {
73724
73738
  var mediaId = getMediaId(media);
@@ -73731,9 +73745,8 @@ function prepareAudioMedia(media, region) {
73731
73745
  if (mediaInRegion) {
73732
73746
  mediaInRegion.remove();
73733
73747
  }
73734
- // Append media to its region
73735
- var $region = document.querySelector('#' + region.containerName);
73736
- $region !== null && $region.appendChild(media.html);
73748
+ // Append media to its region using the direct reference
73749
+ region.html.appendChild(media.html);
73737
73750
  }
73738
73751
  function prepareHtmlMedia(media, region) {
73739
73752
  // Set state as false ( for now )
@@ -73753,60 +73766,102 @@ function prepareHtmlMedia(media, region) {
73753
73766
  media.html.innerHTML = '';
73754
73767
  media.html.appendChild(media.iframe);
73755
73768
  if (!mediaInRegion) {
73756
- // Add fresh copy of the media into the region
73757
- var _$region = document.querySelector('#' + region.containerName);
73758
- _$region !== null && _$region.appendChild(media.html);
73769
+ // Add fresh copy of the media into the region using the direct reference
73770
+ region.html.appendChild(media.html);
73759
73771
  media.ready = true;
73760
73772
  }
73761
73773
  }
73762
73774
  }
73763
- function playerReportFault(_x11, _x12) {
73775
+ exports.FaultCodes = void 0;
73776
+ (function (FaultCodes) {
73777
+ FaultCodes[FaultCodes["FaultVideoSource"] = 2001] = "FaultVideoSource";
73778
+ FaultCodes[FaultCodes["FaultVideoUnexpected"] = 2099] = "FaultVideoUnexpected";
73779
+ })(exports.FaultCodes || (exports.FaultCodes = {}));
73780
+ function playerReportFault(_x10, _x11) {
73764
73781
  return _playerReportFault.apply(this, arguments);
73765
73782
  }
73766
73783
  function _playerReportFault() {
73767
- _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(msg, media) {
73768
- var playerSW, hasSW;
73769
- return _regeneratorRuntime().wrap(function _callee7$(_context7) {
73770
- while (1) switch (_context7.prev = _context7.next) {
73784
+ _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(msg, media) {
73785
+ var code,
73786
+ platform,
73787
+ playerSW,
73788
+ hasSW,
73789
+ mediaFault,
73790
+ channel,
73791
+ _args6 = arguments;
73792
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73793
+ while (1) switch (_context6.prev = _context6.next) {
73771
73794
  case 0:
73795
+ code = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : exports.FaultCodes.FaultVideoUnexpected;
73772
73796
  // Immediately expire media and report a fault
73797
+ platform = media.region.xlr.config.platform;
73773
73798
  playerSW = PwaSW();
73774
- _context7.next = 3;
73799
+ _context6.next = 5;
73775
73800
  return playerSW.getSW();
73776
- case 3:
73777
- hasSW = _context7.sent;
73778
- if (hasSW) {
73779
- playerSW.postMsg({
73780
- type: 'MEDIA_FAULT',
73781
- code: 5002,
73782
- reason: msg,
73801
+ case 5:
73802
+ hasSW = _context6.sent;
73803
+ mediaFault = {
73804
+ type: 'MEDIA_FAULT',
73805
+ code: code,
73806
+ reason: msg,
73807
+ mediaId: media.id,
73808
+ regionId: media.region.id,
73809
+ layoutId: media.region.layout.id,
73810
+ date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73811
+ // Temporary setting
73812
+ expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73813
+ };
73814
+ console.debug('playerReportFault >> Reporting media fault', {
73815
+ mediaFault: mediaFault,
73816
+ platform: platform,
73817
+ hasSW: hasSW
73818
+ });
73819
+ if (!(platform === exports.ConsumerPlatform.CHROMEOS && hasSW)) {
73820
+ _context6.next = 12;
73821
+ break;
73822
+ }
73823
+ return _context6.abrupt("return", playerSW.postMsg(mediaFault).then(function () {
73824
+ // We try to prepare next media if we have more than 1 media
73825
+ if (media.region.totalMediaObjects > 1) {
73826
+ media.region.prepareNextMedia();
73827
+ }
73828
+ })["finally"](function () {
73829
+ // Stopping media as we have reported the error as fault
73830
+ console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73783
73831
  mediaId: media.id,
73784
- regionId: media.region.id,
73785
- layoutId: media.region.layout.id,
73786
- date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73787
- // Temporary setting
73788
- expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73789
- }).then(function () {
73790
- // We try to prepare next media if we have more than 1 media
73791
- if (media.region.totalMediaObjects > 1) {
73792
- media.region.prepareNextMedia();
73793
- }
73794
- })["finally"](function () {
73795
- // Stopping media as we have reported the error as fault
73796
- console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73797
- mediaId: media.id,
73798
- regionItems: media.region.totalMediaObjects
73799
- });
73832
+ regionItems: media.region.totalMediaObjects
73800
73833
  });
73834
+ }));
73835
+ case 12:
73836
+ if (!(platform === exports.ConsumerPlatform.ELECTRON)) {
73837
+ _context6.next = 17;
73838
+ break;
73801
73839
  }
73802
- case 5:
73840
+ // Create a broadcast channel to report media fault to the main process
73841
+ channel = new BroadcastChannel('player-faults-bc');
73842
+ channel.postMessage(mediaFault);
73843
+ console.debug('playerReportFault >> Electron platform - posted media fault to channel', {
73844
+ mediaFault: mediaFault
73845
+ });
73846
+ // channel.close();
73847
+ return _context6.abrupt("return", Promise.resolve());
73848
+ case 17:
73849
+ return _context6.abrupt("return", Promise.resolve());
73850
+ case 18:
73803
73851
  case "end":
73804
- return _context7.stop();
73852
+ return _context6.stop();
73805
73853
  }
73806
- }, _callee7);
73854
+ }, _callee6);
73807
73855
  }));
73808
73856
  return _playerReportFault.apply(this, arguments);
73809
73857
  }
73858
+ function setLayoutIndex(layout, layoutIndex) {
73859
+ if (!layout || layout.id === null) {
73860
+ return;
73861
+ }
73862
+ layout.index = layoutIndex;
73863
+ return layout;
73864
+ }
73810
73865
 
73811
73866
  const urlAlphabet =
73812
73867
  'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
@@ -73824,7 +73879,7 @@ let nanoid = (size = 21) => {
73824
73879
  function AudioMedia(media) {
73825
73880
  var audioMediaObject = {
73826
73881
  init: function init() {
73827
- var $audioMedia = document.getElementById(getMediaId(media));
73882
+ var $audioMedia = media.html;
73828
73883
  var $playBtn = null;
73829
73884
  if ($audioMedia) {
73830
73885
  $audioMedia.onloadstart = function () {
@@ -73887,6 +73942,8 @@ var Media = /*#__PURE__*/function () {
73887
73942
  _this$xml3,
73888
73943
  _this$xml4,
73889
73944
  _this$xml5,
73945
+ _this$xml6,
73946
+ _this$xml7,
73890
73947
  _this = this;
73891
73948
  _classCallCheck(this, Media);
73892
73949
  _defineProperty(this, "attachedAudio", false);
@@ -73899,6 +73956,7 @@ var Media = /*#__PURE__*/function () {
73899
73956
  _defineProperty(this, "enableStat", false);
73900
73957
  _defineProperty(this, "fileId", '');
73901
73958
  _defineProperty(this, "finished", false);
73959
+ _defineProperty(this, "fromDt", '');
73902
73960
  _defineProperty(this, "html", null);
73903
73961
  _defineProperty(this, "id", '');
73904
73962
  _defineProperty(this, "idCounter", 0);
@@ -73920,6 +73978,7 @@ var Media = /*#__PURE__*/function () {
73920
73978
  _defineProperty(this, "state", MediaState.IDLE);
73921
73979
  _defineProperty(this, "tempSrc", '');
73922
73980
  _defineProperty(this, "timeoutId", setTimeout(function () {}, 0));
73981
+ _defineProperty(this, "toDt", '');
73923
73982
  _defineProperty(this, "type", '');
73924
73983
  _defineProperty(this, "uri", '');
73925
73984
  _defineProperty(this, "url", null);
@@ -73946,12 +74005,14 @@ var Media = /*#__PURE__*/function () {
73946
74005
  this.duration = parseInt((_this$xml4 = this.xml) === null || _this$xml4 === void 0 ? void 0 : _this$xml4.getAttribute('duration')) || 0;
73947
74006
  this.enableStat = Boolean(((_this$xml5 = this.xml) === null || _this$xml5 === void 0 ? void 0 : _this$xml5.getAttribute('enableStat')) || false);
73948
74007
  this.hasCommandExecuted = false;
74008
+ this.fromDt = ((_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getAttribute('fromDt')) || '';
74009
+ this.toDt = ((_this$xml7 = this.xml) === null || _this$xml7 === void 0 ? void 0 : _this$xml7.getAttribute('toDt')) || '';
73949
74010
  this.on('start', function (media) {
73950
74011
  if (media.state === MediaState.PLAYING) return;
73951
74012
  media.state = MediaState.PLAYING;
73952
74013
  if (media.mediaType === 'video') {
73953
- var videoMedia = VideoMedia(media, _this.xlr);
73954
- videoMedia.init();
74014
+ media.videoHandler = VideoMedia(media, _this.xlr);
74015
+ media.videoHandler.init();
73955
74016
  if (media.duration > 0) {
73956
74017
  _this.startMediaTimer(media);
73957
74018
  }
@@ -74078,8 +74139,8 @@ var Media = /*#__PURE__*/function () {
74078
74139
  if (media.mediaType === 'video') {
74079
74140
  // Dispose the video media
74080
74141
  console.debug("??? XLR.debug >> VideoMedia::stop - ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
74081
- if (media.player !== undefined) {
74082
- VideoMedia(media, _this2.xlr).stop(true);
74142
+ if (media.videoHandler !== undefined) {
74143
+ media.videoHandler.stop(true);
74083
74144
  }
74084
74145
  }
74085
74146
  }
@@ -74094,8 +74155,8 @@ var Media = /*#__PURE__*/function () {
74094
74155
  }, {
74095
74156
  key: "init",
74096
74157
  value: function init() {
74097
- var _this$xml6;
74098
- var mediaOptions = (_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getElementsByTagName('options');
74158
+ var _this$xml8;
74159
+ var mediaOptions = (_this$xml8 = this.xml) === null || _this$xml8 === void 0 ? void 0 : _this$xml8.getElementsByTagName('options');
74099
74160
  if (mediaOptions) {
74100
74161
  for (var _i = 0, _Array$from = Array.from(mediaOptions); _i < _Array$from.length; _i++) {
74101
74162
  var _options = _Array$from[_i];
@@ -74150,6 +74211,10 @@ var Media = /*#__PURE__*/function () {
74150
74211
  }
74151
74212
  } else if (this.xlr.config.platform === exports.ConsumerPlatform.ELECTRON) {
74152
74213
  tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74214
+ // this is an SSP Layout
74215
+ if (this.region.layout.layoutId === -1) {
74216
+ tmpUrl = this.uri;
74217
+ }
74153
74218
  }
74154
74219
  this.url = tmpUrl;
74155
74220
  // Loop if media has loop, or if region has loop and a single media
@@ -74192,8 +74257,8 @@ var Media = /*#__PURE__*/function () {
74192
74257
  mediaType: _this3.mediaType,
74193
74258
  containerName: _this3.containerName
74194
74259
  });
74195
- var $region = document.querySelector('#' + _this3.region.containerName);
74196
- var $media = $region !== null && $region.querySelector('.' + mediaId);
74260
+ var $region = _this3.region.html;
74261
+ var $media = $region.querySelector('.' + mediaId);
74197
74262
  if (!$media) {
74198
74263
  $media = getNewMedia();
74199
74264
  }
@@ -74257,13 +74322,13 @@ var Media = /*#__PURE__*/function () {
74257
74322
  }
74258
74323
  };
74259
74324
  var getNewMedia = function getNewMedia() {
74260
- var $region = document.getElementById("".concat(_this3.region.containerName));
74325
+ var $region = _this3.region.html;
74261
74326
  // This function is for checking whether
74262
74327
  // the region still has to show a media item
74263
74328
  // when another region is not finished yet
74264
74329
  if (_this3.region.complete && !_this3.region.layout.allEnded) {
74265
74330
  // Add currentMedia to the region
74266
- $region && $region.insertBefore(_this3.html, $region.lastElementChild);
74331
+ $region.insertBefore(_this3.html, $region.lastElementChild);
74267
74332
  return _this3.html;
74268
74333
  }
74269
74334
  return null;
@@ -74274,23 +74339,18 @@ var Media = /*#__PURE__*/function () {
74274
74339
  key: "stop",
74275
74340
  value: function () {
74276
74341
  var _stop = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
74277
- var $media;
74278
74342
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74279
74343
  while (1) switch (_context.prev = _context.next) {
74280
74344
  case 0:
74281
- $media = document.getElementById(getMediaId({
74282
- mediaType: this.mediaType,
74283
- containerName: this.containerName
74284
- }));
74285
- if ($media) {
74286
- $media.style.display = 'none';
74287
- $media.remove();
74345
+ if (this.html) {
74346
+ this.html.style.display = 'none';
74347
+ this.html.remove();
74288
74348
  }
74289
74349
  // Release blob URLs for image media to prevent memory leaks on long-running signage
74290
74350
  if (this.mediaType === 'image' && this.url) {
74291
74351
  BlobLoader.release(this.url);
74292
74352
  }
74293
- case 3:
74353
+ case 2:
74294
74354
  case "end":
74295
74355
  return _context.stop();
74296
74356
  }
@@ -74463,18 +74523,30 @@ var Region = /*#__PURE__*/function () {
74463
74523
  this.html = $region;
74464
74524
  /* Parse region media objects */
74465
74525
  var regionMediaItems = Array.from(this.xml.getElementsByTagName('media'));
74466
- this.totalMediaObjects = regionMediaItems.length;
74467
74526
  $layout && $layout.appendChild(this.html);
74468
74527
  Array.from(regionMediaItems).forEach( /*#__PURE__*/function () {
74469
74528
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(mediaXml, indx) {
74470
- var mediaObj;
74529
+ var fromDt, toDt, mediaObj;
74471
74530
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74472
74531
  while (1) switch (_context.prev = _context.next) {
74473
74532
  case 0:
74533
+ fromDt = mediaXml.getAttribute('fromDt') || '';
74534
+ toDt = mediaXml.getAttribute('toDt') || '';
74535
+ if (isMediaActive(fromDt, toDt)) {
74536
+ _context.next = 5;
74537
+ break;
74538
+ }
74539
+ console.debug('??? XLR.debug >> Region::prepareRegion - skipping expired/inactive media', {
74540
+ mediaId: mediaXml.getAttribute('id'),
74541
+ fromDt: fromDt,
74542
+ toDt: toDt
74543
+ });
74544
+ return _context.abrupt("return");
74545
+ case 5:
74474
74546
  mediaObj = new Media(_this, (mediaXml === null || mediaXml === void 0 ? void 0 : mediaXml.getAttribute('id')) || '', mediaXml, _this.options, _this.xlr);
74475
74547
  mediaObj.index = indx;
74476
74548
  _this.mediaObjects.push(mediaObj);
74477
- case 3:
74549
+ case 8:
74478
74550
  case "end":
74479
74551
  return _context.stop();
74480
74552
  }
@@ -74484,6 +74556,7 @@ var Region = /*#__PURE__*/function () {
74484
74556
  return _ref.apply(this, arguments);
74485
74557
  };
74486
74558
  }());
74559
+ this.totalMediaObjects = this.mediaObjects.length;
74487
74560
  console.debug('??? XLR.debug >> Region - done looping through media', {
74488
74561
  mediaObjects: this.mediaObjects
74489
74562
  });
@@ -74539,6 +74612,23 @@ var Region = /*#__PURE__*/function () {
74539
74612
  key: "prepareNextMedia",
74540
74613
  value: function prepareNextMedia() {
74541
74614
  var nextMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74615
+ // Skip over any media items that are no longer within their active date window
74616
+ var skippedCount = 0;
74617
+ while (skippedCount < this.totalMediaObjects) {
74618
+ var candidate = this.mediaObjects[nextMediaIndex];
74619
+ if (isMediaActive(candidate.fromDt, candidate.toDt)) {
74620
+ break;
74621
+ }
74622
+ skippedCount++;
74623
+ nextMediaIndex = (nextMediaIndex + 1) % this.totalMediaObjects;
74624
+ }
74625
+ // Nothing to pre-load when:
74626
+ // - every item is expired, OR
74627
+ // - the only active item is the one currently playing (skip loop wrapped back to it)
74628
+ if (skippedCount >= this.totalMediaObjects || nextMediaIndex === this.currentMediaIndex) {
74629
+ console.debug('<><> XLR.debug >> [Region::prepareNextMedia()] - no active next media to preload');
74630
+ return;
74631
+ }
74542
74632
  var nextMedia = this.mediaObjects[nextMediaIndex];
74543
74633
  console.debug('<><> XLR.debug >> [Media] - [Region::prepareNextMedia()] - Preparing next media', {
74544
74634
  currentMediaIndex: this.currentMediaIndex,
@@ -74579,9 +74669,21 @@ var Region = /*#__PURE__*/function () {
74579
74669
  }, {
74580
74670
  key: "run",
74581
74671
  value: function run() {
74672
+ var _this$currMedia, _this$oldMedia2;
74582
74673
  console.debug('??? XLR.debug >> Region Called Region::run > ', this.id);
74583
74674
  // Reset region states
74584
74675
  this.reset();
74676
+ // All media were filtered out (all expired/inactive before this run started)
74677
+ if (this.mediaObjects.length === 0) {
74678
+ console.debug('??? XLR.debug >> Region::run - no active media, finishing region', this.id);
74679
+ this.finished();
74680
+ return;
74681
+ }
74682
+ console.debug('??? XLR.debug >> Region Called Region::run - after reset > ', {
74683
+ regionId: this.id,
74684
+ currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74685
+ oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName
74686
+ });
74585
74687
  if (this.currMedia) {
74586
74688
  this.transitionNodes(this.oldMedia, this.currMedia);
74587
74689
  }
@@ -74630,6 +74732,7 @@ var Region = /*#__PURE__*/function () {
74630
74732
  // Hide oldMedia
74631
74733
  if (oldMedia) {
74632
74734
  var $layout = document.querySelector("#".concat(_this2.layout.containerName, "[data-sequence=\"").concat(_this2.layout.index, "\"]"));
74735
+ if (!$layout) return;
74633
74736
  var $region = $layout.querySelector('#' + _this2.containerName);
74634
74737
  var $oldMedia = $region ? $region.querySelector('.' + getMediaId(oldMedia)) : null;
74635
74738
  if ($oldMedia) {
@@ -74650,7 +74753,7 @@ var Region = /*#__PURE__*/function () {
74650
74753
  $videoWrapper.style.setProperty('visibility', 'hidden');
74651
74754
  $videoWrapper.style.setProperty('z-index', '-999');
74652
74755
  $videoWrapper.style.setProperty('opacity', '0');
74653
- if (oldMedia.player && oldMedia.videoHandler) {
74756
+ if (oldMedia.videoHandler) {
74654
74757
  oldMedia.videoHandler.stop(true);
74655
74758
  }
74656
74759
  }
@@ -74710,13 +74813,13 @@ var Region = /*#__PURE__*/function () {
74710
74813
  }, {
74711
74814
  key: "playNextMedia",
74712
74815
  value: function playNextMedia() {
74713
- var _this$oldMedia2, _this$currMedia, _this$nxtMedia, _this$currMedia2, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia3, _this$currMedia7, _this$nxtMedia2;
74816
+ var _this$oldMedia3, _this$currMedia2, _this$nxtMedia, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia4, _this$currMedia7, _this$nxtMedia2;
74714
74817
  console.debug('??? XLR.debug Region playing next media', {
74715
74818
  regionId: this.id,
74716
74819
  currentMediaIndex: this.currentMediaIndex,
74717
74820
  mediaItemsLn: this.mediaObjects.length,
74718
- oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName,
74719
- currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74821
+ oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74822
+ currMedia: (_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.containerName,
74720
74823
  nxtMedia: (_this$nxtMedia = this.nxtMedia) === null || _this$nxtMedia === void 0 ? void 0 : _this$nxtMedia.containerName
74721
74824
  });
74722
74825
  /* The current media has finished running */
@@ -74726,33 +74829,20 @@ var Region = /*#__PURE__*/function () {
74726
74829
  });
74727
74830
  return;
74728
74831
  }
74729
- // Are we in a playlist, and has the playlist completed a full cycle?
74730
- var isLastMediaInPlaylist = this.currentMediaIndex === this.mediaObjects.length - 1 && this.mediaObjects.length > 1;
74731
- // If yes, enable shell command widgets again (if any), so they execute on the next playlist cycle
74732
- if (isLastMediaInPlaylist) {
74733
- this.mediaObjects.forEach(function (media) {
74734
- if (media.mediaType === 'shellcommand') {
74735
- // reset per-playlist-cycle execution state
74736
- media.hasCommandExecuted = false;
74737
- }
74738
- });
74739
- }
74740
- if (!this.layout.isOverlay && this.currentMediaIndex === this.mediaObjects.length - 1) {
74741
- this.finished();
74742
- if (this.layout.allEnded) {
74743
- console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74744
- return;
74745
- }
74746
- }
74832
+ // Snapshot the index of the media that just ended so we can detect
74833
+ // cycle completion after the skip loop runs.
74834
+ var origIndex = this.currentMediaIndex;
74747
74835
  // When the region has completed and when currentMedia is html
74748
74836
  // Then, preserve the currentMedia state
74749
- if (this.complete && ((_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.render) === 'html') {
74837
+ if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html') {
74750
74838
  return;
74751
74839
  }
74752
74840
  // When the region has completed and mediaObjects.length = 1
74753
- // and curMedia.loop = false, then put the media on
74754
- // its current state
74755
- if (this.complete && this.mediaObjects.length === 1 && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) !== 'html' && (((_this$currMedia4 = this.currMedia) === null || _this$currMedia4 === void 0 ? void 0 : _this$currMedia4.mediaType) === 'image' || ((_this$currMedia5 = this.currMedia) === null || _this$currMedia5 === void 0 ? void 0 : _this$currMedia5.mediaType) === 'video') && !((_this$currMedia6 = this.currMedia) !== null && _this$currMedia6 !== void 0 && _this$currMedia6.loop)) {
74841
+ // and render is not html, preserve the current media state without
74842
+ // calling transitionNodes (which would remove the media from DOM for 1s).
74843
+ // Do NOT restart the media timer here the layout will end naturally when
74844
+ // regionExpired() detects all regions complete on the next cycle.
74845
+ if (this.complete && this.mediaObjects.length === 1 && ((_this$currMedia4 = this.currMedia) === null || _this$currMedia4 === void 0 ? void 0 : _this$currMedia4.render) !== 'html' && (((_this$currMedia5 = this.currMedia) === null || _this$currMedia5 === void 0 ? void 0 : _this$currMedia5.mediaType) === 'image' || ((_this$currMedia6 = this.currMedia) === null || _this$currMedia6 === void 0 ? void 0 : _this$currMedia6.mediaType) === 'video')) {
74756
74846
  return;
74757
74847
  }
74758
74848
  if (this.currMedia) {
@@ -74760,17 +74850,51 @@ var Region = /*#__PURE__*/function () {
74760
74850
  } else {
74761
74851
  this.oldMedia = undefined;
74762
74852
  }
74763
- this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74853
+ this.currentMediaIndex = (origIndex + 1) % this.totalMediaObjects;
74764
74854
  this.currMedia = this.mediaObjects[this.currentMediaIndex];
74855
+ // Skip media items that are no longer within their active date window
74856
+ var skippedCount = 0;
74857
+ while (this.currMedia && !isMediaActive(this.currMedia.fromDt, this.currMedia.toDt)) {
74858
+ skippedCount++;
74859
+ if (skippedCount >= this.totalMediaObjects) {
74860
+ // Every item in the playlist has expired; finish this region
74861
+ console.debug('??? XLR.debug >> Region::playNextMedia - all media expired, finishing region', this.id);
74862
+ this.finished();
74863
+ return;
74864
+ }
74865
+ this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74866
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
74867
+ }
74765
74868
  this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
74869
+ // A full playlist cycle has been traversed when the total advancement
74870
+ // (the initial +1 step plus any skips over expired items) reaches or
74871
+ // crosses the end of the array.
74872
+ var crossedEnd = origIndex + 1 + skippedCount >= this.totalMediaObjects;
74873
+ // Re-enable shell command widgets at the end of each full cycle so they
74874
+ // execute again on the next pass.
74875
+ if (crossedEnd && this.mediaObjects.length > 1) {
74876
+ this.mediaObjects.forEach(function (media) {
74877
+ if (media.mediaType === 'shellcommand') {
74878
+ // reset per-playlist-cycle execution state
74879
+ media.hasCommandExecuted = false;
74880
+ }
74881
+ });
74882
+ }
74766
74883
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74767
74884
  regionId: this.id,
74768
74885
  currentMediaIndex: this.currentMediaIndex,
74769
74886
  mediaItemsLn: this.mediaObjects.length,
74770
- oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74887
+ oldMedia: (_this$oldMedia4 = this.oldMedia) === null || _this$oldMedia4 === void 0 ? void 0 : _this$oldMedia4.containerName,
74771
74888
  currMedia: (_this$currMedia7 = this.currMedia) === null || _this$currMedia7 === void 0 ? void 0 : _this$currMedia7.containerName,
74772
74889
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74773
74890
  });
74891
+ if (!this.layout.isOverlay && crossedEnd) {
74892
+ this.finished();
74893
+ if (this.layout.allEnded) {
74894
+ console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74895
+ return;
74896
+ }
74897
+ }
74774
74898
  this.transitionNodes(this.oldMedia, this.currMedia);
74775
74899
  }
74776
74900
  }, {
@@ -74799,8 +74923,7 @@ var Region = /*#__PURE__*/function () {
74799
74923
  }, {
74800
74924
  key: "exitTransition",
74801
74925
  value: function exitTransition() {
74802
- /* TODO: Actually implement region exit transitions */
74803
- document.getElementById("".concat(this.containerName));
74926
+ /* TODO: Actually implement region exit transitions using this.html */
74804
74927
  console.debug('Called Region::exitTransition ', this.id);
74805
74928
  this.exitTransitionComplete();
74806
74929
  }
@@ -74982,10 +75105,16 @@ var ActionController = /*#__PURE__*/function () {
74982
75105
  }, {
74983
75106
  key: "openLayoutInNewTab",
74984
75107
  value: function openLayoutInNewTab(layoutCode, options) {
74985
- if (confirm(this.translations.navigateToLayout.replace('[layoutTag]', layoutCode))) {
74986
- var url = options.layoutPreviewUrl.replace('[layoutCode]', layoutCode) + '?findByCode=1';
74987
- window.open(url, '_blank');
74988
- }
75108
+ var url = options.layoutPreviewUrl.replace('[layoutCode]', layoutCode) + '?findByCode=1';
75109
+ // Send a postMessage to the parent frame so the CMS can handle the confirmation
75110
+ // and navigation (confirm() is blocked in sandboxed iframes without allow-modals).
75111
+ window.parent.postMessage({
75112
+ type: 'xlr:navLayout',
75113
+ layoutCode: layoutCode,
75114
+ url: url
75115
+ }, '*');
75116
+ // Also emit via the XLR event system for non-iframe consumers.
75117
+ this.parent.xlr.emitter.emit('navLayout', layoutCode, url);
74989
75118
  }
74990
75119
  }, {
74991
75120
  key: "openLayoutInPlayer",
@@ -75094,7 +75223,7 @@ var ActionController = /*#__PURE__*/function () {
75094
75223
  if (dataset.source === 'region') {
75095
75224
  // Try to find the region
75096
75225
  if (regionObj.id === dataset.sourceid) {
75097
- $sourceObj = document.getElementById(regionObj.containerName);
75226
+ $sourceObj = regionObj.html;
75098
75227
  break;
75099
75228
  }
75100
75229
  } else if (dataset.source === 'widget') {
@@ -75103,7 +75232,7 @@ var ActionController = /*#__PURE__*/function () {
75103
75232
  for (var _i2 = 0, _mediaObjects = mediaObjects; _i2 < _mediaObjects.length; _i2++) {
75104
75233
  var mediaObject = _mediaObjects[_i2];
75105
75234
  if (mediaObject.id === dataset.sourceid) {
75106
- $sourceObj = document.getElementById(mediaObject.containerName);
75235
+ $sourceObj = mediaObject.html;
75107
75236
  break;
75108
75237
  }
75109
75238
  }
@@ -75368,7 +75497,11 @@ var Layout = /*#__PURE__*/function () {
75368
75497
  this.on('start', function (layout) {
75369
75498
  layout.done = false;
75370
75499
  layout.state = exports.ELayoutState.RUNNING;
75371
- console.debug('>>>> XLR.debug Layout start emitted > Layout ID > ', layout.id);
75500
+ console.debug('>>>> XLR.debug Layout start emitted > Layout > ', {
75501
+ layoutId: layout.id,
75502
+ layoutIndex: layout.index,
75503
+ layoutState: layout.state
75504
+ });
75372
75505
  // Check if stats are enabled for the layout
75373
75506
  if (layout.enableStat) {
75374
75507
  _this.statsBC.postMessage({
@@ -75389,12 +75522,21 @@ var Layout = /*#__PURE__*/function () {
75389
75522
  while (1) switch (_context2.prev = _context2.next) {
75390
75523
  case 0:
75391
75524
  if (!(layout.state === exports.ELayoutState.CANCELLED)) {
75392
- _context2.next = 2;
75525
+ _context2.next = 3;
75393
75526
  break;
75394
75527
  }
75528
+ console.debug('>>>> XLR.debug Layout end emitted but layout is already cancelled > Layout ID > ', {
75529
+ layoutId: layout.id,
75530
+ layoutIndex: layout.index,
75531
+ layoutState: layout.state
75532
+ });
75395
75533
  return _context2.abrupt("return");
75396
- case 2:
75397
- console.debug('>>>> XLR.debug Ending layout with ID of > ', layout.layoutId);
75534
+ case 3:
75535
+ console.debug('>>>> XLR.debug Ending layout', {
75536
+ layoutId: layout.id,
75537
+ layoutIndex: layout.index,
75538
+ layoutState: layout.state
75539
+ });
75398
75540
  /* Remove layout that has ended */
75399
75541
  $layout = document.querySelector("#".concat(layout.containerName, "[data-sequence=\"").concat(layout.index, "\"]")); // Only update layout.state when last state === RUNNING
75400
75542
  if (layout.state === exports.ELayoutState.RUNNING) {
@@ -75402,8 +75544,13 @@ var Layout = /*#__PURE__*/function () {
75402
75544
  layout.state = exports.ELayoutState.PLAYED;
75403
75545
  }
75404
75546
  layout.done = true;
75405
- console.debug({
75406
- $layout: $layout
75547
+ console.debug('>>> XLR.debug Layout end emitted > Layout ID > ', {
75548
+ $layout: $layout,
75549
+ layoutId: layout.id,
75550
+ layoutIndex: layout.index,
75551
+ layoutState: layout.state,
75552
+ isOverlay: layout.isOverlay,
75553
+ isInterrupt: layout.isInterrupt()
75407
75554
  });
75408
75555
  if ($layout !== null) {
75409
75556
  $layout.style.setProperty('visibility', 'hidden');
@@ -75428,9 +75575,9 @@ var Layout = /*#__PURE__*/function () {
75428
75575
  }
75429
75576
  // Emit layout end event
75430
75577
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75431
- _context2.next = 12;
75578
+ _context2.next = 13;
75432
75579
  return layout.xlr.emitSync('layoutEnd', layout);
75433
- case 12:
75580
+ case 13:
75434
75581
  if (_this.xlr.config.platform !== exports.ConsumerPlatform.CMS && layout.inLoop) {
75435
75582
  // Transition next layout to current layout and prepare next layout if exist
75436
75583
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75460,7 +75607,7 @@ var Layout = /*#__PURE__*/function () {
75460
75607
  };
75461
75608
  }());
75462
75609
  }
75463
- case 13:
75610
+ case 14:
75464
75611
  case "end":
75465
75612
  return _context2.stop();
75466
75613
  }
@@ -75473,6 +75620,34 @@ var Layout = /*#__PURE__*/function () {
75473
75620
  this.on('cancelled', function (layout) {
75474
75621
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75475
75622
  layout.state = exports.ELayoutState.CANCELLED;
75623
+ layout.inLoop = false;
75624
+ // Dispose video handlers immediately so their stall watchdogs and error
75625
+ // callbacks can't fire against a layout whose DOM is about to be removed.
75626
+ var _iterator = _createForOfIteratorHelper(layout.regions),
75627
+ _step;
75628
+ try {
75629
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
75630
+ var region = _step.value;
75631
+ var _iterator2 = _createForOfIteratorHelper(region.mediaObjects),
75632
+ _step2;
75633
+ try {
75634
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75635
+ var media = _step2.value;
75636
+ if (media.videoHandler) {
75637
+ media.videoHandler.stop(true);
75638
+ }
75639
+ }
75640
+ } catch (err) {
75641
+ _iterator2.e(err);
75642
+ } finally {
75643
+ _iterator2.f();
75644
+ }
75645
+ }
75646
+ } catch (err) {
75647
+ _iterator.e(err);
75648
+ } finally {
75649
+ _iterator.f();
75650
+ }
75476
75651
  });
75477
75652
  }
75478
75653
  return _createClass(Layout, [{
@@ -75614,6 +75789,14 @@ var Layout = /*#__PURE__*/function () {
75614
75789
  if ($splashScreen) {
75615
75790
  $splashScreen.style.display = 'none';
75616
75791
  }
75792
+ // Check if $layoutContainer is still in the DOM before trying to play regions, as the layout may have been removed while waiting for a long-running region to finish.
75793
+ console.debug('??? XLR.debug >> Layout::run() - Checking if layout container is still in the DOM before playing regions...', {
75794
+ layoutId: this.id,
75795
+ layoutContainerExists: !!$layoutContainer,
75796
+ $layoutContainer: $layoutContainer,
75797
+ layoutIndex: this.index,
75798
+ shouldParse: false
75799
+ });
75617
75800
  if ($layoutContainer) {
75618
75801
  $layoutContainer.style.setProperty('visibility', 'visible');
75619
75802
  $layoutContainer.style.setProperty('opacity', '1');
@@ -75644,19 +75827,19 @@ var Layout = /*#__PURE__*/function () {
75644
75827
  key: "regionExpired",
75645
75828
  value: function regionExpired() {
75646
75829
  this.allExpired = true;
75647
- var _iterator = _createForOfIteratorHelper(this.regions),
75648
- _step;
75830
+ var _iterator3 = _createForOfIteratorHelper(this.regions),
75831
+ _step3;
75649
75832
  try {
75650
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
75651
- var layoutRegion = _step.value;
75833
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
75834
+ var layoutRegion = _step3.value;
75652
75835
  if (!layoutRegion.complete) {
75653
75836
  this.allExpired = false;
75654
75837
  }
75655
75838
  }
75656
75839
  } catch (err) {
75657
- _iterator.e(err);
75840
+ _iterator3.e(err);
75658
75841
  } finally {
75659
- _iterator.f();
75842
+ _iterator3.f();
75660
75843
  }
75661
75844
  if (this.allExpired) {
75662
75845
  this.end();
@@ -75667,17 +75850,17 @@ var Layout = /*#__PURE__*/function () {
75667
75850
  value: function end() {
75668
75851
  console.debug('Executing Layout::end and Calling Region::end ', this);
75669
75852
  /* Ask the layout to gracefully stop running now */
75670
- var _iterator2 = _createForOfIteratorHelper(this.regions),
75671
- _step2;
75853
+ var _iterator4 = _createForOfIteratorHelper(this.regions),
75854
+ _step4;
75672
75855
  try {
75673
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75674
- var layoutRegion = _step2.value;
75856
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
75857
+ var layoutRegion = _step4.value;
75675
75858
  layoutRegion.end();
75676
75859
  }
75677
75860
  } catch (err) {
75678
- _iterator2.e(err);
75861
+ _iterator4.e(err);
75679
75862
  } finally {
75680
- _iterator2.f();
75863
+ _iterator4.f();
75681
75864
  }
75682
75865
  }
75683
75866
  }, {
@@ -75690,7 +75873,11 @@ var Layout = /*#__PURE__*/function () {
75690
75873
  }
75691
75874
  }
75692
75875
  if (this.allEnded) {
75693
- console.debug('starting to end layout . . .');
75876
+ console.debug('starting to end layout . . .', {
75877
+ layoutId: this.layoutId,
75878
+ layoutIndex: this.index,
75879
+ layoutState: this.state
75880
+ });
75694
75881
  if (this.xlr.config.platform === exports.ConsumerPlatform.CMS) {
75695
75882
  var $end = document.getElementById('play_ended');
75696
75883
  var $preview = document.getElementById('screen_container');
@@ -75789,6 +75976,42 @@ var Layout = /*#__PURE__*/function () {
75789
75976
  (_$layout$parentElemen2 = $layout.parentElement) === null || _$layout$parentElemen2 === void 0 || _$layout$parentElemen2.removeChild($layout);
75790
75977
  }
75791
75978
  }
75979
+ }, {
75980
+ key: "discardLayout",
75981
+ value: function discardLayout() {
75982
+ var caller = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : exports.LayoutPlaybackType.NEXT;
75983
+ // Dispose any video.js players that were initialized during prepareVideoMedia
75984
+ // but never played. The isDisposed() guard makes this safe to call on
75985
+ // layouts that were fully played as well.
75986
+ var _iterator5 = _createForOfIteratorHelper(this.regions),
75987
+ _step5;
75988
+ try {
75989
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
75990
+ var region = _step5.value;
75991
+ var _iterator6 = _createForOfIteratorHelper(region.mediaObjects),
75992
+ _step6;
75993
+ try {
75994
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
75995
+ var media = _step6.value;
75996
+ if (media.player && !media.player.isDisposed()) {
75997
+ media.player.dispose();
75998
+ media.player = undefined;
75999
+ media.html = null;
76000
+ }
76001
+ }
76002
+ } catch (err) {
76003
+ _iterator6.e(err);
76004
+ } finally {
76005
+ _iterator6.f();
76006
+ }
76007
+ }
76008
+ } catch (err) {
76009
+ _iterator5.e(err);
76010
+ } finally {
76011
+ _iterator5.f();
76012
+ }
76013
+ this.removeLayout(caller);
76014
+ }
75792
76015
  }, {
75793
76016
  key: "getXlf",
75794
76017
  value: function getXlf() {
@@ -76031,6 +76254,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76031
76254
  }());
76032
76255
  xlrObject.on('updateLoop', /*#__PURE__*/function () {
76033
76256
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(inputLayouts) {
76257
+ var _xlrObject$currentLay;
76034
76258
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
76035
76259
  while (1) switch (_context3.prev = _context3.next) {
76036
76260
  case 0:
@@ -76039,7 +76263,17 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76039
76263
  return xlrObject.updateLoop(inputLayouts);
76040
76264
  case 3:
76041
76265
  xlrObject.isUpdatingLoop = false;
76042
- case 4:
76266
+ // If the running layout finished while isUpdatingLoop was true, the
76267
+ // layout end-handler bailed out of prepareLayouts() early and the
76268
+ // subsequent playLayouts() call saw currentLayout.done === true and
76269
+ // skipped run() — leaving a black screen. Catch up now that the flag
76270
+ // is clear.
76271
+ if ((_xlrObject$currentLay = xlrObject.currentLayout) !== null && _xlrObject$currentLay !== void 0 && _xlrObject$currentLay.done) {
76272
+ xlrObject.prepareLayouts().then(function (xlr) {
76273
+ return xlrObject.playLayouts(xlr);
76274
+ });
76275
+ }
76276
+ case 5:
76043
76277
  case "end":
76044
76278
  return _context3.stop();
76045
76279
  }
@@ -76069,40 +76303,35 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76069
76303
  return _ref4.apply(this, arguments);
76070
76304
  };
76071
76305
  }());
76072
- /**
76073
- * Asynchronous event emitter. Extended nanoevents event emitter.
76074
- *
76075
- * NOTE: Known limitation — nanoevents emit() is synchronous.
76076
- * Any async event handlers registered via .on() are fire-and-forget;
76077
- * emitSync does NOT await them. The returned Promise resolves
76078
- * immediately after handlers are invoked, not after they complete.
76079
- *
76080
- * @param eventName
76081
- * @param args
76082
- */
76083
- xlrObject.emitSync = function (eventName) {
76084
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76085
- args[_key - 1] = arguments[_key];
76086
- }
76087
- return new Promise( /*#__PURE__*/function () {
76088
- var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(resolve) {
76089
- var _xlrObject$emitter;
76090
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76091
- while (1) switch (_context5.prev = _context5.next) {
76092
- case 0:
76093
- (_xlrObject$emitter = xlrObject.emitter).emit.apply(_xlrObject$emitter, [eventName].concat(args));
76094
- resolve();
76095
- case 2:
76096
- case "end":
76097
- return _context5.stop();
76098
- }
76099
- }, _callee5);
76100
- }));
76101
- return function (_x4) {
76102
- return _ref5.apply(this, arguments);
76103
- };
76104
- }());
76105
- };
76306
+ xlrObject.emitSync = /*#__PURE__*/function () {
76307
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(eventName) {
76308
+ var _xlrObject$emitter$ev;
76309
+ var _len,
76310
+ args,
76311
+ _key,
76312
+ handlers,
76313
+ _args5 = arguments;
76314
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76315
+ while (1) switch (_context5.prev = _context5.next) {
76316
+ case 0:
76317
+ for (_len = _args5.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76318
+ args[_key - 1] = _args5[_key];
76319
+ }
76320
+ handlers = (_xlrObject$emitter$ev = xlrObject.emitter.events[eventName]) !== null && _xlrObject$emitter$ev !== void 0 ? _xlrObject$emitter$ev : [];
76321
+ _context5.next = 4;
76322
+ return Promise.all(handlers.map(function (handler) {
76323
+ return handler.apply(void 0, args);
76324
+ }));
76325
+ case 4:
76326
+ case "end":
76327
+ return _context5.stop();
76328
+ }
76329
+ }, _callee5);
76330
+ }));
76331
+ return function (_x4) {
76332
+ return _ref5.apply(this, arguments);
76333
+ };
76334
+ }();
76106
76335
  xlrObject.bootstrap = function () {
76107
76336
  // Place to set configurations and initialize required props
76108
76337
  var self = this;
@@ -76137,13 +76366,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76137
76366
  if ($splashScreen && $splashScreen.style.display === 'block') {
76138
76367
  $splashScreen === null || $splashScreen === void 0 || $splashScreen.hide();
76139
76368
  }
76369
+ console.debug('>>>> XLR.debug XLR::playLayouts > currentLayout', {
76370
+ layoutId: xlr.currentLayout.layoutId,
76371
+ layoutIndex: xlr.currentLayout.index,
76372
+ layoutState: xlr.currentLayout.state
76373
+ });
76140
76374
  if (!xlr.currentLayout.done) {
76141
- console.log('>>>> XLR.debug XLR::playSchedules > Running currentLayout', xlr.currentLayout);
76142
- xlr.currentLayout.run();
76143
76375
  // Hide overlays when current layout is interrupt
76144
76376
  if (xlr.currentLayout.isInterrupt()) {
76145
76377
  xlrObject.overlayLayoutManager.stopOverlays();
76146
76378
  }
76379
+ console.debug('>>>> XLR.debug XLR::playLayouts > Running currentLayout', {
76380
+ layoutId: xlr.currentLayout.layoutId,
76381
+ layoutIndex: xlr.currentLayout.index,
76382
+ layoutState: xlr.currentLayout.state
76383
+ });
76384
+ xlr.currentLayout.run();
76147
76385
  }
76148
76386
  } else {
76149
76387
  // Show splash screen
@@ -76220,7 +76458,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76220
76458
  _context7.next = 10;
76221
76459
  return _overlay.finishAllRegions();
76222
76460
  case 10:
76223
- _overlay.removeLayout();
76461
+ _overlay.removeLayout(exports.LayoutPlaybackType.OVERLAY);
76224
76462
  case 11:
76225
76463
  _context7.next = 14;
76226
76464
  break;
@@ -76286,6 +76524,31 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76286
76524
  var $layout = document.querySelector("#".concat(containerName, "[data-sequence=\"").concat(layoutIndex, "\"]"));
76287
76525
  return $layout !== null;
76288
76526
  };
76527
+ // Scans screen_container for non-overlay layout divs and removes any that
76528
+ // are not the current or next active layout. Prevents DOM accumulation when
76529
+ // prepareLayouts() races with updateLoop and multiple same-layoutId elements
76530
+ // end up in screen_container (e.g. transitioning from a 1-layout loop where
76531
+ // two elements exist for the same layout to a multi-layout schedule).
76532
+ // keepCurrent / keepNext:
76533
+ // undefined → fall back to this.currentLayout / this.nextLayout
76534
+ // null → keep nothing for that slot (explicit "no layout to preserve")
76535
+ // ILayout → keep exactly that instance
76536
+ xlrObject.cleanupOrphanedLayouts = function (keepCurrent, keepNext) {
76537
+ var $screen = document.getElementById('screen_container');
76538
+ if (!$screen) return;
76539
+ var current = keepCurrent !== undefined ? keepCurrent : this.currentLayout;
76540
+ var next = keepNext !== undefined ? keepNext : this.nextLayout;
76541
+ Array.from($screen.querySelectorAll(':scope > div:not(.is-overlay)')).forEach(function (el) {
76542
+ var div = el;
76543
+ var isCurrentLayout = current && div.id === current.containerName && div.dataset.sequence === String(current.index);
76544
+ var isNextLayout = next && div.id === next.containerName && div.dataset.sequence === String(next.index);
76545
+ if (!isCurrentLayout && !isNextLayout) {
76546
+ var _div$parentElement;
76547
+ console.debug('XLR::cleanupOrphanedLayouts - removing orphaned layout element', div.id);
76548
+ (_div$parentElement = div.parentElement) === null || _div$parentElement === void 0 || _div$parentElement.removeChild(div);
76549
+ }
76550
+ });
76551
+ };
76289
76552
  xlrObject.updateLoop = /*#__PURE__*/function () {
76290
76553
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11(inputLayouts) {
76291
76554
  var _this$currentLayout,
@@ -76332,11 +76595,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76332
76595
  };
76333
76596
  }();
76334
76597
  if (isCurrentLayoutValid) {
76335
- _context11.next = 54;
76598
+ _context11.next = 55;
76336
76599
  break;
76337
76600
  }
76338
76601
  if (!playback.hasDefaultOnly) {
76339
- _context11.next = 33;
76602
+ _context11.next = 34;
76340
76603
  break;
76341
76604
  }
76342
76605
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
@@ -76349,82 +76612,102 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76349
76612
  case 19:
76350
76613
  this.currentLayout.removeLayout();
76351
76614
  case 20:
76352
- _context11.next = 22;
76615
+ // Discard old nextLayout before replacing it — same as the
76616
+ // other two branches do, otherwise the prepared DOM element
76617
+ // and any video.js players are orphaned.
76618
+ if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76619
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76620
+ }
76621
+ _context11.next = 23;
76353
76622
  return this.prepareLayoutXlf(playback.currentLayout);
76354
- case 22:
76623
+ case 23:
76355
76624
  this.currentLayout = _context11.sent;
76356
76625
  this.currentLayoutId = this.currentLayout.layoutId;
76357
76626
  _context11.t0 = this;
76358
- _context11.next = 27;
76627
+ _context11.next = 28;
76359
76628
  return this.prepareLayoutXlf(playback.nextLayout);
76360
- case 27:
76629
+ case 28:
76361
76630
  _context11.t1 = _context11.sent;
76362
- _context11.next = 30;
76631
+ _context11.next = 31;
76363
76632
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76364
- case 30:
76633
+ case 31:
76365
76634
  this.nextLayout = _context11.sent;
76366
- _context11.next = 50;
76635
+ _context11.next = 51;
76367
76636
  break;
76368
- case 33:
76637
+ case 34:
76369
76638
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76370
- _context11.next = 38;
76639
+ _context11.next = 39;
76371
76640
  break;
76372
76641
  }
76373
76642
  this.currentLayout.inLoop = false;
76374
- _context11.next = 37;
76643
+ _context11.next = 38;
76375
76644
  return this.currentLayout.finishAllRegions();
76376
- case 37:
76377
- this.currentLayout.removeLayout();
76378
76645
  case 38:
76646
+ this.currentLayout.removeLayout();
76647
+ case 39:
76379
76648
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76380
- this.nextLayout.removeLayout();
76649
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76381
76650
  }
76382
76651
  if (!playback.currentLayout) {
76383
- _context11.next = 42;
76652
+ _context11.next = 43;
76384
76653
  break;
76385
76654
  }
76386
- _context11.next = 42;
76655
+ _context11.next = 43;
76387
76656
  return prepareNewCurrentLayout();
76388
- case 42:
76657
+ case 43:
76389
76658
  if (!playback.nextLayout) {
76390
- _context11.next = 50;
76659
+ _context11.next = 51;
76391
76660
  break;
76392
76661
  }
76393
76662
  _context11.t2 = this;
76394
- _context11.next = 46;
76663
+ _context11.next = 47;
76395
76664
  return this.prepareLayoutXlf(playback.nextLayout);
76396
- case 46:
76665
+ case 47:
76397
76666
  _context11.t3 = _context11.sent;
76398
- _context11.next = 49;
76667
+ _context11.next = 50;
76399
76668
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76400
- case 49:
76401
- this.nextLayout = _context11.sent;
76402
76669
  case 50:
76403
- _context11.next = 52;
76670
+ this.nextLayout = _context11.sent;
76671
+ case 51:
76672
+ _context11.next = 53;
76404
76673
  return this.playSchedules(this);
76405
- case 52:
76674
+ case 53:
76406
76675
  _context11.next = 67;
76407
76676
  break;
76408
- case 54:
76677
+ case 55:
76409
76678
  // Remove next layout if it is in the DOM
76410
76679
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76411
- this.nextLayout.removeLayout();
76680
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76412
76681
  }
76413
- // Prepare new current layout
76682
+ // Purge any other orphaned layouts from screen_container that belong
76683
+ // to the old single-layout loop. When there was only one layout in the
76684
+ // loop, prepareLayouts() kept two DOM elements alive (current + next,
76685
+ // both the same layoutId but different containerNames). On a schedule
76686
+ // change the this.nextLayout check above only discards the element
76687
+ // currently referenced by this.nextLayout, but concurrent
76688
+ // prepareLayouts() calls can leave earlier same-layoutId elements
76689
+ // behind.
76690
+ // Pass null (not undefined) for keepNext: undefined would fall back to
76691
+ // this.nextLayout which may still reference the just-discarded layout
76692
+ // or — if isLayoutInDOM returned false and discardLayout was skipped —
76693
+ // the orphan itself, causing cleanupOrphanedLayouts to preserve it.
76694
+ // null means "no next to keep"; we are about to prepare a fresh one.
76695
+ this.cleanupOrphanedLayouts(this.currentLayout, null);
76696
+ // The current layout is still valid and running — do NOT replace the
76697
+ // live currentLayout object. Only refresh the queued nextLayout so
76698
+ // that when the running layout finishes it transitions to the correct
76699
+ // position in the updated loop. Using playback.currentLayout (the
76700
+ // slot that follows the running layout in the new queue) as the new
76701
+ // nextLayout keeps the cycle in order; the slot after that will be
76702
+ // prepared by the normal prepareLayouts() call at transition time.
76414
76703
  if (!playback.currentLayout) {
76415
- _context11.next = 58;
76416
- break;
76417
- }
76418
- _context11.next = 58;
76419
- return prepareNewCurrentLayout();
76420
- case 58:
76421
- if (!playback.nextLayout) {
76422
76704
  _context11.next = 66;
76423
76705
  break;
76424
76706
  }
76707
+ this.currentLayoutIndex = playback.currentLayoutIndex;
76425
76708
  _context11.t4 = this;
76426
76709
  _context11.next = 62;
76427
- return this.prepareLayoutXlf(playback.nextLayout);
76710
+ return this.prepareLayoutXlf(playback.currentLayout);
76428
76711
  case 62:
76429
76712
  _context11.t5 = _context11.sent;
76430
76713
  _context11.next = 65;
@@ -76491,8 +76774,10 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76491
76774
  var tempNextLayoutIndex = getLayoutIndexByLayoutId(this.inputLayouts, this.nextLayout.layoutId);
76492
76775
  _currentLayoutIndex = tempNextLayoutIndex !== null && tempNextLayoutIndex !== void 0 ? tempNextLayoutIndex : 0;
76493
76776
  _currentLayout = this.getLayout(this.inputLayouts[tempNextLayoutIndex !== null && tempNextLayoutIndex !== void 0 ? tempNextLayoutIndex : 0]);
76777
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76494
76778
  } else {
76495
76779
  _currentLayout = this.getLayout(this.inputLayouts[0]);
76780
+ _currentLayout = setLayoutIndex(_currentLayout, 0);
76496
76781
  }
76497
76782
  if (this.inputLayouts.length > 1) {
76498
76783
  if (_currentLayoutIndex + 1 > this.inputLayouts.length - 1) {
@@ -76501,6 +76786,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76501
76786
  _nextLayoutIndex = _currentLayoutIndex + 1;
76502
76787
  }
76503
76788
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76789
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76504
76790
  } else {
76505
76791
  _nextLayout = _currentLayout;
76506
76792
  }
@@ -76513,20 +76799,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76513
76799
  if (_currentLayout) {
76514
76800
  _currentLayoutIndex = _currentLayout.index;
76515
76801
  }
76802
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76516
76803
  _nextLayoutIndex = 0;
76517
76804
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76805
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76518
76806
  }
76519
76807
  } else {
76520
76808
  _currentLayoutIndex = this.nextLayout.index > this.inputLayouts.length - 1 ? 0 : this.nextLayout.index;
76521
76809
  _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76810
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76522
76811
  _nextLayoutIndex = _currentLayoutIndex + 1 > this.inputLayouts.length - 1 ? 0 : _currentLayoutIndex + 1;
76523
76812
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76813
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76524
76814
  }
76525
76815
  } else {
76816
+ var _this$currentLayout4, _this$currentLayout5;
76526
76817
  _currentLayout = this.nextLayout;
76527
76818
  _currentLayoutIndex = _currentLayout.index;
76819
+ // updateLoop can re-queue the same index that is currently
76820
+ // playing (e.g. it fires while nextLayout.index === currentLayout.index).
76821
+ // When that layout then ends, the catch-up prepareLayouts() would
76822
+ // replay the same slot instead of advancing. Detect this by checking
76823
+ // whether the queued next-to-current is at the same index as the
76824
+ // layout that just finished, and advance past it so the following
76825
+ // slot (e.g. an SSP that now has an ad) becomes current instead.
76826
+ 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)) {
76827
+ _currentLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76828
+ _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76829
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76830
+ }
76528
76831
  _nextLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76529
76832
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76833
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76530
76834
  }
76531
76835
  }
76532
76836
  }
@@ -76534,17 +76838,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76534
76838
  // Initial run: set both currentLayout and nextLayout
76535
76839
  if (hasLayout) {
76536
76840
  _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76841
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76537
76842
  if (this.inputLayouts.length > 1) {
76538
76843
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76844
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76539
76845
  } else {
76540
76846
  _nextLayout = this.getLayout(this.inputLayouts[0]);
76847
+ _nextLayout = setLayoutIndex(_nextLayout, 0);
76541
76848
  }
76542
76849
  }
76543
76850
  }
76544
76851
  if (_currentLayout === undefined && _nextLayout === undefined) {
76545
76852
  if (_hasDefaultOnly) {
76546
76853
  _currentLayout = this.getLayout(this.inputLayouts[0]);
76854
+ _currentLayout = setLayoutIndex(_currentLayout, 0);
76547
76855
  _nextLayout = this.getLayout(this.inputLayouts[0]);
76856
+ _nextLayout = setLayoutIndex(_nextLayout, 0);
76548
76857
  }
76549
76858
  }
76550
76859
  if (_currentLayout !== undefined && _nextLayout !== undefined) {
@@ -76574,10 +76883,19 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76574
76883
  var activeLayout = inputLayout;
76575
76884
  if (isCMS) {
76576
76885
  activeLayout.index = 0;
76886
+ // id stays null without this — setLayoutIndex returns undefined for CMS layouts
76887
+ if (activeLayout.id == null) {
76888
+ activeLayout.id = activeLayout.layoutId;
76889
+ }
76577
76890
  } else {
76578
76891
  activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
76579
76892
  }
76580
76893
  _layout = _objectSpread2(_objectSpread2({}, _layout), activeLayout);
76894
+ console.debug('XLR::getLayout > activeLayout from uniqueLayouts', {
76895
+ activeLayout: activeLayout,
76896
+ inputLayout: inputLayout,
76897
+ uniqueLayouts: this.uniqueLayouts
76898
+ });
76581
76899
  // Must set index/sequence from schedule loop
76582
76900
  _layout.index = activeLayout.index;
76583
76901
  }
@@ -76599,7 +76917,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76599
76917
  };
76600
76918
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76601
76919
  var _layoutPlayback$curre, _layoutPlayback$curre2;
76602
- var self, layoutPlayback, currentLayoutXlf, nextLayoutXlf, layouts;
76920
+ var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76603
76921
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76604
76922
  while (1) switch (_context14.prev = _context14.next) {
76605
76923
  case 0:
@@ -76622,7 +76940,12 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76622
76940
  shouldParse: false
76623
76941
  });
76624
76942
  self.currentLayoutId = (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId;
76625
- if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode)) {
76943
+ // Only reuse the existing Layout instance if it is fully healthy
76944
+ // a done=true instance was removed from the DOM (e.g. an SSP slot that
76945
+ // had no ad), and an empty-XLF instance has no regions so it can never
76946
+ // advance the cycle. In either case re-prepare from scratch so we get
76947
+ // a fresh request (which may now have a valid ad / XLF).
76948
+ if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
76626
76949
  _context14.next = 12;
76627
76950
  break;
76628
76951
  }
@@ -76636,27 +76959,40 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76636
76959
  _context14.t0 = _context14.sent;
76637
76960
  case 15:
76638
76961
  currentLayoutXlf = _context14.t0;
76639
- _context14.next = 18;
76962
+ // True when the same object was returned (reused); false when a fresh
76963
+ // Layout was constructed by prepareLayoutXlf above.
76964
+ wasCurrentReused = currentLayoutXlf === layoutPlayback.currentLayout;
76965
+ _context14.next = 19;
76640
76966
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
76641
- case 18:
76967
+ case 19:
76642
76968
  nextLayoutXlf = _context14.sent;
76643
76969
  _context14.t1 = Promise;
76644
76970
  _context14.t2 = currentLayoutXlf;
76645
- _context14.next = 23;
76971
+ _context14.next = 24;
76646
76972
  return self.prepareForSsp(nextLayoutXlf);
76647
- case 23:
76973
+ case 24:
76648
76974
  _context14.t3 = _context14.sent;
76649
76975
  _context14.t4 = [_context14.t2, _context14.t3];
76650
- _context14.next = 27;
76976
+ _context14.next = 28;
76651
76977
  return _context14.t1.all.call(_context14.t1, _context14.t4);
76652
- case 27:
76978
+ case 28:
76653
76979
  layouts = _context14.sent;
76654
- // Return early when layout loop is updating
76655
- if (self.isUpdatingLoop) {
76656
- if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76657
- nextLayoutXlf.removeLayout();
76658
- }
76980
+ if (!(self.isUpdatingLoop || layouts[0].done)) {
76981
+ _context14.next = 33;
76982
+ break;
76983
+ }
76984
+ // If currentLayout was freshly prepared (not reused from nextLayout),
76985
+ // its DOM element was just appended — discard it now so it does not
76986
+ // accumulate in screen_container. Also disposes any video.js players
76987
+ // that were initialized during prepareVideoMedia but never played.
76988
+ if (!wasCurrentReused && this.isLayoutInDOM(currentLayoutXlf.containerName, currentLayoutXlf.index)) {
76989
+ currentLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
76990
+ }
76991
+ if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76992
+ nextLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
76659
76993
  }
76994
+ return _context14.abrupt("return", Promise.resolve(self));
76995
+ case 33:
76660
76996
  console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
76661
76997
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
76662
76998
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
@@ -76673,8 +77009,15 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76673
77009
  self.currentLayout = self.layouts.current;
76674
77010
  self.currentLayoutId = self.currentLayout.layoutId;
76675
77011
  self.nextLayout = self.layouts.next;
77012
+ // Evict any orphaned layout DOM elements that aren't the current
77013
+ // or next layout. Concurrent prepareLayouts() calls can each append
77014
+ // a freshly-prepared nextLayout to screen_container and then
77015
+ // overwrite this.nextLayout, leaving earlier elements behind.
77016
+ // Calling this here — with explicit references — ensures every
77017
+ // completed prepare cycle leaves the DOM in a clean state.
77018
+ self.cleanupOrphanedLayouts(self.currentLayout, self.nextLayout);
76676
77019
  resolve(xlrObject);
76677
- case 8:
77020
+ case 9:
76678
77021
  case "end":
76679
77022
  return _context13.stop();
76680
77023
  }
@@ -76684,7 +77027,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76684
77027
  return _ref14.apply(this, arguments);
76685
77028
  };
76686
77029
  }()));
76687
- case 31:
77030
+ case 35:
76688
77031
  case "end":
76689
77032
  return _context14.stop();
76690
77033
  }
@@ -76712,34 +77055,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76712
77055
  newOptions.xlfUrl = inputLayout.path;
76713
77056
  }
76714
77057
  if (!(inputLayout && inputLayout.layoutNode === undefined)) {
76715
- _context15.next = 21;
77058
+ _context15.next = 22;
76716
77059
  break;
76717
77060
  }
76718
77061
  if (!(inputLayout.layoutId === -1)) {
76719
- _context15.next = 14;
77062
+ _context15.next = 15;
76720
77063
  break;
76721
77064
  }
76722
77065
  _context15.next = 10;
76723
77066
  return self.emitSync('adRequest', inputLayout.index);
76724
77067
  case 10:
76725
77068
  sspInputLayout = self.inputLayouts[inputLayout.index];
77069
+ console.debug('XLR::prepareLayoutXlf > SSP input layout', {
77070
+ sspInputLayout: sspInputLayout,
77071
+ inputLayout: inputLayout
77072
+ });
76726
77073
  // @ts-ignore
76727
- layoutXlf = ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf()) || '';
76728
- _context15.next = 17;
77074
+ layoutXlf = typeof ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf) === 'function' ? sspInputLayout.getXlf() : '';
77075
+ _context15.next = 18;
76729
77076
  break;
76730
- case 14:
76731
- _context15.next = 16;
77077
+ case 15:
77078
+ _context15.next = 17;
76732
77079
  return getXlf(newOptions);
76733
- case 16:
76734
- layoutXlf = _context15.sent;
76735
77080
  case 17:
77081
+ layoutXlf = _context15.sent;
77082
+ case 18:
76736
77083
  parser = new window.DOMParser();
76737
77084
  layoutXlfNode = parser.parseFromString(layoutXlf, 'text/xml');
76738
- _context15.next = 22;
77085
+ _context15.next = 23;
76739
77086
  break;
76740
- case 21:
76741
- layoutXlfNode = inputLayout && inputLayout.layoutNode;
76742
77087
  case 22:
77088
+ layoutXlfNode = inputLayout && inputLayout.layoutNode;
77089
+ case 23:
76743
77090
  isOverlayLayout = !!(inputLayout !== null && inputLayout !== void 0 && inputLayout.isOverlay);
76744
77091
  return _context15.abrupt("return", new Promise(function (resolve) {
76745
77092
  var _inputLayout$ad;
@@ -76764,13 +77111,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76764
77111
  xlrLayoutObj.duration = sspInputLayout.duration || 0;
76765
77112
  xlrLayoutObj.ad = sspInputLayout.ad;
76766
77113
  }
77114
+ var xlrLayout;
76767
77115
  if (isOverlayLayout) {
76768
- resolve(new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77116
+ xlrLayout = new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76769
77117
  } else {
76770
- resolve(new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77118
+ xlrLayout = new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode);
77119
+ }
77120
+ // Advance the shared counter so the next prepareLayoutXlf() call
77121
+ // starts from where this layout left off — prevents every layout
77122
+ // instance from reusing idCounter=1 and colliding on the same
77123
+ // containerName / DOM element.
77124
+ if (props.options) {
77125
+ props.options.idCounter = newOptions.idCounter;
76771
77126
  }
77127
+ resolve(xlrLayout);
76772
77128
  }));
76773
- case 24:
77129
+ case 25:
76774
77130
  case "end":
76775
77131
  return _context15.stop();
76776
77132
  }
@@ -76782,45 +77138,49 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76782
77138
  }();
76783
77139
  xlrObject.prepareForSsp = /*#__PURE__*/function () {
76784
77140
  var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee16(nextLayout) {
76785
- var self, _nextLayout, nextIndex, inputLayout, nextLayoutObj;
77141
+ var self, _nextLayout, iterations, maxIterations, nextIndex, inputLayout, nextLayoutObj;
76786
77142
  return _regeneratorRuntime().wrap(function _callee16$(_context16) {
76787
77143
  while (1) switch (_context16.prev = _context16.next) {
76788
77144
  case 0:
76789
77145
  self = this;
76790
77146
  _nextLayout = nextLayout;
76791
- case 2:
76792
- if (!(_nextLayout && _nextLayout.xlfString === '')) {
76793
- _context16.next = 16;
76794
- break;
76795
- }
76796
- // Remove skipped layout
76797
- _nextLayout.removeLayout();
76798
- // Get next valid layout
76799
- // We will skip next layout that has no valid xlf
76800
- nextIndex = _nextLayout.index + 1;
76801
- if (!(nextIndex >= self.inputLayouts.length)) {
76802
- _context16.next = 7;
77147
+ iterations = 0;
77148
+ maxIterations = self.inputLayouts.length;
77149
+ case 4:
77150
+ if (!(_nextLayout && _nextLayout.xlfString === '' && iterations < maxIterations)) {
77151
+ _context16.next = 20;
76803
77152
  break;
76804
77153
  }
76805
- return _context16.abrupt("break", 16);
76806
- case 7:
77154
+ // Remove the empty slot's DOM element before skipping past it
77155
+ _nextLayout.removeLayout(exports.LayoutPlaybackType.NEXT);
77156
+ iterations++;
77157
+ // Advance to the next slot, wrapping around so a trailing SSP slot
77158
+ // with no ad does not strand the queue at the end of the array.
77159
+ nextIndex = (_nextLayout.index + 1) % self.inputLayouts.length;
76807
77160
  inputLayout = self.inputLayouts[nextIndex];
76808
77161
  if (inputLayout) {
76809
- _context16.next = 10;
77162
+ _context16.next = 11;
76810
77163
  break;
76811
77164
  }
76812
- return _context16.abrupt("break", 16);
76813
- case 10:
77165
+ return _context16.abrupt("break", 20);
77166
+ case 11:
76814
77167
  nextLayoutObj = self.getLayout(inputLayout);
76815
- _context16.next = 13;
77168
+ nextLayoutObj = setLayoutIndex(nextLayoutObj, nextIndex);
77169
+ if (nextLayoutObj) {
77170
+ _context16.next = 15;
77171
+ break;
77172
+ }
77173
+ return _context16.abrupt("break", 20);
77174
+ case 15:
77175
+ _context16.next = 17;
76816
77176
  return self.prepareLayoutXlf(nextLayoutObj);
76817
- case 13:
77177
+ case 17:
76818
77178
  _nextLayout = _context16.sent;
76819
- _context16.next = 2;
77179
+ _context16.next = 4;
76820
77180
  break;
76821
- case 16:
77181
+ case 20:
76822
77182
  return _context16.abrupt("return", _nextLayout);
76823
- case 17:
77183
+ case 21:
76824
77184
  case "end":
76825
77185
  return _context16.stop();
76826
77186
  }
@@ -76832,7 +77192,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76832
77192
  }();
76833
77193
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
76834
77194
  var _this4 = this;
76835
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout4;
77195
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
76836
77196
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
76837
77197
  while (1) switch (_context18.prev = _context18.next) {
76838
77198
  case 0:
@@ -76855,7 +77215,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76855
77215
  break;
76856
77216
  }
76857
77217
  _context18.next = 8;
76858
- return (_this$currentLayout4 = this.currentLayout) === null || _this$currentLayout4 === void 0 ? void 0 : _this$currentLayout4.finishAllRegions();
77218
+ return (_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.finishAllRegions();
76859
77219
  case 8:
76860
77220
  // and set the previous layout as current layout
76861
77221
  this.currentLayoutIndex = _assumedPrevIndex;
@@ -76883,7 +77243,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76883
77243
  }, _callee18, this);
76884
77244
  }));
76885
77245
  xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
76886
- var _xlrObject$currentLay;
77246
+ var _xlrObject$currentLay2;
76887
77247
  return _regeneratorRuntime().wrap(function _callee19$(_context19) {
76888
77248
  while (1) switch (_context19.prev = _context19.next) {
76889
77249
  case 0:
@@ -76893,7 +77253,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76893
77253
  shouldParse: false
76894
77254
  });
76895
77255
  _context19.next = 3;
76896
- return (_xlrObject$currentLay = xlrObject.currentLayout) === null || _xlrObject$currentLay === void 0 ? void 0 : _xlrObject$currentLay.finishAllRegions();
77256
+ return (_xlrObject$currentLay2 = xlrObject.currentLayout) === null || _xlrObject$currentLay2 === void 0 ? void 0 : _xlrObject$currentLay2.finishAllRegions();
76897
77257
  case 3:
76898
77258
  case "end":
76899
77259
  return _context19.stop();
@@ -76905,6 +77265,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76905
77265
  if (layout !== null) {
76906
77266
  layout.index = xlrInputLayout.index;
76907
77267
  }
77268
+ console.debug('XLR::updateInputLayout', {
77269
+ layoutIndex: layoutIndex,
77270
+ layout: layout,
77271
+ xlrInputLayout: xlrInputLayout
77272
+ });
76908
77273
  this.inputLayouts[layoutIndex] = layout || xlrInputLayout;
76909
77274
  };
76910
77275
  xlrObject.bootstrap();
@@ -76948,11 +77313,11 @@ exports.hasDefaultOnly = hasDefaultOnly;
76948
77313
  exports.hasSspLayout = hasSspLayout;
76949
77314
  exports.initRenderingDOM = initRenderingDOM;
76950
77315
  exports.initialLayout = initialLayout;
76951
- exports.initialMedia = initialMedia;
76952
77316
  exports.initialRegion = initialRegion;
76953
77317
  exports.initialXlr = initialXlr;
76954
77318
  exports.isEmpty = isEmpty;
76955
77319
  exports.isLayoutValid = isLayoutValid;
77320
+ exports.isMediaActive = isMediaActive;
76956
77321
  exports.nextId = nextId;
76957
77322
  exports.playerReportFault = playerReportFault;
76958
77323
  exports.preloadMediaBlob = preloadMediaBlob;
@@ -76960,6 +77325,7 @@ exports.prepareAudioMedia = prepareAudioMedia;
76960
77325
  exports.prepareHtmlMedia = prepareHtmlMedia;
76961
77326
  exports.prepareImageMedia = prepareImageMedia;
76962
77327
  exports.prepareVideoMedia = prepareVideoMedia;
77328
+ exports.reportToPlayerPlatform = reportToPlayerPlatform;
76963
77329
  exports.setExpiry = setExpiry;
76964
77330
  exports.transitionElement = transitionElement;
76965
77331
  exports.videoFileType = videoFileType;