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