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.
@@ -922,7 +922,7 @@ function resolveBidToVastAd(winner, logPrefix) {
922
922
  return Promise.resolve(null);
923
923
  }
924
924
  function createVastAdLayer(contentVideo, options) {
925
- var _ref, _ref1, _ref2, _ref3;
925
+ var _ref, _ref1, _ref2, _ref3, _ref4;
926
926
  var adPlaying = false;
927
927
  var originalMutedState = false;
928
928
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
@@ -931,7 +931,8 @@ function createVastAdLayer(contentVideo, options) {
931
931
  var continueLiveStreamDuringAds = (_ref = options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds) !== null && _ref !== void 0 ? _ref : false;
932
932
  var smartTVMode = (_ref1 = options === null || options === void 0 ? void 0 : options.smartTVMode) !== null && _ref1 !== void 0 ? _ref1 : false;
933
933
  var singleElementMode = (_ref2 = options === null || options === void 0 ? void 0 : options.singleElementMode) !== null && _ref2 !== void 0 ? _ref2 : false;
934
- var debug = (_ref3 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref3 !== void 0 ? _ref3 : false;
934
+ var forceMP4Ads = (_ref3 = options === null || options === void 0 ? void 0 : options.forceMP4Ads) !== null && _ref3 !== void 0 ? _ref3 : smartTVMode || singleElementMode;
935
+ var debug = (_ref4 = options === null || options === void 0 ? void 0 : options.debug) !== null && _ref4 !== void 0 ? _ref4 : false;
935
936
  var adVideoElement;
936
937
  var adHls;
937
938
  var adContainerEl;
@@ -1002,14 +1003,26 @@ function createVastAdLayer(contentVideo, options) {
1002
1003
  var _ref;
1003
1004
  var _scoredFiles_;
1004
1005
  if (mediaFiles.length === 0) throw new Error("No media files available");
1005
- var firstFile = mediaFiles[0];
1006
- if (mediaFiles.length === 1) return firstFile;
1006
+ var candidates = mediaFiles;
1007
+ if (forceMP4Ads) {
1008
+ var mp4Only = candidates.filter(function(f) {
1009
+ return !isHlsMediaFile(f);
1010
+ });
1011
+ if (mp4Only.length > 0) {
1012
+ candidates = mp4Only;
1013
+ if (debug) console.log("".concat(LOG, " forceMP4Ads: filtered to ").concat(mp4Only.length, " MP4-only file(s)"));
1014
+ } else if (debug) {
1015
+ console.warn("".concat(LOG, " forceMP4Ads: no MP4 files available, falling back to all media files"));
1016
+ }
1017
+ }
1018
+ var firstFile = candidates[0];
1019
+ if (candidates.length === 1) return firstFile;
1007
1020
  var mainQuality = getMainStreamQuality();
1008
1021
  if (!mainQuality) {
1009
1022
  if (debug) console.log("".concat(LOG, " No main stream quality info, using first media file"));
1010
1023
  return firstFile;
1011
1024
  }
1012
- var scoredFiles = mediaFiles.map(function(file) {
1025
+ var scoredFiles = candidates.map(function(file) {
1013
1026
  var widthDiff = Math.abs(file.width - mainQuality.width);
1014
1027
  var heightDiff = Math.abs(file.height - mainQuality.height);
1015
1028
  var resolutionDiff = widthDiff + heightDiff;
@@ -1226,6 +1239,16 @@ function createVastAdLayer(contentVideo, options) {
1226
1239
  }
1227
1240
  function startPlayback(mediaFile) {
1228
1241
  if (!adVideoElement) return;
1242
+ if (singleElementMode && isHlsMediaFile(mediaFile)) {
1243
+ var mp4Fallback = currentAd === null || currentAd === void 0 ? void 0 : currentAd.mediaFiles.find(function(f) {
1244
+ return !isHlsMediaFile(f);
1245
+ });
1246
+ if (mp4Fallback) {
1247
+ if (debug) console.log("".concat(LOG, " singleElementMode: HLS ad blocked, using MP4 fallback"));
1248
+ startNativePlayback(mp4Fallback);
1249
+ return;
1250
+ }
1251
+ }
1229
1252
  if (isHlsMediaFile(mediaFile)) {
1230
1253
  startHlsPlayback(mediaFile);
1231
1254
  } else {
@@ -1234,7 +1257,7 @@ function createVastAdLayer(contentVideo, options) {
1234
1257
  }
1235
1258
  function playAd(bids) {
1236
1259
  return _async_to_generator(function() {
1237
- var winner, ad, contentVolume, _contentVideo_parentElement, container, adVolume, mediaFile;
1260
+ var winner, ad, contentVolume, adVolume2, mediaFile2, _contentVideo_parentElement, container, adVolume, mediaFile;
1238
1261
  return _ts_generator(this, function(_state) {
1239
1262
  switch(_state.label){
1240
1263
  case 0:
@@ -1278,37 +1301,71 @@ function createVastAdLayer(contentVideo, options) {
1278
1301
  trackingFired.impression = true;
1279
1302
  contentVolume = contentVideo.volume;
1280
1303
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1281
- if (singleElementMode) {
1282
- mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1283
- teardownCurrentPlayback();
1284
- adVideoElement = contentVideo;
1285
- adHls = void 0;
1304
+ if (!singleElementMode) return [
1305
+ 3,
1306
+ 3
1307
+ ];
1308
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1309
+ teardownCurrentPlayback();
1310
+ adVideoElement = contentVideo;
1311
+ adHls = void 0;
1312
+ adPlaying = true;
1313
+ setAdPlayingFlag(true);
1314
+ contentVideo.removeAttribute("src");
1315
+ contentVideo.load();
1316
+ if (!continueLiveStreamDuringAds) {
1317
+ contentVideo.pause();
1318
+ }
1319
+ contentVideo.muted = true;
1320
+ contentVideo.volume = 0;
1321
+ return [
1322
+ 4,
1323
+ new Promise(function(resolve) {
1324
+ return setTimeout(resolve, 200);
1325
+ })
1326
+ ];
1327
+ case 2:
1328
+ _state.sent();
1329
+ if (destroyed || tornDown) return [
1330
+ 2
1331
+ ];
1332
+ contentVideo.style.visibility = "visible";
1333
+ contentVideo.style.opacity = "1";
1334
+ emit("content_pause");
1335
+ setupAdEventListeners();
1336
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1337
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume2));
1338
+ adVideoElement.muted = false;
1339
+ mediaFile2 = selectBestMediaFile(ad.mediaFiles);
1340
+ if (debug) console.log("".concat(LOG, " Loading ad from: ").concat(mediaFile2.url));
1341
+ startPlayback(mediaFile2);
1342
+ return [
1343
+ 2
1344
+ ];
1345
+ case 3:
1346
+ if (!adContainerEl) {
1347
+ ;
1348
+ container = document.createElement("div");
1349
+ container.style.position = "absolute";
1350
+ container.style.left = "0";
1351
+ container.style.top = "0";
1352
+ container.style.right = "0";
1353
+ container.style.bottom = "0";
1354
+ container.style.display = "none";
1355
+ container.style.alignItems = "center";
1356
+ container.style.justifyContent = "center";
1357
+ container.style.pointerEvents = "none";
1358
+ container.style.zIndex = "10";
1359
+ container.style.backgroundColor = "#000";
1360
+ (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1361
+ adContainerEl = container;
1362
+ }
1363
+ if (!adVideoElement) {
1364
+ adVideoElement = createAdVideoElement();
1365
+ adContainerEl.appendChild(adVideoElement);
1286
1366
  setupAdEventListeners();
1287
1367
  } else {
1288
- if (!adContainerEl) {
1289
- ;
1290
- container = document.createElement("div");
1291
- container.style.position = "absolute";
1292
- container.style.left = "0";
1293
- container.style.top = "0";
1294
- container.style.right = "0";
1295
- container.style.bottom = "0";
1296
- container.style.display = "none";
1297
- container.style.alignItems = "center";
1298
- container.style.justifyContent = "center";
1299
- container.style.pointerEvents = "none";
1300
- container.style.zIndex = "10";
1301
- container.style.backgroundColor = "#000";
1302
- (_contentVideo_parentElement = contentVideo.parentElement) === null || _contentVideo_parentElement === void 0 ? void 0 : _contentVideo_parentElement.appendChild(container);
1303
- adContainerEl = container;
1304
- }
1305
- if (!adVideoElement) {
1306
- adVideoElement = createAdVideoElement();
1307
- adContainerEl.appendChild(adVideoElement);
1308
- setupAdEventListeners();
1309
- } else {
1310
- teardownCurrentPlayback();
1311
- }
1368
+ teardownCurrentPlayback();
1312
1369
  }
1313
1370
  if (!continueLiveStreamDuringAds) {
1314
1371
  contentVideo.pause();
@@ -1320,7 +1377,7 @@ function createVastAdLayer(contentVideo, options) {
1320
1377
  adVolume = originalMutedState ? 1 : originalVolume;
1321
1378
  adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1322
1379
  adVideoElement.muted = false;
1323
- if (!singleElementMode && adContainerEl) {
1380
+ if (adContainerEl) {
1324
1381
  adContainerEl.style.display = "flex";
1325
1382
  adContainerEl.style.pointerEvents = "auto";
1326
1383
  }
@@ -1424,6 +1481,7 @@ function createVastAdLayer(contentVideo, options) {
1424
1481
  if (debug) console.log("".concat(LOG, " [preload] HLS manifest parsed, token=").concat(token));
1425
1482
  });
1426
1483
  hls.on(import_hls.default.Events.ERROR, function(_evt, data) {
1484
+ if (!preloadSlots.has(token)) return;
1427
1485
  if (data.fatal) {
1428
1486
  if (debug) console.warn("".concat(LOG, " [preload] HLS error for token=").concat(token));
1429
1487
  preloadSlots.delete(token);
@@ -1460,113 +1518,146 @@ function createVastAdLayer(contentVideo, options) {
1460
1518
  }
1461
1519
  function playPreloaded(token) {
1462
1520
  return _async_to_generator(function() {
1463
- var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, adVolume, container;
1521
+ var slot, contentVolume, adVolume2, videoEl, container2, adVolume21, nonFatalNetworkErrors, adVolume, container;
1464
1522
  return _ts_generator(this, function(_state) {
1465
- if (destroyed) return [
1466
- 2,
1467
- Promise.reject(new Error("Layer has been destroyed"))
1468
- ];
1469
- slot = preloadSlots.get(token);
1470
- if (!slot) {
1471
- if (debug) console.warn("".concat(LOG, " [preload] No slot found for token=").concat(token, ", nothing to play"));
1472
- return [
1473
- 2
1474
- ];
1475
- }
1476
- preloadSlots.delete(token);
1477
- if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1478
- contentVolume = contentVideo.volume;
1479
- originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1480
- sessionId = generateSessionId();
1481
- currentAd = slot.ad;
1482
- trackingFired = _object_spread({}, createEmptyTrackingState());
1483
- fireTrackingPixels2(slot.ad.trackingUrls.impression);
1484
- trackingFired.impression = true;
1485
- if (singleElementMode) {
1486
- mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1487
- teardownCurrentPlayback();
1488
- adVideoElement = contentVideo;
1489
- adHls = void 0;
1490
- setupAdEventListeners();
1491
- if (!continueLiveStreamDuringAds) {
1492
- contentVideo.pause();
1493
- }
1494
- contentVideo.muted = true;
1495
- contentVideo.volume = 0;
1496
- adPlaying = true;
1497
- setAdPlayingFlag(true);
1498
- adVolume2 = originalMutedState ? 1 : originalVolume;
1499
- contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1500
- contentVideo.muted = false;
1501
- emit("content_pause");
1502
- if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1503
- startPlayback(slot.mediaFile);
1504
- return [
1505
- 2
1506
- ];
1507
- }
1508
- if (smartTVMode && !slot.videoEl) {
1509
- teardownCurrentPlayback();
1510
- if (adVideoElement) {
1511
- adVideoElement.remove();
1512
- adVideoElement = void 0;
1513
- }
1514
- videoEl = createAdVideoElement();
1515
- videoEl.style.visibility = "visible";
1516
- videoEl.style.pointerEvents = "none";
1517
- container2 = ensureAdContainer();
1518
- container2.appendChild(videoEl);
1519
- adVideoElement = videoEl;
1520
- setupAdEventListeners();
1521
- if (!continueLiveStreamDuringAds) {
1522
- contentVideo.pause();
1523
- }
1524
- contentVideo.muted = true;
1525
- contentVideo.volume = 0;
1526
- adPlaying = true;
1527
- setAdPlayingFlag(true);
1528
- adVolume21 = originalMutedState ? 1 : originalVolume;
1529
- adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1530
- adVideoElement.muted = false;
1531
- container2.style.display = "flex";
1532
- container2.style.pointerEvents = "auto";
1533
- emit("content_pause");
1534
- if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1535
- startPlayback(slot.mediaFile);
1536
- return [
1537
- 2
1538
- ];
1539
- }
1540
- teardownCurrentPlayback();
1541
- if (adVideoElement && adVideoElement !== slot.videoEl) {
1542
- adVideoElement.remove();
1543
- }
1544
- slot.videoEl.style.visibility = "visible";
1545
- slot.videoEl.style.pointerEvents = "none";
1546
- adVideoElement = slot.videoEl;
1547
- adHls = slot.hlsInstance;
1548
- setupAdEventListeners();
1549
- if (!continueLiveStreamDuringAds) {
1550
- contentVideo.pause();
1523
+ switch(_state.label){
1524
+ case 0:
1525
+ if (destroyed) return [
1526
+ 2,
1527
+ Promise.reject(new Error("Layer has been destroyed"))
1528
+ ];
1529
+ slot = preloadSlots.get(token);
1530
+ if (!slot) {
1531
+ if (debug) console.warn("".concat(LOG, " [preload] No slot found for token=").concat(token, ", nothing to play"));
1532
+ return [
1533
+ 2
1534
+ ];
1535
+ }
1536
+ preloadSlots.delete(token);
1537
+ if (debug) console.log("".concat(LOG, " [preload] Playing preloaded ad, token=").concat(token, ", ready=").concat(slot.ready));
1538
+ contentVolume = contentVideo.volume;
1539
+ originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1540
+ sessionId = generateSessionId();
1541
+ currentAd = slot.ad;
1542
+ trackingFired = _object_spread({}, createEmptyTrackingState());
1543
+ fireTrackingPixels2(slot.ad.trackingUrls.impression);
1544
+ trackingFired.impression = true;
1545
+ if (!singleElementMode) return [
1546
+ 3,
1547
+ 2
1548
+ ];
1549
+ mainHlsInstance === null || mainHlsInstance === void 0 ? void 0 : mainHlsInstance.detachMedia();
1550
+ teardownCurrentPlayback();
1551
+ adVideoElement = contentVideo;
1552
+ adHls = void 0;
1553
+ adPlaying = true;
1554
+ setAdPlayingFlag(true);
1555
+ contentVideo.removeAttribute("src");
1556
+ contentVideo.load();
1557
+ contentVideo.muted = true;
1558
+ contentVideo.volume = 0;
1559
+ return [
1560
+ 4,
1561
+ new Promise(function(resolve) {
1562
+ return setTimeout(resolve, 200);
1563
+ })
1564
+ ];
1565
+ case 1:
1566
+ _state.sent();
1567
+ if (destroyed || tornDown) return [
1568
+ 2
1569
+ ];
1570
+ contentVideo.style.visibility = "visible";
1571
+ contentVideo.style.opacity = "1";
1572
+ emit("content_pause");
1573
+ setupAdEventListeners();
1574
+ adVolume2 = originalMutedState ? 1 : originalVolume;
1575
+ contentVideo.volume = Math.max(0, Math.min(1, adVolume2));
1576
+ contentVideo.muted = false;
1577
+ if (debug) console.log("".concat(LOG, " [preload] singleElementMode: attaching ad to contentVideo, url=").concat(slot.mediaFile.url));
1578
+ startPlayback(slot.mediaFile);
1579
+ return [
1580
+ 2
1581
+ ];
1582
+ case 2:
1583
+ if (smartTVMode && !slot.videoEl) {
1584
+ teardownCurrentPlayback();
1585
+ if (adVideoElement) {
1586
+ adVideoElement.remove();
1587
+ adVideoElement = void 0;
1588
+ }
1589
+ videoEl = createAdVideoElement();
1590
+ videoEl.style.visibility = "visible";
1591
+ videoEl.style.pointerEvents = "none";
1592
+ container2 = ensureAdContainer();
1593
+ container2.appendChild(videoEl);
1594
+ adVideoElement = videoEl;
1595
+ setupAdEventListeners();
1596
+ if (!continueLiveStreamDuringAds) {
1597
+ contentVideo.pause();
1598
+ }
1599
+ contentVideo.muted = true;
1600
+ contentVideo.volume = 0;
1601
+ adPlaying = true;
1602
+ setAdPlayingFlag(true);
1603
+ adVolume21 = originalMutedState ? 1 : originalVolume;
1604
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume21));
1605
+ adVideoElement.muted = false;
1606
+ container2.style.display = "flex";
1607
+ container2.style.pointerEvents = "auto";
1608
+ emit("content_pause");
1609
+ if (debug) console.log("".concat(LOG, " [preload] smartTVMode deferred: creating video element and loading, url=").concat(slot.mediaFile.url));
1610
+ startPlayback(slot.mediaFile);
1611
+ return [
1612
+ 2
1613
+ ];
1614
+ }
1615
+ teardownCurrentPlayback();
1616
+ if (adVideoElement && adVideoElement !== slot.videoEl) {
1617
+ adVideoElement.remove();
1618
+ }
1619
+ slot.videoEl.style.visibility = "visible";
1620
+ slot.videoEl.style.pointerEvents = "none";
1621
+ adVideoElement = slot.videoEl;
1622
+ adHls = slot.hlsInstance;
1623
+ if (adHls) {
1624
+ nonFatalNetworkErrors = 0;
1625
+ adHls.on(import_hls.default.Events.ERROR, function(_event, data) {
1626
+ if (!adPlaying) return;
1627
+ if (data.fatal) {
1628
+ handleAdError();
1629
+ } else if (data.type === import_hls.default.ErrorTypes.NETWORK_ERROR) {
1630
+ nonFatalNetworkErrors++;
1631
+ if (nonFatalNetworkErrors >= 3) {
1632
+ if (debug) console.warn("".concat(LOG, " [preload] Too many non-fatal HLS network errors during playback, treating as fatal"));
1633
+ handleAdError();
1634
+ }
1635
+ }
1636
+ });
1637
+ }
1638
+ setupAdEventListeners();
1639
+ if (!continueLiveStreamDuringAds) {
1640
+ contentVideo.pause();
1641
+ }
1642
+ contentVideo.muted = true;
1643
+ contentVideo.volume = 0;
1644
+ adPlaying = true;
1645
+ setAdPlayingFlag(true);
1646
+ adVolume = originalMutedState ? 1 : originalVolume;
1647
+ adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1648
+ adVideoElement.muted = false;
1649
+ container = ensureAdContainer();
1650
+ container.style.display = "flex";
1651
+ container.style.pointerEvents = "auto";
1652
+ emit("content_pause");
1653
+ adVideoElement.play().catch(function(error) {
1654
+ console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1655
+ handleAdError();
1656
+ });
1657
+ return [
1658
+ 2
1659
+ ];
1551
1660
  }
1552
- contentVideo.muted = true;
1553
- contentVideo.volume = 0;
1554
- adPlaying = true;
1555
- setAdPlayingFlag(true);
1556
- adVolume = originalMutedState ? 1 : originalVolume;
1557
- adVideoElement.volume = Math.max(0, Math.min(1, adVolume));
1558
- adVideoElement.muted = false;
1559
- container = ensureAdContainer();
1560
- container.style.display = "flex";
1561
- container.style.pointerEvents = "auto";
1562
- emit("content_pause");
1563
- adVideoElement.play().catch(function(error) {
1564
- console.error("".concat(LOG, " [preload] Error playing preloaded ad:"), error);
1565
- handleAdError();
1566
- });
1567
- return [
1568
- 2
1569
- ];
1570
1661
  });
1571
1662
  })();
1572
1663
  }
@@ -2896,10 +2987,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2896
2987
  debug: !!config.debugAdTiming
2897
2988
  } : {});
2898
2989
  var browserForAdLayer = detectBrowser();
2990
+ var isSinglePipeline = browserForAdLayer.isSmartTV || !!this.config.singlePipelineMode;
2899
2991
  this.adLayer = createVastAdLayer(this.video, {
2900
2992
  continueLiveStreamDuringAds: false,
2901
- smartTVMode: browserForAdLayer.isSmartTV,
2902
- singleElementMode: browserForAdLayer.isSmartTV,
2993
+ smartTVMode: isSinglePipeline,
2994
+ singleElementMode: isSinglePipeline,
2995
+ forceMP4Ads: isSinglePipeline,
2903
2996
  debug: !!config.debugAdTiming
2904
2997
  });
2905
2998
  }
@@ -3545,6 +3638,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3545
3638
  if (_this.fillerVideo) {
3546
3639
  _this.fillerVideo.style.display = "none";
3547
3640
  }
3641
+ if (!_this.adLayer.isAdPlaying()) {
3642
+ if (_this.config.debugAdTiming) {
3643
+ console.log("[StormcloudVideoPlayer] Filler video failed \u2014 restoring main video");
3644
+ }
3645
+ _this.adLayer.hidePlaceholder();
3646
+ if (_this.video.paused && _this.video.readyState >= 2) {
3647
+ var _this_video_play;
3648
+ (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
3649
+ }
3650
+ }
3548
3651
  });
3549
3652
  if (this.config.debugAdTiming) {
3550
3653
  console.log("[StormcloudVideoPlayer] Showing filler video layer");
@@ -3810,7 +3913,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3810
3913
  };
3811
3914
  this.adLayer.updateOriginalMutedState(this.video.muted, this.video.volume);
3812
3915
  }
3813
- if (!this.config.disableFiller && !this.video.muted) {
3916
+ if (!this.config.disableFiller && !this.video.muted && !this.adLayer.isAdPlaying()) {
3814
3917
  this.video.muted = true;
3815
3918
  this.video.volume = 0;
3816
3919
  if (this.config.debugAdTiming) {
@@ -4261,6 +4364,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4261
4364
  if (!this.isLiveStream) {
4262
4365
  return false;
4263
4366
  }
4367
+ if (this.config.singlePipelineMode) {
4368
+ return false;
4369
+ }
4264
4370
  var browser = detectBrowser();
4265
4371
  if (browser.isSmartTV) {
4266
4372
  return false;
@@ -5411,32 +5517,55 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5411
5517
  this.video.volume = restoredVolume;
5412
5518
  }
5413
5519
  var browser = detectBrowser();
5414
- var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0;
5520
+ var isSmartTV = browser.tizenVersion !== void 0 || browser.webOSVersion !== void 0 || !!this.config.singlePipelineMode;
5415
5521
  if (isSmartTV && this.hls) {
5416
5522
  var hlsRef = this.hls;
5417
5523
  var savedMuted = restoredMuted;
5418
5524
  var savedVolume = restoredVolume;
5525
+ var videoRef = this.video;
5526
+ var debugEnabled = this.config.debugAdTiming;
5527
+ var tryPlay = function tryPlay1(attempt) {
5528
+ var _videoRef_play;
5529
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5530
+ (_videoRef_play = videoRef.play()) === null || _videoRef_play === void 0 ? void 0 : _videoRef_play.catch(function() {
5531
+ if (attempt < 3) {
5532
+ if (debugEnabled) {
5533
+ console.log("[StormcloudVideoPlayer] Smart TV: play() retry ".concat(attempt + 1, "/3 in ").concat(500 * (attempt + 1), "ms"));
5534
+ }
5535
+ setTimeout(function() {
5536
+ return tryPlay(attempt + 1);
5537
+ }, 500 * (attempt + 1));
5538
+ }
5539
+ });
5540
+ };
5419
5541
  var onManifestParsedRestore = function onManifestParsedRestore1() {
5420
5542
  hlsRef.off(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5421
5543
  if (!_this.inAdBreak && !_this.adLayer.isAdPlaying()) {
5422
- var _this_video_play;
5423
- if (_this.video.muted !== savedMuted) _this.video.muted = savedMuted;
5424
- if (Math.abs(_this.video.volume - savedVolume) > 0.01) _this.video.volume = savedVolume;
5425
- if (_this.config.debugAdTiming) {
5544
+ if (videoRef.muted !== savedMuted) videoRef.muted = savedMuted;
5545
+ if (Math.abs(videoRef.volume - savedVolume) > 0.01) videoRef.volume = savedVolume;
5546
+ if (debugEnabled) {
5426
5547
  console.log("[StormcloudVideoPlayer] Smart TV: audio state restored on MANIFEST_PARSED after re-attach");
5427
5548
  }
5428
5549
  hlsRef.startLoad(-1);
5429
- (_this_video_play = _this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5430
- if (_this.config.debugAdTiming) {
5550
+ if (debugEnabled) {
5431
5551
  console.log("[StormcloudVideoPlayer] Smart TV: seeking to live edge and resuming playback after re-attach");
5432
5552
  }
5433
5553
  }
5434
5554
  };
5435
5555
  hlsRef.on(import_hls2.default.Events.MANIFEST_PARSED, onManifestParsedRestore);
5436
- this.hls.attachMedia(this.video);
5437
- if (this.config.debugAdTiming) {
5438
- console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5556
+ var pipelineDelayMs = 300;
5557
+ if (debugEnabled) {
5558
+ console.log("[StormcloudVideoPlayer] Smart TV: waiting ".concat(pipelineDelayMs, "ms for hardware pipeline release before re-attach"));
5439
5559
  }
5560
+ setTimeout(function() {
5561
+ if (_this.inAdBreak || _this.adLayer.isAdPlaying()) return;
5562
+ if (_this.hls) {
5563
+ _this.hls.attachMedia(_this.video);
5564
+ if (debugEnabled) {
5565
+ console.log("[StormcloudVideoPlayer] Smart TV: re-attached HLS to video element after ad break to restore media pipeline");
5566
+ }
5567
+ }
5568
+ }, pipelineDelayMs);
5440
5569
  } else {
5441
5570
  if (this.shouldContinueLiveStreamDuringAds()) {
5442
5571
  var _this_video_play;
@@ -5510,6 +5639,15 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5510
5639
  }
5511
5640
  this.showPlaceholderLayer();
5512
5641
  this.adLayer.showPlaceholder();
5642
+ } else if (this.inAdBreak) {
5643
+ if (this.config.debugAdTiming) {
5644
+ console.log("[CONTINUOUS-FETCH] Ad failure with no filler \u2014 restoring main video temporarily");
5645
+ }
5646
+ this.adLayer.hidePlaceholder();
5647
+ if (!this.adLayer.isAdPlaying() && this.video.paused && this.video.readyState >= 2) {
5648
+ var _this_video_play;
5649
+ (_this_video_play = this.video.play()) === null || _this_video_play === void 0 ? void 0 : _this_video_play.catch(function() {});
5650
+ }
5513
5651
  }
5514
5652
  }
5515
5653
  },