10 changed files with 87 additions and 21 deletions
@ -1,2 +0,0 @@ |
|||||||
export * from "./atoms"; |
|
||||||
export * from "./base/Container"; |
|
@ -1,3 +1,2 @@ |
|||||||
import { ReactNode } from "react"; |
export * from "./atoms"; |
||||||
export * as Atoms from "./atoms/index"; |
export * from "./base/Container"; |
||||||
export |
|
||||||
|
@ -0,0 +1,16 @@ |
|||||||
|
# Video player component |
||||||
|
|
||||||
|
Video player is quite a complex component, so here is a rundown of all the parts |
||||||
|
|
||||||
|
# Composable parts |
||||||
|
These parts can be used to build any shape of a video player. |
||||||
|
- `/atoms`- any ui element that controls the player. (Seekbar, Pause button, quality selection, etc) |
||||||
|
- `/base` - base components that are used to build a player. Like the main container |
||||||
|
|
||||||
|
# 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 |
@ -0,0 +1,22 @@ |
|||||||
|
import { Source } from "@/components/player/hooks/usePlayer"; |
||||||
|
|
||||||
|
type EventMap = Record<string, any>; |
||||||
|
type EventKey<T extends EventMap> = string & keyof T; |
||||||
|
type EventReceiver<T> = (params: T) => void; |
||||||
|
|
||||||
|
export interface Emitter<T extends EventMap> { |
||||||
|
on<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void; |
||||||
|
off<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void; |
||||||
|
emit<K extends EventKey<T>>(eventName: K, params: T[K]): void; |
||||||
|
} |
||||||
|
|
||||||
|
interface Listener<T extends EventMap> { |
||||||
|
on<K extends EventKey<T>>(eventName: K, fn: EventReceiver<T[K]>): void; |
||||||
|
} |
||||||
|
|
||||||
|
export interface DisplayInterface<Events extends EventMap> |
||||||
|
extends Listener<Events> { |
||||||
|
play(): void; |
||||||
|
pause(): void; |
||||||
|
load(source: Source): void; |
||||||
|
} |
@ -1,14 +1,36 @@ |
|||||||
import { useEffect, useRef } from "react"; |
import { RefObject, 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 { usePlayerStore } from "@/stores/player/store"; |
import { usePlayerStore } from "@/stores/player/store"; |
||||||
|
|
||||||
export function VideoContainer() { |
// should this video container show right now?
|
||||||
const player = usePlayerStore(); |
function useShouldShow(source: SourceSliceSource | null): boolean { |
||||||
const videoEl = useRef<HTMLVideoElement>(null); |
if (!source) return false; |
||||||
|
if (source.type !== MWStreamType.MP4) return false; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// make video element up to par with the state
|
||||||
|
function useRestoreVideo( |
||||||
|
videoRef: RefObject<HTMLVideoElement>, |
||||||
|
player: AllSlices |
||||||
|
) { |
||||||
useEffect(() => { |
useEffect(() => { |
||||||
if (videoEl.current) videoEl.current.src = player.source?.url ?? ""; |
const el = videoRef.current; |
||||||
}, [player.source?.url]); |
const src = player.source?.url ?? ""; |
||||||
|
if (!el) return; |
||||||
|
if (el.src !== src) el.src = src; |
||||||
|
}, [player.source?.url, videoRef]); |
||||||
|
} |
||||||
|
|
||||||
|
export function VideoContainer() { |
||||||
|
const videoEl = useRef<HTMLVideoElement>(null); |
||||||
|
const player = usePlayerStore(); |
||||||
|
useRestoreVideo(videoEl, player); |
||||||
|
const show = useShouldShow(player.source); |
||||||
|
|
||||||
return <video controls ref={videoEl} />; |
if (!show) return null; |
||||||
|
return <video autoPlay ref={videoEl} />; |
||||||
} |
} |
||||||
|
@ -0,0 +1,5 @@ |
|||||||
|
import { Controller } from "@/stores/player/controllers/types"; |
||||||
|
|
||||||
|
function useBaseController(el: HTMLVideoElement): Controller { |
||||||
|
return {}; |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
export interface Controller { |
||||||
|
pause(): void; |
||||||
|
play(): void; |
||||||
|
setVolume(target: number): void; |
||||||
|
registerVideoElement(): void; |
||||||
|
} |
Loading…
Reference in new issue