itube-modern-player 0.7.2 → 0.8.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/vue.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { PropType } from 'vue';
2
- import { Player } from './index';
3
- import { PlayerOptions, VideoSource } from './types';
2
+ import { Player } from './player';
3
+ import { AdsOptions, PlayerOptions, VideoSource } from './types';
4
+ /** `adConfig` may be an async factory — resolved in parallel with the core chunk. */
5
+ export type AdConfigInput = AdsOptions | (() => AdsOptions | undefined | Promise<AdsOptions | undefined>);
4
6
  export declare const ITubePlayer: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
5
7
  /** Single video or a playlist array. */
6
8
  source: {
@@ -8,11 +10,40 @@ export declare const ITubePlayer: import('vue').DefineComponent<import('vue').Ex
8
10
  required: false;
9
11
  default: undefined;
10
12
  };
11
- /** Everything else from PlayerOptions (source inside is ignored in favor of the prop). */
13
+ /** Everything else from PlayerOptions (source/adConfig inside are superseded by the props). */
12
14
  options: {
13
15
  type: PropType<Omit<PlayerOptions, "source">>;
14
16
  default: () => {};
15
17
  };
18
+ /**
19
+ * Poster-first, player-on-intent: SSR-able declarative placeholder, core
20
+ * chunk + CSS fetched on the `loadOn` trigger, swap via reactive state.
21
+ */
22
+ lazy: {
23
+ type: BooleanConstructor;
24
+ default: boolean;
25
+ };
26
+ /**
27
+ * When `lazy`, what fetches the core chunk:
28
+ * `'placeholder'` (default) — any interaction with the placeholder area;
29
+ * `'interaction'` — first interaction anywhere on the page;
30
+ * `'visible'` — placeholder enters the viewport;
31
+ * `'immediate'` — right away (still a split chunk).
32
+ * A click on the placeholder always loads AND starts playback.
33
+ */
34
+ loadOn: {
35
+ type: PropType<"placeholder" | "interaction" | "visible" | "immediate">;
36
+ default: string;
37
+ };
38
+ /**
39
+ * Ad configuration, or an async factory (e.g. a VAST-tag lookup). A factory
40
+ * runs in parallel with the core-chunk download; the player is constructed
41
+ * with the resolved config. Takes precedence over `options.adConfig`.
42
+ */
43
+ adConfig: {
44
+ type: PropType<AdConfigInput>;
45
+ default: undefined;
46
+ };
16
47
  }>, () => import('vue').VNode<import('vue').RendererNode, import('vue').RendererElement, {
17
48
  [key: string]: any;
18
49
  }>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, Record<"pause" | "ended" | "play" | "progress" | "aderror" | "ready" | "timeupdate" | "volumechange" | "ratechange" | "seeking" | "seeked" | "sourcechange" | "playlistitemchange" | "advancerequest" | "chapterchange" | "scenetypechange" | "fullscreenchange" | "pipchange" | "qualitychange" | "subtitlechange" | "relatedshow" | "relatedclick" | "action" | "customaction" | "adstart" | "adend" | "adskip" | "adclick" | "adpause" | "adresume" | "error" | "destroy", (payload: unknown) => boolean>, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
@@ -22,11 +53,40 @@ export declare const ITubePlayer: import('vue').DefineComponent<import('vue').Ex
22
53
  required: false;
23
54
  default: undefined;
24
55
  };
25
- /** Everything else from PlayerOptions (source inside is ignored in favor of the prop). */
56
+ /** Everything else from PlayerOptions (source/adConfig inside are superseded by the props). */
26
57
  options: {
27
58
  type: PropType<Omit<PlayerOptions, "source">>;
28
59
  default: () => {};
29
60
  };
61
+ /**
62
+ * Poster-first, player-on-intent: SSR-able declarative placeholder, core
63
+ * chunk + CSS fetched on the `loadOn` trigger, swap via reactive state.
64
+ */
65
+ lazy: {
66
+ type: BooleanConstructor;
67
+ default: boolean;
68
+ };
69
+ /**
70
+ * When `lazy`, what fetches the core chunk:
71
+ * `'placeholder'` (default) — any interaction with the placeholder area;
72
+ * `'interaction'` — first interaction anywhere on the page;
73
+ * `'visible'` — placeholder enters the viewport;
74
+ * `'immediate'` — right away (still a split chunk).
75
+ * A click on the placeholder always loads AND starts playback.
76
+ */
77
+ loadOn: {
78
+ type: PropType<"placeholder" | "interaction" | "visible" | "immediate">;
79
+ default: string;
80
+ };
81
+ /**
82
+ * Ad configuration, or an async factory (e.g. a VAST-tag lookup). A factory
83
+ * runs in parallel with the core-chunk download; the player is constructed
84
+ * with the resolved config. Takes precedence over `options.adConfig`.
85
+ */
86
+ adConfig: {
87
+ type: PropType<AdConfigInput>;
88
+ default: undefined;
89
+ };
30
90
  }>> & Readonly<{
31
91
  onPause?: ((payload: unknown) => any) | undefined;
32
92
  onEnded?: ((payload: unknown) => any) | undefined;
@@ -62,8 +122,11 @@ export declare const ITubePlayer: import('vue').DefineComponent<import('vue').Ex
62
122
  onDestroy?: ((payload: unknown) => any) | undefined;
63
123
  }>, {
64
124
  source: VideoSource | VideoSource[];
125
+ adConfig: AdConfigInput;
65
126
  options: Omit<PlayerOptions, "source">;
127
+ lazy: boolean;
128
+ loadOn: "placeholder" | "interaction" | "visible" | "immediate";
66
129
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
67
130
  export default ITubePlayer;
68
- export { Player };
131
+ export type { Player };
69
132
  export type * from './types';
package/dist/vue.js CHANGED
@@ -1,7 +1,6 @@
1
- import { defineComponent as m, ref as s, shallowRef as h, onMounted as y, watch as g, onBeforeUnmount as S, h as o } from "vue";
2
- import "./index.js";
3
- import { Player as b } from "./core.js";
4
- const c = [
1
+ import { defineComponent as N, ref as u, shallowRef as j, computed as E, onMounted as q, watch as M, onBeforeUnmount as B, h as d } from "vue";
2
+ import { renderPlaceholder as H } from "./placeholder.js";
3
+ const O = [
5
4
  "ready",
6
5
  "play",
7
6
  "pause",
@@ -34,7 +33,12 @@ const c = [
34
33
  "aderror",
35
34
  "error",
36
35
  "destroy"
37
- ], P = m({
36
+ ];
37
+ function R() {
38
+ return import("itube-modern-player/style.css").catch(() => {
39
+ });
40
+ }
41
+ const V = N({
38
42
  name: "ITubePlayer",
39
43
  props: {
40
44
  /** Single video or a playlist array. */
@@ -43,49 +47,138 @@ const c = [
43
47
  required: !1,
44
48
  default: void 0
45
49
  },
46
- /** Everything else from PlayerOptions (source inside is ignored in favor of the prop). */
50
+ /** Everything else from PlayerOptions (source/adConfig inside are superseded by the props). */
47
51
  options: {
48
52
  type: Object,
49
53
  default: () => ({})
54
+ },
55
+ /**
56
+ * Poster-first, player-on-intent: SSR-able declarative placeholder, core
57
+ * chunk + CSS fetched on the `loadOn` trigger, swap via reactive state.
58
+ */
59
+ lazy: {
60
+ type: Boolean,
61
+ default: !1
62
+ },
63
+ /**
64
+ * When `lazy`, what fetches the core chunk:
65
+ * `'placeholder'` (default) — any interaction with the placeholder area;
66
+ * `'interaction'` — first interaction anywhere on the page;
67
+ * `'visible'` — placeholder enters the viewport;
68
+ * `'immediate'` — right away (still a split chunk).
69
+ * A click on the placeholder always loads AND starts playback.
70
+ */
71
+ loadOn: {
72
+ type: String,
73
+ default: "placeholder"
74
+ },
75
+ /**
76
+ * Ad configuration, or an async factory (e.g. a VAST-tag lookup). A factory
77
+ * runs in parallel with the core-chunk download; the player is constructed
78
+ * with the resolved config. Takes precedence over `options.adConfig`.
79
+ */
80
+ adConfig: {
81
+ type: [Object, Function],
82
+ default: void 0
50
83
  }
51
84
  },
52
- emits: [...c],
53
- setup(r, { emit: d, slots: n, expose: i }) {
54
- const t = s(null), u = s(null), e = h(null);
55
- y(() => {
56
- if (!t.value) return;
57
- const a = new b(t.value, {
58
- ...r.options,
59
- source: r.source,
60
- // The slot wins over whatever was passed in options.
61
- ...n.pauseScreen ? { pauseScreen: !0 } : {}
62
- }), v = d;
63
- for (const l of c)
64
- a.on(l, (f) => v(l, f));
65
- n.pauseScreen && u.value && a.setPauseScreenContent(u.value), e.value = a;
66
- }), g(
67
- () => r.source,
68
- (a) => {
69
- a && e.value && e.value.load(a);
85
+ emits: [...O],
86
+ setup(e, { emit: P, slots: r, expose: C }) {
87
+ const f = u(null), b = u(null), p = u(null), a = j(null), w = u(!1);
88
+ let v = !1, y = !1, m = !1;
89
+ const l = [], S = () => {
90
+ for (const t of l.splice(0)) t();
91
+ }, I = E(() => Array.isArray(e.source) ? e.source[0] : e.source), A = E(
92
+ () => H(
93
+ {
94
+ poster: I.value?.poster,
95
+ icon: e.options.icons?.bigPlay,
96
+ styling: e.options.styling,
97
+ className: e.options.className
98
+ },
99
+ { inlineStyles: !0 }
100
+ )
101
+ ), c = (t) => {
102
+ if (m || (m = t), y || v) return;
103
+ y = !0, S();
104
+ const o = e.adConfig ?? e.options.adConfig, s = Promise.resolve(
105
+ typeof o == "function" ? o() : o
106
+ ).catch(() => {
107
+ });
108
+ Promise.all([import("./index.js"), s, R()]).then(
109
+ ([{ Player: n }, i]) => {
110
+ if (v || !f.value) return;
111
+ const h = {
112
+ ...e.options,
113
+ source: e.source,
114
+ ...i ? { adConfig: i } : {},
115
+ // The slot wins over whatever was passed in options.
116
+ ...r.pauseScreen ? { pauseScreen: !0 } : {}
117
+ };
118
+ e.lazy && (h.playOnInit = m, h.autoplay = m);
119
+ const g = new n(f.value, h), T = P;
120
+ for (const k of O)
121
+ g.on(k, (z) => T(k, z));
122
+ r.pauseScreen && p.value && g.setPauseScreenContent(p.value), a.value = g, w.value = !0;
123
+ },
124
+ (n) => {
125
+ y = !1, console.error("[itube-player] failed to load the player chunk", n);
126
+ }
127
+ );
128
+ };
129
+ q(() => {
130
+ if (!e.lazy) {
131
+ c(!1);
132
+ return;
133
+ }
134
+ const t = b.value;
135
+ if (e.loadOn === "immediate" || !t) {
136
+ c(!1);
137
+ return;
138
+ }
139
+ const o = () => c(!1);
140
+ if (e.loadOn === "interaction") {
141
+ const s = ["pointerdown", "keydown", "touchstart", "wheel"];
142
+ for (const n of s)
143
+ document.addEventListener(n, o, { once: !0, passive: !0 }), l.push(() => document.removeEventListener(n, o));
144
+ } else if (e.loadOn === "visible" && typeof IntersectionObserver < "u") {
145
+ const s = new IntersectionObserver((n) => {
146
+ n.some((i) => i.isIntersecting) && o();
147
+ });
148
+ s.observe(t), l.push(() => s.disconnect());
149
+ } else {
150
+ const s = ["pointerenter", "pointerdown", "touchstart", "focusin"];
151
+ for (const n of s)
152
+ t.addEventListener(n, o, { once: !0, passive: !0 }), l.push(() => t.removeEventListener(n, o));
153
+ }
154
+ }), M(
155
+ () => e.source,
156
+ (t) => {
157
+ t && a.value && a.value.load(t);
70
158
  },
71
159
  { deep: !0 }
72
- ), S(() => {
73
- e.value?.destroy(), e.value = null;
74
- }), i({ player: e });
75
- const p = () => {
76
- e.value?.play();
160
+ ), B(() => {
161
+ v = !0, S(), a.value?.destroy(), a.value = null;
162
+ }), C({ player: a });
163
+ const L = () => {
164
+ a.value?.play();
165
+ };
166
+ return () => {
167
+ const t = [];
168
+ return e.lazy && !w.value && t.push(
169
+ d("div", {
170
+ key: "imp-ph",
171
+ ref: b,
172
+ class: "imp-vue-placeholder",
173
+ innerHTML: A.value,
174
+ onClick: () => c(!0)
175
+ })
176
+ ), t.push(d("div", { key: "imp-mount", ref: f })), r.pauseScreen && t.push(d("div", { ref: p, class: "imp-vue-pause-slot" }, r.pauseScreen({ player: a.value, close: L }))), d("div", { class: "imp-vue-host" }, t);
77
177
  };
78
- return () => o("div", { class: "imp-vue-host" }, [
79
- o("div", { ref: t }),
80
- // Scoped slot: `player` (reactive — null until mounted, then the
81
- // instance) and `close()` are available to the custom pause content.
82
- n.pauseScreen ? o("div", { ref: u, class: "imp-vue-pause-slot" }, n.pauseScreen({ player: e.value, close: p })) : null
83
- ]);
84
178
  }
85
179
  });
86
180
  export {
87
- P as ITubePlayer,
88
- b as Player,
89
- P as default
181
+ V as ITubePlayer,
182
+ V as default
90
183
  };
91
184
  //# sourceMappingURL=vue.js.map
package/dist/vue.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"vue.js","sources":["../src/vue.ts"],"sourcesContent":["/**\n * Vue 3 wrapper — a separate entry (`itube-modern-player/vue`) so that plain\n * JS consumers never pull Vue into their graph.\n *\n * ```vue\n * <ITubePlayer :source=\"video\" :options=\"{ seekStep: 5 }\" @ended=\"onEnded\">\n * <template #pauseScreen>\n * <MyPromo />\n * </template>\n * </ITubePlayer>\n * ```\n */\nimport {\n defineComponent,\n h,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowRef,\n watch,\n type PropType,\n} from 'vue'\n// Import via the main entry so the built-in locales are registered for Vue users too.\nimport { Player } from './index'\nimport type { PlayerEventMap, PlayerOptions, VideoSource } from './types'\n\nconst EVENT_NAMES = [\n 'ready', 'play', 'pause', 'ended', 'timeupdate', 'progress', 'volumechange',\n 'ratechange', 'seeking', 'seeked', 'sourcechange', 'playlistitemchange', 'advancerequest',\n 'chapterchange', 'scenetypechange', 'fullscreenchange', 'pipchange', 'qualitychange',\n 'subtitlechange', 'relatedshow', 'relatedclick', 'action', 'customaction',\n 'adstart', 'adend', 'adskip', 'adclick', 'adpause', 'adresume', 'aderror', 'error', 'destroy',\n] as const satisfies ReadonlyArray<keyof PlayerEventMap>\n\nexport const ITubePlayer = defineComponent({\n name: 'ITubePlayer',\n props: {\n /** Single video or a playlist array. */\n source: {\n type: [Object, Array] as PropType<VideoSource | VideoSource[]>,\n required: false,\n default: undefined,\n },\n /** Everything else from PlayerOptions (source inside is ignored in favor of the prop). */\n options: {\n type: Object as PropType<Omit<PlayerOptions, 'source'>>,\n default: () => ({}),\n },\n },\n emits: [...EVENT_NAMES] as unknown as Record<(typeof EVENT_NAMES)[number], (payload: unknown) => boolean>,\n setup(props, { emit, slots, expose }) {\n const mountEl = ref<HTMLElement | null>(null)\n const slotEl = ref<HTMLElement | null>(null)\n const player = shallowRef<Player | null>(null)\n\n onMounted(() => {\n if (!mountEl.value) return\n const instance = new Player(mountEl.value, {\n ...props.options,\n source: props.source,\n // The slot wins over whatever was passed in options.\n ...(slots.pauseScreen ? { pauseScreen: true } : {}),\n })\n const forward = emit as (event: string, payload?: unknown) => void\n for (const name of EVENT_NAMES) {\n instance.on(name, (payload) => forward(name, payload))\n }\n if (slots.pauseScreen && slotEl.value) {\n instance.setPauseScreenContent(slotEl.value)\n }\n player.value = instance\n })\n\n watch(\n () => props.source,\n (next) => {\n if (next && player.value) player.value.load(next)\n },\n { deep: true },\n )\n\n onBeforeUnmount(() => {\n player.value?.destroy()\n player.value = null\n })\n\n expose({ player })\n\n // \"Close & continue\" — resume playback; the player hides the pause screen\n // on the `play` event, so this both closes the custom block and plays.\n const close = () => {\n void player.value?.play()\n }\n\n return () =>\n h('div', { class: 'imp-vue-host' }, [\n h('div', { ref: mountEl }),\n // Scoped slot: `player` (reactive — null until mounted, then the\n // instance) and `close()` are available to the custom pause content.\n slots.pauseScreen\n ? h('div', { ref: slotEl, class: 'imp-vue-pause-slot' }, slots.pauseScreen({ player: player.value, close }))\n : null,\n ])\n },\n})\n\nexport default ITubePlayer\nexport { Player }\nexport type * from './types'\n"],"names":["EVENT_NAMES","ITubePlayer","defineComponent","props","emit","slots","expose","mountEl","ref","slotEl","player","shallowRef","onMounted","instance","Player","forward","name","payload","watch","next","onBeforeUnmount","close","h"],"mappings":";;;AA0BA,MAAMA,IAAc;AAAA,EAClB;AAAA,EAAS;AAAA,EAAQ;AAAA,EAAS;AAAA,EAAS;AAAA,EAAc;AAAA,EAAY;AAAA,EAC7D;AAAA,EAAc;AAAA,EAAW;AAAA,EAAU;AAAA,EAAgB;AAAA,EAAsB;AAAA,EACzE;AAAA,EAAiB;AAAA,EAAmB;AAAA,EAAoB;AAAA,EAAa;AAAA,EACrE;AAAA,EAAkB;AAAA,EAAe;AAAA,EAAgB;AAAA,EAAU;AAAA,EAC3D;AAAA,EAAW;AAAA,EAAS;AAAA,EAAU;AAAA,EAAW;AAAA,EAAW;AAAA,EAAY;AAAA,EAAW;AAAA,EAAS;AACtF,GAEaC,IAAcC,EAAgB;AAAA,EACzC,MAAM;AAAA,EACN,OAAO;AAAA;AAAA,IAEL,QAAQ;AAAA,MACN,MAAM,CAAC,QAAQ,KAAK;AAAA,MACpB,UAAU;AAAA,MACV,SAAS;AAAA,IAAA;AAAA;AAAA,IAGX,SAAS;AAAA,MACP,MAAM;AAAA,MACN,SAAS,OAAO,CAAA;AAAA,IAAC;AAAA,EACnB;AAAA,EAEF,OAAO,CAAC,GAAGF,CAAW;AAAA,EACtB,MAAMG,GAAO,EAAE,MAAAC,GAAM,OAAAC,GAAO,QAAAC,KAAU;AACpC,UAAMC,IAAUC,EAAwB,IAAI,GACtCC,IAASD,EAAwB,IAAI,GACrCE,IAASC,EAA0B,IAAI;AAE7C,IAAAC,EAAU,MAAM;AACd,UAAI,CAACL,EAAQ,MAAO;AACpB,YAAMM,IAAW,IAAIC,EAAOP,EAAQ,OAAO;AAAA,QACzC,GAAGJ,EAAM;AAAA,QACT,QAAQA,EAAM;AAAA;AAAA,QAEd,GAAIE,EAAM,cAAc,EAAE,aAAa,GAAA,IAAS,CAAA;AAAA,MAAC,CAClD,GACKU,IAAUX;AAChB,iBAAWY,KAAQhB;AACjB,QAAAa,EAAS,GAAGG,GAAM,CAACC,MAAYF,EAAQC,GAAMC,CAAO,CAAC;AAEvD,MAAIZ,EAAM,eAAeI,EAAO,SAC9BI,EAAS,sBAAsBJ,EAAO,KAAK,GAE7CC,EAAO,QAAQG;AAAA,IACjB,CAAC,GAEDK;AAAA,MACE,MAAMf,EAAM;AAAA,MACZ,CAACgB,MAAS;AACR,QAAIA,KAAQT,EAAO,SAAOA,EAAO,MAAM,KAAKS,CAAI;AAAA,MAClD;AAAA,MACA,EAAE,MAAM,GAAA;AAAA,IAAK,GAGfC,EAAgB,MAAM;AACpB,MAAAV,EAAO,OAAO,QAAA,GACdA,EAAO,QAAQ;AAAA,IACjB,CAAC,GAEDJ,EAAO,EAAE,QAAAI,GAAQ;AAIjB,UAAMW,IAAQ,MAAM;AAClB,MAAKX,EAAO,OAAO,KAAA;AAAA,IACrB;AAEA,WAAO,MACLY,EAAE,OAAO,EAAE,OAAO,kBAAkB;AAAA,MAClCA,EAAE,OAAO,EAAE,KAAKf,GAAS;AAAA;AAAA;AAAA,MAGzBF,EAAM,cACFiB,EAAE,OAAO,EAAE,KAAKb,GAAQ,OAAO,qBAAA,GAAwBJ,EAAM,YAAY,EAAE,QAAQK,EAAO,OAAO,OAAAW,EAAA,CAAO,CAAC,IACzG;AAAA,IAAA,CACL;AAAA,EACL;AACF,CAAC;"}
1
+ {"version":3,"file":"vue.js","sources":["../src/vue.ts"],"sourcesContent":["/**\n * Vue 3 wrapper — a separate entry (`itube-modern-player/vue`).\n *\n * The heavy player core is loaded with a DYNAMIC import — importing this\n * module costs a few KB, not the whole player. Two modes:\n *\n * - default: the core chunk is fetched on mount (imperceptible vs the old\n * static import, but it no longer bloats the consumer's entry bundle);\n * - `lazy`: poster-first, player-on-intent. The component renders a\n * declarative SSR-able placeholder (the poster ships in the first HTML and\n * is the LCP candidate — no JS needed), fetches the core chunk + CSS on the\n * `loadOn` trigger, and swaps via reactive state. No `<ClientOnly>` needed.\n *\n * ```vue\n * <ITubePlayer lazy :source=\"video\" :options=\"{ seekStep: 5 }\"\n * :ad-config=\"fetchVast\" @ended=\"onEnded\" />\n * ```\n */\nimport {\n computed,\n defineComponent,\n h,\n onBeforeUnmount,\n onMounted,\n ref,\n shallowRef,\n watch,\n type PropType,\n} from 'vue'\nimport { renderPlaceholder } from './placeholder'\nimport type { Player } from './player'\nimport type { AdsOptions, PlayerEventMap, PlayerOptions, VideoSource } from './types'\n\n/** `adConfig` may be an async factory — resolved in parallel with the core chunk. */\nexport type AdConfigInput = AdsOptions | (() => AdsOptions | undefined | Promise<AdsOptions | undefined>)\n\nconst EVENT_NAMES = [\n 'ready', 'play', 'pause', 'ended', 'timeupdate', 'progress', 'volumechange',\n 'ratechange', 'seeking', 'seeked', 'sourcechange', 'playlistitemchange', 'advancerequest',\n 'chapterchange', 'scenetypechange', 'fullscreenchange', 'pipchange', 'qualitychange',\n 'subtitlechange', 'relatedshow', 'relatedclick', 'action', 'customaction',\n 'adstart', 'adend', 'adskip', 'adclick', 'adpause', 'adresume', 'aderror', 'error', 'destroy',\n] as const satisfies ReadonlyArray<keyof PlayerEventMap>\n\n/**\n * Best-effort load of the player stylesheet next to the core chunk. Resolved\n * via the package's own exports (self-reference) — works in Vite/webpack/Nuxt.\n * If the consumer already imports `itube-modern-player/style.css` themselves,\n * bundlers dedupe it; if the resolution fails in an exotic setup, the manual\n * import keeps working as before.\n */\nfunction loadStyles(): Promise<unknown> {\n return import('itube-modern-player/style.css').catch(() => undefined)\n}\n\nexport const ITubePlayer = defineComponent({\n name: 'ITubePlayer',\n props: {\n /** Single video or a playlist array. */\n source: {\n type: [Object, Array] as PropType<VideoSource | VideoSource[]>,\n required: false,\n default: undefined,\n },\n /** Everything else from PlayerOptions (source/adConfig inside are superseded by the props). */\n options: {\n type: Object as PropType<Omit<PlayerOptions, 'source'>>,\n default: () => ({}),\n },\n /**\n * Poster-first, player-on-intent: SSR-able declarative placeholder, core\n * chunk + CSS fetched on the `loadOn` trigger, swap via reactive state.\n */\n lazy: {\n type: Boolean,\n default: false,\n },\n /**\n * When `lazy`, what fetches the core chunk:\n * `'placeholder'` (default) — any interaction with the placeholder area;\n * `'interaction'` — first interaction anywhere on the page;\n * `'visible'` — placeholder enters the viewport;\n * `'immediate'` — right away (still a split chunk).\n * A click on the placeholder always loads AND starts playback.\n */\n loadOn: {\n type: String as PropType<'placeholder' | 'interaction' | 'visible' | 'immediate'>,\n default: 'placeholder',\n },\n /**\n * Ad configuration, or an async factory (e.g. a VAST-tag lookup). A factory\n * runs in parallel with the core-chunk download; the player is constructed\n * with the resolved config. Takes precedence over `options.adConfig`.\n */\n adConfig: {\n type: [Object, Function] as PropType<AdConfigInput>,\n default: undefined,\n },\n },\n emits: [...EVENT_NAMES] as unknown as Record<(typeof EVENT_NAMES)[number], (payload: unknown) => boolean>,\n setup(props, { emit, slots, expose }) {\n const mountEl = ref<HTMLElement | null>(null)\n const phEl = ref<HTMLElement | null>(null)\n const slotEl = ref<HTMLElement | null>(null)\n const player = shallowRef<Player | null>(null)\n /** Player constructed → the placeholder unmounts (reactive swap, no DOM adoption). */\n const playerReady = ref(false)\n\n let destroyed = false\n let loading = false\n let wantAutoplay = false\n const disposers: Array<() => void> = []\n const cleanupTriggers = () => {\n for (const d of disposers.splice(0)) d()\n }\n\n const firstSource = computed(() => (Array.isArray(props.source) ? props.source[0] : props.source))\n /** Declarative placeholder markup — self-contained (inline styles, no style.css needed). */\n const placeholderHtml = computed(() =>\n renderPlaceholder(\n {\n poster: firstSource.value?.poster,\n icon: props.options.icons?.bigPlay,\n styling: props.options.styling,\n className: props.options.className,\n },\n { inlineStyles: true },\n ),\n )\n\n const load = (autoplay: boolean) => {\n wantAutoplay ||= autoplay\n if (loading || destroyed) return\n loading = true\n cleanupTriggers()\n const adConfigInput = props.adConfig ?? props.options.adConfig\n const adConfigPromise = Promise.resolve(\n typeof adConfigInput === 'function' ? adConfigInput() : adConfigInput,\n ).catch(() => undefined)\n void Promise.all([import('./index'), adConfigPromise, loadStyles()]).then(\n ([{ Player: PlayerCtor }, adCfg]) => {\n if (destroyed || !mountEl.value) return\n const opts: PlayerOptions = {\n ...props.options,\n source: props.source,\n ...(adCfg ? { adConfig: adCfg } : {}),\n // The slot wins over whatever was passed in options.\n ...(slots.pauseScreen ? { pauseScreen: true } : {}),\n }\n if (props.lazy) {\n // Lazy semantics: only an explicit placeholder click autoplays —\n // background triggers must stay invisible (same as createLazyPlayer).\n opts.playOnInit = wantAutoplay\n opts.autoplay = wantAutoplay\n }\n const instance = new PlayerCtor(mountEl.value, opts)\n const forward = emit as (event: string, payload?: unknown) => void\n for (const name of EVENT_NAMES) {\n instance.on(name, (payload) => forward(name, payload))\n }\n if (slots.pauseScreen && slotEl.value) {\n instance.setPauseScreenContent(slotEl.value)\n }\n player.value = instance\n playerReady.value = true\n },\n (err) => {\n // Surfaced, not swallowed — a failed chunk load must be debuggable.\n loading = false\n console.error('[itube-player] failed to load the player chunk', err)\n },\n )\n }\n\n onMounted(() => {\n if (!props.lazy) {\n load(false)\n return\n }\n const ph = phEl.value\n if (props.loadOn === 'immediate' || !ph) {\n load(false)\n return\n }\n const bg = () => load(false)\n if (props.loadOn === 'interaction') {\n const events: Array<keyof DocumentEventMap> = ['pointerdown', 'keydown', 'touchstart', 'wheel']\n for (const name of events) {\n document.addEventListener(name, bg, { once: true, passive: true })\n disposers.push(() => document.removeEventListener(name, bg))\n }\n } else if (props.loadOn === 'visible' && typeof IntersectionObserver !== 'undefined') {\n const io = new IntersectionObserver((entries) => {\n if (entries.some((e) => e.isIntersecting)) bg()\n })\n io.observe(ph)\n disposers.push(() => io.disconnect())\n } else {\n // 'placeholder' (default): any interaction with the placeholder area.\n const events: Array<keyof HTMLElementEventMap> = ['pointerenter', 'pointerdown', 'touchstart', 'focusin']\n for (const name of events) {\n ph.addEventListener(name, bg, { once: true, passive: true })\n disposers.push(() => ph.removeEventListener(name, bg))\n }\n }\n })\n\n watch(\n () => props.source,\n (next) => {\n if (next && player.value) player.value.load(next)\n },\n { deep: true },\n )\n\n onBeforeUnmount(() => {\n destroyed = true\n cleanupTriggers()\n player.value?.destroy()\n player.value = null\n })\n\n expose({ player })\n\n // \"Close & continue\" — resume playback; the player hides the pause screen\n // on the `play` event, so this both closes the custom block and plays.\n const close = () => {\n void player.value?.play()\n }\n\n return () => {\n const children = []\n if (props.lazy && !playerReady.value) {\n // Declarative, SSR-rendered placeholder (innerHTML is serialized by\n // @vue/server-renderer, so the poster <img> ships in the first HTML).\n children.push(\n h('div', {\n key: 'imp-ph',\n ref: phEl,\n class: 'imp-vue-placeholder',\n innerHTML: placeholderHtml.value,\n onClick: () => load(true),\n }),\n )\n }\n children.push(h('div', { key: 'imp-mount', ref: mountEl }))\n if (slots.pauseScreen) {\n // Scoped slot: `player` (reactive — null until mounted, then the\n // instance) and `close()` are available to the custom pause content.\n children.push(h('div', { ref: slotEl, class: 'imp-vue-pause-slot' }, slots.pauseScreen({ player: player.value, close })))\n }\n return h('div', { class: 'imp-vue-host' }, children)\n }\n },\n})\n\nexport default ITubePlayer\nexport type { Player }\nexport type * from './types'\n"],"names":["EVENT_NAMES","loadStyles","ITubePlayer","defineComponent","props","emit","slots","expose","mountEl","ref","phEl","slotEl","player","shallowRef","playerReady","destroyed","loading","wantAutoplay","disposers","cleanupTriggers","d","firstSource","computed","placeholderHtml","renderPlaceholder","load","autoplay","adConfigInput","adConfigPromise","PlayerCtor","adCfg","opts","instance","forward","name","payload","err","onMounted","ph","bg","events","io","entries","e","watch","next","onBeforeUnmount","close","children","h"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itube-modern-player",
3
- "version": "0.7.2",
3
+ "version": "0.8.0",
4
4
  "description": "Lightweight, framework-agnostic HTML5 video player: HLS streams, playlists, chapters, VTT subtitles, sprite previews, VAST ads. Ships with a Vue 3 wrapper.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -32,7 +32,12 @@
32
32
  "import": "./dist/lazy.js",
33
33
  "require": "./dist/lazy.cjs"
34
34
  },
35
- "./style.css": "./dist/style.css"
35
+ "./style.css": "./dist/style.css",
36
+ "./placeholder": {
37
+ "types": "./dist/placeholder.d.ts",
38
+ "import": "./dist/placeholder.js",
39
+ "require": "./dist/placeholder.cjs"
40
+ }
36
41
  },
37
42
  "files": [
38
43
  "dist"
@@ -1,5 +0,0 @@
1
- "use strict";function c(n,t,e){const i=document.createElement(n);if(t&&(i.className=t),e)for(const[o,r]of Object.entries(e))i.setAttribute(o,r);return i}function l(n,t){if(n.textContent="",typeof t=="object"){"url"in t?n.append(c("img","imp-icon-img",{src:t.url,alt:""})):n.innerHTML=t.svg;return}/^\s*</.test(t)?n.innerHTML=t:n.append(c("img","imp-icon-img",{src:t,alt:""}))}function g(n,t=""){const e=c("img","imp-cover-img",{alt:t,decoding:"async"});return e.src=n,e}function h(n,t,e){const i=c("button",`imp-btn ${n}`,{type:"button","aria-label":t,title:t});return l(i,e),i}function v(n,t){const e=c("div",`${t}__meta`);return n.forEach((i,o)=>{o>0&&e.append(document.createTextNode(" "));const r=typeof i=="string"?i:i.text,a=typeof i=="string"?void 0:i.icon,s=c("span",`${t}__meta-chunk`);if(a){const u=c("span",`${t}__meta-icon`);l(u,a),s.append(u)}s.append(document.createTextNode(r)),e.append(s)}),e}function b(n,t,e){l(n,t),e!==void 0&&(n.setAttribute("aria-label",e),n.setAttribute("title",e))}function x(n,t,e){return Math.min(e,Math.max(t,n))}function $(n){if(!Number.isFinite(n)||n<0)return"0:00";const t=Math.floor(n%60),e=Math.floor(n/60%60),i=Math.floor(n/3600),o=i>0?String(e).padStart(2,"0"):String(e),r=String(t).padStart(2,"0");return i>0?`${i}:${o}:${r}`:`${o}:${r}`}function f(n){const t=n.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:[.,](\d{1,3}))?$/);if(!t)return null;const e=t[1]?Number(t[1]):0,i=Number(t[2]),o=Number(t[3]),r=t[4]?Number(t[4].padEnd(3,"0")):0;return e*3600+i*60+o+r/1e3}function w(n){const t=[],e=n.replace(/\r\n?/g,`
2
- `).split(/\n\n+/);for(const i of e){const o=i.split(`
3
- `).filter(p=>p.trim()!=="");if(o.length===0)continue;let r=o.findIndex(p=>p.includes("-->"));if(r===-1)continue;const[a,s]=o[r].split("-->"),u=f(a),m=f((s??"").split(" ")[1]??s??"")??f(s??"");if(u===null||m===null)continue;const d=o.slice(r+1).join(`
4
- `).trim();d&&t.push({start:u,end:m,text:d})}return t}function y(n,t){try{return new URL(n,new URL(t,window.location.href)).toString()}catch{return n}}exports.applyIcon=l;exports.clamp=x;exports.coverImg=g;exports.el=c;exports.formatTime=$;exports.iconButton=h;exports.parseVttCues=w;exports.previewMetaEl=v;exports.resolveUrl=y;exports.setIcon=b;
5
- //# sourceMappingURL=dom-Bq7EQenh.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dom-Bq7EQenh.cjs","sources":["../src/core/dom.ts"],"sourcesContent":["/** DOM helpers. All player markup is built through these — no innerHTML templates. */\n\nimport type { IconSource, PreviewMetaItem } from '../types'\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n attrs?: Record<string, string>,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className) node.className = className\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) node.setAttribute(key, value)\n }\n return node\n}\n\n/**\n * Render an icon into `target`, honoring its kind: raw SVG (set as innerHTML)\n * or an image URL (an `<img class=\"imp-icon-img\">`). A bare string is raw SVG\n * when it starts with `<`, otherwise an image URL.\n */\nexport function applyIcon(target: HTMLElement, icon: IconSource): void {\n target.textContent = ''\n if (typeof icon === 'object') {\n if ('url' in icon) target.append(el('img', 'imp-icon-img', { src: icon.url, alt: '' }))\n else target.innerHTML = icon.svg\n return\n }\n if (/^\\s*</.test(icon)) target.innerHTML = icon\n else target.append(el('img', 'imp-icon-img', { src: icon, alt: '' }))\n}\n\n/**\n * Real `<img>` filling its (positioned, overflow-hidden) container like\n * `background-size: cover`. Used for every poster/preview surface — a real\n * element (not a CSS background) is lazy-loadable, LCP-eligible and keeps\n * the markup identical between the lazy placeholder and the live player.\n */\nexport function coverImg(src: string, alt = ''): HTMLImageElement {\n const img = el('img', 'imp-cover-img', { alt, decoding: 'async' }) as HTMLImageElement\n img.src = src\n return img\n}\n\n/** Icon-only button with an accessible label. */\nexport function iconButton(className: string, label: string, icon: IconSource): HTMLButtonElement {\n const btn = el('button', `imp-btn ${className}`, { type: 'button', 'aria-label': label, title: label })\n applyIcon(btn, icon)\n return btn\n}\n\n/**\n * Build the preview-meta row (used by the next-video hover card and the end\n * overlay). Each chunk renders as a `<span>`; a chunk with an `icon` gets an\n * inline leading glyph (e.g. a user icon before \"Uploaded by user\"). Chunks are\n * separated by a `·` via CSS. `base` is the BEM block, e.g. `\"imp-upnext\"`.\n */\nexport function previewMetaEl(items: PreviewMetaItem[], base: string): HTMLElement {\n const row = el('div', `${base}__meta`)\n items.forEach((item, i) => {\n // A real space between chunks is the only line-break opportunity at the\n // separator: the `·` itself is glued to the end of the previous chunk (CSS\n // ::after), so it never orphans at the start of a wrapped line. Text inside\n // a chunk still wraps word-by-word at its own spaces.\n if (i > 0) row.append(document.createTextNode(' '))\n const text = typeof item === 'string' ? item : item.text\n const icon = typeof item === 'string' ? undefined : item.icon\n const chunk = el('span', `${base}__meta-chunk`)\n if (icon) {\n const ic = el('span', `${base}__meta-icon`)\n applyIcon(ic, icon)\n chunk.append(ic)\n }\n chunk.append(document.createTextNode(text))\n row.append(chunk)\n })\n return row\n}\n\nexport function setIcon(btn: HTMLButtonElement, icon: IconSource, label?: string): void {\n applyIcon(btn, icon)\n if (label !== undefined) {\n btn.setAttribute('aria-label', label)\n btn.setAttribute('title', label)\n }\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/** `73` → `\"1:13\"`, `3673` → `\"1:01:13\"`. Infinity/NaN → `\"0:00\"`. */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00'\n const s = Math.floor(seconds % 60)\n const m = Math.floor((seconds / 60) % 60)\n const h = Math.floor(seconds / 3600)\n const mm = h > 0 ? String(m).padStart(2, '0') : String(m)\n const ss = String(s).padStart(2, '0')\n return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`\n}\n\n/** Parse `\"00:01:02.500\"` / `\"01:02.500\"` VTT timestamps into seconds. */\nexport function parseVttTime(raw: string): number | null {\n const m = raw.trim().match(/^(?:(\\d+):)?(\\d{1,2}):(\\d{2})(?:[.,](\\d{1,3}))?$/)\n if (!m) return null\n const h = m[1] ? Number(m[1]) : 0\n const min = Number(m[2])\n const s = Number(m[3])\n const ms = m[4] ? Number(m[4].padEnd(3, '0')) : 0\n return h * 3600 + min * 60 + s + ms / 1000\n}\n\n/** Tiny WebVTT cue parser — enough for chapters and thumbnail tracks. */\nexport function parseVttCues(text: string): Array<{ start: number; end: number; text: string }> {\n const cues: Array<{ start: number; end: number; text: string }> = []\n // Normalize newlines, drop the WEBVTT header block, split into cue blocks.\n const blocks = text.replace(/\\r\\n?/g, '\\n').split(/\\n\\n+/)\n for (const block of blocks) {\n const lines = block.split('\\n').filter((l) => l.trim() !== '')\n if (lines.length === 0) continue\n let timingIndex = lines.findIndex((l) => l.includes('-->'))\n if (timingIndex === -1) continue\n const [rawStart, rawEnd] = lines[timingIndex].split('-->')\n const start = parseVttTime(rawStart)\n const end = parseVttTime((rawEnd ?? '').split(' ')[1] ?? rawEnd ?? '')\n ?? parseVttTime(rawEnd ?? '')\n if (start === null || end === null) continue\n const cueText = lines.slice(timingIndex + 1).join('\\n').trim()\n if (cueText) cues.push({ start, end, text: cueText })\n }\n return cues\n}\n\n/** Resolve `url` against the location of the VTT file it came from. */\nexport function resolveUrl(url: string, base: string): string {\n try {\n return new URL(url, new URL(base, window.location.href)).toString()\n } catch {\n return url\n }\n}\n"],"names":["el","tag","className","attrs","node","key","value","applyIcon","target","icon","coverImg","src","alt","img","iconButton","label","btn","previewMetaEl","items","base","row","item","i","text","chunk","ic","setIcon","clamp","min","max","formatTime","seconds","s","m","h","mm","ss","parseVttTime","raw","ms","parseVttCues","cues","blocks","block","lines","l","timingIndex","rawStart","rawEnd","start","end","cueText","resolveUrl","url"],"mappings":"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"}
@@ -1,87 +0,0 @@
1
- function s(n, t, e) {
2
- const i = document.createElement(n);
3
- if (t && (i.className = t), e)
4
- for (const [o, r] of Object.entries(e)) i.setAttribute(o, r);
5
- return i;
6
- }
7
- function m(n, t) {
8
- if (n.textContent = "", typeof t == "object") {
9
- "url" in t ? n.append(s("img", "imp-icon-img", { src: t.url, alt: "" })) : n.innerHTML = t.svg;
10
- return;
11
- }
12
- /^\s*</.test(t) ? n.innerHTML = t : n.append(s("img", "imp-icon-img", { src: t, alt: "" }));
13
- }
14
- function g(n, t = "") {
15
- const e = s("img", "imp-cover-img", { alt: t, decoding: "async" });
16
- return e.src = n, e;
17
- }
18
- function h(n, t, e) {
19
- const i = s("button", `imp-btn ${n}`, { type: "button", "aria-label": t, title: t });
20
- return m(i, e), i;
21
- }
22
- function b(n, t) {
23
- const e = s("div", `${t}__meta`);
24
- return n.forEach((i, o) => {
25
- o > 0 && e.append(document.createTextNode(" "));
26
- const r = typeof i == "string" ? i : i.text, a = typeof i == "string" ? void 0 : i.icon, c = s("span", `${t}__meta-chunk`);
27
- if (a) {
28
- const u = s("span", `${t}__meta-icon`);
29
- m(u, a), c.append(u);
30
- }
31
- c.append(document.createTextNode(r)), e.append(c);
32
- }), e;
33
- }
34
- function x(n, t, e) {
35
- m(n, t), e !== void 0 && (n.setAttribute("aria-label", e), n.setAttribute("title", e));
36
- }
37
- function $(n, t, e) {
38
- return Math.min(e, Math.max(t, n));
39
- }
40
- function v(n) {
41
- if (!Number.isFinite(n) || n < 0) return "0:00";
42
- const t = Math.floor(n % 60), e = Math.floor(n / 60 % 60), i = Math.floor(n / 3600), o = i > 0 ? String(e).padStart(2, "0") : String(e), r = String(t).padStart(2, "0");
43
- return i > 0 ? `${i}:${o}:${r}` : `${o}:${r}`;
44
- }
45
- function p(n) {
46
- const t = n.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:[.,](\d{1,3}))?$/);
47
- if (!t) return null;
48
- const e = t[1] ? Number(t[1]) : 0, i = Number(t[2]), o = Number(t[3]), r = t[4] ? Number(t[4].padEnd(3, "0")) : 0;
49
- return e * 3600 + i * 60 + o + r / 1e3;
50
- }
51
- function w(n) {
52
- const t = [], e = n.replace(/\r\n?/g, `
53
- `).split(/\n\n+/);
54
- for (const i of e) {
55
- const o = i.split(`
56
- `).filter((f) => f.trim() !== "");
57
- if (o.length === 0) continue;
58
- let r = o.findIndex((f) => f.includes("-->"));
59
- if (r === -1) continue;
60
- const [a, c] = o[r].split("-->"), u = p(a), l = p((c ?? "").split(" ")[1] ?? c ?? "") ?? p(c ?? "");
61
- if (u === null || l === null) continue;
62
- const d = o.slice(r + 1).join(`
63
- `).trim();
64
- d && t.push({ start: u, end: l, text: d });
65
- }
66
- return t;
67
- }
68
- function y(n, t) {
69
- try {
70
- return new URL(n, new URL(t, window.location.href)).toString();
71
- } catch {
72
- return n;
73
- }
74
- }
75
- export {
76
- m as a,
77
- g as b,
78
- $ as c,
79
- b as d,
80
- s as e,
81
- v as f,
82
- h as i,
83
- w as p,
84
- y as r,
85
- x as s
86
- };
87
- //# sourceMappingURL=dom-DrpWbY0y.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dom-DrpWbY0y.js","sources":["../src/core/dom.ts"],"sourcesContent":["/** DOM helpers. All player markup is built through these — no innerHTML templates. */\n\nimport type { IconSource, PreviewMetaItem } from '../types'\n\nexport function el<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n className?: string,\n attrs?: Record<string, string>,\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (className) node.className = className\n if (attrs) {\n for (const [key, value] of Object.entries(attrs)) node.setAttribute(key, value)\n }\n return node\n}\n\n/**\n * Render an icon into `target`, honoring its kind: raw SVG (set as innerHTML)\n * or an image URL (an `<img class=\"imp-icon-img\">`). A bare string is raw SVG\n * when it starts with `<`, otherwise an image URL.\n */\nexport function applyIcon(target: HTMLElement, icon: IconSource): void {\n target.textContent = ''\n if (typeof icon === 'object') {\n if ('url' in icon) target.append(el('img', 'imp-icon-img', { src: icon.url, alt: '' }))\n else target.innerHTML = icon.svg\n return\n }\n if (/^\\s*</.test(icon)) target.innerHTML = icon\n else target.append(el('img', 'imp-icon-img', { src: icon, alt: '' }))\n}\n\n/**\n * Real `<img>` filling its (positioned, overflow-hidden) container like\n * `background-size: cover`. Used for every poster/preview surface — a real\n * element (not a CSS background) is lazy-loadable, LCP-eligible and keeps\n * the markup identical between the lazy placeholder and the live player.\n */\nexport function coverImg(src: string, alt = ''): HTMLImageElement {\n const img = el('img', 'imp-cover-img', { alt, decoding: 'async' }) as HTMLImageElement\n img.src = src\n return img\n}\n\n/** Icon-only button with an accessible label. */\nexport function iconButton(className: string, label: string, icon: IconSource): HTMLButtonElement {\n const btn = el('button', `imp-btn ${className}`, { type: 'button', 'aria-label': label, title: label })\n applyIcon(btn, icon)\n return btn\n}\n\n/**\n * Build the preview-meta row (used by the next-video hover card and the end\n * overlay). Each chunk renders as a `<span>`; a chunk with an `icon` gets an\n * inline leading glyph (e.g. a user icon before \"Uploaded by user\"). Chunks are\n * separated by a `·` via CSS. `base` is the BEM block, e.g. `\"imp-upnext\"`.\n */\nexport function previewMetaEl(items: PreviewMetaItem[], base: string): HTMLElement {\n const row = el('div', `${base}__meta`)\n items.forEach((item, i) => {\n // A real space between chunks is the only line-break opportunity at the\n // separator: the `·` itself is glued to the end of the previous chunk (CSS\n // ::after), so it never orphans at the start of a wrapped line. Text inside\n // a chunk still wraps word-by-word at its own spaces.\n if (i > 0) row.append(document.createTextNode(' '))\n const text = typeof item === 'string' ? item : item.text\n const icon = typeof item === 'string' ? undefined : item.icon\n const chunk = el('span', `${base}__meta-chunk`)\n if (icon) {\n const ic = el('span', `${base}__meta-icon`)\n applyIcon(ic, icon)\n chunk.append(ic)\n }\n chunk.append(document.createTextNode(text))\n row.append(chunk)\n })\n return row\n}\n\nexport function setIcon(btn: HTMLButtonElement, icon: IconSource, label?: string): void {\n applyIcon(btn, icon)\n if (label !== undefined) {\n btn.setAttribute('aria-label', label)\n btn.setAttribute('title', label)\n }\n}\n\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value))\n}\n\n/** `73` → `\"1:13\"`, `3673` → `\"1:01:13\"`. Infinity/NaN → `\"0:00\"`. */\nexport function formatTime(seconds: number): string {\n if (!Number.isFinite(seconds) || seconds < 0) return '0:00'\n const s = Math.floor(seconds % 60)\n const m = Math.floor((seconds / 60) % 60)\n const h = Math.floor(seconds / 3600)\n const mm = h > 0 ? String(m).padStart(2, '0') : String(m)\n const ss = String(s).padStart(2, '0')\n return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}`\n}\n\n/** Parse `\"00:01:02.500\"` / `\"01:02.500\"` VTT timestamps into seconds. */\nexport function parseVttTime(raw: string): number | null {\n const m = raw.trim().match(/^(?:(\\d+):)?(\\d{1,2}):(\\d{2})(?:[.,](\\d{1,3}))?$/)\n if (!m) return null\n const h = m[1] ? Number(m[1]) : 0\n const min = Number(m[2])\n const s = Number(m[3])\n const ms = m[4] ? Number(m[4].padEnd(3, '0')) : 0\n return h * 3600 + min * 60 + s + ms / 1000\n}\n\n/** Tiny WebVTT cue parser — enough for chapters and thumbnail tracks. */\nexport function parseVttCues(text: string): Array<{ start: number; end: number; text: string }> {\n const cues: Array<{ start: number; end: number; text: string }> = []\n // Normalize newlines, drop the WEBVTT header block, split into cue blocks.\n const blocks = text.replace(/\\r\\n?/g, '\\n').split(/\\n\\n+/)\n for (const block of blocks) {\n const lines = block.split('\\n').filter((l) => l.trim() !== '')\n if (lines.length === 0) continue\n let timingIndex = lines.findIndex((l) => l.includes('-->'))\n if (timingIndex === -1) continue\n const [rawStart, rawEnd] = lines[timingIndex].split('-->')\n const start = parseVttTime(rawStart)\n const end = parseVttTime((rawEnd ?? '').split(' ')[1] ?? rawEnd ?? '')\n ?? parseVttTime(rawEnd ?? '')\n if (start === null || end === null) continue\n const cueText = lines.slice(timingIndex + 1).join('\\n').trim()\n if (cueText) cues.push({ start, end, text: cueText })\n }\n return cues\n}\n\n/** Resolve `url` against the location of the VTT file it came from. */\nexport function resolveUrl(url: string, base: string): string {\n try {\n return new URL(url, new URL(base, window.location.href)).toString()\n } catch {\n return url\n }\n}\n"],"names":["el","tag","className","attrs","node","key","value","applyIcon","target","icon","coverImg","src","alt","img","iconButton","label","btn","previewMetaEl","items","base","row","item","i","text","chunk","ic","setIcon","clamp","min","max","formatTime","seconds","s","m","h","mm","ss","parseVttTime","raw","ms","parseVttCues","cues","blocks","block","lines","l","timingIndex","rawStart","rawEnd","start","end","cueText","resolveUrl","url"],"mappings":"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"}