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