@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/dist/index.d.cts CHANGED
@@ -151,6 +151,12 @@ type StreamingConfig = {
151
151
  * Use when the BFF cannot reach Appextech (Cloudflare challenge on server IP).
152
152
  */
153
153
  getKeyAccessToken?: () => string | null | undefined;
154
+ /**
155
+ * Max same-origin key-proxy failures before the session permanently switches
156
+ * to browser Bearer / signed-query (default 2). Prevents hls.js from hammering
157
+ * a Cloudflare-blocked BFF (9–10× 502s).
158
+ */
159
+ keyProxyMaxFailures?: number;
154
160
  };
155
161
  type LoggingConfig = {
156
162
  enabled?: boolean;
@@ -198,6 +204,7 @@ type PlayerConfig = Required<{
198
204
  hls: Required<NonNullable<StreamingConfig["hls"]>>;
199
205
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
200
206
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
207
+ keyProxyMaxFailures?: StreamingConfig["keyProxyMaxFailures"];
201
208
  };
202
209
  logging: Required<LoggingConfig>;
203
210
  debug: Required<DebugConfig>;
@@ -279,7 +286,7 @@ type PlayerEventMap = {
279
286
  };
280
287
 
281
288
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
282
- declare const PACKAGE_VERSION = "0.1.8";
289
+ declare const PACKAGE_VERSION = "3.0.1";
283
290
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
284
291
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
285
292
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -312,6 +319,7 @@ type PlayerConfigPartial = Partial<{
312
319
  hls?: Partial<PlayerConfig["streaming"]["hls"]>;
313
320
  keyProxyUrlBuilder?: PlayerConfig["streaming"]["keyProxyUrlBuilder"];
314
321
  getKeyAccessToken?: PlayerConfig["streaming"]["getKeyAccessToken"];
322
+ keyProxyMaxFailures?: PlayerConfig["streaming"]["keyProxyMaxFailures"];
315
323
  };
316
324
  logging: Partial<PlayerConfig["logging"]>;
317
325
  debug: Partial<PlayerConfig["debug"]>;
@@ -431,6 +439,7 @@ type HlsEngineOptions = {
431
439
  videoId?: string;
432
440
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
433
441
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
442
+ keyProxyMaxFailures?: StreamingConfig["keyProxyMaxFailures"];
434
443
  hlsConfig: {
435
444
  enableWorker: boolean;
436
445
  lowLatencyMode: boolean;
@@ -646,15 +655,21 @@ declare class RefreshManager {
646
655
 
647
656
  type HlsLoaderInstance = {
648
657
  stats: LoaderStats;
658
+ context: LoaderContext | null;
649
659
  load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
650
660
  destroy(): void;
651
- abort?: () => void;
661
+ abort(): void;
652
662
  };
653
- type HlsLoader = new (config: object) => HlsLoaderInstance;
663
+ /** Constructor shape compatible with `Hls.DefaultConfig.loader`. */
664
+ type HlsLoaderConstructor = new (config: object) => HlsLoaderInstance;
665
+ /** Max same-origin BFF key attempts before the session circuit opens. */
666
+ declare const KEY_PROXY_MAX_FAILURES = 2;
654
667
  type AuthParamsLoaderOptions = {
655
668
  videoId?: string;
656
669
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
657
670
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
671
+ /** Override max BFF key failures before permanent browser fallback (default 2). */
672
+ keyProxyMaxFailures?: number;
658
673
  };
659
674
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
660
675
  /**
@@ -664,26 +679,24 @@ declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolea
664
679
  * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
665
680
  * stack (`InternalError: too much recursion`).
666
681
  */
667
- declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoader;
682
+ declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
668
683
  /**
669
684
  * Rewrites an HLS request URL so signed auth query params are present.
670
685
  *
671
686
  * AES `enc.key` handling (enterprise priority order):
672
687
  * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
673
688
  * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
674
- * params — same auth model as segments on Appextech CDN hosts
675
689
  * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
676
690
  */
677
691
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
678
692
  /**
679
693
  * hls.js loader that:
680
694
  * - Re-applies signed auth query params on every media request
681
- * - Loads AES `enc.key` via BFF / optional Bearer fetch / signed-query XHR
682
- *
683
- * Uses composition (not `extends DefaultConfig.loader`) so repeated mounts,
684
- * HMR, or a polluted DefaultConfig cannot nest `super.load()` into recursion.
695
+ * - Loads AES `enc.key` via controlled BFF (≤2) → Bearer → query XHR
696
+ * with a **session circuit breaker** so a dead Cloudflare BFF is not
697
+ * hammered by hls.js retries (which previously caused 9–10× 502s).
685
698
  */
686
- declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
699
+ declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
687
700
 
688
701
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
689
702
  /**
@@ -692,4 +705,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
692
705
  */
693
706
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
694
707
 
695
- export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isEncryptionKeyUrl, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, resolveBuiltinHlsLoader, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
708
+ export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, KEY_PROXY_MAX_FAILURES, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isEncryptionKeyUrl, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, resolveBuiltinHlsLoader, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
package/dist/index.d.ts CHANGED
@@ -151,6 +151,12 @@ type StreamingConfig = {
151
151
  * Use when the BFF cannot reach Appextech (Cloudflare challenge on server IP).
152
152
  */
153
153
  getKeyAccessToken?: () => string | null | undefined;
154
+ /**
155
+ * Max same-origin key-proxy failures before the session permanently switches
156
+ * to browser Bearer / signed-query (default 2). Prevents hls.js from hammering
157
+ * a Cloudflare-blocked BFF (9–10× 502s).
158
+ */
159
+ keyProxyMaxFailures?: number;
154
160
  };
155
161
  type LoggingConfig = {
156
162
  enabled?: boolean;
@@ -198,6 +204,7 @@ type PlayerConfig = Required<{
198
204
  hls: Required<NonNullable<StreamingConfig["hls"]>>;
199
205
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
200
206
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
207
+ keyProxyMaxFailures?: StreamingConfig["keyProxyMaxFailures"];
201
208
  };
202
209
  logging: Required<LoggingConfig>;
203
210
  debug: Required<DebugConfig>;
@@ -279,7 +286,7 @@ type PlayerEventMap = {
279
286
  };
280
287
 
281
288
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
282
- declare const PACKAGE_VERSION = "0.1.8";
289
+ declare const PACKAGE_VERSION = "3.0.1";
283
290
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
284
291
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
285
292
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -312,6 +319,7 @@ type PlayerConfigPartial = Partial<{
312
319
  hls?: Partial<PlayerConfig["streaming"]["hls"]>;
313
320
  keyProxyUrlBuilder?: PlayerConfig["streaming"]["keyProxyUrlBuilder"];
314
321
  getKeyAccessToken?: PlayerConfig["streaming"]["getKeyAccessToken"];
322
+ keyProxyMaxFailures?: PlayerConfig["streaming"]["keyProxyMaxFailures"];
315
323
  };
316
324
  logging: Partial<PlayerConfig["logging"]>;
317
325
  debug: Partial<PlayerConfig["debug"]>;
@@ -431,6 +439,7 @@ type HlsEngineOptions = {
431
439
  videoId?: string;
432
440
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
433
441
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
442
+ keyProxyMaxFailures?: StreamingConfig["keyProxyMaxFailures"];
434
443
  hlsConfig: {
435
444
  enableWorker: boolean;
436
445
  lowLatencyMode: boolean;
@@ -646,15 +655,21 @@ declare class RefreshManager {
646
655
 
647
656
  type HlsLoaderInstance = {
648
657
  stats: LoaderStats;
658
+ context: LoaderContext | null;
649
659
  load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
650
660
  destroy(): void;
651
- abort?: () => void;
661
+ abort(): void;
652
662
  };
653
- type HlsLoader = new (config: object) => HlsLoaderInstance;
663
+ /** Constructor shape compatible with `Hls.DefaultConfig.loader`. */
664
+ type HlsLoaderConstructor = new (config: object) => HlsLoaderInstance;
665
+ /** Max same-origin BFF key attempts before the session circuit opens. */
666
+ declare const KEY_PROXY_MAX_FAILURES = 2;
654
667
  type AuthParamsLoaderOptions = {
655
668
  videoId?: string;
656
669
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
657
670
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
671
+ /** Override max BFF key failures before permanent browser fallback (default 2). */
672
+ keyProxyMaxFailures?: number;
658
673
  };
659
674
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
660
675
  /**
@@ -664,26 +679,24 @@ declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolea
664
679
  * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
665
680
  * stack (`InternalError: too much recursion`).
666
681
  */
667
- declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoader;
682
+ declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
668
683
  /**
669
684
  * Rewrites an HLS request URL so signed auth query params are present.
670
685
  *
671
686
  * AES `enc.key` handling (enterprise priority order):
672
687
  * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
673
688
  * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
674
- * params — same auth model as segments on Appextech CDN hosts
675
689
  * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
676
690
  */
677
691
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
678
692
  /**
679
693
  * hls.js loader that:
680
694
  * - Re-applies signed auth query params on every media request
681
- * - Loads AES `enc.key` via BFF / optional Bearer fetch / signed-query XHR
682
- *
683
- * Uses composition (not `extends DefaultConfig.loader`) so repeated mounts,
684
- * HMR, or a polluted DefaultConfig cannot nest `super.load()` into recursion.
695
+ * - Loads AES `enc.key` via controlled BFF (≤2) → Bearer → query XHR
696
+ * with a **session circuit breaker** so a dead Cloudflare BFF is not
697
+ * hammered by hls.js retries (which previously caused 9–10× 502s).
685
698
  */
686
- declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
699
+ declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
687
700
 
688
701
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
689
702
  /**
@@ -692,4 +705,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
692
705
  */
693
706
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
694
707
 
695
- export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isEncryptionKeyUrl, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, resolveBuiltinHlsLoader, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
708
+ export { type AccessibilityConfig, type AnalyticsConfig, type AuthConfig, AuthManager, AuthParamsStore, type AuthToken, type AuthTokenProviderContext, type BufferedRange, type ControlsConfig, type CreateVideoPlayerOptions, DEFAULT_CONTROLS, DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS, DEFAULT_PLAYER_BEHAVIOR, DEFAULT_PLAYER_CONFIG, DEFAULT_THEME, DEFAULT_WATERMARK, type DebugConfig, EventEmitter, type HlsQualityLevel, type HlsSupportMode, KEY_PROXY_MAX_FAILURES, type LiveMediaSnapshot, type LoggingConfig, PACKAGE_VERSION, PLAYBACK_RATES, type PlayIconPreset, type PlaybackCredentialConfig, type PlaybackRate, type PlaybackSource, type PlaybackSourceContext, PlaybackSourceManager, type PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isEncryptionKeyUrl, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, resolveBuiltinHlsLoader, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
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.8";
149
+ var PACKAGE_VERSION = "3.0.1";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -325,7 +325,8 @@ function mergePlayerConfig(partial) {
325
325
  ...partial?.streaming?.hls
326
326
  },
327
327
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
328
- getKeyAccessToken: partial?.streaming?.getKeyAccessToken
328
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken,
329
+ keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures
329
330
  },
330
331
  logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
331
332
  debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
@@ -768,6 +769,7 @@ function seekToLiveEdge(media, options) {
768
769
  import { XhrLoader as BuiltinXhrLoader } from "hls.js";
769
770
  var ENC_KEY_PATH_RE = /enc\.key$/i;
770
771
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
772
+ var KEY_PROXY_MAX_FAILURES = 2;
771
773
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
772
774
  try {
773
775
  return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
@@ -828,6 +830,20 @@ function createLoaderStats() {
828
830
  buffering: { start: 0, first: 0, end: 0 }
829
831
  };
830
832
  }
833
+ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
834
+ if (status === 0 || status === 502 || status === 503 || status === 504) {
835
+ return true;
836
+ }
837
+ if (status === 403 || status === 401) return true;
838
+ if (contentType?.includes("text/html")) return true;
839
+ if (/<!DOCTYPE|<html|Just a moment|Cloudflare|blocked/i.test(bodyHead)) {
840
+ return true;
841
+ }
842
+ if (errorText && /Cloudflare|HTML|DOCTYPE|blocked|502/i.test(errorText)) {
843
+ return true;
844
+ }
845
+ return false;
846
+ }
831
847
  async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
832
848
  const stats = createLoaderStats();
833
849
  stats.loading.first = performance.now();
@@ -886,10 +902,122 @@ async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signa
886
902
  response
887
903
  );
888
904
  }
905
+ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
906
+ const stats = createLoaderStats();
907
+ stats.loading.first = performance.now();
908
+ const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
909
+ if (canTryProxy && plan.proxyUrl) {
910
+ while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
911
+ state.proxyFailures += 1;
912
+ try {
913
+ const response = await fetch(plan.proxyUrl, {
914
+ method: "GET",
915
+ headers: { Accept: "*/*" },
916
+ credentials: "same-origin",
917
+ cache: "no-store",
918
+ signal
919
+ });
920
+ const contentType = response.headers.get("content-type");
921
+ const data = await response.arrayBuffer();
922
+ const head = new TextDecoder().decode(data.slice(0, 64));
923
+ if (response.ok && !isProxyUpstreamBlocked(response.status, contentType, head)) {
924
+ const end = performance.now();
925
+ stats.loading.end = end;
926
+ stats.loaded = data.byteLength;
927
+ stats.total = data.byteLength;
928
+ callbacks.onSuccess(
929
+ {
930
+ url: response.url || plan.proxyUrl,
931
+ data,
932
+ code: response.status
933
+ },
934
+ stats,
935
+ { ...context, url: plan.proxyUrl },
936
+ response
937
+ );
938
+ return "ok";
939
+ }
940
+ if (isProxyUpstreamBlocked(
941
+ response.status,
942
+ contentType,
943
+ head,
944
+ response.statusText
945
+ )) {
946
+ if (response.status === 502 || contentType?.includes("text/html") || /Cloudflare|DOCTYPE|blocked/i.test(head)) {
947
+ state.circuitOpen = true;
948
+ break;
949
+ }
950
+ continue;
951
+ }
952
+ callbacks.onError(
953
+ {
954
+ code: response.status,
955
+ text: `enc.key proxy HTTP ${response.status}`
956
+ },
957
+ { ...context, url: plan.proxyUrl },
958
+ response,
959
+ stats
960
+ );
961
+ return "ok";
962
+ } catch (error) {
963
+ if (error instanceof DOMException && error.name === "AbortError") {
964
+ callbacks.onError(
965
+ { code: 0, text: "enc.key proxy aborted/timeout" },
966
+ context,
967
+ null,
968
+ stats
969
+ );
970
+ return "ok";
971
+ }
972
+ state.circuitOpen = true;
973
+ break;
974
+ }
975
+ }
976
+ state.circuitOpen = true;
977
+ }
978
+ if (plan.accessToken) {
979
+ try {
980
+ await loadEncryptionKeyWithFetch(
981
+ { ...context, url: plan.signedKeyUrl },
982
+ callbacks,
983
+ plan.accessToken,
984
+ signal
985
+ );
986
+ return "ok";
987
+ } catch (error) {
988
+ if (error instanceof DOMException && error.name === "AbortError") {
989
+ callbacks.onError(
990
+ { code: 0, text: "enc.key fetch aborted/timeout" },
991
+ { ...context, url: plan.signedKeyUrl },
992
+ null,
993
+ stats
994
+ );
995
+ return "ok";
996
+ }
997
+ if (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message)) {
998
+ return "try_xhr";
999
+ }
1000
+ callbacks.onError(
1001
+ {
1002
+ code: 0,
1003
+ text: error instanceof Error ? error.message : "enc.key Bearer fetch failed"
1004
+ },
1005
+ { ...context, url: plan.signedKeyUrl },
1006
+ null,
1007
+ stats
1008
+ );
1009
+ return "ok";
1010
+ }
1011
+ }
1012
+ return "try_xhr";
1013
+ }
889
1014
  function createAuthParamsLoader(HlsClass, store, options = {}) {
890
1015
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
1016
+ const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1017
+ const keyProxyState = { proxyFailures: 0, circuitOpen: false };
891
1018
  return class AuthParamsLoader {
892
1019
  constructor(config) {
1020
+ this.context = null;
893
1021
  this.keyAbort = null;
894
1022
  this.inner = new BaseLoader(config);
895
1023
  this.stats = this.inner.stats;
@@ -897,14 +1025,16 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
897
1025
  abort() {
898
1026
  this.keyAbort?.abort();
899
1027
  this.keyAbort = null;
900
- this.inner.abort?.();
1028
+ this.inner.abort();
901
1029
  }
902
1030
  destroy() {
903
1031
  this.keyAbort?.abort();
904
1032
  this.keyAbort = null;
1033
+ this.context = null;
905
1034
  this.inner.destroy();
906
1035
  }
907
1036
  load(context, config, callbacks) {
1037
+ this.context = context;
908
1038
  if (!context.url) {
909
1039
  this.inner.load(context, config, callbacks);
910
1040
  return;
@@ -912,44 +1042,59 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
912
1042
  try {
913
1043
  const originalUrl = context.url;
914
1044
  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) {
1045
+ if (!isKey) {
1046
+ if (store.hasParams()) {
1047
+ context.url = rewriteHlsRequestUrl(originalUrl, store, {
1048
+ ...options,
1049
+ keyProxyUrlBuilder: void 0
1050
+ });
1051
+ }
920
1052
  this.inner.load(context, config, callbacks);
921
1053
  return;
922
1054
  }
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;
1055
+ const signedKeyUrl = rewriteHlsRequestUrl(originalUrl, store, {
1056
+ ...options,
1057
+ keyProxyUrlBuilder: void 0
1058
+ });
1059
+ const proxyConfigured = Boolean(
1060
+ options.keyProxyUrlBuilder && options.videoId
1061
+ );
1062
+ const proxyUrl = proxyConfigured && !keyProxyState.circuitOpen ? rewriteHlsRequestUrl(originalUrl, store, options) : null;
1063
+ const accessToken = options.getKeyAccessToken?.() ?? null;
1064
+ this.keyAbort?.abort();
1065
+ this.keyAbort = new AbortController();
1066
+ const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
1067
+ const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
1068
+ void loadEncryptionKeyEnterprise(
1069
+ context,
1070
+ callbacks,
1071
+ {
1072
+ proxyUrl,
1073
+ signedKeyUrl,
1074
+ accessToken,
1075
+ maxProxyFailures
1076
+ },
1077
+ this.keyAbort.signal,
1078
+ keyProxyState
1079
+ ).then((result) => {
1080
+ if (result === "try_xhr") {
1081
+ context.url = signedKeyUrl;
1082
+ this.inner.load(context, config, callbacks);
951
1083
  }
952
- }
1084
+ }).catch((error) => {
1085
+ callbacks.onError(
1086
+ {
1087
+ code: 0,
1088
+ text: error instanceof Error ? error.message : "enc.key load failed"
1089
+ },
1090
+ context,
1091
+ null,
1092
+ createLoaderStats()
1093
+ );
1094
+ }).finally(() => {
1095
+ clearTimeout(timer);
1096
+ });
1097
+ return;
953
1098
  } catch {
954
1099
  }
955
1100
  this.inner.load(context, config, callbacks);
@@ -1019,7 +1164,8 @@ var HlsEngine = class {
1019
1164
  const loader = createAuthParamsLoader(Hls, authParams, {
1020
1165
  videoId,
1021
1166
  keyProxyUrlBuilder,
1022
- getKeyAccessToken
1167
+ getKeyAccessToken,
1168
+ keyProxyMaxFailures: this.options.keyProxyMaxFailures
1023
1169
  });
1024
1170
  this.hls = new Hls({
1025
1171
  enableWorker: hlsConfig.enableWorker,
@@ -1443,12 +1589,12 @@ function createVideoPlayer(options) {
1443
1589
  volume: config.player.volume,
1444
1590
  videoId: options.videoId,
1445
1591
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1446
- // Optional: browser Bearer for Video API–origin enc.key (CORS-dependent).
1447
- // CDN keys use signed query via the auth loader (no Bearer) — same as
1448
- // segments. Apps can pass getKeyAccessToken or rely on keyProxyUrlBuilder.
1449
- // When unset, defaults to AuthManager token only if explicitly requested
1450
- // via streaming.getKeyAccessToken; otherwise query-signed XHR.
1451
- getKeyAccessToken: config.streaming.getKeyAccessToken,
1592
+ // Enterprise AES key auth:
1593
+ // 1) keyProxyUrlBuilder (same-origin BFF) when configured — max 2 failures
1594
+ // 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
1595
+ // 3) else signed query + XHR (CDN hosts)
1596
+ getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1597
+ keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1452
1598
  hlsConfig: config.streaming.hls,
1453
1599
  logger,
1454
1600
  callbacks: {
@@ -1766,7 +1912,8 @@ function createVideoPlayer(options) {
1766
1912
  engine: "hls",
1767
1913
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1768
1914
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1769
- getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
1915
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1916
+ keyProxyMaxFailures: partial?.streaming?.keyProxyMaxFailures ?? config.streaming.keyProxyMaxFailures
1770
1917
  },
1771
1918
  logging: { ...config.logging, ...partial?.logging },
1772
1919
  debug: { ...config.debug, ...partial?.debug },
@@ -1797,6 +1944,7 @@ export {
1797
1944
  DEFAULT_THEME,
1798
1945
  DEFAULT_WATERMARK,
1799
1946
  EventEmitter,
1947
+ KEY_PROXY_MAX_FAILURES,
1800
1948
  PACKAGE_VERSION,
1801
1949
  PLAYBACK_RATES,
1802
1950
  PlaybackSourceManager,