stormcloud-video-player 0.6.1 → 0.6.3

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.
@@ -960,7 +960,7 @@ function resolveBidToVastAd(winner, logPrefix) {
960
960
  return Promise.resolve(null);
961
961
  }
962
962
  function createVastAdLayer(contentVideo, options) {
963
- var _ref, _ref1, _ref2, _ref3;
963
+ var _ref, _ref1, _ref2, _ref3, _ref4;
964
964
  var adPlaying = false;
965
965
  var originalMutedState = false;
966
966
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
@@ -969,7 +969,8 @@ function createVastAdLayer(contentVideo, options) {
969
969
  var continueLiveStreamDuringAds = (_ref = options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) !== null && _ref !== void 0 ? _ref : false;
970
970
  var smartTVMode = (_ref1 = options === null || options === void 0 ? void 0 : options.smartTVMode) !== null && _ref1 !== void 0 ? _ref1 : false;
971
971
  var singleElementMode = (_ref2 = options === null || options === void 0 ? void 0 : options.singleElementMode) !== null && _ref2 !== void 0 ? _ref2 : false;
972
- var debug = (_ref3 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref3 !== void 0 ? _ref3 : false;
972
+ var forceMP4Ads = (_ref3 = options === null || options === void 0 ? void 0 : options.forceMP4Ads) !== null && _ref3 !== void 0 ? _ref3 : smartTVMode || singleElementMode;
973
+ var debug = (_ref4 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref4 !== void 0 ? _ref4 : false;
973
974
  var adVideoElement;
974
975
  var adHls;
975
976
  var adContainerEl;
@@ -1040,14 +1041,26 @@ function createVastAdLayer(contentVideo, options) {
1040
1041
  var _ref;
1041
1042
  var _scoredFiles_;
1042
1043
  if (mediaFiles.length === 0) throw new Error("No media files available");
1043
- var firstFile = mediaFiles[0];
1044
- if (mediaFiles.length === 1) return firstFile;
1044
+ var candidates = mediaFiles;
1045
+ if (forceMP4Ads) {
1046
+ var mp4Only = candidates.filter(function(f) {
1047
+ return !isHlsMediaFile(f);
1048
+ });
1049
+ if (mp4Only.length > 0) {
1050
+ candidates = mp4Only;
1051
+ if (debug) console.log("".concat(LOG, " forceMP4Ads: filtered to ").concat(mp4Only.length, " MP4-only file(s)"));
1052
+ } else if (debug) {
1053
+ console.warn("".concat(LOG, " forceMP4Ads: no MP4 files available, falling back to all media files"));
1054
+ }
1055
+ }
1056
+ var firstFile = candidates[0];
1057
+ if (candidates.length === 1) return firstFile;
1045
1058
  var mainQuality = getMainStreamQuality();
1046
1059
  if (!mainQuality) {
1047
1060
  if (debug) console.log("".concat(LOG, " No main stream quality info, using first media file"));
1048
1061
  return firstFile;
1049
1062
  }
1050
- var scoredFiles = mediaFiles.map(function(file) {
1063
+ var scoredFiles = candidates.map(function(file) {
1051
1064
  var widthDiff = Math.abs(file.width - mainQuality.width);
1052
1065
  var heightDiff = Math.abs(file.height - mainQuality.height);
1053
1066
  var resolutionDiff = widthDiff + heightDiff;
@@ -1264,6 +1277,16 @@ function createVastAdLayer(contentVideo, options) {
1264
1277
  }
1265
1278
  function startPlayback(mediaFile) {
1266
1279
  if (!adVideoElement) return;
1280
+ if (singleElementMode && isHlsMediaFile(mediaFile)) {
1281
+ var mp4Fallback = currentAd === null || currentAd === void 0 ? void 0 : currentAd.mediaFiles.find(function(f) {
1282
+ return !isHlsMediaFile(f);
1283
+ });
1284
+ if (mp4Fallback) {
1285
+ if (debug) console.log("".concat(LOG, " singleElementMode: HLS ad blocked, using MP4 fallback"));
1286
+ startNativePlayback(mp4Fallback);
1287
+ return;
1288
+ }
1289
+ }
1267
1290
  if (isHlsMediaFile(mediaFile)) {
1268
1291
  startHlsPlayback(mediaFile);
1269
1292
  } else {
@@ -1272,7 +1295,7 @@ function createVastAdLayer(contentVideo, options) {
1272
1295
  }
1273
1296
  function playAd(bids) {
1274
1297
  return _async_to_generator(function() {
1275
- var winner, ad, contentVolume, _contentVideo_parentElement, container, adVolume, mediaFile;
1298
+ var winner, ad, contentVolume, adVolume2, mediaFile2, _contentVideo_parentElement, container, adVolume, mediaFile;
1276
1299
  return _ts_generator(this, function(_state) {
1277
1300
  switch(_state.label){
1278
1301
  case 0:
@@ -1316,37 +1339,71 @@ function createVastAdLayer(contentVideo, options) {
1316
1339
  trackingFired.impression = true;
1317
1340
  contentVolume = contentVideo.volume;
1318
1341
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1319
- if (singleElementMode) {
1320
- mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1321
- teardownCurrentPlayback();
1322
- adVideoElement = contentVideo;
1323
- adHls = void 0;
1342
+ if (!singleElementMode) return [
1343
+ 3,
1344
+ 3
1345
+ ];
1346
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1347
+ teardownCurrentPlayback();
1348
+ adVideoElement = contentVideo;
1349
+ adHls = void 0;
1350
+ adPlaying = true;
1351
+ setAdPlayingFlag(true);
1352
+ contentVideo.removeAttribute("src");
1353
+ contentVideo.load();
1354
+ if (!continueLiveStreamDuringAds) {
1355
+ contentVideo.pause();
1356
+ }
1357
+ contentVideo.muted = true;
1358
+ contentVideo.volume = 0;
1359
+ return [
1360
+ 4,
1361
+ new Promise(function(resolve) {
1362
+ return setTimeout(resolve, 200);
1363
+ })
1364
+ ];
1365
+ case 2:
1366
+ _state.sent();
1367
+ if (destroyed || tornDown) return [
1368
+ 2
1369
+ ];
1370
+ contentVideo.style.visibility = "visible";
1371
+ contentVideo.style.opacity = "1";
1372
+ emit("content_pause");
1373
+ setupAdEventListeners();
1374
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1375
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume2));
1376
+ adVideoElement.muted = false;
1377
+ mediaFile2 = selectBestMediaFile(ad.mediaFiles);
1378
+ if (debug) console.log("".concat(LOG, " Loading ad from: ").concat(mediaFile2.url));
1379
+ startPlayback(mediaFile2);
1380
+ return [
1381
+ 2
1382
+ ];
1383
+ case 3:
1384
+ if (!adContainerEl) {
1385
+ ;
1386
+ container = document.createElement("div");
1387
+ container.style.position = "absolute";
1388
+ container.style.left = "0";
1389
+ container.style.top = "0";
1390
+ container.style.right = "0";
1391
+ container.style.bottom = "0";
1392
+ container.style.display = "none";
1393
+ container.style.alignItems = "center";
1394
+ container.style.justifyContent = "center";
1395
+ container.style.pointerEvents = "none";
1396
+ container.style.zIndex = "10";
1397
+ container.style.backgroundColor = "#000";
1398
+ (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1399
+ adContainerEl = container;
1400
+ }
1401
+ if (!adVideoElement) {
1402
+ adVideoElement = createAdVideoElement();
1403
+ adContainerEl.appendChild(adVideoElement);
1324
1404
  setupAdEventListeners();
1325
1405
  } else {
1326
- if (!adContainerEl) {
1327
- ;
1328
- container = document.createElement("div");
1329
- container.style.position = "absolute";
1330
- container.style.left = "0";
1331
- container.style.top = "0";
1332
- container.style.right = "0";
1333
- container.style.bottom = "0";
1334
- container.style.display = "none";
1335
- container.style.alignItems = "center";
1336
- container.style.justifyContent = "center";
1337
- container.style.pointerEvents = "none";
1338
- container.style.zIndex = "10";
1339
- container.style.backgroundColor = "#000";
1340
- (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1341
- adContainerEl = container;
1342
- }
1343
- if (!adVideoElement) {
1344
- adVideoElement = createAdVideoElement();
1345
- adContainerEl.appendChild(adVideoElement);
1346
- setupAdEventListeners();
1347
- } else {
1348
- teardownCurrentPlayback();
1349
- }
1406
+ teardownCurrentPlayback();
1350
1407
  }
1351
1408
  if (!continueLiveStreamDuringAds) {
1352
1409
  contentVideo.pause();
@@ -1358,7 +1415,7 @@ function createVastAdLayer(contentVideo, options) {
1358
1415
  adVolume = originalMutedState ? 1 : originalVolume;
1359
1416
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1360
1417
  adVideoElement.muted = false;
1361
- if (!singleElementMode && adContainerEl) {
1418
+ if (adContainerEl) {
1362
1419
  adContainerEl.style.display = "flex";
1363
1420
  adContainerEl.style.pointerEvents = "auto";
1364
1421
  }
@@ -1462,6 +1519,7 @@ function createVastAdLayer(contentVideo, options) {
1462
1519
  if (debug) console.log("".concat(LOG, " [preload] HLS manifest parsed, token=").concat(token));
1463
1520
  });
1464
1521
  hls.on(import_hls.default.Events.ERROR, function(_evt, data) {
1522
+ if (!preloadSlots.has(token)) return;
1465
1523
  if (data.fatal) {
1466
1524
  if (debug) console.warn("".concat(LOG, " [preload] HLS error for token=").concat(token));
1467
1525
  preloadSlots.delete(token);
@@ -1498,113 +1556,146 @@ function createVastAdLayer(contentVideo, options) {
1498
1556
  }
1499
1557
  function playPreloaded(token) {
1500
1558
  return _async_to_generator(function() {
1501
- var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, adVolume, container;
1559
+ var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, nonFatalNetworkErrors, adVolume, container;
1502
1560
  return _ts_generator(this, function(_state) {
1503
- if (destroyed) return [
1504
- 2,
1505
- Promise.reject(new Error("Layer has been destroyed"))
1506
- ];
1507
- slot = preloadSlots.get(token);
1508
- if (!slot) {
1509
- if (debug) console.warn("".concat(LOG, " [preload] No slot found for token=").concat(token, ", nothing to play"));
1510
- return [
1511
- 2
1512
- ];
1513
- }
1514
- preloadSlots.delete(token);
1515
- if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1516
- contentVolume = contentVideo.volume;
1517
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1518
- sessionId = generateSessionId();
1519
- currentAd = slot.ad;
1520
- trackingFired = _object_spread({}, createEmptyTrackingState());
1521
- fireTrackingPixels2(slot.ad.trackingUrls.impression);
1522
- trackingFired.impression = true;
1523
- if (singleElementMode) {
1524
- mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1525
- teardownCurrentPlayback();
1526
- adVideoElement = contentVideo;
1527
- adHls = void 0;
1528
- setupAdEventListeners();
1529
- if (!continueLiveStreamDuringAds) {
1530
- contentVideo.pause();
1531
- }
1532
- contentVideo.muted = true;
1533
- contentVideo.volume = 0;
1534
- adPlaying = true;
1535
- setAdPlayingFlag(true);
1536
- adVolume2 = originalMutedState ? 1 : originalVolume;
1537
- contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1538
- contentVideo.muted = false;
1539
- emit("content_pause");
1540
- if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1541
- startPlayback(slot.mediaFile);
1542
- return [
1543
- 2
1544
- ];
1545
- }
1546
- if (smartTVMode && !slot.videoEl) {
1547
- teardownCurrentPlayback();
1548
- if (adVideoElement) {
1549
- adVideoElement.remove();
1550
- adVideoElement = void 0;
1551
- }
1552
- videoEl = createAdVideoElement();
1553
- videoEl.style.visibility = "visible";
1554
- videoEl.style.pointerEvents = "none";
1555
- container2 = ensureAdContainer();
1556
- container2.appendChild(videoEl);
1557
- adVideoElement = videoEl;
1558
- setupAdEventListeners();
1559
- if (!continueLiveStreamDuringAds) {
1560
- contentVideo.pause();
1561
- }
1562
- contentVideo.muted = true;
1563
- contentVideo.volume = 0;
1564
- adPlaying = true;
1565
- setAdPlayingFlag(true);
1566
- adVolume21 = originalMutedState ? 1 : originalVolume;
1567
- adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1568
- adVideoElement.muted = false;
1569
- container2.style.display = "flex";
1570
- container2.style.pointerEvents = "auto";
1571
- emit("content_pause");
1572
- if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1573
- startPlayback(slot.mediaFile);
1574
- return [
1575
- 2
1576
- ];
1577
- }
1578
- teardownCurrentPlayback();
1579
- if (adVideoElement && adVideoElement !== slot.videoEl) {
1580
- adVideoElement.remove();
1581
- }
1582
- slot.videoEl.style.visibility = "visible";
1583
- slot.videoEl.style.pointerEvents = "none";
1584
- adVideoElement = slot.videoEl;
1585
- adHls = slot.hlsInstance;
1586
- setupAdEventListeners();
1587
- if (!continueLiveStreamDuringAds) {
1588
- contentVideo.pause();
1561
+ switch(_state.label){
1562
+ case 0:
1563
+ if (destroyed) return [
1564
+ 2,
1565
+ Promise.reject(new Error("Layer has been destroyed"))
1566
+ ];
1567
+ slot = preloadSlots.get(token);
1568
+ if (!slot) {
1569
+ if (debug) console.warn("".concat(LOG, " [preload] No slot found for token=").concat(token, ", nothing to play"));
1570
+ return [
1571
+ 2
1572
+ ];
1573
+ }
1574
+ preloadSlots.delete(token);
1575
+ if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1576
+ contentVolume = contentVideo.volume;
1577
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1578
+ sessionId = generateSessionId();
1579
+ currentAd = slot.ad;
1580
+ trackingFired = _object_spread({}, createEmptyTrackingState());
1581
+ fireTrackingPixels2(slot.ad.trackingUrls.impression);
1582
+ trackingFired.impression = true;
1583
+ if (!singleElementMode) return [
1584
+ 3,
1585
+ 2
1586
+ ];
1587
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1588
+ teardownCurrentPlayback();
1589
+ adVideoElement = contentVideo;
1590
+ adHls = void 0;
1591
+ adPlaying = true;
1592
+ setAdPlayingFlag(true);
1593
+ contentVideo.removeAttribute("src");
1594
+ contentVideo.load();
1595
+ contentVideo.muted = true;
1596
+ contentVideo.volume = 0;
1597
+ return [
1598
+ 4,
1599
+ new Promise(function(resolve) {
1600
+ return setTimeout(resolve, 200);
1601
+ })
1602
+ ];
1603
+ case 1:
1604
+ _state.sent();
1605
+ if (destroyed || tornDown) return [
1606
+ 2
1607
+ ];
1608
+ contentVideo.style.visibility = "visible";
1609
+ contentVideo.style.opacity = "1";
1610
+ emit("content_pause");
1611
+ setupAdEventListeners();
1612
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1613
+ contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1614
+ contentVideo.muted = false;
1615
+ if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1616
+ startPlayback(slot.mediaFile);
1617
+ return [
1618
+ 2
1619
+ ];
1620
+ case 2:
1621
+ if (smartTVMode && !slot.videoEl) {
1622
+ teardownCurrentPlayback();
1623
+ if (adVideoElement) {
1624
+ adVideoElement.remove();
1625
+ adVideoElement = void 0;
1626
+ }
1627
+ videoEl = createAdVideoElement();
1628
+ videoEl.style.visibility = "visible";
1629
+ videoEl.style.pointerEvents = "none";
1630
+ container2 = ensureAdContainer();
1631
+ container2.appendChild(videoEl);
1632
+ adVideoElement = videoEl;
1633
+ setupAdEventListeners();
1634
+ if (!continueLiveStreamDuringAds) {
1635
+ contentVideo.pause();
1636
+ }
1637
+ contentVideo.muted = true;
1638
+ contentVideo.volume = 0;
1639
+ adPlaying = true;
1640
+ setAdPlayingFlag(true);
1641
+ adVolume21 = originalMutedState ? 1 : originalVolume;
1642
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1643
+ adVideoElement.muted = false;
1644
+ container2.style.display = "flex";
1645
+ container2.style.pointerEvents = "auto";
1646
+ emit("content_pause");
1647
+ if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1648
+ startPlayback(slot.mediaFile);
1649
+ return [
1650
+ 2
1651
+ ];
1652
+ }
1653
+ teardownCurrentPlayback();
1654
+ if (adVideoElement && adVideoElement !== slot.videoEl) {
1655
+ adVideoElement.remove();
1656
+ }
1657
+ slot.videoEl.style.visibility = "visible";
1658
+ slot.videoEl.style.pointerEvents = "none";
1659
+ adVideoElement = slot.videoEl;
1660
+ adHls = slot.hlsInstance;
1661
+ if (adHls) {
1662
+ nonFatalNetworkErrors = 0;
1663
+ adHls.on(import_hls.default.Events.ERROR, function(_event, data) {
1664
+ if (!adPlaying) return;
1665
+ if (data.fatal) {
1666
+ handleAdError();
1667
+ } else if (data.type === import_hls.default.ErrorTypes.NETWORK_ERROR) {
1668
+ nonFatalNetworkErrors++;
1669
+ if (nonFatalNetworkErrors >= 3) {
1670
+ if (debug) console.warn("".concat(LOG, " [preload] Too many non-fatal HLS network errors during playback, treating as fatal"));
1671
+ handleAdError();
1672
+ }
1673
+ }
1674
+ });
1675
+ }
1676
+ setupAdEventListeners();
1677
+ if (!continueLiveStreamDuringAds) {
1678
+ contentVideo.pause();
1679
+ }
1680
+ contentVideo.muted = true;
1681
+ contentVideo.volume = 0;
1682
+ adPlaying = true;
1683
+ setAdPlayingFlag(true);
1684
+ adVolume = originalMutedState ? 1 : originalVolume;
1685
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1686
+ adVideoElement.muted = false;
1687
+ container = ensureAdContainer();
1688
+ container.style.display = "flex";
1689
+ container.style.pointerEvents = "auto";
1690
+ emit("content_pause");
1691
+ adVideoElement.play().catch(function(error) {
1692
+ console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1693
+ handleAdError();
1694
+ });
1695
+ return [
1696
+ 2
1697
+ ];
1589
1698
  }
1590
- contentVideo.muted = true;
1591
- contentVideo.volume = 0;
1592
- adPlaying = true;
1593
- setAdPlayingFlag(true);
1594
- adVolume = originalMutedState ? 1 : originalVolume;
1595
- adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1596
- adVideoElement.muted = false;
1597
- container = ensureAdContainer();
1598
- container.style.display = "flex";
1599
- container.style.pointerEvents = "auto";
1600
- emit("content_pause");
1601
- adVideoElement.play().catch(function(error) {
1602
- console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1603
- handleAdError();
1604
- });
1605
- return [
1606
- 2
1607
- ];
1608
1699
  });
1609
1700
  })();
1610
1701
  }
@@ -2934,10 +3025,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2934
3025
  debug: !!config.debugAdTiming
2935
3026
  } : {});
2936
3027
  var browserForAdLayer = detectBrowser();
3028
+ var isSinglePipeline = browserForAdLayer.isSmartTV || !!this.config.singlePipelineMode;
2937
3029
  this.adLayer = createVastAdLayer(this.video, {
2938
3030
  continueLiveStreamDuringAds: false,
2939
- smartTVMode: browserForAdLayer.isSmartTV,
2940
- singleElementMode: browserForAdLayer.isSmartTV,
3031
+ smartTVMode: isSinglePipeline,
3032
+ singleElementMode: isSinglePipeline,
3033
+ forceMP4Ads: isSinglePipeline,
2941
3034
  debug: !!config.debugAdTiming
2942
3035
  });
2943
3036
  }
@@ -3583,6 +3676,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3583
3676
  if (_this.fillerVideo) {
3584
3677
  _this.fillerVideo.style.display = "none";
3585
3678
  }
3679
+ if (!_this.adLayer.isAdPlaying()) {
3680
+ if (_this.config.debugAdTiming) {
3681
+ console.log("[StormcloudVideoPlayer] Filler video failed \u2014 restoring main video");
3682
+ }
3683
+ _this.adLayer.hidePlaceholder();
3684
+ if (_this.video.paused && _this.video.readyState >= 2) {
3685
+ var _this_video_play;
3686
+ (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
3687
+ }
3688
+ }
3586
3689
  });
3587
3690
  if (this.config.debugAdTiming) {
3588
3691
  console.log("[StormcloudVideoPlayer] Showing filler video layer");
@@ -3848,7 +3951,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3848
3951
  };
3849
3952
  this.adLayer.updateOriginalMutedState(this.video.muted, this.video.volume);
3850
3953
  }
3851
- if (!this.config.disableFiller && !this.video.muted) {
3954
+ if (!this.config.disableFiller && !this.video.muted && !this.adLayer.isAdPlaying()) {
3852
3955
  this.video.muted = true;
3853
3956
  this.video.volume = 0;
3854
3957
  if (this.config.debugAdTiming) {
@@ -4299,6 +4402,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4299
4402
  if (!this.isLiveStream) {
4300
4403
  return false;
4301
4404
  }
4405
+ if (this.config.singlePipelineMode) {
4406
+ return false;
4407
+ }
4302
4408
  var browser = detectBrowser();
4303
4409
  if (browser.isSmartTV) {
4304
4410
  return false;
@@ -5449,32 +5555,55 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5449
5555
  this.video.volume = restoredVolume;
5450
5556
  }
5451
5557
  var browser = detectBrowser();
5452
- var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0;
5558
+ var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0 || !!this.config.singlePipelineMode;
5453
5559
  if (isSmartTV && this.hls) {
5454
5560
  var hlsRef = this.hls;
5455
5561
  var savedMuted = restoredMuted;
5456
5562
  var savedVolume = restoredVolume;
5563
+ var videoRef = this.video;
5564
+ var debugEnabled = this.config.debugAdTiming;
5565
+ var tryPlay = function tryPlay1(attempt) {
5566
+ var _videoRef_play;
5567
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5568
+ (_videoRef_play = videoRef.play()) === null || _videoRef_play === void 0 ? void 0 : _videoRef_play.catch(function() {
5569
+ if (attempt < 3) {
5570
+ if (debugEnabled) {
5571
+ console.log("[StormcloudVideoPlayer] Smart TV: play() retry ".concat(attempt + 1, "/3 in ").concat(500 * (attempt + 1), "ms"));
5572
+ }
5573
+ setTimeout(function() {
5574
+ return tryPlay(attempt + 1);
5575
+ }, 500 * (attempt + 1));
5576
+ }
5577
+ });
5578
+ };
5457
5579
  var onManifestParsedRestore = function onManifestParsedRestore1() {
5458
5580
  hlsRef.off(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5459
5581
  if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5460
- var _this_video_play;
5461
- if (_this.video.muted !== savedMuted) _this.video.muted = savedMuted;
5462
- if (Math.abs(_this.video.volume - savedVolume) > 0.01) _this.video.volume = savedVolume;
5463
- if (_this.config.debugAdTiming) {
5582
+ if (videoRef.muted !== savedMuted) videoRef.muted = savedMuted;
5583
+ if (Math.abs(videoRef.volume - savedVolume) > 0.01) videoRef.volume = savedVolume;
5584
+ if (debugEnabled) {
5464
5585
  console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5465
5586
  }
5466
5587
  hlsRef.startLoad(-1);
5467
- (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5468
- if (_this.config.debugAdTiming) {
5588
+ if (debugEnabled) {
5469
5589
  console.log("[StormcloudVideoPlayer] Smart TV: seeking to live edge and resuming playback after re-attach");
5470
5590
  }
5471
5591
  }
5472
5592
  };
5473
5593
  hlsRef.on(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5474
- this.hls.attachMedia(this.video);
5475
- if (this.config.debugAdTiming) {
5476
- console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5594
+ var pipelineDelayMs = 300;
5595
+ if (debugEnabled) {
5596
+ console.log("[StormcloudVideoPlayer] Smart TV: waiting ".concat(pipelineDelayMs, "ms for hardware pipeline release before re-attach"));
5477
5597
  }
5598
+ setTimeout(function() {
5599
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5600
+ if (_this.hls) {
5601
+ _this.hls.attachMedia(_this.video);
5602
+ if (debugEnabled) {
5603
+ console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5604
+ }
5605
+ }
5606
+ }, pipelineDelayMs);
5478
5607
  } else {
5479
5608
  if (this.shouldContinueLiveStreamDuringAds()) {
5480
5609
  var _this_video_play;
@@ -5548,6 +5677,15 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5548
5677
  }
5549
5678
  this.showPlaceholderLayer();
5550
5679
  this.adLayer.showPlaceholder();
5680
+ } else if (this.inAdBreak) {
5681
+ if (this.config.debugAdTiming) {
5682
+ console.log("[CONTINUOUS-FETCH] Ad failure with no filler \u2014 restoring main video temporarily");
5683
+ }
5684
+ this.adLayer.hidePlaceholder();
5685
+ if (!this.adLayer.isAdPlaying() && this.video.paused && this.video.readyState >= 2) {
5686
+ var _this_video_play;
5687
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5688
+ }
5551
5689
  }
5552
5690
  }
5553
5691
  },