@@ -53,9 +53,11 @@ struct TrackMetadata
5353 std::vector<uint8_t > waveformData;
5454 int waveformEntryCount = 0 ;
5555 int waveformBytesPerEntry = 0 ; // 3 = ThreeBand, 6 = ColorNxs2
56+ bool waveformQueried = false ; // true once waveform fetch has been attempted
5657
5758 bool isValid () const { return trackId != 0 && title.isNotEmpty (); }
5859 bool hasWaveform () const { return waveformEntryCount > 0 && !waveformData.empty (); }
60+ bool isFullyCached () const { return isValid () && waveformQueried; }
5961 bool isThreeBandWaveform () const { return waveformBytesPerEntry == 3 ; }
6062};
6163
@@ -129,13 +131,16 @@ class DbServerClient : private juce::Thread
129131 + " ourPlayer=" + juce::String (ourPlayer)
130132 + " model=" + playerModel);
131133
132- // Check cache first -- avoid unnecessary requests
134+ // Check cache first -- avoid unnecessary requests.
135+ // Use isFullyCached() so that entries with metadata but no waveform
136+ // (partial cache from an early query before CDJ was fully ready)
137+ // are re-requested to fetch the missing waveform data.
133138 uint64_t cacheKey = makeCacheKey (playerIP, trackId);
134139 {
135140 const juce::SpinLock::ScopedLockType lock (cacheLock);
136141 auto it = metadataCache.find (cacheKey);
137- if (it != metadataCache.end () && it->second .isValid ())
138- return ; // already cached
142+ if (it != metadataCache.end () && it->second .isFullyCached ())
143+ return ; // fully cached (metadata + waveform attempted)
139144 }
140145
141146 // Enqueue request (producer lock: any thread may call this)
@@ -1337,54 +1342,90 @@ class DbServerClient : private juce::Thread
13371342
13381343 if (req.trackId != 0 )
13391344 {
1340- // Metadata request
1341- auto meta = queryTrackMetadata (*conn, req.slot , req.trackType ,
1342- req.trackId , req.ourPlayer );
1343- if (meta.isValid ())
1345+ // Check if metadata text is already cached (partial cache --
1346+ // metadata succeeded on a previous request but waveform or
1347+ // artwork may still be missing).
1348+ uint64_t cacheKey = makeCacheKey (req.playerIP , req.trackId );
1349+ bool metaAlreadyCached = false ;
1350+ TrackMetadata meta;
13441351 {
1345- cacheMetadata (req.playerIP , meta);
1346- DBG (" DbServerClient: cached metadata for track "
1347- + juce::String (req.trackId ) + " -- \" "
1348- + meta.artist + " - " + meta.title + " \" " );
1352+ const juce::SpinLock::ScopedLockType lock (cacheLock);
1353+ auto it = metadataCache.find (cacheKey);
1354+ if (it != metadataCache.end () && it->second .isValid ())
1355+ {
1356+ metaAlreadyCached = true ;
1357+ meta = it->second ;
1358+ }
1359+ }
1360+
1361+ if (!metaAlreadyCached)
1362+ {
1363+ // First time: query metadata from CDJ dbserver
1364+ meta = queryTrackMetadata (*conn, req.slot , req.trackType ,
1365+ req.trackId , req.ourPlayer );
1366+ if (meta.isValid ())
1367+ {
1368+ cacheMetadata (req.playerIP , meta);
1369+ DBG (" DbServerClient: cached metadata for track "
1370+ + juce::String (req.trackId ) + " -- \" "
1371+ + meta.artist + " - " + meta.title + " \" " );
1372+ }
1373+ else
1374+ {
1375+ errorCount.fetch_add (1 , std::memory_order_relaxed);
1376+ DBG (" DbServerClient: metadata query failed for trackId="
1377+ + juce::String (req.trackId ));
1378+ if (!conn->isConnected ())
1379+ conn->close ();
1380+ return ;
1381+ }
1382+ }
13491383
1350- // Request artwork if available
1351- if (req.wantArt && meta.artworkId != 0 )
1384+ // Artwork: fetch if not yet cached
1385+ if (req.wantArt && meta.artworkId != 0 )
1386+ {
1387+ bool artCached;
1388+ {
1389+ const juce::SpinLock::ScopedLockType lock (artCacheLock);
1390+ artCached = artworkCache.count (meta.artworkId ) > 0 ;
1391+ }
1392+ if (!artCached)
13521393 {
13531394 auto img = queryArtwork (*conn, req.slot , req.trackType ,
13541395 meta.artworkId , req.ourPlayer );
13551396 if (img.isValid ())
13561397 cacheArtwork (meta.artworkId , img);
13571398 }
1399+ }
13581400
1359- // Request preview waveform (PWV6 for CDJ-3000, PWV4 for NXS2)
1360- if (req.wantWaveform )
1401+ // Waveform: fetch if not yet cached
1402+ if (req.wantWaveform && !meta.hasWaveform ())
1403+ {
1404+ auto wfResult = queryPreviewWaveform (
1405+ *conn, req.slot , req.trackType , req.trackId ,
1406+ req.ourPlayer , req.playerModel );
1407+ if (wfResult.entryCount > 0 )
13611408 {
1362- auto wfResult = queryPreviewWaveform (
1363- *conn, req.slot , req.trackType , req.trackId ,
1364- req.ourPlayer , req.playerModel );
1365- if (wfResult.entryCount > 0 )
1409+ const juce::SpinLock::ScopedLockType lock (cacheLock);
1410+ auto it = metadataCache.find (cacheKey);
1411+ if (it != metadataCache.end ())
13661412 {
1367- uint64_t key = makeCacheKey (req.playerIP , req.trackId );
1368- const juce::SpinLock::ScopedLockType lock (cacheLock);
1369- auto it = metadataCache.find (key);
1370- if (it != metadataCache.end ())
1371- {
1372- it->second .waveformData = std::move (wfResult.data );
1373- it->second .waveformEntryCount = wfResult.entryCount ;
1374- it->second .waveformBytesPerEntry =
1375- (wfResult.format == WaveformFormat::ThreeBand) ? 3 : 6 ;
1376- }
1413+ it->second .waveformData = std::move (wfResult.data );
1414+ it->second .waveformEntryCount = wfResult.entryCount ;
1415+ it->second .waveformBytesPerEntry =
1416+ (wfResult.format == WaveformFormat::ThreeBand) ? 3 : 6 ;
13771417 }
13781418 }
13791419 }
1380- else
1420+
1421+ // Mark waveform as attempted so isFullyCached() returns true
1422+ // and we don't re-request indefinitely for tracks without
1423+ // waveform data (e.g. non-rekordbox media).
13811424 {
1382- errorCount.fetch_add (1 , std::memory_order_relaxed);
1383- DBG (" DbServerClient: metadata query failed for trackId="
1384- + juce::String (req.trackId ));
1385- // Connection may be stale -- close so next request reconnects
1386- if (!conn->isConnected ())
1387- conn->close ();
1425+ const juce::SpinLock::ScopedLockType lock (cacheLock);
1426+ auto it = metadataCache.find (cacheKey);
1427+ if (it != metadataCache.end ())
1428+ it->second .waveformQueried = true ;
13881429 }
13891430 }
13901431 else if (req.artworkId != 0 )
0 commit comments