@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.
@@ -707,6 +707,7 @@ var initialLayout = {
707
707
  return Promise.resolve([]);
708
708
  },
709
709
  removeLayout: function removeLayout() {},
710
+ discardLayout: function discardLayout() {},
710
711
  getXlf: function getXlf() {
711
712
  return '';
712
713
  },
@@ -717,6 +718,13 @@ var initialLayout = {
717
718
  html: null
718
719
  };
719
720
 
721
+ var MediaState = {
722
+ IDLE: 'idle',
723
+ PLAYING: 'playing',
724
+ ENDED: 'ended',
725
+ CANCELLED: 'cancelled'
726
+ };
727
+
720
728
  var initialRegion = {
721
729
  complete: false,
722
730
  containerName: '',
@@ -765,56 +773,6 @@ var initialRegion = {
765
773
  xlr: {}
766
774
  };
767
775
 
768
- var MediaState = {
769
- IDLE: 'idle',
770
- PLAYING: 'playing',
771
- ENDED: 'ended',
772
- CANCELLED: 'cancelled'
773
- };
774
- var initialMedia = {
775
- attachedAudio: false,
776
- checkIframeStatus: false,
777
- containerName: '',
778
- divHeight: 0,
779
- divWidth: 0,
780
- duration: 0,
781
- emitter: {},
782
- enableStat: false,
783
- fileId: '',
784
- finished: false,
785
- html: null,
786
- id: '',
787
- idCounter: 0,
788
- iframe: null,
789
- iframeName: '',
790
- index: 0,
791
- loadIframeOnRun: false,
792
- loop: false,
793
- mediaId: '',
794
- mediaType: '',
795
- muted: false,
796
- options: {},
797
- player: undefined,
798
- ready: true,
799
- region: initialRegion,
800
- render: 'html',
801
- run: function run() {},
802
- schemaVersion: '1',
803
- singlePlay: false,
804
- state: MediaState.IDLE,
805
- stop: function stop() {
806
- return Promise.resolve();
807
- },
808
- tempSrc: '',
809
- timeoutId: setTimeout(function () {}, 0),
810
- type: '',
811
- uri: '',
812
- url: null,
813
- useDuration: Boolean(0),
814
- xml: null,
815
- mediaTimer: undefined
816
- };
817
-
818
776
  var OverlayLayoutManager = /*#__PURE__*/function () {
819
777
  function OverlayLayoutManager() {
820
778
  _classCallCheck(this, OverlayLayoutManager);
@@ -1111,6 +1069,7 @@ var initialXlr = {
1111
1069
  isLayoutInDOM: function isLayoutInDOM(containerName, layoutId) {
1112
1070
  return false;
1113
1071
  },
1072
+ cleanupOrphanedLayouts: function cleanupOrphanedLayouts(_keepCurrent, _keepNext) {},
1114
1073
  isSspEnabled: false,
1115
1074
  isUpdatingLoop: false,
1116
1075
  isUpdatingOverlays: false,
@@ -72531,6 +72490,10 @@ if (!videojs.getPlugin || !videojs.getPlugin('reloadSourceOnError')) {
72531
72490
  function composeVideoSource($media, media) {
72532
72491
  // const videoSrc = await preloadMediaBlob(media.url as string, media.mediaType as MediaTypes);
72533
72492
  var vidType = videoFileType(getFileExt(media.uri));
72493
+ if (!vidType) {
72494
+ console.warn("XLR >> VideoMedia: Unsupported video type for media ".concat(media.id, " with uri ").concat(media.uri));
72495
+ return $media;
72496
+ }
72534
72497
  // Only add one source per type
72535
72498
  if ($media.querySelectorAll("source[type=\"".concat(vidType, "\"]")).length === 0) {
72536
72499
  var $videoSource = document.createElement('source');
@@ -72557,8 +72520,38 @@ var vjsDefaultOptions = function vjsDefaultOptions(opts) {
72557
72520
  };
72558
72521
  var reportToPlayerPlatform = [exports.ConsumerPlatform.CHROMEOS, exports.ConsumerPlatform.ELECTRON];
72559
72522
  function VideoMedia(media, xlr) {
72523
+ var stopped = false;
72560
72524
  var mediaId = getMediaId(media);
72525
+ // ── Stall watchdog (closure-level so stop() can cancel it) ───────────────
72526
+ // 'waiting' and 'stalled' fire when the browser stops receiving data.
72527
+ // Unlike codec or source errors they do NOT fire the 'error' event, so
72528
+ // without a watchdog the video silently freezes for its entire duration.
72529
+ var stallWatchdog;
72530
+ var STALL_TIMEOUT_MS = 10000;
72531
+ var clearStallWatchdog = function clearStallWatchdog() {
72532
+ if (stallWatchdog !== undefined) {
72533
+ clearTimeout(stallWatchdog);
72534
+ stallWatchdog = undefined;
72535
+ }
72536
+ };
72537
+ // ─────────────────────────────────────────────────────────────────────────
72538
+ // ── Unified error → report → stop helper (closure-level) ─────────────────
72539
+ // Used by both the 'error' event and the play Promise catch.
72540
+ // playerReportFault only fires for platforms that report faults (Electron,
72541
+ // ChromeOS). All other platforms just advance to the next media via stop().
72542
+ var reportAndStop = function reportAndStop(reason, code) {
72543
+ if (stopped) return;
72544
+ if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72545
+ playerReportFault(reason, media, code).then(function () {
72546
+ return videoPlayer.stop();
72547
+ });
72548
+ } else {
72549
+ videoPlayer.stop();
72550
+ }
72551
+ };
72552
+ // ─────────────────────────────────────────────────────────────────────────
72561
72553
  var videoPlayer = {
72554
+ player: undefined,
72562
72555
  duration: 0,
72563
72556
  init: function init() {
72564
72557
  var _this = this;
@@ -72566,9 +72559,37 @@ function VideoMedia(media, xlr) {
72566
72559
  videoPlayer.duration = media.duration;
72567
72560
  var vjsPlayer = videojs(mediaId);
72568
72561
  if (vjsPlayer) {
72569
- vjsPlayer.on('loadstart', function () {
72570
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72571
- });
72562
+ videoPlayer.player = vjsPlayer;
72563
+ // ── Early source check ────────────────────────────────────────────────
72564
+ // Two-step check before video.js tries to load anything:
72565
+ // 1. Is the file extension one we map to a MIME type?
72566
+ // 2. Can the browser actually play that MIME type?
72567
+ // Failing either step skips the media immediately so video.js
72568
+ // never renders its "No compatible source" error overlay.
72569
+ var vidType = videoFileType(getFileExt(media.uri));
72570
+ if (!vidType) {
72571
+ console.warn("XLR >> VideoMedia: unrecognised file extension for media ".concat(media.id, " (uri: ").concat(media.uri, ")"));
72572
+ reportAndStop("Unsupported video file extension for media ".concat(media.id), exports.FaultCodes.FaultVideoSource);
72573
+ return;
72574
+ }
72575
+ if (document.createElement('video').canPlayType(vidType) === '') {
72576
+ console.warn("XLR >> VideoMedia: browser cannot play type \"".concat(vidType, "\" for media ").concat(media.id));
72577
+ reportAndStop("Browser cannot play video type \"".concat(vidType, "\" for media ").concat(media.id), exports.FaultCodes.FaultVideoSource);
72578
+ return;
72579
+ }
72580
+ // ─────────────────────────────────────────────────────────────────────
72581
+ var armStallWatchdog = function armStallWatchdog() {
72582
+ clearStallWatchdog();
72583
+ stallWatchdog = setTimeout(function () {
72584
+ if (stopped) return;
72585
+ console.warn("XLR >> VideoMedia: stall timeout on media ".concat(media.id));
72586
+ reportAndStop('Video stall timeout', exports.FaultCodes.FaultVideoUnexpected);
72587
+ }, STALL_TIMEOUT_MS);
72588
+ };
72589
+ vjsPlayer.on('waiting', armStallWatchdog);
72590
+ vjsPlayer.on('stalled', armStallWatchdog);
72591
+ vjsPlayer.on('playing', clearStallWatchdog);
72592
+ vjsPlayer.on('ended', clearStallWatchdog);
72572
72593
  vjsPlayer.on('loadedmetadata', function () {
72573
72594
  if (media.duration === 0) {
72574
72595
  videoPlayer.duration = vjsPlayer.duration();
@@ -72639,34 +72660,20 @@ function VideoMedia(media, xlr) {
72639
72660
  }, 5000);
72640
72661
  })]).then(function () {
72641
72662
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay started"));
72642
- })["catch"]( /*#__PURE__*/function () {
72643
- var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72644
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72645
- while (1) switch (_context2.prev = _context2.next) {
72646
- case 0:
72647
- if (error === 'Timeout') {
72648
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72649
- _this.stop();
72650
- } else {
72651
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72652
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72653
- playerReportFault('Media autoplay error', media).then(function () {
72654
- _this.stop();
72655
- });
72656
- }
72657
- }
72658
- case 1:
72659
- case "end":
72660
- return _context2.stop();
72661
- }
72662
- }, _callee2);
72663
- }));
72664
- return function (_x) {
72665
- return _ref2.apply(this, arguments);
72666
- };
72667
- }());
72663
+ })["catch"](function (error) {
72664
+ if (stopped) return;
72665
+ if (error === 'Timeout') {
72666
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72667
+ // Timeout is a scheduling issue, not a media fault — just advance
72668
+ videoPlayer.stop();
72669
+ } else {
72670
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72671
+ reportAndStop('Media autoplay error', exports.FaultCodes.FaultVideoUnexpected);
72672
+ }
72673
+ });
72668
72674
  // Optional: Reset the flag automatically when a new video loads or the source changes
72669
72675
  vjsPlayer.on('loadstart', function () {
72676
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72670
72677
  triggerTimeUpdate = false;
72671
72678
  });
72672
72679
  if (media.duration === 0) {
@@ -72679,13 +72686,9 @@ function VideoMedia(media, xlr) {
72679
72686
  if (mediaDuration !== undefined && currentTime !== undefined) {
72680
72687
  remainingTimeMs = (mediaDuration - currentTime) * 1000;
72681
72688
  }
72682
- if (regionHasMultipleMedia && remainingTimeMs === 0 && !triggerTimeUpdate) {
72683
- // We don't have data yet and we must immediately prepare next media
72684
- media.region.prepareNextMedia();
72685
- } else if (regionHasMultipleMedia && remainingTimeMs <= preloadBufferTimeMs && !triggerTimeUpdate) {
72689
+ if (regionHasMultipleMedia && !triggerTimeUpdate && (remainingTimeMs === 0 || remainingTimeMs <= preloadBufferTimeMs)) {
72686
72690
  // Check if remaining time is less than preloadBufferTimeMs and the action hasn't been triggered yet
72687
72691
  console.log('Less than preloadBufferTimeMs remaining! Do something now.');
72688
- // Prepare next media in region
72689
72692
  media.region.prepareNextMedia();
72690
72693
  triggerTimeUpdate = true; // Set the flag to prevent re-triggering
72691
72694
  }
@@ -72697,33 +72700,16 @@ function VideoMedia(media, xlr) {
72697
72700
  }
72698
72701
  }
72699
72702
  });
72700
- vjsPlayer.on('error', /*#__PURE__*/function () {
72701
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(err) {
72702
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
72703
- while (1) switch (_context3.prev = _context3.next) {
72704
- case 0:
72705
- console.debug("??? XLR.debug >> VideoMedia: Media Error: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id));
72706
- if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72707
- playerReportFault('Video file source not supported', media).then(function () {
72708
- _this.stop();
72709
- });
72710
- } else {
72711
- // End media after 5 seconds
72712
- setTimeout(function () {
72713
- console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended . . ."));
72714
- _this.stop();
72715
- }, 5000);
72716
- }
72717
- case 2:
72718
- case "end":
72719
- return _context3.stop();
72720
- }
72721
- }, _callee3);
72722
- }));
72723
- return function (_x2) {
72724
- return _ref3.apply(this, arguments);
72725
- };
72726
- }());
72703
+ vjsPlayer.on('error', function () {
72704
+ if (stopped) return;
72705
+ clearStallWatchdog();
72706
+ // Extract the actual MediaError so the fault message is
72707
+ // meaningful: code 2 = network, 3 = decode, 4 = not supported.
72708
+ var vjsError = vjsPlayer.error();
72709
+ var reason = vjsError ? "Video error (code ".concat(vjsError.code, "): ").concat(vjsError.message) : 'Unknown video error';
72710
+ console.warn("XLR >> VideoMedia: error on media ".concat(media.id), vjsError);
72711
+ reportAndStop(reason, exports.FaultCodes.FaultVideoUnexpected);
72712
+ });
72727
72713
  if (media.duration === 0) {
72728
72714
  vjsPlayer.on('ended', function () {
72729
72715
  console.debug("??? XLR.debug >> VideoMedia: onended: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
@@ -72733,26 +72719,37 @@ function VideoMedia(media, xlr) {
72733
72719
  }
72734
72720
  },
72735
72721
  stop: function stop() {
72722
+ var _videoPlayer$player;
72736
72723
  var disposeOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
72737
- var vjsPlayer = media.player;
72724
+ clearStallWatchdog();
72725
+ // videoPlayer.player is where init() stores the vjs instance;
72726
+ // media.player is a legacy path kept for backward compat but is
72727
+ // no longer set by init(), so always prefer videoPlayer.player.
72728
+ var vjsPlayer = (_videoPlayer$player = videoPlayer.player) !== null && _videoPlayer$player !== void 0 ? _videoPlayer$player : media.player;
72738
72729
  console.debug('??? XLR.debug >> VideoMedia::stop', {
72739
72730
  vjsPlayer: vjsPlayer,
72740
- isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed_,
72741
- el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el_
72731
+ isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed(),
72732
+ el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el()
72742
72733
  });
72743
72734
  // Expire the media and dispose the video
72744
- if (vjsPlayer !== undefined && !vjsPlayer.isDisposed_) {
72735
+ if (vjsPlayer !== undefined && !vjsPlayer.isDisposed()) {
72745
72736
  if (!disposeOnly) {
72746
72737
  media.emitter.emit('end', media);
72747
72738
  }
72748
72739
  vjsPlayer.dispose();
72749
72740
  // Clear up media player
72741
+ videoPlayer.player = undefined;
72750
72742
  media.player = undefined;
72743
+ media.html = null;
72751
72744
  } else {
72745
+ videoPlayer.player = undefined;
72752
72746
  media.player = undefined;
72753
72747
  media.html = null;
72754
- media.emitter.emit('end', media);
72748
+ if (!disposeOnly) {
72749
+ media.emitter.emit('end', media);
72750
+ }
72755
72751
  }
72752
+ stopped = true;
72756
72753
  },
72757
72754
  play: function play() {
72758
72755
  var _this2 = this;
@@ -72760,9 +72757,9 @@ function VideoMedia(media, xlr) {
72760
72757
  if (vjsPlayer !== undefined) {
72761
72758
  var _vjsPlayer$play;
72762
72759
  (_vjsPlayer$play = vjsPlayer.play()) === null || _vjsPlayer$play === void 0 || _vjsPlayer$play["catch"]( /*#__PURE__*/function () {
72763
- var _ref4 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(error) {
72764
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
72765
- while (1) switch (_context4.prev = _context4.next) {
72760
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72761
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72762
+ while (1) switch (_context2.prev = _context2.next) {
72766
72763
  case 0:
72767
72764
  if (error === 'Timeout') {
72768
72765
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
@@ -72777,12 +72774,12 @@ function VideoMedia(media, xlr) {
72777
72774
  }
72778
72775
  case 1:
72779
72776
  case "end":
72780
- return _context4.stop();
72777
+ return _context2.stop();
72781
72778
  }
72782
- }, _callee4);
72779
+ }, _callee2);
72783
72780
  }));
72784
- return function (_x3) {
72785
- return _ref4.apply(this, arguments);
72781
+ return function (_x) {
72782
+ return _ref2.apply(this, arguments);
72786
72783
  };
72787
72784
  }());
72788
72785
  }
@@ -73194,11 +73191,11 @@ function getDataBlob(_x, _x2) {
73194
73191
  return _getDataBlob.apply(this, arguments);
73195
73192
  }
73196
73193
  function _getDataBlob() {
73197
- _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, jwtToken) {
73198
- return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73199
- while (1) switch (_context3.prev = _context3.next) {
73194
+ _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(src, jwtToken) {
73195
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73196
+ while (1) switch (_context2.prev = _context2.next) {
73200
73197
  case 0:
73201
- return _context3.abrupt("return", fetch(src, {
73198
+ return _context2.abrupt("return", fetch(src, {
73202
73199
  method: 'GET',
73203
73200
  headers: {
73204
73201
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73217,9 +73214,9 @@ function _getDataBlob() {
73217
73214
  }));
73218
73215
  case 1:
73219
73216
  case "end":
73220
- return _context3.stop();
73217
+ return _context2.stop();
73221
73218
  }
73222
- }, _callee3);
73219
+ }, _callee2);
73223
73220
  }));
73224
73221
  return _getDataBlob.apply(this, arguments);
73225
73222
  }
@@ -73227,12 +73224,12 @@ function preloadMediaBlob(_x3, _x4, _x5) {
73227
73224
  return _preloadMediaBlob.apply(this, arguments);
73228
73225
  }
73229
73226
  function _preloadMediaBlob() {
73230
- _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(src, type, jwtToken) {
73227
+ _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, type, jwtToken) {
73231
73228
  var res, blob, data;
73232
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73233
- while (1) switch (_context4.prev = _context4.next) {
73229
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73230
+ while (1) switch (_context3.prev = _context3.next) {
73234
73231
  case 0:
73235
- _context4.next = 2;
73232
+ _context3.next = 2;
73236
73233
  return fetch(src, {
73237
73234
  method: 'GET',
73238
73235
  headers: {
@@ -73240,45 +73237,45 @@ function _preloadMediaBlob() {
73240
73237
  }
73241
73238
  });
73242
73239
  case 2:
73243
- res = _context4.sent;
73240
+ res = _context3.sent;
73244
73241
  blob = new Blob();
73245
73242
  if (!(type === 'image')) {
73246
- _context4.next = 8;
73243
+ _context3.next = 8;
73247
73244
  break;
73248
73245
  }
73249
73246
  blob = new Blob();
73250
- _context4.next = 19;
73247
+ _context3.next = 19;
73251
73248
  break;
73252
73249
  case 8:
73253
73250
  if (!(type === 'video')) {
73254
- _context4.next = 14;
73251
+ _context3.next = 14;
73255
73252
  break;
73256
73253
  }
73257
- _context4.next = 11;
73254
+ _context3.next = 11;
73258
73255
  return res.blob();
73259
73256
  case 11:
73260
- blob = _context4.sent;
73261
- _context4.next = 19;
73257
+ blob = _context3.sent;
73258
+ _context3.next = 19;
73262
73259
  break;
73263
73260
  case 14:
73264
73261
  if (!(type === 'audio')) {
73265
- _context4.next = 19;
73262
+ _context3.next = 19;
73266
73263
  break;
73267
73264
  }
73268
- _context4.next = 17;
73265
+ _context3.next = 17;
73269
73266
  return res.arrayBuffer();
73270
73267
  case 17:
73271
- data = _context4.sent;
73268
+ data = _context3.sent;
73272
73269
  blob = new Blob([data], {
73273
73270
  type: audioFileType(getFileExt(src))
73274
73271
  });
73275
73272
  case 19:
73276
- return _context4.abrupt("return", URL.createObjectURL(blob));
73273
+ return _context3.abrupt("return", URL.createObjectURL(blob));
73277
73274
  case 20:
73278
73275
  case "end":
73279
- return _context4.stop();
73276
+ return _context3.stop();
73280
73277
  }
73281
- }, _callee4);
73278
+ }, _callee3);
73282
73279
  }));
73283
73280
  return _preloadMediaBlob.apply(this, arguments);
73284
73281
  }
@@ -73286,11 +73283,11 @@ function fetchJSON(_x6, _x7) {
73286
73283
  return _fetchJSON.apply(this, arguments);
73287
73284
  }
73288
73285
  function _fetchJSON() {
73289
- _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73290
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73291
- while (1) switch (_context5.prev = _context5.next) {
73286
+ _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(url, jwtToken) {
73287
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73288
+ while (1) switch (_context4.prev = _context4.next) {
73292
73289
  case 0:
73293
- return _context5.abrupt("return", fetch(url, {
73290
+ return _context4.abrupt("return", fetch(url, {
73294
73291
  method: 'GET',
73295
73292
  headers: {
73296
73293
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73302,9 +73299,9 @@ function _fetchJSON() {
73302
73299
  }));
73303
73300
  case 1:
73304
73301
  case "end":
73305
- return _context5.stop();
73302
+ return _context4.stop();
73306
73303
  }
73307
- }, _callee5);
73304
+ }, _callee4);
73308
73305
  }));
73309
73306
  return _fetchJSON.apply(this, arguments);
73310
73307
  }
@@ -73312,11 +73309,11 @@ function fetchText(_x8, _x9) {
73312
73309
  return _fetchText.apply(this, arguments);
73313
73310
  }
73314
73311
  function _fetchText() {
73315
- _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(url, jwtToken) {
73316
- return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73317
- while (1) switch (_context6.prev = _context6.next) {
73312
+ _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73313
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73314
+ while (1) switch (_context5.prev = _context5.next) {
73318
73315
  case 0:
73319
- return _context6.abrupt("return", fetch(url, {
73316
+ return _context5.abrupt("return", fetch(url, {
73320
73317
  method: 'GET',
73321
73318
  headers: {
73322
73319
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73335,9 +73332,9 @@ function _fetchText() {
73335
73332
  }));
73336
73333
  case 1:
73337
73334
  case "end":
73338
- return _context6.stop();
73335
+ return _context5.stop();
73339
73336
  }
73340
- }, _callee6);
73337
+ }, _callee5);
73341
73338
  }));
73342
73339
  return _fetchText.apply(this, arguments);
73343
73340
  }
@@ -73454,6 +73451,32 @@ function setExpiry(numDays) {
73454
73451
  var today = new Date();
73455
73452
  return new Date(today.setHours(24 * numDays || 1)).toJSON();
73456
73453
  }
73454
+ /**
73455
+ * Check whether a media item is currently within its valid date window.
73456
+ * Returns true when the media should be shown, false when it should be skipped.
73457
+ *
73458
+ * Rules:
73459
+ * - Empty / invalid fromDt → treat as "no start restriction"
73460
+ * - Empty / invalid toDt → treat as "no expiry"
73461
+ * - now < fromDt → not yet active → skip
73462
+ * - now > toDt → expired → skip
73463
+ */
73464
+ function isMediaActive(fromDt, toDt) {
73465
+ var now = Date.now();
73466
+ if (fromDt) {
73467
+ var from = new Date(fromDt).getTime();
73468
+ if (!isNaN(from) && now < from) {
73469
+ return false;
73470
+ }
73471
+ }
73472
+ if (toDt) {
73473
+ var to = new Date(toDt).getTime();
73474
+ if (!isNaN(to) && now > to) {
73475
+ return false;
73476
+ }
73477
+ }
73478
+ return true;
73479
+ }
73457
73480
  /**
73458
73481
  * Check if given layout exists in the loop using layoutId
73459
73482
  * @param layouts Schedule loop unique layouts (uniqueLayouts)
@@ -73662,7 +73685,7 @@ function prepareVideoMedia(media, region) {
73662
73685
  var $layout = region.layout.html;
73663
73686
  var layoutSelector = '#' + region.layout.containerName + '[data-sequence="' + region.layout.index + '"]';
73664
73687
  var $layoutWithIndex = document.querySelector(layoutSelector);
73665
- var $region = document.querySelector('#' + region.containerName);
73688
+ var $region = region.html;
73666
73689
  var mediaInRegion = $region === null || $region === void 0 ? void 0 : $region.querySelector('.' + mediaId);
73667
73690
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73668
73691
  layoutSelector: layoutSelector,
@@ -73681,7 +73704,7 @@ function prepareVideoMedia(media, region) {
73681
73704
  media.html = createMediaElement(media);
73682
73705
  }
73683
73706
  // Append fresh copy of the media into the region
73684
- $region !== null && $region.appendChild(media.html);
73707
+ region.html.appendChild(media.html);
73685
73708
  var isMediaInDOM = document.body.contains(media.html);
73686
73709
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73687
73710
  isMediaInDOM: isMediaInDOM,
@@ -73690,29 +73713,9 @@ function prepareVideoMedia(media, region) {
73690
73713
  });
73691
73714
  // Initialize video.js
73692
73715
  media.player = videojs(mediaId, _objectSpread2(_objectSpread2({}, defaultVjsOpts), {}, {
73693
- errorDisplay: region.xlr.config.platform !== exports.ConsumerPlatform.CHROMEOS,
73716
+ errorDisplay: !reportToPlayerPlatform.includes(region.xlr.config.platform),
73694
73717
  loop: media.loop
73695
73718
  }));
73696
- media.player.on('error', /*#__PURE__*/function () {
73697
- var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(err) {
73698
- return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73699
- while (1) switch (_context2.prev = _context2.next) {
73700
- case 0:
73701
- if (media.region.xlr.config.platform === exports.ConsumerPlatform.CHROMEOS) {
73702
- playerReportFault('Video file not supported', media).then(function () {
73703
- media.emitter.emit('end', media);
73704
- });
73705
- }
73706
- case 1:
73707
- case "end":
73708
- return _context2.stop();
73709
- }
73710
- }, _callee2);
73711
- }));
73712
- return function (_x10) {
73713
- return _ref3.apply(this, arguments);
73714
- };
73715
- }());
73716
73719
  media.player.el().style.setProperty('visibility', 'hidden');
73717
73720
  media.player.el().style.setProperty('opacity', '0');
73718
73721
  media.player.el().style.setProperty('z-index', '-99');
@@ -73727,9 +73730,9 @@ function prepareImageMedia(media, region) {
73727
73730
  if (mediaInRegion) {
73728
73731
  mediaInRegion.remove();
73729
73732
  }
73730
- // Append media to its region
73731
- var $region = document.querySelector('#' + region.containerName);
73732
- $region !== null && $region.appendChild(media.html);
73733
+ // Append media to its region using the direct reference to avoid
73734
+ // global querySelector finding a same-named region in another layout
73735
+ region.html.appendChild(media.html);
73733
73736
  }
73734
73737
  function prepareAudioMedia(media, region) {
73735
73738
  var mediaId = getMediaId(media);
@@ -73742,9 +73745,8 @@ function prepareAudioMedia(media, region) {
73742
73745
  if (mediaInRegion) {
73743
73746
  mediaInRegion.remove();
73744
73747
  }
73745
- // Append media to its region
73746
- var $region = document.querySelector('#' + region.containerName);
73747
- $region !== null && $region.appendChild(media.html);
73748
+ // Append media to its region using the direct reference
73749
+ region.html.appendChild(media.html);
73748
73750
  }
73749
73751
  function prepareHtmlMedia(media, region) {
73750
73752
  // Set state as false ( for now )
@@ -73764,57 +73766,92 @@ function prepareHtmlMedia(media, region) {
73764
73766
  media.html.innerHTML = '';
73765
73767
  media.html.appendChild(media.iframe);
73766
73768
  if (!mediaInRegion) {
73767
- // Add fresh copy of the media into the region
73768
- var _$region = document.querySelector('#' + region.containerName);
73769
- _$region !== null && _$region.appendChild(media.html);
73769
+ // Add fresh copy of the media into the region using the direct reference
73770
+ region.html.appendChild(media.html);
73770
73771
  media.ready = true;
73771
73772
  }
73772
73773
  }
73773
73774
  }
73774
- function playerReportFault(_x11, _x12) {
73775
+ exports.FaultCodes = void 0;
73776
+ (function (FaultCodes) {
73777
+ FaultCodes[FaultCodes["FaultVideoSource"] = 2001] = "FaultVideoSource";
73778
+ FaultCodes[FaultCodes["FaultVideoUnexpected"] = 2099] = "FaultVideoUnexpected";
73779
+ })(exports.FaultCodes || (exports.FaultCodes = {}));
73780
+ function playerReportFault(_x10, _x11) {
73775
73781
  return _playerReportFault.apply(this, arguments);
73776
73782
  }
73777
73783
  function _playerReportFault() {
73778
- _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee7(msg, media) {
73779
- var playerSW, hasSW;
73780
- return _regeneratorRuntime().wrap(function _callee7$(_context7) {
73781
- while (1) switch (_context7.prev = _context7.next) {
73784
+ _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(msg, media) {
73785
+ var code,
73786
+ platform,
73787
+ playerSW,
73788
+ hasSW,
73789
+ mediaFault,
73790
+ channel,
73791
+ _args6 = arguments;
73792
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73793
+ while (1) switch (_context6.prev = _context6.next) {
73782
73794
  case 0:
73795
+ code = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : exports.FaultCodes.FaultVideoUnexpected;
73783
73796
  // Immediately expire media and report a fault
73797
+ platform = media.region.xlr.config.platform;
73784
73798
  playerSW = PwaSW();
73785
- _context7.next = 3;
73799
+ _context6.next = 5;
73786
73800
  return playerSW.getSW();
73787
- case 3:
73788
- hasSW = _context7.sent;
73789
- if (hasSW) {
73790
- playerSW.postMsg({
73791
- type: 'MEDIA_FAULT',
73792
- code: 5002,
73793
- reason: msg,
73801
+ case 5:
73802
+ hasSW = _context6.sent;
73803
+ mediaFault = {
73804
+ type: 'MEDIA_FAULT',
73805
+ code: code,
73806
+ reason: msg,
73807
+ mediaId: media.id,
73808
+ regionId: media.region.id,
73809
+ layoutId: media.region.layout.id,
73810
+ date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73811
+ // Temporary setting
73812
+ expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73813
+ };
73814
+ console.debug('playerReportFault >> Reporting media fault', {
73815
+ mediaFault: mediaFault,
73816
+ platform: platform,
73817
+ hasSW: hasSW
73818
+ });
73819
+ if (!(platform === exports.ConsumerPlatform.CHROMEOS && hasSW)) {
73820
+ _context6.next = 12;
73821
+ break;
73822
+ }
73823
+ return _context6.abrupt("return", playerSW.postMsg(mediaFault).then(function () {
73824
+ // We try to prepare next media if we have more than 1 media
73825
+ if (media.region.totalMediaObjects > 1) {
73826
+ media.region.prepareNextMedia();
73827
+ }
73828
+ })["finally"](function () {
73829
+ // Stopping media as we have reported the error as fault
73830
+ console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73794
73831
  mediaId: media.id,
73795
- regionId: media.region.id,
73796
- layoutId: media.region.layout.id,
73797
- date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73798
- // Temporary setting
73799
- expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73800
- }).then(function () {
73801
- // We try to prepare next media if we have more than 1 media
73802
- if (media.region.totalMediaObjects > 1) {
73803
- media.region.prepareNextMedia();
73804
- }
73805
- })["finally"](function () {
73806
- // Stopping media as we have reported the error as fault
73807
- console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73808
- mediaId: media.id,
73809
- regionItems: media.region.totalMediaObjects
73810
- });
73832
+ regionItems: media.region.totalMediaObjects
73811
73833
  });
73834
+ }));
73835
+ case 12:
73836
+ if (!(platform === exports.ConsumerPlatform.ELECTRON)) {
73837
+ _context6.next = 17;
73838
+ break;
73812
73839
  }
73813
- case 5:
73840
+ // Create a broadcast channel to report media fault to the main process
73841
+ channel = new BroadcastChannel('player-faults-bc');
73842
+ channel.postMessage(mediaFault);
73843
+ console.debug('playerReportFault >> Electron platform - posted media fault to channel', {
73844
+ mediaFault: mediaFault
73845
+ });
73846
+ // channel.close();
73847
+ return _context6.abrupt("return", Promise.resolve());
73848
+ case 17:
73849
+ return _context6.abrupt("return", Promise.resolve());
73850
+ case 18:
73814
73851
  case "end":
73815
- return _context7.stop();
73852
+ return _context6.stop();
73816
73853
  }
73817
- }, _callee7);
73854
+ }, _callee6);
73818
73855
  }));
73819
73856
  return _playerReportFault.apply(this, arguments);
73820
73857
  }
@@ -73842,7 +73879,7 @@ let nanoid = (size = 21) => {
73842
73879
  function AudioMedia(media) {
73843
73880
  var audioMediaObject = {
73844
73881
  init: function init() {
73845
- var $audioMedia = document.getElementById(getMediaId(media));
73882
+ var $audioMedia = media.html;
73846
73883
  var $playBtn = null;
73847
73884
  if ($audioMedia) {
73848
73885
  $audioMedia.onloadstart = function () {
@@ -73905,6 +73942,8 @@ var Media = /*#__PURE__*/function () {
73905
73942
  _this$xml3,
73906
73943
  _this$xml4,
73907
73944
  _this$xml5,
73945
+ _this$xml6,
73946
+ _this$xml7,
73908
73947
  _this = this;
73909
73948
  _classCallCheck(this, Media);
73910
73949
  _defineProperty(this, "attachedAudio", false);
@@ -73917,6 +73956,7 @@ var Media = /*#__PURE__*/function () {
73917
73956
  _defineProperty(this, "enableStat", false);
73918
73957
  _defineProperty(this, "fileId", '');
73919
73958
  _defineProperty(this, "finished", false);
73959
+ _defineProperty(this, "fromDt", '');
73920
73960
  _defineProperty(this, "html", null);
73921
73961
  _defineProperty(this, "id", '');
73922
73962
  _defineProperty(this, "idCounter", 0);
@@ -73938,6 +73978,7 @@ var Media = /*#__PURE__*/function () {
73938
73978
  _defineProperty(this, "state", MediaState.IDLE);
73939
73979
  _defineProperty(this, "tempSrc", '');
73940
73980
  _defineProperty(this, "timeoutId", setTimeout(function () {}, 0));
73981
+ _defineProperty(this, "toDt", '');
73941
73982
  _defineProperty(this, "type", '');
73942
73983
  _defineProperty(this, "uri", '');
73943
73984
  _defineProperty(this, "url", null);
@@ -73964,12 +74005,14 @@ var Media = /*#__PURE__*/function () {
73964
74005
  this.duration = parseInt((_this$xml4 = this.xml) === null || _this$xml4 === void 0 ? void 0 : _this$xml4.getAttribute('duration')) || 0;
73965
74006
  this.enableStat = Boolean(((_this$xml5 = this.xml) === null || _this$xml5 === void 0 ? void 0 : _this$xml5.getAttribute('enableStat')) || false);
73966
74007
  this.hasCommandExecuted = false;
74008
+ this.fromDt = ((_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getAttribute('fromDt')) || '';
74009
+ this.toDt = ((_this$xml7 = this.xml) === null || _this$xml7 === void 0 ? void 0 : _this$xml7.getAttribute('toDt')) || '';
73967
74010
  this.on('start', function (media) {
73968
74011
  if (media.state === MediaState.PLAYING) return;
73969
74012
  media.state = MediaState.PLAYING;
73970
74013
  if (media.mediaType === 'video') {
73971
- var videoMedia = VideoMedia(media, _this.xlr);
73972
- videoMedia.init();
74014
+ media.videoHandler = VideoMedia(media, _this.xlr);
74015
+ media.videoHandler.init();
73973
74016
  if (media.duration > 0) {
73974
74017
  _this.startMediaTimer(media);
73975
74018
  }
@@ -74096,8 +74139,8 @@ var Media = /*#__PURE__*/function () {
74096
74139
  if (media.mediaType === 'video') {
74097
74140
  // Dispose the video media
74098
74141
  console.debug("??? XLR.debug >> VideoMedia::stop - ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
74099
- if (media.player !== undefined) {
74100
- VideoMedia(media, _this2.xlr).stop(true);
74142
+ if (media.videoHandler !== undefined) {
74143
+ media.videoHandler.stop(true);
74101
74144
  }
74102
74145
  }
74103
74146
  }
@@ -74112,8 +74155,8 @@ var Media = /*#__PURE__*/function () {
74112
74155
  }, {
74113
74156
  key: "init",
74114
74157
  value: function init() {
74115
- var _this$xml6;
74116
- var mediaOptions = (_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getElementsByTagName('options');
74158
+ var _this$xml8;
74159
+ var mediaOptions = (_this$xml8 = this.xml) === null || _this$xml8 === void 0 ? void 0 : _this$xml8.getElementsByTagName('options');
74117
74160
  if (mediaOptions) {
74118
74161
  for (var _i = 0, _Array$from = Array.from(mediaOptions); _i < _Array$from.length; _i++) {
74119
74162
  var _options = _Array$from[_i];
@@ -74168,6 +74211,10 @@ var Media = /*#__PURE__*/function () {
74168
74211
  }
74169
74212
  } else if (this.xlr.config.platform === exports.ConsumerPlatform.ELECTRON) {
74170
74213
  tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74214
+ // this is an SSP Layout
74215
+ if (this.region.layout.layoutId === -1) {
74216
+ tmpUrl = this.uri;
74217
+ }
74171
74218
  }
74172
74219
  this.url = tmpUrl;
74173
74220
  // Loop if media has loop, or if region has loop and a single media
@@ -74210,8 +74257,8 @@ var Media = /*#__PURE__*/function () {
74210
74257
  mediaType: _this3.mediaType,
74211
74258
  containerName: _this3.containerName
74212
74259
  });
74213
- var $region = document.querySelector('#' + _this3.region.containerName);
74214
- var $media = $region !== null && $region.querySelector('.' + mediaId);
74260
+ var $region = _this3.region.html;
74261
+ var $media = $region.querySelector('.' + mediaId);
74215
74262
  if (!$media) {
74216
74263
  $media = getNewMedia();
74217
74264
  }
@@ -74275,13 +74322,13 @@ var Media = /*#__PURE__*/function () {
74275
74322
  }
74276
74323
  };
74277
74324
  var getNewMedia = function getNewMedia() {
74278
- var $region = document.getElementById("".concat(_this3.region.containerName));
74325
+ var $region = _this3.region.html;
74279
74326
  // This function is for checking whether
74280
74327
  // the region still has to show a media item
74281
74328
  // when another region is not finished yet
74282
74329
  if (_this3.region.complete && !_this3.region.layout.allEnded) {
74283
74330
  // Add currentMedia to the region
74284
- $region && $region.insertBefore(_this3.html, $region.lastElementChild);
74331
+ $region.insertBefore(_this3.html, $region.lastElementChild);
74285
74332
  return _this3.html;
74286
74333
  }
74287
74334
  return null;
@@ -74292,23 +74339,18 @@ var Media = /*#__PURE__*/function () {
74292
74339
  key: "stop",
74293
74340
  value: function () {
74294
74341
  var _stop = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
74295
- var $media;
74296
74342
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74297
74343
  while (1) switch (_context.prev = _context.next) {
74298
74344
  case 0:
74299
- $media = document.getElementById(getMediaId({
74300
- mediaType: this.mediaType,
74301
- containerName: this.containerName
74302
- }));
74303
- if ($media) {
74304
- $media.style.display = 'none';
74305
- $media.remove();
74345
+ if (this.html) {
74346
+ this.html.style.display = 'none';
74347
+ this.html.remove();
74306
74348
  }
74307
74349
  // Release blob URLs for image media to prevent memory leaks on long-running signage
74308
74350
  if (this.mediaType === 'image' && this.url) {
74309
74351
  BlobLoader.release(this.url);
74310
74352
  }
74311
- case 3:
74353
+ case 2:
74312
74354
  case "end":
74313
74355
  return _context.stop();
74314
74356
  }
@@ -74481,18 +74523,30 @@ var Region = /*#__PURE__*/function () {
74481
74523
  this.html = $region;
74482
74524
  /* Parse region media objects */
74483
74525
  var regionMediaItems = Array.from(this.xml.getElementsByTagName('media'));
74484
- this.totalMediaObjects = regionMediaItems.length;
74485
74526
  $layout && $layout.appendChild(this.html);
74486
74527
  Array.from(regionMediaItems).forEach( /*#__PURE__*/function () {
74487
74528
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(mediaXml, indx) {
74488
- var mediaObj;
74529
+ var fromDt, toDt, mediaObj;
74489
74530
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74490
74531
  while (1) switch (_context.prev = _context.next) {
74491
74532
  case 0:
74533
+ fromDt = mediaXml.getAttribute('fromDt') || '';
74534
+ toDt = mediaXml.getAttribute('toDt') || '';
74535
+ if (isMediaActive(fromDt, toDt)) {
74536
+ _context.next = 5;
74537
+ break;
74538
+ }
74539
+ console.debug('??? XLR.debug >> Region::prepareRegion - skipping expired/inactive media', {
74540
+ mediaId: mediaXml.getAttribute('id'),
74541
+ fromDt: fromDt,
74542
+ toDt: toDt
74543
+ });
74544
+ return _context.abrupt("return");
74545
+ case 5:
74492
74546
  mediaObj = new Media(_this, (mediaXml === null || mediaXml === void 0 ? void 0 : mediaXml.getAttribute('id')) || '', mediaXml, _this.options, _this.xlr);
74493
74547
  mediaObj.index = indx;
74494
74548
  _this.mediaObjects.push(mediaObj);
74495
- case 3:
74549
+ case 8:
74496
74550
  case "end":
74497
74551
  return _context.stop();
74498
74552
  }
@@ -74502,6 +74556,7 @@ var Region = /*#__PURE__*/function () {
74502
74556
  return _ref.apply(this, arguments);
74503
74557
  };
74504
74558
  }());
74559
+ this.totalMediaObjects = this.mediaObjects.length;
74505
74560
  console.debug('??? XLR.debug >> Region - done looping through media', {
74506
74561
  mediaObjects: this.mediaObjects
74507
74562
  });
@@ -74557,6 +74612,23 @@ var Region = /*#__PURE__*/function () {
74557
74612
  key: "prepareNextMedia",
74558
74613
  value: function prepareNextMedia() {
74559
74614
  var nextMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74615
+ // Skip over any media items that are no longer within their active date window
74616
+ var skippedCount = 0;
74617
+ while (skippedCount < this.totalMediaObjects) {
74618
+ var candidate = this.mediaObjects[nextMediaIndex];
74619
+ if (isMediaActive(candidate.fromDt, candidate.toDt)) {
74620
+ break;
74621
+ }
74622
+ skippedCount++;
74623
+ nextMediaIndex = (nextMediaIndex + 1) % this.totalMediaObjects;
74624
+ }
74625
+ // Nothing to pre-load when:
74626
+ // - every item is expired, OR
74627
+ // - the only active item is the one currently playing (skip loop wrapped back to it)
74628
+ if (skippedCount >= this.totalMediaObjects || nextMediaIndex === this.currentMediaIndex) {
74629
+ console.debug('<><> XLR.debug >> [Region::prepareNextMedia()] - no active next media to preload');
74630
+ return;
74631
+ }
74560
74632
  var nextMedia = this.mediaObjects[nextMediaIndex];
74561
74633
  console.debug('<><> XLR.debug >> [Media] - [Region::prepareNextMedia()] - Preparing next media', {
74562
74634
  currentMediaIndex: this.currentMediaIndex,
@@ -74597,9 +74669,21 @@ var Region = /*#__PURE__*/function () {
74597
74669
  }, {
74598
74670
  key: "run",
74599
74671
  value: function run() {
74672
+ var _this$currMedia, _this$oldMedia2;
74600
74673
  console.debug('??? XLR.debug >> Region Called Region::run > ', this.id);
74601
74674
  // Reset region states
74602
74675
  this.reset();
74676
+ // All media were filtered out (all expired/inactive before this run started)
74677
+ if (this.mediaObjects.length === 0) {
74678
+ console.debug('??? XLR.debug >> Region::run - no active media, finishing region', this.id);
74679
+ this.finished();
74680
+ return;
74681
+ }
74682
+ console.debug('??? XLR.debug >> Region Called Region::run - after reset > ', {
74683
+ regionId: this.id,
74684
+ currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74685
+ oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName
74686
+ });
74603
74687
  if (this.currMedia) {
74604
74688
  this.transitionNodes(this.oldMedia, this.currMedia);
74605
74689
  }
@@ -74648,6 +74732,7 @@ var Region = /*#__PURE__*/function () {
74648
74732
  // Hide oldMedia
74649
74733
  if (oldMedia) {
74650
74734
  var $layout = document.querySelector("#".concat(_this2.layout.containerName, "[data-sequence=\"").concat(_this2.layout.index, "\"]"));
74735
+ if (!$layout) return;
74651
74736
  var $region = $layout.querySelector('#' + _this2.containerName);
74652
74737
  var $oldMedia = $region ? $region.querySelector('.' + getMediaId(oldMedia)) : null;
74653
74738
  if ($oldMedia) {
@@ -74668,7 +74753,7 @@ var Region = /*#__PURE__*/function () {
74668
74753
  $videoWrapper.style.setProperty('visibility', 'hidden');
74669
74754
  $videoWrapper.style.setProperty('z-index', '-999');
74670
74755
  $videoWrapper.style.setProperty('opacity', '0');
74671
- if (oldMedia.player && oldMedia.videoHandler) {
74756
+ if (oldMedia.videoHandler) {
74672
74757
  oldMedia.videoHandler.stop(true);
74673
74758
  }
74674
74759
  }
@@ -74728,13 +74813,13 @@ var Region = /*#__PURE__*/function () {
74728
74813
  }, {
74729
74814
  key: "playNextMedia",
74730
74815
  value: function playNextMedia() {
74731
- var _this$oldMedia2, _this$currMedia, _this$nxtMedia, _this$currMedia2, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia3, _this$currMedia7, _this$nxtMedia2;
74816
+ var _this$oldMedia3, _this$currMedia2, _this$nxtMedia, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia4, _this$currMedia7, _this$nxtMedia2;
74732
74817
  console.debug('??? XLR.debug Region playing next media', {
74733
74818
  regionId: this.id,
74734
74819
  currentMediaIndex: this.currentMediaIndex,
74735
74820
  mediaItemsLn: this.mediaObjects.length,
74736
- oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName,
74737
- currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74821
+ oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74822
+ currMedia: (_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.containerName,
74738
74823
  nxtMedia: (_this$nxtMedia = this.nxtMedia) === null || _this$nxtMedia === void 0 ? void 0 : _this$nxtMedia.containerName
74739
74824
  });
74740
74825
  /* The current media has finished running */
@@ -74744,33 +74829,20 @@ var Region = /*#__PURE__*/function () {
74744
74829
  });
74745
74830
  return;
74746
74831
  }
74747
- // Are we in a playlist, and has the playlist completed a full cycle?
74748
- var isLastMediaInPlaylist = this.currentMediaIndex === this.mediaObjects.length - 1 && this.mediaObjects.length > 1;
74749
- // If yes, enable shell command widgets again (if any), so they execute on the next playlist cycle
74750
- if (isLastMediaInPlaylist) {
74751
- this.mediaObjects.forEach(function (media) {
74752
- if (media.mediaType === 'shellcommand') {
74753
- // reset per-playlist-cycle execution state
74754
- media.hasCommandExecuted = false;
74755
- }
74756
- });
74757
- }
74758
- if (!this.layout.isOverlay && this.currentMediaIndex === this.mediaObjects.length - 1) {
74759
- this.finished();
74760
- if (this.layout.allEnded) {
74761
- console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74762
- return;
74763
- }
74764
- }
74832
+ // Snapshot the index of the media that just ended so we can detect
74833
+ // cycle completion after the skip loop runs.
74834
+ var origIndex = this.currentMediaIndex;
74765
74835
  // When the region has completed and when currentMedia is html
74766
74836
  // Then, preserve the currentMedia state
74767
- if (this.complete && ((_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.render) === 'html') {
74837
+ if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html') {
74768
74838
  return;
74769
74839
  }
74770
74840
  // When the region has completed and mediaObjects.length = 1
74771
- // and curMedia.loop = false, then put the media on
74772
- // its current state
74773
- if (this.complete && this.mediaObjects.length === 1 && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) !== 'html' && (((_this$currMedia4 = this.currMedia) === null || _this$currMedia4 === void 0 ? void 0 : _this$currMedia4.mediaType) === 'image' || ((_this$currMedia5 = this.currMedia) === null || _this$currMedia5 === void 0 ? void 0 : _this$currMedia5.mediaType) === 'video') && !((_this$currMedia6 = this.currMedia) !== null && _this$currMedia6 !== void 0 && _this$currMedia6.loop)) {
74841
+ // and render is not html, preserve the current media state without
74842
+ // calling transitionNodes (which would remove the media from DOM for 1s).
74843
+ // Do NOT restart the media timer here the layout will end naturally when
74844
+ // regionExpired() detects all regions complete on the next cycle.
74845
+ if (this.complete && this.mediaObjects.length === 1 && ((_this$currMedia4 = this.currMedia) === null || _this$currMedia4 === void 0 ? void 0 : _this$currMedia4.render) !== 'html' && (((_this$currMedia5 = this.currMedia) === null || _this$currMedia5 === void 0 ? void 0 : _this$currMedia5.mediaType) === 'image' || ((_this$currMedia6 = this.currMedia) === null || _this$currMedia6 === void 0 ? void 0 : _this$currMedia6.mediaType) === 'video')) {
74774
74846
  return;
74775
74847
  }
74776
74848
  if (this.currMedia) {
@@ -74778,17 +74850,51 @@ var Region = /*#__PURE__*/function () {
74778
74850
  } else {
74779
74851
  this.oldMedia = undefined;
74780
74852
  }
74781
- this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74853
+ this.currentMediaIndex = (origIndex + 1) % this.totalMediaObjects;
74782
74854
  this.currMedia = this.mediaObjects[this.currentMediaIndex];
74855
+ // Skip media items that are no longer within their active date window
74856
+ var skippedCount = 0;
74857
+ while (this.currMedia && !isMediaActive(this.currMedia.fromDt, this.currMedia.toDt)) {
74858
+ skippedCount++;
74859
+ if (skippedCount >= this.totalMediaObjects) {
74860
+ // Every item in the playlist has expired; finish this region
74861
+ console.debug('??? XLR.debug >> Region::playNextMedia - all media expired, finishing region', this.id);
74862
+ this.finished();
74863
+ return;
74864
+ }
74865
+ this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74866
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
74867
+ }
74783
74868
  this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
74869
+ // A full playlist cycle has been traversed when the total advancement
74870
+ // (the initial +1 step plus any skips over expired items) reaches or
74871
+ // crosses the end of the array.
74872
+ var crossedEnd = origIndex + 1 + skippedCount >= this.totalMediaObjects;
74873
+ // Re-enable shell command widgets at the end of each full cycle so they
74874
+ // execute again on the next pass.
74875
+ if (crossedEnd && this.mediaObjects.length > 1) {
74876
+ this.mediaObjects.forEach(function (media) {
74877
+ if (media.mediaType === 'shellcommand') {
74878
+ // reset per-playlist-cycle execution state
74879
+ media.hasCommandExecuted = false;
74880
+ }
74881
+ });
74882
+ }
74784
74883
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74785
74884
  regionId: this.id,
74786
74885
  currentMediaIndex: this.currentMediaIndex,
74787
74886
  mediaItemsLn: this.mediaObjects.length,
74788
- oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74887
+ oldMedia: (_this$oldMedia4 = this.oldMedia) === null || _this$oldMedia4 === void 0 ? void 0 : _this$oldMedia4.containerName,
74789
74888
  currMedia: (_this$currMedia7 = this.currMedia) === null || _this$currMedia7 === void 0 ? void 0 : _this$currMedia7.containerName,
74790
74889
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74791
74890
  });
74891
+ if (!this.layout.isOverlay && crossedEnd) {
74892
+ this.finished();
74893
+ if (this.layout.allEnded) {
74894
+ console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74895
+ return;
74896
+ }
74897
+ }
74792
74898
  this.transitionNodes(this.oldMedia, this.currMedia);
74793
74899
  }
74794
74900
  }, {
@@ -74817,8 +74923,7 @@ var Region = /*#__PURE__*/function () {
74817
74923
  }, {
74818
74924
  key: "exitTransition",
74819
74925
  value: function exitTransition() {
74820
- /* TODO: Actually implement region exit transitions */
74821
- document.getElementById("".concat(this.containerName));
74926
+ /* TODO: Actually implement region exit transitions using this.html */
74822
74927
  console.debug('Called Region::exitTransition ', this.id);
74823
74928
  this.exitTransitionComplete();
74824
74929
  }
@@ -75118,7 +75223,7 @@ var ActionController = /*#__PURE__*/function () {
75118
75223
  if (dataset.source === 'region') {
75119
75224
  // Try to find the region
75120
75225
  if (regionObj.id === dataset.sourceid) {
75121
- $sourceObj = document.getElementById(regionObj.containerName);
75226
+ $sourceObj = regionObj.html;
75122
75227
  break;
75123
75228
  }
75124
75229
  } else if (dataset.source === 'widget') {
@@ -75127,7 +75232,7 @@ var ActionController = /*#__PURE__*/function () {
75127
75232
  for (var _i2 = 0, _mediaObjects = mediaObjects; _i2 < _mediaObjects.length; _i2++) {
75128
75233
  var mediaObject = _mediaObjects[_i2];
75129
75234
  if (mediaObject.id === dataset.sourceid) {
75130
- $sourceObj = document.getElementById(mediaObject.containerName);
75235
+ $sourceObj = mediaObject.html;
75131
75236
  break;
75132
75237
  }
75133
75238
  }
@@ -75392,7 +75497,11 @@ var Layout = /*#__PURE__*/function () {
75392
75497
  this.on('start', function (layout) {
75393
75498
  layout.done = false;
75394
75499
  layout.state = exports.ELayoutState.RUNNING;
75395
- console.debug('>>>> XLR.debug Layout start emitted > Layout ID > ', layout.id);
75500
+ console.debug('>>>> XLR.debug Layout start emitted > Layout > ', {
75501
+ layoutId: layout.id,
75502
+ layoutIndex: layout.index,
75503
+ layoutState: layout.state
75504
+ });
75396
75505
  // Check if stats are enabled for the layout
75397
75506
  if (layout.enableStat) {
75398
75507
  _this.statsBC.postMessage({
@@ -75413,12 +75522,21 @@ var Layout = /*#__PURE__*/function () {
75413
75522
  while (1) switch (_context2.prev = _context2.next) {
75414
75523
  case 0:
75415
75524
  if (!(layout.state === exports.ELayoutState.CANCELLED)) {
75416
- _context2.next = 2;
75525
+ _context2.next = 3;
75417
75526
  break;
75418
75527
  }
75528
+ console.debug('>>>> XLR.debug Layout end emitted but layout is already cancelled > Layout ID > ', {
75529
+ layoutId: layout.id,
75530
+ layoutIndex: layout.index,
75531
+ layoutState: layout.state
75532
+ });
75419
75533
  return _context2.abrupt("return");
75420
- case 2:
75421
- console.debug('>>>> XLR.debug Ending layout with ID of > ', layout.layoutId);
75534
+ case 3:
75535
+ console.debug('>>>> XLR.debug Ending layout', {
75536
+ layoutId: layout.id,
75537
+ layoutIndex: layout.index,
75538
+ layoutState: layout.state
75539
+ });
75422
75540
  /* Remove layout that has ended */
75423
75541
  $layout = document.querySelector("#".concat(layout.containerName, "[data-sequence=\"").concat(layout.index, "\"]")); // Only update layout.state when last state === RUNNING
75424
75542
  if (layout.state === exports.ELayoutState.RUNNING) {
@@ -75429,6 +75547,8 @@ var Layout = /*#__PURE__*/function () {
75429
75547
  console.debug('>>> XLR.debug Layout end emitted > Layout ID > ', {
75430
75548
  $layout: $layout,
75431
75549
  layoutId: layout.id,
75550
+ layoutIndex: layout.index,
75551
+ layoutState: layout.state,
75432
75552
  isOverlay: layout.isOverlay,
75433
75553
  isInterrupt: layout.isInterrupt()
75434
75554
  });
@@ -75455,9 +75575,9 @@ var Layout = /*#__PURE__*/function () {
75455
75575
  }
75456
75576
  // Emit layout end event
75457
75577
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75458
- _context2.next = 12;
75578
+ _context2.next = 13;
75459
75579
  return layout.xlr.emitSync('layoutEnd', layout);
75460
- case 12:
75580
+ case 13:
75461
75581
  if (_this.xlr.config.platform !== exports.ConsumerPlatform.CMS && layout.inLoop) {
75462
75582
  // Transition next layout to current layout and prepare next layout if exist
75463
75583
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75487,7 +75607,7 @@ var Layout = /*#__PURE__*/function () {
75487
75607
  };
75488
75608
  }());
75489
75609
  }
75490
- case 13:
75610
+ case 14:
75491
75611
  case "end":
75492
75612
  return _context2.stop();
75493
75613
  }
@@ -75500,6 +75620,34 @@ var Layout = /*#__PURE__*/function () {
75500
75620
  this.on('cancelled', function (layout) {
75501
75621
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75502
75622
  layout.state = exports.ELayoutState.CANCELLED;
75623
+ layout.inLoop = false;
75624
+ // Dispose video handlers immediately so their stall watchdogs and error
75625
+ // callbacks can't fire against a layout whose DOM is about to be removed.
75626
+ var _iterator = _createForOfIteratorHelper(layout.regions),
75627
+ _step;
75628
+ try {
75629
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
75630
+ var region = _step.value;
75631
+ var _iterator2 = _createForOfIteratorHelper(region.mediaObjects),
75632
+ _step2;
75633
+ try {
75634
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75635
+ var media = _step2.value;
75636
+ if (media.videoHandler) {
75637
+ media.videoHandler.stop(true);
75638
+ }
75639
+ }
75640
+ } catch (err) {
75641
+ _iterator2.e(err);
75642
+ } finally {
75643
+ _iterator2.f();
75644
+ }
75645
+ }
75646
+ } catch (err) {
75647
+ _iterator.e(err);
75648
+ } finally {
75649
+ _iterator.f();
75650
+ }
75503
75651
  });
75504
75652
  }
75505
75653
  return _createClass(Layout, [{
@@ -75641,6 +75789,14 @@ var Layout = /*#__PURE__*/function () {
75641
75789
  if ($splashScreen) {
75642
75790
  $splashScreen.style.display = 'none';
75643
75791
  }
75792
+ // Check if $layoutContainer is still in the DOM before trying to play regions, as the layout may have been removed while waiting for a long-running region to finish.
75793
+ console.debug('??? XLR.debug >> Layout::run() - Checking if layout container is still in the DOM before playing regions...', {
75794
+ layoutId: this.id,
75795
+ layoutContainerExists: !!$layoutContainer,
75796
+ $layoutContainer: $layoutContainer,
75797
+ layoutIndex: this.index,
75798
+ shouldParse: false
75799
+ });
75644
75800
  if ($layoutContainer) {
75645
75801
  $layoutContainer.style.setProperty('visibility', 'visible');
75646
75802
  $layoutContainer.style.setProperty('opacity', '1');
@@ -75671,19 +75827,19 @@ var Layout = /*#__PURE__*/function () {
75671
75827
  key: "regionExpired",
75672
75828
  value: function regionExpired() {
75673
75829
  this.allExpired = true;
75674
- var _iterator = _createForOfIteratorHelper(this.regions),
75675
- _step;
75830
+ var _iterator3 = _createForOfIteratorHelper(this.regions),
75831
+ _step3;
75676
75832
  try {
75677
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
75678
- var layoutRegion = _step.value;
75833
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
75834
+ var layoutRegion = _step3.value;
75679
75835
  if (!layoutRegion.complete) {
75680
75836
  this.allExpired = false;
75681
75837
  }
75682
75838
  }
75683
75839
  } catch (err) {
75684
- _iterator.e(err);
75840
+ _iterator3.e(err);
75685
75841
  } finally {
75686
- _iterator.f();
75842
+ _iterator3.f();
75687
75843
  }
75688
75844
  if (this.allExpired) {
75689
75845
  this.end();
@@ -75694,17 +75850,17 @@ var Layout = /*#__PURE__*/function () {
75694
75850
  value: function end() {
75695
75851
  console.debug('Executing Layout::end and Calling Region::end ', this);
75696
75852
  /* Ask the layout to gracefully stop running now */
75697
- var _iterator2 = _createForOfIteratorHelper(this.regions),
75698
- _step2;
75853
+ var _iterator4 = _createForOfIteratorHelper(this.regions),
75854
+ _step4;
75699
75855
  try {
75700
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75701
- var layoutRegion = _step2.value;
75856
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
75857
+ var layoutRegion = _step4.value;
75702
75858
  layoutRegion.end();
75703
75859
  }
75704
75860
  } catch (err) {
75705
- _iterator2.e(err);
75861
+ _iterator4.e(err);
75706
75862
  } finally {
75707
- _iterator2.f();
75863
+ _iterator4.f();
75708
75864
  }
75709
75865
  }
75710
75866
  }, {
@@ -75717,7 +75873,11 @@ var Layout = /*#__PURE__*/function () {
75717
75873
  }
75718
75874
  }
75719
75875
  if (this.allEnded) {
75720
- console.debug('starting to end layout . . .');
75876
+ console.debug('starting to end layout . . .', {
75877
+ layoutId: this.layoutId,
75878
+ layoutIndex: this.index,
75879
+ layoutState: this.state
75880
+ });
75721
75881
  if (this.xlr.config.platform === exports.ConsumerPlatform.CMS) {
75722
75882
  var $end = document.getElementById('play_ended');
75723
75883
  var $preview = document.getElementById('screen_container');
@@ -75816,6 +75976,42 @@ var Layout = /*#__PURE__*/function () {
75816
75976
  (_$layout$parentElemen2 = $layout.parentElement) === null || _$layout$parentElemen2 === void 0 || _$layout$parentElemen2.removeChild($layout);
75817
75977
  }
75818
75978
  }
75979
+ }, {
75980
+ key: "discardLayout",
75981
+ value: function discardLayout() {
75982
+ var caller = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : exports.LayoutPlaybackType.NEXT;
75983
+ // Dispose any video.js players that were initialized during prepareVideoMedia
75984
+ // but never played. The isDisposed() guard makes this safe to call on
75985
+ // layouts that were fully played as well.
75986
+ var _iterator5 = _createForOfIteratorHelper(this.regions),
75987
+ _step5;
75988
+ try {
75989
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
75990
+ var region = _step5.value;
75991
+ var _iterator6 = _createForOfIteratorHelper(region.mediaObjects),
75992
+ _step6;
75993
+ try {
75994
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
75995
+ var media = _step6.value;
75996
+ if (media.player && !media.player.isDisposed()) {
75997
+ media.player.dispose();
75998
+ media.player = undefined;
75999
+ media.html = null;
76000
+ }
76001
+ }
76002
+ } catch (err) {
76003
+ _iterator6.e(err);
76004
+ } finally {
76005
+ _iterator6.f();
76006
+ }
76007
+ }
76008
+ } catch (err) {
76009
+ _iterator5.e(err);
76010
+ } finally {
76011
+ _iterator5.f();
76012
+ }
76013
+ this.removeLayout(caller);
76014
+ }
75819
76015
  }, {
75820
76016
  key: "getXlf",
75821
76017
  value: function getXlf() {
@@ -76058,6 +76254,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76058
76254
  }());
76059
76255
  xlrObject.on('updateLoop', /*#__PURE__*/function () {
76060
76256
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(inputLayouts) {
76257
+ var _xlrObject$currentLay;
76061
76258
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
76062
76259
  while (1) switch (_context3.prev = _context3.next) {
76063
76260
  case 0:
@@ -76066,7 +76263,17 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76066
76263
  return xlrObject.updateLoop(inputLayouts);
76067
76264
  case 3:
76068
76265
  xlrObject.isUpdatingLoop = false;
76069
- case 4:
76266
+ // If the running layout finished while isUpdatingLoop was true, the
76267
+ // layout end-handler bailed out of prepareLayouts() early and the
76268
+ // subsequent playLayouts() call saw currentLayout.done === true and
76269
+ // skipped run() — leaving a black screen. Catch up now that the flag
76270
+ // is clear.
76271
+ if ((_xlrObject$currentLay = xlrObject.currentLayout) !== null && _xlrObject$currentLay !== void 0 && _xlrObject$currentLay.done) {
76272
+ xlrObject.prepareLayouts().then(function (xlr) {
76273
+ return xlrObject.playLayouts(xlr);
76274
+ });
76275
+ }
76276
+ case 5:
76070
76277
  case "end":
76071
76278
  return _context3.stop();
76072
76279
  }
@@ -76096,40 +76303,35 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76096
76303
  return _ref4.apply(this, arguments);
76097
76304
  };
76098
76305
  }());
76099
- /**
76100
- * Asynchronous event emitter. Extended nanoevents event emitter.
76101
- *
76102
- * NOTE: Known limitation — nanoevents emit() is synchronous.
76103
- * Any async event handlers registered via .on() are fire-and-forget;
76104
- * emitSync does NOT await them. The returned Promise resolves
76105
- * immediately after handlers are invoked, not after they complete.
76106
- *
76107
- * @param eventName
76108
- * @param args
76109
- */
76110
- xlrObject.emitSync = function (eventName) {
76111
- for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76112
- args[_key - 1] = arguments[_key];
76113
- }
76114
- return new Promise( /*#__PURE__*/function () {
76115
- var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(resolve) {
76116
- var _xlrObject$emitter;
76117
- return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76118
- while (1) switch (_context5.prev = _context5.next) {
76119
- case 0:
76120
- (_xlrObject$emitter = xlrObject.emitter).emit.apply(_xlrObject$emitter, [eventName].concat(args));
76121
- resolve();
76122
- case 2:
76123
- case "end":
76124
- return _context5.stop();
76125
- }
76126
- }, _callee5);
76127
- }));
76128
- return function (_x4) {
76129
- return _ref5.apply(this, arguments);
76130
- };
76131
- }());
76132
- };
76306
+ xlrObject.emitSync = /*#__PURE__*/function () {
76307
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(eventName) {
76308
+ var _xlrObject$emitter$ev;
76309
+ var _len,
76310
+ args,
76311
+ _key,
76312
+ handlers,
76313
+ _args5 = arguments;
76314
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76315
+ while (1) switch (_context5.prev = _context5.next) {
76316
+ case 0:
76317
+ for (_len = _args5.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76318
+ args[_key - 1] = _args5[_key];
76319
+ }
76320
+ handlers = (_xlrObject$emitter$ev = xlrObject.emitter.events[eventName]) !== null && _xlrObject$emitter$ev !== void 0 ? _xlrObject$emitter$ev : [];
76321
+ _context5.next = 4;
76322
+ return Promise.all(handlers.map(function (handler) {
76323
+ return handler.apply(void 0, args);
76324
+ }));
76325
+ case 4:
76326
+ case "end":
76327
+ return _context5.stop();
76328
+ }
76329
+ }, _callee5);
76330
+ }));
76331
+ return function (_x4) {
76332
+ return _ref5.apply(this, arguments);
76333
+ };
76334
+ }();
76133
76335
  xlrObject.bootstrap = function () {
76134
76336
  // Place to set configurations and initialize required props
76135
76337
  var self = this;
@@ -76164,12 +76366,21 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76164
76366
  if ($splashScreen && $splashScreen.style.display === 'block') {
76165
76367
  $splashScreen === null || $splashScreen === void 0 || $splashScreen.hide();
76166
76368
  }
76369
+ console.debug('>>>> XLR.debug XLR::playLayouts > currentLayout', {
76370
+ layoutId: xlr.currentLayout.layoutId,
76371
+ layoutIndex: xlr.currentLayout.index,
76372
+ layoutState: xlr.currentLayout.state
76373
+ });
76167
76374
  if (!xlr.currentLayout.done) {
76168
76375
  // Hide overlays when current layout is interrupt
76169
76376
  if (xlr.currentLayout.isInterrupt()) {
76170
76377
  xlrObject.overlayLayoutManager.stopOverlays();
76171
76378
  }
76172
- console.log('>>>> XLR.debug XLR::playSchedules > Running currentLayout', xlr.currentLayout);
76379
+ console.debug('>>>> XLR.debug XLR::playLayouts > Running currentLayout', {
76380
+ layoutId: xlr.currentLayout.layoutId,
76381
+ layoutIndex: xlr.currentLayout.index,
76382
+ layoutState: xlr.currentLayout.state
76383
+ });
76173
76384
  xlr.currentLayout.run();
76174
76385
  }
76175
76386
  } else {
@@ -76247,7 +76458,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76247
76458
  _context7.next = 10;
76248
76459
  return _overlay.finishAllRegions();
76249
76460
  case 10:
76250
- _overlay.removeLayout();
76461
+ _overlay.removeLayout(exports.LayoutPlaybackType.OVERLAY);
76251
76462
  case 11:
76252
76463
  _context7.next = 14;
76253
76464
  break;
@@ -76313,6 +76524,31 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76313
76524
  var $layout = document.querySelector("#".concat(containerName, "[data-sequence=\"").concat(layoutIndex, "\"]"));
76314
76525
  return $layout !== null;
76315
76526
  };
76527
+ // Scans screen_container for non-overlay layout divs and removes any that
76528
+ // are not the current or next active layout. Prevents DOM accumulation when
76529
+ // prepareLayouts() races with updateLoop and multiple same-layoutId elements
76530
+ // end up in screen_container (e.g. transitioning from a 1-layout loop where
76531
+ // two elements exist for the same layout to a multi-layout schedule).
76532
+ // keepCurrent / keepNext:
76533
+ // undefined → fall back to this.currentLayout / this.nextLayout
76534
+ // null → keep nothing for that slot (explicit "no layout to preserve")
76535
+ // ILayout → keep exactly that instance
76536
+ xlrObject.cleanupOrphanedLayouts = function (keepCurrent, keepNext) {
76537
+ var $screen = document.getElementById('screen_container');
76538
+ if (!$screen) return;
76539
+ var current = keepCurrent !== undefined ? keepCurrent : this.currentLayout;
76540
+ var next = keepNext !== undefined ? keepNext : this.nextLayout;
76541
+ Array.from($screen.querySelectorAll(':scope > div:not(.is-overlay)')).forEach(function (el) {
76542
+ var div = el;
76543
+ var isCurrentLayout = current && div.id === current.containerName && div.dataset.sequence === String(current.index);
76544
+ var isNextLayout = next && div.id === next.containerName && div.dataset.sequence === String(next.index);
76545
+ if (!isCurrentLayout && !isNextLayout) {
76546
+ var _div$parentElement;
76547
+ console.debug('XLR::cleanupOrphanedLayouts - removing orphaned layout element', div.id);
76548
+ (_div$parentElement = div.parentElement) === null || _div$parentElement === void 0 || _div$parentElement.removeChild(div);
76549
+ }
76550
+ });
76551
+ };
76316
76552
  xlrObject.updateLoop = /*#__PURE__*/function () {
76317
76553
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11(inputLayouts) {
76318
76554
  var _this$currentLayout,
@@ -76359,11 +76595,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76359
76595
  };
76360
76596
  }();
76361
76597
  if (isCurrentLayoutValid) {
76362
- _context11.next = 54;
76598
+ _context11.next = 55;
76363
76599
  break;
76364
76600
  }
76365
76601
  if (!playback.hasDefaultOnly) {
76366
- _context11.next = 33;
76602
+ _context11.next = 34;
76367
76603
  break;
76368
76604
  }
76369
76605
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
@@ -76376,82 +76612,102 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76376
76612
  case 19:
76377
76613
  this.currentLayout.removeLayout();
76378
76614
  case 20:
76379
- _context11.next = 22;
76615
+ // Discard old nextLayout before replacing it — same as the
76616
+ // other two branches do, otherwise the prepared DOM element
76617
+ // and any video.js players are orphaned.
76618
+ if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76619
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76620
+ }
76621
+ _context11.next = 23;
76380
76622
  return this.prepareLayoutXlf(playback.currentLayout);
76381
- case 22:
76623
+ case 23:
76382
76624
  this.currentLayout = _context11.sent;
76383
76625
  this.currentLayoutId = this.currentLayout.layoutId;
76384
76626
  _context11.t0 = this;
76385
- _context11.next = 27;
76627
+ _context11.next = 28;
76386
76628
  return this.prepareLayoutXlf(playback.nextLayout);
76387
- case 27:
76629
+ case 28:
76388
76630
  _context11.t1 = _context11.sent;
76389
- _context11.next = 30;
76631
+ _context11.next = 31;
76390
76632
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76391
- case 30:
76633
+ case 31:
76392
76634
  this.nextLayout = _context11.sent;
76393
- _context11.next = 50;
76635
+ _context11.next = 51;
76394
76636
  break;
76395
- case 33:
76637
+ case 34:
76396
76638
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76397
- _context11.next = 38;
76639
+ _context11.next = 39;
76398
76640
  break;
76399
76641
  }
76400
76642
  this.currentLayout.inLoop = false;
76401
- _context11.next = 37;
76643
+ _context11.next = 38;
76402
76644
  return this.currentLayout.finishAllRegions();
76403
- case 37:
76404
- this.currentLayout.removeLayout();
76405
76645
  case 38:
76646
+ this.currentLayout.removeLayout();
76647
+ case 39:
76406
76648
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76407
- this.nextLayout.removeLayout();
76649
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76408
76650
  }
76409
76651
  if (!playback.currentLayout) {
76410
- _context11.next = 42;
76652
+ _context11.next = 43;
76411
76653
  break;
76412
76654
  }
76413
- _context11.next = 42;
76655
+ _context11.next = 43;
76414
76656
  return prepareNewCurrentLayout();
76415
- case 42:
76657
+ case 43:
76416
76658
  if (!playback.nextLayout) {
76417
- _context11.next = 50;
76659
+ _context11.next = 51;
76418
76660
  break;
76419
76661
  }
76420
76662
  _context11.t2 = this;
76421
- _context11.next = 46;
76663
+ _context11.next = 47;
76422
76664
  return this.prepareLayoutXlf(playback.nextLayout);
76423
- case 46:
76665
+ case 47:
76424
76666
  _context11.t3 = _context11.sent;
76425
- _context11.next = 49;
76667
+ _context11.next = 50;
76426
76668
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76427
- case 49:
76428
- this.nextLayout = _context11.sent;
76429
76669
  case 50:
76430
- _context11.next = 52;
76670
+ this.nextLayout = _context11.sent;
76671
+ case 51:
76672
+ _context11.next = 53;
76431
76673
  return this.playSchedules(this);
76432
- case 52:
76674
+ case 53:
76433
76675
  _context11.next = 67;
76434
76676
  break;
76435
- case 54:
76677
+ case 55:
76436
76678
  // Remove next layout if it is in the DOM
76437
76679
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76438
- this.nextLayout.removeLayout();
76680
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76439
76681
  }
76440
- // Prepare new current layout
76682
+ // Purge any other orphaned layouts from screen_container that belong
76683
+ // to the old single-layout loop. When there was only one layout in the
76684
+ // loop, prepareLayouts() kept two DOM elements alive (current + next,
76685
+ // both the same layoutId but different containerNames). On a schedule
76686
+ // change the this.nextLayout check above only discards the element
76687
+ // currently referenced by this.nextLayout, but concurrent
76688
+ // prepareLayouts() calls can leave earlier same-layoutId elements
76689
+ // behind.
76690
+ // Pass null (not undefined) for keepNext: undefined would fall back to
76691
+ // this.nextLayout which may still reference the just-discarded layout
76692
+ // or — if isLayoutInDOM returned false and discardLayout was skipped —
76693
+ // the orphan itself, causing cleanupOrphanedLayouts to preserve it.
76694
+ // null means "no next to keep"; we are about to prepare a fresh one.
76695
+ this.cleanupOrphanedLayouts(this.currentLayout, null);
76696
+ // The current layout is still valid and running — do NOT replace the
76697
+ // live currentLayout object. Only refresh the queued nextLayout so
76698
+ // that when the running layout finishes it transitions to the correct
76699
+ // position in the updated loop. Using playback.currentLayout (the
76700
+ // slot that follows the running layout in the new queue) as the new
76701
+ // nextLayout keeps the cycle in order; the slot after that will be
76702
+ // prepared by the normal prepareLayouts() call at transition time.
76441
76703
  if (!playback.currentLayout) {
76442
- _context11.next = 58;
76443
- break;
76444
- }
76445
- _context11.next = 58;
76446
- return prepareNewCurrentLayout();
76447
- case 58:
76448
- if (!playback.nextLayout) {
76449
76704
  _context11.next = 66;
76450
76705
  break;
76451
76706
  }
76707
+ this.currentLayoutIndex = playback.currentLayoutIndex;
76452
76708
  _context11.t4 = this;
76453
76709
  _context11.next = 62;
76454
- return this.prepareLayoutXlf(playback.nextLayout);
76710
+ return this.prepareLayoutXlf(playback.currentLayout);
76455
76711
  case 62:
76456
76712
  _context11.t5 = _context11.sent;
76457
76713
  _context11.next = 65;
@@ -76557,8 +76813,21 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76557
76813
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76558
76814
  }
76559
76815
  } else {
76816
+ var _this$currentLayout4, _this$currentLayout5;
76560
76817
  _currentLayout = this.nextLayout;
76561
76818
  _currentLayoutIndex = _currentLayout.index;
76819
+ // updateLoop can re-queue the same index that is currently
76820
+ // playing (e.g. it fires while nextLayout.index === currentLayout.index).
76821
+ // When that layout then ends, the catch-up prepareLayouts() would
76822
+ // replay the same slot instead of advancing. Detect this by checking
76823
+ // whether the queued next-to-current is at the same index as the
76824
+ // layout that just finished, and advance past it so the following
76825
+ // slot (e.g. an SSP that now has an ad) becomes current instead.
76826
+ if (this.inputLayouts.length > 1 && (_this$currentLayout4 = this.currentLayout) !== null && _this$currentLayout4 !== void 0 && _this$currentLayout4.done && _currentLayoutIndex === ((_this$currentLayout5 = this.currentLayout) === null || _this$currentLayout5 === void 0 ? void 0 : _this$currentLayout5.index)) {
76827
+ _currentLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76828
+ _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76829
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76830
+ }
76562
76831
  _nextLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76563
76832
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76564
76833
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
@@ -76614,6 +76883,10 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76614
76883
  var activeLayout = inputLayout;
76615
76884
  if (isCMS) {
76616
76885
  activeLayout.index = 0;
76886
+ // id stays null without this — setLayoutIndex returns undefined for CMS layouts
76887
+ if (activeLayout.id == null) {
76888
+ activeLayout.id = activeLayout.layoutId;
76889
+ }
76617
76890
  } else {
76618
76891
  activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
76619
76892
  }
@@ -76644,7 +76917,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76644
76917
  };
76645
76918
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76646
76919
  var _layoutPlayback$curre, _layoutPlayback$curre2;
76647
- var self, layoutPlayback, currentLayoutXlf, nextLayoutXlf, layouts;
76920
+ var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76648
76921
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76649
76922
  while (1) switch (_context14.prev = _context14.next) {
76650
76923
  case 0:
@@ -76667,7 +76940,12 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76667
76940
  shouldParse: false
76668
76941
  });
76669
76942
  self.currentLayoutId = (_layoutPlayback$curre = layoutPlayback.currentLayout) === null || _layoutPlayback$curre === void 0 ? void 0 : _layoutPlayback$curre.layoutId;
76670
- if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode)) {
76943
+ // Only reuse the existing Layout instance if it is fully healthy
76944
+ // a done=true instance was removed from the DOM (e.g. an SSP slot that
76945
+ // had no ad), and an empty-XLF instance has no regions so it can never
76946
+ // advance the cycle. In either case re-prepare from scratch so we get
76947
+ // a fresh request (which may now have a valid ad / XLF).
76948
+ if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
76671
76949
  _context14.next = 12;
76672
76950
  break;
76673
76951
  }
@@ -76681,27 +76959,40 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76681
76959
  _context14.t0 = _context14.sent;
76682
76960
  case 15:
76683
76961
  currentLayoutXlf = _context14.t0;
76684
- _context14.next = 18;
76962
+ // True when the same object was returned (reused); false when a fresh
76963
+ // Layout was constructed by prepareLayoutXlf above.
76964
+ wasCurrentReused = currentLayoutXlf === layoutPlayback.currentLayout;
76965
+ _context14.next = 19;
76685
76966
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
76686
- case 18:
76967
+ case 19:
76687
76968
  nextLayoutXlf = _context14.sent;
76688
76969
  _context14.t1 = Promise;
76689
76970
  _context14.t2 = currentLayoutXlf;
76690
- _context14.next = 23;
76971
+ _context14.next = 24;
76691
76972
  return self.prepareForSsp(nextLayoutXlf);
76692
- case 23:
76973
+ case 24:
76693
76974
  _context14.t3 = _context14.sent;
76694
76975
  _context14.t4 = [_context14.t2, _context14.t3];
76695
- _context14.next = 27;
76976
+ _context14.next = 28;
76696
76977
  return _context14.t1.all.call(_context14.t1, _context14.t4);
76697
- case 27:
76978
+ case 28:
76698
76979
  layouts = _context14.sent;
76699
- // Return early when layout loop is updating
76700
- if (self.isUpdatingLoop) {
76701
- if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76702
- nextLayoutXlf.removeLayout();
76703
- }
76980
+ if (!(self.isUpdatingLoop || layouts[0].done)) {
76981
+ _context14.next = 33;
76982
+ break;
76983
+ }
76984
+ // If currentLayout was freshly prepared (not reused from nextLayout),
76985
+ // its DOM element was just appended — discard it now so it does not
76986
+ // accumulate in screen_container. Also disposes any video.js players
76987
+ // that were initialized during prepareVideoMedia but never played.
76988
+ if (!wasCurrentReused && this.isLayoutInDOM(currentLayoutXlf.containerName, currentLayoutXlf.index)) {
76989
+ currentLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
76990
+ }
76991
+ if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
76992
+ nextLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
76704
76993
  }
76994
+ return _context14.abrupt("return", Promise.resolve(self));
76995
+ case 33:
76705
76996
  console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
76706
76997
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
76707
76998
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
@@ -76718,8 +77009,15 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76718
77009
  self.currentLayout = self.layouts.current;
76719
77010
  self.currentLayoutId = self.currentLayout.layoutId;
76720
77011
  self.nextLayout = self.layouts.next;
77012
+ // Evict any orphaned layout DOM elements that aren't the current
77013
+ // or next layout. Concurrent prepareLayouts() calls can each append
77014
+ // a freshly-prepared nextLayout to screen_container and then
77015
+ // overwrite this.nextLayout, leaving earlier elements behind.
77016
+ // Calling this here — with explicit references — ensures every
77017
+ // completed prepare cycle leaves the DOM in a clean state.
77018
+ self.cleanupOrphanedLayouts(self.currentLayout, self.nextLayout);
76721
77019
  resolve(xlrObject);
76722
- case 8:
77020
+ case 9:
76723
77021
  case "end":
76724
77022
  return _context13.stop();
76725
77023
  }
@@ -76729,7 +77027,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76729
77027
  return _ref14.apply(this, arguments);
76730
77028
  };
76731
77029
  }()));
76732
- case 31:
77030
+ case 35:
76733
77031
  case "end":
76734
77032
  return _context14.stop();
76735
77033
  }
@@ -76757,34 +77055,38 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76757
77055
  newOptions.xlfUrl = inputLayout.path;
76758
77056
  }
76759
77057
  if (!(inputLayout && inputLayout.layoutNode === undefined)) {
76760
- _context15.next = 21;
77058
+ _context15.next = 22;
76761
77059
  break;
76762
77060
  }
76763
77061
  if (!(inputLayout.layoutId === -1)) {
76764
- _context15.next = 14;
77062
+ _context15.next = 15;
76765
77063
  break;
76766
77064
  }
76767
77065
  _context15.next = 10;
76768
77066
  return self.emitSync('adRequest', inputLayout.index);
76769
77067
  case 10:
76770
77068
  sspInputLayout = self.inputLayouts[inputLayout.index];
77069
+ console.debug('XLR::prepareLayoutXlf > SSP input layout', {
77070
+ sspInputLayout: sspInputLayout,
77071
+ inputLayout: inputLayout
77072
+ });
76771
77073
  // @ts-ignore
76772
- layoutXlf = ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf()) || '';
76773
- _context15.next = 17;
77074
+ layoutXlf = typeof ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf) === 'function' ? sspInputLayout.getXlf() : '';
77075
+ _context15.next = 18;
76774
77076
  break;
76775
- case 14:
76776
- _context15.next = 16;
77077
+ case 15:
77078
+ _context15.next = 17;
76777
77079
  return getXlf(newOptions);
76778
- case 16:
76779
- layoutXlf = _context15.sent;
76780
77080
  case 17:
77081
+ layoutXlf = _context15.sent;
77082
+ case 18:
76781
77083
  parser = new window.DOMParser();
76782
77084
  layoutXlfNode = parser.parseFromString(layoutXlf, 'text/xml');
76783
- _context15.next = 22;
77085
+ _context15.next = 23;
76784
77086
  break;
76785
- case 21:
76786
- layoutXlfNode = inputLayout && inputLayout.layoutNode;
76787
77087
  case 22:
77088
+ layoutXlfNode = inputLayout && inputLayout.layoutNode;
77089
+ case 23:
76788
77090
  isOverlayLayout = !!(inputLayout !== null && inputLayout !== void 0 && inputLayout.isOverlay);
76789
77091
  return _context15.abrupt("return", new Promise(function (resolve) {
76790
77092
  var _inputLayout$ad;
@@ -76809,13 +77111,22 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76809
77111
  xlrLayoutObj.duration = sspInputLayout.duration || 0;
76810
77112
  xlrLayoutObj.ad = sspInputLayout.ad;
76811
77113
  }
77114
+ var xlrLayout;
76812
77115
  if (isOverlayLayout) {
76813
- resolve(new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77116
+ xlrLayout = new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76814
77117
  } else {
76815
- resolve(new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77118
+ xlrLayout = new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76816
77119
  }
77120
+ // Advance the shared counter so the next prepareLayoutXlf() call
77121
+ // starts from where this layout left off — prevents every layout
77122
+ // instance from reusing idCounter=1 and colliding on the same
77123
+ // containerName / DOM element.
77124
+ if (props.options) {
77125
+ props.options.idCounter = newOptions.idCounter;
77126
+ }
77127
+ resolve(xlrLayout);
76817
77128
  }));
76818
- case 24:
77129
+ case 25:
76819
77130
  case "end":
76820
77131
  return _context15.stop();
76821
77132
  }
@@ -76827,46 +77138,49 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76827
77138
  }();
76828
77139
  xlrObject.prepareForSsp = /*#__PURE__*/function () {
76829
77140
  var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee16(nextLayout) {
76830
- var self, _nextLayout, nextIndex, inputLayout, nextLayoutObj;
77141
+ var self, _nextLayout, iterations, maxIterations, nextIndex, inputLayout, nextLayoutObj;
76831
77142
  return _regeneratorRuntime().wrap(function _callee16$(_context16) {
76832
77143
  while (1) switch (_context16.prev = _context16.next) {
76833
77144
  case 0:
76834
77145
  self = this;
76835
77146
  _nextLayout = nextLayout;
76836
- case 2:
76837
- if (!(_nextLayout && _nextLayout.xlfString === '')) {
76838
- _context16.next = 17;
76839
- break;
76840
- }
76841
- // Remove skipped layout
76842
- _nextLayout.removeLayout();
76843
- // Get next valid layout
76844
- // We will skip next layout that has no valid xlf
76845
- nextIndex = _nextLayout.index + 1;
76846
- if (!(nextIndex >= self.inputLayouts.length)) {
76847
- _context16.next = 7;
77147
+ iterations = 0;
77148
+ maxIterations = self.inputLayouts.length;
77149
+ case 4:
77150
+ if (!(_nextLayout && _nextLayout.xlfString === '' && iterations < maxIterations)) {
77151
+ _context16.next = 20;
76848
77152
  break;
76849
77153
  }
76850
- return _context16.abrupt("break", 17);
76851
- case 7:
77154
+ // Remove the empty slot's DOM element before skipping past it
77155
+ _nextLayout.removeLayout(exports.LayoutPlaybackType.NEXT);
77156
+ iterations++;
77157
+ // Advance to the next slot, wrapping around so a trailing SSP slot
77158
+ // with no ad does not strand the queue at the end of the array.
77159
+ nextIndex = (_nextLayout.index + 1) % self.inputLayouts.length;
76852
77160
  inputLayout = self.inputLayouts[nextIndex];
76853
77161
  if (inputLayout) {
76854
- _context16.next = 10;
77162
+ _context16.next = 11;
76855
77163
  break;
76856
77164
  }
76857
- return _context16.abrupt("break", 17);
76858
- case 10:
77165
+ return _context16.abrupt("break", 20);
77166
+ case 11:
76859
77167
  nextLayoutObj = self.getLayout(inputLayout);
76860
77168
  nextLayoutObj = setLayoutIndex(nextLayoutObj, nextIndex);
76861
- _context16.next = 14;
77169
+ if (nextLayoutObj) {
77170
+ _context16.next = 15;
77171
+ break;
77172
+ }
77173
+ return _context16.abrupt("break", 20);
77174
+ case 15:
77175
+ _context16.next = 17;
76862
77176
  return self.prepareLayoutXlf(nextLayoutObj);
76863
- case 14:
77177
+ case 17:
76864
77178
  _nextLayout = _context16.sent;
76865
- _context16.next = 2;
77179
+ _context16.next = 4;
76866
77180
  break;
76867
- case 17:
77181
+ case 20:
76868
77182
  return _context16.abrupt("return", _nextLayout);
76869
- case 18:
77183
+ case 21:
76870
77184
  case "end":
76871
77185
  return _context16.stop();
76872
77186
  }
@@ -76878,7 +77192,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76878
77192
  }();
76879
77193
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
76880
77194
  var _this4 = this;
76881
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout4;
77195
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
76882
77196
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
76883
77197
  while (1) switch (_context18.prev = _context18.next) {
76884
77198
  case 0:
@@ -76901,7 +77215,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76901
77215
  break;
76902
77216
  }
76903
77217
  _context18.next = 8;
76904
- return (_this$currentLayout4 = this.currentLayout) === null || _this$currentLayout4 === void 0 ? void 0 : _this$currentLayout4.finishAllRegions();
77218
+ return (_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.finishAllRegions();
76905
77219
  case 8:
76906
77220
  // and set the previous layout as current layout
76907
77221
  this.currentLayoutIndex = _assumedPrevIndex;
@@ -76929,7 +77243,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76929
77243
  }, _callee18, this);
76930
77244
  }));
76931
77245
  xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
76932
- var _xlrObject$currentLay;
77246
+ var _xlrObject$currentLay2;
76933
77247
  return _regeneratorRuntime().wrap(function _callee19$(_context19) {
76934
77248
  while (1) switch (_context19.prev = _context19.next) {
76935
77249
  case 0:
@@ -76939,7 +77253,7 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76939
77253
  shouldParse: false
76940
77254
  });
76941
77255
  _context19.next = 3;
76942
- return (_xlrObject$currentLay = xlrObject.currentLayout) === null || _xlrObject$currentLay === void 0 ? void 0 : _xlrObject$currentLay.finishAllRegions();
77256
+ return (_xlrObject$currentLay2 = xlrObject.currentLayout) === null || _xlrObject$currentLay2 === void 0 ? void 0 : _xlrObject$currentLay2.finishAllRegions();
76943
77257
  case 3:
76944
77258
  case "end":
76945
77259
  return _context19.stop();
@@ -76951,6 +77265,11 @@ function XiboLayoutRenderer(inputLayouts, overlays, options) {
76951
77265
  if (layout !== null) {
76952
77266
  layout.index = xlrInputLayout.index;
76953
77267
  }
77268
+ console.debug('XLR::updateInputLayout', {
77269
+ layoutIndex: layoutIndex,
77270
+ layout: layout,
77271
+ xlrInputLayout: xlrInputLayout
77272
+ });
76954
77273
  this.inputLayouts[layoutIndex] = layout || xlrInputLayout;
76955
77274
  };
76956
77275
  xlrObject.bootstrap();
@@ -76994,11 +77313,11 @@ exports.hasDefaultOnly = hasDefaultOnly;
76994
77313
  exports.hasSspLayout = hasSspLayout;
76995
77314
  exports.initRenderingDOM = initRenderingDOM;
76996
77315
  exports.initialLayout = initialLayout;
76997
- exports.initialMedia = initialMedia;
76998
77316
  exports.initialRegion = initialRegion;
76999
77317
  exports.initialXlr = initialXlr;
77000
77318
  exports.isEmpty = isEmpty;
77001
77319
  exports.isLayoutValid = isLayoutValid;
77320
+ exports.isMediaActive = isMediaActive;
77002
77321
  exports.nextId = nextId;
77003
77322
  exports.playerReportFault = playerReportFault;
77004
77323
  exports.preloadMediaBlob = preloadMediaBlob;
@@ -77006,6 +77325,7 @@ exports.prepareAudioMedia = prepareAudioMedia;
77006
77325
  exports.prepareHtmlMedia = prepareHtmlMedia;
77007
77326
  exports.prepareImageMedia = prepareImageMedia;
77008
77327
  exports.prepareVideoMedia = prepareVideoMedia;
77328
+ exports.reportToPlayerPlatform = reportToPlayerPlatform;
77009
77329
  exports.setExpiry = setExpiry;
77010
77330
  exports.transitionElement = transitionElement;
77011
77331
  exports.videoFileType = videoFileType;