@scarlett-player/hls 1.6.0 → 1.7.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/dist/{chunk-IFUZZQQE.js → chunk-NU6WD6KW.js} +139 -6
- package/dist/index.cjs +142 -8
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -3
- package/dist/light.cjs +142 -8
- package/dist/light.d.cts +2 -2
- package/dist/light.d.ts +2 -2
- package/dist/light.js +5 -3
- package/dist/{types-D-HW4lmM.d.cts → sanitize-url-DcZXD_K-.d.cts} +38 -3
- package/dist/{types-D-HW4lmM.d.ts → sanitize-url-DcZXD_K-.d.ts} +38 -3
- package/package.json +2 -2
|
@@ -4,6 +4,17 @@ var __export = (target, all) => {
|
|
|
4
4
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
5
|
};
|
|
6
6
|
|
|
7
|
+
// src/sanitize-url.ts
|
|
8
|
+
function sanitizeUrl(url) {
|
|
9
|
+
if (!url) return void 0;
|
|
10
|
+
try {
|
|
11
|
+
const parsed = new URL(url);
|
|
12
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
13
|
+
} catch {
|
|
14
|
+
return void 0;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
// src/create-hls-plugin.ts
|
|
8
19
|
import { ErrorCode } from "@scarlett-player/core";
|
|
9
20
|
|
|
@@ -437,6 +448,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
437
448
|
let reconnectWindowStart = 0;
|
|
438
449
|
let reconnectResumePosition = 0;
|
|
439
450
|
let onlineListener = null;
|
|
451
|
+
let reconnectTriggerError = null;
|
|
452
|
+
let reconnectExhausted = false;
|
|
440
453
|
const getOrCreateVideo = () => {
|
|
441
454
|
if (video) return video;
|
|
442
455
|
const existing = api?.container.querySelector("video");
|
|
@@ -545,6 +558,22 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
545
558
|
return ErrorCode.PLAYBACK_FAILED;
|
|
546
559
|
}
|
|
547
560
|
};
|
|
561
|
+
const buildErrorDetail = (error, retriesExhausted) => {
|
|
562
|
+
const attempts = error.type === "network" ? networkRetryCount : error.type === "media" ? mediaRetryCount : 0;
|
|
563
|
+
const detail = {
|
|
564
|
+
type: error.type,
|
|
565
|
+
retriesExhausted,
|
|
566
|
+
attempts
|
|
567
|
+
};
|
|
568
|
+
if (typeof error.response?.code === "number" && error.response.code > 0) {
|
|
569
|
+
detail.httpStatus = error.response.code;
|
|
570
|
+
}
|
|
571
|
+
const url = sanitizeUrl(error.url);
|
|
572
|
+
if (url) {
|
|
573
|
+
detail.url = url;
|
|
574
|
+
}
|
|
575
|
+
return detail;
|
|
576
|
+
};
|
|
548
577
|
const emitFatalError = (error, retriesExhausted) => {
|
|
549
578
|
const message = retriesExhausted ? `HLS error: ${error.details} (max retries exceeded)` : `HLS error: ${error.details}`;
|
|
550
579
|
api?.logger.error(message, { type: error.type, details: error.details });
|
|
@@ -554,7 +583,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
554
583
|
code: mapFatalErrorCode(error),
|
|
555
584
|
message,
|
|
556
585
|
fatal: true,
|
|
557
|
-
timestamp: Date.now()
|
|
586
|
+
timestamp: Date.now(),
|
|
587
|
+
detail: buildErrorDetail(error, retriesExhausted)
|
|
558
588
|
});
|
|
559
589
|
maybeScheduleReconnect(error);
|
|
560
590
|
};
|
|
@@ -631,6 +661,62 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
631
661
|
}
|
|
632
662
|
return false;
|
|
633
663
|
};
|
|
664
|
+
const handleNativeFatalError = (error, resumePosition) => {
|
|
665
|
+
const is_network = error.type === "network";
|
|
666
|
+
const max_retries = is_network ? mergedConfig.maxNetworkRetries ?? 3 : mergedConfig.maxMediaRetries ?? 2;
|
|
667
|
+
const used = is_network ? networkRetryCount : mediaRetryCount;
|
|
668
|
+
if (!currentSrc || used >= max_retries) {
|
|
669
|
+
emitFatalError(error, used >= max_retries);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const resume_position = resumePosition ?? video?.currentTime ?? 0;
|
|
673
|
+
if (is_network) {
|
|
674
|
+
networkRetryCount++;
|
|
675
|
+
} else {
|
|
676
|
+
mediaRetryCount++;
|
|
677
|
+
}
|
|
678
|
+
const attempt = used + 1;
|
|
679
|
+
const delay = getRetryDelay(attempt - 1);
|
|
680
|
+
api?.logger.info(
|
|
681
|
+
`Attempting native ${error.type} error recovery (attempt ${attempt}/${max_retries}) in ${delay}ms`
|
|
682
|
+
);
|
|
683
|
+
api?.emit(is_network ? "error:network" : "error:media", {
|
|
684
|
+
error: new Error(error.details)
|
|
685
|
+
});
|
|
686
|
+
if (retryTimeout) {
|
|
687
|
+
clearTimeout(retryTimeout);
|
|
688
|
+
}
|
|
689
|
+
const retry_session = loadSession;
|
|
690
|
+
retryTimeout = setTimeout(() => {
|
|
691
|
+
if (retry_session !== loadSession) return;
|
|
692
|
+
void recoverNative(error, resume_position);
|
|
693
|
+
}, delay);
|
|
694
|
+
};
|
|
695
|
+
const recoverNative = async (error, resumePosition) => {
|
|
696
|
+
if (!currentSrc) return;
|
|
697
|
+
const session = ++loadSession;
|
|
698
|
+
const saved_src = currentSrc;
|
|
699
|
+
const was_live = api?.getState("live") ?? false;
|
|
700
|
+
try {
|
|
701
|
+
teardownPipeline(new Error("HLS load cancelled: native error recovery"));
|
|
702
|
+
api?.setState("playbackState", "loading");
|
|
703
|
+
await loadNative(saved_src);
|
|
704
|
+
if (session !== loadSession) return;
|
|
705
|
+
if (!was_live && video && resumePosition > 0) {
|
|
706
|
+
video.currentTime = resumePosition;
|
|
707
|
+
}
|
|
708
|
+
api?.setState("playbackState", "ready");
|
|
709
|
+
api?.setState("buffering", false);
|
|
710
|
+
try {
|
|
711
|
+
await video?.play();
|
|
712
|
+
} catch {
|
|
713
|
+
}
|
|
714
|
+
} catch {
|
|
715
|
+
if (session !== loadSession) return;
|
|
716
|
+
api?.logger.warn("Native error recovery attempt failed");
|
|
717
|
+
handleNativeFatalError(error, resumePosition);
|
|
718
|
+
}
|
|
719
|
+
};
|
|
634
720
|
const loadNative = async (src) => {
|
|
635
721
|
const session = loadSession;
|
|
636
722
|
const videoEl = getOrCreateVideo();
|
|
@@ -675,10 +761,21 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
675
761
|
details: media_error?.message || "Native HLS playback error",
|
|
676
762
|
fatal: true
|
|
677
763
|
};
|
|
678
|
-
|
|
764
|
+
handleNativeFatalError(hls_error);
|
|
679
765
|
};
|
|
680
766
|
videoEl.addEventListener("error", onFatalVideoError);
|
|
681
|
-
const
|
|
767
|
+
const onPlayingResetBudget = () => {
|
|
768
|
+
if (networkRetryCount > 0 || mediaRetryCount > 0) {
|
|
769
|
+
api?.logger.debug("Native playback recovered, resetting retry budgets");
|
|
770
|
+
networkRetryCount = 0;
|
|
771
|
+
mediaRetryCount = 0;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
videoEl.addEventListener("playing", onPlayingResetBudget);
|
|
775
|
+
const removeFatalListener = () => {
|
|
776
|
+
videoEl.removeEventListener("error", onFatalVideoError);
|
|
777
|
+
videoEl.removeEventListener("playing", onPlayingResetBudget);
|
|
778
|
+
};
|
|
682
779
|
const previous_cleanup = cleanupVideoEvents;
|
|
683
780
|
cleanupVideoEvents = () => {
|
|
684
781
|
removeFatalListener();
|
|
@@ -803,12 +900,38 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
803
900
|
reconnectAttempts = 0;
|
|
804
901
|
reconnectWindowStart = 0;
|
|
805
902
|
reconnectResumePosition = 0;
|
|
903
|
+
reconnectTriggerError = null;
|
|
904
|
+
reconnectExhausted = false;
|
|
905
|
+
};
|
|
906
|
+
const emitReconnectExhausted = (elapsedMs, windowMs) => {
|
|
907
|
+
if (reconnectExhausted) return;
|
|
908
|
+
reconnectExhausted = true;
|
|
909
|
+
const attempts = reconnectAttempts;
|
|
910
|
+
const trigger = reconnectTriggerError;
|
|
911
|
+
api?.emit("error:reconnect-exhausted", { attempts, elapsedMs, windowMs });
|
|
912
|
+
api?.setState("playbackState", "error");
|
|
913
|
+
api?.setState("buffering", false);
|
|
914
|
+
api?.emit("error", {
|
|
915
|
+
code: trigger ? mapFatalErrorCode(trigger) : ErrorCode.PLAYBACK_FAILED,
|
|
916
|
+
message: `HLS auto-reconnect gave up after ${attempts} attempts over ${Math.round(elapsedMs / 1e3)}s`,
|
|
917
|
+
fatal: true,
|
|
918
|
+
timestamp: Date.now(),
|
|
919
|
+
detail: {
|
|
920
|
+
type: trigger?.type ?? "other",
|
|
921
|
+
retriesExhausted: true,
|
|
922
|
+
attempts,
|
|
923
|
+
reconnectExhausted: true
|
|
924
|
+
}
|
|
925
|
+
});
|
|
806
926
|
};
|
|
807
927
|
const scheduleReconnectAttempt = () => {
|
|
928
|
+
if (reconnectExhausted) return;
|
|
808
929
|
if (reconnectTimer) return;
|
|
809
930
|
const window_ms = mergedConfig.reconnectWindowMs ?? 3e5;
|
|
810
|
-
|
|
931
|
+
const elapsed_ms = Date.now() - reconnectWindowStart;
|
|
932
|
+
if (elapsed_ms > window_ms) {
|
|
811
933
|
api?.logger.warn(`Auto-reconnect window exhausted after ${reconnectAttempts} attempts`);
|
|
934
|
+
emitReconnectExhausted(elapsed_ms, window_ms);
|
|
812
935
|
return;
|
|
813
936
|
}
|
|
814
937
|
const base_delay = mergedConfig.reconnectBaseDelayMs ?? 2e3;
|
|
@@ -816,7 +939,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
816
939
|
const backoff = Math.min(base_delay * Math.pow(2, reconnectAttempts), max_delay);
|
|
817
940
|
const delay = Math.round(backoff * (0.7 + Math.random() * 0.3));
|
|
818
941
|
api?.logger.info(`Scheduling auto-reconnect attempt ${reconnectAttempts + 1} in ${delay}ms`);
|
|
819
|
-
api?.emit("error:reconnecting", {
|
|
942
|
+
api?.emit("error:reconnecting", {
|
|
943
|
+
attempt: reconnectAttempts + 1,
|
|
944
|
+
delayMs: delay,
|
|
945
|
+
elapsedMs: elapsed_ms,
|
|
946
|
+
windowMs: window_ms
|
|
947
|
+
});
|
|
820
948
|
reconnectTimer = setTimeout(() => {
|
|
821
949
|
reconnectTimer = null;
|
|
822
950
|
void attemptReconnect();
|
|
@@ -829,6 +957,7 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
829
957
|
if (reconnectWindowStart === 0) {
|
|
830
958
|
reconnectWindowStart = Date.now();
|
|
831
959
|
reconnectResumePosition = video?.currentTime ?? 0;
|
|
960
|
+
reconnectTriggerError = error;
|
|
832
961
|
}
|
|
833
962
|
scheduleReconnectAttempt();
|
|
834
963
|
};
|
|
@@ -860,7 +989,10 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
860
989
|
}
|
|
861
990
|
api.setState("playbackState", "ready");
|
|
862
991
|
api.setState("buffering", false);
|
|
863
|
-
api.emit("error:recovered",
|
|
992
|
+
api.emit("error:recovered", {
|
|
993
|
+
attempt: reconnectAttempts,
|
|
994
|
+
elapsedMs: Date.now() - reconnectWindowStart
|
|
995
|
+
});
|
|
864
996
|
api.logger.info("Auto-reconnect succeeded");
|
|
865
997
|
cancelReconnect();
|
|
866
998
|
try {
|
|
@@ -1137,5 +1269,6 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
1137
1269
|
|
|
1138
1270
|
export {
|
|
1139
1271
|
__export,
|
|
1272
|
+
sanitizeUrl,
|
|
1140
1273
|
createHLSPluginWith
|
|
1141
1274
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
createHLSPlugin: () => createHLSPlugin,
|
|
34
|
-
default: () => index_default
|
|
34
|
+
default: () => index_default,
|
|
35
|
+
sanitizeUrl: () => sanitizeUrl
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(index_exports);
|
|
37
38
|
|
|
@@ -486,6 +487,17 @@ function createValidatingPlaylistLoader(Hls) {
|
|
|
486
487
|
};
|
|
487
488
|
}
|
|
488
489
|
|
|
490
|
+
// src/sanitize-url.ts
|
|
491
|
+
function sanitizeUrl(url) {
|
|
492
|
+
if (!url) return void 0;
|
|
493
|
+
try {
|
|
494
|
+
const parsed = new URL(url);
|
|
495
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
496
|
+
} catch {
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
489
501
|
// src/create-hls-plugin.ts
|
|
490
502
|
var DEFAULT_CONFIG = {
|
|
491
503
|
debug: false,
|
|
@@ -542,6 +554,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
542
554
|
let reconnectWindowStart = 0;
|
|
543
555
|
let reconnectResumePosition = 0;
|
|
544
556
|
let onlineListener = null;
|
|
557
|
+
let reconnectTriggerError = null;
|
|
558
|
+
let reconnectExhausted = false;
|
|
545
559
|
const getOrCreateVideo = () => {
|
|
546
560
|
if (video) return video;
|
|
547
561
|
const existing = api?.container.querySelector("video");
|
|
@@ -650,6 +664,22 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
650
664
|
return import_core.ErrorCode.PLAYBACK_FAILED;
|
|
651
665
|
}
|
|
652
666
|
};
|
|
667
|
+
const buildErrorDetail = (error, retriesExhausted) => {
|
|
668
|
+
const attempts = error.type === "network" ? networkRetryCount : error.type === "media" ? mediaRetryCount : 0;
|
|
669
|
+
const detail = {
|
|
670
|
+
type: error.type,
|
|
671
|
+
retriesExhausted,
|
|
672
|
+
attempts
|
|
673
|
+
};
|
|
674
|
+
if (typeof error.response?.code === "number" && error.response.code > 0) {
|
|
675
|
+
detail.httpStatus = error.response.code;
|
|
676
|
+
}
|
|
677
|
+
const url = sanitizeUrl(error.url);
|
|
678
|
+
if (url) {
|
|
679
|
+
detail.url = url;
|
|
680
|
+
}
|
|
681
|
+
return detail;
|
|
682
|
+
};
|
|
653
683
|
const emitFatalError = (error, retriesExhausted) => {
|
|
654
684
|
const message = retriesExhausted ? `HLS error: ${error.details} (max retries exceeded)` : `HLS error: ${error.details}`;
|
|
655
685
|
api?.logger.error(message, { type: error.type, details: error.details });
|
|
@@ -659,7 +689,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
659
689
|
code: mapFatalErrorCode(error),
|
|
660
690
|
message,
|
|
661
691
|
fatal: true,
|
|
662
|
-
timestamp: Date.now()
|
|
692
|
+
timestamp: Date.now(),
|
|
693
|
+
detail: buildErrorDetail(error, retriesExhausted)
|
|
663
694
|
});
|
|
664
695
|
maybeScheduleReconnect(error);
|
|
665
696
|
};
|
|
@@ -736,6 +767,62 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
736
767
|
}
|
|
737
768
|
return false;
|
|
738
769
|
};
|
|
770
|
+
const handleNativeFatalError = (error, resumePosition) => {
|
|
771
|
+
const is_network = error.type === "network";
|
|
772
|
+
const max_retries = is_network ? mergedConfig.maxNetworkRetries ?? 3 : mergedConfig.maxMediaRetries ?? 2;
|
|
773
|
+
const used = is_network ? networkRetryCount : mediaRetryCount;
|
|
774
|
+
if (!currentSrc || used >= max_retries) {
|
|
775
|
+
emitFatalError(error, used >= max_retries);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const resume_position = resumePosition ?? video?.currentTime ?? 0;
|
|
779
|
+
if (is_network) {
|
|
780
|
+
networkRetryCount++;
|
|
781
|
+
} else {
|
|
782
|
+
mediaRetryCount++;
|
|
783
|
+
}
|
|
784
|
+
const attempt = used + 1;
|
|
785
|
+
const delay = getRetryDelay(attempt - 1);
|
|
786
|
+
api?.logger.info(
|
|
787
|
+
`Attempting native ${error.type} error recovery (attempt ${attempt}/${max_retries}) in ${delay}ms`
|
|
788
|
+
);
|
|
789
|
+
api?.emit(is_network ? "error:network" : "error:media", {
|
|
790
|
+
error: new Error(error.details)
|
|
791
|
+
});
|
|
792
|
+
if (retryTimeout) {
|
|
793
|
+
clearTimeout(retryTimeout);
|
|
794
|
+
}
|
|
795
|
+
const retry_session = loadSession;
|
|
796
|
+
retryTimeout = setTimeout(() => {
|
|
797
|
+
if (retry_session !== loadSession) return;
|
|
798
|
+
void recoverNative(error, resume_position);
|
|
799
|
+
}, delay);
|
|
800
|
+
};
|
|
801
|
+
const recoverNative = async (error, resumePosition) => {
|
|
802
|
+
if (!currentSrc) return;
|
|
803
|
+
const session = ++loadSession;
|
|
804
|
+
const saved_src = currentSrc;
|
|
805
|
+
const was_live = api?.getState("live") ?? false;
|
|
806
|
+
try {
|
|
807
|
+
teardownPipeline(new Error("HLS load cancelled: native error recovery"));
|
|
808
|
+
api?.setState("playbackState", "loading");
|
|
809
|
+
await loadNative(saved_src);
|
|
810
|
+
if (session !== loadSession) return;
|
|
811
|
+
if (!was_live && video && resumePosition > 0) {
|
|
812
|
+
video.currentTime = resumePosition;
|
|
813
|
+
}
|
|
814
|
+
api?.setState("playbackState", "ready");
|
|
815
|
+
api?.setState("buffering", false);
|
|
816
|
+
try {
|
|
817
|
+
await video?.play();
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
if (session !== loadSession) return;
|
|
822
|
+
api?.logger.warn("Native error recovery attempt failed");
|
|
823
|
+
handleNativeFatalError(error, resumePosition);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
739
826
|
const loadNative = async (src) => {
|
|
740
827
|
const session = loadSession;
|
|
741
828
|
const videoEl = getOrCreateVideo();
|
|
@@ -780,10 +867,21 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
780
867
|
details: media_error?.message || "Native HLS playback error",
|
|
781
868
|
fatal: true
|
|
782
869
|
};
|
|
783
|
-
|
|
870
|
+
handleNativeFatalError(hls_error);
|
|
784
871
|
};
|
|
785
872
|
videoEl.addEventListener("error", onFatalVideoError);
|
|
786
|
-
const
|
|
873
|
+
const onPlayingResetBudget = () => {
|
|
874
|
+
if (networkRetryCount > 0 || mediaRetryCount > 0) {
|
|
875
|
+
api?.logger.debug("Native playback recovered, resetting retry budgets");
|
|
876
|
+
networkRetryCount = 0;
|
|
877
|
+
mediaRetryCount = 0;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
videoEl.addEventListener("playing", onPlayingResetBudget);
|
|
881
|
+
const removeFatalListener = () => {
|
|
882
|
+
videoEl.removeEventListener("error", onFatalVideoError);
|
|
883
|
+
videoEl.removeEventListener("playing", onPlayingResetBudget);
|
|
884
|
+
};
|
|
787
885
|
const previous_cleanup = cleanupVideoEvents;
|
|
788
886
|
cleanupVideoEvents = () => {
|
|
789
887
|
removeFatalListener();
|
|
@@ -908,12 +1006,38 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
908
1006
|
reconnectAttempts = 0;
|
|
909
1007
|
reconnectWindowStart = 0;
|
|
910
1008
|
reconnectResumePosition = 0;
|
|
1009
|
+
reconnectTriggerError = null;
|
|
1010
|
+
reconnectExhausted = false;
|
|
1011
|
+
};
|
|
1012
|
+
const emitReconnectExhausted = (elapsedMs, windowMs) => {
|
|
1013
|
+
if (reconnectExhausted) return;
|
|
1014
|
+
reconnectExhausted = true;
|
|
1015
|
+
const attempts = reconnectAttempts;
|
|
1016
|
+
const trigger = reconnectTriggerError;
|
|
1017
|
+
api?.emit("error:reconnect-exhausted", { attempts, elapsedMs, windowMs });
|
|
1018
|
+
api?.setState("playbackState", "error");
|
|
1019
|
+
api?.setState("buffering", false);
|
|
1020
|
+
api?.emit("error", {
|
|
1021
|
+
code: trigger ? mapFatalErrorCode(trigger) : import_core.ErrorCode.PLAYBACK_FAILED,
|
|
1022
|
+
message: `HLS auto-reconnect gave up after ${attempts} attempts over ${Math.round(elapsedMs / 1e3)}s`,
|
|
1023
|
+
fatal: true,
|
|
1024
|
+
timestamp: Date.now(),
|
|
1025
|
+
detail: {
|
|
1026
|
+
type: trigger?.type ?? "other",
|
|
1027
|
+
retriesExhausted: true,
|
|
1028
|
+
attempts,
|
|
1029
|
+
reconnectExhausted: true
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
911
1032
|
};
|
|
912
1033
|
const scheduleReconnectAttempt = () => {
|
|
1034
|
+
if (reconnectExhausted) return;
|
|
913
1035
|
if (reconnectTimer) return;
|
|
914
1036
|
const window_ms = mergedConfig.reconnectWindowMs ?? 3e5;
|
|
915
|
-
|
|
1037
|
+
const elapsed_ms = Date.now() - reconnectWindowStart;
|
|
1038
|
+
if (elapsed_ms > window_ms) {
|
|
916
1039
|
api?.logger.warn(`Auto-reconnect window exhausted after ${reconnectAttempts} attempts`);
|
|
1040
|
+
emitReconnectExhausted(elapsed_ms, window_ms);
|
|
917
1041
|
return;
|
|
918
1042
|
}
|
|
919
1043
|
const base_delay = mergedConfig.reconnectBaseDelayMs ?? 2e3;
|
|
@@ -921,7 +1045,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
921
1045
|
const backoff = Math.min(base_delay * Math.pow(2, reconnectAttempts), max_delay);
|
|
922
1046
|
const delay = Math.round(backoff * (0.7 + Math.random() * 0.3));
|
|
923
1047
|
api?.logger.info(`Scheduling auto-reconnect attempt ${reconnectAttempts + 1} in ${delay}ms`);
|
|
924
|
-
api?.emit("error:reconnecting", {
|
|
1048
|
+
api?.emit("error:reconnecting", {
|
|
1049
|
+
attempt: reconnectAttempts + 1,
|
|
1050
|
+
delayMs: delay,
|
|
1051
|
+
elapsedMs: elapsed_ms,
|
|
1052
|
+
windowMs: window_ms
|
|
1053
|
+
});
|
|
925
1054
|
reconnectTimer = setTimeout(() => {
|
|
926
1055
|
reconnectTimer = null;
|
|
927
1056
|
void attemptReconnect();
|
|
@@ -934,6 +1063,7 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
934
1063
|
if (reconnectWindowStart === 0) {
|
|
935
1064
|
reconnectWindowStart = Date.now();
|
|
936
1065
|
reconnectResumePosition = video?.currentTime ?? 0;
|
|
1066
|
+
reconnectTriggerError = error;
|
|
937
1067
|
}
|
|
938
1068
|
scheduleReconnectAttempt();
|
|
939
1069
|
};
|
|
@@ -965,7 +1095,10 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
965
1095
|
}
|
|
966
1096
|
api.setState("playbackState", "ready");
|
|
967
1097
|
api.setState("buffering", false);
|
|
968
|
-
api.emit("error:recovered",
|
|
1098
|
+
api.emit("error:recovered", {
|
|
1099
|
+
attempt: reconnectAttempts,
|
|
1100
|
+
elapsedMs: Date.now() - reconnectWindowStart
|
|
1101
|
+
});
|
|
969
1102
|
api.logger.info("Auto-reconnect succeeded");
|
|
970
1103
|
cancelReconnect();
|
|
971
1104
|
try {
|
|
@@ -1256,5 +1389,6 @@ function createHLSPlugin(config) {
|
|
|
1256
1389
|
var index_default = createHLSPlugin;
|
|
1257
1390
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1258
1391
|
0 && (module.exports = {
|
|
1259
|
-
createHLSPlugin
|
|
1392
|
+
createHLSPlugin,
|
|
1393
|
+
sanitizeUrl
|
|
1260
1394
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-DcZXD_K-.cjs';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-DcZXD_K-.cjs';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-DcZXD_K-.js';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-DcZXD_K-.js';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__export,
|
|
3
|
-
createHLSPluginWith
|
|
4
|
-
|
|
3
|
+
createHLSPluginWith,
|
|
4
|
+
sanitizeUrl
|
|
5
|
+
} from "./chunk-NU6WD6KW.js";
|
|
5
6
|
|
|
6
7
|
// src/hls-loader.ts
|
|
7
8
|
var hls_loader_exports = {};
|
|
@@ -93,5 +94,6 @@ function createHLSPlugin(config) {
|
|
|
93
94
|
var index_default = createHLSPlugin;
|
|
94
95
|
export {
|
|
95
96
|
createHLSPlugin,
|
|
96
|
-
index_default as default
|
|
97
|
+
index_default as default,
|
|
98
|
+
sanitizeUrl
|
|
97
99
|
};
|
package/dist/light.cjs
CHANGED
|
@@ -31,7 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var light_exports = {};
|
|
32
32
|
__export(light_exports, {
|
|
33
33
|
createHLSPlugin: () => createHLSPlugin,
|
|
34
|
-
default: () => light_default
|
|
34
|
+
default: () => light_default,
|
|
35
|
+
sanitizeUrl: () => sanitizeUrl
|
|
35
36
|
});
|
|
36
37
|
module.exports = __toCommonJS(light_exports);
|
|
37
38
|
|
|
@@ -486,6 +487,17 @@ function createValidatingPlaylistLoader(Hls) {
|
|
|
486
487
|
};
|
|
487
488
|
}
|
|
488
489
|
|
|
490
|
+
// src/sanitize-url.ts
|
|
491
|
+
function sanitizeUrl(url) {
|
|
492
|
+
if (!url) return void 0;
|
|
493
|
+
try {
|
|
494
|
+
const parsed = new URL(url);
|
|
495
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
496
|
+
} catch {
|
|
497
|
+
return void 0;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
489
501
|
// src/create-hls-plugin.ts
|
|
490
502
|
var DEFAULT_CONFIG = {
|
|
491
503
|
debug: false,
|
|
@@ -542,6 +554,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
542
554
|
let reconnectWindowStart = 0;
|
|
543
555
|
let reconnectResumePosition = 0;
|
|
544
556
|
let onlineListener = null;
|
|
557
|
+
let reconnectTriggerError = null;
|
|
558
|
+
let reconnectExhausted = false;
|
|
545
559
|
const getOrCreateVideo = () => {
|
|
546
560
|
if (video) return video;
|
|
547
561
|
const existing = api?.container.querySelector("video");
|
|
@@ -650,6 +664,22 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
650
664
|
return import_core.ErrorCode.PLAYBACK_FAILED;
|
|
651
665
|
}
|
|
652
666
|
};
|
|
667
|
+
const buildErrorDetail = (error, retriesExhausted) => {
|
|
668
|
+
const attempts = error.type === "network" ? networkRetryCount : error.type === "media" ? mediaRetryCount : 0;
|
|
669
|
+
const detail = {
|
|
670
|
+
type: error.type,
|
|
671
|
+
retriesExhausted,
|
|
672
|
+
attempts
|
|
673
|
+
};
|
|
674
|
+
if (typeof error.response?.code === "number" && error.response.code > 0) {
|
|
675
|
+
detail.httpStatus = error.response.code;
|
|
676
|
+
}
|
|
677
|
+
const url = sanitizeUrl(error.url);
|
|
678
|
+
if (url) {
|
|
679
|
+
detail.url = url;
|
|
680
|
+
}
|
|
681
|
+
return detail;
|
|
682
|
+
};
|
|
653
683
|
const emitFatalError = (error, retriesExhausted) => {
|
|
654
684
|
const message = retriesExhausted ? `HLS error: ${error.details} (max retries exceeded)` : `HLS error: ${error.details}`;
|
|
655
685
|
api?.logger.error(message, { type: error.type, details: error.details });
|
|
@@ -659,7 +689,8 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
659
689
|
code: mapFatalErrorCode(error),
|
|
660
690
|
message,
|
|
661
691
|
fatal: true,
|
|
662
|
-
timestamp: Date.now()
|
|
692
|
+
timestamp: Date.now(),
|
|
693
|
+
detail: buildErrorDetail(error, retriesExhausted)
|
|
663
694
|
});
|
|
664
695
|
maybeScheduleReconnect(error);
|
|
665
696
|
};
|
|
@@ -736,6 +767,62 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
736
767
|
}
|
|
737
768
|
return false;
|
|
738
769
|
};
|
|
770
|
+
const handleNativeFatalError = (error, resumePosition) => {
|
|
771
|
+
const is_network = error.type === "network";
|
|
772
|
+
const max_retries = is_network ? mergedConfig.maxNetworkRetries ?? 3 : mergedConfig.maxMediaRetries ?? 2;
|
|
773
|
+
const used = is_network ? networkRetryCount : mediaRetryCount;
|
|
774
|
+
if (!currentSrc || used >= max_retries) {
|
|
775
|
+
emitFatalError(error, used >= max_retries);
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
const resume_position = resumePosition ?? video?.currentTime ?? 0;
|
|
779
|
+
if (is_network) {
|
|
780
|
+
networkRetryCount++;
|
|
781
|
+
} else {
|
|
782
|
+
mediaRetryCount++;
|
|
783
|
+
}
|
|
784
|
+
const attempt = used + 1;
|
|
785
|
+
const delay = getRetryDelay(attempt - 1);
|
|
786
|
+
api?.logger.info(
|
|
787
|
+
`Attempting native ${error.type} error recovery (attempt ${attempt}/${max_retries}) in ${delay}ms`
|
|
788
|
+
);
|
|
789
|
+
api?.emit(is_network ? "error:network" : "error:media", {
|
|
790
|
+
error: new Error(error.details)
|
|
791
|
+
});
|
|
792
|
+
if (retryTimeout) {
|
|
793
|
+
clearTimeout(retryTimeout);
|
|
794
|
+
}
|
|
795
|
+
const retry_session = loadSession;
|
|
796
|
+
retryTimeout = setTimeout(() => {
|
|
797
|
+
if (retry_session !== loadSession) return;
|
|
798
|
+
void recoverNative(error, resume_position);
|
|
799
|
+
}, delay);
|
|
800
|
+
};
|
|
801
|
+
const recoverNative = async (error, resumePosition) => {
|
|
802
|
+
if (!currentSrc) return;
|
|
803
|
+
const session = ++loadSession;
|
|
804
|
+
const saved_src = currentSrc;
|
|
805
|
+
const was_live = api?.getState("live") ?? false;
|
|
806
|
+
try {
|
|
807
|
+
teardownPipeline(new Error("HLS load cancelled: native error recovery"));
|
|
808
|
+
api?.setState("playbackState", "loading");
|
|
809
|
+
await loadNative(saved_src);
|
|
810
|
+
if (session !== loadSession) return;
|
|
811
|
+
if (!was_live && video && resumePosition > 0) {
|
|
812
|
+
video.currentTime = resumePosition;
|
|
813
|
+
}
|
|
814
|
+
api?.setState("playbackState", "ready");
|
|
815
|
+
api?.setState("buffering", false);
|
|
816
|
+
try {
|
|
817
|
+
await video?.play();
|
|
818
|
+
} catch {
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
if (session !== loadSession) return;
|
|
822
|
+
api?.logger.warn("Native error recovery attempt failed");
|
|
823
|
+
handleNativeFatalError(error, resumePosition);
|
|
824
|
+
}
|
|
825
|
+
};
|
|
739
826
|
const loadNative = async (src) => {
|
|
740
827
|
const session = loadSession;
|
|
741
828
|
const videoEl = getOrCreateVideo();
|
|
@@ -780,10 +867,21 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
780
867
|
details: media_error?.message || "Native HLS playback error",
|
|
781
868
|
fatal: true
|
|
782
869
|
};
|
|
783
|
-
|
|
870
|
+
handleNativeFatalError(hls_error);
|
|
784
871
|
};
|
|
785
872
|
videoEl.addEventListener("error", onFatalVideoError);
|
|
786
|
-
const
|
|
873
|
+
const onPlayingResetBudget = () => {
|
|
874
|
+
if (networkRetryCount > 0 || mediaRetryCount > 0) {
|
|
875
|
+
api?.logger.debug("Native playback recovered, resetting retry budgets");
|
|
876
|
+
networkRetryCount = 0;
|
|
877
|
+
mediaRetryCount = 0;
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
videoEl.addEventListener("playing", onPlayingResetBudget);
|
|
881
|
+
const removeFatalListener = () => {
|
|
882
|
+
videoEl.removeEventListener("error", onFatalVideoError);
|
|
883
|
+
videoEl.removeEventListener("playing", onPlayingResetBudget);
|
|
884
|
+
};
|
|
787
885
|
const previous_cleanup = cleanupVideoEvents;
|
|
788
886
|
cleanupVideoEvents = () => {
|
|
789
887
|
removeFatalListener();
|
|
@@ -908,12 +1006,38 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
908
1006
|
reconnectAttempts = 0;
|
|
909
1007
|
reconnectWindowStart = 0;
|
|
910
1008
|
reconnectResumePosition = 0;
|
|
1009
|
+
reconnectTriggerError = null;
|
|
1010
|
+
reconnectExhausted = false;
|
|
1011
|
+
};
|
|
1012
|
+
const emitReconnectExhausted = (elapsedMs, windowMs) => {
|
|
1013
|
+
if (reconnectExhausted) return;
|
|
1014
|
+
reconnectExhausted = true;
|
|
1015
|
+
const attempts = reconnectAttempts;
|
|
1016
|
+
const trigger = reconnectTriggerError;
|
|
1017
|
+
api?.emit("error:reconnect-exhausted", { attempts, elapsedMs, windowMs });
|
|
1018
|
+
api?.setState("playbackState", "error");
|
|
1019
|
+
api?.setState("buffering", false);
|
|
1020
|
+
api?.emit("error", {
|
|
1021
|
+
code: trigger ? mapFatalErrorCode(trigger) : import_core.ErrorCode.PLAYBACK_FAILED,
|
|
1022
|
+
message: `HLS auto-reconnect gave up after ${attempts} attempts over ${Math.round(elapsedMs / 1e3)}s`,
|
|
1023
|
+
fatal: true,
|
|
1024
|
+
timestamp: Date.now(),
|
|
1025
|
+
detail: {
|
|
1026
|
+
type: trigger?.type ?? "other",
|
|
1027
|
+
retriesExhausted: true,
|
|
1028
|
+
attempts,
|
|
1029
|
+
reconnectExhausted: true
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
911
1032
|
};
|
|
912
1033
|
const scheduleReconnectAttempt = () => {
|
|
1034
|
+
if (reconnectExhausted) return;
|
|
913
1035
|
if (reconnectTimer) return;
|
|
914
1036
|
const window_ms = mergedConfig.reconnectWindowMs ?? 3e5;
|
|
915
|
-
|
|
1037
|
+
const elapsed_ms = Date.now() - reconnectWindowStart;
|
|
1038
|
+
if (elapsed_ms > window_ms) {
|
|
916
1039
|
api?.logger.warn(`Auto-reconnect window exhausted after ${reconnectAttempts} attempts`);
|
|
1040
|
+
emitReconnectExhausted(elapsed_ms, window_ms);
|
|
917
1041
|
return;
|
|
918
1042
|
}
|
|
919
1043
|
const base_delay = mergedConfig.reconnectBaseDelayMs ?? 2e3;
|
|
@@ -921,7 +1045,12 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
921
1045
|
const backoff = Math.min(base_delay * Math.pow(2, reconnectAttempts), max_delay);
|
|
922
1046
|
const delay = Math.round(backoff * (0.7 + Math.random() * 0.3));
|
|
923
1047
|
api?.logger.info(`Scheduling auto-reconnect attempt ${reconnectAttempts + 1} in ${delay}ms`);
|
|
924
|
-
api?.emit("error:reconnecting", {
|
|
1048
|
+
api?.emit("error:reconnecting", {
|
|
1049
|
+
attempt: reconnectAttempts + 1,
|
|
1050
|
+
delayMs: delay,
|
|
1051
|
+
elapsedMs: elapsed_ms,
|
|
1052
|
+
windowMs: window_ms
|
|
1053
|
+
});
|
|
925
1054
|
reconnectTimer = setTimeout(() => {
|
|
926
1055
|
reconnectTimer = null;
|
|
927
1056
|
void attemptReconnect();
|
|
@@ -934,6 +1063,7 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
934
1063
|
if (reconnectWindowStart === 0) {
|
|
935
1064
|
reconnectWindowStart = Date.now();
|
|
936
1065
|
reconnectResumePosition = video?.currentTime ?? 0;
|
|
1066
|
+
reconnectTriggerError = error;
|
|
937
1067
|
}
|
|
938
1068
|
scheduleReconnectAttempt();
|
|
939
1069
|
};
|
|
@@ -965,7 +1095,10 @@ function createHLSPluginWith(loader, variant, config) {
|
|
|
965
1095
|
}
|
|
966
1096
|
api.setState("playbackState", "ready");
|
|
967
1097
|
api.setState("buffering", false);
|
|
968
|
-
api.emit("error:recovered",
|
|
1098
|
+
api.emit("error:recovered", {
|
|
1099
|
+
attempt: reconnectAttempts,
|
|
1100
|
+
elapsedMs: Date.now() - reconnectWindowStart
|
|
1101
|
+
});
|
|
969
1102
|
api.logger.info("Auto-reconnect succeeded");
|
|
970
1103
|
cancelReconnect();
|
|
971
1104
|
try {
|
|
@@ -1256,5 +1389,6 @@ function createHLSPlugin(config) {
|
|
|
1256
1389
|
var light_default = createHLSPlugin;
|
|
1257
1390
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1258
1391
|
0 && (module.exports = {
|
|
1259
|
-
createHLSPlugin
|
|
1392
|
+
createHLSPlugin,
|
|
1393
|
+
sanitizeUrl
|
|
1260
1394
|
});
|
package/dist/light.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-DcZXD_K-.cjs';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-DcZXD_K-.cjs';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/light.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { H as HLSPluginConfig, I as IHLSPlugin } from './
|
|
2
|
-
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel } from './
|
|
1
|
+
import { H as HLSPluginConfig, I as IHLSPlugin } from './sanitize-url-DcZXD_K-.js';
|
|
2
|
+
export { b as HLSError, c as HLSLiveInfo, a as HLSQualityLevel, s as sanitizeUrl } from './sanitize-url-DcZXD_K-.js';
|
|
3
3
|
import '@scarlett-player/core';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/light.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
__export,
|
|
3
|
-
createHLSPluginWith
|
|
4
|
-
|
|
3
|
+
createHLSPluginWith,
|
|
4
|
+
sanitizeUrl
|
|
5
|
+
} from "./chunk-NU6WD6KW.js";
|
|
5
6
|
|
|
6
7
|
// src/hls-loader-light.ts
|
|
7
8
|
var hls_loader_light_exports = {};
|
|
@@ -93,5 +94,6 @@ function createHLSPlugin(config) {
|
|
|
93
94
|
var light_default = createHLSPlugin;
|
|
94
95
|
export {
|
|
95
96
|
createHLSPlugin,
|
|
96
|
-
light_default as default
|
|
97
|
+
light_default as default,
|
|
98
|
+
sanitizeUrl
|
|
97
99
|
};
|
|
@@ -22,9 +22,19 @@ interface HLSPluginConfig {
|
|
|
22
22
|
backBufferLength?: number;
|
|
23
23
|
/** Enable worker for hls.js (better performance) */
|
|
24
24
|
enableWorker?: boolean;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Max network error retries before giving up (default: 3).
|
|
27
|
+
*
|
|
28
|
+
* Governs both playback branches: hls.js retries the load, and the native
|
|
29
|
+
* (Safari/iOS) path reloads the source and restores position.
|
|
30
|
+
*/
|
|
26
31
|
maxNetworkRetries?: number;
|
|
27
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Max media error retries before giving up (default: 2).
|
|
34
|
+
*
|
|
35
|
+
* Governs both playback branches: hls.js calls recoverMediaError(), and the
|
|
36
|
+
* native (Safari/iOS) path reloads the source and restores position.
|
|
37
|
+
*/
|
|
28
38
|
maxMediaRetries?: number;
|
|
29
39
|
/** Cap quality to player element dimensions (default: true) */
|
|
30
40
|
capLevelToPlayerSize?: boolean;
|
|
@@ -128,4 +138,29 @@ interface IHLSPlugin extends Plugin<HLSPluginConfig> {
|
|
|
128
138
|
switchToHlsJs(): Promise<void>;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
/**
|
|
142
|
+
* URL sanitizer for error telemetry.
|
|
143
|
+
*
|
|
144
|
+
* Segment and playlist URLs are the single most useful field when
|
|
145
|
+
* diagnosing a dead stream, and the single most dangerous one to hand to a
|
|
146
|
+
* consumer's telemetry: signed-URL tokens, HMAC signatures, and session ids
|
|
147
|
+
* all live in the query string. Stripping the whole query (and fragment) is
|
|
148
|
+
* the privacy-safe default for EVERY consumer; anyone who genuinely needs
|
|
149
|
+
* the parameters can subscribe to hls.js directly.
|
|
150
|
+
*/
|
|
151
|
+
/**
|
|
152
|
+
* Reduce a URL to origin + pathname, dropping the query string and fragment.
|
|
153
|
+
*
|
|
154
|
+
* @param url - Raw URL from a provider error, if any
|
|
155
|
+
* @returns Sanitized `origin + pathname`, or undefined when there is
|
|
156
|
+
* nothing usable to report (absent or unparseable URL)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* sanitizeUrl('https://cdn.example.com/live/x.m3u8?token=secret#frag');
|
|
161
|
+
* // 'https://cdn.example.com/live/x.m3u8'
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
declare function sanitizeUrl(url: string | undefined | null): string | undefined;
|
|
165
|
+
|
|
166
|
+
export { type HLSPluginConfig as H, type IHLSPlugin as I, type HLSQualityLevel as a, type HLSError as b, type HLSLiveInfo as c, sanitizeUrl as s };
|
|
@@ -22,9 +22,19 @@ interface HLSPluginConfig {
|
|
|
22
22
|
backBufferLength?: number;
|
|
23
23
|
/** Enable worker for hls.js (better performance) */
|
|
24
24
|
enableWorker?: boolean;
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Max network error retries before giving up (default: 3).
|
|
27
|
+
*
|
|
28
|
+
* Governs both playback branches: hls.js retries the load, and the native
|
|
29
|
+
* (Safari/iOS) path reloads the source and restores position.
|
|
30
|
+
*/
|
|
26
31
|
maxNetworkRetries?: number;
|
|
27
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Max media error retries before giving up (default: 2).
|
|
34
|
+
*
|
|
35
|
+
* Governs both playback branches: hls.js calls recoverMediaError(), and the
|
|
36
|
+
* native (Safari/iOS) path reloads the source and restores position.
|
|
37
|
+
*/
|
|
28
38
|
maxMediaRetries?: number;
|
|
29
39
|
/** Cap quality to player element dimensions (default: true) */
|
|
30
40
|
capLevelToPlayerSize?: boolean;
|
|
@@ -128,4 +138,29 @@ interface IHLSPlugin extends Plugin<HLSPluginConfig> {
|
|
|
128
138
|
switchToHlsJs(): Promise<void>;
|
|
129
139
|
}
|
|
130
140
|
|
|
131
|
-
|
|
141
|
+
/**
|
|
142
|
+
* URL sanitizer for error telemetry.
|
|
143
|
+
*
|
|
144
|
+
* Segment and playlist URLs are the single most useful field when
|
|
145
|
+
* diagnosing a dead stream, and the single most dangerous one to hand to a
|
|
146
|
+
* consumer's telemetry: signed-URL tokens, HMAC signatures, and session ids
|
|
147
|
+
* all live in the query string. Stripping the whole query (and fragment) is
|
|
148
|
+
* the privacy-safe default for EVERY consumer; anyone who genuinely needs
|
|
149
|
+
* the parameters can subscribe to hls.js directly.
|
|
150
|
+
*/
|
|
151
|
+
/**
|
|
152
|
+
* Reduce a URL to origin + pathname, dropping the query string and fragment.
|
|
153
|
+
*
|
|
154
|
+
* @param url - Raw URL from a provider error, if any
|
|
155
|
+
* @returns Sanitized `origin + pathname`, or undefined when there is
|
|
156
|
+
* nothing usable to report (absent or unparseable URL)
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```ts
|
|
160
|
+
* sanitizeUrl('https://cdn.example.com/live/x.m3u8?token=secret#frag');
|
|
161
|
+
* // 'https://cdn.example.com/live/x.m3u8'
|
|
162
|
+
* ```
|
|
163
|
+
*/
|
|
164
|
+
declare function sanitizeUrl(url: string | undefined | null): string | undefined;
|
|
165
|
+
|
|
166
|
+
export { type HLSPluginConfig as H, type IHLSPlugin as I, type HLSQualityLevel as a, type HLSError as b, type HLSLiveInfo as c, sanitizeUrl as s };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scarlett-player/hls",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "HLS Provider Plugin for Scarlett Player",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"vitest": "^1.6.0",
|
|
38
38
|
"@vitest/coverage-v8": "^1.6.0",
|
|
39
39
|
"jsdom": "^24.0.0",
|
|
40
|
-
"@scarlett-player/core": "1.
|
|
40
|
+
"@scarlett-player/core": "1.7.0"
|
|
41
41
|
},
|
|
42
42
|
"keywords": [
|
|
43
43
|
"video",
|