mirror of
https://github.com/movie-web/movie-web.git
synced 2024-11-13 08:15:06 +01:00
bottom control layout + fullscreen + hovering
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
a813efe5ba
commit
7e182a4b7a
@ -1,2 +1,3 @@
|
|||||||
export * from "./atoms";
|
export * from "./atoms";
|
||||||
export * from "./base/Container";
|
export * from "./base/Container";
|
||||||
|
export * from "./base/BottomControls";
|
||||||
|
15
src/components/player/atoms/Fullscreen.tsx
Normal file
15
src/components/player/atoms/Fullscreen.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
|
export function Fullscreen() {
|
||||||
|
const { isFullscreen } = usePlayerStore((s) => s.interface);
|
||||||
|
const display = usePlayerStore((s) => s.display);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<VideoPlayerButton
|
||||||
|
onClick={() => display?.toggleFullscreen()}
|
||||||
|
icon={isFullscreen ? Icons.COMPRESS : Icons.EXPAND}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import { Icons } from "@/components/Icon";
|
||||||
|
import { VideoPlayerButton } from "@/components/player/internals/Button";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
export function Pause() {
|
export function Pause() {
|
||||||
@ -10,8 +12,9 @@ export function Pause() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type="button" onClick={toggle}>
|
<VideoPlayerButton
|
||||||
play/pause
|
onClick={toggle}
|
||||||
</button>
|
icon={isPaused ? Icons.PLAY : Icons.PAUSE}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1 +1,2 @@
|
|||||||
export * from "./pause";
|
export * from "./Pause";
|
||||||
|
export * from "./Fullscreen";
|
||||||
|
18
src/components/player/base/BottomControls.tsx
Normal file
18
src/components/player/base/BottomControls.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Transition } from "@/components/Transition";
|
||||||
|
|
||||||
|
export function BottomControls(props: {
|
||||||
|
show: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="w-full absolute bottom-0 flex flex-col pt-32 bg-gradient-to-t from-black to-transparent [margin-bottom:env(safe-area-inset-bottom)]">
|
||||||
|
<Transition
|
||||||
|
animation="slide-up"
|
||||||
|
show={props.show}
|
||||||
|
className="pointer-events-auto px-4 pb-2 flex justify-end"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,16 +1,90 @@
|
|||||||
import { ReactNode } from "react";
|
import { ReactNode, RefObject, useEffect, useRef } from "react";
|
||||||
|
|
||||||
import { VideoContainer } from "@/components/player/internals/VideoContainer";
|
import { VideoContainer } from "@/components/player/internals/VideoContainer";
|
||||||
|
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
export interface PlayerProps {
|
export interface PlayerProps {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
|
onLoad?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Container(props: PlayerProps) {
|
function useHovering(containerEl: RefObject<HTMLDivElement>) {
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
const updateInterfaceHovering = usePlayerStore(
|
||||||
|
(s) => s.updateInterfaceHovering
|
||||||
|
);
|
||||||
|
const hovering = usePlayerStore((s) => s.interface.hovering);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!containerEl.current) return;
|
||||||
|
const el = containerEl.current;
|
||||||
|
|
||||||
|
function pointerMove(e: PointerEvent) {
|
||||||
|
if (e.pointerType !== "mouse") return;
|
||||||
|
updateInterfaceHovering(PlayerHoverState.MOUSE_HOVER);
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
timeoutRef.current = setTimeout(() => {
|
||||||
|
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||||
|
timeoutRef.current = null;
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerLeave(e: PointerEvent) {
|
||||||
|
if (e.pointerType !== "mouse") return;
|
||||||
|
updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pointerUp(e: PointerEvent) {
|
||||||
|
if (e.pointerType === "mouse") return;
|
||||||
|
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||||
|
if (hovering !== PlayerHoverState.MOBILE_TAPPED)
|
||||||
|
updateInterfaceHovering(PlayerHoverState.MOBILE_TAPPED);
|
||||||
|
else updateInterfaceHovering(PlayerHoverState.NOT_HOVERING);
|
||||||
|
}
|
||||||
|
|
||||||
|
el.addEventListener("pointermove", pointerMove);
|
||||||
|
el.addEventListener("pointerleave", pointerLeave);
|
||||||
|
el.addEventListener("pointerup", pointerUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
el.removeEventListener("pointermove", pointerMove);
|
||||||
|
el.removeEventListener("pointerleave", pointerLeave);
|
||||||
|
el.removeEventListener("pointerup", pointerUp);
|
||||||
|
};
|
||||||
|
}, [containerEl, hovering, updateInterfaceHovering]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function BaseContainer(props: { children?: ReactNode }) {
|
||||||
|
const containerEl = useRef<HTMLDivElement | null>(null);
|
||||||
|
const display = usePlayerStore((s) => s.display);
|
||||||
|
useHovering(containerEl);
|
||||||
|
|
||||||
|
// report container element to display interface
|
||||||
|
useEffect(() => {
|
||||||
|
if (display && containerEl.current) {
|
||||||
|
display.processContainerElement(containerEl.current);
|
||||||
|
}
|
||||||
|
}, [display, containerEl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="relative overflow-hidden h-screen" ref={containerEl}>
|
||||||
<VideoContainer />
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Container(props: PlayerProps) {
|
||||||
|
const propRef = useRef(props.onLoad);
|
||||||
|
useEffect(() => {
|
||||||
|
propRef.current?.();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BaseContainer>
|
||||||
|
<VideoContainer />
|
||||||
|
{props.children}
|
||||||
|
</BaseContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -1,14 +1,23 @@
|
|||||||
|
import fscreen from "fscreen";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DisplayInterface,
|
DisplayInterface,
|
||||||
DisplayInterfaceEvents,
|
DisplayInterfaceEvents,
|
||||||
} from "@/components/player/display/displayInterface";
|
} from "@/components/player/display/displayInterface";
|
||||||
import { Source } from "@/components/player/hooks/usePlayer";
|
import { Source } from "@/components/player/hooks/usePlayer";
|
||||||
|
import {
|
||||||
|
canFullscreen,
|
||||||
|
canFullscreenAnyElement,
|
||||||
|
canWebkitFullscreen,
|
||||||
|
} from "@/utils/detectFeatures";
|
||||||
import { makeEmitter } from "@/utils/events";
|
import { makeEmitter } from "@/utils/events";
|
||||||
|
|
||||||
export function makeVideoElementDisplayInterface(): DisplayInterface {
|
export function makeVideoElementDisplayInterface(): DisplayInterface {
|
||||||
const { emit, on, off } = makeEmitter<DisplayInterfaceEvents>();
|
const { emit, on, off } = makeEmitter<DisplayInterfaceEvents>();
|
||||||
let source: Source | null = null;
|
let source: Source | null = null;
|
||||||
let videoElement: HTMLVideoElement | null = null;
|
let videoElement: HTMLVideoElement | null = null;
|
||||||
|
let containerElement: HTMLElement | null = null;
|
||||||
|
let isFullscreen = false;
|
||||||
|
|
||||||
function setSource() {
|
function setSource() {
|
||||||
if (!videoElement || !source) return;
|
if (!videoElement || !source) return;
|
||||||
@ -17,13 +26,19 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
|||||||
videoElement.addEventListener("pause", () => emit("pause", undefined));
|
videoElement.addEventListener("pause", () => emit("pause", undefined));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fullscreenChange() {
|
||||||
|
isFullscreen =
|
||||||
|
!!document.fullscreenElement || // other browsers
|
||||||
|
!!(document as any).webkitFullscreenElement; // safari
|
||||||
|
}
|
||||||
|
fscreen.addEventListener("fullscreenchange", fullscreenChange);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
on,
|
on,
|
||||||
off,
|
off,
|
||||||
|
destroy: () => {
|
||||||
// no need to destroy anything
|
fscreen.removeEventListener("fullscreenchange", fullscreenChange);
|
||||||
destroy: () => {},
|
},
|
||||||
|
|
||||||
load(newSource) {
|
load(newSource) {
|
||||||
source = newSource;
|
source = newSource;
|
||||||
setSource();
|
setSource();
|
||||||
@ -33,13 +48,36 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
|
|||||||
videoElement = video;
|
videoElement = video;
|
||||||
setSource();
|
setSource();
|
||||||
},
|
},
|
||||||
|
processContainerElement(container) {
|
||||||
|
containerElement = container;
|
||||||
|
},
|
||||||
|
|
||||||
pause() {
|
pause() {
|
||||||
videoElement?.pause();
|
videoElement?.pause();
|
||||||
},
|
},
|
||||||
|
|
||||||
play() {
|
play() {
|
||||||
videoElement?.play();
|
videoElement?.play();
|
||||||
},
|
},
|
||||||
|
toggleFullscreen() {
|
||||||
|
if (isFullscreen) {
|
||||||
|
isFullscreen = false;
|
||||||
|
emit("fullscreen", isFullscreen);
|
||||||
|
if (!fscreen.fullscreenElement) return;
|
||||||
|
fscreen.exitFullscreen();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enter fullscreen
|
||||||
|
isFullscreen = true;
|
||||||
|
emit("fullscreen", isFullscreen);
|
||||||
|
if (!canFullscreen() || fscreen.fullscreenElement) return;
|
||||||
|
if (canFullscreenAnyElement()) {
|
||||||
|
if (containerElement) fscreen.requestFullscreen(containerElement);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (canWebkitFullscreen()) {
|
||||||
|
if (videoElement) (videoElement as any).webkitEnterFullscreen();
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { Listener } from "@/utils/events";
|
|||||||
export type DisplayInterfaceEvents = {
|
export type DisplayInterfaceEvents = {
|
||||||
play: void;
|
play: void;
|
||||||
pause: void;
|
pause: void;
|
||||||
|
fullscreen: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
|
export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
|
||||||
@ -11,5 +12,7 @@ export interface DisplayInterface extends Listener<DisplayInterfaceEvents> {
|
|||||||
pause(): void;
|
pause(): void;
|
||||||
load(source: Source): void;
|
load(source: Source): void;
|
||||||
processVideoElement(video: HTMLVideoElement): void;
|
processVideoElement(video: HTMLVideoElement): void;
|
||||||
|
processContainerElement(container: HTMLElement): void;
|
||||||
|
toggleFullscreen(): void;
|
||||||
destroy(): void;
|
destroy(): void;
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,8 @@ export function usePlayer() {
|
|||||||
display?.load(source);
|
display?.load(source);
|
||||||
setStatus(playerStatus.PLAYING);
|
setStatus(playerStatus.PLAYING);
|
||||||
},
|
},
|
||||||
|
setScrapeStatus() {
|
||||||
|
setStatus(playerStatus.SCRAPING);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
18
src/components/player/internals/Button.tsx
Normal file
18
src/components/player/internals/Button.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
||||||
|
export function VideoPlayerButton(props: {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
onClick: () => void;
|
||||||
|
icon?: Icons;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={props.onClick}
|
||||||
|
className="p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-75 transition-transform duration-100 active:scale-110 active:bg-opacity-100 active:text-white"
|
||||||
|
>
|
||||||
|
{props.icon && <Icon className="text-2xl" icon={props.icon} />}
|
||||||
|
{props.children}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
@ -34,7 +34,7 @@ function VideoElement() {
|
|||||||
}
|
}
|
||||||
}, [display, videoEl]);
|
}, [display, videoEl]);
|
||||||
|
|
||||||
return <video autoPlay ref={videoEl} />;
|
return <video className="w-full h-screen" autoPlay ref={videoEl} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VideoContainer() {
|
export function VideoContainer() {
|
||||||
|
@ -1,24 +1,33 @@
|
|||||||
import { MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamType } from "@/backend/helpers/streams";
|
||||||
import { Player } from "@/components/player";
|
import { Player } from "@/components/player";
|
||||||
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
import { usePlayer } from "@/components/player/hooks/usePlayer";
|
||||||
|
import { PlayerHoverState } from "@/stores/player/slices/interface";
|
||||||
import { playerStatus } from "@/stores/player/slices/source";
|
import { playerStatus } from "@/stores/player/slices/source";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
export function PlayerView() {
|
export function PlayerView() {
|
||||||
const { status, playMedia } = usePlayer();
|
const { status, playMedia, setScrapeStatus } = usePlayer();
|
||||||
|
const hovering = usePlayerStore((s) => s.interface.hovering);
|
||||||
|
|
||||||
function scrape() {
|
function scrape() {
|
||||||
playMedia({
|
playMedia({
|
||||||
type: MWStreamType.MP4,
|
type: MWStreamType.MP4,
|
||||||
url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
// url: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||||
|
url: "http://95.111.247.180/darude.mp4",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const showControlElements = hovering !== PlayerHoverState.NOT_HOVERING;
|
||||||
<Player.Container>
|
|
||||||
<Player.Pause />
|
|
||||||
|
|
||||||
{status === playerStatus.IDLE ? (
|
return (
|
||||||
<div>
|
<Player.Container onLoad={setScrapeStatus}>
|
||||||
|
<Player.BottomControls show={showControlElements}>
|
||||||
|
<Player.Pause />
|
||||||
|
<Player.Fullscreen />
|
||||||
|
</Player.BottomControls>
|
||||||
|
|
||||||
|
{status === playerStatus.SCRAPING ? (
|
||||||
|
<div className="w-full h-screen">
|
||||||
<p>Its now scraping</p>
|
<p>Its now scraping</p>
|
||||||
<button type="button" onClick={scrape}>
|
<button type="button" onClick={scrape}>
|
||||||
Finish scraping
|
Finish scraping
|
||||||
|
38
src/stores/player/slices/display.ts
Normal file
38
src/stores/player/slices/display.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { DisplayInterface } from "@/components/player/display/displayInterface";
|
||||||
|
import { MakeSlice } from "@/stores/player/slices/types";
|
||||||
|
|
||||||
|
export interface DisplaySlice {
|
||||||
|
display: DisplayInterface | null;
|
||||||
|
setDisplay(display: DisplayInterface): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createDisplaySlice: MakeSlice<DisplaySlice> = (set, get) => ({
|
||||||
|
display: null,
|
||||||
|
setDisplay(newDisplay: DisplayInterface) {
|
||||||
|
const display = get().display;
|
||||||
|
if (display) display.destroy();
|
||||||
|
|
||||||
|
// make display events update the state
|
||||||
|
newDisplay.on("pause", () =>
|
||||||
|
set((s) => {
|
||||||
|
s.mediaPlaying.isPaused = true;
|
||||||
|
s.mediaPlaying.isPlaying = false;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
newDisplay.on("play", () =>
|
||||||
|
set((s) => {
|
||||||
|
s.mediaPlaying.isPaused = false;
|
||||||
|
s.mediaPlaying.isPlaying = true;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
newDisplay.on("fullscreen", (isFullscreen) =>
|
||||||
|
set((s) => {
|
||||||
|
s.interface.isFullscreen = isFullscreen;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
set((s) => {
|
||||||
|
s.display = newDisplay;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
@ -5,9 +5,16 @@ export enum VideoPlayerTimeFormat {
|
|||||||
REMAINING = 1,
|
REMAINING = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum PlayerHoverState {
|
||||||
|
NOT_HOVERING = "not_hovering",
|
||||||
|
MOUSE_HOVER = "mouse_hover",
|
||||||
|
MOBILE_TAPPED = "mobile_tapped",
|
||||||
|
}
|
||||||
|
|
||||||
export interface InterfaceSlice {
|
export interface InterfaceSlice {
|
||||||
interface: {
|
interface: {
|
||||||
isFullscreen: boolean;
|
isFullscreen: boolean;
|
||||||
|
hovering: PlayerHoverState;
|
||||||
|
|
||||||
volumeChangedWithKeybind: boolean; // has the volume recently been adjusted with the up/down arrows recently?
|
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"
|
volumeChangedWithKeybindDebounce: NodeJS.Timeout | null; // debounce for the duration of the "volume changed thingamajig"
|
||||||
@ -15,14 +22,23 @@ export interface InterfaceSlice {
|
|||||||
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
leftControlHovering: boolean; // is the cursor hovered over the left side of player controls
|
||||||
timeFormat: VideoPlayerTimeFormat; // Time format of the video player
|
timeFormat: VideoPlayerTimeFormat; // Time format of the video player
|
||||||
};
|
};
|
||||||
|
updateInterfaceHovering(newState: PlayerHoverState): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = () => ({
|
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set) => ({
|
||||||
interface: {
|
interface: {
|
||||||
isFullscreen: false,
|
isFullscreen: false,
|
||||||
leftControlHovering: false,
|
leftControlHovering: false,
|
||||||
|
hovering: PlayerHoverState.NOT_HOVERING,
|
||||||
volumeChangedWithKeybind: false,
|
volumeChangedWithKeybind: false,
|
||||||
volumeChangedWithKeybindDebounce: null,
|
volumeChangedWithKeybindDebounce: null,
|
||||||
timeFormat: VideoPlayerTimeFormat.REGULAR,
|
timeFormat: VideoPlayerTimeFormat.REGULAR,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateInterfaceHovering(newState: PlayerHoverState) {
|
||||||
|
set((s) => {
|
||||||
|
console.log("setting", newState);
|
||||||
|
s.interface.hovering = newState;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamType } from "@/backend/helpers/streams";
|
||||||
import { DisplayInterface } from "@/components/player/display/displayInterface";
|
|
||||||
import { MakeSlice } from "@/stores/player/slices/types";
|
import { MakeSlice } from "@/stores/player/slices/types";
|
||||||
import { ValuesOf } from "@/utils/typeguard";
|
import { ValuesOf } from "@/utils/typeguard";
|
||||||
|
|
||||||
@ -19,16 +18,13 @@ export interface SourceSliceSource {
|
|||||||
export interface SourceSlice {
|
export interface SourceSlice {
|
||||||
status: PlayerStatus;
|
status: PlayerStatus;
|
||||||
source: SourceSliceSource | null;
|
source: SourceSliceSource | null;
|
||||||
display: DisplayInterface | null;
|
|
||||||
setStatus(status: PlayerStatus): void;
|
setStatus(status: PlayerStatus): void;
|
||||||
setSource(url: string, type: MWStreamType): void;
|
setSource(url: string, type: MWStreamType): void;
|
||||||
setDisplay(display: DisplayInterface): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
export const createSourceSlice: MakeSlice<SourceSlice> = (set) => ({
|
||||||
source: null,
|
source: null,
|
||||||
status: playerStatus.IDLE,
|
status: playerStatus.IDLE,
|
||||||
display: null,
|
|
||||||
setStatus(status: PlayerStatus) {
|
setStatus(status: PlayerStatus) {
|
||||||
set((s) => {
|
set((s) => {
|
||||||
s.status = status;
|
s.status = status;
|
||||||
@ -42,26 +38,4 @@ export const createSourceSlice: MakeSlice<SourceSlice> = (set, get) => ({
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setDisplay(newDisplay: DisplayInterface) {
|
|
||||||
const display = get().display;
|
|
||||||
if (display) display.destroy();
|
|
||||||
|
|
||||||
// make display events update the state
|
|
||||||
newDisplay.on("pause", () =>
|
|
||||||
set((s) => {
|
|
||||||
s.mediaPlaying.isPaused = true;
|
|
||||||
s.mediaPlaying.isPlaying = false;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
newDisplay.on("play", () =>
|
|
||||||
set((s) => {
|
|
||||||
s.mediaPlaying.isPaused = false;
|
|
||||||
s.mediaPlaying.isPlaying = true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
set((s) => {
|
|
||||||
s.display = newDisplay;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { StateCreator } from "zustand";
|
import { StateCreator } from "zustand";
|
||||||
|
|
||||||
|
import { DisplaySlice } from "@/stores/player/slices/display";
|
||||||
import { InterfaceSlice } from "@/stores/player/slices/interface";
|
import { InterfaceSlice } from "@/stores/player/slices/interface";
|
||||||
import { PlayingSlice } from "@/stores/player/slices/playing";
|
import { PlayingSlice } from "@/stores/player/slices/playing";
|
||||||
import { ProgressSlice } from "@/stores/player/slices/progress";
|
import { ProgressSlice } from "@/stores/player/slices/progress";
|
||||||
@ -8,7 +9,8 @@ import { SourceSlice } from "@/stores/player/slices/source";
|
|||||||
export type AllSlices = InterfaceSlice &
|
export type AllSlices = InterfaceSlice &
|
||||||
PlayingSlice &
|
PlayingSlice &
|
||||||
ProgressSlice &
|
ProgressSlice &
|
||||||
SourceSlice;
|
SourceSlice &
|
||||||
|
DisplaySlice;
|
||||||
export type MakeSlice<Slice> = StateCreator<
|
export type MakeSlice<Slice> = StateCreator<
|
||||||
AllSlices,
|
AllSlices,
|
||||||
[["zustand/immer", never]],
|
[["zustand/immer", never]],
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { immer } from "zustand/middleware/immer";
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
|
import { createDisplaySlice } from "@/stores/player/slices/display";
|
||||||
import { createInterfaceSlice } from "@/stores/player/slices/interface";
|
import { createInterfaceSlice } from "@/stores/player/slices/interface";
|
||||||
import { createPlayingSlice } from "@/stores/player/slices/playing";
|
import { createPlayingSlice } from "@/stores/player/slices/playing";
|
||||||
import { createProgressSlice } from "@/stores/player/slices/progress";
|
import { createProgressSlice } from "@/stores/player/slices/progress";
|
||||||
@ -13,5 +14,6 @@ export const usePlayerStore = create(
|
|||||||
...createProgressSlice(...a),
|
...createProgressSlice(...a),
|
||||||
...createPlayingSlice(...a),
|
...createPlayingSlice(...a),
|
||||||
...createSourceSlice(...a),
|
...createSourceSlice(...a),
|
||||||
|
...createDisplaySlice(...a),
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
@ -26,23 +26,23 @@ module.exports = {
|
|||||||
"ash-400": "#3D394D",
|
"ash-400": "#3D394D",
|
||||||
"ash-300": "#2C293A",
|
"ash-300": "#2C293A",
|
||||||
"ash-200": "#2B2836",
|
"ash-200": "#2B2836",
|
||||||
"ash-100": "#1E1C26",
|
"ash-100": "#1E1C26"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* fonts */
|
/* fonts */
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
"open-sans": "'Open Sans'",
|
"open-sans": "'Open Sans'"
|
||||||
},
|
},
|
||||||
|
|
||||||
/* animations */
|
/* animations */
|
||||||
keyframes: {
|
keyframes: {
|
||||||
"loading-pin": {
|
"loading-pin": {
|
||||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||||
"20%": { height: "1em", "background-color": "white" },
|
"20%": { height: "1em", "background-color": "white" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
require("tailwind-scrollbar"),
|
require("tailwind-scrollbar"),
|
||||||
@ -52,25 +52,25 @@ module.exports = {
|
|||||||
colors: {
|
colors: {
|
||||||
// Branding
|
// Branding
|
||||||
pill: {
|
pill: {
|
||||||
background: "#1C1C36",
|
background: "#1C1C36"
|
||||||
},
|
},
|
||||||
|
|
||||||
// meta data for the theme itself
|
// meta data for the theme itself
|
||||||
global: {
|
global: {
|
||||||
accentA: "#505DBD",
|
accentA: "#505DBD",
|
||||||
accentB: "#3440A1",
|
accentB: "#3440A1"
|
||||||
},
|
},
|
||||||
|
|
||||||
// light bar
|
// light bar
|
||||||
lightBar: {
|
lightBar: {
|
||||||
light: "#2A2A71",
|
light: "#2A2A71"
|
||||||
},
|
},
|
||||||
|
|
||||||
// only used for body colors/textures
|
// only used for body colors/textures
|
||||||
background: {
|
background: {
|
||||||
main: "#0A0A10",
|
main: "#0A0A10",
|
||||||
accentA: "#6E3B80",
|
accentA: "#6E3B80",
|
||||||
accentB: "#1F1F50",
|
accentB: "#1F1F50"
|
||||||
},
|
},
|
||||||
|
|
||||||
// typography
|
// typography
|
||||||
@ -78,7 +78,7 @@ module.exports = {
|
|||||||
emphasis: "#FFFFFF",
|
emphasis: "#FFFFFF",
|
||||||
text: "#73739D",
|
text: "#73739D",
|
||||||
dimmed: "#926CAD",
|
dimmed: "#926CAD",
|
||||||
divider: "#262632",
|
divider: "#262632"
|
||||||
},
|
},
|
||||||
|
|
||||||
// search bar
|
// search bar
|
||||||
@ -87,7 +87,7 @@ module.exports = {
|
|||||||
focused: "#24243C",
|
focused: "#24243C",
|
||||||
placeholder: "#4A4A71",
|
placeholder: "#4A4A71",
|
||||||
icon: "#545476",
|
icon: "#545476",
|
||||||
text: "#FFFFFF",
|
text: "#FFFFFF"
|
||||||
},
|
},
|
||||||
|
|
||||||
// media cards
|
// media cards
|
||||||
@ -99,11 +99,16 @@ module.exports = {
|
|||||||
barColor: "#4B4B63",
|
barColor: "#4B4B63",
|
||||||
barFillColor: "#BA7FD6",
|
barFillColor: "#BA7FD6",
|
||||||
badge: "#151522",
|
badge: "#151522",
|
||||||
badgeText: "#5F5F7A",
|
badgeText: "#5F5F7A"
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
// video player
|
||||||
},
|
video: {
|
||||||
}),
|
buttonBackground: "#444B5C"
|
||||||
],
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user