From b1decb21293484110d4727dc7e9a24c1ab91c10a Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 27 Sep 2020 10:24:38 +0200 Subject: [PATCH] Improve duration display and positioning --- src/routes/player/Player.tsx | 4 ++- src/routes/player/SeekBar.tsx | 61 ++++++++++++++++++++--------------- src/util/formatDuration.ts | 11 +++++++ 3 files changed, 49 insertions(+), 27 deletions(-) create mode 100644 src/util/formatDuration.ts diff --git a/src/routes/player/Player.tsx b/src/routes/player/Player.tsx index 5846192..179f1ea 100644 --- a/src/routes/player/Player.tsx +++ b/src/routes/player/Player.tsx @@ -3,6 +3,7 @@ import {createUseStyles} from "react-jss"; import {TitleMeta} from "../../api/models/dto/TitleMeta"; import {Media} from "../../api/models/Media"; import {getLocalizedDescription, getLocalizedName, getLocalizedRating} from "../../api/models/Title"; +import {formatDuration} from "../../util/formatDuration"; import {useLocale} from "../../util/locale/LocalizedContext"; import {useDuration} from "../../util/media/useDuration"; import {usePosition} from "../../util/media/usePosition"; @@ -42,11 +43,12 @@ export function Player(props: Props) { <div> <p>{name?.name}</p> {video} - <p>{position}:{duration}</p> + <p style={{fontVariant: "tabular-nums"}}>{formatDuration(position)} / {formatDuration(duration)}</p> <SeekBar video={videoElement} previewTrack={previewTrackElement} duration={duration} + position={position} /> </div> ); diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx index 3a38e70..4efb327 100644 --- a/src/routes/player/SeekBar.tsx +++ b/src/routes/player/SeekBar.tsx @@ -1,4 +1,4 @@ -import React, {useMemo, useRef, useState} from "react"; +import React, {MouseEvent, useCallback, useMemo, useRef, useState} from "react"; import {createUseStyles} from "react-jss"; import {PreviewViewer} from "./PreviewViewer"; @@ -6,6 +6,7 @@ interface Props { previewTrack: HTMLTrackElement | null, video: HTMLVideoElement | null, duration: number, + position: number, } interface MousePosition { @@ -13,7 +14,7 @@ interface MousePosition { relative: number } -function getMousePosition(event: React.MouseEvent<HTMLDivElement>): MousePosition | null { +function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | null { const position = event.clientX - event.currentTarget.offsetLeft; const width = event.currentTarget.offsetWidth; if (position > width) return null; @@ -23,38 +24,46 @@ function getMousePosition(event: React.MouseEvent<HTMLDivElement>): MousePositio }; } -export function SeekBar({video, previewTrack, duration}: Props) { +export function SeekBar({video, previewTrack, duration, position}: Props) { const classes = useStyles(); const seekHeadRef = useRef<HTMLDivElement | null>(null); const [seekPosition, setSeekPosition] = useState<number | null>(null); const isVisible = seekPosition !== null; + const onMouseLeave = useCallback(() => { + setSeekPosition(null) + }, [setSeekPosition]); + + const onClick = useCallback((event: MouseEvent<HTMLDivElement>) => { + const position = getMousePosition(event); + if (position === null) { + return; + } + if (video) { + video.currentTime = position.relative * duration; + } + }, [duration, video]); + + const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => { + const position = getMousePosition(event); + if (position === null) { + return; + } + if (seekHeadRef.current) { + seekHeadRef.current.style.transform = `translate3d(${position.absolute}px, 0, 0)` + } + window.requestAnimationFrame(() => { + setSeekPosition(position.relative * duration) + }) + }, [duration]); + const seekBar = useMemo(() => ( <div className={classes.seekBar} - onMouseLeave={() => setSeekPosition(null)} - onClick={(event) => { - const position = getMousePosition(event); - if (position === null) { - return; - } - if (video) { - video.currentTime = position.relative * duration; - } - }} - onMouseMove={(event) => { - const position = getMousePosition(event); - if (position === null) { - return; - } - if (seekHeadRef.current) { - seekHeadRef.current.style.transform = `translate3d(${position.absolute}px, 0, 0)` - } - window.requestAnimationFrame(() => { - setSeekPosition(position.relative * duration) - }) - }} + onMouseLeave={onMouseLeave} + onClick={onClick} + onMouseMove={onMouseMove} > <div className={classes.seekHead} @@ -64,7 +73,7 @@ export function SeekBar({video, previewTrack, duration}: Props) { }} /> </div> - ), [classes.seekBar, classes.seekHead, duration, isVisible, video]); + ), [classes.seekBar, classes.seekHead, isVisible, onClick, onMouseLeave, onMouseMove]); return ( <div> diff --git a/src/util/formatDuration.ts b/src/util/formatDuration.ts new file mode 100644 index 0000000..5556a1b --- /dev/null +++ b/src/util/formatDuration.ts @@ -0,0 +1,11 @@ +export function formatDuration(duration: number): string { + const seconds = Math.round(duration) % 60; + const minutes = Math.floor(duration / 60) % 60; + const hours = Math.floor(duration / 3600); + + if (hours === 0) { + return `${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; + } else { + return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds.toString().padStart(2, "0")}`; + } +} -- GitLab