@codenkay/video-nsgplayer-core 3.0.1 → 3.0.4
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 +23 -0
- package/dist/index.cjs +162 -94
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -22
- package/dist/index.d.ts +65 -22
- package/dist/index.js +162 -94
- package/dist/index.js.map +1 -1
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.0.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Preserve "use client" in React/UI builds, forward analytics, intermittent watermark, Angular Ivy packaging, and packaging metadata.
|
|
8
|
+
|
|
9
|
+
## 3.0.3
|
|
10
|
+
|
|
11
|
+
- Browser-direct AES keys: after signed-query CORS/network failure, retry once
|
|
12
|
+
with `getKeyAccessToken` Bearer before XHR (needed when AUTH_MODE=browser and
|
|
13
|
+
Appextech enables CORS + Authorization on `enc.key`).
|
|
14
|
+
- Clearer KEY_LOAD_ERROR message distinguishing BFF vs browser-direct CORS.
|
|
15
|
+
|
|
16
|
+
## 3.0.2
|
|
17
|
+
|
|
18
|
+
- Fix pack/build regression on AES `enc.key`: do **not** statically import
|
|
19
|
+
`XhrLoader` from `hls.js` (second module copy after npm pack broke key loads).
|
|
20
|
+
- Resolve builtin loader via same-module `DefaultConfig` + composition unwrap
|
|
21
|
+
(`Symbol` stash / WeakMap) so nested wrappers never replace real XhrLoader.
|
|
22
|
+
- Key auth order: BFF (≤2) → **signed-query without Bearer** → Bearer only on
|
|
23
|
+
401/403. Query-only fetch uses no custom headers (CORS simple request).
|
|
24
|
+
- `keyProxyMaxFailures` circuit breaker retained.
|
|
25
|
+
|
|
3
26
|
## 3.0.1
|
|
4
27
|
|
|
5
28
|
- Permanent AES key path: dedicated key loader with **session circuit breaker**
|
package/dist/index.cjs
CHANGED
|
@@ -213,7 +213,7 @@ var AuthManager = class {
|
|
|
213
213
|
|
|
214
214
|
// src/config/defaults.ts
|
|
215
215
|
var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
|
|
216
|
-
var PACKAGE_VERSION = "3.0.
|
|
216
|
+
var PACKAGE_VERSION = "3.0.3";
|
|
217
217
|
var DEFAULT_PLAYER_BEHAVIOR = {
|
|
218
218
|
autoplay: true,
|
|
219
219
|
startMuted: true,
|
|
@@ -237,7 +237,8 @@ var DEFAULT_CONTROLS = {
|
|
|
237
237
|
showSkip: true,
|
|
238
238
|
showLive: true,
|
|
239
239
|
seekCueDurationMs: 720,
|
|
240
|
-
keyboardShortcuts: true
|
|
240
|
+
keyboardShortcuts: true,
|
|
241
|
+
persistQualityPreference: false
|
|
241
242
|
};
|
|
242
243
|
var DEFAULT_THEME = {
|
|
243
244
|
playIconPreset: "triangle",
|
|
@@ -833,9 +834,12 @@ function seekToLiveEdge(media, options) {
|
|
|
833
834
|
}
|
|
834
835
|
|
|
835
836
|
// src/streaming/authParamsLoader.ts
|
|
836
|
-
var import_hls = require("hls.js");
|
|
837
837
|
var ENC_KEY_PATH_RE = /enc\.key$/i;
|
|
838
838
|
var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
|
|
839
|
+
var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
|
|
840
|
+
"@codenkay/video-nsgplayer-core/builtin-hls-loader"
|
|
841
|
+
);
|
|
842
|
+
var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
|
|
839
843
|
var KEY_PROXY_MAX_FAILURES = 2;
|
|
840
844
|
function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
841
845
|
try {
|
|
@@ -844,18 +848,35 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
|
|
|
844
848
|
return /enc\.key/i.test(rawUrl);
|
|
845
849
|
}
|
|
846
850
|
}
|
|
847
|
-
function
|
|
848
|
-
|
|
849
|
-
|
|
851
|
+
function isBuiltinLoaderName(name) {
|
|
852
|
+
return BUILTIN_LOADER_NAMES.has(name);
|
|
853
|
+
}
|
|
854
|
+
function rememberBuiltin(HlsClass, loader) {
|
|
855
|
+
if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
|
|
856
|
+
builtinLoaderByHls.set(HlsClass, loader);
|
|
850
857
|
}
|
|
858
|
+
return loader;
|
|
859
|
+
}
|
|
860
|
+
function getStashedBuiltinLoader(loader) {
|
|
861
|
+
return loader[BUILTIN_LOADER_REF];
|
|
862
|
+
}
|
|
863
|
+
function resolveBuiltinHlsLoader(HlsModule) {
|
|
864
|
+
const HlsClass = HlsModule;
|
|
865
|
+
const cached = builtinLoaderByHls.get(HlsClass);
|
|
866
|
+
if (cached) return cached;
|
|
851
867
|
const start = HlsClass.DefaultConfig.loader;
|
|
852
868
|
let current = start;
|
|
853
869
|
const seen = /* @__PURE__ */ new Set();
|
|
854
870
|
while (current && !seen.has(current)) {
|
|
855
871
|
seen.add(current);
|
|
856
872
|
const name = typeof current.name === "string" ? current.name : "";
|
|
857
|
-
if (
|
|
858
|
-
return current;
|
|
873
|
+
if (isBuiltinLoaderName(name)) {
|
|
874
|
+
return rememberBuiltin(HlsClass, current);
|
|
875
|
+
}
|
|
876
|
+
const stashed = getStashedBuiltinLoader(current);
|
|
877
|
+
if (stashed) {
|
|
878
|
+
current = stashed;
|
|
879
|
+
continue;
|
|
859
880
|
}
|
|
860
881
|
const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
|
|
861
882
|
if (!parentCtor || parentCtor === Object || parentCtor === Function) {
|
|
@@ -863,7 +884,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
|
|
|
863
884
|
}
|
|
864
885
|
current = parentCtor;
|
|
865
886
|
}
|
|
866
|
-
|
|
887
|
+
const fallback = builtinLoaderByHls.get(HlsClass);
|
|
888
|
+
return fallback ?? start;
|
|
867
889
|
}
|
|
868
890
|
function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
|
|
869
891
|
if (!rawUrl) return rawUrl;
|
|
@@ -911,67 +933,82 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
|
|
|
911
933
|
}
|
|
912
934
|
return false;
|
|
913
935
|
}
|
|
914
|
-
async function
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
936
|
+
async function fetchEncryptionKeyBytes(url, accessToken, signal) {
|
|
937
|
+
const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
|
|
938
|
+
try {
|
|
939
|
+
const response = await fetch(url, {
|
|
940
|
+
method: "GET",
|
|
941
|
+
...headers ? { headers } : {},
|
|
942
|
+
credentials: "omit",
|
|
943
|
+
cache: "no-store",
|
|
944
|
+
signal
|
|
945
|
+
});
|
|
946
|
+
if (!response.ok) {
|
|
947
|
+
return {
|
|
948
|
+
ok: false,
|
|
949
|
+
status: response.status,
|
|
950
|
+
text: response.statusText || `enc.key HTTP ${response.status}`,
|
|
951
|
+
cors: false,
|
|
952
|
+
response
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
const data = await response.arrayBuffer();
|
|
956
|
+
const head = new TextDecoder().decode(data.slice(0, 32));
|
|
957
|
+
if (/<!DOCTYPE|<html/i.test(head)) {
|
|
958
|
+
return {
|
|
959
|
+
ok: false,
|
|
960
|
+
status: 403,
|
|
961
|
+
text: "enc.key returned HTML (CDN challenge/block), not an AES key",
|
|
962
|
+
cors: false,
|
|
963
|
+
response
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
return {
|
|
967
|
+
ok: true,
|
|
968
|
+
data,
|
|
938
969
|
response,
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
970
|
+
url: response.url || url
|
|
971
|
+
};
|
|
972
|
+
} catch (error) {
|
|
973
|
+
if (error instanceof DOMException && error.name === "AbortError") {
|
|
974
|
+
return {
|
|
975
|
+
ok: false,
|
|
976
|
+
status: 0,
|
|
977
|
+
text: "enc.key fetch aborted/timeout",
|
|
978
|
+
cors: false,
|
|
979
|
+
response: null
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
|
|
983
|
+
return {
|
|
984
|
+
ok: false,
|
|
985
|
+
status: 0,
|
|
986
|
+
text: error instanceof Error ? error.message : "enc.key fetch failed",
|
|
987
|
+
cors,
|
|
988
|
+
response: null
|
|
989
|
+
};
|
|
942
990
|
}
|
|
943
|
-
|
|
991
|
+
}
|
|
992
|
+
function reportKeySuccess(context, callbacks, result) {
|
|
993
|
+
const stats = createLoaderStats();
|
|
944
994
|
const end = performance.now();
|
|
995
|
+
stats.loading.first = stats.loading.start;
|
|
945
996
|
stats.loading.end = end;
|
|
946
|
-
stats.loaded = data.byteLength;
|
|
947
|
-
stats.total = data.byteLength;
|
|
948
|
-
const head = new TextDecoder().decode(data.slice(0, 32));
|
|
949
|
-
if (/<!DOCTYPE|<html/i.test(head)) {
|
|
950
|
-
callbacks.onError(
|
|
951
|
-
{
|
|
952
|
-
code: 403,
|
|
953
|
-
text: "enc.key returned HTML (CDN challenge/block), not an AES key"
|
|
954
|
-
},
|
|
955
|
-
context,
|
|
956
|
-
response,
|
|
957
|
-
stats
|
|
958
|
-
);
|
|
959
|
-
return;
|
|
960
|
-
}
|
|
997
|
+
stats.loaded = result.data.byteLength;
|
|
998
|
+
stats.total = result.data.byteLength;
|
|
961
999
|
callbacks.onSuccess(
|
|
962
1000
|
{
|
|
963
|
-
url:
|
|
964
|
-
data,
|
|
965
|
-
code: response.status
|
|
1001
|
+
url: result.url,
|
|
1002
|
+
data: result.data,
|
|
1003
|
+
code: result.response.status
|
|
966
1004
|
},
|
|
967
1005
|
stats,
|
|
968
1006
|
context,
|
|
969
|
-
response
|
|
1007
|
+
result.response
|
|
970
1008
|
);
|
|
971
1009
|
}
|
|
972
1010
|
async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
|
|
973
1011
|
const stats = createLoaderStats();
|
|
974
|
-
stats.loading.first = performance.now();
|
|
975
1012
|
const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
|
|
976
1013
|
if (canTryProxy && plan.proxyUrl) {
|
|
977
1014
|
while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
|
|
@@ -1042,47 +1079,71 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
|
|
|
1042
1079
|
}
|
|
1043
1080
|
state.circuitOpen = true;
|
|
1044
1081
|
}
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1082
|
+
const queryResult = await fetchEncryptionKeyBytes(
|
|
1083
|
+
plan.signedKeyUrl,
|
|
1084
|
+
null,
|
|
1085
|
+
signal
|
|
1086
|
+
);
|
|
1087
|
+
if (queryResult.ok) {
|
|
1088
|
+
reportKeySuccess(
|
|
1089
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1090
|
+
callbacks,
|
|
1091
|
+
queryResult
|
|
1092
|
+
);
|
|
1093
|
+
return "ok";
|
|
1094
|
+
}
|
|
1095
|
+
const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403 || // Browser-direct: opaque CORS/network on unsigned GET — try Bearer once
|
|
1096
|
+
// Appextech allows ACAO + Authorization (AUTH_MODE=browser, no BFF).
|
|
1097
|
+
queryResult.cors || queryResult.status === 0);
|
|
1098
|
+
if (needsBearer && plan.accessToken) {
|
|
1099
|
+
const bearerResult = await fetchEncryptionKeyBytes(
|
|
1100
|
+
plan.signedKeyUrl,
|
|
1101
|
+
plan.accessToken,
|
|
1102
|
+
signal
|
|
1103
|
+
);
|
|
1104
|
+
if (bearerResult.ok) {
|
|
1105
|
+
reportKeySuccess(
|
|
1048
1106
|
{ ...context, url: plan.signedKeyUrl },
|
|
1049
1107
|
callbacks,
|
|
1050
|
-
|
|
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
|
|
1108
|
+
bearerResult
|
|
1075
1109
|
);
|
|
1076
1110
|
return "ok";
|
|
1077
1111
|
}
|
|
1112
|
+
if (bearerResult.cors || bearerResult.status === 0) {
|
|
1113
|
+
return "try_xhr";
|
|
1114
|
+
}
|
|
1115
|
+
callbacks.onError(
|
|
1116
|
+
{
|
|
1117
|
+
code: bearerResult.status,
|
|
1118
|
+
text: bearerResult.text
|
|
1119
|
+
},
|
|
1120
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1121
|
+
bearerResult.response,
|
|
1122
|
+
stats
|
|
1123
|
+
);
|
|
1124
|
+
return "ok";
|
|
1125
|
+
}
|
|
1126
|
+
if (queryResult.cors || queryResult.status === 0) {
|
|
1127
|
+
return "try_xhr";
|
|
1078
1128
|
}
|
|
1079
|
-
|
|
1129
|
+
callbacks.onError(
|
|
1130
|
+
{
|
|
1131
|
+
code: queryResult.status,
|
|
1132
|
+
text: queryResult.text
|
|
1133
|
+
},
|
|
1134
|
+
{ ...context, url: plan.signedKeyUrl },
|
|
1135
|
+
queryResult.response,
|
|
1136
|
+
stats
|
|
1137
|
+
);
|
|
1138
|
+
return "ok";
|
|
1080
1139
|
}
|
|
1081
|
-
function createAuthParamsLoader(
|
|
1140
|
+
function createAuthParamsLoader(HlsModule, store, options = {}) {
|
|
1141
|
+
const HlsClass = HlsModule;
|
|
1082
1142
|
const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
|
|
1143
|
+
rememberBuiltin(HlsClass, BaseLoader);
|
|
1083
1144
|
const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
|
|
1084
1145
|
const keyProxyState = { proxyFailures: 0, circuitOpen: false };
|
|
1085
|
-
|
|
1146
|
+
class AuthParamsLoader {
|
|
1086
1147
|
constructor(config) {
|
|
1087
1148
|
this.context = null;
|
|
1088
1149
|
this.keyAbort = null;
|
|
@@ -1166,7 +1227,13 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
|
|
|
1166
1227
|
}
|
|
1167
1228
|
this.inner.load(context, config, callbacks);
|
|
1168
1229
|
}
|
|
1169
|
-
}
|
|
1230
|
+
}
|
|
1231
|
+
Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
|
|
1232
|
+
value: BaseLoader,
|
|
1233
|
+
enumerable: false,
|
|
1234
|
+
configurable: false
|
|
1235
|
+
});
|
|
1236
|
+
return AuthParamsLoader;
|
|
1170
1237
|
}
|
|
1171
1238
|
|
|
1172
1239
|
// src/streaming/HlsEngine.ts
|
|
@@ -1281,7 +1348,7 @@ var HlsEngine = class {
|
|
|
1281
1348
|
if (!data.fatal) return;
|
|
1282
1349
|
} else if (!data.fatal) {
|
|
1283
1350
|
callbacks.onFatalError?.(
|
|
1284
|
-
status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS).
|
|
1351
|
+
status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS). For browser-direct mode the key host must allow CORS (and Authorization if Bearer is required). Otherwise use keyProxyUrlBuilder (same-origin BFF) when the server IP can reach Appextech."
|
|
1285
1352
|
);
|
|
1286
1353
|
return;
|
|
1287
1354
|
}
|
|
@@ -1656,10 +1723,11 @@ function createVideoPlayer(options) {
|
|
|
1656
1723
|
volume: config.player.volume,
|
|
1657
1724
|
videoId: options.videoId,
|
|
1658
1725
|
keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
|
|
1659
|
-
// Enterprise AES key auth:
|
|
1660
|
-
// 1) keyProxyUrlBuilder
|
|
1661
|
-
// 2)
|
|
1662
|
-
// 3)
|
|
1726
|
+
// Enterprise AES key auth (see authParamsLoader):
|
|
1727
|
+
// 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
|
|
1728
|
+
// 2) signed-query fetch (no Bearer / no CORS preflight)
|
|
1729
|
+
// 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
|
|
1730
|
+
// 4) signed-query XHR fallback if fetch is CORS-blocked
|
|
1663
1731
|
getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
|
|
1664
1732
|
keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
|
|
1665
1733
|
hlsConfig: config.streaming.hls,
|