@codenkay/video-nsgplayer-core 2.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 +20 -0
- package/dist/index.cjs +194 -45
- 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 +193 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
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
|
+
|
|
11
|
+
## 3.0.0
|
|
12
|
+
|
|
13
|
+
### Major Changes
|
|
14
|
+
|
|
15
|
+
- Fix minor issues
|
|
16
|
+
|
|
17
|
+
## 2.0.1
|
|
18
|
+
|
|
19
|
+
- Auto-wire `AuthManager.getAccessToken()` as default `streaming.getKeyAccessToken` so AES `enc.key` can load with Bearer when no BFF key proxy is configured.
|
|
20
|
+
- When `keyProxyUrlBuilder` returns 401/403/502/CORS (e.g. Cloudflare-blocked BFF), automatically fall back to browser Bearer `enc.key` fetch.
|
|
21
|
+
- On Bearer `enc.key` fetch network/CORS failure, fall back to signed-query XHR (CDN hosts).
|
|
22
|
+
|
|
3
23
|
## 2.0.0
|
|
4
24
|
|
|
5
25
|
### Minor 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 = "0.1
|
|
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,44 +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
|
-
|
|
1112
|
+
if (!isKey) {
|
|
1113
|
+
if (store.hasParams()) {
|
|
1114
|
+
context.url = rewriteHlsRequestUrl(originalUrl, store, {
|
|
1115
|
+
...options,
|
|
1116
|
+
keyProxyUrlBuilder: void 0
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
986
1119
|
this.inner.load(context, config, callbacks);
|
|
987
1120
|
return;
|
|
988
1121
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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);
|
|
1017
1150
|
}
|
|
1018
|
-
}
|
|
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;
|
|
1019
1165
|
} catch {
|
|
1020
1166
|
}
|
|
1021
1167
|
this.inner.load(context, config, callbacks);
|
|
@@ -1085,7 +1231,8 @@ var HlsEngine = class {
|
|
|
1085
1231
|
const loader = createAuthParamsLoader(Hls, authParams, {
|
|
1086
1232
|
videoId,
|
|
1087
1233
|
keyProxyUrlBuilder,
|
|
1088
|
-
getKeyAccessToken
|
|
1234
|
+
getKeyAccessToken,
|
|
1235
|
+
keyProxyMaxFailures: this.options.keyProxyMaxFailures
|
|
1089
1236
|
});
|
|
1090
1237
|
this.hls = new Hls({
|
|
1091
1238
|
enableWorker: hlsConfig.enableWorker,
|
|
@@ -1509,12 +1656,12 @@ function createVideoPlayer(options) {
|
|
|
1509
1656
|
volume: config.player.volume,
|
|
1510
1657
|
videoId: options.videoId,
|
|
1511
1658
|
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1512
|
-
//
|
|
1513
|
-
//
|
|
1514
|
-
//
|
|
1515
|
-
//
|
|
1516
|
-
|
|
1517
|
-
|
|
1659
|
+
// Enterprise AES key auth:
|
|
1660
|
+
// 1) keyProxyUrlBuilder (same-origin BFF) when configured — max 2 failures
|
|
1661
|
+
// 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
|
|
1662
|
+
// 3) else signed query + XHR (CDN hosts)
|
|
1663
|
+
getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
|
|
1664
|
+
keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
|
|
1518
1665
|
hlsConfig: config.streaming.hls,
|
|
1519
1666
|
logger,
|
|
1520
1667
|
callbacks: {
|
|
@@ -1832,7 +1979,8 @@ function createVideoPlayer(options) {
|
|
|
1832
1979
|
engine: "hls",
|
|
1833
1980
|
hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
|
|
1834
1981
|
keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
|
|
1835
|
-
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
|
|
1982
|
+
getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
|
|
1983
|
+
keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures ?? config.streaming.keyProxyMaxFailures
|
|
1836
1984
|
},
|
|
1837
1985
|
logging: { ...config.logging, ...partial?.logging },
|
|
1838
1986
|
debug: { ...config.debug, ...partial?.debug },
|
|
@@ -1864,6 +2012,7 @@ function createVideoPlayer(options) {
|
|
|
1864
2012
|
DEFAULT_THEME,
|
|
1865
2013
|
DEFAULT_WATERMARK,
|
|
1866
2014
|
EventEmitter,
|
|
2015
|
+
KEY_PROXY_MAX_FAILURES,
|
|
1867
2016
|
PACKAGE_VERSION,
|
|
1868
2017
|
PLAYBACK_RATES,
|
|
1869
2018
|
PlaybackSourceManager,
|