Add video load time fields to restreamer

thrimbletrimmer-solid
ElementalAlchemist 2 weeks ago
parent c54bd3c63f
commit a50d5d11b2

@ -1,5 +1,11 @@
import { DateTime } from "luxon";
export enum TimeType {
UTC,
BusTime,
TimeAgo,
}
export function dateTimeFromWubloaderTime(wubloaderTime: string): DateTime | null {
const dt = DateTime.fromISO(wubloaderTime, { zone: "UTC" });
if (dt.isValid) {

@ -0,0 +1,11 @@
.streamTimeSettings {
display: flex;
align-items: flex-end;
gap: 5px;
margin-bottom: 10px;
margin-top: 5px;
}
.streamTimeSettingLabel {
margin-right: 3px;
}

@ -0,0 +1,179 @@
import { Accessor, Component, createSignal, Setter, Show } from "solid-js";
import { DateTime } from "luxon";
import {
TimeType,
wubloaderTimeFromDateTime,
busTimeFromDateTime,
timeAgoFromDateTime,
} from "./convertTime";
import styles from "./video.module.scss";
export const VIDEO_FRAMES_PER_SECOND = 30;
export const PLAYBACK_RATES = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4, 8];
export class StreamVideoInfo {
streamName: string;
streamStartTime: DateTime;
streamEndTime: DateTime | null;
}
export interface StreamTimeSettingsProps {
busStartTime: Accessor<DateTime>;
streamVideoInfo: Accessor<StreamVideoInfo>;
setStreamVideoInfo: Setter<StreamVideoInfo>;
showTimeRangeLink: boolean;
}
export const StreamTimeSettings: Component<StreamTimeSettingsProps> = (props) => {
const [timeType, setTimeType] = createSignal<TimeType>(TimeType.UTC);
const submitHandler = (event: SubmitEvent) => {
const form = event.currentTarget as HTMLFormElement;
const formData = new FormData(form);
// TODO
};
const startTimeDisplay = () => {
const startTime = props.streamVideoInfo().streamStartTime;
switch (timeType()) {
case TimeType.UTC:
return wubloaderTimeFromDateTime(startTime);
case TimeType.BusTime:
return busTimeFromDateTime(props.busStartTime(), startTime);
case TimeType.TimeAgo:
return timeAgoFromDateTime(startTime);
}
};
const endTimeDisplay = () => {
const endTime = props.streamVideoInfo().streamEndTime;
if (endTime === null) {
return "";
}
switch (timeType()) {
case TimeType.UTC:
return wubloaderTimeFromDateTime(endTime);
case TimeType.BusTime:
return busTimeFromDateTime(props.busStartTime(), endTime);
case TimeType.TimeAgo:
return timeAgoFromDateTime(endTime);
}
};
const timeRangeLink = () => {
const streamInfo = props.streamVideoInfo();
const startTime = wubloaderTimeFromDateTime(streamInfo.streamStartTime);
const query = new URLSearchParams({
stream: streamInfo.streamName,
start: startTime,
});
if (streamInfo.streamEndTime) {
const endTime = wubloaderTimeFromDateTime(streamInfo.streamEndTime);
query.append("end", endTime);
}
return `?${query}`;
};
return (
<form onSubmit={submitHandler} class={styles.streamTimeSettings}>
<label>
<span class={styles.streamTimeSettingLabel}>Stream</span>
<input type="text" name="stream" value={props.streamVideoInfo().streamName} />
</label>
<label>
<span class={styles.streamTimeSettingLabel}>Start Time</span>
<input type="text" name="start-time" value={startTimeDisplay()} />
</label>
<label>
<span class={styles.streamTimeSettingLabel}>End Time</span>
<input type="text" name="end-time" value={endTimeDisplay()} />
</label>
<div>
<label>
<input
type="radio"
name="time-type"
value={TimeType.UTC}
checked={timeType() === TimeType.UTC}
onClick={(event) => setTimeType(TimeType.UTC)}
/>
UTC
</label>
<label>
<input
type="radio"
name="time-type"
value={TimeType.BusTime}
checked={timeType() === TimeType.BusTime}
onClick={(event) => setTimeType(TimeType.BusTime)}
/>
Bus Time
</label>
<label>
<input
type="radio"
name="time-type"
value={TimeType.TimeAgo}
checked={timeType() === TimeType.TimeAgo}
onClick={(event) => setTimeType(TimeType.TimeAgo)}
/>
Time Ago
</label>
</div>
<div>
<button type="submit">Update Time Range</button>
</div>
<Show when={props.showTimeRangeLink}>
<div>
<a href={timeRangeLink()}>Link to this time range</a>
</div>
</Show>
</form>
);
};
export interface KeyboardShortcutProps {
includeEditorShortcuts: boolean;
}
export const KeyboardShortcuts: Component<KeyboardShortcutProps> = (
props: KeyboardShortcutProps,
) => {
return (
<details>
<summary>Keyboard Shortcuts</summary>
<ul>
<li>Number keys (0-9): Jump to that 10% interval of the video (0% - 90%)</li>
<li>K or Space: Toggle pause</li>
<li>M: Toggle mute</li>
<li>J: Back 10 seconds</li>
<li>L: Forward 10 seconds</li>
<li>Left arrow: Back 5 seconds</li>
<li>Right arrow: Forward 5 seconds</li>
<li>Shift+J: Back 1 second</li>
<li>Shift+L: Forward 1 second</li>
<li>Comma (,): Back 1 frame</li>
<li>Period (.): Forward 1 frame</li>
<li>Equals (=): Increase playback speed 1 step</li>
<li>Hyphen (-): Decrease playback speed 1 step</li>
<li>Shift+=: 2x or maximum playback speed</li>
<li>Shift+-: Minimum playback speed</li>
<li>Backspace: Reset playback speed to 1x</li>
<Show when={props.includeEditorShortcuts}>
<li>
Left bracket ([): Set start point for active range (indicated by arrow) to current video
time
</li>
<li>Right bracket (]): Set end point for active range to current video time</li>
<li>O: Set active range one above current active range</li>
<li>
P: Set active range one below current active range, adding a new range if the current
range is the last one
</li>
</Show>
</ul>
</details>
);
};

@ -1,3 +0,0 @@
export const VIDEO_FRAMES_PER_SECOND = 30;
export const PLAYBACK_RATES = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4, 8];

@ -1,45 +0,0 @@
import { Component, Show } from "solid-js";
export interface KeyboardShortcutProps {
includeEditorShortcuts: boolean;
}
export const KeyboardShortcuts: Component<KeyboardShortcutProps> = (
props: KeyboardShortcutProps,
) => {
return (
<details>
<summary>Keyboard Shortcuts</summary>
<ul>
<li>Number keys (0-9): Jump to that 10% interval of the video (0% - 90%)</li>
<li>K or Space: Toggle pause</li>
<li>M: Toggle mute</li>
<li>J: Back 10 seconds</li>
<li>L: Forward 10 seconds</li>
<li>Left arrow: Back 5 seconds</li>
<li>Right arrow: Forward 5 seconds</li>
<li>Shift+J: Back 1 second</li>
<li>Shift+L: Forward 1 second</li>
<li>Comma (,): Back 1 frame</li>
<li>Period (.): Forward 1 frame</li>
<li>Equals (=): Increase playback speed 1 step</li>
<li>Hyphen (-): Decrease playback speed 1 step</li>
<li>Shift+=: 2x or maximum playback speed</li>
<li>Shift+-: Minimum playback speed</li>
<li>Backspace: Reset playback speed to 1x</li>
<Show when={props.includeEditorShortcuts}>
<li>
Left bracket ([): Set start point for active range (indicated by arrow) to current video
time
</li>
<li>Right bracket (]): Set end point for active range to current video time</li>
<li>O: Set active range one above current active range</li>
<li>
P: Set active range one below current active range, adding a new range if the current
range is the last one
</li>
</Show>
</ul>
</details>
);
};

@ -1,6 +1,6 @@
import "./globalStyle.scss";
import { render } from "solid-js/web";
import Restreamer from "./restreamer/Restreamer";
import { Restreamer } from "./restreamer/Restreamer";
const root = document.getElementById("root");

@ -18,4 +18,4 @@
position: absolute;
top: 0;
right: 0;
}
}

@ -1,9 +1,38 @@
import { Accessor, Component, createSignal, For } from "solid-js";
import { Accessor, Component, createResource, createSignal, For, Show, Suspense } from "solid-js";
import { DateTime } from "luxon";
import styles from "./Restreamer.module.scss";
import { KeyboardShortcuts } from "../common/videoKeyboardShortcuts";
import { dateTimeFromWubloaderTime } from "../common/convertTime";
import { KeyboardShortcuts, StreamTimeSettings, StreamVideoInfo } from "../common/video";
const Restreamer: Component = () => {
export interface DefaultsData {
video_channel: string;
bustime_start: string;
title_prefix: string;
title_max_length: string;
upload_locations: string[];
}
export const Restreamer: Component = () => {
const [pageErrors, setPageErrors] = createSignal<string[]>([]);
const [defaultsData] = createResource<DefaultsData | null>(
async (source, { value, refetching }) => {
const response = await fetch("/thrimshim/defaults");
if (!response.ok) {
return null;
}
return await response.json();
},
);
const busStartTime = () => {
const defaults = defaultsData();
if (defaults && defaults.hasOwnProperty("bustime_start")) {
return dateTimeFromWubloaderTime(defaults.bustime_start);
}
return null;
};
const now = DateTime.utc();
return (
<>
@ -20,8 +49,37 @@ const Restreamer: Component = () => {
<div class={styles.keyboardShortcutHelp}>
<KeyboardShortcuts includeEditorShortcuts={false} />
</div>
<Suspense>
<Show when={defaultsData()}>
<RestreamerWithDefaults defaults={defaultsData()} />
</Show>
</Suspense>
</>
);
};
export default Restreamer;
interface RestreamerDefaultProps {
defaults: DefaultsData;
}
const RestreamerWithDefaults: Component<RestreamerDefaultProps> = (props) => {
const [busStartTime, setBusStartTime] = createSignal<DateTime>(
dateTimeFromWubloaderTime(props.defaults.bustime_start),
);
const [streamVideoInfo, setStreamVideoInfo] = createSignal<StreamVideoInfo>({
streamName: props.defaults.video_channel,
streamStartTime: DateTime.utc().minus({ minutes: 10 }),
streamEndTime: null,
});
return (
<>
<StreamTimeSettings
busStartTime={busStartTime}
streamVideoInfo={streamVideoInfo}
setStreamVideoInfo={setStreamVideoInfo}
showTimeRangeLink={false}
/>
</>
);
};

@ -7,18 +7,13 @@ import {
wubloaderTimeFromDateTime,
busTimeFromDateTime,
timeAgoFromDateTime,
TimeType,
} from "../common/convertTime";
interface TimeConverterProps {
busStartTime: Accessor<DateTime | null>;
}
enum TimeType {
UTC,
BusTime,
TimeAgo,
}
const TimeConverter: Component<TimeConverterProps> = (props) => {
const [enteredTime, setEnteredTime] = createSignal<string>("");
const [startTimeType, setStartTimeType] = createSignal<TimeType>(TimeType.UTC);

Loading…
Cancel
Save