Add vidstack video player

thrimbletrimmer-solid
ElementalAlchemist 2 weeks ago
parent 5ee213455d
commit d0640bf5c8

@ -22,6 +22,7 @@
"dependencies": { "dependencies": {
"hls.js": "1.5.17", "hls.js": "1.5.17",
"luxon": "3.4.4", "luxon": "3.4.4",
"solid-js": "1.9.3" "solid-js": "1.9.3",
"vidstack": "1.12.12"
} }
} }

@ -1,16 +1,16 @@
import { Accessor, Component, createEffect, createSignal, Setter, Show } from "solid-js"; import { Accessor, Component, createEffect, createSignal, Setter, Show } from "solid-js";
import Hls from "hls.js";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { import {
TimeType, TimeType,
wubloaderTimeFromDateTime, wubloaderTimeFromDateTime,
busTimeFromDateTime, busTimeFromDateTime,
timeAgoFromDateTime, timeAgoFromDateTime,
dateTimeFromWubloaderTime,
dateTimeFromBusTime,
dateTimeFromTimeAgo,
} from "./convertTime"; } from "./convertTime";
import styles from "./video.module.scss"; import styles from "./video.module.scss";
Hls.DefaultConfig.maxBufferHole = 600;
export const VIDEO_FRAMES_PER_SECOND = 30; 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 const PLAYBACK_RATES = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 4, 8];
@ -26,16 +26,58 @@ export interface StreamTimeSettingsProps {
streamVideoInfo: Accessor<StreamVideoInfo>; streamVideoInfo: Accessor<StreamVideoInfo>;
setStreamVideoInfo: Setter<StreamVideoInfo>; setStreamVideoInfo: Setter<StreamVideoInfo>;
showTimeRangeLink: boolean; showTimeRangeLink: boolean;
errorList: Accessor<string[]>;
setErrorList: Setter<string[]>;
} }
export const StreamTimeSettings: Component<StreamTimeSettingsProps> = (props) => { export const StreamTimeSettings: Component<StreamTimeSettingsProps> = (props) => {
const [timeType, setTimeType] = createSignal<TimeType>(TimeType.UTC); const [timeType, setTimeType] = createSignal<TimeType>(TimeType.UTC);
const submitHandler = (event: SubmitEvent) => { const submitHandler = (event: SubmitEvent) => {
event.preventDefault();
const form = event.currentTarget as HTMLFormElement; const form = event.currentTarget as HTMLFormElement;
const formData = new FormData(form); const formData = new FormData(form);
// TODO const streamName = formData.get("stream") as string;
const startTimeEntered = formData.get("start-time") as string;
const endTimeEntered = formData.get("end-time") as string;
const timeType = +formData.get("time-type") as TimeType;
let startTime: DateTime | null = null;
let endTime: DateTime | null = null;
switch (timeType) {
case TimeType.UTC:
startTime = dateTimeFromWubloaderTime(startTimeEntered);
if (endTimeEntered !== "") {
endTime = dateTimeFromWubloaderTime(endTimeEntered);
}
break;
case TimeType.BusTime:
startTime = dateTimeFromBusTime(props.busStartTime(), startTimeEntered);
if (endTimeEntered !== "") {
endTime = dateTimeFromBusTime(props.busStartTime(), endTimeEntered);
}
break;
case TimeType.TimeAgo:
startTime = dateTimeFromTimeAgo(startTimeEntered);
if (endTimeEntered !== "") {
endTime = dateTimeFromTimeAgo(endTimeEntered);
}
break;
}
if (startTime === null || (endTimeEntered !== "" && endTime === null)) {
const error = "A load boundary time could not be parsed. Check the format of your times.";
props.setErrorList([...props.errorList(), error]);
return;
}
props.setStreamVideoInfo({
streamName: streamName,
streamStartTime: startTime,
streamEndTime: endTime,
});
}; };
const startTimeDisplay = () => { const startTimeDisplay = () => {
@ -180,28 +222,3 @@ export const KeyboardShortcuts: Component<KeyboardShortcutProps> = (
</details> </details>
); );
}; };
export interface VideoPlayerProps {
videoURL: string;
errorList: Accessor<string[]>;
setErrorList: Setter<string[]>;
videoPlayer: Accessor<Hls>;
}
export const VideoPlayer: Component<VideoPlayerProps> = (props) => {
if (!Hls.isSupported()) {
const newError =
"Your browser doesn't support MediaSource extensions. Video playback and editing won't work.";
props.setErrorList([...props.errorList(), newError]);
return <></>;
}
let videoElement;
createEffect(() => {
if (videoElement) {
props.videoPlayer().attachMedia(videoElement);
}
});
return <video ref={videoElement} controls={true}></video>;
};

@ -1,8 +1,3 @@
.hidden { .hidden {
display: none; display: none;
} }
video {
max-width: 100%;
max-height: 50vh;
}

@ -8,16 +8,17 @@ import {
Show, Show,
Suspense, Suspense,
} from "solid-js"; } from "solid-js";
import Hls from "hls.js";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import styles from "./Restreamer.module.scss"; import styles from "./Restreamer.module.scss";
import { dateTimeFromWubloaderTime, wubloaderTimeFromDateTime } from "../common/convertTime"; import { dateTimeFromWubloaderTime, wubloaderTimeFromDateTime } from "../common/convertTime";
import { import { KeyboardShortcuts, StreamTimeSettings, StreamVideoInfo } from "../common/video";
KeyboardShortcuts,
StreamTimeSettings, import "vidstack/player/styles/default/theme.css";
StreamVideoInfo, import "vidstack/player/styles/default/layouts/video.css";
VideoPlayer, import "vidstack/player";
} from "../common/video"; import "vidstack/player/layouts/default";
import "vidstack/player/ui";
import { MediaPlayerElement } from "vidstack/elements";
export interface DefaultsData { export interface DefaultsData {
video_channel: string; video_channel: string;
@ -93,8 +94,6 @@ const RestreamerWithDefaults: Component<RestreamerDefaultProps> = (props) => {
streamEndTime: null, streamEndTime: null,
}); });
const [videoPlayer, setVideoPlayer] = createSignal(new Hls());
const videoURL = () => { const videoURL = () => {
const streamInfo = streamVideoInfo(); const streamInfo = streamVideoInfo();
const startTime = wubloaderTimeFromDateTime(streamInfo.streamStartTime); const startTime = wubloaderTimeFromDateTime(streamInfo.streamStartTime);
@ -111,8 +110,6 @@ const RestreamerWithDefaults: Component<RestreamerDefaultProps> = (props) => {
return url; return url;
}; };
videoPlayer().loadSource(videoURL());
return ( return (
<> <>
<StreamTimeSettings <StreamTimeSettings
@ -121,12 +118,10 @@ const RestreamerWithDefaults: Component<RestreamerDefaultProps> = (props) => {
setStreamVideoInfo={setStreamVideoInfo} setStreamVideoInfo={setStreamVideoInfo}
showTimeRangeLink={false} showTimeRangeLink={false}
/> />
<VideoPlayer <media-player src={videoURL()} preload="auto">
videoURL={videoURL()} <media-provider />
errorList={props.errorList} <media-video-layout />
setErrorList={props.setErrorList} </media-player>
videoPlayer={videoPlayer}
/>
</> </>
); );
}; };

@ -7,7 +7,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"jsx": "preserve", "jsx": "preserve",
"jsxImportSource": "solid-js", "jsxImportSource": "solid-js",
"types": ["vite/client"], "types": ["vidstack/solid", "vite/client"],
"noEmit": true, "noEmit": true,
"isolatedModules": true "isolatedModules": true
} }

Loading…
Cancel
Save