From 92f0144b48d8ca99eb3cd283a83de4d4d46e21c8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 11 Mar 2026 19:03:09 +0200 Subject: [PATCH] feat(audio): reintroduce seek bar --- .../src/widgets/type_widgets/file/Audio.tsx | 12 +++-- .../widgets/type_widgets/file/MediaPlayer.tsx | 49 +++++++++++++++++ .../src/widgets/type_widgets/file/Video.tsx | 52 +------------------ 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/file/Audio.tsx b/apps/client/src/widgets/type_widgets/file/Audio.tsx index 45c2482215..60db5cf169 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 } from "./MediaPlayer"; +import { PlayPauseButton, SeekBar } from "./MediaPlayer"; export default function AudioPreview({ note }: { note: FNote }) { const [playing, setPlaying] = useState(false); @@ -18,10 +18,14 @@ export default function AudioPreview({ note }: { note: FNote }) { onPause={() => setPlaying(false)} />
-
- + + +
+
+ +
); -} \ No newline at end of file +} diff --git a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx index 54d76f16c6..7105027a85 100644 --- a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx +++ b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx @@ -1,8 +1,57 @@ import { RefObject } from "preact"; +import { useEffect, useState } from "preact/hooks"; import { t } from "../../../services/i18n"; import ActionButton from "../../react/ActionButton"; +export function SeekBar({ mediaRef }: { mediaRef: RefObject }) { + const [currentTime, setCurrentTime] = useState(0); + const [duration, setDuration] = useState(0); + + useEffect(() => { + const media = mediaRef.current; + if (!media) return; + + const onTimeUpdate = () => setCurrentTime(media.currentTime); + const onDurationChange = () => setDuration(media.duration); + + media.addEventListener("timeupdate", onTimeUpdate); + media.addEventListener("durationchange", onDurationChange); + return () => { + media.removeEventListener("timeupdate", onTimeUpdate); + media.removeEventListener("durationchange", onDurationChange); + }; + }, []); + + const onSeek = (e: Event) => { + const media = mediaRef.current; + if (!media) return; + media.currentTime = parseFloat((e.target as HTMLInputElement).value); + }; + + return ( +
+ {formatTime(currentTime)} + + -{formatTime(Math.max(0, duration - currentTime))} +
+ ); +} + +function formatTime(seconds: number): string { + const mins = Math.floor(seconds / 60); + const secs = Math.floor(seconds % 60); + return `${mins}:${secs.toString().padStart(2, "0")}`; +} + export function PlayPauseButton({ mediaRef, playing }: { mediaRef: RefObject, playing: boolean }) { const togglePlayback = () => { const media = mediaRef.current; diff --git a/apps/client/src/widgets/type_widgets/file/Video.tsx b/apps/client/src/widgets/type_widgets/file/Video.tsx index 7c22649d3e..f89aa0c90d 100644 --- a/apps/client/src/widgets/type_widgets/file/Video.tsx +++ b/apps/client/src/widgets/type_widgets/file/Video.tsx @@ -10,13 +10,7 @@ import ActionButton from "../../react/ActionButton"; import Dropdown from "../../react/Dropdown"; import Icon from "../../react/Icon"; import NoItems from "../../react/NoItems"; -import { PlayPauseButton } from "./MediaPlayer"; - -function formatTime(seconds: number): string { - const mins = Math.floor(seconds / 60); - const secs = Math.floor(seconds % 60); - return `${mins}:${secs.toString().padStart(2, "0")}`; -} +import { PlayPauseButton, SeekBar } from "./MediaPlayer"; const AUTO_HIDE_DELAY = 3000; @@ -120,7 +114,7 @@ export default function VideoPreview({ note }: { note: FNote }) { />
- +
@@ -187,48 +181,6 @@ function SkipButton({ videoRef, seconds, icon, text }: { videoRef: RefObject }) { - const [currentTime, setCurrentTime] = useState(0); - const [duration, setDuration] = useState(0); - - useEffect(() => { - const video = videoRef.current; - if (!video) return; - - const onTimeUpdate = () => setCurrentTime(video.currentTime); - const onDurationChange = () => setDuration(video.duration); - - video.addEventListener("timeupdate", onTimeUpdate); - video.addEventListener("durationchange", onDurationChange); - return () => { - video.removeEventListener("timeupdate", onTimeUpdate); - video.removeEventListener("durationchange", onDurationChange); - }; - }, []); - - const onSeek = (e: Event) => { - const video = videoRef.current; - if (!video) return; - video.currentTime = parseFloat((e.target as HTMLInputElement).value); - }; - - return ( -
- {formatTime(currentTime)} - - -{formatTime(Math.max(0, duration - currentTime))} -
- ); -} - function VolumeControl({ videoRef }: { videoRef: RefObject }) { const [volume, setVolume] = useState(() => videoRef.current?.volume ?? 1); const [muted, setMuted] = useState(() => videoRef.current?.muted ?? false);