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.cjs CHANGED
@@ -1104,14 +1104,17 @@ function resolveBidToVastAd(winner, logPrefix) {
1104
1104
  return Promise.resolve(null);
1105
1105
  }
1106
1106
  function createVastAdLayer(contentVideo, options) {
1107
- var _ref, _ref1;
1107
+ var _ref, _ref1, _ref2, _ref3, _ref4;
1108
1108
  var adPlaying = false;
1109
1109
  var originalMutedState = false;
1110
1110
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
1111
1111
  var listeners = /* @__PURE__ */ new Map();
1112
1112
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
1113
1113
  var continueLiveStreamDuringAds = (_ref = options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) !== null && _ref !== void 0 ? _ref : false;
1114
- var debug = (_ref1 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref1 !== void 0 ? _ref1 : false;
1114
+ var smartTVMode = (_ref1 = options === null || options === void 0 ? void 0 : options.smartTVMode) !== null && _ref1 !== void 0 ? _ref1 : false;
1115
+ var singleElementMode = (_ref2 = options === null || options === void 0 ? void 0 : options.singleElementMode) !== null && _ref2 !== void 0 ? _ref2 : false;
1116
+ var forceMP4Ads = (_ref3 = options === null || options === void 0 ? void 0 : options.forceMP4Ads) !== null && _ref3 !== void 0 ? _ref3 : smartTVMode || singleElementMode;
1117
+ var debug = (_ref4 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref4 !== void 0 ? _ref4 : false;
1115
1118
  var adVideoElement;
1116
1119
  var adHls;
1117
1120
  var adContainerEl;
@@ -1120,6 +1123,7 @@ function createVastAdLayer(contentVideo, options) {
1120
1123
  var destroyed = false;
1121
1124
  var tornDown = false;
1122
1125
  var trackingFired = createEmptyTrackingState();
1126
+ var currentAdEventHandlers;
1123
1127
  var preloadSlots = /* @__PURE__ */ new Map();
1124
1128
  function emit(event, payload) {
1125
1129
  var set = listeners.get(event);
@@ -1181,14 +1185,26 @@ function createVastAdLayer(contentVideo, options) {
1181
1185
  var _ref;
1182
1186
  var _scoredFiles_;
1183
1187
  if (mediaFiles.length === 0) throw new Error("No media files available");
1184
- var firstFile = mediaFiles[0];
1185
- if (mediaFiles.length === 1) return firstFile;
1188
+ var candidates = mediaFiles;
1189
+ if (forceMP4Ads) {
1190
+ var mp4Only = candidates.filter(function(f) {
1191
+ return !isHlsMediaFile(f);
1192
+ });
1193
+ if (mp4Only.length > 0) {
1194
+ candidates = mp4Only;
1195
+ if (debug) console.log("".concat(LOG, " forceMP4Ads: filtered to ").concat(mp4Only.length, " MP4-only file(s)"));
1196
+ } else if (debug) {
1197
+ console.warn("".concat(LOG, " forceMP4Ads: no MP4 files available, falling back to all media files"));
1198
+ }
1199
+ }
1200
+ var firstFile = candidates[0];
1201
+ if (candidates.length === 1) return firstFile;
1186
1202
  var mainQuality = getMainStreamQuality();
1187
1203
  if (!mainQuality) {
1188
1204
  if (debug) console.log("".concat(LOG, " No main stream quality info, using first media file"));
1189
1205
  return firstFile;
1190
1206
  }
1191
- var scoredFiles = mediaFiles.map(function(file) {
1207
+ var scoredFiles = candidates.map(function(file) {
1192
1208
  var widthDiff = Math.abs(file.width - mainQuality.width);
1193
1209
  var heightDiff = Math.abs(file.height - mainQuality.height);
1194
1210
  var resolutionDiff = widthDiff + heightDiff;
@@ -1222,63 +1238,86 @@ function createVastAdLayer(contentVideo, options) {
1222
1238
  video.volume = 1;
1223
1239
  return video;
1224
1240
  }
1241
+ function removeAdEventListeners() {
1242
+ if (!currentAdEventHandlers || !adVideoElement) return;
1243
+ var el = adVideoElement;
1244
+ el.removeEventListener("timeupdate", currentAdEventHandlers.timeupdate);
1245
+ el.removeEventListener("playing", currentAdEventHandlers.playing);
1246
+ el.removeEventListener("ended", currentAdEventHandlers.ended);
1247
+ el.removeEventListener("error", currentAdEventHandlers.error);
1248
+ el.removeEventListener("volumechange", currentAdEventHandlers.volumechange);
1249
+ el.removeEventListener("pause", currentAdEventHandlers.pause);
1250
+ el.removeEventListener("play", currentAdEventHandlers.play);
1251
+ currentAdEventHandlers = void 0;
1252
+ }
1225
1253
  function setupAdEventListeners() {
1226
1254
  if (!adVideoElement) return;
1227
- adVideoElement.addEventListener("timeupdate", function() {
1228
- var ad = currentAd;
1229
- if (!ad || !adVideoElement) return;
1230
- var progress = adVideoElement.currentTime / ad.duration;
1231
- if (progress >= 0.25 && !trackingFired.firstQuartile) {
1232
- trackingFired.firstQuartile = true;
1233
- fireTrackingPixels2(ad.trackingUrls.firstQuartile);
1234
- }
1235
- if (progress >= 0.5 && !trackingFired.midpoint) {
1236
- trackingFired.midpoint = true;
1237
- fireTrackingPixels2(ad.trackingUrls.midpoint);
1238
- }
1239
- if (progress >= 0.75 && !trackingFired.thirdQuartile) {
1240
- trackingFired.thirdQuartile = true;
1241
- fireTrackingPixels2(ad.trackingUrls.thirdQuartile);
1242
- }
1243
- });
1244
- adVideoElement.addEventListener("playing", function() {
1245
- var ad = currentAd;
1246
- if (!ad || trackingFired.start) return;
1247
- trackingFired.start = true;
1248
- fireTrackingPixels2(ad.trackingUrls.start);
1249
- if (debug) console.log("".concat(LOG, " Ad started playing"));
1250
- });
1251
- adVideoElement.addEventListener("ended", function() {
1252
- if (tornDown || !currentAd || trackingFired.complete) return;
1253
- trackingFired.complete = true;
1254
- fireTrackingPixels2(currentAd.trackingUrls.complete);
1255
- if (debug) console.log("".concat(LOG, " Ad completed"));
1256
- handleAdComplete();
1257
- });
1258
- adVideoElement.addEventListener("error", function(e) {
1259
- if (tornDown) return;
1260
- console.error("".concat(LOG, " Ad video error:"), e);
1261
- if (currentAd) fireTrackingPixels2(currentAd.trackingUrls.error);
1262
- handleAdError();
1263
- });
1264
- adVideoElement.addEventListener("volumechange", function() {
1265
- if (!currentAd || !adVideoElement) return;
1266
- if (adVideoElement.muted) {
1267
- fireTrackingPixels2(currentAd.trackingUrls.mute);
1268
- } else {
1269
- fireTrackingPixels2(currentAd.trackingUrls.unmute);
1270
- }
1271
- });
1272
- adVideoElement.addEventListener("pause", function() {
1273
- if (currentAd && adVideoElement && !adVideoElement.ended) {
1274
- fireTrackingPixels2(currentAd.trackingUrls.pause);
1275
- }
1276
- });
1277
- adVideoElement.addEventListener("play", function() {
1278
- if (currentAd && adVideoElement && adVideoElement.currentTime > 0) {
1279
- fireTrackingPixels2(currentAd.trackingUrls.resume);
1255
+ removeAdEventListeners();
1256
+ var handlers = {
1257
+ timeupdate: function timeupdate() {
1258
+ var ad = currentAd;
1259
+ if (!ad || !adVideoElement) return;
1260
+ var progress = adVideoElement.currentTime / ad.duration;
1261
+ if (progress >= 0.25 && !trackingFired.firstQuartile) {
1262
+ trackingFired.firstQuartile = true;
1263
+ fireTrackingPixels2(ad.trackingUrls.firstQuartile);
1264
+ }
1265
+ if (progress >= 0.5 && !trackingFired.midpoint) {
1266
+ trackingFired.midpoint = true;
1267
+ fireTrackingPixels2(ad.trackingUrls.midpoint);
1268
+ }
1269
+ if (progress >= 0.75 && !trackingFired.thirdQuartile) {
1270
+ trackingFired.thirdQuartile = true;
1271
+ fireTrackingPixels2(ad.trackingUrls.thirdQuartile);
1272
+ }
1273
+ },
1274
+ playing: function playing() {
1275
+ var ad = currentAd;
1276
+ if (!ad || trackingFired.start) return;
1277
+ trackingFired.start = true;
1278
+ fireTrackingPixels2(ad.trackingUrls.start);
1279
+ if (debug) console.log("".concat(LOG, " Ad started playing"));
1280
+ },
1281
+ ended: function ended() {
1282
+ if (tornDown || !currentAd || trackingFired.complete) return;
1283
+ trackingFired.complete = true;
1284
+ fireTrackingPixels2(currentAd.trackingUrls.complete);
1285
+ if (debug) console.log("".concat(LOG, " Ad completed"));
1286
+ handleAdComplete();
1287
+ },
1288
+ error: function error(e) {
1289
+ if (tornDown) return;
1290
+ console.error("".concat(LOG, " Ad video error:"), e);
1291
+ if (currentAd) fireTrackingPixels2(currentAd.trackingUrls.error);
1292
+ handleAdError();
1293
+ },
1294
+ volumechange: function volumechange() {
1295
+ if (!currentAd || !adVideoElement) return;
1296
+ if (adVideoElement.muted) {
1297
+ fireTrackingPixels2(currentAd.trackingUrls.mute);
1298
+ } else {
1299
+ fireTrackingPixels2(currentAd.trackingUrls.unmute);
1300
+ }
1301
+ },
1302
+ pause: function pause() {
1303
+ if (currentAd && adVideoElement && !adVideoElement.ended) {
1304
+ fireTrackingPixels2(currentAd.trackingUrls.pause);
1305
+ }
1306
+ },
1307
+ play: function play() {
1308
+ if (currentAd && adVideoElement && adVideoElement.currentTime > 0) {
1309
+ fireTrackingPixels2(currentAd.trackingUrls.resume);
1310
+ }
1280
1311
  }
1281
- });
1312
+ };
1313
+ adVideoElement.addEventListener("timeupdate", handlers.timeupdate);
1314
+ adVideoElement.addEventListener("playing", handlers.playing);
1315
+ adVideoElement.addEventListener("ended", handlers.ended);
1316
+ adVideoElement.addEventListener("error", handlers.error);
1317
+ adVideoElement.addEventListener("volumechange", handlers.volumechange);
1318
+ adVideoElement.addEventListener("pause", handlers.pause);
1319
+ adVideoElement.addEventListener("play", handlers.play);
1320
+ currentAdEventHandlers = handlers;
1282
1321
  }
1283
1322
  function setAdPlayingFlag(isPlaying) {
1284
1323
  if (isPlaying) {
@@ -1301,6 +1340,7 @@ function createVastAdLayer(contentVideo, options) {
1301
1340
  }
1302
1341
  function handleAdError() {
1303
1342
  if (tornDown) return;
1343
+ if (!adPlaying) return;
1304
1344
  if (debug) console.log("".concat(LOG, " Handling ad error"));
1305
1345
  adPlaying = false;
1306
1346
  setAdPlayingFlag(false);
@@ -1311,14 +1351,19 @@ function createVastAdLayer(contentVideo, options) {
1311
1351
  emit("ad_error");
1312
1352
  }
1313
1353
  function teardownCurrentPlayback() {
1354
+ removeAdEventListeners();
1314
1355
  if (adHls) {
1315
1356
  adHls.destroy();
1316
1357
  adHls = void 0;
1317
1358
  }
1318
1359
  if (adVideoElement) {
1319
- adVideoElement.pause();
1320
- adVideoElement.removeAttribute("src");
1321
- adVideoElement.load();
1360
+ if (singleElementMode && adVideoElement === contentVideo) {
1361
+ contentVideo.pause();
1362
+ } else {
1363
+ adVideoElement.pause();
1364
+ adVideoElement.removeAttribute("src");
1365
+ adVideoElement.load();
1366
+ }
1322
1367
  }
1323
1368
  }
1324
1369
  function startNativePlayback(mediaFile) {
@@ -1351,8 +1396,17 @@ function createVastAdLayer(contentVideo, options) {
1351
1396
  handleAdError();
1352
1397
  });
1353
1398
  });
1399
+ var nonFatalNetworkErrors = 0;
1354
1400
  adHls.on(import_hls.default.Events.ERROR, function(_event, data) {
1355
- if (data.fatal) handleAdError();
1401
+ if (data.fatal) {
1402
+ handleAdError();
1403
+ } else if (data.type === import_hls.default.ErrorTypes.NETWORK_ERROR) {
1404
+ nonFatalNetworkErrors++;
1405
+ if (nonFatalNetworkErrors >= 3) {
1406
+ if (debug) console.warn("".concat(LOG, " Too many non-fatal HLS network errors (").concat(nonFatalNetworkErrors, "), treating as fatal"));
1407
+ handleAdError();
1408
+ }
1409
+ }
1356
1410
  });
1357
1411
  } else if (adVideoElement.canPlayType("application/vnd.apple.mpegurl")) {
1358
1412
  adVideoElement.src = mediaFile.url;
@@ -1367,6 +1421,16 @@ function createVastAdLayer(contentVideo, options) {
1367
1421
  }
1368
1422
  function startPlayback(mediaFile) {
1369
1423
  if (!adVideoElement) return;
1424
+ if (singleElementMode && isHlsMediaFile(mediaFile)) {
1425
+ var mp4Fallback = currentAd === null || currentAd === void 0 ? void 0 : currentAd.mediaFiles.find(function(f) {
1426
+ return !isHlsMediaFile(f);
1427
+ });
1428
+ if (mp4Fallback) {
1429
+ if (debug) console.log("".concat(LOG, " singleElementMode: HLS ad blocked, using MP4 fallback"));
1430
+ startNativePlayback(mp4Fallback);
1431
+ return;
1432
+ }
1433
+ }
1370
1434
  if (isHlsMediaFile(mediaFile)) {
1371
1435
  startHlsPlayback(mediaFile);
1372
1436
  } else {
@@ -1375,7 +1439,7 @@ function createVastAdLayer(contentVideo, options) {
1375
1439
  }
1376
1440
  function playAd(bids) {
1377
1441
  return _async_to_generator(function() {
1378
- var winner, ad, _contentVideo_parentElement, container, contentVolume, adVolume, mediaFile;
1442
+ var winner, ad, contentVolume, _contentVideo_parentElement, container, adVolume, mediaFile;
1379
1443
  return _ts_generator(this, function(_state) {
1380
1444
  switch(_state.label){
1381
1445
  case 0:
@@ -1417,32 +1481,40 @@ function createVastAdLayer(contentVideo, options) {
1417
1481
  trackingFired = _object_spread({}, createEmptyTrackingState());
1418
1482
  fireTrackingPixels2(ad.trackingUrls.impression);
1419
1483
  trackingFired.impression = true;
1420
- if (!adContainerEl) {
1421
- ;
1422
- container = document.createElement("div");
1423
- container.style.position = "absolute";
1424
- container.style.left = "0";
1425
- container.style.top = "0";
1426
- container.style.right = "0";
1427
- container.style.bottom = "0";
1428
- container.style.display = "none";
1429
- container.style.alignItems = "center";
1430
- container.style.justifyContent = "center";
1431
- container.style.pointerEvents = "none";
1432
- container.style.zIndex = "10";
1433
- container.style.backgroundColor = "#000";
1434
- (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1435
- adContainerEl = container;
1436
- }
1437
- if (!adVideoElement) {
1438
- adVideoElement = createAdVideoElement();
1439
- adContainerEl.appendChild(adVideoElement);
1484
+ contentVolume = contentVideo.volume;
1485
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1486
+ if (singleElementMode) {
1487
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1488
+ teardownCurrentPlayback();
1489
+ adVideoElement = contentVideo;
1490
+ adHls = void 0;
1440
1491
  setupAdEventListeners();
1441
1492
  } else {
1442
- teardownCurrentPlayback();
1493
+ if (!adContainerEl) {
1494
+ ;
1495
+ container = document.createElement("div");
1496
+ container.style.position = "absolute";
1497
+ container.style.left = "0";
1498
+ container.style.top = "0";
1499
+ container.style.right = "0";
1500
+ container.style.bottom = "0";
1501
+ container.style.display = "none";
1502
+ container.style.alignItems = "center";
1503
+ container.style.justifyContent = "center";
1504
+ container.style.pointerEvents = "none";
1505
+ container.style.zIndex = "10";
1506
+ container.style.backgroundColor = "#000";
1507
+ (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1508
+ adContainerEl = container;
1509
+ }
1510
+ if (!adVideoElement) {
1511
+ adVideoElement = createAdVideoElement();
1512
+ adContainerEl.appendChild(adVideoElement);
1513
+ setupAdEventListeners();
1514
+ } else {
1515
+ teardownCurrentPlayback();
1516
+ }
1443
1517
  }
1444
- contentVolume = contentVideo.volume;
1445
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1446
1518
  if (!continueLiveStreamDuringAds) {
1447
1519
  contentVideo.pause();
1448
1520
  }
@@ -1453,7 +1525,7 @@ function createVastAdLayer(contentVideo, options) {
1453
1525
  adVolume = originalMutedState ? 1 : originalVolume;
1454
1526
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1455
1527
  adVideoElement.muted = false;
1456
- if (adContainerEl) {
1528
+ if (!singleElementMode && adContainerEl) {
1457
1529
  adContainerEl.style.display = "flex";
1458
1530
  adContainerEl.style.pointerEvents = "auto";
1459
1531
  }
@@ -1490,7 +1562,7 @@ function createVastAdLayer(contentVideo, options) {
1490
1562
  }
1491
1563
  function preloadAd(bids, token) {
1492
1564
  return _async_to_generator(function() {
1493
- var winner, ad, mediaFile, videoEl, container, hls, slot, slot1;
1565
+ var winner, ad, mediaFile, slot, videoEl, container, hls, slot1, slot2;
1494
1566
  return _ts_generator(this, function(_state) {
1495
1567
  switch(_state.label){
1496
1568
  case 0:
@@ -1515,6 +1587,20 @@ function createVastAdLayer(contentVideo, options) {
1515
1587
  if (!mediaFile) return [
1516
1588
  2
1517
1589
  ];
1590
+ if (smartTVMode || singleElementMode) {
1591
+ slot = {
1592
+ bids: bids,
1593
+ ad: ad,
1594
+ mediaFile: mediaFile,
1595
+ videoEl: null,
1596
+ ready: true
1597
+ };
1598
+ preloadSlots.set(token, slot);
1599
+ if (debug) console.log("".concat(LOG, " [preload] Metadata-only preload (smartTV/singleElement), token=").concat(token, ", url=").concat(mediaFile.url));
1600
+ return [
1601
+ 2
1602
+ ];
1603
+ }
1518
1604
  videoEl = createAdVideoElement();
1519
1605
  videoEl.style.visibility = "hidden";
1520
1606
  videoEl.style.pointerEvents = "none";
@@ -1528,7 +1614,7 @@ function createVastAdLayer(contentVideo, options) {
1528
1614
  });
1529
1615
  hls.loadSource(mediaFile.url);
1530
1616
  hls.attachMedia(videoEl);
1531
- slot = {
1617
+ slot1 = {
1532
1618
  bids: bids,
1533
1619
  ad: ad,
1534
1620
  mediaFile: mediaFile,
@@ -1536,7 +1622,7 @@ function createVastAdLayer(contentVideo, options) {
1536
1622
  hlsInstance: hls,
1537
1623
  ready: false
1538
1624
  };
1539
- preloadSlots.set(token, slot);
1625
+ preloadSlots.set(token, slot1);
1540
1626
  hls.on(import_hls.default.Events.MANIFEST_PARSED, function() {
1541
1627
  var s = preloadSlots.get(token);
1542
1628
  if (s) s.ready = true;
@@ -1553,14 +1639,14 @@ function createVastAdLayer(contentVideo, options) {
1553
1639
  } else {
1554
1640
  videoEl.src = mediaFile.url;
1555
1641
  videoEl.load();
1556
- slot1 = {
1642
+ slot2 = {
1557
1643
  bids: bids,
1558
1644
  ad: ad,
1559
1645
  mediaFile: mediaFile,
1560
1646
  videoEl: videoEl,
1561
1647
  ready: false
1562
1648
  };
1563
- preloadSlots.set(token, slot1);
1649
+ preloadSlots.set(token, slot2);
1564
1650
  videoEl.addEventListener("canplay", function() {
1565
1651
  var s = preloadSlots.get(token);
1566
1652
  if (s) s.ready = true;
@@ -1579,7 +1665,7 @@ function createVastAdLayer(contentVideo, options) {
1579
1665
  }
1580
1666
  function playPreloaded(token) {
1581
1667
  return _async_to_generator(function() {
1582
- var slot, contentVolume, adVolume, container;
1668
+ var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, adVolume, container;
1583
1669
  return _ts_generator(this, function(_state) {
1584
1670
  if (destroyed) return [
1585
1671
  2,
@@ -1594,6 +1680,68 @@ function createVastAdLayer(contentVideo, options) {
1594
1680
  }
1595
1681
  preloadSlots.delete(token);
1596
1682
  if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1683
+ contentVolume = contentVideo.volume;
1684
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1685
+ sessionId = generateSessionId();
1686
+ currentAd = slot.ad;
1687
+ trackingFired = _object_spread({}, createEmptyTrackingState());
1688
+ fireTrackingPixels2(slot.ad.trackingUrls.impression);
1689
+ trackingFired.impression = true;
1690
+ if (singleElementMode) {
1691
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1692
+ teardownCurrentPlayback();
1693
+ adVideoElement = contentVideo;
1694
+ adHls = void 0;
1695
+ setupAdEventListeners();
1696
+ if (!continueLiveStreamDuringAds) {
1697
+ contentVideo.pause();
1698
+ }
1699
+ contentVideo.muted = true;
1700
+ contentVideo.volume = 0;
1701
+ adPlaying = true;
1702
+ setAdPlayingFlag(true);
1703
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1704
+ contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1705
+ contentVideo.muted = false;
1706
+ emit("content_pause");
1707
+ if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1708
+ startPlayback(slot.mediaFile);
1709
+ return [
1710
+ 2
1711
+ ];
1712
+ }
1713
+ if (smartTVMode && !slot.videoEl) {
1714
+ teardownCurrentPlayback();
1715
+ if (adVideoElement) {
1716
+ adVideoElement.remove();
1717
+ adVideoElement = void 0;
1718
+ }
1719
+ videoEl = createAdVideoElement();
1720
+ videoEl.style.visibility = "visible";
1721
+ videoEl.style.pointerEvents = "none";
1722
+ container2 = ensureAdContainer();
1723
+ container2.appendChild(videoEl);
1724
+ adVideoElement = videoEl;
1725
+ setupAdEventListeners();
1726
+ if (!continueLiveStreamDuringAds) {
1727
+ contentVideo.pause();
1728
+ }
1729
+ contentVideo.muted = true;
1730
+ contentVideo.volume = 0;
1731
+ adPlaying = true;
1732
+ setAdPlayingFlag(true);
1733
+ adVolume21 = originalMutedState ? 1 : originalVolume;
1734
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1735
+ adVideoElement.muted = false;
1736
+ container2.style.display = "flex";
1737
+ container2.style.pointerEvents = "auto";
1738
+ emit("content_pause");
1739
+ if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1740
+ startPlayback(slot.mediaFile);
1741
+ return [
1742
+ 2
1743
+ ];
1744
+ }
1597
1745
  teardownCurrentPlayback();
1598
1746
  if (adVideoElement && adVideoElement !== slot.videoEl) {
1599
1747
  adVideoElement.remove();
@@ -1603,13 +1751,6 @@ function createVastAdLayer(contentVideo, options) {
1603
1751
  adVideoElement = slot.videoEl;
1604
1752
  adHls = slot.hlsInstance;
1605
1753
  setupAdEventListeners();
1606
- sessionId = generateSessionId();
1607
- currentAd = slot.ad;
1608
- trackingFired = _object_spread({}, createEmptyTrackingState());
1609
- fireTrackingPixels2(slot.ad.trackingUrls.impression);
1610
- trackingFired.impression = true;
1611
- contentVolume = contentVideo.volume;
1612
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1613
1754
  if (!continueLiveStreamDuringAds) {
1614
1755
  contentVideo.pause();
1615
1756
  }
@@ -1624,17 +1765,10 @@ function createVastAdLayer(contentVideo, options) {
1624
1765
  container.style.display = "flex";
1625
1766
  container.style.pointerEvents = "auto";
1626
1767
  emit("content_pause");
1627
- if (slot.hlsInstance) {
1628
- adVideoElement.play().catch(function(error) {
1629
- console.error("".concat(LOG, " [preload] Error playing preloaded HLS ad:"), error);
1630
- handleAdError();
1631
- });
1632
- } else {
1633
- adVideoElement.play().catch(function(error) {
1634
- console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1635
- handleAdError();
1636
- });
1637
- }
1768
+ adVideoElement.play().catch(function(error) {
1769
+ console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1770
+ handleAdError();
1771
+ });
1638
1772
  return [
1639
1773
  2
1640
1774
  ];
@@ -1648,10 +1782,12 @@ function createVastAdLayer(contentVideo, options) {
1648
1782
  if (slot.hlsInstance) {
1649
1783
  slot.hlsInstance.destroy();
1650
1784
  }
1651
- slot.videoEl.pause();
1652
- slot.videoEl.removeAttribute("src");
1653
- slot.videoEl.load();
1654
- slot.videoEl.remove();
1785
+ if (slot.videoEl) {
1786
+ slot.videoEl.pause();
1787
+ slot.videoEl.removeAttribute("src");
1788
+ slot.videoEl.load();
1789
+ slot.videoEl.remove();
1790
+ }
1655
1791
  if (debug) console.log("".concat(LOG, " [preload] Cancelled and cleaned up token=").concat(token));
1656
1792
  }
1657
1793
  return {
@@ -1699,20 +1835,27 @@ function createVastAdLayer(contentVideo, options) {
1699
1835
  setAdPlayingFlag(false);
1700
1836
  contentVideo.muted = originalMutedState;
1701
1837
  contentVideo.volume = originalMutedState ? 0 : originalVolume;
1702
- if (adContainerEl) {
1703
- adContainerEl.style.display = "none";
1704
- adContainerEl.style.pointerEvents = "none";
1705
- }
1706
1838
  contentVideo.style.visibility = "visible";
1707
1839
  contentVideo.style.opacity = "1";
1708
- if (continueLiveStreamDuringAds) {
1709
- contentVideo.play().catch(function() {});
1710
- }
1711
- teardownCurrentPlayback();
1712
- if (adVideoElement) {
1713
- adVideoElement.pause();
1714
- adVideoElement.removeAttribute("src");
1715
- adVideoElement.load();
1840
+ if (singleElementMode) {
1841
+ teardownCurrentPlayback();
1842
+ contentVideo.removeAttribute("src");
1843
+ contentVideo.load();
1844
+ adVideoElement = void 0;
1845
+ } else {
1846
+ if (adContainerEl) {
1847
+ adContainerEl.style.display = "none";
1848
+ adContainerEl.style.pointerEvents = "none";
1849
+ }
1850
+ if (continueLiveStreamDuringAds) {
1851
+ contentVideo.play().catch(function() {});
1852
+ }
1853
+ teardownCurrentPlayback();
1854
+ if (adVideoElement) {
1855
+ adVideoElement.pause();
1856
+ adVideoElement.removeAttribute("src");
1857
+ adVideoElement.load();
1858
+ }
1716
1859
  }
1717
1860
  currentAd = void 0;
1718
1861
  tornDown = false;
@@ -1752,9 +1895,14 @@ function createVastAdLayer(contentVideo, options) {
1752
1895
  }
1753
1896
  teardownCurrentPlayback();
1754
1897
  if (adVideoElement) {
1755
- adVideoElement.pause();
1756
- adVideoElement.removeAttribute("src");
1757
- adVideoElement.remove();
1898
+ if (singleElementMode && adVideoElement === contentVideo) {
1899
+ contentVideo.removeAttribute("src");
1900
+ contentVideo.load();
1901
+ } else {
1902
+ adVideoElement.pause();
1903
+ adVideoElement.removeAttribute("src");
1904
+ adVideoElement.remove();
1905
+ }
1758
1906
  adVideoElement = void 0;
1759
1907
  }
1760
1908
  if (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.parentElement) {
@@ -2977,8 +3125,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2977
3125
  this.vastManager = createVastManager(config.debugAdTiming !== void 0 ? {
2978
3126
  debug: !!config.debugAdTiming
2979
3127
  } : {});
3128
+ var browserForAdLayer = detectBrowser();
3129
+ var isSinglePipeline = browserForAdLayer.isSmartTV || !!this.config.singlePipelineMode;
2980
3130
  this.adLayer = createVastAdLayer(this.video, {
2981
3131
  continueLiveStreamDuringAds: false,
3132
+ smartTVMode: isSinglePipeline,
3133
+ singleElementMode: isSinglePipeline,
3134
+ forceMP4Ads: isSinglePipeline,
2982
3135
  debug: !!config.debugAdTiming
2983
3136
  });
2984
3137
  }
@@ -3624,6 +3777,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3624
3777
  if (_this.fillerVideo) {
3625
3778
  _this.fillerVideo.style.display = "none";
3626
3779
  }
3780
+ if (!_this.adLayer.isAdPlaying()) {
3781
+ if (_this.config.debugAdTiming) {
3782
+ console.log("[StormcloudVideoPlayer] Filler video failed \u2014 restoring main video");
3783
+ }
3784
+ _this.adLayer.hidePlaceholder();
3785
+ if (_this.video.paused && _this.video.readyState >= 2) {
3786
+ var _this_video_play;
3787
+ (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
3788
+ }
3789
+ }
3627
3790
  });
3628
3791
  if (this.config.debugAdTiming) {
3629
3792
  console.log("[StormcloudVideoPlayer] Showing filler video layer");
@@ -4340,6 +4503,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4340
4503
  if (!this.isLiveStream) {
4341
4504
  return false;
4342
4505
  }
4506
+ if (this.config.singlePipelineMode) {
4507
+ return false;
4508
+ }
4509
+ var browser = detectBrowser();
4510
+ if (browser.isSmartTV) {
4511
+ return false;
4512
+ }
4343
4513
  return true;
4344
4514
  }
4345
4515
  },
@@ -4547,7 +4717,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4547
4717
  switch(_state.label){
4548
4718
  case 0:
4549
4719
  _loop = function() {
4550
- var remaining, prefetchContext, bids, err, bids1, remainingNow, delay, elapsed, remaining1, context, bids2, remainingNow1, err1;
4720
+ var remaining, prefetchContext, bids, err, bids1, remainingNow, urgentNeedAd, delay, elapsed, remaining1, context, bids2, remainingNow1, err1, sleepMs;
4551
4721
  return _ts_generator(this, function(_state) {
4552
4722
  switch(_state.label){
4553
4723
  case 0:
@@ -4683,7 +4853,8 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4683
4853
  "continue"
4684
4854
  ];
4685
4855
  case 12:
4686
- delay = _this.lastAdRequestTime ? _this.minAdRequestIntervalMs + (_this.consecutiveFailures > 0 ? backoffMs() : 0) : 0;
4856
+ urgentNeedAd = _this.inAdBreak && !_this.adLayer.isAdPlaying();
4857
+ delay = _this.lastAdRequestTime ? _this.minAdRequestIntervalMs + (!urgentNeedAd && _this.consecutiveFailures > 0 ? backoffMs() : 0) : 0;
4687
4858
  elapsed = Date.now() - _this.lastAdRequestTime;
4688
4859
  if (!(elapsed < delay && _this.lastAdRequestTime > 0)) return [
4689
4860
  3,
@@ -4802,10 +4973,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4802
4973
  24
4803
4974
  ];
4804
4975
  case 24:
4976
+ sleepMs = _this.inAdBreak && !_this.adLayer.isAdPlaying() ? 0 : backoffMs();
4805
4977
  return [
4806
4978
  4,
4807
4979
  new Promise(function(r) {
4808
- return setTimeout(r, backoffMs());
4980
+ return setTimeout(r, sleepMs);
4809
4981
  })
4810
4982
  ];
4811
4983
  case 25:
@@ -5484,42 +5656,71 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5484
5656
  this.video.volume = restoredVolume;
5485
5657
  }
5486
5658
  var browser = detectBrowser();
5487
- var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0;
5659
+ var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0 || !!this.config.singlePipelineMode;
5488
5660
  if (isSmartTV && this.hls) {
5489
- if (!restoredMuted) {
5490
- var hlsRef = this.hls;
5491
- var savedMuted = restoredMuted;
5492
- var savedVolume = restoredVolume;
5493
- var onManifestParsedRestore = function onManifestParsedRestore1() {
5494
- hlsRef.off(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5495
- if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5496
- if (_this.video.muted !== savedMuted) _this.video.muted = savedMuted;
5497
- if (Math.abs(_this.video.volume - savedVolume) > 0.01) _this.video.volume = savedVolume;
5498
- if (_this.config.debugAdTiming) {
5499
- console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5661
+ var hlsRef = this.hls;
5662
+ var savedMuted = restoredMuted;
5663
+ var savedVolume = restoredVolume;
5664
+ var videoRef = this.video;
5665
+ var debugEnabled = this.config.debugAdTiming;
5666
+ var tryPlay = function tryPlay1(attempt) {
5667
+ var _videoRef_play;
5668
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5669
+ (_videoRef_play = videoRef.play()) === null || _videoRef_play === void 0 ? void 0 : _videoRef_play.catch(function() {
5670
+ if (attempt < 3) {
5671
+ if (debugEnabled) {
5672
+ console.log("[StormcloudVideoPlayer] Smart TV: play() retry ".concat(attempt + 1, "/3 in ").concat(500 * (attempt + 1), "ms"));
5500
5673
  }
5674
+ setTimeout(function() {
5675
+ return tryPlay(attempt + 1);
5676
+ }, 500 * (attempt + 1));
5501
5677
  }
5502
- };
5503
- hlsRef.on(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5504
- }
5505
- this.hls.attachMedia(this.video);
5506
- if (this.config.debugAdTiming) {
5507
- console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5678
+ });
5679
+ };
5680
+ var onManifestParsedRestore = function onManifestParsedRestore1() {
5681
+ hlsRef.off(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5682
+ if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5683
+ if (videoRef.muted !== savedMuted) videoRef.muted = savedMuted;
5684
+ if (Math.abs(videoRef.volume - savedVolume) > 0.01) videoRef.volume = savedVolume;
5685
+ if (debugEnabled) {
5686
+ console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5687
+ }
5688
+ hlsRef.startLoad(-1);
5689
+ tryPlay(0);
5690
+ if (debugEnabled) {
5691
+ console.log("[StormcloudVideoPlayer] Smart TV: seeking to live edge and resuming playback after re-attach");
5692
+ }
5693
+ }
5694
+ };
5695
+ hlsRef.on(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5696
+ var pipelineDelayMs = 300;
5697
+ if (debugEnabled) {
5698
+ console.log("[StormcloudVideoPlayer] Smart TV: waiting ".concat(pipelineDelayMs, "ms for hardware pipeline release before re-attach"));
5508
5699
  }
5509
- }
5510
- if (this.shouldContinueLiveStreamDuringAds()) {
5511
- var _this_video_play;
5512
- if (this.config.debugAdTiming) {
5513
- if (this.video.paused) {
5514
- console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
5515
- } else {
5516
- console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
5700
+ setTimeout(function() {
5701
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5702
+ if (_this.hls) {
5703
+ _this.hls.attachMedia(_this.video);
5704
+ if (debugEnabled) {
5705
+ console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5706
+ }
5517
5707
  }
5708
+ }, pipelineDelayMs);
5709
+ } else {
5710
+ if (this.shouldContinueLiveStreamDuringAds()) {
5711
+ var _this_video_play;
5712
+ if (this.config.debugAdTiming) {
5713
+ if (this.video.paused) {
5714
+ console.log("[StormcloudVideoPlayer] Content video paused in live mode after ads, resuming playback");
5715
+ } else {
5716
+ console.log("[StormcloudVideoPlayer] Content video already playing in live mode after ads");
5717
+ }
5718
+ }
5719
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5720
+ } else if (this.video.paused) {
5721
+ var _this_video_play1;
5722
+ (_this_video_play1 = this.video.play()) === null || _this_video_play1 === void 0 ? void 0 : _this_video_play1.catch(function() {});
5518
5723
  }
5519
- (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5520
- } else if (this.video.paused) {
5521
- var _this_video_play1;
5522
- (_this_video_play1 = this.video.play()) === null || _this_video_play1 === void 0 ? void 0 : _this_video_play1.catch(function() {});
5523
5724
  }
5524
5725
  this.syncMainContentAudioWhenVisible();
5525
5726
  if (!restoredMuted) {
@@ -5570,6 +5771,23 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5570
5771
  console.log("[CONTINUOUS-FETCH] \uD83D\uDED1 Max consecutive failures reached (".concat(this.consecutiveFailures, "), ending ad break gracefully"));
5571
5772
  }
5572
5773
  this.handleAdPodComplete();
5774
+ return;
5775
+ }
5776
+ if (this.inAdBreak && !this.config.disableFiller) {
5777
+ if (this.config.debugAdTiming) {
5778
+ console.log("[CONTINUOUS-FETCH] Ad failure in active break \u2014 showing filler while awaiting next ad");
5779
+ }
5780
+ this.showPlaceholderLayer();
5781
+ this.adLayer.showPlaceholder();
5782
+ } else if (this.inAdBreak) {
5783
+ if (this.config.debugAdTiming) {
5784
+ console.log("[CONTINUOUS-FETCH] Ad failure with no filler \u2014 restoring main video temporarily");
5785
+ }
5786
+ this.adLayer.hidePlaceholder();
5787
+ if (!this.adLayer.isAdPlaying() && this.video.paused && this.video.readyState >= 2) {
5788
+ var _this_video_play;
5789
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5790
+ }
5573
5791
  }
5574
5792
  }
5575
5793
  },