stormcloud-video-player 0.6.0 → 0.6.2

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.
package/lib/index.js CHANGED
@@ -944,14 +944,17 @@ function resolveBidToVastAd(winner, logPrefix) {
944
944
  return Promise.resolve(null);
945
945
  }
946
946
  function createVastAdLayer(contentVideo, options) {
947
- var _ref, _ref1;
947
+ var _ref, _ref1, _ref2, _ref3, _ref4;
948
948
  var adPlaying = false;
949
949
  var originalMutedState = false;
950
950
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
951
951
  var listeners = /* @__PURE__ */ new Map();
952
952
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
953
953
  var continueLiveStreamDuringAds = (_ref = options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) !== null && _ref !== void 0 ? _ref : false;
954
- var debug = (_ref1 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref1 !== void 0 ? _ref1 : false;
954
+ var smartTVMode = (_ref1 = options === null || options === void 0 ? void 0 : options.smartTVMode) !== null && _ref1 !== void 0 ? _ref1 : false;
955
+ var singleElementMode = (_ref2 = options === null || options === void 0 ? void 0 : options.singleElementMode) !== null && _ref2 !== void 0 ? _ref2 : false;
956
+ var forceMP4Ads = (_ref3 = options === null || options === void 0 ? void 0 : options.forceMP4Ads) !== null && _ref3 !== void 0 ? _ref3 : smartTVMode || singleElementMode;
957
+ var debug = (_ref4 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref4 !== void 0 ? _ref4 : false;
955
958
  var adVideoElement;
956
959
  var adHls;
957
960
  var adContainerEl;
@@ -960,6 +963,7 @@ function createVastAdLayer(contentVideo, options) {
960
963
  var destroyed = false;
961
964
  var tornDown = false;
962
965
  var trackingFired = createEmptyTrackingState();
966
+ var currentAdEventHandlers;
963
967
  var preloadSlots = /* @__PURE__ */ new Map();
964
968
  function emit(event, payload) {
965
969
  var set = listeners.get(event);
@@ -1021,14 +1025,26 @@ function createVastAdLayer(contentVideo, options) {
1021
1025
  var _ref;
1022
1026
  var _scoredFiles_;
1023
1027
  if (mediaFiles.length === 0) throw new Error("No media files available");
1024
- var firstFile = mediaFiles[0];
1025
- if (mediaFiles.length === 1) return firstFile;
1028
+ var candidates = mediaFiles;
1029
+ if (forceMP4Ads) {
1030
+ var mp4Only = candidates.filter(function(f) {
1031
+ return !isHlsMediaFile(f);
1032
+ });
1033
+ if (mp4Only.length > 0) {
1034
+ candidates = mp4Only;
1035
+ if (debug) console.log("".concat(LOG, " forceMP4Ads: filtered to ").concat(mp4Only.length, " MP4-only file(s)"));
1036
+ } else if (debug) {
1037
+ console.warn("".concat(LOG, " forceMP4Ads: no MP4 files available, falling back to all media files"));
1038
+ }
1039
+ }
1040
+ var firstFile = candidates[0];
1041
+ if (candidates.length === 1) return firstFile;
1026
1042
  var mainQuality = getMainStreamQuality();
1027
1043
  if (!mainQuality) {
1028
1044
  if (debug) console.log("".concat(LOG, " No main stream quality info, using first media file"));
1029
1045
  return firstFile;
1030
1046
  }
1031
- var scoredFiles = mediaFiles.map(function(file) {
1047
+ var scoredFiles = candidates.map(function(file) {
1032
1048
  var widthDiff = Math.abs(file.width - mainQuality.width);
1033
1049
  var heightDiff = Math.abs(file.height - mainQuality.height);
1034
1050
  var resolutionDiff = widthDiff + heightDiff;
@@ -1062,63 +1078,86 @@ function createVastAdLayer(contentVideo, options) {
1062
1078
  video.volume = 1;
1063
1079
  return video;
1064
1080
  }
1081
+ function removeAdEventListeners() {
1082
+ if (!currentAdEventHandlers || !adVideoElement) return;
1083
+ var el = adVideoElement;
1084
+ el.removeEventListener("timeupdate", currentAdEventHandlers.timeupdate);
1085
+ el.removeEventListener("playing", currentAdEventHandlers.playing);
1086
+ el.removeEventListener("ended", currentAdEventHandlers.ended);
1087
+ el.removeEventListener("error", currentAdEventHandlers.error);
1088
+ el.removeEventListener("volumechange", currentAdEventHandlers.volumechange);
1089
+ el.removeEventListener("pause", currentAdEventHandlers.pause);
1090
+ el.removeEventListener("play", currentAdEventHandlers.play);
1091
+ currentAdEventHandlers = void 0;
1092
+ }
1065
1093
  function setupAdEventListeners() {
1066
1094
  if (!adVideoElement) return;
1067
- adVideoElement.addEventListener("timeupdate", function() {
1068
- var ad = currentAd;
1069
- if (!ad || !adVideoElement) return;
1070
- var progress = adVideoElement.currentTime / ad.duration;
1071
- if (progress >= 0.25 && !trackingFired.firstQuartile) {
1072
- trackingFired.firstQuartile = true;
1073
- fireTrackingPixels2(ad.trackingUrls.firstQuartile);
1074
- }
1075
- if (progress >= 0.5 && !trackingFired.midpoint) {
1076
- trackingFired.midpoint = true;
1077
- fireTrackingPixels2(ad.trackingUrls.midpoint);
1078
- }
1079
- if (progress >= 0.75 && !trackingFired.thirdQuartile) {
1080
- trackingFired.thirdQuartile = true;
1081
- fireTrackingPixels2(ad.trackingUrls.thirdQuartile);
1082
- }
1083
- });
1084
- adVideoElement.addEventListener("playing", function() {
1085
- var ad = currentAd;
1086
- if (!ad || trackingFired.start) return;
1087
- trackingFired.start = true;
1088
- fireTrackingPixels2(ad.trackingUrls.start);
1089
- if (debug) console.log("".concat(LOG, " Ad started playing"));
1090
- });
1091
- adVideoElement.addEventListener("ended", function() {
1092
- if (tornDown || !currentAd || trackingFired.complete) return;
1093
- trackingFired.complete = true;
1094
- fireTrackingPixels2(currentAd.trackingUrls.complete);
1095
- if (debug) console.log("".concat(LOG, " Ad completed"));
1096
- handleAdComplete();
1097
- });
1098
- adVideoElement.addEventListener("error", function(e) {
1099
- if (tornDown) return;
1100
- console.error("".concat(LOG, " Ad video error:"), e);
1101
- if (currentAd) fireTrackingPixels2(currentAd.trackingUrls.error);
1102
- handleAdError();
1103
- });
1104
- adVideoElement.addEventListener("volumechange", function() {
1105
- if (!currentAd || !adVideoElement) return;
1106
- if (adVideoElement.muted) {
1107
- fireTrackingPixels2(currentAd.trackingUrls.mute);
1108
- } else {
1109
- fireTrackingPixels2(currentAd.trackingUrls.unmute);
1110
- }
1111
- });
1112
- adVideoElement.addEventListener("pause", function() {
1113
- if (currentAd && adVideoElement && !adVideoElement.ended) {
1114
- fireTrackingPixels2(currentAd.trackingUrls.pause);
1115
- }
1116
- });
1117
- adVideoElement.addEventListener("play", function() {
1118
- if (currentAd && adVideoElement && adVideoElement.currentTime > 0) {
1119
- fireTrackingPixels2(currentAd.trackingUrls.resume);
1095
+ removeAdEventListeners();
1096
+ var handlers = {
1097
+ timeupdate: function timeupdate() {
1098
+ var ad = currentAd;
1099
+ if (!ad || !adVideoElement) return;
1100
+ var progress = adVideoElement.currentTime / ad.duration;
1101
+ if (progress >= 0.25 && !trackingFired.firstQuartile) {
1102
+ trackingFired.firstQuartile = true;
1103
+ fireTrackingPixels2(ad.trackingUrls.firstQuartile);
1104
+ }
1105
+ if (progress >= 0.5 && !trackingFired.midpoint) {
1106
+ trackingFired.midpoint = true;
1107
+ fireTrackingPixels2(ad.trackingUrls.midpoint);
1108
+ }
1109
+ if (progress >= 0.75 && !trackingFired.thirdQuartile) {
1110
+ trackingFired.thirdQuartile = true;
1111
+ fireTrackingPixels2(ad.trackingUrls.thirdQuartile);
1112
+ }
1113
+ },
1114
+ playing: function playing() {
1115
+ var ad = currentAd;
1116
+ if (!ad || trackingFired.start) return;
1117
+ trackingFired.start = true;
1118
+ fireTrackingPixels2(ad.trackingUrls.start);
1119
+ if (debug) console.log("".concat(LOG, " Ad started playing"));
1120
+ },
1121
+ ended: function ended() {
1122
+ if (tornDown || !currentAd || trackingFired.complete) return;
1123
+ trackingFired.complete = true;
1124
+ fireTrackingPixels2(currentAd.trackingUrls.complete);
1125
+ if (debug) console.log("".concat(LOG, " Ad completed"));
1126
+ handleAdComplete();
1127
+ },
1128
+ error: function error(e) {
1129
+ if (tornDown) return;
1130
+ console.error("".concat(LOG, " Ad video error:"), e);
1131
+ if (currentAd) fireTrackingPixels2(currentAd.trackingUrls.error);
1132
+ handleAdError();
1133
+ },
1134
+ volumechange: function volumechange() {
1135
+ if (!currentAd || !adVideoElement) return;
1136
+ if (adVideoElement.muted) {
1137
+ fireTrackingPixels2(currentAd.trackingUrls.mute);
1138
+ } else {
1139
+ fireTrackingPixels2(currentAd.trackingUrls.unmute);
1140
+ }
1141
+ },
1142
+ pause: function pause() {
1143
+ if (currentAd && adVideoElement && !adVideoElement.ended) {
1144
+ fireTrackingPixels2(currentAd.trackingUrls.pause);
1145
+ }
1146
+ },
1147
+ play: function play() {
1148
+ if (currentAd && adVideoElement && adVideoElement.currentTime > 0) {
1149
+ fireTrackingPixels2(currentAd.trackingUrls.resume);
1150
+ }
1120
1151
  }
1121
- });
1152
+ };
1153
+ adVideoElement.addEventListener("timeupdate", handlers.timeupdate);
1154
+ adVideoElement.addEventListener("playing", handlers.playing);
1155
+ adVideoElement.addEventListener("ended", handlers.ended);
1156
+ adVideoElement.addEventListener("error", handlers.error);
1157
+ adVideoElement.addEventListener("volumechange", handlers.volumechange);
1158
+ adVideoElement.addEventListener("pause", handlers.pause);
1159
+ adVideoElement.addEventListener("play", handlers.play);
1160
+ currentAdEventHandlers = handlers;
1122
1161
  }
1123
1162
  function setAdPlayingFlag(isPlaying) {
1124
1163
  if (isPlaying) {
@@ -1141,6 +1180,7 @@ function createVastAdLayer(contentVideo, options) {
1141
1180
  }
1142
1181
  function handleAdError() {
1143
1182
  if (tornDown) return;
1183
+ if (!adPlaying) return;
1144
1184
  if (debug) console.log("".concat(LOG, " Handling ad error"));
1145
1185
  adPlaying = false;
1146
1186
  setAdPlayingFlag(false);
@@ -1151,14 +1191,19 @@ function createVastAdLayer(contentVideo, options) {
1151
1191
  emit("ad_error");
1152
1192
  }
1153
1193
  function teardownCurrentPlayback() {
1194
+ removeAdEventListeners();
1154
1195
  if (adHls) {
1155
1196
  adHls.destroy();
1156
1197
  adHls = void 0;
1157
1198
  }
1158
1199
  if (adVideoElement) {
1159
- adVideoElement.pause();
1160
- adVideoElement.removeAttribute("src");
1161
- adVideoElement.load();
1200
+ if (singleElementMode && adVideoElement === contentVideo) {
1201
+ contentVideo.pause();
1202
+ } else {
1203
+ adVideoElement.pause();
1204
+ adVideoElement.removeAttribute("src");
1205
+ adVideoElement.load();
1206
+ }
1162
1207
  }
1163
1208
  }
1164
1209
  function startNativePlayback(mediaFile) {
@@ -1191,8 +1236,17 @@ function createVastAdLayer(contentVideo, options) {
1191
1236
  handleAdError();
1192
1237
  });
1193
1238
  });
1239
+ var nonFatalNetworkErrors = 0;
1194
1240
  adHls.on(Hls.Events.ERROR, function(_event, data) {
1195
- if (data.fatal) handleAdError();
1241
+ if (data.fatal) {
1242
+ handleAdError();
1243
+ } else if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
1244
+ nonFatalNetworkErrors++;
1245
+ if (nonFatalNetworkErrors >= 3) {
1246
+ if (debug) console.warn("".concat(LOG, " Too many non-fatal HLS network errors (").concat(nonFatalNetworkErrors, "), treating as fatal"));
1247
+ handleAdError();
1248
+ }
1249
+ }
1196
1250
  });
1197
1251
  } else if (adVideoElement.canPlayType("application/vnd.apple.mpegurl")) {
1198
1252
  adVideoElement.src = mediaFile.url;
@@ -1207,6 +1261,16 @@ function createVastAdLayer(contentVideo, options) {
1207
1261
  }
1208
1262
  function startPlayback(mediaFile) {
1209
1263
  if (!adVideoElement) return;
1264
+ if (singleElementMode && isHlsMediaFile(mediaFile)) {
1265
+ var mp4Fallback = currentAd === null || currentAd === void 0 ? void 0 : currentAd.mediaFiles.find(function(f) {
1266
+ return !isHlsMediaFile(f);
1267
+ });
1268
+ if (mp4Fallback) {
1269
+ if (debug) console.log("".concat(LOG, " singleElementMode: HLS ad blocked, using MP4 fallback"));
1270
+ startNativePlayback(mp4Fallback);
1271
+ return;
1272
+ }
1273
+ }
1210
1274
  if (isHlsMediaFile(mediaFile)) {
1211
1275
  startHlsPlayback(mediaFile);
1212
1276
  } else {
@@ -1215,7 +1279,7 @@ function createVastAdLayer(contentVideo, options) {
1215
1279
  }
1216
1280
  function playAd(bids) {
1217
1281
  return _async_to_generator(function() {
1218
- var winner, ad, _contentVideo_parentElement, container, contentVolume, adVolume, mediaFile;
1282
+ var winner, ad, contentVolume, _contentVideo_parentElement, container, adVolume, mediaFile;
1219
1283
  return _ts_generator(this, function(_state) {
1220
1284
  switch(_state.label){
1221
1285
  case 0:
@@ -1257,32 +1321,40 @@ function createVastAdLayer(contentVideo, options) {
1257
1321
  trackingFired = _object_spread({}, createEmptyTrackingState());
1258
1322
  fireTrackingPixels2(ad.trackingUrls.impression);
1259
1323
  trackingFired.impression = true;
1260
- if (!adContainerEl) {
1261
- ;
1262
- container = document.createElement("div");
1263
- container.style.position = "absolute";
1264
- container.style.left = "0";
1265
- container.style.top = "0";
1266
- container.style.right = "0";
1267
- container.style.bottom = "0";
1268
- container.style.display = "none";
1269
- container.style.alignItems = "center";
1270
- container.style.justifyContent = "center";
1271
- container.style.pointerEvents = "none";
1272
- container.style.zIndex = "10";
1273
- container.style.backgroundColor = "#000";
1274
- (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1275
- adContainerEl = container;
1276
- }
1277
- if (!adVideoElement) {
1278
- adVideoElement = createAdVideoElement();
1279
- adContainerEl.appendChild(adVideoElement);
1324
+ contentVolume = contentVideo.volume;
1325
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1326
+ if (singleElementMode) {
1327
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1328
+ teardownCurrentPlayback();
1329
+ adVideoElement = contentVideo;
1330
+ adHls = void 0;
1280
1331
  setupAdEventListeners();
1281
1332
  } else {
1282
- teardownCurrentPlayback();
1333
+ if (!adContainerEl) {
1334
+ ;
1335
+ container = document.createElement("div");
1336
+ container.style.position = "absolute";
1337
+ container.style.left = "0";
1338
+ container.style.top = "0";
1339
+ container.style.right = "0";
1340
+ container.style.bottom = "0";
1341
+ container.style.display = "none";
1342
+ container.style.alignItems = "center";
1343
+ container.style.justifyContent = "center";
1344
+ container.style.pointerEvents = "none";
1345
+ container.style.zIndex = "10";
1346
+ container.style.backgroundColor = "#000";
1347
+ (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1348
+ adContainerEl = container;
1349
+ }
1350
+ if (!adVideoElement) {
1351
+ adVideoElement = createAdVideoElement();
1352
+ adContainerEl.appendChild(adVideoElement);
1353
+ setupAdEventListeners();
1354
+ } else {
1355
+ teardownCurrentPlayback();
1356
+ }
1283
1357
  }
1284
- contentVolume = contentVideo.volume;
1285
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1286
1358
  if (!continueLiveStreamDuringAds) {
1287
1359
  contentVideo.pause();
1288
1360
  }
@@ -1293,7 +1365,7 @@ function createVastAdLayer(contentVideo, options) {
1293
1365
  adVolume = originalMutedState ? 1 : originalVolume;
1294
1366
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1295
1367
  adVideoElement.muted = false;
1296
- if (adContainerEl) {
1368
+ if (!singleElementMode && adContainerEl) {
1297
1369
  adContainerEl.style.display = "flex";
1298
1370
  adContainerEl.style.pointerEvents = "auto";
1299
1371
  }
@@ -1330,7 +1402,7 @@ function createVastAdLayer(contentVideo, options) {
1330
1402
  }
1331
1403
  function preloadAd(bids, token) {
1332
1404
  return _async_to_generator(function() {
1333
- var winner, ad, mediaFile, videoEl, container, hls, slot, slot1;
1405
+ var winner, ad, mediaFile, slot, videoEl, container, hls, slot1, slot2;
1334
1406
  return _ts_generator(this, function(_state) {
1335
1407
  switch(_state.label){
1336
1408
  case 0:
@@ -1355,6 +1427,20 @@ function createVastAdLayer(contentVideo, options) {
1355
1427
  if (!mediaFile) return [
1356
1428
  2
1357
1429
  ];
1430
+ if (smartTVMode || singleElementMode) {
1431
+ slot = {
1432
+ bids: bids,
1433
+ ad: ad,
1434
+ mediaFile: mediaFile,
1435
+ videoEl: null,
1436
+ ready: true
1437
+ };
1438
+ preloadSlots.set(token, slot);
1439
+ if (debug) console.log("".concat(LOG, " [preload] Metadata-only preload (smartTV/singleElement), token=").concat(token, ", url=").concat(mediaFile.url));
1440
+ return [
1441
+ 2
1442
+ ];
1443
+ }
1358
1444
  videoEl = createAdVideoElement();
1359
1445
  videoEl.style.visibility = "hidden";
1360
1446
  videoEl.style.pointerEvents = "none";
@@ -1368,7 +1454,7 @@ function createVastAdLayer(contentVideo, options) {
1368
1454
  });
1369
1455
  hls.loadSource(mediaFile.url);
1370
1456
  hls.attachMedia(videoEl);
1371
- slot = {
1457
+ slot1 = {
1372
1458
  bids: bids,
1373
1459
  ad: ad,
1374
1460
  mediaFile: mediaFile,
@@ -1376,7 +1462,7 @@ function createVastAdLayer(contentVideo, options) {
1376
1462
  hlsInstance: hls,
1377
1463
  ready: false
1378
1464
  };
1379
- preloadSlots.set(token, slot);
1465
+ preloadSlots.set(token, slot1);
1380
1466
  hls.on(Hls.Events.MANIFEST_PARSED, function() {
1381
1467
  var s = preloadSlots.get(token);
1382
1468
  if (s) s.ready = true;
@@ -1393,14 +1479,14 @@ function createVastAdLayer(contentVideo, options) {
1393
1479
  } else {
1394
1480
  videoEl.src = mediaFile.url;
1395
1481
  videoEl.load();
1396
- slot1 = {
1482
+ slot2 = {
1397
1483
  bids: bids,
1398
1484
  ad: ad,
1399
1485
  mediaFile: mediaFile,
1400
1486
  videoEl: videoEl,
1401
1487
  ready: false
1402
1488
  };
1403
- preloadSlots.set(token, slot1);
1489
+ preloadSlots.set(token, slot2);
1404
1490
  videoEl.addEventListener("canplay", function() {
1405
1491
  var s = preloadSlots.get(token);
1406
1492
  if (s) s.ready = true;
@@ -1419,7 +1505,7 @@ function createVastAdLayer(contentVideo, options) {
1419
1505
  }
1420
1506
  function playPreloaded(token) {
1421
1507
  return _async_to_generator(function() {
1422
- var slot, contentVolume, adVolume, container;
1508
+ var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, adVolume, container;
1423
1509
  return _ts_generator(this, function(_state) {
1424
1510
  if (destroyed) return [
1425
1511
  2,
@@ -1434,6 +1520,68 @@ function createVastAdLayer(contentVideo, options) {
1434
1520
  }
1435
1521
  preloadSlots.delete(token);
1436
1522
  if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1523
+ contentVolume = contentVideo.volume;
1524
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1525
+ sessionId = generateSessionId();
1526
+ currentAd = slot.ad;
1527
+ trackingFired = _object_spread({}, createEmptyTrackingState());
1528
+ fireTrackingPixels2(slot.ad.trackingUrls.impression);
1529
+ trackingFired.impression = true;
1530
+ if (singleElementMode) {
1531
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1532
+ teardownCurrentPlayback();
1533
+ adVideoElement = contentVideo;
1534
+ adHls = void 0;
1535
+ setupAdEventListeners();
1536
+ if (!continueLiveStreamDuringAds) {
1537
+ contentVideo.pause();
1538
+ }
1539
+ contentVideo.muted = true;
1540
+ contentVideo.volume = 0;
1541
+ adPlaying = true;
1542
+ setAdPlayingFlag(true);
1543
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1544
+ contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1545
+ contentVideo.muted = false;
1546
+ emit("content_pause");
1547
+ if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1548
+ startPlayback(slot.mediaFile);
1549
+ return [
1550
+ 2
1551
+ ];
1552
+ }
1553
+ if (smartTVMode && !slot.videoEl) {
1554
+ teardownCurrentPlayback();
1555
+ if (adVideoElement) {
1556
+ adVideoElement.remove();
1557
+ adVideoElement = void 0;
1558
+ }
1559
+ videoEl = createAdVideoElement();
1560
+ videoEl.style.visibility = "visible";
1561
+ videoEl.style.pointerEvents = "none";
1562
+ container2 = ensureAdContainer();
1563
+ container2.appendChild(videoEl);
1564
+ adVideoElement = videoEl;
1565
+ setupAdEventListeners();
1566
+ if (!continueLiveStreamDuringAds) {
1567
+ contentVideo.pause();
1568
+ }
1569
+ contentVideo.muted = true;
1570
+ contentVideo.volume = 0;
1571
+ adPlaying = true;
1572
+ setAdPlayingFlag(true);
1573
+ adVolume21 = originalMutedState ? 1 : originalVolume;
1574
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1575
+ adVideoElement.muted = false;
1576
+ container2.style.display = "flex";
1577
+ container2.style.pointerEvents = "auto";
1578
+ emit("content_pause");
1579
+ if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1580
+ startPlayback(slot.mediaFile);
1581
+ return [
1582
+ 2
1583
+ ];
1584
+ }
1437
1585
  teardownCurrentPlayback();
1438
1586
  if (adVideoElement && adVideoElement !== slot.videoEl) {
1439
1587
  adVideoElement.remove();
@@ -1443,13 +1591,6 @@ function createVastAdLayer(contentVideo, options) {
1443
1591
  adVideoElement = slot.videoEl;
1444
1592
  adHls = slot.hlsInstance;
1445
1593
  setupAdEventListeners();
1446
- sessionId = generateSessionId();
1447
- currentAd = slot.ad;
1448
- trackingFired = _object_spread({}, createEmptyTrackingState());
1449
- fireTrackingPixels2(slot.ad.trackingUrls.impression);
1450
- trackingFired.impression = true;
1451
- contentVolume = contentVideo.volume;
1452
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1453
1594
  if (!continueLiveStreamDuringAds) {
1454
1595
  contentVideo.pause();
1455
1596
  }
@@ -1464,17 +1605,10 @@ function createVastAdLayer(contentVideo, options) {
1464
1605
  container.style.display = "flex";
1465
1606
  container.style.pointerEvents = "auto";
1466
1607
  emit("content_pause");
1467
- if (slot.hlsInstance) {
1468
- adVideoElement.play().catch(function(error) {
1469
- console.error("".concat(LOG, " [preload] Error playing preloaded HLS ad:"), error);
1470
- handleAdError();
1471
- });
1472
- } else {
1473
- adVideoElement.play().catch(function(error) {
1474
- console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1475
- handleAdError();
1476
- });
1477
- }
1608
+ adVideoElement.play().catch(function(error) {
1609
+ console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1610
+ handleAdError();
1611
+ });
1478
1612
  return [
1479
1613
  2
1480
1614
  ];
@@ -1488,10 +1622,12 @@ function createVastAdLayer(contentVideo, options) {
1488
1622
  if (slot.hlsInstance) {
1489
1623
  slot.hlsInstance.destroy();
1490
1624
  }
1491
- slot.videoEl.pause();
1492
- slot.videoEl.removeAttribute("src");
1493
- slot.videoEl.load();
1494
- slot.videoEl.remove();
1625
+ if (slot.videoEl) {
1626
+ slot.videoEl.pause();
1627
+ slot.videoEl.removeAttribute("src");
1628
+ slot.videoEl.load();
1629
+ slot.videoEl.remove();
1630
+ }
1495
1631
  if (debug) console.log("".concat(LOG, " [preload] Cancelled and cleaned up token=").concat(token));
1496
1632
  }
1497
1633
  return {
@@ -1539,20 +1675,27 @@ function createVastAdLayer(contentVideo, options) {
1539
1675
  setAdPlayingFlag(false);
1540
1676
  contentVideo.muted = originalMutedState;
1541
1677
  contentVideo.volume = originalMutedState ? 0 : originalVolume;
1542
- if (adContainerEl) {
1543
- adContainerEl.style.display = "none";
1544
- adContainerEl.style.pointerEvents = "none";
1545
- }
1546
1678
  contentVideo.style.visibility = "visible";
1547
1679
  contentVideo.style.opacity = "1";
1548
- if (continueLiveStreamDuringAds) {
1549
- contentVideo.play().catch(function() {});
1550
- }
1551
- teardownCurrentPlayback();
1552
- if (adVideoElement) {
1553
- adVideoElement.pause();
1554
- adVideoElement.removeAttribute("src");
1555
- adVideoElement.load();
1680
+ if (singleElementMode) {
1681
+ teardownCurrentPlayback();
1682
+ contentVideo.removeAttribute("src");
1683
+ contentVideo.load();
1684
+ adVideoElement = void 0;
1685
+ } else {
1686
+ if (adContainerEl) {
1687
+ adContainerEl.style.display = "none";
1688
+ adContainerEl.style.pointerEvents = "none";
1689
+ }
1690
+ if (continueLiveStreamDuringAds) {
1691
+ contentVideo.play().catch(function() {});
1692
+ }
1693
+ teardownCurrentPlayback();
1694
+ if (adVideoElement) {
1695
+ adVideoElement.pause();
1696
+ adVideoElement.removeAttribute("src");
1697
+ adVideoElement.load();
1698
+ }
1556
1699
  }
1557
1700
  currentAd = void 0;
1558
1701
  tornDown = false;
@@ -1592,9 +1735,14 @@ function createVastAdLayer(contentVideo, options) {
1592
1735
  }
1593
1736
  teardownCurrentPlayback();
1594
1737
  if (adVideoElement) {
1595
- adVideoElement.pause();
1596
- adVideoElement.removeAttribute("src");
1597
- adVideoElement.remove();
1738
+ if (singleElementMode && adVideoElement === contentVideo) {
1739
+ contentVideo.removeAttribute("src");
1740
+ contentVideo.load();
1741
+ } else {
1742
+ adVideoElement.pause();
1743
+ adVideoElement.removeAttribute("src");
1744
+ adVideoElement.remove();
1745
+ }
1598
1746
  adVideoElement = void 0;
1599
1747
  }
1600
1748
  if (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.parentElement) {
@@ -2820,8 +2968,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2820
2968
  this.vastManager = createVastManager(config.debugAdTiming !== void 0 ? {
2821
2969
  debug: !!config.debugAdTiming
2822
2970
  } : {});
2971
+ var browserForAdLayer = detectBrowser();
2972
+ var isSinglePipeline = browserForAdLayer.isSmartTV || !!this.config.singlePipelineMode;
2823
2973
  this.adLayer = createVastAdLayer(this.video, {
2824
2974
  continueLiveStreamDuringAds: false,
2975
+ smartTVMode: isSinglePipeline,
2976
+ singleElementMode: isSinglePipeline,
2977
+ forceMP4Ads: isSinglePipeline,
2825
2978
  debug: !!config.debugAdTiming
2826
2979
  });
2827
2980
  }
@@ -3467,6 +3620,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3467
3620
  if (_this.fillerVideo) {
3468
3621
  _this.fillerVideo.style.display = "none";
3469
3622
  }
3623
+ if (!_this.adLayer.isAdPlaying()) {
3624
+ if (_this.config.debugAdTiming) {
3625
+ console.log("[StormcloudVideoPlayer] Filler video failed \u2014 restoring main video");
3626
+ }
3627
+ _this.adLayer.hidePlaceholder();
3628
+ if (_this.video.paused && _this.video.readyState >= 2) {
3629
+ var _this_video_play;
3630
+ (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
3631
+ }
3632
+ }
3470
3633
  });
3471
3634
  if (this.config.debugAdTiming) {
3472
3635
  console.log("[StormcloudVideoPlayer] Showing filler video layer");
@@ -4183,6 +4346,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4183
4346
  if (!this.isLiveStream) {
4184
4347
  return false;
4185
4348
  }
4349
+ if (this.config.singlePipelineMode) {
4350
+ return false;
4351
+ }
4352
+ var browser = detectBrowser();
4353
+ if (browser.isSmartTV) {
4354
+ return false;
4355
+ }
4186
4356
  return true;
4187
4357
  }
4188
4358
  },
@@ -4390,7 +4560,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4390
4560
  switch(_state.label){
4391
4561
  case 0:
4392
4562
  _loop = function() {
4393
- var remaining, prefetchContext, bids, err, bids1, remainingNow, delay, elapsed, remaining1, context, bids2, remainingNow1, err1;
4563
+ var remaining, prefetchContext, bids, err, bids1, remainingNow, urgentNeedAd, delay, elapsed, remaining1, context, bids2, remainingNow1, err1, sleepMs;
4394
4564
  return _ts_generator(this, function(_state) {
4395
4565
  switch(_state.label){
4396
4566
  case 0:
@@ -4526,7 +4696,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4526
4696
  "continue"
4527
4697
  ];
4528
4698
  case 12:
4529
- delay = _this.lastAdRequestTime ? _this.minAdRequestIntervalMs + (_this.consecutiveFailures > 0 ? backoffMs() : 0) : 0;
4699
+ urgentNeedAd = _this.inAdBreak && !_this.adLayer.isAdPlaying();
4700
+ delay = _this.lastAdRequestTime ? _this.minAdRequestIntervalMs + (!urgentNeedAd && _this.consecutiveFailures > 0 ? backoffMs() : 0) : 0;
4530
4701
  elapsed = Date.now() - _this.lastAdRequestTime;
4531
4702
  if (!(elapsed < delay && _this.lastAdRequestTime > 0)) return [
4532
4703
  3,
@@ -4645,10 +4816,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4645
4816
  24
4646
4817
  ];
4647
4818
  case 24:
4819
+ sleepMs = _this.inAdBreak && !_this.adLayer.isAdPlaying() ? 0 : backoffMs();
4648
4820
  return [
4649
4821
  4,
4650
4822
  new Promise(function(r) {
4651
- return setTimeout(r, backoffMs());
4823
+ return setTimeout(r, sleepMs);
4652
4824
  })
4653
4825
  ];
4654
4826
  case 25:
@@ -5327,42 +5499,71 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5327
5499
  this.video.volume = restoredVolume;
5328
5500
  }
5329
5501
  var browser = detectBrowser();
5330
- var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0;
5502
+ var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0 || !!this.config.singlePipelineMode;
5331
5503
  if (isSmartTV && this.hls) {
5332
- if (!restoredMuted) {
5333
- var hlsRef = this.hls;
5334
- var savedMuted = restoredMuted;
5335
- var savedVolume = restoredVolume;
5336
- var onManifestParsedRestore = function onManifestParsedRestore1() {
5337
- hlsRef.off(Hls2.Events.MANIFEST_PARSED, onManifestParsedRestore);
5338
- if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5339
- if (_this.video.muted !== savedMuted) _this.video.muted = savedMuted;
5340
- if (Math.abs(_this.video.volume - savedVolume) > 0.01) _this.video.volume = savedVolume;
5341
- if (_this.config.debugAdTiming) {
5342
- console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5504
+ var hlsRef = this.hls;
5505
+ var savedMuted = restoredMuted;
5506
+ var savedVolume = restoredVolume;
5507
+ var videoRef = this.video;
5508
+ var debugEnabled = this.config.debugAdTiming;
5509
+ var tryPlay = function tryPlay1(attempt) {
5510
+ var _videoRef_play;
5511
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5512
+ (_videoRef_play = videoRef.play()) === null || _videoRef_play === void 0 ? void 0 : _videoRef_play.catch(function() {
5513
+ if (attempt < 3) {
5514
+ if (debugEnabled) {
5515
+ console.log("[StormcloudVideoPlayer] Smart TV: play() retry ".concat(attempt + 1, "/3 in ").concat(500 * (attempt + 1), "ms"));
5343
5516
  }
5517
+ setTimeout(function() {
5518
+ return tryPlay(attempt + 1);
5519
+ }, 500 * (attempt + 1));
5344
5520
  }
5345
- };
5346
- hlsRef.on(Hls2.Events.MANIFEST_PARSED, onManifestParsedRestore);
5347
- }
5348
- this.hls.attachMedia(this.video);
5349
- if (this.config.debugAdTiming) {
5350
- console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5521
+ });
5522
+ };
5523
+ var onManifestParsedRestore = function onManifestParsedRestore1() {
5524
+ hlsRef.off(Hls2.Events.MANIFEST_PARSED, onManifestParsedRestore);
5525
+ if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5526
+ if (videoRef.muted !== savedMuted) videoRef.muted = savedMuted;
5527
+ if (Math.abs(videoRef.volume - savedVolume) > 0.01) videoRef.volume = savedVolume;
5528
+ if (debugEnabled) {
5529
+ console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5530
+ }
5531
+ hlsRef.startLoad(-1);
5532
+ tryPlay(0);
5533
+ if (debugEnabled) {
5534
+ console.log("[StormcloudVideoPlayer] Smart TV: seeking to live edge and resuming playback after re-attach");
5535
+ }
5536
+ }
5537
+ };
5538
+ hlsRef.on(Hls2.Events.MANIFEST_PARSED, onManifestParsedRestore);
5539
+ var pipelineDelayMs = 300;
5540
+ if (debugEnabled) {
5541
+ console.log("[StormcloudVideoPlayer] Smart TV: waiting ".concat(pipelineDelayMs, "ms for hardware pipeline release before re-attach"));
5351
5542
  }
5352
- }
5353
- if (this.shouldContinueLiveStreamDuringAds()) {
5354
- var _this_video_play;
5355
- if (this.config.debugAdTiming) {
5356
- if (this.video.paused) {
5357
- console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
5358
- } else {
5359
- console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
5543
+ setTimeout(function() {
5544
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5545
+ if (_this.hls) {
5546
+ _this.hls.attachMedia(_this.video);
5547
+ if (debugEnabled) {
5548
+ console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5549
+ }
5360
5550
  }
5551
+ }, pipelineDelayMs);
5552
+ } else {
5553
+ if (this.shouldContinueLiveStreamDuringAds()) {
5554
+ var _this_video_play;
5555
+ if (this.config.debugAdTiming) {
5556
+ if (this.video.paused) {
5557
+ console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
5558
+ } else {
5559
+ console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
5560
+ }
5561
+ }
5562
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5563
+ } else if (this.video.paused) {
5564
+ var _this_video_play1;
5565
+ (_this_video_play1 = this.video.play()) === null || _this_video_play1 === void 0 ? void 0 : _this_video_play1.catch(function() {});
5361
5566
  }
5362
- (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5363
- } else if (this.video.paused) {
5364
- var _this_video_play1;
5365
- (_this_video_play1 = this.video.play()) === null || _this_video_play1 === void 0 ? void 0 : _this_video_play1.catch(function() {});
5366
5567
  }
5367
5568
  this.syncMainContentAudioWhenVisible();
5368
5569
  if (!restoredMuted) {
@@ -5413,6 +5614,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5413
5614
  console.log("[CONTINUOUS-FETCH] \uD83D\uDED1 Max consecutive failures reached (".concat(this.consecutiveFailures, "), ending ad break gracefully"));
5414
5615
  }
5415
5616
  this.handleAdPodComplete();
5617
+ return;
5618
+ }
5619
+ if (this.inAdBreak && !this.config.disableFiller) {
5620
+ if (this.config.debugAdTiming) {
5621
+ console.log("[CONTINUOUS-FETCH] Ad failure in active break \u2014 showing filler while awaiting next ad");
5622
+ }
5623
+ this.showPlaceholderLayer();
5624
+ this.adLayer.showPlaceholder();
5625
+ } else if (this.inAdBreak) {
5626
+ if (this.config.debugAdTiming) {
5627
+ console.log("[CONTINUOUS-FETCH] Ad failure with no filler \u2014 restoring main video temporarily");
5628
+ }
5629
+ this.adLayer.hidePlaceholder();
5630
+ if (!this.adLayer.isAdPlaying() && this.video.paused && this.video.readyState >= 2) {
5631
+ var _this_video_play;
5632
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5633
+ }
5416
5634
  }
5417
5635
  }
5418
5636
  },