@lumra/react 0.1.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.cjs +474 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +240 -0
- package/dist/index.d.ts +240 -0
- package/dist/index.js +468 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var plugins = require('@lumra/plugins');
|
|
5
|
+
var ui = require('@lumra/ui');
|
|
6
|
+
var core = require('@lumra/core');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
|
|
9
|
+
// src/LumraPlayer.tsx
|
|
10
|
+
function usePlayer(options = {}) {
|
|
11
|
+
const videoRef = react.useRef(null);
|
|
12
|
+
const playerRef = react.useRef(null);
|
|
13
|
+
const [player, setPlayer] = react.useState(null);
|
|
14
|
+
const [paused, setPaused] = react.useState(true);
|
|
15
|
+
const [currentTime, setCurrentTime] = react.useState(0);
|
|
16
|
+
const [duration, setDuration] = react.useState(0);
|
|
17
|
+
const [volume, setVolume] = react.useState(options.volume ?? 1);
|
|
18
|
+
const [muted, setMuted] = react.useState(options.muted ?? false);
|
|
19
|
+
const srcSyncedRef = react.useRef(false);
|
|
20
|
+
react.useEffect(() => {
|
|
21
|
+
const video = videoRef.current;
|
|
22
|
+
if (!video) return;
|
|
23
|
+
const p = core.createPlayer({ ...options, target: video });
|
|
24
|
+
playerRef.current = p;
|
|
25
|
+
srcSyncedRef.current = true;
|
|
26
|
+
setPlayer(p);
|
|
27
|
+
p.on("play", () => setPaused(false));
|
|
28
|
+
p.on("pause", () => setPaused(true));
|
|
29
|
+
p.on("ended", () => setPaused(true));
|
|
30
|
+
p.on("timeupdate", ({ currentTime: ct, duration: d }) => {
|
|
31
|
+
setCurrentTime(ct);
|
|
32
|
+
setDuration(d);
|
|
33
|
+
});
|
|
34
|
+
p.on("volumechange", ({ volume: v, muted: m }) => {
|
|
35
|
+
setVolume(v);
|
|
36
|
+
setMuted(m);
|
|
37
|
+
});
|
|
38
|
+
return () => {
|
|
39
|
+
p.destroy();
|
|
40
|
+
playerRef.current = null;
|
|
41
|
+
setPlayer(null);
|
|
42
|
+
};
|
|
43
|
+
}, []);
|
|
44
|
+
react.useEffect(() => {
|
|
45
|
+
if (!srcSyncedRef.current) return;
|
|
46
|
+
if (options.src && playerRef.current) {
|
|
47
|
+
playerRef.current.setSrc(options.src);
|
|
48
|
+
}
|
|
49
|
+
}, [options.src]);
|
|
50
|
+
return {
|
|
51
|
+
player,
|
|
52
|
+
videoRef,
|
|
53
|
+
paused,
|
|
54
|
+
currentTime,
|
|
55
|
+
duration,
|
|
56
|
+
volume,
|
|
57
|
+
muted
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function defineLumraConfig(config) {
|
|
61
|
+
return config;
|
|
62
|
+
}
|
|
63
|
+
var ConfigCtx = react.createContext({});
|
|
64
|
+
function LumraConfigProvider({
|
|
65
|
+
config,
|
|
66
|
+
children
|
|
67
|
+
}) {
|
|
68
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ConfigCtx.Provider, { value: config, children });
|
|
69
|
+
}
|
|
70
|
+
function useLumraConfig() {
|
|
71
|
+
return react.useContext(ConfigCtx);
|
|
72
|
+
}
|
|
73
|
+
var LumraPlayer = react.forwardRef(
|
|
74
|
+
function LumraPlayer2(props, ref) {
|
|
75
|
+
const {
|
|
76
|
+
src,
|
|
77
|
+
poster,
|
|
78
|
+
autoplay,
|
|
79
|
+
muted: mutedProp,
|
|
80
|
+
volume,
|
|
81
|
+
loop,
|
|
82
|
+
paywall,
|
|
83
|
+
mediaInfo,
|
|
84
|
+
tracks,
|
|
85
|
+
chapters,
|
|
86
|
+
heatmapData,
|
|
87
|
+
headless = false,
|
|
88
|
+
plugins: plugins$1 = [],
|
|
89
|
+
components = {},
|
|
90
|
+
className,
|
|
91
|
+
style,
|
|
92
|
+
onPlay,
|
|
93
|
+
onPause,
|
|
94
|
+
onEnded,
|
|
95
|
+
onTimeUpdate,
|
|
96
|
+
onError
|
|
97
|
+
} = props;
|
|
98
|
+
const config = useLumraConfig();
|
|
99
|
+
const signUrl = config.cloud?.signUrl;
|
|
100
|
+
const baseUrl = config.storage?.baseUrl?.replace(/\/$/, "") ?? "";
|
|
101
|
+
const [resolvedSrc, setResolvedSrc] = react.useState(void 0);
|
|
102
|
+
react.useEffect(() => {
|
|
103
|
+
if (!src) {
|
|
104
|
+
setResolvedSrc(void 0);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const expanded = baseUrl && !src.startsWith("http") && !src.startsWith("//") ? `${baseUrl}/${src.replace(/^\//, "")}` : src;
|
|
108
|
+
if (!signUrl) {
|
|
109
|
+
setResolvedSrc(expanded);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
let cancelled = false;
|
|
113
|
+
void signUrl(expanded).then((s) => {
|
|
114
|
+
if (!cancelled) setResolvedSrc(s);
|
|
115
|
+
}).catch(() => {
|
|
116
|
+
if (!cancelled) setResolvedSrc(expanded);
|
|
117
|
+
});
|
|
118
|
+
return () => {
|
|
119
|
+
cancelled = true;
|
|
120
|
+
};
|
|
121
|
+
}, [src, signUrl, baseUrl]);
|
|
122
|
+
const effectiveAutoplay = autoplay ?? config.behavior?.autoplay;
|
|
123
|
+
const effectiveMuted = mutedProp ?? config.behavior?.muted;
|
|
124
|
+
const effectiveLoop = loop ?? config.behavior?.loop;
|
|
125
|
+
const { player, videoRef, paused, currentTime, duration, volume: vol, muted } = usePlayer({
|
|
126
|
+
...resolvedSrc !== void 0 && { src: resolvedSrc },
|
|
127
|
+
...effectiveAutoplay !== void 0 && { autoplay: effectiveAutoplay },
|
|
128
|
+
...effectiveMuted !== void 0 && { muted: effectiveMuted },
|
|
129
|
+
...volume !== void 0 && { volume },
|
|
130
|
+
...effectiveLoop !== void 0 && { loop: effectiveLoop }
|
|
131
|
+
});
|
|
132
|
+
react.useImperativeHandle(ref, () => player, [player]);
|
|
133
|
+
const [gateTriggered, setGateTriggered] = react.useState(false);
|
|
134
|
+
const paywallPluginRef = react.useRef(null);
|
|
135
|
+
react.useEffect(() => {
|
|
136
|
+
if (!player || !paywall?.locked) return;
|
|
137
|
+
const previewDuration = config.paywall?.previewDuration ?? 5;
|
|
138
|
+
paywallPluginRef.current = plugins.paywallGatePlugin({
|
|
139
|
+
previewDuration,
|
|
140
|
+
onBlock: () => setGateTriggered(true)
|
|
141
|
+
});
|
|
142
|
+
player.use(paywallPluginRef.current);
|
|
143
|
+
}, [player]);
|
|
144
|
+
const [adState, setAdState] = react.useState(null);
|
|
145
|
+
const adPluginRef = react.useRef(null);
|
|
146
|
+
react.useEffect(() => {
|
|
147
|
+
if (!player || !config.ads) return;
|
|
148
|
+
const instance = plugins.adPlugin({
|
|
149
|
+
...config.ads,
|
|
150
|
+
onAdStart: (info) => setAdState(info),
|
|
151
|
+
onAdTick: (info) => setAdState((prev) => prev ? { ...prev, elapsed: info.elapsed } : null),
|
|
152
|
+
onAdEnd: () => setAdState(null),
|
|
153
|
+
onAdSkip: () => setAdState(null)
|
|
154
|
+
});
|
|
155
|
+
adPluginRef.current = instance;
|
|
156
|
+
player.use(instance);
|
|
157
|
+
}, [player]);
|
|
158
|
+
react.useEffect(() => {
|
|
159
|
+
if (!player) return;
|
|
160
|
+
for (const plugin of plugins$1) player.use(plugin);
|
|
161
|
+
}, [player]);
|
|
162
|
+
react.useEffect(() => {
|
|
163
|
+
if (!player) return;
|
|
164
|
+
if (onPlay) player.on("play", onPlay);
|
|
165
|
+
if (onPause) player.on("pause", onPause);
|
|
166
|
+
if (onEnded) player.on("ended", onEnded);
|
|
167
|
+
if (onTimeUpdate) player.on("timeupdate", ({ currentTime: ct, duration: d }) => onTimeUpdate(ct, d));
|
|
168
|
+
if (onError) player.on("error", ({ code, message }) => onError(code, message));
|
|
169
|
+
return () => {
|
|
170
|
+
if (onPlay) player.off("play", onPlay);
|
|
171
|
+
if (onPause) player.off("pause", onPause);
|
|
172
|
+
if (onEnded) player.off("ended", onEnded);
|
|
173
|
+
};
|
|
174
|
+
}, [player]);
|
|
175
|
+
const [buffering, setBuffering] = react.useState(false);
|
|
176
|
+
const [error, setError] = react.useState(null);
|
|
177
|
+
react.useEffect(() => {
|
|
178
|
+
if (!player) return;
|
|
179
|
+
player.on("buffering", () => setBuffering(true));
|
|
180
|
+
player.on("ready", () => setBuffering(false));
|
|
181
|
+
player.on("play", () => {
|
|
182
|
+
setBuffering(false);
|
|
183
|
+
setError(null);
|
|
184
|
+
});
|
|
185
|
+
player.on("error", ({ message }) => setError(message));
|
|
186
|
+
}, [player]);
|
|
187
|
+
const containerRef = react.useRef(null);
|
|
188
|
+
const [fullscreen, setFullscreen] = react.useState(false);
|
|
189
|
+
react.useEffect(() => {
|
|
190
|
+
const onChange = () => setFullscreen(document.fullscreenElement === containerRef.current);
|
|
191
|
+
document.addEventListener("fullscreenchange", onChange);
|
|
192
|
+
return () => document.removeEventListener("fullscreenchange", onChange);
|
|
193
|
+
}, []);
|
|
194
|
+
const toggleFullscreen = react.useCallback(() => {
|
|
195
|
+
if (!containerRef.current) return;
|
|
196
|
+
if (fullscreen) void document.exitFullscreen();
|
|
197
|
+
else void containerRef.current.requestFullscreen();
|
|
198
|
+
}, [fullscreen]);
|
|
199
|
+
const [pip, setPip] = react.useState(false);
|
|
200
|
+
react.useEffect(() => {
|
|
201
|
+
const onEnter = () => setPip(true);
|
|
202
|
+
const onLeave = () => setPip(false);
|
|
203
|
+
document.addEventListener("enterpictureinpicture", onEnter);
|
|
204
|
+
document.addEventListener("leavepictureinpicture", onLeave);
|
|
205
|
+
return () => {
|
|
206
|
+
document.removeEventListener("enterpictureinpicture", onEnter);
|
|
207
|
+
document.removeEventListener("leavepictureinpicture", onLeave);
|
|
208
|
+
};
|
|
209
|
+
}, []);
|
|
210
|
+
const togglePip = react.useCallback(async () => {
|
|
211
|
+
if (!videoRef.current) return;
|
|
212
|
+
try {
|
|
213
|
+
if (pip) await document.exitPictureInPicture();
|
|
214
|
+
else await videoRef.current.requestPictureInPicture();
|
|
215
|
+
} catch {
|
|
216
|
+
}
|
|
217
|
+
}, [pip, videoRef]);
|
|
218
|
+
const autoHide = config.controls?.autoHide ?? true;
|
|
219
|
+
const autoHideDelay = config.controls?.autoHideDelay ?? 3e3;
|
|
220
|
+
const [controlsVisible, setControlsVisible] = react.useState(true);
|
|
221
|
+
const hideTimer = react.useRef();
|
|
222
|
+
const pausedRef = react.useRef(paused);
|
|
223
|
+
react.useEffect(() => {
|
|
224
|
+
pausedRef.current = paused;
|
|
225
|
+
}, [paused]);
|
|
226
|
+
const showControls = react.useCallback(() => {
|
|
227
|
+
setControlsVisible(true);
|
|
228
|
+
clearTimeout(hideTimer.current);
|
|
229
|
+
if (autoHide) {
|
|
230
|
+
hideTimer.current = setTimeout(() => {
|
|
231
|
+
if (!pausedRef.current) setControlsVisible(false);
|
|
232
|
+
}, autoHideDelay);
|
|
233
|
+
}
|
|
234
|
+
}, [autoHide, autoHideDelay]);
|
|
235
|
+
react.useEffect(() => {
|
|
236
|
+
if (paused) {
|
|
237
|
+
setControlsVisible(true);
|
|
238
|
+
clearTimeout(hideTimer.current);
|
|
239
|
+
}
|
|
240
|
+
}, [paused]);
|
|
241
|
+
react.useEffect(() => () => clearTimeout(hideTimer.current), []);
|
|
242
|
+
const handleKeyDown = react.useCallback(
|
|
243
|
+
(e) => {
|
|
244
|
+
if (!player) return;
|
|
245
|
+
switch (e.code) {
|
|
246
|
+
case "Space":
|
|
247
|
+
case "KeyK":
|
|
248
|
+
e.preventDefault();
|
|
249
|
+
paused ? void player.play() : player.pause();
|
|
250
|
+
break;
|
|
251
|
+
case "KeyF":
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
toggleFullscreen();
|
|
254
|
+
break;
|
|
255
|
+
case "KeyP":
|
|
256
|
+
e.preventDefault();
|
|
257
|
+
void togglePip();
|
|
258
|
+
break;
|
|
259
|
+
case "KeyM":
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
muted ? player.unmute() : player.mute();
|
|
262
|
+
break;
|
|
263
|
+
case "ArrowLeft":
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
player.seek(Math.max(0, currentTime - 10));
|
|
266
|
+
break;
|
|
267
|
+
case "ArrowRight":
|
|
268
|
+
e.preventDefault();
|
|
269
|
+
player.seek(Math.min(duration, currentTime + 10));
|
|
270
|
+
break;
|
|
271
|
+
case "ArrowUp":
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
player.setVolume(Math.min(1, vol + 0.1));
|
|
274
|
+
break;
|
|
275
|
+
case "ArrowDown":
|
|
276
|
+
e.preventDefault();
|
|
277
|
+
player.setVolume(Math.max(0, vol - 0.1));
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
[player, paused, muted, currentTime, duration, vol, toggleFullscreen, togglePip]
|
|
282
|
+
);
|
|
283
|
+
const handleTogglePlay = react.useCallback(() => {
|
|
284
|
+
if (!player) return;
|
|
285
|
+
paused ? void player.play() : player.pause();
|
|
286
|
+
}, [player, paused]);
|
|
287
|
+
const handleSeek = react.useCallback((t) => player?.seek(t), [player]);
|
|
288
|
+
const handleVolumeChange = react.useCallback((v) => {
|
|
289
|
+
player?.setVolume(v);
|
|
290
|
+
if (v > 0 && muted) player?.unmute();
|
|
291
|
+
}, [player, muted]);
|
|
292
|
+
const handleToggleMute = react.useCallback(() => {
|
|
293
|
+
if (!player) return;
|
|
294
|
+
muted ? player.unmute() : player.mute();
|
|
295
|
+
}, [player, muted]);
|
|
296
|
+
const blockDownload = config.behavior?.blockDownload ?? false;
|
|
297
|
+
const borderRadius = config.theme?.borderRadius ?? "0px";
|
|
298
|
+
const accentColor = config.theme?.accentColor ?? "#ffffff";
|
|
299
|
+
const show = {
|
|
300
|
+
play: config.controls?.play ?? true,
|
|
301
|
+
progress: config.controls?.progress ?? true,
|
|
302
|
+
volume: config.controls?.volume ?? true,
|
|
303
|
+
fullscreen: config.controls?.fullscreen ?? true,
|
|
304
|
+
pip: config.controls?.pip ?? true,
|
|
305
|
+
time: config.controls?.time ?? true
|
|
306
|
+
};
|
|
307
|
+
const theme = {
|
|
308
|
+
accentColor,
|
|
309
|
+
textColor: config.theme?.textColor ?? "#ffffff",
|
|
310
|
+
...config.theme?.controlsBg !== void 0 && { controlsBg: config.theme.controlsBg }
|
|
311
|
+
};
|
|
312
|
+
const showPaywallOverlay = paywall?.locked === true && gateTriggered;
|
|
313
|
+
const handleUnlock = react.useCallback(() => {
|
|
314
|
+
void paywall?.onUnlock();
|
|
315
|
+
}, [paywall]);
|
|
316
|
+
const { Paywall: DeprecatedPaywall } = components;
|
|
317
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
318
|
+
"div",
|
|
319
|
+
{
|
|
320
|
+
ref: containerRef,
|
|
321
|
+
style: { position: "relative", background: "#000", borderRadius, overflow: "hidden", outline: "none", ...style },
|
|
322
|
+
className,
|
|
323
|
+
tabIndex: 0,
|
|
324
|
+
onKeyDown: handleKeyDown,
|
|
325
|
+
onMouseMove: showControls,
|
|
326
|
+
onMouseEnter: showControls,
|
|
327
|
+
onTouchStart: showControls,
|
|
328
|
+
onMouseLeave: () => {
|
|
329
|
+
if (!paused && autoHide) setControlsVisible(false);
|
|
330
|
+
},
|
|
331
|
+
children: [
|
|
332
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
333
|
+
"video",
|
|
334
|
+
{
|
|
335
|
+
ref: videoRef,
|
|
336
|
+
style: { width: "100%", height: "100%", display: headless ? "none" : "block" },
|
|
337
|
+
playsInline: true,
|
|
338
|
+
...poster !== void 0 && { poster },
|
|
339
|
+
controlsList: blockDownload ? "nodownload" : void 0,
|
|
340
|
+
onContextMenu: blockDownload ? (e) => e.preventDefault() : void 0,
|
|
341
|
+
children: tracks?.map((t) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
342
|
+
"track",
|
|
343
|
+
{
|
|
344
|
+
src: t.src,
|
|
345
|
+
kind: t.kind,
|
|
346
|
+
srcLang: t.srcLang,
|
|
347
|
+
label: t.label,
|
|
348
|
+
...t.default ? { default: true } : {}
|
|
349
|
+
},
|
|
350
|
+
`${t.kind}-${t.srcLang}`
|
|
351
|
+
))
|
|
352
|
+
}
|
|
353
|
+
),
|
|
354
|
+
blockDownload && !headless && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, zIndex: 1, pointerEvents: "none" } }),
|
|
355
|
+
!headless && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
356
|
+
mediaInfo && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { position: "absolute", inset: 0, zIndex: 4, pointerEvents: "none" }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
357
|
+
ui.MediaInfoOverlay,
|
|
358
|
+
{
|
|
359
|
+
...mediaInfo.title !== void 0 && { title: mediaInfo.title },
|
|
360
|
+
...mediaInfo.creator !== void 0 && { creator: mediaInfo.creator }
|
|
361
|
+
}
|
|
362
|
+
) }),
|
|
363
|
+
buffering && !paused && /* @__PURE__ */ jsxRuntime.jsx(ui.BufferingSpinner, { color: accentColor }),
|
|
364
|
+
error && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: errorOverlay, children: [
|
|
365
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { style: { fontSize: "13px", opacity: 0.75 }, children: [
|
|
366
|
+
"\u26A0 ",
|
|
367
|
+
error
|
|
368
|
+
] }),
|
|
369
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
370
|
+
"button",
|
|
371
|
+
{
|
|
372
|
+
style: retryBtn,
|
|
373
|
+
onClick: () => {
|
|
374
|
+
setError(null);
|
|
375
|
+
if (resolvedSrc) player?.setSrc(resolvedSrc);
|
|
376
|
+
},
|
|
377
|
+
children: "Retry"
|
|
378
|
+
}
|
|
379
|
+
)
|
|
380
|
+
] }),
|
|
381
|
+
adState && /* @__PURE__ */ jsxRuntime.jsx(
|
|
382
|
+
ui.AdOverlay,
|
|
383
|
+
{
|
|
384
|
+
phase: adState.phase,
|
|
385
|
+
elapsed: adState.elapsed,
|
|
386
|
+
skipAfter: adState.skipAfter,
|
|
387
|
+
accentColor,
|
|
388
|
+
...adState.clickThroughUrl !== void 0 && { clickThroughUrl: adState.clickThroughUrl },
|
|
389
|
+
onSkip: () => adPluginRef.current?.skipAd()
|
|
390
|
+
}
|
|
391
|
+
),
|
|
392
|
+
showPaywallOverlay && (paywall?.overlay ? /* @__PURE__ */ jsxRuntime.jsx(paywall.overlay, { onUnlock: handleUnlock }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
393
|
+
ui.PaywallOverlay,
|
|
394
|
+
{
|
|
395
|
+
onUnlock: handleUnlock,
|
|
396
|
+
...paywall?.title !== void 0 && { title: paywall.title },
|
|
397
|
+
...paywall?.subtitle !== void 0 && { subtitle: paywall.subtitle },
|
|
398
|
+
...paywall?.options !== void 0 && { options: paywall.options },
|
|
399
|
+
accentColor
|
|
400
|
+
}
|
|
401
|
+
)),
|
|
402
|
+
!showPaywallOverlay && DeprecatedPaywall && /* @__PURE__ */ jsxRuntime.jsx(DeprecatedPaywall, { onUnlock: () => void player?.play() }),
|
|
403
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
404
|
+
"div",
|
|
405
|
+
{
|
|
406
|
+
style: {
|
|
407
|
+
position: "absolute",
|
|
408
|
+
bottom: 0,
|
|
409
|
+
left: 0,
|
|
410
|
+
right: 0,
|
|
411
|
+
zIndex: 5,
|
|
412
|
+
opacity: controlsVisible ? 1 : 0,
|
|
413
|
+
transition: "opacity 0.25s ease",
|
|
414
|
+
pointerEvents: controlsVisible ? "auto" : "none"
|
|
415
|
+
},
|
|
416
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
417
|
+
ui.ControlsBar,
|
|
418
|
+
{
|
|
419
|
+
paused,
|
|
420
|
+
currentTime,
|
|
421
|
+
duration,
|
|
422
|
+
volume: vol,
|
|
423
|
+
muted,
|
|
424
|
+
fullscreen,
|
|
425
|
+
pip,
|
|
426
|
+
show,
|
|
427
|
+
theme,
|
|
428
|
+
...chapters !== void 0 && { chapters },
|
|
429
|
+
...heatmapData !== void 0 && { heatmapData },
|
|
430
|
+
onTogglePlay: handleTogglePlay,
|
|
431
|
+
onSeek: handleSeek,
|
|
432
|
+
onVolumeChange: handleVolumeChange,
|
|
433
|
+
onToggleMute: handleToggleMute,
|
|
434
|
+
onToggleFullscreen: toggleFullscreen,
|
|
435
|
+
onTogglePip: () => void togglePip()
|
|
436
|
+
}
|
|
437
|
+
)
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
] })
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
var errorOverlay = {
|
|
447
|
+
position: "absolute",
|
|
448
|
+
inset: 0,
|
|
449
|
+
zIndex: 10,
|
|
450
|
+
display: "flex",
|
|
451
|
+
flexDirection: "column",
|
|
452
|
+
alignItems: "center",
|
|
453
|
+
justifyContent: "center",
|
|
454
|
+
background: "rgba(0,0,0,0.75)",
|
|
455
|
+
color: "#fff",
|
|
456
|
+
gap: "12px"
|
|
457
|
+
};
|
|
458
|
+
var retryBtn = {
|
|
459
|
+
padding: "8px 18px",
|
|
460
|
+
background: "rgba(255,255,255,0.12)",
|
|
461
|
+
border: "1px solid rgba(255,255,255,0.25)",
|
|
462
|
+
borderRadius: "6px",
|
|
463
|
+
color: "#fff",
|
|
464
|
+
cursor: "pointer",
|
|
465
|
+
fontSize: "13px"
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
exports.LumraConfigProvider = LumraConfigProvider;
|
|
469
|
+
exports.LumraPlayer = LumraPlayer;
|
|
470
|
+
exports.defineLumraConfig = defineLumraConfig;
|
|
471
|
+
exports.useLumraConfig = useLumraConfig;
|
|
472
|
+
exports.usePlayer = usePlayer;
|
|
473
|
+
//# sourceMappingURL=index.cjs.map
|
|
474
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/usePlayer.ts","../src/config.tsx","../src/LumraPlayer.tsx"],"names":["useRef","useState","useEffect","createPlayer","createContext","useContext","forwardRef","LumraPlayer","plugins","useImperativeHandle","paywallGatePlugin","adPlugin","useCallback","jsxs","jsx","Fragment","MediaInfoOverlay","BufferingSpinner","AdOverlay","PaywallOverlay","ControlsBar"],"mappings":"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","file":"index.cjs","sourcesContent":["import { useEffect, useRef, useState } from 'react'\nimport { createPlayer } from '@lumra/core'\nimport type { PlayerInstance, PlayerOptions } from '@lumra/core'\n\nexport interface UsePlayerOptions extends Omit<PlayerOptions, 'target'> {}\n\nexport interface UsePlayerReturn {\n player: PlayerInstance | null\n videoRef: React.RefObject<HTMLVideoElement>\n paused: boolean\n currentTime: number\n duration: number\n volume: number\n muted: boolean\n}\n\nexport function usePlayer(options: UsePlayerOptions = {}): UsePlayerReturn {\n const videoRef = useRef<HTMLVideoElement>(null)\n const playerRef = useRef<PlayerInstance | null>(null)\n // State (not just ref) so that components re-render once the player is ready\n const [player, setPlayer] = useState<PlayerInstance | null>(null)\n const [paused, setPaused] = useState(true)\n const [currentTime, setCurrentTime] = useState(0)\n const [duration, setDuration] = useState(0)\n const [volume, setVolume] = useState(options.volume ?? 1)\n const [muted, setMuted] = useState(options.muted ?? false)\n // Prevent double-setting src on initial mount (player constructor already loads it)\n const srcSyncedRef = useRef(false)\n\n useEffect(() => {\n const video = videoRef.current\n if (!video) return\n\n const p = createPlayer({ ...options, target: video })\n playerRef.current = p\n srcSyncedRef.current = true\n setPlayer(p)\n\n p.on('play', () => setPaused(false))\n p.on('pause', () => setPaused(true))\n p.on('ended', () => setPaused(true))\n p.on('timeupdate', ({ currentTime: ct, duration: d }) => {\n setCurrentTime(ct)\n setDuration(d)\n })\n p.on('volumechange', ({ volume: v, muted: m }) => {\n setVolume(v)\n setMuted(m)\n })\n\n return () => {\n p.destroy()\n playerRef.current = null\n setPlayer(null)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n // Sync src changes after mount (skip the initial value — constructor handles it)\n useEffect(() => {\n if (!srcSyncedRef.current) return\n if (options.src && playerRef.current) {\n playerRef.current.setSrc(options.src)\n }\n }, [options.src])\n\n return {\n player,\n videoRef,\n paused,\n currentTime,\n duration,\n volume,\n muted,\n }\n}\n","import React, { createContext, useContext } from 'react'\n\n// ── Theme ─────────────────────────────────────────────────────────────────────\n\nexport interface LumraTheme {\n /** Progress bar fill + scrubber thumb colour (default: '#ffffff') */\n accentColor?: string\n /** Icon + time display colour (default: '#ffffff') */\n textColor?: string\n /** Controls bar background — any CSS value (default: black gradient) */\n controlsBg?: string\n /** Player container border-radius (default: '0px') */\n borderRadius?: string\n}\n\n// ── Controls ──────────────────────────────────────────────────────────────────\n\nexport interface LumraControlsConfig {\n /** Show play/pause button (default: true) */\n play?: boolean\n /** Show seek bar (default: true) */\n progress?: boolean\n /** Show volume control (default: true) */\n volume?: boolean\n /** Show fullscreen button (default: true) */\n fullscreen?: boolean\n /** Show Picture-in-Picture button (default: true) */\n pip?: boolean\n /** Show current / duration timestamp (default: true) */\n time?: boolean\n /** Hide toolbar after inactivity (default: true) */\n autoHide?: boolean\n /** Milliseconds of inactivity before toolbar hides (default: 3000) */\n autoHideDelay?: number\n}\n\n// ── Behaviour ─────────────────────────────────────────────────────────────────\n\nexport interface LumraBehaviorConfig {\n /** Disable right-click + nodownload attribute to discourage saving (default: false) */\n blockDownload?: boolean\n autoplay?: boolean\n muted?: boolean\n loop?: boolean\n}\n\n// ── Paywall ───────────────────────────────────────────────────────────────────\n\nexport interface LumraPaywallConfig {\n /**\n * Seconds of free preview before the gate fires and the paywall overlay appears.\n * Controlled here so you never touch App.tsx — just change this number.\n * (default: 5)\n */\n previewDuration?: number\n}\n\n// ── Ads ───────────────────────────────────────────────────────────────────────\n\nexport interface LumraAdConfig {\n /**\n * VAST 2.0 / 3.0 tag URL — the plugin fetches and parses the XML automatically.\n * Supply either `vast` or `src`, not both.\n */\n vast?: string\n /** Direct video URL (MP4 / HLS) — bypasses VAST, used as a simple pre-baked ad */\n src?: string\n /** Seconds of ad playback before the Skip button appears. 0 = not skippable. */\n skipAfter?: number\n /** Override the click-through URL (ignored when VAST provides one) */\n clickThroughUrl?: string\n}\n\nexport interface LumraMidRollConfig extends LumraAdConfig {\n /** Content timestamp (in seconds) at which this mid-roll fires */\n at: number\n}\n\nexport interface LumraAdsConfig {\n /**\n * Lumra Ads premium license key.\n * Format: LUMRA-ADS-XXXXXXXX-XXXXXXXX\n * Obtain a key at https://lumra.dev/premium\n */\n licenseKey: string\n /** Optional server endpoint to verify the license key */\n verifyEndpoint?: string\n /** Ad that plays before the main content */\n preRoll?: LumraAdConfig\n /** One or more ads that interrupt the main content at specific timestamps */\n midRoll?: LumraMidRollConfig[]\n /** Ad that plays after the main content ends */\n postRoll?: LumraAdConfig\n}\n\n// ── Storage / Cloud ───────────────────────────────────────────────────────────\n\nexport interface LumraStorageConfig {\n /**\n * Base URL prepended to any relative `src` value.\n * Swap between local dev and production CDN by changing this one line.\n *\n * @example\n * // local dev\n * baseUrl: 'http://localhost:9000/videos'\n * // production\n * baseUrl: 'https://your-cdn.com/videos'\n */\n baseUrl?: string\n}\n\nexport interface LumraCloudConfig {\n /**\n * Called before every video load — return a signed or proxied URL.\n * Works with AWS S3, Cloudflare R2 / Stream, Mux, Bunny CDN, etc.\n *\n * @example\n * // AWS S3 presigned URL (via your own API)\n * signUrl: async (src) => {\n * const res = await fetch(`/api/sign-url?src=${encodeURIComponent(src)}`)\n * const { url } = await res.json()\n * return url\n * }\n *\n * @example\n * // Cloudflare Stream signed URL\n * signUrl: async (src) => {\n * const res = await fetch('/api/cf-sign', { method: 'POST', body: JSON.stringify({ src }) })\n * const { url } = await res.json()\n * return url\n * }\n */\n signUrl?: (src: string) => Promise<string>\n}\n\n// ── Root config ───────────────────────────────────────────────────────────────\n\nexport interface LumraConfig {\n theme?: LumraTheme\n controls?: LumraControlsConfig\n behavior?: LumraBehaviorConfig\n paywall?: LumraPaywallConfig\n ads?: LumraAdsConfig\n storage?: LumraStorageConfig\n cloud?: LumraCloudConfig\n}\n\n/** Type-safe helper — returns the config unchanged but provides IDE autocomplete */\nexport function defineLumraConfig(config: LumraConfig): LumraConfig {\n return config\n}\n\n// ── Context ───────────────────────────────────────────────────────────────────\n\nconst ConfigCtx = createContext<LumraConfig>({})\n\nexport function LumraConfigProvider({\n config,\n children,\n}: {\n config: LumraConfig\n children: React.ReactNode\n}) {\n return <ConfigCtx.Provider value={config}>{children}</ConfigCtx.Provider>\n}\n\nexport function useLumraConfig(): LumraConfig {\n return useContext(ConfigCtx)\n}\n","import React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react'\nimport type { Plugin, PlayerInstance } from '@lumra/core'\nimport { paywallGatePlugin } from '@lumra/plugins'\nimport { adPlugin } from '@lumra/plugins'\nimport {\n ControlsBar,\n BufferingSpinner,\n AdOverlay,\n PaywallOverlay,\n MediaInfoOverlay,\n} from '@lumra/ui'\nimport type { ChapterMark, LumraPurchaseOption } from '@lumra/ui'\nimport { usePlayer } from './usePlayer'\nimport { useLumraConfig } from './config'\nimport type { AdInfo } from '@lumra/plugins'\n\n// ── Public types ──────────────────────────────────────────────────────────────\n\nexport interface LumraMediaInfo {\n title?: string\n creator?: {\n name: string\n avatarUrl?: string\n }\n}\n\nexport interface LumraTrack {\n /** Path or URL to the .vtt file */\n src: string\n kind: 'subtitles' | 'captions' | 'descriptions' | 'chapters' | 'metadata'\n /** BCP 47 language tag, e.g. \"en\", \"es-419\" */\n srcLang: string\n label: string\n default?: boolean\n}\n\nexport interface LumraPlayerPaywall {\n /** Whether the current user is locked out of this content */\n locked: boolean\n /** Called when the user clicks \"Buy now\" in the paywall overlay */\n onUnlock: () => void | Promise<void>\n /** Multi-option mode: Buy / Rent / Custom — pricing comes from your platform API */\n options?: LumraPurchaseOption[]\n /** Override title shown in paywall overlay */\n title?: string\n /** Override subtitle shown in paywall overlay */\n subtitle?: string\n /** Custom paywall overlay component — defaults to the built-in PaywallOverlay */\n overlay?: React.ComponentType<{ onUnlock: () => void }>\n}\n\nexport interface LumraPlayerComponents {\n /** @deprecated Pass `paywall` prop instead — it wires the preview gate automatically */\n Paywall?: React.ComponentType<{ onUnlock: () => void }>\n}\n\nexport interface LumraPlayerProps {\n src?: string\n /** Poster image shown before the video loads */\n poster?: string\n autoplay?: boolean\n muted?: boolean\n volume?: number\n loop?: boolean\n /**\n * Paywall configuration. When provided:\n * - Automatically registers `paywallGatePlugin` with `config.paywall.previewDuration`\n * - Shows the paywall overlay after the gate fires\n * - No manual plugin wiring needed in your component\n */\n paywall?: LumraPlayerPaywall\n /**\n * Title + creator info displayed as an overlay at the top of the player.\n * Data should come from your platform API, not hardcoded here.\n */\n mediaInfo?: LumraMediaInfo\n /**\n * WebVTT subtitle / caption tracks.\n * Maps directly to HTML <track> elements.\n */\n tracks?: LumraTrack[]\n /**\n * Chapter markers shown as dividers on the seek bar.\n * Load from your platform API — e.g. `video.chapters` from your DB.\n */\n chapters?: ChapterMark[]\n /**\n * Normalized 0-1 heatmap values (one per seek-bar segment).\n * Supply via heatmapPlugin's onData callback or directly from your API.\n */\n heatmapData?: number[]\n /**\n * Render only the <video> element with no built-in controls or overlays.\n * Use when you want full UI control.\n */\n headless?: boolean\n /** Additional plugins (analytics, custom gates, etc.) */\n plugins?: Plugin[]\n /** @deprecated Use `paywall` prop for paywall overlay */\n components?: LumraPlayerComponents\n className?: string\n style?: React.CSSProperties\n onPlay?: () => void\n onPause?: () => void\n onEnded?: () => void\n onTimeUpdate?: (currentTime: number, duration: number) => void\n onError?: (code: number, message: string) => void\n}\n\n// ── Component ─────────────────────────────────────────────────────────────────\n\nexport const LumraPlayer = forwardRef<PlayerInstance, LumraPlayerProps>(\n function LumraPlayer(props, ref) {\n const {\n src,\n poster,\n autoplay,\n muted: mutedProp,\n volume,\n loop,\n paywall,\n mediaInfo,\n tracks,\n chapters,\n heatmapData,\n headless = false,\n plugins = [],\n components = {},\n className,\n style,\n onPlay,\n onPause,\n onEnded,\n onTimeUpdate,\n onError,\n } = props\n\n const config = useLumraConfig()\n\n // ── Resolve src (storage baseUrl + optional cloud signing) ─────────────────\n const signUrl = config.cloud?.signUrl\n const baseUrl = config.storage?.baseUrl?.replace(/\\/$/, '') ?? ''\n const [resolvedSrc, setResolvedSrc] = useState<string | undefined>(undefined)\n\n useEffect(() => {\n if (!src) { setResolvedSrc(undefined); return }\n\n const expanded = baseUrl && !src.startsWith('http') && !src.startsWith('//')\n ? `${baseUrl}/${src.replace(/^\\//, '')}`\n : src\n\n if (!signUrl) { setResolvedSrc(expanded); return }\n\n let cancelled = false\n void signUrl(expanded)\n .then((s) => { if (!cancelled) setResolvedSrc(s) })\n .catch(() => { if (!cancelled) setResolvedSrc(expanded) })\n return () => { cancelled = true }\n }, [src, signUrl, baseUrl])\n\n // ── Player ─────────────────────────────────────────────────────────────────\n const effectiveAutoplay = autoplay ?? config.behavior?.autoplay\n const effectiveMuted = mutedProp ?? config.behavior?.muted\n const effectiveLoop = loop ?? config.behavior?.loop\n\n const { player, videoRef, paused, currentTime, duration, volume: vol, muted } = usePlayer({\n ...(resolvedSrc !== undefined && { src: resolvedSrc }),\n ...(effectiveAutoplay !== undefined && { autoplay: effectiveAutoplay }),\n ...(effectiveMuted !== undefined && { muted: effectiveMuted }),\n ...(volume !== undefined && { volume }),\n ...(effectiveLoop !== undefined && { loop: effectiveLoop }),\n })\n\n useImperativeHandle(ref, () => player!, [player])\n\n // ── Paywall gate (auto-wired when `paywall` prop is passed) ───────────────\n const [gateTriggered, setGateTriggered] = useState(false)\n const paywallPluginRef = useRef<ReturnType<typeof paywallGatePlugin> | null>(null)\n\n useEffect(() => {\n if (!player || !paywall?.locked) return\n const previewDuration = config.paywall?.previewDuration ?? 5\n paywallPluginRef.current = paywallGatePlugin({\n previewDuration,\n onBlock: () => setGateTriggered(true),\n })\n player.use(paywallPluginRef.current)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [player])\n\n // ── Ad system (auto-wired from config.ads) ─────────────────────────────────\n const [adState, setAdState] = useState<AdInfo | null>(null)\n const adPluginRef = useRef<(Plugin & { skipAd(): void }) | null>(null)\n\n useEffect(() => {\n if (!player || !config.ads) return\n const instance = adPlugin({\n ...config.ads,\n onAdStart: (info) => setAdState(info),\n onAdTick: (info) => setAdState((prev) => prev ? { ...prev, elapsed: info.elapsed } : null),\n onAdEnd: () => setAdState(null),\n onAdSkip: () => setAdState(null),\n }) as Plugin & { skipAd(): void }\n adPluginRef.current = instance\n player.use(instance)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [player])\n\n // ── User-provided plugins ─────────────────────────────────────────────────\n useEffect(() => {\n if (!player) return\n for (const plugin of plugins) player.use(plugin)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [player])\n\n // ── External callbacks ────────────────────────────────────────────────────\n useEffect(() => {\n if (!player) return\n if (onPlay) player.on('play', onPlay)\n if (onPause) player.on('pause', onPause)\n if (onEnded) player.on('ended', onEnded)\n if (onTimeUpdate) player.on('timeupdate', ({ currentTime: ct, duration: d }) => onTimeUpdate(ct, d))\n if (onError) player.on('error', ({ code, message }) => onError(code, message))\n return () => {\n if (onPlay) player.off('play', onPlay)\n if (onPause) player.off('pause', onPause)\n if (onEnded) player.off('ended', onEnded)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [player])\n\n // ── Buffering & error ─────────────────────────────────────────────────────\n const [buffering, setBuffering] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n if (!player) return\n player.on('buffering', () => setBuffering(true))\n player.on('ready', () => setBuffering(false))\n player.on('play', () => { setBuffering(false); setError(null) })\n player.on('error', ({ message }) => setError(message))\n }, [player])\n\n // ── Fullscreen ────────────────────────────────────────────────────────────\n const containerRef = useRef<HTMLDivElement>(null)\n const [fullscreen, setFullscreen] = useState(false)\n\n useEffect(() => {\n const onChange = () => setFullscreen(document.fullscreenElement === containerRef.current)\n document.addEventListener('fullscreenchange', onChange)\n return () => document.removeEventListener('fullscreenchange', onChange)\n }, [])\n\n const toggleFullscreen = useCallback(() => {\n if (!containerRef.current) return\n if (fullscreen) void document.exitFullscreen()\n else void containerRef.current.requestFullscreen()\n }, [fullscreen])\n\n // ── Picture-in-Picture ────────────────────────────────────────────────────\n const [pip, setPip] = useState(false)\n\n useEffect(() => {\n const onEnter = () => setPip(true)\n const onLeave = () => setPip(false)\n document.addEventListener('enterpictureinpicture', onEnter)\n document.addEventListener('leavepictureinpicture', onLeave)\n return () => {\n document.removeEventListener('enterpictureinpicture', onEnter)\n document.removeEventListener('leavepictureinpicture', onLeave)\n }\n }, [])\n\n const togglePip = useCallback(async () => {\n if (!videoRef.current) return\n try {\n if (pip) await document.exitPictureInPicture()\n else await videoRef.current.requestPictureInPicture()\n } catch { /* PiP not supported or denied */ }\n }, [pip, videoRef])\n\n // ── Controls auto-hide ────────────────────────────────────────────────────\n const autoHide = config.controls?.autoHide ?? true\n const autoHideDelay = config.controls?.autoHideDelay ?? 3000\n const [controlsVisible, setControlsVisible] = useState(true)\n const hideTimer = useRef<ReturnType<typeof setTimeout>>()\n const pausedRef = useRef(paused)\n useEffect(() => { pausedRef.current = paused }, [paused])\n\n const showControls = useCallback(() => {\n setControlsVisible(true)\n clearTimeout(hideTimer.current)\n if (autoHide) {\n hideTimer.current = setTimeout(() => {\n if (!pausedRef.current) setControlsVisible(false)\n }, autoHideDelay)\n }\n }, [autoHide, autoHideDelay])\n\n useEffect(() => {\n if (paused) { setControlsVisible(true); clearTimeout(hideTimer.current) }\n }, [paused])\n\n useEffect(() => () => clearTimeout(hideTimer.current), [])\n\n // ── Keyboard shortcuts ────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (!player) return\n switch (e.code) {\n case 'Space': case 'KeyK':\n e.preventDefault(); paused ? void player.play() : player.pause(); break\n case 'KeyF':\n e.preventDefault(); toggleFullscreen(); break\n case 'KeyP':\n e.preventDefault(); void togglePip(); break\n case 'KeyM':\n e.preventDefault(); muted ? player.unmute() : player.mute(); break\n case 'ArrowLeft':\n e.preventDefault(); player.seek(Math.max(0, currentTime - 10)); break\n case 'ArrowRight':\n e.preventDefault(); player.seek(Math.min(duration, currentTime + 10)); break\n case 'ArrowUp':\n e.preventDefault(); player.setVolume(Math.min(1, vol + 0.1)); break\n case 'ArrowDown':\n e.preventDefault(); player.setVolume(Math.max(0, vol - 0.1)); break\n }\n },\n [player, paused, muted, currentTime, duration, vol, toggleFullscreen, togglePip],\n )\n\n // ── Handlers ──────────────────────────────────────────────────────────────\n const handleTogglePlay = useCallback(() => { if (!player) return; paused ? void player.play() : player.pause() }, [player, paused])\n const handleSeek = useCallback((t: number) => player?.seek(t), [player])\n const handleVolumeChange = useCallback((v: number) => { player?.setVolume(v); if (v > 0 && muted) player?.unmute() }, [player, muted])\n const handleToggleMute = useCallback(() => { if (!player) return; muted ? player.unmute() : player.mute() }, [player, muted])\n\n // ── Config-driven display options ─────────────────────────────────────────\n const blockDownload = config.behavior?.blockDownload ?? false\n const borderRadius = config.theme?.borderRadius ?? '0px'\n const accentColor = config.theme?.accentColor ?? '#ffffff'\n\n const show = {\n play: config.controls?.play ?? true,\n progress: config.controls?.progress ?? true,\n volume: config.controls?.volume ?? true,\n fullscreen: config.controls?.fullscreen ?? true,\n pip: config.controls?.pip ?? true,\n time: config.controls?.time ?? true,\n }\n\n const theme = {\n accentColor,\n textColor: config.theme?.textColor ?? '#ffffff',\n ...(config.theme?.controlsBg !== undefined && { controlsBg: config.theme.controlsBg }),\n }\n\n // ── Paywall overlay resolution ─────────────────────────────────────────────\n const showPaywallOverlay = paywall?.locked === true && gateTriggered\n const handleUnlock = useCallback(() => { void paywall?.onUnlock() }, [paywall])\n\n // ── Deprecated components.Paywall passthrough ──────────────────────────────\n const { Paywall: DeprecatedPaywall } = components\n\n return (\n <div\n ref={containerRef}\n style={{ position: 'relative', background: '#000', borderRadius, overflow: 'hidden', outline: 'none', ...style }}\n className={className}\n tabIndex={0}\n onKeyDown={handleKeyDown}\n onMouseMove={showControls}\n onMouseEnter={showControls}\n onTouchStart={showControls}\n onMouseLeave={() => { if (!paused && autoHide) setControlsVisible(false) }}\n >\n {/* Video element */}\n <video\n ref={videoRef}\n style={{ width: '100%', height: '100%', display: headless ? 'none' : 'block' }}\n playsInline\n {...(poster !== undefined && { poster })}\n controlsList={blockDownload ? 'nodownload' : undefined}\n onContextMenu={blockDownload ? (e) => e.preventDefault() : undefined}\n >\n {tracks?.map((t) => (\n <track\n key={`${t.kind}-${t.srcLang}`}\n src={t.src}\n kind={t.kind}\n srcLang={t.srcLang}\n label={t.label}\n {...(t.default ? { default: true } : {})}\n />\n ))}\n </video>\n\n {/* Download blocker overlay (blocks drag-to-save) */}\n {blockDownload && !headless && (\n <div style={{ position: 'absolute', inset: 0, zIndex: 1, pointerEvents: 'none' }} />\n )}\n\n {!headless && (\n <>\n {/* Media info overlay (title + creator) */}\n {mediaInfo && (\n <div style={{ position: 'absolute', inset: 0, zIndex: 4, pointerEvents: 'none' }}>\n <MediaInfoOverlay\n {...(mediaInfo.title !== undefined && { title: mediaInfo.title })}\n {...(mediaInfo.creator !== undefined && { creator: mediaInfo.creator })}\n />\n </div>\n )}\n\n {/* Buffering spinner */}\n {buffering && !paused && <BufferingSpinner color={accentColor} />}\n\n {/* Error state */}\n {error && (\n <div style={errorOverlay}>\n <span style={{ fontSize: '13px', opacity: 0.75 }}>⚠ {error}</span>\n <button\n style={retryBtn}\n onClick={() => { setError(null); if (resolvedSrc) player?.setSrc(resolvedSrc) }}\n >\n Retry\n </button>\n </div>\n )}\n\n {/* Ad overlay — shown while an ad is playing */}\n {adState && (\n <AdOverlay\n phase={adState.phase}\n elapsed={adState.elapsed}\n skipAfter={adState.skipAfter}\n accentColor={accentColor}\n {...(adState.clickThroughUrl !== undefined && { clickThroughUrl: adState.clickThroughUrl })}\n onSkip={() => adPluginRef.current?.skipAd()}\n />\n )}\n\n {/* Paywall overlay — shown after the preview gate fires */}\n {showPaywallOverlay && (\n paywall?.overlay\n ? <paywall.overlay onUnlock={handleUnlock} />\n : <PaywallOverlay\n onUnlock={handleUnlock}\n {...(paywall?.title !== undefined && { title: paywall.title })}\n {...(paywall?.subtitle !== undefined && { subtitle: paywall.subtitle })}\n {...(paywall?.options !== undefined && { options: paywall.options })}\n accentColor={accentColor}\n />\n )}\n\n {/* Deprecated components.Paywall passthrough */}\n {!showPaywallOverlay && DeprecatedPaywall && (\n <DeprecatedPaywall onUnlock={() => void player?.play()} />\n )}\n\n {/* Built-in controls bar */}\n <div\n style={{\n position: 'absolute', bottom: 0, left: 0, right: 0, zIndex: 5,\n opacity: controlsVisible ? 1 : 0,\n transition: 'opacity 0.25s ease',\n pointerEvents: controlsVisible ? 'auto' : 'none',\n }}\n >\n <ControlsBar\n paused={paused}\n currentTime={currentTime}\n duration={duration}\n volume={vol}\n muted={muted}\n fullscreen={fullscreen}\n pip={pip}\n show={show}\n theme={theme}\n {...(chapters !== undefined && { chapters })}\n {...(heatmapData !== undefined && { heatmapData })}\n onTogglePlay={handleTogglePlay}\n onSeek={handleSeek}\n onVolumeChange={handleVolumeChange}\n onToggleMute={handleToggleMute}\n onToggleFullscreen={toggleFullscreen}\n onTogglePip={() => void togglePip()}\n />\n </div>\n </>\n )}\n </div>\n )\n },\n)\n\nconst errorOverlay: React.CSSProperties = {\n position: 'absolute', inset: 0, zIndex: 10,\n display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center',\n background: 'rgba(0,0,0,0.75)', color: '#fff', gap: '12px',\n}\n\nconst retryBtn: React.CSSProperties = {\n padding: '8px 18px',\n background: 'rgba(255,255,255,0.12)',\n border: '1px solid rgba(255,255,255,0.25)',\n borderRadius: '6px', color: '#fff', cursor: 'pointer', fontSize: '13px',\n}\n"]}
|