mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
volume controls
Co-authored-by: Jip Frijlink <JipFr@users.noreply.github.com>
This commit is contained in:
parent
4289b96039
commit
3e210b979e
12 changed files with 186 additions and 4 deletions
|
@ -5,4 +5,5 @@ export * from "./base/CenterControls";
|
||||||
export * from "./base/BottomControls";
|
export * from "./base/BottomControls";
|
||||||
export * from "./base/BlackOverlay";
|
export * from "./base/BlackOverlay";
|
||||||
export * from "./base/BackLink";
|
export * from "./base/BackLink";
|
||||||
|
export * from "./base/LeftSideControls";
|
||||||
export * from "./internals/BookmarkButton";
|
export * from "./internals/BookmarkButton";
|
||||||
|
|
3
src/components/player/atoms/LeftSideControls.tsx
Normal file
3
src/components/player/atoms/LeftSideControls.tsx
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export function LeftSideControls(props: {children: React.ReactNode}) {
|
||||||
|
|
||||||
|
}
|
82
src/components/player/atoms/Volume.tsx
Normal file
82
src/components/player/atoms/Volume.tsx
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import { useCallback, useRef, useState } from "react";
|
||||||
|
|
||||||
|
import { Icon, Icons } from "@/components/Icon";
|
||||||
|
import {
|
||||||
|
makePercentage,
|
||||||
|
makePercentageString,
|
||||||
|
useProgressBar,
|
||||||
|
} from "@/hooks/useProgressBar";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
import { canChangeVolume } from "@/utils/detectFeatures";
|
||||||
|
|
||||||
|
import { useVolume } from "../hooks/useVolume";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Volume(props: Props) {
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const setHovering = usePlayerStore((s) => s.setHoveringLeftControls);
|
||||||
|
const hovering = usePlayerStore((s) => s.interface.leftControlHovering);
|
||||||
|
const volume = usePlayerStore((s) => s.mediaPlaying.volume);
|
||||||
|
const { setVolume, toggleMute } = useVolume();
|
||||||
|
|
||||||
|
const commitVolume = useCallback(
|
||||||
|
(percentage) => {
|
||||||
|
setVolume(percentage);
|
||||||
|
},
|
||||||
|
[setVolume]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { dragging, dragPercentage, dragMouseDown } = useProgressBar(
|
||||||
|
ref,
|
||||||
|
commitVolume,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
toggleMute();
|
||||||
|
}, [toggleMute]);
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback(async () => {
|
||||||
|
if (await canChangeVolume()) setHovering(true);
|
||||||
|
}, [setHovering]);
|
||||||
|
|
||||||
|
let percentage = makePercentage(volume * 100);
|
||||||
|
if (dragging) percentage = makePercentage(dragPercentage);
|
||||||
|
const percentageString = makePercentageString(percentage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={props.className} onMouseEnter={handleMouseEnter}>
|
||||||
|
<div className="pointer-events-auto flex cursor-pointer items-center py-0">
|
||||||
|
<div className="px-4 text-2xl text-white" onClick={handleClick}>
|
||||||
|
<Icon icon={percentage > 0 ? Icons.VOLUME : Icons.VOLUME_X} />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`linear -ml-2 w-0 overflow-hidden transition-[width,opacity] duration-300 ${
|
||||||
|
hovering || dragging ? "!w-24 opacity-100" : "w-4 opacity-0"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className="flex h-10 w-20 items-center px-2"
|
||||||
|
onMouseDown={dragMouseDown}
|
||||||
|
onTouchStart={dragMouseDown}
|
||||||
|
>
|
||||||
|
<div className="relative h-1 flex-1 rounded-full bg-gray-500 bg-opacity-50">
|
||||||
|
<div
|
||||||
|
className="absolute inset-y-0 left-0 flex items-center justify-end rounded-full bg-video-audio-set"
|
||||||
|
style={{
|
||||||
|
width: percentageString,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="absolute h-3 w-3 translate-x-1/2 rounded-full bg-white" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -5,3 +5,4 @@ export * from "./Skips";
|
||||||
export * from "./Time";
|
export * from "./Time";
|
||||||
export * from "./LoadingSpinner";
|
export * from "./LoadingSpinner";
|
||||||
export * from "./AutoPlayStart";
|
export * from "./AutoPlayStart";
|
||||||
|
export * from "./Volume";
|
||||||
|
|
25
src/components/player/base/LeftSideControls.tsx
Normal file
25
src/components/player/base/LeftSideControls.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
|
export function LeftSideControls(props: { children: React.ReactNode }) {
|
||||||
|
const setHoveringLeftControls = usePlayerStore(
|
||||||
|
(s) => s.setHoveringLeftControls
|
||||||
|
);
|
||||||
|
|
||||||
|
const mouseLeave = useCallback(() => {
|
||||||
|
setHoveringLeftControls(false);
|
||||||
|
}, [setHoveringLeftControls]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
setHoveringLeftControls(false);
|
||||||
|
};
|
||||||
|
}, [setHoveringLeftControls]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-3 items-center" onMouseLeave={mouseLeave}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
19
src/components/player/hooks/useInitializePlayer.ts
Normal file
19
src/components/player/hooks/useInitializePlayer.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
import { getStoredVolume } from "@/_oldvideo/components/hooks/volumeStore";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
|
// TODO use new stored volume
|
||||||
|
|
||||||
|
export function useInitializePlayer() {
|
||||||
|
const display = usePlayerStore((s) => s.display);
|
||||||
|
|
||||||
|
const init = useCallback(() => {
|
||||||
|
const storedVolume = getStoredVolume();
|
||||||
|
display?.setVolume(storedVolume);
|
||||||
|
}, [display]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
init,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { MWStreamType } from "@/backend/helpers/streams";
|
import { MWStreamType } from "@/backend/helpers/streams";
|
||||||
|
import { useInitializePlayer } from "@/components/player/hooks/useInitializePlayer";
|
||||||
import { playerStatus } from "@/stores/player/slices/source";
|
import { playerStatus } from "@/stores/player/slices/source";
|
||||||
import { usePlayerStore } from "@/stores/player/store";
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
|
@ -11,12 +12,14 @@ export function usePlayer() {
|
||||||
const setStatus = usePlayerStore((s) => s.setStatus);
|
const setStatus = usePlayerStore((s) => s.setStatus);
|
||||||
const status = usePlayerStore((s) => s.status);
|
const status = usePlayerStore((s) => s.status);
|
||||||
const display = usePlayerStore((s) => s.display);
|
const display = usePlayerStore((s) => s.display);
|
||||||
|
const { init } = useInitializePlayer();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
playMedia(source: Source) {
|
playMedia(source: Source) {
|
||||||
display?.load(source);
|
display?.load(source);
|
||||||
setStatus(playerStatus.PLAYING);
|
setStatus(playerStatus.PLAYING);
|
||||||
|
init();
|
||||||
},
|
},
|
||||||
setScrapeStatus() {
|
setScrapeStatus() {
|
||||||
setStatus(playerStatus.SCRAPING);
|
setStatus(playerStatus.SCRAPING);
|
||||||
|
|
34
src/components/player/hooks/useVolume.ts
Normal file
34
src/components/player/hooks/useVolume.ts
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import {
|
||||||
|
getStoredVolume,
|
||||||
|
setStoredVolume,
|
||||||
|
} from "@/_oldvideo/components/hooks/volumeStore";
|
||||||
|
import { usePlayerStore } from "@/stores/player/store";
|
||||||
|
|
||||||
|
// TODO use new stored volume
|
||||||
|
|
||||||
|
export function useVolume() {
|
||||||
|
const volume = usePlayerStore((s) => s.mediaPlaying.volume);
|
||||||
|
const display = usePlayerStore((s) => s.display);
|
||||||
|
|
||||||
|
const toggleVolume = (_isKeyboardEvent = false) => {
|
||||||
|
// TODO use keyboard event
|
||||||
|
if (volume > 0) {
|
||||||
|
setStoredVolume(volume);
|
||||||
|
display?.setVolume(0);
|
||||||
|
} else {
|
||||||
|
const storedVolume = getStoredVolume();
|
||||||
|
if (storedVolume > 0) display?.setVolume(storedVolume);
|
||||||
|
else display?.setVolume(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
toggleMute() {
|
||||||
|
toggleVolume();
|
||||||
|
},
|
||||||
|
setVolume(vol: number) {
|
||||||
|
setStoredVolume(vol);
|
||||||
|
display?.setVolume(vol);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
|
@ -2,17 +2,20 @@ import { Icon, Icons } from "@/components/Icon";
|
||||||
|
|
||||||
export function VideoPlayerButton(props: {
|
export function VideoPlayerButton(props: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
onClick: () => void;
|
onClick?: () => void;
|
||||||
icon?: Icons;
|
icon?: Icons;
|
||||||
iconSizeClass?: string;
|
iconSizeClass?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
activeClass?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={props.onClick}
|
onClick={props.onClick}
|
||||||
className={[
|
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",
|
"p-2 rounded-full hover:bg-video-buttonBackground hover:bg-opacity-75 transition-transform duration-100",
|
||||||
|
props.activeClass ??
|
||||||
|
"active:scale-110 active:bg-opacity-100 active:text-white",
|
||||||
props.className ?? "",
|
props.className ?? "",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
>
|
>
|
||||||
|
|
|
@ -64,12 +64,13 @@ export function PlayerView() {
|
||||||
<Player.BottomControls show={desktopControlsVisible}>
|
<Player.BottomControls show={desktopControlsVisible}>
|
||||||
<Player.ProgressBar />
|
<Player.ProgressBar />
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<div className="flex space-x-3 items-center">
|
<Player.LeftSideControls>
|
||||||
<Player.Pause />
|
<Player.Pause />
|
||||||
<Player.SkipBackward />
|
<Player.SkipBackward />
|
||||||
<Player.SkipForward />
|
<Player.SkipForward />
|
||||||
|
<Player.Volume />
|
||||||
<Player.Time />
|
<Player.Time />
|
||||||
</div>
|
</Player.LeftSideControls>
|
||||||
<div>
|
<div>
|
||||||
<Player.Fullscreen />
|
<Player.Fullscreen />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,6 +26,7 @@ export interface InterfaceSlice {
|
||||||
updateInterfaceHovering(newState: PlayerHoverState): void;
|
updateInterfaceHovering(newState: PlayerHoverState): void;
|
||||||
setSeeking(seeking: boolean): void;
|
setSeeking(seeking: boolean): void;
|
||||||
setTimeFormat(format: VideoPlayerTimeFormat): void;
|
setTimeFormat(format: VideoPlayerTimeFormat): void;
|
||||||
|
setHoveringLeftControls(state: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
|
export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
|
||||||
|
@ -56,4 +57,9 @@ export const createInterfaceSlice: MakeSlice<InterfaceSlice> = (set, get) => ({
|
||||||
s.interface.isSeeking = seeking;
|
s.interface.isSeeking = seeking;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setHoveringLeftControls(state) {
|
||||||
|
set((s) => {
|
||||||
|
s.interface.leftControlHovering = state;
|
||||||
|
});
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -119,6 +119,10 @@ module.exports = {
|
||||||
background: "#8787A8",
|
background: "#8787A8",
|
||||||
preloaded: "#8787A8",
|
preloaded: "#8787A8",
|
||||||
watched: "#A75FC9"
|
watched: "#A75FC9"
|
||||||
|
},
|
||||||
|
|
||||||
|
audio: {
|
||||||
|
set: "#A75FC9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue