From e86d84c4633bd9f0ca1a064b54396318c2846679 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Wed, 11 Mar 2026 19:14:52 +0200 Subject: [PATCH] feat(audio): introduce loop button --- .../src/widgets/type_widgets/file/Audio.tsx | 4 ++- .../widgets/type_widgets/file/MediaPlayer.tsx | 30 ++++++++++++++++ .../src/widgets/type_widgets/file/Video.tsx | 34 ++----------------- 3 files changed, 35 insertions(+), 33 deletions(-) diff --git a/apps/client/src/widgets/type_widgets/file/Audio.tsx b/apps/client/src/widgets/type_widgets/file/Audio.tsx index ffd83b5e51..32b67ac1b3 100644 --- a/apps/client/src/widgets/type_widgets/file/Audio.tsx +++ b/apps/client/src/widgets/type_widgets/file/Audio.tsx @@ -3,7 +3,7 @@ import { useRef, useState } from "preact/hooks"; import FNote from "../../../entities/fnote"; import { t } from "../../../services/i18n"; import { getUrlForDownload } from "../../../services/open"; -import { PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer"; +import { LoopButton, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer"; export default function AudioPreview({ note }: { note: FNote }) { const [playing, setPlaying] = useState(false); @@ -25,9 +25,11 @@ export default function AudioPreview({ note }: { note: FNote }) {
+
+
diff --git a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx index 6049a53ee6..6985773214 100644 --- a/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx +++ b/apps/client/src/widgets/type_widgets/file/MediaPlayer.tsx @@ -146,3 +146,33 @@ export function SkipButton({ mediaRef, seconds, icon, text }: { mediaRef: RefObj ); } + +export function LoopButton({ mediaRef }: { mediaRef: RefObject }) { + const [loop, setLoop] = useState(() => mediaRef.current?.loop ?? false); + + useEffect(() => { + const media = mediaRef.current; + if (!media) return; + setLoop(media.loop); + + const observer = new MutationObserver(() => setLoop(media.loop)); + observer.observe(media, { attributes: true, attributeFilter: ["loop"] }); + return () => observer.disconnect(); + }, []); + + const toggle = () => { + const media = mediaRef.current; + if (!media) return; + media.loop = !media.loop; + setLoop(media.loop); + }; + + return ( + + ); +} diff --git a/apps/client/src/widgets/type_widgets/file/Video.tsx b/apps/client/src/widgets/type_widgets/file/Video.tsx index c22725f70e..41654140fc 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, SkipButton, VolumeControl } from "./MediaPlayer"; +import { LoopButton, PlayPauseButton, SeekBar, SkipButton, VolumeControl } from "./MediaPlayer"; const AUTO_HIDE_DELAY = 3000; @@ -125,7 +125,7 @@ export default function VideoPreview({ note }: { note: FNote }) { - +
@@ -217,36 +217,6 @@ function PlaybackSpeed({ videoRef }: { videoRef: RefObject }) ); } -function LoopButton({ videoRef }: { videoRef: RefObject }) { - const [loop, setLoop] = useState(() => videoRef.current?.loop ?? false); - - useEffect(() => { - const video = videoRef.current; - if (!video) return; - setLoop(video.loop); - - const observer = new MutationObserver(() => setLoop(video.loop)); - observer.observe(video, { attributes: true, attributeFilter: ["loop"] }); - return () => observer.disconnect(); - }, []); - - const toggle = () => { - const video = videoRef.current; - if (!video) return; - video.loop = !video.loop; - setLoop(video.loop); - }; - - return ( - - ); -} - function RotateButton({ videoRef }: { videoRef: RefObject }) { const [rotation, setRotation] = useState(0);