17 changed files with 255 additions and 2 deletions
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
import { ReactNode } from "react"; |
||||
export * as Atoms from "./atoms/index"; |
||||
export |
@ -1,9 +1,16 @@
@@ -1,9 +1,16 @@
|
||||
import { ReactNode } from "react"; |
||||
|
||||
import { VideoContainer } from "@/components/player/internals/VideoContainer"; |
||||
|
||||
export interface PlayerProps { |
||||
children?: ReactNode; |
||||
} |
||||
|
||||
export function Container(props: PlayerProps) { |
||||
return <div>{props.children}</div>; |
||||
return ( |
||||
<div> |
||||
<VideoContainer /> |
||||
{props.children} |
||||
</div> |
||||
); |
||||
} |
||||
|
@ -0,0 +1,20 @@
@@ -0,0 +1,20 @@
|
||||
import { MWStreamType } from "@/backend/helpers/streams"; |
||||
import { playerStatus } from "@/stores/player/slices/source"; |
||||
import { usePlayerStore } from "@/stores/player/store"; |
||||
|
||||
export interface Source { |
||||
url: string; |
||||
type: MWStreamType; |
||||
} |
||||
|
||||
export function usePlayer() { |
||||
const setStatus = usePlayerStore((s) => s.setStatus); |
||||
const setSource = usePlayerStore((s) => s.setSource); |
||||
|
||||
return { |
||||
playMedia(source: Source) { |
||||
setSource(source.url, source.type); |
||||
setStatus(playerStatus.PLAYING); |
||||
}, |
||||
}; |
||||
} |
@ -1,3 +1,14 @@
@@ -1,3 +1,14 @@
|
||||
import { useEffect, useRef } from "react"; |
||||
|
||||
import { usePlayerStore } from "@/stores/player/store"; |
||||
|
||||
export function VideoContainer() { |
||||
return <div />; |
||||
const player = usePlayerStore(); |
||||
const videoEl = useRef<HTMLVideoElement>(null); |
||||
|
||||
useEffect(() => { |
||||
if (videoEl.current) videoEl.current.src = player.source?.url ?? ""; |
||||
}, [player.source?.url]); |
||||
|
||||
return <video controls ref={videoEl} />; |
||||
} |
||||
|
@ -0,0 +1,28 @@
@@ -0,0 +1,28 @@
|
||||
import { MakeSlice } from "@/stores/player/slices/types"; |
||||
|
||||
export enum VideoPlayerTimeFormat { |
||||
REGULAR = 0, |
||||
REMAINING = 1, |
||||
} |
||||
|
||||
export interface InterfaceSlice { |
||||
interface: { |
||||
isFullscreen: boolean; |
||||
|
||||
volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently?
|
||||
volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig"
|
||||
|
||||
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
||||
timeFormat: VideoPlayerTimeFormat; // Time format of the video player
|
||||
}; |
||||
} |
||||
|
||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = () => ({ |
||||
interface: { |
||||
isFullscreen: false, |
||||
leftControlHovering: false, |
||||
volumeChangedWithKeybind: false, |
||||
volumeChangedWithKeybindDebounce: null, |
||||
timeFormat: VideoPlayerTimeFormat.REGULAR, |
||||
}, |
||||
}); |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
import { MakeSlice } from "@/stores/player/slices/types"; |
||||
|
||||
export interface PlayingSlice { |
||||
mediaPlaying: { |
||||
isPlaying: boolean; |
||||
isPaused: boolean; |
||||
isSeeking: boolean; // seeking with progress bar
|
||||
isDragSeeking: boolean; // is seeking for our custom progress bar
|
||||
isLoading: boolean; // buffering or not
|
||||
isFirstLoading: boolean; // first buffering of the video, when set to false the video can start playing
|
||||
hasPlayedOnce: boolean; // has the video played at all?
|
||||
volume: number; |
||||
playbackSpeed: number; |
||||
}; |
||||
play(): void; |
||||
pause(): void; |
||||
} |
||||
|
||||
export const createPlayingSlice: MakeSlice<PlayingSlice> = (set) => ({ |
||||
mediaPlaying: { |
||||
isPlaying: false, |
||||
isPaused: true, |
||||
isLoading: false, |
||||
isSeeking: false, |
||||
isDragSeeking: false, |
||||
isFirstLoading: true, |
||||
hasPlayedOnce: false, |
||||
volume: 0, |
||||
playbackSpeed: 1, |
||||
}, |
||||
play() { |
||||
set((state) => { |
||||
state.mediaPlaying.isPlaying = true; |
||||
state.mediaPlaying.isPaused = false; |
||||
}); |
||||
}, |
||||
pause() { |
||||
set((state) => { |
||||
state.mediaPlaying.isPlaying = false; |
||||
state.mediaPlaying.isPaused = false; |
||||
}); |
||||
}, |
||||
}); |
@ -0,0 +1,19 @@
@@ -0,0 +1,19 @@
|
||||
import { MakeSlice } from "@/stores/player/slices/types"; |
||||
|
||||
export interface ProgressSlice { |
||||
progress: { |
||||
time: number; // current time of video
|
||||
duration: number; // length of video
|
||||
buffered: number; // how much is buffered
|
||||
draggingTime: number; // when dragging, time thats at the cursor
|
||||
}; |
||||
} |
||||
|
||||
export const createProgressSlice: MakeSlice<ProgressSlice> = () => ({ |
||||
progress: { |
||||
time: 0, |
||||
duration: 0, |
||||
buffered: 0, |
||||
draggingTime: 0, |
||||
}, |
||||
}); |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
import { MWStreamType } from "@/backend/helpers/streams"; |
||||
import { MakeSlice } from "@/stores/player/slices/types"; |
||||
import { ValuesOf } from "@/utils/typeguard"; |
||||
|
||||
export const playerStatus = { |
||||
IDLE: "idle", |
||||
SCRAPING: "scraping", |
||||
PLAYING: "playing", |
||||
} as const; |
||||
|
||||
export type PlayerStatus = ValuesOf<typeof playerStatus>; |
||||
|
||||
export interface SourceSlice { |
||||
status: PlayerStatus; |
||||
source: { |
||||
url: string; |
||||
type: MWStreamType; |
||||
} | null; |
||||
setStatus(status: PlayerStatus): void; |
||||
setSource(url: string, type: MWStreamType): void; |
||||
} |
||||
|
||||
export const createSourceSlice: MakeSlice<SourceSlice> = (set) => ({ |
||||
source: null, |
||||
status: playerStatus.IDLE, |
||||
setStatus(status: PlayerStatus) { |
||||
set((s) => { |
||||
s.status = status; |
||||
}); |
||||
}, |
||||
setSource(url: string, type: MWStreamType) { |
||||
set((s) => { |
||||
s.source = { |
||||
type, |
||||
url, |
||||
}; |
||||
}); |
||||
}, |
||||
}); |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
import { StateCreator } from "zustand"; |
||||
|
||||
import { InterfaceSlice } from "@/stores/player/slices/interface"; |
||||
import { PlayingSlice } from "@/stores/player/slices/playing"; |
||||
import { ProgressSlice } from "@/stores/player/slices/progress"; |
||||
import { SourceSlice } from "@/stores/player/slices/source"; |
||||
|
||||
export type AllSlices = InterfaceSlice & |
||||
PlayingSlice & |
||||
ProgressSlice & |
||||
SourceSlice; |
||||
export type MakeSlice<Slice> = StateCreator< |
||||
AllSlices, |
||||
[["zustand/immer", never]], |
||||
[], |
||||
Slice |
||||
>; |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
import { create } from "zustand"; |
||||
import { immer } from "zustand/middleware/immer"; |
||||
|
||||
import { createInterfaceSlice } from "@/stores/player/slices/interface"; |
||||
import { createPlayingSlice } from "@/stores/player/slices/playing"; |
||||
import { createProgressSlice } from "@/stores/player/slices/progress"; |
||||
import { createSourceSlice } from "@/stores/player/slices/source"; |
||||
import { AllSlices } from "@/stores/player/slices/types"; |
||||
|
||||
export const usePlayerStore = create( |
||||
immer<AllSlices>((...a) => ({ |
||||
...createInterfaceSlice(...a), |
||||
...createProgressSlice(...a), |
||||
...createPlayingSlice(...a), |
||||
...createSourceSlice(...a), |
||||
})) |
||||
); |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { MWCaption } from "@/backend/helpers/streams"; |
||||
import { DetailedMeta } from "@/backend/metadata/getmeta"; |
||||
|
||||
export interface Thumbnail { |
||||
from: number; |
||||
to: number; |
||||
imgUrl: string; |
||||
} |
||||
export type VideoPlayerMeta = { |
||||
meta: DetailedMeta; |
||||
captions: MWCaption[]; |
||||
episode?: { |
||||
episodeId: string; |
||||
seasonId: string; |
||||
}; |
||||
seasons?: { |
||||
id: string; |
||||
number: number; |
||||
title: string; |
||||
episodes?: { id: string; number: number; title: string }[]; |
||||
}[]; |
||||
}; |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
import { create } from "zustand"; |
||||
|
||||
export const useVideo = create(() => ({ |
||||
|
||||
})); |
@ -1,3 +1,5 @@
@@ -1,3 +1,5 @@
|
||||
export function isNotNull<T>(obj: T | null): obj is T { |
||||
return obj != null; |
||||
} |
||||
|
||||
export type ValuesOf<T> = T[keyof T]; |
||||
|
@ -1,5 +1,18 @@
@@ -1,5 +1,18 @@
|
||||
import { useEffect } from "react"; |
||||
|
||||
import { MWStreamType } from "@/backend/helpers/streams"; |
||||
import { usePlayer } from "@/components/player/hooks/usePlayer"; |
||||
import { PlayerView } from "@/views/PlayerView"; |
||||
|
||||
export default function VideoTesterView() { |
||||
const player = usePlayer(); |
||||
|
||||
useEffect(() => { |
||||
player.playMedia({ |
||||
type: MWStreamType.MP4, |
||||
url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", |
||||
}); |
||||
}); |
||||
|
||||
return <PlayerView />; |
||||
} |
||||
|
Loading…
Reference in new issue