@codenkay/video-nsgplayer-core 1.0.0 → 2.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;
@@ -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;
@@ -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
@@ -971,6 +971,11 @@ var HlsEngine = class {
971
971
  this.mediaRecoveryCount = 0;
972
972
  this.qualities = [];
973
973
  this.qualitySelection = "auto";
974
+ this.textTracks = [];
975
+ this.textTrackSelection = "off";
976
+ this.audioTracks = [];
977
+ this.audioTrackSelection = 0;
978
+ this.nativeTextTrackCleanup = null;
974
979
  }
975
980
  async start() {
976
981
  const { videoEl, playbackUrl, startMuted, volume } = this.options;
@@ -990,6 +995,7 @@ var HlsEngine = class {
990
995
  this.options.callbacks.onSupportMode?.("native");
991
996
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
992
997
  this.bindMediaEvents();
998
+ this.bindNativeTextTracks();
993
999
  this.options.callbacks.onStateChange?.("ready");
994
1000
  await this.applyStartPositionAndAutoplay();
995
1001
  return;
@@ -1033,9 +1039,19 @@ var HlsEngine = class {
1033
1039
  label: level.height != null && level.height > 0 ? `${level.height}p` : `Level ${index}`
1034
1040
  }));
1035
1041
  callbacks.onQualitiesChange?.(this.qualities);
1042
+ this.syncHlsSubtitleTracks();
1043
+ this.syncHlsAudioTracks();
1036
1044
  callbacks.onStateChange?.("ready");
1037
1045
  void this.applyStartPositionAndAutoplay();
1038
1046
  });
1047
+ this.hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {
1048
+ if (this.destroyed) return;
1049
+ this.syncHlsSubtitleTracks();
1050
+ });
1051
+ this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {
1052
+ if (this.destroyed) return;
1053
+ this.syncHlsAudioTracks();
1054
+ });
1039
1055
  this.hls.on(Hls.Events.ERROR, (_event, data) => {
1040
1056
  this.handleHlsError(Hls, data);
1041
1057
  });
@@ -1161,12 +1177,120 @@ var HlsEngine = class {
1161
1177
  getQualities() {
1162
1178
  return this.qualities;
1163
1179
  }
1180
+ getTextTracks() {
1181
+ return this.textTracks;
1182
+ }
1183
+ getTextTrackSelection() {
1184
+ return this.textTrackSelection;
1185
+ }
1186
+ setTextTrack(selection) {
1187
+ if (this.destroyed) return;
1188
+ this.textTrackSelection = selection;
1189
+ this.options.callbacks.onTextTrackSelectionChange?.(selection);
1190
+ if (this.supportMode === "hls.js" && this.hls) {
1191
+ this.hls.subtitleDisplay = selection !== "off";
1192
+ this.hls.subtitleTrack = selection === "off" ? -1 : selection;
1193
+ return;
1194
+ }
1195
+ if (this.supportMode === "native" && this.options.videoEl) {
1196
+ const tracks = this.options.videoEl.textTracks;
1197
+ for (let i = 0; i < tracks.length; i += 1) {
1198
+ const track = tracks[i];
1199
+ track.mode = selection !== "off" && i === selection ? "showing" : "disabled";
1200
+ }
1201
+ }
1202
+ }
1203
+ getAudioTracks() {
1204
+ return this.audioTracks;
1205
+ }
1206
+ getAudioTrackSelection() {
1207
+ return this.audioTrackSelection;
1208
+ }
1209
+ setAudioTrack(index) {
1210
+ if (this.destroyed) return;
1211
+ if (!Number.isInteger(index) || index < 0 || this.audioTracks.length > 0 && index >= this.audioTracks.length) {
1212
+ return;
1213
+ }
1214
+ this.audioTrackSelection = index;
1215
+ this.options.callbacks.onAudioTrackSelectionChange?.(index);
1216
+ if (this.supportMode === "hls.js" && this.hls) {
1217
+ this.hls.audioTrack = index;
1218
+ return;
1219
+ }
1220
+ if (this.supportMode === "native" && this.options.videoEl) {
1221
+ const audioTracks = this.options.videoEl.audioTracks;
1222
+ if (audioTracks && audioTracks.length > 0) {
1223
+ for (let i = 0; i < audioTracks.length; i += 1) {
1224
+ audioTracks[i].enabled = i === index;
1225
+ }
1226
+ }
1227
+ }
1228
+ }
1164
1229
  getSupportMode() {
1165
1230
  return this.supportMode;
1166
1231
  }
1232
+ syncHlsSubtitleTracks() {
1233
+ if (!this.hls) return;
1234
+ const tracks = (this.hls.subtitleTracks ?? []).map(
1235
+ (track, index) => ({
1236
+ index,
1237
+ id: String(track.id ?? index),
1238
+ label: track.name || track.lang || `Subtitles ${index + 1}`,
1239
+ language: track.lang
1240
+ })
1241
+ );
1242
+ this.textTracks = tracks;
1243
+ this.options.callbacks.onTextTracksChange?.(tracks);
1244
+ }
1245
+ syncHlsAudioTracks() {
1246
+ if (!this.hls) return;
1247
+ const tracks = (this.hls.audioTracks ?? []).map(
1248
+ (track, index) => ({
1249
+ index,
1250
+ id: String(track.id ?? index),
1251
+ label: track.name || track.lang || `Audio ${index + 1}`,
1252
+ language: track.lang
1253
+ })
1254
+ );
1255
+ this.audioTracks = tracks;
1256
+ this.options.callbacks.onAudioTracksChange?.(tracks);
1257
+ if (tracks.length > 0) {
1258
+ this.audioTrackSelection = 0;
1259
+ this.options.callbacks.onAudioTrackSelectionChange?.(0);
1260
+ }
1261
+ }
1262
+ bindNativeTextTracks() {
1263
+ const { videoEl } = this.options;
1264
+ const syncFromElement = () => {
1265
+ if (this.destroyed || this.textTracks.length > 0) return;
1266
+ const list = videoEl.textTracks;
1267
+ const tracks = [];
1268
+ for (let i = 0; i < list.length; i += 1) {
1269
+ const track = list[i];
1270
+ if (track.kind !== "subtitles" && track.kind !== "captions") continue;
1271
+ tracks.push({
1272
+ index: tracks.length,
1273
+ id: track.id || `native-${tracks.length}`,
1274
+ label: track.label || track.language || `Track ${tracks.length + 1}`,
1275
+ language: track.language || void 0
1276
+ });
1277
+ }
1278
+ if (tracks.length === 0) return;
1279
+ this.textTracks = tracks;
1280
+ this.options.callbacks.onTextTracksChange?.(tracks);
1281
+ };
1282
+ const textTracks = videoEl.textTracks;
1283
+ textTracks.addEventListener?.("addtrack", syncFromElement);
1284
+ syncFromElement();
1285
+ this.nativeTextTrackCleanup = () => {
1286
+ textTracks.removeEventListener?.("addtrack", syncFromElement);
1287
+ };
1288
+ }
1167
1289
  destroy() {
1168
1290
  if (this.destroyed) return;
1169
1291
  this.destroyed = true;
1292
+ this.nativeTextTrackCleanup?.();
1293
+ this.nativeTextTrackCleanup = null;
1170
1294
  this.hls?.destroy();
1171
1295
  this.hls = null;
1172
1296
  const { videoEl } = this.options;
@@ -1223,11 +1347,40 @@ function createVideoPlayer(options) {
1223
1347
  let refreshManager = null;
1224
1348
  let fullscreenEl = null;
1225
1349
  let isLiveStream = false;
1350
+ let mediaCleanup = null;
1226
1351
  const setState = (next) => {
1227
1352
  if (destroyed && next !== "destroyed") return;
1228
1353
  state = next;
1229
1354
  events.emit("statechange", { state: next });
1230
1355
  };
1356
+ const bindMediaProgressEvents = (el) => {
1357
+ mediaCleanup?.();
1358
+ const onTimeUpdate = () => {
1359
+ if (destroyed) return;
1360
+ events.emit("timeupdate", {
1361
+ currentTime: el.currentTime,
1362
+ duration: Number.isFinite(el.duration) ? el.duration : 0
1363
+ });
1364
+ };
1365
+ const onProgress = () => {
1366
+ if (destroyed) return;
1367
+ let bufferedEnd = 0;
1368
+ try {
1369
+ if (el.buffered.length > 0) {
1370
+ bufferedEnd = el.buffered.end(el.buffered.length - 1);
1371
+ }
1372
+ } catch {
1373
+ bufferedEnd = 0;
1374
+ }
1375
+ events.emit("progress", { bufferedEnd });
1376
+ };
1377
+ el.addEventListener("timeupdate", onTimeUpdate);
1378
+ el.addEventListener("progress", onProgress);
1379
+ mediaCleanup = () => {
1380
+ el.removeEventListener("timeupdate", onTimeUpdate);
1381
+ el.removeEventListener("progress", onProgress);
1382
+ };
1383
+ };
1231
1384
  const authManager = new AuthManager(
1232
1385
  options.auth,
1233
1386
  options.auth?.refreshBeforeExpiryMs ?? 6e4,
@@ -1278,6 +1431,7 @@ function createVideoPlayer(options) {
1278
1431
  isLiveStream = Boolean(source.live);
1279
1432
  }
1280
1433
  const el = ensureVideoElement();
1434
+ bindMediaProgressEvents(el);
1281
1435
  engine?.destroy();
1282
1436
  engine = new HlsEngine({
1283
1437
  videoEl: el,
@@ -1336,6 +1490,30 @@ function createVideoPlayer(options) {
1336
1490
  label: typeof selection === "number" ? level?.label : "auto"
1337
1491
  });
1338
1492
  },
1493
+ onTextTracksChange: (tracks) => {
1494
+ events.emit("texttrackchange", {
1495
+ tracks,
1496
+ selection: engine?.getTextTrackSelection() ?? "off"
1497
+ });
1498
+ },
1499
+ onTextTrackSelectionChange: (selection) => {
1500
+ events.emit("texttrackchange", {
1501
+ tracks: engine?.getTextTracks() ?? [],
1502
+ selection
1503
+ });
1504
+ },
1505
+ onAudioTracksChange: (tracks) => {
1506
+ events.emit("audiotrackchange", {
1507
+ tracks,
1508
+ selection: engine?.getAudioTrackSelection() ?? 0
1509
+ });
1510
+ },
1511
+ onAudioTrackSelectionChange: (selection) => {
1512
+ events.emit("audiotrackchange", {
1513
+ tracks: engine?.getAudioTracks() ?? [],
1514
+ selection
1515
+ });
1516
+ },
1339
1517
  onEnded: () => events.emit("ended")
1340
1518
  }
1341
1519
  });
@@ -1494,6 +1672,24 @@ function createVideoPlayer(options) {
1494
1672
  getAvailableQualities() {
1495
1673
  return engine?.getQualities() ?? [];
1496
1674
  },
1675
+ getTextTracks() {
1676
+ return engine?.getTextTracks() ?? [];
1677
+ },
1678
+ getSelectedTextTrack() {
1679
+ return engine?.getTextTrackSelection() ?? "off";
1680
+ },
1681
+ setTextTrack(selection) {
1682
+ engine?.setTextTrack(selection);
1683
+ },
1684
+ getAudioTracks() {
1685
+ return engine?.getAudioTracks() ?? [];
1686
+ },
1687
+ getSelectedAudioTrack() {
1688
+ return engine?.getAudioTrackSelection() ?? 0;
1689
+ },
1690
+ setAudioTrack(index) {
1691
+ engine?.setAudioTrack(index);
1692
+ },
1497
1693
  async enterFullscreen() {
1498
1694
  const target = fullscreenEl ?? videoEl?.parentElement ?? videoEl;
1499
1695
  if (target && "requestFullscreen" in target) {
@@ -1510,6 +1706,9 @@ function createVideoPlayer(options) {
1510
1706
  isFullscreen() {
1511
1707
  return Boolean(document.fullscreenElement);
1512
1708
  },
1709
+ setFullscreenElement(el) {
1710
+ fullscreenEl = el;
1711
+ },
1513
1712
  async reload() {
1514
1713
  refreshManager?.stop();
1515
1714
  engine?.destroy();
@@ -1518,6 +1717,8 @@ function createVideoPlayer(options) {
1518
1717
  destroy() {
1519
1718
  if (destroyed) return;
1520
1719
  destroyed = true;
1720
+ mediaCleanup?.();
1721
+ mediaCleanup = null;
1521
1722
  refreshManager?.stop();
1522
1723
  refreshManager = null;
1523
1724
  engine?.destroy();