@vplayer/core 0.2.3 → 0.4.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
@@ -18,8 +18,10 @@ export { canUseMSE, canUseNativeHLS, canUseFullscreen, canUsePictureInPicture, c
18
18
  export type { MediaCapabilitiesSnapshot } from './media-capabilities';
19
19
  export { formatTime } from './utils';
20
20
  export { defaultPlayerLabels } from './defaults';
21
+ export { createMediaSessionController } from './media-session';
22
+ export type { MediaSessionController } from './media-session';
21
23
  export { BaseMediaEngine, NativeVideoEngine, HlsMediaEngine, DashMediaEngine } from './media-engine';
22
24
  export type { MediaEngine, MediaEngineDimensions, MediaEngineError, MediaEngineEvent, MediaEngineEventHandler, HlsMediaEngineOptions, DashMediaEngineOptions, } from './media-engine';
23
25
  export { createMediaStore, getInitialMediaState, selectMedia, selectAudio, selectPreferences, selectUI, selectPlugins, selectThumbnails, selectError, } from './state/slices';
24
- export type { PlayerOptions, TransformThumbnailVTT, MediaState, ResumeState, MediaRemote, PlayerLabels, PlayerInstance, PlayerSystems, PlayerError, PlaybackStatus, } from './types';
26
+ export type { PlayerOptions, MediaSessionMetadataOptions, TransformThumbnailVTT, MediaState, ResumeState, MediaRemote, PlayerLabels, PlayerInstance, PlayerSystems, PlayerError, PlaybackStatus, } from './types';
25
27
  export type { ControlRegistration, SettingRegistration, LayerRegistration, ContextMenuItem, ContextMenuRegistration, PlayerPlugin, PluginAPI, RemoteRef, PlayerContextRef, FlipState, AspectRatioState, SettingItem, } from './plugin-api';
package/dist/index.js CHANGED
@@ -188,12 +188,98 @@ function f(e) {
188
188
  return Number.isFinite(e) && e > 0;
189
189
  }
190
190
  //#endregion
191
+ //#region src/media-session.ts
192
+ var p = 10, m = 1, h = [
193
+ "play",
194
+ "pause",
195
+ "seekbackward",
196
+ "seekforward",
197
+ "seekto"
198
+ ];
199
+ function g() {
200
+ return typeof navigator > "u" || !("mediaSession" in navigator) ? null : navigator.mediaSession;
201
+ }
202
+ function _(e, t, n) {
203
+ if (typeof e.setActionHandler == "function") try {
204
+ e.setActionHandler(t, n);
205
+ } catch {}
206
+ }
207
+ function v() {
208
+ let e = null, t, n, r, i, a = !1, o = NaN, s = NaN, c = NaN;
209
+ function l() {
210
+ let a = g();
211
+ a && e && a.metadata === e && (a.metadata = null), e = null, t = void 0, n = void 0, r = void 0, i = void 0;
212
+ }
213
+ function u(e) {
214
+ let t = g();
215
+ !t || a || (_(t, "play", () => e.play()), _(t, "pause", () => e.pause()), _(t, "seekbackward", (t) => {
216
+ e.skip(-(t.seekOffset ?? p));
217
+ }), _(t, "seekforward", (t) => {
218
+ e.skip(t.seekOffset ?? p);
219
+ }), _(t, "seekto", (t) => {
220
+ typeof t.seekTime == "number" && e.seek(t.seekTime);
221
+ }), a = !0);
222
+ }
223
+ function d(a, o, s) {
224
+ let c = g();
225
+ if (!c || typeof MediaMetadata > "u") return;
226
+ let u = a?.trim() || void 0, d = o?.artwork ?? s, f = o?.artist?.trim() || void 0, p = o?.album?.trim() || void 0;
227
+ if (!u) {
228
+ l();
229
+ return;
230
+ }
231
+ if (e && c.metadata === e && t === u && n === d && r === f && i === p) return;
232
+ let m = new MediaMetadata({
233
+ title: u,
234
+ ...f ? { artist: f } : {},
235
+ ...p ? { album: p } : {},
236
+ artwork: d ? [{ src: d }] : []
237
+ });
238
+ c.metadata = m, e = m, t = u, n = d, r = f, i = p;
239
+ }
240
+ function v(e) {
241
+ let t = g();
242
+ t && (t.playbackState = e);
243
+ }
244
+ function y(e, t, n, r = !1) {
245
+ let i = g();
246
+ if (!(!i || typeof i.setPositionState != "function") && !(!f(e) || !Number.isFinite(t) || t < 0) && !(!r && Number.isFinite(o) && Math.abs(t - o) < m && e === s && n === c)) try {
247
+ i.setPositionState({
248
+ duration: e,
249
+ playbackRate: n,
250
+ position: Math.min(t, e)
251
+ }), o = t, s = e, c = n;
252
+ } catch {}
253
+ }
254
+ function b() {
255
+ let e = g();
256
+ if (e) {
257
+ if (a) {
258
+ for (let t of h) _(e, t, null);
259
+ a = !1;
260
+ }
261
+ e.playbackState = "none";
262
+ }
263
+ l(), o = NaN, s = NaN, c = NaN;
264
+ }
265
+ return {
266
+ setup: u,
267
+ syncMetadata: d,
268
+ setPlaybackState: v,
269
+ syncPosition: y,
270
+ get hasMetadata() {
271
+ return e !== null;
272
+ },
273
+ teardown: b
274
+ };
275
+ }
276
+ //#endregion
191
277
  //#region src/storage.ts
192
- var ee = "vplayer", p = class {
278
+ var y = "vplayer", b = class {
193
279
  fallback = /* @__PURE__ */ new Map();
194
280
  prefix;
195
281
  constructor(e = {}) {
196
- this.prefix = e.prefix ?? ee;
282
+ this.prefix = e.prefix ?? y;
197
283
  }
198
284
  fullKey(e) {
199
285
  return `${this.prefix}:${e}`;
@@ -234,7 +320,7 @@ var ee = "vplayer", p = class {
234
320
  } catch {}
235
321
  this.fallback.clear();
236
322
  }
237
- }, m = {
323
+ }, x = {
238
324
  VOLUME: "volume",
239
325
  MUTED: "muted",
240
326
  PLAYBACK_RATE: "playbackRate",
@@ -244,10 +330,10 @@ var ee = "vplayer", p = class {
244
330
  LOOP: "loop",
245
331
  FLIP: "flip",
246
332
  ASPECT_RATIO: "aspectRatio"
247
- }, te = class {
333
+ }, ee = class {
248
334
  storage;
249
335
  maxEntries;
250
- constructor(e = new p(), t = 100) {
336
+ constructor(e = new b(), t = 100) {
251
337
  this.storage = e, this.maxEntries = t;
252
338
  }
253
339
  async load(e) {
@@ -267,7 +353,7 @@ var ee = "vplayer", p = class {
267
353
  let t = this.storage.get("progress") ?? {};
268
354
  delete t[e], Object.keys(t).length === 0 ? this.storage.remove("progress") : this.storage.set("progress", t);
269
355
  }
270
- }, h = class {
356
+ }, S = class {
271
357
  element;
272
358
  destroyed = !1;
273
359
  listeners = /* @__PURE__ */ new Map();
@@ -415,9 +501,9 @@ var ee = "vplayer", p = class {
415
501
  this.domListeners = [];
416
502
  }
417
503
  destroy() {
418
- this.destroyed || (this.destroyed = !0, this.detachDOMListeners(), this.listeners.clear());
504
+ this.destroyed || (this.destroyed = !0, this.element.pause(), this.detachDOMListeners(), this.listeners.clear());
419
505
  }
420
- }, g = class extends h {}, _ = class extends h {
506
+ }, C = class extends S {}, w = class extends S {
421
507
  hls = null;
422
508
  hlsSrc;
423
509
  hlsConfig;
@@ -455,7 +541,7 @@ var ee = "vplayer", p = class {
455
541
  destroy() {
456
542
  this.destroyed || (super.destroy(), this.hls &&= (this.hls.destroy(), null));
457
543
  }
458
- }, v = class extends h {
544
+ }, T = class extends S {
459
545
  dashPlayer = null;
460
546
  dashSrc;
461
547
  dashConfig;
@@ -483,37 +569,37 @@ var ee = "vplayer", p = class {
483
569
  };
484
570
  //#endregion
485
571
  //#region src/source-resolver.ts
486
- function y(e) {
572
+ function E(e) {
487
573
  try {
488
574
  return new URL(e, typeof window < "u" ? window.location.href : "http://localhost").pathname.toLowerCase();
489
575
  } catch {
490
576
  return e.toLowerCase().split("?")[0].split("#")[0];
491
577
  }
492
578
  }
493
- function b(e) {
494
- let t = e.type?.toLowerCase() ?? "", n = y(e.src);
579
+ function D(e) {
580
+ let t = e.type?.toLowerCase() ?? "", n = E(e.src);
495
581
  return t.includes("mpegurl") || t.includes("hls") || n.endsWith(".m3u8") ? "hls" : t.includes("dash") || n.endsWith(".mpd") ? "dash" : /\.(mp4|m4v|webm|ogv|ogg|mov)$/i.test(n) || t.startsWith("video/") ? "native" : "unknown";
496
582
  }
497
- function x(e) {
583
+ function O(e) {
498
584
  return {
499
585
  src: e.src,
500
586
  type: e.type
501
587
  };
502
588
  }
503
- function ne(e, t) {
589
+ function te(e, t) {
504
590
  if (t.engine) return typeof t.engine == "function" ? t.engine(e) : t.engine;
505
- let n = x(t), r = b(n);
506
- return r === "hls" && !s(e) ? new _(e, {
591
+ let n = O(t), r = D(n);
592
+ return r === "hls" && !s(e) ? new w(e, {
507
593
  src: n.src,
508
594
  hlsConfig: t.hlsConfig
509
- }) : r === "dash" ? new v(e, {
595
+ }) : r === "dash" ? new T(e, {
510
596
  src: n.src,
511
597
  dashConfig: t.dashConfig
512
- }) : (e.src = n.src, new g(e));
598
+ }) : (e.src = n.src, new C(e));
513
599
  }
514
600
  //#endregion
515
601
  //#region src/subtitle-parser.ts
516
- var S = {
602
+ var k = {
517
603
  fontSize: "medium",
518
604
  fontScale: 100,
519
605
  fontFamily: "sans",
@@ -526,16 +612,16 @@ var S = {
526
612
  position: 0,
527
613
  lineHeight: 1.35,
528
614
  delay: 0
529
- }, C = "(?:\\d{2}:)?\\d{2}:\\d{2}[.,]\\d{3}", w = RegExp(`^(${C})\\s*-->\\s*(${C})(?:\\s+(.*))?$`);
530
- function T(e) {
615
+ }, A = "(?:\\d{2}:)?\\d{2}:\\d{2}[.,]\\d{3}", j = RegExp(`^(${A})\\s*-->\\s*(${A})(?:\\s+(.*))?$`);
616
+ function M(e) {
531
617
  return e.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
532
618
  }
533
- function E(e) {
619
+ function N(e) {
534
620
  let t = e.replace(",", ".").split(":").map(Number);
535
621
  return t.some((e) => !Number.isFinite(e)) ? NaN : t.length === 3 ? t[0] * 3600 + t[1] * 60 + t[2] : t.length === 2 ? t[0] * 60 + t[1] : t[0];
536
622
  }
537
- function D(e, t) {
538
- let n = T(e).trim();
623
+ function P(e, t) {
624
+ let n = M(e).trim();
539
625
  if (t === "vtt") {
540
626
  let e = n.indexOf("\n");
541
627
  n = (e === -1 ? "" : n.slice(e + 1)).trim();
@@ -544,11 +630,11 @@ function D(e, t) {
544
630
  for (let e of n.split(/\n{2,}/)) {
545
631
  let t = e.split("\n").map((e) => e.trimEnd()), n = t[0]?.trim();
546
632
  if (!n || n.startsWith("NOTE") || n === "STYLE" || n === "REGION") continue;
547
- let i = t.findIndex((e) => w.test(e.trim()));
633
+ let i = t.findIndex((e) => j.test(e.trim()));
548
634
  if (i === -1) continue;
549
- let a = w.exec(t[i].trim());
635
+ let a = j.exec(t[i].trim());
550
636
  if (!a) continue;
551
- let o = E(a[1]), s = E(a[2]), c = t.slice(i + 1).filter((e) => e.trim().length > 0).join("\n");
637
+ let o = N(a[1]), s = N(a[2]), c = t.slice(i + 1).filter((e) => e.trim().length > 0).join("\n");
552
638
  if (!Number.isFinite(o) || !Number.isFinite(s) || s <= o || !c) continue;
553
639
  let l = i > 0 ? t[i - 1]?.trim() : void 0;
554
640
  r.push({
@@ -562,14 +648,14 @@ function D(e, t) {
562
648
  }
563
649
  return r;
564
650
  }
565
- function O(e) {
566
- return D(e, "srt");
651
+ function F(e) {
652
+ return P(e, "srt");
567
653
  }
568
- function k(e) {
569
- return D(e, "vtt");
654
+ function I(e) {
655
+ return P(e, "vtt");
570
656
  }
571
- function A(e, t = {}) {
572
- let n = T(e).trimStart(), r = t.fileName?.toLowerCase().match(/\\.(vtt|srt)$/)?.[1], i = t.mimeType === "text/vtt" ? "vtt" : t.mimeType === "application/x-subrip" ? "srt" : void 0, a = n.startsWith("WEBVTT") ? "vtt" : w.test(n.split("\n")[0] ?? "") ? "srt" : void 0, o = t.format ?? i ?? r ?? a;
657
+ function L(e, t = {}) {
658
+ let n = M(e).trimStart(), r = t.fileName?.toLowerCase().match(/\\.(vtt|srt)$/)?.[1], i = t.mimeType === "text/vtt" ? "vtt" : t.mimeType === "application/x-subrip" ? "srt" : void 0, a = n.startsWith("WEBVTT") ? "vtt" : j.test(n.split("\n")[0] ?? "") ? "srt" : void 0, o = t.format ?? i ?? r ?? a;
573
659
  if (!o) return {
574
660
  ok: !1,
575
661
  error: {
@@ -577,7 +663,7 @@ function A(e, t = {}) {
577
663
  message: "Expected a WebVTT or SRT subtitle file."
578
664
  }
579
665
  };
580
- let s = o === "vtt" ? k(n) : O(n);
666
+ let s = o === "vtt" ? I(n) : F(n);
581
667
  return s.length === 0 ? {
582
668
  ok: !1,
583
669
  error: {
@@ -590,9 +676,9 @@ function A(e, t = {}) {
590
676
  cues: s
591
677
  };
592
678
  }
593
- async function j(e, t) {
679
+ async function R(e, t) {
594
680
  if (e.content !== void 0) {
595
- let t = A(e.content, {
681
+ let t = L(e.content, {
596
682
  format: e.format,
597
683
  fileName: e.label
598
684
  });
@@ -602,7 +688,7 @@ async function j(e, t) {
602
688
  if (!e.src) throw Error("Subtitle track has no source.");
603
689
  let n = await fetch(e.src, { signal: t });
604
690
  if (!n.ok) throw Error(`Failed to fetch subtitles: ${n.status}`);
605
- let r = A(await n.text(), {
691
+ let r = L(await n.text(), {
606
692
  format: e.format,
607
693
  fileName: e.src,
608
694
  mimeType: n.headers.get("content-type")?.split(";")[0]
@@ -610,7 +696,7 @@ async function j(e, t) {
610
696
  if (!r.ok) throw Error(r.error.message);
611
697
  return r.cues;
612
698
  }
613
- function M(e, t) {
699
+ function z(e, t) {
614
700
  let n = 0, r = e.length - 1;
615
701
  for (; n <= r;) {
616
702
  let i = n + r >> 1, a = e[i];
@@ -620,7 +706,7 @@ function M(e, t) {
620
706
  }
621
707
  return null;
622
708
  }
623
- function N(e) {
709
+ function ne(e) {
624
710
  let t = e.match(/#xywh=(\d+),(\d+),(\d+),(\d+)/);
625
711
  return t ? {
626
712
  src: e.replace(/#xywh=.*$/, ""),
@@ -630,7 +716,7 @@ function N(e) {
630
716
  h: Number(t[4])
631
717
  } : null;
632
718
  }
633
- function P(e) {
719
+ function B(e) {
634
720
  let t = e.replace(/\r\n/g, "\n"), n = t.indexOf("\n\n");
635
721
  return n > 0 && (t = t.slice(n + 2)), t.trim().split(/\n\n+/).map((e) => {
636
722
  let t = e.split("\n"), n = t.findIndex((e) => e.includes("-->"));
@@ -639,7 +725,7 @@ function P(e) {
639
725
  if (!r) return null;
640
726
  let i = t.slice(n + 1).find((e) => e.trim().length > 0);
641
727
  if (!i) return null;
642
- let a = i.trim(), o = N(a), s = E(r[1]), c = E(r[2]);
728
+ let a = i.trim(), o = ne(a), s = N(r[1]), c = N(r[2]);
643
729
  return o ? {
644
730
  start: s,
645
731
  end: c,
@@ -659,20 +745,20 @@ function P(e) {
659
745
  };
660
746
  }).filter((e) => e !== null);
661
747
  }
662
- function F(e, t) {
748
+ function V(e, t) {
663
749
  try {
664
750
  return new URL(e, t).toString();
665
751
  } catch {
666
752
  return e;
667
753
  }
668
754
  }
669
- async function I(e, t, n) {
755
+ async function re(e, t, n) {
670
756
  let r = await fetch(e, { signal: t });
671
757
  if (!r.ok) throw Error(`Failed to fetch thumbnail VTT: ${r.status}`);
672
758
  let i = r.url || e, a = await r.text();
673
- return P(n ? await n(a, i) : a).map((e) => (e.src = F(e.src, i), e));
759
+ return B(n ? await n(a, i) : a).map((e) => (e.src = V(e.src, i), e));
674
760
  }
675
- function L(e, t) {
761
+ function ie(e, t) {
676
762
  if (e.length === 0) return null;
677
763
  let n = 0, r = e.length - 1;
678
764
  for (; n <= r;) {
@@ -685,10 +771,10 @@ function L(e, t) {
685
771
  }
686
772
  //#endregion
687
773
  //#region src/state/slices.ts
688
- function re() {
689
- return new e(R());
774
+ function ae() {
775
+ return new e(H());
690
776
  }
691
- function R() {
777
+ function H() {
692
778
  return {
693
779
  status: "idle",
694
780
  source: null,
@@ -715,7 +801,7 @@ function R() {
715
801
  subtitleSearchResults: [],
716
802
  subtitleSearchStatus: "idle",
717
803
  subtitleSearchError: null,
718
- captionSettings: { ...S },
804
+ captionSettings: { ...k },
719
805
  activeQuality: "Auto",
720
806
  qualities: [],
721
807
  controlsVisible: !0,
@@ -739,7 +825,7 @@ function R() {
739
825
  resumeState: { status: "idle" }
740
826
  };
741
827
  }
742
- var ie = (e) => ({
828
+ var U = (e) => ({
743
829
  status: e.status,
744
830
  source: e.source,
745
831
  isPlaying: e.isPlaying,
@@ -752,10 +838,10 @@ var ie = (e) => ({
752
838
  playbackRate: e.playbackRate,
753
839
  isLive: e.isLive,
754
840
  resumeState: e.resumeState
755
- }), z = (e) => ({
841
+ }), W = (e) => ({
756
842
  volume: e.volume,
757
843
  isMuted: e.isMuted
758
- }), B = (e) => ({
844
+ }), G = (e) => ({
759
845
  isLooping: e.isLooping,
760
846
  flip: e.flip,
761
847
  aspectRatio: e.aspectRatio,
@@ -771,27 +857,27 @@ var ie = (e) => ({
771
857
  captionSettings: e.captionSettings,
772
858
  activeQuality: e.activeQuality,
773
859
  qualities: e.qualities
774
- }), ae = (e) => ({
860
+ }), K = (e) => ({
775
861
  controlsVisible: e.controlsVisible,
776
862
  capabilities: e.capabilities,
777
863
  isFullscreen: e.isFullscreen,
778
864
  infoPanelVisible: e.infoPanelVisible,
779
865
  notification: e.notification
780
- }), V = (e) => ({
866
+ }), oe = (e) => ({
781
867
  controls: e.controls,
782
868
  settings: e.settings,
783
869
  layers: e.layers,
784
870
  contextMenuItems: e.contextMenuItems,
785
871
  contextMenuEnabled: e.contextMenuEnabled
786
- }), H = (e) => ({ thumbnailCues: e.thumbnailCues }), U = (e) => e.error, W = [
872
+ }), se = (e) => ({ thumbnailCues: e.thumbnailCues }), ce = (e) => e.error, q = [
787
873
  "default",
788
874
  "16:9",
789
875
  "4:3",
790
876
  "21:9",
791
877
  "cover",
792
878
  "fill"
793
- ], oe = 5e3, se = 2;
794
- function ce(e) {
879
+ ], le = 5e3, ue = 2;
880
+ function de(e) {
795
881
  try {
796
882
  let t = new URL(e, typeof document > "u" ? void 0 : document.baseURI);
797
883
  return t.search = "", t.hash = "", t.href;
@@ -799,11 +885,11 @@ function ce(e) {
799
885
  return e.split(/[?#]/, 1)[0] ?? e;
800
886
  }
801
887
  }
802
- var le = {
888
+ var fe = {
803
889
  "16:9": "16 / 9",
804
890
  "4:3": "4 / 3",
805
891
  "21:9": "21 / 9"
806
- }, G = {
892
+ }, pe = {
807
893
  default: "default",
808
894
  "16:9": "16-9",
809
895
  "4:3": "4-3",
@@ -811,81 +897,81 @@ var le = {
811
897
  cover: "cover",
812
898
  fill: "fill"
813
899
  };
814
- function ue(e) {
900
+ function me(e) {
815
901
  e.classList.remove("vplayer--media-default", "vplayer--media-16-9", "vplayer--media-4-3", "vplayer--media-21-9", "vplayer--media-cover", "vplayer--media-fill");
816
902
  }
817
- function de(e) {
818
- return typeof e == "string" && e in G ? e : "default";
903
+ function he(e) {
904
+ return typeof e == "string" && e in pe ? e : "default";
819
905
  }
820
- function fe(e) {
906
+ function ge(e) {
821
907
  return e === "cover" ? "cover" : e === "fill" ? "fill" : "contain";
822
908
  }
823
- function K(e) {
909
+ function J(e) {
824
910
  return {
825
911
  ...e,
826
912
  id: e.id ?? `${e.local ? "local" : "track"}:${e.src ?? e.label}:${e.lang}`
827
913
  };
828
914
  }
829
- function q(e) {
830
- let r = re(), i = { ...e }, o = new t(), s = new p(), c = new a(i.lang);
915
+ function Y(e) {
916
+ let r = ae(), i = { ...e }, o = new t(), s = new b(), c = new a(i.lang);
831
917
  i.translations && c.addTranslations(i.lang ?? "en", i.translations);
832
- let l = new n(), u = i.reconnectMax ?? 3, ee = i.reconnectSleep ?? 1500, h = null, g = null, _ = null, v = 0, y = NaN, b = null, C = 0, w = null, T = null, E = null, D = null, O = new te(), k = 0, A = !1, M = null, N = [], P = !1;
833
- function F(e) {
834
- let t = e.playbackProgress?.id ?? (e.playbackProgress?.normalizeUrl === !1 ? e.src : ce(e.src));
918
+ let l = new n(), u = i.reconnectMax ?? 3, p = i.reconnectSleep ?? 1500, m = null, h = null, g = null, _ = 0, y = NaN, S = null, C = 0, w = null, T = null, E = null, D = v(), A = new ee(), j = 0, M = !1, N = null, P = [], F = !1;
919
+ function I(e) {
920
+ let t = e.playbackProgress?.id ?? (e.playbackProgress?.normalizeUrl === !1 ? e.src : de(e.src));
835
921
  return {
836
- generation: k,
922
+ generation: j,
837
923
  id: t,
838
- store: e.playbackProgress?.store ?? O
924
+ store: e.playbackProgress?.store ?? A
839
925
  };
840
926
  }
841
- let L = F(i);
842
- function R() {
843
- if (P) return;
844
- let e = N.shift();
845
- e && (P = !0, (e.kind === "save" ? e.target.store.save(e.target.id, e.progress) : e.target.store.clear(e.target.id)).catch((t) => {
927
+ let L = I(i);
928
+ function z() {
929
+ if (F) return;
930
+ let e = P.shift();
931
+ e && (F = !0, (e.kind === "save" ? e.target.store.save(e.target.id, e.progress) : e.target.store.clear(e.target.id)).catch((t) => {
846
932
  console.warn(e.kind === "save" ? "[vplayer] playback progress save failed:" : "[vplayer] playback progress clear failed:", t);
847
933
  }).finally(() => {
848
- P = !1, R();
934
+ F = !1, z();
849
935
  }));
850
936
  }
851
- function ie(e, t) {
852
- let n = N.at(-1);
853
- n?.kind === "save" && n.target.generation === e.generation ? n.progress = t : N.push({
937
+ function ne(e, t) {
938
+ let n = P.at(-1);
939
+ n?.kind === "save" && n.target.generation === e.generation ? n.progress = t : P.push({
854
940
  kind: "save",
855
941
  target: e,
856
942
  progress: t
857
- }), R();
943
+ }), z();
858
944
  }
859
- function z(e) {
860
- N.push({
945
+ function B(e) {
946
+ P.push({
861
947
  kind: "clear",
862
948
  target: e
863
- }), R();
949
+ }), z();
864
950
  }
865
- function B(e = L, t = g) {
866
- !t || t.ended || r.state.resumeState.status === "prompt" || !Number.isFinite(t.currentTime) || t.currentTime <= 3 || !f(t.duration) || (v = Date.now(), y = t.currentTime, ie(e, {
951
+ function V(e = L, t = h) {
952
+ !t || t.ended || r.state.resumeState.status === "prompt" || !Number.isFinite(t.currentTime) || t.currentTime <= 3 || !f(t.duration) || (_ = Date.now(), y = t.currentTime, ne(e, {
867
953
  time: t.currentTime,
868
954
  duration: t.duration
869
955
  }));
870
956
  }
871
- function ae(e) {
872
- !r.state.isPlaying || Date.now() - v < oe || Number.isFinite(y) && Math.abs(e.currentTime - y) < se || B(L, e);
957
+ function ie(e) {
958
+ !r.state.isPlaying || Date.now() - _ < le || Number.isFinite(y) && Math.abs(e.currentTime - y) < ue || V(L, e);
873
959
  }
874
- function V() {
875
- k++, L = F(i), A = !1, v = 0, y = NaN, r.setState((e) => ({
960
+ function H() {
961
+ j++, L = I(i), M = !1, _ = 0, y = NaN, r.setState((e) => ({
876
962
  ...e,
877
963
  resumeState: { status: "idle" }
878
964
  }));
879
965
  }
880
- function H(e) {
881
- if (A || !f(e.duration)) return;
882
- A = !0;
966
+ function U(e) {
967
+ if (M || !f(e.duration)) return;
968
+ M = !0;
883
969
  let t = L, n = e.duration;
884
970
  r.setState((e) => ({
885
971
  ...e,
886
972
  resumeState: { status: "loading" }
887
973
  })), t.store.load(t.id).then((i) => {
888
- if (t.generation === k && e === g) {
974
+ if (t.generation === j && e === h) {
889
975
  if (!i || !Number.isFinite(i.time) || !Number.isFinite(i.duration) || i.time <= 3 || i.time >= n - 3 || Math.abs(i.duration - n) > Math.max(1, n * .01)) {
890
976
  r.setState((e) => ({
891
977
  ...e,
@@ -902,17 +988,17 @@ function q(e) {
902
988
  }));
903
989
  }
904
990
  }).catch((n) => {
905
- t.generation === k && e === g && r.setState((e) => ({
991
+ t.generation === j && e === h && r.setState((e) => ({
906
992
  ...e,
907
993
  resumeState: { status: "idle" }
908
994
  })), console.warn("[vplayer] playback progress load failed:", n);
909
995
  });
910
996
  }
911
- function U(e) {
997
+ function W(e) {
912
998
  r.setState((t) => {
913
999
  let n = new Map(t.subtitleTracks.map((e) => [e.id, e]));
914
1000
  for (let t of e) {
915
- let e = K(t);
1001
+ let e = J(t);
916
1002
  n.set(e.id, e);
917
1003
  }
918
1004
  return {
@@ -921,7 +1007,7 @@ function q(e) {
921
1007
  };
922
1008
  });
923
1009
  }
924
- function q(e) {
1010
+ function G(e) {
925
1011
  T?.abort();
926
1012
  let t = new AbortController();
927
1013
  T = t, r.setState((t) => ({
@@ -930,7 +1016,7 @@ function q(e) {
930
1016
  subtitleCues: [],
931
1017
  subtitleStatus: "loading",
932
1018
  subtitleError: null
933
- })), j(e, t.signal).then((e) => {
1019
+ })), R(e, t.signal).then((e) => {
934
1020
  t.signal.aborted || r.setState((t) => ({
935
1021
  ...t,
936
1022
  subtitleCues: e,
@@ -946,7 +1032,7 @@ function q(e) {
946
1032
  }));
947
1033
  });
948
1034
  }
949
- function J() {
1035
+ function K() {
950
1036
  let e = (i.subtitleProviders ?? []).map(({ id: e, label: t }) => ({
951
1037
  id: e,
952
1038
  label: t
@@ -959,7 +1045,7 @@ function q(e) {
959
1045
  subtitleSearchError: null
960
1046
  }));
961
1047
  }
962
- function pe(e = {}) {
1048
+ function oe(e = {}) {
963
1049
  E?.abort();
964
1050
  let t = i.subtitleProviders ?? [], n = e.providerId ? t.filter((t) => t.id === e.providerId) : t;
965
1051
  if (n.length === 0) {
@@ -999,7 +1085,7 @@ function q(e) {
999
1085
  }));
1000
1086
  });
1001
1087
  }
1002
- function me(e) {
1088
+ function se(e) {
1003
1089
  T?.abort();
1004
1090
  let t = (i.subtitleProviders ?? []).find((t) => t.id === e.providerId);
1005
1091
  if (!t) {
@@ -1019,15 +1105,15 @@ function q(e) {
1019
1105
  subtitleError: null
1020
1106
  })), (async () => {
1021
1107
  try {
1022
- let i = await t.fetch(e, n.signal), a = K({
1108
+ let i = await t.fetch(e, n.signal), a = J({
1023
1109
  id: `provider:${t.id}:${e.id}`,
1024
1110
  lang: e.language,
1025
1111
  label: e.label,
1026
1112
  format: i.format ?? e.format,
1027
1113
  ...i.content === void 0 ? { src: i.src } : { content: i.content }
1028
- }), o = await j(a, n.signal);
1114
+ }), o = await R(a, n.signal);
1029
1115
  if (n.signal.aborted) return;
1030
- U([a]), r.setState((e) => ({
1116
+ W([a]), r.setState((e) => ({
1031
1117
  ...e,
1032
1118
  activeSubtitle: a,
1033
1119
  subtitleCues: o,
@@ -1045,77 +1131,61 @@ function q(e) {
1045
1131
  }
1046
1132
  })();
1047
1133
  }
1048
- i.subtitles?.length && U(i.subtitles), J();
1049
- function he() {
1050
- D && typeof navigator < "u" && "mediaSession" in navigator && navigator.mediaSession.metadata === D && (navigator.mediaSession.metadata = null), D = null;
1051
- }
1052
- function ge() {
1053
- if (typeof navigator > "u" || !("mediaSession" in navigator) || typeof MediaMetadata > "u") return;
1054
- let e = i.title?.trim();
1055
- if (!e) {
1056
- he();
1057
- return;
1058
- }
1059
- let t = new MediaMetadata({
1060
- title: e,
1061
- artwork: i.poster ? [{ src: i.poster }] : []
1062
- });
1063
- navigator.mediaSession.metadata = t, D = t;
1064
- }
1065
- function _e() {
1134
+ i.subtitles?.length && W(i.subtitles), K();
1135
+ function ce() {
1066
1136
  l.register({
1067
1137
  key: "Space",
1068
1138
  description: "Toggle play/pause",
1069
1139
  handler: (e) => {
1070
- e.preventDefault(), Y.togglePlay();
1140
+ e.preventDefault(), X.togglePlay();
1071
1141
  }
1072
1142
  }), l.register({
1073
1143
  key: "KeyK",
1074
1144
  description: "Toggle play/pause",
1075
1145
  handler: (e) => {
1076
- e.preventDefault(), Y.togglePlay();
1146
+ e.preventDefault(), X.togglePlay();
1077
1147
  }
1078
1148
  }), l.register({
1079
1149
  key: "KeyL",
1080
1150
  description: "Toggle loop",
1081
1151
  handler: (e) => {
1082
- e.preventDefault(), Y.toggleLoop();
1152
+ e.preventDefault(), X.toggleLoop();
1083
1153
  }
1084
1154
  }), l.register({
1085
1155
  key: "KeyI",
1086
1156
  description: "Toggle info panel",
1087
1157
  handler: (e) => {
1088
- e.preventDefault(), Y.toggleInfoPanel();
1158
+ e.preventDefault(), X.toggleInfoPanel();
1089
1159
  }
1090
1160
  }), l.register({
1091
1161
  key: "KeyF",
1092
1162
  description: "Toggle fullscreen",
1093
1163
  handler: (e) => {
1094
- e.preventDefault(), Y.toggleFullscreen();
1164
+ e.preventDefault(), X.toggleFullscreen();
1095
1165
  }
1096
1166
  }), l.register({
1097
1167
  key: "KeyA",
1098
1168
  description: "Cycle aspect ratio",
1099
1169
  handler: (e) => {
1100
- e.preventDefault(), Y.cycleAspectRatio();
1170
+ e.preventDefault(), X.cycleAspectRatio();
1101
1171
  }
1102
1172
  }), l.register({
1103
1173
  key: "KeyM",
1104
1174
  description: "Toggle mute",
1105
1175
  handler: (e) => {
1106
- e.preventDefault(), Y.toggleMute();
1176
+ e.preventDefault(), X.toggleMute();
1107
1177
  }
1108
1178
  }), l.register({
1109
1179
  key: "ArrowLeft",
1110
1180
  description: "Seek backward 5s",
1111
1181
  handler: (e) => {
1112
- e.preventDefault(), Y.skip(-5);
1182
+ e.preventDefault(), X.skip(-5);
1113
1183
  }
1114
1184
  }), l.register({
1115
1185
  key: "ArrowRight",
1116
1186
  description: "Seek forward 5s",
1117
1187
  handler: (e) => {
1118
- e.preventDefault(), Y.skip(5);
1188
+ e.preventDefault(), X.skip(5);
1119
1189
  }
1120
1190
  }), l.register({
1121
1191
  key: "ArrowUp",
@@ -1123,7 +1193,7 @@ function q(e) {
1123
1193
  handler: (e) => {
1124
1194
  e.preventDefault();
1125
1195
  let t = Math.min(1, r.state.volume + .1);
1126
- Y.setVolume(t);
1196
+ X.setVolume(t);
1127
1197
  }
1128
1198
  }), l.register({
1129
1199
  key: "ArrowDown",
@@ -1131,27 +1201,27 @@ function q(e) {
1131
1201
  handler: (e) => {
1132
1202
  e.preventDefault();
1133
1203
  let t = Math.max(0, r.state.volume - .1);
1134
- Y.setVolume(t);
1204
+ X.setVolume(t);
1135
1205
  }
1136
1206
  });
1137
1207
  }
1138
- i.defaultHotkeys !== !1 && _e();
1139
- function ve() {
1208
+ i.defaultHotkeys !== !1 && ce();
1209
+ function Y() {
1140
1210
  if (typeof document > "u") return;
1141
1211
  let e = document;
1142
1212
  r.setState((t) => ({
1143
1213
  ...t,
1144
1214
  isFullscreen: !!(document.fullscreenElement || e.webkitFullscreenElement),
1145
- capabilities: d(g?.element ?? void 0, h)
1215
+ capabilities: d(h?.element ?? void 0, m)
1146
1216
  }));
1147
1217
  }
1148
- let Y = {
1218
+ let X = {
1149
1219
  play: () => {
1150
- g?.play();
1220
+ h?.play();
1151
1221
  },
1152
- pause: () => g?.pause(),
1222
+ pause: () => h?.pause(),
1153
1223
  togglePlay: () => {
1154
- let e = g;
1224
+ let e = h;
1155
1225
  e && (e.paused || e.ended ? e.play() : e.pause());
1156
1226
  },
1157
1227
  resumeFromSavedProgress: () => {
@@ -1159,14 +1229,14 @@ function q(e) {
1159
1229
  e.status === "prompt" && (r.setState((e) => ({
1160
1230
  ...e,
1161
1231
  resumeState: { status: "idle" }
1162
- })), g?.seek(e.progress.time), g?.play());
1232
+ })), h?.seek(e.progress.time), h?.play());
1163
1233
  },
1164
1234
  startPlaybackOver: () => {
1165
1235
  let e = L;
1166
1236
  r.setState((e) => ({
1167
1237
  ...e,
1168
1238
  resumeState: { status: "idle" }
1169
- })), g?.seek(0), z(e);
1239
+ })), h?.seek(0), B(e);
1170
1240
  },
1171
1241
  dismissSavedProgress: () => {
1172
1242
  r.setState((e) => ({
@@ -1174,15 +1244,15 @@ function q(e) {
1174
1244
  resumeState: { status: "idle" }
1175
1245
  }));
1176
1246
  },
1177
- seek: (e) => g?.seek(e),
1247
+ seek: (e) => h?.seek(e),
1178
1248
  skip: (e) => {
1179
- let t = g;
1249
+ let t = h;
1180
1250
  if (!t) return;
1181
1251
  let n = f(t.duration) ? t.duration : Infinity, r = Math.max(0, Math.min(t.currentTime + e, n));
1182
1252
  t.seek(r);
1183
1253
  },
1184
1254
  setVolume: (e) => {
1185
- let t = g;
1255
+ let t = h;
1186
1256
  if (!t) return;
1187
1257
  let n = Math.max(0, Math.min(1, e));
1188
1258
  t.setVolume(n), n > 0 && t.setMuted(!1), r.setState((e) => ({
@@ -1192,28 +1262,28 @@ function q(e) {
1192
1262
  }));
1193
1263
  },
1194
1264
  toggleMute: () => {
1195
- let e = g;
1265
+ let e = h;
1196
1266
  e && (e.setMuted(!e.muted), r.setState((t) => ({
1197
1267
  ...t,
1198
1268
  isMuted: e.muted
1199
1269
  })));
1200
1270
  },
1201
1271
  setPlaybackRate: (e) => {
1202
- let t = g;
1272
+ let t = h;
1203
1273
  t && (t.setPlaybackRate(e), r.setState((e) => ({
1204
1274
  ...e,
1205
1275
  playbackRate: t.playbackRate
1206
1276
  })));
1207
1277
  },
1208
1278
  toggleFullscreen: () => {
1209
- if (!h || typeof document > "u") return;
1210
- let e = h, t = document, n = !!(document.fullscreenElement || t.webkitFullscreenElement), r = () => {
1211
- h?.focus({ preventScroll: !0 }), ve();
1212
- }, i = n ? typeof document.exitFullscreen == "function" ? document.exitFullscreen() : t.webkitExitFullscreen?.() : typeof h.requestFullscreen == "function" ? h.requestFullscreen() : e.webkitRequestFullscreen?.();
1279
+ if (!m || typeof document > "u") return;
1280
+ let e = m, t = document, n = !!(document.fullscreenElement || t.webkitFullscreenElement), r = () => {
1281
+ m?.focus({ preventScroll: !0 }), Y();
1282
+ }, i = n ? typeof document.exitFullscreen == "function" ? document.exitFullscreen() : t.webkitExitFullscreen?.() : typeof m.requestFullscreen == "function" ? m.requestFullscreen() : e.webkitRequestFullscreen?.();
1213
1283
  Promise.resolve(i).then(r, r);
1214
1284
  },
1215
1285
  togglePiP: () => {
1216
- let e = g;
1286
+ let e = h;
1217
1287
  !e || typeof document > "u" || (document.pictureInPictureElement ? e.exitPictureInPicture() : e.requestPictureInPicture());
1218
1288
  },
1219
1289
  setActiveSubtitle: (e) => {
@@ -1227,20 +1297,20 @@ function q(e) {
1227
1297
  }));
1228
1298
  return;
1229
1299
  }
1230
- q(e);
1300
+ G(e);
1231
1301
  },
1232
1302
  addSubtitleTrack: (e) => {
1233
- let t = K(e);
1234
- U([t]), q(t);
1303
+ let t = J(e);
1304
+ W([t]), G(t);
1235
1305
  },
1236
1306
  removeSubtitleTrack: (e) => {
1237
- r.state.activeSubtitle?.id === e && Y.setActiveSubtitle(null), r.setState((t) => ({
1307
+ r.state.activeSubtitle?.id === e && X.setActiveSubtitle(null), r.setState((t) => ({
1238
1308
  ...t,
1239
1309
  subtitleTracks: t.subtitleTracks.filter((t) => t.id !== e)
1240
1310
  }));
1241
1311
  },
1242
- searchSubtitles: pe,
1243
- selectSubtitleResult: me,
1312
+ searchSubtitles: oe,
1313
+ selectSubtitleResult: se,
1244
1314
  clearSubtitleSearch: () => {
1245
1315
  E?.abort(), E = null, r.setState((e) => ({
1246
1316
  ...e,
@@ -1267,9 +1337,12 @@ function q(e) {
1267
1337
  resetCaptionSettings: () => {
1268
1338
  r.setState((e) => ({
1269
1339
  ...e,
1270
- captionSettings: { ...S }
1340
+ captionSettings: { ...k }
1271
1341
  }));
1272
1342
  },
1343
+ saveCaptionSettings: () => {
1344
+ s.set(x.CAPTION_SETTINGS, r.state.captionSettings);
1345
+ },
1273
1346
  setActiveQuality: (e) => {
1274
1347
  r.setState((t) => ({
1275
1348
  ...t,
@@ -1277,7 +1350,7 @@ function q(e) {
1277
1350
  }));
1278
1351
  },
1279
1352
  takeScreenshot: async () => {
1280
- let e = g;
1353
+ let e = h;
1281
1354
  if (!e) return;
1282
1355
  let t = await e.screenshot();
1283
1356
  if (!t) return;
@@ -1285,7 +1358,7 @@ function q(e) {
1285
1358
  r.href = n, r.download = `vplayer-screenshot-${Date.now()}.png`, r.click(), URL.revokeObjectURL(n);
1286
1359
  },
1287
1360
  setFlip: (e) => {
1288
- let t = g?.element;
1361
+ let t = h?.element;
1289
1362
  if (!t) return;
1290
1363
  let n = "";
1291
1364
  e === "horizontal" && (n = "scaleX(-1)"), e === "vertical" && (n = "scaleY(-1)"), t.style.transform = n, r.setState((t) => ({
@@ -1294,20 +1367,20 @@ function q(e) {
1294
1367
  }));
1295
1368
  },
1296
1369
  setAspectRatio: (e) => {
1297
- let t = de(e), n = g?.element;
1370
+ let t = he(e), n = h?.element;
1298
1371
  if (!n) return;
1299
- let i = le[t];
1300
- i ? n.style.setProperty("--vplayer-media-aspect-ratio", i) : n.style.removeProperty("--vplayer-media-aspect-ratio"), n.style.objectFit = fe(t), n.style.setProperty("--vplayer-media-object-fit", fe(t)), h && (ue(h), h.classList.add(`vplayer--media-${G[t]}`)), r.setState((e) => ({
1372
+ let i = fe[t];
1373
+ i ? n.style.setProperty("--vplayer-media-aspect-ratio", i) : n.style.removeProperty("--vplayer-media-aspect-ratio"), n.style.objectFit = ge(t), n.style.setProperty("--vplayer-media-object-fit", ge(t)), m && (me(m), m.classList.add(`vplayer--media-${pe[t]}`)), r.setState((e) => ({
1301
1374
  ...e,
1302
1375
  aspectRatio: t
1303
1376
  }));
1304
1377
  },
1305
1378
  cycleAspectRatio: () => {
1306
- let e = r.state.aspectRatio, t = W[(W.indexOf(e) + 1) % W.length] ?? "default";
1307
- Y.setAspectRatio(t);
1379
+ let e = r.state.aspectRatio, t = q[(q.indexOf(e) + 1) % q.length] ?? "default";
1380
+ X.setAspectRatio(t);
1308
1381
  },
1309
1382
  toggleLoop: () => {
1310
- let e = g;
1383
+ let e = h;
1311
1384
  e && (e.setLooping(!e.looping), r.setState((t) => ({
1312
1385
  ...t,
1313
1386
  isLooping: e.looping
@@ -1320,10 +1393,10 @@ function q(e) {
1320
1393
  }));
1321
1394
  }
1322
1395
  };
1323
- function ye(e) {
1396
+ function _e(e) {
1324
1397
  return [
1325
1398
  e.on("play", () => {
1326
- ge(), r.setState((e) => ({
1399
+ D.syncMetadata(i.title, i.mediaSession, i.poster), D.setPlaybackState("playing"), r.setState((e) => ({
1327
1400
  ...e,
1328
1401
  status: "playing",
1329
1402
  isPlaying: !0,
@@ -1341,15 +1414,15 @@ function q(e) {
1341
1414
  isPaused: !e.isEnded,
1342
1415
  isBuffering: !1,
1343
1416
  controlsVisible: !0
1344
- })), e.ended || B(L, e);
1417
+ })), D.setPlaybackState("paused"), D.syncPosition(e.duration, e.currentTime, e.playbackRate, !0), e.ended || V(L, e);
1345
1418
  }),
1346
1419
  e.on("ended", () => {
1347
- v = 0, y = NaN;
1348
- for (let e = N.length - 1; e >= 0; e--) {
1349
- let t = N[e];
1350
- t?.kind === "save" && t.target.generation === L.generation && N.splice(e, 1);
1420
+ _ = 0, y = NaN;
1421
+ for (let e = P.length - 1; e >= 0; e--) {
1422
+ let t = P[e];
1423
+ t?.kind === "save" && t.target.generation === L.generation && P.splice(e, 1);
1351
1424
  }
1352
- z(L), r.setState((e) => ({
1425
+ B(L), D.setPlaybackState("paused"), D.syncPosition(e.duration, e.currentTime, e.playbackRate, !0), r.setState((e) => ({
1353
1426
  ...e,
1354
1427
  status: "ended",
1355
1428
  isPlaying: !1,
@@ -1364,10 +1437,10 @@ function q(e) {
1364
1437
  r.setState((t) => ({
1365
1438
  ...t,
1366
1439
  currentTime: e.currentTime
1367
- })), i.onTimeUpdate?.(e.currentTime), ae(e);
1440
+ })), i.onTimeUpdate?.(e.currentTime), D.syncPosition(e.duration, e.currentTime, e.playbackRate), ie(e);
1368
1441
  }),
1369
1442
  e.on("loadedmetadata", () => {
1370
- b !== null && (clearTimeout(b), b = null), C = 0, r.setState((t) => ({
1443
+ S !== null && (clearTimeout(S), S = null), C = 0, r.setState((t) => ({
1371
1444
  ...t,
1372
1445
  status: t.status === "idle" || t.status === "loading" || t.status === "error" ? "ready" : t.status,
1373
1446
  duration: f(e.duration) ? e.duration : 0,
@@ -1376,7 +1449,7 @@ function q(e) {
1376
1449
  isMuted: e.muted,
1377
1450
  playbackRate: e.playbackRate,
1378
1451
  error: null
1379
- })), H(e);
1452
+ })), U(e);
1380
1453
  }),
1381
1454
  e.on("progress", () => {
1382
1455
  let t = e.buffered;
@@ -1395,7 +1468,7 @@ function q(e) {
1395
1468
  }));
1396
1469
  }),
1397
1470
  e.on("canplay", () => {
1398
- b !== null && (clearTimeout(b), b = null), C = 0, r.setState((e) => ({
1471
+ S !== null && (clearTimeout(S), S = null), C = 0, r.setState((e) => ({
1399
1472
  ...e,
1400
1473
  status: e.isPlaying ? "playing" : "ready",
1401
1474
  isBuffering: !1,
@@ -1407,7 +1480,7 @@ function q(e) {
1407
1480
  ...t,
1408
1481
  duration: f(e.duration) ? e.duration : 0,
1409
1482
  isLive: !f(e.duration)
1410
- })), H(e);
1483
+ })), U(e);
1411
1484
  }),
1412
1485
  e.on("seeking", () => {
1413
1486
  r.setState((e) => ({
@@ -1419,7 +1492,7 @@ function q(e) {
1419
1492
  r.setState((e) => ({
1420
1493
  ...e,
1421
1494
  status: e.isPlaying ? "playing" : "ready"
1422
- })), B(L, e);
1495
+ })), D.syncPosition(e.duration, e.currentTime, e.playbackRate, !0), V(L, e);
1423
1496
  }),
1424
1497
  e.on("playing", () => {
1425
1498
  r.setState((e) => ({
@@ -1472,16 +1545,27 @@ function q(e) {
1472
1545
  reconnectAttempt: C,
1473
1546
  isReconnecting: !0
1474
1547
  }
1475
- })), o.emit("video:reconnecting", { attempt: C + 1 }), b !== null && clearTimeout(b), b = setTimeout(() => {
1476
- b = null, C++, o.emit("video:reconnect", { attempt: C }), e.load();
1477
- }, ee));
1548
+ })), o.emit("video:reconnecting", { attempt: C + 1 }), S !== null && clearTimeout(S), S = setTimeout(() => {
1549
+ S = null, C++, o.emit("video:reconnect", { attempt: C }), e.load();
1550
+ }, p));
1478
1551
  })
1479
1552
  ];
1480
1553
  }
1481
- let X = null;
1482
- function be() {
1554
+ let Z = null;
1555
+ function ve() {
1556
+ let e = s.get(x.CAPTION_SETTINGS);
1557
+ e && r.setState((t) => ({
1558
+ ...t,
1559
+ captionSettings: {
1560
+ ...k,
1561
+ ...e,
1562
+ backgroundOpacity: Math.max(0, Math.min(1, e.backgroundOpacity))
1563
+ }
1564
+ }));
1565
+ }
1566
+ function ye() {
1483
1567
  if (!i.persistPreferences) return;
1484
- let e = s.get(m.VOLUME), t = s.get(m.MUTED), n = s.get(m.PLAYBACK_RATE), a = s.get(m.LOOP), o = s.get(m.FLIP), c = s.get(m.ASPECT_RATIO), l = s.get(m.CAPTION_SETTINGS);
1568
+ let e = s.get(x.VOLUME), t = s.get(x.MUTED), n = s.get(x.PLAYBACK_RATE), a = s.get(x.LOOP), o = s.get(x.FLIP), c = s.get(x.ASPECT_RATIO);
1485
1569
  r.setState((r) => ({
1486
1570
  ...r,
1487
1571
  volume: e ?? r.volume,
@@ -1489,24 +1573,19 @@ function q(e) {
1489
1573
  playbackRate: n ?? r.playbackRate,
1490
1574
  isLooping: a ?? r.isLooping,
1491
1575
  flip: o ?? r.flip,
1492
- aspectRatio: de(c),
1493
- captionSettings: l ? {
1494
- ...S,
1495
- ...l,
1496
- backgroundOpacity: Math.max(0, Math.min(1, l.backgroundOpacity))
1497
- } : r.captionSettings
1498
- })), X = r.subscribe(() => {
1576
+ aspectRatio: he(c)
1577
+ })), ve(), Z = r.subscribe(() => {
1499
1578
  let e = r.state;
1500
- e.volume !== 1 && s.set(m.VOLUME, e.volume), e.isMuted && s.set(m.MUTED, !0), e.playbackRate !== 1 && s.set(m.PLAYBACK_RATE, e.playbackRate), e.isLooping ? s.set(m.LOOP, !0) : s.remove(m.LOOP), e.flip === "normal" ? s.remove(m.FLIP) : s.set(m.FLIP, e.flip), e.aspectRatio === "default" ? s.remove(m.ASPECT_RATIO) : s.set(m.ASPECT_RATIO, e.aspectRatio), s.set(m.CAPTION_SETTINGS, e.captionSettings);
1579
+ e.volume !== 1 && s.set(x.VOLUME, e.volume), e.isMuted && s.set(x.MUTED, !0), e.playbackRate !== 1 && s.set(x.PLAYBACK_RATE, e.playbackRate), e.isLooping ? s.set(x.LOOP, !0) : s.remove(x.LOOP), e.flip === "normal" ? s.remove(x.FLIP) : s.set(x.FLIP, e.flip), e.aspectRatio === "default" ? s.remove(x.ASPECT_RATIO) : s.set(x.ASPECT_RATIO, e.aspectRatio), s.set(x.CAPTION_SETTINGS, e.captionSettings);
1501
1580
  });
1502
1581
  }
1503
- function xe() {
1504
- if (X) {
1505
- let e = X;
1506
- typeof e == "function" ? e() : e.unsubscribe(), X = null;
1582
+ function be() {
1583
+ if (Z) {
1584
+ let e = Z;
1585
+ typeof e == "function" ? e() : e.unsubscribe(), Z = null;
1507
1586
  }
1508
1587
  }
1509
- function Z(e) {
1588
+ function Q(e) {
1510
1589
  if (w?.abort(), w = null, !e) {
1511
1590
  r.setState((e) => ({
1512
1591
  ...e,
@@ -1515,7 +1594,7 @@ function q(e) {
1515
1594
  return;
1516
1595
  }
1517
1596
  let t = new AbortController();
1518
- w = t, I(e, t.signal, i.transformThumbnailVTT).then((e) => {
1597
+ w = t, re(e, t.signal, i.transformThumbnailVTT).then((e) => {
1519
1598
  t.signal.aborted || r.setState((t) => ({
1520
1599
  ...t,
1521
1600
  thumbnailCues: e
@@ -1527,21 +1606,21 @@ function q(e) {
1527
1606
  })));
1528
1607
  });
1529
1608
  }
1530
- function Se(e) {
1609
+ function xe(e) {
1531
1610
  return {
1532
1611
  name: e,
1533
1612
  store: r,
1534
- remote: Y,
1613
+ remote: X,
1535
1614
  events: o,
1536
1615
  storage: s,
1537
1616
  hotkeys: l,
1538
1617
  i18n: c,
1539
1618
  context: {
1540
1619
  containerRef: { get current() {
1541
- return h;
1620
+ return m;
1542
1621
  } },
1543
1622
  videoRef: { get current() {
1544
- return g?.element;
1623
+ return h?.element;
1545
1624
  } }
1546
1625
  },
1547
1626
  addControl: (e) => (r.setState((t) => ({
@@ -1610,15 +1689,15 @@ function q(e) {
1610
1689
  }
1611
1690
  };
1612
1691
  }
1613
- let Q = [];
1614
- function Ce(e = !0) {
1615
- b !== null && (clearTimeout(b), b = null), C = 0;
1616
- for (let e of Q) e();
1617
- Q = [], g && (e && B(L, g), g.destroy(), g = null, $.engine = null);
1692
+ let $ = [];
1693
+ function Se(e = !0) {
1694
+ S !== null && (clearTimeout(S), S = null), C = 0;
1695
+ for (let e of $) e();
1696
+ $ = [], h && (e && V(L, h), h.pause(), h.destroy(), h = null, Te.engine = null);
1618
1697
  }
1619
- function we(e) {
1620
- let t = x(i), n = ne(e, i);
1621
- g = n, $.engine = n, Q = ye(n), e.playsInline = !0, e.autoplay = !!i.autoPlay, e.preload = e.preload || "metadata", r.setState((n) => ({
1698
+ function Ce(e) {
1699
+ let t = O(i), n = te(e, i);
1700
+ h = n, Te.engine = n, $ = _e(n), e.playsInline = !0, e.autoplay = !!i.autoPlay, e.preload = e.preload || "metadata", D.setPlaybackState("paused"), r.setState((n) => ({
1622
1701
  ...n,
1623
1702
  status: "loading",
1624
1703
  source: t,
@@ -1629,20 +1708,20 @@ function q(e) {
1629
1708
  currentTime: 0,
1630
1709
  duration: 0,
1631
1710
  bufferedPercent: 0,
1632
- capabilities: d(e, h),
1711
+ capabilities: d(e, m),
1633
1712
  error: null
1634
1713
  })), i.autoPlay && n.play();
1635
1714
  }
1636
- function Te(e = !0) {
1637
- let t = g?.element;
1715
+ function we(e = !0) {
1716
+ let t = h?.element;
1638
1717
  if (t) {
1639
- for (Ce(e), t.pause(), t.removeAttribute("src"); t.firstChild;) t.removeChild(t.firstChild);
1640
- t.load(), we(t);
1718
+ for (Se(e), t.pause(), t.removeAttribute("src"); t.firstChild;) t.removeChild(t.firstChild);
1719
+ t.load(), Ce(t);
1641
1720
  }
1642
1721
  }
1643
- let $ = {
1722
+ let Te = {
1644
1723
  store: r,
1645
- remote: Y,
1724
+ remote: X,
1646
1725
  events: o,
1647
1726
  storage: s,
1648
1727
  i18n: c,
@@ -1652,30 +1731,30 @@ function q(e) {
1652
1731
  let t = typeof e.src == "string" && e.src !== i.src, n = Object.hasOwn(e, "type") && e.type !== i.type, a = Object.hasOwn(e, "thumbnails") && e.thumbnails !== i.thumbnails, o = Object.hasOwn(e, "transformThumbnailVTT") && e.transformThumbnailVTT !== i.transformThumbnailVTT, s = Object.hasOwn(e, "subtitleProviders") && e.subtitleProviders !== i.subtitleProviders, c = {
1653
1732
  ...i,
1654
1733
  ...e
1655
- }, l = F(c), u = l.id !== L.id || l.store !== L.store;
1656
- if ((t || u) && B(L, g), i = c, (t || u) && V(), (r.state.isPlaying || D) && ge(), r.setState((t) => {
1734
+ }, l = I(c), u = l.id !== L.id || l.store !== L.store;
1735
+ if ((t || u) && V(L, h), i = c, (t || u) && H(), (r.state.isPlaying || D.hasMetadata) && D.syncMetadata(i.title, i.mediaSession, i.poster), r.setState((t) => {
1657
1736
  let n = e.qualities ?? t.qualities;
1658
1737
  return {
1659
1738
  ...t,
1660
1739
  qualities: n,
1661
1740
  activeQuality: e.qualities ? e.qualities.includes(t.activeQuality) ? t.activeQuality : e.qualities[0] ?? "Auto" : t.activeQuality
1662
1741
  };
1663
- }), e.subtitles && (U(e.subtitles), !r.state.activeSubtitle)) {
1742
+ }), e.subtitles && (W(e.subtitles), !r.state.activeSubtitle)) {
1664
1743
  let t = e.subtitles.find((e) => e.default) ?? null;
1665
- t && Y.setActiveSubtitle(K(t));
1744
+ t && X.setActiveSubtitle(J(t));
1666
1745
  }
1667
- s && (E?.abort(), E = null, J()), (a || o) && Z(i.thumbnails), t || n ? Te(!(t || u)) : u && g && f(g.duration) && H(g);
1746
+ s && (E?.abort(), E = null, K()), (a || o) && Q(i.thumbnails), t || n ? we(!(t || u)) : u && h && f(h.duration) && U(h);
1668
1747
  },
1669
1748
  setThumbnails(e) {
1670
1749
  i = {
1671
1750
  ...i,
1672
1751
  thumbnails: e
1673
- }, Z(e);
1752
+ }, Q(e);
1674
1753
  },
1675
1754
  initPlugins(e) {
1676
1755
  let t = [];
1677
1756
  for (let n of e) {
1678
- let e = Se(n.name);
1757
+ let e = xe(n.name);
1679
1758
  try {
1680
1759
  let r = n.setup?.(e);
1681
1760
  typeof r == "function" && t.push(r);
@@ -1692,12 +1771,12 @@ function q(e) {
1692
1771
  };
1693
1772
  },
1694
1773
  mount(e, t) {
1695
- g && this.unmount(), h = t, we(e), _ = ve, document.addEventListener("fullscreenchange", _), document.addEventListener("webkitfullscreenchange", _), M = () => B(), window.addEventListener("pagehide", M), Z(i.thumbnails);
1774
+ h && this.unmount(), m = t, Ce(e), g = Y, document.addEventListener("fullscreenchange", g), document.addEventListener("webkitfullscreenchange", g), N = () => V(), window.addEventListener("pagehide", N), D.setup(X), Q(i.thumbnails);
1696
1775
  let n = r.state.subtitleTracks.find((e) => e.default);
1697
- n && Y.setActiveSubtitle(n), be(), Y.setAspectRatio(r.state.aspectRatio);
1776
+ n && X.setActiveSubtitle(n), ve(), ye(), X.setAspectRatio(r.state.aspectRatio);
1698
1777
  },
1699
1778
  unmount() {
1700
- Ce(), V(), he(), h = null, w?.abort(), w = null, T?.abort(), T = null, E?.abort(), E = null, xe(), b !== null && (clearTimeout(b), b = null), _ &&= (document.removeEventListener("fullscreenchange", _), document.removeEventListener("webkitfullscreenchange", _), null), M &&= (window.removeEventListener("pagehide", M), null), r.setState((e) => ({
1779
+ Se(), H(), D.teardown(), m = null, w?.abort(), w = null, T?.abort(), T = null, E?.abort(), E = null, be(), S !== null && (clearTimeout(S), S = null), g &&= (document.removeEventListener("fullscreenchange", g), document.removeEventListener("webkitfullscreenchange", g), null), N &&= (window.removeEventListener("pagehide", N), null), r.setState((e) => ({
1701
1780
  ...e,
1702
1781
  activeSubtitle: null,
1703
1782
  subtitleTracks: e.subtitleTracks.filter((e) => !e.local),
@@ -1715,11 +1794,11 @@ function q(e) {
1715
1794
  this.unmount(), o.clear();
1716
1795
  }
1717
1796
  };
1718
- return $;
1797
+ return Te;
1719
1798
  }
1720
1799
  //#endregion
1721
1800
  //#region src/gesture-engine.ts
1722
- function J(e, t) {
1801
+ function X(e, t) {
1723
1802
  let n = null, r = null, i = null, a = typeof window < "u" && "ontouchstart" in window;
1724
1803
  function o(t) {
1725
1804
  if (!a || t.touches.length !== 1) return;
@@ -1779,14 +1858,14 @@ function J(e, t) {
1779
1858
  }
1780
1859
  //#endregion
1781
1860
  //#region src/utils.ts
1782
- function pe(e) {
1861
+ function _e(e) {
1783
1862
  if (!Number.isFinite(e) || e < 0) return "0:00";
1784
1863
  let t = Math.floor(e / 3600), n = Math.floor(e % 3600 / 60), r = Math.floor(e % 60);
1785
1864
  return t > 0 ? `${t}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}` : `${n}:${String(r).padStart(2, "0")}`;
1786
1865
  }
1787
1866
  //#endregion
1788
1867
  //#region src/defaults.ts
1789
- var me = {
1868
+ var Z = {
1790
1869
  play: "Play",
1791
1870
  pause: "Pause",
1792
1871
  replay: "Replay",
@@ -1822,6 +1901,8 @@ var me = {
1822
1901
  captionMedium: "Default",
1823
1902
  captionLarge: "Large",
1824
1903
  captionReset: "Reset",
1904
+ captionSave: "Save",
1905
+ captionSaved: "Saved",
1825
1906
  captionFontFamily: "Font",
1826
1907
  captionFontScale: "Scale",
1827
1908
  captionTextOpacity: "Text opacity",
@@ -1861,4 +1942,4 @@ var me = {
1861
1942
  loop: "Loop"
1862
1943
  };
1863
1944
  //#endregion
1864
- export { h as BaseMediaEngine, v as DashMediaEngine, t as EventBus, _ as HlsMediaEngine, n as HotkeyRegistry, a as I18n, te as LocalPlaybackProgressStore, g as NativeVideoEngine, m as STORAGE_KEYS, p as Storage, c as canUseFullscreen, o as canUseMSE, s as canUseNativeHLS, l as canUsePictureInPicture, u as canUseTextTracks, J as createGestureEngine, re as createMediaStore, q as createPlayer, ne as createResolvedMediaEngine, me as defaultPlayerLabels, b as detectSourceKind, j as fetchSubtitles, I as fetchThumbnails, pe as formatTime, M as getActiveCue, R as getInitialMediaState, d as getMediaCapabilities, L as getThumbnailAtTime, f as isFiniteDuration, O as parseSRT, A as parseSubtitles, P as parseThumbnailVTT, E as parseTimestamp, k as parseVTT, z as selectAudio, U as selectError, ie as selectMedia, V as selectPlugins, B as selectPreferences, H as selectThumbnails, ae as selectUI, x as toPlayerSource };
1945
+ export { S as BaseMediaEngine, T as DashMediaEngine, t as EventBus, w as HlsMediaEngine, n as HotkeyRegistry, a as I18n, ee as LocalPlaybackProgressStore, C as NativeVideoEngine, x as STORAGE_KEYS, b as Storage, c as canUseFullscreen, o as canUseMSE, s as canUseNativeHLS, l as canUsePictureInPicture, u as canUseTextTracks, X as createGestureEngine, v as createMediaSessionController, ae as createMediaStore, Y as createPlayer, te as createResolvedMediaEngine, Z as defaultPlayerLabels, D as detectSourceKind, R as fetchSubtitles, re as fetchThumbnails, _e as formatTime, z as getActiveCue, H as getInitialMediaState, d as getMediaCapabilities, ie as getThumbnailAtTime, f as isFiniteDuration, F as parseSRT, L as parseSubtitles, B as parseThumbnailVTT, N as parseTimestamp, I as parseVTT, W as selectAudio, ce as selectError, U as selectMedia, oe as selectPlugins, G as selectPreferences, se as selectThumbnails, K as selectUI, O as toPlayerSource };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * media-session — Media Session API integration.
3
+ *
4
+ * Browsers project this to OS media controls (MPRIS on Linux desktops),
5
+ * which drive system notifications, lock-screen controls, and media keys.
6
+ * Metadata alone is not enough: without `playbackState` and action
7
+ * handlers the OS entry goes stale and its buttons are dead.
8
+ */
9
+ import type { MediaRemote, MediaSessionMetadataOptions } from './types';
10
+ type SessionPlaybackState = 'playing' | 'paused';
11
+ export interface MediaSessionController {
12
+ setup(remote: MediaRemote): void;
13
+ syncMetadata(title: string | undefined, metadata?: MediaSessionMetadataOptions, poster?: string): void;
14
+ setPlaybackState(state: SessionPlaybackState): void;
15
+ syncPosition(duration: number, currentTime: number, playbackRate: number, force?: boolean): void;
16
+ readonly hasMetadata: boolean;
17
+ teardown(): void;
18
+ }
19
+ export declare function createMediaSessionController(): MediaSessionController;
20
+ export {};
package/dist/types.d.ts CHANGED
@@ -12,6 +12,15 @@ import type { MediaState, PlaybackStatus, ResumeState } from './state/slices';
12
12
  import type { Storage } from './storage';
13
13
  import type { CaptionSettings, SubtitleProvider, SubtitleProviderResult, SubtitleSearchQuery, SubtitleTrack } from './subtitle-parser';
14
14
  export type { MediaState, ResumeState };
15
+ /** OS media-control metadata overrides (Media Session → MPRIS). */
16
+ export interface MediaSessionMetadataOptions {
17
+ /** Overrides the artwork sent to OS controls; falls back to `poster`. */
18
+ artwork?: string;
19
+ /** Artist line shown under the title (e.g. studio or channel name). */
20
+ artist?: string;
21
+ /** Album line; rarely meaningful for video, supported for completeness. */
22
+ album?: string;
23
+ }
15
24
  export interface PlayerOptions {
16
25
  src: string;
17
26
  /** MIME/content type hint used by source provider selection. */
@@ -19,6 +28,12 @@ export interface PlayerOptions {
19
28
  /** Title exposed to browser and operating-system media controls. */
20
29
  title?: string;
21
30
  poster?: string;
31
+ /**
32
+ * OS media-control overrides (Media Session → MPRIS), e.g. a scaled-down
33
+ * artwork image when full-size posters are too big, or a studio name as
34
+ * the artist line. Artwork falls back to `poster` when omitted.
35
+ */
36
+ mediaSession?: MediaSessionMetadataOptions;
22
37
  subtitles?: SubtitleTrack[];
23
38
  subtitleProviders?: SubtitleProvider[];
24
39
  qualities?: string[];
@@ -96,6 +111,7 @@ export interface MediaRemote {
96
111
  clearSubtitleSearch: () => void;
97
112
  setCaptionSettings: (patch: Partial<CaptionSettings>) => void;
98
113
  resetCaptionSettings: () => void;
114
+ saveCaptionSettings: () => void;
99
115
  setActiveQuality: (q: string) => void;
100
116
  takeScreenshot: () => void;
101
117
  setFlip: (flip: FlipState) => void;
@@ -152,6 +168,8 @@ export interface PlayerLabels {
152
168
  captionDelay: string;
153
169
  captionPreview: string;
154
170
  captionReset: string;
171
+ captionSave: string;
172
+ captionSaved: string;
155
173
  subtitleLoadError: string;
156
174
  endedTitle: string;
157
175
  screenshot: string;
@@ -193,7 +211,7 @@ export interface PlayerInstance {
193
211
  hotkeys: HotkeyRegistry;
194
212
  /** The active media engine. Null before mount and after unmount. */
195
213
  engine: MediaEngine | null;
196
- updateOptions(opts: Partial<Pick<PlayerOptions, 'src' | 'type' | 'title' | 'poster' | 'autoPlay' | 'subtitles' | 'subtitleProviders' | 'qualities' | 'thumbnails' | 'transformThumbnailVTT' | 'playbackProgress'>>): void;
214
+ updateOptions(opts: Partial<Pick<PlayerOptions, 'src' | 'type' | 'title' | 'poster' | 'mediaSession' | 'autoPlay' | 'subtitles' | 'subtitleProviders' | 'qualities' | 'thumbnails' | 'transformThumbnailVTT' | 'playbackProgress'>>): void;
197
215
  setThumbnails(url?: string): void;
198
216
  initPlugins(plugins: PlayerPlugin[]): () => void;
199
217
  mount(videoEl: HTMLVideoElement, containerEl: HTMLDivElement): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vplayer/core",
3
- "version": "0.2.3",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",