mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
basics of new video player state
This commit is contained in:
parent
ba25c18390
commit
4bc8106cb3
17 changed files with 255 additions and 2 deletions
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
"no-await-in-loop": "off",
|
||||
"no-nested-ternary": "off",
|
||||
"prefer-destructuring": "off",
|
||||
"no-param-reassign": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
|
||||
"react/jsx-filename-extension": [
|
||||
"error",
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"hls.js": "^1.0.7",
|
||||
"i18next": "^22.4.5",
|
||||
"i18next-browser-languagedetector": "^7.0.1",
|
||||
"immer": "^10.0.2",
|
||||
"json5": "^2.2.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"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 { 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>
|
||||
);
|
||||
}
|
||||
|
|
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() {
|
||||
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 {
|
||||
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";
|
||||
|
||||
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 />;
|
||||
}
|
||||
|
|
|
@ -3269,6 +3269,11 @@ immediate@~3.0.5:
|
|||
resolved "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz"
|
||||
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:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz"
|
||||
|
|
Loading…
Reference in a new issue