@codenkay/video-nsgplayer-core 0.1.3 → 1.0.0

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,15 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - Fix minor errors
8
+
9
+ ### Minor Changes
10
+
11
+ - 1a4dc9b: Initial public SDK packages: core createVideoPlayer API with dual auth/playback lifecycles, React and Angular adapters, full PlayerConfig parity with demo settings.
12
+
13
+ ## 0.1.8
14
+
15
+ - Fix Firefox `InternalError: too much recursion` from nested hls.js loaders:
16
+ `createAuthParamsLoader` now **composes** a builtin `XhrLoader`/`FetchLoader`
17
+ instead of `extends DefaultConfig.loader` (which could nest `super.load()` after
18
+ HMR / repeated mounts if DefaultConfig was polluted).
19
+ - `resolveBuiltinHlsLoader()` walks past any wrapper classes to the builtin loader.
20
+
21
+ ## 0.1.7
22
+
23
+ - Fix false AES key fatal: remove the post-publish gate that required
24
+ `getKeyAccessToken` or `keyProxyUrlBuilder` even when CDN `enc.key` only needs
25
+ signed query params (same auth as segments — pre-0.1.5 behavior).
26
+ - Load `enc.key` via signed query + XHR by default; use browser `fetch` + Bearer
27
+ only when `streaming.getKeyAccessToken` returns a token; use BFF when
28
+ `keyProxyUrlBuilder` is set.
29
+ - Clearer key-load errors that report HTTP/network cause instead of a configure prompt.
30
+
31
+ ## 0.1.6
32
+
33
+ - Fix false "Playback refresh failed": `PlayerError` is not an `Error`, so refresh failures lost their real message.
34
+ - Do not treat HLS key CORS/`status 0` as signed-URL expiry (that caused renew loops). Only 401/403 trigger auth recovery.
35
+
36
+ ## 0.1.5
37
+
38
+ - Add `streaming.getKeyAccessToken` for browser-direct AES `enc.key` loads with `Authorization: Bearer`.
39
+
40
+ ## 0.1.4
41
+
42
+ - Fix stall after segments return 200; renew only on 401/403; clear sticky renewing state.
43
+
3
44
  ## 0.1.3
4
45
 
5
- - Fix CDN **403 on child HLS playlists/segments** (`stream_N.m3u8`, `.ts`, keys): hls.js now uses an auth-params loader that re-applies signed `token`/`expires` query params on every request, not only the master playlist.
6
- - Preserve optional `streaming.keyProxyUrlBuilder` through config merge.
46
+ - Re-apply signed query params on every HLS child request (fix CDN 403).
7
47
 
8
48
  ## 0.1.2
9
49
 
10
- - Fix `InternalError: too much recursion` / stack overflow in `assertNoSecretsInConfig` when `createVideoPlayer` options include an `HTMLVideoElement` mount or credential callbacks.
11
- - Secret scanning is now plain-object only, iterative, depth-capped, and does not deep-walk `mount` / `auth` / `playback`.
12
- - Harden `redactObject` against circular graphs.
50
+ - Fix `too much recursion` in secret config scanning.
13
51
 
14
52
  ## 0.1.1
15
53
 
package/dist/index.cjs CHANGED
@@ -51,11 +51,13 @@ __export(index_exports, {
51
51
  createVideoPlayer: () => createVideoPlayer,
52
52
  getLiveEdgeTime: () => getLiveEdgeTime,
53
53
  isAtLiveEdge: () => isAtLiveEdge,
54
+ isEncryptionKeyUrl: () => isEncryptionKeyUrl,
54
55
  isPlainObject: () => isPlainObject,
55
56
  mergePlayerConfig: () => mergePlayerConfig,
56
57
  redactObject: () => redactObject,
57
58
  redactSensitive: () => redactSensitive,
58
59
  redactValue: () => redactValue,
60
+ resolveBuiltinHlsLoader: () => resolveBuiltinHlsLoader,
59
61
  rewriteHlsRequestUrl: () => rewriteHlsRequestUrl,
60
62
  seekToLiveEdge: () => seekToLiveEdge,
61
63
  themeToCssVars: () => themeToCssVars
@@ -210,7 +212,7 @@ var AuthManager = class {
210
212
 
211
213
  // src/config/defaults.ts
212
214
  var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
213
- var PACKAGE_VERSION = "0.1.3";
215
+ var PACKAGE_VERSION = "0.1.8";
214
216
  var DEFAULT_PLAYER_BEHAVIOR = {
215
217
  autoplay: true,
216
218
  startMuted: true,
@@ -303,7 +305,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
303
305
  "gettoken",
304
306
  "getsource",
305
307
  "refreshsource",
306
- "keyproxyurlbuilder"
308
+ "keyproxyurlbuilder",
309
+ "getkeyaccesstoken"
307
310
  ]);
308
311
  var SECRET_SCAN_MAX_DEPTH = 32;
309
312
  function isPlainObject(value) {
@@ -387,7 +390,8 @@ function mergePlayerConfig(partial) {
387
390
  ...DEFAULT_PLAYER_CONFIG.streaming.hls,
388
391
  ...partial?.streaming?.hls
389
392
  },
390
- keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
393
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
394
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken
391
395
  },
392
396
  logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
393
397
  debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
@@ -468,23 +472,25 @@ function isPlainObject2(value) {
468
472
  const proto = Object.getPrototypeOf(value);
469
473
  return proto === Object.prototype || proto === null;
470
474
  }
471
- function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
472
- if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
473
- if (typeof value === "string") {
474
- if (/^https?:\/\//i.test(value) && /[?&](token|expires)=/i.test(value)) {
475
- try {
476
- const url = new URL(value);
477
- for (const param of ["token", "expires", "signature", "sig"]) {
478
- if (url.searchParams.has(param)) {
479
- url.searchParams.set(param, "[REDACTED]");
480
- }
481
- }
482
- return url.toString();
483
- } catch {
484
- return "[REDACTED_URL]";
475
+ function redactStringUrl(value) {
476
+ if (!/^https?:\/\//i.test(value) || !/[?&](token|expires)=/i.test(value)) {
477
+ return value;
478
+ }
479
+ try {
480
+ const url = new URL(value);
481
+ for (const param of ["token", "expires", "signature", "sig"]) {
482
+ if (url.searchParams.has(param)) {
483
+ url.searchParams.set(param, "[REDACTED]");
485
484
  }
486
485
  }
486
+ return url.toString();
487
+ } catch {
488
+ return "[REDACTED_URL]";
487
489
  }
490
+ }
491
+ function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
492
+ if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
493
+ if (typeof value === "string") return redactStringUrl(value);
488
494
  if (Array.isArray(value)) {
489
495
  if (depth >= REDACT_MAX_DEPTH || seen.has(value)) return "[REDACTED_CYCLE]";
490
496
  seen.add(value);
@@ -751,10 +757,11 @@ var RefreshManager = class {
751
757
  this.options.logger.debug("Playback source renewed", { reason });
752
758
  } catch (error) {
753
759
  this.consecutiveFailures += 1;
754
- const message = error instanceof Error ? error.message : "Playback refresh failed";
760
+ const message = refreshErrorMessage(error);
755
761
  this.options.logger.warn("Playback refresh failed", {
756
762
  reason,
757
- failure: this.consecutiveFailures
763
+ failure: this.consecutiveFailures,
764
+ message
758
765
  });
759
766
  if (this.consecutiveFailures >= this.options.renewMaxRetries) {
760
767
  this.options.onFatal?.(message);
@@ -774,6 +781,13 @@ var RefreshManager = class {
774
781
  }
775
782
  }
776
783
  };
784
+ function refreshErrorMessage(error) {
785
+ if (error instanceof Error && error.message) return error.message;
786
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message) {
787
+ return error.message;
788
+ }
789
+ return "Playback refresh failed";
790
+ }
777
791
 
778
792
  // src/playback/liveEdge.ts
779
793
  var DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS = 3;
@@ -817,7 +831,37 @@ function seekToLiveEdge(media, options) {
817
831
  }
818
832
 
819
833
  // src/streaming/authParamsLoader.ts
820
- var ENC_KEY_PATH_RE = /\/enc\.key$/i;
834
+ var import_hls = require("hls.js");
835
+ var ENC_KEY_PATH_RE = /enc\.key$/i;
836
+ var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
837
+ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
838
+ try {
839
+ return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
840
+ } catch {
841
+ return /enc\.key/i.test(rawUrl);
842
+ }
843
+ }
844
+ function resolveBuiltinHlsLoader(HlsClass) {
845
+ if (typeof import_hls.XhrLoader === "function") {
846
+ return import_hls.XhrLoader;
847
+ }
848
+ const start = HlsClass.DefaultConfig.loader;
849
+ let current = start;
850
+ const seen = /* @__PURE__ */ new Set();
851
+ while (current && !seen.has(current)) {
852
+ seen.add(current);
853
+ const name = typeof current.name === "string" ? current.name : "";
854
+ if (BUILTIN_LOADER_NAMES.has(name)) {
855
+ return current;
856
+ }
857
+ const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
858
+ if (!parentCtor || parentCtor === Object || parentCtor === Function) {
859
+ break;
860
+ }
861
+ current = parentCtor;
862
+ }
863
+ return start;
864
+ }
821
865
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
822
866
  if (!rawUrl) return rawUrl;
823
867
  const parsed = new URL(rawUrl, baseOrigin);
@@ -828,28 +872,161 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
828
872
  videoId: options.videoId,
829
873
  keyUrl: parsed.toString(),
830
874
  token: snapshot.token,
831
- expires: snapshot.expires
875
+ expires: snapshot.expires,
876
+ username: snapshot.username,
877
+ mobile: snapshot.mobile
832
878
  });
833
879
  }
834
880
  if (!store.hasParams()) return parsed.toString();
835
881
  return store.applyToUrl(rawUrl, baseOrigin);
836
882
  }
883
+ function createLoaderStats() {
884
+ const now = performance.now();
885
+ return {
886
+ aborted: false,
887
+ loaded: 0,
888
+ retry: 0,
889
+ total: 0,
890
+ chunkCount: 0,
891
+ bwEstimate: 0,
892
+ loading: { start: now, first: 0, end: 0 },
893
+ parsing: { start: 0, end: 0 },
894
+ buffering: { start: 0, first: 0, end: 0 }
895
+ };
896
+ }
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,
921
+ response,
922
+ stats
923
+ );
924
+ return;
925
+ }
926
+ const data = await response.arrayBuffer();
927
+ const end = performance.now();
928
+ 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)) {
933
+ callbacks.onError(
934
+ {
935
+ code: 403,
936
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key"
937
+ },
938
+ context,
939
+ response,
940
+ stats
941
+ );
942
+ return;
943
+ }
944
+ callbacks.onSuccess(
945
+ {
946
+ url: response.url || context.url,
947
+ data,
948
+ code: response.status
949
+ },
950
+ stats,
951
+ context,
952
+ response
953
+ );
954
+ }
837
955
  function createAuthParamsLoader(HlsClass, store, options = {}) {
838
- const BaseLoader = HlsClass.DefaultConfig.loader;
839
- return class AuthParamsLoader extends BaseLoader {
956
+ const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
957
+ return class AuthParamsLoader {
958
+ constructor(config) {
959
+ this.keyAbort = null;
960
+ this.inner = new BaseLoader(config);
961
+ this.stats = this.inner.stats;
962
+ }
963
+ abort() {
964
+ this.keyAbort?.abort();
965
+ this.keyAbort = null;
966
+ this.inner.abort?.();
967
+ }
968
+ destroy() {
969
+ this.keyAbort?.abort();
970
+ this.keyAbort = null;
971
+ this.inner.destroy();
972
+ }
840
973
  load(context, config, callbacks) {
841
- if (context.url) {
842
- try {
843
- context.url = rewriteHlsRequestUrl(context.url, store, options);
844
- } catch {
974
+ if (!context.url) {
975
+ this.inner.load(context, config, callbacks);
976
+ return;
977
+ }
978
+ try {
979
+ const originalUrl = context.url;
980
+ const isKey = isEncryptionKeyUrl(originalUrl);
981
+ const usingKeyProxy = Boolean(
982
+ isKey && options.keyProxyUrlBuilder && options.videoId
983
+ );
984
+ context.url = rewriteHlsRequestUrl(originalUrl, store, options);
985
+ if (usingKeyProxy) {
986
+ this.inner.load(context, config, callbacks);
987
+ return;
845
988
  }
989
+ if (isKey && typeof options.getKeyAccessToken === "function") {
990
+ const accessToken = options.getKeyAccessToken() ?? null;
991
+ if (accessToken) {
992
+ this.keyAbort?.abort();
993
+ this.keyAbort = new AbortController();
994
+ const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
995
+ const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
996
+ void loadEncryptionKeyWithFetch(
997
+ context,
998
+ callbacks,
999
+ accessToken,
1000
+ this.keyAbort.signal
1001
+ ).catch((error) => {
1002
+ const stats = createLoaderStats();
1003
+ const aborted = error instanceof DOMException && error.name === "AbortError";
1004
+ callbacks.onError(
1005
+ {
1006
+ code: 0,
1007
+ text: aborted ? "enc.key fetch aborted/timeout" : error instanceof Error ? error.message : "enc.key fetch failed"
1008
+ },
1009
+ context,
1010
+ null,
1011
+ stats
1012
+ );
1013
+ }).finally(() => {
1014
+ clearTimeout(timer);
1015
+ });
1016
+ return;
1017
+ }
1018
+ }
1019
+ } catch {
846
1020
  }
847
- super.load(context, config, callbacks);
1021
+ this.inner.load(context, config, callbacks);
848
1022
  }
849
1023
  };
850
1024
  }
851
1025
 
852
1026
  // src/streaming/HlsEngine.ts
1027
+ function isAuthHttpStatus(code) {
1028
+ return code === 401 || code === 403;
1029
+ }
853
1030
  var HlsEngine = class {
854
1031
  constructor(options) {
855
1032
  this.options = options;
@@ -862,49 +1039,47 @@ var HlsEngine = class {
862
1039
  this.qualitySelection = "auto";
863
1040
  }
864
1041
  async start() {
865
- const { videoEl, playbackUrl, autoplay, startMuted, volume, startPosition } = this.options;
1042
+ const { videoEl, playbackUrl, startMuted, volume } = this.options;
866
1043
  videoEl.muted = startMuted;
867
- if (typeof volume === "number") videoEl.volume = Math.min(1, Math.max(0, volume));
1044
+ if (typeof volume === "number") {
1045
+ videoEl.volume = Math.min(1, Math.max(0, volume));
1046
+ }
868
1047
  const Hls = (await import("hls.js")).default;
869
1048
  if (Hls.isSupported()) {
870
1049
  this.supportMode = "hls.js";
871
1050
  this.options.callbacks.onSupportMode?.("hls.js");
872
1051
  await this.startWithHlsJs(Hls, playbackUrl);
873
- } else if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
1052
+ return;
1053
+ }
1054
+ if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
874
1055
  this.supportMode = "native";
875
1056
  this.options.callbacks.onSupportMode?.("native");
876
1057
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
877
1058
  this.bindMediaEvents();
878
1059
  this.options.callbacks.onStateChange?.("ready");
879
- } else {
880
- this.supportMode = "unsupported";
881
- this.options.callbacks.onSupportMode?.("unsupported");
882
- this.options.callbacks.onFatalError?.("HLS is not supported in this browser");
1060
+ await this.applyStartPositionAndAutoplay();
883
1061
  return;
884
1062
  }
885
- if (typeof startPosition === "number" && startPosition > 0) {
886
- videoEl.currentTime = startPosition;
887
- }
888
- if (autoplay) {
889
- try {
890
- await videoEl.play();
891
- } catch {
892
- if (!videoEl.muted) {
893
- videoEl.muted = true;
894
- try {
895
- await videoEl.play();
896
- } catch {
897
- this.options.logger.warn("Autoplay blocked");
898
- }
899
- }
900
- }
901
- }
1063
+ this.supportMode = "unsupported";
1064
+ this.options.callbacks.onSupportMode?.("unsupported");
1065
+ this.options.callbacks.onFatalError?.(
1066
+ "HLS is not supported in this browser"
1067
+ );
902
1068
  }
903
1069
  async startWithHlsJs(Hls, playbackUrl) {
904
- const { videoEl, authParams, hlsConfig, callbacks, videoId, keyProxyUrlBuilder } = this.options;
1070
+ const {
1071
+ videoEl,
1072
+ authParams,
1073
+ hlsConfig,
1074
+ callbacks,
1075
+ videoId,
1076
+ keyProxyUrlBuilder,
1077
+ getKeyAccessToken
1078
+ } = this.options;
905
1079
  const loader = createAuthParamsLoader(Hls, authParams, {
906
1080
  videoId,
907
- keyProxyUrlBuilder
1081
+ keyProxyUrlBuilder,
1082
+ getKeyAccessToken
908
1083
  });
909
1084
  this.hls = new Hls({
910
1085
  enableWorker: hlsConfig.enableWorker,
@@ -915,6 +1090,7 @@ var HlsEngine = class {
915
1090
  this.hls.attachMedia(videoEl);
916
1091
  this.hls.loadSource(authParams.applyToUrl(playbackUrl));
917
1092
  this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
1093
+ if (this.destroyed) return;
918
1094
  this.qualities = (data.levels ?? []).map((level, index) => ({
919
1095
  index,
920
1096
  height: level.height ?? 0,
@@ -924,42 +1100,100 @@ var HlsEngine = class {
924
1100
  }));
925
1101
  callbacks.onQualitiesChange?.(this.qualities);
926
1102
  callbacks.onStateChange?.("ready");
1103
+ void this.applyStartPositionAndAutoplay();
927
1104
  });
928
1105
  this.hls.on(Hls.Events.ERROR, (_event, data) => {
929
- if (!data.fatal) {
930
- 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)) {
931
- callbacks.onAuthRecoveryNeeded?.(
932
- data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
933
- );
934
- }
1106
+ this.handleHlsError(Hls, data);
1107
+ });
1108
+ this.bindMediaEvents();
1109
+ }
1110
+ handleHlsError(Hls, data) {
1111
+ if (this.destroyed) return;
1112
+ const { callbacks, hlsConfig } = this.options;
1113
+ const status = data.response?.code;
1114
+ const authFailure = isAuthHttpStatus(status);
1115
+ if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR) {
1116
+ if (authFailure) {
1117
+ callbacks.onAuthRecoveryNeeded?.("key_error");
1118
+ if (!data.fatal) return;
1119
+ } else if (!data.fatal) {
1120
+ callbacks.onFatalError?.(
1121
+ 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."
1122
+ );
935
1123
  return;
936
1124
  }
937
- if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
938
- this.networkRecoveryCount += 1;
939
- if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
1125
+ }
1126
+ if (!data.fatal) {
1127
+ if (authFailure && data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR)) {
1128
+ callbacks.onAuthRecoveryNeeded?.(
1129
+ data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
1130
+ );
1131
+ }
1132
+ return;
1133
+ }
1134
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
1135
+ this.networkRecoveryCount += 1;
1136
+ if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
1137
+ if (authFailure) {
940
1138
  callbacks.onAuthRecoveryNeeded?.("fatal_network");
941
- this.hls?.startLoad();
942
- return;
943
1139
  }
1140
+ this.hls?.startLoad();
1141
+ return;
944
1142
  }
945
- if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
946
- this.mediaRecoveryCount += 1;
947
- if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
948
- this.hls?.recoverMediaError();
949
- return;
1143
+ }
1144
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
1145
+ this.mediaRecoveryCount += 1;
1146
+ if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
1147
+ this.hls?.recoverMediaError();
1148
+ return;
1149
+ }
1150
+ }
1151
+ callbacks.onFatalError?.(data.details || "Fatal HLS error");
1152
+ }
1153
+ async applyStartPositionAndAutoplay() {
1154
+ if (this.destroyed) return;
1155
+ const { videoEl, autoplay, startPosition } = this.options;
1156
+ if (typeof startPosition === "number" && startPosition > 0) {
1157
+ const seek = () => {
1158
+ if (this.destroyed) return;
1159
+ try {
1160
+ videoEl.currentTime = startPosition;
1161
+ } catch {
950
1162
  }
1163
+ };
1164
+ if (videoEl.seekable.length > 0) {
1165
+ seek();
1166
+ } else {
1167
+ videoEl.addEventListener("loadedmetadata", seek, { once: true });
951
1168
  }
952
- callbacks.onFatalError?.(data.details || "Fatal HLS error");
953
- });
954
- this.bindMediaEvents();
1169
+ }
1170
+ if (!autoplay) return;
1171
+ try {
1172
+ await videoEl.play();
1173
+ } catch {
1174
+ if (!videoEl.muted) {
1175
+ videoEl.muted = true;
1176
+ try {
1177
+ await videoEl.play();
1178
+ } catch {
1179
+ this.options.logger.warn("Autoplay blocked");
1180
+ }
1181
+ }
1182
+ }
955
1183
  }
956
1184
  bindMediaEvents() {
957
1185
  const { videoEl, callbacks } = this.options;
958
- videoEl.addEventListener("playing", () => callbacks.onStateChange?.("playing"));
1186
+ videoEl.addEventListener(
1187
+ "playing",
1188
+ () => callbacks.onStateChange?.("playing")
1189
+ );
959
1190
  videoEl.addEventListener("pause", () => {
960
1191
  if (!videoEl.ended) callbacks.onStateChange?.("paused");
961
1192
  });
962
- videoEl.addEventListener("waiting", () => callbacks.onStateChange?.("buffering"));
1193
+ videoEl.addEventListener(
1194
+ "waiting",
1195
+ () => callbacks.onStateChange?.("buffering")
1196
+ );
963
1197
  videoEl.addEventListener("ended", () => {
964
1198
  callbacks.onStateChange?.("ended");
965
1199
  callbacks.onEnded?.();
@@ -977,6 +1211,7 @@ var HlsEngine = class {
977
1211
  }
978
1212
  }
979
1213
  resumeAfterAuthRenew() {
1214
+ this.networkRecoveryCount = 0;
980
1215
  this.hls?.startLoad();
981
1216
  this.updateAuthParams();
982
1217
  }
@@ -1119,7 +1354,13 @@ function createVideoPlayer(options) {
1119
1354
  startPosition: config.player.startPosition,
1120
1355
  volume: config.player.volume,
1121
1356
  videoId: options.videoId,
1122
- keyProxyUrlBuilder: options.streaming?.keyProxyUrlBuilder,
1357
+ keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1358
+ // Optional: browser Bearer for Video API–origin enc.key (CORS-dependent).
1359
+ // CDN keys use signed query via the auth loader (no Bearer) — same as
1360
+ // segments. Apps can pass getKeyAccessToken or rely on keyProxyUrlBuilder.
1361
+ // When unset, defaults to AuthManager token only if explicitly requested
1362
+ // via streaming.getKeyAccessToken; otherwise query-signed XHR.
1363
+ getKeyAccessToken: config.streaming.getKeyAccessToken,
1123
1364
  hlsConfig: config.streaming.hls,
1124
1365
  logger,
1125
1366
  callbacks: {
@@ -1180,6 +1421,14 @@ function createVideoPlayer(options) {
1180
1421
  onRenewing: () => setState("renewing"),
1181
1422
  onRenewed: (nextExpiresAt) => {
1182
1423
  engine?.resumeAfterAuthRenew();
1424
+ if (!destroyed) {
1425
+ const el2 = videoEl;
1426
+ if (el2 && !el2.paused && !el2.ended) {
1427
+ setState("playing");
1428
+ } else {
1429
+ setState("ready");
1430
+ }
1431
+ }
1183
1432
  events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
1184
1433
  events.emit("sourcechange", { expiresAt: nextExpiresAt });
1185
1434
  },
@@ -1381,7 +1630,8 @@ function createVideoPlayer(options) {
1381
1630
  streaming: {
1382
1631
  engine: "hls",
1383
1632
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1384
- keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
1633
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1634
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
1385
1635
  },
1386
1636
  logging: { ...config.logging, ...partial?.logging },
1387
1637
  debug: { ...config.debug, ...partial?.debug },
@@ -1425,11 +1675,13 @@ function createVideoPlayer(options) {
1425
1675
  createVideoPlayer,
1426
1676
  getLiveEdgeTime,
1427
1677
  isAtLiveEdge,
1678
+ isEncryptionKeyUrl,
1428
1679
  isPlainObject,
1429
1680
  mergePlayerConfig,
1430
1681
  redactObject,
1431
1682
  redactSensitive,
1432
1683
  redactValue,
1684
+ resolveBuiltinHlsLoader,
1433
1685
  rewriteHlsRequestUrl,
1434
1686
  seekToLiveEdge,
1435
1687
  themeToCssVars