@xibosignage/xibo-layout-renderer 1.0.25 → 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);
@@ -1107,6 +1065,7 @@ var initialXlr = {
1107
1065
  isLayoutInDOM: function isLayoutInDOM(containerName, layoutId) {
1108
1066
  return false;
1109
1067
  },
1068
+ cleanupOrphanedLayouts: function cleanupOrphanedLayouts(_keepCurrent, _keepNext) {},
1110
1069
  isSspEnabled: false,
1111
1070
  isUpdatingLoop: false,
1112
1071
  isUpdatingOverlays: false,
@@ -72527,6 +72486,10 @@ if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
72527
72486
  function composeVideoSource($media, media) {
72528
72487
  // const videoSrc = await preloadMediaBlob(media.url as string, media.mediaType as MediaTypes);
72529
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
+ }
72530
72493
  // Only add one source per type
72531
72494
  if ($media.querySelectorAll("source[type=\"".concat(vidType, "\"]")).length === 0) {
72532
72495
  var $videoSource = document.createElement('source');
@@ -72553,8 +72516,38 @@ var vjsDefaultOptions = function vjsDefaultOptions(opts) {
72553
72516
  };
72554
72517
  var reportToPlayerPlatform = [ConsumerPlatform.CHROMEOS, ConsumerPlatform.ELECTRON];
72555
72518
  function VideoMedia(media, xlr) {
72519
+ var stopped = false;
72556
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
+ // ─────────────────────────────────────────────────────────────────────────
72557
72549
  var videoPlayer = {
72550
+ player: undefined,
72558
72551
  duration: 0,
72559
72552
  init: function init() {
72560
72553
  var _this = this;
@@ -72562,9 +72555,37 @@ function VideoMedia(media, xlr) {
72562
72555
  videoPlayer.duration = media.duration;
72563
72556
  var vjsPlayer = videojs(mediaId);
72564
72557
  if (vjsPlayer) {
72565
- vjsPlayer.on('loadstart', function () {
72566
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72567
- });
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);
72568
72589
  vjsPlayer.on('loadedmetadata', function () {
72569
72590
  if (media.duration === 0) {
72570
72591
  videoPlayer.duration = vjsPlayer.duration();
@@ -72635,34 +72656,20 @@ function VideoMedia(media, xlr) {
72635
72656
  }, 5000);
72636
72657
  })]).then(function () {
72637
72658
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay started"));
72638
- })["catch"]( /*#__PURE__*/function () {
72639
- var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72640
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72641
- while (1) switch (_context2.prev = _context2.next) {
72642
- case 0:
72643
- if (error === 'Timeout') {
72644
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72645
- _this.stop();
72646
- } else {
72647
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72648
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72649
- playerReportFault('Media autoplay error', media).then(function () {
72650
- _this.stop();
72651
- });
72652
- }
72653
- }
72654
- case 1:
72655
- case "end":
72656
- return _context2.stop();
72657
- }
72658
- }, _callee2);
72659
- }));
72660
- return function (_x) {
72661
- return _ref2.apply(this, arguments);
72662
- };
72663
- }());
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
+ });
72664
72670
  // Optional: Reset the flag automatically when a new video loads or the source changes
72665
72671
  vjsPlayer.on('loadstart', function () {
72672
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72666
72673
  triggerTimeUpdate = false;
72667
72674
  });
72668
72675
  if (media.duration === 0) {
@@ -72675,13 +72682,9 @@ function VideoMedia(media, xlr) {
72675
72682
  if (mediaDuration !== undefined && currentTime !== undefined) {
72676
72683
  remainingTimeMs = (mediaDuration - currentTime) * 1000;
72677
72684
  }
72678
- if (regionHasMultipleMedia && remainingTimeMs === 0 && !triggerTimeUpdate) {
72679
- // We don't have data yet and we must immediately prepare next media
72680
- media.region.prepareNextMedia();
72681
- } else if (regionHasMultipleMedia && remainingTimeMs <= preloadBufferTimeMs && !triggerTimeUpdate) {
72685
+ if (regionHasMultipleMedia && !triggerTimeUpdate && (remainingTimeMs === 0 || remainingTimeMs <= preloadBufferTimeMs)) {
72682
72686
  // Check if remaining time is less than preloadBufferTimeMs and the action hasn't been triggered yet
72683
72687
  console.log('Less than preloadBufferTimeMs remaining! Do something now.');
72684
- // Prepare next media in region
72685
72688
  media.region.prepareNextMedia();
72686
72689
  triggerTimeUpdate = true; // Set the flag to prevent re-triggering
72687
72690
  }
@@ -72693,33 +72696,16 @@ function VideoMedia(media, xlr) {
72693
72696
  }
72694
72697
  }
72695
72698
  });
72696
- vjsPlayer.on('error', /*#__PURE__*/function () {
72697
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(err) {
72698
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
72699
- while (1) switch (_context3.prev = _context3.next) {
72700
- case 0:
72701
- console.debug("??? XLR.debug >> VideoMedia: Media Error: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id));
72702
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72703
- playerReportFault('Video file source not supported', media).then(function () {
72704
- _this.stop();
72705
- });
72706
- } else {
72707
- // End media after 5 seconds
72708
- setTimeout(function () {
72709
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended . . ."));
72710
- _this.stop();
72711
- }, 5000);
72712
- }
72713
- case 2:
72714
- case "end":
72715
- return _context3.stop();
72716
- }
72717
- }, _callee3);
72718
- }));
72719
- return function (_x2) {
72720
- return _ref3.apply(this, arguments);
72721
- };
72722
- }());
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
+ });
72723
72709
  if (media.duration === 0) {
72724
72710
  vjsPlayer.on('ended', function () {
72725
72711
  console.debug("??? XLR.debug >> VideoMedia: onended: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
@@ -72729,26 +72715,37 @@ function VideoMedia(media, xlr) {
72729
72715
  }
72730
72716
  },
72731
72717
  stop: function stop() {
72718
+ var _videoPlayer$player;
72732
72719
  var disposeOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
72733
- 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;
72734
72725
  console.debug('??? XLR.debug >> VideoMedia::stop', {
72735
72726
  vjsPlayer: vjsPlayer,
72736
- isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed_,
72737
- 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()
72738
72729
  });
72739
72730
  // Expire the media and dispose the video
72740
- if (vjsPlayer !== undefined && !vjsPlayer.isDisposed_) {
72731
+ if (vjsPlayer !== undefined && !vjsPlayer.isDisposed()) {
72741
72732
  if (!disposeOnly) {
72742
72733
  media.emitter.emit('end', media);
72743
72734
  }
72744
72735
  vjsPlayer.dispose();
72745
72736
  // Clear up media player
72737
+ videoPlayer.player = undefined;
72746
72738
  media.player = undefined;
72739
+ media.html = null;
72747
72740
  } else {
72741
+ videoPlayer.player = undefined;
72748
72742
  media.player = undefined;
72749
72743
  media.html = null;
72750
- media.emitter.emit('end', media);
72744
+ if (!disposeOnly) {
72745
+ media.emitter.emit('end', media);
72746
+ }
72751
72747
  }
72748
+ stopped = true;
72752
72749
  },
72753
72750
  play: function play() {
72754
72751
  var _this2 = this;
@@ -72756,9 +72753,9 @@ function VideoMedia(media, xlr) {
72756
72753
  if (vjsPlayer !== undefined) {
72757
72754
  var _vjsPlayer$play;
72758
72755
  (_vjsPlayer$play = vjsPlayer.play()) === null || _vjsPlayer$play === void 0 || _vjsPlayer$play["catch"]( /*#__PURE__*/function () {
72759
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(error) {
72760
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
72761
- 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) {
72762
72759
  case 0:
72763
72760
  if (error === 'Timeout') {
72764
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"));
@@ -72773,12 +72770,12 @@ function VideoMedia(media, xlr) {
72773
72770
  }
72774
72771
  case 1:
72775
72772
  case "end":
72776
- return _context4.stop();
72773
+ return _context2.stop();
72777
72774
  }
72778
- }, _callee4);
72775
+ }, _callee2);
72779
72776
  }));
72780
- return function (_x3) {
72781
- return _ref4.apply(this, arguments);
72777
+ return function (_x) {
72778
+ return _ref2.apply(this, arguments);
72782
72779
  };
72783
72780
  }());
72784
72781
  }
@@ -73190,11 +73187,11 @@ function getDataBlob(_x, _x2) {
73190
73187
  return _getDataBlob.apply(this, arguments);
73191
73188
  }
73192
73189
  function _getDataBlob() {
73193
- _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, jwtToken) {
73194
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73195
- 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) {
73196
73193
  case 0:
73197
- return _context3.abrupt("return", fetch(src, {
73194
+ return _context2.abrupt("return", fetch(src, {
73198
73195
  method: 'GET',
73199
73196
  headers: {
73200
73197
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73213,9 +73210,9 @@ function _getDataBlob() {
73213
73210
  }));
73214
73211
  case 1:
73215
73212
  case "end":
73216
- return _context3.stop();
73213
+ return _context2.stop();
73217
73214
  }
73218
- }, _callee3);
73215
+ }, _callee2);
73219
73216
  }));
73220
73217
  return _getDataBlob.apply(this, arguments);
73221
73218
  }
@@ -73223,12 +73220,12 @@ function preloadMediaBlob(_x3, _x4, _x5) {
73223
73220
  return _preloadMediaBlob.apply(this, arguments);
73224
73221
  }
73225
73222
  function _preloadMediaBlob() {
73226
- _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(src, type, jwtToken) {
73223
+ _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, type, jwtToken) {
73227
73224
  var res, blob, data;
73228
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73229
- while (1) switch (_context4.prev = _context4.next) {
73225
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73226
+ while (1) switch (_context3.prev = _context3.next) {
73230
73227
  case 0:
73231
- _context4.next = 2;
73228
+ _context3.next = 2;
73232
73229
  return fetch(src, {
73233
73230
  method: 'GET',
73234
73231
  headers: {
@@ -73236,45 +73233,45 @@ function _preloadMediaBlob() {
73236
73233
  }
73237
73234
  });
73238
73235
  case 2:
73239
- res = _context4.sent;
73236
+ res = _context3.sent;
73240
73237
  blob = new Blob();
73241
73238
  if (!(type === 'image')) {
73242
- _context4.next = 8;
73239
+ _context3.next = 8;
73243
73240
  break;
73244
73241
  }
73245
73242
  blob = new Blob();
73246
- _context4.next = 19;
73243
+ _context3.next = 19;
73247
73244
  break;
73248
73245
  case 8:
73249
73246
  if (!(type === 'video')) {
73250
- _context4.next = 14;
73247
+ _context3.next = 14;
73251
73248
  break;
73252
73249
  }
73253
- _context4.next = 11;
73250
+ _context3.next = 11;
73254
73251
  return res.blob();
73255
73252
  case 11:
73256
- blob = _context4.sent;
73257
- _context4.next = 19;
73253
+ blob = _context3.sent;
73254
+ _context3.next = 19;
73258
73255
  break;
73259
73256
  case 14:
73260
73257
  if (!(type === 'audio')) {
73261
- _context4.next = 19;
73258
+ _context3.next = 19;
73262
73259
  break;
73263
73260
  }
73264
- _context4.next = 17;
73261
+ _context3.next = 17;
73265
73262
  return res.arrayBuffer();
73266
73263
  case 17:
73267
- data = _context4.sent;
73264
+ data = _context3.sent;
73268
73265
  blob = new Blob([data], {
73269
73266
  type: audioFileType(getFileExt(src))
73270
73267
  });
73271
73268
  case 19:
73272
- return _context4.abrupt("return", URL.createObjectURL(blob));
73269
+ return _context3.abrupt("return", URL.createObjectURL(blob));
73273
73270
  case 20:
73274
73271
  case "end":
73275
- return _context4.stop();
73272
+ return _context3.stop();
73276
73273
  }
73277
- }, _callee4);
73274
+ }, _callee3);
73278
73275
  }));
73279
73276
  return _preloadMediaBlob.apply(this, arguments);
73280
73277
  }
@@ -73282,11 +73279,11 @@ function fetchJSON(_x6, _x7) {
73282
73279
  return _fetchJSON.apply(this, arguments);
73283
73280
  }
73284
73281
  function _fetchJSON() {
73285
- _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73286
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73287
- 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) {
73288
73285
  case 0:
73289
- return _context5.abrupt("return", fetch(url, {
73286
+ return _context4.abrupt("return", fetch(url, {
73290
73287
  method: 'GET',
73291
73288
  headers: {
73292
73289
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73298,9 +73295,9 @@ function _fetchJSON() {
73298
73295
  }));
73299
73296
  case 1:
73300
73297
  case "end":
73301
- return _context5.stop();
73298
+ return _context4.stop();
73302
73299
  }
73303
- }, _callee5);
73300
+ }, _callee4);
73304
73301
  }));
73305
73302
  return _fetchJSON.apply(this, arguments);
73306
73303
  }
@@ -73308,11 +73305,11 @@ function fetchText(_x8, _x9) {
73308
73305
  return _fetchText.apply(this, arguments);
73309
73306
  }
73310
73307
  function _fetchText() {
73311
- _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(url, jwtToken) {
73312
- return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73313
- 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) {
73314
73311
  case 0:
73315
- return _context6.abrupt("return", fetch(url, {
73312
+ return _context5.abrupt("return", fetch(url, {
73316
73313
  method: 'GET',
73317
73314
  headers: {
73318
73315
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73331,9 +73328,9 @@ function _fetchText() {
73331
73328
  }));
73332
73329
  case 1:
73333
73330
  case "end":
73334
- return _context6.stop();
73331
+ return _context5.stop();
73335
73332
  }
73336
- }, _callee6);
73333
+ }, _callee5);
73337
73334
  }));
73338
73335
  return _fetchText.apply(this, arguments);
73339
73336
  }
@@ -73450,6 +73447,32 @@ function setExpiry(numDays) {
73450
73447
  var today = new Date();
73451
73448
  return new Date(today.setHours(24 * numDays || 1)).toJSON();
73452
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
+ }
73453
73476
  /**
73454
73477
  * Check if given layout exists in the loop using layoutId
73455
73478
  * @param layouts Schedule loop unique layouts (uniqueLayouts)
@@ -73658,7 +73681,7 @@ function prepareVideoMedia(media, region) {
73658
73681
  var $layout = region.layout.html;
73659
73682
  var layoutSelector = '#' + region.layout.containerName + '[data-sequence="' + region.layout.index + '"]';
73660
73683
  var $layoutWithIndex = document.querySelector(layoutSelector);
73661
- var $region = document.querySelector('#' + region.containerName);
73684
+ var $region = region.html;
73662
73685
  var mediaInRegion = $region === null || $region === void 0 ? void 0 : $region.querySelector('.' + mediaId);
73663
73686
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73664
73687
  layoutSelector: layoutSelector,
@@ -73677,7 +73700,7 @@ function prepareVideoMedia(media, region) {
73677
73700
  media.html = createMediaElement(media);
73678
73701
  }
73679
73702
  // Append fresh copy of the media into the region
73680
- $region !== null && $region.appendChild(media.html);
73703
+ region.html.appendChild(media.html);
73681
73704
  var isMediaInDOM = document.body.contains(media.html);
73682
73705
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73683
73706
  isMediaInDOM: isMediaInDOM,
@@ -73686,29 +73709,9 @@ function prepareVideoMedia(media, region) {
73686
73709
  });
73687
73710
  // Initialize video.js
73688
73711
  media.player = videojs(mediaId, _objectSpread2(_objectSpread2({}, defaultVjsOpts), {}, {
73689
- errorDisplay: region.xlr.config.platform !== ConsumerPlatform.CHROMEOS,
73712
+ errorDisplay: !reportToPlayerPlatform.includes(region.xlr.config.platform),
73690
73713
  loop: media.loop
73691
73714
  }));
73692
- media.player.on('error', /*#__PURE__*/function () {
73693
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(err) {
73694
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73695
- while (1) switch (_context2.prev = _context2.next) {
73696
- case 0:
73697
- if (media.region.xlr.config.platform === ConsumerPlatform.CHROMEOS) {
73698
- playerReportFault('Video file not supported', media).then(function () {
73699
- media.emitter.emit('end', media);
73700
- });
73701
- }
73702
- case 1:
73703
- case "end":
73704
- return _context2.stop();
73705
- }
73706
- }, _callee2);
73707
- }));
73708
- return function (_x10) {
73709
- return _ref3.apply(this, arguments);
73710
- };
73711
- }());
73712
73715
  media.player.el().style.setProperty('visibility', 'hidden');
73713
73716
  media.player.el().style.setProperty('opacity', '0');
73714
73717
  media.player.el().style.setProperty('z-index', '-99');
@@ -73723,9 +73726,9 @@ function prepareImageMedia(media, region) {
73723
73726
  if (mediaInRegion) {
73724
73727
  mediaInRegion.remove();
73725
73728
  }
73726
- // Append media to its region
73727
- var $region = document.querySelector('#' + region.containerName);
73728
- $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);
73729
73732
  }
73730
73733
  function prepareAudioMedia(media, region) {
73731
73734
  var mediaId = getMediaId(media);
@@ -73738,9 +73741,8 @@ function prepareAudioMedia(media, region) {
73738
73741
  if (mediaInRegion) {
73739
73742
  mediaInRegion.remove();
73740
73743
  }
73741
- // Append media to its region
73742
- var $region = document.querySelector('#' + region.containerName);
73743
- $region !== null && $region.appendChild(media.html);
73744
+ // Append media to its region using the direct reference
73745
+ region.html.appendChild(media.html);
73744
73746
  }
73745
73747
  function prepareHtmlMedia(media, region) {
73746
73748
  // Set state as false ( for now )
@@ -73760,57 +73762,92 @@ function prepareHtmlMedia(media, region) {
73760
73762
  media.html.innerHTML = '';
73761
73763
  media.html.appendChild(media.iframe);
73762
73764
  if (!mediaInRegion) {
73763
- // Add fresh copy of the media into the region
73764
- var _$region = document.querySelector('#' + region.containerName);
73765
- _$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);
73766
73767
  media.ready = true;
73767
73768
  }
73768
73769
  }
73769
73770
  }
73770
- 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) {
73771
73777
  return _playerReportFault.apply(this, arguments);
73772
73778
  }
73773
73779
  function _playerReportFault() {
73774
- _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(msg, media) {
73775
- var playerSW, hasSW;
73776
- return _regeneratorRuntime().wrap(function _callee7$(_context7) {
73777
- 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) {
73778
73790
  case 0:
73791
+ code = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : FaultCodes.FaultVideoUnexpected;
73779
73792
  // Immediately expire media and report a fault
73793
+ platform = media.region.xlr.config.platform;
73780
73794
  playerSW = PwaSW();
73781
- _context7.next = 3;
73795
+ _context6.next = 5;
73782
73796
  return playerSW.getSW();
73783
- case 3:
73784
- hasSW = _context7.sent;
73785
- if (hasSW) {
73786
- playerSW.postMsg({
73787
- type: 'MEDIA_FAULT',
73788
- code: 5002,
73789
- 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', {
73790
73827
  mediaId: media.id,
73791
- regionId: media.region.id,
73792
- layoutId: media.region.layout.id,
73793
- date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73794
- // Temporary setting
73795
- expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73796
- }).then(function () {
73797
- // We try to prepare next media if we have more than 1 media
73798
- if (media.region.totalMediaObjects > 1) {
73799
- media.region.prepareNextMedia();
73800
- }
73801
- })["finally"](function () {
73802
- // Stopping media as we have reported the error as fault
73803
- console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73804
- mediaId: media.id,
73805
- regionItems: media.region.totalMediaObjects
73806
- });
73828
+ regionItems: media.region.totalMediaObjects
73807
73829
  });
73830
+ }));
73831
+ case 12:
73832
+ if (!(platform === ConsumerPlatform.ELECTRON)) {
73833
+ _context6.next = 17;
73834
+ break;
73808
73835
  }
73809
- 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:
73810
73847
  case "end":
73811
- return _context7.stop();
73848
+ return _context6.stop();
73812
73849
  }
73813
- }, _callee7);
73850
+ }, _callee6);
73814
73851
  }));
73815
73852
  return _playerReportFault.apply(this, arguments);
73816
73853
  }
@@ -73838,7 +73875,7 @@ let nanoid = (size = 21) => {
73838
73875
  function AudioMedia(media) {
73839
73876
  var audioMediaObject = {
73840
73877
  init: function init() {
73841
- var $audioMedia = document.getElementById(getMediaId(media));
73878
+ var $audioMedia = media.html;
73842
73879
  var $playBtn = null;
73843
73880
  if ($audioMedia) {
73844
73881
  $audioMedia.onloadstart = function () {
@@ -73901,6 +73938,8 @@ var Media = /*#__PURE__*/function () {
73901
73938
  _this$xml3,
73902
73939
  _this$xml4,
73903
73940
  _this$xml5,
73941
+ _this$xml6,
73942
+ _this$xml7,
73904
73943
  _this = this;
73905
73944
  _classCallCheck(this, Media);
73906
73945
  _defineProperty(this, "attachedAudio", false);
@@ -73913,6 +73952,7 @@ var Media = /*#__PURE__*/function () {
73913
73952
  _defineProperty(this, "enableStat", false);
73914
73953
  _defineProperty(this, "fileId", '');
73915
73954
  _defineProperty(this, "finished", false);
73955
+ _defineProperty(this, "fromDt", '');
73916
73956
  _defineProperty(this, "html", null);
73917
73957
  _defineProperty(this, "id", '');
73918
73958
  _defineProperty(this, "idCounter", 0);
@@ -73934,6 +73974,7 @@ var Media = /*#__PURE__*/function () {
73934
73974
  _defineProperty(this, "state", MediaState.IDLE);
73935
73975
  _defineProperty(this, "tempSrc", '');
73936
73976
  _defineProperty(this, "timeoutId", setTimeout(function () {}, 0));
73977
+ _defineProperty(this, "toDt", '');
73937
73978
  _defineProperty(this, "type", '');
73938
73979
  _defineProperty(this, "uri", '');
73939
73980
  _defineProperty(this, "url", null);
@@ -73960,12 +74001,14 @@ var Media = /*#__PURE__*/function () {
73960
74001
  this.duration = parseInt((_this$xml4 = this.xml) === null || _this$xml4 === void 0 ? void 0 : _this$xml4.getAttribute('duration')) || 0;
73961
74002
  this.enableStat = Boolean(((_this$xml5 = this.xml) === null || _this$xml5 === void 0 ? void 0 : _this$xml5.getAttribute('enableStat')) || false);
73962
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')) || '';
73963
74006
  this.on('start', function (media) {
73964
74007
  if (media.state === MediaState.PLAYING) return;
73965
74008
  media.state = MediaState.PLAYING;
73966
74009
  if (media.mediaType === 'video') {
73967
- var videoMedia = VideoMedia(media, _this.xlr);
73968
- videoMedia.init();
74010
+ media.videoHandler = VideoMedia(media, _this.xlr);
74011
+ media.videoHandler.init();
73969
74012
  if (media.duration > 0) {
73970
74013
  _this.startMediaTimer(media);
73971
74014
  }
@@ -74092,8 +74135,8 @@ var Media = /*#__PURE__*/function () {
74092
74135
  if (media.mediaType === 'video') {
74093
74136
  // Dispose the video media
74094
74137
  console.debug("??? XLR.debug >> VideoMedia::stop - ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
74095
- if (media.player !== undefined) {
74096
- VideoMedia(media, _this2.xlr).stop(true);
74138
+ if (media.videoHandler !== undefined) {
74139
+ media.videoHandler.stop(true);
74097
74140
  }
74098
74141
  }
74099
74142
  }
@@ -74108,8 +74151,8 @@ var Media = /*#__PURE__*/function () {
74108
74151
  }, {
74109
74152
  key: "init",
74110
74153
  value: function init() {
74111
- var _this$xml6;
74112
- 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');
74113
74156
  if (mediaOptions) {
74114
74157
  for (var _i = 0, _Array$from = Array.from(mediaOptions); _i < _Array$from.length; _i++) {
74115
74158
  var _options = _Array$from[_i];
@@ -74164,6 +74207,10 @@ var Media = /*#__PURE__*/function () {
74164
74207
  }
74165
74208
  } else if (this.xlr.config.platform === ConsumerPlatform.ELECTRON) {
74166
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
+ }
74167
74214
  }
74168
74215
  this.url = tmpUrl;
74169
74216
  // Loop if media has loop, or if region has loop and a single media
@@ -74206,8 +74253,8 @@ var Media = /*#__PURE__*/function () {
74206
74253
  mediaType: _this3.mediaType,
74207
74254
  containerName: _this3.containerName
74208
74255
  });
74209
- var $region = document.querySelector('#' + _this3.region.containerName);
74210
- var $media = $region !== null && $region.querySelector('.' + mediaId);
74256
+ var $region = _this3.region.html;
74257
+ var $media = $region.querySelector('.' + mediaId);
74211
74258
  if (!$media) {
74212
74259
  $media = getNewMedia();
74213
74260
  }
@@ -74271,13 +74318,13 @@ var Media = /*#__PURE__*/function () {
74271
74318
  }
74272
74319
  };
74273
74320
  var getNewMedia = function getNewMedia() {
74274
- var $region = document.getElementById("".concat(_this3.region.containerName));
74321
+ var $region = _this3.region.html;
74275
74322
  // This function is for checking whether
74276
74323
  // the region still has to show a media item
74277
74324
  // when another region is not finished yet
74278
74325
  if (_this3.region.complete && !_this3.region.layout.allEnded) {
74279
74326
  // Add currentMedia to the region
74280
- $region && $region.insertBefore(_this3.html, $region.lastElementChild);
74327
+ $region.insertBefore(_this3.html, $region.lastElementChild);
74281
74328
  return _this3.html;
74282
74329
  }
74283
74330
  return null;
@@ -74288,23 +74335,18 @@ var Media = /*#__PURE__*/function () {
74288
74335
  key: "stop",
74289
74336
  value: function () {
74290
74337
  var _stop = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
74291
- var $media;
74292
74338
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74293
74339
  while (1) switch (_context.prev = _context.next) {
74294
74340
  case 0:
74295
- $media = document.getElementById(getMediaId({
74296
- mediaType: this.mediaType,
74297
- containerName: this.containerName
74298
- }));
74299
- if ($media) {
74300
- $media.style.display = 'none';
74301
- $media.remove();
74341
+ if (this.html) {
74342
+ this.html.style.display = 'none';
74343
+ this.html.remove();
74302
74344
  }
74303
74345
  // Release blob URLs for image media to prevent memory leaks on long-running signage
74304
74346
  if (this.mediaType === 'image' && this.url) {
74305
74347
  BlobLoader.release(this.url);
74306
74348
  }
74307
- case 3:
74349
+ case 2:
74308
74350
  case "end":
74309
74351
  return _context.stop();
74310
74352
  }
@@ -74477,18 +74519,30 @@ var Region = /*#__PURE__*/function () {
74477
74519
  this.html = $region;
74478
74520
  /* Parse region media objects */
74479
74521
  var regionMediaItems = Array.from(this.xml.getElementsByTagName('media'));
74480
- this.totalMediaObjects = regionMediaItems.length;
74481
74522
  $layout && $layout.appendChild(this.html);
74482
74523
  Array.from(regionMediaItems).forEach( /*#__PURE__*/function () {
74483
74524
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(mediaXml, indx) {
74484
- var mediaObj;
74525
+ var fromDt, toDt, mediaObj;
74485
74526
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74486
74527
  while (1) switch (_context.prev = _context.next) {
74487
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:
74488
74542
  mediaObj = new Media(_this, (mediaXml === null || mediaXml === void 0 ? void 0 : mediaXml.getAttribute('id')) || '', mediaXml, _this.options, _this.xlr);
74489
74543
  mediaObj.index = indx;
74490
74544
  _this.mediaObjects.push(mediaObj);
74491
- case 3:
74545
+ case 8:
74492
74546
  case "end":
74493
74547
  return _context.stop();
74494
74548
  }
@@ -74498,6 +74552,7 @@ var Region = /*#__PURE__*/function () {
74498
74552
  return _ref.apply(this, arguments);
74499
74553
  };
74500
74554
  }());
74555
+ this.totalMediaObjects = this.mediaObjects.length;
74501
74556
  console.debug('??? XLR.debug >> Region - done looping through media', {
74502
74557
  mediaObjects: this.mediaObjects
74503
74558
  });
@@ -74553,6 +74608,23 @@ var Region = /*#__PURE__*/function () {
74553
74608
  key: "prepareNextMedia",
74554
74609
  value: function prepareNextMedia() {
74555
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
+ }
74556
74628
  var nextMedia = this.mediaObjects[nextMediaIndex];
74557
74629
  console.debug('<><> XLR.debug >> [Media] - [Region::prepareNextMedia()] - Preparing next media', {
74558
74630
  currentMediaIndex: this.currentMediaIndex,
@@ -74593,9 +74665,21 @@ var Region = /*#__PURE__*/function () {
74593
74665
  }, {
74594
74666
  key: "run",
74595
74667
  value: function run() {
74668
+ var _this$currMedia, _this$oldMedia2;
74596
74669
  console.debug('??? XLR.debug >> Region Called Region::run > ', this.id);
74597
74670
  // Reset region states
74598
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
+ });
74599
74683
  if (this.currMedia) {
74600
74684
  this.transitionNodes(this.oldMedia, this.currMedia);
74601
74685
  }
@@ -74644,6 +74728,7 @@ var Region = /*#__PURE__*/function () {
74644
74728
  // Hide oldMedia
74645
74729
  if (oldMedia) {
74646
74730
  var $layout = document.querySelector("#".concat(_this2.layout.containerName, "[data-sequence=\"").concat(_this2.layout.index, "\"]"));
74731
+ if (!$layout) return;
74647
74732
  var $region = $layout.querySelector('#' + _this2.containerName);
74648
74733
  var $oldMedia = $region ? $region.querySelector('.' + getMediaId(oldMedia)) : null;
74649
74734
  if ($oldMedia) {
@@ -74664,7 +74749,7 @@ var Region = /*#__PURE__*/function () {
74664
74749
  $videoWrapper.style.setProperty('visibility', 'hidden');
74665
74750
  $videoWrapper.style.setProperty('z-index', '-999');
74666
74751
  $videoWrapper.style.setProperty('opacity', '0');
74667
- if (oldMedia.player && oldMedia.videoHandler) {
74752
+ if (oldMedia.videoHandler) {
74668
74753
  oldMedia.videoHandler.stop(true);
74669
74754
  }
74670
74755
  }
@@ -74724,13 +74809,13 @@ var Region = /*#__PURE__*/function () {
74724
74809
  }, {
74725
74810
  key: "playNextMedia",
74726
74811
  value: function playNextMedia() {
74727
- 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;
74728
74813
  console.debug('??? XLR.debug Region playing next media', {
74729
74814
  regionId: this.id,
74730
74815
  currentMediaIndex: this.currentMediaIndex,
74731
74816
  mediaItemsLn: this.mediaObjects.length,
74732
- oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName,
74733
- 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,
74734
74819
  nxtMedia: (_this$nxtMedia = this.nxtMedia) === null || _this$nxtMedia === void 0 ? void 0 : _this$nxtMedia.containerName
74735
74820
  });
74736
74821
  /* The current media has finished running */
@@ -74740,33 +74825,20 @@ var Region = /*#__PURE__*/function () {
74740
74825
  });
74741
74826
  return;
74742
74827
  }
74743
- // Are we in a playlist, and has the playlist completed a full cycle?
74744
- var isLastMediaInPlaylist = this.currentMediaIndex === this.mediaObjects.length - 1 && this.mediaObjects.length > 1;
74745
- // If yes, enable shell command widgets again (if any), so they execute on the next playlist cycle
74746
- if (isLastMediaInPlaylist) {
74747
- this.mediaObjects.forEach(function (media) {
74748
- if (media.mediaType === 'shellcommand') {
74749
- // reset per-playlist-cycle execution state
74750
- media.hasCommandExecuted = false;
74751
- }
74752
- });
74753
- }
74754
- if (!this.layout.isOverlay && this.currentMediaIndex === this.mediaObjects.length - 1) {
74755
- this.finished();
74756
- if (this.layout.allEnded) {
74757
- console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74758
- return;
74759
- }
74760
- }
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;
74761
74831
  // When the region has completed and when currentMedia is html
74762
74832
  // Then, preserve the currentMedia state
74763
- 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') {
74764
74834
  return;
74765
74835
  }
74766
74836
  // When the region has completed and mediaObjects.length = 1
74767
- // and curMedia.loop = false, then put the media on
74768
- // its current state
74769
- 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')) {
74770
74842
  return;
74771
74843
  }
74772
74844
  if (this.currMedia) {
@@ -74774,17 +74846,51 @@ var Region = /*#__PURE__*/function () {
74774
74846
  } else {
74775
74847
  this.oldMedia = undefined;
74776
74848
  }
74777
- this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74849
+ this.currentMediaIndex = (origIndex + 1) % this.totalMediaObjects;
74778
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
+ }
74779
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
+ }
74780
74879
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74781
74880
  regionId: this.id,
74782
74881
  currentMediaIndex: this.currentMediaIndex,
74783
74882
  mediaItemsLn: this.mediaObjects.length,
74784
- 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,
74785
74884
  currMedia: (_this$currMedia7 = this.currMedia) === null || _this$currMedia7 === void 0 ? void 0 : _this$currMedia7.containerName,
74786
74885
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74787
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
+ }
74788
74894
  this.transitionNodes(this.oldMedia, this.currMedia);
74789
74895
  }
74790
74896
  }, {
@@ -74813,8 +74919,7 @@ var Region = /*#__PURE__*/function () {
74813
74919
  }, {
74814
74920
  key: "exitTransition",
74815
74921
  value: function exitTransition() {
74816
- /* TODO: Actually implement region exit transitions */
74817
- document.getElementById("".concat(this.containerName));
74922
+ /* TODO: Actually implement region exit transitions using this.html */
74818
74923
  console.debug('Called Region::exitTransition ', this.id);
74819
74924
  this.exitTransitionComplete();
74820
74925
  }
@@ -75114,7 +75219,7 @@ var ActionController = /*#__PURE__*/function () {
75114
75219
  if (dataset.source === 'region') {
75115
75220
  // Try to find the region
75116
75221
  if (regionObj.id === dataset.sourceid) {
75117
- $sourceObj = document.getElementById(regionObj.containerName);
75222
+ $sourceObj = regionObj.html;
75118
75223
  break;
75119
75224
  }
75120
75225
  } else if (dataset.source === 'widget') {
@@ -75123,7 +75228,7 @@ var ActionController = /*#__PURE__*/function () {
75123
75228
  for (var _i2 = 0, _mediaObjects = mediaObjects; _i2 < _mediaObjects.length; _i2++) {
75124
75229
  var mediaObject = _mediaObjects[_i2];
75125
75230
  if (mediaObject.id === dataset.sourceid) {
75126
- $sourceObj = document.getElementById(mediaObject.containerName);
75231
+ $sourceObj = mediaObject.html;
75127
75232
  break;
75128
75233
  }
75129
75234
  }
@@ -75388,7 +75493,11 @@ var Layout = /*#__PURE__*/function () {
75388
75493
  this.on('start', function (layout) {
75389
75494
  layout.done = false;
75390
75495
  layout.state = ELayoutState.RUNNING;
75391
- 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
+ });
75392
75501
  // Check if stats are enabled for the layout
75393
75502
  if (layout.enableStat) {
75394
75503
  _this.statsBC.postMessage({
@@ -75409,12 +75518,21 @@ var Layout = /*#__PURE__*/function () {
75409
75518
  while (1) switch (_context2.prev = _context2.next) {
75410
75519
  case 0:
75411
75520
  if (!(layout.state === ELayoutState.CANCELLED)) {
75412
- _context2.next = 2;
75521
+ _context2.next = 3;
75413
75522
  break;
75414
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
+ });
75415
75529
  return _context2.abrupt("return");
75416
- case 2:
75417
- 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
+ });
75418
75536
  /* Remove layout that has ended */
75419
75537
  $layout = document.querySelector("#".concat(layout.containerName, "[data-sequence=\"").concat(layout.index, "\"]")); // Only update layout.state when last state === RUNNING
75420
75538
  if (layout.state === ELayoutState.RUNNING) {
@@ -75425,6 +75543,8 @@ var Layout = /*#__PURE__*/function () {
75425
75543
  console.debug('>>> XLR.debug Layout end emitted > Layout ID > ', {
75426
75544
  $layout: $layout,
75427
75545
  layoutId: layout.id,
75546
+ layoutIndex: layout.index,
75547
+ layoutState: layout.state,
75428
75548
  isOverlay: layout.isOverlay,
75429
75549
  isInterrupt: layout.isInterrupt()
75430
75550
  });
@@ -75451,9 +75571,9 @@ var Layout = /*#__PURE__*/function () {
75451
75571
  }
75452
75572
  // Emit layout end event
75453
75573
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75454
- _context2.next = 12;
75574
+ _context2.next = 13;
75455
75575
  return layout.xlr.emitSync('layoutEnd', layout);
75456
- case 12:
75576
+ case 13:
75457
75577
  if (_this.xlr.config.platform !== ConsumerPlatform.CMS && layout.inLoop) {
75458
75578
  // Transition next layout to current layout and prepare next layout if exist
75459
75579
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75483,7 +75603,7 @@ var Layout = /*#__PURE__*/function () {
75483
75603
  };
75484
75604
  }());
75485
75605
  }
75486
- case 13:
75606
+ case 14:
75487
75607
  case "end":
75488
75608
  return _context2.stop();
75489
75609
  }
@@ -75496,6 +75616,34 @@ var Layout = /*#__PURE__*/function () {
75496
75616
  this.on('cancelled', function (layout) {
75497
75617
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75498
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
+ }
75499
75647
  });
75500
75648
  }
75501
75649
  return _createClass(Layout, [{
@@ -75637,6 +75785,14 @@ var Layout = /*#__PURE__*/function () {
75637
75785
  if ($splashScreen) {
75638
75786
  $splashScreen.style.display = 'none';
75639
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
+ });
75640
75796
  if ($layoutContainer) {
75641
75797
  $layoutContainer.style.setProperty('visibility', 'visible');
75642
75798
  $layoutContainer.style.setProperty('opacity', '1');
@@ -75667,19 +75823,19 @@ var Layout = /*#__PURE__*/function () {
75667
75823
  key: "regionExpired",
75668
75824
  value: function regionExpired() {
75669
75825
  this.allExpired = true;
75670
- var _iterator = _createForOfIteratorHelper(this.regions),
75671
- _step;
75826
+ var _iterator3 = _createForOfIteratorHelper(this.regions),
75827
+ _step3;
75672
75828
  try {
75673
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
75674
- var layoutRegion = _step.value;
75829
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
75830
+ var layoutRegion = _step3.value;
75675
75831
  if (!layoutRegion.complete) {
75676
75832
  this.allExpired = false;
75677
75833
  }
75678
75834
  }
75679
75835
  } catch (err) {
75680
- _iterator.e(err);
75836
+ _iterator3.e(err);
75681
75837
  } finally {
75682
- _iterator.f();
75838
+ _iterator3.f();
75683
75839
  }
75684
75840
  if (this.allExpired) {
75685
75841
  this.end();
@@ -75690,17 +75846,17 @@ var Layout = /*#__PURE__*/function () {
75690
75846
  value: function end() {
75691
75847
  console.debug('Executing Layout::end and Calling Region::end ', this);
75692
75848
  /* Ask the layout to gracefully stop running now */
75693
- var _iterator2 = _createForOfIteratorHelper(this.regions),
75694
- _step2;
75849
+ var _iterator4 = _createForOfIteratorHelper(this.regions),
75850
+ _step4;
75695
75851
  try {
75696
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75697
- var layoutRegion = _step2.value;
75852
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
75853
+ var layoutRegion = _step4.value;
75698
75854
  layoutRegion.end();
75699
75855
  }
75700
75856
  } catch (err) {
75701
- _iterator2.e(err);
75857
+ _iterator4.e(err);
75702
75858
  } finally {
75703
- _iterator2.f();
75859
+ _iterator4.f();
75704
75860
  }
75705
75861
  }
75706
75862
  }, {
@@ -75713,7 +75869,11 @@ var Layout = /*#__PURE__*/function () {
75713
75869
  }
75714
75870
  }
75715
75871
  if (this.allEnded) {
75716
- 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
+ });
75717
75877
  if (this.xlr.config.platform === ConsumerPlatform.CMS) {
75718
75878
  var $end = document.getElementById('play_ended');
75719
75879
  var $preview = document.getElementById('screen_container');
@@ -75812,6 +75972,42 @@ var Layout = /*#__PURE__*/function () {
75812
75972
  (_$layout$parentElemen2 = $layout.parentElement) === null || _$layout$parentElemen2 === void 0 || _$layout$parentElemen2.removeChild($layout);
75813
75973
  }
75814
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
+ }
75815
76011
  }, {
75816
76012
  key: "getXlf",
75817
76013
  value: function getXlf() {
@@ -76054,6 +76250,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76054
76250
  }());
76055
76251
  xlrObject.on('updateLoop', /*#__PURE__*/function () {
76056
76252
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(inputLayouts) {
76253
+ var _xlrObject$currentLay;
76057
76254
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
76058
76255
  while (1) switch (_context3.prev = _context3.next) {
76059
76256
  case 0:
@@ -76062,7 +76259,17 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76062
76259
  return xlrObject.updateLoop(inputLayouts);
76063
76260
  case 3:
76064
76261
  xlrObject.isUpdatingLoop = false;
76065
- 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:
76066
76273
  case "end":
76067
76274
  return _context3.stop();
76068
76275
  }
@@ -76092,40 +76299,35 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76092
76299
  return _ref4.apply(this, arguments);
76093
76300
  };
76094
76301
  }());
76095
- /**
76096
- * Asynchronous event emitter. Extended nanoevents event emitter.
76097
- *
76098
- * NOTE: Known limitation — nanoevents emit() is synchronous.
76099
- * Any async event handlers registered via .on() are fire-and-forget;
76100
- * emitSync does NOT await them. The returned Promise resolves
76101
- * immediately after handlers are invoked, not after they complete.
76102
- *
76103
- * @param eventName
76104
- * @param args
76105
- */
76106
- xlrObject.emitSync = function (eventName) {
76107
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76108
- args[_key - 1] = arguments[_key];
76109
- }
76110
- return new Promise( /*#__PURE__*/function () {
76111
- var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(resolve) {
76112
- var _xlrObject$emitter;
76113
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76114
- while (1) switch (_context5.prev = _context5.next) {
76115
- case 0:
76116
- (_xlrObject$emitter = xlrObject.emitter).emit.apply(_xlrObject$emitter, [eventName].concat(args));
76117
- resolve();
76118
- case 2:
76119
- case "end":
76120
- return _context5.stop();
76121
- }
76122
- }, _callee5);
76123
- }));
76124
- return function (_x4) {
76125
- return _ref5.apply(this, arguments);
76126
- };
76127
- }());
76128
- };
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
+ }();
76129
76331
  xlrObject.bootstrap = function () {
76130
76332
  // Place to set configurations and initialize required props
76131
76333
  var self = this;
@@ -76160,12 +76362,21 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76160
76362
  if ($splashScreen && $splashScreen.style.display === 'block') {
76161
76363
  $splashScreen === null || $splashScreen === void 0 || $splashScreen.hide();
76162
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
+ });
76163
76370
  if (!xlr.currentLayout.done) {
76164
76371
  // Hide overlays when current layout is interrupt
76165
76372
  if (xlr.currentLayout.isInterrupt()) {
76166
76373
  xlrObject.overlayLayoutManager.stopOverlays();
76167
76374
  }
76168
- console.log('>>>> XLR.debug XLR::playSchedules > Running currentLayout', xlr.currentLayout);
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
+ });
76169
76380
  xlr.currentLayout.run();
76170
76381
  }
76171
76382
  } else {
@@ -76243,7 +76454,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76243
76454
  _context7.next = 10;
76244
76455
  return _overlay.finishAllRegions();
76245
76456
  case 10:
76246
- _overlay.removeLayout();
76457
+ _overlay.removeLayout(LayoutPlaybackType.OVERLAY);
76247
76458
  case 11:
76248
76459
  _context7.next = 14;
76249
76460
  break;
@@ -76309,6 +76520,31 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76309
76520
  var $layout = document.querySelector("#".concat(containerName, "[data-sequence=\"").concat(layoutIndex, "\"]"));
76310
76521
  return $layout !== null;
76311
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
+ };
76312
76548
  xlrObject.updateLoop = /*#__PURE__*/function () {
76313
76549
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11(inputLayouts) {
76314
76550
  var _this$currentLayout,
@@ -76355,11 +76591,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76355
76591
  };
76356
76592
  }();
76357
76593
  if (isCurrentLayoutValid) {
76358
- _context11.next = 54;
76594
+ _context11.next = 55;
76359
76595
  break;
76360
76596
  }
76361
76597
  if (!playback.hasDefaultOnly) {
76362
- _context11.next = 33;
76598
+ _context11.next = 34;
76363
76599
  break;
76364
76600
  }
76365
76601
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
@@ -76372,82 +76608,102 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76372
76608
  case 19:
76373
76609
  this.currentLayout.removeLayout();
76374
76610
  case 20:
76375
- _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;
76376
76618
  return this.prepareLayoutXlf(playback.currentLayout);
76377
- case 22:
76619
+ case 23:
76378
76620
  this.currentLayout = _context11.sent;
76379
76621
  this.currentLayoutId = this.currentLayout.layoutId;
76380
76622
  _context11.t0 = this;
76381
- _context11.next = 27;
76623
+ _context11.next = 28;
76382
76624
  return this.prepareLayoutXlf(playback.nextLayout);
76383
- case 27:
76625
+ case 28:
76384
76626
  _context11.t1 = _context11.sent;
76385
- _context11.next = 30;
76627
+ _context11.next = 31;
76386
76628
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76387
- case 30:
76629
+ case 31:
76388
76630
  this.nextLayout = _context11.sent;
76389
- _context11.next = 50;
76631
+ _context11.next = 51;
76390
76632
  break;
76391
- case 33:
76633
+ case 34:
76392
76634
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76393
- _context11.next = 38;
76635
+ _context11.next = 39;
76394
76636
  break;
76395
76637
  }
76396
76638
  this.currentLayout.inLoop = false;
76397
- _context11.next = 37;
76639
+ _context11.next = 38;
76398
76640
  return this.currentLayout.finishAllRegions();
76399
- case 37:
76400
- this.currentLayout.removeLayout();
76401
76641
  case 38:
76642
+ this.currentLayout.removeLayout();
76643
+ case 39:
76402
76644
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76403
- this.nextLayout.removeLayout();
76645
+ this.nextLayout.discardLayout(LayoutPlaybackType.NEXT);
76404
76646
  }
76405
76647
  if (!playback.currentLayout) {
76406
- _context11.next = 42;
76648
+ _context11.next = 43;
76407
76649
  break;
76408
76650
  }
76409
- _context11.next = 42;
76651
+ _context11.next = 43;
76410
76652
  return prepareNewCurrentLayout();
76411
- case 42:
76653
+ case 43:
76412
76654
  if (!playback.nextLayout) {
76413
- _context11.next = 50;
76655
+ _context11.next = 51;
76414
76656
  break;
76415
76657
  }
76416
76658
  _context11.t2 = this;
76417
- _context11.next = 46;
76659
+ _context11.next = 47;
76418
76660
  return this.prepareLayoutXlf(playback.nextLayout);
76419
- case 46:
76661
+ case 47:
76420
76662
  _context11.t3 = _context11.sent;
76421
- _context11.next = 49;
76663
+ _context11.next = 50;
76422
76664
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76423
- case 49:
76424
- this.nextLayout = _context11.sent;
76425
76665
  case 50:
76426
- _context11.next = 52;
76666
+ this.nextLayout = _context11.sent;
76667
+ case 51:
76668
+ _context11.next = 53;
76427
76669
  return this.playSchedules(this);
76428
- case 52:
76670
+ case 53:
76429
76671
  _context11.next = 67;
76430
76672
  break;
76431
- case 54:
76673
+ case 55:
76432
76674
  // Remove next layout if it is in the DOM
76433
76675
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76434
- this.nextLayout.removeLayout();
76676
+ this.nextLayout.discardLayout(LayoutPlaybackType.NEXT);
76435
76677
  }
76436
- // 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.
76437
76699
  if (!playback.currentLayout) {
76438
- _context11.next = 58;
76439
- break;
76440
- }
76441
- _context11.next = 58;
76442
- return prepareNewCurrentLayout();
76443
- case 58:
76444
- if (!playback.nextLayout) {
76445
76700
  _context11.next = 66;
76446
76701
  break;
76447
76702
  }
76703
+ this.currentLayoutIndex = playback.currentLayoutIndex;
76448
76704
  _context11.t4 = this;
76449
76705
  _context11.next = 62;
76450
- return this.prepareLayoutXlf(playback.nextLayout);
76706
+ return this.prepareLayoutXlf(playback.currentLayout);
76451
76707
  case 62:
76452
76708
  _context11.t5 = _context11.sent;
76453
76709
  _context11.next = 65;
@@ -76553,8 +76809,21 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76553
76809
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76554
76810
  }
76555
76811
  } else {
76812
+ var _this$currentLayout4, _this$currentLayout5;
76556
76813
  _currentLayout = this.nextLayout;
76557
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
+ }
76558
76827
  _nextLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76559
76828
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76560
76829
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
@@ -76610,6 +76879,10 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76610
76879
  var activeLayout = inputLayout;
76611
76880
  if (isCMS) {
76612
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
+ }
76613
76886
  } else {
76614
76887
  activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
76615
76888
  }
@@ -76640,7 +76913,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76640
76913
  };
76641
76914
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76642
76915
  var _layoutPlayback$curre, _layoutPlayback$curre2;
76643
- var self, layoutPlayback, currentLayoutXlf, nextLayoutXlf, layouts;
76916
+ var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76644
76917
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76645
76918
  while (1) switch (_context14.prev = _context14.next) {
76646
76919
  case 0:
@@ -76663,7 +76936,12 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76663
76936
  shouldParse: false
76664
76937
  });
76665
76938
  self.currentLayoutId = (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId;
76666
- 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 !== '')) {
76667
76945
  _context14.next = 12;
76668
76946
  break;
76669
76947
  }
@@ -76677,27 +76955,40 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76677
76955
  _context14.t0 = _context14.sent;
76678
76956
  case 15:
76679
76957
  currentLayoutXlf = _context14.t0;
76680
- _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;
76681
76962
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
76682
- case 18:
76963
+ case 19:
76683
76964
  nextLayoutXlf = _context14.sent;
76684
76965
  _context14.t1 = Promise;
76685
76966
  _context14.t2 = currentLayoutXlf;
76686
- _context14.next = 23;
76967
+ _context14.next = 24;
76687
76968
  return self.prepareForSsp(nextLayoutXlf);
76688
- case 23:
76969
+ case 24:
76689
76970
  _context14.t3 = _context14.sent;
76690
76971
  _context14.t4 = [_context14.t2, _context14.t3];
76691
- _context14.next = 27;
76972
+ _context14.next = 28;
76692
76973
  return _context14.t1.all.call(_context14.t1, _context14.t4);
76693
- case 27:
76974
+ case 28:
76694
76975
  layouts = _context14.sent;
76695
- // Return early when layout loop is updating
76696
- if (self.isUpdatingLoop) {
76697
- if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76698
- nextLayoutXlf.removeLayout();
76699
- }
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);
76700
76989
  }
76990
+ return _context14.abrupt("return", Promise.resolve(self));
76991
+ case 33:
76701
76992
  console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
76702
76993
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
76703
76994
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
@@ -76714,8 +77005,15 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76714
77005
  self.currentLayout = self.layouts.current;
76715
77006
  self.currentLayoutId = self.currentLayout.layoutId;
76716
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);
76717
77015
  resolve(xlrObject);
76718
- case 8:
77016
+ case 9:
76719
77017
  case "end":
76720
77018
  return _context13.stop();
76721
77019
  }
@@ -76725,7 +77023,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76725
77023
  return _ref14.apply(this, arguments);
76726
77024
  };
76727
77025
  }()));
76728
- case 31:
77026
+ case 35:
76729
77027
  case "end":
76730
77028
  return _context14.stop();
76731
77029
  }
@@ -76753,34 +77051,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76753
77051
  newOptions.xlfUrl = inputLayout.path;
76754
77052
  }
76755
77053
  if (!(inputLayout && inputLayout.layoutNode === undefined)) {
76756
- _context15.next = 21;
77054
+ _context15.next = 22;
76757
77055
  break;
76758
77056
  }
76759
77057
  if (!(inputLayout.layoutId === -1)) {
76760
- _context15.next = 14;
77058
+ _context15.next = 15;
76761
77059
  break;
76762
77060
  }
76763
77061
  _context15.next = 10;
76764
77062
  return self.emitSync('adRequest', inputLayout.index);
76765
77063
  case 10:
76766
77064
  sspInputLayout = self.inputLayouts[inputLayout.index];
77065
+ console.debug('XLR::prepareLayoutXlf > SSP input layout', {
77066
+ sspInputLayout: sspInputLayout,
77067
+ inputLayout: inputLayout
77068
+ });
76767
77069
  // @ts-ignore
76768
- layoutXlf = ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf()) || '';
76769
- _context15.next = 17;
77070
+ layoutXlf = typeof ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf) === 'function' ? sspInputLayout.getXlf() : '';
77071
+ _context15.next = 18;
76770
77072
  break;
76771
- case 14:
76772
- _context15.next = 16;
77073
+ case 15:
77074
+ _context15.next = 17;
76773
77075
  return getXlf(newOptions);
76774
- case 16:
76775
- layoutXlf = _context15.sent;
76776
77076
  case 17:
77077
+ layoutXlf = _context15.sent;
77078
+ case 18:
76777
77079
  parser = new window.DOMParser();
76778
77080
  layoutXlfNode = parser.parseFromString(layoutXlf, 'text/xml');
76779
- _context15.next = 22;
77081
+ _context15.next = 23;
76780
77082
  break;
76781
- case 21:
76782
- layoutXlfNode = inputLayout && inputLayout.layoutNode;
76783
77083
  case 22:
77084
+ layoutXlfNode = inputLayout && inputLayout.layoutNode;
77085
+ case 23:
76784
77086
  isOverlayLayout = !!(inputLayout !== null && inputLayout !== void 0 && inputLayout.isOverlay);
76785
77087
  return _context15.abrupt("return", new Promise(function (resolve) {
76786
77088
  var _inputLayout$ad;
@@ -76805,13 +77107,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76805
77107
  xlrLayoutObj.duration = sspInputLayout.duration || 0;
76806
77108
  xlrLayoutObj.ad = sspInputLayout.ad;
76807
77109
  }
77110
+ var xlrLayout;
76808
77111
  if (isOverlayLayout) {
76809
- resolve(new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77112
+ xlrLayout = new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76810
77113
  } else {
76811
- resolve(new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77114
+ xlrLayout = new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76812
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;
77122
+ }
77123
+ resolve(xlrLayout);
76813
77124
  }));
76814
- case 24:
77125
+ case 25:
76815
77126
  case "end":
76816
77127
  return _context15.stop();
76817
77128
  }
@@ -76823,46 +77134,49 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76823
77134
  }();
76824
77135
  xlrObject.prepareForSsp = /*#__PURE__*/function () {
76825
77136
  var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee16(nextLayout) {
76826
- var self, _nextLayout, nextIndex, inputLayout, nextLayoutObj;
77137
+ var self, _nextLayout, iterations, maxIterations, nextIndex, inputLayout, nextLayoutObj;
76827
77138
  return _regeneratorRuntime().wrap(function _callee16$(_context16) {
76828
77139
  while (1) switch (_context16.prev = _context16.next) {
76829
77140
  case 0:
76830
77141
  self = this;
76831
77142
  _nextLayout = nextLayout;
76832
- case 2:
76833
- if (!(_nextLayout && _nextLayout.xlfString === '')) {
76834
- _context16.next = 17;
76835
- break;
76836
- }
76837
- // Remove skipped layout
76838
- _nextLayout.removeLayout();
76839
- // Get next valid layout
76840
- // We will skip next layout that has no valid xlf
76841
- nextIndex = _nextLayout.index + 1;
76842
- if (!(nextIndex >= self.inputLayouts.length)) {
76843
- _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;
76844
77148
  break;
76845
77149
  }
76846
- return _context16.abrupt("break", 17);
76847
- 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;
76848
77156
  inputLayout = self.inputLayouts[nextIndex];
76849
77157
  if (inputLayout) {
76850
- _context16.next = 10;
77158
+ _context16.next = 11;
76851
77159
  break;
76852
77160
  }
76853
- return _context16.abrupt("break", 17);
76854
- case 10:
77161
+ return _context16.abrupt("break", 20);
77162
+ case 11:
76855
77163
  nextLayoutObj = self.getLayout(inputLayout);
76856
77164
  nextLayoutObj = setLayoutIndex(nextLayoutObj, nextIndex);
76857
- _context16.next = 14;
77165
+ if (nextLayoutObj) {
77166
+ _context16.next = 15;
77167
+ break;
77168
+ }
77169
+ return _context16.abrupt("break", 20);
77170
+ case 15:
77171
+ _context16.next = 17;
76858
77172
  return self.prepareLayoutXlf(nextLayoutObj);
76859
- case 14:
77173
+ case 17:
76860
77174
  _nextLayout = _context16.sent;
76861
- _context16.next = 2;
77175
+ _context16.next = 4;
76862
77176
  break;
76863
- case 17:
77177
+ case 20:
76864
77178
  return _context16.abrupt("return", _nextLayout);
76865
- case 18:
77179
+ case 21:
76866
77180
  case "end":
76867
77181
  return _context16.stop();
76868
77182
  }
@@ -76874,7 +77188,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76874
77188
  }();
76875
77189
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
76876
77190
  var _this4 = this;
76877
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout4;
77191
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
76878
77192
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
76879
77193
  while (1) switch (_context18.prev = _context18.next) {
76880
77194
  case 0:
@@ -76897,7 +77211,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76897
77211
  break;
76898
77212
  }
76899
77213
  _context18.next = 8;
76900
- 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();
76901
77215
  case 8:
76902
77216
  // and set the previous layout as current layout
76903
77217
  this.currentLayoutIndex = _assumedPrevIndex;
@@ -76925,7 +77239,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76925
77239
  }, _callee18, this);
76926
77240
  }));
76927
77241
  xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
76928
- var _xlrObject$currentLay;
77242
+ var _xlrObject$currentLay2;
76929
77243
  return _regeneratorRuntime().wrap(function _callee19$(_context19) {
76930
77244
  while (1) switch (_context19.prev = _context19.next) {
76931
77245
  case 0:
@@ -76935,7 +77249,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76935
77249
  shouldParse: false
76936
77250
  });
76937
77251
  _context19.next = 3;
76938
- 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();
76939
77253
  case 3:
76940
77254
  case "end":
76941
77255
  return _context19.stop();
@@ -76947,11 +77261,16 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76947
77261
  if (layout !== null) {
76948
77262
  layout.index = xlrInputLayout.index;
76949
77263
  }
77264
+ console.debug('XLR::updateInputLayout', {
77265
+ layoutIndex: layoutIndex,
77266
+ layout: layout,
77267
+ xlrInputLayout: xlrInputLayout
77268
+ });
76950
77269
  this.inputLayouts[layoutIndex] = layout || xlrInputLayout;
76951
77270
  };
76952
77271
  xlrObject.bootstrap();
76953
77272
  return xlrObject;
76954
77273
  }
76955
77274
 
76956
- 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 };
76957
77276
  //# sourceMappingURL=xibo-layout-renderer.esm.js.map