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/index.js CHANGED
@@ -6826,12 +6826,93 @@ var NotFound_default = NotFound;
6826
6826
  var import_react22 = require("react");
6827
6827
  var import_phosphor_react18 = require("phosphor-react");
6828
6828
  var import_jsx_runtime36 = require("react/jsx-runtime");
6829
+ var CONTROLS_HIDE_TIMEOUT = 3e3;
6830
+ var LEAVE_HIDE_TIMEOUT = 1e3;
6829
6831
  var formatTime = (seconds) => {
6830
6832
  if (!seconds || isNaN(seconds)) return "0:00";
6831
6833
  const mins = Math.floor(seconds / 60);
6832
6834
  const secs = Math.floor(seconds % 60);
6833
6835
  return `${mins}:${secs.toString().padStart(2, "0")}`;
6834
6836
  };
6837
+ var ProgressBar2 = ({
6838
+ currentTime,
6839
+ duration,
6840
+ progressPercentage,
6841
+ onSeek
6842
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6843
+ "input",
6844
+ {
6845
+ type: "range",
6846
+ min: 0,
6847
+ max: duration || 100,
6848
+ value: currentTime,
6849
+ onChange: (e) => onSeek(parseFloat(e.target.value)),
6850
+ 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",
6851
+ "aria-label": "Video progress",
6852
+ style: {
6853
+ background: `linear-gradient(to right, var(--color-primary-700) ${progressPercentage}%, var(--color-secondary-300) ${progressPercentage}%)`
6854
+ }
6855
+ }
6856
+ ) });
6857
+ var VolumeControls = ({
6858
+ volume,
6859
+ isMuted,
6860
+ onVolumeChange,
6861
+ onToggleMute
6862
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
6863
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6864
+ IconButton_default,
6865
+ {
6866
+ icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerHigh, { size: 24 }),
6867
+ onClick: onToggleMute,
6868
+ "aria-label": isMuted ? "Unmute" : "Mute",
6869
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6870
+ }
6871
+ ),
6872
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6873
+ "input",
6874
+ {
6875
+ type: "range",
6876
+ min: 0,
6877
+ max: 100,
6878
+ value: Math.round(volume * 100),
6879
+ onChange: (e) => onVolumeChange(parseInt(e.target.value)),
6880
+ className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
6881
+ "aria-label": "Volume control",
6882
+ style: {
6883
+ background: `linear-gradient(to right, var(--color-primary-700) ${volume * 100}%, var(--color-secondary-300) ${volume * 100}%)`
6884
+ }
6885
+ }
6886
+ )
6887
+ ] });
6888
+ var SpeedMenu = ({
6889
+ showSpeedMenu,
6890
+ playbackRate,
6891
+ onToggleMenu,
6892
+ onSpeedChange
6893
+ }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative", children: [
6894
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
6895
+ IconButton_default,
6896
+ {
6897
+ icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.DotsThreeVertical, { size: 24 }),
6898
+ onClick: onToggleMenu,
6899
+ "aria-label": "Playback speed",
6900
+ className: "!bg-transparent !text-white hover:!bg-white/20"
6901
+ }
6902
+ ),
6903
+ showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime36.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_runtime36.jsxs)(
6904
+ "button",
6905
+ {
6906
+ onClick: () => onSpeedChange(speed),
6907
+ 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"}`,
6908
+ children: [
6909
+ speed,
6910
+ "x"
6911
+ ]
6912
+ },
6913
+ speed
6914
+ )) })
6915
+ ] });
6835
6916
  var VideoPlayer = ({
6836
6917
  src,
6837
6918
  poster,
@@ -6856,10 +6937,68 @@ var VideoPlayer = ({
6856
6937
  const [showControls, setShowControls] = (0, import_react22.useState)(true);
6857
6938
  const [hasCompleted, setHasCompleted] = (0, import_react22.useState)(false);
6858
6939
  const [showCaptions, setShowCaptions] = (0, import_react22.useState)(false);
6940
+ (0, import_react22.useEffect)(() => {
6941
+ setHasCompleted(false);
6942
+ }, [src]);
6859
6943
  const [playbackRate, setPlaybackRate] = (0, import_react22.useState)(1);
6860
6944
  const [showSpeedMenu, setShowSpeedMenu] = (0, import_react22.useState)(false);
6861
6945
  const lastSaveTimeRef = (0, import_react22.useRef)(0);
6862
6946
  const trackRef = (0, import_react22.useRef)(null);
6947
+ const controlsTimeoutRef = (0, import_react22.useRef)(null);
6948
+ const lastMousePositionRef = (0, import_react22.useRef)({ x: 0, y: 0 });
6949
+ const mouseMoveTimeoutRef = (0, import_react22.useRef)(null);
6950
+ const isUserInteracting = (0, import_react22.useCallback)(() => {
6951
+ if (showSpeedMenu) return true;
6952
+ const activeElement = document.activeElement;
6953
+ const videoContainer = videoRef.current?.parentElement;
6954
+ if (activeElement && videoContainer?.contains(activeElement)) {
6955
+ const isControl = activeElement.matches("button, input, [tabindex]");
6956
+ if (isControl) return true;
6957
+ }
6958
+ return false;
6959
+ }, [showSpeedMenu]);
6960
+ const clearControlsTimeout = (0, import_react22.useCallback)(() => {
6961
+ if (controlsTimeoutRef.current) {
6962
+ clearTimeout(controlsTimeoutRef.current);
6963
+ controlsTimeoutRef.current = null;
6964
+ }
6965
+ }, []);
6966
+ const clearMouseMoveTimeout = (0, import_react22.useCallback)(() => {
6967
+ if (mouseMoveTimeoutRef.current) {
6968
+ clearTimeout(mouseMoveTimeoutRef.current);
6969
+ mouseMoveTimeoutRef.current = null;
6970
+ }
6971
+ }, []);
6972
+ const showControlsWithTimer = (0, import_react22.useCallback)(() => {
6973
+ setShowControls(true);
6974
+ clearControlsTimeout();
6975
+ if (isPlaying) {
6976
+ controlsTimeoutRef.current = window.setTimeout(() => {
6977
+ setShowControls(false);
6978
+ }, CONTROLS_HIDE_TIMEOUT);
6979
+ }
6980
+ }, [isPlaying, clearControlsTimeout]);
6981
+ const handleMouseMove = (0, import_react22.useCallback)(
6982
+ (event) => {
6983
+ const currentX = event.clientX;
6984
+ const currentY = event.clientY;
6985
+ const lastPos = lastMousePositionRef.current;
6986
+ const hasMoved = Math.abs(currentX - lastPos.x) > 5 || Math.abs(currentY - lastPos.y) > 5;
6987
+ if (hasMoved) {
6988
+ lastMousePositionRef.current = { x: currentX, y: currentY };
6989
+ showControlsWithTimer();
6990
+ }
6991
+ },
6992
+ [showControlsWithTimer]
6993
+ );
6994
+ const handleMouseLeave = (0, import_react22.useCallback)(() => {
6995
+ clearControlsTimeout();
6996
+ if (isPlaying && !isUserInteracting()) {
6997
+ controlsTimeoutRef.current = window.setTimeout(() => {
6998
+ setShowControls(false);
6999
+ }, LEAVE_HIDE_TIMEOUT);
7000
+ }
7001
+ }, [isPlaying, clearControlsTimeout, isUserInteracting]);
6863
7002
  (0, import_react22.useEffect)(() => {
6864
7003
  if (videoRef.current) {
6865
7004
  videoRef.current.volume = volume;
@@ -6867,84 +7006,129 @@ var VideoPlayer = ({
6867
7006
  }
6868
7007
  }, [volume, isMuted]);
6869
7008
  (0, import_react22.useEffect)(() => {
6870
- if (!autoSave || !storageKey) return;
6871
- const raw = localStorage.getItem(`${storageKey}-${src}`);
6872
- const saved = raw !== null ? Number(raw) : NaN;
6873
- const hasValidSaved = Number.isFinite(saved) && saved >= 0;
6874
- const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
6875
- let start;
6876
- if (hasValidInitial) {
6877
- start = initialTime;
6878
- } else if (hasValidSaved) {
6879
- start = saved;
7009
+ const video = videoRef.current;
7010
+ if (!video) return;
7011
+ const onPlay = () => setIsPlaying(true);
7012
+ const onPause = () => setIsPlaying(false);
7013
+ const onEnded = () => setIsPlaying(false);
7014
+ video.addEventListener("play", onPlay);
7015
+ video.addEventListener("pause", onPause);
7016
+ video.addEventListener("ended", onEnded);
7017
+ return () => {
7018
+ video.removeEventListener("play", onPlay);
7019
+ video.removeEventListener("pause", onPause);
7020
+ video.removeEventListener("ended", onEnded);
7021
+ };
7022
+ }, []);
7023
+ (0, import_react22.useEffect)(() => {
7024
+ if (isPlaying) {
7025
+ showControlsWithTimer();
6880
7026
  } else {
6881
- start = void 0;
7027
+ clearControlsTimeout();
7028
+ setShowControls(true);
7029
+ }
7030
+ }, [isPlaying, showControlsWithTimer, clearControlsTimeout]);
7031
+ (0, import_react22.useEffect)(() => {
7032
+ const handleFullscreenChange = () => {
7033
+ const isCurrentlyFullscreen = !!document.fullscreenElement;
7034
+ setIsFullscreen(isCurrentlyFullscreen);
7035
+ if (isCurrentlyFullscreen) {
7036
+ showControlsWithTimer();
7037
+ }
7038
+ };
7039
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
7040
+ return () => {
7041
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
7042
+ };
7043
+ }, [showControlsWithTimer]);
7044
+ const getInitialTime = (0, import_react22.useCallback)(() => {
7045
+ if (!autoSave || !storageKey) {
7046
+ return Number.isFinite(initialTime) && initialTime >= 0 ? initialTime : void 0;
6882
7047
  }
7048
+ const saved = Number(localStorage.getItem(`${storageKey}-${src}`) || NaN);
7049
+ const hasValidInitial = Number.isFinite(initialTime) && initialTime >= 0;
7050
+ const hasValidSaved = Number.isFinite(saved) && saved >= 0;
7051
+ if (hasValidInitial) return initialTime;
7052
+ if (hasValidSaved) return saved;
7053
+ return void 0;
7054
+ }, [autoSave, storageKey, src, initialTime]);
7055
+ (0, import_react22.useEffect)(() => {
7056
+ const start = getInitialTime();
6883
7057
  if (start !== void 0 && videoRef.current) {
6884
7058
  videoRef.current.currentTime = start;
6885
7059
  setCurrentTime(start);
6886
7060
  }
6887
- }, [src, storageKey, autoSave, initialTime]);
6888
- const saveProgress = (0, import_react22.useCallback)(() => {
6889
- if (!autoSave || !storageKey) return;
6890
- const now = Date.now();
6891
- if (now - lastSaveTimeRef.current > 5e3) {
6892
- localStorage.setItem(`${storageKey}-${src}`, currentTime.toString());
6893
- lastSaveTimeRef.current = now;
6894
- }
6895
- }, [autoSave, storageKey, src, currentTime]);
6896
- const togglePlayPause = (0, import_react22.useCallback)(() => {
6897
- if (videoRef.current) {
6898
- if (isPlaying) {
6899
- videoRef.current.pause();
6900
- } else {
6901
- videoRef.current.play();
7061
+ }, [getInitialTime]);
7062
+ const saveProgress = (0, import_react22.useCallback)(
7063
+ (time) => {
7064
+ if (!autoSave || !storageKey) return;
7065
+ const now = Date.now();
7066
+ if (now - lastSaveTimeRef.current > 5e3) {
7067
+ localStorage.setItem(`${storageKey}-${src}`, time.toString());
7068
+ lastSaveTimeRef.current = now;
6902
7069
  }
6903
- setIsPlaying(!isPlaying);
7070
+ },
7071
+ [autoSave, storageKey, src]
7072
+ );
7073
+ const togglePlayPause = (0, import_react22.useCallback)(async () => {
7074
+ const video = videoRef.current;
7075
+ if (!video) return;
7076
+ if (!video.paused) {
7077
+ video.pause();
7078
+ return;
7079
+ }
7080
+ try {
7081
+ await video.play();
7082
+ } catch {
6904
7083
  }
6905
- }, [isPlaying]);
7084
+ }, []);
6906
7085
  const handleVolumeChange = (0, import_react22.useCallback)(
6907
7086
  (newVolume) => {
6908
- if (videoRef.current) {
6909
- const volumeValue = newVolume / 100;
6910
- videoRef.current.volume = volumeValue;
6911
- setVolume(volumeValue);
6912
- if (volumeValue === 0) {
6913
- videoRef.current.muted = true;
6914
- setIsMuted(true);
6915
- } else if (isMuted) {
6916
- videoRef.current.muted = false;
6917
- setIsMuted(false);
6918
- }
7087
+ const video = videoRef.current;
7088
+ if (!video) return;
7089
+ const volumeValue = newVolume / 100;
7090
+ video.volume = volumeValue;
7091
+ setVolume(volumeValue);
7092
+ const shouldMute = volumeValue === 0;
7093
+ const shouldUnmute = volumeValue > 0 && isMuted;
7094
+ if (shouldMute) {
7095
+ video.muted = true;
7096
+ setIsMuted(true);
7097
+ } else if (shouldUnmute) {
7098
+ video.muted = false;
7099
+ setIsMuted(false);
6919
7100
  }
6920
7101
  },
6921
7102
  [isMuted]
6922
7103
  );
6923
7104
  const toggleMute = (0, import_react22.useCallback)(() => {
6924
- if (videoRef.current) {
6925
- if (isMuted) {
6926
- const restoreVolume = volume > 0 ? volume : 0.5;
6927
- videoRef.current.volume = restoreVolume;
6928
- videoRef.current.muted = false;
6929
- setVolume(restoreVolume);
6930
- setIsMuted(false);
6931
- } else {
6932
- videoRef.current.muted = true;
6933
- setIsMuted(true);
6934
- }
7105
+ const video = videoRef.current;
7106
+ if (!video) return;
7107
+ if (isMuted) {
7108
+ const restoreVolume = volume > 0 ? volume : 0.5;
7109
+ video.volume = restoreVolume;
7110
+ video.muted = false;
7111
+ setVolume(restoreVolume);
7112
+ setIsMuted(false);
7113
+ } else {
7114
+ video.muted = true;
7115
+ setIsMuted(true);
6935
7116
  }
6936
7117
  }, [isMuted, volume]);
7118
+ const handleSeek = (0, import_react22.useCallback)((newTime) => {
7119
+ const video = videoRef.current;
7120
+ if (video) {
7121
+ video.currentTime = newTime;
7122
+ }
7123
+ }, []);
6937
7124
  const toggleFullscreen = (0, import_react22.useCallback)(() => {
6938
7125
  const container = videoRef.current?.parentElement;
6939
7126
  if (!container) return;
6940
- if (!isFullscreen) {
6941
- if (container.requestFullscreen) {
6942
- container.requestFullscreen();
6943
- }
6944
- } else if (document.exitFullscreen) {
7127
+ if (!isFullscreen && container.requestFullscreen) {
7128
+ container.requestFullscreen();
7129
+ } else if (isFullscreen && document.exitFullscreen) {
6945
7130
  document.exitFullscreen();
6946
7131
  }
6947
- setIsFullscreen(!isFullscreen);
6948
7132
  }, [isFullscreen]);
6949
7133
  const handleSpeedChange = (0, import_react22.useCallback)((speed) => {
6950
7134
  if (videoRef.current) {
@@ -6962,29 +7146,28 @@ var VideoPlayer = ({
6962
7146
  setShowCaptions(newShowCaptions);
6963
7147
  trackRef.current.track.mode = newShowCaptions && subtitles ? "showing" : "hidden";
6964
7148
  }, [showCaptions, subtitles]);
6965
- const handleTimeUpdate = (0, import_react22.useCallback)(() => {
6966
- if (videoRef.current) {
6967
- const current = videoRef.current.currentTime;
6968
- setCurrentTime(current);
6969
- saveProgress();
6970
- onTimeUpdate?.(current);
6971
- if (duration > 0) {
6972
- const progressPercent = current / duration * 100;
6973
- onProgress?.(progressPercent);
6974
- if (progressPercent >= 95 && !hasCompleted) {
6975
- setHasCompleted(true);
6976
- onVideoComplete?.();
6977
- }
7149
+ const checkVideoCompletion = (0, import_react22.useCallback)(
7150
+ (progressPercent) => {
7151
+ if (progressPercent >= 95 && !hasCompleted) {
7152
+ setHasCompleted(true);
7153
+ onVideoComplete?.();
6978
7154
  }
7155
+ },
7156
+ [hasCompleted, onVideoComplete]
7157
+ );
7158
+ const handleTimeUpdate = (0, import_react22.useCallback)(() => {
7159
+ const video = videoRef.current;
7160
+ if (!video) return;
7161
+ const current = video.currentTime;
7162
+ setCurrentTime(current);
7163
+ saveProgress(current);
7164
+ onTimeUpdate?.(current);
7165
+ if (duration > 0) {
7166
+ const progressPercent = current / duration * 100;
7167
+ onProgress?.(progressPercent);
7168
+ checkVideoCompletion(progressPercent);
6979
7169
  }
6980
- }, [
6981
- duration,
6982
- saveProgress,
6983
- onTimeUpdate,
6984
- onProgress,
6985
- onVideoComplete,
6986
- hasCompleted
6987
- ]);
7170
+ }, [duration, saveProgress, onTimeUpdate, onProgress, checkVideoCompletion]);
6988
7171
  const handleLoadedMetadata = (0, import_react22.useCallback)(() => {
6989
7172
  if (videoRef.current) {
6990
7173
  setDuration(videoRef.current.duration);
@@ -7013,9 +7196,78 @@ var VideoPlayer = ({
7013
7196
  return () => {
7014
7197
  document.removeEventListener("visibilitychange", handleVisibilityChange);
7015
7198
  window.removeEventListener("blur", handleBlur);
7199
+ clearControlsTimeout();
7200
+ clearMouseMoveTimeout();
7016
7201
  };
7017
- }, [isPlaying]);
7202
+ }, [isPlaying, clearControlsTimeout, clearMouseMoveTimeout]);
7018
7203
  const progressPercentage = duration > 0 ? currentTime / duration * 100 : 0;
7204
+ const getTopControlsOpacity = (0, import_react22.useCallback)(() => {
7205
+ if (isFullscreen) {
7206
+ return showControls ? "opacity-100" : "opacity-0";
7207
+ }
7208
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7209
+ }, [isFullscreen, showControls, isPlaying]);
7210
+ const getBottomControlsOpacity = (0, import_react22.useCallback)(() => {
7211
+ if (isFullscreen) {
7212
+ return showControls ? "opacity-100" : "opacity-0";
7213
+ }
7214
+ return !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100";
7215
+ }, [isFullscreen, showControls, isPlaying]);
7216
+ const handleVideoKeyDown = (0, import_react22.useCallback)(
7217
+ (e) => {
7218
+ if (e.key) {
7219
+ e.stopPropagation();
7220
+ showControlsWithTimer();
7221
+ }
7222
+ switch (e.key) {
7223
+ case " ":
7224
+ case "Enter":
7225
+ e.preventDefault();
7226
+ togglePlayPause();
7227
+ break;
7228
+ case "ArrowLeft":
7229
+ e.preventDefault();
7230
+ if (videoRef.current) {
7231
+ videoRef.current.currentTime -= 10;
7232
+ }
7233
+ break;
7234
+ case "ArrowRight":
7235
+ e.preventDefault();
7236
+ if (videoRef.current) {
7237
+ videoRef.current.currentTime += 10;
7238
+ }
7239
+ break;
7240
+ case "ArrowUp":
7241
+ e.preventDefault();
7242
+ handleVolumeChange(Math.min(100, volume * 100 + 10));
7243
+ break;
7244
+ case "ArrowDown":
7245
+ e.preventDefault();
7246
+ handleVolumeChange(Math.max(0, volume * 100 - 10));
7247
+ break;
7248
+ case "m":
7249
+ case "M":
7250
+ e.preventDefault();
7251
+ toggleMute();
7252
+ break;
7253
+ case "f":
7254
+ case "F":
7255
+ e.preventDefault();
7256
+ toggleFullscreen();
7257
+ break;
7258
+ default:
7259
+ break;
7260
+ }
7261
+ },
7262
+ [
7263
+ showControlsWithTimer,
7264
+ togglePlayPause,
7265
+ handleVolumeChange,
7266
+ volume,
7267
+ toggleMute,
7268
+ toggleFullscreen
7269
+ ]
7270
+ );
7019
7271
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: cn("flex flex-col", className), children: [
7020
7272
  (title || subtitleText) && /* @__PURE__ */ (0, import_jsx_runtime36.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_runtime36.jsxs)("div", { className: "flex flex-col gap-1", children: [
7021
7273
  title && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7042,12 +7294,18 @@ var VideoPlayer = ({
7042
7294
  )
7043
7295
  ] }) }),
7044
7296
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
7045
- "div",
7297
+ "section",
7046
7298
  {
7047
7299
  className: cn(
7048
7300
  "relative w-full bg-background overflow-hidden group",
7049
- title || subtitleText ? "rounded-b-xl" : "rounded-xl"
7301
+ title || subtitleText ? "rounded-b-xl" : "rounded-xl",
7302
+ // Hide cursor when controls are hidden and video is playing
7303
+ isPlaying && !showControls ? "cursor-none group-hover:cursor-default" : "cursor-default"
7050
7304
  ),
7305
+ "aria-label": title ? `Video player: ${title}` : "Video player",
7306
+ onMouseMove: handleMouseMove,
7307
+ onMouseEnter: showControlsWithTimer,
7308
+ onMouseLeave: handleMouseLeave,
7051
7309
  children: [
7052
7310
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7053
7311
  "video",
@@ -7060,39 +7318,7 @@ var VideoPlayer = ({
7060
7318
  onTimeUpdate: handleTimeUpdate,
7061
7319
  onLoadedMetadata: handleLoadedMetadata,
7062
7320
  onClick: togglePlayPause,
7063
- onKeyDown: (e) => {
7064
- if (e.key) {
7065
- setShowControls(true);
7066
- }
7067
- if (e.key === " " || e.key === "Enter") {
7068
- e.preventDefault();
7069
- togglePlayPause();
7070
- }
7071
- if (e.key === "ArrowLeft" && videoRef.current) {
7072
- e.preventDefault();
7073
- videoRef.current.currentTime -= 10;
7074
- }
7075
- if (e.key === "ArrowRight" && videoRef.current) {
7076
- e.preventDefault();
7077
- videoRef.current.currentTime += 10;
7078
- }
7079
- if (e.key === "ArrowUp") {
7080
- e.preventDefault();
7081
- handleVolumeChange(Math.min(100, volume * 100 + 10));
7082
- }
7083
- if (e.key === "ArrowDown") {
7084
- e.preventDefault();
7085
- handleVolumeChange(Math.max(0, volume * 100 - 10));
7086
- }
7087
- if (e.key === "m" || e.key === "M") {
7088
- e.preventDefault();
7089
- toggleMute();
7090
- }
7091
- if (e.key === "f" || e.key === "F") {
7092
- e.preventDefault();
7093
- toggleFullscreen();
7094
- }
7095
- },
7321
+ onKeyDown: handleVideoKeyDown,
7096
7322
  tabIndex: 0,
7097
7323
  "aria-label": title ? `Video: ${title}` : "Video player",
7098
7324
  children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7122,9 +7348,9 @@ var VideoPlayer = ({
7122
7348
  {
7123
7349
  className: cn(
7124
7350
  "absolute top-0 left-0 right-0 p-4 bg-gradient-to-b from-black/70 to-transparent transition-opacity",
7125
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7351
+ getTopControlsOpacity()
7126
7352
  ),
7127
- children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "ml-auto block", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7353
+ children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex justify-start", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7128
7354
  IconButton_default,
7129
7355
  {
7130
7356
  icon: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.ArrowsInSimple, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.ArrowsOutSimple, { size: 24 }),
@@ -7140,29 +7366,18 @@ var VideoPlayer = ({
7140
7366
  {
7141
7367
  className: cn(
7142
7368
  "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/90 to-transparent transition-opacity",
7143
- !isPlaying || showControls ? "opacity-100" : "opacity-0 group-hover:opacity-100"
7369
+ getBottomControlsOpacity()
7144
7370
  ),
7145
7371
  children: [
7146
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "px-4 pb-2", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7147
- "input",
7372
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7373
+ ProgressBar2,
7148
7374
  {
7149
- type: "range",
7150
- min: 0,
7151
- max: duration || 100,
7152
- value: currentTime,
7153
- onChange: (e) => {
7154
- const newTime = parseFloat(e.target.value);
7155
- if (videoRef.current) {
7156
- videoRef.current.currentTime = newTime;
7157
- }
7158
- },
7159
- 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",
7160
- "aria-label": "Video progress",
7161
- style: {
7162
- background: `linear-gradient(to right, #2271C4 ${progressPercentage}%, #D5D4D4 ${progressPercentage}%)`
7163
- }
7375
+ currentTime,
7376
+ duration,
7377
+ progressPercentage,
7378
+ onSeek: handleSeek
7164
7379
  }
7165
- ) }),
7380
+ ),
7166
7381
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center justify-between px-4 pb-4", children: [
7167
7382
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-4", children: [
7168
7383
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
@@ -7174,32 +7389,15 @@ var VideoPlayer = ({
7174
7389
  className: "!bg-transparent !text-white hover:!bg-white/20"
7175
7390
  }
7176
7391
  ),
7177
- /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "flex items-center gap-2", children: [
7178
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7179
- IconButton_default,
7180
- {
7181
- icon: isMuted ? /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerSlash, { size: 24 }) : /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.SpeakerHigh, { size: 24 }),
7182
- onClick: toggleMute,
7183
- "aria-label": isMuted ? "Unmute" : "Mute",
7184
- className: "!bg-transparent !text-white hover:!bg-white/20"
7185
- }
7186
- ),
7187
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7188
- "input",
7189
- {
7190
- type: "range",
7191
- min: 0,
7192
- max: 100,
7193
- value: Math.round(volume * 100),
7194
- onChange: (e) => handleVolumeChange(parseInt(e.target.value)),
7195
- className: "w-20 h-1 bg-neutral-600 rounded-full appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500",
7196
- "aria-label": "Volume control",
7197
- style: {
7198
- background: `linear-gradient(to right, #2271C4 ${volume * 100}%, #D5D4D4 ${volume * 100}%)`
7199
- }
7200
- }
7201
- )
7202
- ] }),
7392
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7393
+ VolumeControls,
7394
+ {
7395
+ volume,
7396
+ isMuted,
7397
+ onVolumeChange: handleVolumeChange,
7398
+ onToggleMute: toggleMute
7399
+ }
7400
+ ),
7203
7401
  subtitles && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7204
7402
  IconButton_default,
7205
7403
  {
@@ -7218,29 +7416,15 @@ var VideoPlayer = ({
7218
7416
  formatTime(duration)
7219
7417
  ] })
7220
7418
  ] }),
7221
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "relative", children: [
7222
- /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7223
- IconButton_default,
7224
- {
7225
- icon: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(import_phosphor_react18.DotsThreeVertical, { size: 24 }),
7226
- onClick: toggleSpeedMenu,
7227
- "aria-label": "Playback speed",
7228
- className: "!bg-transparent !text-white hover:!bg-white/20"
7229
- }
7230
- ),
7231
- showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime36.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_runtime36.jsxs)(
7232
- "button",
7233
- {
7234
- onClick: () => handleSpeedChange(speed),
7235
- 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"}`,
7236
- children: [
7237
- speed,
7238
- "x"
7239
- ]
7240
- },
7241
- speed
7242
- )) })
7243
- ] }) })
7419
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("div", { className: "flex items-center gap-4", children: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
7420
+ SpeedMenu,
7421
+ {
7422
+ showSpeedMenu,
7423
+ playbackRate,
7424
+ onToggleMenu: toggleSpeedMenu,
7425
+ onSpeedChange: handleSpeedChange
7426
+ }
7427
+ ) })
7244
7428
  ] })
7245
7429
  ]
7246
7430
  }