From dbb8c52e5daa6cd7ea4f57aa20a191496b0fc8df Mon Sep 17 00:00:00 2001 From: Matthias Van Parijs Date: Sat, 4 Jan 2025 09:33:02 +0100 Subject: [PATCH] fix: Added seeking state to player UI --- .../app/src/components/PlayerControls.tsx | 59 ++++++++++++++++--- packages/player/src/hls-player.ts | 6 ++ packages/player/src/state.ts | 3 + 3 files changed, 60 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/PlayerControls.tsx b/packages/app/src/components/PlayerControls.tsx index 150e2cb0..adc0c229 100644 --- a/packages/app/src/components/PlayerControls.tsx +++ b/packages/app/src/components/PlayerControls.tsx @@ -1,8 +1,9 @@ import { Button } from "@nextui-org/react"; +import { Events } from "@superstreamer/player"; import cn from "clsx"; -import { useRef } from "react"; +import { useEffect, useRef, useState } from "react"; import { Selection } from "./Selection"; -import { usePlayerSelector } from "../context/PlayerContext"; +import { usePlayer, usePlayerSelector } from "../context/PlayerContext"; import { useSeekbar } from "../hooks/useSeekbar"; import type { ReactNode, RefObject } from "react"; @@ -35,22 +36,45 @@ function PlayButton() { } function Seekbar() { + const { player } = usePlayer(); const seekableStart = usePlayerSelector((player) => player.seekableStart); const time = usePlayerSelector((player) => player.time); const duration = usePlayerSelector((player) => player.duration); const seekTo = usePlayerSelector((player) => player.seekTo); + const [lastSeekTime, setLastSeekTime] = useState(null); + const seekbar = useSeekbar({ min: seekableStart, max: duration, - onSeeked: seekTo, + onSeeked: (time) => { + if (seekTo(time)) { + setLastSeekTime(time); + } + }, }); - let percentage = Number.isNaN(duration) - ? 0 - : (time - seekableStart) / (duration - seekableStart); - if (percentage < 0) { - percentage = 0; + useEffect(() => { + if (!player) { + return; + } + + const onSeekingChange = () => { + if (!player.seeking) { + setLastSeekTime(null); + } + }; + + player.on(Events.SEEKING_CHANGE, onSeekingChange); + return () => { + player.off(Events.SEEKING_CHANGE, onSeekingChange); + }; + }, [player]); + + const fakeTime = lastSeekTime ?? time; + let percentage = getPercentage(fakeTime, duration, seekableStart); + if (seekbar.seeking) { + percentage = seekbar.x; } return ( @@ -209,3 +233,22 @@ function hms(seconds: number) { "00:00:00" ); } + +function getPercentage(time: number, duration: number, seekableStart: number) { + if (Number.isNaN(duration)) { + return 0; + } + + let timeRel = time - seekableStart; + const durationRel = duration - seekableStart; + + if (timeRel < 0) { + timeRel = 0; + } else if (timeRel > durationRel) { + timeRel = durationRel; + } + + const percentage = timeRel / durationRel; + + return percentage; +} diff --git a/packages/player/src/hls-player.ts b/packages/player/src/hls-player.ts index d42c39be..5de4f489 100644 --- a/packages/player/src/hls-player.ts +++ b/packages/player/src/hls-player.ts @@ -100,11 +100,17 @@ export class HlsPlayer { seekTo(time: number) { assert(this.hls_); + if (this.state_?.interstitial) { + return false; + } + if (this.hls_.interstitialsManager) { this.hls_.interstitialsManager.primary.seekTo(time); } else { this.media_.currentTime = time; } + + return true; } setQuality(height: number | null) { diff --git a/packages/player/src/state.ts b/packages/player/src/state.ts index 03423820..0bec033f 100644 --- a/packages/player/src/state.ts +++ b/packages/player/src/state.ts @@ -94,6 +94,7 @@ export class State implements StateProperties { setInterstitial(interstitial: Interstitial | null) { this.interstitial = interstitial; + this.setSeeking(false); this.params_.onEvent(Events.INTERSTITIAL_CHANGE); } @@ -163,11 +164,13 @@ export class State implements StateProperties { return; } this.seeking = seeking; + this.requestTimingSync(); this.params_.onEvent(Events.SEEKING_CHANGE); } setCuePoints(cuePoints: number[]) { this.cuePoints = cuePoints; + this.requestTimingSync(); this.params_.onEvent(Events.CUEPOINTS_CHANGE); }