diff --git a/src/components/player/README.md b/src/components/player/README.md index d85289fd..404e138a 100644 --- a/src/components/player/README.md +++ b/src/components/player/README.md @@ -9,8 +9,23 @@ These parts can be used to build any shape of a video player. # internal parts These parts are internally used, they aren't exported. Do not use them outside of player internals. -- `/display` - display interface, abstraction on how to actually play the content (e.g Video element, HLS player, Standard video element, etc) -- `/internals` - Internal components that are always rendered on every player. -- `/utils` - miscellaneous logic -- `/hooks` - hooks only used for video player -- `~/src/stores/player` - state for the video player. Should only be used by internal parts + +### `/display` +The display interface, abstraction on how to actually play the content (e.g Video element, chrome casting, etc) + - It must be completely seperate from any react code + - It must not interact with state, pass async data back with events + +### `/internals` +Internal components that are always rendered on every player. + - Only components that are always present on the player instance, they must never unmount + +### `/utils` +miscellaneous logic, put anything that is unique to the video player internals. + +### `/hooks` +Hooks only used for video player. + - only exception is usePlayer, as its used outside of the player to control the player + +### `~/src/stores/player` +State for the video player. + - Only parts related to the video player may utilize the state diff --git a/src/components/player/atoms/pause.tsx b/src/components/player/atoms/pause.tsx index f04e9232..752fdcc4 100644 --- a/src/components/player/atoms/pause.tsx +++ b/src/components/player/atoms/pause.tsx @@ -1,3 +1,17 @@ +import { usePlayerStore } from "@/stores/player/store"; + export function Pause() { - return + ); } diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index cb0ff5c3..63737b76 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -1 +1,45 @@ -export {}; +import { + DisplayInterface, + DisplayInterfaceEvents, +} from "@/components/player/display/displayInterface"; +import { Source } from "@/components/player/hooks/usePlayer"; +import { makeEmitter } from "@/utils/events"; + +export function makeVideoElementDisplayInterface(): DisplayInterface { + const { emit, on, off } = makeEmitter(); + let source: Source | null = null; + let videoElement: HTMLVideoElement | null = null; + + function setSource() { + if (!videoElement || !source) return; + videoElement.src = source.url; + videoElement.addEventListener("play", () => emit("play", undefined)); + videoElement.addEventListener("pause", () => emit("pause", undefined)); + } + + return { + on, + off, + + // no need to destroy anything + destroy: () => {}, + + load(newSource) { + source = newSource; + setSource(); + }, + + processVideoElement(video) { + videoElement = video; + setSource(); + }, + + pause() { + videoElement?.pause(); + }, + + play() { + videoElement?.play(); + }, + }; +} diff --git a/src/components/player/display/displayInterface.ts b/src/components/player/display/displayInterface.ts index 0ce4cfdf..4dff00d9 100644 --- a/src/components/player/display/displayInterface.ts +++ b/src/components/player/display/displayInterface.ts @@ -10,4 +10,6 @@ export interface DisplayInterface extends Listener { play(): void; pause(): void; load(source: Source): void; + processVideoElement(video: HTMLVideoElement): void; + destroy(): void; } diff --git a/src/components/player/hooks/usePlayer.ts b/src/components/player/hooks/usePlayer.ts index 0db61398..9b5301fa 100644 --- a/src/components/player/hooks/usePlayer.ts +++ b/src/components/player/hooks/usePlayer.ts @@ -9,11 +9,13 @@ export interface Source { export function usePlayer() { const setStatus = usePlayerStore((s) => s.setStatus); - const setSource = usePlayerStore((s) => s.setSource); + const status = usePlayerStore((s) => s.status); + const display = usePlayerStore((s) => s.display); return { + status, playMedia(source: Source) { - setSource(source.url, source.type); + display?.load(source); setStatus(playerStatus.PLAYING); }, }; diff --git a/src/components/player/internals/VideoContainer.tsx b/src/components/player/internals/VideoContainer.tsx index 39076e67..59679181 100644 --- a/src/components/player/internals/VideoContainer.tsx +++ b/src/components/player/internals/VideoContainer.tsx @@ -1,36 +1,46 @@ -import { RefObject, useEffect, useRef } from "react"; +import { useEffect, useRef } from "react"; -import { MWStreamType } from "@/backend/helpers/streams"; -import { SourceSliceSource } from "@/stores/player/slices/source"; -import { AllSlices } from "@/stores/player/slices/types"; +import { makeVideoElementDisplayInterface } from "@/components/player/display/base"; +import { playerStatus } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; -// should this video container show right now? -function useShouldShow(source: SourceSliceSource | null): boolean { - if (!source) return false; - if (source.type !== MWStreamType.MP4) return false; +// initialize display interface +function useDisplayInterface() { + const display = usePlayerStore((s) => s.display); + const setDisplay = usePlayerStore((s) => s.setDisplay); + + useEffect(() => { + if (!display) { + setDisplay(makeVideoElementDisplayInterface()); + } + }, [display, setDisplay]); +} + +function useShouldShowVideoElement() { + const status = usePlayerStore((s) => s.status); + + if (status !== playerStatus.PLAYING) return false; return true; } -// make video element up to par with the state -function useRestoreVideo( - videoRef: RefObject, - player: AllSlices -) { +function VideoElement() { + const videoEl = useRef(null); + const display = usePlayerStore((s) => s.display); + + // report video element to display interface useEffect(() => { - const el = videoRef.current; - const src = player.source?.url ?? ""; - if (!el) return; - if (el.src !== src) el.src = src; - }, [player.source?.url, videoRef]); + if (display && videoEl.current) { + display.processVideoElement(videoEl.current); + } + }, [display, videoEl]); + + return