diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 135d292a4..59369115c 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -79,6 +79,7 @@
"dismiss": "dismiss",
"doNotShowAgain": "do not show this again",
"duration": "duration",
+ "external": "external",
"view": "view",
"edit": "edit",
"enable": "enable",
@@ -901,7 +902,7 @@
"musicbrainzPrioritizeCountries": "prioritize MusicBrainz countries",
"musicbrainzPrioritizeCountries_description": "country codes to prioritize when ordering MusicBrainz releases, comma separated and non case-sensitive (e.g. us, gb, de)",
"youtube": "enable youtube integration",
- "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback",
+ "youtube_description": "external songs will attempt to use YouTube to resolve stream URLs for playback (desktop only)",
"neteaseTranslation_description": "When enabled, fetches and displays translated lyrics from NetEase if available",
"neteaseTranslation": "Enable NetEase translations",
"notify": "enable song notifications",
diff --git a/src/renderer/components/item-card/item-card.module.css b/src/renderer/components/item-card/item-card.module.css
index ea170c795..1a93ca7e4 100644
--- a/src/renderer/components/item-card/item-card.module.css
+++ b/src/renderer/components/item-card/item-card.module.css
@@ -61,7 +61,7 @@
.image-container.external {
img {
- opacity: 0.3;
+ opacity: 0.5;
filter: grayscale(0.5) saturate(0.7);
transition: all 0.2s ease-in-out;
}
diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx
index ea0770132..7e53a24a7 100644
--- a/src/renderer/components/item-card/item-card.tsx
+++ b/src/renderer/components/item-card/item-card.tsx
@@ -19,7 +19,7 @@ import { ItemControls } from '/@/renderer/components/item-list/types';
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
import { AppRoute } from '/@/renderer/router/routes';
-import { useShowRatings } from '/@/renderer/store';
+import { useIntegrationsSettings, useShowRatings } from '/@/renderer/store';
import {
formatDateAbsolute,
formatDateAbsoluteUTC,
@@ -179,6 +179,7 @@ const CompactItemCard = ({
showRating,
withControls,
}: ItemCardDerivativeProps) => {
+ const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings();
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
@@ -342,9 +343,12 @@ const CompactItemCard = ({
const hasRating = showRating && userRating !== null && userRating > 0;
const isExternal = data._serverType === ServerType.EXTERNAL;
+ const showItemCardControls =
+ withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled);
+
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.isRound]: isRound,
- [styles.noHoverOverlay]: isExternal,
+ [styles.noHoverOverlay]: isExternal && !showItemCardControls,
});
const imageContainerContent = (
@@ -377,7 +381,7 @@ const CompactItemCard = ({
{isFavorite &&
}
{hasRating && {userRating}
}
- {withControls && showControls && data && !isExternal && (
+ {showItemCardControls && (
{
+ const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings();
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
@@ -584,10 +589,13 @@ const DefaultItemCard = ({
const hasRating = showRating && userRating !== null && userRating > 0;
const isExternal = data._serverType === ServerType.EXTERNAL;
+ const showItemCardControls =
+ withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled);
+
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.external]: isExternal,
[styles.isRound]: isRound,
- [styles.noHoverOverlay]: isExternal,
+ [styles.noHoverOverlay]: isExternal && !showItemCardControls,
});
const imageContainerContent = (
@@ -618,7 +626,7 @@ const DefaultItemCard = ({
{isFavorite && }
{hasRating && {userRating}
}
- {withControls && showControls && !isExternal && (
+ {showItemCardControls && (
{
+ const { youtube: youtubeIntegrationEnabled } = useIntegrationsSettings();
const [showControls, setShowControls] = useState(false);
const itemRowId =
data && internalState && typeof data === 'object' && 'id' in data
@@ -888,10 +897,13 @@ const PosterItemCard = ({
const hasRating = showRating && userRating !== null && userRating > 0;
const isExternal = data._serverType === ServerType.EXTERNAL;
+ const showItemCardControls =
+ withControls && showControls && data && (!isExternal || youtubeIntegrationEnabled);
+
const imageContainerClassName = clsx(styles.imageContainer, {
[styles.external]: isExternal,
[styles.isRound]: isRound,
- [styles.noHoverOverlay]: isExternal,
+ [styles.noHoverOverlay]: isExternal && !showItemCardControls,
});
const imageContainerContent = (
@@ -922,7 +934,7 @@ const PosterItemCard = ({
{isFavorite && }
{hasRating && {userRating}
}
- {withControls && showControls && data && !isExternal && (
+ {showItemCardControls && (
{
const player = usePlayer();
+ const queryClient = useQueryClient();
const navigate = useNavigate();
const navigateRef = useRef(navigate);
const setFavorite = useSetFavorite();
@@ -384,6 +387,40 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
return;
}
+ const isExternal =
+ (item as Song & { _serverType?: ServerType })._serverType ===
+ ServerType.EXTERNAL;
+
+ if (isExternal) {
+ if (
+ itemType === LibraryItem.SONG ||
+ itemType === LibraryItem.PLAYLIST_SONG ||
+ (item as { _itemType?: LibraryItem })._itemType === LibraryItem.SONG
+ ) {
+ player.addToQueueByData([item as Song], playType, item.id);
+ return;
+ }
+ if (itemType === LibraryItem.ALBUM) {
+ (async () => {
+ try {
+ const album = await queryClient.fetchQuery(
+ albumQueries.detail({
+ query: { id: item.id },
+ serverId: 'musicbrainz',
+ }),
+ );
+ const songs = album?.songs ?? [];
+ if (songs.length > 0) {
+ player.addToQueueByData(songs, playType);
+ }
+ } catch {
+ console.error('Error fetching album songs for item', item);
+ }
+ })();
+ return;
+ }
+ }
+
player.addToQueueByFetch(item._serverId, [item.id], itemType, playType);
},
@@ -417,10 +454,11 @@ export const useDefaultItemListControls = (args?: UseDefaultItemListControlsArgs
};
}, [
enableMultiSelect,
- overrides,
onColumnReordered,
onColumnResized,
+ overrides,
player,
+ queryClient,
setFavorite,
setRating,
]);
diff --git a/src/renderer/features/settings/components/integrations/integrations-tab.tsx b/src/renderer/features/settings/components/integrations/integrations-tab.tsx
index 55dd3162e..ad4fffed3 100644
--- a/src/renderer/features/settings/components/integrations/integrations-tab.tsx
+++ b/src/renderer/features/settings/components/integrations/integrations-tab.tsx
@@ -1,3 +1,4 @@
+import isElectron from 'is-electron';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -116,6 +117,7 @@ export const IntegrationsTab = memo(() => {
updateIntegrations({ youtube: e.currentTarget.checked })}
/>
),