refactor: abstract built-in providers into generic registry and unify platform bridge API

Replace hardcoded Tidal/Qobuz switch/case with builtInProviderSpec registry
pattern. Unify searchTidalAll/searchQobuzAll into searchProviderAll,
getDeezerMetadata/getTidalMetadata/getQobuzMetadata into getProviderMetadata,
and parseDeezerUrl/parseQobuzUrl/parseTidalUrl into parseProviderUrl. Remove
extension-specific getAlbum/Playlist/ArtistWithExtension in favor of generic
getProviderMetadata routing. Extract provider UI helpers into
provider_ui_utils.dart. Preserve track_number fallback for zero-value
TrackNumber in album/playlist track lists.
This commit is contained in:
zarzet
2026-04-17 03:59:02 +07:00
parent e87f7a1177
commit 6895e45f2c
21 changed files with 1180 additions and 1365 deletions
+7 -4
View File
@@ -2575,9 +2575,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final providerTrackId = track.id.substring(colonIdx + 1);
_log.d('No ISRC, fetching from $provider API: $providerTrackId');
final providerData = provider == 'tidal'
? await PlatformBridge.getTidalMetadata('track', providerTrackId)
: await PlatformBridge.getQobuzMetadata('track', providerTrackId);
final providerData = await PlatformBridge.getProviderMetadata(
provider,
'track',
providerTrackId,
);
final trackData = providerData['track'] as Map<String, dynamic>?;
if (trackData == null) {
@@ -4448,7 +4450,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
);
final rawId = trackToDownload.id.split(':')[1];
_log.d('Fetching full metadata for Deezer ID: $rawId');
final fullData = await PlatformBridge.getDeezerMetadata(
final fullData = await PlatformBridge.getProviderMetadata(
'deezer',
'track',
rawId,
);
+149 -9
View File
@@ -15,6 +15,44 @@ const _metadataProviderPriorityKey = 'metadata_provider_priority';
const _providerPriorityKey = 'provider_priority';
const _spotifyWebExtensionId = 'spotify-web';
class BuiltInProviderSpec {
final String id;
final String displayName;
final bool supportsMetadata;
final bool supportsDownload;
final bool supportsSearch;
const BuiltInProviderSpec({
required this.id,
required this.displayName,
this.supportsMetadata = false,
this.supportsDownload = false,
this.supportsSearch = false,
});
factory BuiltInProviderSpec.fromJson(Map<String, dynamic> json) {
return BuiltInProviderSpec(
id: json['id'] as String? ?? '',
displayName:
json['display_name'] as String? ??
json['displayName'] as String? ??
'',
supportsMetadata: json['supports_metadata'] as bool? ?? false,
supportsDownload: json['supports_download'] as bool? ?? false,
supportsSearch: json['supports_search'] as bool? ?? false,
);
}
}
List<BuiltInProviderSpec> _builtInProviderRegistry = const [];
List<BuiltInProviderSpec> get builtInProviderSpecs =>
List<BuiltInProviderSpec>.unmodifiable(_builtInProviderRegistry);
void _replaceBuiltInProviderRegistry(List<BuiltInProviderSpec> providers) {
_builtInProviderRegistry = List<BuiltInProviderSpec>.unmodifiable(providers);
}
class Extension {
final String id;
final String name;
@@ -195,6 +233,83 @@ class Extension {
}
}
BuiltInProviderSpec? builtInProviderSpecForId(String? providerId) {
if (providerId == null) return null;
for (final provider in builtInProviderSpecs) {
if (provider.id == providerId) {
return provider;
}
}
return null;
}
List<BuiltInProviderSpec> _builtInProvidersWhere(
bool Function(BuiltInProviderSpec provider) predicate,
) {
return List<BuiltInProviderSpec>.unmodifiable(
builtInProviderSpecs.where(predicate),
);
}
List<BuiltInProviderSpec> get builtInSearchProviderSpecs =>
_builtInProvidersWhere((provider) => provider.supportsSearch);
List<BuiltInProviderSpec> get builtInMetadataProviderSpecs =>
_builtInProvidersWhere((provider) => provider.supportsMetadata);
List<BuiltInProviderSpec> get builtInDownloadProviderSpecs =>
_builtInProvidersWhere((provider) => provider.supportsDownload);
List<String> get builtInSearchProviderIds => List<String>.unmodifiable(
builtInSearchProviderSpecs.map((provider) => provider.id),
);
List<String> get builtInMetadataProviderIds => List<String>.unmodifiable(
builtInMetadataProviderSpecs.map((provider) => provider.id),
);
List<String> get builtInDownloadProviderIds => List<String>.unmodifiable(
builtInDownloadProviderSpecs.map((provider) => provider.id),
);
bool isBuiltInSearchProvider(String? providerId) =>
builtInProviderSpecForId(providerId)?.supportsSearch ?? false;
bool isBuiltInMetadataProvider(String? providerId) =>
builtInProviderSpecForId(providerId)?.supportsMetadata ?? false;
bool isBuiltInDownloadProvider(String? providerId) =>
builtInProviderSpecForId(providerId)?.supportsDownload ?? false;
String? get defaultBuiltInSearchProviderId => builtInSearchProviderSpecs.isEmpty
? null
: builtInSearchProviderSpecs.first.id;
String? get defaultBuiltInSearchProviderDisplayName =>
builtInSearchProviderSpecs.isEmpty
? null
: builtInSearchProviderSpecs.first.displayName;
String resolveProviderDisplayName(
String providerId, {
Iterable<Extension> extensions = const [],
}) {
final builtIn = builtInProviderSpecForId(providerId);
if (builtIn != null) {
return builtIn.displayName;
}
for (final extension in extensions) {
if (extension.id == providerId) {
return extension.displayName;
}
}
return providerId;
}
class SearchFilter {
final String id;
final String? label;
@@ -460,6 +575,7 @@ class ExtensionSetting {
class ExtensionState {
final List<Extension> extensions;
final List<BuiltInProviderSpec> builtInProviders;
final List<String> providerPriority;
final List<String> metadataProviderPriority;
final bool isLoading;
@@ -468,6 +584,7 @@ class ExtensionState {
const ExtensionState({
this.extensions = const [],
this.builtInProviders = const [],
this.providerPriority = const [],
this.metadataProviderPriority = const [],
this.isLoading = false,
@@ -477,6 +594,7 @@ class ExtensionState {
ExtensionState copyWith({
List<Extension>? extensions,
List<BuiltInProviderSpec>? builtInProviders,
List<String>? providerPriority,
List<String>? metadataProviderPriority,
bool? isLoading,
@@ -485,6 +603,7 @@ class ExtensionState {
}) {
return ExtensionState(
extensions: extensions ?? this.extensions,
builtInProviders: builtInProviders ?? this.builtInProviders,
providerPriority: providerPriority ?? this.providerPriority,
metadataProviderPriority:
metadataProviderPriority ?? this.metadataProviderPriority,
@@ -496,7 +615,6 @@ class ExtensionState {
}
class ExtensionNotifier extends Notifier<ExtensionState> {
static const _builtInMetadataProviders = ['qobuz', 'tidal'];
AppLifecycleListener? _appLifecycleListener;
bool _cleanupInFlight = false;
Completer<void>? _initializationCompleter;
@@ -547,6 +665,12 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
state = state.copyWith(isLoading: true, error: null);
try {
await refreshBuiltInProviders();
} catch (e) {
_log.w('Failed to refresh built-in providers before init: $e');
}
if (!PlatformBridge.supportsExtensionSystem) {
state = state.copyWith(
isInitialized: true,
@@ -634,6 +758,16 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
Future<void> refreshBuiltInProviders() async {
final list = await PlatformBridge.getBuiltInProviders();
final providers = list
.map((e) => BuiltInProviderSpec.fromJson(e))
.where((provider) => provider.id.isNotEmpty)
.toList();
_replaceBuiltInProviderRegistry(providers);
state = state.copyWith(builtInProviders: providers);
}
void clearError() {
state = state.copyWith(error: null);
}
@@ -727,10 +861,16 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
if (ext.hasDownloadProvider && settings.defaultService == extensionId) {
ref.read(settingsProvider.notifier).setDefaultService('tidal');
_log.d(
'Reset default service to Tidal because extension $extensionId was disabled',
);
final availableProviders = getAllDownloadProviders();
if (availableProviders.isNotEmpty) {
final fallbackService = availableProviders.first;
ref
.read(settingsProvider.notifier)
.setDefaultService(fallbackService);
_log.d(
'Reset default service to $fallbackService because extension $extensionId was disabled',
);
}
}
}
} catch (e) {
@@ -973,7 +1113,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
List<String> getAllDownloadProviders() {
final providers = ['tidal', 'qobuz'];
final providers = List<String>.from(builtInDownloadProviderIds);
for (final ext in state.extensions) {
if (ext.enabled && ext.hasDownloadProvider) {
providers.add(ext.id);
@@ -995,7 +1135,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
return [
...primarySearchMetadataExtensions,
..._builtInMetadataProviders,
...builtInMetadataProviderIds,
...otherMetadataExtensions,
];
}
@@ -1012,10 +1152,10 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
final hasPreferredExtension = preferredOrder.any(
(provider) => !_builtInMetadataProviders.contains(provider),
(provider) => !isBuiltInMetadataProvider(provider),
);
final hasSavedExtension = result.any(
(provider) => !_builtInMetadataProviders.contains(provider),
(provider) => !isBuiltInMetadataProvider(provider),
);
if (!hasSavedExtension && hasPreferredExtension) {
+178 -259
View File
@@ -353,212 +353,12 @@ class TrackNotifier extends Notifier<TrackState> {
}
}
if (url.contains('deezer.com') || url.contains('deezer.page.link')) {
_log.i('Detected Deezer URL, parsing...');
final parsed = await PlatformBridge.parseDeezerUrl(url);
if (!_isRequestValid(requestId)) return;
final type = parsed['type'] as String;
final id = parsed['id'] as String;
final metadata = await PlatformBridge.getDeezerMetadata(type, id);
if (!_isRequestValid(requestId)) return;
if (type == 'track') {
final trackData = metadata['track'] as Map<String, dynamic>;
final track = _parseTrack(trackData);
state = TrackState(
tracks: [track],
isLoading: false,
coverUrl: track.coverUrl,
);
} else if (type == 'album') {
final albumInfo = metadata['album_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: tracks,
isLoading: false,
albumId: id,
albumName: albumInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()),
);
_preWarmCacheForTracks(tracks);
} else if (type == 'playlist') {
final playlistInfo =
metadata['playlist_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: tracks,
isLoading: false,
playlistName: playlistInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(
playlistInfo['images']?.toString(),
),
);
_preWarmCacheForTracks(tracks);
} else if (type == 'artist') {
final artistInfo = metadata['artist_info'] as Map<String, dynamic>;
final albumsList = metadata['albums'] as List<dynamic>;
final albums = albumsList
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: [],
isLoading: false,
artistId: artistInfo['id'] as String?,
artistName: artistInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(artistInfo['images']?.toString()),
artistAlbums: albums,
);
}
return;
}
if (url.contains('qobuz.com') || url.startsWith('qobuzapp://')) {
_log.i('Detected Qobuz URL, parsing...');
final parsed = await PlatformBridge.parseQobuzUrl(url);
if (!_isRequestValid(requestId)) return;
final type = parsed['type'] as String;
final id = parsed['id'] as String;
final metadata = await PlatformBridge.getQobuzMetadata(type, id);
if (!_isRequestValid(requestId)) return;
if (type == 'track') {
final trackData = metadata['track'] as Map<String, dynamic>;
final track = _parseTrack(trackData);
state = TrackState(
tracks: [track],
isLoading: false,
coverUrl: track.coverUrl,
);
} else if (type == 'album') {
final albumInfo = metadata['album_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: tracks,
isLoading: false,
albumId: 'qobuz:$id',
albumName: albumInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()),
);
_preWarmCacheForTracks(tracks);
} else if (type == 'playlist') {
final playlistInfo =
metadata['playlist_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
final owner = playlistInfo['owner'] as Map<String, dynamic>?;
final playlistName =
(playlistInfo['name'] ?? owner?['name']) as String?;
final coverUrl = normalizeRemoteHttpUrl(
(playlistInfo['images'] ?? owner?['images'])?.toString(),
);
state = TrackState(
tracks: tracks,
isLoading: false,
playlistName: playlistName,
coverUrl: coverUrl,
);
_preWarmCacheForTracks(tracks);
} else if (type == 'artist') {
final artistInfo = metadata['artist_info'] as Map<String, dynamic>;
final albumsList = metadata['albums'] as List<dynamic>;
final albums = albumsList
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: [],
isLoading: false,
artistId: artistInfo['id'] as String?,
artistName: artistInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(artistInfo['images']?.toString()),
artistAlbums: albums,
);
}
return;
}
if (url.contains('tidal.com')) {
_log.i('Detected Tidal URL, parsing...');
final parsed = await PlatformBridge.parseTidalUrl(url);
if (!_isRequestValid(requestId)) return;
final type = parsed['type'] as String;
final id = parsed['id'] as String;
final metadata = await PlatformBridge.getTidalMetadata(type, id);
if (!_isRequestValid(requestId)) return;
if (type == 'track') {
final trackData = metadata['track'] as Map<String, dynamic>;
final track = _parseTrack(trackData);
state = TrackState(
tracks: [track],
isLoading: false,
coverUrl: track.coverUrl,
);
} else if (type == 'album') {
final albumInfo = metadata['album_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: tracks,
isLoading: false,
albumId: 'tidal:$id',
albumName: albumInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()),
);
_preWarmCacheForTracks(tracks);
} else if (type == 'playlist') {
final playlistInfo =
metadata['playlist_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
final owner = playlistInfo['owner'] as Map<String, dynamic>?;
final playlistName =
(playlistInfo['name'] ?? owner?['name']) as String?;
final coverUrl = normalizeRemoteHttpUrl(
(playlistInfo['images'] ?? owner?['images'])?.toString(),
);
state = TrackState(
tracks: tracks,
isLoading: false,
playlistName: playlistName,
coverUrl: coverUrl,
);
_preWarmCacheForTracks(tracks);
} else if (type == 'artist') {
final artistInfo = metadata['artist_info'] as Map<String, dynamic>;
final albumsList = metadata['albums'] as List<dynamic>;
final albums = albumsList
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: [],
isLoading: false,
artistId: artistInfo['id'] as String?,
artistName: artistInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(artistInfo['images']?.toString()),
artistAlbums: albums,
);
}
final handledBuiltInUrl = await _tryResolveBuiltInProviderUrl(
url,
requestId,
);
if (!_isRequestValid(requestId)) return;
if (handledBuiltInUrl) {
return;
}
@@ -577,6 +377,134 @@ class TrackNotifier extends Notifier<TrackState> {
}
}
Future<bool> _tryResolveBuiltInProviderUrl(String url, int requestId) async {
Map<String, dynamic> parsed;
try {
parsed = await PlatformBridge.parseProviderUrl(url);
} catch (_) {
return false;
}
if (!_isRequestValid(requestId)) return true;
final providerId = parsed['provider_id']?.toString();
final type = parsed['type']?.toString();
final id = parsed['id']?.toString();
if (providerId == null ||
providerId.isEmpty ||
type == null ||
type.isEmpty ||
id == null ||
id.isEmpty) {
return false;
}
_log.i('Detected built-in provider URL: $providerId:$type:$id');
final metadata = await _getResolvedProviderMetadata(providerId, type, id);
if (!_isRequestValid(requestId)) return true;
_applyResolvedProviderMetadata(providerId, type, id, metadata);
return true;
}
Future<Map<String, dynamic>> _getResolvedProviderMetadata(
String providerId,
String resourceType,
String resourceId,
) async {
return PlatformBridge.getProviderMetadata(
providerId,
resourceType,
resourceId,
);
}
void _applyResolvedProviderMetadata(
String providerId,
String resourceType,
String resourceId,
Map<String, dynamic> metadata,
) {
switch (resourceType) {
case 'track':
final trackData = metadata['track'] as Map<String, dynamic>;
final track = _parseTrack(trackData);
state = TrackState(
tracks: [track],
isLoading: false,
coverUrl: track.coverUrl,
);
return;
case 'album':
final albumInfo = metadata['album_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: tracks,
isLoading: false,
albumId: _buildResolvedAlbumId(providerId, resourceId),
albumName: albumInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()),
);
_preWarmCacheForTracks(tracks);
return;
case 'playlist':
final playlistInfo = metadata['playlist_info'] as Map<String, dynamic>;
final trackList = metadata['track_list'] as List<dynamic>;
final tracks = trackList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
final owner = playlistInfo['owner'] as Map<String, dynamic>?;
final playlistName =
(playlistInfo['name'] ?? owner?['name']) as String?;
final coverUrl = normalizeRemoteHttpUrl(
(playlistInfo['images'] ?? owner?['images'])?.toString(),
);
state = TrackState(
tracks: tracks,
isLoading: false,
playlistName: playlistName,
coverUrl: coverUrl,
);
_preWarmCacheForTracks(tracks);
return;
case 'artist':
final artistInfo = metadata['artist_info'] as Map<String, dynamic>;
final albumsList = metadata['albums'] as List<dynamic>;
final albums = albumsList
.map((a) => _parseArtistAlbum(a as Map<String, dynamic>))
.toList();
final topTracksList = metadata['top_tracks'] as List<dynamic>? ?? [];
final topTracks = topTracksList
.map((t) => _parseTrack(t as Map<String, dynamic>))
.toList();
state = TrackState(
tracks: [],
isLoading: false,
artistId: artistInfo['id'] as String?,
artistName: artistInfo['name'] as String?,
coverUrl: normalizeRemoteHttpUrl(artistInfo['images']?.toString()),
headerImageUrl: normalizeRemoteHttpUrl(
(artistInfo['header_image'] ?? artistInfo['cover_url'])?.toString(),
),
monthlyListeners: artistInfo['listeners'] as int?,
artistAlbums: albums,
artistTopTracks: topTracks.isNotEmpty ? topTracks : null,
);
return;
}
}
String _buildResolvedAlbumId(String providerId, String resourceId) {
if (providerId == 'deezer') {
return resourceId;
}
return '$providerId:$resourceId';
}
Future<void> search(
String query, {
String? filterOverride,
@@ -609,19 +537,15 @@ class TrackNotifier extends Notifier<TrackState> {
.map((ext) => ext.id)
.firstOrNull;
}
resolvedProvider ??= 'tidal';
resolvedProvider ??= defaultBuiltInSearchProviderId;
}
final isEnabledExtensionProvider =
if (resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
extensionState.extensions.any(
!isBuiltInSearchProvider(resolvedProvider) &&
!extensionState.extensions.any(
(ext) => ext.enabled && ext.id == resolvedProvider,
);
if (resolvedProvider.isNotEmpty &&
resolvedProvider != 'tidal' &&
resolvedProvider != 'qobuz' &&
!isEnabledExtensionProvider &&
) &&
settings.searchProvider?.trim() == resolvedProvider) {
ref.read(settingsProvider.notifier).setSearchProvider(null);
resolvedProvider =
@@ -638,15 +562,21 @@ class TrackNotifier extends Notifier<TrackState> {
.where((ext) => ext.enabled && ext.hasCustomSearch)
.map((ext) => ext.id)
.firstOrNull;
resolvedProvider ??= 'tidal';
resolvedProvider ??= defaultBuiltInSearchProviderId;
}
if (resolvedProvider.isNotEmpty &&
resolvedProvider != 'tidal' &&
resolvedProvider != 'qobuz' &&
final isEnabledExtensionProvider =
resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
extensionState.extensions.any(
(ext) => ext.enabled && ext.id == resolvedProvider,
)) {
);
final isBuiltInProvider = isBuiltInSearchProvider(resolvedProvider);
if (resolvedProvider != null &&
resolvedProvider.isNotEmpty &&
!isBuiltInProvider &&
isEnabledExtensionProvider) {
final resolvedFilter = requestFilter ?? 'track';
Map<String, dynamic>? options;
options = {'filter': resolvedFilter};
@@ -659,12 +589,12 @@ class TrackNotifier extends Notifier<TrackState> {
return;
}
final effectiveBuiltInProvider =
resolvedProvider == 'tidal' || resolvedProvider == 'qobuz'
final fallbackBuiltInProvider = builtInSearchProvider?.isNotEmpty == true
? builtInSearchProvider
: defaultBuiltInSearchProviderId;
final effectiveBuiltInProvider = isBuiltInProvider
? resolvedProvider
: (builtInSearchProvider?.isNotEmpty == true
? builtInSearchProvider
: 'tidal');
: fallbackBuiltInProvider;
if (effectiveBuiltInProvider == null || effectiveBuiltInProvider.isEmpty) {
state = TrackState(
@@ -700,40 +630,29 @@ class TrackNotifier extends Notifier<TrackState> {
Map<String, dynamic> results;
List<Map<String, dynamic>> metadataTrackResults = [];
switch (effectiveProvider) {
case 'tidal':
_log.d('Calling Tidal search API...');
results = await PlatformBridge.searchTidalAll(
query,
trackLimit: 20,
artistLimit: 2,
filter: requestFilter,
);
break;
case 'qobuz':
_log.d('Calling Qobuz search API...');
results = await PlatformBridge.searchQobuzAll(
query,
trackLimit: 20,
artistLimit: 2,
filter: requestFilter,
);
break;
default:
_log.d('Calling metadata provider track search API...');
metadataTrackResults =
await PlatformBridge.searchTracksWithMetadataProviders(
query,
limit: 20,
includeExtensions: includeExtensions,
);
results = const <String, List<dynamic>>{
'tracks': <dynamic>[],
'artists': <dynamic>[],
'albums': <dynamic>[],
'playlists': <dynamic>[],
};
break;
if (isBuiltInSearchProvider(effectiveProvider)) {
_log.d('Calling built-in search API for $effectiveProvider...');
results = await PlatformBridge.searchProviderAll(
effectiveProvider,
query,
trackLimit: 20,
artistLimit: 2,
filter: requestFilter,
);
} else {
_log.d('Calling metadata provider track search API...');
metadataTrackResults =
await PlatformBridge.searchTracksWithMetadataProviders(
query,
limit: 20,
includeExtensions: includeExtensions,
);
results = const <String, List<dynamic>>{
'tracks': <dynamic>[],
'artists': <dynamic>[],
'albums': <dynamic>[],
'playlists': <dynamic>[],
};
}
_log.i(
'$effectiveProvider returned ${(results['tracks'] as List?)?.length ?? 0} tracks, ${(results['artists'] as List?)?.length ?? 0} artists, ${(results['albums'] as List?)?.length ?? 0} albums',