fix: improve extension download reliability and Qobuz API integration

- Add dedicated long-timeout download client (24h) for extension file downloads,
  preventing timeouts on large lossless audio files
- Skip unnecessary SongLink Deezer prelookup when an extension download provider
  handles the track, reducing latency and avoiding spurious API failures
- Prefer native track ID over Spotify ID when a source/provider is set, ensuring
  extension providers receive their own IDs correctly
- Update Qobuz MusicDL API endpoint and switch payload URL to open.qobuz.com
- Extract buildQobuzMusicDLPayload helper and add test coverage
This commit is contained in:
zarzet
2026-03-18 01:06:22 +07:00
parent 855d0e3ffc
commit 75db2f162b
6 changed files with 86 additions and 29 deletions
+15 -2
View File
@@ -3594,6 +3594,15 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
String? genre;
String? label;
String? copyright;
final extensionState = ref.read(extensionProvider);
final selectedExtensionDownloadProvider =
settings.useExtensionProviders &&
extensionState.extensions.any(
(e) =>
e.enabled &&
e.hasDownloadProvider &&
e.id.toLowerCase() == item.service.toLowerCase(),
);
String? deezerTrackId = trackToDownload.deezerId;
if (deezerTrackId == null && trackToDownload.id.startsWith('deezer:')) {
@@ -3628,7 +3637,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
// Fallback: Use SongLink to convert Spotify ID to Deezer ID
if (deezerTrackId == null &&
if (!selectedExtensionDownloadProvider &&
deezerTrackId == null &&
trackToDownload.id.isNotEmpty &&
!trackToDownload.id.startsWith('deezer:') &&
!trackToDownload.id.startsWith('extension:')) {
@@ -3729,6 +3739,10 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
if (shouldAbortWork('during SongLink availability lookup')) {
return;
}
} else if (selectedExtensionDownloadProvider && deezerTrackId == null) {
_log.d(
'Skipping Flutter SongLink Deezer prelookup for extension provider: ${item.service}',
);
}
if (deezerTrackId != null && deezerTrackId.isNotEmpty) {
@@ -3756,7 +3770,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
Map<String, dynamic> result;
final extensionState = ref.read(extensionProvider);
final hasActiveExtensions = extensionState.extensions.any(
(e) => e.enabled,
);
+9 -5
View File
@@ -958,9 +958,16 @@ class TrackNotifier extends Notifier<TrackState> {
final durationMs = _extractDurationMs(data);
final itemType = data['item_type']?.toString();
final effectiveSource =
source ?? data['source']?.toString() ?? data['provider_id']?.toString();
final spotifyId = (data['spotify_id'] ?? '').toString();
final nativeId = (data['id'] ?? '').toString();
final preferredId = effectiveSource != null && effectiveSource.isNotEmpty
? (nativeId.isNotEmpty ? nativeId : spotifyId)
: (spotifyId.isNotEmpty ? spotifyId : nativeId);
return Track(
id: (data['spotify_id'] ?? data['id'] ?? '').toString(),
id: preferredId,
name: (data['name'] ?? '').toString(),
artistName: (data['artists'] ?? data['artist'] ?? '').toString(),
albumName: (data['album_name'] ?? data['album'] ?? '').toString(),
@@ -974,10 +981,7 @@ class TrackNotifier extends Notifier<TrackState> {
discNumber: data['disc_number'] as int?,
releaseDate: data['release_date']?.toString(),
totalTracks: data['total_tracks'] as int?,
source:
source ??
data['source']?.toString() ??
data['provider_id']?.toString(),
source: effectiveSource,
albumType: data['album_type']?.toString(),
itemType: itemType,
);