[feat] Add a dynamic image option to the fullscreen player (#526)
* Add an option for a dynamic background image in the fullscreen player * Center the background image and fix some more bugs * More cleaning up the background image * Add option for customizable blur amount * Fix missing translation key for image blur * Fix dynamic image shifting when player is opened * Hide image blur size config if dynamic background is disabled --------- Co-authored-by: Jeff <42182408+jeffvli@users.noreply.github.com>
This commit is contained in:
parent
a45e7f24e4
commit
d52d9136b8
3 changed files with 76 additions and 8 deletions
|
@ -311,6 +311,8 @@
|
|||
"fullscreenPlayer": {
|
||||
"config": {
|
||||
"dynamicBackground": "dynamic background",
|
||||
"dynamicImageBlur": "image blur size",
|
||||
"dynamicIsImage": "enable background image",
|
||||
"followCurrentLyric": "follow current lyric",
|
||||
"lyricAlignment": "lyric alignment",
|
||||
"lyricGap": "lyric gap",
|
||||
|
|
|
@ -60,7 +60,11 @@ const ResponsiveContainer = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
const BackgroundImageOverlay = styled.div`
|
||||
interface BackgroundImageOverlayProps {
|
||||
$blur: number;
|
||||
}
|
||||
|
||||
const BackgroundImageOverlay = styled.div<BackgroundImageOverlayProps>`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -68,12 +72,21 @@ const BackgroundImageOverlay = styled.div`
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--bg-header-overlay);
|
||||
backdrop-filter: blur(${({ $blur }) => $blur}rem);
|
||||
`;
|
||||
|
||||
const mainBackground = 'var(--main-bg)';
|
||||
|
||||
const Controls = () => {
|
||||
const { t } = useTranslation();
|
||||
const { dynamicBackground, expanded, opacity, useImageAspectRatio } =
|
||||
useFullScreenPlayerStore();
|
||||
const {
|
||||
dynamicBackground,
|
||||
dynamicImageBlur,
|
||||
dynamicIsImage,
|
||||
expanded,
|
||||
opacity,
|
||||
useImageAspectRatio,
|
||||
} = useFullScreenPlayerStore();
|
||||
const { setStore } = useFullScreenPlayerStoreActions();
|
||||
const { setSettings } = useSettingsStoreActions();
|
||||
const lyricConfig = useLyricsSettings();
|
||||
|
@ -141,6 +154,45 @@ const Controls = () => {
|
|||
/>
|
||||
</Option.Control>
|
||||
</Option>
|
||||
{dynamicBackground && (
|
||||
<Option>
|
||||
<Option.Label>
|
||||
{t('page.fullscreenPlayer.config.dynamicIsImage', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</Option.Label>
|
||||
<Option.Control>
|
||||
<Switch
|
||||
defaultChecked={dynamicIsImage}
|
||||
onChange={(e) =>
|
||||
setStore({
|
||||
dynamicIsImage: e.target.checked,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Option.Control>
|
||||
</Option>
|
||||
)}
|
||||
{dynamicBackground && dynamicIsImage && (
|
||||
<Option>
|
||||
<Option.Label>
|
||||
{t('page.fullscreenPlayer.config.dynamicImageBlur', {
|
||||
postProcess: 'sentenceCase',
|
||||
})}
|
||||
</Option.Label>
|
||||
<Option.Control>
|
||||
<Slider
|
||||
defaultValue={dynamicImageBlur}
|
||||
label={(e) => `${e} rem`}
|
||||
max={6}
|
||||
min={0}
|
||||
step={0.5}
|
||||
w="100%"
|
||||
onChangeEnd={(e) => setStore({ dynamicImageBlur: Number(e) })}
|
||||
/>
|
||||
</Option.Control>
|
||||
</Option>
|
||||
)}
|
||||
{dynamicBackground && (
|
||||
<Option>
|
||||
<Option.Label>
|
||||
|
@ -368,9 +420,13 @@ const containerVariants: Variants = {
|
|||
};
|
||||
},
|
||||
open: (custom) => {
|
||||
const { dynamicBackground, background, windowBarStyle } = custom;
|
||||
const { background, backgroundImage, dynamicBackground, windowBarStyle } = custom;
|
||||
return {
|
||||
background: dynamicBackground ? background : 'var(--main-bg)',
|
||||
background: dynamicBackground ? backgroundImage : mainBackground,
|
||||
backgroundColor: dynamicBackground ? background : mainBackground,
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: 'cover',
|
||||
height:
|
||||
windowBarStyle === Platform.WINDOWS || windowBarStyle === Platform.MACOS
|
||||
? 'calc(100vh - 120px)'
|
||||
|
@ -394,7 +450,7 @@ const containerVariants: Variants = {
|
|||
};
|
||||
|
||||
export const FullScreenPlayer = () => {
|
||||
const { dynamicBackground } = useFullScreenPlayerStore();
|
||||
const { dynamicBackground, dynamicImageBlur, dynamicIsImage } = useFullScreenPlayerStore();
|
||||
const { setStore } = useFullScreenPlayerStoreActions();
|
||||
const { windowBarStyle } = useWindowSettings();
|
||||
|
||||
|
@ -416,17 +472,23 @@ export const FullScreenPlayer = () => {
|
|||
srcLoaded: true,
|
||||
});
|
||||
|
||||
const imageUrl = currentSong?.imageUrl;
|
||||
const backgroundImage =
|
||||
imageUrl && dynamicIsImage
|
||||
? `url("${imageUrl.replace(/size=\d+/g, 'size=500')}`
|
||||
: mainBackground;
|
||||
|
||||
return (
|
||||
<Container
|
||||
animate="open"
|
||||
custom={{ background, dynamicBackground, windowBarStyle }}
|
||||
custom={{ background, backgroundImage, dynamicBackground, windowBarStyle }}
|
||||
exit="closed"
|
||||
initial="closed"
|
||||
transition={{ duration: 2 }}
|
||||
variants={containerVariants}
|
||||
>
|
||||
<Controls />
|
||||
{dynamicBackground && <BackgroundImageOverlay />}
|
||||
{dynamicBackground && <BackgroundImageOverlay $blur={dynamicImageBlur} />}
|
||||
<ResponsiveContainer>
|
||||
<FullScreenPlayerImage />
|
||||
<FullScreenPlayerQueue />
|
||||
|
|
|
@ -6,6 +6,8 @@ import { immer } from 'zustand/middleware/immer';
|
|||
interface FullScreenPlayerState {
|
||||
activeTab: string | 'queue' | 'related' | 'lyrics';
|
||||
dynamicBackground?: boolean;
|
||||
dynamicImageBlur: number;
|
||||
dynamicIsImage?: boolean;
|
||||
expanded: boolean;
|
||||
opacity: number;
|
||||
useImageAspectRatio: boolean;
|
||||
|
@ -28,6 +30,8 @@ export const useFullScreenPlayerStore = create<FullScreenPlayerSlice>()(
|
|||
},
|
||||
activeTab: 'queue',
|
||||
dynamicBackground: true,
|
||||
dynamicImageBlur: 1.5,
|
||||
dynamicIsImage: false,
|
||||
expanded: false,
|
||||
opacity: 60,
|
||||
useImageAspectRatio: false,
|
||||
|
|
Reference in a new issue