diff --git a/src/components/VideoPlayer.module.css b/src/components/VideoPlayer.module.css index eccb012..2e7cc1b 100644 --- a/src/components/VideoPlayer.module.css +++ b/src/components/VideoPlayer.module.css @@ -27,7 +27,7 @@ /* width: calc(100% - 2.25rem); */ width: 100%; - margin: 1rem 0 0 0; + margin: 0; /* Removes default focus */ &:focus { @@ -101,3 +101,45 @@ min-width: 0; } } + +.loopGroup { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + gap: 6px; + + & button { + text-decoration: none; + display: inline-block; + outline: 0; + border: 0; + cursor: pointer; + background: rgb(var(--accent-light)); + color: #ffffff; + border-radius: 8px; + padding: 12px 12px; + font-size: 16px; + font-weight: 700; + line-height: 1; + transition: + transform 200ms, + background 200ms; + &:hover { + transform: translateY(-2px); + } + } +} + +.loopDisplay { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: 100%; + gap: 0.5rem; +} + +h3 { + margin: 0; +} diff --git a/src/components/VideoPlayer.tsx b/src/components/VideoPlayer.tsx index 875f79d..d224b50 100644 --- a/src/components/VideoPlayer.tsx +++ b/src/components/VideoPlayer.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import ReactPlayer from "react-player"; import classes from "./VideoPlayer.module.css"; @@ -53,28 +53,101 @@ const tryGetVideoUrl = (urlInput: string | null) => { }; export const VideoPlayer = () => { + const playerRef = useRef(null); const urlParams = new URLSearchParams(window.location.search); const videoUrl = tryGetVideoUrl(urlParams.get("v")); + + const [duration, setDuration] = useState(99999); + const handleDuration = (duration: number) => { + setDuration(duration); + setLoopTo(duration); + }; + + // #region Speed const defaultSpeed = tryParseDefaultSpeed(urlParams.get("s")); const [speed, setSpeed] = useState(defaultSpeed); - const handleSpeedChange = (e: React.ChangeEvent) => { setSpeed(parseFloat(e.target.value)); replaceSpeedHistory(e.target.value); }; + // #endregion + + // #region loop + const [isLooping, setIsLooping] = useState(false); + const [loopFrom, setLoopFrom] = useState(null); + const [loopTo, setLoopTo] = useState(null); + const minLoopSeconds = 2; + const hasValidLoopingValues = loopFrom !== null && loopTo !== null; + + const handleProgress = (state: { played: number; playedSeconds: number }) => { + if (!isLooping || loopFrom === null || loopTo === null) { + return; + } + + if (state.playedSeconds > loopTo) { + playerRef.current?.seekTo(loopFrom); + // playing=true needs to be set on the player otherwise it pauses after seeking + } + }; + + const handleSetLoopLast = (xSeconds: number) => { + const to = Math.max( + playerRef.current?.getCurrentTime() || 0, + minLoopSeconds, + ); + setLoopTo(to); + setLoopFrom(Math.max(to - xSeconds, 0)); + setIsLooping(true); + }; + + const handleSetLoopNext = (xSeconds: number) => { + const from = playerRef.current?.getCurrentTime() || 0; + setLoopFrom(from); + setLoopTo(from + xSeconds); + setIsLooping(true); + }; + + // tap-twice - start here, end here + const handleSetStartLoopHere = () => { + setLoopFrom(playerRef.current?.getCurrentTime() || 0); + }; + + const handleSetEndLoopHere = () => { + setLoopTo( + Math.max(playerRef.current?.getCurrentTime() || 0, minLoopSeconds), + ); + if (loopFrom !== null) { + setIsLooping(true); + } + }; + + const handleClearLoop = () => { + setLoopFrom(null); + setLoopTo(null); + setIsLooping(false); + }; + + // #endregion + return (
+

Speed

{
100%
+

Looping

+
+
Loop: {isLooping ? "Enabled" : "Disabled"}
+ {hasValidLoopingValues && ( +
+ from: {loopFrom.toFixed(2)}s to {loopTo.toFixed(2)}s +
+ )} +
+
+ + + + + + +
+
+ + +
+
+ + +
); }; diff --git a/src/pages/play.astro b/src/pages/play.astro index e26cec5..454b21a 100644 --- a/src/pages/play.astro +++ b/src/pages/play.astro @@ -11,7 +11,8 @@ import Footer from "../components/Footer.astro";
-

☝️ Adjust playback speed using the slider above.

+

☝️ Adjust playback speed using the slider.

+

🔁 Adjust looping using the loop controls.

🔖 Bookmark this page to come back to the same video at the same speed.