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