@codenkay/video-nsgplayer-core 0.1.3 → 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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.5
4
+
5
+ - Add `streaming.getKeyAccessToken` for browser-direct AES `enc.key` loads with `Authorization: Bearer` (needed when Cloudflare blocks server/BFF fetches to Appextech).
6
+ - Clearer key-load guidance when neither key proxy nor browser Bearer is configured.
7
+
8
+ ## 0.1.4
9
+
10
+ - Fix stall after segments return 200: wait for `MANIFEST_PARSED` before autoplay/seek.
11
+ - Only trigger signed-URL auth recovery on HTTP 401/403 (not every network blip).
12
+ - Require/surface `streaming.keyProxyUrlBuilder` when AES `enc.key` load fails (Appextech VOD needs same-origin BFF + API Bearer).
13
+ - Clear sticky `renewing` state after successful credential refresh.
14
+
3
15
  ## 0.1.3
4
16
 
5
17
  - 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.
package/dist/index.cjs CHANGED
@@ -210,7 +210,7 @@ var AuthManager = class {
210
210
 
211
211
  // src/config/defaults.ts
212
212
  var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
213
- var PACKAGE_VERSION = "0.1.3";
213
+ var PACKAGE_VERSION = "0.1.5";
214
214
  var DEFAULT_PLAYER_BEHAVIOR = {
215
215
  autoplay: true,
216
216
  startMuted: true,
@@ -303,7 +303,8 @@ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
303
303
  "gettoken",
304
304
  "getsource",
305
305
  "refreshsource",
306
- "keyproxyurlbuilder"
306
+ "keyproxyurlbuilder",
307
+ "getkeyaccesstoken"
307
308
  ]);
308
309
  var SECRET_SCAN_MAX_DEPTH = 32;
309
310
  function isPlainObject(value) {
@@ -387,7 +388,8 @@ function mergePlayerConfig(partial) {
387
388
  ...DEFAULT_PLAYER_CONFIG.streaming.hls,
388
389
  ...partial?.streaming?.hls
389
390
  },
390
- keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
391
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder,
392
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken
391
393
  },
392
394
  logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
393
395
  debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
@@ -817,7 +819,14 @@ function seekToLiveEdge(media, options) {
817
819
  }
818
820
 
819
821
  // src/streaming/authParamsLoader.ts
820
- var ENC_KEY_PATH_RE = /\/enc\.key$/i;
822
+ var ENC_KEY_PATH_RE = /enc\.key$/i;
823
+ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
824
+ try {
825
+ return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
826
+ } catch {
827
+ return /enc\.key/i.test(rawUrl);
828
+ }
829
+ }
821
830
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
822
831
  if (!rawUrl) return rawUrl;
823
832
  const parsed = new URL(rawUrl, baseOrigin);
@@ -828,7 +837,9 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
828
837
  videoId: options.videoId,
829
838
  keyUrl: parsed.toString(),
830
839
  token: snapshot.token,
831
- expires: snapshot.expires
840
+ expires: snapshot.expires,
841
+ username: snapshot.username,
842
+ mobile: snapshot.mobile
832
843
  });
833
844
  }
834
845
  if (!store.hasParams()) return parsed.toString();
@@ -840,7 +851,18 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
840
851
  load(context, config, callbacks) {
841
852
  if (context.url) {
842
853
  try {
843
- context.url = rewriteHlsRequestUrl(context.url, store, options);
854
+ const originalUrl = context.url;
855
+ const isKey = isEncryptionKeyUrl(originalUrl);
856
+ context.url = rewriteHlsRequestUrl(originalUrl, store, options);
857
+ if (isKey && !options.keyProxyUrlBuilder && options.getKeyAccessToken) {
858
+ const accessToken = options.getKeyAccessToken();
859
+ if (accessToken) {
860
+ context.headers = {
861
+ ...context.headers ?? {},
862
+ Authorization: `Bearer ${accessToken}`
863
+ };
864
+ }
865
+ }
844
866
  } catch {
845
867
  }
846
868
  }
@@ -850,6 +872,9 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
850
872
  }
851
873
 
852
874
  // src/streaming/HlsEngine.ts
875
+ function isAuthHttpStatus(code) {
876
+ return code === 401 || code === 403;
877
+ }
853
878
  var HlsEngine = class {
854
879
  constructor(options) {
855
880
  this.options = options;
@@ -862,49 +887,47 @@ var HlsEngine = class {
862
887
  this.qualitySelection = "auto";
863
888
  }
864
889
  async start() {
865
- const { videoEl, playbackUrl, autoplay, startMuted, volume, startPosition } = this.options;
890
+ const { videoEl, playbackUrl, startMuted, volume } = this.options;
866
891
  videoEl.muted = startMuted;
867
- if (typeof volume === "number") videoEl.volume = Math.min(1, Math.max(0, volume));
892
+ if (typeof volume === "number") {
893
+ videoEl.volume = Math.min(1, Math.max(0, volume));
894
+ }
868
895
  const Hls = (await import("hls.js")).default;
869
896
  if (Hls.isSupported()) {
870
897
  this.supportMode = "hls.js";
871
898
  this.options.callbacks.onSupportMode?.("hls.js");
872
899
  await this.startWithHlsJs(Hls, playbackUrl);
873
- } else if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
900
+ return;
901
+ }
902
+ if (videoEl.canPlayType("application/vnd.apple.mpegurl")) {
874
903
  this.supportMode = "native";
875
904
  this.options.callbacks.onSupportMode?.("native");
876
905
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
877
906
  this.bindMediaEvents();
878
907
  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");
908
+ await this.applyStartPositionAndAutoplay();
883
909
  return;
884
910
  }
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
- }
911
+ this.supportMode = "unsupported";
912
+ this.options.callbacks.onSupportMode?.("unsupported");
913
+ this.options.callbacks.onFatalError?.(
914
+ "HLS is not supported in this browser"
915
+ );
902
916
  }
903
917
  async startWithHlsJs(Hls, playbackUrl) {
904
- const { videoEl, authParams, hlsConfig, callbacks, videoId, keyProxyUrlBuilder } = this.options;
918
+ const {
919
+ videoEl,
920
+ authParams,
921
+ hlsConfig,
922
+ callbacks,
923
+ videoId,
924
+ keyProxyUrlBuilder,
925
+ getKeyAccessToken
926
+ } = this.options;
905
927
  const loader = createAuthParamsLoader(Hls, authParams, {
906
928
  videoId,
907
- keyProxyUrlBuilder
929
+ keyProxyUrlBuilder,
930
+ getKeyAccessToken
908
931
  });
909
932
  this.hls = new Hls({
910
933
  enableWorker: hlsConfig.enableWorker,
@@ -915,6 +938,7 @@ var HlsEngine = class {
915
938
  this.hls.attachMedia(videoEl);
916
939
  this.hls.loadSource(authParams.applyToUrl(playbackUrl));
917
940
  this.hls.on(Hls.Events.MANIFEST_PARSED, (_event, data) => {
941
+ if (this.destroyed) return;
918
942
  this.qualities = (data.levels ?? []).map((level, index) => ({
919
943
  index,
920
944
  height: level.height ?? 0,
@@ -924,42 +948,95 @@ var HlsEngine = class {
924
948
  }));
925
949
  callbacks.onQualitiesChange?.(this.qualities);
926
950
  callbacks.onStateChange?.("ready");
951
+ void this.applyStartPositionAndAutoplay();
927
952
  });
928
953
  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
- );
954
+ this.handleHlsError(Hls, data);
955
+ });
956
+ this.bindMediaEvents();
957
+ }
958
+ handleHlsError(Hls, data) {
959
+ if (this.destroyed) return;
960
+ const { callbacks, hlsConfig, keyProxyUrlBuilder, getKeyAccessToken } = this.options;
961
+ const status = data.response?.code;
962
+ if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && !keyProxyUrlBuilder && !getKeyAccessToken) {
963
+ callbacks.onFatalError?.(
964
+ "Encryption key load failed. Configure streaming.getKeyAccessToken (browser Bearer) or streaming.keyProxyUrlBuilder (Cloudflare-allowlisted BFF) for Appextech AES-encrypted HLS."
965
+ );
966
+ return;
967
+ }
968
+ const authFailure = isAuthHttpStatus(status) || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && (status === void 0 || status === 0 || isAuthHttpStatus(status));
969
+ if (!data.fatal) {
970
+ 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)) {
971
+ callbacks.onAuthRecoveryNeeded?.(
972
+ data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
973
+ );
974
+ }
975
+ return;
976
+ }
977
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
978
+ this.networkRecoveryCount += 1;
979
+ if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
980
+ if (authFailure) {
981
+ callbacks.onAuthRecoveryNeeded?.("fatal_network");
934
982
  }
983
+ this.hls?.startLoad();
935
984
  return;
936
985
  }
937
- if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
938
- this.networkRecoveryCount += 1;
939
- if (this.networkRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
940
- callbacks.onAuthRecoveryNeeded?.("fatal_network");
941
- this.hls?.startLoad();
942
- return;
986
+ }
987
+ if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
988
+ this.mediaRecoveryCount += 1;
989
+ if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
990
+ this.hls?.recoverMediaError();
991
+ return;
992
+ }
993
+ }
994
+ callbacks.onFatalError?.(data.details || "Fatal HLS error");
995
+ }
996
+ async applyStartPositionAndAutoplay() {
997
+ if (this.destroyed) return;
998
+ const { videoEl, autoplay, startPosition } = this.options;
999
+ if (typeof startPosition === "number" && startPosition > 0) {
1000
+ const seek = () => {
1001
+ if (this.destroyed) return;
1002
+ try {
1003
+ videoEl.currentTime = startPosition;
1004
+ } catch {
943
1005
  }
1006
+ };
1007
+ if (videoEl.seekable.length > 0) {
1008
+ seek();
1009
+ } else {
1010
+ videoEl.addEventListener("loadedmetadata", seek, { once: true });
944
1011
  }
945
- if (data.type === Hls.ErrorTypes.MEDIA_ERROR) {
946
- this.mediaRecoveryCount += 1;
947
- if (this.mediaRecoveryCount <= hlsConfig.fatalRecoveryLimit) {
948
- this.hls?.recoverMediaError();
949
- return;
1012
+ }
1013
+ if (!autoplay) return;
1014
+ try {
1015
+ await videoEl.play();
1016
+ } catch {
1017
+ if (!videoEl.muted) {
1018
+ videoEl.muted = true;
1019
+ try {
1020
+ await videoEl.play();
1021
+ } catch {
1022
+ this.options.logger.warn("Autoplay blocked");
950
1023
  }
951
1024
  }
952
- callbacks.onFatalError?.(data.details || "Fatal HLS error");
953
- });
954
- this.bindMediaEvents();
1025
+ }
955
1026
  }
956
1027
  bindMediaEvents() {
957
1028
  const { videoEl, callbacks } = this.options;
958
- videoEl.addEventListener("playing", () => callbacks.onStateChange?.("playing"));
1029
+ videoEl.addEventListener(
1030
+ "playing",
1031
+ () => callbacks.onStateChange?.("playing")
1032
+ );
959
1033
  videoEl.addEventListener("pause", () => {
960
1034
  if (!videoEl.ended) callbacks.onStateChange?.("paused");
961
1035
  });
962
- videoEl.addEventListener("waiting", () => callbacks.onStateChange?.("buffering"));
1036
+ videoEl.addEventListener(
1037
+ "waiting",
1038
+ () => callbacks.onStateChange?.("buffering")
1039
+ );
963
1040
  videoEl.addEventListener("ended", () => {
964
1041
  callbacks.onStateChange?.("ended");
965
1042
  callbacks.onEnded?.();
@@ -977,6 +1054,7 @@ var HlsEngine = class {
977
1054
  }
978
1055
  }
979
1056
  resumeAfterAuthRenew() {
1057
+ this.networkRecoveryCount = 0;
980
1058
  this.hls?.startLoad();
981
1059
  this.updateAuthParams();
982
1060
  }
@@ -1119,7 +1197,8 @@ function createVideoPlayer(options) {
1119
1197
  startPosition: config.player.startPosition,
1120
1198
  volume: config.player.volume,
1121
1199
  videoId: options.videoId,
1122
- keyProxyUrlBuilder: options.streaming?.keyProxyUrlBuilder,
1200
+ keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1201
+ getKeyAccessToken: config.streaming.getKeyAccessToken,
1123
1202
  hlsConfig: config.streaming.hls,
1124
1203
  logger,
1125
1204
  callbacks: {
@@ -1180,6 +1259,14 @@ function createVideoPlayer(options) {
1180
1259
  onRenewing: () => setState("renewing"),
1181
1260
  onRenewed: (nextExpiresAt) => {
1182
1261
  engine?.resumeAfterAuthRenew();
1262
+ if (!destroyed) {
1263
+ const el2 = videoEl;
1264
+ if (el2 && !el2.paused && !el2.ended) {
1265
+ setState("playing");
1266
+ } else {
1267
+ setState("ready");
1268
+ }
1269
+ }
1183
1270
  events.emit("tokenrefresh", { expiresAt: nextExpiresAt });
1184
1271
  events.emit("sourcechange", { expiresAt: nextExpiresAt });
1185
1272
  },
@@ -1381,7 +1468,8 @@ function createVideoPlayer(options) {
1381
1468
  streaming: {
1382
1469
  engine: "hls",
1383
1470
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1384
- keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
1471
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1472
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
1385
1473
  },
1386
1474
  logging: { ...config.logging, ...partial?.logging },
1387
1475
  debug: { ...config.debug, ...partial?.debug },