@codenkay/video-nsgplayer-core 0.1.5 → 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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import Hls, { LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
1
+ import Hls, { LoaderStats, LoaderContext, LoaderConfiguration, LoaderCallbacks } from 'hls.js';
2
2
 
3
3
  /** Player lifecycle states. */
4
4
  type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffering" | "renewing" | "ended" | "error" | "destroyed";
@@ -255,7 +255,7 @@ type PlayerEventMap = {
255
255
  };
256
256
 
257
257
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
258
- declare const PACKAGE_VERSION = "0.1.5";
258
+ declare const PACKAGE_VERSION = "0.1.8";
259
259
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
260
260
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
261
261
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -415,9 +415,12 @@ type HlsEngineOptions = {
415
415
  /**
416
416
  * Dual-path HLS engine: hls.js when supported, otherwise native Safari HLS.
417
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.
418
+ * Signed CDN playback requires auth query params on every HLS request
419
+ * (playlists, segments, and AES `enc.key` on CDN hosts).
420
+ *
421
+ * Optional `streaming.keyProxyUrlBuilder` (same-origin BFF) or
422
+ * `streaming.getKeyAccessToken` (browser Bearer) for Video API–origin keys
423
+ * when query-signed CDN access is insufficient.
421
424
  */
422
425
  declare class HlsEngine {
423
426
  private readonly options;
@@ -591,32 +594,52 @@ declare class RefreshManager {
591
594
  private renew;
592
595
  }
593
596
 
594
- type HlsLoader = new (config: object) => {
597
+ type HlsLoaderInstance = {
598
+ stats: LoaderStats;
595
599
  load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
596
600
  destroy(): void;
601
+ abort?: () => void;
597
602
  };
603
+ type HlsLoader = new (config: object) => HlsLoaderInstance;
598
604
  type AuthParamsLoaderOptions = {
599
605
  videoId?: string;
600
606
  keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
601
607
  getKeyAccessToken?: StreamingConfig["getKeyAccessToken"];
602
608
  };
609
+ declare function isEncryptionKeyUrl(rawUrl: string, baseOrigin?: string): boolean;
610
+ /**
611
+ * Always resolve a builtin hls.js loader (XhrLoader).
612
+ *
613
+ * Never trust `Hls.DefaultConfig.loader` alone — if it was reassigned to a
614
+ * custom wrapper, `extends` / nested `inner.load()` chains blow Firefox's
615
+ * stack (`InternalError: too much recursion`).
616
+ */
617
+ declare function resolveBuiltinHlsLoader(HlsClass: typeof Hls): HlsLoader;
603
618
  /**
604
619
  * 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
620
  *
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
621
+ * AES `enc.key` handling (enterprise priority order):
622
+ * 1. `keyProxyUrlBuilder` — same-origin BFF (production; server IP must pass CDN/WAF)
623
+ * 2. Otherwise keep CDN/API URL and apply signed `token`/`expires` (and related) query
624
+ * params — same auth model as segments on Appextech CDN hosts
625
+ * 3. Optional `getKeyAccessToken` is applied at load-time (Bearer), not in this rewrite
611
626
  */
612
627
  declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
613
628
  /**
614
- * hls.js loader that re-applies live AuthParamsStore credentials on every
615
- * manifest / level / fragment / key request.
629
+ * hls.js loader that:
630
+ * - Re-applies signed auth query params on every media request
631
+ * - Loads AES `enc.key` via BFF / optional Bearer fetch / signed-query XHR
632
+ *
633
+ * Uses composition (not `extends DefaultConfig.loader`) so repeated mounts,
634
+ * HMR, or a polluted DefaultConfig cannot nest `super.load()` into recursion.
616
635
  */
617
636
  declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
618
637
 
619
638
  declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
639
+ /**
640
+ * Redacts sensitive fields. Cycle-safe and depth-capped.
641
+ * Only walks plain objects/arrays — never DOM / host graphs.
642
+ */
620
643
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
621
644
 
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 };
645
+ 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, isEncryptionKeyUrl, isPlainObject, mergePlayerConfig, redactObject, redactSensitive, redactValue, resolveBuiltinHlsLoader, rewriteHlsRequestUrl, seekToLiveEdge, themeToCssVars };
package/dist/index.js CHANGED
@@ -146,7 +146,7 @@ var AuthManager = class {
146
146
 
147
147
  // src/config/defaults.ts
148
148
  var PLAYBACK_RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
149
- var PACKAGE_VERSION = "0.1.5";
149
+ var PACKAGE_VERSION = "0.1.8";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -406,23 +406,25 @@ function isPlainObject2(value) {
406
406
  const proto = Object.getPrototypeOf(value);
407
407
  return proto === Object.prototype || proto === null;
408
408
  }
409
- function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
410
- if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
411
- if (typeof value === "string") {
412
- if (/^https?:\/\//i.test(value) && /[?&](token|expires)=/i.test(value)) {
413
- try {
414
- const url = new URL(value);
415
- for (const param of ["token", "expires", "signature", "sig"]) {
416
- if (url.searchParams.has(param)) {
417
- url.searchParams.set(param, "[REDACTED]");
418
- }
419
- }
420
- return url.toString();
421
- } catch {
422
- return "[REDACTED_URL]";
409
+ function redactStringUrl(value) {
410
+ if (!/^https?:\/\//i.test(value) || !/[?&](token|expires)=/i.test(value)) {
411
+ return value;
412
+ }
413
+ try {
414
+ const url = new URL(value);
415
+ for (const param of ["token", "expires", "signature", "sig"]) {
416
+ if (url.searchParams.has(param)) {
417
+ url.searchParams.set(param, "[REDACTED]");
423
418
  }
424
419
  }
420
+ return url.toString();
421
+ } catch {
422
+ return "[REDACTED_URL]";
425
423
  }
424
+ }
425
+ function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
426
+ if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
427
+ if (typeof value === "string") return redactStringUrl(value);
426
428
  if (Array.isArray(value)) {
427
429
  if (depth >= REDACT_MAX_DEPTH || seen.has(value)) return "[REDACTED_CYCLE]";
428
430
  seen.add(value);
@@ -689,10 +691,11 @@ var RefreshManager = class {
689
691
  this.options.logger.debug("Playback source renewed", { reason });
690
692
  } catch (error) {
691
693
  this.consecutiveFailures += 1;
692
- const message = error instanceof Error ? error.message : "Playback refresh failed";
694
+ const message = refreshErrorMessage(error);
693
695
  this.options.logger.warn("Playback refresh failed", {
694
696
  reason,
695
- failure: this.consecutiveFailures
697
+ failure: this.consecutiveFailures,
698
+ message
696
699
  });
697
700
  if (this.consecutiveFailures >= this.options.renewMaxRetries) {
698
701
  this.options.onFatal?.(message);
@@ -712,6 +715,13 @@ var RefreshManager = class {
712
715
  }
713
716
  }
714
717
  };
718
+ function refreshErrorMessage(error) {
719
+ if (error instanceof Error && error.message) return error.message;
720
+ if (error && typeof error === "object" && "message" in error && typeof error.message === "string" && error.message) {
721
+ return error.message;
722
+ }
723
+ return "Playback refresh failed";
724
+ }
715
725
 
716
726
  // src/playback/liveEdge.ts
717
727
  var DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS = 3;
@@ -755,7 +765,9 @@ function seekToLiveEdge(media, options) {
755
765
  }
756
766
 
757
767
  // src/streaming/authParamsLoader.ts
768
+ import { XhrLoader as BuiltinXhrLoader } from "hls.js";
758
769
  var ENC_KEY_PATH_RE = /enc\.key$/i;
770
+ var BUILTIN_LOADER_NAMES = /* @__PURE__ */ new Set(["XhrLoader", "FetchLoader"]);
759
771
  function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
760
772
  try {
761
773
  return ENC_KEY_PATH_RE.test(new URL(rawUrl, baseOrigin).pathname);
@@ -763,6 +775,27 @@ function isEncryptionKeyUrl(rawUrl, baseOrigin = typeof location !== "undefined"
763
775
  return /enc\.key/i.test(rawUrl);
764
776
  }
765
777
  }
778
+ function resolveBuiltinHlsLoader(HlsClass) {
779
+ if (typeof BuiltinXhrLoader === "function") {
780
+ return BuiltinXhrLoader;
781
+ }
782
+ const start = HlsClass.DefaultConfig.loader;
783
+ let current = start;
784
+ const seen = /* @__PURE__ */ new Set();
785
+ while (current && !seen.has(current)) {
786
+ seen.add(current);
787
+ const name = typeof current.name === "string" ? current.name : "";
788
+ if (BUILTIN_LOADER_NAMES.has(name)) {
789
+ return current;
790
+ }
791
+ const parentCtor = Object.getPrototypeOf(current.prototype)?.constructor;
792
+ if (!parentCtor || parentCtor === Object || parentCtor === Function) {
793
+ break;
794
+ }
795
+ current = parentCtor;
796
+ }
797
+ return start;
798
+ }
766
799
  function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
767
800
  if (!rawUrl) return rawUrl;
768
801
  const parsed = new URL(rawUrl, baseOrigin);
@@ -781,28 +814,145 @@ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof l
781
814
  if (!store.hasParams()) return parsed.toString();
782
815
  return store.applyToUrl(rawUrl, baseOrigin);
783
816
  }
817
+ function createLoaderStats() {
818
+ const now = performance.now();
819
+ return {
820
+ aborted: false,
821
+ loaded: 0,
822
+ retry: 0,
823
+ total: 0,
824
+ chunkCount: 0,
825
+ bwEstimate: 0,
826
+ loading: { start: now, first: 0, end: 0 },
827
+ parsing: { start: 0, end: 0 },
828
+ buffering: { start: 0, first: 0, end: 0 }
829
+ };
830
+ }
831
+ async function loadEncryptionKeyWithFetch(context, callbacks, accessToken, signal) {
832
+ const stats = createLoaderStats();
833
+ stats.loading.first = performance.now();
834
+ const headers = {
835
+ Accept: "*/*",
836
+ ...context.headers
837
+ };
838
+ if (accessToken) {
839
+ headers.Authorization = `Bearer ${accessToken}`;
840
+ }
841
+ const response = await fetch(context.url, {
842
+ method: "GET",
843
+ headers,
844
+ credentials: "omit",
845
+ cache: "no-store",
846
+ signal
847
+ });
848
+ if (!response.ok) {
849
+ callbacks.onError(
850
+ {
851
+ code: response.status,
852
+ text: response.statusText || `enc.key HTTP ${response.status}`
853
+ },
854
+ context,
855
+ response,
856
+ stats
857
+ );
858
+ return;
859
+ }
860
+ const data = await response.arrayBuffer();
861
+ const end = performance.now();
862
+ stats.loading.end = end;
863
+ stats.loaded = data.byteLength;
864
+ stats.total = data.byteLength;
865
+ const head = new TextDecoder().decode(data.slice(0, 32));
866
+ if (/<!DOCTYPE|<html/i.test(head)) {
867
+ callbacks.onError(
868
+ {
869
+ code: 403,
870
+ text: "enc.key returned HTML (CDN challenge/block), not an AES key"
871
+ },
872
+ context,
873
+ response,
874
+ stats
875
+ );
876
+ return;
877
+ }
878
+ callbacks.onSuccess(
879
+ {
880
+ url: response.url || context.url,
881
+ data,
882
+ code: response.status
883
+ },
884
+ stats,
885
+ context,
886
+ response
887
+ );
888
+ }
784
889
  function createAuthParamsLoader(HlsClass, store, options = {}) {
785
- const BaseLoader = HlsClass.DefaultConfig.loader;
786
- return class AuthParamsLoader extends BaseLoader {
890
+ const BaseLoader = resolveBuiltinHlsLoader(HlsClass);
891
+ return class AuthParamsLoader {
892
+ constructor(config) {
893
+ this.keyAbort = null;
894
+ this.inner = new BaseLoader(config);
895
+ this.stats = this.inner.stats;
896
+ }
897
+ abort() {
898
+ this.keyAbort?.abort();
899
+ this.keyAbort = null;
900
+ this.inner.abort?.();
901
+ }
902
+ destroy() {
903
+ this.keyAbort?.abort();
904
+ this.keyAbort = null;
905
+ this.inner.destroy();
906
+ }
787
907
  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
- }
908
+ if (!context.url) {
909
+ this.inner.load(context, config, callbacks);
910
+ return;
911
+ }
912
+ try {
913
+ const originalUrl = context.url;
914
+ const isKey = isEncryptionKeyUrl(originalUrl);
915
+ const usingKeyProxy = Boolean(
916
+ isKey && options.keyProxyUrlBuilder && options.videoId
917
+ );
918
+ context.url = rewriteHlsRequestUrl(originalUrl, store, options);
919
+ if (usingKeyProxy) {
920
+ this.inner.load(context, config, callbacks);
921
+ return;
922
+ }
923
+ if (isKey && typeof options.getKeyAccessToken === "function") {
924
+ const accessToken = options.getKeyAccessToken() ?? null;
925
+ if (accessToken) {
926
+ this.keyAbort?.abort();
927
+ this.keyAbort = new AbortController();
928
+ const timeoutMs = typeof config?.timeout === "number" && config.timeout > 0 ? config.timeout : 2e4;
929
+ const timer = setTimeout(() => this.keyAbort?.abort(), timeoutMs);
930
+ void loadEncryptionKeyWithFetch(
931
+ context,
932
+ callbacks,
933
+ accessToken,
934
+ this.keyAbort.signal
935
+ ).catch((error) => {
936
+ const stats = createLoaderStats();
937
+ const aborted = error instanceof DOMException && error.name === "AbortError";
938
+ callbacks.onError(
939
+ {
940
+ code: 0,
941
+ text: aborted ? "enc.key fetch aborted/timeout" : error instanceof Error ? error.message : "enc.key fetch failed"
942
+ },
943
+ context,
944
+ null,
945
+ stats
946
+ );
947
+ }).finally(() => {
948
+ clearTimeout(timer);
949
+ });
950
+ return;
801
951
  }
802
- } catch {
803
952
  }
953
+ } catch {
804
954
  }
805
- super.load(context, config, callbacks);
955
+ this.inner.load(context, config, callbacks);
806
956
  }
807
957
  };
808
958
  }
@@ -893,19 +1043,24 @@ var HlsEngine = class {
893
1043
  }
894
1044
  handleHlsError(Hls, data) {
895
1045
  if (this.destroyed) return;
896
- const { callbacks, hlsConfig, keyProxyUrlBuilder, getKeyAccessToken } = this.options;
1046
+ const { callbacks, hlsConfig } = this.options;
897
1047
  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;
1048
+ const authFailure = isAuthHttpStatus(status);
1049
+ if (data.details === Hls.ErrorDetails.KEY_LOAD_ERROR) {
1050
+ if (authFailure) {
1051
+ callbacks.onAuthRecoveryNeeded?.("key_error");
1052
+ if (!data.fatal) return;
1053
+ } else if (!data.fatal) {
1054
+ callbacks.onFatalError?.(
1055
+ 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."
1056
+ );
1057
+ return;
1058
+ }
903
1059
  }
904
- const authFailure = isAuthHttpStatus(status) || data.details === Hls.ErrorDetails.KEY_LOAD_ERROR && (status === void 0 || status === 0 || isAuthHttpStatus(status));
905
1060
  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)) {
1061
+ if (authFailure && data.type === Hls.ErrorTypes.NETWORK_ERROR && (data.details === Hls.ErrorDetails.FRAG_LOAD_ERROR || data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR)) {
907
1062
  callbacks.onAuthRecoveryNeeded?.(
908
- data.details === Hls.ErrorDetails.KEY_LOAD_ERROR ? "key_error" : data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
1063
+ data.details === Hls.ErrorDetails.LEVEL_LOAD_ERROR ? "level_error" : "frag_error"
909
1064
  );
910
1065
  }
911
1066
  return;
@@ -1134,6 +1289,11 @@ function createVideoPlayer(options) {
1134
1289
  volume: config.player.volume,
1135
1290
  videoId: options.videoId,
1136
1291
  keyProxyUrlBuilder: config.streaming.keyProxyUrlBuilder,
1292
+ // Optional: browser Bearer for Video API–origin enc.key (CORS-dependent).
1293
+ // CDN keys use signed query via the auth loader (no Bearer) — same as
1294
+ // segments. Apps can pass getKeyAccessToken or rely on keyProxyUrlBuilder.
1295
+ // When unset, defaults to AuthManager token only if explicitly requested
1296
+ // via streaming.getKeyAccessToken; otherwise query-signed XHR.
1137
1297
  getKeyAccessToken: config.streaming.getKeyAccessToken,
1138
1298
  hlsConfig: config.streaming.hls,
1139
1299
  logger,
@@ -1448,11 +1608,13 @@ export {
1448
1608
  createVideoPlayer,
1449
1609
  getLiveEdgeTime,
1450
1610
  isAtLiveEdge,
1611
+ isEncryptionKeyUrl,
1451
1612
  isPlainObject,
1452
1613
  mergePlayerConfig,
1453
1614
  redactObject,
1454
1615
  redactSensitive,
1455
1616
  redactValue,
1617
+ resolveBuiltinHlsLoader,
1456
1618
  rewriteHlsRequestUrl,
1457
1619
  seekToLiveEdge,
1458
1620
  themeToCssVars