@codenkay/video-nsgplayer-core 0.1.0 → 0.1.3

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";
@@ -166,6 +168,7 @@ type PlayerConfig = Required<{
166
168
  streaming: {
167
169
  engine: "hls";
168
170
  hls: Required<NonNullable<StreamingConfig["hls"]>>;
171
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
169
172
  };
170
173
  logging: Required<LoggingConfig>;
171
174
  debug: Required<DebugConfig>;
@@ -239,14 +242,30 @@ type PlayerEventMap = {
239
242
  };
240
243
 
241
244
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
242
- declare const PACKAGE_VERSION = "0.1.0";
245
+ declare const PACKAGE_VERSION = "0.1.3";
243
246
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
244
247
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
245
248
  declare const DEFAULT_THEME: Required<ThemeConfig>;
246
249
  declare const DEFAULT_WATERMARK: Required<WatermarkConfig>;
247
250
  declare const DEFAULT_PLAYER_CONFIG: PlayerConfig;
251
+ /** Hard cap so pathological graphs cannot blow the stack. */
252
+ declare const SECRET_SCAN_MAX_DEPTH = 32;
253
+ /**
254
+ * True only for JSON-like plain objects (`{}` / `Object.create(null)`).
255
+ * Excludes DOM nodes, class instances, arrays, dates, etc.
256
+ */
257
+ declare function isPlainObject(value: unknown): value is Record<string, unknown>;
258
+ /**
259
+ * Scans plain config objects for forbidden secret key names.
260
+ *
261
+ * Enterprise guarantees:
262
+ * - Never deep-walks DOM / host / class instances (plain-object only)
263
+ * - Does not descend into `mount` / `auth` / `playback` (shallow key check only)
264
+ * - Cycle-safe via WeakSet
265
+ * - Depth-capped + iterative (no call-stack recursion)
266
+ */
248
267
  declare function assertNoSecretsInConfig(raw: unknown): void;
249
- declare function mergePlayerConfig(partial?: Partial<{
268
+ type PlayerConfigPartial = Partial<{
250
269
  player: Partial<PlayerBehaviorConfig>;
251
270
  controls: Partial<ControlsConfig>;
252
271
  theme: Partial<ThemeConfig>;
@@ -254,12 +273,13 @@ declare function mergePlayerConfig(partial?: Partial<{
254
273
  streaming: {
255
274
  engine?: "hls";
256
275
  hls?: Partial<PlayerConfig["streaming"]["hls"]>;
257
- keyProxyUrlBuilder?: unknown;
276
+ keyProxyUrlBuilder?: PlayerConfig["streaming"]["keyProxyUrlBuilder"];
258
277
  };
259
278
  logging: Partial<PlayerConfig["logging"]>;
260
279
  debug: Partial<PlayerConfig["debug"]>;
261
280
  accessibility: Partial<PlayerConfig["accessibility"]>;
262
- }>): PlayerConfig;
281
+ }>;
282
+ declare function mergePlayerConfig(partial?: PlayerConfigPartial): PlayerConfig;
263
283
  /** Maps theme colors to CSS custom properties for UI adapters. */
264
284
  declare function themeToCssVars(theme: Required<ThemeConfig>): Record<string, string>;
265
285
 
@@ -326,7 +346,14 @@ declare class AuthParamsStore {
326
346
  constructor(initial?: URLSearchParams | Record<string, string>);
327
347
  getSnapshot(): URLSearchParams;
328
348
  toRecord(): Record<string, string>;
349
+ /** True when at least one auth query param is stored. */
350
+ hasParams(): boolean;
351
+ toString(): string;
329
352
  update(next: URLSearchParams | Record<string, string>): void;
353
+ /**
354
+ * Merges current auth params onto a target URL, preserving unrelated query keys.
355
+ * Critical for child HLS playlists/segments that omit signed query params.
356
+ */
330
357
  applyToUrl(rawUrl: string, baseOrigin?: string): string;
331
358
  }
332
359
 
@@ -359,6 +386,8 @@ type HlsEngineOptions = {
359
386
  startMuted: boolean;
360
387
  startPosition?: number;
361
388
  volume?: number;
389
+ videoId?: string;
390
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
362
391
  hlsConfig: {
363
392
  enableWorker: boolean;
364
393
  lowLatencyMode: boolean;
@@ -370,6 +399,11 @@ type HlsEngineOptions = {
370
399
  };
371
400
  /**
372
401
  * Dual-path HLS engine: hls.js when supported, otherwise native Safari HLS.
402
+ *
403
+ * Signed CDN playback requires auth query params on *every* HLS request
404
+ * (master, media playlist, segments, keys). hls.js only receives the signed
405
+ * master URL via loadSource; child URIs are relative and unsigned unless a
406
+ * custom loader re-applies AuthParamsStore on each request.
373
407
  */
374
408
  declare class HlsEngine {
375
409
  private readonly options;
@@ -541,7 +575,27 @@ declare class RefreshManager {
541
575
  private renew;
542
576
  }
543
577
 
544
- declare function redactValue(key: string, value: unknown): unknown;
545
- declare function redactObject(obj: Record<string, unknown>): Record<string, unknown>;
578
+ type HlsLoader = new (config: object) => {
579
+ load(context: LoaderContext, config: LoaderConfiguration, callbacks: LoaderCallbacks<LoaderContext>): void;
580
+ destroy(): void;
581
+ };
582
+ type AuthParamsLoaderOptions = {
583
+ videoId?: string;
584
+ keyProxyUrlBuilder?: StreamingConfig["keyProxyUrlBuilder"];
585
+ };
586
+ /**
587
+ * Rewrites an HLS request URL so signed auth query params are present.
588
+ * Master playlists are signed on loadSource; child playlists / segments / keys
589
+ * are relative and would otherwise be fetched unsigned → CDN 403.
590
+ */
591
+ declare function rewriteHlsRequestUrl(rawUrl: string, store: AuthParamsStore, options?: AuthParamsLoaderOptions, baseOrigin?: string): string;
592
+ /**
593
+ * hls.js loader that re-applies live AuthParamsStore credentials on every
594
+ * manifest / level / fragment / key request.
595
+ */
596
+ declare function createAuthParamsLoader(HlsClass: typeof Hls, store: AuthParamsStore, options?: AuthParamsLoaderOptions): HlsLoader;
597
+
598
+ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>, depth?: number): unknown;
599
+ declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
546
600
 
547
- 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, type SeekToLiveResult, type StreamingConfig, type ThemeConfig, type VideoPlayer, type WatermarkConfig, assertNoSecretsInConfig, createPlayerError, createVideoPlayer, getLiveEdgeTime, isAtLiveEdge, mergePlayerConfig, redactObject, redactSensitive, redactValue, seekToLiveEdge, themeToCssVars };
601
+ 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.0";
149
+ var PACKAGE_VERSION = "0.1.3";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -232,22 +232,83 @@ var FORBIDDEN_SECRET_KEYS = [
232
232
  "basicauthpassword",
233
233
  "password"
234
234
  ];
235
+ var SKIP_DEEP_SCAN_KEYS = /* @__PURE__ */ new Set([
236
+ "mount",
237
+ "auth",
238
+ "playback",
239
+ "gettoken",
240
+ "getsource",
241
+ "refreshsource",
242
+ "keyproxyurlbuilder"
243
+ ]);
244
+ var SECRET_SCAN_MAX_DEPTH = 32;
245
+ function isPlainObject(value) {
246
+ if (value === null || typeof value !== "object") return false;
247
+ if (Array.isArray(value)) return false;
248
+ const proto = Object.getPrototypeOf(value);
249
+ return proto === Object.prototype || proto === null;
250
+ }
251
+ function normalizeConfigKey(key) {
252
+ return key.toLowerCase().replace(/[^a-z0-9]/g, "");
253
+ }
254
+ function isForbiddenSecretKey(key) {
255
+ const lower = normalizeConfigKey(key);
256
+ return FORBIDDEN_SECRET_KEYS.some((k) => lower.includes(k));
257
+ }
258
+ function throwForbidden(path, key) {
259
+ throw new Error(
260
+ `INVALID_CONFIGURATION: Forbidden secret key "${path}${key}" must never be passed to the browser SDK. Keep clientSecret on your server/BFF and use auth.getToken().`
261
+ );
262
+ }
235
263
  function assertNoSecretsInConfig(raw) {
236
- if (!raw || typeof raw !== "object") return;
237
- const walk = (obj, path) => {
238
- for (const [key, value] of Object.entries(obj)) {
239
- const lower = key.toLowerCase().replace(/[^a-z0-9]/g, "");
240
- if (FORBIDDEN_SECRET_KEYS.some((k) => lower.includes(k))) {
241
- throw new Error(
242
- `INVALID_CONFIGURATION: Forbidden secret key "${path}${key}" must never be passed to the browser SDK. Keep clientSecret on your server/BFF and use auth.getToken().`
243
- );
264
+ if (!isPlainObject(raw) && !Array.isArray(raw)) return;
265
+ const seen = /* @__PURE__ */ new WeakSet();
266
+ const stack = [
267
+ { value: raw, path: "", depth: 0 }
268
+ ];
269
+ while (stack.length > 0) {
270
+ const current = stack.pop();
271
+ if (current.depth > SECRET_SCAN_MAX_DEPTH) continue;
272
+ const { value, path, depth } = current;
273
+ if (Array.isArray(value)) {
274
+ if (seen.has(value)) continue;
275
+ seen.add(value);
276
+ for (let i = 0; i < value.length; i += 1) {
277
+ const item = value[i];
278
+ if (isPlainObject(item) || Array.isArray(item)) {
279
+ stack.push({ value: item, path: `${path}[${i}]`, depth: depth + 1 });
280
+ }
281
+ }
282
+ continue;
283
+ }
284
+ if (!isPlainObject(value)) continue;
285
+ if (seen.has(value)) continue;
286
+ seen.add(value);
287
+ for (const [key, child] of Object.entries(value)) {
288
+ const normalized = normalizeConfigKey(key);
289
+ if (isForbiddenSecretKey(key)) {
290
+ throwForbidden(path, key);
291
+ }
292
+ if (normalized === "mount") continue;
293
+ if (SKIP_DEEP_SCAN_KEYS.has(normalized)) {
294
+ if (isPlainObject(child)) {
295
+ for (const nestedKey of Object.keys(child)) {
296
+ if (isForbiddenSecretKey(nestedKey)) {
297
+ throwForbidden(`${path}${key}.`, nestedKey);
298
+ }
299
+ }
300
+ }
301
+ continue;
244
302
  }
245
- if (value && typeof value === "object" && !Array.isArray(value)) {
246
- walk(value, `${path}${key}.`);
303
+ if (isPlainObject(child) || Array.isArray(child)) {
304
+ stack.push({
305
+ value: child,
306
+ path: path ? `${path}${key}.` : `${key}.`,
307
+ depth: depth + 1
308
+ });
247
309
  }
248
310
  }
249
- };
250
- walk(raw, "");
311
+ }
251
312
  }
252
313
  function mergePlayerConfig(partial) {
253
314
  assertNoSecretsInConfig(partial);
@@ -261,7 +322,8 @@ function mergePlayerConfig(partial) {
261
322
  hls: {
262
323
  ...DEFAULT_PLAYER_CONFIG.streaming.hls,
263
324
  ...partial?.streaming?.hls
264
- }
325
+ },
326
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder
265
327
  },
266
328
  logging: { ...DEFAULT_PLAYER_CONFIG.logging, ...partial?.logging },
267
329
  debug: { ...DEFAULT_PLAYER_CONFIG.debug, ...partial?.debug },
@@ -335,7 +397,14 @@ var EventEmitter = class {
335
397
 
336
398
  // src/security/redaction.ts
337
399
  var SENSITIVE_KEYS = /^(authorization|cookie|set-cookie|x-api-key|token|accessToken|access_token|clientSecret|password)$/i;
338
- function redactValue(key, value) {
400
+ var REDACT_MAX_DEPTH = 32;
401
+ function isPlainObject2(value) {
402
+ if (value === null || typeof value !== "object") return false;
403
+ if (Array.isArray(value)) return false;
404
+ const proto = Object.getPrototypeOf(value);
405
+ return proto === Object.prototype || proto === null;
406
+ }
407
+ function redactValue(key, value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
339
408
  if (SENSITIVE_KEYS.test(key)) return "[REDACTED]";
340
409
  if (typeof value === "string") {
341
410
  if (/^https?:\/\//i.test(value) && /[?&](token|expires)=/i.test(value)) {
@@ -352,15 +421,25 @@ function redactValue(key, value) {
352
421
  }
353
422
  }
354
423
  }
355
- if (value && typeof value === "object" && !Array.isArray(value)) {
356
- return redactObject(value);
424
+ if (Array.isArray(value)) {
425
+ if (depth >= REDACT_MAX_DEPTH || seen.has(value)) return "[REDACTED_CYCLE]";
426
+ seen.add(value);
427
+ return value.map(
428
+ (item, index) => redactValue(String(index), item, seen, depth + 1)
429
+ );
430
+ }
431
+ if (isPlainObject2(value)) {
432
+ return redactObject(value, seen, depth + 1);
357
433
  }
358
434
  return value;
359
435
  }
360
- function redactObject(obj) {
436
+ function redactObject(obj, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
437
+ if (depth > REDACT_MAX_DEPTH) return { "[REDACTED]": "max_depth" };
438
+ if (seen.has(obj)) return { "[REDACTED]": "cycle" };
439
+ seen.add(obj);
361
440
  const out = {};
362
441
  for (const [key, value] of Object.entries(obj)) {
363
- out[key] = redactValue(key, value);
442
+ out[key] = redactValue(key, value, seen, depth);
364
443
  }
365
444
  return out;
366
445
  }
@@ -420,11 +499,25 @@ var AuthParamsStore = class {
420
499
  });
421
500
  return out;
422
501
  }
502
+ /** True when at least one auth query param is stored. */
503
+ hasParams() {
504
+ return [...this.params.keys()].length > 0;
505
+ }
506
+ toString() {
507
+ return this.params.toString();
508
+ }
423
509
  update(next) {
424
510
  this.params = next instanceof URLSearchParams ? new URLSearchParams(next) : new URLSearchParams(next);
425
511
  }
512
+ /**
513
+ * Merges current auth params onto a target URL, preserving unrelated query keys.
514
+ * Critical for child HLS playlists/segments that omit signed query params.
515
+ */
426
516
  applyToUrl(rawUrl, baseOrigin) {
427
- const target = new URL(rawUrl, baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost"));
517
+ const target = new URL(
518
+ rawUrl,
519
+ baseOrigin ?? (typeof location !== "undefined" ? location.origin : "http://localhost")
520
+ );
428
521
  this.params.forEach((value, key) => {
429
522
  target.searchParams.set(key, value);
430
523
  });
@@ -659,6 +752,39 @@ function seekToLiveEdge(media, options) {
659
752
  return { moved: true, edge };
660
753
  }
661
754
 
755
+ // src/streaming/authParamsLoader.ts
756
+ var ENC_KEY_PATH_RE = /\/enc\.key$/i;
757
+ function rewriteHlsRequestUrl(rawUrl, store, options = {}, baseOrigin = typeof location !== "undefined" ? location.origin : "http://localhost") {
758
+ if (!rawUrl) return rawUrl;
759
+ const parsed = new URL(rawUrl, baseOrigin);
760
+ const isKey = ENC_KEY_PATH_RE.test(parsed.pathname);
761
+ if (isKey && options.keyProxyUrlBuilder && options.videoId) {
762
+ const snapshot = store.toRecord();
763
+ return options.keyProxyUrlBuilder({
764
+ videoId: options.videoId,
765
+ keyUrl: parsed.toString(),
766
+ token: snapshot.token,
767
+ expires: snapshot.expires
768
+ });
769
+ }
770
+ if (!store.hasParams()) return parsed.toString();
771
+ return store.applyToUrl(rawUrl, baseOrigin);
772
+ }
773
+ function createAuthParamsLoader(HlsClass, store, options = {}) {
774
+ const BaseLoader = HlsClass.DefaultConfig.loader;
775
+ return class AuthParamsLoader extends BaseLoader {
776
+ load(context, config, callbacks) {
777
+ if (context.url) {
778
+ try {
779
+ context.url = rewriteHlsRequestUrl(context.url, store, options);
780
+ } catch {
781
+ }
782
+ }
783
+ super.load(context, config, callbacks);
784
+ }
785
+ };
786
+ }
787
+
662
788
  // src/streaming/HlsEngine.ts
663
789
  var HlsEngine = class {
664
790
  constructor(options) {
@@ -711,15 +837,16 @@ var HlsEngine = class {
711
837
  }
712
838
  }
713
839
  async startWithHlsJs(Hls, playbackUrl) {
714
- const { videoEl, authParams, hlsConfig, callbacks } = this.options;
840
+ const { videoEl, authParams, hlsConfig, callbacks, videoId, keyProxyUrlBuilder } = this.options;
841
+ const loader = createAuthParamsLoader(Hls, authParams, {
842
+ videoId,
843
+ keyProxyUrlBuilder
844
+ });
715
845
  this.hls = new Hls({
716
846
  enableWorker: hlsConfig.enableWorker,
717
847
  lowLatencyMode: hlsConfig.lowLatencyMode,
718
848
  backBufferLength: hlsConfig.backBufferLength,
719
- xhrSetup: (xhr, url) => {
720
- void xhr;
721
- void url;
722
- }
849
+ loader
723
850
  });
724
851
  this.hls.attachMedia(videoEl);
725
852
  this.hls.loadSource(authParams.applyToUrl(playbackUrl));
@@ -830,7 +957,18 @@ function generateSessionId() {
830
957
  return `ps_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
831
958
  }
832
959
  function createVideoPlayer(options) {
833
- assertNoSecretsInConfig(options);
960
+ assertNoSecretsInConfig({
961
+ videoId: options.videoId,
962
+ sourceUrl: options.sourceUrl,
963
+ player: options.player,
964
+ controls: options.controls,
965
+ theme: options.theme,
966
+ watermark: options.watermark,
967
+ streaming: options.streaming,
968
+ logging: options.logging,
969
+ debug: options.debug,
970
+ accessibility: options.accessibility
971
+ });
834
972
  let config = mergePlayerConfig({
835
973
  player: options.player,
836
974
  controls: options.controls,
@@ -916,6 +1054,8 @@ function createVideoPlayer(options) {
916
1054
  startMuted: config.player.startMuted,
917
1055
  startPosition: config.player.startPosition,
918
1056
  volume: config.player.volume,
1057
+ videoId: options.videoId,
1058
+ keyProxyUrlBuilder: options.streaming?.keyProxyUrlBuilder,
919
1059
  hlsConfig: config.streaming.hls,
920
1060
  logger,
921
1061
  callbacks: {
@@ -1176,7 +1316,8 @@ function createVideoPlayer(options) {
1176
1316
  watermark: { ...config.watermark, ...partial?.watermark },
1177
1317
  streaming: {
1178
1318
  engine: "hls",
1179
- hls: { ...config.streaming.hls, ...partial?.streaming?.hls }
1319
+ hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1320
+ keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder
1180
1321
  },
1181
1322
  logging: { ...config.logging, ...partial?.logging },
1182
1323
  debug: { ...config.debug, ...partial?.debug },
@@ -1212,15 +1353,19 @@ export {
1212
1353
  PlaybackSourceManager,
1213
1354
  PlayerErrorCode,
1214
1355
  RefreshManager,
1356
+ SECRET_SCAN_MAX_DEPTH,
1215
1357
  assertNoSecretsInConfig,
1358
+ createAuthParamsLoader,
1216
1359
  createPlayerError,
1217
1360
  createVideoPlayer,
1218
1361
  getLiveEdgeTime,
1219
1362
  isAtLiveEdge,
1363
+ isPlainObject,
1220
1364
  mergePlayerConfig,
1221
1365
  redactObject,
1222
1366
  redactSensitive,
1223
1367
  redactValue,
1368
+ rewriteHlsRequestUrl,
1224
1369
  seekToLiveEdge,
1225
1370
  themeToCssVars
1226
1371
  };