[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:
Benjamin 2024-03-05 02:30:37 -06:00 committed by GitHub
parent a45e7f24e4
commit d52d9136b8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 76 additions and 8 deletions

View file

@ -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",

View file

@ -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 />

View file

@ -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,