@xibosignage/xibo-layout-renderer 1.0.25 → 1.0.27

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: '',
@@ -747,6 +755,7 @@ var XiboLayoutRenderer = (function (exports) {
747
755
  options: {},
748
756
  playNextMedia: function playNextMedia() {},
749
757
  playPreviousMedia: function playPreviousMedia() {},
758
+ prepareMedia: function prepareMedia(_media) {},
750
759
  prepareMediaObjects: function prepareMediaObjects() {},
751
760
  prepareRegion: function prepareRegion() {},
752
761
  ready: false,
@@ -764,56 +773,6 @@ var XiboLayoutRenderer = (function (exports) {
764
773
  xlr: {}
765
774
  };
766
775
 
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
776
  var OverlayLayoutManager = /*#__PURE__*/function () {
818
777
  function OverlayLayoutManager() {
819
778
  _classCallCheck(this, OverlayLayoutManager);
@@ -1110,6 +1069,7 @@ var XiboLayoutRenderer = (function (exports) {
1110
1069
  isLayoutInDOM: function isLayoutInDOM(containerName, layoutId) {
1111
1070
  return false;
1112
1071
  },
1072
+ cleanupOrphanedLayouts: function cleanupOrphanedLayouts(_keepCurrent, _keepNext) {},
1113
1073
  isSspEnabled: false,
1114
1074
  isUpdatingLoop: false,
1115
1075
  isUpdatingOverlays: false,
@@ -72530,6 +72490,10 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72530
72490
  function composeVideoSource($media, media) {
72531
72491
  // const videoSrc = await preloadMediaBlob(media.url as string, media.mediaType as MediaTypes);
72532
72492
  var vidType = videoFileType(getFileExt(media.uri));
72493
+ if (!vidType) {
72494
+ console.warn("XLR >> VideoMedia: Unsupported video type for media ".concat(media.id, " with uri ").concat(media.uri));
72495
+ return $media;
72496
+ }
72533
72497
  // Only add one source per type
72534
72498
  if ($media.querySelectorAll("source[type=\"".concat(vidType, "\"]")).length === 0) {
72535
72499
  var $videoSource = document.createElement('source');
@@ -72556,8 +72520,38 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72556
72520
  };
72557
72521
  var reportToPlayerPlatform = [exports.ConsumerPlatform.CHROMEOS, exports.ConsumerPlatform.ELECTRON];
72558
72522
  function VideoMedia(media, xlr) {
72523
+ var stopped = false;
72559
72524
  var mediaId = getMediaId(media);
72525
+ // ── Stall watchdog (closure-level so stop() can cancel it) ───────────────
72526
+ // 'waiting' and 'stalled' fire when the browser stops receiving data.
72527
+ // Unlike codec or source errors they do NOT fire the 'error' event, so
72528
+ // without a watchdog the video silently freezes for its entire duration.
72529
+ var stallWatchdog;
72530
+ var STALL_TIMEOUT_MS = 10000;
72531
+ var clearStallWatchdog = function clearStallWatchdog() {
72532
+ if (stallWatchdog !== undefined) {
72533
+ clearTimeout(stallWatchdog);
72534
+ stallWatchdog = undefined;
72535
+ }
72536
+ };
72537
+ // ─────────────────────────────────────────────────────────────────────────
72538
+ // ── Unified error → report → stop helper (closure-level) ─────────────────
72539
+ // Used by both the 'error' event and the play Promise catch.
72540
+ // playerReportFault only fires for platforms that report faults (Electron,
72541
+ // ChromeOS). All other platforms just advance to the next media via stop().
72542
+ var reportAndStop = function reportAndStop(reason, code) {
72543
+ if (stopped) return;
72544
+ if (reportToPlayerPlatform.includes(xlr.config.platform)) {
72545
+ playerReportFault(reason, media, code).then(function () {
72546
+ return videoPlayer.stop();
72547
+ });
72548
+ } else {
72549
+ videoPlayer.stop();
72550
+ }
72551
+ };
72552
+ // ─────────────────────────────────────────────────────────────────────────
72560
72553
  var videoPlayer = {
72554
+ player: undefined,
72561
72555
  duration: 0,
72562
72556
  init: function init() {
72563
72557
  var _this = this;
@@ -72565,9 +72559,37 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72565
72559
  videoPlayer.duration = media.duration;
72566
72560
  var vjsPlayer = videojs(mediaId);
72567
72561
  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
- });
72562
+ videoPlayer.player = vjsPlayer;
72563
+ // ── Early source check ────────────────────────────────────────────────
72564
+ // Two-step check before video.js tries to load anything:
72565
+ // 1. Is the file extension one we map to a MIME type?
72566
+ // 2. Can the browser actually play that MIME type?
72567
+ // Failing either step skips the media immediately so video.js
72568
+ // never renders its "No compatible source" error overlay.
72569
+ var vidType = videoFileType(getFileExt(media.uri));
72570
+ if (!vidType) {
72571
+ console.warn("XLR >> VideoMedia: unrecognised file extension for media ".concat(media.id, " (uri: ").concat(media.uri, ")"));
72572
+ reportAndStop("Unsupported video file extension for media ".concat(media.id), exports.FaultCodes.FaultVideoSource);
72573
+ return;
72574
+ }
72575
+ if (document.createElement('video').canPlayType(vidType) === '') {
72576
+ console.warn("XLR >> VideoMedia: browser cannot play type \"".concat(vidType, "\" for media ").concat(media.id));
72577
+ reportAndStop("Browser cannot play video type \"".concat(vidType, "\" for media ").concat(media.id), exports.FaultCodes.FaultVideoSource);
72578
+ return;
72579
+ }
72580
+ // ─────────────────────────────────────────────────────────────────────
72581
+ var armStallWatchdog = function armStallWatchdog() {
72582
+ clearStallWatchdog();
72583
+ stallWatchdog = setTimeout(function () {
72584
+ if (stopped) return;
72585
+ console.warn("XLR >> VideoMedia: stall timeout on media ".concat(media.id));
72586
+ reportAndStop('Video stall timeout', exports.FaultCodes.FaultVideoUnexpected);
72587
+ }, STALL_TIMEOUT_MS);
72588
+ };
72589
+ vjsPlayer.on('waiting', armStallWatchdog);
72590
+ vjsPlayer.on('stalled', armStallWatchdog);
72591
+ vjsPlayer.on('playing', clearStallWatchdog);
72592
+ vjsPlayer.on('ended', clearStallWatchdog);
72571
72593
  vjsPlayer.on('loadedmetadata', function () {
72572
72594
  if (media.duration === 0) {
72573
72595
  videoPlayer.duration = vjsPlayer.duration();
@@ -72638,34 +72660,20 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72638
72660
  }, 5000);
72639
72661
  })]).then(function () {
72640
72662
  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
- }());
72663
+ })["catch"](function (error) {
72664
+ if (stopped) return;
72665
+ if (error === 'Timeout') {
72666
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
72667
+ // Timeout is a scheduling issue, not a media fault — just advance
72668
+ videoPlayer.stop();
72669
+ } else {
72670
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Autoplay error: ").concat(error));
72671
+ reportAndStop('Media autoplay error', exports.FaultCodes.FaultVideoUnexpected);
72672
+ }
72673
+ });
72667
72674
  // Optional: Reset the flag automatically when a new video loads or the source changes
72668
72675
  vjsPlayer.on('loadstart', function () {
72676
+ console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has started loading data . . ."));
72669
72677
  triggerTimeUpdate = false;
72670
72678
  });
72671
72679
  if (media.duration === 0) {
@@ -72678,13 +72686,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72678
72686
  if (mediaDuration !== undefined && currentTime !== undefined) {
72679
72687
  remainingTimeMs = (mediaDuration - currentTime) * 1000;
72680
72688
  }
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) {
72689
+ if (regionHasMultipleMedia && !triggerTimeUpdate && (remainingTimeMs === 0 || remainingTimeMs <= preloadBufferTimeMs)) {
72685
72690
  // Check if remaining time is less than preloadBufferTimeMs and the action hasn't been triggered yet
72686
72691
  console.log('Less than preloadBufferTimeMs remaining! Do something now.');
72687
- // Prepare next media in region
72688
72692
  media.region.prepareNextMedia();
72689
72693
  triggerTimeUpdate = true; // Set the flag to prevent re-triggering
72690
72694
  }
@@ -72696,33 +72700,16 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72696
72700
  }
72697
72701
  }
72698
72702
  });
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
- }());
72703
+ vjsPlayer.on('error', function () {
72704
+ if (stopped) return;
72705
+ clearStallWatchdog();
72706
+ // Extract the actual MediaError so the fault message is
72707
+ // meaningful: code 2 = network, 3 = decode, 4 = not supported.
72708
+ var vjsError = vjsPlayer.error();
72709
+ var reason = vjsError ? "Video error (code ".concat(vjsError.code, "): ").concat(vjsError.message) : 'Unknown video error';
72710
+ console.warn("XLR >> VideoMedia: error on media ".concat(media.id), vjsError);
72711
+ reportAndStop(reason, exports.FaultCodes.FaultVideoUnexpected);
72712
+ });
72726
72713
  if (media.duration === 0) {
72727
72714
  vjsPlayer.on('ended', function () {
72728
72715
  console.debug("??? XLR.debug >> VideoMedia: onended: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " has ended playing . . ."));
@@ -72732,26 +72719,37 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72732
72719
  }
72733
72720
  },
72734
72721
  stop: function stop() {
72722
+ var _videoPlayer$player;
72735
72723
  var disposeOnly = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
72736
- var vjsPlayer = media.player;
72724
+ clearStallWatchdog();
72725
+ // videoPlayer.player is where init() stores the vjs instance;
72726
+ // media.player is a legacy path kept for backward compat but is
72727
+ // no longer set by init(), so always prefer videoPlayer.player.
72728
+ var vjsPlayer = (_videoPlayer$player = videoPlayer.player) !== null && _videoPlayer$player !== void 0 ? _videoPlayer$player : media.player;
72737
72729
  console.debug('??? XLR.debug >> VideoMedia::stop', {
72738
72730
  vjsPlayer: vjsPlayer,
72739
- isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed_,
72740
- el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el_
72731
+ isDisposed: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.isDisposed(),
72732
+ el: vjsPlayer === null || vjsPlayer === void 0 ? void 0 : vjsPlayer.el()
72741
72733
  });
72742
72734
  // Expire the media and dispose the video
72743
- if (vjsPlayer !== undefined && !vjsPlayer.isDisposed_) {
72735
+ if (vjsPlayer !== undefined && !vjsPlayer.isDisposed()) {
72744
72736
  if (!disposeOnly) {
72745
72737
  media.emitter.emit('end', media);
72746
72738
  }
72747
72739
  vjsPlayer.dispose();
72748
72740
  // Clear up media player
72741
+ videoPlayer.player = undefined;
72749
72742
  media.player = undefined;
72743
+ media.html = null;
72750
72744
  } else {
72745
+ videoPlayer.player = undefined;
72751
72746
  media.player = undefined;
72752
72747
  media.html = null;
72753
- media.emitter.emit('end', media);
72748
+ if (!disposeOnly) {
72749
+ media.emitter.emit('end', media);
72750
+ }
72754
72751
  }
72752
+ stopped = true;
72755
72753
  },
72756
72754
  play: function play() {
72757
72755
  var _this2 = this;
@@ -72759,9 +72757,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72759
72757
  if (vjsPlayer !== undefined) {
72760
72758
  var _vjsPlayer$play;
72761
72759
  (_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) {
72760
+ var _ref2 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(error) {
72761
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
72762
+ while (1) switch (_context2.prev = _context2.next) {
72765
72763
  case 0:
72766
72764
  if (error === 'Timeout') {
72767
72765
  console.debug("??? XLR.debug >> VideoMedia: ".concat(capitalizeStr(media.mediaType), " for media > ").concat(media.id, " : Promise not resolved within 5 seconds. Move to next media"));
@@ -72776,12 +72774,12 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
72776
72774
  }
72777
72775
  case 1:
72778
72776
  case "end":
72779
- return _context4.stop();
72777
+ return _context2.stop();
72780
72778
  }
72781
- }, _callee4);
72779
+ }, _callee2);
72782
72780
  }));
72783
- return function (_x3) {
72784
- return _ref4.apply(this, arguments);
72781
+ return function (_x) {
72782
+ return _ref2.apply(this, arguments);
72785
72783
  };
72786
72784
  }());
72787
72785
  }
@@ -73193,11 +73191,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73193
73191
  return _getDataBlob.apply(this, arguments);
73194
73192
  }
73195
73193
  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) {
73194
+ _getDataBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(src, jwtToken) {
73195
+ return _regeneratorRuntime().wrap(function _callee2$(_context2) {
73196
+ while (1) switch (_context2.prev = _context2.next) {
73199
73197
  case 0:
73200
- return _context3.abrupt("return", fetch(src, {
73198
+ return _context2.abrupt("return", fetch(src, {
73201
73199
  method: 'GET',
73202
73200
  headers: {
73203
73201
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73216,9 +73214,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73216
73214
  }));
73217
73215
  case 1:
73218
73216
  case "end":
73219
- return _context3.stop();
73217
+ return _context2.stop();
73220
73218
  }
73221
- }, _callee3);
73219
+ }, _callee2);
73222
73220
  }));
73223
73221
  return _getDataBlob.apply(this, arguments);
73224
73222
  }
@@ -73226,12 +73224,12 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73226
73224
  return _preloadMediaBlob.apply(this, arguments);
73227
73225
  }
73228
73226
  function _preloadMediaBlob() {
73229
- _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(src, type, jwtToken) {
73227
+ _preloadMediaBlob = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(src, type, jwtToken) {
73230
73228
  var res, blob, data;
73231
- return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73232
- while (1) switch (_context4.prev = _context4.next) {
73229
+ return _regeneratorRuntime().wrap(function _callee3$(_context3) {
73230
+ while (1) switch (_context3.prev = _context3.next) {
73233
73231
  case 0:
73234
- _context4.next = 2;
73232
+ _context3.next = 2;
73235
73233
  return fetch(src, {
73236
73234
  method: 'GET',
73237
73235
  headers: {
@@ -73239,45 +73237,45 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73239
73237
  }
73240
73238
  });
73241
73239
  case 2:
73242
- res = _context4.sent;
73240
+ res = _context3.sent;
73243
73241
  blob = new Blob();
73244
73242
  if (!(type === 'image')) {
73245
- _context4.next = 8;
73243
+ _context3.next = 8;
73246
73244
  break;
73247
73245
  }
73248
73246
  blob = new Blob();
73249
- _context4.next = 19;
73247
+ _context3.next = 19;
73250
73248
  break;
73251
73249
  case 8:
73252
73250
  if (!(type === 'video')) {
73253
- _context4.next = 14;
73251
+ _context3.next = 14;
73254
73252
  break;
73255
73253
  }
73256
- _context4.next = 11;
73254
+ _context3.next = 11;
73257
73255
  return res.blob();
73258
73256
  case 11:
73259
- blob = _context4.sent;
73260
- _context4.next = 19;
73257
+ blob = _context3.sent;
73258
+ _context3.next = 19;
73261
73259
  break;
73262
73260
  case 14:
73263
73261
  if (!(type === 'audio')) {
73264
- _context4.next = 19;
73262
+ _context3.next = 19;
73265
73263
  break;
73266
73264
  }
73267
- _context4.next = 17;
73265
+ _context3.next = 17;
73268
73266
  return res.arrayBuffer();
73269
73267
  case 17:
73270
- data = _context4.sent;
73268
+ data = _context3.sent;
73271
73269
  blob = new Blob([data], {
73272
73270
  type: audioFileType(getFileExt(src))
73273
73271
  });
73274
73272
  case 19:
73275
- return _context4.abrupt("return", URL.createObjectURL(blob));
73273
+ return _context3.abrupt("return", URL.createObjectURL(blob));
73276
73274
  case 20:
73277
73275
  case "end":
73278
- return _context4.stop();
73276
+ return _context3.stop();
73279
73277
  }
73280
- }, _callee4);
73278
+ }, _callee3);
73281
73279
  }));
73282
73280
  return _preloadMediaBlob.apply(this, arguments);
73283
73281
  }
@@ -73285,11 +73283,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73285
73283
  return _fetchJSON.apply(this, arguments);
73286
73284
  }
73287
73285
  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) {
73286
+ _fetchJSON = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee4(url, jwtToken) {
73287
+ return _regeneratorRuntime().wrap(function _callee4$(_context4) {
73288
+ while (1) switch (_context4.prev = _context4.next) {
73291
73289
  case 0:
73292
- return _context5.abrupt("return", fetch(url, {
73290
+ return _context4.abrupt("return", fetch(url, {
73293
73291
  method: 'GET',
73294
73292
  headers: {
73295
73293
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73301,9 +73299,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73301
73299
  }));
73302
73300
  case 1:
73303
73301
  case "end":
73304
- return _context5.stop();
73302
+ return _context4.stop();
73305
73303
  }
73306
- }, _callee5);
73304
+ }, _callee4);
73307
73305
  }));
73308
73306
  return _fetchJSON.apply(this, arguments);
73309
73307
  }
@@ -73311,11 +73309,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73311
73309
  return _fetchText.apply(this, arguments);
73312
73310
  }
73313
73311
  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) {
73312
+ _fetchText = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(url, jwtToken) {
73313
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
73314
+ while (1) switch (_context5.prev = _context5.next) {
73317
73315
  case 0:
73318
- return _context6.abrupt("return", fetch(url, {
73316
+ return _context5.abrupt("return", fetch(url, {
73319
73317
  method: 'GET',
73320
73318
  headers: {
73321
73319
  'X-PREVIEW-JWT': jwtToken || ''
@@ -73334,9 +73332,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73334
73332
  }));
73335
73333
  case 1:
73336
73334
  case "end":
73337
- return _context6.stop();
73335
+ return _context5.stop();
73338
73336
  }
73339
- }, _callee6);
73337
+ }, _callee5);
73340
73338
  }));
73341
73339
  return _fetchText.apply(this, arguments);
73342
73340
  }
@@ -73453,6 +73451,32 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73453
73451
  var today = new Date();
73454
73452
  return new Date(today.setHours(24 * numDays || 1)).toJSON();
73455
73453
  }
73454
+ /**
73455
+ * Check whether a media item is currently within its valid date window.
73456
+ * Returns true when the media should be shown, false when it should be skipped.
73457
+ *
73458
+ * Rules:
73459
+ * - Empty / invalid fromDt → treat as "no start restriction"
73460
+ * - Empty / invalid toDt → treat as "no expiry"
73461
+ * - now < fromDt → not yet active → skip
73462
+ * - now > toDt → expired → skip
73463
+ */
73464
+ function isMediaActive(fromDt, toDt) {
73465
+ var now = Date.now();
73466
+ if (fromDt) {
73467
+ var from = new Date(fromDt).getTime();
73468
+ if (!isNaN(from) && now < from) {
73469
+ return false;
73470
+ }
73471
+ }
73472
+ if (toDt) {
73473
+ var to = new Date(toDt).getTime();
73474
+ if (!isNaN(to) && now > to) {
73475
+ return false;
73476
+ }
73477
+ }
73478
+ return true;
73479
+ }
73456
73480
  /**
73457
73481
  * Check if given layout exists in the loop using layoutId
73458
73482
  * @param layouts Schedule loop unique layouts (uniqueLayouts)
@@ -73661,7 +73685,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73661
73685
  var $layout = region.layout.html;
73662
73686
  var layoutSelector = '#' + region.layout.containerName + '[data-sequence="' + region.layout.index + '"]';
73663
73687
  var $layoutWithIndex = document.querySelector(layoutSelector);
73664
- var $region = document.querySelector('#' + region.containerName);
73688
+ var $region = region.html;
73665
73689
  var mediaInRegion = $region === null || $region === void 0 ? void 0 : $region.querySelector('.' + mediaId);
73666
73690
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73667
73691
  layoutSelector: layoutSelector,
@@ -73680,7 +73704,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73680
73704
  media.html = createMediaElement(media);
73681
73705
  }
73682
73706
  // Append fresh copy of the media into the region
73683
- $region !== null && $region.appendChild(media.html);
73707
+ region.html.appendChild(media.html);
73684
73708
  var isMediaInDOM = document.body.contains(media.html);
73685
73709
  console.debug('??? XLR.debug >> [Generators::prepareVideoMedia]', {
73686
73710
  isMediaInDOM: isMediaInDOM,
@@ -73689,29 +73713,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73689
73713
  });
73690
73714
  // Initialize video.js
73691
73715
  media.player = videojs(mediaId, _objectSpread2(_objectSpread2({}, defaultVjsOpts), {}, {
73692
- errorDisplay: region.xlr.config.platform !== exports.ConsumerPlatform.CHROMEOS,
73716
+ errorDisplay: !reportToPlayerPlatform.includes(region.xlr.config.platform),
73693
73717
  loop: media.loop
73694
73718
  }));
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
73719
  media.player.el().style.setProperty('visibility', 'hidden');
73716
73720
  media.player.el().style.setProperty('opacity', '0');
73717
73721
  media.player.el().style.setProperty('z-index', '-99');
@@ -73726,9 +73730,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73726
73730
  if (mediaInRegion) {
73727
73731
  mediaInRegion.remove();
73728
73732
  }
73729
- // Append media to its region
73730
- var $region = document.querySelector('#' + region.containerName);
73731
- $region !== null && $region.appendChild(media.html);
73733
+ // Append media to its region using the direct reference to avoid
73734
+ // global querySelector finding a same-named region in another layout
73735
+ region.html.appendChild(media.html);
73732
73736
  }
73733
73737
  function prepareAudioMedia(media, region) {
73734
73738
  var mediaId = getMediaId(media);
@@ -73741,9 +73745,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73741
73745
  if (mediaInRegion) {
73742
73746
  mediaInRegion.remove();
73743
73747
  }
73744
- // Append media to its region
73745
- var $region = document.querySelector('#' + region.containerName);
73746
- $region !== null && $region.appendChild(media.html);
73748
+ // Append media to its region using the direct reference
73749
+ region.html.appendChild(media.html);
73747
73750
  }
73748
73751
  function prepareHtmlMedia(media, region) {
73749
73752
  // Set state as false ( for now )
@@ -73763,57 +73766,92 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73763
73766
  media.html.innerHTML = '';
73764
73767
  media.html.appendChild(media.iframe);
73765
73768
  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);
73769
+ // Add fresh copy of the media into the region using the direct reference
73770
+ region.html.appendChild(media.html);
73769
73771
  media.ready = true;
73770
73772
  }
73771
73773
  }
73772
73774
  }
73773
- function playerReportFault(_x11, _x12) {
73775
+ exports.FaultCodes = void 0;
73776
+ (function (FaultCodes) {
73777
+ FaultCodes[FaultCodes["FaultVideoSource"] = 2001] = "FaultVideoSource";
73778
+ FaultCodes[FaultCodes["FaultVideoUnexpected"] = 2099] = "FaultVideoUnexpected";
73779
+ })(exports.FaultCodes || (exports.FaultCodes = {}));
73780
+ function playerReportFault(_x10, _x11) {
73774
73781
  return _playerReportFault.apply(this, arguments);
73775
73782
  }
73776
73783
  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) {
73784
+ _playerReportFault = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee6(msg, media) {
73785
+ var code,
73786
+ platform,
73787
+ playerSW,
73788
+ hasSW,
73789
+ mediaFault,
73790
+ channel,
73791
+ _args6 = arguments;
73792
+ return _regeneratorRuntime().wrap(function _callee6$(_context6) {
73793
+ while (1) switch (_context6.prev = _context6.next) {
73781
73794
  case 0:
73795
+ code = _args6.length > 2 && _args6[2] !== undefined ? _args6[2] : exports.FaultCodes.FaultVideoUnexpected;
73782
73796
  // Immediately expire media and report a fault
73797
+ platform = media.region.xlr.config.platform;
73783
73798
  playerSW = PwaSW();
73784
- _context7.next = 3;
73799
+ _context6.next = 5;
73785
73800
  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,
73801
+ case 5:
73802
+ hasSW = _context6.sent;
73803
+ mediaFault = {
73804
+ type: 'MEDIA_FAULT',
73805
+ code: code,
73806
+ reason: msg,
73807
+ mediaId: media.id,
73808
+ regionId: media.region.id,
73809
+ layoutId: media.region.layout.id,
73810
+ date: format(new Date(), 'yyyy-MM-dd HH:mm:ss'),
73811
+ // Temporary setting
73812
+ expires: format(new Date(setExpiry(1)), 'yyyy-MM-dd HH:mm:ss')
73813
+ };
73814
+ console.debug('playerReportFault >> Reporting media fault', {
73815
+ mediaFault: mediaFault,
73816
+ platform: platform,
73817
+ hasSW: hasSW
73818
+ });
73819
+ if (!(platform === exports.ConsumerPlatform.CHROMEOS && hasSW)) {
73820
+ _context6.next = 12;
73821
+ break;
73822
+ }
73823
+ return _context6.abrupt("return", playerSW.postMsg(mediaFault).then(function () {
73824
+ // We try to prepare next media if we have more than 1 media
73825
+ if (media.region.totalMediaObjects > 1) {
73826
+ media.region.prepareNextMedia();
73827
+ }
73828
+ })["finally"](function () {
73829
+ // Stopping media as we have reported the error as fault
73830
+ console.debug('??? XLR.debug >> VideoMedia - Done reporting media fault', {
73793
73831
  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
- });
73832
+ regionItems: media.region.totalMediaObjects
73810
73833
  });
73834
+ }));
73835
+ case 12:
73836
+ if (!(platform === exports.ConsumerPlatform.ELECTRON)) {
73837
+ _context6.next = 17;
73838
+ break;
73811
73839
  }
73812
- case 5:
73840
+ // Create a broadcast channel to report media fault to the main process
73841
+ channel = new BroadcastChannel('player-faults-bc');
73842
+ channel.postMessage(mediaFault);
73843
+ console.debug('playerReportFault >> Electron platform - posted media fault to channel', {
73844
+ mediaFault: mediaFault
73845
+ });
73846
+ // channel.close();
73847
+ return _context6.abrupt("return", Promise.resolve());
73848
+ case 17:
73849
+ return _context6.abrupt("return", Promise.resolve());
73850
+ case 18:
73813
73851
  case "end":
73814
- return _context7.stop();
73852
+ return _context6.stop();
73815
73853
  }
73816
- }, _callee7);
73854
+ }, _callee6);
73817
73855
  }));
73818
73856
  return _playerReportFault.apply(this, arguments);
73819
73857
  }
@@ -73841,7 +73879,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73841
73879
  function AudioMedia(media) {
73842
73880
  var audioMediaObject = {
73843
73881
  init: function init() {
73844
- var $audioMedia = document.getElementById(getMediaId(media));
73882
+ var $audioMedia = media.html;
73845
73883
  var $playBtn = null;
73846
73884
  if ($audioMedia) {
73847
73885
  $audioMedia.onloadstart = function () {
@@ -73904,6 +73942,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73904
73942
  _this$xml3,
73905
73943
  _this$xml4,
73906
73944
  _this$xml5,
73945
+ _this$xml6,
73946
+ _this$xml7,
73907
73947
  _this = this;
73908
73948
  _classCallCheck(this, Media);
73909
73949
  _defineProperty(this, "attachedAudio", false);
@@ -73916,6 +73956,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73916
73956
  _defineProperty(this, "enableStat", false);
73917
73957
  _defineProperty(this, "fileId", '');
73918
73958
  _defineProperty(this, "finished", false);
73959
+ _defineProperty(this, "fromDt", '');
73919
73960
  _defineProperty(this, "html", null);
73920
73961
  _defineProperty(this, "id", '');
73921
73962
  _defineProperty(this, "idCounter", 0);
@@ -73937,6 +73978,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73937
73978
  _defineProperty(this, "state", MediaState.IDLE);
73938
73979
  _defineProperty(this, "tempSrc", '');
73939
73980
  _defineProperty(this, "timeoutId", setTimeout(function () {}, 0));
73981
+ _defineProperty(this, "toDt", '');
73940
73982
  _defineProperty(this, "type", '');
73941
73983
  _defineProperty(this, "uri", '');
73942
73984
  _defineProperty(this, "url", null);
@@ -73944,6 +73986,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73944
73986
  _defineProperty(this, "xml", null);
73945
73987
  _defineProperty(this, "videoHandler", void 0);
73946
73988
  _defineProperty(this, "mediaTimer", void 0);
73989
+ _defineProperty(this, "sspImpressionUrls", undefined);
73990
+ _defineProperty(this, "sspErrorUrls", undefined);
73991
+ _defineProperty(this, "isSspWidget", false);
73947
73992
  _defineProperty(this, "mediaTimeCount", 0);
73948
73993
  _defineProperty(this, "xlr", {});
73949
73994
  _defineProperty(this, "statsBC", new BroadcastChannel('statsBC'));
@@ -73963,12 +74008,14 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
73963
74008
  this.duration = parseInt((_this$xml4 = this.xml) === null || _this$xml4 === void 0 ? void 0 : _this$xml4.getAttribute('duration')) || 0;
73964
74009
  this.enableStat = Boolean(((_this$xml5 = this.xml) === null || _this$xml5 === void 0 ? void 0 : _this$xml5.getAttribute('enableStat')) || false);
73965
74010
  this.hasCommandExecuted = false;
74011
+ this.fromDt = ((_this$xml6 = this.xml) === null || _this$xml6 === void 0 ? void 0 : _this$xml6.getAttribute('fromDt')) || '';
74012
+ this.toDt = ((_this$xml7 = this.xml) === null || _this$xml7 === void 0 ? void 0 : _this$xml7.getAttribute('toDt')) || '';
73966
74013
  this.on('start', function (media) {
73967
74014
  if (media.state === MediaState.PLAYING) return;
73968
74015
  media.state = MediaState.PLAYING;
73969
74016
  if (media.mediaType === 'video') {
73970
- var videoMedia = VideoMedia(media, _this.xlr);
73971
- videoMedia.init();
74017
+ media.videoHandler = VideoMedia(media, _this.xlr);
74018
+ media.videoHandler.init();
73972
74019
  if (media.duration > 0) {
73973
74020
  _this.startMediaTimer(media);
73974
74021
  }
@@ -74027,6 +74074,10 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74027
74074
  layoutId: media.region.layout.id
74028
74075
  });
74029
74076
  _this.xlr.emitter.emit('widgetEnd', parseInt(media.id));
74077
+ if (_this.isSspWidget) {
74078
+ var _this$sspImpressionUr, _this$sspErrorUrls;
74079
+ _this.xlr.emitter.emit('sspWidgetEnd', (_this$sspImpressionUr = _this.sspImpressionUrls) !== null && _this$sspImpressionUr !== void 0 ? _this$sspImpressionUr : [], (_this$sspErrorUrls = _this.sspErrorUrls) !== null && _this$sspErrorUrls !== void 0 ? _this$sspErrorUrls : [], _this.sspImpressionUrls ? _this.duration : 0);
74080
+ }
74030
74081
  media.region.playNextMedia();
74031
74082
  });
74032
74083
  this.on('cancelled', function (media) {
@@ -74053,6 +74104,10 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74053
74104
  layoutId: media.region.layout.id
74054
74105
  });
74055
74106
  _this.xlr.emitter.emit('widgetEnd', parseInt(media.id));
74107
+ if (_this.isSspWidget) {
74108
+ var _this$sspImpressionUr2, _this$sspErrorUrls2;
74109
+ _this.xlr.emitter.emit('sspWidgetEnd', (_this$sspImpressionUr2 = _this.sspImpressionUrls) !== null && _this$sspImpressionUr2 !== void 0 ? _this$sspImpressionUr2 : [], (_this$sspErrorUrls2 = _this.sspErrorUrls) !== null && _this$sspErrorUrls2 !== void 0 ? _this$sspErrorUrls2 : [], _this.sspImpressionUrls ? _this.duration : 0);
74110
+ }
74056
74111
  media.region.playNextMedia();
74057
74112
  });
74058
74113
  // Initialize Media object
@@ -74095,8 +74150,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74095
74150
  if (media.mediaType === 'video') {
74096
74151
  // Dispose the video media
74097
74152
  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);
74153
+ if (media.videoHandler !== undefined) {
74154
+ media.videoHandler.stop(true);
74100
74155
  }
74101
74156
  }
74102
74157
  }
@@ -74111,8 +74166,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74111
74166
  }, {
74112
74167
  key: "init",
74113
74168
  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');
74169
+ var _this$xml8;
74170
+ var mediaOptions = (_this$xml8 = this.xml) === null || _this$xml8 === void 0 ? void 0 : _this$xml8.getElementsByTagName('options');
74116
74171
  if (mediaOptions) {
74117
74172
  for (var _i = 0, _Array$from = Array.from(mediaOptions); _i < _Array$from.length; _i++) {
74118
74173
  var _options = _Array$from[_i];
@@ -74153,22 +74208,33 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74153
74208
  if (this.mediaType === 'image' || this.mediaType === 'video') {
74154
74209
  resourceUrlParams.mediaType = this.mediaType;
74155
74210
  }
74156
- var tmpUrl = '';
74157
- if (this.xlr.config.platform === exports.ConsumerPlatform.CMS) {
74158
- tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74159
- } else if (this.xlr.config.platform === exports.ConsumerPlatform.CHROMEOS) {
74160
- tmpUrl = composeResourceUrl(this.xlr.config, resourceUrlParams);
74161
- if (this.mediaType === 'image' || this.mediaType === 'video' || this.mediaType === 'audio') {
74162
- tmpUrl = composeMediaUrl(resourceUrlParams);
74211
+ // SSP widget: URL is not known until the consumer resolves an ad at play-time.
74212
+ // Skip all URL composition and leave url as null.
74213
+ if (this.mediaType === 'ssp') {
74214
+ this.url = null;
74215
+ this.isSspWidget = true;
74216
+ } else {
74217
+ var tmpUrl = '';
74218
+ if (this.xlr.config.platform === exports.ConsumerPlatform.CMS) {
74219
+ tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74220
+ } else if (this.xlr.config.platform === exports.ConsumerPlatform.CHROMEOS) {
74221
+ tmpUrl = composeResourceUrl(this.xlr.config, resourceUrlParams);
74222
+ if (this.mediaType === 'image' || this.mediaType === 'video' || this.mediaType === 'audio') {
74223
+ tmpUrl = composeMediaUrl(resourceUrlParams);
74224
+ // this is an SSP Layout
74225
+ if (this.region.layout.layoutId === -1) {
74226
+ tmpUrl = this.uri;
74227
+ }
74228
+ }
74229
+ } else if (this.xlr.config.platform === exports.ConsumerPlatform.ELECTRON) {
74230
+ tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74163
74231
  // this is an SSP Layout
74164
74232
  if (this.region.layout.layoutId === -1) {
74165
74233
  tmpUrl = this.uri;
74166
74234
  }
74167
74235
  }
74168
- } else if (this.xlr.config.platform === exports.ConsumerPlatform.ELECTRON) {
74169
- tmpUrl = composeResourceUrlByPlatform(this.xlr.config, resourceUrlParams);
74236
+ this.url = tmpUrl;
74170
74237
  }
74171
- this.url = tmpUrl;
74172
74238
  // Loop if media has loop, or if region has loop and a single media
74173
74239
  this.loop = this.options['loop'] == '1' || this.region.options['loop'] == '1' && this.region.totalMediaObjects == 1;
74174
74240
  this.html = createMediaElement(this);
@@ -74209,8 +74275,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74209
74275
  mediaType: _this3.mediaType,
74210
74276
  containerName: _this3.containerName
74211
74277
  });
74212
- var $region = document.querySelector('#' + _this3.region.containerName);
74213
- var $media = $region !== null && $region.querySelector('.' + mediaId);
74278
+ var $region = _this3.region.html;
74279
+ var $media = $region.querySelector('.' + mediaId);
74214
74280
  if (!$media) {
74215
74281
  $media = getNewMedia();
74216
74282
  }
@@ -74274,40 +74340,66 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74274
74340
  }
74275
74341
  };
74276
74342
  var getNewMedia = function getNewMedia() {
74277
- var $region = document.getElementById("".concat(_this3.region.containerName));
74343
+ var $region = _this3.region.html;
74278
74344
  // This function is for checking whether
74279
74345
  // the region still has to show a media item
74280
74346
  // when another region is not finished yet
74281
74347
  if (_this3.region.complete && !_this3.region.layout.allEnded) {
74282
74348
  // Add currentMedia to the region
74283
- $region && $region.insertBefore(_this3.html, $region.lastElementChild);
74349
+ $region.insertBefore(_this3.html, $region.lastElementChild);
74284
74350
  return _this3.html;
74285
74351
  }
74286
74352
  return null;
74287
74353
  };
74354
+ // SSP widget: if the consumer did not resolve an ad during the preload window
74355
+ // (i.e. setSspAdUrl was never called), skip this widget and advance normally.
74356
+ if (this.mediaType === 'ssp') {
74357
+ console.debug('??? XLR.debug >> Media.run() > SSP widget: no ad resolved during preload, skipping');
74358
+ this.emitter.emit('end', this);
74359
+ return;
74360
+ }
74288
74361
  showCurrentMedia();
74289
74362
  }
74363
+ }, {
74364
+ key: "setSspAdUrl",
74365
+ value: function setSspAdUrl(url, adMediaType, impressionUrls, errorUrls) {
74366
+ // Ignore if the media has already been skipped or cancelled before the ad arrived.
74367
+ if (this.state !== MediaState.IDLE) {
74368
+ console.debug('??? XLR.debug >> Media::setSspAdUrl - ignoring, media is no longer idle', {
74369
+ state: this.state
74370
+ });
74371
+ return;
74372
+ }
74373
+ // Remove the placeholder <div> so the correct element type can take its place.
74374
+ if (this.html) {
74375
+ this.html.remove();
74376
+ this.html = null;
74377
+ }
74378
+ this.url = url;
74379
+ this.mediaType = adMediaType;
74380
+ this.sspImpressionUrls = impressionUrls;
74381
+ this.sspErrorUrls = errorUrls;
74382
+ // Re-create the element now that mediaType is known, then prepare and append to region DOM.
74383
+ // Visibility and playback are handled by run() when this media's turn comes.
74384
+ this.html = createMediaElement(this);
74385
+ this.region.prepareMedia(this);
74386
+ }
74290
74387
  }, {
74291
74388
  key: "stop",
74292
74389
  value: function () {
74293
74390
  var _stop = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
74294
- var $media;
74295
74391
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74296
74392
  while (1) switch (_context.prev = _context.next) {
74297
74393
  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();
74394
+ if (this.html) {
74395
+ this.html.style.display = 'none';
74396
+ this.html.remove();
74305
74397
  }
74306
74398
  // Release blob URLs for image media to prevent memory leaks on long-running signage
74307
74399
  if (this.mediaType === 'image' && this.url) {
74308
74400
  BlobLoader.release(this.url);
74309
74401
  }
74310
- case 3:
74402
+ case 2:
74311
74403
  case "end":
74312
74404
  return _context.stop();
74313
74405
  }
@@ -74480,18 +74572,30 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74480
74572
  this.html = $region;
74481
74573
  /* Parse region media objects */
74482
74574
  var regionMediaItems = Array.from(this.xml.getElementsByTagName('media'));
74483
- this.totalMediaObjects = regionMediaItems.length;
74484
74575
  $layout && $layout.appendChild(this.html);
74485
74576
  Array.from(regionMediaItems).forEach( /*#__PURE__*/function () {
74486
74577
  var _ref = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(mediaXml, indx) {
74487
- var mediaObj;
74578
+ var fromDt, toDt, mediaObj;
74488
74579
  return _regeneratorRuntime().wrap(function _callee$(_context) {
74489
74580
  while (1) switch (_context.prev = _context.next) {
74490
74581
  case 0:
74582
+ fromDt = mediaXml.getAttribute('fromDt') || '';
74583
+ toDt = mediaXml.getAttribute('toDt') || '';
74584
+ if (isMediaActive(fromDt, toDt)) {
74585
+ _context.next = 5;
74586
+ break;
74587
+ }
74588
+ console.debug('??? XLR.debug >> Region::prepareRegion - skipping expired/inactive media', {
74589
+ mediaId: mediaXml.getAttribute('id'),
74590
+ fromDt: fromDt,
74591
+ toDt: toDt
74592
+ });
74593
+ return _context.abrupt("return");
74594
+ case 5:
74491
74595
  mediaObj = new Media(_this, (mediaXml === null || mediaXml === void 0 ? void 0 : mediaXml.getAttribute('id')) || '', mediaXml, _this.options, _this.xlr);
74492
74596
  mediaObj.index = indx;
74493
74597
  _this.mediaObjects.push(mediaObj);
74494
- case 3:
74598
+ case 8:
74495
74599
  case "end":
74496
74600
  return _context.stop();
74497
74601
  }
@@ -74501,6 +74605,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74501
74605
  return _ref.apply(this, arguments);
74502
74606
  };
74503
74607
  }());
74608
+ this.totalMediaObjects = this.mediaObjects.length;
74504
74609
  console.debug('??? XLR.debug >> Region - done looping through media', {
74505
74610
  mediaObjects: this.mediaObjects
74506
74611
  });
@@ -74531,6 +74636,13 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74531
74636
  }, {
74532
74637
  key: "prepareMedia",
74533
74638
  value: function prepareMedia(media) {
74639
+ // SSP widget: signal the consumer to fetch an ad before this media's turn to play.
74640
+ // The consumer calls media.setSspAdUrl() to resolve it; if it never arrives,
74641
+ // Media.run() skips the widget and advances normally.
74642
+ if (media.mediaType === 'ssp') {
74643
+ this.xlr.emitter.emit('sspWidgetRequest', media);
74644
+ return;
74645
+ }
74534
74646
  if (media.mediaType === 'video') {
74535
74647
  prepareVideoMedia(media, this);
74536
74648
  } else if (media.mediaType === 'image' && media.url !== null) {
@@ -74556,6 +74668,23 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74556
74668
  key: "prepareNextMedia",
74557
74669
  value: function prepareNextMedia() {
74558
74670
  var nextMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74671
+ // Skip over any media items that are no longer within their active date window
74672
+ var skippedCount = 0;
74673
+ while (skippedCount < this.totalMediaObjects) {
74674
+ var candidate = this.mediaObjects[nextMediaIndex];
74675
+ if (isMediaActive(candidate.fromDt, candidate.toDt)) {
74676
+ break;
74677
+ }
74678
+ skippedCount++;
74679
+ nextMediaIndex = (nextMediaIndex + 1) % this.totalMediaObjects;
74680
+ }
74681
+ // Nothing to pre-load when:
74682
+ // - every item is expired, OR
74683
+ // - the only active item is the one currently playing (skip loop wrapped back to it)
74684
+ if (skippedCount >= this.totalMediaObjects || nextMediaIndex === this.currentMediaIndex) {
74685
+ console.debug('<><> XLR.debug >> [Region::prepareNextMedia()] - no active next media to preload');
74686
+ return;
74687
+ }
74559
74688
  var nextMedia = this.mediaObjects[nextMediaIndex];
74560
74689
  console.debug('<><> XLR.debug >> [Media] - [Region::prepareNextMedia()] - Preparing next media', {
74561
74690
  currentMediaIndex: this.currentMediaIndex,
@@ -74596,9 +74725,21 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74596
74725
  }, {
74597
74726
  key: "run",
74598
74727
  value: function run() {
74728
+ var _this$currMedia, _this$oldMedia2;
74599
74729
  console.debug('??? XLR.debug >> Region Called Region::run > ', this.id);
74600
74730
  // Reset region states
74601
74731
  this.reset();
74732
+ // All media were filtered out (all expired/inactive before this run started)
74733
+ if (this.mediaObjects.length === 0) {
74734
+ console.debug('??? XLR.debug >> Region::run - no active media, finishing region', this.id);
74735
+ this.finished();
74736
+ return;
74737
+ }
74738
+ console.debug('??? XLR.debug >> Region Called Region::run - after reset > ', {
74739
+ regionId: this.id,
74740
+ currMedia: (_this$currMedia = this.currMedia) === null || _this$currMedia === void 0 ? void 0 : _this$currMedia.containerName,
74741
+ oldMedia: (_this$oldMedia2 = this.oldMedia) === null || _this$oldMedia2 === void 0 ? void 0 : _this$oldMedia2.containerName
74742
+ });
74602
74743
  if (this.currMedia) {
74603
74744
  this.transitionNodes(this.oldMedia, this.currMedia);
74604
74745
  }
@@ -74647,6 +74788,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74647
74788
  // Hide oldMedia
74648
74789
  if (oldMedia) {
74649
74790
  var $layout = document.querySelector("#".concat(_this2.layout.containerName, "[data-sequence=\"").concat(_this2.layout.index, "\"]"));
74791
+ if (!$layout) return;
74650
74792
  var $region = $layout.querySelector('#' + _this2.containerName);
74651
74793
  var $oldMedia = $region ? $region.querySelector('.' + getMediaId(oldMedia)) : null;
74652
74794
  if ($oldMedia) {
@@ -74667,7 +74809,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74667
74809
  $videoWrapper.style.setProperty('visibility', 'hidden');
74668
74810
  $videoWrapper.style.setProperty('z-index', '-999');
74669
74811
  $videoWrapper.style.setProperty('opacity', '0');
74670
- if (oldMedia.player && oldMedia.videoHandler) {
74812
+ if (oldMedia.videoHandler) {
74671
74813
  oldMedia.videoHandler.stop(true);
74672
74814
  }
74673
74815
  }
@@ -74717,7 +74859,12 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74717
74859
  }
74718
74860
  };
74719
74861
  if (oldMedia) {
74720
- hideOldMedia();
74862
+ // Skip hiding old media when it is the same object as new media
74863
+ // (single-media loop): removing it would also remove the element
74864
+ // that is about to be shown, leaving the region blank.
74865
+ if (oldMedia !== newMedia) {
74866
+ hideOldMedia();
74867
+ }
74721
74868
  newMedia.run();
74722
74869
  } else {
74723
74870
  newMedia.run();
@@ -74727,13 +74874,13 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74727
74874
  }, {
74728
74875
  key: "playNextMedia",
74729
74876
  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;
74877
+ var _this$oldMedia3, _this$currMedia2, _this$nxtMedia, _this$currMedia3, _this$currMedia4, _this$currMedia5, _this$currMedia6, _this$oldMedia4, _this$currMedia7, _this$nxtMedia2;
74731
74878
  console.debug('??? XLR.debug Region playing next media', {
74732
74879
  regionId: this.id,
74733
74880
  currentMediaIndex: this.currentMediaIndex,
74734
74881
  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,
74882
+ oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74883
+ currMedia: (_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.containerName,
74737
74884
  nxtMedia: (_this$nxtMedia = this.nxtMedia) === null || _this$nxtMedia === void 0 ? void 0 : _this$nxtMedia.containerName
74738
74885
  });
74739
74886
  /* The current media has finished running */
@@ -74743,33 +74890,20 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74743
74890
  });
74744
74891
  return;
74745
74892
  }
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
- }
74893
+ // Snapshot the index of the media that just ended so we can detect
74894
+ // cycle completion after the skip loop runs.
74895
+ var origIndex = this.currentMediaIndex;
74764
74896
  // When the region has completed and when currentMedia is html
74765
74897
  // Then, preserve the currentMedia state
74766
- if (this.complete && ((_this$currMedia2 = this.currMedia) === null || _this$currMedia2 === void 0 ? void 0 : _this$currMedia2.render) === 'html') {
74898
+ if (this.complete && ((_this$currMedia3 = this.currMedia) === null || _this$currMedia3 === void 0 ? void 0 : _this$currMedia3.render) === 'html') {
74767
74899
  return;
74768
74900
  }
74769
74901
  // 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)) {
74902
+ // and render is not html, preserve the current media state without
74903
+ // calling transitionNodes (which would remove the media from DOM for 1s).
74904
+ // Do NOT restart the media timer here the layout will end naturally when
74905
+ // regionExpired() detects all regions complete on the next cycle.
74906
+ 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
74907
  return;
74774
74908
  }
74775
74909
  if (this.currMedia) {
@@ -74777,17 +74911,51 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74777
74911
  } else {
74778
74912
  this.oldMedia = undefined;
74779
74913
  }
74780
- this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74914
+ this.currentMediaIndex = (origIndex + 1) % this.totalMediaObjects;
74781
74915
  this.currMedia = this.mediaObjects[this.currentMediaIndex];
74916
+ // Skip media items that are no longer within their active date window
74917
+ var skippedCount = 0;
74918
+ while (this.currMedia && !isMediaActive(this.currMedia.fromDt, this.currMedia.toDt)) {
74919
+ skippedCount++;
74920
+ if (skippedCount >= this.totalMediaObjects) {
74921
+ // Every item in the playlist has expired; finish this region
74922
+ console.debug('??? XLR.debug >> Region::playNextMedia - all media expired, finishing region', this.id);
74923
+ this.finished();
74924
+ return;
74925
+ }
74926
+ this.currentMediaIndex = (this.currentMediaIndex + 1) % this.totalMediaObjects;
74927
+ this.currMedia = this.mediaObjects[this.currentMediaIndex];
74928
+ }
74782
74929
  this.nxtMedia = this.mediaObjects[(this.currentMediaIndex + 1) % this.totalMediaObjects];
74930
+ // A full playlist cycle has been traversed when the total advancement
74931
+ // (the initial +1 step plus any skips over expired items) reaches or
74932
+ // crosses the end of the array.
74933
+ var crossedEnd = origIndex + 1 + skippedCount >= this.totalMediaObjects;
74934
+ // Re-enable shell command widgets at the end of each full cycle so they
74935
+ // execute again on the next pass.
74936
+ if (crossedEnd && this.mediaObjects.length > 1) {
74937
+ this.mediaObjects.forEach(function (media) {
74938
+ if (media.mediaType === 'shellcommand') {
74939
+ // reset per-playlist-cycle execution state
74940
+ media.hasCommandExecuted = false;
74941
+ }
74942
+ });
74943
+ }
74783
74944
  console.debug('??? XLR.debug >> End Region::playNextMedia > execute transitionNodes', {
74784
74945
  regionId: this.id,
74785
74946
  currentMediaIndex: this.currentMediaIndex,
74786
74947
  mediaItemsLn: this.mediaObjects.length,
74787
- oldMedia: (_this$oldMedia3 = this.oldMedia) === null || _this$oldMedia3 === void 0 ? void 0 : _this$oldMedia3.containerName,
74948
+ oldMedia: (_this$oldMedia4 = this.oldMedia) === null || _this$oldMedia4 === void 0 ? void 0 : _this$oldMedia4.containerName,
74788
74949
  currMedia: (_this$currMedia7 = this.currMedia) === null || _this$currMedia7 === void 0 ? void 0 : _this$currMedia7.containerName,
74789
74950
  nxtMedia: (_this$nxtMedia2 = this.nxtMedia) === null || _this$nxtMedia2 === void 0 ? void 0 : _this$nxtMedia2.containerName
74790
74951
  });
74952
+ if (!this.layout.isOverlay && crossedEnd) {
74953
+ this.finished();
74954
+ if (this.layout.allEnded) {
74955
+ console.debug('??? XLR.debug >> Region - playNextMedia - layout all ended');
74956
+ return;
74957
+ }
74958
+ }
74791
74959
  this.transitionNodes(this.oldMedia, this.currMedia);
74792
74960
  }
74793
74961
  }, {
@@ -74816,8 +74984,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
74816
74984
  }, {
74817
74985
  key: "exitTransition",
74818
74986
  value: function exitTransition() {
74819
- /* TODO: Actually implement region exit transitions */
74820
- document.getElementById("".concat(this.containerName));
74987
+ /* TODO: Actually implement region exit transitions using this.html */
74821
74988
  console.debug('Called Region::exitTransition ', this.id);
74822
74989
  this.exitTransitionComplete();
74823
74990
  }
@@ -75117,7 +75284,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75117
75284
  if (dataset.source === 'region') {
75118
75285
  // Try to find the region
75119
75286
  if (regionObj.id === dataset.sourceid) {
75120
- $sourceObj = document.getElementById(regionObj.containerName);
75287
+ $sourceObj = regionObj.html;
75121
75288
  break;
75122
75289
  }
75123
75290
  } else if (dataset.source === 'widget') {
@@ -75126,7 +75293,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75126
75293
  for (var _i2 = 0, _mediaObjects = mediaObjects; _i2 < _mediaObjects.length; _i2++) {
75127
75294
  var mediaObject = _mediaObjects[_i2];
75128
75295
  if (mediaObject.id === dataset.sourceid) {
75129
- $sourceObj = document.getElementById(mediaObject.containerName);
75296
+ $sourceObj = mediaObject.html;
75130
75297
  break;
75131
75298
  }
75132
75299
  }
@@ -75391,7 +75558,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75391
75558
  this.on('start', function (layout) {
75392
75559
  layout.done = false;
75393
75560
  layout.state = exports.ELayoutState.RUNNING;
75394
- console.debug('>>>> XLR.debug Layout start emitted > Layout ID > ', layout.id);
75561
+ console.debug('>>>> XLR.debug Layout start emitted > Layout > ', {
75562
+ layoutId: layout.id,
75563
+ layoutIndex: layout.index,
75564
+ layoutState: layout.state
75565
+ });
75395
75566
  // Check if stats are enabled for the layout
75396
75567
  if (layout.enableStat) {
75397
75568
  _this.statsBC.postMessage({
@@ -75412,12 +75583,21 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75412
75583
  while (1) switch (_context2.prev = _context2.next) {
75413
75584
  case 0:
75414
75585
  if (!(layout.state === exports.ELayoutState.CANCELLED)) {
75415
- _context2.next = 2;
75586
+ _context2.next = 3;
75416
75587
  break;
75417
75588
  }
75589
+ console.debug('>>>> XLR.debug Layout end emitted but layout is already cancelled > Layout ID > ', {
75590
+ layoutId: layout.id,
75591
+ layoutIndex: layout.index,
75592
+ layoutState: layout.state
75593
+ });
75418
75594
  return _context2.abrupt("return");
75419
- case 2:
75420
- console.debug('>>>> XLR.debug Ending layout with ID of > ', layout.layoutId);
75595
+ case 3:
75596
+ console.debug('>>>> XLR.debug Ending layout', {
75597
+ layoutId: layout.id,
75598
+ layoutIndex: layout.index,
75599
+ layoutState: layout.state
75600
+ });
75421
75601
  /* Remove layout that has ended */
75422
75602
  $layout = document.querySelector("#".concat(layout.containerName, "[data-sequence=\"").concat(layout.index, "\"]")); // Only update layout.state when last state === RUNNING
75423
75603
  if (layout.state === exports.ELayoutState.RUNNING) {
@@ -75428,6 +75608,8 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75428
75608
  console.debug('>>> XLR.debug Layout end emitted > Layout ID > ', {
75429
75609
  $layout: $layout,
75430
75610
  layoutId: layout.id,
75611
+ layoutIndex: layout.index,
75612
+ layoutState: layout.state,
75431
75613
  isOverlay: layout.isOverlay,
75432
75614
  isInterrupt: layout.isInterrupt()
75433
75615
  });
@@ -75454,9 +75636,9 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75454
75636
  }
75455
75637
  // Emit layout end event
75456
75638
  console.debug('>>>>> XLR.debug Awaited XLR::emitSync > End - Calling layoutEnd event');
75457
- _context2.next = 12;
75639
+ _context2.next = 13;
75458
75640
  return layout.xlr.emitSync('layoutEnd', layout);
75459
- case 12:
75641
+ case 13:
75460
75642
  if (_this.xlr.config.platform !== exports.ConsumerPlatform.CMS && layout.inLoop) {
75461
75643
  // Transition next layout to current layout and prepare next layout if exist
75462
75644
  _this.xlr.prepareLayouts().then( /*#__PURE__*/function () {
@@ -75486,7 +75668,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75486
75668
  };
75487
75669
  }());
75488
75670
  }
75489
- case 13:
75671
+ case 14:
75490
75672
  case "end":
75491
75673
  return _context2.stop();
75492
75674
  }
@@ -75499,6 +75681,34 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75499
75681
  this.on('cancelled', function (layout) {
75500
75682
  console.debug('>>>>> XLR.debug / Layout cancelled > Layout ID > ', layout.id);
75501
75683
  layout.state = exports.ELayoutState.CANCELLED;
75684
+ layout.inLoop = false;
75685
+ // Dispose video handlers immediately so their stall watchdogs and error
75686
+ // callbacks can't fire against a layout whose DOM is about to be removed.
75687
+ var _iterator = _createForOfIteratorHelper(layout.regions),
75688
+ _step;
75689
+ try {
75690
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
75691
+ var region = _step.value;
75692
+ var _iterator2 = _createForOfIteratorHelper(region.mediaObjects),
75693
+ _step2;
75694
+ try {
75695
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75696
+ var media = _step2.value;
75697
+ if (media.videoHandler) {
75698
+ media.videoHandler.stop(true);
75699
+ }
75700
+ }
75701
+ } catch (err) {
75702
+ _iterator2.e(err);
75703
+ } finally {
75704
+ _iterator2.f();
75705
+ }
75706
+ }
75707
+ } catch (err) {
75708
+ _iterator.e(err);
75709
+ } finally {
75710
+ _iterator.f();
75711
+ }
75502
75712
  });
75503
75713
  }
75504
75714
  return _createClass(Layout, [{
@@ -75640,6 +75850,14 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75640
75850
  if ($splashScreen) {
75641
75851
  $splashScreen.style.display = 'none';
75642
75852
  }
75853
+ // 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.
75854
+ console.debug('??? XLR.debug >> Layout::run() - Checking if layout container is still in the DOM before playing regions...', {
75855
+ layoutId: this.id,
75856
+ layoutContainerExists: !!$layoutContainer,
75857
+ $layoutContainer: $layoutContainer,
75858
+ layoutIndex: this.index,
75859
+ shouldParse: false
75860
+ });
75643
75861
  if ($layoutContainer) {
75644
75862
  $layoutContainer.style.setProperty('visibility', 'visible');
75645
75863
  $layoutContainer.style.setProperty('opacity', '1');
@@ -75670,19 +75888,19 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75670
75888
  key: "regionExpired",
75671
75889
  value: function regionExpired() {
75672
75890
  this.allExpired = true;
75673
- var _iterator = _createForOfIteratorHelper(this.regions),
75674
- _step;
75891
+ var _iterator3 = _createForOfIteratorHelper(this.regions),
75892
+ _step3;
75675
75893
  try {
75676
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
75677
- var layoutRegion = _step.value;
75894
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
75895
+ var layoutRegion = _step3.value;
75678
75896
  if (!layoutRegion.complete) {
75679
75897
  this.allExpired = false;
75680
75898
  }
75681
75899
  }
75682
75900
  } catch (err) {
75683
- _iterator.e(err);
75901
+ _iterator3.e(err);
75684
75902
  } finally {
75685
- _iterator.f();
75903
+ _iterator3.f();
75686
75904
  }
75687
75905
  if (this.allExpired) {
75688
75906
  this.end();
@@ -75693,17 +75911,17 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75693
75911
  value: function end() {
75694
75912
  console.debug('Executing Layout::end and Calling Region::end ', this);
75695
75913
  /* Ask the layout to gracefully stop running now */
75696
- var _iterator2 = _createForOfIteratorHelper(this.regions),
75697
- _step2;
75914
+ var _iterator4 = _createForOfIteratorHelper(this.regions),
75915
+ _step4;
75698
75916
  try {
75699
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
75700
- var layoutRegion = _step2.value;
75917
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
75918
+ var layoutRegion = _step4.value;
75701
75919
  layoutRegion.end();
75702
75920
  }
75703
75921
  } catch (err) {
75704
- _iterator2.e(err);
75922
+ _iterator4.e(err);
75705
75923
  } finally {
75706
- _iterator2.f();
75924
+ _iterator4.f();
75707
75925
  }
75708
75926
  }
75709
75927
  }, {
@@ -75716,7 +75934,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75716
75934
  }
75717
75935
  }
75718
75936
  if (this.allEnded) {
75719
- console.debug('starting to end layout . . .');
75937
+ console.debug('starting to end layout . . .', {
75938
+ layoutId: this.layoutId,
75939
+ layoutIndex: this.index,
75940
+ layoutState: this.state
75941
+ });
75720
75942
  if (this.xlr.config.platform === exports.ConsumerPlatform.CMS) {
75721
75943
  var $end = document.getElementById('play_ended');
75722
75944
  var $preview = document.getElementById('screen_container');
@@ -75815,6 +76037,42 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
75815
76037
  (_$layout$parentElemen2 = $layout.parentElement) === null || _$layout$parentElemen2 === void 0 || _$layout$parentElemen2.removeChild($layout);
75816
76038
  }
75817
76039
  }
76040
+ }, {
76041
+ key: "discardLayout",
76042
+ value: function discardLayout() {
76043
+ var caller = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : exports.LayoutPlaybackType.NEXT;
76044
+ // Dispose any video.js players that were initialized during prepareVideoMedia
76045
+ // but never played. The isDisposed() guard makes this safe to call on
76046
+ // layouts that were fully played as well.
76047
+ var _iterator5 = _createForOfIteratorHelper(this.regions),
76048
+ _step5;
76049
+ try {
76050
+ for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
76051
+ var region = _step5.value;
76052
+ var _iterator6 = _createForOfIteratorHelper(region.mediaObjects),
76053
+ _step6;
76054
+ try {
76055
+ for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
76056
+ var media = _step6.value;
76057
+ if (media.player && !media.player.isDisposed()) {
76058
+ media.player.dispose();
76059
+ media.player = undefined;
76060
+ media.html = null;
76061
+ }
76062
+ }
76063
+ } catch (err) {
76064
+ _iterator6.e(err);
76065
+ } finally {
76066
+ _iterator6.f();
76067
+ }
76068
+ }
76069
+ } catch (err) {
76070
+ _iterator5.e(err);
76071
+ } finally {
76072
+ _iterator5.f();
76073
+ }
76074
+ this.removeLayout(caller);
76075
+ }
75818
76076
  }, {
75819
76077
  key: "getXlf",
75820
76078
  value: function getXlf() {
@@ -76057,6 +76315,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76057
76315
  }());
76058
76316
  xlrObject.on('updateLoop', /*#__PURE__*/function () {
76059
76317
  var _ref3 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee3(inputLayouts) {
76318
+ var _xlrObject$currentLay;
76060
76319
  return _regeneratorRuntime().wrap(function _callee3$(_context3) {
76061
76320
  while (1) switch (_context3.prev = _context3.next) {
76062
76321
  case 0:
@@ -76065,7 +76324,17 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76065
76324
  return xlrObject.updateLoop(inputLayouts);
76066
76325
  case 3:
76067
76326
  xlrObject.isUpdatingLoop = false;
76068
- case 4:
76327
+ // If the running layout finished while isUpdatingLoop was true, the
76328
+ // layout end-handler bailed out of prepareLayouts() early and the
76329
+ // subsequent playLayouts() call saw currentLayout.done === true and
76330
+ // skipped run() — leaving a black screen. Catch up now that the flag
76331
+ // is clear.
76332
+ if ((_xlrObject$currentLay = xlrObject.currentLayout) !== null && _xlrObject$currentLay !== void 0 && _xlrObject$currentLay.done) {
76333
+ xlrObject.prepareLayouts().then(function (xlr) {
76334
+ return xlrObject.playLayouts(xlr);
76335
+ });
76336
+ }
76337
+ case 5:
76069
76338
  case "end":
76070
76339
  return _context3.stop();
76071
76340
  }
@@ -76095,40 +76364,35 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76095
76364
  return _ref4.apply(this, arguments);
76096
76365
  };
76097
76366
  }());
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
- };
76367
+ xlrObject.emitSync = /*#__PURE__*/function () {
76368
+ var _ref5 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee5(eventName) {
76369
+ var _xlrObject$emitter$ev;
76370
+ var _len,
76371
+ args,
76372
+ _key,
76373
+ handlers,
76374
+ _args5 = arguments;
76375
+ return _regeneratorRuntime().wrap(function _callee5$(_context5) {
76376
+ while (1) switch (_context5.prev = _context5.next) {
76377
+ case 0:
76378
+ for (_len = _args5.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
76379
+ args[_key - 1] = _args5[_key];
76380
+ }
76381
+ handlers = (_xlrObject$emitter$ev = xlrObject.emitter.events[eventName]) !== null && _xlrObject$emitter$ev !== void 0 ? _xlrObject$emitter$ev : [];
76382
+ _context5.next = 4;
76383
+ return Promise.all(handlers.map(function (handler) {
76384
+ return handler.apply(void 0, args);
76385
+ }));
76386
+ case 4:
76387
+ case "end":
76388
+ return _context5.stop();
76389
+ }
76390
+ }, _callee5);
76391
+ }));
76392
+ return function (_x4) {
76393
+ return _ref5.apply(this, arguments);
76394
+ };
76395
+ }();
76132
76396
  xlrObject.bootstrap = function () {
76133
76397
  // Place to set configurations and initialize required props
76134
76398
  var self = this;
@@ -76163,12 +76427,21 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76163
76427
  if ($splashScreen && $splashScreen.style.display === 'block') {
76164
76428
  $splashScreen === null || $splashScreen === void 0 || $splashScreen.hide();
76165
76429
  }
76430
+ console.debug('>>>> XLR.debug XLR::playLayouts > currentLayout', {
76431
+ layoutId: xlr.currentLayout.layoutId,
76432
+ layoutIndex: xlr.currentLayout.index,
76433
+ layoutState: xlr.currentLayout.state
76434
+ });
76166
76435
  if (!xlr.currentLayout.done) {
76167
76436
  // Hide overlays when current layout is interrupt
76168
76437
  if (xlr.currentLayout.isInterrupt()) {
76169
76438
  xlrObject.overlayLayoutManager.stopOverlays();
76170
76439
  }
76171
- console.log('>>>> XLR.debug XLR::playSchedules > Running currentLayout', xlr.currentLayout);
76440
+ console.debug('>>>> XLR.debug XLR::playLayouts > Running currentLayout', {
76441
+ layoutId: xlr.currentLayout.layoutId,
76442
+ layoutIndex: xlr.currentLayout.index,
76443
+ layoutState: xlr.currentLayout.state
76444
+ });
76172
76445
  xlr.currentLayout.run();
76173
76446
  }
76174
76447
  } else {
@@ -76246,7 +76519,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76246
76519
  _context7.next = 10;
76247
76520
  return _overlay.finishAllRegions();
76248
76521
  case 10:
76249
- _overlay.removeLayout();
76522
+ _overlay.removeLayout(exports.LayoutPlaybackType.OVERLAY);
76250
76523
  case 11:
76251
76524
  _context7.next = 14;
76252
76525
  break;
@@ -76312,6 +76585,31 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76312
76585
  var $layout = document.querySelector("#".concat(containerName, "[data-sequence=\"").concat(layoutIndex, "\"]"));
76313
76586
  return $layout !== null;
76314
76587
  };
76588
+ // Scans screen_container for non-overlay layout divs and removes any that
76589
+ // are not the current or next active layout. Prevents DOM accumulation when
76590
+ // prepareLayouts() races with updateLoop and multiple same-layoutId elements
76591
+ // end up in screen_container (e.g. transitioning from a 1-layout loop where
76592
+ // two elements exist for the same layout to a multi-layout schedule).
76593
+ // keepCurrent / keepNext:
76594
+ // undefined → fall back to this.currentLayout / this.nextLayout
76595
+ // null → keep nothing for that slot (explicit "no layout to preserve")
76596
+ // ILayout → keep exactly that instance
76597
+ xlrObject.cleanupOrphanedLayouts = function (keepCurrent, keepNext) {
76598
+ var $screen = document.getElementById('screen_container');
76599
+ if (!$screen) return;
76600
+ var current = keepCurrent !== undefined ? keepCurrent : this.currentLayout;
76601
+ var next = keepNext !== undefined ? keepNext : this.nextLayout;
76602
+ Array.from($screen.querySelectorAll(':scope > div:not(.is-overlay)')).forEach(function (el) {
76603
+ var div = el;
76604
+ var isCurrentLayout = current && div.id === current.containerName && div.dataset.sequence === String(current.index);
76605
+ var isNextLayout = next && div.id === next.containerName && div.dataset.sequence === String(next.index);
76606
+ if (!isCurrentLayout && !isNextLayout) {
76607
+ var _div$parentElement;
76608
+ console.debug('XLR::cleanupOrphanedLayouts - removing orphaned layout element', div.id);
76609
+ (_div$parentElement = div.parentElement) === null || _div$parentElement === void 0 || _div$parentElement.removeChild(div);
76610
+ }
76611
+ });
76612
+ };
76315
76613
  xlrObject.updateLoop = /*#__PURE__*/function () {
76316
76614
  var _ref10 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee11(inputLayouts) {
76317
76615
  var _this$currentLayout,
@@ -76358,11 +76656,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76358
76656
  };
76359
76657
  }();
76360
76658
  if (isCurrentLayoutValid) {
76361
- _context11.next = 54;
76659
+ _context11.next = 55;
76362
76660
  break;
76363
76661
  }
76364
76662
  if (!playback.hasDefaultOnly) {
76365
- _context11.next = 33;
76663
+ _context11.next = 34;
76366
76664
  break;
76367
76665
  }
76368
76666
  if (!(this.currentLayout && playback.currentLayout && this.currentLayout.layoutId !== playback.currentLayout.layoutId)) {
@@ -76375,82 +76673,102 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76375
76673
  case 19:
76376
76674
  this.currentLayout.removeLayout();
76377
76675
  case 20:
76378
- _context11.next = 22;
76676
+ // Discard old nextLayout before replacing it — same as the
76677
+ // other two branches do, otherwise the prepared DOM element
76678
+ // and any video.js players are orphaned.
76679
+ if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76680
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76681
+ }
76682
+ _context11.next = 23;
76379
76683
  return this.prepareLayoutXlf(playback.currentLayout);
76380
- case 22:
76684
+ case 23:
76381
76685
  this.currentLayout = _context11.sent;
76382
76686
  this.currentLayoutId = this.currentLayout.layoutId;
76383
76687
  _context11.t0 = this;
76384
- _context11.next = 27;
76688
+ _context11.next = 28;
76385
76689
  return this.prepareLayoutXlf(playback.nextLayout);
76386
- case 27:
76690
+ case 28:
76387
76691
  _context11.t1 = _context11.sent;
76388
- _context11.next = 30;
76692
+ _context11.next = 31;
76389
76693
  return _context11.t0.prepareForSsp.call(_context11.t0, _context11.t1);
76390
- case 30:
76694
+ case 31:
76391
76695
  this.nextLayout = _context11.sent;
76392
- _context11.next = 50;
76696
+ _context11.next = 51;
76393
76697
  break;
76394
- case 33:
76698
+ case 34:
76395
76699
  if (!(this.currentLayout && this.isLayoutInDOM(this.currentLayout.containerName, this.currentLayout.index))) {
76396
- _context11.next = 38;
76700
+ _context11.next = 39;
76397
76701
  break;
76398
76702
  }
76399
76703
  this.currentLayout.inLoop = false;
76400
- _context11.next = 37;
76704
+ _context11.next = 38;
76401
76705
  return this.currentLayout.finishAllRegions();
76402
- case 37:
76403
- this.currentLayout.removeLayout();
76404
76706
  case 38:
76707
+ this.currentLayout.removeLayout();
76708
+ case 39:
76405
76709
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76406
- this.nextLayout.removeLayout();
76710
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76407
76711
  }
76408
76712
  if (!playback.currentLayout) {
76409
- _context11.next = 42;
76713
+ _context11.next = 43;
76410
76714
  break;
76411
76715
  }
76412
- _context11.next = 42;
76716
+ _context11.next = 43;
76413
76717
  return prepareNewCurrentLayout();
76414
- case 42:
76718
+ case 43:
76415
76719
  if (!playback.nextLayout) {
76416
- _context11.next = 50;
76720
+ _context11.next = 51;
76417
76721
  break;
76418
76722
  }
76419
76723
  _context11.t2 = this;
76420
- _context11.next = 46;
76724
+ _context11.next = 47;
76421
76725
  return this.prepareLayoutXlf(playback.nextLayout);
76422
- case 46:
76726
+ case 47:
76423
76727
  _context11.t3 = _context11.sent;
76424
- _context11.next = 49;
76728
+ _context11.next = 50;
76425
76729
  return _context11.t2.prepareForSsp.call(_context11.t2, _context11.t3);
76426
- case 49:
76427
- this.nextLayout = _context11.sent;
76428
76730
  case 50:
76429
- _context11.next = 52;
76731
+ this.nextLayout = _context11.sent;
76732
+ case 51:
76733
+ _context11.next = 53;
76430
76734
  return this.playSchedules(this);
76431
- case 52:
76735
+ case 53:
76432
76736
  _context11.next = 67;
76433
76737
  break;
76434
- case 54:
76738
+ case 55:
76435
76739
  // Remove next layout if it is in the DOM
76436
76740
  if (this.nextLayout && this.isLayoutInDOM(this.nextLayout.containerName, this.nextLayout.index)) {
76437
- this.nextLayout.removeLayout();
76741
+ this.nextLayout.discardLayout(exports.LayoutPlaybackType.NEXT);
76438
76742
  }
76439
- // Prepare new current layout
76743
+ // Purge any other orphaned layouts from screen_container that belong
76744
+ // to the old single-layout loop. When there was only one layout in the
76745
+ // loop, prepareLayouts() kept two DOM elements alive (current + next,
76746
+ // both the same layoutId but different containerNames). On a schedule
76747
+ // change the this.nextLayout check above only discards the element
76748
+ // currently referenced by this.nextLayout, but concurrent
76749
+ // prepareLayouts() calls can leave earlier same-layoutId elements
76750
+ // behind.
76751
+ // Pass null (not undefined) for keepNext: undefined would fall back to
76752
+ // this.nextLayout which may still reference the just-discarded layout
76753
+ // or — if isLayoutInDOM returned false and discardLayout was skipped —
76754
+ // the orphan itself, causing cleanupOrphanedLayouts to preserve it.
76755
+ // null means "no next to keep"; we are about to prepare a fresh one.
76756
+ this.cleanupOrphanedLayouts(this.currentLayout, null);
76757
+ // The current layout is still valid and running — do NOT replace the
76758
+ // live currentLayout object. Only refresh the queued nextLayout so
76759
+ // that when the running layout finishes it transitions to the correct
76760
+ // position in the updated loop. Using playback.currentLayout (the
76761
+ // slot that follows the running layout in the new queue) as the new
76762
+ // nextLayout keeps the cycle in order; the slot after that will be
76763
+ // prepared by the normal prepareLayouts() call at transition time.
76440
76764
  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
76765
  _context11.next = 66;
76449
76766
  break;
76450
76767
  }
76768
+ this.currentLayoutIndex = playback.currentLayoutIndex;
76451
76769
  _context11.t4 = this;
76452
76770
  _context11.next = 62;
76453
- return this.prepareLayoutXlf(playback.nextLayout);
76771
+ return this.prepareLayoutXlf(playback.currentLayout);
76454
76772
  case 62:
76455
76773
  _context11.t5 = _context11.sent;
76456
76774
  _context11.next = 65;
@@ -76556,8 +76874,21 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76556
76874
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
76557
76875
  }
76558
76876
  } else {
76877
+ var _this$currentLayout4, _this$currentLayout5;
76559
76878
  _currentLayout = this.nextLayout;
76560
76879
  _currentLayoutIndex = _currentLayout.index;
76880
+ // updateLoop can re-queue the same index that is currently
76881
+ // playing (e.g. it fires while nextLayout.index === currentLayout.index).
76882
+ // When that layout then ends, the catch-up prepareLayouts() would
76883
+ // replay the same slot instead of advancing. Detect this by checking
76884
+ // whether the queued next-to-current is at the same index as the
76885
+ // layout that just finished, and advance past it so the following
76886
+ // slot (e.g. an SSP that now has an ad) becomes current instead.
76887
+ 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)) {
76888
+ _currentLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76889
+ _currentLayout = this.getLayout(this.inputLayouts[_currentLayoutIndex]);
76890
+ _currentLayout = setLayoutIndex(_currentLayout, _currentLayoutIndex);
76891
+ }
76561
76892
  _nextLayoutIndex = (_currentLayoutIndex + 1) % this.inputLayouts.length;
76562
76893
  _nextLayout = this.getLayout(this.inputLayouts[_nextLayoutIndex]);
76563
76894
  _nextLayout = setLayoutIndex(_nextLayout, _nextLayoutIndex);
@@ -76613,6 +76944,10 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76613
76944
  var activeLayout = inputLayout;
76614
76945
  if (isCMS) {
76615
76946
  activeLayout.index = 0;
76947
+ // id stays null without this — setLayoutIndex returns undefined for CMS layouts
76948
+ if (activeLayout.id == null) {
76949
+ activeLayout.id = activeLayout.layoutId;
76950
+ }
76616
76951
  } else {
76617
76952
  activeLayout = _objectSpread2({}, this.uniqueLayouts[inputLayout.layoutId]);
76618
76953
  }
@@ -76643,7 +76978,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76643
76978
  };
76644
76979
  xlrObject.prepareLayouts = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14() {
76645
76980
  var _layoutPlayback$curre, _layoutPlayback$curre2;
76646
- var self, layoutPlayback, currentLayoutXlf, nextLayoutXlf, layouts;
76981
+ var self, layoutPlayback, currentLayoutXlf, wasCurrentReused, nextLayoutXlf, layouts;
76647
76982
  return _regeneratorRuntime().wrap(function _callee14$(_context14) {
76648
76983
  while (1) switch (_context14.prev = _context14.next) {
76649
76984
  case 0:
@@ -76666,7 +77001,12 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76666
77001
  shouldParse: false
76667
77002
  });
76668
77003
  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)) {
77004
+ // Only reuse the existing Layout instance if it is fully healthy
77005
+ // a done=true instance was removed from the DOM (e.g. an SSP slot that
77006
+ // had no ad), and an empty-XLF instance has no regions so it can never
77007
+ // advance the cycle. In either case re-prepare from scratch so we get
77008
+ // a fresh request (which may now have a valid ad / XLF).
77009
+ if (!((_layoutPlayback$curre2 = layoutPlayback.currentLayout) !== null && _layoutPlayback$curre2 !== void 0 && _layoutPlayback$curre2.layoutNode && !layoutPlayback.currentLayout.done && layoutPlayback.currentLayout.xlfString !== '')) {
76670
77010
  _context14.next = 12;
76671
77011
  break;
76672
77012
  }
@@ -76680,27 +77020,40 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76680
77020
  _context14.t0 = _context14.sent;
76681
77021
  case 15:
76682
77022
  currentLayoutXlf = _context14.t0;
76683
- _context14.next = 18;
77023
+ // True when the same object was returned (reused); false when a fresh
77024
+ // Layout was constructed by prepareLayoutXlf above.
77025
+ wasCurrentReused = currentLayoutXlf === layoutPlayback.currentLayout;
77026
+ _context14.next = 19;
76684
77027
  return self.prepareLayoutXlf(layoutPlayback.nextLayout);
76685
- case 18:
77028
+ case 19:
76686
77029
  nextLayoutXlf = _context14.sent;
76687
77030
  _context14.t1 = Promise;
76688
77031
  _context14.t2 = currentLayoutXlf;
76689
- _context14.next = 23;
77032
+ _context14.next = 24;
76690
77033
  return self.prepareForSsp(nextLayoutXlf);
76691
- case 23:
77034
+ case 24:
76692
77035
  _context14.t3 = _context14.sent;
76693
77036
  _context14.t4 = [_context14.t2, _context14.t3];
76694
- _context14.next = 27;
77037
+ _context14.next = 28;
76695
77038
  return _context14.t1.all.call(_context14.t1, _context14.t4);
76696
- case 27:
77039
+ case 28:
76697
77040
  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
- }
77041
+ if (!(self.isUpdatingLoop || layouts[0].done)) {
77042
+ _context14.next = 33;
77043
+ break;
77044
+ }
77045
+ // If currentLayout was freshly prepared (not reused from nextLayout),
77046
+ // its DOM element was just appended — discard it now so it does not
77047
+ // accumulate in screen_container. Also disposes any video.js players
77048
+ // that were initialized during prepareVideoMedia but never played.
77049
+ if (!wasCurrentReused && this.isLayoutInDOM(currentLayoutXlf.containerName, currentLayoutXlf.index)) {
77050
+ currentLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
76703
77051
  }
77052
+ if (layoutPlayback.nextLayout && nextLayoutXlf && this.isLayoutInDOM(nextLayoutXlf.containerName, nextLayoutXlf.index)) {
77053
+ nextLayoutXlf.discardLayout(exports.LayoutPlaybackType.NEXT);
77054
+ }
77055
+ return _context14.abrupt("return", Promise.resolve(self));
77056
+ case 33:
76704
77057
  console.debug('>>>>> XLR.debug prepared layout XLF', layouts);
76705
77058
  return _context14.abrupt("return", new Promise( /*#__PURE__*/function () {
76706
77059
  var _ref14 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13(resolve) {
@@ -76717,8 +77070,15 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76717
77070
  self.currentLayout = self.layouts.current;
76718
77071
  self.currentLayoutId = self.currentLayout.layoutId;
76719
77072
  self.nextLayout = self.layouts.next;
77073
+ // Evict any orphaned layout DOM elements that aren't the current
77074
+ // or next layout. Concurrent prepareLayouts() calls can each append
77075
+ // a freshly-prepared nextLayout to screen_container and then
77076
+ // overwrite this.nextLayout, leaving earlier elements behind.
77077
+ // Calling this here — with explicit references — ensures every
77078
+ // completed prepare cycle leaves the DOM in a clean state.
77079
+ self.cleanupOrphanedLayouts(self.currentLayout, self.nextLayout);
76720
77080
  resolve(xlrObject);
76721
- case 8:
77081
+ case 9:
76722
77082
  case "end":
76723
77083
  return _context13.stop();
76724
77084
  }
@@ -76728,7 +77088,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76728
77088
  return _ref14.apply(this, arguments);
76729
77089
  };
76730
77090
  }()));
76731
- case 31:
77091
+ case 35:
76732
77092
  case "end":
76733
77093
  return _context14.stop();
76734
77094
  }
@@ -76756,34 +77116,38 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76756
77116
  newOptions.xlfUrl = inputLayout.path;
76757
77117
  }
76758
77118
  if (!(inputLayout && inputLayout.layoutNode === undefined)) {
76759
- _context15.next = 21;
77119
+ _context15.next = 22;
76760
77120
  break;
76761
77121
  }
76762
77122
  if (!(inputLayout.layoutId === -1)) {
76763
- _context15.next = 14;
77123
+ _context15.next = 15;
76764
77124
  break;
76765
77125
  }
76766
77126
  _context15.next = 10;
76767
77127
  return self.emitSync('adRequest', inputLayout.index);
76768
77128
  case 10:
76769
77129
  sspInputLayout = self.inputLayouts[inputLayout.index];
77130
+ console.debug('XLR::prepareLayoutXlf > SSP input layout', {
77131
+ sspInputLayout: sspInputLayout,
77132
+ inputLayout: inputLayout
77133
+ });
76770
77134
  // @ts-ignore
76771
- layoutXlf = ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf()) || '';
76772
- _context15.next = 17;
77135
+ layoutXlf = typeof ((_sspInputLayout = sspInputLayout) === null || _sspInputLayout === void 0 ? void 0 : _sspInputLayout.getXlf) === 'function' ? sspInputLayout.getXlf() : '';
77136
+ _context15.next = 18;
76773
77137
  break;
76774
- case 14:
76775
- _context15.next = 16;
77138
+ case 15:
77139
+ _context15.next = 17;
76776
77140
  return getXlf(newOptions);
76777
- case 16:
76778
- layoutXlf = _context15.sent;
76779
77141
  case 17:
77142
+ layoutXlf = _context15.sent;
77143
+ case 18:
76780
77144
  parser = new window.DOMParser();
76781
77145
  layoutXlfNode = parser.parseFromString(layoutXlf, 'text/xml');
76782
- _context15.next = 22;
77146
+ _context15.next = 23;
76783
77147
  break;
76784
- case 21:
76785
- layoutXlfNode = inputLayout && inputLayout.layoutNode;
76786
77148
  case 22:
77149
+ layoutXlfNode = inputLayout && inputLayout.layoutNode;
77150
+ case 23:
76787
77151
  isOverlayLayout = !!(inputLayout !== null && inputLayout !== void 0 && inputLayout.isOverlay);
76788
77152
  return _context15.abrupt("return", new Promise(function (resolve) {
76789
77153
  var _inputLayout$ad;
@@ -76808,13 +77172,22 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76808
77172
  xlrLayoutObj.duration = sspInputLayout.duration || 0;
76809
77173
  xlrLayoutObj.ad = sspInputLayout.ad;
76810
77174
  }
77175
+ var xlrLayout;
76811
77176
  if (isOverlayLayout) {
76812
- resolve(new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77177
+ xlrLayout = new OverlayLayout(xlrLayoutObj, newOptions, self, layoutXlfNode);
76813
77178
  } else {
76814
- resolve(new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode));
77179
+ xlrLayout = new Layout(xlrLayoutObj, newOptions, self, layoutXlfNode);
77180
+ }
77181
+ // Advance the shared counter so the next prepareLayoutXlf() call
77182
+ // starts from where this layout left off — prevents every layout
77183
+ // instance from reusing idCounter=1 and colliding on the same
77184
+ // containerName / DOM element.
77185
+ if (props.options) {
77186
+ props.options.idCounter = newOptions.idCounter;
76815
77187
  }
77188
+ resolve(xlrLayout);
76816
77189
  }));
76817
- case 24:
77190
+ case 25:
76818
77191
  case "end":
76819
77192
  return _context15.stop();
76820
77193
  }
@@ -76826,46 +77199,49 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76826
77199
  }();
76827
77200
  xlrObject.prepareForSsp = /*#__PURE__*/function () {
76828
77201
  var _ref16 = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee16(nextLayout) {
76829
- var self, _nextLayout, nextIndex, inputLayout, nextLayoutObj;
77202
+ var self, _nextLayout, iterations, maxIterations, nextIndex, inputLayout, nextLayoutObj;
76830
77203
  return _regeneratorRuntime().wrap(function _callee16$(_context16) {
76831
77204
  while (1) switch (_context16.prev = _context16.next) {
76832
77205
  case 0:
76833
77206
  self = this;
76834
77207
  _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;
77208
+ iterations = 0;
77209
+ maxIterations = self.inputLayouts.length;
77210
+ case 4:
77211
+ if (!(_nextLayout && _nextLayout.xlfString === '' && iterations < maxIterations)) {
77212
+ _context16.next = 20;
76847
77213
  break;
76848
77214
  }
76849
- return _context16.abrupt("break", 17);
76850
- case 7:
77215
+ // Remove the empty slot's DOM element before skipping past it
77216
+ _nextLayout.removeLayout(exports.LayoutPlaybackType.NEXT);
77217
+ iterations++;
77218
+ // Advance to the next slot, wrapping around so a trailing SSP slot
77219
+ // with no ad does not strand the queue at the end of the array.
77220
+ nextIndex = (_nextLayout.index + 1) % self.inputLayouts.length;
76851
77221
  inputLayout = self.inputLayouts[nextIndex];
76852
77222
  if (inputLayout) {
76853
- _context16.next = 10;
77223
+ _context16.next = 11;
76854
77224
  break;
76855
77225
  }
76856
- return _context16.abrupt("break", 17);
76857
- case 10:
77226
+ return _context16.abrupt("break", 20);
77227
+ case 11:
76858
77228
  nextLayoutObj = self.getLayout(inputLayout);
76859
77229
  nextLayoutObj = setLayoutIndex(nextLayoutObj, nextIndex);
76860
- _context16.next = 14;
77230
+ if (nextLayoutObj) {
77231
+ _context16.next = 15;
77232
+ break;
77233
+ }
77234
+ return _context16.abrupt("break", 20);
77235
+ case 15:
77236
+ _context16.next = 17;
76861
77237
  return self.prepareLayoutXlf(nextLayoutObj);
76862
- case 14:
77238
+ case 17:
76863
77239
  _nextLayout = _context16.sent;
76864
- _context16.next = 2;
77240
+ _context16.next = 4;
76865
77241
  break;
76866
- case 17:
77242
+ case 20:
76867
77243
  return _context16.abrupt("return", _nextLayout);
76868
- case 18:
77244
+ case 21:
76869
77245
  case "end":
76870
77246
  return _context16.stop();
76871
77247
  }
@@ -76877,7 +77253,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76877
77253
  }();
76878
77254
  xlrObject.gotoPrevLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee18() {
76879
77255
  var _this4 = this;
76880
- var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout4;
77256
+ var _currentLayoutIndex, _assumedPrevIndex, _this$currentLayout6;
76881
77257
  return _regeneratorRuntime().wrap(function _callee18$(_context18) {
76882
77258
  while (1) switch (_context18.prev = _context18.next) {
76883
77259
  case 0:
@@ -76900,7 +77276,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76900
77276
  break;
76901
77277
  }
76902
77278
  _context18.next = 8;
76903
- return (_this$currentLayout4 = this.currentLayout) === null || _this$currentLayout4 === void 0 ? void 0 : _this$currentLayout4.finishAllRegions();
77279
+ return (_this$currentLayout6 = this.currentLayout) === null || _this$currentLayout6 === void 0 ? void 0 : _this$currentLayout6.finishAllRegions();
76904
77280
  case 8:
76905
77281
  // and set the previous layout as current layout
76906
77282
  this.currentLayoutIndex = _assumedPrevIndex;
@@ -76928,7 +77304,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76928
77304
  }, _callee18, this);
76929
77305
  }));
76930
77306
  xlrObject.gotoNextLayout = /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee19() {
76931
- var _xlrObject$currentLay;
77307
+ var _xlrObject$currentLay2;
76932
77308
  return _regeneratorRuntime().wrap(function _callee19$(_context19) {
76933
77309
  while (1) switch (_context19.prev = _context19.next) {
76934
77310
  case 0:
@@ -76938,7 +77314,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76938
77314
  shouldParse: false
76939
77315
  });
76940
77316
  _context19.next = 3;
76941
- return (_xlrObject$currentLay = xlrObject.currentLayout) === null || _xlrObject$currentLay === void 0 ? void 0 : _xlrObject$currentLay.finishAllRegions();
77317
+ return (_xlrObject$currentLay2 = xlrObject.currentLayout) === null || _xlrObject$currentLay2 === void 0 ? void 0 : _xlrObject$currentLay2.finishAllRegions();
76942
77318
  case 3:
76943
77319
  case "end":
76944
77320
  return _context19.stop();
@@ -76950,6 +77326,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76950
77326
  if (layout !== null) {
76951
77327
  layout.index = xlrInputLayout.index;
76952
77328
  }
77329
+ console.debug('XLR::updateInputLayout', {
77330
+ layoutIndex: layoutIndex,
77331
+ layout: layout,
77332
+ xlrInputLayout: xlrInputLayout
77333
+ });
76953
77334
  this.inputLayouts[layoutIndex] = layout || xlrInputLayout;
76954
77335
  };
76955
77336
  xlrObject.bootstrap();
@@ -76993,11 +77374,11 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
76993
77374
  exports.hasSspLayout = hasSspLayout;
76994
77375
  exports.initRenderingDOM = initRenderingDOM;
76995
77376
  exports.initialLayout = initialLayout;
76996
- exports.initialMedia = initialMedia;
76997
77377
  exports.initialRegion = initialRegion;
76998
77378
  exports.initialXlr = initialXlr;
76999
77379
  exports.isEmpty = isEmpty;
77000
77380
  exports.isLayoutValid = isLayoutValid;
77381
+ exports.isMediaActive = isMediaActive;
77001
77382
  exports.nextId = nextId;
77002
77383
  exports.playerReportFault = playerReportFault;
77003
77384
  exports.preloadMediaBlob = preloadMediaBlob;
@@ -77005,6 +77386,7 @@ ${segmentInfoString(segmentInfo)}`); // If there's an init segment associated wi
77005
77386
  exports.prepareHtmlMedia = prepareHtmlMedia;
77006
77387
  exports.prepareImageMedia = prepareImageMedia;
77007
77388
  exports.prepareVideoMedia = prepareVideoMedia;
77389
+ exports.reportToPlayerPlatform = reportToPlayerPlatform;
77008
77390
  exports.setExpiry = setExpiry;
77009
77391
  exports.transitionElement = transitionElement;
77010
77392
  exports.videoFileType = videoFileType;