@codenkay/video-nsgplayer-core 3.0.0 → 3.0.1
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 +8 -0
- package/dist/index.cjs +190 -83
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +24 -11
- package/dist/index.d.ts +24 -11
- package/dist/index.js +189 -83
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
- Permanent AES key path: dedicated key loader with **session circuit breaker**
|
|
6
|
+
(`keyProxyMaxFailures` default **2**). A Cloudflare-blocked BFF is not hammered
|
|
7
|
+
by hls.js retries (previously 9–10× `/hls-key` 502s).
|
|
8
|
+
- After the circuit opens, the session uses AuthManager Bearer (or signed-query XHR) only.
|
|
9
|
+
- Host apps with `AUTH_MODE=browser` should omit `keyProxyUrlBuilder` entirely.
|
|
10
|
+
|
|
3
11
|
## 3.0.0
|
|
4
12
|
|
|
5
13
|
### Major Changes
|
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
DEFAULT_THEME: () => DEFAULT_THEME,
|
|
40
40
|
DEFAULT_WATERMARK: () => DEFAULT_WATERMARK,
|
|
41
41
|
EventEmitter: () => EventEmitter,
|
|
42
|
+
KEY_PROXY_MAX_FAILURES: () => KEY_PROXY_MAX_FAILURES,
|
|
42
43
|
PACKAGE_VERSION: () => PACKAGE_VERSION,
|
|
43
44
|
PLAYBACK_RATES: () => PLAYBACK_RATES,
|
|
44
45
|
PlaybackSourceManager: () => PlaybackSourceManager,
|
|
@@ -212,7 +213,7 @@ var AuthManager = class {
|
|
|
212
213
|
|
|
213
214
|
// src/config/defaults.ts
|
|
214
215
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
215
|
-
var PACKAGE_VERSION = "
|
|
216
|
+
var PACKAGE_VERSION = "3.0.1";
|
|
216
217
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
217
218
|
autoplay: true,
|
|
218
219
|
startMuted: true,
|
|
@@ -391,7 +392,8 @@ function mergePlayerConfig(partial) {
|
|
|
391
392
|
...partial?.streaming?.hls
|
|
392
393
|
},
|
|
393
394
|
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
|
|
394
|
-
getKeyAccessToken: partial?.streaming?.getKeyAccessToken
|
|
395
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken,
|
|
396
|
+
keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures
|
|
395
397
|
},
|
|
396
398
|
logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
|
|
397
399
|
debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
|
|
@@ -834,6 +836,7 @@ function seekToLiveEdge(media, options) {
|
|
|
834
836
|
var import_hls = require("hls.js");
|
|
835
837
|
var ENC_KEY_PATH_RE = /enc\.key$/i;
|
|
836
838
|
var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
|
|
839
|
+
var KEY_PROXY_MAX_FAILURES = 2;
|
|
837
840
|
function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
838
841
|
try {
|
|
839
842
|
return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
|
|
@@ -894,6 +897,20 @@ function createLoaderStats() {
|
|
|
894
897
|
buffering: { start: 0, first: 0, end: 0 }
|
|
895
898
|
};
|
|
896
899
|
}
|
|
900
|
+
function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
|
|
901
|
+
if (status === 0 || status === 502 || status === 503 || status === 504) {
|
|
902
|
+
return true;
|
|
903
|
+
}
|
|
904
|
+
if (status === 403 || status === 401) return true;
|
|
905
|
+
if (contentType?.includes("text/html")) return true;
|
|
906
|
+
if (/<!DOCTYPE|<html|Just a moment|Cloudflare|blocked/i.test(bodyHead)) {
|
|
907
|
+
return true;
|
|
908
|
+
}
|
|
909
|
+
if (errorText && /Cloudflare|HTML|DOCTYPE|blocked|502/i.test(errorText)) {
|
|
910
|
+
return true;
|
|
911
|
+
}
|
|
912
|
+
return false;
|
|
913
|
+
}
|
|
897
914
|
async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
|
|
898
915
|
const stats = createLoaderStats();
|
|
899
916
|
stats.loading.first = performance.now();
|
|
@@ -952,10 +969,122 @@ async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signa
|
|
|
952
969
|
response
|
|
953
970
|
);
|
|
954
971
|
}
|
|
972
|
+
async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
|
|
973
|
+
const stats = createLoaderStats();
|
|
974
|
+
stats.loading.first = performance.now();
|
|
975
|
+
const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
|
|
976
|
+
if (canTryProxy && plan.proxyUrl) {
|
|
977
|
+
while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
|
|
978
|
+
state.proxyFailures += 1;
|
|
979
|
+
try {
|
|
980
|
+
const response = await fetch(plan.proxyUrl, {
|
|
981
|
+
method: "GET",
|
|
982
|
+
headers: { Accept: "*/*" },
|
|
983
|
+
credentials: "same-origin",
|
|
984
|
+
cache: "no-store",
|
|
985
|
+
signal
|
|
986
|
+
});
|
|
987
|
+
const contentType = response.headers.get("content-type");
|
|
988
|
+
const data = await response.arrayBuffer();
|
|
989
|
+
const head = new TextDecoder().decode(data.slice(0, 64));
|
|
990
|
+
if (response.ok && !isProxyUpstreamBlocked(response.status, contentType, head)) {
|
|
991
|
+
const end = performance.now();
|
|
992
|
+
stats.loading.end = end;
|
|
993
|
+
stats.loaded = data.byteLength;
|
|
994
|
+
stats.total = data.byteLength;
|
|
995
|
+
callbacks.onSuccess(
|
|
996
|
+
{
|
|
997
|
+
url: response.url || plan.proxyUrl,
|
|
998
|
+
data,
|
|
999
|
+
code: response.status
|
|
1000
|
+
},
|
|
1001
|
+
stats,
|
|
1002
|
+
{ ...context, url: plan.proxyUrl },
|
|
1003
|
+
response
|
|
1004
|
+
);
|
|
1005
|
+
return "ok";
|
|
1006
|
+
}
|
|
1007
|
+
if (isProxyUpstreamBlocked(
|
|
1008
|
+
response.status,
|
|
1009
|
+
contentType,
|
|
1010
|
+
head,
|
|
1011
|
+
response.statusText
|
|
1012
|
+
)) {
|
|
1013
|
+
if (response.status === 502 || contentType?.includes("text/html") || /Cloudflare|DOCTYPE|blocked/i.test(head)) {
|
|
1014
|
+
state.circuitOpen = true;
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
callbacks.onError(
|
|
1020
|
+
{
|
|
1021
|
+
code: response.status,
|
|
1022
|
+
text: `enc.key proxy HTTP ${response.status}`
|
|
1023
|
+
},
|
|
1024
|
+
{ ...context, url: plan.proxyUrl },
|
|
1025
|
+
response,
|
|
1026
|
+
stats
|
|
1027
|
+
);
|
|
1028
|
+
return "ok";
|
|
1029
|
+
} catch (error) {
|
|
1030
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1031
|
+
callbacks.onError(
|
|
1032
|
+
{ code: 0, text: "enc.key proxy aborted/timeout" },
|
|
1033
|
+
context,
|
|
1034
|
+
null,
|
|
1035
|
+
stats
|
|
1036
|
+
);
|
|
1037
|
+
return "ok";
|
|
1038
|
+
}
|
|
1039
|
+
state.circuitOpen = true;
|
|
1040
|
+
break;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
state.circuitOpen = true;
|
|
1044
|
+
}
|
|
1045
|
+
if (plan.accessToken) {
|
|
1046
|
+
try {
|
|
1047
|
+
await loadEncryptionKeyWithFetch(
|
|
1048
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1049
|
+
callbacks,
|
|
1050
|
+
plan.accessToken,
|
|
1051
|
+
signal
|
|
1052
|
+
);
|
|
1053
|
+
return "ok";
|
|
1054
|
+
} catch (error) {
|
|
1055
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
1056
|
+
callbacks.onError(
|
|
1057
|
+
{ code: 0, text: "enc.key fetch aborted/timeout" },
|
|
1058
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1059
|
+
null,
|
|
1060
|
+
stats
|
|
1061
|
+
);
|
|
1062
|
+
return "ok";
|
|
1063
|
+
}
|
|
1064
|
+
if (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message)) {
|
|
1065
|
+
return "try_xhr";
|
|
1066
|
+
}
|
|
1067
|
+
callbacks.onError(
|
|
1068
|
+
{
|
|
1069
|
+
code: 0,
|
|
1070
|
+
text: error instanceof Error ? error.message : "enc.key Bearer fetch failed"
|
|
1071
|
+
},
|
|
1072
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1073
|
+
null,
|
|
1074
|
+
stats
|
|
1075
|
+
);
|
|
1076
|
+
return "ok";
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
return "try_xhr";
|
|
1080
|
+
}
|
|
955
1081
|
function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
956
1082
|
const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
|
|
1083
|
+
const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
|
|
1084
|
+
const keyProxyState = { proxyFailures: 0, circuitOpen: false };
|
|
957
1085
|
return class AuthParamsLoader {
|
|
958
1086
|
constructor(config) {
|
|
1087
|
+
this.context = null;
|
|
959
1088
|
this.keyAbort = null;
|
|
960
1089
|
this.inner = new BaseLoader(config);
|
|
961
1090
|
this.stats = this.inner.stats;
|
|
@@ -963,14 +1092,16 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
963
1092
|
abort() {
|
|
964
1093
|
this.keyAbort?.abort();
|
|
965
1094
|
this.keyAbort = null;
|
|
966
|
-
this.inner.abort
|
|
1095
|
+
this.inner.abort();
|
|
967
1096
|
}
|
|
968
1097
|
destroy() {
|
|
969
1098
|
this.keyAbort?.abort();
|
|
970
1099
|
this.keyAbort = null;
|
|
1100
|
+
this.context = null;
|
|
971
1101
|
this.inner.destroy();
|
|
972
1102
|
}
|
|
973
1103
|
load(context, config, callbacks) {
|
|
1104
|
+
this.context = context;
|
|
974
1105
|
if (!context.url) {
|
|
975
1106
|
this.inner.load(context, config, callbacks);
|
|
976
1107
|
return;
|
|
@@ -978,87 +1109,59 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
978
1109
|
try {
|
|
979
1110
|
const originalUrl = context.url;
|
|
980
1111
|
const isKey = isEncryptionKeyUrl(originalUrl);
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
}) : context.url;
|
|
988
|
-
context.url = rewriteHlsRequestUrl(originalUrl, store, options);
|
|
989
|
-
if (usingKeyProxy) {
|
|
990
|
-
const accessToken = options.getKeyAccessToken?.() ?? null;
|
|
991
|
-
if (!accessToken) {
|
|
992
|
-
this.inner.load(context, config, callbacks);
|
|
993
|
-
return;
|
|
1112
|
+
if (!isKey) {
|
|
1113
|
+
if (store.hasParams()) {
|
|
1114
|
+
context.url = rewriteHlsRequestUrl(originalUrl, store, {
|
|
1115
|
+
...options,
|
|
1116
|
+
keyProxyUrlBuilder: void 0
|
|
1117
|
+
});
|
|
994
1118
|
}
|
|
995
|
-
|
|
996
|
-
...callbacks,
|
|
997
|
-
onError: (error, errContext, networkDetails, stats) => {
|
|
998
|
-
const code = error?.code;
|
|
999
|
-
const retryable = code === 0 || code === 502 || code === 403 || code === 401 || /Cloudflare|HTML|DOCTYPE|blocked/i.test(error?.text ?? "");
|
|
1000
|
-
if (!retryable) {
|
|
1001
|
-
callbacks.onError(error, errContext, networkDetails, stats);
|
|
1002
|
-
return;
|
|
1003
|
-
}
|
|
1004
|
-
this.keyAbort?.abort();
|
|
1005
|
-
this.keyAbort = new AbortController();
|
|
1006
|
-
const bearerContext = { ...context, url: signedKeyUrl };
|
|
1007
|
-
void loadEncryptionKeyWithFetch(
|
|
1008
|
-
bearerContext,
|
|
1009
|
-
callbacks,
|
|
1010
|
-
accessToken,
|
|
1011
|
-
this.keyAbort.signal
|
|
1012
|
-
).catch((fetchError) => {
|
|
1013
|
-
callbacks.onError(
|
|
1014
|
-
{
|
|
1015
|
-
code: 0,
|
|
1016
|
-
text: fetchError instanceof Error ? fetchError.message : error.text || "enc.key proxy and Bearer fetch failed"
|
|
1017
|
-
},
|
|
1018
|
-
bearerContext,
|
|
1019
|
-
null,
|
|
1020
|
-
stats
|
|
1021
|
-
);
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
};
|
|
1025
|
-
this.inner.load(context, config, proxyCallbacks);
|
|
1119
|
+
this.inner.load(context, config, callbacks);
|
|
1026
1120
|
return;
|
|
1027
1121
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
}).finally(() => {
|
|
1057
|
-
clearTimeout(timer);
|
|
1058
|
-
});
|
|
1059
|
-
return;
|
|
1122
|
+
const signedKeyUrl = rewriteHlsRequestUrl(originalUrl, store, {
|
|
1123
|
+
...options,
|
|
1124
|
+
keyProxyUrlBuilder: void 0
|
|
1125
|
+
});
|
|
1126
|
+
const proxyConfigured = Boolean(
|
|
1127
|
+
options.keyProxyUrlBuilder && options.videoId
|
|
1128
|
+
);
|
|
1129
|
+
const proxyUrl = proxyConfigured && !keyProxyState.circuitOpen ? rewriteHlsRequestUrl(originalUrl, store, options) : null;
|
|
1130
|
+
const accessToken = options.getKeyAccessToken?.() ?? null;
|
|
1131
|
+
this.keyAbort?.abort();
|
|
1132
|
+
this.keyAbort = new AbortController();
|
|
1133
|
+
const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
|
|
1134
|
+
const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
|
|
1135
|
+
void loadEncryptionKeyEnterprise(
|
|
1136
|
+
context,
|
|
1137
|
+
callbacks,
|
|
1138
|
+
{
|
|
1139
|
+
proxyUrl,
|
|
1140
|
+
signedKeyUrl,
|
|
1141
|
+
accessToken,
|
|
1142
|
+
maxProxyFailures
|
|
1143
|
+
},
|
|
1144
|
+
this.keyAbort.signal,
|
|
1145
|
+
keyProxyState
|
|
1146
|
+
).then((result) => {
|
|
1147
|
+
if (result === "try_xhr") {
|
|
1148
|
+
context.url = signedKeyUrl;
|
|
1149
|
+
this.inner.load(context, config, callbacks);
|
|
1060
1150
|
}
|
|
1061
|
-
}
|
|
1151
|
+
}).catch((error) => {
|
|
1152
|
+
callbacks.onError(
|
|
1153
|
+
{
|
|
1154
|
+
code: 0,
|
|
1155
|
+
text: error instanceof Error ? error.message : "enc.key load failed"
|
|
1156
|
+
},
|
|
1157
|
+
context,
|
|
1158
|
+
null,
|
|
1159
|
+
createLoaderStats()
|
|
1160
|
+
);
|
|
1161
|
+
}).finally(() => {
|
|
1162
|
+
clearTimeout(timer);
|
|
1163
|
+
});
|
|
1164
|
+
return;
|
|
1062
1165
|
} catch {
|
|
1063
1166
|
}
|
|
1064
1167
|
this.inner.load(context, config, callbacks);
|
|
@@ -1128,7 +1231,8 @@ var HlsEngine = class {
|
|
|
1128
1231
|
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
1129
1232
|
videoId,
|
|
1130
1233
|
keyProxyUrlBuilder,
|
|
1131
|
-
getKeyAccessToken
|
|
1234
|
+
getKeyAccessToken,
|
|
1235
|
+
keyProxyMaxFailures: this.options.keyProxyMaxFailures
|
|
1132
1236
|
});
|
|
1133
1237
|
this.hls = new Hls({
|
|
1134
1238
|
enableWorker: hlsConfig.enableWorker,
|
|
@@ -1553,10 +1657,11 @@ function createVideoPlayer(options) {
|
|
|
1553
1657
|
videoId: options.videoId,
|
|
1554
1658
|
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1555
1659
|
// Enterprise AES key auth:
|
|
1556
|
-
// 1) keyProxyUrlBuilder (same-origin BFF) when configured
|
|
1660
|
+
// 1) keyProxyUrlBuilder (same-origin BFF) when configured — max 2 failures
|
|
1557
1661
|
// 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
|
|
1558
1662
|
// 3) else signed query + XHR (CDN hosts)
|
|
1559
1663
|
getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
|
|
1664
|
+
keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
|
|
1560
1665
|
hlsConfig: config.streaming.hls,
|
|
1561
1666
|
logger,
|
|
1562
1667
|
callbacks: {
|
|
@@ -1874,7 +1979,8 @@ function createVideoPlayer(options) {
|
|
|
1874
1979
|
engine: "hls",
|
|
1875
1980
|
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1876
1981
|
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
|
|
1877
|
-
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken())
|
|
1982
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
|
|
1983
|
+
keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures ?? config.streaming.keyProxyMaxFailures
|
|
1878
1984
|
},
|
|
1879
1985
|
logging: { ...config.logging, ...partial?.logging },
|
|
1880
1986
|
debug: { ...config.debug, ...partial?.debug },
|
|
@@ -1906,6 +2012,7 @@ function createVideoPlayer(options) {
|
|
|
1906
2012
|
DEFAULT_THEME,
|
|
1907
2013
|
DEFAULT_WATERMARK,
|
|
1908
2014
|
EventEmitter,
|
|
2015
|
+
KEY_PROXY_MAX_FAILURES,
|
|
1909
2016
|
PACKAGE_VERSION,
|
|
1910
2017
|
PLAYBACK_RATES,
|
|
1911
2018
|
PlaybackSourceManager,
|