@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/dist/index.d.cts CHANGED
@@ -1,5 +1,3 @@
1
- import Hls, { LoaderStats, LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
2
-
3
1
  /** Player lifecycle states. */
4
2
  type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffering" | "renewing" | "ended" | "error" | "destroyed";
5
3
  type HlsSupportMode = "hls.js" | "native" | "unsupported";
@@ -89,6 +87,11 @@ type ControlsConfig = {
89
87
  showLive?: boolean;
90
88
  seekCueDurationMs?: number;
91
89
  keyboardShortcuts?: boolean;
90
+ /**
91
+ * When true, UI chrome persists the last quality choice (by height) in
92
+ * localStorage and restores it for later sessions.
93
+ */
94
+ persistQualityPreference?: boolean;
92
95
  };
93
96
  type ThemeConfig = {
94
97
  playIconPreset?: PlayIconPreset;
@@ -286,7 +289,7 @@ type PlayerEventMap = {
286
289
  };
287
290
 
288
291
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
289
- declare const PACKAGE_VERSION = "3.0.1";
292
+ declare const PACKAGE_VERSION = "3.0.3";
290
293
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
291
294
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
292
295
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -456,7 +459,8 @@ type HlsEngineOptions = {
456
459
  * (playlists, segments, and AES `enc.key` on CDN hosts).
457
460
  *
458
461
  * Optional `streaming.keyProxyUrlBuilder` (same-origin BFF) or
459
- * `streaming.getKeyAccessToken` (browser Bearer) for Video API–origin keys
462
+ * `streaming.getKeyAccessToken` (Bearer only after signed-query 401/403).
463
+ * CDN keys prefer signed query without Authorization (no CORS preflight).
460
464
  * when query-signed CDN access is insufficient.
461
465
  */
462
466
  declare class HlsEngine {
@@ -653,6 +657,54 @@ declare class RefreshManager {
653
657
  private renew;
654
658
  }
655
659
 
660
+ /**
661
+ * Local loader types (structurally compatible with hls.js) so the public
662
+ * `.d.ts` does not require a value import of optional peer `hls.js`.
663
+ */
664
+ type HlsLoaderTiming = {
665
+ start: number;
666
+ first: number;
667
+ end: number;
668
+ };
669
+ type LoaderStats = {
670
+ aborted: boolean;
671
+ loaded: number;
672
+ retry: number;
673
+ total: number;
674
+ chunkCount: number;
675
+ bwEstimate: number;
676
+ loading: HlsLoaderTiming;
677
+ parsing: {
678
+ start: number;
679
+ end: number;
680
+ };
681
+ buffering: HlsLoaderTiming;
682
+ };
683
+ type LoaderContext = {
684
+ url: string;
685
+ responseType?: string;
686
+ rangeStart?: number;
687
+ rangeEnd?: number;
688
+ progressData?: boolean;
689
+ [key: string]: unknown;
690
+ };
691
+ type LoaderConfiguration = {
692
+ timeout?: number;
693
+ };
694
+ type LoaderCallbacks<TContext> = {
695
+ onSuccess: (response: {
696
+ url: string;
697
+ data: ArrayBuffer | string;
698
+ code?: number;
699
+ }, stats: LoaderStats, context: TContext, networkDetails: unknown) => void;
700
+ onError: (error: {
701
+ code: number;
702
+ text: string;
703
+ }, context: TContext, networkDetails: unknown, stats: LoaderStats) => void;
704
+ onTimeout?: (...args: unknown[]) => void;
705
+ onAbort?: (...args: unknown[]) => void;
706
+ onProgress?: (...args: unknown[]) => void;
707
+ };
656
708
  type HlsLoaderInstance = {
657
709
  stats: LoaderStats;
658
710
  context: LoaderContext | null;
@@ -662,6 +714,8 @@ type HlsLoaderInstance = {
662
714
  };
663
715
  /** Constructor shape compatible with `Hls.DefaultConfig.loader`. */
664
716
  type HlsLoaderConstructor = new (config: object) => HlsLoaderInstance;
717
+ /** Public alias — pass `hls.js` default export (typed as object at the boundary). */
718
+ type HlsModuleLike = object;
665
719
  /** Max same-origin BFF key attempts before the session circuit opens. */
666
720
  declare const KEY_PROXY_MAX_FAILURES = 2;
667
721
  type AuthParamsLoaderOptions = {
@@ -673,30 +727,19 @@ type AuthParamsLoaderOptions = {
673
727
  };
674
728
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
675
729
  /**
676
- * Always resolve a builtin hls.js loader (XhrLoader).
677
- *
678
- * Never trust `Hls.DefaultConfig.loader` alone — if it was reassigned to a
679
- * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
680
- * stack (`InternalError: too much recursion`).
730
+ * Resolve the builtin loader from the **same** `hls.js` module instance the
731
+ * engine loaded. Never statically import `XhrLoader` — that can pull a second
732
+ * hls.js copy into the app bundle after npm pack/publish and break key loads.
681
733
  */
682
- declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
734
+ declare function resolveBuiltinHlsLoader(HlsModule: HlsModuleLike): HlsLoaderConstructor;
683
735
  /**
684
736
  * Rewrites an HLS request URL so signed auth query params are present.
685
- *
686
- * AES `enc.key` handling (enterprise priority order):
687
- * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
688
- * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
689
- * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
690
737
  */
691
738
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
692
739
  /**
693
- * hls.js loader that:
694
- * - Re-applies signed auth query params on every media request
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).
740
+ * hls.js loader: auth query rewrite + enterprise AES key loading.
698
741
  */
699
- declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
742
+ declare function createAuthParamsLoader(HlsModule: HlsModuleLike, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
700
743
 
701
744
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
702
745
  /**
@@ -705,4 +748,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
705
748
  */
706
749
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
707
750
 
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 };
751
+ 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 HlsLoaderConstructor, type HlsModuleLike, 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
@@ -1,5 +1,3 @@
1
- import Hls, { LoaderStats, LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
2
-
3
1
  /** Player lifecycle states. */
4
2
  type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffering" | "renewing" | "ended" | "error" | "destroyed";
5
3
  type HlsSupportMode = "hls.js" | "native" | "unsupported";
@@ -89,6 +87,11 @@ type ControlsConfig = {
89
87
  showLive?: boolean;
90
88
  seekCueDurationMs?: number;
91
89
  keyboardShortcuts?: boolean;
90
+ /**
91
+ * When true, UI chrome persists the last quality choice (by height) in
92
+ * localStorage and restores it for later sessions.
93
+ */
94
+ persistQualityPreference?: boolean;
92
95
  };
93
96
  type ThemeConfig = {
94
97
  playIconPreset?: PlayIconPreset;
@@ -286,7 +289,7 @@ type PlayerEventMap = {
286
289
  };
287
290
 
288
291
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
289
- declare const PACKAGE_VERSION = "3.0.1";
292
+ declare const PACKAGE_VERSION = "3.0.3";
290
293
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
291
294
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
292
295
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -456,7 +459,8 @@ type HlsEngineOptions = {
456
459
  * (playlists, segments, and AES `enc.key` on CDN hosts).
457
460
  *
458
461
  * Optional `streaming.keyProxyUrlBuilder` (same-origin BFF) or
459
- * `streaming.getKeyAccessToken` (browser Bearer) for Video API–origin keys
462
+ * `streaming.getKeyAccessToken` (Bearer only after signed-query 401/403).
463
+ * CDN keys prefer signed query without Authorization (no CORS preflight).
460
464
  * when query-signed CDN access is insufficient.
461
465
  */
462
466
  declare class HlsEngine {
@@ -653,6 +657,54 @@ declare class RefreshManager {
653
657
  private renew;
654
658
  }
655
659
 
660
+ /**
661
+ * Local loader types (structurally compatible with hls.js) so the public
662
+ * `.d.ts` does not require a value import of optional peer `hls.js`.
663
+ */
664
+ type HlsLoaderTiming = {
665
+ start: number;
666
+ first: number;
667
+ end: number;
668
+ };
669
+ type LoaderStats = {
670
+ aborted: boolean;
671
+ loaded: number;
672
+ retry: number;
673
+ total: number;
674
+ chunkCount: number;
675
+ bwEstimate: number;
676
+ loading: HlsLoaderTiming;
677
+ parsing: {
678
+ start: number;
679
+ end: number;
680
+ };
681
+ buffering: HlsLoaderTiming;
682
+ };
683
+ type LoaderContext = {
684
+ url: string;
685
+ responseType?: string;
686
+ rangeStart?: number;
687
+ rangeEnd?: number;
688
+ progressData?: boolean;
689
+ [key: string]: unknown;
690
+ };
691
+ type LoaderConfiguration = {
692
+ timeout?: number;
693
+ };
694
+ type LoaderCallbacks<TContext> = {
695
+ onSuccess: (response: {
696
+ url: string;
697
+ data: ArrayBuffer | string;
698
+ code?: number;
699
+ }, stats: LoaderStats, context: TContext, networkDetails: unknown) => void;
700
+ onError: (error: {
701
+ code: number;
702
+ text: string;
703
+ }, context: TContext, networkDetails: unknown, stats: LoaderStats) => void;
704
+ onTimeout?: (...args: unknown[]) => void;
705
+ onAbort?: (...args: unknown[]) => void;
706
+ onProgress?: (...args: unknown[]) => void;
707
+ };
656
708
  type HlsLoaderInstance = {
657
709
  stats: LoaderStats;
658
710
  context: LoaderContext | null;
@@ -662,6 +714,8 @@ type HlsLoaderInstance = {
662
714
  };
663
715
  /** Constructor shape compatible with `Hls.DefaultConfig.loader`. */
664
716
  type HlsLoaderConstructor = new (config: object) => HlsLoaderInstance;
717
+ /** Public alias — pass `hls.js` default export (typed as object at the boundary). */
718
+ type HlsModuleLike = object;
665
719
  /** Max same-origin BFF key attempts before the session circuit opens. */
666
720
  declare const KEY_PROXY_MAX_FAILURES = 2;
667
721
  type AuthParamsLoaderOptions = {
@@ -673,30 +727,19 @@ type AuthParamsLoaderOptions = {
673
727
  };
674
728
  declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
675
729
  /**
676
- * Always resolve a builtin hls.js loader (XhrLoader).
677
- *
678
- * Never trust `Hls.DefaultConfig.loader` alone — if it was reassigned to a
679
- * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
680
- * stack (`InternalError: too much recursion`).
730
+ * Resolve the builtin loader from the **same** `hls.js` module instance the
731
+ * engine loaded. Never statically import `XhrLoader` — that can pull a second
732
+ * hls.js copy into the app bundle after npm pack/publish and break key loads.
681
733
  */
682
- declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoaderConstructor;
734
+ declare function resolveBuiltinHlsLoader(HlsModule: HlsModuleLike): HlsLoaderConstructor;
683
735
  /**
684
736
  * Rewrites an HLS request URL so signed auth query params are present.
685
- *
686
- * AES `enc.key` handling (enterprise priority order):
687
- * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
688
- * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
689
- * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
690
737
  */
691
738
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
692
739
  /**
693
- * hls.js loader that:
694
- * - Re-applies signed auth query params on every media request
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).
740
+ * hls.js loader: auth query rewrite + enterprise AES key loading.
698
741
  */
699
- declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
742
+ declare function createAuthParamsLoader(HlsModule: HlsModuleLike, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoaderConstructor;
700
743
 
701
744
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
702
745
  /**
@@ -705,4 +748,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
705
748
  */
706
749
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
707
750
 
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 };
751
+ 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 HlsLoaderConstructor, type HlsModuleLike, 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 = "3.0.1";
149
+ var PACKAGE_VERSION = "3.0.3";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -170,7 +170,8 @@ var DEFAULT_CONTROLS = {
170
170
  showSkip: true,
171
171
  showLive: true,
172
172
  seekCueDurationMs: 720,
173
- keyboardShortcuts: true
173
+ keyboardShortcuts: true,
174
+ persistQualityPreference: false
174
175
  };
175
176
  var DEFAULT_THEME = {
176
177
  playIconPreset: "triangle",
@@ -766,9 +767,12 @@ function seekToLiveEdge(media, options) {
766
767
  }
767
768
 
768
769
  // src/streaming/authParamsLoader.ts
769
- import { XhrLoader as BuiltinXhrLoader } from "hls.js";
770
770
  var ENC_KEY_PATH_RE = /enc\.key$/i;
771
771
  var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
772
+ var BUILTIN_LOADER_REF = /* @__PURE__ */ Symbol.for(
773
+ "@codenkay/video-nsgplayer-core/builtin-hls-loader"
774
+ );
775
+ var builtinLoaderByHls = /* @__PURE__ */ new WeakMap();
772
776
  var KEY_PROXY_MAX_FAILURES = 2;
773
777
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
774
778
  try {
@@ -777,18 +781,35 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
777
781
  return /enc\.key/i.test(rawUrl);
778
782
  }
779
783
  }
780
- function resolveBuiltinHlsLoader(HlsClass) {
781
- if (typeof BuiltinXhrLoader === "function") {
782
- return BuiltinXhrLoader;
784
+ function isBuiltinLoaderName(name) {
785
+ return BUILTIN_LOADER_NAMES.has(name);
786
+ }
787
+ function rememberBuiltin(HlsClass, loader) {
788
+ if (isBuiltinLoaderName(typeof loader.name === "string" ? loader.name : "")) {
789
+ builtinLoaderByHls.set(HlsClass, loader);
783
790
  }
791
+ return loader;
792
+ }
793
+ function getStashedBuiltinLoader(loader) {
794
+ return loader[BUILTIN_LOADER_REF];
795
+ }
796
+ function resolveBuiltinHlsLoader(HlsModule) {
797
+ const HlsClass = HlsModule;
798
+ const cached = builtinLoaderByHls.get(HlsClass);
799
+ if (cached) return cached;
784
800
  const start = HlsClass.DefaultConfig.loader;
785
801
  let current = start;
786
802
  const seen = /* @__PURE__ */ new Set();
787
803
  while (current && !seen.has(current)) {
788
804
  seen.add(current);
789
805
  const name = typeof current.name === "string" ? current.name : "";
790
- if (BUILTIN_LOADER_NAMES.has(name)) {
791
- return current;
806
+ if (isBuiltinLoaderName(name)) {
807
+ return rememberBuiltin(HlsClass, current);
808
+ }
809
+ const stashed = getStashedBuiltinLoader(current);
810
+ if (stashed) {
811
+ current = stashed;
812
+ continue;
792
813
  }
793
814
  const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
794
815
  if (!parentCtor || parentCtor === Object || parentCtor === Function) {
@@ -796,7 +817,8 @@ function resolveBuiltinHlsLoader(HlsClass) {
796
817
  }
797
818
  current = parentCtor;
798
819
  }
799
- return start;
820
+ const fallback = builtinLoaderByHls.get(HlsClass);
821
+ return fallback ?? start;
800
822
  }
801
823
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
802
824
  if (!rawUrl) return rawUrl;
@@ -844,67 +866,82 @@ function isProxyUpstreamBlocked(status, contentType, bodyHead, errorText) {
844
866
  }
845
867
  return false;
846
868
  }
847
- async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
848
- const stats = createLoaderStats();
849
- stats.loading.first = performance.now();
850
- const headers = {
851
- Accept: "*/*",
852
- ...context.headers
853
- };
854
- if (accessToken) {
855
- headers.Authorization = `Bearer ${accessToken}`;
856
- }
857
- const response = await fetch(context.url, {
858
- method: "GET",
859
- headers,
860
- credentials: "omit",
861
- cache: "no-store",
862
- signal
863
- });
864
- if (!response.ok) {
865
- callbacks.onError(
866
- {
867
- code: response.status,
868
- text: response.statusText || `enc.key HTTP ${response.status}`
869
- },
870
- context,
869
+ async function fetchEncryptionKeyBytes(url, accessToken, signal) {
870
+ const headers = accessToken ? { Authorization: `Bearer ${accessToken}`, Accept: "*/*" } : void 0;
871
+ try {
872
+ const response = await fetch(url, {
873
+ method: "GET",
874
+ ...headers ? { headers } : {},
875
+ credentials: "omit",
876
+ cache: "no-store",
877
+ signal
878
+ });
879
+ if (!response.ok) {
880
+ return {
881
+ ok: false,
882
+ status: response.status,
883
+ text: response.statusText || `enc.key HTTP ${response.status}`,
884
+ cors: false,
885
+ response
886
+ };
887
+ }
888
+ const data = await response.arrayBuffer();
889
+ const head = new TextDecoder().decode(data.slice(0, 32));
890
+ if (/<!DOCTYPE|<html/i.test(head)) {
891
+ return {
892
+ ok: false,
893
+ status: 403,
894
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key",
895
+ cors: false,
896
+ response
897
+ };
898
+ }
899
+ return {
900
+ ok: true,
901
+ data,
871
902
  response,
872
- stats
873
- );
874
- return;
903
+ url: response.url || url
904
+ };
905
+ } catch (error) {
906
+ if (error instanceof DOMException && error.name === "AbortError") {
907
+ return {
908
+ ok: false,
909
+ status: 0,
910
+ text: "enc.key fetch aborted/timeout",
911
+ cors: false,
912
+ response: null
913
+ };
914
+ }
915
+ const cors = error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message);
916
+ return {
917
+ ok: false,
918
+ status: 0,
919
+ text: error instanceof Error ? error.message : "enc.key fetch failed",
920
+ cors,
921
+ response: null
922
+ };
875
923
  }
876
- const data = await response.arrayBuffer();
924
+ }
925
+ function reportKeySuccess(context, callbacks, result) {
926
+ const stats = createLoaderStats();
877
927
  const end = performance.now();
928
+ stats.loading.first = stats.loading.start;
878
929
  stats.loading.end = end;
879
- stats.loaded = data.byteLength;
880
- stats.total = data.byteLength;
881
- const head = new TextDecoder().decode(data.slice(0, 32));
882
- if (/<!DOCTYPE|<html/i.test(head)) {
883
- callbacks.onError(
884
- {
885
- code: 403,
886
- text: "enc.key returned HTML (CDN challenge/block), not an AES key"
887
- },
888
- context,
889
- response,
890
- stats
891
- );
892
- return;
893
- }
930
+ stats.loaded = result.data.byteLength;
931
+ stats.total = result.data.byteLength;
894
932
  callbacks.onSuccess(
895
933
  {
896
- url: response.url || context.url,
897
- data,
898
- code: response.status
934
+ url: result.url,
935
+ data: result.data,
936
+ code: result.response.status
899
937
  },
900
938
  stats,
901
939
  context,
902
- response
940
+ result.response
903
941
  );
904
942
  }
905
943
  async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, state) {
906
944
  const stats = createLoaderStats();
907
- stats.loading.first = performance.now();
908
945
  const canTryProxy = Boolean(plan.proxyUrl) && !state.circuitOpen && state.proxyFailures < plan.maxProxyFailures;
909
946
  if (canTryProxy && plan.proxyUrl) {
910
947
  while (!state.circuitOpen && state.proxyFailures < plan.maxProxyFailures && !signal.aborted) {
@@ -975,47 +1012,71 @@ async function loadEncryptionKeyEnterprise(context, callbacks, plan, signal, sta
975
1012
  }
976
1013
  state.circuitOpen = true;
977
1014
  }
978
- if (plan.accessToken) {
979
- try {
980
- await loadEncryptionKeyWithFetch(
1015
+ const queryResult = await fetchEncryptionKeyBytes(
1016
+ plan.signedKeyUrl,
1017
+ null,
1018
+ signal
1019
+ );
1020
+ if (queryResult.ok) {
1021
+ reportKeySuccess(
1022
+ { ...context, url: plan.signedKeyUrl },
1023
+ callbacks,
1024
+ queryResult
1025
+ );
1026
+ return "ok";
1027
+ }
1028
+ const needsBearer = Boolean(plan.accessToken) && (queryResult.status === 401 || queryResult.status === 403 || // Browser-direct: opaque CORS/network on unsigned GET — try Bearer once
1029
+ // Appextech allows ACAO + Authorization (AUTH_MODE=browser, no BFF).
1030
+ queryResult.cors || queryResult.status === 0);
1031
+ if (needsBearer && plan.accessToken) {
1032
+ const bearerResult = await fetchEncryptionKeyBytes(
1033
+ plan.signedKeyUrl,
1034
+ plan.accessToken,
1035
+ signal
1036
+ );
1037
+ if (bearerResult.ok) {
1038
+ reportKeySuccess(
981
1039
  { ...context, url: plan.signedKeyUrl },
982
1040
  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
1041
+ bearerResult
1008
1042
  );
1009
1043
  return "ok";
1010
1044
  }
1045
+ if (bearerResult.cors || bearerResult.status === 0) {
1046
+ return "try_xhr";
1047
+ }
1048
+ callbacks.onError(
1049
+ {
1050
+ code: bearerResult.status,
1051
+ text: bearerResult.text
1052
+ },
1053
+ { ...context, url: plan.signedKeyUrl },
1054
+ bearerResult.response,
1055
+ stats
1056
+ );
1057
+ return "ok";
1058
+ }
1059
+ if (queryResult.cors || queryResult.status === 0) {
1060
+ return "try_xhr";
1011
1061
  }
1012
- return "try_xhr";
1062
+ callbacks.onError(
1063
+ {
1064
+ code: queryResult.status,
1065
+ text: queryResult.text
1066
+ },
1067
+ { ...context, url: plan.signedKeyUrl },
1068
+ queryResult.response,
1069
+ stats
1070
+ );
1071
+ return "ok";
1013
1072
  }
1014
- function createAuthParamsLoader(HlsClass, store, options = {}) {
1073
+ function createAuthParamsLoader(HlsModule, store, options = {}) {
1074
+ const HlsClass = HlsModule;
1015
1075
  const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
1076
+ rememberBuiltin(HlsClass, BaseLoader);
1016
1077
  const maxProxyFailures = typeof options.keyProxyMaxFailures === "number" && options.keyProxyMaxFailures >= 0 ? options.keyProxyMaxFailures : KEY_PROXY_MAX_FAILURES;
1017
1078
  const keyProxyState = { proxyFailures: 0, circuitOpen: false };
1018
- return class AuthParamsLoader {
1079
+ class AuthParamsLoader {
1019
1080
  constructor(config) {
1020
1081
  this.context = null;
1021
1082
  this.keyAbort = null;
@@ -1099,7 +1160,13 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
1099
1160
  }
1100
1161
  this.inner.load(context, config, callbacks);
1101
1162
  }
1102
- };
1163
+ }
1164
+ Object.defineProperty(AuthParamsLoader, BUILTIN_LOADER_REF, {
1165
+ value: BaseLoader,
1166
+ enumerable: false,
1167
+ configurable: false
1168
+ });
1169
+ return AuthParamsLoader;
1103
1170
  }
1104
1171
 
1105
1172
  // src/streaming/HlsEngine.ts
@@ -1214,7 +1281,7 @@ var HlsEngine = class {
1214
1281
  if (!data.fatal) return;
1215
1282
  } else if (!data.fatal) {
1216
1283
  callbacks.onFatalError?.(
1217
- status ? `Encryption key load failed (HTTP ${status}).` : "Encryption key load failed (network/CORS). Ensure signed query params are applied, or use keyProxyUrlBuilder / getKeyAccessToken when the key host requires API Bearer."
1284
+ 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."
1218
1285
  );
1219
1286
  return;
1220
1287
  }
@@ -1589,10 +1656,11 @@ function createVideoPlayer(options) {
1589
1656
  volume: config.player.volume,
1590
1657
  videoId: options.videoId,
1591
1658
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
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)
1659
+ // Enterprise AES key auth (see authParamsLoader):
1660
+ // 1) keyProxyUrlBuilder BFF — max 2 failures then circuit
1661
+ // 2) signed-query fetch (no Bearer / no CORS preflight)
1662
+ // 3) Bearer only on HTTP 401/403 when getKeyAccessToken is set
1663
+ // 4) signed-query XHR fallback if fetch is CORS-blocked
1596
1664
  getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1597
1665
  keyProxyMaxFailures: config.streaming.keyProxyMaxFailures,
1598
1666
  hlsConfig: config.streaming.hls,