@vplayer/core 0.1.8 → 0.2.1

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
@@ -4,6 +4,8 @@ export type { GestureEngineCallbacks, GestureHandlers } from './gesture-engine';
4
4
  export { EventBus } from './event-bus';
5
5
  export type { PlayerEventName, PlayerEventHandler } from './event-bus';
6
6
  export { Storage, STORAGE_KEYS } from './storage';
7
+ export { LocalPlaybackProgressStore } from './playback-progress';
8
+ export type { PlaybackProgress, PlaybackProgressStore, PlaybackProgressOptions } from './playback-progress';
7
9
  export { I18n } from './i18n';
8
10
  export type { LanguageDict } from './i18n';
9
11
  export { HotkeyRegistry } from './hotkey-registry';
package/dist/index.js CHANGED
@@ -188,8 +188,76 @@ function f(e) {
188
188
  return Number.isFinite(e) && e > 0;
189
189
  }
190
190
  //#endregion
191
- //#region src/media-engine/base-engine.ts
192
- var p = class {
191
+ //#region src/storage.ts
192
+ var p = "vplayer", m = class {
193
+ fallback = /* @__PURE__ */ new Map();
194
+ prefix;
195
+ constructor(e = {}) {
196
+ this.prefix = e.prefix ?? p;
197
+ }
198
+ fullKey(e) {
199
+ return `${this.prefix}:${e}`;
200
+ }
201
+ get(e) {
202
+ let t = this.fullKey(e);
203
+ try {
204
+ let e = window.localStorage.getItem(t);
205
+ return e === null ? void 0 : JSON.parse(e);
206
+ } catch {
207
+ let e = this.fallback.get(t);
208
+ return e === void 0 ? void 0 : JSON.parse(e);
209
+ }
210
+ }
211
+ set(e, t) {
212
+ let n = this.fullKey(e);
213
+ try {
214
+ window.localStorage.setItem(n, JSON.stringify(t));
215
+ } catch {
216
+ this.fallback.set(n, JSON.stringify(t));
217
+ }
218
+ }
219
+ remove(e) {
220
+ let t = this.fullKey(e);
221
+ try {
222
+ window.localStorage.removeItem(t);
223
+ } catch {}
224
+ this.fallback.delete(t);
225
+ }
226
+ clear() {
227
+ try {
228
+ let e = [];
229
+ for (let t = 0; t < window.localStorage.length; t++) {
230
+ let n = window.localStorage.key(t);
231
+ n?.startsWith(this.prefix + ":") && e.push(n);
232
+ }
233
+ e.forEach((e) => window.localStorage.removeItem(e));
234
+ } catch {}
235
+ this.fallback.clear();
236
+ }
237
+ }, h = {
238
+ VOLUME: "volume",
239
+ MUTED: "muted",
240
+ PLAYBACK_RATE: "playbackRate",
241
+ SUBTITLE_LANG: "subtitleLang",
242
+ QUALITY: "quality",
243
+ LOOP: "loop",
244
+ FLIP: "flip",
245
+ ASPECT_RATIO: "aspectRatio"
246
+ }, g = class {
247
+ storage;
248
+ constructor(e = new m({ prefix: "vplayer:progress" })) {
249
+ this.storage = e;
250
+ }
251
+ async load(e) {
252
+ return this.storage.get(encodeURIComponent(e)) ?? null;
253
+ }
254
+ async save(e, t) {
255
+ this.storage.set(encodeURIComponent(e), t);
256
+ }
257
+ async clear(e) {
258
+ this.storage.remove(encodeURIComponent(e));
259
+ }
260
+ }, _ = class {
193
261
  element;
194
262
  destroyed = !1;
195
263
  listeners = /* @__PURE__ */ new Map();
@@ -339,7 +407,7 @@ var p = class {
339
407
  destroy() {
340
408
  this.destroyed || (this.destroyed = !0, this.detachDOMListeners(), this.listeners.clear());
341
409
  }
342
- }, m = class extends p {}, h = class extends p {
410
+ }, v = class extends _ {}, y = class extends _ {
343
411
  hls = null;
344
412
  hlsSrc;
345
413
  hlsConfig;
@@ -379,7 +447,7 @@ var p = class {
379
447
  destroy() {
380
448
  this.destroyed || (super.destroy(), this.hls &&= (this.hls.destroy(), null));
381
449
  }
382
- }, g = class extends p {
450
+ }, b = class extends _ {
383
451
  dashPlayer = null;
384
452
  dashSrc;
385
453
  dashConfig;
@@ -407,40 +475,40 @@ var p = class {
407
475
  };
408
476
  //#endregion
409
477
  //#region src/source-resolver.ts
410
- function _(e) {
478
+ function x(e) {
411
479
  try {
412
480
  return new URL(e, typeof window < "u" ? window.location.href : "http://localhost").pathname.toLowerCase();
413
481
  } catch {
414
482
  return e.toLowerCase().split("?")[0].split("#")[0];
415
483
  }
416
484
  }
417
- function v(e) {
418
- let t = e.type?.toLowerCase() ?? "", n = _(e.src);
485
+ function S(e) {
486
+ let t = e.type?.toLowerCase() ?? "", n = x(e.src);
419
487
  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";
420
488
  }
421
- function y(e) {
489
+ function C(e) {
422
490
  return {
423
491
  src: e.src,
424
492
  type: e.type
425
493
  };
426
494
  }
427
- function b(e, t) {
495
+ function ee(e, t) {
428
496
  if (t.engine) return typeof t.engine == "function" ? t.engine(e) : t.engine;
429
- let n = y(t), r = v(n);
430
- return r === "hls" && !s(e) ? new h(e, {
497
+ let n = C(t), r = S(n);
498
+ return r === "hls" && !s(e) ? new y(e, {
431
499
  src: n.src,
432
500
  hlsConfig: t.hlsConfig
433
- }) : r === "dash" ? new g(e, {
501
+ }) : r === "dash" ? new b(e, {
434
502
  src: n.src,
435
503
  dashConfig: t.dashConfig
436
- }) : (e.src = n.src, new m(e));
504
+ }) : (e.src = n.src, new v(e));
437
505
  }
438
506
  //#endregion
439
507
  //#region src/state/slices.ts
440
- function x() {
441
- return new e(S());
508
+ function w() {
509
+ return new e(T());
442
510
  }
443
- function S() {
511
+ function T() {
444
512
  return {
445
513
  status: "idle",
446
514
  source: null,
@@ -479,10 +547,11 @@ function S() {
479
547
  contextMenuItems: [],
480
548
  contextMenuEnabled: !0,
481
549
  thumbnailCues: [],
482
- error: null
550
+ error: null,
551
+ resumeProgress: null
483
552
  };
484
553
  }
485
- var C = (e) => ({
554
+ var E = (e) => ({
486
555
  status: e.status,
487
556
  source: e.source,
488
557
  isPlaying: e.isPlaying,
@@ -493,11 +562,12 @@ var C = (e) => ({
493
562
  duration: e.duration,
494
563
  bufferedPercent: e.bufferedPercent,
495
564
  playbackRate: e.playbackRate,
496
- isLive: e.isLive
497
- }), w = (e) => ({
565
+ isLive: e.isLive,
566
+ resumeProgress: e.resumeProgress
567
+ }), D = (e) => ({
498
568
  volume: e.volume,
499
569
  isMuted: e.isMuted
500
- }), T = (e) => ({
570
+ }), O = (e) => ({
501
571
  isLooping: e.isLooping,
502
572
  flip: e.flip,
503
573
  aspectRatio: e.aspectRatio,
@@ -505,74 +575,19 @@ var C = (e) => ({
505
575
  subtitleTracks: e.subtitleTracks,
506
576
  activeQuality: e.activeQuality,
507
577
  qualities: e.qualities
508
- }), E = (e) => ({
578
+ }), k = (e) => ({
509
579
  controlsVisible: e.controlsVisible,
510
580
  capabilities: e.capabilities,
511
581
  isFullscreen: e.isFullscreen,
512
582
  infoPanelVisible: e.infoPanelVisible,
513
583
  notification: e.notification
514
- }), D = (e) => ({
584
+ }), A = (e) => ({
515
585
  controls: e.controls,
516
586
  settings: e.settings,
517
587
  layers: e.layers,
518
588
  contextMenuItems: e.contextMenuItems,
519
589
  contextMenuEnabled: e.contextMenuEnabled
520
- }), O = (e) => ({ thumbnailCues: e.thumbnailCues }), k = (e) => e.error, A = "vplayer", j = class {
521
- fallback = /* @__PURE__ */ new Map();
522
- prefix;
523
- constructor(e = {}) {
524
- this.prefix = e.prefix ?? A;
525
- }
526
- fullKey(e) {
527
- return `${this.prefix}:${e}`;
528
- }
529
- get(e) {
530
- let t = this.fullKey(e);
531
- try {
532
- let e = window.localStorage.getItem(t);
533
- return e === null ? void 0 : JSON.parse(e);
534
- } catch {
535
- let e = this.fallback.get(t);
536
- return e === void 0 ? void 0 : JSON.parse(e);
537
- }
538
- }
539
- set(e, t) {
540
- let n = this.fullKey(e);
541
- try {
542
- window.localStorage.setItem(n, JSON.stringify(t));
543
- } catch {
544
- this.fallback.set(n, JSON.stringify(t));
545
- }
546
- }
547
- remove(e) {
548
- let t = this.fullKey(e);
549
- try {
550
- window.localStorage.removeItem(t);
551
- } catch {}
552
- this.fallback.delete(t);
553
- }
554
- clear() {
555
- try {
556
- let e = [];
557
- for (let t = 0; t < window.localStorage.length; t++) {
558
- let n = window.localStorage.key(t);
559
- n?.startsWith(this.prefix + ":") && e.push(n);
560
- }
561
- e.forEach((e) => window.localStorage.removeItem(e));
562
- } catch {}
563
- this.fallback.clear();
564
- }
565
- }, M = {
566
- VOLUME: "volume",
567
- MUTED: "muted",
568
- PLAYBACK_RATE: "playbackRate",
569
- SUBTITLE_LANG: "subtitleLang",
570
- QUALITY: "quality",
571
- PLAYBACK_PROGRESS: "playbackProgress",
572
- LOOP: "loop",
573
- FLIP: "flip",
574
- ASPECT_RATIO: "aspectRatio"
575
- };
590
+ }), j = (e) => ({ thumbnailCues: e.thumbnailCues }), M = (e) => e.error;
576
591
  //#endregion
577
592
  //#region src/subtitle-parser.ts
578
593
  function N(e) {
@@ -620,7 +635,7 @@ async function I(e) {
620
635
  let t = await (await fetch(e.src)).text();
621
636
  return e.src.endsWith(".srt") ? P(t) : F(t);
622
637
  }
623
- function L(e, t) {
638
+ function te(e, t) {
624
639
  let n = 0, r = e.length - 1;
625
640
  for (; n <= r;) {
626
641
  let i = n + r >> 1, a = e[i];
@@ -630,7 +645,7 @@ function L(e, t) {
630
645
  }
631
646
  return null;
632
647
  }
633
- function R(e) {
648
+ function L(e) {
634
649
  let t = e.match(/#xywh=(\d+),(\d+),(\d+),(\d+)/);
635
650
  return t ? {
636
651
  src: e.replace(/#xywh=.*$/, ""),
@@ -640,7 +655,7 @@ function R(e) {
640
655
  h: Number(t[4])
641
656
  } : null;
642
657
  }
643
- function z(e) {
658
+ function R(e) {
644
659
  let t = e.replace(/\r\n/g, "\n"), n = t.indexOf("\n\n");
645
660
  return n > 0 && (t = t.slice(n + 2)), t.trim().split(/\n\n+/).map((e) => {
646
661
  let t = e.split("\n"), n = t.findIndex((e) => e.includes("-->"));
@@ -649,7 +664,7 @@ function z(e) {
649
664
  if (!r) return null;
650
665
  let i = t.slice(n + 1).find((e) => e.trim().length > 0);
651
666
  if (!i) return null;
652
- let a = i.trim(), o = R(a), s = N(r[1]), c = N(r[2]);
667
+ let a = i.trim(), o = L(a), s = N(r[1]), c = N(r[2]);
653
668
  return o ? {
654
669
  start: s,
655
670
  end: c,
@@ -669,20 +684,20 @@ function z(e) {
669
684
  };
670
685
  }).filter((e) => e !== null);
671
686
  }
672
- function B(e, t) {
687
+ function z(e, t) {
673
688
  try {
674
689
  return new URL(e, t).toString();
675
690
  } catch {
676
691
  return e;
677
692
  }
678
693
  }
679
- async function V(e, t, n) {
694
+ async function B(e, t, n) {
680
695
  let r = await fetch(e, { signal: t });
681
696
  if (!r.ok) throw Error(`Failed to fetch thumbnail VTT: ${r.status}`);
682
697
  let i = r.url || e, a = await r.text();
683
- return z(n ? await n(a, i) : a).map((e) => (e.src = B(e.src, i), e));
698
+ return R(n ? await n(a, i) : a).map((e) => (e.src = z(e.src, i), e));
684
699
  }
685
- function H(e, t) {
700
+ function V(e, t) {
686
701
  if (e.length === 0) return null;
687
702
  let n = 0, r = e.length - 1;
688
703
  for (; n <= r;) {
@@ -695,18 +710,18 @@ function H(e, t) {
695
710
  }
696
711
  //#endregion
697
712
  //#region src/player.ts
698
- var U = [
713
+ var H = [
699
714
  "default",
700
715
  "16:9",
701
716
  "4:3",
702
717
  "21:9",
703
718
  "cover",
704
719
  "fill"
705
- ], W = {
720
+ ], ne = {
706
721
  "16:9": "16 / 9",
707
722
  "4:3": "4 / 3",
708
723
  "21:9": "21 / 9"
709
- }, G = {
724
+ }, re = {
710
725
  default: "default",
711
726
  "16:9": "16-9",
712
727
  "4:3": "4-3",
@@ -714,89 +729,143 @@ var U = [
714
729
  cover: "cover",
715
730
  fill: "fill"
716
731
  };
717
- function K(e) {
732
+ function ie(e) {
718
733
  e.classList.remove("vplayer--media-default", "vplayer--media-16-9", "vplayer--media-4-3", "vplayer--media-21-9", "vplayer--media-cover", "vplayer--media-fill");
719
734
  }
720
- function q(e) {
721
- return typeof e == "string" && e in G ? e : "default";
735
+ function ae(e) {
736
+ return typeof e == "string" && e in re ? e : "default";
722
737
  }
723
- function J(e) {
738
+ function U(e) {
724
739
  return e === "cover" ? "cover" : e === "fill" ? "fill" : "contain";
725
740
  }
726
- function Y(e) {
727
- let r = x(), i = { ...e }, o = new t(), s = new j(), c = new a(i.lang);
741
+ function W(e) {
742
+ let r = w(), i = { ...e }, o = new t(), s = new m(), c = new a(i.lang);
728
743
  i.translations && c.addTranslations(i.lang ?? "en", i.translations);
729
- let l = new n(), u = i.reconnectMax ?? 3, p = i.reconnectSleep ?? 1500, m = null, h = null, g = null, _ = null, v = null, S = 0, C = null, w = null;
730
- function T() {
731
- w && typeof navigator < "u" && "mediaSession" in navigator && navigator.mediaSession.metadata === w && (navigator.mediaSession.metadata = null), w = null;
744
+ let l = new n(), u = i.reconnectMax ?? 3, p = i.reconnectSleep ?? 1500, _ = null, v = null, y = null, b = null, x = null, S = 0, T = null, E = null, D = new g(), O = 0, k = !1, A = !1, j = null, M = [], N = !1;
745
+ function P(e) {
746
+ return {
747
+ generation: O,
748
+ id: e.playbackProgress?.id ?? e.src,
749
+ store: e.playbackProgress?.store ?? D
750
+ };
751
+ }
752
+ let F = P(i);
753
+ function I() {
754
+ if (N) return;
755
+ let e = M.shift();
756
+ e && (N = !0, (e.kind === "save" ? e.target.store.save(e.target.id, e.progress) : e.target.store.clear(e.target.id)).catch((t) => {
757
+ console.warn(e.kind === "save" ? "[vplayer] playback progress save failed:" : "[vplayer] playback progress clear failed:", t);
758
+ }).finally(() => {
759
+ N = !1, I();
760
+ }));
761
+ }
762
+ function te(e, t) {
763
+ let n = M.at(-1);
764
+ n?.kind === "save" && n.target.generation === e.generation ? n.progress = t : M.push({
765
+ kind: "save",
766
+ target: e,
767
+ progress: t
768
+ }), I();
769
+ }
770
+ function L(e) {
771
+ M.push({
772
+ kind: "clear",
773
+ target: e
774
+ }), I();
775
+ }
776
+ function R(e = F, t = v) {
777
+ !t || t.ended || !Number.isFinite(t.currentTime) || t.currentTime < 0 || !f(t.duration) || te(e, {
778
+ time: t.currentTime,
779
+ duration: t.duration
780
+ });
781
+ }
782
+ function z() {
783
+ O++, F = P(i), k = !1, A = !1, Y(), r.setState((e) => ({
784
+ ...e,
785
+ resumeProgress: null
786
+ }));
787
+ }
788
+ function V(e) {
789
+ if (k || !f(e.duration)) return;
790
+ k = !0;
791
+ let t = F, n = e.duration;
792
+ t.store.load(t.id).then((i) => {
793
+ !i || t.generation !== O || e !== v || A || e.currentTime !== 0 || !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) || r.setState((e) => ({
794
+ ...e,
795
+ resumeProgress: i
796
+ }));
797
+ }).catch((e) => console.warn("[vplayer] playback progress load failed:", e));
798
+ }
799
+ function W() {
800
+ E && typeof navigator < "u" && "mediaSession" in navigator && navigator.mediaSession.metadata === E && (navigator.mediaSession.metadata = null), E = null;
732
801
  }
733
- function E() {
802
+ function G() {
734
803
  if (typeof navigator > "u" || !("mediaSession" in navigator) || typeof MediaMetadata > "u") return;
735
804
  let e = i.title?.trim();
736
805
  if (!e) {
737
- T();
806
+ W();
738
807
  return;
739
808
  }
740
809
  let t = new MediaMetadata({
741
810
  title: e,
742
811
  artwork: i.poster ? [{ src: i.poster }] : []
743
812
  });
744
- navigator.mediaSession.metadata = t, w = t;
813
+ navigator.mediaSession.metadata = t, E = t;
745
814
  }
746
- function D() {
815
+ function K() {
747
816
  l.register({
748
817
  key: "Space",
749
818
  description: "Toggle play/pause",
750
819
  handler: (e) => {
751
- e.preventDefault(), k.togglePlay();
820
+ e.preventDefault(), J.togglePlay();
752
821
  }
753
822
  }), l.register({
754
823
  key: "KeyK",
755
824
  description: "Toggle play/pause",
756
825
  handler: (e) => {
757
- e.preventDefault(), k.togglePlay();
826
+ e.preventDefault(), J.togglePlay();
758
827
  }
759
828
  }), l.register({
760
829
  key: "KeyL",
761
830
  description: "Toggle loop",
762
831
  handler: (e) => {
763
- e.preventDefault(), k.toggleLoop();
832
+ e.preventDefault(), J.toggleLoop();
764
833
  }
765
834
  }), l.register({
766
835
  key: "KeyI",
767
836
  description: "Toggle info panel",
768
837
  handler: (e) => {
769
- e.preventDefault(), k.toggleInfoPanel();
838
+ e.preventDefault(), J.toggleInfoPanel();
770
839
  }
771
840
  }), l.register({
772
841
  key: "KeyF",
773
842
  description: "Toggle fullscreen",
774
843
  handler: (e) => {
775
- e.preventDefault(), k.toggleFullscreen();
844
+ e.preventDefault(), J.toggleFullscreen();
776
845
  }
777
846
  }), l.register({
778
847
  key: "KeyA",
779
848
  description: "Cycle aspect ratio",
780
849
  handler: (e) => {
781
- e.preventDefault(), k.cycleAspectRatio();
850
+ e.preventDefault(), J.cycleAspectRatio();
782
851
  }
783
852
  }), l.register({
784
853
  key: "KeyM",
785
854
  description: "Toggle mute",
786
855
  handler: (e) => {
787
- e.preventDefault(), k.toggleMute();
856
+ e.preventDefault(), J.toggleMute();
788
857
  }
789
858
  }), l.register({
790
859
  key: "ArrowLeft",
791
860
  description: "Seek backward 5s",
792
861
  handler: (e) => {
793
- e.preventDefault(), k.skip(-5);
862
+ e.preventDefault(), J.skip(-5);
794
863
  }
795
864
  }), l.register({
796
865
  key: "ArrowRight",
797
866
  description: "Seek forward 5s",
798
867
  handler: (e) => {
799
- e.preventDefault(), k.skip(5);
868
+ e.preventDefault(), J.skip(5);
800
869
  }
801
870
  }), l.register({
802
871
  key: "ArrowUp",
@@ -804,7 +873,7 @@ function Y(e) {
804
873
  handler: (e) => {
805
874
  e.preventDefault();
806
875
  let t = Math.min(1, r.state.volume + .1);
807
- k.setVolume(t);
876
+ J.setVolume(t);
808
877
  }
809
878
  }), l.register({
810
879
  key: "ArrowDown",
@@ -812,38 +881,52 @@ function Y(e) {
812
881
  handler: (e) => {
813
882
  e.preventDefault();
814
883
  let t = Math.max(0, r.state.volume - .1);
815
- k.setVolume(t);
884
+ J.setVolume(t);
816
885
  }
817
886
  });
818
887
  }
819
- i.defaultHotkeys !== !1 && D();
820
- function O() {
888
+ i.defaultHotkeys !== !1 && K();
889
+ function q() {
821
890
  if (typeof document > "u") return;
822
891
  let e = document;
823
892
  r.setState((t) => ({
824
893
  ...t,
825
894
  isFullscreen: !!(document.fullscreenElement || e.webkitFullscreenElement),
826
- capabilities: d(h?.element ?? void 0, m)
895
+ capabilities: d(v?.element ?? void 0, _)
827
896
  }));
828
897
  }
829
- let k = {
898
+ let J = {
830
899
  play: () => {
831
- h?.play();
900
+ v?.play();
832
901
  },
833
- pause: () => h?.pause(),
902
+ pause: () => v?.pause(),
834
903
  togglePlay: () => {
835
- let e = h;
904
+ let e = v;
836
905
  e && (e.paused || e.ended ? e.play() : e.pause());
837
906
  },
838
- seek: (e) => h?.seek(e),
907
+ resumeFromSavedProgress: () => {
908
+ let e = r.state.resumeProgress;
909
+ e && (r.setState((e) => ({
910
+ ...e,
911
+ resumeProgress: null
912
+ })), v?.seek(e.time), v?.play());
913
+ },
914
+ startPlaybackOver: () => {
915
+ let e = F;
916
+ r.setState((e) => ({
917
+ ...e,
918
+ resumeProgress: null
919
+ })), v?.seek(0), L(e);
920
+ },
921
+ seek: (e) => v?.seek(e),
839
922
  skip: (e) => {
840
- let t = h;
923
+ let t = v;
841
924
  if (!t) return;
842
925
  let n = f(t.duration) ? t.duration : Infinity, r = Math.max(0, Math.min(t.currentTime + e, n));
843
926
  t.seek(r);
844
927
  },
845
928
  setVolume: (e) => {
846
- let t = h;
929
+ let t = v;
847
930
  if (!t) return;
848
931
  let n = Math.max(0, Math.min(1, e));
849
932
  t.setVolume(n), n > 0 && t.setMuted(!1), r.setState((e) => ({
@@ -853,28 +936,28 @@ function Y(e) {
853
936
  }));
854
937
  },
855
938
  toggleMute: () => {
856
- let e = h;
939
+ let e = v;
857
940
  e && (e.setMuted(!e.muted), r.setState((t) => ({
858
941
  ...t,
859
942
  isMuted: e.muted
860
943
  })));
861
944
  },
862
945
  setPlaybackRate: (e) => {
863
- let t = h;
946
+ let t = v;
864
947
  t && (t.setPlaybackRate(e), r.setState((e) => ({
865
948
  ...e,
866
949
  playbackRate: t.playbackRate
867
950
  })));
868
951
  },
869
952
  toggleFullscreen: () => {
870
- if (!m || typeof document > "u") return;
871
- let e = m, t = document, n = !!(document.fullscreenElement || t.webkitFullscreenElement), r = () => {
872
- m?.focus({ preventScroll: !0 }), O();
873
- }, i = n ? typeof document.exitFullscreen == "function" ? document.exitFullscreen() : t.webkitExitFullscreen?.() : typeof m.requestFullscreen == "function" ? m.requestFullscreen() : e.webkitRequestFullscreen?.();
953
+ if (!_ || typeof document > "u") return;
954
+ let e = _, t = document, n = !!(document.fullscreenElement || t.webkitFullscreenElement), r = () => {
955
+ _?.focus({ preventScroll: !0 }), q();
956
+ }, i = n ? typeof document.exitFullscreen == "function" ? document.exitFullscreen() : t.webkitExitFullscreen?.() : typeof _.requestFullscreen == "function" ? _.requestFullscreen() : e.webkitRequestFullscreen?.();
874
957
  Promise.resolve(i).then(r, r);
875
958
  },
876
959
  togglePiP: () => {
877
- let e = h;
960
+ let e = v;
878
961
  !e || typeof document > "u" || (document.pictureInPictureElement ? e.exitPictureInPicture() : e.requestPictureInPicture());
879
962
  },
880
963
  setActiveSubtitle: (e) => {
@@ -890,7 +973,7 @@ function Y(e) {
890
973
  }));
891
974
  },
892
975
  takeScreenshot: async () => {
893
- let e = h;
976
+ let e = v;
894
977
  if (!e) return;
895
978
  let t = await e.screenshot();
896
979
  if (!t) return;
@@ -898,7 +981,7 @@ function Y(e) {
898
981
  r.href = n, r.download = `vplayer-screenshot-${Date.now()}.png`, r.click(), URL.revokeObjectURL(n);
899
982
  },
900
983
  setFlip: (e) => {
901
- let t = h?.element;
984
+ let t = v?.element;
902
985
  if (!t) return;
903
986
  let n = "";
904
987
  e === "horizontal" && (n = "scaleX(-1)"), e === "vertical" && (n = "scaleY(-1)"), t.style.transform = n, r.setState((t) => ({
@@ -907,20 +990,20 @@ function Y(e) {
907
990
  }));
908
991
  },
909
992
  setAspectRatio: (e) => {
910
- let t = q(e), n = h?.element;
993
+ let t = ae(e), n = v?.element;
911
994
  if (!n) return;
912
- let i = W[t];
913
- i ? n.style.setProperty("--vplayer-media-aspect-ratio", i) : n.style.removeProperty("--vplayer-media-aspect-ratio"), n.style.objectFit = J(t), n.style.setProperty("--vplayer-media-object-fit", J(t)), m && (K(m), m.classList.add(`vplayer--media-${G[t]}`)), r.setState((e) => ({
995
+ let i = ne[t];
996
+ i ? n.style.setProperty("--vplayer-media-aspect-ratio", i) : n.style.removeProperty("--vplayer-media-aspect-ratio"), n.style.objectFit = U(t), n.style.setProperty("--vplayer-media-object-fit", U(t)), _ && (ie(_), _.classList.add(`vplayer--media-${re[t]}`)), r.setState((e) => ({
914
997
  ...e,
915
998
  aspectRatio: t
916
999
  }));
917
1000
  },
918
1001
  cycleAspectRatio: () => {
919
- let e = r.state.aspectRatio, t = U[(U.indexOf(e) + 1) % U.length] ?? "default";
920
- k.setAspectRatio(t);
1002
+ let e = r.state.aspectRatio, t = H[(H.indexOf(e) + 1) % H.length] ?? "default";
1003
+ J.setAspectRatio(t);
921
1004
  },
922
1005
  toggleLoop: () => {
923
- let e = h;
1006
+ let e = v;
924
1007
  e && (e.setLooping(!e.looping), r.setState((t) => ({
925
1008
  ...t,
926
1009
  isLooping: e.looping
@@ -933,10 +1016,13 @@ function Y(e) {
933
1016
  }));
934
1017
  }
935
1018
  };
936
- function A(e) {
1019
+ function oe(e) {
937
1020
  return [
938
1021
  e.on("play", () => {
939
- E(), r.setState((e) => ({
1022
+ A = !0, r.setState((e) => ({
1023
+ ...e,
1024
+ resumeProgress: null
1025
+ })), G(), r.setState((e) => ({
940
1026
  ...e,
941
1027
  status: "playing",
942
1028
  isPlaying: !0,
@@ -954,17 +1040,23 @@ function Y(e) {
954
1040
  isPaused: !e.isEnded,
955
1041
  isBuffering: !1,
956
1042
  controlsVisible: !0
957
- }));
1043
+ })), e.ended || R(F, e);
958
1044
  }),
959
1045
  e.on("ended", () => {
960
- r.setState((e) => ({
1046
+ Y();
1047
+ for (let e = M.length - 1; e >= 0; e--) {
1048
+ let t = M[e];
1049
+ t?.kind === "save" && t.target.generation === F.generation && M.splice(e, 1);
1050
+ }
1051
+ L(F), r.setState((e) => ({
961
1052
  ...e,
962
1053
  status: "ended",
963
1054
  isPlaying: !1,
964
1055
  isPaused: !1,
965
1056
  isBuffering: !1,
966
1057
  isEnded: !0,
967
- controlsVisible: !0
1058
+ controlsVisible: !0,
1059
+ resumeProgress: null
968
1060
  })), i.onEnded?.();
969
1061
  }),
970
1062
  e.on("timeupdate", () => {
@@ -974,7 +1066,7 @@ function Y(e) {
974
1066
  })), i.onTimeUpdate?.(e.currentTime);
975
1067
  }),
976
1068
  e.on("loadedmetadata", () => {
977
- v !== null && (clearTimeout(v), v = null), S = 0, r.setState((t) => ({
1069
+ x !== null && (clearTimeout(x), x = null), S = 0, r.setState((t) => ({
978
1070
  ...t,
979
1071
  status: t.status === "idle" || t.status === "loading" || t.status === "error" ? "ready" : t.status,
980
1072
  duration: f(e.duration) ? e.duration : 0,
@@ -983,7 +1075,7 @@ function Y(e) {
983
1075
  isMuted: e.muted,
984
1076
  playbackRate: e.playbackRate,
985
1077
  error: null
986
- }));
1078
+ })), V(e);
987
1079
  }),
988
1080
  e.on("progress", () => {
989
1081
  let t = e.buffered;
@@ -1002,7 +1094,7 @@ function Y(e) {
1002
1094
  }));
1003
1095
  }),
1004
1096
  e.on("canplay", () => {
1005
- v !== null && (clearTimeout(v), v = null), S = 0, r.setState((e) => ({
1097
+ x !== null && (clearTimeout(x), x = null), S = 0, r.setState((e) => ({
1006
1098
  ...e,
1007
1099
  status: e.isPlaying ? "playing" : "ready",
1008
1100
  isBuffering: !1,
@@ -1014,7 +1106,7 @@ function Y(e) {
1014
1106
  ...t,
1015
1107
  duration: f(e.duration) ? e.duration : 0,
1016
1108
  isLive: !f(e.duration)
1017
- }));
1109
+ })), V(e);
1018
1110
  }),
1019
1111
  e.on("seeking", () => {
1020
1112
  r.setState((e) => ({
@@ -1079,31 +1171,25 @@ function Y(e) {
1079
1171
  reconnectAttempt: S,
1080
1172
  isReconnecting: !0
1081
1173
  }
1082
- })), o.emit("video:reconnecting", { attempt: S + 1 }), v !== null && clearTimeout(v), v = setTimeout(() => {
1083
- v = null, S++, o.emit("video:reconnect", { attempt: S }), e.load();
1174
+ })), o.emit("video:reconnecting", { attempt: S + 1 }), x !== null && clearTimeout(x), x = setTimeout(() => {
1175
+ x = null, S++, o.emit("video:reconnect", { attempt: S }), e.load();
1084
1176
  }, p));
1085
1177
  })
1086
1178
  ];
1087
1179
  }
1088
- function N() {
1089
- P(), _ = setInterval(() => {
1090
- let e = h;
1091
- e && s.set(M.PLAYBACK_PROGRESS, {
1092
- time: e.currentTime,
1093
- duration: e.duration
1094
- });
1095
- }, 5e3);
1180
+ function se() {
1181
+ Y(), b = setInterval(() => R(), 5e3);
1096
1182
  }
1097
- function P() {
1098
- _ !== null && (clearInterval(_), _ = null);
1183
+ function Y() {
1184
+ b !== null && (clearInterval(b), b = null);
1099
1185
  }
1100
- let F = r.subscribe(() => {
1186
+ let ce = r.subscribe(() => {
1101
1187
  let e = r.state;
1102
- e.isPlaying && e.duration > 0 ? _ === null && N() : P();
1103
- }), I = null;
1104
- function L() {
1188
+ e.isPlaying && e.duration > 0 && !e.isLive ? b === null && se() : Y();
1189
+ }), X = null;
1190
+ function le() {
1105
1191
  if (!i.persistPreferences) return;
1106
- 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);
1192
+ let e = s.get(h.VOLUME), t = s.get(h.MUTED), n = s.get(h.PLAYBACK_RATE), a = s.get(h.LOOP), o = s.get(h.FLIP), c = s.get(h.ASPECT_RATIO);
1107
1193
  r.setState((r) => ({
1108
1194
  ...r,
1109
1195
  volume: e ?? r.volume,
@@ -1111,17 +1197,17 @@ function Y(e) {
1111
1197
  playbackRate: n ?? r.playbackRate,
1112
1198
  isLooping: a ?? r.isLooping,
1113
1199
  flip: o ?? r.flip,
1114
- aspectRatio: q(c)
1115
- })), I = r.subscribe(() => {
1200
+ aspectRatio: ae(c)
1201
+ })), X = r.subscribe(() => {
1116
1202
  let e = r.state;
1117
- 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);
1203
+ e.volume !== 1 && s.set(h.VOLUME, e.volume), e.isMuted && s.set(h.MUTED, !0), e.playbackRate !== 1 && s.set(h.PLAYBACK_RATE, e.playbackRate), e.isLooping ? s.set(h.LOOP, !0) : s.remove(h.LOOP), e.flip === "normal" ? s.remove(h.FLIP) : s.set(h.FLIP, e.flip), e.aspectRatio === "default" ? s.remove(h.ASPECT_RATIO) : s.set(h.ASPECT_RATIO, e.aspectRatio);
1118
1204
  });
1119
1205
  }
1120
- function R() {
1121
- I &&= (I(), null);
1206
+ function ue() {
1207
+ X &&= (X(), null);
1122
1208
  }
1123
- function z(e) {
1124
- if (C?.abort(), C = null, !e) {
1209
+ function Z(e) {
1210
+ if (T?.abort(), T = null, !e) {
1125
1211
  r.setState((e) => ({
1126
1212
  ...e,
1127
1213
  thumbnailCues: []
@@ -1129,7 +1215,7 @@ function Y(e) {
1129
1215
  return;
1130
1216
  }
1131
1217
  let t = new AbortController();
1132
- C = t, V(e, t.signal, i.transformThumbnailVTT).then((e) => {
1218
+ T = t, B(e, t.signal, i.transformThumbnailVTT).then((e) => {
1133
1219
  t.signal.aborted || r.setState((t) => ({
1134
1220
  ...t,
1135
1221
  thumbnailCues: e
@@ -1141,21 +1227,21 @@ function Y(e) {
1141
1227
  })));
1142
1228
  });
1143
1229
  }
1144
- function B(e) {
1230
+ function de(e) {
1145
1231
  return {
1146
1232
  name: e,
1147
1233
  store: r,
1148
- remote: k,
1234
+ remote: J,
1149
1235
  events: o,
1150
1236
  storage: s,
1151
1237
  hotkeys: l,
1152
1238
  i18n: c,
1153
1239
  context: {
1154
1240
  containerRef: { get current() {
1155
- return m;
1241
+ return _;
1156
1242
  } },
1157
1243
  videoRef: { get current() {
1158
- return h?.element;
1244
+ return v?.element;
1159
1245
  } }
1160
1246
  },
1161
1247
  addControl: (e) => (r.setState((t) => ({
@@ -1224,15 +1310,15 @@ function Y(e) {
1224
1310
  }
1225
1311
  };
1226
1312
  }
1227
- let H = [];
1228
- function Y() {
1229
- v !== null && (clearTimeout(v), v = null), S = 0;
1230
- for (let e of H) e();
1231
- H = [], h && (h.destroy(), h = null, Q.engine = null);
1313
+ let Q = [];
1314
+ function fe(e = !0) {
1315
+ x !== null && (clearTimeout(x), x = null), S = 0;
1316
+ for (let e of Q) e();
1317
+ Q = [], v && (e && R(F, v), v.destroy(), v = null, $.engine = null);
1232
1318
  }
1233
- function X(e) {
1234
- let t = y(i), n = b(e, i);
1235
- h = n, Q.engine = n, H = A(n), e.playsInline = !0, e.autoplay = !!i.autoPlay, e.preload = e.preload || "metadata", r.setState((n) => ({
1319
+ function pe(e) {
1320
+ let t = C(i), n = ee(e, i);
1321
+ v = n, $.engine = n, Q = oe(n), e.playsInline = !0, e.autoplay = !!i.autoPlay, e.preload = e.preload || "metadata", r.setState((n) => ({
1236
1322
  ...n,
1237
1323
  status: "loading",
1238
1324
  source: t,
@@ -1243,31 +1329,31 @@ function Y(e) {
1243
1329
  currentTime: 0,
1244
1330
  duration: 0,
1245
1331
  bufferedPercent: 0,
1246
- capabilities: d(e, m),
1332
+ capabilities: d(e, _),
1247
1333
  error: null
1248
1334
  })), i.autoPlay && n.play();
1249
1335
  }
1250
- function Z() {
1251
- let e = h?.element;
1252
- if (e) {
1253
- for (Y(), e.pause(), e.removeAttribute("src"); e.firstChild;) e.removeChild(e.firstChild);
1254
- e.load(), X(e);
1336
+ function me(e = !0) {
1337
+ let t = v?.element;
1338
+ if (t) {
1339
+ for (fe(e), t.pause(), t.removeAttribute("src"); t.firstChild;) t.removeChild(t.firstChild);
1340
+ t.load(), pe(t);
1255
1341
  }
1256
1342
  }
1257
- let Q = {
1343
+ let $ = {
1258
1344
  store: r,
1259
- remote: k,
1345
+ remote: J,
1260
1346
  events: o,
1261
1347
  storage: s,
1262
1348
  i18n: c,
1263
1349
  hotkeys: l,
1264
1350
  engine: null,
1265
1351
  updateOptions(e) {
1266
- 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;
1267
- i = {
1352
+ 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 = {
1268
1353
  ...i,
1269
1354
  ...e
1270
- }, (r.state.isPlaying || w) && E(), r.setState((t) => {
1355
+ }, c = P(s), l = c.id !== F.id || c.store !== F.store;
1356
+ (t || l) && R(F, v), i = s, (t || l) && z(), (r.state.isPlaying || E) && G(), r.setState((t) => {
1271
1357
  let n = e.subtitles ?? t.subtitleTracks, r = e.qualities ?? t.qualities;
1272
1358
  return {
1273
1359
  ...t,
@@ -1276,18 +1362,18 @@ function Y(e) {
1276
1362
  activeSubtitle: e.subtitles ? t.activeSubtitle && e.subtitles.some((e) => e.lang === t.activeSubtitle.lang) ? t.activeSubtitle : e.subtitles.find((e) => e.default) ?? e.subtitles[0] ?? null : t.activeSubtitle,
1277
1363
  activeQuality: e.qualities ? e.qualities.includes(t.activeQuality) ? t.activeQuality : e.qualities[0] ?? "Auto" : t.activeQuality
1278
1364
  };
1279
- }), (a || o) && z(i.thumbnails), (t || n) && Z();
1365
+ }), (a || o) && Z(i.thumbnails), t || n ? me(!(t || l)) : l && v && f(v.duration) && V(v);
1280
1366
  },
1281
1367
  setThumbnails(e) {
1282
1368
  i = {
1283
1369
  ...i,
1284
1370
  thumbnails: e
1285
- }, z(e);
1371
+ }, Z(e);
1286
1372
  },
1287
1373
  initPlugins(e) {
1288
1374
  let t = [];
1289
1375
  for (let n of e) {
1290
- let e = B(n.name);
1376
+ let e = de(n.name);
1291
1377
  try {
1292
1378
  let r = n.setup?.(e);
1293
1379
  typeof r == "function" && t.push(r);
@@ -1304,10 +1390,10 @@ function Y(e) {
1304
1390
  };
1305
1391
  },
1306
1392
  mount(e, t) {
1307
- h && this.unmount(), m = t, X(e), g = O, document.addEventListener("fullscreenchange", g), document.addEventListener("webkitfullscreenchange", g), z(i.thumbnails), L(), k.setAspectRatio(r.state.aspectRatio);
1393
+ v && this.unmount(), _ = t, pe(e), y = q, document.addEventListener("fullscreenchange", y), document.addEventListener("webkitfullscreenchange", y), j = () => R(), window.addEventListener("pagehide", j), Z(i.thumbnails), le(), J.setAspectRatio(r.state.aspectRatio);
1308
1394
  },
1309
1395
  unmount() {
1310
- Y(), T(), m = null, P(), C?.abort(), C = null, R(), v !== null && (clearTimeout(v), v = null), g &&= (document.removeEventListener("fullscreenchange", g), document.removeEventListener("webkitfullscreenchange", g), null), r.setState((e) => ({
1396
+ fe(), z(), W(), _ = null, T?.abort(), T = null, ue(), x !== null && (clearTimeout(x), x = null), y &&= (document.removeEventListener("fullscreenchange", y), document.removeEventListener("webkitfullscreenchange", y), null), j &&= (window.removeEventListener("pagehide", j), null), r.setState((e) => ({
1311
1397
  ...e,
1312
1398
  status: "idle",
1313
1399
  isPlaying: !1,
@@ -1317,14 +1403,14 @@ function Y(e) {
1317
1403
  }));
1318
1404
  },
1319
1405
  destroy() {
1320
- this.unmount(), F(), o.clear();
1406
+ this.unmount(), ce(), o.clear();
1321
1407
  }
1322
1408
  };
1323
- return Q;
1409
+ return $;
1324
1410
  }
1325
1411
  //#endregion
1326
1412
  //#region src/gesture-engine.ts
1327
- function X(e, t) {
1413
+ function G(e, t) {
1328
1414
  let n = null, r = null, i = null, a = typeof window < "u" && "ontouchstart" in window;
1329
1415
  function o(t) {
1330
1416
  if (!a || t.touches.length !== 1) return;
@@ -1384,14 +1470,14 @@ function X(e, t) {
1384
1470
  }
1385
1471
  //#endregion
1386
1472
  //#region src/utils.ts
1387
- function Z(e) {
1473
+ function K(e) {
1388
1474
  if (!Number.isFinite(e) || e < 0) return "0:00";
1389
1475
  let t = Math.floor(e / 3600), n = Math.floor(e % 3600 / 60), r = Math.floor(e % 60);
1390
1476
  return t > 0 ? `${t}:${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}` : `${n}:${String(r).padStart(2, "0")}`;
1391
1477
  }
1392
1478
  //#endregion
1393
1479
  //#region src/defaults.ts
1394
- var Q = {
1480
+ var q = {
1395
1481
  play: "Play",
1396
1482
  pause: "Pause",
1397
1483
  replay: "Replay",
@@ -1422,7 +1508,7 @@ var Q = {
1422
1508
  aspectRatioCover: "Crop / Cover",
1423
1509
  aspectRatio21: "21 : 9",
1424
1510
  continue: "Continue",
1425
- continuePlay: "Continue from",
1511
+ continuePlay: "Continue playing at",
1426
1512
  continueStartOver: "Start over",
1427
1513
  error: "An error occurred",
1428
1514
  retry: "Retry",
@@ -1433,4 +1519,4 @@ var Q = {
1433
1519
  loop: "Loop"
1434
1520
  };
1435
1521
  //#endregion
1436
- export { p as BaseMediaEngine, g as DashMediaEngine, t as EventBus, h as HlsMediaEngine, n as HotkeyRegistry, a as I18n, m as NativeVideoEngine, M as STORAGE_KEYS, j as Storage, c as canUseFullscreen, o as canUseMSE, s as canUseNativeHLS, l as canUsePictureInPicture, u as canUseTextTracks, X as createGestureEngine, x as createMediaStore, Y as createPlayer, b as createResolvedMediaEngine, Q as defaultPlayerLabels, v as detectSourceKind, I as fetchSubtitles, V as fetchThumbnails, Z as formatTime, L as getActiveCue, S as getInitialMediaState, d as getMediaCapabilities, H as getThumbnailAtTime, f as isFiniteDuration, P as parseSRT, z as parseThumbnailVTT, N as parseTimestamp, F as parseVTT, w as selectAudio, k as selectError, C as selectMedia, D as selectPlugins, T as selectPreferences, O as selectThumbnails, E as selectUI, y as toPlayerSource };
1522
+ export { _ as BaseMediaEngine, b as DashMediaEngine, t as EventBus, y as HlsMediaEngine, n as HotkeyRegistry, a as I18n, g as LocalPlaybackProgressStore, v as NativeVideoEngine, h as STORAGE_KEYS, m as Storage, c as canUseFullscreen, o as canUseMSE, s as canUseNativeHLS, l as canUsePictureInPicture, u as canUseTextTracks, G as createGestureEngine, w as createMediaStore, W as createPlayer, ee as createResolvedMediaEngine, q as defaultPlayerLabels, S as detectSourceKind, I as fetchSubtitles, B as fetchThumbnails, K as formatTime, te as getActiveCue, T as getInitialMediaState, d as getMediaCapabilities, V as getThumbnailAtTime, f as isFiniteDuration, P as parseSRT, R as parseThumbnailVTT, N as parseTimestamp, F as parseVTT, D as selectAudio, M as selectError, E as selectMedia, A as selectPlugins, O as selectPreferences, j as selectThumbnails, k as selectUI, C as toPlayerSource };
@@ -0,0 +1,21 @@
1
+ import { Storage } from './storage';
2
+ export interface PlaybackProgress {
3
+ time: number;
4
+ duration: number;
5
+ }
6
+ export interface PlaybackProgressStore {
7
+ load(mediaId: string): Promise<PlaybackProgress | null>;
8
+ save(mediaId: string, progress: PlaybackProgress): Promise<void>;
9
+ clear(mediaId: string): Promise<void>;
10
+ }
11
+ export interface PlaybackProgressOptions {
12
+ id?: string;
13
+ store?: PlaybackProgressStore;
14
+ }
15
+ export declare class LocalPlaybackProgressStore implements PlaybackProgressStore {
16
+ private readonly storage;
17
+ constructor(storage?: Storage);
18
+ load(mediaId: string): Promise<PlaybackProgress | null>;
19
+ save(mediaId: string, progress: PlaybackProgress): Promise<void>;
20
+ clear(mediaId: string): Promise<void>;
21
+ }
@@ -8,6 +8,8 @@ export interface RemoteRef {
8
8
  pause: () => void;
9
9
  togglePlay: () => void;
10
10
  seek: (time: number) => void;
11
+ resumeFromSavedProgress: () => void;
12
+ startPlaybackOver: () => void;
11
13
  skip: (seconds: number) => void;
12
14
  setVolume: (v: number) => void;
13
15
  toggleMute: () => void;
@@ -12,6 +12,7 @@
12
12
  */
13
13
  import { Store } from '@tanstack/store';
14
14
  import type { MediaCapabilitiesSnapshot } from '../media-capabilities';
15
+ import type { PlaybackProgress } from '../playback-progress';
15
16
  import type { ControlRegistration, ContextMenuItem, FlipState, AspectRatioState } from '../plugin-api';
16
17
  import type { LayerRegistration, SettingRegistration } from '../plugin-api';
17
18
  import type { PlayerSource } from '../source-resolver';
@@ -70,13 +71,16 @@ export interface ErrorSlice {
70
71
  isReconnecting: boolean;
71
72
  } | null;
72
73
  }
74
+ export interface PlaybackProgressSlice {
75
+ resumeProgress: PlaybackProgress | null;
76
+ }
73
77
  /**
74
78
  * Full MediaState — flat at runtime, logically organized here.
75
79
  *
76
80
  * Consumers can reference MediaState or individual slices depending
77
81
  * on how granular they need to be.
78
82
  */
79
- export interface MediaState extends MediaSlice, AudioSlice, PreferencesSlice, UISlice, PluginSlice, ThumbnailSlice, ErrorSlice {
83
+ export interface MediaState extends MediaSlice, AudioSlice, PreferencesSlice, UISlice, PluginSlice, ThumbnailSlice, ErrorSlice, PlaybackProgressSlice {
80
84
  }
81
85
  export type PlayerStore = Store<MediaState>;
82
86
  export declare function createMediaStore(): PlayerStore;
@@ -93,6 +97,7 @@ export declare const selectMedia: (s: MediaState) => {
93
97
  bufferedPercent: number;
94
98
  playbackRate: number;
95
99
  isLive: boolean;
100
+ resumeProgress: PlaybackProgress | null;
96
101
  };
97
102
  export declare const selectAudio: (s: MediaState) => {
98
103
  volume: number;
package/dist/storage.d.ts CHANGED
@@ -18,7 +18,6 @@ export declare const STORAGE_KEYS: {
18
18
  readonly PLAYBACK_RATE: "playbackRate";
19
19
  readonly SUBTITLE_LANG: "subtitleLang";
20
20
  readonly QUALITY: "quality";
21
- readonly PLAYBACK_PROGRESS: "playbackProgress";
22
21
  readonly LOOP: "loop";
23
22
  readonly FLIP: "flip";
24
23
  readonly ASPECT_RATIO: "aspectRatio";
package/dist/types.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { HotkeyRegistry } from './hotkey-registry';
4
4
  import type { I18n } from './i18n';
5
5
  import type { MediaCapabilitiesSnapshot } from './media-capabilities';
6
6
  import type { DashMediaEngineOptions, HlsMediaEngineOptions, MediaEngine } from './media-engine';
7
+ import type { PlaybackProgressOptions } from './playback-progress';
7
8
  import type { PlayerPlugin } from './plugin-api';
8
9
  import type { AspectRatioState, FlipState } from './plugin-api';
9
10
  import type { PlayerSource } from './source-resolver';
@@ -28,6 +29,7 @@ export interface PlayerOptions {
28
29
  lang?: string;
29
30
  translations?: Record<string, string>;
30
31
  plugins?: PlayerPlugin[];
32
+ playbackProgress?: PlaybackProgressOptions;
31
33
  persistPreferences?: boolean;
32
34
  defaultHotkeys?: boolean;
33
35
  reconnectMax?: number;
@@ -76,6 +78,8 @@ export interface MediaRemote {
76
78
  play: () => void;
77
79
  pause: () => void;
78
80
  togglePlay: () => void;
81
+ resumeFromSavedProgress: () => void;
82
+ startPlaybackOver: () => void;
79
83
  seek: (time: number) => void;
80
84
  skip: (seconds: number) => void;
81
85
  setVolume: (v: number) => void;
@@ -148,7 +152,7 @@ export interface PlayerInstance {
148
152
  hotkeys: HotkeyRegistry;
149
153
  /** The active media engine. Null before mount and after unmount. */
150
154
  engine: MediaEngine | null;
151
- updateOptions(opts: Partial<Pick<PlayerOptions, 'src' | 'type' | 'title' | 'poster' | 'autoPlay' | 'subtitles' | 'qualities' | 'thumbnails' | 'transformThumbnailVTT'>>): void;
155
+ updateOptions(opts: Partial<Pick<PlayerOptions, 'src' | 'type' | 'title' | 'poster' | 'autoPlay' | 'subtitles' | 'qualities' | 'thumbnails' | 'transformThumbnailVTT' | 'playbackProgress'>>): void;
152
156
  setThumbnails(url?: string): void;
153
157
  initPlugins(plugins: PlayerPlugin[]): () => void;
154
158
  mount(videoEl: HTMLVideoElement, containerEl: HTMLDivElement): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vplayer/core",
3
- "version": "0.1.8",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",