@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.
@@ -703,6 +703,7 @@ var initialLayout = {
703
703
  return Promise.resolve([]);
704
704
  },
705
705
  removeLayout: function removeLayout() {},
706
+ discardLayout: function discardLayout() {},
706
707
  getXlf: function getXlf() {
707
708
  return '';
708
709
  },
@@ -713,6 +714,13 @@ var initialLayout = {
713
714
  html: null
714
715
  };
715
716
 
717
+ var MediaState = {
718
+ IDLE: 'idle',
719
+ PLAYING: 'playing',
720
+ ENDED: 'ended',
721
+ CANCELLED: 'cancelled'
722
+ };
723
+
716
724
  var initialRegion = {
717
725
  complete: false,
718
726
  containerName: '',
@@ -761,56 +769,6 @@ var initialRegion = {
761
769
  xlr: {}
762
770
  };
763
771
 
764
- var MediaState = {
765
- IDLE: 'idle',
766
- PLAYING: 'playing',
767
- ENDED: 'ended',
768
- CANCELLED: 'cancelled'
769
- };
770
- var initialMedia = {
771
- attachedAudio: false,
772
- checkIframeStatus: false,
773
- containerName: '',
774
- divHeight: 0,
775
- divWidth: 0,
776
- duration: 0,
777
- emitter: {},
778
- enableStat: false,
779
- fileId: '',
780
- finished: false,
781
- html: null,
782
- id: '',
783
- idCounter: 0,
784
- iframe: null,
785
- iframeName: '',
786
- index: 0,
787
- loadIframeOnRun: false,
788
- loop: false,
789
- mediaId: '',
790
- mediaType: '',
791
- muted: false,
792
- options: {},
793
- player: undefined,
794
- ready: true,
795
- region: initialRegion,
796
- render: 'html',
797
- run: function run() {},
798
- schemaVersion: '1',
799
- singlePlay: false,
800
- state: MediaState.IDLE,
801
- stop: function stop() {
802
- return Promise.resolve();
803
- },
804
- tempSrc: '',
805
- timeoutId: setTimeout(function () {}, 0),
806
- type: '',
807
- uri: '',
808
- url: null,
809
- useDuration: Boolean(0),
810
- xml: null,
811
- mediaTimer: undefined
812
- };
813
-
814
772
  var OverlayLayoutManager = /*#__PURE__*/function () {
815
773
  function OverlayLayoutManager() {
816
774
  _classCallCheck(this, OverlayLayoutManager);
@@ -831,31 +789,37 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
831
789
  case 0:
832
790
  _context2.next = 2;
833
791
  return Promise.all(list.map( /*#__PURE__*/function () {
834
- var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(item) {
835
- var inputOverlay, overlayLayout, $overlay;
792
+ var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(item, index) {
793
+ var _item$index;
794
+ var inputOverlay, overlayLayout, $overlay, _overlayLayout$zIndex;
836
795
  return _regeneratorRuntime().wrap(function _callee$(_context) {
837
796
  while (1) switch (_context.prev = _context.next) {
838
797
  case 0:
839
798
  inputOverlay = {};
840
799
  inputOverlay = _objectSpread2(_objectSpread2({}, inputOverlay), item);
841
- inputOverlay.index = item.index;
800
+ inputOverlay.index = (_item$index = item.index) !== null && _item$index !== void 0 ? _item$index : index;
842
801
  _context.next = 5;
843
802
  return _this.parent.prepareLayoutXlf(_objectSpread2(_objectSpread2({}, initialLayout), inputOverlay));
844
803
  case 5:
845
804
  overlayLayout = _context.sent;
805
+ console.debug('<> XLR.debug OverlayLayoutManager::parseOverlays prepared overlay layout', {
806
+ overlayLayout: overlayLayout,
807
+ inputOverlay: inputOverlay
808
+ });
846
809
  // Hide all overlays first
847
810
  $overlay = document.querySelector("#".concat(overlayLayout.containerName, "[data-sequence=\"").concat(overlayLayout.index, "\"]"));
848
811
  if ($overlay !== null) {
849
- $overlay.style.setProperty('display', 'none');
812
+ $overlay.style.setProperty('visibility', 'hidden');
813
+ $overlay.style.setProperty('z-index', "".concat((_overlayLayout$zIndex = overlayLayout.zIndex) !== null && _overlayLayout$zIndex !== void 0 ? _overlayLayout$zIndex : -999));
850
814
  }
851
815
  return _context.abrupt("return", overlayLayout);
852
- case 9:
816
+ case 10:
853
817
  case "end":
854
818
  return _context.stop();
855
819
  }
856
820
  }, _callee);
857
821
  }));
858
- return function (_x2) {
822
+ return function (_x2, _x3) {
859
823
  return _ref.apply(this, arguments);
860
824
  };
861
825
  }()));
@@ -984,7 +948,7 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
984
948
  }
985
949
  }, _callee3, this, [[12, 21, 24, 27]]);
986
950
  }));
987
- function prepareOverlayLayouts(_x3, _x4) {
951
+ function prepareOverlayLayouts(_x4, _x5) {
988
952
  return _prepareOverlayLayouts.apply(this, arguments);
989
953
  }
990
954
  return prepareOverlayLayouts;
@@ -995,7 +959,8 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
995
959
  var _this$parent$currentL;
996
960
  if (this.overlays.length === 0) return;
997
961
  if (this.parent && (_this$parent$currentL = this.parent.currentLayout) !== null && _this$parent$currentL !== void 0 && _this$parent$currentL.isInterrupt()) {
998
- this.container.style.setProperty('display', 'none');
962
+ this.container.style.setProperty('visibility', 'hidden');
963
+ this.container.style.setProperty('z-index', '-999');
999
964
  return;
1000
965
  }
1001
966
  this.overlays.forEach(function (overlay) {
@@ -1013,21 +978,25 @@ var OverlayLayoutManager = /*#__PURE__*/function () {
1013
978
  while (1) switch (_context5.prev = _context5.next) {
1014
979
  case 0:
1015
980
  overlayHtml = document.querySelector("#".concat(overlay.containerName, "[data-sequence=\"").concat(overlay.index, "\"]"));
981
+ console.debug('<> XLR.debug OverlayLayoutManager::stopOverlays', {
982
+ overlay: overlay,
983
+ overlayHtml: overlayHtml
984
+ });
1016
985
  if (!(overlayHtml !== null)) {
1017
- _context5.next = 5;
986
+ _context5.next = 6;
1018
987
  break;
1019
988
  }
1020
- _context5.next = 4;
989
+ _context5.next = 5;
1021
990
  return overlay.finishAllRegions();
1022
- case 4:
1023
- overlay.emitter.emit('end', overlay);
1024
991
  case 5:
992
+ overlay.emitter.emit('end', overlay);
993
+ case 6:
1025
994
  case "end":
1026
995
  return _context5.stop();
1027
996
  }
1028
997
  }, _callee4);
1029
998
  }));
1030
- return function (_x5) {
999
+ return function (_x6) {
1031
1000
  return _ref2.apply(this, arguments);
1032
1001
  };
1033
1002
  }());
@@ -1096,6 +1065,7 @@ var initialXlr = {
1096
1065
  isLayoutInDOM: function isLayoutInDOM(containerName, layoutId) {
1097
1066
  return false;
1098
1067
  },
1068
+ cleanupOrphanedLayouts: function cleanupOrphanedLayouts(_keepCurrent, _keepNext) {},
1099
1069
  isSspEnabled: false,
1100
1070
  isUpdatingLoop: false,
1101
1071
  isUpdatingOverlays: false,
@@ -72516,6 +72486,10 @@ if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
72516
72486
  function composeVideoSource($media, media) {
72517
72487
  // const videoSrc = await preloadMediaBlob(media.url as string, media.mediaType as MediaTypes);
72518
72488
  var vidType = videoFileType(getFileExt(media.uri));
72489
+ if (!vidType) {
72490
+ console.warn("XLR >> VideoMedia: Unsupported video type for media ".concat(media.id, " with uri ").concat(media.uri));
72491
+ return $media;
72492
+ }
72519
72493
  // Only add one source per type
72520
72494
  if ($media.querySelectorAll("source[type=\"".concat(vidType, "\"]")).length === 0) {
72521
72495
  var $videoSource = document.createElement('source');
@@ -72542,8 +72516,38 @@ var vjsDefaultOptions = function vjsDefaultOptions(opts) {
72542
72516
  };
72543
72517
  var reportToPlayerPlatform = [ConsumerPlatform.CHROMEOS, ConsumerPlatform.ELECTRON];
72544
72518
  function VideoMedia(media, xlr) {
72519
+ var stopped = false;
72545
72520
  var mediaId = getMediaId(media);
72521
+ // ── Stall watchdog (closure-level so stop() can cancel it) ───────────────
72522
+ // 'waiting' and 'stalled' fire when the browser stops receiving data.
72523
+ // Unlike codec or source errors they do NOT fire the 'error' event, so
72524
+ // without a watchdog the video silently freezes for its entire duration.
72525
+ var stallWatchdog;
72526
+ var STALL_TIMEOUT_MS = 10000;
72527
+ var clearStallWatchdog = function clearStallWatchdog() {
72528
+ if (stallWatchdog !== undefined) {
72529
+ clearTimeout(stallWatchdog);
72530
+ stallWatchdog = undefined;
72531
+ }
72532
+ };
72533
+ // ─────────────────────────────────────────────────────────────────────────
72534
+ // ── Unified error → report → stop helper (closure-level) ─────────────────
72535
+ // Used by both the 'error' event and the play Promise catch.
72536
+ // playerReportFault only fires for platforms that report faults (Electron,
72537
+ // ChromeOS). All other platforms just advance to the next media via stop().
72538
+ var reportAndStop = function reportAndStop(reason, code) {
72539
+ if (stopped) return;
72540
+ if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72541
+ playerReportFault(reason, media, code).then(function () {
72542
+ return videoPlayer.stop();
72543
+ });
72544
+ } else {
72545
+ videoPlayer.stop();
72546
+ }
72547
+ };
72548
+ // ─────────────────────────────────────────────────────────────────────────
72546
72549
  var videoPlayer = {
72550
+ player: undefined,
72547
72551
  duration: 0,
72548
72552
  init: function init() {
72549
72553
  var _this = this;
@@ -72551,9 +72555,37 @@ function VideoMedia(media, xlr) {
72551
72555
  videoPlayer.duration = media.duration;
72552
72556
  var vjsPlayer = videojs(mediaId);
72553
72557
  if (vjsPlayer) {
72554
- vjsPlayer.on('loadstart', function () {
72555
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72556
- });
72558
+ videoPlayer.player = vjsPlayer;
72559
+ // ── Early source check ────────────────────────────────────────────────
72560
+ // Two-step check before video.js tries to load anything:
72561
+ // 1. Is the file extension one we map to a MIME type?
72562
+ // 2. Can the browser actually play that MIME type?
72563
+ // Failing either step skips the media immediately so video.js
72564
+ // never renders its "No compatible source" error overlay.
72565
+ var vidType = videoFileType(getFileExt(media.uri));
72566
+ if (!vidType) {
72567
+ console.warn("XLR >> VideoMedia: unrecognised file extension for media ".concat(media.id, " (uri: ").concat(media.uri, ")"));
72568
+ reportAndStop("Unsupported video file extension for media ".concat(media.id), FaultCodes.FaultVideoSource);
72569
+ return;
72570
+ }
72571
+ if (document.createElement('video').canPlayType(vidType) === '') {
72572
+ console.warn("XLR >> VideoMedia: browser cannot play type \"".concat(vidType, "\" for media ").concat(media.id));
72573
+ reportAndStop("Browser cannot play video type \"".concat(vidType, "\" for media ").concat(media.id), FaultCodes.FaultVideoSource);
72574
+ return;
72575
+ }
72576
+ // ─────────────────────────────────────────────────────────────────────
72577
+ var armStallWatchdog = function armStallWatchdog() {
72578
+ clearStallWatchdog();
72579
+ stallWatchdog = setTimeout(function () {
72580
+ if (stopped) return;
72581
+ console.warn("XLR >> VideoMedia: stall timeout on media ".concat(media.id));
72582
+ reportAndStop('Video stall timeout', FaultCodes.FaultVideoUnexpected);
72583
+ }, STALL_TIMEOUT_MS);
72584
+ };
72585
+ vjsPlayer.on('waiting', armStallWatchdog);
72586
+ vjsPlayer.on('stalled', armStallWatchdog);
72587
+ vjsPlayer.on('playing', clearStallWatchdog);
72588
+ vjsPlayer.on('ended', clearStallWatchdog);
72557
72589
  vjsPlayer.on('loadedmetadata', function () {
72558
72590
  if (media.duration === 0) {
72559
72591
  videoPlayer.duration = vjsPlayer.duration();
@@ -72624,34 +72656,20 @@ function VideoMedia(media, xlr) {
72624
72656
  }, 5000);
72625
72657
  })]).then(function () {
72626
72658
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay started"));
72627
- })["catch"]( /*#__PURE__*/function () {
72628
- var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72629
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72630
- while (1) switch (_context2.prev = _context2.next) {
72631
- case 0:
72632
- if (error === 'Timeout') {
72633
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72634
- _this.stop();
72635
- } else {
72636
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72637
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72638
- playerReportFault('Media autoplay error', media).then(function () {
72639
- _this.stop();
72640
- });
72641
- }
72642
- }
72643
- case 1:
72644
- case "end":
72645
- return _context2.stop();
72646
- }
72647
- }, _callee2);
72648
- }));
72649
- return function (_x) {
72650
- return _ref2.apply(this, arguments);
72651
- };
72652
- }());
72659
+ })["catch"](function (error) {
72660
+ if (stopped) return;
72661
+ if (error === 'Timeout') {
72662
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72663
+ // Timeout is a scheduling issue, not a media fault — just advance
72664
+ videoPlayer.stop();
72665
+ } else {
72666
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72667
+ reportAndStop('Media autoplay error', FaultCodes.FaultVideoUnexpected);
72668
+ }
72669
+ });
72653
72670
  // Optional: Reset the flag automatically when a new video loads or the source changes
72654
72671
  vjsPlayer.on('loadstart', function () {
72672
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72655
72673
  triggerTimeUpdate = false;
72656
72674
  });
72657
72675
  if (media.duration === 0) {
@@ -72664,13 +72682,9 @@ function VideoMedia(media, xlr) {
72664
72682
  if (mediaDuration !== undefined && currentTime !== undefined) {
72665
72683
  remainingTimeMs = (mediaDuration - currentTime) * 1000;
72666
72684
  }
72667
- if (regionHasMultipleMedia && remainingTimeMs === 0 && !triggerTimeUpdate) {
72668
- // We don't have data yet and we must immediately prepare next media
72669
- media.region.prepareNextMedia();
72670
- } else if (regionHasMultipleMedia && remainingTimeMs <= preloadBufferTimeMs && !triggerTimeUpdate) {
72685
+ if (regionHasMultipleMedia && !triggerTimeUpdate && (remainingTimeMs === 0 || remainingTimeMs <= preloadBufferTimeMs)) {
72671
72686
  // Check if remaining time is less than preloadBufferTimeMs and the action hasn't been triggered yet
72672
72687
  console.log('Less than preloadBufferTimeMs remaining! Do something now.');
72673
- // Prepare next media in region
72674
72688
  media.region.prepareNextMedia();
72675
72689
  triggerTimeUpdate = true; // Set the flag to prevent re-triggering
72676
72690
  }
@@ -72682,33 +72696,16 @@ function VideoMedia(media, xlr) {
72682
72696
  }
72683
72697
  }
72684
72698
  });
72685
- vjsPlayer.on('error', /*#__PURE__*/function () {
72686
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(err) {
72687
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
72688
- while (1) switch (_context3.prev = _context3.next) {
72689
- case 0:
72690
- console.debug("??? XLR.debug >> VideoMedia: Media Error: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id));
72691
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72692
- playerReportFault('Video file source not supported', media).then(function () {
72693
- _this.stop();
72694
- });
72695
- } else {
72696
- // End media after 5 seconds
72697
- setTimeout(function () {
72698
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended . . ."));
72699
- _this.stop();
72700
- }, 5000);
72701
- }
72702
- case 2:
72703
- case "end":
72704
- return _context3.stop();
72705
- }
72706
- }, _callee3);
72707
- }));
72708
- return function (_x2) {
72709
- return _ref3.apply(this, arguments);
72710
- };
72711
- }());
72699
+ vjsPlayer.on('error', function () {
72700
+ if (stopped) return;
72701
+ clearStallWatchdog();
72702
+ // Extract the actual MediaError so the fault message is
72703
+ // meaningful: code 2 = network, 3 = decode, 4 = not supported.
72704
+ var vjsError = vjsPlayer.error();
72705
+ var reason = vjsError ? "Video error (code ".concat(vjsError.code, "): ").concat(vjsError.message) : 'Unknown video error';
72706
+ console.warn("XLR >> VideoMedia: error on media ".concat(media.id), vjsError);
72707
+ reportAndStop(reason, FaultCodes.FaultVideoUnexpected);
72708
+ });
72712
72709
  if (media.duration === 0) {
72713
72710
  vjsPlayer.on('ended', function () {
72714
72711
  console.debug("??? XLR.debug >> VideoMedia: onended: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
@@ -72718,26 +72715,37 @@ function VideoMedia(media, xlr) {
72718
72715
  }
72719
72716
  },
72720
72717
  stop: function stop() {
72718
+ var _videoPlayer$player;
72721
72719
  var disposeOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
72722
- var vjsPlayer = media.player;
72720
+ clearStallWatchdog();
72721
+ // videoPlayer.player is where init() stores the vjs instance;
72722
+ // media.player is a legacy path kept for backward compat but is
72723
+ // no longer set by init(), so always prefer videoPlayer.player.
72724
+ var vjsPlayer = (_videoPlayer$player = videoPlayer.player) !== null && _videoPlayer$player !== void 0 ? _videoPlayer$player : media.player;
72723
72725
  console.debug('??? XLR.debug >> VideoMedia::stop', {
72724
72726
  vjsPlayer: vjsPlayer,
72725
- isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed_,
72726
- el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el_
72727
+ isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed(),
72728
+ el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el()
72727
72729
  });
72728
72730
  // Expire the media and dispose the video
72729
- if (vjsPlayer !== undefined && !vjsPlayer.isDisposed_) {
72731
+ if (vjsPlayer !== undefined && !vjsPlayer.isDisposed()) {
72730
72732
  if (!disposeOnly) {
72731
72733
  media.emitter.emit('end', media);
72732
72734
  }
72733
72735
  vjsPlayer.dispose();
72734
72736
  // Clear up media player
72737
+ videoPlayer.player = undefined;
72735
72738
  media.player = undefined;
72739
+ media.html = null;
72736
72740
  } else {
72741
+ videoPlayer.player = undefined;
72737
72742
  media.player = undefined;
72738
72743
  media.html = null;
72739
- media.emitter.emit('end', media);
72744
+ if (!disposeOnly) {
72745
+ media.emitter.emit('end', media);
72746
+ }
72740
72747
  }
72748
+ stopped = true;
72741
72749
  },
72742
72750
  play: function play() {
72743
72751
  var _this2 = this;
@@ -72745,9 +72753,9 @@ function VideoMedia(media, xlr) {
72745
72753
  if (vjsPlayer !== undefined) {
72746
72754
  var _vjsPlayer$play;
72747
72755
  (_vjsPlayer$play = vjsPlayer.play()) === null || _vjsPlayer$play === void 0 || _vjsPlayer$play["catch"]( /*#__PURE__*/function () {
72748
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(error) {
72749
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
72750
- while (1) switch (_context4.prev = _context4.next) {
72756
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72757
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72758
+ while (1) switch (_context2.prev = _context2.next) {
72751
72759
  case 0:
72752
72760
  if (error === 'Timeout') {
72753
72761
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
@@ -72762,12 +72770,12 @@ function VideoMedia(media, xlr) {
72762
72770
  }
72763
72771
  case 1:
72764
72772
  case "end":
72765
- return _context4.stop();
72773
+ return _context2.stop();
72766
72774
  }
72767
- }, _callee4);
72775
+ }, _callee2);
72768
72776
  }));
72769
- return function (_x3) {
72770
- return _ref4.apply(this, arguments);
72777
+ return function (_x) {
72778
+ return _ref2.apply(this, arguments);
72771
72779
  };
72772
72780
  }());
72773
72781
  }
@@ -73179,11 +73187,11 @@ function getDataBlob(_x, _x2) {
73179
73187
  return _getDataBlob.apply(this, arguments);
73180
73188
  }
73181
73189
  function _getDataBlob() {
73182
- _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, jwtToken) {
73183
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73184
- while (1) switch (_context3.prev = _context3.next) {
73190
+ _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(src, jwtToken) {
73191
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73192
+ while (1) switch (_context2.prev = _context2.next) {
73185
73193
  case 0:
73186
- return _context3.abrupt("return", fetch(src, {
73194
+ return _context2.abrupt("return", fetch(src, {
73187
73195
  method: 'GET',
73188
73196
  headers: {
73189
73197
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73202,9 +73210,9 @@ function _getDataBlob() {
73202
73210
  }));
73203
73211
  case 1:
73204
73212
  case "end":
73205
- return _context3.stop();
73213
+ return _context2.stop();
73206
73214
  }
73207
- }, _callee3);
73215
+ }, _callee2);
73208
73216
  }));
73209
73217
  return _getDataBlob.apply(this, arguments);
73210
73218
  }
@@ -73212,12 +73220,12 @@ function preloadMediaBlob(_x3, _x4, _x5) {
73212
73220
  return _preloadMediaBlob.apply(this, arguments);
73213
73221
  }
73214
73222
  function _preloadMediaBlob() {
73215
- _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(src, type, jwtToken) {
73223
+ _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, type, jwtToken) {
73216
73224
  var res, blob, data;
73217
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73218
- while (1) switch (_context4.prev = _context4.next) {
73225
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73226
+ while (1) switch (_context3.prev = _context3.next) {
73219
73227
  case 0:
73220
- _context4.next = 2;
73228
+ _context3.next = 2;
73221
73229
  return fetch(src, {
73222
73230
  method: 'GET',
73223
73231
  headers: {
@@ -73225,45 +73233,45 @@ function _preloadMediaBlob() {
73225
73233
  }
73226
73234
  });
73227
73235
  case 2:
73228
- res = _context4.sent;
73236
+ res = _context3.sent;
73229
73237
  blob = new Blob();
73230
73238
  if (!(type === 'image')) {
73231
- _context4.next = 8;
73239
+ _context3.next = 8;
73232
73240
  break;
73233
73241
  }
73234
73242
  blob = new Blob();
73235
- _context4.next = 19;
73243
+ _context3.next = 19;
73236
73244
  break;
73237
73245
  case 8:
73238
73246
  if (!(type === 'video')) {
73239
- _context4.next = 14;
73247
+ _context3.next = 14;
73240
73248
  break;
73241
73249
  }
73242
- _context4.next = 11;
73250
+ _context3.next = 11;
73243
73251
  return res.blob();
73244
73252
  case 11:
73245
- blob = _context4.sent;
73246
- _context4.next = 19;
73253
+ blob = _context3.sent;
73254
+ _context3.next = 19;
73247
73255
  break;
73248
73256
  case 14:
73249
73257
  if (!(type === 'audio')) {
73250
- _context4.next = 19;
73258
+ _context3.next = 19;
73251
73259
  break;
73252
73260
  }
73253
- _context4.next = 17;
73261
+ _context3.next = 17;
73254
73262
  return res.arrayBuffer();
73255
73263
  case 17:
73256
- data = _context4.sent;
73264
+ data = _context3.sent;
73257
73265
  blob = new Blob([data], {
73258
73266
  type: audioFileType(getFileExt(src))
73259
73267
  });
73260
73268
  case 19:
73261
- return _context4.abrupt("return", URL.createObjectURL(blob));
73269
+ return _context3.abrupt("return", URL.createObjectURL(blob));
73262
73270
  case 20:
73263
73271
  case "end":
73264
- return _context4.stop();
73272
+ return _context3.stop();
73265
73273
  }
73266
- }, _callee4);
73274
+ }, _callee3);
73267
73275
  }));
73268
73276
  return _preloadMediaBlob.apply(this, arguments);
73269
73277
  }
@@ -73271,11 +73279,11 @@ function fetchJSON(_x6, _x7) {
73271
73279
  return _fetchJSON.apply(this, arguments);
73272
73280
  }
73273
73281
  function _fetchJSON() {
73274
- _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73275
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73276
- while (1) switch (_context5.prev = _context5.next) {
73282
+ _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(url, jwtToken) {
73283
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73284
+ while (1) switch (_context4.prev = _context4.next) {
73277
73285
  case 0:
73278
- return _context5.abrupt("return", fetch(url, {
73286
+ return _context4.abrupt("return", fetch(url, {
73279
73287
  method: 'GET',
73280
73288
  headers: {
73281
73289
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73287,9 +73295,9 @@ function _fetchJSON() {
73287
73295
  }));
73288
73296
  case 1:
73289
73297
  case "end":
73290
- return _context5.stop();
73298
+ return _context4.stop();
73291
73299
  }
73292
- }, _callee5);
73300
+ }, _callee4);
73293
73301
  }));
73294
73302
  return _fetchJSON.apply(this, arguments);
73295
73303
  }
@@ -73297,11 +73305,11 @@ function fetchText(_x8, _x9) {
73297
73305
  return _fetchText.apply(this, arguments);
73298
73306
  }
73299
73307
  function _fetchText() {
73300
- _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(url, jwtToken) {
73301
- return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73302
- while (1) switch (_context6.prev = _context6.next) {
73308
+ _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73309
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73310
+ while (1) switch (_context5.prev = _context5.next) {
73303
73311
  case 0:
73304
- return _context6.abrupt("return", fetch(url, {
73312
+ return _context5.abrupt("return", fetch(url, {
73305
73313
  method: 'GET',
73306
73314
  headers: {
73307
73315
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73320,9 +73328,9 @@ function _fetchText() {
73320
73328
  }));
73321
73329
  case 1:
73322
73330
  case "end":
73323
- return _context6.stop();
73331
+ return _context5.stop();
73324
73332
  }
73325
- }, _callee6);
73333
+ }, _callee5);
73326
73334
  }));
73327
73335
  return _fetchText.apply(this, arguments);
73328
73336
  }
@@ -73439,6 +73447,32 @@ function setExpiry(numDays) {
73439
73447
  var today = new Date();
73440
73448
  return new Date(today.setHours(24 * numDays || 1)).toJSON();
73441
73449
  }
73450
+ /**
73451
+ * Check whether a media item is currently within its valid date window.
73452
+ * Returns true when the media should be shown, false when it should be skipped.
73453
+ *
73454
+ * Rules:
73455
+ * - Empty / invalid fromDt → treat as "no start restriction"
73456
+ * - Empty / invalid toDt → treat as "no expiry"
73457
+ * - now < fromDt → not yet active → skip
73458
+ * - now > toDt → expired → skip
73459
+ */
73460
+ function isMediaActive(fromDt, toDt) {
73461
+ var now = Date.now();
73462
+ if (fromDt) {
73463
+ var from = new Date(fromDt).getTime();
73464
+ if (!isNaN(from) && now < from) {
73465
+ return false;
73466
+ }
73467
+ }
73468
+ if (toDt) {
73469
+ var to = new Date(toDt).getTime();
73470
+ if (!isNaN(to) && now > to) {
73471
+ return false;
73472
+ }
73473
+ }
73474
+ return true;
73475
+ }
73442
73476
  /**
73443
73477
  * Check if given layout exists in the loop using layoutId
73444
73478
  * @param layouts Schedule loop unique layouts (uniqueLayouts)
@@ -73647,7 +73681,7 @@ function prepareVideoMedia(media, region) {
73647
73681
  var $layout = region.layout.html;
73648
73682
  var layoutSelector = '#' + region.layout.containerName + '[data-sequence="' + region.layout.index + '"]';
73649
73683
  var $layoutWithIndex = document.querySelector(layoutSelector);
73650
- var $region = document.querySelector('#' + region.containerName);
73684
+ var $region = region.html;
73651
73685
  var mediaInRegion = $region === null || $region === void 0 ? void 0 : $region.querySelector('.' + mediaId);
73652
73686
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73653
73687
  layoutSelector: layoutSelector,
@@ -73666,7 +73700,7 @@ function prepareVideoMedia(media, region) {
73666
73700
  media.html = createMediaElement(media);
73667
73701
  }
73668
73702
  // Append fresh copy of the media into the region
73669
- $region !== null && $region.appendChild(media.html);
73703
+ region.html.appendChild(media.html);
73670
73704
  var isMediaInDOM = document.body.contains(media.html);
73671
73705
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73672
73706
  isMediaInDOM: isMediaInDOM,
@@ -73675,29 +73709,9 @@ function prepareVideoMedia(media, region) {
73675
73709
  });
73676
73710
  // Initialize video.js
73677
73711
  media.player = videojs(mediaId, _objectSpread2(_objectSpread2({}, defaultVjsOpts), {}, {
73678
- errorDisplay: region.xlr.config.platform !== ConsumerPlatform.CHROMEOS,
73712
+ errorDisplay: !reportToPlayerPlatform.includes(region.xlr.config.platform),
73679
73713
  loop: media.loop
73680
73714
  }));
73681
- media.player.on('error', /*#__PURE__*/function () {
73682
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(err) {
73683
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73684
- while (1) switch (_context2.prev = _context2.next) {
73685
- case 0:
73686
- if (media.region.xlr.config.platform === ConsumerPlatform.CHROMEOS) {
73687
- playerReportFault('Video file not supported', media).then(function () {
73688
- media.emitter.emit('end', media);
73689
- });
73690
- }
73691
- case 1:
73692
- case "end":
73693
- return _context2.stop();
73694
- }
73695
- }, _callee2);
73696
- }));
73697
- return function (_x10) {
73698
- return _ref3.apply(this, arguments);
73699
- };
73700
- }());
73701
73715
  media.player.el().style.setProperty('visibility', 'hidden');
73702
73716
  media.player.el().style.setProperty('opacity', '0');
73703
73717
  media.player.el().style.setProperty('z-index', '-99');
@@ -73712,9 +73726,9 @@ function prepareImageMedia(media, region) {
73712
73726
  if (mediaInRegion) {
73713
73727
  mediaInRegion.remove();
73714
73728
  }
73715
- // Append media to its region
73716
- var $region = document.querySelector('#' + region.containerName);
73717
- $region !== null && $region.appendChild(media.html);
73729
+ // Append media to its region using the direct reference to avoid
73730
+ // global querySelector finding a same-named region in another layout
73731
+ region.html.appendChild(media.html);
73718
73732
  }
73719
73733
  function prepareAudioMedia(media, region) {
73720
73734
  var mediaId = getMediaId(media);
@@ -73727,9 +73741,8 @@ function prepareAudioMedia(media, region) {
73727
73741
  if (mediaInRegion) {
73728
73742
  mediaInRegion.remove();
73729
73743
  }
73730
- // Append media to its region
73731
- var $region = document.querySelector('#' + region.containerName);
73732
- $region !== null && $region.appendChild(media.html);
73744
+ // Append media to its region using the direct reference
73745
+ region.html.appendChild(media.html);
73733
73746
  }
73734
73747
  function prepareHtmlMedia(media, region) {
73735
73748
  // Set state as false ( for now )
@@ -73749,60 +73762,102 @@ function prepareHtmlMedia(media, region) {
73749
73762
  media.html.innerHTML = '';
73750
73763
  media.html.appendChild(media.iframe);
73751
73764
  if (!mediaInRegion) {
73752
- // Add fresh copy of the media into the region
73753
- var _$region = document.querySelector('#' + region.containerName);
73754
- _$region !== null && _$region.appendChild(media.html);
73765
+ // Add fresh copy of the media into the region using the direct reference
73766
+ region.html.appendChild(media.html);
73755
73767
  media.ready = true;
73756
73768
  }
73757
73769
  }
73758
73770
  }
73759
- function playerReportFault(_x11, _x12) {
73771
+ var FaultCodes;
73772
+ (function (FaultCodes) {
73773
+ FaultCodes[FaultCodes["FaultVideoSource"] = 2001] = "FaultVideoSource";
73774
+ FaultCodes[FaultCodes["FaultVideoUnexpected"] = 2099] = "FaultVideoUnexpected";
73775
+ })(FaultCodes || (FaultCodes = {}));
73776
+ function playerReportFault(_x10, _x11) {
73760
73777
  return _playerReportFault.apply(this, arguments);
73761
73778
  }
73762
73779
  function _playerReportFault() {
73763
- _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(msg, media) {
73764
- var playerSW, hasSW;
73765
- return _regeneratorRuntime().wrap(function _callee7$(_context7) {
73766
- while (1) switch (_context7.prev = _context7.next) {
73780
+ _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(msg, media) {
73781
+ var code,
73782
+ platform,
73783
+ playerSW,
73784
+ hasSW,
73785
+ mediaFault,
73786
+ channel,
73787
+ _args6 = arguments;
73788
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73789
+ while (1) switch (_context6.prev = _context6.next) {
73767
73790
  case 0:
73791
+ code = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : FaultCodes.FaultVideoUnexpected;
73768
73792
  // Immediately expire media and report a fault
73793
+ platform = media.region.xlr.config.platform;
73769
73794
  playerSW = PwaSW();
73770
- _context7.next = 3;
73795
+ _context6.next = 5;
73771
73796
  return playerSW.getSW();
73772
- case 3:
73773
- hasSW = _context7.sent;
73774
- if (hasSW) {
73775
- playerSW.postMsg({
73776
- type: 'MEDIA_FAULT',
73777
- code: 5002,
73778
- reason: msg,
73797
+ case 5:
73798
+ hasSW = _context6.sent;
73799
+ mediaFault = {
73800
+ type: 'MEDIA_FAULT',
73801
+ code: code,
73802
+ reason: msg,
73803
+ mediaId: media.id,
73804
+ regionId: media.region.id,
73805
+ layoutId: media.region.layout.id,
73806
+ date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73807
+ // Temporary setting
73808
+ expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73809
+ };
73810
+ console.debug('playerReportFault >> Reporting media fault', {
73811
+ mediaFault: mediaFault,
73812
+ platform: platform,
73813
+ hasSW: hasSW
73814
+ });
73815
+ if (!(platform === ConsumerPlatform.CHROMEOS && hasSW)) {
73816
+ _context6.next = 12;
73817
+ break;
73818
+ }
73819
+ return _context6.abrupt("return", playerSW.postMsg(mediaFault).then(function () {
73820
+ // We try to prepare next media if we have more than 1 media
73821
+ if (media.region.totalMediaObjects > 1) {
73822
+ media.region.prepareNextMedia();
73823
+ }
73824
+ })["finally"](function () {
73825
+ // Stopping media as we have reported the error as fault
73826
+ console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73779
73827
  mediaId: media.id,
73780
- regionId: media.region.id,
73781
- layoutId: media.region.layout.id,
73782
- date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73783
- // Temporary setting
73784
- expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73785
- }).then(function () {
73786
- // We try to prepare next media if we have more than 1 media
73787
- if (media.region.totalMediaObjects > 1) {
73788
- media.region.prepareNextMedia();
73789
- }
73790
- })["finally"](function () {
73791
- // Stopping media as we have reported the error as fault
73792
- console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73793
- mediaId: media.id,
73794
- regionItems: media.region.totalMediaObjects
73795
- });
73828
+ regionItems: media.region.totalMediaObjects
73796
73829
  });
73830
+ }));
73831
+ case 12:
73832
+ if (!(platform === ConsumerPlatform.ELECTRON)) {
73833
+ _context6.next = 17;
73834
+ break;
73797
73835
  }
73798
- case 5:
73836
+ // Create a broadcast channel to report media fault to the main process
73837
+ channel = new BroadcastChannel('player-faults-bc');
73838
+ channel.postMessage(mediaFault);
73839
+ console.debug('playerReportFault >> Electron platform - posted media fault to channel', {
73840
+ mediaFault: mediaFault
73841
+ });
73842
+ // channel.close();
73843
+ return _context6.abrupt("return", Promise.resolve());
73844
+ case 17:
73845
+ return _context6.abrupt("return", Promise.resolve());
73846
+ case 18:
73799
73847
  case "end":
73800
- return _context7.stop();
73848
+ return _context6.stop();
73801
73849
  }
73802
- }, _callee7);
73850
+ }, _callee6);
73803
73851
  }));
73804
73852
  return _playerReportFault.apply(this, arguments);
73805
73853
  }
73854
+ function setLayoutIndex(layout, layoutIndex) {
73855
+ if (!layout || layout.id === null) {
73856
+ return;
73857
+ }
73858
+ layout.index = layoutIndex;
73859
+ return layout;
73860
+ }
73806
73861
 
73807
73862
  const urlAlphabet =
73808
73863
  'useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict';
@@ -73820,7 +73875,7 @@ let nanoid = (size = 21) => {
73820
73875
  function AudioMedia(media) {
73821
73876
  var audioMediaObject = {
73822
73877
  init: function init() {
73823
- var $audioMedia = document.getElementById(getMediaId(media));
73878
+ var $audioMedia = media.html;
73824
73879
  var $playBtn = null;
73825
73880
  if ($audioMedia) {
73826
73881
  $audioMedia.onloadstart = function () {
@@ -73883,6 +73938,8 @@ var Media = /*#__PURE__*/function () {
73883
73938
  _this$xml3,
73884
73939
  _this$xml4,
73885
73940
  _this$xml5,
73941
+ _this$xml6,
73942
+ _this$xml7,
73886
73943
  _this = this;
73887
73944
  _classCallCheck(this, Media);
73888
73945
  _defineProperty(this, "attachedAudio", false);
@@ -73895,6 +73952,7 @@ var Media = /*#__PURE__*/function () {
73895
73952
  _defineProperty(this, "enableStat", false);
73896
73953
  _defineProperty(this, "fileId", '');
73897
73954
  _defineProperty(this, "finished", false);
73955
+ _defineProperty(this, "fromDt", '');
73898
73956
  _defineProperty(this, "html", null);
73899
73957
  _defineProperty(this, "id", '');
73900
73958
  _defineProperty(this, "idCounter", 0);
@@ -73916,6 +73974,7 @@ var Media = /*#__PURE__*/function () {
73916
73974
  _defineProperty(this, "state", MediaState.IDLE);
73917
73975
  _defineProperty(this, "tempSrc", '');
73918
73976
  _defineProperty(this, "timeoutId", setTimeout(function () {}, 0));
73977
+ _defineProperty(this, "toDt", '');
73919
73978
  _defineProperty(this, "type", '');
73920
73979
  _defineProperty(this, "uri", '');
73921
73980
  _defineProperty(this, "url", null);
@@ -73942,12 +74001,14 @@ var Media = /*#__PURE__*/function () {
73942
74001
  this.duration = parseInt((_this$xml4 = this.xml) === null || _this$xml4 === void 0 ? void 0 : _this$xml4.getAttribute('duration')) || 0;
73943
74002
  this.enableStat = Boolean(((_this$xml5 = this.xml) === null || _this$xml5 === void 0 ? void 0 : _this$xml5.getAttribute('enableStat')) || false);
73944
74003
  this.hasCommandExecuted = false;
74004
+ this.fromDt = ((_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getAttribute('fromDt')) || '';
74005
+ this.toDt = ((_this$xml7 = this.xml) === null || _this$xml7 === void 0 ? void 0 : _this$xml7.getAttribute('toDt')) || '';
73945
74006
  this.on('start', function (media) {
73946
74007
  if (media.state === MediaState.PLAYING) return;
73947
74008
  media.state = MediaState.PLAYING;
73948
74009
  if (media.mediaType === 'video') {
73949
- var videoMedia = VideoMedia(media, _this.xlr);
73950
- videoMedia.init();
74010
+ media.videoHandler = VideoMedia(media, _this.xlr);
74011
+ media.videoHandler.init();
73951
74012
  if (media.duration > 0) {
73952
74013
  _this.startMediaTimer(media);
73953
74014
  }
@@ -74074,8 +74135,8 @@ var Media = /*#__PURE__*/function () {
74074
74135
  if (media.mediaType === 'video') {
74075
74136
  // Dispose the video media
74076
74137
  console.debug("??? XLR.debug >> VideoMedia::stop - ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
74077
- if (media.player !== undefined) {
74078
- VideoMedia(media, _this2.xlr).stop(true);
74138
+ if (media.videoHandler !== undefined) {
74139
+ media.videoHandler.stop(true);
74079
74140
  }
74080
74141
  }
74081
74142
  }
@@ -74090,8 +74151,8 @@ var Media = /*#__PURE__*/function () {
74090
74151
  }, {
74091
74152
  key: "init",
74092
74153
  value: function init() {
74093
- var _this$xml6;
74094
- var mediaOptions = (_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getElementsByTagName('options');
74154
+ var _this$xml8;
74155
+ var mediaOptions = (_this$xml8 = this.xml) === null || _this$xml8 === void 0 ? void 0 : _this$xml8.getElementsByTagName('options');
74095
74156
  if (mediaOptions) {
74096
74157
  for (var _i = 0, _Array$from = Array.from(mediaOptions); _i < _Array$from.length; _i++) {
74097
74158
  var _options = _Array$from[_i];
@@ -74146,6 +74207,10 @@ var Media = /*#__PURE__*/function () {
74146
74207
  }
74147
74208
  } else if (this.xlr.config.platform === ConsumerPlatform.ELECTRON) {
74148
74209
  tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74210
+ // this is an SSP Layout
74211
+ if (this.region.layout.layoutId === -1) {
74212
+ tmpUrl = this.uri;
74213
+ }
74149
74214
  }
74150
74215
  this.url = tmpUrl;
74151
74216
  // Loop if media has loop, or if region has loop and a single media
@@ -74188,8 +74253,8 @@ var Media = /*#__PURE__*/function () {
74188
74253
  mediaType: _this3.mediaType,
74189
74254
  containerName: _this3.containerName
74190
74255
  });
74191
- var $region = document.querySelector('#' + _this3.region.containerName);
74192
- var $media = $region !== null && $region.querySelector('.' + mediaId);
74256
+ var $region = _this3.region.html;
74257
+ var $media = $region.querySelector('.' + mediaId);
74193
74258
  if (!$media) {
74194
74259
  $media = getNewMedia();
74195
74260
  }
@@ -74253,13 +74318,13 @@ var Media = /*#__PURE__*/function () {
74253
74318
  }
74254
74319
  };
74255
74320
  var getNewMedia = function getNewMedia() {
74256
- var $region = document.getElementById("".concat(_this3.region.containerName));
74321
+ var $region = _this3.region.html;
74257
74322
  // This function is for checking whether
74258
74323
  // the region still has to show a media item
74259
74324
  // when another region is not finished yet
74260
74325
  if (_this3.region.complete && !_this3.region.layout.allEnded) {
74261
74326
  // Add currentMedia to the region
74262
- $region && $region.insertBefore(_this3.html, $region.lastElementChild);
74327
+ $region.insertBefore(_this3.html, $region.lastElementChild);
74263
74328
  return _this3.html;
74264
74329
  }
74265
74330
  return null;
@@ -74270,23 +74335,18 @@ var Media = /*#__PURE__*/function () {
74270
74335
  key: "stop",
74271
74336
  value: function () {
74272
74337
  var _stop = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
74273
- var $media;
74274
74338
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74275
74339
  while (1) switch (_context.prev = _context.next) {
74276
74340
  case 0:
74277
- $media = document.getElementById(getMediaId({
74278
- mediaType: this.mediaType,
74279
- containerName: this.containerName
74280
- }));
74281
- if ($media) {
74282
- $media.style.display = 'none';
74283
- $media.remove();
74341
+ if (this.html) {
74342
+ this.html.style.display = 'none';
74343
+ this.html.remove();
74284
74344
  }
74285
74345
  // Release blob URLs for image media to prevent memory leaks on long-running signage
74286
74346
  if (this.mediaType === 'image' && this.url) {
74287
74347
  BlobLoader.release(this.url);
74288
74348
  }
74289
- case 3:
74349
+ case 2:
74290
74350
  case "end":
74291
74351
  return _context.stop();
74292
74352
  }
@@ -74459,18 +74519,30 @@ var Region = /*#__PURE__*/function () {
74459
74519
  this.html = $region;
74460
74520
  /* Parse region media objects */
74461
74521
  var regionMediaItems = Array.from(this.xml.getElementsByTagName('media'));
74462
- this.totalMediaObjects = regionMediaItems.length;
74463
74522
  $layout && $layout.appendChild(this.html);
74464
74523
  Array.from(regionMediaItems).forEach( /*#__PURE__*/function () {
74465
74524
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(mediaXml, indx) {
74466
- var mediaObj;
74525
+ var fromDt, toDt, mediaObj;
74467
74526
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74468
74527
  while (1) switch (_context.prev = _context.next) {
74469
74528
  case 0:
74529
+ fromDt = mediaXml.getAttribute('fromDt') || '';
74530
+ toDt = mediaXml.getAttribute('toDt') || '';
74531
+ if (isMediaActive(fromDt, toDt)) {
74532
+ _context.next = 5;
74533
+ break;
74534
+ }
74535
+ console.debug('??? XLR.debug >> Region::prepareRegion - skipping expired/inactive media', {
74536
+ mediaId: mediaXml.getAttribute('id'),
74537
+ fromDt: fromDt,
74538
+ toDt: toDt
74539
+ });
74540
+ return _context.abrupt("return");
74541
+ case 5:
74470
74542
  mediaObj = new Media(_this, (mediaXml === null || mediaXml === void 0 ? void 0 : mediaXml.getAttribute('id')) || '', mediaXml, _this.options, _this.xlr);
74471
74543
  mediaObj.index = indx;
74472
74544
  _this.mediaObjects.push(mediaObj);
74473
- case 3:
74545
+ case 8:
74474
74546
  case "end":
74475
74547
  return _context.stop();
74476
74548
  }
@@ -74480,6 +74552,7 @@ var Region = /*#__PURE__*/function () {
74480
74552
  return _ref.apply(this, arguments);
74481
74553
  };
74482
74554
  }());
74555
+ this.totalMediaObjects = this.mediaObjects.length;
74483
74556
  console.debug('??? XLR.debug >> Region - done looping through media', {
74484
74557
  mediaObjects: this.mediaObjects
74485
74558
  });
@@ -74535,6 +74608,23 @@ var Region = /*#__PURE__*/function () {
74535
74608
  key: "prepareNextMedia",
74536
74609
  value: function prepareNextMedia() {
74537
74610
  var nextMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74611
+ // Skip over any media items that are no longer within their active date window
74612
+ var skippedCount = 0;
74613
+ while (skippedCount < this.totalMediaObjects) {
74614
+ var candidate = this.mediaObjects[nextMediaIndex];
74615
+ if (isMediaActive(candidate.fromDt, candidate.toDt)) {
74616
+ break;
74617
+ }
74618
+ skippedCount++;
74619
+ nextMediaIndex = (nextMediaIndex + 1) % this.totalMediaObjects;
74620
+ }
74621
+ // Nothing to pre-load when:
74622
+ // - every item is expired, OR
74623
+ // - the only active item is the one currently playing (skip loop wrapped back to it)
74624
+ if (skippedCount >= this.totalMediaObjects || nextMediaIndex === this.currentMediaIndex) {
74625
+ console.debug('<><> XLR.debug >> [Region::prepareNextMedia()] - no active next media to preload');
74626
+ return;
74627
+ }
74538
74628
  var nextMedia = this.mediaObjects[nextMediaIndex];
74539
74629
  console.debug('<><> XLR.debug >> [Media] - [Region::prepareNextMedia()] - Preparing next media', {
74540
74630
  currentMediaIndex: this.currentMediaIndex,
@@ -74575,9 +74665,21 @@ var Region = /*#__PURE__*/function () {
74575
74665
  }, {
74576
74666
  key: "run",
74577
74667
  value: function run() {
74668
+ var _this$currMedia, _this$oldMedia2;
74578
74669
  console.debug('??? XLR.debug >> Region Called Region::run > ', this.id);
74579
74670
  // Reset region states
74580
74671
  this.reset();
74672
+ // All media were filtered out (all expired/inactive before this run started)
74673
+ if (this.mediaObjects.length === 0) {
74674
+ console.debug('??? XLR.debug >> Region::run - no active media, finishing region', this.id);
74675
+ this.finished();
74676
+ return;
74677
+ }
74678
+ console.debug('??? XLR.debug >> Region Called Region::run - after reset > ', {
74679
+ regionId: this.id,
74680
+ currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74681
+ oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName
74682
+ });
74581
74683
  if (this.currMedia) {
74582
74684
  this.transitionNodes(this.oldMedia, this.currMedia);
74583
74685
  }
@@ -74626,6 +74728,7 @@ var Region = /*#__PURE__*/function () {
74626
74728
  // Hide oldMedia
74627
74729
  if (oldMedia) {
74628
74730
  var $layout = document.querySelector("#".concat(_this2.layout.containerName, "[data-sequence=\"").concat(_this2.layout.index, "\"]"));
74731
+ if (!$layout) return;
74629
74732
  var $region = $layout.querySelector('#' + _this2.containerName);
74630
74733
  var $oldMedia = $region ? $region.querySelector('.' + getMediaId(oldMedia)) : null;
74631
74734
  if ($oldMedia) {
@@ -74646,7 +74749,7 @@ var Region = /*#__PURE__*/function () {
74646
74749
  $videoWrapper.style.setProperty('visibility', 'hidden');
74647
74750
  $videoWrapper.style.setProperty('z-index', '-999');
74648
74751
  $videoWrapper.style.setProperty('opacity', '0');
74649
- if (oldMedia.player && oldMedia.videoHandler) {
74752
+ if (oldMedia.videoHandler) {
74650
74753
  oldMedia.videoHandler.stop(true);
74651
74754
  }
74652
74755
  }
@@ -74706,13 +74809,13 @@ var Region = /*#__PURE__*/function () {
74706
74809
  }, {
74707
74810
  key: "playNextMedia",
74708
74811
  value: function playNextMedia() {
74709
- var _this$oldMedia2, _this$currMedia, _this$nxtMedia, _this$currMedia2, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia3, _this$currMedia7, _this$nxtMedia2;
74812
+ var _this$oldMedia3, _this$currMedia2, _this$nxtMedia, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia4, _this$currMedia7, _this$nxtMedia2;
74710
74813
  console.debug('??? XLR.debug Region playing next media', {
74711
74814
  regionId: this.id,
74712
74815
  currentMediaIndex: this.currentMediaIndex,
74713
74816
  mediaItemsLn: this.mediaObjects.length,
74714
- oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName,
74715
- currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74817
+ oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74818
+ currMedia: (_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.containerName,
74716
74819
  nxtMedia: (_this$nxtMedia = this.nxtMedia) === null || _this$nxtMedia === void 0 ? void 0 : _this$nxtMedia.containerName
74717
74820
  });
74718
74821
  /* The current media has finished running */
@@ -74722,33 +74825,20 @@ var Region = /*#__PURE__*/function () {
74722
74825
  });
74723
74826
  return;
74724
74827
  }
74725
- // Are we in a playlist, and has the playlist completed a full cycle?
74726
- var isLastMediaInPlaylist = this.currentMediaIndex === this.mediaObjects.length - 1 && this.mediaObjects.length > 1;
74727
- // If yes, enable shell command widgets again (if any), so they execute on the next playlist cycle
74728
- if (isLastMediaInPlaylist) {
74729
- this.mediaObjects.forEach(function (media) {
74730
- if (media.mediaType === 'shellcommand') {
74731
- // reset per-playlist-cycle execution state
74732
- media.hasCommandExecuted = false;
74733
- }
74734
- });
74735
- }
74736
- if (!this.layout.isOverlay && this.currentMediaIndex === this.mediaObjects.length - 1) {
74737
- this.finished();
74738
- if (this.layout.allEnded) {
74739
- console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74740
- return;
74741
- }
74742
- }
74828
+ // Snapshot the index of the media that just ended so we can detect
74829
+ // cycle completion after the skip loop runs.
74830
+ var origIndex = this.currentMediaIndex;
74743
74831
  // When the region has completed and when currentMedia is html
74744
74832
  // Then, preserve the currentMedia state
74745
- if (this.complete && ((_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.render) === 'html') {
74833
+ if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html') {
74746
74834
  return;
74747
74835
  }
74748
74836
  // When the region has completed and mediaObjects.length = 1
74749
- // and curMedia.loop = false, then put the media on
74750
- // its current state
74751
- 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)) {
74837
+ // and render is not html, preserve the current media state without
74838
+ // calling transitionNodes (which would remove the media from DOM for 1s).
74839
+ // Do NOT restart the media timer here the layout will end naturally when
74840
+ // regionExpired() detects all regions complete on the next cycle.
74841
+ 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')) {
74752
74842
  return;
74753
74843
  }
74754
74844
  if (this.currMedia) {
@@ -74756,17 +74846,51 @@ var Region = /*#__PURE__*/function () {
74756
74846
  } else {
74757
74847
  this.oldMedia = undefined;
74758
74848
  }
74759
- this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74849
+ this.currentMediaIndex = (origIndex + 1) % this.totalMediaObjects;
74760
74850
  this.currMedia = this.mediaObjects[this.currentMediaIndex];
74851
+ // Skip media items that are no longer within their active date window
74852
+ var skippedCount = 0;
74853
+ while (this.currMedia && !isMediaActive(this.currMedia.fromDt, this.currMedia.toDt)) {
74854
+ skippedCount++;
74855
+ if (skippedCount >= this.totalMediaObjects) {
74856
+ // Every item in the playlist has expired; finish this region
74857
+ console.debug('??? XLR.debug >> Region::playNextMedia - all media expired, finishing region', this.id);
74858
+ this.finished();
74859
+ return;
74860
+ }
74861
+ this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74862
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
74863
+ }
74761
74864
  this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
74865
+ // A full playlist cycle has been traversed when the total advancement
74866
+ // (the initial +1 step plus any skips over expired items) reaches or
74867
+ // crosses the end of the array.
74868
+ var crossedEnd = origIndex + 1 + skippedCount >= this.totalMediaObjects;
74869
+ // Re-enable shell command widgets at the end of each full cycle so they
74870
+ // execute again on the next pass.
74871
+ if (crossedEnd && this.mediaObjects.length > 1) {
74872
+ this.mediaObjects.forEach(function (media) {
74873
+ if (media.mediaType === 'shellcommand') {
74874
+ // reset per-playlist-cycle execution state
74875
+ media.hasCommandExecuted = false;
74876
+ }
74877
+ });
74878
+ }
74762
74879
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74763
74880
  regionId: this.id,
74764
74881
  currentMediaIndex: this.currentMediaIndex,
74765
74882
  mediaItemsLn: this.mediaObjects.length,
74766
- oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74883
+ oldMedia: (_this$oldMedia4 = this.oldMedia) === null || _this$oldMedia4 === void 0 ? void 0 : _this$oldMedia4.containerName,
74767
74884
  currMedia: (_this$currMedia7 = this.currMedia) === null || _this$currMedia7 === void 0 ? void 0 : _this$currMedia7.containerName,
74768
74885
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74769
74886
  });
74887
+ if (!this.layout.isOverlay && crossedEnd) {
74888
+ this.finished();
74889
+ if (this.layout.allEnded) {
74890
+ console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74891
+ return;
74892
+ }
74893
+ }
74770
74894
  this.transitionNodes(this.oldMedia, this.currMedia);
74771
74895
  }
74772
74896
  }, {
@@ -74795,8 +74919,7 @@ var Region = /*#__PURE__*/function () {
74795
74919
  }, {
74796
74920
  key: "exitTransition",
74797
74921
  value: function exitTransition() {
74798
- /* TODO: Actually implement region exit transitions */
74799
- document.getElementById("".concat(this.containerName));
74922
+ /* TODO: Actually implement region exit transitions using this.html */
74800
74923
  console.debug('Called Region::exitTransition ', this.id);
74801
74924
  this.exitTransitionComplete();
74802
74925
  }
@@ -74978,10 +75101,16 @@ var ActionController = /*#__PURE__*/function () {
74978
75101
  }, {
74979
75102
  key: "openLayoutInNewTab",
74980
75103
  value: function openLayoutInNewTab(layoutCode, options) {
74981
- if (confirm(this.translations.navigateToLayout.replace('[layoutTag]', layoutCode))) {
74982
- var url = options.layoutPreviewUrl.replace('[layoutCode]', layoutCode) + '?findByCode=1';
74983
- window.open(url, '_blank');
74984
- }
75104
+ var url = options.layoutPreviewUrl.replace('[layoutCode]', layoutCode) + '?findByCode=1';
75105
+ // Send a postMessage to the parent frame so the CMS can handle the confirmation
75106
+ // and navigation (confirm() is blocked in sandboxed iframes without allow-modals).
75107
+ window.parent.postMessage({
75108
+ type: 'xlr:navLayout',
75109
+ layoutCode: layoutCode,
75110
+ url: url
75111
+ }, '*');
75112
+ // Also emit via the XLR event system for non-iframe consumers.
75113
+ this.parent.xlr.emitter.emit('navLayout', layoutCode, url);
74985
75114
  }
74986
75115
  }, {
74987
75116
  key: "openLayoutInPlayer",
@@ -75090,7 +75219,7 @@ var ActionController = /*#__PURE__*/function () {
75090
75219
  if (dataset.source === 'region') {
75091
75220
  // Try to find the region
75092
75221
  if (regionObj.id === dataset.sourceid) {
75093
- $sourceObj = document.getElementById(regionObj.containerName);
75222
+ $sourceObj = regionObj.html;
75094
75223
  break;
75095
75224
  }
75096
75225
  } else if (dataset.source === 'widget') {
@@ -75099,7 +75228,7 @@ var ActionController = /*#__PURE__*/function () {
75099
75228
  for (var _i2 = 0, _mediaObjects = mediaObjects; _i2 < _mediaObjects.length; _i2++) {
75100
75229
  var mediaObject = _mediaObjects[_i2];
75101
75230
  if (mediaObject.id === dataset.sourceid) {
75102
- $sourceObj = document.getElementById(mediaObject.containerName);
75231
+ $sourceObj = mediaObject.html;
75103
75232
  break;
75104
75233
  }
75105
75234
  }
@@ -75364,7 +75493,11 @@ var Layout = /*#__PURE__*/function () {
75364
75493
  this.on('start', function (layout) {
75365
75494
  layout.done = false;
75366
75495
  layout.state = ELayoutState.RUNNING;
75367
- console.debug('>>>> XLR.debug Layout start emitted > Layout ID > ', layout.id);
75496
+ console.debug('>>>> XLR.debug Layout start emitted > Layout > ', {
75497
+ layoutId: layout.id,
75498
+ layoutIndex: layout.index,
75499
+ layoutState: layout.state
75500
+ });
75368
75501
  // Check if stats are enabled for the layout
75369
75502
  if (layout.enableStat) {
75370
75503
  _this.statsBC.postMessage({
@@ -75385,12 +75518,21 @@ var Layout = /*#__PURE__*/function () {
75385
75518
  while (1) switch (_context2.prev = _context2.next) {
75386
75519
  case 0:
75387
75520
  if (!(layout.state === ELayoutState.CANCELLED)) {
75388
- _context2.next = 2;
75521
+ _context2.next = 3;
75389
75522
  break;
75390
75523
  }
75524
+ console.debug('>>>> XLR.debug Layout end emitted but layout is already cancelled > Layout ID > ', {
75525
+ layoutId: layout.id,
75526
+ layoutIndex: layout.index,
75527
+ layoutState: layout.state
75528
+ });
75391
75529
  return _context2.abrupt("return");
75392
- case 2:
75393
- console.debug('>>>> XLR.debug Ending layout with ID of > ', layout.layoutId);
75530
+ case 3:
75531
+ console.debug('>>>> XLR.debug Ending layout', {
75532
+ layoutId: layout.id,
75533
+ layoutIndex: layout.index,
75534
+ layoutState: layout.state
75535
+ });
75394
75536
  /* Remove layout that has ended */
75395
75537
  $layout = document.querySelector("#".concat(layout.containerName, "[data-sequence=\"").concat(layout.index, "\"]")); // Only update layout.state when last state === RUNNING
75396
75538
  if (layout.state === ELayoutState.RUNNING) {
@@ -75398,8 +75540,13 @@ var Layout = /*#__PURE__*/function () {
75398
75540
  layout.state = ELayoutState.PLAYED;
75399
75541
  }
75400
75542
  layout.done = true;
75401
- console.debug({
75402
- $layout: $layout
75543
+ console.debug('>>> XLR.debug Layout end emitted > Layout ID > ', {
75544
+ $layout: $layout,
75545
+ layoutId: layout.id,
75546
+ layoutIndex: layout.index,
75547
+ layoutState: layout.state,
75548
+ isOverlay: layout.isOverlay,
75549
+ isInterrupt: layout.isInterrupt()
75403
75550
  });
75404
75551
  if ($layout !== null) {
75405
75552
  $layout.style.setProperty('visibility', 'hidden');
@@ -75424,9 +75571,9 @@ var Layout = /*#__PURE__*/function () {
75424
75571
  }
75425
75572
  // Emit layout end event
75426
75573
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75427
- _context2.next = 12;
75574
+ _context2.next = 13;
75428
75575
  return layout.xlr.emitSync('layoutEnd', layout);
75429
- case 12:
75576
+ case 13:
75430
75577
  if (_this.xlr.config.platform !== ConsumerPlatform.CMS && layout.inLoop) {
75431
75578
  // Transition next layout to current layout and prepare next layout if exist
75432
75579
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75456,7 +75603,7 @@ var Layout = /*#__PURE__*/function () {
75456
75603
  };
75457
75604
  }());
75458
75605
  }
75459
- case 13:
75606
+ case 14:
75460
75607
  case "end":
75461
75608
  return _context2.stop();
75462
75609
  }
@@ -75469,6 +75616,34 @@ var Layout = /*#__PURE__*/function () {
75469
75616
  this.on('cancelled', function (layout) {
75470
75617
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75471
75618
  layout.state = ELayoutState.CANCELLED;
75619
+ layout.inLoop = false;
75620
+ // Dispose video handlers immediately so their stall watchdogs and error
75621
+ // callbacks can't fire against a layout whose DOM is about to be removed.
75622
+ var _iterator = _createForOfIteratorHelper(layout.regions),
75623
+ _step;
75624
+ try {
75625
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
75626
+ var region = _step.value;
75627
+ var _iterator2 = _createForOfIteratorHelper(region.mediaObjects),
75628
+ _step2;
75629
+ try {
75630
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75631
+ var media = _step2.value;
75632
+ if (media.videoHandler) {
75633
+ media.videoHandler.stop(true);
75634
+ }
75635
+ }
75636
+ } catch (err) {
75637
+ _iterator2.e(err);
75638
+ } finally {
75639
+ _iterator2.f();
75640
+ }
75641
+ }
75642
+ } catch (err) {
75643
+ _iterator.e(err);
75644
+ } finally {
75645
+ _iterator.f();
75646
+ }
75472
75647
  });
75473
75648
  }
75474
75649
  return _createClass(Layout, [{
@@ -75610,6 +75785,14 @@ var Layout = /*#__PURE__*/function () {
75610
75785
  if ($splashScreen) {
75611
75786
  $splashScreen.style.display = 'none';
75612
75787
  }
75788
+ // 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.
75789
+ console.debug('??? XLR.debug >> Layout::run() - Checking if layout container is still in the DOM before playing regions...', {
75790
+ layoutId: this.id,
75791
+ layoutContainerExists: !!$layoutContainer,
75792
+ $layoutContainer: $layoutContainer,
75793
+ layoutIndex: this.index,
75794
+ shouldParse: false
75795
+ });
75613
75796
  if ($layoutContainer) {
75614
75797
  $layoutContainer.style.setProperty('visibility', 'visible');
75615
75798
  $layoutContainer.style.setProperty('opacity', '1');
@@ -75640,19 +75823,19 @@ var Layout = /*#__PURE__*/function () {
75640
75823
  key: "regionExpired",
75641
75824
  value: function regionExpired() {
75642
75825
  this.allExpired = true;
75643
- var _iterator = _createForOfIteratorHelper(this.regions),
75644
- _step;
75826
+ var _iterator3 = _createForOfIteratorHelper(this.regions),
75827
+ _step3;
75645
75828
  try {
75646
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
75647
- var layoutRegion = _step.value;
75829
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
75830
+ var layoutRegion = _step3.value;
75648
75831
  if (!layoutRegion.complete) {
75649
75832
  this.allExpired = false;
75650
75833
  }
75651
75834
  }
75652
75835
  } catch (err) {
75653
- _iterator.e(err);
75836
+ _iterator3.e(err);
75654
75837
  } finally {
75655
- _iterator.f();
75838
+ _iterator3.f();
75656
75839
  }
75657
75840
  if (this.allExpired) {
75658
75841
  this.end();
@@ -75663,17 +75846,17 @@ var Layout = /*#__PURE__*/function () {
75663
75846
  value: function end() {
75664
75847
  console.debug('Executing Layout::end and Calling Region::end ', this);
75665
75848
  /* Ask the layout to gracefully stop running now */
75666
- var _iterator2 = _createForOfIteratorHelper(this.regions),
75667
- _step2;
75849
+ var _iterator4 = _createForOfIteratorHelper(this.regions),
75850
+ _step4;
75668
75851
  try {
75669
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75670
- var layoutRegion = _step2.value;
75852
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
75853
+ var layoutRegion = _step4.value;
75671
75854
  layoutRegion.end();
75672
75855
  }
75673
75856
  } catch (err) {
75674
- _iterator2.e(err);
75857
+ _iterator4.e(err);
75675
75858
  } finally {
75676
- _iterator2.f();
75859
+ _iterator4.f();
75677
75860
  }
75678
75861
  }
75679
75862
  }, {
@@ -75686,7 +75869,11 @@ var Layout = /*#__PURE__*/function () {
75686
75869
  }
75687
75870
  }
75688
75871
  if (this.allEnded) {
75689
- console.debug('starting to end layout . . .');
75872
+ console.debug('starting to end layout . . .', {
75873
+ layoutId: this.layoutId,
75874
+ layoutIndex: this.index,
75875
+ layoutState: this.state
75876
+ });
75690
75877
  if (this.xlr.config.platform === ConsumerPlatform.CMS) {
75691
75878
  var $end = document.getElementById('play_ended');
75692
75879
  var $preview = document.getElementById('screen_container');
@@ -75785,6 +75972,42 @@ var Layout = /*#__PURE__*/function () {
75785
75972
  (_$layout$parentElemen2 = $layout.parentElement) === null || _$layout$parentElemen2 === void 0 || _$layout$parentElemen2.removeChild($layout);
75786
75973
  }
75787
75974
  }
75975
+ }, {
75976
+ key: "discardLayout",
75977
+ value: function discardLayout() {
75978
+ var caller = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : LayoutPlaybackType.NEXT;
75979
+ // Dispose any video.js players that were initialized during prepareVideoMedia
75980
+ // but never played. The isDisposed() guard makes this safe to call on
75981
+ // layouts that were fully played as well.
75982
+ var _iterator5 = _createForOfIteratorHelper(this.regions),
75983
+ _step5;
75984
+ try {
75985
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
75986
+ var region = _step5.value;
75987
+ var _iterator6 = _createForOfIteratorHelper(region.mediaObjects),
75988
+ _step6;
75989
+ try {
75990
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
75991
+ var media = _step6.value;
75992
+ if (media.player && !media.player.isDisposed()) {
75993
+ media.player.dispose();
75994
+ media.player = undefined;
75995
+ media.html = null;
75996
+ }
75997
+ }
75998
+ } catch (err) {
75999
+ _iterator6.e(err);
76000
+ } finally {
76001
+ _iterator6.f();
76002
+ }
76003
+ }
76004
+ } catch (err) {
76005
+ _iterator5.e(err);
76006
+ } finally {
76007
+ _iterator5.f();
76008
+ }
76009
+ this.removeLayout(caller);
76010
+ }
75788
76011
  }, {
75789
76012
  key: "getXlf",
75790
76013
  value: function getXlf() {
@@ -76027,6 +76250,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76027
76250
  }());
76028
76251
  xlrObject.on('updateLoop', /*#__PURE__*/function () {
76029
76252
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(inputLayouts) {
76253
+ var _xlrObject$currentLay;
76030
76254
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
76031
76255
  while (1) switch (_context3.prev = _context3.next) {
76032
76256
  case 0:
@@ -76035,7 +76259,17 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76035
76259
  return xlrObject.updateLoop(inputLayouts);
76036
76260
  case 3:
76037
76261
  xlrObject.isUpdatingLoop = false;
76038
- case 4:
76262
+ // If the running layout finished while isUpdatingLoop was true, the
76263
+ // layout end-handler bailed out of prepareLayouts() early and the
76264
+ // subsequent playLayouts() call saw currentLayout.done === true and
76265
+ // skipped run() — leaving a black screen. Catch up now that the flag
76266
+ // is clear.
76267
+ if ((_xlrObject$currentLay = xlrObject.currentLayout) !== null && _xlrObject$currentLay !== void 0 && _xlrObject$currentLay.done) {
76268
+ xlrObject.prepareLayouts().then(function (xlr) {
76269
+ return xlrObject.playLayouts(xlr);
76270
+ });
76271
+ }
76272
+ case 5:
76039
76273
  case "end":
76040
76274
  return _context3.stop();
76041
76275
  }
@@ -76065,40 +76299,35 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76065
76299
  return _ref4.apply(this, arguments);
76066
76300
  };
76067
76301
  }());
76068
- /**
76069
- * Asynchronous event emitter. Extended nanoevents event emitter.
76070
- *
76071
- * NOTE: Known limitation — nanoevents emit() is synchronous.
76072
- * Any async event handlers registered via .on() are fire-and-forget;
76073
- * emitSync does NOT await them. The returned Promise resolves
76074
- * immediately after handlers are invoked, not after they complete.
76075
- *
76076
- * @param eventName
76077
- * @param args
76078
- */
76079
- xlrObject.emitSync = function (eventName) {
76080
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76081
- args[_key - 1] = arguments[_key];
76082
- }
76083
- return new Promise( /*#__PURE__*/function () {
76084
- var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(resolve) {
76085
- var _xlrObject$emitter;
76086
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76087
- while (1) switch (_context5.prev = _context5.next) {
76088
- case 0:
76089
- (_xlrObject$emitter = xlrObject.emitter).emit.apply(_xlrObject$emitter, [eventName].concat(args));
76090
- resolve();
76091
- case 2:
76092
- case "end":
76093
- return _context5.stop();
76094
- }
76095
- }, _callee5);
76096
- }));
76097
- return function (_x4) {
76098
- return _ref5.apply(this, arguments);
76099
- };
76100
- }());
76101
- };
76302
+ xlrObject.emitSync = /*#__PURE__*/function () {
76303
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(eventName) {
76304
+ var _xlrObject$emitter$ev;
76305
+ var _len,
76306
+ args,
76307
+ _key,
76308
+ handlers,
76309
+ _args5 = arguments;
76310
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76311
+ while (1) switch (_context5.prev = _context5.next) {
76312
+ case 0:
76313
+ for (_len = _args5.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76314
+ args[_key - 1] = _args5[_key];
76315
+ }
76316
+ handlers = (_xlrObject$emitter$ev = xlrObject.emitter.events[eventName]) !== null && _xlrObject$emitter$ev !== void 0 ? _xlrObject$emitter$ev : [];
76317
+ _context5.next = 4;
76318
+ return Promise.all(handlers.map(function (handler) {
76319
+ return handler.apply(void 0, args);
76320
+ }));
76321
+ case 4:
76322
+ case "end":
76323
+ return _context5.stop();
76324
+ }
76325
+ }, _callee5);
76326
+ }));
76327
+ return function (_x4) {
76328
+ return _ref5.apply(this, arguments);
76329
+ };
76330
+ }();
76102
76331
  xlrObject.bootstrap = function () {
76103
76332
  // Place to set configurations and initialize required props
76104
76333
  var self = this;
@@ -76133,13 +76362,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76133
76362
  if ($splashScreen && $splashScreen.style.display === 'block') {
76134
76363
  $splashScreen === null || $splashScreen === void 0 || $splashScreen.hide();
76135
76364
  }
76365
+ console.debug('>>>> XLR.debug XLR::playLayouts > currentLayout', {
76366
+ layoutId: xlr.currentLayout.layoutId,
76367
+ layoutIndex: xlr.currentLayout.index,
76368
+ layoutState: xlr.currentLayout.state
76369
+ });
76136
76370
  if (!xlr.currentLayout.done) {
76137
- console.log('>>>> XLR.debug XLR::playSchedules > Running currentLayout', xlr.currentLayout);
76138
- xlr.currentLayout.run();
76139
76371
  // Hide overlays when current layout is interrupt
76140
76372
  if (xlr.currentLayout.isInterrupt()) {
76141
76373
  xlrObject.overlayLayoutManager.stopOverlays();
76142
76374
  }
76375
+ console.debug('>>>> XLR.debug XLR::playLayouts > Running currentLayout', {
76376
+ layoutId: xlr.currentLayout.layoutId,
76377
+ layoutIndex: xlr.currentLayout.index,
76378
+ layoutState: xlr.currentLayout.state
76379
+ });
76380
+ xlr.currentLayout.run();
76143
76381
  }
76144
76382
  } else {
76145
76383
  // Show splash screen
@@ -76216,7 +76454,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76216
76454
  _context7.next = 10;
76217
76455
  return _overlay.finishAllRegions();
76218
76456
  case 10:
76219
- _overlay.removeLayout();
76457
+ _overlay.removeLayout(LayoutPlaybackType.OVERLAY);
76220
76458
  case 11:
76221
76459
  _context7.next = 14;
76222
76460
  break;
@@ -76282,6 +76520,31 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76282
76520
  var $layout = document.querySelector("#".concat(containerName, "[data-sequence=\"").concat(layoutIndex, "\"]"));
76283
76521
  return $layout !== null;
76284
76522
  };
76523
+ // Scans screen_container for non-overlay layout divs and removes any that
76524
+ // are not the current or next active layout. Prevents DOM accumulation when
76525
+ // prepareLayouts() races with updateLoop and multiple same-layoutId elements
76526
+ // end up in screen_container (e.g. transitioning from a 1-layout loop where
76527
+ // two elements exist for the same layout to a multi-layout schedule).
76528
+ // keepCurrent / keepNext:
76529
+ // undefined → fall back to this.currentLayout / this.nextLayout
76530
+ // null → keep nothing for that slot (explicit "no layout to preserve")
76531
+ // ILayout → keep exactly that instance
76532
+ xlrObject.cleanupOrphanedLayouts = function (keepCurrent, keepNext) {
76533
+ var $screen = document.getElementById('screen_container');
76534
+ if (!$screen) return;
76535
+ var current = keepCurrent !== undefined ? keepCurrent : this.currentLayout;
76536
+ var next = keepNext !== undefined ? keepNext : this.nextLayout;
76537
+ Array.from($screen.querySelectorAll(':scope > div:not(.is-overlay)')).forEach(function (el) {
76538
+ var div = el;
76539
+ var isCurrentLayout = current && div.id === current.containerName && div.dataset.sequence === String(current.index);
76540
+ var isNextLayout = next && div.id === next.containerName && div.dataset.sequence === String(next.index);
76541
+ if (!isCurrentLayout && !isNextLayout) {
76542
+ var _div$parentElement;
76543
+ console.debug('XLR::cleanupOrphanedLayouts - removing orphaned layout element', div.id);
76544
+ (_div$parentElement = div.parentElement) === null || _div$parentElement === void 0 || _div$parentElement.removeChild(div);
76545
+ }
76546
+ });
76547
+ };
76285
76548
  xlrObject.updateLoop = /*#__PURE__*/function () {
76286
76549
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11(inputLayouts) {
76287
76550
  var _this$currentLayout,
@@ -76328,11 +76591,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76328
76591
  };
76329
76592
  }();
76330
76593
  if (isCurrentLayoutValid) {
76331
- _context11.next = 54;
76594
+ _context11.next = 55;
76332
76595
  break;
76333
76596
  }
76334
76597
  if (!playback.hasDefaultOnly) {
76335
- _context11.next = 33;
76598
+ _context11.next = 34;
76336
76599
  break;
76337
76600
  }
76338
76601
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
@@ -76345,82 +76608,102 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76345
76608
  case 19:
76346
76609
  this.currentLayout.removeLayout();
76347
76610
  case 20:
76348
- _context11.next = 22;
76611
+ // Discard old nextLayout before replacing it — same as the
76612
+ // other two branches do, otherwise the prepared DOM element
76613
+ // and any video.js players are orphaned.
76614
+ if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76615
+ this.nextLayout.discardLayout(LayoutPlaybackType.NEXT);
76616
+ }
76617
+ _context11.next = 23;
76349
76618
  return this.prepareLayoutXlf(playback.currentLayout);
76350
- case 22:
76619
+ case 23:
76351
76620
  this.currentLayout = _context11.sent;
76352
76621
  this.currentLayoutId = this.currentLayout.layoutId;
76353
76622
  _context11.t0 = this;
76354
- _context11.next = 27;
76623
+ _context11.next = 28;
76355
76624
  return this.prepareLayoutXlf(playback.nextLayout);
76356
- case 27:
76625
+ case 28:
76357
76626
  _context11.t1 = _context11.sent;
76358
- _context11.next = 30;
76627
+ _context11.next = 31;
76359
76628
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76360
- case 30:
76629
+ case 31:
76361
76630
  this.nextLayout = _context11.sent;
76362
- _context11.next = 50;
76631
+ _context11.next = 51;
76363
76632
  break;
76364
- case 33:
76633
+ case 34:
76365
76634
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76366
- _context11.next = 38;
76635
+ _context11.next = 39;
76367
76636
  break;
76368
76637
  }
76369
76638
  this.currentLayout.inLoop = false;
76370
- _context11.next = 37;
76639
+ _context11.next = 38;
76371
76640
  return this.currentLayout.finishAllRegions();
76372
- case 37:
76373
- this.currentLayout.removeLayout();
76374
76641
  case 38:
76642
+ this.currentLayout.removeLayout();
76643
+ case 39:
76375
76644
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76376
- this.nextLayout.removeLayout();
76645
+ this.nextLayout.discardLayout(LayoutPlaybackType.NEXT);
76377
76646
  }
76378
76647
  if (!playback.currentLayout) {
76379
- _context11.next = 42;
76648
+ _context11.next = 43;
76380
76649
  break;
76381
76650
  }
76382
- _context11.next = 42;
76651
+ _context11.next = 43;
76383
76652
  return prepareNewCurrentLayout();
76384
- case 42:
76653
+ case 43:
76385
76654
  if (!playback.nextLayout) {
76386
- _context11.next = 50;
76655
+ _context11.next = 51;
76387
76656
  break;
76388
76657
  }
76389
76658
  _context11.t2 = this;
76390
- _context11.next = 46;
76659
+ _context11.next = 47;
76391
76660
  return this.prepareLayoutXlf(playback.nextLayout);
76392
- case 46:
76661
+ case 47:
76393
76662
  _context11.t3 = _context11.sent;
76394
- _context11.next = 49;
76663
+ _context11.next = 50;
76395
76664
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76396
- case 49:
76397
- this.nextLayout = _context11.sent;
76398
76665
  case 50:
76399
- _context11.next = 52;
76666
+ this.nextLayout = _context11.sent;
76667
+ case 51:
76668
+ _context11.next = 53;
76400
76669
  return this.playSchedules(this);
76401
- case 52:
76670
+ case 53:
76402
76671
  _context11.next = 67;
76403
76672
  break;
76404
- case 54:
76673
+ case 55:
76405
76674
  // Remove next layout if it is in the DOM
76406
76675
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76407
- this.nextLayout.removeLayout();
76676
+ this.nextLayout.discardLayout(LayoutPlaybackType.NEXT);
76408
76677
  }
76409
- // Prepare new current layout
76678
+ // Purge any other orphaned layouts from screen_container that belong
76679
+ // to the old single-layout loop. When there was only one layout in the
76680
+ // loop, prepareLayouts() kept two DOM elements alive (current + next,
76681
+ // both the same layoutId but different containerNames). On a schedule
76682
+ // change the this.nextLayout check above only discards the element
76683
+ // currently referenced by this.nextLayout, but concurrent
76684
+ // prepareLayouts() calls can leave earlier same-layoutId elements
76685
+ // behind.
76686
+ // Pass null (not undefined) for keepNext: undefined would fall back to
76687
+ // this.nextLayout which may still reference the just-discarded layout
76688
+ // or — if isLayoutInDOM returned false and discardLayout was skipped —
76689
+ // the orphan itself, causing cleanupOrphanedLayouts to preserve it.
76690
+ // null means "no next to keep"; we are about to prepare a fresh one.
76691
+ this.cleanupOrphanedLayouts(this.currentLayout, null);
76692
+ // The current layout is still valid and running — do NOT replace the
76693
+ // live currentLayout object. Only refresh the queued nextLayout so
76694
+ // that when the running layout finishes it transitions to the correct
76695
+ // position in the updated loop. Using playback.currentLayout (the
76696
+ // slot that follows the running layout in the new queue) as the new
76697
+ // nextLayout keeps the cycle in order; the slot after that will be
76698
+ // prepared by the normal prepareLayouts() call at transition time.
76410
76699
  if (!playback.currentLayout) {
76411
- _context11.next = 58;
76412
- break;
76413
- }
76414
- _context11.next = 58;
76415
- return prepareNewCurrentLayout();
76416
- case 58:
76417
- if (!playback.nextLayout) {
76418
76700
  _context11.next = 66;
76419
76701
  break;
76420
76702
  }
76703
+ this.currentLayoutIndex = playback.currentLayoutIndex;
76421
76704
  _context11.t4 = this;
76422
76705
  _context11.next = 62;
76423
- return this.prepareLayoutXlf(playback.nextLayout);
76706
+ return this.prepareLayoutXlf(playback.currentLayout);
76424
76707
  case 62:
76425
76708
  _context11.t5 = _context11.sent;
76426
76709
  _context11.next = 65;
@@ -76487,8 +76770,10 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76487
76770
  var tempNextLayoutIndex = getLayoutIndexByLayoutId(this.inputLayouts, this.nextLayout.layoutId);
76488
76771
  _currentLayoutIndex = tempNextLayoutIndex !== null && tempNextLayoutIndex !== void 0 ? tempNextLayoutIndex : 0;
76489
76772
  _currentLayout = this.getLayout(this.inputLayouts[tempNextLayoutIndex !== null && tempNextLayoutIndex !== void 0 ? tempNextLayoutIndex : 0]);
76773
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76490
76774
  } else {
76491
76775
  _currentLayout = this.getLayout(this.inputLayouts[0]);
76776
+ _currentLayout = setLayoutIndex(_currentLayout, 0);
76492
76777
  }
76493
76778
  if (this.inputLayouts.length > 1) {
76494
76779
  if (_currentLayoutIndex + 1 > this.inputLayouts.length - 1) {
@@ -76497,6 +76782,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76497
76782
  _nextLayoutIndex = _currentLayoutIndex + 1;
76498
76783
  }
76499
76784
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76785
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76500
76786
  } else {
76501
76787
  _nextLayout = _currentLayout;
76502
76788
  }
@@ -76509,20 +76795,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76509
76795
  if (_currentLayout) {
76510
76796
  _currentLayoutIndex = _currentLayout.index;
76511
76797
  }
76798
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76512
76799
  _nextLayoutIndex = 0;
76513
76800
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76801
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76514
76802
  }
76515
76803
  } else {
76516
76804
  _currentLayoutIndex = this.nextLayout.index > this.inputLayouts.length - 1 ? 0 : this.nextLayout.index;
76517
76805
  _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76806
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76518
76807
  _nextLayoutIndex = _currentLayoutIndex + 1 > this.inputLayouts.length - 1 ? 0 : _currentLayoutIndex + 1;
76519
76808
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76809
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76520
76810
  }
76521
76811
  } else {
76812
+ var _this$currentLayout4, _this$currentLayout5;
76522
76813
  _currentLayout = this.nextLayout;
76523
76814
  _currentLayoutIndex = _currentLayout.index;
76815
+ // updateLoop can re-queue the same index that is currently
76816
+ // playing (e.g. it fires while nextLayout.index === currentLayout.index).
76817
+ // When that layout then ends, the catch-up prepareLayouts() would
76818
+ // replay the same slot instead of advancing. Detect this by checking
76819
+ // whether the queued next-to-current is at the same index as the
76820
+ // layout that just finished, and advance past it so the following
76821
+ // slot (e.g. an SSP that now has an ad) becomes current instead.
76822
+ 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)) {
76823
+ _currentLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76824
+ _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76825
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76826
+ }
76524
76827
  _nextLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76525
76828
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76829
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76526
76830
  }
76527
76831
  }
76528
76832
  }
@@ -76530,17 +76834,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76530
76834
  // Initial run: set both currentLayout and nextLayout
76531
76835
  if (hasLayout) {
76532
76836
  _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76837
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76533
76838
  if (this.inputLayouts.length > 1) {
76534
76839
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76840
+ _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76535
76841
  } else {
76536
76842
  _nextLayout = this.getLayout(this.inputLayouts[0]);
76843
+ _nextLayout = setLayoutIndex(_nextLayout, 0);
76537
76844
  }
76538
76845
  }
76539
76846
  }
76540
76847
  if (_currentLayout === undefined && _nextLayout === undefined) {
76541
76848
  if (_hasDefaultOnly) {
76542
76849
  _currentLayout = this.getLayout(this.inputLayouts[0]);
76850
+ _currentLayout = setLayoutIndex(_currentLayout, 0);
76543
76851
  _nextLayout = this.getLayout(this.inputLayouts[0]);
76852
+ _nextLayout = setLayoutIndex(_nextLayout, 0);
76544
76853
  }
76545
76854
  }
76546
76855
  if (_currentLayout !== undefined && _nextLayout !== undefined) {
@@ -76570,10 +76879,19 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76570
76879
  var activeLayout = inputLayout;
76571
76880
  if (isCMS) {
76572
76881
  activeLayout.index = 0;
76882
+ // id stays null without this — setLayoutIndex returns undefined for CMS layouts
76883
+ if (activeLayout.id == null) {
76884
+ activeLayout.id = activeLayout.layoutId;
76885
+ }
76573
76886
  } else {
76574
76887
  activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
76575
76888
  }
76576
76889
  _layout = _objectSpread2(_objectSpread2({}, _layout), activeLayout);
76890
+ console.debug('XLR::getLayout > activeLayout from uniqueLayouts', {
76891
+ activeLayout: activeLayout,
76892
+ inputLayout: inputLayout,
76893
+ uniqueLayouts: this.uniqueLayouts
76894
+ });
76577
76895
  // Must set index/sequence from schedule loop
76578
76896
  _layout.index = activeLayout.index;
76579
76897
  }
@@ -76595,7 +76913,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76595
76913
  };
76596
76914
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76597
76915
  var _layoutPlayback$curre, _layoutPlayback$curre2;
76598
- var self, layoutPlayback, currentLayoutXlf, nextLayoutXlf, layouts;
76916
+ var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76599
76917
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76600
76918
  while (1) switch (_context14.prev = _context14.next) {
76601
76919
  case 0:
@@ -76618,7 +76936,12 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76618
76936
  shouldParse: false
76619
76937
  });
76620
76938
  self.currentLayoutId = (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId;
76621
- if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode)) {
76939
+ // Only reuse the existing Layout instance if it is fully healthy
76940
+ // a done=true instance was removed from the DOM (e.g. an SSP slot that
76941
+ // had no ad), and an empty-XLF instance has no regions so it can never
76942
+ // advance the cycle. In either case re-prepare from scratch so we get
76943
+ // a fresh request (which may now have a valid ad / XLF).
76944
+ if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
76622
76945
  _context14.next = 12;
76623
76946
  break;
76624
76947
  }
@@ -76632,27 +76955,40 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76632
76955
  _context14.t0 = _context14.sent;
76633
76956
  case 15:
76634
76957
  currentLayoutXlf = _context14.t0;
76635
- _context14.next = 18;
76958
+ // True when the same object was returned (reused); false when a fresh
76959
+ // Layout was constructed by prepareLayoutXlf above.
76960
+ wasCurrentReused = currentLayoutXlf === layoutPlayback.currentLayout;
76961
+ _context14.next = 19;
76636
76962
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
76637
- case 18:
76963
+ case 19:
76638
76964
  nextLayoutXlf = _context14.sent;
76639
76965
  _context14.t1 = Promise;
76640
76966
  _context14.t2 = currentLayoutXlf;
76641
- _context14.next = 23;
76967
+ _context14.next = 24;
76642
76968
  return self.prepareForSsp(nextLayoutXlf);
76643
- case 23:
76969
+ case 24:
76644
76970
  _context14.t3 = _context14.sent;
76645
76971
  _context14.t4 = [_context14.t2, _context14.t3];
76646
- _context14.next = 27;
76972
+ _context14.next = 28;
76647
76973
  return _context14.t1.all.call(_context14.t1, _context14.t4);
76648
- case 27:
76974
+ case 28:
76649
76975
  layouts = _context14.sent;
76650
- // Return early when layout loop is updating
76651
- if (self.isUpdatingLoop) {
76652
- if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76653
- nextLayoutXlf.removeLayout();
76654
- }
76976
+ if (!(self.isUpdatingLoop || layouts[0].done)) {
76977
+ _context14.next = 33;
76978
+ break;
76979
+ }
76980
+ // If currentLayout was freshly prepared (not reused from nextLayout),
76981
+ // its DOM element was just appended — discard it now so it does not
76982
+ // accumulate in screen_container. Also disposes any video.js players
76983
+ // that were initialized during prepareVideoMedia but never played.
76984
+ if (!wasCurrentReused && this.isLayoutInDOM(currentLayoutXlf.containerName, currentLayoutXlf.index)) {
76985
+ currentLayoutXlf.discardLayout(LayoutPlaybackType.NEXT);
76986
+ }
76987
+ if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76988
+ nextLayoutXlf.discardLayout(LayoutPlaybackType.NEXT);
76655
76989
  }
76990
+ return _context14.abrupt("return", Promise.resolve(self));
76991
+ case 33:
76656
76992
  console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
76657
76993
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
76658
76994
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
@@ -76669,8 +77005,15 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76669
77005
  self.currentLayout = self.layouts.current;
76670
77006
  self.currentLayoutId = self.currentLayout.layoutId;
76671
77007
  self.nextLayout = self.layouts.next;
77008
+ // Evict any orphaned layout DOM elements that aren't the current
77009
+ // or next layout. Concurrent prepareLayouts() calls can each append
77010
+ // a freshly-prepared nextLayout to screen_container and then
77011
+ // overwrite this.nextLayout, leaving earlier elements behind.
77012
+ // Calling this here — with explicit references — ensures every
77013
+ // completed prepare cycle leaves the DOM in a clean state.
77014
+ self.cleanupOrphanedLayouts(self.currentLayout, self.nextLayout);
76672
77015
  resolve(xlrObject);
76673
- case 8:
77016
+ case 9:
76674
77017
  case "end":
76675
77018
  return _context13.stop();
76676
77019
  }
@@ -76680,7 +77023,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76680
77023
  return _ref14.apply(this, arguments);
76681
77024
  };
76682
77025
  }()));
76683
- case 31:
77026
+ case 35:
76684
77027
  case "end":
76685
77028
  return _context14.stop();
76686
77029
  }
@@ -76708,34 +77051,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76708
77051
  newOptions.xlfUrl = inputLayout.path;
76709
77052
  }
76710
77053
  if (!(inputLayout && inputLayout.layoutNode === undefined)) {
76711
- _context15.next = 21;
77054
+ _context15.next = 22;
76712
77055
  break;
76713
77056
  }
76714
77057
  if (!(inputLayout.layoutId === -1)) {
76715
- _context15.next = 14;
77058
+ _context15.next = 15;
76716
77059
  break;
76717
77060
  }
76718
77061
  _context15.next = 10;
76719
77062
  return self.emitSync('adRequest', inputLayout.index);
76720
77063
  case 10:
76721
77064
  sspInputLayout = self.inputLayouts[inputLayout.index];
77065
+ console.debug('XLR::prepareLayoutXlf > SSP input layout', {
77066
+ sspInputLayout: sspInputLayout,
77067
+ inputLayout: inputLayout
77068
+ });
76722
77069
  // @ts-ignore
76723
- layoutXlf = ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf()) || '';
76724
- _context15.next = 17;
77070
+ layoutXlf = typeof ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf) === 'function' ? sspInputLayout.getXlf() : '';
77071
+ _context15.next = 18;
76725
77072
  break;
76726
- case 14:
76727
- _context15.next = 16;
77073
+ case 15:
77074
+ _context15.next = 17;
76728
77075
  return getXlf(newOptions);
76729
- case 16:
76730
- layoutXlf = _context15.sent;
76731
77076
  case 17:
77077
+ layoutXlf = _context15.sent;
77078
+ case 18:
76732
77079
  parser = new window.DOMParser();
76733
77080
  layoutXlfNode = parser.parseFromString(layoutXlf, 'text/xml');
76734
- _context15.next = 22;
77081
+ _context15.next = 23;
76735
77082
  break;
76736
- case 21:
76737
- layoutXlfNode = inputLayout && inputLayout.layoutNode;
76738
77083
  case 22:
77084
+ layoutXlfNode = inputLayout && inputLayout.layoutNode;
77085
+ case 23:
76739
77086
  isOverlayLayout = !!(inputLayout !== null && inputLayout !== void 0 && inputLayout.isOverlay);
76740
77087
  return _context15.abrupt("return", new Promise(function (resolve) {
76741
77088
  var _inputLayout$ad;
@@ -76760,13 +77107,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76760
77107
  xlrLayoutObj.duration = sspInputLayout.duration || 0;
76761
77108
  xlrLayoutObj.ad = sspInputLayout.ad;
76762
77109
  }
77110
+ var xlrLayout;
76763
77111
  if (isOverlayLayout) {
76764
- resolve(new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77112
+ xlrLayout = new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76765
77113
  } else {
76766
- resolve(new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77114
+ xlrLayout = new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode);
77115
+ }
77116
+ // Advance the shared counter so the next prepareLayoutXlf() call
77117
+ // starts from where this layout left off — prevents every layout
77118
+ // instance from reusing idCounter=1 and colliding on the same
77119
+ // containerName / DOM element.
77120
+ if (props.options) {
77121
+ props.options.idCounter = newOptions.idCounter;
76767
77122
  }
77123
+ resolve(xlrLayout);
76768
77124
  }));
76769
- case 24:
77125
+ case 25:
76770
77126
  case "end":
76771
77127
  return _context15.stop();
76772
77128
  }
@@ -76778,45 +77134,49 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76778
77134
  }();
76779
77135
  xlrObject.prepareForSsp = /*#__PURE__*/function () {
76780
77136
  var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee16(nextLayout) {
76781
- var self, _nextLayout, nextIndex, inputLayout, nextLayoutObj;
77137
+ var self, _nextLayout, iterations, maxIterations, nextIndex, inputLayout, nextLayoutObj;
76782
77138
  return _regeneratorRuntime().wrap(function _callee16$(_context16) {
76783
77139
  while (1) switch (_context16.prev = _context16.next) {
76784
77140
  case 0:
76785
77141
  self = this;
76786
77142
  _nextLayout = nextLayout;
76787
- case 2:
76788
- if (!(_nextLayout && _nextLayout.xlfString === '')) {
76789
- _context16.next = 16;
76790
- break;
76791
- }
76792
- // Remove skipped layout
76793
- _nextLayout.removeLayout();
76794
- // Get next valid layout
76795
- // We will skip next layout that has no valid xlf
76796
- nextIndex = _nextLayout.index + 1;
76797
- if (!(nextIndex >= self.inputLayouts.length)) {
76798
- _context16.next = 7;
77143
+ iterations = 0;
77144
+ maxIterations = self.inputLayouts.length;
77145
+ case 4:
77146
+ if (!(_nextLayout && _nextLayout.xlfString === '' && iterations < maxIterations)) {
77147
+ _context16.next = 20;
76799
77148
  break;
76800
77149
  }
76801
- return _context16.abrupt("break", 16);
76802
- case 7:
77150
+ // Remove the empty slot's DOM element before skipping past it
77151
+ _nextLayout.removeLayout(LayoutPlaybackType.NEXT);
77152
+ iterations++;
77153
+ // Advance to the next slot, wrapping around so a trailing SSP slot
77154
+ // with no ad does not strand the queue at the end of the array.
77155
+ nextIndex = (_nextLayout.index + 1) % self.inputLayouts.length;
76803
77156
  inputLayout = self.inputLayouts[nextIndex];
76804
77157
  if (inputLayout) {
76805
- _context16.next = 10;
77158
+ _context16.next = 11;
76806
77159
  break;
76807
77160
  }
76808
- return _context16.abrupt("break", 16);
76809
- case 10:
77161
+ return _context16.abrupt("break", 20);
77162
+ case 11:
76810
77163
  nextLayoutObj = self.getLayout(inputLayout);
76811
- _context16.next = 13;
77164
+ nextLayoutObj = setLayoutIndex(nextLayoutObj, nextIndex);
77165
+ if (nextLayoutObj) {
77166
+ _context16.next = 15;
77167
+ break;
77168
+ }
77169
+ return _context16.abrupt("break", 20);
77170
+ case 15:
77171
+ _context16.next = 17;
76812
77172
  return self.prepareLayoutXlf(nextLayoutObj);
76813
- case 13:
77173
+ case 17:
76814
77174
  _nextLayout = _context16.sent;
76815
- _context16.next = 2;
77175
+ _context16.next = 4;
76816
77176
  break;
76817
- case 16:
77177
+ case 20:
76818
77178
  return _context16.abrupt("return", _nextLayout);
76819
- case 17:
77179
+ case 21:
76820
77180
  case "end":
76821
77181
  return _context16.stop();
76822
77182
  }
@@ -76828,7 +77188,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76828
77188
  }();
76829
77189
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
76830
77190
  var _this4 = this;
76831
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout4;
77191
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
76832
77192
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
76833
77193
  while (1) switch (_context18.prev = _context18.next) {
76834
77194
  case 0:
@@ -76851,7 +77211,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76851
77211
  break;
76852
77212
  }
76853
77213
  _context18.next = 8;
76854
- return (_this$currentLayout4 = this.currentLayout) === null || _this$currentLayout4 === void 0 ? void 0 : _this$currentLayout4.finishAllRegions();
77214
+ return (_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.finishAllRegions();
76855
77215
  case 8:
76856
77216
  // and set the previous layout as current layout
76857
77217
  this.currentLayoutIndex = _assumedPrevIndex;
@@ -76879,7 +77239,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76879
77239
  }, _callee18, this);
76880
77240
  }));
76881
77241
  xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
76882
- var _xlrObject$currentLay;
77242
+ var _xlrObject$currentLay2;
76883
77243
  return _regeneratorRuntime().wrap(function _callee19$(_context19) {
76884
77244
  while (1) switch (_context19.prev = _context19.next) {
76885
77245
  case 0:
@@ -76889,7 +77249,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76889
77249
  shouldParse: false
76890
77250
  });
76891
77251
  _context19.next = 3;
76892
- return (_xlrObject$currentLay = xlrObject.currentLayout) === null || _xlrObject$currentLay === void 0 ? void 0 : _xlrObject$currentLay.finishAllRegions();
77252
+ return (_xlrObject$currentLay2 = xlrObject.currentLayout) === null || _xlrObject$currentLay2 === void 0 ? void 0 : _xlrObject$currentLay2.finishAllRegions();
76893
77253
  case 3:
76894
77254
  case "end":
76895
77255
  return _context19.stop();
@@ -76901,11 +77261,16 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76901
77261
  if (layout !== null) {
76902
77262
  layout.index = xlrInputLayout.index;
76903
77263
  }
77264
+ console.debug('XLR::updateInputLayout', {
77265
+ layoutIndex: layoutIndex,
77266
+ layout: layout,
77267
+ xlrInputLayout: xlrInputLayout
77268
+ });
76904
77269
  this.inputLayouts[layoutIndex] = layout || xlrInputLayout;
76905
77270
  };
76906
77271
  xlrObject.bootstrap();
76907
77272
  return xlrObject;
76908
77273
  }
76909
77274
 
76910
- export { Action, ActionsWrapper, AudioMedia, ConsumerPlatform, ELayoutState, ELayoutType, LayoutPlaybackType, Media, MediaState, Region, VideoMedia, audioFileType, capitalizeStr, composeBgUrlByPlatform, composeMediaUrl, composeResourceUrl, composeResourceUrlByPlatform, composeVideoSource, createMediaElement, XiboLayoutRenderer as default, defaultTrans, defaultVjsOpts, fadeInElem, fadeOutElem, fetchJSON, fetchText, flyInElem, flyOutElem, flyTransitionKeyframes, getAllAttributes, getDataBlob, getFileExt, getIndexByLayoutId, getLayout, getLayoutIndexByLayoutId, getMediaId, getXlf, hasDefaultOnly, hasSspLayout, initRenderingDOM, initialLayout, initialMedia, initialRegion, initialXlr, isEmpty, isLayoutValid, nextId, playerReportFault, preloadMediaBlob, prepareAudioMedia, prepareHtmlMedia, prepareImageMedia, prepareVideoMedia, setExpiry, transitionElement, videoFileType, vjsDefaultOptions };
77275
+ export { Action, ActionsWrapper, AudioMedia, ConsumerPlatform, ELayoutState, ELayoutType, FaultCodes, LayoutPlaybackType, Media, MediaState, Region, VideoMedia, audioFileType, capitalizeStr, composeBgUrlByPlatform, composeMediaUrl, composeResourceUrl, composeResourceUrlByPlatform, composeVideoSource, createMediaElement, XiboLayoutRenderer as default, defaultTrans, defaultVjsOpts, fadeInElem, fadeOutElem, fetchJSON, fetchText, flyInElem, flyOutElem, flyTransitionKeyframes, getAllAttributes, getDataBlob, getFileExt, getIndexByLayoutId, getLayout, getLayoutIndexByLayoutId, getMediaId, getXlf, hasDefaultOnly, hasSspLayout, initRenderingDOM, initialLayout, initialRegion, initialXlr, isEmpty, isLayoutValid, isMediaActive, nextId, playerReportFault, preloadMediaBlob, prepareAudioMedia, prepareHtmlMedia, prepareImageMedia, prepareVideoMedia, reportToPlayerPlatform, setExpiry, transitionElement, videoFileType, vjsDefaultOptions };
76911
77276
  //# sourceMappingURL=xibo-layout-renderer.esm.js.map