diff --git a/apps/client/src/widgets/type_widgets/file/Audio.tsx b/apps/client/src/widgets/type_widgets/file/Audio.tsx
index 623b4efbfc..99de10caf2 100644
--- a/apps/client/src/widgets/type_widgets/file/Audio.tsx
+++ b/apps/client/src/widgets/type_widgets/file/Audio.tsx
@@ -2,7 +2,7 @@ import { useRef, useState } from "preact/hooks";
import FNote from "../../../entities/fnote";
import { getUrlForDownload } from "../../../services/open";
-import { PlayPauseButton, SeekBar } from "./MediaPlayer";
+import { PlayPauseButton, SeekBar, VolumeControl } from "./MediaPlayer";
export default function AudioPreview({ note }: { note: FNote }) {
const [playing, setPlaying] = useState(false);
@@ -21,9 +21,15 @@ export default function AudioPreview({ note }: { note: FNote }) {
diff --git a/apps/client/src/widgets/type_widgets/file/MediaPlayer.css b/apps/client/src/widgets/type_widgets/file/MediaPlayer.css
index 9e7c5dbdc6..f387b41ebd 100644
--- a/apps/client/src/widgets/type_widgets/file/MediaPlayer.css
+++ b/apps/client/src/widgets/type_widgets/file/MediaPlayer.css
@@ -55,4 +55,15 @@
cursor: pointer;
}
}
+
+ .media-volume-row {
+ display: flex;
+ align-items: center;
+ gap: 0.25em;
+
+ .media-volume-slider {
+ width: 80px;
+ cursor: pointer;
+ }
+ }
}
diff --git a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx
index efdcfb8948..376984ca5c 100644
--- a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx
+++ b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx
@@ -75,3 +75,62 @@ export function PlayPauseButton({ mediaRef, playing }: { mediaRef: RefObject
);
}
+
+export function VolumeControl({ mediaRef }: { mediaRef: RefObject }) {
+ const [volume, setVolume] = useState(() => mediaRef.current?.volume ?? 1);
+ const [muted, setMuted] = useState(() => mediaRef.current?.muted ?? false);
+
+ // Sync state when the video element changes volume externally.
+ useEffect(() => {
+ const media = mediaRef.current;
+ if (!media) return;
+
+ setVolume(media.volume);
+ setMuted(media.muted);
+
+ const onVolumeChange = () => {
+ setVolume(media.volume);
+ setMuted(media.muted);
+ };
+ media.addEventListener("volumechange", onVolumeChange);
+ return () => media.removeEventListener("volumechange", onVolumeChange);
+ }, []);
+
+ const onVolumeChange = (e: Event) => {
+ const media = mediaRef.current;
+ if (!media) return;
+ const val = parseFloat((e.target as HTMLInputElement).value);
+ media.volume = val;
+ setVolume(val);
+ if (val > 0 && media.muted) {
+ media.muted = false;
+ setMuted(false);
+ }
+ };
+
+ const toggleMute = () => {
+ const media = mediaRef.current;
+ if (!media) return;
+ media.muted = !media.muted;
+ setMuted(media.muted);
+ };
+
+ return (
+
+ );
+}
diff --git a/apps/client/src/widgets/type_widgets/file/Video.css b/apps/client/src/widgets/type_widgets/file/Video.css
index 7d2516cab0..9224c02752 100644
--- a/apps/client/src/widgets/type_widgets/file/Video.css
+++ b/apps/client/src/widgets/type_widgets/file/Video.css
@@ -19,17 +19,6 @@
}
}
- .video-volume-row {
- display: flex;
- align-items: center;
- gap: 0.25em;
- }
-
- .video-volume-slider {
- width: 80px;
- cursor: pointer;
- }
-
.media-preview-controls {
--icon-button-hover-color: white;
--icon-button-hover-background: rgba(255, 255, 255, 0.2);
diff --git a/apps/client/src/widgets/type_widgets/file/Video.tsx b/apps/client/src/widgets/type_widgets/file/Video.tsx
index 2b9a5fb473..f6a22e3d4c 100644
--- a/apps/client/src/widgets/type_widgets/file/Video.tsx
+++ b/apps/client/src/widgets/type_widgets/file/Video.tsx
@@ -10,7 +10,7 @@ import ActionButton from "../../react/ActionButton";
import Dropdown from "../../react/Dropdown";
import Icon from "../../react/Icon";
import NoItems from "../../react/NoItems";
-import { PlayPauseButton, SeekBar } from "./MediaPlayer";
+import { PlayPauseButton, SeekBar, VolumeControl } from "./MediaPlayer";
const AUTO_HIDE_DELAY = 3000;
@@ -128,7 +128,7 @@ export default function VideoPreview({ note }: { note: FNote }) {
-
+
@@ -181,65 +181,6 @@ function SkipButton({ videoRef, seconds, icon, text }: { videoRef: RefObject
}) {
- const [volume, setVolume] = useState(() => videoRef.current?.volume ?? 1);
- const [muted, setMuted] = useState(() => videoRef.current?.muted ?? false);
-
- // Sync state when the video element changes volume externally.
- useEffect(() => {
- const video = videoRef.current;
- if (!video) return;
-
- setVolume(video.volume);
- setMuted(video.muted);
-
- const onVolumeChange = () => {
- setVolume(video.volume);
- setMuted(video.muted);
- };
- video.addEventListener("volumechange", onVolumeChange);
- return () => video.removeEventListener("volumechange", onVolumeChange);
- }, []);
-
- const onVolumeChange = (e: Event) => {
- const video = videoRef.current;
- if (!video) return;
- const val = parseFloat((e.target as HTMLInputElement).value);
- video.volume = val;
- setVolume(val);
- if (val > 0 && video.muted) {
- video.muted = false;
- setMuted(false);
- }
- };
-
- const toggleMute = () => {
- const video = videoRef.current;
- if (!video) return;
- video.muted = !video.muted;
- setMuted(video.muted);
- };
-
- return (
-
- );
-}
-
const PLAYBACK_SPEEDS = [0.5, 1, 1.25, 1.5, 2];
function PlaybackSpeed({ videoRef }: { videoRef: RefObject }) {