mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-13 22:20:13 +02:00
Add image URL generation at runtime to allow for dynamic image sizes (#1439)
* add getImageUrl to domain endpoints * add new ItemImage component and hooks to generate image url * add configuration for image resolution based on types
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import clsx from 'clsx';
|
||||
import { AnimatePresence, HTMLMotionProps, motion, Variants } from 'motion/react';
|
||||
import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { Link } from 'react-router';
|
||||
import { Fragment, useEffect, useRef } from 'react';
|
||||
import { generatePath, Link } from 'react-router';
|
||||
|
||||
import styles from './full-screen-player-image.module.css';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { usePlayerData, usePlayerSong } from '/@/renderer/store';
|
||||
import { useSettingsStore } from '/@/renderer/store/settings.store';
|
||||
@@ -17,6 +17,7 @@ import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Stack } from '/@/shared/components/stack/stack';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { useSetState } from '/@/shared/hooks/use-set-state';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
const imageVariants: Variants = {
|
||||
closed: {
|
||||
@@ -41,13 +42,6 @@ const imageVariants: Variants = {
|
||||
},
|
||||
};
|
||||
|
||||
const scaleImageUrl = (imageSize: number, url?: null | string) => {
|
||||
return url
|
||||
?.replace(/&size=\d+/, `&size=${imageSize}`)
|
||||
.replace(/\?width=\d+/, `?width=${imageSize}`)
|
||||
.replace(/&height=\d+/, `&height=${imageSize}`);
|
||||
};
|
||||
|
||||
const MotionImage = motion.img;
|
||||
|
||||
const ImageWithPlaceholder = ({
|
||||
@@ -85,44 +79,27 @@ const ImageWithPlaceholder = ({
|
||||
|
||||
export const FullScreenPlayerImage = () => {
|
||||
const mainImageRef = useRef<HTMLImageElement | null>(null);
|
||||
const [mainImageDimensions, setMainImageDimensions] = useState({ idealSize: 1 });
|
||||
|
||||
const albumArtRes = useSettingsStore((store) => store.general.albumArtRes);
|
||||
|
||||
const currentSong = usePlayerSong();
|
||||
const { nextSong } = usePlayerData();
|
||||
|
||||
const [imageState, setImageState] = useSetState({
|
||||
bottomImage: scaleImageUrl(mainImageDimensions.idealSize, nextSong?.imageUrl),
|
||||
current: 0,
|
||||
topImage: scaleImageUrl(mainImageDimensions.idealSize, currentSong?.imageUrl),
|
||||
const currentImageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'fullScreenPlayer',
|
||||
});
|
||||
|
||||
const updateImageSize = useCallback(() => {
|
||||
if (mainImageRef.current) {
|
||||
setMainImageDimensions({
|
||||
idealSize:
|
||||
albumArtRes ||
|
||||
Math.ceil((mainImageRef.current as HTMLDivElement).offsetHeight / 100) * 100,
|
||||
});
|
||||
const nextImageUrl = useItemImageUrl({
|
||||
id: nextSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'fullScreenPlayer',
|
||||
});
|
||||
|
||||
setImageState({
|
||||
bottomImage: scaleImageUrl(mainImageDimensions.idealSize, nextSong?.imageUrl),
|
||||
current: 0,
|
||||
topImage: scaleImageUrl(mainImageDimensions.idealSize, currentSong?.imageUrl),
|
||||
});
|
||||
}
|
||||
}, [
|
||||
mainImageDimensions.idealSize,
|
||||
setImageState,
|
||||
albumArtRes,
|
||||
currentSong?.imageUrl,
|
||||
nextSong?.imageUrl,
|
||||
]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateImageSize();
|
||||
}, [updateImageSize]);
|
||||
const [imageState, setImageState] = useSetState({
|
||||
bottomImage: nextImageUrl,
|
||||
current: 0,
|
||||
topImage: currentImageUrl,
|
||||
});
|
||||
|
||||
// Track previous song to detect changes
|
||||
const previousSongRef = useRef<string | undefined>(currentSong?._uniqueId);
|
||||
@@ -133,15 +110,13 @@ export const FullScreenPlayerImage = () => {
|
||||
imageStateRef.current = imageState;
|
||||
}, [imageState]);
|
||||
|
||||
// Update images when song changes
|
||||
// Update images when song or size changes
|
||||
useEffect(() => {
|
||||
if (currentSong?._uniqueId === previousSongRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isTop = imageStateRef.current.current === 0;
|
||||
const currentImageUrl = scaleImageUrl(mainImageDimensions.idealSize, currentSong?.imageUrl);
|
||||
const nextImageUrl = scaleImageUrl(mainImageDimensions.idealSize, nextSong?.imageUrl);
|
||||
|
||||
setImageState({
|
||||
bottomImage: isTop ? currentImageUrl : nextImageUrl,
|
||||
@@ -150,13 +125,7 @@ export const FullScreenPlayerImage = () => {
|
||||
});
|
||||
|
||||
previousSongRef.current = currentSong?._uniqueId;
|
||||
}, [
|
||||
currentSong?._uniqueId,
|
||||
currentSong?.imageUrl,
|
||||
nextSong?.imageUrl,
|
||||
mainImageDimensions.idealSize,
|
||||
setImageState,
|
||||
]);
|
||||
}, [currentSong?._uniqueId, currentImageUrl, nextSong?._uniqueId, nextImageUrl, setImageState]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
@@ -13,6 +13,7 @@ import { useLocation } from 'react-router';
|
||||
|
||||
import styles from './full-screen-player.module.css';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { SONG_TABLE_COLUMNS } from '/@/renderer/components/item-list/item-table-list/default-columns';
|
||||
import { FullScreenPlayerImage } from '/@/renderer/features/player/components/full-screen-player-image';
|
||||
import { FullScreenPlayerQueue } from '/@/renderer/features/player/components/full-screen-player-queue';
|
||||
@@ -38,6 +39,7 @@ import { Select } from '/@/shared/components/select/select';
|
||||
import { Slider } from '/@/shared/components/slider/slider';
|
||||
import { Switch } from '/@/shared/components/switch/switch';
|
||||
import { useHotkeys } from '/@/shared/hooks/use-hotkeys';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { ItemListKey, ListDisplayType, Platform } from '/@/shared/types/types';
|
||||
|
||||
const mainBackground = 'var(--theme-colors-background)';
|
||||
@@ -74,14 +76,22 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
const currentSong = usePlayerSong();
|
||||
const { nextSong } = usePlayerData();
|
||||
|
||||
const currentImageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const nextImageUrl = useItemImageUrl({
|
||||
id: nextSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const [imageState, setImageState] = useState({
|
||||
bottomImage: nextSong?.imageUrl
|
||||
? nextSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined,
|
||||
bottomImage: nextImageUrl,
|
||||
current: 0,
|
||||
topImage: currentSong?.imageUrl
|
||||
? currentSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined,
|
||||
topImage: currentImageUrl,
|
||||
});
|
||||
|
||||
const previousSongRef = useRef<string | undefined>(currentSong?._uniqueId);
|
||||
@@ -99,12 +109,6 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
}
|
||||
|
||||
const isTop = imageStateRef.current.current === 0;
|
||||
const currentImageUrl = currentSong?.imageUrl
|
||||
? currentSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined;
|
||||
const nextImageUrl = nextSong?.imageUrl
|
||||
? nextSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined;
|
||||
|
||||
setImageState({
|
||||
bottomImage: isTop ? currentImageUrl : nextImageUrl,
|
||||
@@ -113,7 +117,7 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
});
|
||||
|
||||
previousSongRef.current = currentSong?._uniqueId;
|
||||
}, [currentSong?._uniqueId, currentSong?.imageUrl, nextSong?._uniqueId, nextSong?.imageUrl]);
|
||||
}, [currentSong?._uniqueId, currentImageUrl, nextSong?._uniqueId, nextImageUrl]);
|
||||
|
||||
if (!dynamicBackground || !dynamicIsImage) {
|
||||
return null;
|
||||
@@ -610,9 +614,15 @@ const PlayerContainer = memo(
|
||||
windowBarStyle,
|
||||
}: PlayerContainerProps) => {
|
||||
const currentSong = usePlayerSong();
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
imageUrl: currentSong?.imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
const { background } = useFastAverageColor({
|
||||
algorithm: 'dominant',
|
||||
src: currentSong?.imageUrl,
|
||||
src: imageUrl,
|
||||
srcLoaded: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { shallow } from 'zustand/shallow';
|
||||
|
||||
import styles from './left-controls.module.css';
|
||||
|
||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { RadioMetadataDisplay } from '/@/renderer/features/player/components/radio-metadata-display';
|
||||
import { useIsRadioActive } from '/@/renderer/features/radio/hooks/use-radio-player';
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
} from '/@/renderer/store';
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Separator } from '/@/shared/components/separator/separator';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
@@ -116,13 +116,14 @@ export const LeftControls = () => {
|
||||
})}
|
||||
openDelay={0}
|
||||
>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={clsx(
|
||||
styles.playerbarImage,
|
||||
PlaybackSelectors.playerCoverArt,
|
||||
)}
|
||||
id={currentSong?.id}
|
||||
itemType={LibraryItem.SONG}
|
||||
loading="eager"
|
||||
src={isRadioMode ? '' : (currentSong?.imageUrl ?? '')}
|
||||
/>
|
||||
</Tooltip>
|
||||
{!collapsed && (
|
||||
|
||||
@@ -4,16 +4,18 @@ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react
|
||||
|
||||
import styles from './mobile-fullscreen-player.module.css';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import {
|
||||
useFullScreenPlayerStore,
|
||||
useGeneralSettings,
|
||||
usePlayerData,
|
||||
usePlayerSong,
|
||||
useSettingsStore,
|
||||
} from '/@/renderer/store';
|
||||
import { Center } from '/@/shared/components/center/center';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { PlaybackSelectors } from '/@/shared/constants/playback-selectors';
|
||||
import { useSetState } from '/@/shared/hooks/use-set-state';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
|
||||
const imageVariants: Variants = {
|
||||
closed: {
|
||||
@@ -38,13 +40,6 @@ const imageVariants: Variants = {
|
||||
},
|
||||
};
|
||||
|
||||
const scaleImageUrl = (imageSize: number, url?: null | string) => {
|
||||
return url
|
||||
?.replace(/&size=\d+/, `&size=${imageSize}`)
|
||||
.replace(/\?width=\d+/, `?width=${imageSize}`)
|
||||
.replace(/&height=\d+/, `&height=${imageSize}`);
|
||||
};
|
||||
|
||||
const MotionImage = motion.img;
|
||||
|
||||
const ImageWithPlaceholder = ({
|
||||
@@ -83,15 +78,29 @@ export const MobileFullscreenPlayerAlbumArt = () => {
|
||||
const mainImageRef = useRef<HTMLImageElement | null>(null);
|
||||
const [mainImageDimensions, setMainImageDimensions] = useState({ idealSize: 1000 });
|
||||
|
||||
const { albumArtRes } = useGeneralSettings();
|
||||
const albumArtRes = useSettingsStore((store) => store.general.imageRes.fullScreenPlayer);
|
||||
const { useImageAspectRatio } = useFullScreenPlayerStore();
|
||||
const currentSong = usePlayerSong();
|
||||
const { nextSong } = usePlayerData();
|
||||
|
||||
const currentImageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
size: mainImageDimensions.idealSize,
|
||||
type: 'fullScreenPlayer',
|
||||
});
|
||||
|
||||
const nextImageUrl = useItemImageUrl({
|
||||
id: nextSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
size: mainImageDimensions.idealSize,
|
||||
type: 'fullScreenPlayer',
|
||||
});
|
||||
|
||||
const [imageState, setImageState] = useSetState({
|
||||
bottomImage: scaleImageUrl(mainImageDimensions.idealSize, nextSong?.imageUrl),
|
||||
bottomImage: nextImageUrl,
|
||||
current: 0,
|
||||
topImage: scaleImageUrl(mainImageDimensions.idealSize, currentSong?.imageUrl),
|
||||
topImage: currentImageUrl,
|
||||
});
|
||||
|
||||
const updateImageSize = useCallback(() => {
|
||||
@@ -101,14 +110,8 @@ export const MobileFullscreenPlayerAlbumArt = () => {
|
||||
Math.ceil((mainImageRef.current as HTMLDivElement).offsetHeight / 100) * 100;
|
||||
|
||||
setMainImageDimensions({ idealSize });
|
||||
|
||||
setImageState({
|
||||
bottomImage: scaleImageUrl(idealSize, nextSong?.imageUrl),
|
||||
current: 0,
|
||||
topImage: scaleImageUrl(idealSize, currentSong?.imageUrl),
|
||||
});
|
||||
}
|
||||
}, [albumArtRes, currentSong?.imageUrl, nextSong?.imageUrl, setImageState]);
|
||||
}, [albumArtRes]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
updateImageSize();
|
||||
@@ -123,15 +126,13 @@ export const MobileFullscreenPlayerAlbumArt = () => {
|
||||
imageStateRef.current = imageState;
|
||||
}, [imageState]);
|
||||
|
||||
// Update images when song changes
|
||||
// Update images when song or size changes
|
||||
useEffect(() => {
|
||||
if (currentSong?._uniqueId === previousSongRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isTop = imageStateRef.current.current === 0;
|
||||
const currentImageUrl = scaleImageUrl(mainImageDimensions.idealSize, currentSong?.imageUrl);
|
||||
const nextImageUrl = scaleImageUrl(mainImageDimensions.idealSize, nextSong?.imageUrl);
|
||||
|
||||
setImageState({
|
||||
bottomImage: isTop ? currentImageUrl : nextImageUrl,
|
||||
@@ -140,13 +141,7 @@ export const MobileFullscreenPlayerAlbumArt = () => {
|
||||
});
|
||||
|
||||
previousSongRef.current = currentSong?._uniqueId;
|
||||
}, [
|
||||
currentSong?._uniqueId,
|
||||
currentSong?.imageUrl,
|
||||
nextSong?.imageUrl,
|
||||
mainImageDimensions.idealSize,
|
||||
setImageState,
|
||||
]);
|
||||
}, [currentSong?._uniqueId, currentImageUrl, nextSong?._uniqueId, nextImageUrl, setImageState]);
|
||||
|
||||
return (
|
||||
<div className={styles.imageContainer} ref={mainImageRef}>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
import styles from './mobile-fullscreen-player.module.css';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { Lyrics } from '/@/renderer/features/lyrics/lyrics';
|
||||
import { PlayQueue } from '/@/renderer/features/now-playing/components/play-queue';
|
||||
@@ -74,14 +75,22 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
const currentSong = usePlayerSong();
|
||||
const { nextSong } = usePlayerData();
|
||||
|
||||
const currentImageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const nextImageUrl = useItemImageUrl({
|
||||
id: nextSong?.id,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const [imageState, setImageState] = useState({
|
||||
bottomImage: nextSong?.imageUrl
|
||||
? nextSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined,
|
||||
bottomImage: nextImageUrl,
|
||||
current: 0,
|
||||
topImage: currentSong?.imageUrl
|
||||
? currentSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined,
|
||||
topImage: currentImageUrl,
|
||||
});
|
||||
|
||||
const previousSongRef = useRef<string | undefined>(currentSong?._uniqueId);
|
||||
@@ -98,12 +107,6 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
}
|
||||
|
||||
const isTop = imageStateRef.current.current === 0;
|
||||
const currentImageUrl = currentSong?.imageUrl
|
||||
? currentSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined;
|
||||
const nextImageUrl = nextSong?.imageUrl
|
||||
? nextSong.imageUrl.replace(/size=\d+/g, 'size=500')
|
||||
: undefined;
|
||||
|
||||
setImageState({
|
||||
bottomImage: isTop ? currentImageUrl : nextImageUrl,
|
||||
@@ -112,7 +115,7 @@ const BackgroundImage = memo(({ dynamicBackground, dynamicIsImage }: BackgroundI
|
||||
});
|
||||
|
||||
previousSongRef.current = currentSong?._uniqueId;
|
||||
}, [currentSong?._uniqueId, currentSong?.imageUrl, nextSong?._uniqueId, nextSong?.imageUrl]);
|
||||
}, [currentSong?._uniqueId, currentImageUrl, nextSong?._uniqueId, nextImageUrl]);
|
||||
|
||||
if (!dynamicBackground || !dynamicIsImage) {
|
||||
return null;
|
||||
@@ -299,9 +302,15 @@ interface MobilePlayerContainerProps {
|
||||
const MobilePlayerContainer = memo(
|
||||
({ children, dynamicBackground, dynamicIsImage }: MobilePlayerContainerProps) => {
|
||||
const currentSong = usePlayerSong();
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
imageUrl: currentSong?.imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
const { background } = useFastAverageColor({
|
||||
algorithm: 'dominant',
|
||||
src: currentSong?.imageUrl,
|
||||
src: imageUrl,
|
||||
srcLoaded: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { generatePath, Link } from 'react-router';
|
||||
|
||||
import styles from './mobile-playerbar.module.css';
|
||||
|
||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
|
||||
import { MainPlayButton, PlayerButton } from '/@/renderer/features/player/components/player-button';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
|
||||
import { Group } from '/@/shared/components/group/group';
|
||||
import { Icon } from '/@/shared/components/icon/icon';
|
||||
import { Image } from '/@/shared/components/image/image';
|
||||
import { Separator } from '/@/shared/components/separator/separator';
|
||||
import { Text } from '/@/shared/components/text/text';
|
||||
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
|
||||
@@ -68,7 +68,7 @@ export const MobilePlayerbar = () => {
|
||||
<div className={styles.contentWrapper}>
|
||||
<LayoutGroup>
|
||||
<AnimatePresence initial={false} mode="popLayout">
|
||||
{currentSong?.imageUrl && (
|
||||
{currentSong?.id && (
|
||||
<div className={styles.imageWrapper}>
|
||||
<motion.div
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
@@ -87,13 +87,14 @@ export const MobilePlayerbar = () => {
|
||||
})}
|
||||
openDelay={0}
|
||||
>
|
||||
<Image
|
||||
<ItemImage
|
||||
className={clsx(
|
||||
styles.playerbarImage,
|
||||
PlaybackSelectors.playerCoverArt,
|
||||
)}
|
||||
id={currentSong.id}
|
||||
itemType={LibraryItem.SONG}
|
||||
loading="eager"
|
||||
src={currentSong.imageUrl}
|
||||
/>
|
||||
</Tooltip>
|
||||
</motion.div>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||
import { usePlayer } from '/@/renderer/features/player/context/player-context';
|
||||
import { usePlaybackSettings, useSettingsStore, useTimestampStoreBase } from '/@/renderer/store';
|
||||
import {
|
||||
usePlaybackSettings,
|
||||
usePlayerSong,
|
||||
useSettingsStore,
|
||||
useTimestampStoreBase,
|
||||
} from '/@/renderer/store';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { PlayerStatus, PlayerType } from '/@/shared/types/types';
|
||||
|
||||
const mediaSession = navigator.mediaSession;
|
||||
@@ -13,6 +20,14 @@ export const useMediaSession = () => {
|
||||
const player = usePlayer();
|
||||
const skip = useSettingsStore((state) => state.general.skipButtons);
|
||||
const playbackType = useSettingsStore((state) => state.playback.type);
|
||||
const currentSong = usePlayerSong();
|
||||
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
imageUrl: currentSong?.imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const isMediaSessionEnabled = useMemo(() => {
|
||||
// Always enable media session on web
|
||||
@@ -94,7 +109,7 @@ export const useMediaSession = () => {
|
||||
mediaSession.metadata = new MediaMetadata({
|
||||
album: song?.album ?? '',
|
||||
artist: song?.artistName ?? '',
|
||||
artwork: song?.imageUrl ? [{ src: song.imageUrl, type: 'image/png' }] : [],
|
||||
artwork: imageUrl ? [{ src: imageUrl, type: 'image/png' }] : [],
|
||||
title: song?.name ?? '',
|
||||
});
|
||||
},
|
||||
@@ -107,6 +122,6 @@ export const useMediaSession = () => {
|
||||
mediaSession.playbackState = status === PlayerStatus.PLAYING ? 'playing' : 'paused';
|
||||
},
|
||||
},
|
||||
[isMediaSessionEnabled, mediaSession],
|
||||
[isMediaSessionEnabled, mediaSession, imageUrl],
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import isElectron from 'is-electron';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||
import { usePlayerStore } from '/@/renderer/store';
|
||||
import { usePlayerSong, usePlayerStore } from '/@/renderer/store';
|
||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||
import { PlayerShuffle } from '/@/shared/types/types';
|
||||
|
||||
const ipc = isElectron() ? window.api.ipc : null;
|
||||
@@ -11,6 +13,14 @@ const mpris = isElectron() && utils?.isLinux() ? window.api.mpris : null;
|
||||
|
||||
export const useMPRIS = () => {
|
||||
const player = usePlayerStore();
|
||||
const currentSong = usePlayerSong();
|
||||
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
imageUrl: currentSong?.imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!mpris) {
|
||||
@@ -41,32 +51,19 @@ export const useMPRIS = () => {
|
||||
};
|
||||
}, [player]);
|
||||
|
||||
const isInitializedRef = useRef(false);
|
||||
|
||||
// Update MPRIS when song or imageUrl changes
|
||||
useEffect(() => {
|
||||
if (isInitializedRef.current) {
|
||||
if (!mpris) {
|
||||
return;
|
||||
}
|
||||
|
||||
isInitializedRef.current = true;
|
||||
|
||||
const currentSong = player.getCurrentSong();
|
||||
|
||||
if (!currentSong) {
|
||||
return;
|
||||
}
|
||||
|
||||
mpris?.updateSong(currentSong);
|
||||
}, [player]);
|
||||
mpris?.updateSong(currentSong, imageUrl);
|
||||
}, [currentSong, imageUrl]);
|
||||
|
||||
usePlayerEvents(
|
||||
{
|
||||
onCurrentSongChange: (properties) => {
|
||||
if (!mpris) {
|
||||
return;
|
||||
}
|
||||
|
||||
mpris?.updateSong(properties.song);
|
||||
onCurrentSongChange: () => {
|
||||
// The effect above will handle the update when currentSong changes
|
||||
},
|
||||
onPlayerProgress: (properties) => {
|
||||
if (!mpris) {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
|
||||
import { usePlayerEvents } from '/@/renderer/features/player/audio-player/hooks/use-player-events';
|
||||
import { useSendScrobble } from '/@/renderer/features/player/mutations/scrobble-mutation';
|
||||
import { useAppStore, usePlaybackSettings, usePlayerStore } from '/@/renderer/store';
|
||||
import { useAppStore, usePlaybackSettings, usePlayerSong, usePlayerStore } from '/@/renderer/store';
|
||||
import { LogCategory, logFn } from '/@/renderer/utils/logger';
|
||||
import { logMsg } from '/@/renderer/utils/logger-message';
|
||||
import { QueueSong, ServerType } from '/@/shared/types/domain-types';
|
||||
import { LibraryItem, QueueSong, ServerType } from '/@/shared/types/domain-types';
|
||||
import { PlayerStatus } from '/@/shared/types/types';
|
||||
|
||||
/*
|
||||
@@ -59,7 +60,16 @@ export const useScrobble = () => {
|
||||
const isScrobbleEnabled = scrobbleSettings?.enabled;
|
||||
const isPrivateModeEnabled = useAppStore((state) => state.privateMode);
|
||||
const sendScrobble = useSendScrobble();
|
||||
const currentSong = usePlayerSong();
|
||||
|
||||
const imageUrl = useItemImageUrl({
|
||||
id: currentSong?.id,
|
||||
imageUrl: currentSong?.imageUrl,
|
||||
itemType: LibraryItem.SONG,
|
||||
type: 'itemCard',
|
||||
});
|
||||
|
||||
const imageUrlRef = useRef<null | string | undefined>(imageUrl);
|
||||
const [isCurrentSongScrobbled, setIsCurrentSongScrobbled] = useState(false);
|
||||
const previousSongRef = useRef<QueueSong | undefined>(undefined);
|
||||
const previousTimestampRef = useRef<number>(0);
|
||||
@@ -68,6 +78,10 @@ export const useScrobble = () => {
|
||||
const songChangeTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
const notifyTimeoutRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
imageUrlRef.current = imageUrl;
|
||||
}, [imageUrl]);
|
||||
|
||||
const handleScrobbleFromProgress = useCallback(
|
||||
(properties: { timestamp: number }, prev: { timestamp: number }) => {
|
||||
if (!isScrobbleEnabled || isPrivateModeEnabled) return;
|
||||
@@ -198,7 +212,7 @@ export const useScrobble = () => {
|
||||
|
||||
new Notification(`${currentSong.name}`, {
|
||||
body: `${artists}\n${currentSong.album}`,
|
||||
icon: currentSong.imageUrl || undefined,
|
||||
icon: imageUrlRef.current || undefined,
|
||||
silent: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5,20 +5,15 @@ import { QueueSong } from '/@/shared/types/domain-types';
|
||||
const remote = isElectron() ? window.api.remote : null;
|
||||
const mediaSession = navigator.mediaSession;
|
||||
|
||||
export const updateSong = (song: QueueSong | undefined) => {
|
||||
export const updateSong = (song: QueueSong | undefined, imageUrl?: null | string) => {
|
||||
if (mediaSession) {
|
||||
let metadata: MediaMetadata;
|
||||
|
||||
if (song?.id) {
|
||||
let artwork: MediaImage[];
|
||||
|
||||
if (song.imageUrl) {
|
||||
const image300 = song.imageUrl
|
||||
?.replace(/&size=\d+/, '&size=300')
|
||||
.replace(/\?width=\d+/, '?width=300')
|
||||
.replace(/&height=\d+/, '&height=300');
|
||||
|
||||
artwork = [{ sizes: '300x300', src: image300, type: 'image/png' }];
|
||||
if (imageUrl) {
|
||||
artwork = [{ sizes: '300x300', src: imageUrl, type: 'image/png' }];
|
||||
} else {
|
||||
artwork = [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user