mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-26 18:41:51 +01:00
basics of new video player state
This commit is contained in:
parent
ba25c18390
commit
4bc8106cb3
@ -52,6 +52,7 @@ module.exports = {
|
|||||||
"no-await-in-loop": "off",
|
"no-await-in-loop": "off",
|
||||||
"no-nested-ternary": "off",
|
"no-nested-ternary": "off",
|
||||||
"prefer-destructuring": "off",
|
"prefer-destructuring": "off",
|
||||||
|
"no-param-reassign": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
||||||
"react/jsx-filename-extension": [
|
"react/jsx-filename-extension": [
|
||||||
"error",
|
"error",
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
"hls.js": "^1.0.7",
|
"hls.js": "^1.0.7",
|
||||||
"i18next": "^22.4.5",
|
"i18next": "^22.4.5",
|
||||||
"i18next-browser-languagedetector": "^7.0.1",
|
"i18next-browser-languagedetector": "^7.0.1",
|
||||||
|
"immer": "^10.0.2",
|
||||||
"json5": "^2.2.0",
|
"json5": "^2.2.0",
|
||||||
"lodash.throttle": "^4.1.1",
|
"lodash.throttle": "^4.1.1",
|
||||||
"nanoid": "^4.0.0",
|
"nanoid": "^4.0.0",
|
||||||
|
3
src/components/player/Player.tsx
Normal file
3
src/components/player/Player.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { ReactNode } from "react";
|
||||||
|
export * as Atoms from "./atoms/index";
|
||||||
|
export
|
@ -1,9 +1,16 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
import { VideoContainer } from "@/components/player/internals/VideoContainer";
|
||||||
|
|
||||||
export interface PlayerProps {
|
export interface PlayerProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Container(props: PlayerProps) {
|
export function Container(props: PlayerProps) {
|
||||||
return <div>{props.children}</div>;
|
return (
|
||||||
|
<div>
|
||||||
|
<VideoContainer />
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
20
src/components/player/hooks/usePlayer.ts
Normal file
20
src/components/player/hooks/usePlayer.ts
Normal file
@ -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 @@
|
|||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
export function VideoContainer() {
|
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} />;
|
||||||
}
|
}
|
||||||
|
28
src/stores/player/slices/interface.ts
Normal file
28
src/stores/player/slices/interface.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
});
|
43
src/stores/player/slices/playing.ts
Normal file
43
src/stores/player/slices/playing.ts
Normal file
@ -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;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
19
src/stores/player/slices/progress.ts
Normal file
19
src/stores/player/slices/progress.ts
Normal file
@ -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,
|
||||||
|
},
|
||||||
|
});
|
39
src/stores/player/slices/source.ts
Normal file
39
src/stores/player/slices/source.ts
Normal file
@ -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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
17
src/stores/player/slices/types.ts
Normal file
17
src/stores/player/slices/types.ts
Normal file
@ -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
|
||||||
|
>;
|
17
src/stores/player/store.ts
Normal file
17
src/stores/player/store.ts
Normal file
@ -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),
|
||||||
|
}))
|
||||||
|
);
|
22
src/stores/player/types.ts
Normal file
22
src/stores/player/types.ts
Normal file
@ -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 }[];
|
||||||
|
}[];
|
||||||
|
};
|
5
src/stores/video.ts
Normal file
5
src/stores/video.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
|
||||||
|
export const useVideo = create(() => ({
|
||||||
|
|
||||||
|
}));
|
@ -1,3 +1,5 @@
|
|||||||
export function isNotNull<T>(obj: T | null): obj is T {
|
export function isNotNull<T>(obj: T | null): obj is T {
|
||||||
return obj != null;
|
return obj != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ValuesOf<T> = T[keyof T];
|
||||||
|
@ -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";
|
import { PlayerView } from "@/views/PlayerView";
|
||||||
|
|
||||||
export default function VideoTesterView() {
|
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 />;
|
return <PlayerView />;
|
||||||
}
|
}
|
||||||
|
@ -3269,6 +3269,11 @@ immediate@~3.0.5:
|
|||||||
resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz"
|
resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz"
|
||||||
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
|
||||||
|
|
||||||
|
immer@^10.0.2, immer@>=9.0:
|
||||||
|
version "10.0.2"
|
||||||
|
resolved "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz"
|
||||||
|
integrity sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==
|
||||||
|
|
||||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user