v1.0.2: Mood mix optimizations and media player improvements
- Fixed player seek flicker on podcasts (30s skip buttons) - Added dual-layer seek lock mechanism to prevent stale time updates - Optimized cached podcast seeking (direct seek before reload fallback) - Large skips now execute immediately for responsive feel - Mood mix performance optimizations
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import Image from "next/image";
|
||||
import { SimilarArtist } from "../types";
|
||||
import { Music } from "lucide-react";
|
||||
import { Music, Library } from "lucide-react";
|
||||
import { api } from "@/lib/api";
|
||||
|
||||
interface SimilarArtistsProps {
|
||||
@@ -20,10 +20,11 @@ export function SimilarArtists({
|
||||
|
||||
return (
|
||||
<section>
|
||||
<h2 className="text-xl font-bold mb-4">
|
||||
Fans Also Like
|
||||
</h2>
|
||||
<div data-tv-section="similar-artists" className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
|
||||
<h2 className="text-xl font-bold mb-4">Fans Also Like</h2>
|
||||
<div
|
||||
data-tv-section="similar-artists"
|
||||
className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4"
|
||||
>
|
||||
{similarArtists.map((artist, index) => {
|
||||
const rawImage = artist.coverArt || artist.image;
|
||||
const imageUrl = rawImage
|
||||
@@ -33,17 +34,22 @@ export function SimilarArtists({
|
||||
? Math.round(artist.weight * 100)
|
||||
: null;
|
||||
|
||||
// For library artists, use the library ID; otherwise use mbid or name
|
||||
const navigationId = artist.inLibrary
|
||||
? artist.id
|
||||
: artist.mbid || artist.id;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={artist.id || artist.name}
|
||||
data-tv-card
|
||||
data-tv-card-index={index}
|
||||
tabIndex={0}
|
||||
onClick={() => onNavigate(artist.mbid || artist.id)}
|
||||
onClick={() => onNavigate(navigationId)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
onNavigate(artist.mbid || artist.id);
|
||||
onNavigate(navigationId);
|
||||
}
|
||||
}}
|
||||
className="bg-transparent hover:bg-white/5 transition-all p-3 rounded-md cursor-pointer group"
|
||||
@@ -64,6 +70,15 @@ export function SimilarArtists({
|
||||
<Music className="w-12 h-12 text-gray-600" />
|
||||
</div>
|
||||
)}
|
||||
{/* Library indicator badge */}
|
||||
{artist.inLibrary && (
|
||||
<div
|
||||
className="absolute bottom-1 right-1 bg-[#ecb200] rounded-full p-1"
|
||||
title="In your library"
|
||||
>
|
||||
<Library className="w-3 h-3 text-black" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Artist Name */}
|
||||
@@ -71,13 +86,13 @@ export function SimilarArtists({
|
||||
{artist.name}
|
||||
</h3>
|
||||
|
||||
{/* Album Count */}
|
||||
{/* Album Count - show owned count if in library */}
|
||||
<p className="text-xs text-gray-400 truncate">
|
||||
{artist.ownedAlbumCount &&
|
||||
artist.ownedAlbumCount > 0
|
||||
? `${artist.ownedAlbumCount}/${artist.albumCount} albums`
|
||||
: artist.albumCount && artist.albumCount > 0
|
||||
? `${artist.albumCount} albums`
|
||||
? `${artist.ownedAlbumCount} album${
|
||||
artist.ownedAlbumCount > 1 ? "s" : ""
|
||||
} in library`
|
||||
: "Artist"}
|
||||
</p>
|
||||
|
||||
|
||||
@@ -56,4 +56,5 @@ export interface SimilarArtist {
|
||||
albumCount?: number;
|
||||
ownedAlbumCount?: number;
|
||||
weight?: number;
|
||||
inLibrary?: boolean;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
* and providing refresh functionality for mixes.
|
||||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { toast } from 'sonner';
|
||||
import { useEffect } from "react";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { useAuth } from "@/lib/auth-context";
|
||||
import { toast } from "sonner";
|
||||
import type {
|
||||
Artist,
|
||||
ListenedItem,
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
Audiobook,
|
||||
Mix,
|
||||
PopularArtist,
|
||||
} from '../types';
|
||||
} from "../types";
|
||||
import {
|
||||
useRecentlyListenedQuery,
|
||||
useRecentlyAddedQuery,
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
useRefreshMixesMutation,
|
||||
useBrowseAllQuery,
|
||||
queryKeys,
|
||||
} from '@/hooks/useQueries';
|
||||
} from "@/hooks/useQueries";
|
||||
|
||||
interface PlaylistPreview {
|
||||
id: string;
|
||||
@@ -81,27 +81,38 @@ export function useHomeData(): UseHomeDataReturn {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
// Listen for mixes-updated event (fired when user saves mood preferences)
|
||||
// Use refetchQueries instead of invalidateQueries to force immediate UI update
|
||||
useEffect(() => {
|
||||
const handleMixesUpdated = () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.mixes() });
|
||||
// refetchQueries forces immediate refetch, unlike invalidateQueries which only marks stale
|
||||
queryClient.refetchQueries({ queryKey: queryKeys.mixes() });
|
||||
};
|
||||
|
||||
window.addEventListener('mixes-updated', handleMixesUpdated);
|
||||
return () => window.removeEventListener('mixes-updated', handleMixesUpdated);
|
||||
window.addEventListener("mixes-updated", handleMixesUpdated);
|
||||
return () =>
|
||||
window.removeEventListener("mixes-updated", handleMixesUpdated);
|
||||
}, [queryClient]);
|
||||
|
||||
// React Query hooks - these automatically handle caching, refetching, and loading states
|
||||
const { data: recentlyListenedData, isLoading: isLoadingListened } = useRecentlyListenedQuery(10);
|
||||
const { data: recentlyAddedData, isLoading: isLoadingAdded } = useRecentlyAddedQuery(10);
|
||||
const { data: recommendedData, isLoading: isLoadingRecommended } = useRecommendationsQuery(10);
|
||||
const { data: recentlyListenedData, isLoading: isLoadingListened } =
|
||||
useRecentlyListenedQuery(10);
|
||||
const { data: recentlyAddedData, isLoading: isLoadingAdded } =
|
||||
useRecentlyAddedQuery(10);
|
||||
const { data: recommendedData, isLoading: isLoadingRecommended } =
|
||||
useRecommendationsQuery(10);
|
||||
const { data: mixesData, isLoading: isLoadingMixes } = useMixesQuery();
|
||||
const { data: popularData, isLoading: isLoadingPopular } = usePopularArtistsQuery(20);
|
||||
const { data: podcastsData, isLoading: isLoadingPodcasts } = useTopPodcastsQuery(10);
|
||||
const { data: audiobooksData, isLoading: isLoadingAudiobooks } = useAudiobooksQuery();
|
||||
const { data: browseData, isLoading: isBrowseLoading } = useBrowseAllQuery();
|
||||
const { data: popularData, isLoading: isLoadingPopular } =
|
||||
usePopularArtistsQuery(20);
|
||||
const { data: podcastsData, isLoading: isLoadingPodcasts } =
|
||||
useTopPodcastsQuery(10);
|
||||
const { data: audiobooksData, isLoading: isLoadingAudiobooks } =
|
||||
useAudiobooksQuery();
|
||||
const { data: browseData, isLoading: isBrowseLoading } =
|
||||
useBrowseAllQuery();
|
||||
|
||||
// Mutation for refreshing mixes
|
||||
const { mutateAsync: refreshMixes, isPending: isRefreshingMixes } = useRefreshMixesMutation();
|
||||
const { mutateAsync: refreshMixes, isPending: isRefreshingMixes } =
|
||||
useRefreshMixesMutation();
|
||||
|
||||
/**
|
||||
* Refresh mixes and update cache
|
||||
@@ -109,10 +120,10 @@ export function useHomeData(): UseHomeDataReturn {
|
||||
const handleRefreshMixes = async () => {
|
||||
try {
|
||||
await refreshMixes();
|
||||
toast.success('Mixes refreshed! Check out your new daily picks');
|
||||
toast.success("Mixes refreshed! Check out your new daily picks");
|
||||
} catch (error) {
|
||||
console.error('Failed to refresh mixes:', error);
|
||||
toast.error('Failed to refresh mixes');
|
||||
console.error("Failed to refresh mixes:", error);
|
||||
toast.error("Failed to refresh mixes");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,8 +147,12 @@ export function useHomeData(): UseHomeDataReturn {
|
||||
recommended: recommendedData?.artists || [],
|
||||
mixes: Array.isArray(mixesData) ? mixesData : [],
|
||||
popularArtists: popularData?.artists || [],
|
||||
recentPodcasts: Array.isArray(podcastsData) ? podcastsData.slice(0, 10) : [],
|
||||
recentAudiobooks: Array.isArray(audiobooksData) ? audiobooksData.slice(0, 10) : [],
|
||||
recentPodcasts: Array.isArray(podcastsData)
|
||||
? podcastsData.slice(0, 10)
|
||||
: [],
|
||||
recentAudiobooks: Array.isArray(audiobooksData)
|
||||
? audiobooksData.slice(0, 10)
|
||||
: [],
|
||||
featuredPlaylists: browseData?.playlists || [],
|
||||
isLoading,
|
||||
isRefreshingMixes,
|
||||
|
||||
Reference in New Issue
Block a user