@codenkay/video-nsgplayer-core 3.0.0 → 3.0.2

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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 3.0.2
4
+
5
+ - Fix pack/build regression on AES `enc.key`: do **not** statically import
6
+ `XhrLoader` from `hls.js` (second module copy after npm pack broke key loads).
7
+ - Resolve builtin loader via same-module `DefaultConfig` + composition unwrap
8
+ (`Symbol` stash / WeakMap) so nested wrappers never replace real XhrLoader.
9
+ - Key auth order: BFF (≤2) → **signed-query without Bearer** → Bearer only on
10
+ 401/403. Query-only fetch uses no custom headers (CORS simple request).
11
+ - `keyProxyMaxFailures` circuit breaker retained.
12
+
13
+ ## 3.0.1
14
+
15
+ - Permanent AES key path: dedicated key loader with **session circuit breaker**
16
+ (`keyProxyMaxFailures` default **2**). A Cloudflare-blocked BFF is not hammered
17
+ by hls.js retries (previously 9–10× `/hls-key` 502s).
18
+ - After the circuit opens, the session uses AuthManager Bearer (or signed-query XHR) only.
19
+ - Host apps with `AUTH_MODE=browser` should omit `keyProxyUrlBuilder` entirely.
20
+
3
21
  ## 3.0.0
4
22
 
5
23
  ### 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 = "2.0.1";
216
+ var PACKAGE_VERSION = "3.0.2";
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 },
@@ -831,9 +833,13 @@ function seekToLiveEdge(media, options) {
831
833
  }
832
834
 
833
835
  // src/streaming/authParamsLoader.ts
834
- var import_hls = require("hls.js");
835
836
  var ENC_KEY_PATH_RE = /enc\.key$/i;
836
837
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
838
+ var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
839
+ "@codenkay/video-nsgplayer-core/builtin-hls-loader"
840
+ );
841
+ var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
842
+ var KEY_PROXY_MAX_FAILURES = 2;
837
843
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
838
844
  try {
839
845
  return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
@@ -841,18 +847,34 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
841
847
  return /enc\.key/i.test(rawUrl);
842
848
  }
843
849
  }
844
- function resolveBuiltinHlsLoader(HlsClass) {
845
- if (typeof import_hls.XhrLoader === "function") {
846
- return import_hls.XhrLoader;
850
+ function isBuiltinLoaderName(name) {
851
+ return BUILTIN_LOADER_NAMES.has(name);
852
+ }
853
+ function rememberBuiltin(HlsClass, loader) {
854
+ if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
855
+ builtinLoaderByHls.set(HlsClass, loader);
847
856
  }
857
+ return loader;
858
+ }
859
+ function getStashedBuiltinLoader(loader) {
860
+ return loader[BUILTIN_LOADER_REF];
861
+ }
862
+ function resolveBuiltinHlsLoader(HlsClass) {
863
+ const cached = builtinLoaderByHls.get(HlsClass);
864
+ if (cached) return cached;
848
865
  const start = HlsClass.DefaultConfig.loader;
849
866
  let current = start;
850
867
  const seen = /* @__PURE__ */ new Set();
851
868
  while (current && !seen.has(current)) {
852
869
  seen.add(current);
853
870
  const name = typeof current.name === "string" ? current.name : "";
854
- if (BUILTIN_LOADER_NAMES.has(name)) {
855
- return current;
871
+ if (isBuiltinLoaderName(name)) {
872
+ return rememberBuiltin(HlsClass, current);
873
+ }
874
+ const stashed = getStashedBuiltinLoader(current);
875
+ if (stashed) {
876
+ current = stashed;
877
+ continue;
856
878
  }
857
879
  const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
858
880
  if (!parentCtor || parentCtor === Object || parentCtor === Function) {
@@ -860,7 +882,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
860
882
  }
861
883
  current = parentCtor;
862
884
  }
863
- return start;
885
+ const fallback = builtinLoaderByHls.get(HlsClass);
886
+ return fallback ?? start;
864
887
  }
865
888
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
866
889
  if (!rawUrl) return rawUrl;
@@ -894,68 +917,230 @@ function createLoaderStats() {
894
917
  buffering: { start: 0, first: 0, end: 0 }
895
918
  };
896
919
  }
897
- async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
898
- const stats = createLoaderStats();
899
- stats.loading.first = performance.now();
900
- const headers = {
901
- Accept: "*/*",
902
- ...context.headers
903
- };
904
- if (accessToken) {
905
- headers.Authorization = `Bearer ${accessToken}`;
906
- }
907
- const response = await fetch(context.url, {
908
- method: "GET",
909
- headers,
910
- credentials: "omit",
911
- cache: "no-store",
912
- signal
913
- });
914
- if (!response.ok) {
915
- callbacks.onError(
916
- {
917
- code: response.status,
918
- text: response.statusText || `enc.key HTTP ${response.status}`
919
- },
920
- context,
920
+ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
921
+ if (status === 0 || status === 502 || status === 503 || status === 504) {
922
+ return true;
923
+ }
924
+ if (status === 403 || status === 401) return true;
925
+ if (contentType?.includes("text/html")) return true;
926
+ if (/<!DOCTYPE|<html|Just a moment|Cloudflare|blocked/i.test(bodyHead)) {
927
+ return true;
928
+ }
929
+ if (errorText && /Cloudflare|HTML|DOCTYPE|blocked|502/i.test(errorText)) {
930
+ return true;
931
+ }
932
+ return false;
933
+ }
934
+ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
935
+ const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
936
+ try {
937
+ const response = await fetch(url, {
938
+ method: "GET",
939
+ ...headers ? { headers } : {},
940
+ credentials: "omit",
941
+ cache: "no-store",
942
+ signal
943
+ });
944
+ if (!response.ok) {
945
+ return {
946
+ ok: false,
947
+ status: response.status,
948
+ text: response.statusText || `enc.key HTTP ${response.status}`,
949
+ cors: false,
950
+ response
951
+ };
952
+ }
953
+ const data = await response.arrayBuffer();
954
+ const head = new TextDecoder().decode(data.slice(0, 32));
955
+ if (/<!DOCTYPE|<html/i.test(head)) {
956
+ return {
957
+ ok: false,
958
+ status: 403,
959
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key",
960
+ cors: false,
961
+ response
962
+ };
963
+ }
964
+ return {
965
+ ok: true,
966
+ data,
921
967
  response,
922
- stats
923
- );
924
- return;
968
+ url: response.url || url
969
+ };
970
+ } catch (error) {
971
+ if (error instanceof DOMException && error.name === "AbortError") {
972
+ return {
973
+ ok: false,
974
+ status: 0,
975
+ text: "enc.key fetch aborted/timeout",
976
+ cors: false,
977
+ response: null
978
+ };
979
+ }
980
+ const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
981
+ return {
982
+ ok: false,
983
+ status: 0,
984
+ text: error instanceof Error ? error.message : "enc.key fetch failed",
985
+ cors,
986
+ response: null
987
+ };
925
988
  }
926
- const data = await response.arrayBuffer();
989
+ }
990
+ function reportKeySuccess(context, callbacks, result) {
991
+ const stats = createLoaderStats();
927
992
  const end = performance.now();
993
+ stats.loading.first = stats.loading.start;
928
994
  stats.loading.end = end;
929
- stats.loaded = data.byteLength;
930
- stats.total = data.byteLength;
931
- const head = new TextDecoder().decode(data.slice(0, 32));
932
- if (/<!DOCTYPE|<html/i.test(head)) {
995
+ stats.loaded = result.data.byteLength;
996
+ stats.total = result.data.byteLength;
997
+ callbacks.onSuccess(
998
+ {
999
+ url: result.url,
1000
+ data: result.data,
1001
+ code: result.response.status
1002
+ },
1003
+ stats,
1004
+ context,
1005
+ result.response
1006
+ );
1007
+ }
1008
+ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
1009
+ const stats = createLoaderStats();
1010
+ const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
1011
+ if (canTryProxy && plan.proxyUrl) {
1012
+ while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
1013
+ state.proxyFailures += 1;
1014
+ try {
1015
+ const response = await fetch(plan.proxyUrl, {
1016
+ method: "GET",
1017
+ headers: { Accept: "*/*" },
1018
+ credentials: "same-origin",
1019
+ cache: "no-store",
1020
+ signal
1021
+ });
1022
+ const contentType = response.headers.get("content-type");
1023
+ const data = await response.arrayBuffer();
1024
+ const head = new TextDecoder().decode(data.slice(0, 64));
1025
+ if (response.ok && !isProxyUpstreamBlocked(response.status, contentType, head)) {
1026
+ const end = performance.now();
1027
+ stats.loading.end = end;
1028
+ stats.loaded = data.byteLength;
1029
+ stats.total = data.byteLength;
1030
+ callbacks.onSuccess(
1031
+ {
1032
+ url: response.url || plan.proxyUrl,
1033
+ data,
1034
+ code: response.status
1035
+ },
1036
+ stats,
1037
+ { ...context, url: plan.proxyUrl },
1038
+ response
1039
+ );
1040
+ return "ok";
1041
+ }
1042
+ if (isProxyUpstreamBlocked(
1043
+ response.status,
1044
+ contentType,
1045
+ head,
1046
+ response.statusText
1047
+ )) {
1048
+ if (response.status === 502 || contentType?.includes("text/html") || /Cloudflare|DOCTYPE|blocked/i.test(head)) {
1049
+ state.circuitOpen = true;
1050
+ break;
1051
+ }
1052
+ continue;
1053
+ }
1054
+ callbacks.onError(
1055
+ {
1056
+ code: response.status,
1057
+ text: `enc.key proxy HTTP ${response.status}`
1058
+ },
1059
+ { ...context, url: plan.proxyUrl },
1060
+ response,
1061
+ stats
1062
+ );
1063
+ return "ok";
1064
+ } catch (error) {
1065
+ if (error instanceof DOMException && error.name === "AbortError") {
1066
+ callbacks.onError(
1067
+ { code: 0, text: "enc.key proxy aborted/timeout" },
1068
+ context,
1069
+ null,
1070
+ stats
1071
+ );
1072
+ return "ok";
1073
+ }
1074
+ state.circuitOpen = true;
1075
+ break;
1076
+ }
1077
+ }
1078
+ state.circuitOpen = true;
1079
+ }
1080
+ const queryResult = await fetchEncryptionKeyBytes(
1081
+ plan.signedKeyUrl,
1082
+ null,
1083
+ signal
1084
+ );
1085
+ if (queryResult.ok) {
1086
+ reportKeySuccess(
1087
+ { ...context, url: plan.signedKeyUrl },
1088
+ callbacks,
1089
+ queryResult
1090
+ );
1091
+ return "ok";
1092
+ }
1093
+ const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403);
1094
+ if (needsBearer && plan.accessToken) {
1095
+ const bearerResult = await fetchEncryptionKeyBytes(
1096
+ plan.signedKeyUrl,
1097
+ plan.accessToken,
1098
+ signal
1099
+ );
1100
+ if (bearerResult.ok) {
1101
+ reportKeySuccess(
1102
+ { ...context, url: plan.signedKeyUrl },
1103
+ callbacks,
1104
+ bearerResult
1105
+ );
1106
+ return "ok";
1107
+ }
1108
+ if (bearerResult.cors) {
1109
+ return "try_xhr";
1110
+ }
933
1111
  callbacks.onError(
934
1112
  {
935
- code: 403,
936
- text: "enc.key returned HTML (CDN challenge/block), not an AES key"
1113
+ code: bearerResult.status,
1114
+ text: bearerResult.text
937
1115
  },
938
- context,
939
- response,
1116
+ { ...context, url: plan.signedKeyUrl },
1117
+ bearerResult.response,
940
1118
  stats
941
1119
  );
942
- return;
1120
+ return "ok";
943
1121
  }
944
- callbacks.onSuccess(
1122
+ if (queryResult.cors || queryResult.status === 0) {
1123
+ return "try_xhr";
1124
+ }
1125
+ callbacks.onError(
945
1126
  {
946
- url: response.url || context.url,
947
- data,
948
- code: response.status
1127
+ code: queryResult.status,
1128
+ text: queryResult.text
949
1129
  },
950
- stats,
951
- context,
952
- response
1130
+ { ...context, url: plan.signedKeyUrl },
1131
+ queryResult.response,
1132
+ stats
953
1133
  );
1134
+ return "ok";
954
1135
  }
955
1136
  function createAuthParamsLoader(HlsClass, store, options = {}) {
956
1137
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
957
- return class AuthParamsLoader {
1138
+ rememberBuiltin(HlsClass, BaseLoader);
1139
+ const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1140
+ const keyProxyState = { proxyFailures: 0, circuitOpen: false };
1141
+ class AuthParamsLoader {
958
1142
  constructor(config) {
1143
+ this.context = null;
959
1144
  this.keyAbort = null;
960
1145
  this.inner = new BaseLoader(config);
961
1146
  this.stats = this.inner.stats;
@@ -963,14 +1148,16 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
963
1148
  abort() {
964
1149
  this.keyAbort?.abort();
965
1150
  this.keyAbort = null;
966
- this.inner.abort?.();
1151
+ this.inner.abort();
967
1152
  }
968
1153
  destroy() {
969
1154
  this.keyAbort?.abort();
970
1155
  this.keyAbort = null;
1156
+ this.context = null;
971
1157
  this.inner.destroy();
972
1158
  }
973
1159
  load(context, config, callbacks) {
1160
+ this.context = context;
974
1161
  if (!context.url) {
975
1162
  this.inner.load(context, config, callbacks);
976
1163
  return;
@@ -978,92 +1165,70 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
978
1165
  try {
979
1166
  const originalUrl = context.url;
980
1167
  const isKey = isEncryptionKeyUrl(originalUrl);
981
- const usingKeyProxy = Boolean(
982
- isKey && options.keyProxyUrlBuilder && options.videoId
983
- );
984
- const signedKeyUrl = isKey ? rewriteHlsRequestUrl(originalUrl, store, {
985
- ...options,
986
- keyProxyUrlBuilder: void 0
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;
1168
+ if (!isKey) {
1169
+ if (store.hasParams()) {
1170
+ context.url = rewriteHlsRequestUrl(originalUrl, store, {
1171
+ ...options,
1172
+ keyProxyUrlBuilder: void 0
1173
+ });
994
1174
  }
995
- const proxyCallbacks = {
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);
1175
+ this.inner.load(context, config, callbacks);
1026
1176
  return;
1027
1177
  }
1028
- if (isKey && typeof options.getKeyAccessToken === "function") {
1029
- const accessToken = options.getKeyAccessToken() ?? null;
1030
- if (accessToken) {
1031
- this.keyAbort?.abort();
1032
- this.keyAbort = new AbortController();
1033
- const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
1034
- const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
1035
- void loadEncryptionKeyWithFetch(
1036
- context,
1037
- callbacks,
1038
- accessToken,
1039
- this.keyAbort.signal
1040
- ).catch((error) => {
1041
- const stats = createLoaderStats();
1042
- const aborted = error instanceof DOMException && error.name === "AbortError";
1043
- if (!aborted && (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message))) {
1044
- this.inner.load(context, config, callbacks);
1045
- return;
1046
- }
1047
- callbacks.onError(
1048
- {
1049
- code: 0,
1050
- text: aborted ? "enc.key fetch aborted/timeout" : error instanceof Error ? error.message : "enc.key fetch failed"
1051
- },
1052
- context,
1053
- null,
1054
- stats
1055
- );
1056
- }).finally(() => {
1057
- clearTimeout(timer);
1058
- });
1059
- return;
1178
+ const signedKeyUrl = rewriteHlsRequestUrl(originalUrl, store, {
1179
+ ...options,
1180
+ keyProxyUrlBuilder: void 0
1181
+ });
1182
+ const proxyConfigured = Boolean(
1183
+ options.keyProxyUrlBuilder && options.videoId
1184
+ );
1185
+ const proxyUrl = proxyConfigured && !keyProxyState.circuitOpen ? rewriteHlsRequestUrl(originalUrl, store, options) : null;
1186
+ const accessToken = options.getKeyAccessToken?.() ?? null;
1187
+ this.keyAbort?.abort();
1188
+ this.keyAbort = new AbortController();
1189
+ const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
1190
+ const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
1191
+ void loadEncryptionKeyEnterprise(
1192
+ context,
1193
+ callbacks,
1194
+ {
1195
+ proxyUrl,
1196
+ signedKeyUrl,
1197
+ accessToken,
1198
+ maxProxyFailures
1199
+ },
1200
+ this.keyAbort.signal,
1201
+ keyProxyState
1202
+ ).then((result) => {
1203
+ if (result === "try_xhr") {
1204
+ context.url = signedKeyUrl;
1205
+ this.inner.load(context, config, callbacks);
1060
1206
  }
1061
- }
1207
+ }).catch((error) => {
1208
+ callbacks.onError(
1209
+ {
1210
+ code: 0,
1211
+ text: error instanceof Error ? error.message : "enc.key load failed"
1212
+ },
1213
+ context,
1214
+ null,
1215
+ createLoaderStats()
1216
+ );
1217
+ }).finally(() => {
1218
+ clearTimeout(timer);
1219
+ });
1220
+ return;
1062
1221
  } catch {
1063
1222
  }
1064
1223
  this.inner.load(context, config, callbacks);
1065
1224
  }
1066
- };
1225
+ }
1226
+ Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
1227
+ value: BaseLoader,
1228
+ enumerable: false,
1229
+ configurable: false
1230
+ });
1231
+ return AuthParamsLoader;
1067
1232
  }
1068
1233
 
1069
1234
  // src/streaming/HlsEngine.ts
@@ -1128,7 +1293,8 @@ var HlsEngine = class {
1128
1293
  const loader = createAuthParamsLoader(Hls, authParams, {
1129
1294
  videoId,
1130
1295
  keyProxyUrlBuilder,
1131
- getKeyAccessToken
1296
+ getKeyAccessToken,
1297
+ keyProxyMaxFailures: this.options.keyProxyMaxFailures
1132
1298
  });
1133
1299
  this.hls = new Hls({
1134
1300
  enableWorker: hlsConfig.enableWorker,
@@ -1552,11 +1718,13 @@ function createVideoPlayer(options) {
1552
1718
  volume: config.player.volume,
1553
1719
  videoId: options.videoId,
1554
1720
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1555
- // Enterprise AES key auth:
1556
- // 1) keyProxyUrlBuilder (same-origin BFF) when configured
1557
- // 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
1558
- // 3) else signed query + XHR (CDN hosts)
1721
+ // Enterprise AES key auth (see authParamsLoader):
1722
+ // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
1723
+ // 2) signed-query fetch (no Bearer / no CORS preflight)
1724
+ // 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
1725
+ // 4) signed-query XHR fallback if fetch is CORS-blocked
1559
1726
  getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1727
+ keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1560
1728
  hlsConfig: config.streaming.hls,
1561
1729
  logger,
1562
1730
  callbacks: {
@@ -1874,7 +2042,8 @@ function createVideoPlayer(options) {
1874
2042
  engine: "hls",
1875
2043
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1876
2044
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1877
- getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken())
2045
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
2046
+ keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures ?? config.streaming.keyProxyMaxFailures
1878
2047
  },
1879
2048
  logging: { ...config.logging, ...partial?.logging },
1880
2049
  debug: { ...config.debug, ...partial?.debug },
@@ -1906,6 +2075,7 @@ function createVideoPlayer(options) {
1906
2075
  DEFAULT_THEME,
1907
2076
  DEFAULT_WATERMARK,
1908
2077
  EventEmitter,
2078
+ KEY_PROXY_MAX_FAILURES,
1909
2079
  PACKAGE_VERSION,
1910
2080
  PLAYBACK_RATES,
1911
2081
  PlaybackSourceManager,