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 ;
+ const display = usePlayerStore((s) => s.display);
+ const { isPaused } = usePlayerStore((s) => s.mediaPlaying);
+
+ const toggle = () => {
+ if (isPaused) display?.play();
+ else display?.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 ;
}
export function VideoContainer() {
- const videoEl = useRef(null);
- const player = usePlayerStore();
- useRestoreVideo(videoEl, player);
- const show = useShouldShow(player.source);
+ const show = useShouldShowVideoElement();
+ useDisplayInterface();
if (!show) return null;
- return ;
+ return ;
}
diff --git a/src/pages/PlayerView.tsx b/src/pages/PlayerView.tsx
index d81b7f0a..b31efc1d 100644
--- a/src/pages/PlayerView.tsx
+++ b/src/pages/PlayerView.tsx
@@ -1,9 +1,30 @@
+import { MWStreamType } from "@/backend/helpers/streams";
import { Player } from "@/components/player";
+import { usePlayer } from "@/components/player/hooks/usePlayer";
+import { playerStatus } from "@/stores/player/slices/source";
export function PlayerView() {
+ const { status, playMedia } = usePlayer();
+
+ function scrape() {
+ playMedia({
+ type: MWStreamType.MP4,
+ url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
+ });
+ }
+
return (
+
+ {status === playerStatus.IDLE ? (
+