@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/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add `@codenkay/video-nsgplayer-ui` control chrome package; emit timeupdate/progress and text/audio track Runtime APIs from core for host UI parity.
8
+
9
+ ## 1.1.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Emit `timeupdate` and `progress` from the mounted media element for host UI seek bars.
14
+ - Add text/audio track Runtime API: `getTextTracks`, `getSelectedTextTrack`, `setTextTrack`, `getAudioTracks`, `getSelectedAudioTrack`, `setAudioTrack`, plus `texttrackchange` / `audiotrackchange` events.
15
+ - Add `setFullscreenElement` for host chrome fullscreen targeting. Zoom remains UI-only (CSS transform).
16
+
3
17
  ## 1.0.0
4
18
 
5
19
  ### Major Changes
package/dist/index.cjs CHANGED
@@ -1037,6 +1037,11 @@ var HlsEngine = class {
1037
1037
  this.mediaRecoveryCount = 0;
1038
1038
  this.qualities = [];
1039
1039
  this.qualitySelection = "auto";
1040
+ this.textTracks = [];
1041
+ this.textTrackSelection = "off";
1042
+ this.audioTracks = [];
1043
+ this.audioTrackSelection = 0;
1044
+ this.nativeTextTrackCleanup = null;
1040
1045
  }
1041
1046
  async start() {
1042
1047
  const { videoEl, playbackUrl, startMuted, volume } = this.options;
@@ -1056,6 +1061,7 @@ var HlsEngine = class {
1056
1061
  this.options.callbacks.onSupportMode?.("native");
1057
1062
  videoEl.src = this.options.authParams.applyToUrl(playbackUrl);
1058
1063
  this.bindMediaEvents();
1064
+ this.bindNativeTextTracks();
1059
1065
  this.options.callbacks.onStateChange?.("ready");
1060
1066
  await this.applyStartPositionAndAutoplay();
1061
1067
  return;
@@ -1099,9 +1105,19 @@ var HlsEngine = class {
1099
1105
  label: level.height != null && level.height > 0 ? `${level.height}p` : `Level ${index}`
1100
1106
  }));
1101
1107
  callbacks.onQualitiesChange?.(this.qualities);
1108
+ this.syncHlsSubtitleTracks();
1109
+ this.syncHlsAudioTracks();
1102
1110
  callbacks.onStateChange?.("ready");
1103
1111
  void this.applyStartPositionAndAutoplay();
1104
1112
  });
1113
+ this.hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, () => {
1114
+ if (this.destroyed) return;
1115
+ this.syncHlsSubtitleTracks();
1116
+ });
1117
+ this.hls.on(Hls.Events.AUDIO_TRACKS_UPDATED, () => {
1118
+ if (this.destroyed) return;
1119
+ this.syncHlsAudioTracks();
1120
+ });
1105
1121
  this.hls.on(Hls.Events.ERROR, (_event, data) => {
1106
1122
  this.handleHlsError(Hls, data);
1107
1123
  });
@@ -1227,12 +1243,120 @@ var HlsEngine = class {
1227
1243
  getQualities() {
1228
1244
  return this.qualities;
1229
1245
  }
1246
+ getTextTracks() {
1247
+ return this.textTracks;
1248
+ }
1249
+ getTextTrackSelection() {
1250
+ return this.textTrackSelection;
1251
+ }
1252
+ setTextTrack(selection) {
1253
+ if (this.destroyed) return;
1254
+ this.textTrackSelection = selection;
1255
+ this.options.callbacks.onTextTrackSelectionChange?.(selection);
1256
+ if (this.supportMode === "hls.js" && this.hls) {
1257
+ this.hls.subtitleDisplay = selection !== "off";
1258
+ this.hls.subtitleTrack = selection === "off" ? -1 : selection;
1259
+ return;
1260
+ }
1261
+ if (this.supportMode === "native" && this.options.videoEl) {
1262
+ const tracks = this.options.videoEl.textTracks;
1263
+ for (let i = 0; i < tracks.length; i += 1) {
1264
+ const track = tracks[i];
1265
+ track.mode = selection !== "off" && i === selection ? "showing" : "disabled";
1266
+ }
1267
+ }
1268
+ }
1269
+ getAudioTracks() {
1270
+ return this.audioTracks;
1271
+ }
1272
+ getAudioTrackSelection() {
1273
+ return this.audioTrackSelection;
1274
+ }
1275
+ setAudioTrack(index) {
1276
+ if (this.destroyed) return;
1277
+ if (!Number.isInteger(index) || index < 0 || this.audioTracks.length > 0 && index >= this.audioTracks.length) {
1278
+ return;
1279
+ }
1280
+ this.audioTrackSelection = index;
1281
+ this.options.callbacks.onAudioTrackSelectionChange?.(index);
1282
+ if (this.supportMode === "hls.js" && this.hls) {
1283
+ this.hls.audioTrack = index;
1284
+ return;
1285
+ }
1286
+ if (this.supportMode === "native" && this.options.videoEl) {
1287
+ const audioTracks = this.options.videoEl.audioTracks;
1288
+ if (audioTracks && audioTracks.length > 0) {
1289
+ for (let i = 0; i < audioTracks.length; i += 1) {
1290
+ audioTracks[i].enabled = i === index;
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1230
1295
  getSupportMode() {
1231
1296
  return this.supportMode;
1232
1297
  }
1298
+ syncHlsSubtitleTracks() {
1299
+ if (!this.hls) return;
1300
+ const tracks = (this.hls.subtitleTracks ?? []).map(
1301
+ (track, index) => ({
1302
+ index,
1303
+ id: String(track.id ?? index),
1304
+ label: track.name || track.lang || `Subtitles ${index + 1}`,
1305
+ language: track.lang
1306
+ })
1307
+ );
1308
+ this.textTracks = tracks;
1309
+ this.options.callbacks.onTextTracksChange?.(tracks);
1310
+ }
1311
+ syncHlsAudioTracks() {
1312
+ if (!this.hls) return;
1313
+ const tracks = (this.hls.audioTracks ?? []).map(
1314
+ (track, index) => ({
1315
+ index,
1316
+ id: String(track.id ?? index),
1317
+ label: track.name || track.lang || `Audio ${index + 1}`,
1318
+ language: track.lang
1319
+ })
1320
+ );
1321
+ this.audioTracks = tracks;
1322
+ this.options.callbacks.onAudioTracksChange?.(tracks);
1323
+ if (tracks.length > 0) {
1324
+ this.audioTrackSelection = 0;
1325
+ this.options.callbacks.onAudioTrackSelectionChange?.(0);
1326
+ }
1327
+ }
1328
+ bindNativeTextTracks() {
1329
+ const { videoEl } = this.options;
1330
+ const syncFromElement = () => {
1331
+ if (this.destroyed || this.textTracks.length > 0) return;
1332
+ const list = videoEl.textTracks;
1333
+ const tracks = [];
1334
+ for (let i = 0; i < list.length; i += 1) {
1335
+ const track = list[i];
1336
+ if (track.kind !== "subtitles" && track.kind !== "captions") continue;
1337
+ tracks.push({
1338
+ index: tracks.length,
1339
+ id: track.id || `native-${tracks.length}`,
1340
+ label: track.label || track.language || `Track ${tracks.length + 1}`,
1341
+ language: track.language || void 0
1342
+ });
1343
+ }
1344
+ if (tracks.length === 0) return;
1345
+ this.textTracks = tracks;
1346
+ this.options.callbacks.onTextTracksChange?.(tracks);
1347
+ };
1348
+ const textTracks = videoEl.textTracks;
1349
+ textTracks.addEventListener?.("addtrack", syncFromElement);
1350
+ syncFromElement();
1351
+ this.nativeTextTrackCleanup = () => {
1352
+ textTracks.removeEventListener?.("addtrack", syncFromElement);
1353
+ };
1354
+ }
1233
1355
  destroy() {
1234
1356
  if (this.destroyed) return;
1235
1357
  this.destroyed = true;
1358
+ this.nativeTextTrackCleanup?.();
1359
+ this.nativeTextTrackCleanup = null;
1236
1360
  this.hls?.destroy();
1237
1361
  this.hls = null;
1238
1362
  const { videoEl } = this.options;
@@ -1289,11 +1413,40 @@ function createVideoPlayer(options) {
1289
1413
  let refreshManager = null;
1290
1414
  let fullscreenEl = null;
1291
1415
  let isLiveStream = false;
1416
+ let mediaCleanup = null;
1292
1417
  const setState = (next) => {
1293
1418
  if (destroyed && next !== "destroyed") return;
1294
1419
  state = next;
1295
1420
  events.emit("statechange", { state: next });
1296
1421
  };
1422
+ const bindMediaProgressEvents = (el) => {
1423
+ mediaCleanup?.();
1424
+ const onTimeUpdate = () => {
1425
+ if (destroyed) return;
1426
+ events.emit("timeupdate", {
1427
+ currentTime: el.currentTime,
1428
+ duration: Number.isFinite(el.duration) ? el.duration : 0
1429
+ });
1430
+ };
1431
+ const onProgress = () => {
1432
+ if (destroyed) return;
1433
+ let bufferedEnd = 0;
1434
+ try {
1435
+ if (el.buffered.length > 0) {
1436
+ bufferedEnd = el.buffered.end(el.buffered.length - 1);
1437
+ }
1438
+ } catch {
1439
+ bufferedEnd = 0;
1440
+ }
1441
+ events.emit("progress", { bufferedEnd });
1442
+ };
1443
+ el.addEventListener("timeupdate", onTimeUpdate);
1444
+ el.addEventListener("progress", onProgress);
1445
+ mediaCleanup = () => {
1446
+ el.removeEventListener("timeupdate", onTimeUpdate);
1447
+ el.removeEventListener("progress", onProgress);
1448
+ };
1449
+ };
1297
1450
  const authManager = new AuthManager(
1298
1451
  options.auth,
1299
1452
  options.auth?.refreshBeforeExpiryMs ?? 6e4,
@@ -1344,6 +1497,7 @@ function createVideoPlayer(options) {
1344
1497
  isLiveStream = Boolean(source.live);
1345
1498
  }
1346
1499
  const el = ensureVideoElement();
1500
+ bindMediaProgressEvents(el);
1347
1501
  engine?.destroy();
1348
1502
  engine = new HlsEngine({
1349
1503
  videoEl: el,
@@ -1402,6 +1556,30 @@ function createVideoPlayer(options) {
1402
1556
  label: typeof selection === "number" ? level?.label : "auto"
1403
1557
  });
1404
1558
  },
1559
+ onTextTracksChange: (tracks) => {
1560
+ events.emit("texttrackchange", {
1561
+ tracks,
1562
+ selection: engine?.getTextTrackSelection() ?? "off"
1563
+ });
1564
+ },
1565
+ onTextTrackSelectionChange: (selection) => {
1566
+ events.emit("texttrackchange", {
1567
+ tracks: engine?.getTextTracks() ?? [],
1568
+ selection
1569
+ });
1570
+ },
1571
+ onAudioTracksChange: (tracks) => {
1572
+ events.emit("audiotrackchange", {
1573
+ tracks,
1574
+ selection: engine?.getAudioTrackSelection() ?? 0
1575
+ });
1576
+ },
1577
+ onAudioTrackSelectionChange: (selection) => {
1578
+ events.emit("audiotrackchange", {
1579
+ tracks: engine?.getAudioTracks() ?? [],
1580
+ selection
1581
+ });
1582
+ },
1405
1583
  onEnded: () => events.emit("ended")
1406
1584
  }
1407
1585
  });
@@ -1560,6 +1738,24 @@ function createVideoPlayer(options) {
1560
1738
  getAvailableQualities() {
1561
1739
  return engine?.getQualities() ?? [];
1562
1740
  },
1741
+ getTextTracks() {
1742
+ return engine?.getTextTracks() ?? [];
1743
+ },
1744
+ getSelectedTextTrack() {
1745
+ return engine?.getTextTrackSelection() ?? "off";
1746
+ },
1747
+ setTextTrack(selection) {
1748
+ engine?.setTextTrack(selection);
1749
+ },
1750
+ getAudioTracks() {
1751
+ return engine?.getAudioTracks() ?? [];
1752
+ },
1753
+ getSelectedAudioTrack() {
1754
+ return engine?.getAudioTrackSelection() ?? 0;
1755
+ },
1756
+ setAudioTrack(index) {
1757
+ engine?.setAudioTrack(index);
1758
+ },
1563
1759
  async enterFullscreen() {
1564
1760
  const target = fullscreenEl ?? videoEl?.parentElement ?? videoEl;
1565
1761
  if (target && "requestFullscreen" in target) {
@@ -1576,6 +1772,9 @@ function createVideoPlayer(options) {
1576
1772
  isFullscreen() {
1577
1773
  return Boolean(document.fullscreenElement);
1578
1774
  },
1775
+ setFullscreenElement(el) {
1776
+ fullscreenEl = el;
1777
+ },
1579
1778
  async reload() {
1580
1779
  refreshManager?.stop();
1581
1780
  engine?.destroy();
@@ -1584,6 +1783,8 @@ function createVideoPlayer(options) {
1584
1783
  destroy() {
1585
1784
  if (destroyed) return;
1586
1785
  destroyed = true;
1786
+ mediaCleanup?.();
1787
+ mediaCleanup = null;
1587
1788
  refreshManager?.stop();
1588
1789
  refreshManager = null;
1589
1790
  engine?.destroy();