mirror of
https://github.com/markuryy/shark.git
synced 2025-12-13 03:51:02 +00:00
fix(wip): add fallback format support for unavailable tracks
Add support for selecting a fallback audio format if the requested track format is unavailable. Users can now choose to fall back to MP3_320, MP3_128, or the highest available format, or opt to fail if the requested format is not found. The queue manager and downloader now fetch fresh track data just-in-time, handle dz fallback parameters, and ensure the correct track ID is used for decryption. Settings UI and store are updated to allow configuring the fallback format.
This commit is contained in:
@@ -142,6 +142,13 @@ export class DeezerAPI {
|
||||
const errorObj = resultJson.error as any;
|
||||
console.error(`[ERROR] API returned error for ${method}:`, errorStr);
|
||||
|
||||
// Handle fallback parameters (Deezer provides alternative parameters to retry with)
|
||||
if ((resultJson as any).payload?.FALLBACK) {
|
||||
console.log(`[DEBUG] Using FALLBACK parameters for ${method}:`, (resultJson as any).payload.FALLBACK);
|
||||
const fallbackArgs = { ...args, ...(resultJson as any).payload.FALLBACK };
|
||||
return this.apiCall(method, fallbackArgs, params, retryCount);
|
||||
}
|
||||
|
||||
// Handle rate limiting (error codes 4 and 700) - wait 5 seconds and retry
|
||||
if (errorObj.code && [4, 700].includes(errorObj.code)) {
|
||||
console.log(`[DEBUG] Rate limited (code ${errorObj.code}), waiting 5s before retry...`);
|
||||
@@ -250,6 +257,29 @@ export class DeezerAPI {
|
||||
return this.apiCall('song.getData', { SNG_ID: trackId });
|
||||
}
|
||||
|
||||
// Get track page data (includes more complete track token info)
|
||||
async getTrackPage(trackId: string): Promise<any> {
|
||||
return this.apiCall('deezer.pageTrack', { SNG_ID: trackId });
|
||||
}
|
||||
|
||||
// Get track with fallback (tries pageTrack first, then getData)
|
||||
async getTrackWithFallback(trackId: string): Promise<any> {
|
||||
try {
|
||||
const pageData = await this.getTrackPage(trackId);
|
||||
if (pageData && pageData.DATA) {
|
||||
console.log(`[DEBUG] pageTrack returned for ${trackId}:`, {
|
||||
FILESIZE_FLAC: pageData.DATA.FILESIZE_FLAC,
|
||||
FALLBACK: pageData.DATA.FALLBACK,
|
||||
SNG_ID: pageData.DATA.SNG_ID
|
||||
});
|
||||
return pageData.DATA;
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`[DEBUG] pageTrack failed for ${trackId}, falling back to getData`);
|
||||
}
|
||||
return this.getTrack(trackId);
|
||||
}
|
||||
|
||||
// Search tracks using public API (no authentication required)
|
||||
async searchTracks(query: string, limit: number = 25): Promise<any> {
|
||||
const url = `https://api.deezer.com/search/track?q=${encodeURIComponent(query)}&limit=${limit}`;
|
||||
@@ -412,8 +442,8 @@ export class DeezerAPI {
|
||||
console.log('[DEBUG] Getting track download URL...', { trackToken, format, licenseToken });
|
||||
|
||||
try {
|
||||
// media.deezer.com ONLY needs arl cookie, not sid or other cookies
|
||||
const cookieHeader = this.arl ? `arl=${this.arl}` : '';
|
||||
// Use all cookies, not just arl - sid (session ID) is also required
|
||||
const cookieHeader = this.getCookieHeader();
|
||||
console.log('[DEBUG] Sending request to media.deezer.com with cookies:', cookieHeader);
|
||||
|
||||
const response = await fetch('https://media.deezer.com/v1/get_url', {
|
||||
@@ -444,6 +474,15 @@ export class DeezerAPI {
|
||||
|
||||
if (result.data && result.data.length > 0) {
|
||||
const trackData = result.data[0];
|
||||
|
||||
// Check for errors in the response
|
||||
if (trackData.errors && trackData.errors.length > 0) {
|
||||
const error = trackData.errors[0];
|
||||
console.error('[ERROR] Deezer media API error:', error);
|
||||
// Return null to trigger fallback, don't throw - let caller handle it
|
||||
return null;
|
||||
}
|
||||
|
||||
if (trackData.media && trackData.media.length > 0) {
|
||||
const url = trackData.media[0].sources[0].url;
|
||||
console.log('[DEBUG] Got download URL:', url);
|
||||
|
||||
Reference in New Issue
Block a user