analytica-frontend-lib 1.1.10 → 1.1.12
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/VideoPlayer/index.js +367 -183
- package/dist/VideoPlayer/index.js.map +1 -1
- package/dist/VideoPlayer/index.mjs +373 -184
- package/dist/VideoPlayer/index.mjs.map +1 -1
- package/dist/index.css +10 -3
- package/dist/index.css.map +1 -1
- package/dist/index.js +367 -183
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +373 -184
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +10 -3
- package/dist/styles.css.map +1 -1
- package/package.json +1 -1
|
@@ -139,12 +139,93 @@ var Text_default = Text;
|
|
|
139
139
|
|
|
140
140
|
// src/components/VideoPlayer/VideoPlayer.tsx
|
|
141
141
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
142
|
+
var CONTROLS_HIDE_TIMEOUT = 3e3;
|
|
143
|
+
var LEAVE_HIDE_TIMEOUT = 1e3;
|
|
142
144
|
var formatTime = (seconds) => {
|
|
143
145
|
if (!seconds || isNaN(seconds)) return "0:00";
|
|
144
146
|
const mins = Math.floor(seconds / 60);
|
|
145
147
|
const secs = Math.floor(seconds % 60);
|
|
146
148
|
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
147
149
|
};
|
|
150
|
+
var ProgressBar = ({
|
|
151
|
+
currentTime,
|
|
152
|
+
duration,
|
|
153
|
+
progressPercentage,
|
|
154
|
+
onSeek
|
|
155
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
156
|
+
"input",
|
|
157
|
+
{
|
|
158
|
+
type: "range",
|
|
159
|
+
min: 0,
|
|
160
|
+
max: duration || 100,
|
|
161
|
+
value: currentTime,
|
|
162
|
+
onChange: (e) => onSeek(parseFloat(e.target.value)),
|
|
163
|
+
className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
164
|
+
"aria-label": "Video progress",
|
|
165
|
+
style: {
|
|
166
|
+
background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
) });
|
|
170
|
+
var VolumeControls = ({
|
|
171
|
+
volume,
|
|
172
|
+
isMuted,
|
|
173
|
+
onVolumeChange,
|
|
174
|
+
onToggleMute
|
|
175
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
176
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
177
|
+
IconButton_default,
|
|
178
|
+
{
|
|
179
|
+
icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.SpeakerHigh, { size: 24 }),
|
|
180
|
+
onClick: onToggleMute,
|
|
181
|
+
"aria-label": isMuted ? "Unmute" : "Mute",
|
|
182
|
+
className: "!bg-transparent !text-white hover:!bg-white/20"
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
186
|
+
"input",
|
|
187
|
+
{
|
|
188
|
+
type: "range",
|
|
189
|
+
min: 0,
|
|
190
|
+
max: 100,
|
|
191
|
+
value: Math.round(volume * 100),
|
|
192
|
+
onChange: (e) => onVolumeChange(parseInt(e.target.value)),
|
|
193
|
+
className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
194
|
+
"aria-label": "Volume control",
|
|
195
|
+
style: {
|
|
196
|
+
background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
)
|
|
200
|
+
] });
|
|
201
|
+
var SpeedMenu = ({
|
|
202
|
+
showSpeedMenu,
|
|
203
|
+
playbackRate,
|
|
204
|
+
onToggleMenu,
|
|
205
|
+
onSpeedChange
|
|
206
|
+
}) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "relative", children: [
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
208
|
+
IconButton_default,
|
|
209
|
+
{
|
|
210
|
+
icon: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.DotsThreeVertical, { size: 24 }),
|
|
211
|
+
onClick: onToggleMenu,
|
|
212
|
+
"aria-label": "Playback speed",
|
|
213
|
+
className: "!bg-transparent !text-white hover:!bg-white/20"
|
|
214
|
+
}
|
|
215
|
+
),
|
|
216
|
+
showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
217
|
+
"button",
|
|
218
|
+
{
|
|
219
|
+
onClick: () => onSpeedChange(speed),
|
|
220
|
+
className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
|
|
221
|
+
children: [
|
|
222
|
+
speed,
|
|
223
|
+
"x"
|
|
224
|
+
]
|
|
225
|
+
},
|
|
226
|
+
speed
|
|
227
|
+
)) })
|
|
228
|
+
] });
|
|
148
229
|
var VideoPlayer = ({
|
|
149
230
|
src,
|
|
150
231
|
poster,
|
|
@@ -169,10 +250,68 @@ var VideoPlayer = ({
|
|
|
169
250
|
const [showControls, setShowControls] = (0, import_react2.useState)(true);
|
|
170
251
|
const [hasCompleted, setHasCompleted] = (0, import_react2.useState)(false);
|
|
171
252
|
const [showCaptions, setShowCaptions] = (0, import_react2.useState)(false);
|
|
253
|
+
(0, import_react2.useEffect)(() => {
|
|
254
|
+
setHasCompleted(false);
|
|
255
|
+
}, [src]);
|
|
172
256
|
const [playbackRate, setPlaybackRate] = (0, import_react2.useState)(1);
|
|
173
257
|
const [showSpeedMenu, setShowSpeedMenu] = (0, import_react2.useState)(false);
|
|
174
258
|
const lastSaveTimeRef = (0, import_react2.useRef)(0);
|
|
175
259
|
const trackRef = (0, import_react2.useRef)(null);
|
|
260
|
+
const controlsTimeoutRef = (0, import_react2.useRef)(null);
|
|
261
|
+
const lastMousePositionRef = (0, import_react2.useRef)({ x: 0, y: 0 });
|
|
262
|
+
const mouseMoveTimeoutRef = (0, import_react2.useRef)(null);
|
|
263
|
+
const isUserInteracting = (0, import_react2.useCallback)(() => {
|
|
264
|
+
if (showSpeedMenu) return true;
|
|
265
|
+
const activeElement = document.activeElement;
|
|
266
|
+
const videoContainer = videoRef.current?.parentElement;
|
|
267
|
+
if (activeElement && videoContainer?.contains(activeElement)) {
|
|
268
|
+
const isControl = activeElement.matches("button, input, [tabindex]");
|
|
269
|
+
if (isControl) return true;
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
}, [showSpeedMenu]);
|
|
273
|
+
const clearControlsTimeout = (0, import_react2.useCallback)(() => {
|
|
274
|
+
if (controlsTimeoutRef.current) {
|
|
275
|
+
clearTimeout(controlsTimeoutRef.current);
|
|
276
|
+
controlsTimeoutRef.current = null;
|
|
277
|
+
}
|
|
278
|
+
}, []);
|
|
279
|
+
const clearMouseMoveTimeout = (0, import_react2.useCallback)(() => {
|
|
280
|
+
if (mouseMoveTimeoutRef.current) {
|
|
281
|
+
clearTimeout(mouseMoveTimeoutRef.current);
|
|
282
|
+
mouseMoveTimeoutRef.current = null;
|
|
283
|
+
}
|
|
284
|
+
}, []);
|
|
285
|
+
const showControlsWithTimer = (0, import_react2.useCallback)(() => {
|
|
286
|
+
setShowControls(true);
|
|
287
|
+
clearControlsTimeout();
|
|
288
|
+
if (isPlaying) {
|
|
289
|
+
controlsTimeoutRef.current = window.setTimeout(() => {
|
|
290
|
+
setShowControls(false);
|
|
291
|
+
}, CONTROLS_HIDE_TIMEOUT);
|
|
292
|
+
}
|
|
293
|
+
}, [isPlaying, clearControlsTimeout]);
|
|
294
|
+
const handleMouseMove = (0, import_react2.useCallback)(
|
|
295
|
+
(event) => {
|
|
296
|
+
const currentX = event.clientX;
|
|
297
|
+
const currentY = event.clientY;
|
|
298
|
+
const lastPos = lastMousePositionRef.current;
|
|
299
|
+
const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
|
|
300
|
+
if (hasMoved) {
|
|
301
|
+
lastMousePositionRef.current = { x: currentX, y: currentY };
|
|
302
|
+
showControlsWithTimer();
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
[showControlsWithTimer]
|
|
306
|
+
);
|
|
307
|
+
const handleMouseLeave = (0, import_react2.useCallback)(() => {
|
|
308
|
+
clearControlsTimeout();
|
|
309
|
+
if (isPlaying && !isUserInteracting()) {
|
|
310
|
+
controlsTimeoutRef.current = window.setTimeout(() => {
|
|
311
|
+
setShowControls(false);
|
|
312
|
+
}, LEAVE_HIDE_TIMEOUT);
|
|
313
|
+
}
|
|
314
|
+
}, [isPlaying, clearControlsTimeout, isUserInteracting]);
|
|
176
315
|
(0, import_react2.useEffect)(() => {
|
|
177
316
|
if (videoRef.current) {
|
|
178
317
|
videoRef.current.volume = volume;
|
|
@@ -180,84 +319,129 @@ var VideoPlayer = ({
|
|
|
180
319
|
}
|
|
181
320
|
}, [volume, isMuted]);
|
|
182
321
|
(0, import_react2.useEffect)(() => {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
322
|
+
const video = videoRef.current;
|
|
323
|
+
if (!video) return;
|
|
324
|
+
const onPlay = () => setIsPlaying(true);
|
|
325
|
+
const onPause = () => setIsPlaying(false);
|
|
326
|
+
const onEnded = () => setIsPlaying(false);
|
|
327
|
+
video.addEventListener("play", onPlay);
|
|
328
|
+
video.addEventListener("pause", onPause);
|
|
329
|
+
video.addEventListener("ended", onEnded);
|
|
330
|
+
return () => {
|
|
331
|
+
video.removeEventListener("play", onPlay);
|
|
332
|
+
video.removeEventListener("pause", onPause);
|
|
333
|
+
video.removeEventListener("ended", onEnded);
|
|
334
|
+
};
|
|
335
|
+
}, []);
|
|
336
|
+
(0, import_react2.useEffect)(() => {
|
|
337
|
+
if (isPlaying) {
|
|
338
|
+
showControlsWithTimer();
|
|
193
339
|
} else {
|
|
194
|
-
|
|
340
|
+
clearControlsTimeout();
|
|
341
|
+
setShowControls(true);
|
|
342
|
+
}
|
|
343
|
+
}, [isPlaying, showControlsWithTimer, clearControlsTimeout]);
|
|
344
|
+
(0, import_react2.useEffect)(() => {
|
|
345
|
+
const handleFullscreenChange = () => {
|
|
346
|
+
const isCurrentlyFullscreen = !!document.fullscreenElement;
|
|
347
|
+
setIsFullscreen(isCurrentlyFullscreen);
|
|
348
|
+
if (isCurrentlyFullscreen) {
|
|
349
|
+
showControlsWithTimer();
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
|
353
|
+
return () => {
|
|
354
|
+
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
|
355
|
+
};
|
|
356
|
+
}, [showControlsWithTimer]);
|
|
357
|
+
const getInitialTime = (0, import_react2.useCallback)(() => {
|
|
358
|
+
if (!autoSave || !storageKey) {
|
|
359
|
+
return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
|
|
195
360
|
}
|
|
361
|
+
const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
|
|
362
|
+
const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
|
|
363
|
+
const hasValidSaved = Number.isFinite(saved) && saved >= 0;
|
|
364
|
+
if (hasValidInitial) return initialTime;
|
|
365
|
+
if (hasValidSaved) return saved;
|
|
366
|
+
return void 0;
|
|
367
|
+
}, [autoSave, storageKey, src, initialTime]);
|
|
368
|
+
(0, import_react2.useEffect)(() => {
|
|
369
|
+
const start = getInitialTime();
|
|
196
370
|
if (start !== void 0 && videoRef.current) {
|
|
197
371
|
videoRef.current.currentTime = start;
|
|
198
372
|
setCurrentTime(start);
|
|
199
373
|
}
|
|
200
|
-
}, [
|
|
201
|
-
const saveProgress = (0, import_react2.useCallback)(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}, [autoSave, storageKey, src, currentTime]);
|
|
209
|
-
const togglePlayPause = (0, import_react2.useCallback)(() => {
|
|
210
|
-
if (videoRef.current) {
|
|
211
|
-
if (isPlaying) {
|
|
212
|
-
videoRef.current.pause();
|
|
213
|
-
} else {
|
|
214
|
-
videoRef.current.play();
|
|
374
|
+
}, [getInitialTime]);
|
|
375
|
+
const saveProgress = (0, import_react2.useCallback)(
|
|
376
|
+
(time) => {
|
|
377
|
+
if (!autoSave || !storageKey) return;
|
|
378
|
+
const now = Date.now();
|
|
379
|
+
if (now - lastSaveTimeRef.current > 5e3) {
|
|
380
|
+
localStorage.setItem(`${storageKey}-${src}`, time.toString());
|
|
381
|
+
lastSaveTimeRef.current = now;
|
|
215
382
|
}
|
|
216
|
-
|
|
383
|
+
},
|
|
384
|
+
[autoSave, storageKey, src]
|
|
385
|
+
);
|
|
386
|
+
const togglePlayPause = (0, import_react2.useCallback)(async () => {
|
|
387
|
+
const video = videoRef.current;
|
|
388
|
+
if (!video) return;
|
|
389
|
+
if (!video.paused) {
|
|
390
|
+
video.pause();
|
|
391
|
+
return;
|
|
217
392
|
}
|
|
218
|
-
|
|
393
|
+
try {
|
|
394
|
+
await video.play();
|
|
395
|
+
} catch {
|
|
396
|
+
}
|
|
397
|
+
}, []);
|
|
219
398
|
const handleVolumeChange = (0, import_react2.useCallback)(
|
|
220
399
|
(newVolume) => {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
400
|
+
const video = videoRef.current;
|
|
401
|
+
if (!video) return;
|
|
402
|
+
const volumeValue = newVolume / 100;
|
|
403
|
+
video.volume = volumeValue;
|
|
404
|
+
setVolume(volumeValue);
|
|
405
|
+
const shouldMute = volumeValue === 0;
|
|
406
|
+
const shouldUnmute = volumeValue > 0 && isMuted;
|
|
407
|
+
if (shouldMute) {
|
|
408
|
+
video.muted = true;
|
|
409
|
+
setIsMuted(true);
|
|
410
|
+
} else if (shouldUnmute) {
|
|
411
|
+
video.muted = false;
|
|
412
|
+
setIsMuted(false);
|
|
232
413
|
}
|
|
233
414
|
},
|
|
234
415
|
[isMuted]
|
|
235
416
|
);
|
|
236
417
|
const toggleMute = (0, import_react2.useCallback)(() => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
418
|
+
const video = videoRef.current;
|
|
419
|
+
if (!video) return;
|
|
420
|
+
if (isMuted) {
|
|
421
|
+
const restoreVolume = volume > 0 ? volume : 0.5;
|
|
422
|
+
video.volume = restoreVolume;
|
|
423
|
+
video.muted = false;
|
|
424
|
+
setVolume(restoreVolume);
|
|
425
|
+
setIsMuted(false);
|
|
426
|
+
} else {
|
|
427
|
+
video.muted = true;
|
|
428
|
+
setIsMuted(true);
|
|
248
429
|
}
|
|
249
430
|
}, [isMuted, volume]);
|
|
431
|
+
const handleSeek = (0, import_react2.useCallback)((newTime) => {
|
|
432
|
+
const video = videoRef.current;
|
|
433
|
+
if (video) {
|
|
434
|
+
video.currentTime = newTime;
|
|
435
|
+
}
|
|
436
|
+
}, []);
|
|
250
437
|
const toggleFullscreen = (0, import_react2.useCallback)(() => {
|
|
251
438
|
const container = videoRef.current?.parentElement;
|
|
252
439
|
if (!container) return;
|
|
253
|
-
if (!isFullscreen) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
} else if (document.exitFullscreen) {
|
|
440
|
+
if (!isFullscreen && container.requestFullscreen) {
|
|
441
|
+
container.requestFullscreen();
|
|
442
|
+
} else if (isFullscreen && document.exitFullscreen) {
|
|
258
443
|
document.exitFullscreen();
|
|
259
444
|
}
|
|
260
|
-
setIsFullscreen(!isFullscreen);
|
|
261
445
|
}, [isFullscreen]);
|
|
262
446
|
const handleSpeedChange = (0, import_react2.useCallback)((speed) => {
|
|
263
447
|
if (videoRef.current) {
|
|
@@ -275,29 +459,28 @@ var VideoPlayer = ({
|
|
|
275
459
|
setShowCaptions(newShowCaptions);
|
|
276
460
|
trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
|
|
277
461
|
}, [showCaptions, subtitles]);
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
onTimeUpdate?.(current);
|
|
284
|
-
if (duration > 0) {
|
|
285
|
-
const progressPercent = current / duration * 100;
|
|
286
|
-
onProgress?.(progressPercent);
|
|
287
|
-
if (progressPercent >= 95 && !hasCompleted) {
|
|
288
|
-
setHasCompleted(true);
|
|
289
|
-
onVideoComplete?.();
|
|
290
|
-
}
|
|
462
|
+
const checkVideoCompletion = (0, import_react2.useCallback)(
|
|
463
|
+
(progressPercent) => {
|
|
464
|
+
if (progressPercent >= 95 && !hasCompleted) {
|
|
465
|
+
setHasCompleted(true);
|
|
466
|
+
onVideoComplete?.();
|
|
291
467
|
}
|
|
468
|
+
},
|
|
469
|
+
[hasCompleted, onVideoComplete]
|
|
470
|
+
);
|
|
471
|
+
const handleTimeUpdate = (0, import_react2.useCallback)(() => {
|
|
472
|
+
const video = videoRef.current;
|
|
473
|
+
if (!video) return;
|
|
474
|
+
const current = video.currentTime;
|
|
475
|
+
setCurrentTime(current);
|
|
476
|
+
saveProgress(current);
|
|
477
|
+
onTimeUpdate?.(current);
|
|
478
|
+
if (duration > 0) {
|
|
479
|
+
const progressPercent = current / duration * 100;
|
|
480
|
+
onProgress?.(progressPercent);
|
|
481
|
+
checkVideoCompletion(progressPercent);
|
|
292
482
|
}
|
|
293
|
-
}, [
|
|
294
|
-
duration,
|
|
295
|
-
saveProgress,
|
|
296
|
-
onTimeUpdate,
|
|
297
|
-
onProgress,
|
|
298
|
-
onVideoComplete,
|
|
299
|
-
hasCompleted
|
|
300
|
-
]);
|
|
483
|
+
}, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
|
|
301
484
|
const handleLoadedMetadata = (0, import_react2.useCallback)(() => {
|
|
302
485
|
if (videoRef.current) {
|
|
303
486
|
setDuration(videoRef.current.duration);
|
|
@@ -326,9 +509,78 @@ var VideoPlayer = ({
|
|
|
326
509
|
return () => {
|
|
327
510
|
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
328
511
|
window.removeEventListener("blur", handleBlur);
|
|
512
|
+
clearControlsTimeout();
|
|
513
|
+
clearMouseMoveTimeout();
|
|
329
514
|
};
|
|
330
|
-
}, [isPlaying]);
|
|
515
|
+
}, [isPlaying, clearControlsTimeout, clearMouseMoveTimeout]);
|
|
331
516
|
const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
|
|
517
|
+
const getTopControlsOpacity = (0, import_react2.useCallback)(() => {
|
|
518
|
+
if (isFullscreen) {
|
|
519
|
+
return showControls ? "opacity-100" : "opacity-0";
|
|
520
|
+
}
|
|
521
|
+
return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
|
|
522
|
+
}, [isFullscreen, showControls, isPlaying]);
|
|
523
|
+
const getBottomControlsOpacity = (0, import_react2.useCallback)(() => {
|
|
524
|
+
if (isFullscreen) {
|
|
525
|
+
return showControls ? "opacity-100" : "opacity-0";
|
|
526
|
+
}
|
|
527
|
+
return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
|
|
528
|
+
}, [isFullscreen, showControls, isPlaying]);
|
|
529
|
+
const handleVideoKeyDown = (0, import_react2.useCallback)(
|
|
530
|
+
(e) => {
|
|
531
|
+
if (e.key) {
|
|
532
|
+
e.stopPropagation();
|
|
533
|
+
showControlsWithTimer();
|
|
534
|
+
}
|
|
535
|
+
switch (e.key) {
|
|
536
|
+
case " ":
|
|
537
|
+
case "Enter":
|
|
538
|
+
e.preventDefault();
|
|
539
|
+
togglePlayPause();
|
|
540
|
+
break;
|
|
541
|
+
case "ArrowLeft":
|
|
542
|
+
e.preventDefault();
|
|
543
|
+
if (videoRef.current) {
|
|
544
|
+
videoRef.current.currentTime -= 10;
|
|
545
|
+
}
|
|
546
|
+
break;
|
|
547
|
+
case "ArrowRight":
|
|
548
|
+
e.preventDefault();
|
|
549
|
+
if (videoRef.current) {
|
|
550
|
+
videoRef.current.currentTime += 10;
|
|
551
|
+
}
|
|
552
|
+
break;
|
|
553
|
+
case "ArrowUp":
|
|
554
|
+
e.preventDefault();
|
|
555
|
+
handleVolumeChange(Math.min(100, volume * 100 + 10));
|
|
556
|
+
break;
|
|
557
|
+
case "ArrowDown":
|
|
558
|
+
e.preventDefault();
|
|
559
|
+
handleVolumeChange(Math.max(0, volume * 100 - 10));
|
|
560
|
+
break;
|
|
561
|
+
case "m":
|
|
562
|
+
case "M":
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
toggleMute();
|
|
565
|
+
break;
|
|
566
|
+
case "f":
|
|
567
|
+
case "F":
|
|
568
|
+
e.preventDefault();
|
|
569
|
+
toggleFullscreen();
|
|
570
|
+
break;
|
|
571
|
+
default:
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
},
|
|
575
|
+
[
|
|
576
|
+
showControlsWithTimer,
|
|
577
|
+
togglePlayPause,
|
|
578
|
+
handleVolumeChange,
|
|
579
|
+
volume,
|
|
580
|
+
toggleMute,
|
|
581
|
+
toggleFullscreen
|
|
582
|
+
]
|
|
583
|
+
);
|
|
332
584
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("flex flex-col", className), children: [
|
|
333
585
|
(title || subtitleText) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "bg-subject-1 rounded-t-xl px-8 py-4 flex items-end justify-between min-h-20", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
334
586
|
title && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -355,12 +607,18 @@ var VideoPlayer = ({
|
|
|
355
607
|
)
|
|
356
608
|
] }) }),
|
|
357
609
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
358
|
-
"
|
|
610
|
+
"section",
|
|
359
611
|
{
|
|
360
612
|
className: cn(
|
|
361
613
|
"relative w-full bg-background overflow-hidden group",
|
|
362
|
-
title || subtitleText ? "rounded-b-xl" : "rounded-xl"
|
|
614
|
+
title || subtitleText ? "rounded-b-xl" : "rounded-xl",
|
|
615
|
+
// Hide cursor when controls are hidden and video is playing
|
|
616
|
+
isPlaying && !showControls ? "cursor-none group-hover:cursor-default" : "cursor-default"
|
|
363
617
|
),
|
|
618
|
+
"aria-label": title ? `Video player: ${title}` : "Video player",
|
|
619
|
+
onMouseMove: handleMouseMove,
|
|
620
|
+
onMouseEnter: showControlsWithTimer,
|
|
621
|
+
onMouseLeave: handleMouseLeave,
|
|
364
622
|
children: [
|
|
365
623
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
366
624
|
"video",
|
|
@@ -373,39 +631,7 @@ var VideoPlayer = ({
|
|
|
373
631
|
onTimeUpdate: handleTimeUpdate,
|
|
374
632
|
onLoadedMetadata: handleLoadedMetadata,
|
|
375
633
|
onClick: togglePlayPause,
|
|
376
|
-
onKeyDown:
|
|
377
|
-
if (e.key) {
|
|
378
|
-
setShowControls(true);
|
|
379
|
-
}
|
|
380
|
-
if (e.key === " " || e.key === "Enter") {
|
|
381
|
-
e.preventDefault();
|
|
382
|
-
togglePlayPause();
|
|
383
|
-
}
|
|
384
|
-
if (e.key === "ArrowLeft" && videoRef.current) {
|
|
385
|
-
e.preventDefault();
|
|
386
|
-
videoRef.current.currentTime -= 10;
|
|
387
|
-
}
|
|
388
|
-
if (e.key === "ArrowRight" && videoRef.current) {
|
|
389
|
-
e.preventDefault();
|
|
390
|
-
videoRef.current.currentTime += 10;
|
|
391
|
-
}
|
|
392
|
-
if (e.key === "ArrowUp") {
|
|
393
|
-
e.preventDefault();
|
|
394
|
-
handleVolumeChange(Math.min(100, volume * 100 + 10));
|
|
395
|
-
}
|
|
396
|
-
if (e.key === "ArrowDown") {
|
|
397
|
-
e.preventDefault();
|
|
398
|
-
handleVolumeChange(Math.max(0, volume * 100 - 10));
|
|
399
|
-
}
|
|
400
|
-
if (e.key === "m" || e.key === "M") {
|
|
401
|
-
e.preventDefault();
|
|
402
|
-
toggleMute();
|
|
403
|
-
}
|
|
404
|
-
if (e.key === "f" || e.key === "F") {
|
|
405
|
-
e.preventDefault();
|
|
406
|
-
toggleFullscreen();
|
|
407
|
-
}
|
|
408
|
-
},
|
|
634
|
+
onKeyDown: handleVideoKeyDown,
|
|
409
635
|
tabIndex: 0,
|
|
410
636
|
"aria-label": title ? `Video: ${title}` : "Video player",
|
|
411
637
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -435,9 +661,9 @@ var VideoPlayer = ({
|
|
|
435
661
|
{
|
|
436
662
|
className: cn(
|
|
437
663
|
"absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
|
|
438
|
-
|
|
664
|
+
getTopControlsOpacity()
|
|
439
665
|
),
|
|
440
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "
|
|
666
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
441
667
|
IconButton_default,
|
|
442
668
|
{
|
|
443
669
|
icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_phosphor_react.ArrowsOutSimple, { size: 24 }),
|
|
@@ -453,29 +679,18 @@ var VideoPlayer = ({
|
|
|
453
679
|
{
|
|
454
680
|
className: cn(
|
|
455
681
|
"absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
|
|
456
|
-
|
|
682
|
+
getBottomControlsOpacity()
|
|
457
683
|
),
|
|
458
684
|
children: [
|
|
459
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
460
|
-
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
686
|
+
ProgressBar,
|
|
461
687
|
{
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
onChange: (e) => {
|
|
467
|
-
const newTime = parseFloat(e.target.value);
|
|
468
|
-
if (videoRef.current) {
|
|
469
|
-
videoRef.current.currentTime = newTime;
|
|
470
|
-
}
|
|
471
|
-
},
|
|
472
|
-
className: "w-full h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer slider:bg-primary-600 focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
473
|
-
"aria-label": "Video progress",
|
|
474
|
-
style: {
|
|
475
|
-
background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
|
|
476
|
-
}
|
|
688
|
+
currentTime,
|
|
689
|
+
duration,
|
|
690
|
+
progressPercentage,
|
|
691
|
+
onSeek: handleSeek
|
|
477
692
|
}
|
|
478
|
-
)
|
|
693
|
+
),
|
|
479
694
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between px-4 pb-4", children: [
|
|
480
695
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-4", children: [
|
|
481
696
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -487,32 +702,15 @@ var VideoPlayer = ({
|
|
|
487
702
|
className: "!bg-transparent !text-white hover:!bg-white/20"
|
|
488
703
|
}
|
|
489
704
|
),
|
|
490
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
),
|
|
500
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
501
|
-
"input",
|
|
502
|
-
{
|
|
503
|
-
type: "range",
|
|
504
|
-
min: 0,
|
|
505
|
-
max: 100,
|
|
506
|
-
value: Math.round(volume * 100),
|
|
507
|
-
onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
|
|
508
|
-
className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
|
|
509
|
-
"aria-label": "Volume control",
|
|
510
|
-
style: {
|
|
511
|
-
background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
)
|
|
515
|
-
] }),
|
|
705
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
706
|
+
VolumeControls,
|
|
707
|
+
{
|
|
708
|
+
volume,
|
|
709
|
+
isMuted,
|
|
710
|
+
onVolumeChange: handleVolumeChange,
|
|
711
|
+
onToggleMute: toggleMute
|
|
712
|
+
}
|
|
713
|
+
),
|
|
516
714
|
subtitles && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
517
715
|
IconButton_default,
|
|
518
716
|
{
|
|
@@ -531,29 +729,15 @@ var VideoPlayer = ({
|
|
|
531
729
|
formatTime(duration)
|
|
532
730
|
] })
|
|
533
731
|
] }),
|
|
534
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
),
|
|
544
|
-
showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute bottom-12 right-0 bg-black/90 rounded-lg p-2 min-w-20", children: [0.5, 0.75, 1, 1.25, 1.5, 2].map((speed) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
545
|
-
"button",
|
|
546
|
-
{
|
|
547
|
-
onClick: () => handleSpeedChange(speed),
|
|
548
|
-
className: `block w-full text-left px-3 py-1 text-sm rounded hover:bg-white/20 transition-colors ${playbackRate === speed ? "text-primary-400" : "text-white"}`,
|
|
549
|
-
children: [
|
|
550
|
-
speed,
|
|
551
|
-
"x"
|
|
552
|
-
]
|
|
553
|
-
},
|
|
554
|
-
speed
|
|
555
|
-
)) })
|
|
556
|
-
] }) })
|
|
732
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
733
|
+
SpeedMenu,
|
|
734
|
+
{
|
|
735
|
+
showSpeedMenu,
|
|
736
|
+
playbackRate,
|
|
737
|
+
onToggleMenu: toggleSpeedMenu,
|
|
738
|
+
onSpeedChange: handleSpeedChange
|
|
739
|
+
}
|
|
740
|
+
) })
|
|
557
741
|
] })
|
|
558
742
|
]
|
|
559
743
|
}
|