feat(audio): introduce loop button

This commit is contained in:
Elian Doran 2026-03-11 19:14:52 +02:00
parent 6b974c2ac7
commit e86d84c463
No known key found for this signature in database
3 changed files with 35 additions and 33 deletions

View File

@ -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 }) {
<div className="left" />
<div className="center">
<div className="spacer" />
<SkipButton mediaRef={audioRef} seconds={-10} icon="bx bx-rewind" text={t("video.back-10s")} />
<PlayPauseButton mediaRef={audioRef} playing={playing} />
<SkipButton mediaRef={audioRef} seconds={30} icon="bx bx-fast-forward" text={t("video.forward-30s")} />
<LoopButton mediaRef={audioRef} />
</div>
<div className="right">

View File

@ -146,3 +146,33 @@ export function SkipButton({ mediaRef, seconds, icon, text }: { mediaRef: RefObj
<ActionButton icon={icon} text={text} onClick={skip} />
);
}
export function LoopButton({ mediaRef }: { mediaRef: RefObject<HTMLVideoElement | HTMLAudioElement> }) {
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 (
<ActionButton
className={loop ? "active" : ""}
icon="bx bx-repeat"
text={loop ? t("video.disable-loop") : t("video.loop")}
onClick={toggle}
/>
);
}

View File

@ -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 }) {
<SkipButton mediaRef={videoRef} seconds={-10} icon="bx bx-rewind" text={t("video.back-10s")} />
<PlayPauseButton mediaRef={videoRef} playing={playing} />
<SkipButton mediaRef={videoRef} seconds={30} icon="bx bx-fast-forward" text={t("video.forward-30s")} />
<LoopButton videoRef={videoRef} />
<LoopButton mediaRef={videoRef} />
</div>
<div className="right">
<VolumeControl mediaRef={videoRef} />
@ -217,36 +217,6 @@ function PlaybackSpeed({ videoRef }: { videoRef: RefObject<HTMLVideoElement> })
);
}
function LoopButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
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 (
<ActionButton
className={loop ? "active" : ""}
icon="bx bx-repeat"
text={loop ? t("video.disable-loop") : t("video.loop")}
onClick={toggle}
/>
);
}
function RotateButton({ videoRef }: { videoRef: RefObject<HTMLVideoElement> }) {
const [rotation, setRotation] = useState(0);