@haklex/rich-renderer-video 0.0.40 → 0.0.42
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/VideoRenderer-B3RM54FU.js +333 -0
- package/dist/index.mjs +3 -315
- package/dist/static.mjs +4 -0
- package/package.json +4 -4
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slider } from "@base-ui/react/slider";
|
|
3
|
+
import { Play, Pause, VolumeX, Volume2, Loader2, Download, Minimize, Maximize } from "lucide-react";
|
|
4
|
+
import { useRef, useState, useMemo, useCallback, useEffect } from "react";
|
|
5
|
+
var semanticClassNames = { root: "rr-video-root", player: "rr-video-player", element: "rr-video-element", indicator: "rr-video-indicator", controls: "rr-video-controls", button: "rr-video-btn", progress: "rr-video-progress", progressControl: "rr-video-progress-control", progressTrack: "rr-video-progress-track", progressRange: "rr-video-progress-range", progressThumb: "rr-video-progress-thumb", spin: "rr-video-spin", editTrigger: "rr-video-edit-trigger", editPreview: "rr-video-edit-preview", editVideo: "rr-video-edit-video", editOverlay: "rr-video-edit-overlay", editPlaceholder: "rr-video-edit-placeholder", editPanel: "rr-video-edit-panel", editField: "rr-video-edit-field", editFieldIcon: "rr-video-edit-field-icon", editInput: "rr-video-edit-input", editActions: "rr-video-edit-actions", editActionButton: "rr-video-edit-action-btn", editActionButtonEnd: "rr-video-edit-action-btn--end" };
|
|
6
|
+
var root = "_1x2h1ol0";
|
|
7
|
+
var player = "_1x2h1ol1";
|
|
8
|
+
var element = "_1x2h1ol2";
|
|
9
|
+
var indicator = "_1x2h1ol4";
|
|
10
|
+
var controls = "_1x2h1ol5";
|
|
11
|
+
var button = "_1x2h1ol6";
|
|
12
|
+
var progress = "_1x2h1ol7";
|
|
13
|
+
var progressControl = "_1x2h1ol8";
|
|
14
|
+
var progressTrack = "_1x2h1ol9";
|
|
15
|
+
var progressRange = "_1x2h1ola";
|
|
16
|
+
var progressThumb = "_1x2h1olb";
|
|
17
|
+
var spin = "_1x2h1old";
|
|
18
|
+
var editTrigger = "_1x2h1ole";
|
|
19
|
+
var editPreview = "_1x2h1olf";
|
|
20
|
+
var editVideo = "_1x2h1olg";
|
|
21
|
+
var editOverlay = "_1x2h1olh";
|
|
22
|
+
var editPlaceholder = "_1x2h1oli";
|
|
23
|
+
var editPanel = "_1x2h1olj";
|
|
24
|
+
var editField = "_1x2h1olk";
|
|
25
|
+
var editFieldIcon = "_1x2h1oll";
|
|
26
|
+
var editInput = "_1x2h1olm";
|
|
27
|
+
var editActions = "_1x2h1oln";
|
|
28
|
+
var editActionButton = "_1x2h1olo";
|
|
29
|
+
var editActionButtonEnd = "_1x2h1olp";
|
|
30
|
+
const PlayIcon = ({ size = 20 }) => /* @__PURE__ */ jsx(Play, { size });
|
|
31
|
+
const PauseIcon = ({ size = 20 }) => /* @__PURE__ */ jsx(Pause, { size });
|
|
32
|
+
const VolumeIcon = () => /* @__PURE__ */ jsx(Volume2, { size: 20 });
|
|
33
|
+
const VolumeMuteIcon = () => /* @__PURE__ */ jsx(VolumeX, { size: 20 });
|
|
34
|
+
const DownloadIcon = () => /* @__PURE__ */ jsx(Download, { size: 20 });
|
|
35
|
+
const LoadingIcon = () => /* @__PURE__ */ jsx(
|
|
36
|
+
Loader2,
|
|
37
|
+
{
|
|
38
|
+
size: 20,
|
|
39
|
+
className: `${spin} ${semanticClassNames.spin}`
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
const FullscreenIcon = () => /* @__PURE__ */ jsx(Maximize, { size: 20 });
|
|
43
|
+
const ExitFullscreenIcon = () => /* @__PURE__ */ jsx(Minimize, { size: 20 });
|
|
44
|
+
const VideoRenderer = ({
|
|
45
|
+
src,
|
|
46
|
+
poster,
|
|
47
|
+
width,
|
|
48
|
+
height
|
|
49
|
+
}) => {
|
|
50
|
+
const wrapperRef = useRef(null);
|
|
51
|
+
const videoRef = useRef(null);
|
|
52
|
+
const [playing, setPlaying] = useState(false);
|
|
53
|
+
const [duration, setDuration] = useState(0);
|
|
54
|
+
const [currentTime, setCurrentTime] = useState(0);
|
|
55
|
+
const [muted, setMuted] = useState(false);
|
|
56
|
+
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
57
|
+
const [indicator$1, setIndicator] = useState(null);
|
|
58
|
+
const [draggingTime, setDraggingTime] = useState(null);
|
|
59
|
+
const [isDownloading, setIsDownloading] = useState(false);
|
|
60
|
+
const dragWasPlayingRef = useRef(false);
|
|
61
|
+
const indicatorTimerRef = useRef(void 0);
|
|
62
|
+
const indicatorKeyRef = useRef(0);
|
|
63
|
+
const aspectRatio = useMemo(() => {
|
|
64
|
+
if (width && height && width > 0 && height > 0) {
|
|
65
|
+
return `${width} / ${height}`;
|
|
66
|
+
}
|
|
67
|
+
return "16 / 9";
|
|
68
|
+
}, [height, width]);
|
|
69
|
+
const showIndicator = useCallback((type) => {
|
|
70
|
+
indicatorKeyRef.current += 1;
|
|
71
|
+
setIndicator({ type, key: indicatorKeyRef.current });
|
|
72
|
+
clearTimeout(indicatorTimerRef.current);
|
|
73
|
+
indicatorTimerRef.current = setTimeout(() => setIndicator(null), 500);
|
|
74
|
+
}, []);
|
|
75
|
+
const play = useCallback(() => {
|
|
76
|
+
void videoRef.current?.play();
|
|
77
|
+
}, []);
|
|
78
|
+
const pause = useCallback(() => {
|
|
79
|
+
videoRef.current?.pause();
|
|
80
|
+
}, []);
|
|
81
|
+
const togglePlay = useCallback(() => {
|
|
82
|
+
const video = videoRef.current;
|
|
83
|
+
if (!video) return;
|
|
84
|
+
if (video.paused) {
|
|
85
|
+
play();
|
|
86
|
+
showIndicator("play");
|
|
87
|
+
} else {
|
|
88
|
+
pause();
|
|
89
|
+
showIndicator("pause");
|
|
90
|
+
}
|
|
91
|
+
}, [pause, play, showIndicator]);
|
|
92
|
+
const seekTo = useCallback((time) => {
|
|
93
|
+
const video = videoRef.current;
|
|
94
|
+
if (!video) return;
|
|
95
|
+
const clamped = Math.min(
|
|
96
|
+
Math.max(0, time),
|
|
97
|
+
Number.isFinite(video.duration) ? video.duration : time
|
|
98
|
+
);
|
|
99
|
+
video.currentTime = clamped;
|
|
100
|
+
setCurrentTime(clamped);
|
|
101
|
+
}, []);
|
|
102
|
+
const toggleMute = useCallback(() => {
|
|
103
|
+
const video = videoRef.current;
|
|
104
|
+
if (!video) return;
|
|
105
|
+
video.muted = !video.muted;
|
|
106
|
+
setMuted(video.muted);
|
|
107
|
+
}, []);
|
|
108
|
+
const toggleFullscreen = useCallback(() => {
|
|
109
|
+
const wrapper = wrapperRef.current;
|
|
110
|
+
if (!wrapper) return;
|
|
111
|
+
if (!document.fullscreenElement) {
|
|
112
|
+
void wrapper.requestFullscreen();
|
|
113
|
+
} else {
|
|
114
|
+
void document.exitFullscreen();
|
|
115
|
+
}
|
|
116
|
+
}, []);
|
|
117
|
+
const downloadingRef = useRef(false);
|
|
118
|
+
const downloadVideo = useCallback(() => {
|
|
119
|
+
if (downloadingRef.current) return;
|
|
120
|
+
downloadingRef.current = true;
|
|
121
|
+
setIsDownloading(true);
|
|
122
|
+
const filename = src.split("/").pop() || "video";
|
|
123
|
+
const fallback = () => {
|
|
124
|
+
const a = document.createElement("a");
|
|
125
|
+
a.href = src;
|
|
126
|
+
a.download = filename;
|
|
127
|
+
a.rel = "noopener noreferrer";
|
|
128
|
+
a.click();
|
|
129
|
+
};
|
|
130
|
+
fetch(src).then((res) => {
|
|
131
|
+
if (!res.ok) throw new Error(res.statusText);
|
|
132
|
+
return res.blob();
|
|
133
|
+
}).then((blob) => {
|
|
134
|
+
const url = URL.createObjectURL(blob);
|
|
135
|
+
const a = document.createElement("a");
|
|
136
|
+
a.href = url;
|
|
137
|
+
a.download = filename;
|
|
138
|
+
a.click();
|
|
139
|
+
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
140
|
+
}).catch(() => fallback()).finally(() => {
|
|
141
|
+
downloadingRef.current = false;
|
|
142
|
+
setIsDownloading(false);
|
|
143
|
+
});
|
|
144
|
+
}, [src]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
const video = videoRef.current;
|
|
147
|
+
if (!video) return;
|
|
148
|
+
const onLoadedMetadata = () => {
|
|
149
|
+
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
150
|
+
setMuted(video.muted);
|
|
151
|
+
};
|
|
152
|
+
const onDurationChange = () => {
|
|
153
|
+
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
154
|
+
};
|
|
155
|
+
const onTimeUpdate = () => setCurrentTime(video.currentTime);
|
|
156
|
+
const onPlay = () => setPlaying(true);
|
|
157
|
+
const onPause = () => setPlaying(false);
|
|
158
|
+
const onVolumeChange = () => setMuted(video.muted);
|
|
159
|
+
video.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
160
|
+
video.addEventListener("durationchange", onDurationChange);
|
|
161
|
+
video.addEventListener("timeupdate", onTimeUpdate);
|
|
162
|
+
video.addEventListener("play", onPlay);
|
|
163
|
+
video.addEventListener("pause", onPause);
|
|
164
|
+
video.addEventListener("volumechange", onVolumeChange);
|
|
165
|
+
return () => {
|
|
166
|
+
video.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
167
|
+
video.removeEventListener("durationchange", onDurationChange);
|
|
168
|
+
video.removeEventListener("timeupdate", onTimeUpdate);
|
|
169
|
+
video.removeEventListener("play", onPlay);
|
|
170
|
+
video.removeEventListener("pause", onPause);
|
|
171
|
+
video.removeEventListener("volumechange", onVolumeChange);
|
|
172
|
+
};
|
|
173
|
+
}, []);
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const handler = () => setIsFullscreen(!!document.fullscreenElement);
|
|
176
|
+
document.addEventListener("fullscreenchange", handler);
|
|
177
|
+
return () => document.removeEventListener("fullscreenchange", handler);
|
|
178
|
+
}, []);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
return () => clearTimeout(indicatorTimerRef.current);
|
|
181
|
+
}, []);
|
|
182
|
+
const timelineValue = draggingTime ?? currentTime;
|
|
183
|
+
return /* @__PURE__ */ jsx("figure", { className: `${root} ${semanticClassNames.root}`, children: /* @__PURE__ */ jsxs(
|
|
184
|
+
"div",
|
|
185
|
+
{
|
|
186
|
+
className: `${player} ${semanticClassNames.player}`,
|
|
187
|
+
ref: wrapperRef,
|
|
188
|
+
style: { aspectRatio },
|
|
189
|
+
children: [
|
|
190
|
+
/* @__PURE__ */ jsx(
|
|
191
|
+
"video",
|
|
192
|
+
{
|
|
193
|
+
ref: videoRef,
|
|
194
|
+
className: `${element} ${semanticClassNames.element}`,
|
|
195
|
+
src,
|
|
196
|
+
poster,
|
|
197
|
+
preload: "metadata",
|
|
198
|
+
playsInline: true,
|
|
199
|
+
onClick: togglePlay,
|
|
200
|
+
onDoubleClick: toggleFullscreen
|
|
201
|
+
}
|
|
202
|
+
),
|
|
203
|
+
indicator$1 && /* @__PURE__ */ jsx(
|
|
204
|
+
"span",
|
|
205
|
+
{
|
|
206
|
+
className: `${indicator} ${semanticClassNames.indicator}`,
|
|
207
|
+
"aria-hidden": true,
|
|
208
|
+
children: indicator$1.type === "play" ? /* @__PURE__ */ jsx(PlayIcon, { size: 32 }) : /* @__PURE__ */ jsx(PauseIcon, { size: 32 })
|
|
209
|
+
},
|
|
210
|
+
indicator$1.key
|
|
211
|
+
),
|
|
212
|
+
/* @__PURE__ */ jsxs(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
className: `${controls} ${semanticClassNames.controls}`,
|
|
216
|
+
onClick: (e) => e.stopPropagation(),
|
|
217
|
+
children: [
|
|
218
|
+
/* @__PURE__ */ jsx(
|
|
219
|
+
"button",
|
|
220
|
+
{
|
|
221
|
+
type: "button",
|
|
222
|
+
className: `${button} ${semanticClassNames.button}`,
|
|
223
|
+
"aria-label": playing ? "Pause" : "Play",
|
|
224
|
+
onClick: togglePlay,
|
|
225
|
+
children: playing ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {})
|
|
226
|
+
}
|
|
227
|
+
),
|
|
228
|
+
/* @__PURE__ */ jsx(
|
|
229
|
+
Slider.Root,
|
|
230
|
+
{
|
|
231
|
+
className: `${progress} ${semanticClassNames.progress}`,
|
|
232
|
+
min: 0,
|
|
233
|
+
max: duration || 0,
|
|
234
|
+
step: 0.01,
|
|
235
|
+
value: timelineValue,
|
|
236
|
+
onValueChange: (value) => {
|
|
237
|
+
setDraggingTime(value);
|
|
238
|
+
},
|
|
239
|
+
onValueCommitted: (value) => {
|
|
240
|
+
seekTo(value);
|
|
241
|
+
setDraggingTime(null);
|
|
242
|
+
if (dragWasPlayingRef.current) {
|
|
243
|
+
play();
|
|
244
|
+
}
|
|
245
|
+
},
|
|
246
|
+
children: /* @__PURE__ */ jsx(
|
|
247
|
+
Slider.Control,
|
|
248
|
+
{
|
|
249
|
+
className: `${progressControl} ${semanticClassNames.progressControl}`,
|
|
250
|
+
onPointerDown: () => {
|
|
251
|
+
dragWasPlayingRef.current = playing;
|
|
252
|
+
pause();
|
|
253
|
+
setDraggingTime(currentTime);
|
|
254
|
+
},
|
|
255
|
+
children: /* @__PURE__ */ jsxs(
|
|
256
|
+
Slider.Track,
|
|
257
|
+
{
|
|
258
|
+
className: `${progressTrack} ${semanticClassNames.progressTrack}`,
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ jsx(
|
|
261
|
+
Slider.Indicator,
|
|
262
|
+
{
|
|
263
|
+
className: `${progressRange} ${semanticClassNames.progressRange}`
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
/* @__PURE__ */ jsx(
|
|
267
|
+
Slider.Thumb,
|
|
268
|
+
{
|
|
269
|
+
className: `${progressThumb} ${semanticClassNames.progressThumb}`,
|
|
270
|
+
"aria-label": "Playback progress"
|
|
271
|
+
}
|
|
272
|
+
)
|
|
273
|
+
]
|
|
274
|
+
}
|
|
275
|
+
)
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
),
|
|
280
|
+
/* @__PURE__ */ jsx(
|
|
281
|
+
"button",
|
|
282
|
+
{
|
|
283
|
+
type: "button",
|
|
284
|
+
className: `${button} ${semanticClassNames.button}`,
|
|
285
|
+
"aria-label": muted ? "Unmute" : "Mute",
|
|
286
|
+
onClick: toggleMute,
|
|
287
|
+
children: muted ? /* @__PURE__ */ jsx(VolumeMuteIcon, {}) : /* @__PURE__ */ jsx(VolumeIcon, {})
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
/* @__PURE__ */ jsx(
|
|
291
|
+
"button",
|
|
292
|
+
{
|
|
293
|
+
type: "button",
|
|
294
|
+
className: `${button} ${semanticClassNames.button}`,
|
|
295
|
+
"aria-label": "Download",
|
|
296
|
+
onClick: downloadVideo,
|
|
297
|
+
children: isDownloading ? /* @__PURE__ */ jsx(LoadingIcon, {}) : /* @__PURE__ */ jsx(DownloadIcon, {})
|
|
298
|
+
}
|
|
299
|
+
),
|
|
300
|
+
/* @__PURE__ */ jsx(
|
|
301
|
+
"button",
|
|
302
|
+
{
|
|
303
|
+
type: "button",
|
|
304
|
+
className: `${button} ${semanticClassNames.button}`,
|
|
305
|
+
"aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
|
|
306
|
+
onClick: toggleFullscreen,
|
|
307
|
+
children: isFullscreen ? /* @__PURE__ */ jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx(FullscreenIcon, {})
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
) });
|
|
316
|
+
};
|
|
317
|
+
export {
|
|
318
|
+
VideoRenderer as V,
|
|
319
|
+
editVideo as a,
|
|
320
|
+
editOverlay as b,
|
|
321
|
+
editPlaceholder as c,
|
|
322
|
+
editTrigger as d,
|
|
323
|
+
editPreview as e,
|
|
324
|
+
editPanel as f,
|
|
325
|
+
editField as g,
|
|
326
|
+
editFieldIcon as h,
|
|
327
|
+
editInput as i,
|
|
328
|
+
editActions as j,
|
|
329
|
+
editActionButton as k,
|
|
330
|
+
editActionButtonEnd as l,
|
|
331
|
+
root as r,
|
|
332
|
+
semanticClassNames as s
|
|
333
|
+
};
|
package/dist/index.mjs
CHANGED
|
@@ -3,321 +3,9 @@ import { useRendererMode } from "@haklex/rich-editor";
|
|
|
3
3
|
import { Popover, PopoverTrigger, PopoverPanel } from "@haklex/rich-editor-ui";
|
|
4
4
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
5
5
|
import { $getNearestNodeFromDOMNode } from "lexical";
|
|
6
|
-
import {
|
|
7
|
-
import { useRef, useState,
|
|
8
|
-
import {
|
|
9
|
-
var semanticClassNames = { root: "rr-video-root", player: "rr-video-player", element: "rr-video-element", indicator: "rr-video-indicator", controls: "rr-video-controls", button: "rr-video-btn", progress: "rr-video-progress", progressControl: "rr-video-progress-control", progressTrack: "rr-video-progress-track", progressRange: "rr-video-progress-range", progressThumb: "rr-video-progress-thumb", spin: "rr-video-spin", editTrigger: "rr-video-edit-trigger", editPreview: "rr-video-edit-preview", editVideo: "rr-video-edit-video", editOverlay: "rr-video-edit-overlay", editPlaceholder: "rr-video-edit-placeholder", editPanel: "rr-video-edit-panel", editField: "rr-video-edit-field", editFieldIcon: "rr-video-edit-field-icon", editInput: "rr-video-edit-input", editActions: "rr-video-edit-actions", editActionButton: "rr-video-edit-action-btn", editActionButtonEnd: "rr-video-edit-action-btn--end" };
|
|
10
|
-
var root = "_1x2h1ol0";
|
|
11
|
-
var player = "_1x2h1ol1";
|
|
12
|
-
var element = "_1x2h1ol2";
|
|
13
|
-
var indicator = "_1x2h1ol4";
|
|
14
|
-
var controls = "_1x2h1ol5";
|
|
15
|
-
var button = "_1x2h1ol6";
|
|
16
|
-
var progress = "_1x2h1ol7";
|
|
17
|
-
var progressControl = "_1x2h1ol8";
|
|
18
|
-
var progressTrack = "_1x2h1ol9";
|
|
19
|
-
var progressRange = "_1x2h1ola";
|
|
20
|
-
var progressThumb = "_1x2h1olb";
|
|
21
|
-
var spin = "_1x2h1old";
|
|
22
|
-
var editTrigger = "_1x2h1ole";
|
|
23
|
-
var editPreview = "_1x2h1olf";
|
|
24
|
-
var editVideo = "_1x2h1olg";
|
|
25
|
-
var editOverlay = "_1x2h1olh";
|
|
26
|
-
var editPlaceholder = "_1x2h1oli";
|
|
27
|
-
var editPanel = "_1x2h1olj";
|
|
28
|
-
var editField = "_1x2h1olk";
|
|
29
|
-
var editFieldIcon = "_1x2h1oll";
|
|
30
|
-
var editInput = "_1x2h1olm";
|
|
31
|
-
var editActions = "_1x2h1oln";
|
|
32
|
-
var editActionButton = "_1x2h1olo";
|
|
33
|
-
var editActionButtonEnd = "_1x2h1olp";
|
|
34
|
-
const PlayIcon = ({ size = 20 }) => /* @__PURE__ */ jsx(Play, { size });
|
|
35
|
-
const PauseIcon = ({ size = 20 }) => /* @__PURE__ */ jsx(Pause, { size });
|
|
36
|
-
const VolumeIcon = () => /* @__PURE__ */ jsx(Volume2, { size: 20 });
|
|
37
|
-
const VolumeMuteIcon = () => /* @__PURE__ */ jsx(VolumeX, { size: 20 });
|
|
38
|
-
const DownloadIcon = () => /* @__PURE__ */ jsx(Download, { size: 20 });
|
|
39
|
-
const LoadingIcon = () => /* @__PURE__ */ jsx(
|
|
40
|
-
Loader2,
|
|
41
|
-
{
|
|
42
|
-
size: 20,
|
|
43
|
-
className: `${spin} ${semanticClassNames.spin}`
|
|
44
|
-
}
|
|
45
|
-
);
|
|
46
|
-
const FullscreenIcon = () => /* @__PURE__ */ jsx(Maximize, { size: 20 });
|
|
47
|
-
const ExitFullscreenIcon = () => /* @__PURE__ */ jsx(Minimize, { size: 20 });
|
|
48
|
-
const VideoRenderer = ({
|
|
49
|
-
src,
|
|
50
|
-
poster,
|
|
51
|
-
width,
|
|
52
|
-
height
|
|
53
|
-
}) => {
|
|
54
|
-
const wrapperRef = useRef(null);
|
|
55
|
-
const videoRef = useRef(null);
|
|
56
|
-
const [playing, setPlaying] = useState(false);
|
|
57
|
-
const [duration, setDuration] = useState(0);
|
|
58
|
-
const [currentTime, setCurrentTime] = useState(0);
|
|
59
|
-
const [muted, setMuted] = useState(false);
|
|
60
|
-
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
61
|
-
const [indicator$1, setIndicator] = useState(null);
|
|
62
|
-
const [draggingTime, setDraggingTime] = useState(null);
|
|
63
|
-
const [isDownloading, setIsDownloading] = useState(false);
|
|
64
|
-
const dragWasPlayingRef = useRef(false);
|
|
65
|
-
const indicatorTimerRef = useRef(void 0);
|
|
66
|
-
const indicatorKeyRef = useRef(0);
|
|
67
|
-
const aspectRatio = useMemo(() => {
|
|
68
|
-
if (width && height && width > 0 && height > 0) {
|
|
69
|
-
return `${width} / ${height}`;
|
|
70
|
-
}
|
|
71
|
-
return "16 / 9";
|
|
72
|
-
}, [height, width]);
|
|
73
|
-
const showIndicator = useCallback((type) => {
|
|
74
|
-
indicatorKeyRef.current += 1;
|
|
75
|
-
setIndicator({ type, key: indicatorKeyRef.current });
|
|
76
|
-
clearTimeout(indicatorTimerRef.current);
|
|
77
|
-
indicatorTimerRef.current = setTimeout(() => setIndicator(null), 500);
|
|
78
|
-
}, []);
|
|
79
|
-
const play = useCallback(() => {
|
|
80
|
-
void videoRef.current?.play();
|
|
81
|
-
}, []);
|
|
82
|
-
const pause = useCallback(() => {
|
|
83
|
-
videoRef.current?.pause();
|
|
84
|
-
}, []);
|
|
85
|
-
const togglePlay = useCallback(() => {
|
|
86
|
-
const video = videoRef.current;
|
|
87
|
-
if (!video) return;
|
|
88
|
-
if (video.paused) {
|
|
89
|
-
play();
|
|
90
|
-
showIndicator("play");
|
|
91
|
-
} else {
|
|
92
|
-
pause();
|
|
93
|
-
showIndicator("pause");
|
|
94
|
-
}
|
|
95
|
-
}, [pause, play, showIndicator]);
|
|
96
|
-
const seekTo = useCallback((time) => {
|
|
97
|
-
const video = videoRef.current;
|
|
98
|
-
if (!video) return;
|
|
99
|
-
const clamped = Math.min(
|
|
100
|
-
Math.max(0, time),
|
|
101
|
-
Number.isFinite(video.duration) ? video.duration : time
|
|
102
|
-
);
|
|
103
|
-
video.currentTime = clamped;
|
|
104
|
-
setCurrentTime(clamped);
|
|
105
|
-
}, []);
|
|
106
|
-
const toggleMute = useCallback(() => {
|
|
107
|
-
const video = videoRef.current;
|
|
108
|
-
if (!video) return;
|
|
109
|
-
video.muted = !video.muted;
|
|
110
|
-
setMuted(video.muted);
|
|
111
|
-
}, []);
|
|
112
|
-
const toggleFullscreen = useCallback(() => {
|
|
113
|
-
const wrapper = wrapperRef.current;
|
|
114
|
-
if (!wrapper) return;
|
|
115
|
-
if (!document.fullscreenElement) {
|
|
116
|
-
void wrapper.requestFullscreen();
|
|
117
|
-
} else {
|
|
118
|
-
void document.exitFullscreen();
|
|
119
|
-
}
|
|
120
|
-
}, []);
|
|
121
|
-
const downloadingRef = useRef(false);
|
|
122
|
-
const downloadVideo = useCallback(() => {
|
|
123
|
-
if (downloadingRef.current) return;
|
|
124
|
-
downloadingRef.current = true;
|
|
125
|
-
setIsDownloading(true);
|
|
126
|
-
const filename = src.split("/").pop() || "video";
|
|
127
|
-
const fallback = () => {
|
|
128
|
-
const a = document.createElement("a");
|
|
129
|
-
a.href = src;
|
|
130
|
-
a.download = filename;
|
|
131
|
-
a.rel = "noopener noreferrer";
|
|
132
|
-
a.click();
|
|
133
|
-
};
|
|
134
|
-
fetch(src).then((res) => {
|
|
135
|
-
if (!res.ok) throw new Error(res.statusText);
|
|
136
|
-
return res.blob();
|
|
137
|
-
}).then((blob) => {
|
|
138
|
-
const url = URL.createObjectURL(blob);
|
|
139
|
-
const a = document.createElement("a");
|
|
140
|
-
a.href = url;
|
|
141
|
-
a.download = filename;
|
|
142
|
-
a.click();
|
|
143
|
-
setTimeout(() => URL.revokeObjectURL(url), 1e3);
|
|
144
|
-
}).catch(() => fallback()).finally(() => {
|
|
145
|
-
downloadingRef.current = false;
|
|
146
|
-
setIsDownloading(false);
|
|
147
|
-
});
|
|
148
|
-
}, [src]);
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
const video = videoRef.current;
|
|
151
|
-
if (!video) return;
|
|
152
|
-
const onLoadedMetadata = () => {
|
|
153
|
-
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
154
|
-
setMuted(video.muted);
|
|
155
|
-
};
|
|
156
|
-
const onDurationChange = () => {
|
|
157
|
-
setDuration(Number.isFinite(video.duration) ? video.duration : 0);
|
|
158
|
-
};
|
|
159
|
-
const onTimeUpdate = () => setCurrentTime(video.currentTime);
|
|
160
|
-
const onPlay = () => setPlaying(true);
|
|
161
|
-
const onPause = () => setPlaying(false);
|
|
162
|
-
const onVolumeChange = () => setMuted(video.muted);
|
|
163
|
-
video.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
164
|
-
video.addEventListener("durationchange", onDurationChange);
|
|
165
|
-
video.addEventListener("timeupdate", onTimeUpdate);
|
|
166
|
-
video.addEventListener("play", onPlay);
|
|
167
|
-
video.addEventListener("pause", onPause);
|
|
168
|
-
video.addEventListener("volumechange", onVolumeChange);
|
|
169
|
-
return () => {
|
|
170
|
-
video.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
171
|
-
video.removeEventListener("durationchange", onDurationChange);
|
|
172
|
-
video.removeEventListener("timeupdate", onTimeUpdate);
|
|
173
|
-
video.removeEventListener("play", onPlay);
|
|
174
|
-
video.removeEventListener("pause", onPause);
|
|
175
|
-
video.removeEventListener("volumechange", onVolumeChange);
|
|
176
|
-
};
|
|
177
|
-
}, []);
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
const handler = () => setIsFullscreen(!!document.fullscreenElement);
|
|
180
|
-
document.addEventListener("fullscreenchange", handler);
|
|
181
|
-
return () => document.removeEventListener("fullscreenchange", handler);
|
|
182
|
-
}, []);
|
|
183
|
-
useEffect(() => {
|
|
184
|
-
return () => clearTimeout(indicatorTimerRef.current);
|
|
185
|
-
}, []);
|
|
186
|
-
const timelineValue = draggingTime ?? currentTime;
|
|
187
|
-
return /* @__PURE__ */ jsx("figure", { className: `${root} ${semanticClassNames.root}`, children: /* @__PURE__ */ jsxs(
|
|
188
|
-
"div",
|
|
189
|
-
{
|
|
190
|
-
className: `${player} ${semanticClassNames.player}`,
|
|
191
|
-
ref: wrapperRef,
|
|
192
|
-
style: { aspectRatio },
|
|
193
|
-
children: [
|
|
194
|
-
/* @__PURE__ */ jsx(
|
|
195
|
-
"video",
|
|
196
|
-
{
|
|
197
|
-
ref: videoRef,
|
|
198
|
-
className: `${element} ${semanticClassNames.element}`,
|
|
199
|
-
src,
|
|
200
|
-
poster,
|
|
201
|
-
preload: "metadata",
|
|
202
|
-
playsInline: true,
|
|
203
|
-
onClick: togglePlay,
|
|
204
|
-
onDoubleClick: toggleFullscreen
|
|
205
|
-
}
|
|
206
|
-
),
|
|
207
|
-
indicator$1 && /* @__PURE__ */ jsx(
|
|
208
|
-
"span",
|
|
209
|
-
{
|
|
210
|
-
className: `${indicator} ${semanticClassNames.indicator}`,
|
|
211
|
-
"aria-hidden": true,
|
|
212
|
-
children: indicator$1.type === "play" ? /* @__PURE__ */ jsx(PlayIcon, { size: 32 }) : /* @__PURE__ */ jsx(PauseIcon, { size: 32 })
|
|
213
|
-
},
|
|
214
|
-
indicator$1.key
|
|
215
|
-
),
|
|
216
|
-
/* @__PURE__ */ jsxs(
|
|
217
|
-
"div",
|
|
218
|
-
{
|
|
219
|
-
className: `${controls} ${semanticClassNames.controls}`,
|
|
220
|
-
onClick: (e) => e.stopPropagation(),
|
|
221
|
-
children: [
|
|
222
|
-
/* @__PURE__ */ jsx(
|
|
223
|
-
"button",
|
|
224
|
-
{
|
|
225
|
-
type: "button",
|
|
226
|
-
className: `${button} ${semanticClassNames.button}`,
|
|
227
|
-
"aria-label": playing ? "Pause" : "Play",
|
|
228
|
-
onClick: togglePlay,
|
|
229
|
-
children: playing ? /* @__PURE__ */ jsx(PauseIcon, {}) : /* @__PURE__ */ jsx(PlayIcon, {})
|
|
230
|
-
}
|
|
231
|
-
),
|
|
232
|
-
/* @__PURE__ */ jsx(
|
|
233
|
-
Slider.Root,
|
|
234
|
-
{
|
|
235
|
-
className: `${progress} ${semanticClassNames.progress}`,
|
|
236
|
-
min: 0,
|
|
237
|
-
max: duration || 0,
|
|
238
|
-
step: 0.01,
|
|
239
|
-
value: timelineValue,
|
|
240
|
-
onValueChange: (value) => {
|
|
241
|
-
setDraggingTime(value);
|
|
242
|
-
},
|
|
243
|
-
onValueCommitted: (value) => {
|
|
244
|
-
seekTo(value);
|
|
245
|
-
setDraggingTime(null);
|
|
246
|
-
if (dragWasPlayingRef.current) {
|
|
247
|
-
play();
|
|
248
|
-
}
|
|
249
|
-
},
|
|
250
|
-
children: /* @__PURE__ */ jsx(
|
|
251
|
-
Slider.Control,
|
|
252
|
-
{
|
|
253
|
-
className: `${progressControl} ${semanticClassNames.progressControl}`,
|
|
254
|
-
onPointerDown: () => {
|
|
255
|
-
dragWasPlayingRef.current = playing;
|
|
256
|
-
pause();
|
|
257
|
-
setDraggingTime(currentTime);
|
|
258
|
-
},
|
|
259
|
-
children: /* @__PURE__ */ jsxs(
|
|
260
|
-
Slider.Track,
|
|
261
|
-
{
|
|
262
|
-
className: `${progressTrack} ${semanticClassNames.progressTrack}`,
|
|
263
|
-
children: [
|
|
264
|
-
/* @__PURE__ */ jsx(
|
|
265
|
-
Slider.Indicator,
|
|
266
|
-
{
|
|
267
|
-
className: `${progressRange} ${semanticClassNames.progressRange}`
|
|
268
|
-
}
|
|
269
|
-
),
|
|
270
|
-
/* @__PURE__ */ jsx(
|
|
271
|
-
Slider.Thumb,
|
|
272
|
-
{
|
|
273
|
-
className: `${progressThumb} ${semanticClassNames.progressThumb}`,
|
|
274
|
-
"aria-label": "Playback progress"
|
|
275
|
-
}
|
|
276
|
-
)
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
)
|
|
280
|
-
}
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
),
|
|
284
|
-
/* @__PURE__ */ jsx(
|
|
285
|
-
"button",
|
|
286
|
-
{
|
|
287
|
-
type: "button",
|
|
288
|
-
className: `${button} ${semanticClassNames.button}`,
|
|
289
|
-
"aria-label": muted ? "Unmute" : "Mute",
|
|
290
|
-
onClick: toggleMute,
|
|
291
|
-
children: muted ? /* @__PURE__ */ jsx(VolumeMuteIcon, {}) : /* @__PURE__ */ jsx(VolumeIcon, {})
|
|
292
|
-
}
|
|
293
|
-
),
|
|
294
|
-
/* @__PURE__ */ jsx(
|
|
295
|
-
"button",
|
|
296
|
-
{
|
|
297
|
-
type: "button",
|
|
298
|
-
className: `${button} ${semanticClassNames.button}`,
|
|
299
|
-
"aria-label": "Download",
|
|
300
|
-
onClick: downloadVideo,
|
|
301
|
-
children: isDownloading ? /* @__PURE__ */ jsx(LoadingIcon, {}) : /* @__PURE__ */ jsx(DownloadIcon, {})
|
|
302
|
-
}
|
|
303
|
-
),
|
|
304
|
-
/* @__PURE__ */ jsx(
|
|
305
|
-
"button",
|
|
306
|
-
{
|
|
307
|
-
type: "button",
|
|
308
|
-
className: `${button} ${semanticClassNames.button}`,
|
|
309
|
-
"aria-label": isFullscreen ? "Exit fullscreen" : "Fullscreen",
|
|
310
|
-
onClick: toggleFullscreen,
|
|
311
|
-
children: isFullscreen ? /* @__PURE__ */ jsx(ExitFullscreenIcon, {}) : /* @__PURE__ */ jsx(FullscreenIcon, {})
|
|
312
|
-
}
|
|
313
|
-
)
|
|
314
|
-
]
|
|
315
|
-
}
|
|
316
|
-
)
|
|
317
|
-
]
|
|
318
|
-
}
|
|
319
|
-
) });
|
|
320
|
-
};
|
|
6
|
+
import { Video, ImageIcon, ExternalLink, Trash2 } from "lucide-react";
|
|
7
|
+
import { useRef, useState, useEffect, useCallback } from "react";
|
|
8
|
+
import { V as VideoRenderer, r as root, s as semanticClassNames, e as editPreview, a as editVideo, b as editOverlay, c as editPlaceholder, d as editTrigger, f as editPanel, g as editField, h as editFieldIcon, i as editInput, j as editActions, k as editActionButton, l as editActionButtonEnd } from "./VideoRenderer-B3RM54FU.js";
|
|
321
9
|
function VideoEditRenderer(props) {
|
|
322
10
|
const mode = useRendererMode();
|
|
323
11
|
if (mode !== "editor") {
|
package/dist/static.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haklex/rich-renderer-video",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.42",
|
|
4
4
|
"description": "Video player renderer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@base-ui/react": "^1.2.0",
|
|
24
24
|
"lucide-react": "^0.574.0",
|
|
25
|
-
"@haklex/rich-editor": "0.0.
|
|
26
|
-
"@haklex/rich-editor-ui": "0.0.
|
|
27
|
-
"@haklex/rich-style-token": "0.0.
|
|
25
|
+
"@haklex/rich-editor": "0.0.42",
|
|
26
|
+
"@haklex/rich-editor-ui": "0.0.42",
|
|
27
|
+
"@haklex/rich-style-token": "0.0.42"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@lexical/react": "^0.41.0",
|