@scarlett-player/hls 1.1.1 → 1.4.0
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/README.md +59 -11
- package/dist/chunk-IFUZZQQE.js +1141 -0
- package/dist/index.cjs +202 -53
- package/dist/index.d.cts +6 -8
- package/dist/index.d.ts +6 -8
- package/dist/index.js +34 -691
- package/dist/light.cjs +431 -43
- package/dist/light.d.cts +11 -4
- package/dist/light.d.ts +11 -4
- package/dist/light.js +34 -461
- package/dist/{types-DOzoPHQe.d.cts → types-D-HW4lmM.d.cts} +8 -0
- package/dist/{types-DOzoPHQe.d.ts → types-D-HW4lmM.d.ts} +8 -0
- package/package.json +2 -2
- package/dist/chunk-L73PRXS4.js +0 -337
package/dist/index.cjs
CHANGED
|
@@ -34,9 +34,19 @@ __export(index_exports, {
|
|
|
34
34
|
default: () => index_default
|
|
35
35
|
});
|
|
36
36
|
module.exports = __toCommonJS(index_exports);
|
|
37
|
-
var import_core = require("@scarlett-player/core");
|
|
38
37
|
|
|
39
38
|
// src/hls-loader.ts
|
|
39
|
+
var hls_loader_exports = {};
|
|
40
|
+
__export(hls_loader_exports, {
|
|
41
|
+
createHlsInstance: () => createHlsInstance,
|
|
42
|
+
getHlsConstructor: () => getHlsConstructor,
|
|
43
|
+
isHLSSupported: () => isHLSSupported,
|
|
44
|
+
isHlsJsSupported: () => isHlsJsSupported,
|
|
45
|
+
loadHlsJs: () => loadHlsJs,
|
|
46
|
+
resetLoader: () => resetLoader,
|
|
47
|
+
shouldPreferNativeHLS: () => shouldPreferNativeHLS,
|
|
48
|
+
supportsNativeHLS: () => supportsNativeHLS
|
|
49
|
+
});
|
|
40
50
|
var hlsConstructor = null;
|
|
41
51
|
var loadingPromise = null;
|
|
42
52
|
function supportsNativeHLS() {
|
|
@@ -44,6 +54,13 @@ function supportsNativeHLS() {
|
|
|
44
54
|
const video = document.createElement("video");
|
|
45
55
|
return video.canPlayType("application/vnd.apple.mpegurl") !== "";
|
|
46
56
|
}
|
|
57
|
+
function shouldPreferNativeHLS() {
|
|
58
|
+
if (!supportsNativeHLS()) return false;
|
|
59
|
+
if (typeof navigator === "undefined") return false;
|
|
60
|
+
const ua = navigator.userAgent;
|
|
61
|
+
const isSafari = /Safari/.test(ua) && !/Chrome/.test(ua) && !/CriOS/.test(ua);
|
|
62
|
+
return isSafari;
|
|
63
|
+
}
|
|
47
64
|
function isHlsJsSupported() {
|
|
48
65
|
if (hlsConstructor) {
|
|
49
66
|
return hlsConstructor.isSupported();
|
|
@@ -87,6 +104,13 @@ function createHlsInstance(config) {
|
|
|
87
104
|
function getHlsConstructor() {
|
|
88
105
|
return hlsConstructor;
|
|
89
106
|
}
|
|
107
|
+
function resetLoader() {
|
|
108
|
+
hlsConstructor = null;
|
|
109
|
+
loadingPromise = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/create-hls-plugin.ts
|
|
113
|
+
var import_core = require("@scarlett-player/core");
|
|
90
114
|
|
|
91
115
|
// src/quality.ts
|
|
92
116
|
function formatLevel(level) {
|
|
@@ -418,7 +442,51 @@ function setupVideoEventHandlers(video, api) {
|
|
|
418
442
|
};
|
|
419
443
|
}
|
|
420
444
|
|
|
421
|
-
// src/
|
|
445
|
+
// src/playlist-validation.ts
|
|
446
|
+
var PLAYLIST_INVALID_TEXT = "Invalid playlist document";
|
|
447
|
+
var MEDIA_PLAYLIST_CONTEXTS = ["level", "audioTrack", "subtitleTrack"];
|
|
448
|
+
function isValidPlaylistDocument(data, contextType) {
|
|
449
|
+
if (typeof data !== "string" || data.length === 0) return false;
|
|
450
|
+
const text = data.trimStart();
|
|
451
|
+
if (!text.startsWith("#EXTM3U")) return false;
|
|
452
|
+
if (contextType && MEDIA_PLAYLIST_CONTEXTS.includes(contextType)) {
|
|
453
|
+
return /^#EXT(?:INF|-X-TARGETDURATION):/m.test(text);
|
|
454
|
+
}
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
function createValidatingPlaylistLoader(Hls) {
|
|
458
|
+
const BaseLoader = Hls.DefaultConfig.loader;
|
|
459
|
+
return class ValidatingPlaylistLoader extends BaseLoader {
|
|
460
|
+
/**
|
|
461
|
+
* Load a playlist, validating the response document before it reaches
|
|
462
|
+
* the M3U8 parser.
|
|
463
|
+
*
|
|
464
|
+
* @param context - hls.js loader context
|
|
465
|
+
* @param config - hls.js loader config
|
|
466
|
+
* @param callbacks - hls.js loader callbacks
|
|
467
|
+
*/
|
|
468
|
+
load(context, config, callbacks) {
|
|
469
|
+
const wrapped = {
|
|
470
|
+
...callbacks,
|
|
471
|
+
onSuccess: (response, stats, ctx, networkDetails) => {
|
|
472
|
+
if (!isValidPlaylistDocument(response?.data, ctx?.type)) {
|
|
473
|
+
callbacks.onError(
|
|
474
|
+
{ code: 0, text: PLAYLIST_INVALID_TEXT },
|
|
475
|
+
ctx,
|
|
476
|
+
networkDetails,
|
|
477
|
+
stats
|
|
478
|
+
);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
callbacks.onSuccess(response, stats, ctx, networkDetails);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
super.load(context, config, wrapped);
|
|
485
|
+
}
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/create-hls-plugin.ts
|
|
422
490
|
var DEFAULT_CONFIG = {
|
|
423
491
|
debug: false,
|
|
424
492
|
autoStartLoad: true,
|
|
@@ -440,14 +508,16 @@ var DEFAULT_CONFIG = {
|
|
|
440
508
|
autoReconnect: true,
|
|
441
509
|
reconnectBaseDelayMs: 2e3,
|
|
442
510
|
reconnectMaxDelayMs: 3e4,
|
|
443
|
-
reconnectWindowMs: 3e5
|
|
511
|
+
reconnectWindowMs: 3e5,
|
|
512
|
+
// Never index a malformed live playlist refresh blindly
|
|
513
|
+
validatePlaylists: true
|
|
444
514
|
};
|
|
445
515
|
var MANIFEST_PHASE_ERRORS = [
|
|
446
516
|
"manifestLoadError",
|
|
447
517
|
"manifestLoadTimeOut",
|
|
448
518
|
"manifestParsingError"
|
|
449
519
|
];
|
|
450
|
-
function
|
|
520
|
+
function createHLSPluginWith(loader, variant, config) {
|
|
451
521
|
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
452
522
|
let api = null;
|
|
453
523
|
let hls = null;
|
|
@@ -457,6 +527,8 @@ function createHLSPlugin(config) {
|
|
|
457
527
|
let cleanupHlsEvents = null;
|
|
458
528
|
let cleanupVideoEvents = null;
|
|
459
529
|
let isAutoQuality = true;
|
|
530
|
+
let loadSession = 0;
|
|
531
|
+
let abortPendingLoad = null;
|
|
460
532
|
let networkRetryCount = 0;
|
|
461
533
|
let mediaRetryCount = 0;
|
|
462
534
|
let retryTimeout = null;
|
|
@@ -489,7 +561,9 @@ function createHLSPlugin(config) {
|
|
|
489
561
|
api?.container.appendChild(video);
|
|
490
562
|
return video;
|
|
491
563
|
};
|
|
492
|
-
const
|
|
564
|
+
const teardownPipeline = (reason) => {
|
|
565
|
+
abortPendingLoad?.(reason ?? new Error("HLS load cancelled"));
|
|
566
|
+
abortPendingLoad = null;
|
|
493
567
|
cleanupHlsEvents?.();
|
|
494
568
|
cleanupHlsEvents = null;
|
|
495
569
|
cleanupVideoEvents?.();
|
|
@@ -502,6 +576,9 @@ function createHLSPlugin(config) {
|
|
|
502
576
|
hls.destroy();
|
|
503
577
|
hls = null;
|
|
504
578
|
}
|
|
579
|
+
};
|
|
580
|
+
const cleanup = (reason) => {
|
|
581
|
+
teardownPipeline(reason);
|
|
505
582
|
currentSrc = null;
|
|
506
583
|
isNative = false;
|
|
507
584
|
isAutoQuality = true;
|
|
@@ -510,7 +587,17 @@ function createHLSPlugin(config) {
|
|
|
510
587
|
errorCount = 0;
|
|
511
588
|
errorWindowStart = 0;
|
|
512
589
|
};
|
|
513
|
-
const buildHlsConfig = () =>
|
|
590
|
+
const buildHlsConfig = () => {
|
|
591
|
+
const config2 = buildBaseHlsConfig();
|
|
592
|
+
if (mergedConfig.validatePlaylists !== false) {
|
|
593
|
+
const Hls = loader.getHlsConstructor();
|
|
594
|
+
if (Hls && Hls.DefaultConfig?.loader) {
|
|
595
|
+
config2.pLoader = createValidatingPlaylistLoader(Hls);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return config2;
|
|
599
|
+
};
|
|
600
|
+
const buildBaseHlsConfig = () => ({
|
|
514
601
|
debug: mergedConfig.debug,
|
|
515
602
|
autoStartLoad: mergedConfig.autoStartLoad,
|
|
516
603
|
startPosition: mergedConfig.startPosition,
|
|
@@ -538,7 +625,21 @@ function createHLSPlugin(config) {
|
|
|
538
625
|
const jitter = delay * (0.7 + Math.random() * 0.3);
|
|
539
626
|
return jitter;
|
|
540
627
|
};
|
|
628
|
+
const APPEND_ERROR_DETAILS = [
|
|
629
|
+
"bufferAppendError",
|
|
630
|
+
"bufferAppendingError",
|
|
631
|
+
"bufferAddCodecError"
|
|
632
|
+
];
|
|
541
633
|
const mapFatalErrorCode = (error) => {
|
|
634
|
+
if (error.response?.text === PLAYLIST_INVALID_TEXT) {
|
|
635
|
+
return import_core.ErrorCode.PLAYLIST_INVALID;
|
|
636
|
+
}
|
|
637
|
+
if (error.details === "bufferFullError") {
|
|
638
|
+
return import_core.ErrorCode.MEDIA_BUFFER_FULL;
|
|
639
|
+
}
|
|
640
|
+
if (APPEND_ERROR_DETAILS.includes(error.details)) {
|
|
641
|
+
return import_core.ErrorCode.MEDIA_APPEND_ERROR;
|
|
642
|
+
}
|
|
542
643
|
switch (error.type) {
|
|
543
644
|
case "network":
|
|
544
645
|
return import_core.ErrorCode.MEDIA_NETWORK_ERROR;
|
|
@@ -563,7 +664,7 @@ function createHLSPlugin(config) {
|
|
|
563
664
|
maybeScheduleReconnect(error);
|
|
564
665
|
};
|
|
565
666
|
const handleHlsError = (error) => {
|
|
566
|
-
const Hls = getHlsConstructor();
|
|
667
|
+
const Hls = loader.getHlsConstructor();
|
|
567
668
|
if (!Hls || !hls) return false;
|
|
568
669
|
const now = Date.now();
|
|
569
670
|
if (now - errorWindowStart > ERROR_WINDOW_MS) {
|
|
@@ -575,10 +676,7 @@ function createHLSPlugin(config) {
|
|
|
575
676
|
if (errorCount >= MAX_ERRORS_IN_WINDOW) {
|
|
576
677
|
api?.logger.error(`Too many errors (${errorCount} in ${ERROR_WINDOW_MS}ms), giving up`);
|
|
577
678
|
emitFatalError(error, true);
|
|
578
|
-
|
|
579
|
-
cleanupHlsEvents = null;
|
|
580
|
-
hls.destroy();
|
|
581
|
-
hls = null;
|
|
679
|
+
teardownPipeline(new Error(error.details));
|
|
582
680
|
return true;
|
|
583
681
|
}
|
|
584
682
|
if (error.fatal) {
|
|
@@ -599,8 +697,9 @@ function createHLSPlugin(config) {
|
|
|
599
697
|
clearTimeout(retryTimeout);
|
|
600
698
|
}
|
|
601
699
|
const isManifestPhase = MANIFEST_PHASE_ERRORS.includes(error.details);
|
|
700
|
+
const retry_session = loadSession;
|
|
602
701
|
retryTimeout = setTimeout(() => {
|
|
603
|
-
if (!hls) return;
|
|
702
|
+
if (retry_session !== loadSession || !hls) return;
|
|
604
703
|
if (isManifestPhase && currentSrc) {
|
|
605
704
|
hls.loadSource(currentSrc);
|
|
606
705
|
} else {
|
|
@@ -623,10 +722,10 @@ function createHLSPlugin(config) {
|
|
|
623
722
|
if (retryTimeout) {
|
|
624
723
|
clearTimeout(retryTimeout);
|
|
625
724
|
}
|
|
725
|
+
const retry_session = loadSession;
|
|
626
726
|
retryTimeout = setTimeout(() => {
|
|
627
|
-
if (hls)
|
|
628
|
-
|
|
629
|
-
}
|
|
727
|
+
if (retry_session !== loadSession || !hls) return;
|
|
728
|
+
hls.recoverMediaError();
|
|
630
729
|
}, delay);
|
|
631
730
|
break;
|
|
632
731
|
}
|
|
@@ -638,6 +737,7 @@ function createHLSPlugin(config) {
|
|
|
638
737
|
return false;
|
|
639
738
|
};
|
|
640
739
|
const loadNative = async (src) => {
|
|
740
|
+
const session = loadSession;
|
|
641
741
|
const videoEl = getOrCreateVideo();
|
|
642
742
|
isNative = true;
|
|
643
743
|
if (api) {
|
|
@@ -645,7 +745,12 @@ function createHLSPlugin(config) {
|
|
|
645
745
|
}
|
|
646
746
|
return new Promise((resolve, reject) => {
|
|
647
747
|
let watchdog = null;
|
|
748
|
+
let settled = false;
|
|
648
749
|
const settle = () => {
|
|
750
|
+
settled = true;
|
|
751
|
+
if (abortPendingLoad === abort) {
|
|
752
|
+
abortPendingLoad = null;
|
|
753
|
+
}
|
|
649
754
|
videoEl.removeEventListener("loadedmetadata", onLoaded);
|
|
650
755
|
videoEl.removeEventListener("error", onError);
|
|
651
756
|
if (watchdog !== null) {
|
|
@@ -653,7 +758,19 @@ function createHLSPlugin(config) {
|
|
|
653
758
|
watchdog = null;
|
|
654
759
|
}
|
|
655
760
|
};
|
|
761
|
+
const abort = (reason) => {
|
|
762
|
+
if (settled) return;
|
|
763
|
+
settle();
|
|
764
|
+
reject(reason);
|
|
765
|
+
};
|
|
766
|
+
abortPendingLoad = abort;
|
|
656
767
|
const onLoaded = () => {
|
|
768
|
+
if (settled) return;
|
|
769
|
+
if (session !== loadSession) {
|
|
770
|
+
settle();
|
|
771
|
+
reject(new Error("HLS load cancelled"));
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
657
774
|
settle();
|
|
658
775
|
hasPlayedContent = true;
|
|
659
776
|
const onFatalVideoError = () => {
|
|
@@ -677,6 +794,7 @@ function createHLSPlugin(config) {
|
|
|
677
794
|
resolve();
|
|
678
795
|
};
|
|
679
796
|
const onError = () => {
|
|
797
|
+
if (settled) return;
|
|
680
798
|
settle();
|
|
681
799
|
const error = videoEl.error;
|
|
682
800
|
reject(new Error(error?.message || "Failed to load HLS source"));
|
|
@@ -684,6 +802,7 @@ function createHLSPlugin(config) {
|
|
|
684
802
|
const timeout_ms = mergedConfig.loadTimeoutMs ?? 3e4;
|
|
685
803
|
if (timeout_ms > 0) {
|
|
686
804
|
watchdog = setTimeout(() => {
|
|
805
|
+
if (settled || session !== loadSession) return;
|
|
687
806
|
settle();
|
|
688
807
|
reject(new Error("Video took too long to load (network timeout)"));
|
|
689
808
|
}, timeout_ms);
|
|
@@ -695,10 +814,14 @@ function createHLSPlugin(config) {
|
|
|
695
814
|
});
|
|
696
815
|
};
|
|
697
816
|
const loadWithHlsJs = async (src) => {
|
|
698
|
-
|
|
817
|
+
const session = loadSession;
|
|
818
|
+
await loader.loadHlsJs();
|
|
819
|
+
if (session !== loadSession) {
|
|
820
|
+
throw new Error("HLS load cancelled");
|
|
821
|
+
}
|
|
699
822
|
const videoEl = getOrCreateVideo();
|
|
700
823
|
isNative = false;
|
|
701
|
-
hls = createHlsInstance(buildHlsConfig());
|
|
824
|
+
hls = loader.createHlsInstance(buildHlsConfig());
|
|
702
825
|
if (api) {
|
|
703
826
|
cleanupVideoEvents = setupVideoEventHandlers(videoEl, api);
|
|
704
827
|
}
|
|
@@ -715,10 +838,24 @@ function createHLSPlugin(config) {
|
|
|
715
838
|
watchdog = null;
|
|
716
839
|
}
|
|
717
840
|
};
|
|
841
|
+
const abort = (reason) => {
|
|
842
|
+
if (resolved) return;
|
|
843
|
+
resolved = true;
|
|
844
|
+
clearWatchdog();
|
|
845
|
+
reject(reason);
|
|
846
|
+
};
|
|
847
|
+
abortPendingLoad = abort;
|
|
848
|
+
const releaseAbort = () => {
|
|
849
|
+
if (abortPendingLoad === abort) {
|
|
850
|
+
abortPendingLoad = null;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
718
853
|
cleanupHlsEvents = setupHlsEventHandlers(hls, api, {
|
|
719
854
|
onManifestParsed: () => {
|
|
855
|
+
if (session !== loadSession) return;
|
|
720
856
|
if (!resolved) {
|
|
721
857
|
resolved = true;
|
|
858
|
+
releaseAbort();
|
|
722
859
|
clearWatchdog();
|
|
723
860
|
hasPlayedContent = true;
|
|
724
861
|
api?.setState("source", { src, type: "application/x-mpegURL" });
|
|
@@ -729,14 +866,17 @@ function createHLSPlugin(config) {
|
|
|
729
866
|
onLevelSwitched: () => {
|
|
730
867
|
},
|
|
731
868
|
onError: (error) => {
|
|
869
|
+
if (session !== loadSession) return;
|
|
732
870
|
const terminal = handleHlsError(error);
|
|
733
871
|
if (terminal && !resolved) {
|
|
734
872
|
resolved = true;
|
|
873
|
+
releaseAbort();
|
|
735
874
|
clearWatchdog();
|
|
736
875
|
reject(new Error(error.details));
|
|
737
876
|
}
|
|
738
877
|
},
|
|
739
878
|
onFragLoaded: () => {
|
|
879
|
+
if (session !== loadSession) return;
|
|
740
880
|
if (networkRetryCount > 0 || mediaRetryCount > 0) {
|
|
741
881
|
api?.logger.debug("Playback recovered, resetting retry budgets");
|
|
742
882
|
networkRetryCount = 0;
|
|
@@ -748,17 +888,11 @@ function createHLSPlugin(config) {
|
|
|
748
888
|
const timeout_ms = mergedConfig.loadTimeoutMs ?? 3e4;
|
|
749
889
|
if (timeout_ms > 0) {
|
|
750
890
|
watchdog = setTimeout(() => {
|
|
751
|
-
if (resolved) return;
|
|
891
|
+
if (resolved || session !== loadSession) return;
|
|
752
892
|
resolved = true;
|
|
893
|
+
releaseAbort();
|
|
753
894
|
api?.logger.error(`HLS load timed out after ${timeout_ms}ms`, { src });
|
|
754
|
-
|
|
755
|
-
clearTimeout(retryTimeout);
|
|
756
|
-
retryTimeout = null;
|
|
757
|
-
}
|
|
758
|
-
cleanupHlsEvents?.();
|
|
759
|
-
cleanupHlsEvents = null;
|
|
760
|
-
hls?.destroy();
|
|
761
|
-
hls = null;
|
|
895
|
+
teardownPipeline();
|
|
762
896
|
reject(new Error("Video took too long to load (network timeout)"));
|
|
763
897
|
}, timeout_ms);
|
|
764
898
|
}
|
|
@@ -805,6 +939,7 @@ function createHLSPlugin(config) {
|
|
|
805
939
|
};
|
|
806
940
|
const attemptReconnect = async () => {
|
|
807
941
|
if (!api || !currentSrc) return;
|
|
942
|
+
const session = ++loadSession;
|
|
808
943
|
reconnectAttempts++;
|
|
809
944
|
const saved_src = currentSrc;
|
|
810
945
|
const was_live = api.getState("live");
|
|
@@ -812,27 +947,19 @@ function createHLSPlugin(config) {
|
|
|
812
947
|
const resume_position = reconnectResumePosition;
|
|
813
948
|
api.logger.info(`Auto-reconnect attempt ${reconnectAttempts}`, { src: saved_src });
|
|
814
949
|
try {
|
|
815
|
-
|
|
816
|
-
cleanupHlsEvents = null;
|
|
817
|
-
cleanupVideoEvents?.();
|
|
818
|
-
cleanupVideoEvents = null;
|
|
819
|
-
if (retryTimeout) {
|
|
820
|
-
clearTimeout(retryTimeout);
|
|
821
|
-
retryTimeout = null;
|
|
822
|
-
}
|
|
823
|
-
hls?.destroy();
|
|
824
|
-
hls = null;
|
|
950
|
+
teardownPipeline(new Error("HLS load cancelled: reconnecting"));
|
|
825
951
|
networkRetryCount = 0;
|
|
826
952
|
mediaRetryCount = 0;
|
|
827
953
|
errorCount = 0;
|
|
828
954
|
errorWindowStart = 0;
|
|
829
955
|
currentSrc = saved_src;
|
|
830
956
|
api.setState("playbackState", "loading");
|
|
831
|
-
if (was_native && supportsNativeHLS()) {
|
|
957
|
+
if (was_native && loader.supportsNativeHLS()) {
|
|
832
958
|
await loadNative(saved_src);
|
|
833
959
|
} else {
|
|
834
960
|
await loadWithHlsJs(saved_src);
|
|
835
961
|
}
|
|
962
|
+
if (session !== loadSession) return;
|
|
836
963
|
if (!was_live && video && resume_position > 0) {
|
|
837
964
|
video.currentTime = resume_position;
|
|
838
965
|
}
|
|
@@ -846,18 +973,19 @@ function createHLSPlugin(config) {
|
|
|
846
973
|
} catch {
|
|
847
974
|
}
|
|
848
975
|
} catch {
|
|
976
|
+
if (session !== loadSession) return;
|
|
849
977
|
api?.logger.warn(`Auto-reconnect attempt ${reconnectAttempts} failed`);
|
|
850
978
|
scheduleReconnectAttempt();
|
|
851
979
|
}
|
|
852
980
|
};
|
|
853
981
|
const plugin = {
|
|
854
982
|
id: "hls-provider",
|
|
855
|
-
name:
|
|
983
|
+
name: variant.name,
|
|
856
984
|
version: "1.0.0",
|
|
857
985
|
type: "provider",
|
|
858
|
-
description:
|
|
986
|
+
description: variant.description,
|
|
859
987
|
canPlay(src) {
|
|
860
|
-
if (!isHLSSupported()) return false;
|
|
988
|
+
if (!loader.isHLSSupported()) return false;
|
|
861
989
|
const url = src.toLowerCase();
|
|
862
990
|
const urlWithoutQuery = url.split("?")[0].split("#")[0];
|
|
863
991
|
if (urlWithoutQuery.endsWith(".m3u8")) return true;
|
|
@@ -867,7 +995,7 @@ function createHLSPlugin(config) {
|
|
|
867
995
|
},
|
|
868
996
|
async init(pluginApi) {
|
|
869
997
|
api = pluginApi;
|
|
870
|
-
api.logger.info(
|
|
998
|
+
api.logger.info(`HLS plugin${variant.logSuffix} initialized`);
|
|
871
999
|
const unsubPlay = api.on("playback:play", async () => {
|
|
872
1000
|
if (!video) return;
|
|
873
1001
|
try {
|
|
@@ -955,13 +1083,14 @@ function createHLSPlugin(config) {
|
|
|
955
1083
|
});
|
|
956
1084
|
},
|
|
957
1085
|
async destroy() {
|
|
958
|
-
api?.logger.info(
|
|
1086
|
+
api?.logger.info(`HLS plugin${variant.logSuffix} destroying`);
|
|
1087
|
+
loadSession++;
|
|
959
1088
|
cancelReconnect();
|
|
960
1089
|
if (onlineListener && typeof window !== "undefined") {
|
|
961
1090
|
window.removeEventListener("online", onlineListener);
|
|
962
1091
|
onlineListener = null;
|
|
963
1092
|
}
|
|
964
|
-
cleanup();
|
|
1093
|
+
cleanup(new Error("HLS load cancelled: player destroyed"));
|
|
965
1094
|
if (video?.parentNode) {
|
|
966
1095
|
video.parentNode.removeChild(video);
|
|
967
1096
|
}
|
|
@@ -970,25 +1099,27 @@ function createHLSPlugin(config) {
|
|
|
970
1099
|
},
|
|
971
1100
|
async loadSource(src) {
|
|
972
1101
|
if (!api) throw new Error("Plugin not initialized");
|
|
973
|
-
api.logger.info(
|
|
1102
|
+
api.logger.info(`Loading HLS source${variant.logSuffix}`, { src });
|
|
1103
|
+
const session = ++loadSession;
|
|
974
1104
|
cancelReconnect();
|
|
975
1105
|
hasPlayedContent = false;
|
|
976
|
-
cleanup();
|
|
1106
|
+
cleanup(new Error("HLS load cancelled: superseded by a new load"));
|
|
977
1107
|
currentSrc = src;
|
|
978
1108
|
api.setState("playbackState", "loading");
|
|
979
1109
|
api.setState("buffering", true);
|
|
980
|
-
if (api.getState("airplayActive") && supportsNativeHLS()) {
|
|
1110
|
+
if (api.getState("airplayActive") && loader.supportsNativeHLS()) {
|
|
981
1111
|
api.logger.info("Using native HLS (AirPlay active)");
|
|
982
1112
|
await loadNative(src);
|
|
983
|
-
} else if (isHlsJsSupported()) {
|
|
984
|
-
api.logger.info(
|
|
1113
|
+
} else if (loader.isHlsJsSupported()) {
|
|
1114
|
+
api.logger.info(`Using ${variant.engineLabel} for HLS playback`);
|
|
985
1115
|
await loadWithHlsJs(src);
|
|
986
|
-
} else if (supportsNativeHLS()) {
|
|
1116
|
+
} else if (loader.supportsNativeHLS()) {
|
|
987
1117
|
api.logger.info("Using native HLS playback (hls.js not supported)");
|
|
988
1118
|
await loadNative(src);
|
|
989
1119
|
} else {
|
|
990
1120
|
throw new Error("HLS playback not supported in this browser");
|
|
991
1121
|
}
|
|
1122
|
+
if (session !== loadSession) return;
|
|
992
1123
|
if (video) {
|
|
993
1124
|
const muted = api.getState("muted");
|
|
994
1125
|
const volume = api.getState("volume");
|
|
@@ -1040,7 +1171,7 @@ function createHLSPlugin(config) {
|
|
|
1040
1171
|
api?.logger.debug("Already using native HLS");
|
|
1041
1172
|
return;
|
|
1042
1173
|
}
|
|
1043
|
-
if (!supportsNativeHLS()) {
|
|
1174
|
+
if (!loader.supportsNativeHLS()) {
|
|
1044
1175
|
api?.logger.warn("Native HLS not supported in this browser");
|
|
1045
1176
|
return;
|
|
1046
1177
|
}
|
|
@@ -1052,8 +1183,10 @@ function createHLSPlugin(config) {
|
|
|
1052
1183
|
const wasPlaying = api?.getState("playing") || false;
|
|
1053
1184
|
const currentTime = video?.currentTime || 0;
|
|
1054
1185
|
const savedSrc = currentSrc;
|
|
1055
|
-
|
|
1186
|
+
const session = ++loadSession;
|
|
1187
|
+
cleanup(new Error("HLS load cancelled: switching to native HLS"));
|
|
1056
1188
|
await loadNative(savedSrc);
|
|
1189
|
+
if (session !== loadSession) return;
|
|
1057
1190
|
if (video && currentTime > 0) {
|
|
1058
1191
|
video.currentTime = currentTime;
|
|
1059
1192
|
}
|
|
@@ -1075,7 +1208,7 @@ function createHLSPlugin(config) {
|
|
|
1075
1208
|
api?.logger.debug("Already using hls.js");
|
|
1076
1209
|
return;
|
|
1077
1210
|
}
|
|
1078
|
-
if (!isHlsJsSupported()) {
|
|
1211
|
+
if (!loader.isHlsJsSupported()) {
|
|
1079
1212
|
api?.logger.warn("hls.js not supported in this browser");
|
|
1080
1213
|
return;
|
|
1081
1214
|
}
|
|
@@ -1087,8 +1220,10 @@ function createHLSPlugin(config) {
|
|
|
1087
1220
|
const wasPlaying = api?.getState("playing") || false;
|
|
1088
1221
|
const currentTime = video?.currentTime || 0;
|
|
1089
1222
|
const savedSrc = currentSrc;
|
|
1090
|
-
|
|
1223
|
+
const session = ++loadSession;
|
|
1224
|
+
cleanup(new Error("HLS load cancelled: switching to hls.js"));
|
|
1091
1225
|
await loadWithHlsJs(savedSrc);
|
|
1226
|
+
if (session !== loadSession) return;
|
|
1092
1227
|
if (video && currentTime > 0) {
|
|
1093
1228
|
video.currentTime = currentTime;
|
|
1094
1229
|
}
|
|
@@ -1104,6 +1239,20 @@ function createHLSPlugin(config) {
|
|
|
1104
1239
|
};
|
|
1105
1240
|
return plugin;
|
|
1106
1241
|
}
|
|
1242
|
+
|
|
1243
|
+
// src/index.ts
|
|
1244
|
+
function createHLSPlugin(config) {
|
|
1245
|
+
return createHLSPluginWith(
|
|
1246
|
+
hls_loader_exports,
|
|
1247
|
+
{
|
|
1248
|
+
name: "HLS Provider",
|
|
1249
|
+
description: "HLS playback provider using hls.js",
|
|
1250
|
+
logSuffix: "",
|
|
1251
|
+
engineLabel: "hls.js"
|
|
1252
|
+
},
|
|
1253
|
+
config
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1107
1256
|
var index_default = createHLSPlugin;
|
|
1108
1257
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1109
1258
|
0 && (module.exports = {
|
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './types-
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './types-
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './types-D-HW4lmM.cjs';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './types-D-HW4lmM.cjs';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* HLS Provider Plugin for Scarlett Player
|
|
7
7
|
*
|
|
8
8
|
* Provides HLS playback using hls.js or native HLS (Safari).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Error recovery
|
|
14
|
-
* - Live stream support
|
|
9
|
+
* Thin wrapper around the shared factory in `create-hls-plugin.ts`; the
|
|
10
|
+
* light entry (`light.ts`) wraps the same factory with the hls.js/light
|
|
11
|
+
* loader, so both builds always carry identical error handling, retry,
|
|
12
|
+
* reconnect, and playlist-validation machinery.
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './types-
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './types-
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './types-D-HW4lmM.js';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './types-D-HW4lmM.js';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* HLS Provider Plugin for Scarlett Player
|
|
7
7
|
*
|
|
8
8
|
* Provides HLS playback using hls.js or native HLS (Safari).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* - Error recovery
|
|
14
|
-
* - Live stream support
|
|
9
|
+
* Thin wrapper around the shared factory in `create-hls-plugin.ts`; the
|
|
10
|
+
* light entry (`light.ts`) wraps the same factory with the hls.js/light
|
|
11
|
+
* loader, so both builds always carry identical error handling, retry,
|
|
12
|
+
* reconnect, and playlist-validation machinery.
|
|
15
13
|
*/
|
|
16
14
|
|
|
17
15
|
/**
|