Sub second marker timestamp precision (#5431)

* Allow DurationInput to accept/format timestamps with milliseconds
* Get current frame at sub-second precision
This commit is contained in:
WithoutPants
2024-11-02 14:59:54 +11:00
committed by GitHub
parent 0d40056f8c
commit e8125d08db
4 changed files with 55 additions and 19 deletions

View File

@@ -297,10 +297,14 @@ export const ScenePlayer: React.FC<IScenePlayerProps> = ({
sendSetTimestamp((value: number) => { sendSetTimestamp((value: number) => {
const player = getPlayer(); const player = getPlayer();
if (player && value >= 0) { if (player && value >= 0) {
if (player.hasStarted() && player.paused()) {
player.currentTime(value);
} else {
player.play()?.then(() => { player.play()?.then(() => {
player.currentTime(value); player.currentTime(value);
}); });
} }
}
}); });
}, [sendSetTimestamp, getPlayer]); }, [sendSetTimestamp, getPlayer]);

View File

@@ -213,7 +213,7 @@ export const SceneMarkerForm: React.FC<ISceneMarkerForm> = ({
value={formik.values.seconds} value={formik.values.seconds}
setValue={(v) => formik.setFieldValue("seconds", v)} setValue={(v) => formik.setFieldValue("seconds", v)}
onReset={() => onReset={() =>
formik.setFieldValue("seconds", Math.round(getPlayerPosition() ?? 0)) formik.setFieldValue("seconds", getPlayerPosition() ?? 0)
} }
error={error} error={error}
/> />

View File

@@ -3,7 +3,7 @@ import {
faChevronUp, faChevronUp,
faClock, faClock,
} from "@fortawesome/free-solid-svg-icons"; } 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 { Button, ButtonGroup, InputGroup, Form } from "react-bootstrap";
import { Icon } from "./Icon"; import { Icon } from "./Icon";
import TextUtils from "src/utils/text"; import TextUtils from "src/utils/text";
@@ -19,6 +19,8 @@ interface IProps {
allowNegative?: boolean; allowNegative?: boolean;
} }
const includeMS = true;
export const DurationInput: React.FC<IProps> = ({ export const DurationInput: React.FC<IProps> = ({
disabled, disabled,
value, value,
@@ -96,17 +98,20 @@ export const DurationInput: React.FC<IProps> = ({
} }
} }
let inputValue = ""; const inputValue = useMemo(() => {
if (tmpValue !== undefined) { if (tmpValue !== undefined) {
inputValue = tmpValue; return tmpValue;
} else if (value !== null && value !== undefined) { } else if (value !== null && value !== undefined) {
inputValue = TextUtils.secondsToTimestamp(value); return TextUtils.secondsToTimestamp(value, includeMS);
} }
}, [value, tmpValue]);
const format = "hh:mm:ss.ms";
if (placeholder) { if (placeholder) {
placeholder = `${placeholder} (hh:mm:ss)`; placeholder = `${placeholder} (${format})`;
} else { } else {
placeholder = "hh:mm:ss"; placeholder = format;
} }
return ( return (

View File

@@ -151,16 +151,20 @@ const fileSizeFractionalDigits = (unit: Unit) => {
return 0; 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. // A negative input will result in a -hh:mm:ss or -mm:ss output.
// Fractional inputs are truncated. const secondsToTimestamp = (secondsInput: number, includeMS?: boolean) => {
const secondsToTimestamp = (seconds: number) => {
let neg = false; let neg = false;
if (seconds < 0) { if (secondsInput < 0) {
neg = true; 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; const s = seconds % 60;
seconds = (seconds - s) / 60; seconds = (seconds - s) / 60;
@@ -177,6 +181,11 @@ const secondsToTimestamp = (seconds: number) => {
ret = String(m).padStart(2, "0") + ":" + ret; ret = String(m).padStart(2, "0") + ":" + ret;
ret = String(h) + ":" + ret; ret = String(h) + ":" + ret;
} }
if (includeMS && ms > 0) {
ret += "." + ms.toString().padStart(3, "0");
}
if (neg) { if (neg) {
return "-" + ret; return "-" + ret;
} else { } else {
@@ -202,6 +211,24 @@ const timestampToSeconds = (v: string | null | undefined) => {
return null; 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 seconds = 0;
let factor = 1; let factor = 1;
while (splits.length > 0) { while (splits.length > 0) {
@@ -219,7 +246,7 @@ const timestampToSeconds = (v: string | null | undefined) => {
factor *= 60; factor *= 60;
} }
return seconds; return seconds + msFrac;
}; };
const fileNameFromPath = (path: string) => { const fileNameFromPath = (path: string) => {