@codenkay/video-nsgplayer-core 0.1.3 → 1.0.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/CHANGELOG.md +43 -5
- package/dist/index.cjs +329 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -12
- package/dist/index.d.ts +56 -12
- package/dist/index.js +327 -77
- package/dist/index.js.map +1 -1
- package/package.json +14 -3
package/dist/index.js
CHANGED
|
@@ -146,7 +146,7 @@ var AuthManager = class {
|
|
|
146
146
|
|
|
147
147
|
// src/config/defaults.ts
|
|
148
148
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
149
|
-
var PACKAGE_VERSION = "0.1.
|
|
149
|
+
var PACKAGE_VERSION = "0.1.8";
|
|
150
150
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
151
151
|
autoplay: true,
|
|
152
152
|
startMuted: true,
|
|
@@ -239,7 +239,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
|
|
|
239
239
|
"gettoken",
|
|
240
240
|
"getsource",
|
|
241
241
|
"refreshsource",
|
|
242
|
-
"keyproxyurlbuilder"
|
|
242
|
+
"keyproxyurlbuilder",
|
|
243
|
+
"getkeyaccesstoken"
|
|
243
244
|
]);
|
|
244
245
|
var SECRET_SCAN_MAX_DEPTH = 32;
|
|
245
246
|
function isPlainObject(value) {
|
|
@@ -323,7 +324,8 @@ function mergePlayerConfig(partial) {
|
|
|
323
324
|
...DEFAULT_PLAYER_CONFIG.streaming.hls,
|
|
324
325
|
...partial?.streaming?.hls
|
|
325
326
|
},
|
|
326
|
-
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
|
|
327
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
|
|
328
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken
|
|
327
329
|
},
|
|
328
330
|
logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
|
|
329
331
|
debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
|
|
@@ -404,23 +406,25 @@ function isPlainObject2(value) {
|
|
|
404
406
|
const proto = Object.getPrototypeOf(value);
|
|
405
407
|
return proto === Object.prototype || proto === null;
|
|
406
408
|
}
|
|
407
|
-
function
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
return url.toString();
|
|
419
|
-
} catch {
|
|
420
|
-
return "[REDACTED_URL]";
|
|
409
|
+
function redactStringUrl(value) {
|
|
410
|
+
if (!/^https?:\/\//i.test(value) || !/[?&](token|expires)=/i.test(value)) {
|
|
411
|
+
return value;
|
|
412
|
+
}
|
|
413
|
+
try {
|
|
414
|
+
const url = new URL(value);
|
|
415
|
+
for (const param of ["token", "expires", "signature", "sig"]) {
|
|
416
|
+
if (url.searchParams.has(param)) {
|
|
417
|
+
url.searchParams.set(param, "[REDACTED]");
|
|
421
418
|
}
|
|
422
419
|
}
|
|
420
|
+
return url.toString();
|
|
421
|
+
} catch {
|
|
422
|
+
return "[REDACTED_URL]";
|
|
423
423
|
}
|
|
424
|
+
}
|
|
425
|
+
function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
|
|
426
|
+
if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
|
|
427
|
+
if (typeof value === "string") return redactStringUrl(value);
|
|
424
428
|
if (Array.isArray(value)) {
|
|
425
429
|
if (depth >= REDACT_MAX_DEPTH || seen.has(value)) return "[REDACTED_CYCLE]";
|
|
426
430
|
seen.add(value);
|
|
@@ -687,10 +691,11 @@ var RefreshManager = class {
|
|
|
687
691
|
this.options.logger.debug("Playback source renewed", { reason });
|
|
688
692
|
} catch (error) {
|
|
689
693
|
this.consecutiveFailures += 1;
|
|
690
|
-
const message = error
|
|
694
|
+
const message = refreshErrorMessage(error);
|
|
691
695
|
this.options.logger.warn("Playback refresh failed", {
|
|
692
696
|
reason,
|
|
693
|
-
failure: this.consecutiveFailures
|
|
697
|
+
failure: this.consecutiveFailures,
|
|
698
|
+
message
|
|
694
699
|
});
|
|
695
700
|
if (this.consecutiveFailures >= this.options.renewMaxRetries) {
|
|
696
701
|
this.options.onFatal?.(message);
|
|
@@ -710,6 +715,13 @@ var RefreshManager = class {
|
|
|
710
715
|
}
|
|
711
716
|
}
|
|
712
717
|
};
|
|
718
|
+
function refreshErrorMessage(error) {
|
|
719
|
+
if (error instanceof Error && error.message) return error.message;
|
|
720
|
+
if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message) {
|
|
721
|
+
return error.message;
|
|
722
|
+
}
|
|
723
|
+
return "Playback refresh failed";
|
|
724
|
+
}
|
|
713
725
|
|
|
714
726
|
// src/playback/liveEdge.ts
|
|
715
727
|
var DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS = 3;
|
|
@@ -753,7 +765,37 @@ function seekToLiveEdge(media, options) {
|
|
|
753
765
|
}
|
|
754
766
|
|
|
755
767
|
// src/streaming/authParamsLoader.ts
|
|
756
|
-
|
|
768
|
+
import { XhrLoader as BuiltinXhrLoader } from "hls.js";
|
|
769
|
+
var ENC_KEY_PATH_RE = /enc\.key$/i;
|
|
770
|
+
var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
|
|
771
|
+
function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
772
|
+
try {
|
|
773
|
+
return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
|
|
774
|
+
} catch {
|
|
775
|
+
return /enc\.key/i.test(rawUrl);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function resolveBuiltinHlsLoader(HlsClass) {
|
|
779
|
+
if (typeof BuiltinXhrLoader === "function") {
|
|
780
|
+
return BuiltinXhrLoader;
|
|
781
|
+
}
|
|
782
|
+
const start = HlsClass.DefaultConfig.loader;
|
|
783
|
+
let current = start;
|
|
784
|
+
const seen = /* @__PURE__ */ new Set();
|
|
785
|
+
while (current && !seen.has(current)) {
|
|
786
|
+
seen.add(current);
|
|
787
|
+
const name = typeof current.name === "string" ? current.name : "";
|
|
788
|
+
if (BUILTIN_LOADER_NAMES.has(name)) {
|
|
789
|
+
return current;
|
|
790
|
+
}
|
|
791
|
+
const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
|
|
792
|
+
if (!parentCtor || parentCtor === Object || parentCtor === Function) {
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
current = parentCtor;
|
|
796
|
+
}
|
|
797
|
+
return start;
|
|
798
|
+
}
|
|
757
799
|
function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
758
800
|
if (!rawUrl) return rawUrl;
|
|
759
801
|
const parsed = new URL(rawUrl, baseOrigin);
|
|
@@ -764,28 +806,161 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
|
|
|
764
806
|
videoId: options.videoId,
|
|
765
807
|
keyUrl: parsed.toString(),
|
|
766
808
|
token: snapshot.token,
|
|
767
|
-
expires: snapshot.expires
|
|
809
|
+
expires: snapshot.expires,
|
|
810
|
+
username: snapshot.username,
|
|
811
|
+
mobile: snapshot.mobile
|
|
768
812
|
});
|
|
769
813
|
}
|
|
770
814
|
if (!store.hasParams()) return parsed.toString();
|
|
771
815
|
return store.applyToUrl(rawUrl, baseOrigin);
|
|
772
816
|
}
|
|
817
|
+
function createLoaderStats() {
|
|
818
|
+
const now = performance.now();
|
|
819
|
+
return {
|
|
820
|
+
aborted: false,
|
|
821
|
+
loaded: 0,
|
|
822
|
+
retry: 0,
|
|
823
|
+
total: 0,
|
|
824
|
+
chunkCount: 0,
|
|
825
|
+
bwEstimate: 0,
|
|
826
|
+
loading: { start: now, first: 0, end: 0 },
|
|
827
|
+
parsing: { start: 0, end: 0 },
|
|
828
|
+
buffering: { start: 0, first: 0, end: 0 }
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
|
|
832
|
+
const stats = createLoaderStats();
|
|
833
|
+
stats.loading.first = performance.now();
|
|
834
|
+
const headers = {
|
|
835
|
+
Accept: "*/*",
|
|
836
|
+
...context.headers
|
|
837
|
+
};
|
|
838
|
+
if (accessToken) {
|
|
839
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
840
|
+
}
|
|
841
|
+
const response = await fetch(context.url, {
|
|
842
|
+
method: "GET",
|
|
843
|
+
headers,
|
|
844
|
+
credentials: "omit",
|
|
845
|
+
cache: "no-store",
|
|
846
|
+
signal
|
|
847
|
+
});
|
|
848
|
+
if (!response.ok) {
|
|
849
|
+
callbacks.onError(
|
|
850
|
+
{
|
|
851
|
+
code: response.status,
|
|
852
|
+
text: response.statusText || `enc.key HTTP ${response.status}`
|
|
853
|
+
},
|
|
854
|
+
context,
|
|
855
|
+
response,
|
|
856
|
+
stats
|
|
857
|
+
);
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
const data = await response.arrayBuffer();
|
|
861
|
+
const end = performance.now();
|
|
862
|
+
stats.loading.end = end;
|
|
863
|
+
stats.loaded = data.byteLength;
|
|
864
|
+
stats.total = data.byteLength;
|
|
865
|
+
const head = new TextDecoder().decode(data.slice(0, 32));
|
|
866
|
+
if (/<!DOCTYPE|<html/i.test(head)) {
|
|
867
|
+
callbacks.onError(
|
|
868
|
+
{
|
|
869
|
+
code: 403,
|
|
870
|
+
text: "enc.key returned HTML (CDN challenge/block), not an AES key"
|
|
871
|
+
},
|
|
872
|
+
context,
|
|
873
|
+
response,
|
|
874
|
+
stats
|
|
875
|
+
);
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
callbacks.onSuccess(
|
|
879
|
+
{
|
|
880
|
+
url: response.url || context.url,
|
|
881
|
+
data,
|
|
882
|
+
code: response.status
|
|
883
|
+
},
|
|
884
|
+
stats,
|
|
885
|
+
context,
|
|
886
|
+
response
|
|
887
|
+
);
|
|
888
|
+
}
|
|
773
889
|
function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
774
|
-
const BaseLoader = HlsClass
|
|
775
|
-
return class AuthParamsLoader
|
|
890
|
+
const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
|
|
891
|
+
return class AuthParamsLoader {
|
|
892
|
+
constructor(config) {
|
|
893
|
+
this.keyAbort = null;
|
|
894
|
+
this.inner = new BaseLoader(config);
|
|
895
|
+
this.stats = this.inner.stats;
|
|
896
|
+
}
|
|
897
|
+
abort() {
|
|
898
|
+
this.keyAbort?.abort();
|
|
899
|
+
this.keyAbort = null;
|
|
900
|
+
this.inner.abort?.();
|
|
901
|
+
}
|
|
902
|
+
destroy() {
|
|
903
|
+
this.keyAbort?.abort();
|
|
904
|
+
this.keyAbort = null;
|
|
905
|
+
this.inner.destroy();
|
|
906
|
+
}
|
|
776
907
|
load(context, config, callbacks) {
|
|
777
|
-
if (context.url) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
908
|
+
if (!context.url) {
|
|
909
|
+
this.inner.load(context, config, callbacks);
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
try {
|
|
913
|
+
const originalUrl = context.url;
|
|
914
|
+
const isKey = isEncryptionKeyUrl(originalUrl);
|
|
915
|
+
const usingKeyProxy = Boolean(
|
|
916
|
+
isKey && options.keyProxyUrlBuilder && options.videoId
|
|
917
|
+
);
|
|
918
|
+
context.url = rewriteHlsRequestUrl(originalUrl, store, options);
|
|
919
|
+
if (usingKeyProxy) {
|
|
920
|
+
this.inner.load(context, config, callbacks);
|
|
921
|
+
return;
|
|
781
922
|
}
|
|
923
|
+
if (isKey && typeof options.getKeyAccessToken === "function") {
|
|
924
|
+
const accessToken = options.getKeyAccessToken() ?? null;
|
|
925
|
+
if (accessToken) {
|
|
926
|
+
this.keyAbort?.abort();
|
|
927
|
+
this.keyAbort = new AbortController();
|
|
928
|
+
const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
|
|
929
|
+
const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
|
|
930
|
+
void loadEncryptionKeyWithFetch(
|
|
931
|
+
context,
|
|
932
|
+
callbacks,
|
|
933
|
+
accessToken,
|
|
934
|
+
this.keyAbort.signal
|
|
935
|
+
).catch((error) => {
|
|
936
|
+
const stats = createLoaderStats();
|
|
937
|
+
const aborted = error instanceof DOMException && error.name === "AbortError";
|
|
938
|
+
callbacks.onError(
|
|
939
|
+
{
|
|
940
|
+
code: 0,
|
|
941
|
+
text: aborted ? "enc.key fetch aborted/timeout" : error instanceof Error ? error.message : "enc.key fetch failed"
|
|
942
|
+
},
|
|
943
|
+
context,
|
|
944
|
+
null,
|
|
945
|
+
stats
|
|
946
|
+
);
|
|
947
|
+
}).finally(() => {
|
|
948
|
+
clearTimeout(timer);
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
} catch {
|
|
782
954
|
}
|
|
783
|
-
|
|
955
|
+
this.inner.load(context, config, callbacks);
|
|
784
956
|
}
|
|
785
957
|
};
|
|
786
958
|
}
|
|
787
959
|
|
|
788
960
|
// src/streaming/HlsEngine.ts
|
|
961
|
+
function isAuthHttpStatus(code) {
|
|
962
|
+
return code === 401 || code === 403;
|
|
963
|
+
}
|
|
789
964
|
var HlsEngine = class {
|
|
790
965
|
constructor(options) {
|
|
791
966
|
this.options = options;
|
|
@@ -798,49 +973,47 @@ var HlsEngine = class {
|
|
|
798
973
|
this.qualitySelection = "auto";
|
|
799
974
|
}
|
|
800
975
|
async start() {
|
|
801
|
-
const { videoEl, playbackUrl,
|
|
976
|
+
const { videoEl, playbackUrl, startMuted, volume } = this.options;
|
|
802
977
|
videoEl.muted = startMuted;
|
|
803
|
-
if (typeof volume === "number")
|
|
978
|
+
if (typeof volume === "number") {
|
|
979
|
+
videoEl.volume = Math.min(1, Math.max(0, volume));
|
|
980
|
+
}
|
|
804
981
|
const Hls = (await import("hls.js")).default;
|
|
805
982
|
if (Hls.isSupported()) {
|
|
806
983
|
this.supportMode = "hls.js";
|
|
807
984
|
this.options.callbacks.onSupportMode?.("hls.js");
|
|
808
985
|
await this.startWithHlsJs(Hls, playbackUrl);
|
|
809
|
-
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
|
|
810
989
|
this.supportMode = "native";
|
|
811
990
|
this.options.callbacks.onSupportMode?.("native");
|
|
812
991
|
videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
|
|
813
992
|
this.bindMediaEvents();
|
|
814
993
|
this.options.callbacks.onStateChange?.("ready");
|
|
815
|
-
|
|
816
|
-
this.supportMode = "unsupported";
|
|
817
|
-
this.options.callbacks.onSupportMode?.("unsupported");
|
|
818
|
-
this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
|
|
994
|
+
await this.applyStartPositionAndAutoplay();
|
|
819
995
|
return;
|
|
820
996
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
await videoEl.play();
|
|
827
|
-
} catch {
|
|
828
|
-
if (!videoEl.muted) {
|
|
829
|
-
videoEl.muted = true;
|
|
830
|
-
try {
|
|
831
|
-
await videoEl.play();
|
|
832
|
-
} catch {
|
|
833
|
-
this.options.logger.warn("Autoplay blocked");
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
997
|
+
this.supportMode = "unsupported";
|
|
998
|
+
this.options.callbacks.onSupportMode?.("unsupported");
|
|
999
|
+
this.options.callbacks.onFatalError?.(
|
|
1000
|
+
"HLS is not supported in this browser"
|
|
1001
|
+
);
|
|
838
1002
|
}
|
|
839
1003
|
async startWithHlsJs(Hls, playbackUrl) {
|
|
840
|
-
const {
|
|
1004
|
+
const {
|
|
1005
|
+
videoEl,
|
|
1006
|
+
authParams,
|
|
1007
|
+
hlsConfig,
|
|
1008
|
+
callbacks,
|
|
1009
|
+
videoId,
|
|
1010
|
+
keyProxyUrlBuilder,
|
|
1011
|
+
getKeyAccessToken
|
|
1012
|
+
} = this.options;
|
|
841
1013
|
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
842
1014
|
videoId,
|
|
843
|
-
keyProxyUrlBuilder
|
|
1015
|
+
keyProxyUrlBuilder,
|
|
1016
|
+
getKeyAccessToken
|
|
844
1017
|
});
|
|
845
1018
|
this.hls = new Hls({
|
|
846
1019
|
enableWorker: hlsConfig.enableWorker,
|
|
@@ -851,6 +1024,7 @@ var HlsEngine = class {
|
|
|
851
1024
|
this.hls.attachMedia(videoEl);
|
|
852
1025
|
this.hls.loadSource(authParams.applyToUrl(playbackUrl));
|
|
853
1026
|
this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
|
|
1027
|
+
if (this.destroyed) return;
|
|
854
1028
|
this.qualities = (data.levels ?? []).map((level, index) => ({
|
|
855
1029
|
index,
|
|
856
1030
|
height: level.height ?? 0,
|
|
@@ -860,42 +1034,100 @@ var HlsEngine = class {
|
|
|
860
1034
|
}));
|
|
861
1035
|
callbacks.onQualitiesChange?.(this.qualities);
|
|
862
1036
|
callbacks.onStateChange?.("ready");
|
|
1037
|
+
void this.applyStartPositionAndAutoplay();
|
|
863
1038
|
});
|
|
864
1039
|
this.hls.on(Hls.Events.ERROR, (_event, data) => {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
1040
|
+
this.handleHlsError(Hls, data);
|
|
1041
|
+
});
|
|
1042
|
+
this.bindMediaEvents();
|
|
1043
|
+
}
|
|
1044
|
+
handleHlsError(Hls, data) {
|
|
1045
|
+
if (this.destroyed) return;
|
|
1046
|
+
const { callbacks, hlsConfig } = this.options;
|
|
1047
|
+
const status = data.response?.code;
|
|
1048
|
+
const authFailure = isAuthHttpStatus(status);
|
|
1049
|
+
if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR) {
|
|
1050
|
+
if (authFailure) {
|
|
1051
|
+
callbacks.onAuthRecoveryNeeded?.("key_error");
|
|
1052
|
+
if (!data.fatal) return;
|
|
1053
|
+
} else if (!data.fatal) {
|
|
1054
|
+
callbacks.onFatalError?.(
|
|
1055
|
+
status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS). Ensure signed query params are applied, or use keyProxyUrlBuilder / getKeyAccessToken when the key host requires API Bearer."
|
|
1056
|
+
);
|
|
871
1057
|
return;
|
|
872
1058
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1059
|
+
}
|
|
1060
|
+
if (!data.fatal) {
|
|
1061
|
+
if (authFailure && data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR)) {
|
|
1062
|
+
callbacks.onAuthRecoveryNeeded?.(
|
|
1063
|
+
data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
|
|
1069
|
+
this.networkRecoveryCount += 1;
|
|
1070
|
+
if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
1071
|
+
if (authFailure) {
|
|
876
1072
|
callbacks.onAuthRecoveryNeeded?.("fatal_network");
|
|
877
|
-
this.hls?.startLoad();
|
|
878
|
-
return;
|
|
879
1073
|
}
|
|
1074
|
+
this.hls?.startLoad();
|
|
1075
|
+
return;
|
|
880
1076
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1077
|
+
}
|
|
1078
|
+
if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
|
|
1079
|
+
this.mediaRecoveryCount += 1;
|
|
1080
|
+
if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
|
|
1081
|
+
this.hls?.recoverMediaError();
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
callbacks.onFatalError?.(data.details || "Fatal HLS error");
|
|
1086
|
+
}
|
|
1087
|
+
async applyStartPositionAndAutoplay() {
|
|
1088
|
+
if (this.destroyed) return;
|
|
1089
|
+
const { videoEl, autoplay, startPosition } = this.options;
|
|
1090
|
+
if (typeof startPosition === "number" && startPosition > 0) {
|
|
1091
|
+
const seek = () => {
|
|
1092
|
+
if (this.destroyed) return;
|
|
1093
|
+
try {
|
|
1094
|
+
videoEl.currentTime = startPosition;
|
|
1095
|
+
} catch {
|
|
886
1096
|
}
|
|
1097
|
+
};
|
|
1098
|
+
if (videoEl.seekable.length > 0) {
|
|
1099
|
+
seek();
|
|
1100
|
+
} else {
|
|
1101
|
+
videoEl.addEventListener("loadedmetadata", seek, { once: true });
|
|
887
1102
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
1103
|
+
}
|
|
1104
|
+
if (!autoplay) return;
|
|
1105
|
+
try {
|
|
1106
|
+
await videoEl.play();
|
|
1107
|
+
} catch {
|
|
1108
|
+
if (!videoEl.muted) {
|
|
1109
|
+
videoEl.muted = true;
|
|
1110
|
+
try {
|
|
1111
|
+
await videoEl.play();
|
|
1112
|
+
} catch {
|
|
1113
|
+
this.options.logger.warn("Autoplay blocked");
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
891
1117
|
}
|
|
892
1118
|
bindMediaEvents() {
|
|
893
1119
|
const { videoEl, callbacks } = this.options;
|
|
894
|
-
videoEl.addEventListener(
|
|
1120
|
+
videoEl.addEventListener(
|
|
1121
|
+
"playing",
|
|
1122
|
+
() => callbacks.onStateChange?.("playing")
|
|
1123
|
+
);
|
|
895
1124
|
videoEl.addEventListener("pause", () => {
|
|
896
1125
|
if (!videoEl.ended) callbacks.onStateChange?.("paused");
|
|
897
1126
|
});
|
|
898
|
-
videoEl.addEventListener(
|
|
1127
|
+
videoEl.addEventListener(
|
|
1128
|
+
"waiting",
|
|
1129
|
+
() => callbacks.onStateChange?.("buffering")
|
|
1130
|
+
);
|
|
899
1131
|
videoEl.addEventListener("ended", () => {
|
|
900
1132
|
callbacks.onStateChange?.("ended");
|
|
901
1133
|
callbacks.onEnded?.();
|
|
@@ -913,6 +1145,7 @@ var HlsEngine = class {
|
|
|
913
1145
|
}
|
|
914
1146
|
}
|
|
915
1147
|
resumeAfterAuthRenew() {
|
|
1148
|
+
this.networkRecoveryCount = 0;
|
|
916
1149
|
this.hls?.startLoad();
|
|
917
1150
|
this.updateAuthParams();
|
|
918
1151
|
}
|
|
@@ -1055,7 +1288,13 @@ function createVideoPlayer(options) {
|
|
|
1055
1288
|
startPosition: config.player.startPosition,
|
|
1056
1289
|
volume: config.player.volume,
|
|
1057
1290
|
videoId: options.videoId,
|
|
1058
|
-
keyProxyUrlBuilder:
|
|
1291
|
+
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1292
|
+
// Optional: browser Bearer for Video API–origin enc.key (CORS-dependent).
|
|
1293
|
+
// CDN keys use signed query via the auth loader (no Bearer) — same as
|
|
1294
|
+
// segments. Apps can pass getKeyAccessToken or rely on keyProxyUrlBuilder.
|
|
1295
|
+
// When unset, defaults to AuthManager token only if explicitly requested
|
|
1296
|
+
// via streaming.getKeyAccessToken; otherwise query-signed XHR.
|
|
1297
|
+
getKeyAccessToken: config.streaming.getKeyAccessToken,
|
|
1059
1298
|
hlsConfig: config.streaming.hls,
|
|
1060
1299
|
logger,
|
|
1061
1300
|
callbacks: {
|
|
@@ -1116,6 +1355,14 @@ function createVideoPlayer(options) {
|
|
|
1116
1355
|
onRenewing: () => setState("renewing"),
|
|
1117
1356
|
onRenewed: (nextExpiresAt) => {
|
|
1118
1357
|
engine?.resumeAfterAuthRenew();
|
|
1358
|
+
if (!destroyed) {
|
|
1359
|
+
const el2 = videoEl;
|
|
1360
|
+
if (el2 && !el2.paused && !el2.ended) {
|
|
1361
|
+
setState("playing");
|
|
1362
|
+
} else {
|
|
1363
|
+
setState("ready");
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1119
1366
|
events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
|
|
1120
1367
|
events.emit("sourcechange", { expiresAt: nextExpiresAt });
|
|
1121
1368
|
},
|
|
@@ -1317,7 +1564,8 @@ function createVideoPlayer(options) {
|
|
|
1317
1564
|
streaming: {
|
|
1318
1565
|
engine: "hls",
|
|
1319
1566
|
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1320
|
-
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
|
|
1567
|
+
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
|
|
1568
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
|
|
1321
1569
|
},
|
|
1322
1570
|
logging: { ...config.logging, ...partial?.logging },
|
|
1323
1571
|
debug: { ...config.debug, ...partial?.debug },
|
|
@@ -1360,11 +1608,13 @@ export {
|
|
|
1360
1608
|
createVideoPlayer,
|
|
1361
1609
|
getLiveEdgeTime,
|
|
1362
1610
|
isAtLiveEdge,
|
|
1611
|
+
isEncryptionKeyUrl,
|
|
1363
1612
|
isPlainObject,
|
|
1364
1613
|
mergePlayerConfig,
|
|
1365
1614
|
redactObject,
|
|
1366
1615
|
redactSensitive,
|
|
1367
1616
|
redactValue,
|
|
1617
|
+
resolveBuiltinHlsLoader,
|
|
1368
1618
|
rewriteHlsRequestUrl,
|
|
1369
1619
|
seekToLiveEdge,
|
|
1370
1620
|
themeToCssVars
|