@codenkay/video-nsgplayer-core 1.0.0 → 3.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.cts CHANGED
@@ -5,6 +5,22 @@ type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffer
5
5
  type HlsSupportMode = "hls.js" | "native" | "unsupported";
6
6
  type QualitySelection = "auto" | number;
7
7
  type PlaybackRate = 0.5 | 0.75 | 1 | 1.25 | 1.5 | 2;
8
+ type PlayerTextTrack = {
9
+ index: number;
10
+ id: string;
11
+ label: string;
12
+ language?: string;
13
+ uri?: string;
14
+ };
15
+ type PlayerAudioTrack = {
16
+ index: number;
17
+ id: string;
18
+ label: string;
19
+ language?: string;
20
+ uri?: string;
21
+ };
22
+ /** Runtime caption selection: off or a concrete subtitle track index. */
23
+ type TextTrackSelection = "off" | number;
8
24
  type HlsQualityLevel = {
9
25
  index: number;
10
26
  height: number;
@@ -222,6 +238,14 @@ type PlayerEventMap = {
222
238
  selection: QualitySelection;
223
239
  label?: string;
224
240
  };
241
+ texttrackchange: {
242
+ tracks: PlayerTextTrack[];
243
+ selection: TextTrackSelection;
244
+ };
245
+ audiotrackchange: {
246
+ tracks: PlayerAudioTrack[];
247
+ selection: number;
248
+ };
225
249
  volumechange: {
226
250
  volume: number;
227
251
  muted: boolean;
@@ -255,7 +279,7 @@ type PlayerEventMap = {
255
279
  };
256
280
 
257
281
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
258
- declare const PACKAGE_VERSION = "0.1.8";
282
+ declare const PACKAGE_VERSION = "2.0.1";
259
283
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
260
284
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
261
285
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -390,6 +414,10 @@ type HlsEngineCallbacks = {
390
414
  onAuthRecoveryNeeded?: (reason: string) => void;
391
415
  onQualitiesChange?: (levels: HlsQualityLevel[]) => void;
392
416
  onQualitySelectionChange?: (selection: QualitySelection) => void;
417
+ onTextTracksChange?: (tracks: PlayerTextTrack[]) => void;
418
+ onTextTrackSelectionChange?: (selection: TextTrackSelection) => void;
419
+ onAudioTracksChange?: (tracks: PlayerAudioTrack[]) => void;
420
+ onAudioTrackSelectionChange?: (selection: number) => void;
393
421
  onEnded?: () => void;
394
422
  };
395
423
  type HlsEngineOptions = {
@@ -431,6 +459,11 @@ declare class HlsEngine {
431
459
  private mediaRecoveryCount;
432
460
  private qualities;
433
461
  private qualitySelection;
462
+ private textTracks;
463
+ private textTrackSelection;
464
+ private audioTracks;
465
+ private audioTrackSelection;
466
+ private nativeTextTrackCleanup;
434
467
  constructor(options: HlsEngineOptions);
435
468
  start(): Promise<void>;
436
469
  private startWithHlsJs;
@@ -442,7 +475,16 @@ declare class HlsEngine {
442
475
  setQuality(selection: QualitySelection): void;
443
476
  getQuality(): QualitySelection;
444
477
  getQualities(): HlsQualityLevel[];
478
+ getTextTracks(): PlayerTextTrack[];
479
+ getTextTrackSelection(): TextTrackSelection;
480
+ setTextTrack(selection: TextTrackSelection): void;
481
+ getAudioTracks(): PlayerAudioTrack[];
482
+ getAudioTrackSelection(): number;
483
+ setAudioTrack(index: number): void;
445
484
  getSupportMode(): HlsSupportMode;
485
+ private syncHlsSubtitleTracks;
486
+ private syncHlsAudioTracks;
487
+ private bindNativeTextTracks;
446
488
  destroy(): void;
447
489
  }
448
490
 
@@ -468,9 +510,17 @@ type VideoPlayer = {
468
510
  getQuality: () => QualitySelection;
469
511
  setQuality: (selection: QualitySelection | string) => void;
470
512
  getAvailableQualities: () => ReturnType<HlsEngine["getQualities"]>;
513
+ getTextTracks: () => PlayerTextTrack[];
514
+ getSelectedTextTrack: () => TextTrackSelection;
515
+ setTextTrack: (selection: TextTrackSelection) => void;
516
+ getAudioTracks: () => PlayerAudioTrack[];
517
+ getSelectedAudioTrack: () => number;
518
+ setAudioTrack: (index: number) => void;
471
519
  enterFullscreen: () => Promise<void>;
472
520
  exitFullscreen: () => Promise<void>;
473
521
  isFullscreen: () => boolean;
522
+ /** Attach fullscreen target container (host UI chrome). Zoom stays UI-only. */
523
+ setFullscreenElement: (el: HTMLElement | null) => void;
474
524
  reload: () => Promise<void>;
475
525
  destroy: () => void;
476
526
  getDiagnostics: () => PlayerDiagnostics;
@@ -642,4 +692,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
642
692
  */
643
693
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
644
694
 
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 };
695
+ 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 PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, 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.d.ts CHANGED
@@ -5,6 +5,22 @@ type PlayerState = "idle" | "loading" | "ready" | "playing" | "paused" | "buffer
5
5
  type HlsSupportMode = "hls.js" | "native" | "unsupported";
6
6
  type QualitySelection = "auto" | number;
7
7
  type PlaybackRate = 0.5 | 0.75 | 1 | 1.25 | 1.5 | 2;
8
+ type PlayerTextTrack = {
9
+ index: number;
10
+ id: string;
11
+ label: string;
12
+ language?: string;
13
+ uri?: string;
14
+ };
15
+ type PlayerAudioTrack = {
16
+ index: number;
17
+ id: string;
18
+ label: string;
19
+ language?: string;
20
+ uri?: string;
21
+ };
22
+ /** Runtime caption selection: off or a concrete subtitle track index. */
23
+ type TextTrackSelection = "off" | number;
8
24
  type HlsQualityLevel = {
9
25
  index: number;
10
26
  height: number;
@@ -222,6 +238,14 @@ type PlayerEventMap = {
222
238
  selection: QualitySelection;
223
239
  label?: string;
224
240
  };
241
+ texttrackchange: {
242
+ tracks: PlayerTextTrack[];
243
+ selection: TextTrackSelection;
244
+ };
245
+ audiotrackchange: {
246
+ tracks: PlayerAudioTrack[];
247
+ selection: number;
248
+ };
225
249
  volumechange: {
226
250
  volume: number;
227
251
  muted: boolean;
@@ -255,7 +279,7 @@ type PlayerEventMap = {
255
279
  };
256
280
 
257
281
  declare const PLAYBACK_RATES: readonly [0.5, 0.75, 1, 1.25, 1.5, 2];
258
- declare const PACKAGE_VERSION = "0.1.8";
282
+ declare const PACKAGE_VERSION = "2.0.1";
259
283
  declare const DEFAULT_PLAYER_BEHAVIOR: Required<PlayerBehaviorConfig>;
260
284
  declare const DEFAULT_CONTROLS: Required<ControlsConfig>;
261
285
  declare const DEFAULT_THEME: Required<ThemeConfig>;
@@ -390,6 +414,10 @@ type HlsEngineCallbacks = {
390
414
  onAuthRecoveryNeeded?: (reason: string) => void;
391
415
  onQualitiesChange?: (levels: HlsQualityLevel[]) => void;
392
416
  onQualitySelectionChange?: (selection: QualitySelection) => void;
417
+ onTextTracksChange?: (tracks: PlayerTextTrack[]) => void;
418
+ onTextTrackSelectionChange?: (selection: TextTrackSelection) => void;
419
+ onAudioTracksChange?: (tracks: PlayerAudioTrack[]) => void;
420
+ onAudioTrackSelectionChange?: (selection: number) => void;
393
421
  onEnded?: () => void;
394
422
  };
395
423
  type HlsEngineOptions = {
@@ -431,6 +459,11 @@ declare class HlsEngine {
431
459
  private mediaRecoveryCount;
432
460
  private qualities;
433
461
  private qualitySelection;
462
+ private textTracks;
463
+ private textTrackSelection;
464
+ private audioTracks;
465
+ private audioTrackSelection;
466
+ private nativeTextTrackCleanup;
434
467
  constructor(options: HlsEngineOptions);
435
468
  start(): Promise<void>;
436
469
  private startWithHlsJs;
@@ -442,7 +475,16 @@ declare class HlsEngine {
442
475
  setQuality(selection: QualitySelection): void;
443
476
  getQuality(): QualitySelection;
444
477
  getQualities(): HlsQualityLevel[];
478
+ getTextTracks(): PlayerTextTrack[];
479
+ getTextTrackSelection(): TextTrackSelection;
480
+ setTextTrack(selection: TextTrackSelection): void;
481
+ getAudioTracks(): PlayerAudioTrack[];
482
+ getAudioTrackSelection(): number;
483
+ setAudioTrack(index: number): void;
445
484
  getSupportMode(): HlsSupportMode;
485
+ private syncHlsSubtitleTracks;
486
+ private syncHlsAudioTracks;
487
+ private bindNativeTextTracks;
446
488
  destroy(): void;
447
489
  }
448
490
 
@@ -468,9 +510,17 @@ type VideoPlayer = {
468
510
  getQuality: () => QualitySelection;
469
511
  setQuality: (selection: QualitySelection | string) => void;
470
512
  getAvailableQualities: () => ReturnType<HlsEngine["getQualities"]>;
513
+ getTextTracks: () => PlayerTextTrack[];
514
+ getSelectedTextTrack: () => TextTrackSelection;
515
+ setTextTrack: (selection: TextTrackSelection) => void;
516
+ getAudioTracks: () => PlayerAudioTrack[];
517
+ getSelectedAudioTrack: () => number;
518
+ setAudioTrack: (index: number) => void;
471
519
  enterFullscreen: () => Promise<void>;
472
520
  exitFullscreen: () => Promise<void>;
473
521
  isFullscreen: () => boolean;
522
+ /** Attach fullscreen target container (host UI chrome). Zoom stays UI-only. */
523
+ setFullscreenElement: (el: HTMLElement | null) => void;
474
524
  reload: () => Promise<void>;
475
525
  destroy: () => void;
476
526
  getDiagnostics: () => PlayerDiagnostics;
@@ -642,4 +692,4 @@ declare function redactValue(key: string, value: unknown, seen?: WeakSet<object>
642
692
  */
643
693
  declare function redactObject(obj: Record<string, unknown>, seen?: WeakSet<object>, depth?: number): Record<string, unknown>;
644
694
 
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 };
695
+ 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 PlayerAudioTrack, type PlayerBehaviorConfig, type PlayerConfig, type PlayerDiagnostics, type PlayerError, PlayerErrorCode, type PlayerEventMap, type PlayerSkipSeconds, type PlayerState, type PlayerTextTrack, type PlayerZoomLevel, type QualitySelection, RefreshManager, SECRET_SCAN_MAX_DEPTH, type SeekToLiveResult, type StreamingConfig, type TextTrackSelection, 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.8";
149
+ var PACKAGE_VERSION = "2.0.1";
150
150
  var DEFAULT_PLAYER_BEHAVIOR = {
151
151
  autoplay: true,
152
152
  startMuted: true,
@@ -915,9 +915,48 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
915
915
  const usingKeyProxy = Boolean(
916
916
  isKey && options.keyProxyUrlBuilder && options.videoId
917
917
  );
918
+ const signedKeyUrl = isKey ? rewriteHlsRequestUrl(originalUrl, store, {
919
+ ...options,
920
+ keyProxyUrlBuilder: void 0
921
+ }) : context.url;
918
922
  context.url = rewriteHlsRequestUrl(originalUrl, store, options);
919
923
  if (usingKeyProxy) {
920
- this.inner.load(context, config, callbacks);
924
+ const accessToken = options.getKeyAccessToken?.() ?? null;
925
+ if (!accessToken) {
926
+ this.inner.load(context, config, callbacks);
927
+ return;
928
+ }
929
+ const proxyCallbacks = {
930
+ ...callbacks,
931
+ onError: (error, errContext, networkDetails, stats) => {
932
+ const code = error?.code;
933
+ const retryable = code === 0 || code === 502 || code === 403 || code === 401 || /Cloudflare|HTML|DOCTYPE|blocked/i.test(error?.text ?? "");
934
+ if (!retryable) {
935
+ callbacks.onError(error, errContext, networkDetails, stats);
936
+ return;
937
+ }
938
+ this.keyAbort?.abort();
939
+ this.keyAbort = new AbortController();
940
+ const bearerContext = { ...context, url: signedKeyUrl };
941
+ void loadEncryptionKeyWithFetch(
942
+ bearerContext,
943
+ callbacks,
944
+ accessToken,
945
+ this.keyAbort.signal
946
+ ).catch((fetchError) => {
947
+ callbacks.onError(
948
+ {
949
+ code: 0,
950
+ text: fetchError instanceof Error ? fetchError.message : error.text || "enc.key proxy and Bearer fetch failed"
951
+ },
952
+ bearerContext,
953
+ null,
954
+ stats
955
+ );
956
+ });
957
+ }
958
+ };
959
+ this.inner.load(context, config, proxyCallbacks);
921
960
  return;
922
961
  }
923
962
  if (isKey && typeof options.getKeyAccessToken === "function") {
@@ -935,6 +974,10 @@ function createAuthParamsLoader(HlsClass, store, options = {}) {
935
974
  ).catch((error) => {
936
975
  const stats = createLoaderStats();
937
976
  const aborted = error instanceof DOMException && error.name === "AbortError";
977
+ if (!aborted && (error instanceof TypeError || error instanceof Error && /Failed to fetch|NetworkError|CORS/i.test(error.message))) {
978
+ this.inner.load(context, config, callbacks);
979
+ return;
980
+ }
938
981
  callbacks.onError(
939
982
  {
940
983
  code: 0,
@@ -971,6 +1014,11 @@ var HlsEngine = class {
971
1014
  this.mediaRecoveryCount = 0;
972
1015
  this.qualities = [];
973
1016
  this.qualitySelection = "auto";
1017
+ this.textTracks = [];
1018
+ this.textTrackSelection = "off";
1019
+ this.audioTracks = [];
1020
+ this.audioTrackSelection = 0;
1021
+ this.nativeTextTrackCleanup = null;
974
1022
  }
975
1023
  async start() {
976
1024
  const { videoEl, playbackUrl, startMuted, volume } = this.options;
@@ -990,6 +1038,7 @@ var HlsEngine = class {
990
1038
  this.options.callbacks.onSupportMode?.("native");
991
1039
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
992
1040
  this.bindMediaEvents();
1041
+ this.bindNativeTextTracks();
993
1042
  this.options.callbacks.onStateChange?.("ready");
994
1043
  await this.applyStartPositionAndAutoplay();
995
1044
  return;
@@ -1033,9 +1082,19 @@ var HlsEngine = class {
1033
1082
  label: level.height != null && level.height > 0 ? `${level.height}p` : `Level ${index}`
1034
1083
  }));
1035
1084
  callbacks.onQualitiesChange?.(this.qualities);
1085
+ this.syncHlsSubtitleTracks();
1086
+ this.syncHlsAudioTracks();
1036
1087
  callbacks.onStateChange?.("ready");
1037
1088
  void this.applyStartPositionAndAutoplay();
1038
1089
  });
1090
+ this.hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {
1091
+ if (this.destroyed) return;
1092
+ this.syncHlsSubtitleTracks();
1093
+ });
1094
+ this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {
1095
+ if (this.destroyed) return;
1096
+ this.syncHlsAudioTracks();
1097
+ });
1039
1098
  this.hls.on(Hls.Events.ERROR, (_event, data) => {
1040
1099
  this.handleHlsError(Hls, data);
1041
1100
  });
@@ -1161,12 +1220,120 @@ var HlsEngine = class {
1161
1220
  getQualities() {
1162
1221
  return this.qualities;
1163
1222
  }
1223
+ getTextTracks() {
1224
+ return this.textTracks;
1225
+ }
1226
+ getTextTrackSelection() {
1227
+ return this.textTrackSelection;
1228
+ }
1229
+ setTextTrack(selection) {
1230
+ if (this.destroyed) return;
1231
+ this.textTrackSelection = selection;
1232
+ this.options.callbacks.onTextTrackSelectionChange?.(selection);
1233
+ if (this.supportMode === "hls.js" && this.hls) {
1234
+ this.hls.subtitleDisplay = selection !== "off";
1235
+ this.hls.subtitleTrack = selection === "off" ? -1 : selection;
1236
+ return;
1237
+ }
1238
+ if (this.supportMode === "native" && this.options.videoEl) {
1239
+ const tracks = this.options.videoEl.textTracks;
1240
+ for (let i = 0; i < tracks.length; i += 1) {
1241
+ const track = tracks[i];
1242
+ track.mode = selection !== "off" && i === selection ? "showing" : "disabled";
1243
+ }
1244
+ }
1245
+ }
1246
+ getAudioTracks() {
1247
+ return this.audioTracks;
1248
+ }
1249
+ getAudioTrackSelection() {
1250
+ return this.audioTrackSelection;
1251
+ }
1252
+ setAudioTrack(index) {
1253
+ if (this.destroyed) return;
1254
+ if (!Number.isInteger(index) || index < 0 || this.audioTracks.length > 0 && index >= this.audioTracks.length) {
1255
+ return;
1256
+ }
1257
+ this.audioTrackSelection = index;
1258
+ this.options.callbacks.onAudioTrackSelectionChange?.(index);
1259
+ if (this.supportMode === "hls.js" && this.hls) {
1260
+ this.hls.audioTrack = index;
1261
+ return;
1262
+ }
1263
+ if (this.supportMode === "native" && this.options.videoEl) {
1264
+ const audioTracks = this.options.videoEl.audioTracks;
1265
+ if (audioTracks && audioTracks.length > 0) {
1266
+ for (let i = 0; i < audioTracks.length; i += 1) {
1267
+ audioTracks[i].enabled = i === index;
1268
+ }
1269
+ }
1270
+ }
1271
+ }
1164
1272
  getSupportMode() {
1165
1273
  return this.supportMode;
1166
1274
  }
1275
+ syncHlsSubtitleTracks() {
1276
+ if (!this.hls) return;
1277
+ const tracks = (this.hls.subtitleTracks ?? []).map(
1278
+ (track, index) => ({
1279
+ index,
1280
+ id: String(track.id ?? index),
1281
+ label: track.name || track.lang || `Subtitles ${index + 1}`,
1282
+ language: track.lang
1283
+ })
1284
+ );
1285
+ this.textTracks = tracks;
1286
+ this.options.callbacks.onTextTracksChange?.(tracks);
1287
+ }
1288
+ syncHlsAudioTracks() {
1289
+ if (!this.hls) return;
1290
+ const tracks = (this.hls.audioTracks ?? []).map(
1291
+ (track, index) => ({
1292
+ index,
1293
+ id: String(track.id ?? index),
1294
+ label: track.name || track.lang || `Audio ${index + 1}`,
1295
+ language: track.lang
1296
+ })
1297
+ );
1298
+ this.audioTracks = tracks;
1299
+ this.options.callbacks.onAudioTracksChange?.(tracks);
1300
+ if (tracks.length > 0) {
1301
+ this.audioTrackSelection = 0;
1302
+ this.options.callbacks.onAudioTrackSelectionChange?.(0);
1303
+ }
1304
+ }
1305
+ bindNativeTextTracks() {
1306
+ const { videoEl } = this.options;
1307
+ const syncFromElement = () => {
1308
+ if (this.destroyed || this.textTracks.length > 0) return;
1309
+ const list = videoEl.textTracks;
1310
+ const tracks = [];
1311
+ for (let i = 0; i < list.length; i += 1) {
1312
+ const track = list[i];
1313
+ if (track.kind !== "subtitles" && track.kind !== "captions") continue;
1314
+ tracks.push({
1315
+ index: tracks.length,
1316
+ id: track.id || `native-${tracks.length}`,
1317
+ label: track.label || track.language || `Track ${tracks.length + 1}`,
1318
+ language: track.language || void 0
1319
+ });
1320
+ }
1321
+ if (tracks.length === 0) return;
1322
+ this.textTracks = tracks;
1323
+ this.options.callbacks.onTextTracksChange?.(tracks);
1324
+ };
1325
+ const textTracks = videoEl.textTracks;
1326
+ textTracks.addEventListener?.("addtrack", syncFromElement);
1327
+ syncFromElement();
1328
+ this.nativeTextTrackCleanup = () => {
1329
+ textTracks.removeEventListener?.("addtrack", syncFromElement);
1330
+ };
1331
+ }
1167
1332
  destroy() {
1168
1333
  if (this.destroyed) return;
1169
1334
  this.destroyed = true;
1335
+ this.nativeTextTrackCleanup?.();
1336
+ this.nativeTextTrackCleanup = null;
1170
1337
  this.hls?.destroy();
1171
1338
  this.hls = null;
1172
1339
  const { videoEl } = this.options;
@@ -1223,11 +1390,40 @@ function createVideoPlayer(options) {
1223
1390
  let refreshManager = null;
1224
1391
  let fullscreenEl = null;
1225
1392
  let isLiveStream = false;
1393
+ let mediaCleanup = null;
1226
1394
  const setState = (next) => {
1227
1395
  if (destroyed && next !== "destroyed") return;
1228
1396
  state = next;
1229
1397
  events.emit("statechange", { state: next });
1230
1398
  };
1399
+ const bindMediaProgressEvents = (el) => {
1400
+ mediaCleanup?.();
1401
+ const onTimeUpdate = () => {
1402
+ if (destroyed) return;
1403
+ events.emit("timeupdate", {
1404
+ currentTime: el.currentTime,
1405
+ duration: Number.isFinite(el.duration) ? el.duration : 0
1406
+ });
1407
+ };
1408
+ const onProgress = () => {
1409
+ if (destroyed) return;
1410
+ let bufferedEnd = 0;
1411
+ try {
1412
+ if (el.buffered.length > 0) {
1413
+ bufferedEnd = el.buffered.end(el.buffered.length - 1);
1414
+ }
1415
+ } catch {
1416
+ bufferedEnd = 0;
1417
+ }
1418
+ events.emit("progress", { bufferedEnd });
1419
+ };
1420
+ el.addEventListener("timeupdate", onTimeUpdate);
1421
+ el.addEventListener("progress", onProgress);
1422
+ mediaCleanup = () => {
1423
+ el.removeEventListener("timeupdate", onTimeUpdate);
1424
+ el.removeEventListener("progress", onProgress);
1425
+ };
1426
+ };
1231
1427
  const authManager = new AuthManager(
1232
1428
  options.auth,
1233
1429
  options.auth?.refreshBeforeExpiryMs ?? 6e4,
@@ -1278,6 +1474,7 @@ function createVideoPlayer(options) {
1278
1474
  isLiveStream = Boolean(source.live);
1279
1475
  }
1280
1476
  const el = ensureVideoElement();
1477
+ bindMediaProgressEvents(el);
1281
1478
  engine?.destroy();
1282
1479
  engine = new HlsEngine({
1283
1480
  videoEl: el,
@@ -1289,12 +1486,11 @@ function createVideoPlayer(options) {
1289
1486
  volume: config.player.volume,
1290
1487
  videoId: options.videoId,
1291
1488
  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.
1297
- getKeyAccessToken: config.streaming.getKeyAccessToken,
1489
+ // Enterprise AES key auth:
1490
+ // 1) keyProxyUrlBuilder (same-origin BFF) when configured
1491
+ // 2) else getKeyAccessToken / AuthManager Bearer via browser fetch
1492
+ // 3) else signed query + XHR (CDN hosts)
1493
+ getKeyAccessToken: config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken()),
1298
1494
  hlsConfig: config.streaming.hls,
1299
1495
  logger,
1300
1496
  callbacks: {
@@ -1336,6 +1532,30 @@ function createVideoPlayer(options) {
1336
1532
  label: typeof selection === "number" ? level?.label : "auto"
1337
1533
  });
1338
1534
  },
1535
+ onTextTracksChange: (tracks) => {
1536
+ events.emit("texttrackchange", {
1537
+ tracks,
1538
+ selection: engine?.getTextTrackSelection() ?? "off"
1539
+ });
1540
+ },
1541
+ onTextTrackSelectionChange: (selection) => {
1542
+ events.emit("texttrackchange", {
1543
+ tracks: engine?.getTextTracks() ?? [],
1544
+ selection
1545
+ });
1546
+ },
1547
+ onAudioTracksChange: (tracks) => {
1548
+ events.emit("audiotrackchange", {
1549
+ tracks,
1550
+ selection: engine?.getAudioTrackSelection() ?? 0
1551
+ });
1552
+ },
1553
+ onAudioTrackSelectionChange: (selection) => {
1554
+ events.emit("audiotrackchange", {
1555
+ tracks: engine?.getAudioTracks() ?? [],
1556
+ selection
1557
+ });
1558
+ },
1339
1559
  onEnded: () => events.emit("ended")
1340
1560
  }
1341
1561
  });
@@ -1494,6 +1714,24 @@ function createVideoPlayer(options) {
1494
1714
  getAvailableQualities() {
1495
1715
  return engine?.getQualities() ?? [];
1496
1716
  },
1717
+ getTextTracks() {
1718
+ return engine?.getTextTracks() ?? [];
1719
+ },
1720
+ getSelectedTextTrack() {
1721
+ return engine?.getTextTrackSelection() ?? "off";
1722
+ },
1723
+ setTextTrack(selection) {
1724
+ engine?.setTextTrack(selection);
1725
+ },
1726
+ getAudioTracks() {
1727
+ return engine?.getAudioTracks() ?? [];
1728
+ },
1729
+ getSelectedAudioTrack() {
1730
+ return engine?.getAudioTrackSelection() ?? 0;
1731
+ },
1732
+ setAudioTrack(index) {
1733
+ engine?.setAudioTrack(index);
1734
+ },
1497
1735
  async enterFullscreen() {
1498
1736
  const target = fullscreenEl ?? videoEl?.parentElement ?? videoEl;
1499
1737
  if (target && "requestFullscreen" in target) {
@@ -1510,6 +1748,9 @@ function createVideoPlayer(options) {
1510
1748
  isFullscreen() {
1511
1749
  return Boolean(document.fullscreenElement);
1512
1750
  },
1751
+ setFullscreenElement(el) {
1752
+ fullscreenEl = el;
1753
+ },
1513
1754
  async reload() {
1514
1755
  refreshManager?.stop();
1515
1756
  engine?.destroy();
@@ -1518,6 +1759,8 @@ function createVideoPlayer(options) {
1518
1759
  destroy() {
1519
1760
  if (destroyed) return;
1520
1761
  destroyed = true;
1762
+ mediaCleanup?.();
1763
+ mediaCleanup = null;
1521
1764
  refreshManager?.stop();
1522
1765
  refreshManager = null;
1523
1766
  engine?.destroy();
@@ -1565,7 +1808,7 @@ function createVideoPlayer(options) {
1565
1808
  engine: "hls",
1566
1809
  hls: { ...config.streaming.hls, ...partial?.streaming?.hls },
1567
1810
  keyProxyUrlBuilder: partial?.streaming?.keyProxyUrlBuilder ?? config.streaming.keyProxyUrlBuilder,
1568
- getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken
1811
+ getKeyAccessToken: partial?.streaming?.getKeyAccessToken ?? config.streaming.getKeyAccessToken ?? (() => authManager.getAccessToken())
1569
1812
  },
1570
1813
  logging: { ...config.logging, ...partial?.logging },
1571
1814
  debug: { ...config.debug, ...partial?.debug },