@codenkay/video-nsgplayer-core 0.1.2 → 0.1.5

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.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import Hls, { LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
2
+
1
3
  /** Player lifecycle states. */
2
4
  type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffering" | "renewing" | "ended" | "error" | "destroyed";
3
5
  type HlsSupportMode = "hls.js" | "native" | "unsupported";
@@ -115,12 +117,24 @@ type StreamingConfig = {
115
117
  backBufferLength?: number;
116
118
  fatalRecoveryLimit?: number;
117
119
  };
120
+ /**
121
+ * Same-origin BFF proxy for `enc.key` (preferred in production when the
122
+ * server IP is allowlisted by the CDN / Cloudflare).
123
+ */
118
124
  keyProxyUrlBuilder?: (ctx: {
119
125
  videoId: string;
120
126
  keyUrl: string;
121
127
  token?: string;
122
128
  expires?: string;
129
+ username?: string;
130
+ mobile?: string;
123
131
  }) => string;
132
+ /**
133
+ * Browser-direct AES key auth: returns the Video API access token so hls.js
134
+ * can send `Authorization: Bearer …` on `enc.key` requests.
135
+ * Use when the BFF cannot reach Appextech (Cloudflare challenge on server IP).
136
+ */
137
+ getKeyAccessToken?: () => string | null | undefined;
124
138
  };
125
139
  type LoggingConfig = {
126
140
  enabled?: boolean;
@@ -166,6 +180,8 @@ type PlayerConfig = Required<{
166
180
  streaming: {
167
181
  engine: "hls";
168
182
  hls: Required<NonNullable<StreamingConfig["hls"]>>;
183
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
184
+ getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
169
185
  };
170
186
  logging: Required<LoggingConfig>;
171
187
  debug: Required<DebugConfig>;
@@ -239,7 +255,7 @@ type PlayerEventMap = {
239
255
  };
240
256
 
241
257
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
242
- declare const PACKAGE_VERSION = "0.1.2";
258
+ declare const PACKAGE_VERSION = "0.1.5";
243
259
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
244
260
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
245
261
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -270,7 +286,8 @@ type PlayerConfigPartial = Partial<{
270
286
  streaming: {
271
287
  engine?: "hls";
272
288
  hls?: Partial<PlayerConfig["streaming"]["hls"]>;
273
- keyProxyUrlBuilder?: unknown;
289
+ keyProxyUrlBuilder?: PlayerConfig["streaming"]["keyProxyUrlBuilder"];
290
+ getKeyAccessToken?: PlayerConfig["streaming"]["getKeyAccessToken"];
274
291
  };
275
292
  logging: Partial<PlayerConfig["logging"]>;
276
293
  debug: Partial<PlayerConfig["debug"]>;
@@ -343,7 +360,14 @@ declare class AuthParamsStore {
343
360
  constructor(initial?: URLSearchParams | Record<string, string>);
344
361
  getSnapshot(): URLSearchParams;
345
362
  toRecord(): Record<string, string>;
363
+ /** True when at least one auth query param is stored. */
364
+ hasParams(): boolean;
365
+ toString(): string;
346
366
  update(next: URLSearchParams | Record<string, string>): void;
367
+ /**
368
+ * Merges current auth params onto a target URL, preserving unrelated query keys.
369
+ * Critical for child HLS playlists/segments that omit signed query params.
370
+ */
347
371
  applyToUrl(rawUrl: string, baseOrigin?: string): string;
348
372
  }
349
373
 
@@ -376,6 +400,9 @@ type HlsEngineOptions = {
376
400
  startMuted: boolean;
377
401
  startPosition?: number;
378
402
  volume?: number;
403
+ videoId?: string;
404
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
405
+ getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
379
406
  hlsConfig: {
380
407
  enableWorker: boolean;
381
408
  lowLatencyMode: boolean;
@@ -387,6 +414,10 @@ type HlsEngineOptions = {
387
414
  };
388
415
  /**
389
416
  * Dual-path HLS engine: hls.js when supported, otherwise native Safari HLS.
417
+ *
418
+ * Signed CDN playback requires auth query params on every HLS request.
419
+ * Encrypted VOD also requires a same-origin `enc.key` proxy (Bearer + query)
420
+ * via `streaming.keyProxyUrlBuilder` — browser CORS cannot fetch Video API keys.
390
421
  */
391
422
  declare class HlsEngine {
392
423
  private readonly options;
@@ -400,6 +431,8 @@ declare class HlsEngine {
400
431
  constructor(options: HlsEngineOptions);
401
432
  start(): Promise<void>;
402
433
  private startWithHlsJs;
434
+ private handleHlsError;
435
+ private applyStartPositionAndAutoplay;
403
436
  private bindMediaEvents;
404
437
  updateAuthParams(): void;
405
438
  resumeAfterAuthRenew(): void;
@@ -558,7 +591,32 @@ declare class RefreshManager {
558
591
  private renew;
559
592
  }
560
593
 
594
+ type HlsLoader = new (config: object) => {
595
+ load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
596
+ destroy(): void;
597
+ };
598
+ type AuthParamsLoaderOptions = {
599
+ videoId?: string;
600
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
601
+ getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
602
+ };
603
+ /**
604
+ * Rewrites an HLS request URL so signed auth query params are present.
605
+ * Master playlists are signed on loadSource; child playlists / segments / keys
606
+ * are relative and would otherwise be fetched unsigned → CDN 403.
607
+ *
608
+ * AES `enc.key` handling (pick one):
609
+ * 1. `keyProxyUrlBuilder` — same-origin BFF (needs Cloudflare-allowlisted server IP)
610
+ * 2. `getKeyAccessToken` — browser-direct CDN fetch with API Bearer header
611
+ */
612
+ declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
613
+ /**
614
+ * hls.js loader that re-applies live AuthParamsStore credentials on every
615
+ * manifest / level / fragment / key request.
616
+ */
617
+ declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
618
+
561
619
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
562
620
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
563
621
 
564
- 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 PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, seekToLiveEdge, themeToCssVars };
622
+ 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 PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createAuthParamsLoader, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, 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.2";
149
+ var PACKAGE_VERSION = "0.1.5";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -239,7 +239,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
239
239
  "gettoken",
240
240
  "getsource",
241
241
  "refreshsource",
242
- "keyproxyurlbuilder"
242
+ "keyproxyurlbuilder",
243
+ "getkeyaccesstoken"
243
244
  ]);
244
245
  var SECRET_SCAN_MAX_DEPTH = 32;
245
246
  function isPlainObject(value) {
@@ -322,7 +323,9 @@ function mergePlayerConfig(partial) {
322
323
  hls: {
323
324
  ...DEFAULT_PLAYER_CONFIG.streaming.hls,
324
325
  ...partial?.streaming?.hls
325
- }
326
+ },
327
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
328
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken
326
329
  },
327
330
  logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
328
331
  debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
@@ -498,11 +501,25 @@ var AuthParamsStore = class {
498
501
  });
499
502
  return out;
500
503
  }
504
+ /** True when at least one auth query param is stored. */
505
+ hasParams() {
506
+ return [...this.params.keys()].length > 0;
507
+ }
508
+ toString() {
509
+ return this.params.toString();
510
+ }
501
511
  update(next) {
502
512
  this.params = next instanceof URLSearchParams ? new URLSearchParams(next) : new URLSearchParams(next);
503
513
  }
514
+ /**
515
+ * Merges current auth params onto a target URL, preserving unrelated query keys.
516
+ * Critical for child HLS playlists/segments that omit signed query params.
517
+ */
504
518
  applyToUrl(rawUrl, baseOrigin) {
505
- const target = new URL(rawUrl, baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost"));
519
+ const target = new URL(
520
+ rawUrl,
521
+ baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost")
522
+ );
506
523
  this.params.forEach((value, key) => {
507
524
  target.searchParams.set(key, value);
508
525
  });
@@ -737,7 +754,63 @@ function seekToLiveEdge(media, options) {
737
754
  return { moved: true, edge };
738
755
  }
739
756
 
757
+ // src/streaming/authParamsLoader.ts
758
+ var ENC_KEY_PATH_RE = /enc\.key$/i;
759
+ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
760
+ try {
761
+ return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
762
+ } catch {
763
+ return /enc\.key/i.test(rawUrl);
764
+ }
765
+ }
766
+ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
767
+ if (!rawUrl) return rawUrl;
768
+ const parsed = new URL(rawUrl, baseOrigin);
769
+ const isKey = ENC_KEY_PATH_RE.test(parsed.pathname);
770
+ if (isKey && options.keyProxyUrlBuilder && options.videoId) {
771
+ const snapshot = store.toRecord();
772
+ return options.keyProxyUrlBuilder({
773
+ videoId: options.videoId,
774
+ keyUrl: parsed.toString(),
775
+ token: snapshot.token,
776
+ expires: snapshot.expires,
777
+ username: snapshot.username,
778
+ mobile: snapshot.mobile
779
+ });
780
+ }
781
+ if (!store.hasParams()) return parsed.toString();
782
+ return store.applyToUrl(rawUrl, baseOrigin);
783
+ }
784
+ function createAuthParamsLoader(HlsClass, store, options = {}) {
785
+ const BaseLoader = HlsClass.DefaultConfig.loader;
786
+ return class AuthParamsLoader extends BaseLoader {
787
+ load(context, config, callbacks) {
788
+ if (context.url) {
789
+ try {
790
+ const originalUrl = context.url;
791
+ const isKey = isEncryptionKeyUrl(originalUrl);
792
+ context.url = rewriteHlsRequestUrl(originalUrl, store, options);
793
+ if (isKey && !options.keyProxyUrlBuilder && options.getKeyAccessToken) {
794
+ const accessToken = options.getKeyAccessToken();
795
+ if (accessToken) {
796
+ context.headers = {
797
+ ...context.headers ?? {},
798
+ Authorization: `Bearer ${accessToken}`
799
+ };
800
+ }
801
+ }
802
+ } catch {
803
+ }
804
+ }
805
+ super.load(context, config, callbacks);
806
+ }
807
+ };
808
+ }
809
+
740
810
  // src/streaming/HlsEngine.ts
811
+ function isAuthHttpStatus(code) {
812
+ return code === 401 || code === 403;
813
+ }
741
814
  var HlsEngine = class {
742
815
  constructor(options) {
743
816
  this.options = options;
@@ -750,58 +823,58 @@ var HlsEngine = class {
750
823
  this.qualitySelection = "auto";
751
824
  }
752
825
  async start() {
753
- const { videoEl, playbackUrl, autoplay, startMuted, volume, startPosition } = this.options;
826
+ const { videoEl, playbackUrl, startMuted, volume } = this.options;
754
827
  videoEl.muted = startMuted;
755
- if (typeof volume === "number") videoEl.volume = Math.min(1, Math.max(0, volume));
828
+ if (typeof volume === "number") {
829
+ videoEl.volume = Math.min(1, Math.max(0, volume));
830
+ }
756
831
  const Hls = (await import("hls.js")).default;
757
832
  if (Hls.isSupported()) {
758
833
  this.supportMode = "hls.js";
759
834
  this.options.callbacks.onSupportMode?.("hls.js");
760
835
  await this.startWithHlsJs(Hls, playbackUrl);
761
- } else if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
836
+ return;
837
+ }
838
+ if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
762
839
  this.supportMode = "native";
763
840
  this.options.callbacks.onSupportMode?.("native");
764
841
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
765
842
  this.bindMediaEvents();
766
843
  this.options.callbacks.onStateChange?.("ready");
767
- } else {
768
- this.supportMode = "unsupported";
769
- this.options.callbacks.onSupportMode?.("unsupported");
770
- this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
844
+ await this.applyStartPositionAndAutoplay();
771
845
  return;
772
846
  }
773
- if (typeof startPosition === "number" && startPosition > 0) {
774
- videoEl.currentTime = startPosition;
775
- }
776
- if (autoplay) {
777
- try {
778
- await videoEl.play();
779
- } catch {
780
- if (!videoEl.muted) {
781
- videoEl.muted = true;
782
- try {
783
- await videoEl.play();
784
- } catch {
785
- this.options.logger.warn("Autoplay blocked");
786
- }
787
- }
788
- }
789
- }
847
+ this.supportMode = "unsupported";
848
+ this.options.callbacks.onSupportMode?.("unsupported");
849
+ this.options.callbacks.onFatalError?.(
850
+ "HLS is not supported in this browser"
851
+ );
790
852
  }
791
853
  async startWithHlsJs(Hls, playbackUrl) {
792
- const { videoEl, authParams, hlsConfig, callbacks } = this.options;
854
+ const {
855
+ videoEl,
856
+ authParams,
857
+ hlsConfig,
858
+ callbacks,
859
+ videoId,
860
+ keyProxyUrlBuilder,
861
+ getKeyAccessToken
862
+ } = this.options;
863
+ const loader = createAuthParamsLoader(Hls, authParams, {
864
+ videoId,
865
+ keyProxyUrlBuilder,
866
+ getKeyAccessToken
867
+ });
793
868
  this.hls = new Hls({
794
869
  enableWorker: hlsConfig.enableWorker,
795
870
  lowLatencyMode: hlsConfig.lowLatencyMode,
796
871
  backBufferLength: hlsConfig.backBufferLength,
797
- xhrSetup: (xhr, url) => {
798
- void xhr;
799
- void url;
800
- }
872
+ loader
801
873
  });
802
874
  this.hls.attachMedia(videoEl);
803
875
  this.hls.loadSource(authParams.applyToUrl(playbackUrl));
804
876
  this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
877
+ if (this.destroyed) return;
805
878
  this.qualities = (data.levels ?? []).map((level, index) => ({
806
879
  index,
807
880
  height: level.height ?? 0,
@@ -811,42 +884,95 @@ var HlsEngine = class {
811
884
  }));
812
885
  callbacks.onQualitiesChange?.(this.qualities);
813
886
  callbacks.onStateChange?.("ready");
887
+ void this.applyStartPositionAndAutoplay();
814
888
  });
815
889
  this.hls.on(Hls.Events.ERROR, (_event, data) => {
816
- if (!data.fatal) {
817
- if (data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR)) {
818
- callbacks.onAuthRecoveryNeeded?.(
819
- data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
820
- );
890
+ this.handleHlsError(Hls, data);
891
+ });
892
+ this.bindMediaEvents();
893
+ }
894
+ handleHlsError(Hls, data) {
895
+ if (this.destroyed) return;
896
+ const { callbacks, hlsConfig, keyProxyUrlBuilder, getKeyAccessToken } = this.options;
897
+ const status = data.response?.code;
898
+ if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && !keyProxyUrlBuilder && !getKeyAccessToken) {
899
+ callbacks.onFatalError?.(
900
+ "Encryption key load failed. Configure streaming.getKeyAccessToken (browser Bearer) or streaming.keyProxyUrlBuilder (Cloudflare-allowlisted BFF) for Appextech AES-encrypted HLS."
901
+ );
902
+ return;
903
+ }
904
+ const authFailure = isAuthHttpStatus(status) || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && (status === void 0 || status === 0 || isAuthHttpStatus(status));
905
+ if (!data.fatal) {
906
+ if (authFailure && data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR)) {
907
+ callbacks.onAuthRecoveryNeeded?.(
908
+ data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
909
+ );
910
+ }
911
+ return;
912
+ }
913
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
914
+ this.networkRecoveryCount += 1;
915
+ if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
916
+ if (authFailure) {
917
+ callbacks.onAuthRecoveryNeeded?.("fatal_network");
821
918
  }
919
+ this.hls?.startLoad();
822
920
  return;
823
921
  }
824
- if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
825
- this.networkRecoveryCount += 1;
826
- if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
827
- callbacks.onAuthRecoveryNeeded?.("fatal_network");
828
- this.hls?.startLoad();
829
- return;
922
+ }
923
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
924
+ this.mediaRecoveryCount += 1;
925
+ if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
926
+ this.hls?.recoverMediaError();
927
+ return;
928
+ }
929
+ }
930
+ callbacks.onFatalError?.(data.details || "Fatal HLS error");
931
+ }
932
+ async applyStartPositionAndAutoplay() {
933
+ if (this.destroyed) return;
934
+ const { videoEl, autoplay, startPosition } = this.options;
935
+ if (typeof startPosition === "number" && startPosition > 0) {
936
+ const seek = () => {
937
+ if (this.destroyed) return;
938
+ try {
939
+ videoEl.currentTime = startPosition;
940
+ } catch {
830
941
  }
942
+ };
943
+ if (videoEl.seekable.length > 0) {
944
+ seek();
945
+ } else {
946
+ videoEl.addEventListener("loadedmetadata", seek, { once: true });
831
947
  }
832
- if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
833
- this.mediaRecoveryCount += 1;
834
- if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
835
- this.hls?.recoverMediaError();
836
- return;
948
+ }
949
+ if (!autoplay) return;
950
+ try {
951
+ await videoEl.play();
952
+ } catch {
953
+ if (!videoEl.muted) {
954
+ videoEl.muted = true;
955
+ try {
956
+ await videoEl.play();
957
+ } catch {
958
+ this.options.logger.warn("Autoplay blocked");
837
959
  }
838
960
  }
839
- callbacks.onFatalError?.(data.details || "Fatal HLS error");
840
- });
841
- this.bindMediaEvents();
961
+ }
842
962
  }
843
963
  bindMediaEvents() {
844
964
  const { videoEl, callbacks } = this.options;
845
- videoEl.addEventListener("playing", () => callbacks.onStateChange?.("playing"));
965
+ videoEl.addEventListener(
966
+ "playing",
967
+ () => callbacks.onStateChange?.("playing")
968
+ );
846
969
  videoEl.addEventListener("pause", () => {
847
970
  if (!videoEl.ended) callbacks.onStateChange?.("paused");
848
971
  });
849
- videoEl.addEventListener("waiting", () => callbacks.onStateChange?.("buffering"));
972
+ videoEl.addEventListener(
973
+ "waiting",
974
+ () => callbacks.onStateChange?.("buffering")
975
+ );
850
976
  videoEl.addEventListener("ended", () => {
851
977
  callbacks.onStateChange?.("ended");
852
978
  callbacks.onEnded?.();
@@ -864,6 +990,7 @@ var HlsEngine = class {
864
990
  }
865
991
  }
866
992
  resumeAfterAuthRenew() {
993
+ this.networkRecoveryCount = 0;
867
994
  this.hls?.startLoad();
868
995
  this.updateAuthParams();
869
996
  }
@@ -1005,6 +1132,9 @@ function createVideoPlayer(options) {
1005
1132
  startMuted: config.player.startMuted,
1006
1133
  startPosition: config.player.startPosition,
1007
1134
  volume: config.player.volume,
1135
+ videoId: options.videoId,
1136
+ keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1137
+ getKeyAccessToken: config.streaming.getKeyAccessToken,
1008
1138
  hlsConfig: config.streaming.hls,
1009
1139
  logger,
1010
1140
  callbacks: {
@@ -1065,6 +1195,14 @@ function createVideoPlayer(options) {
1065
1195
  onRenewing: () => setState("renewing"),
1066
1196
  onRenewed: (nextExpiresAt) => {
1067
1197
  engine?.resumeAfterAuthRenew();
1198
+ if (!destroyed) {
1199
+ const el2 = videoEl;
1200
+ if (el2 && !el2.paused && !el2.ended) {
1201
+ setState("playing");
1202
+ } else {
1203
+ setState("ready");
1204
+ }
1205
+ }
1068
1206
  events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
1069
1207
  events.emit("sourcechange", { expiresAt: nextExpiresAt });
1070
1208
  },
@@ -1265,7 +1403,9 @@ function createVideoPlayer(options) {
1265
1403
  watermark: { ...config.watermark, ...partial?.watermark },
1266
1404
  streaming: {
1267
1405
  engine: "hls",
1268
- hls: { ...config.streaming.hls, ...partial?.streaming?.hls }
1406
+ hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1407
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1408
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
1269
1409
  },
1270
1410
  logging: { ...config.logging, ...partial?.logging },
1271
1411
  debug: { ...config.debug, ...partial?.debug },
@@ -1303,6 +1443,7 @@ export {
1303
1443
  RefreshManager,
1304
1444
  SECRET_SCAN_MAX_DEPTH,
1305
1445
  assertNoSecretsInConfig,
1446
+ createAuthParamsLoader,
1306
1447
  createPlayerError,
1307
1448
  createVideoPlayer,
1308
1449
  getLiveEdgeTime,
@@ -1312,6 +1453,7 @@ export {
1312
1453
  redactObject,
1313
1454
  redactSensitive,
1314
1455
  redactValue,
1456
+ rewriteHlsRequestUrl,
1315
1457
  seekToLiveEdge,
1316
1458
  themeToCssVars
1317
1459
  };