diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index 6858e2cd1..24453043b 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -297,9 +297,13 @@ export const ScenePlayer: React.FC = ({ sendSetTimestamp((value: number) => { const player = getPlayer(); if (player && value >= 0) { - player.play()?.then(() => { + if (player.hasStarted() && player.paused()) { player.currentTime(value); - }); + } else { + player.play()?.then(() => { + player.currentTime(value); + }); + } } }); }, [sendSetTimestamp, getPlayer]); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx index 03fcb3b48..baaf028dc 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneMarkerForm.tsx @@ -213,7 +213,7 @@ export const SceneMarkerForm: React.FC = ({ value={formik.values.seconds} setValue={(v) => formik.setFieldValue("seconds", v)} onReset={() => - formik.setFieldValue("seconds", Math.round(getPlayerPosition() ?? 0)) + formik.setFieldValue("seconds", getPlayerPosition() ?? 0) } error={error} /> diff --git a/ui/v2.5/src/components/Shared/DurationInput.tsx b/ui/v2.5/src/components/Shared/DurationInput.tsx index 7c7fde431..5bd58b1e5 100644 --- a/ui/v2.5/src/components/Shared/DurationInput.tsx +++ b/ui/v2.5/src/components/Shared/DurationInput.tsx @@ -3,7 +3,7 @@ import { faChevronUp, faClock, } from "@fortawesome/free-solid-svg-icons"; -import React, { useState } from "react"; +import React, { useMemo, useState } from "react"; import { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap"; import { Icon } from "./Icon"; import TextUtils from "src/utils/text"; @@ -19,6 +19,8 @@ interface IProps { allowNegative?: boolean; } +const includeMS = true; + export const DurationInput: React.FC = ({ disabled, value, @@ -96,17 +98,20 @@ export const DurationInput: React.FC = ({ } } - let inputValue = ""; - if (tmpValue !== undefined) { - inputValue = tmpValue; - } else if (value !== null && value !== undefined) { - inputValue = TextUtils.secondsToTimestamp(value); - } + const inputValue = useMemo(() => { + if (tmpValue !== undefined) { + return tmpValue; + } else if (value !== null && value !== undefined) { + return TextUtils.secondsToTimestamp(value, includeMS); + } + }, [value, tmpValue]); + + const format = "hh:mm:ss.ms"; if (placeholder) { - placeholder = `${placeholder} (hh:mm:ss)`; + placeholder = `${placeholder} (${format})`; } else { - placeholder = "hh:mm:ss"; + placeholder = format; } return ( diff --git a/ui/v2.5/src/utils/text.ts b/ui/v2.5/src/utils/text.ts index da7f7e024..246dcb0e4 100644 --- a/ui/v2.5/src/utils/text.ts +++ b/ui/v2.5/src/utils/text.ts @@ -151,16 +151,20 @@ const fileSizeFractionalDigits = (unit: Unit) => { return 0; }; -// Converts seconds to a hh:mm:ss or mm:ss timestamp. +// Converts seconds to a [hh:]mm:ss[.ffff] where hh is only shown if hours is non-zero, +// and ffff is shown only if frameRate is set, and the seconds includes a fractional component. // A negative input will result in a -hh:mm:ss or -mm:ss output. -// Fractional inputs are truncated. -const secondsToTimestamp = (seconds: number) => { +const secondsToTimestamp = (secondsInput: number, includeMS?: boolean) => { let neg = false; - if (seconds < 0) { + if (secondsInput < 0) { neg = true; - seconds = -seconds; + secondsInput = -secondsInput; } - seconds = Math.trunc(seconds); + + const fracSeconds = secondsInput % 1; + const ms = Math.round(fracSeconds * 1000); + + let seconds = Math.trunc(secondsInput); const s = seconds % 60; seconds = (seconds - s) / 60; @@ -177,6 +181,11 @@ const secondsToTimestamp = (seconds: number) => { ret = String(m).padStart(2, "0") + ":" + ret; ret = String(h) + ":" + ret; } + + if (includeMS && ms > 0) { + ret += "." + ms.toString().padStart(3, "0"); + } + if (neg) { return "-" + ret; } else { @@ -202,6 +211,24 @@ const timestampToSeconds = (v: string | null | undefined) => { return null; } + let secondsPart = splits[splits.length - 1]; + let msFrac = 0; + if (secondsPart.includes(".")) { + const secondsParts = secondsPart.split("."); + if (secondsParts.length !== 2) { + return null; + } + + secondsPart = secondsParts[0]; + + const msPart = parseInt(secondsParts[1], 10); + if (Number.isNaN(msPart)) { + return null; + } + + msFrac = msPart / 1000; + } + let seconds = 0; let factor = 1; while (splits.length > 0) { @@ -219,7 +246,7 @@ const timestampToSeconds = (v: string | null | undefined) => { factor *= 60; } - return seconds; + return seconds + msFrac; }; const fileNameFromPath = (path: string) => {