search method
- String query, {
- SearchFilter? filter,
- String? scope,
- int limit = 20,
- bool ignoreSpelling = false,
Search YouTube music.
Returns results within the provided category.
queryQuery string, i.e. 'Oasis Wonderwall'.filterFilter for item types. (Default: Default search, including all types of items).scopeSearch scope. Allowed values:library,uploads. (Default: Search the public YouTube Music catalogue). Changing scope from the default will reduce the number of settable filters. Setting a filter that is not permitted will throw an exception. Foruploads, no filter can be set. Forlibrary,community_playlistsandfeatured_playlistsfilter cannot be set.limitNumber of search results to return. (Default:20).ignoreSpellingWhether to ignore YTM spelling suggestions. Iftrue, the exact search term will be searched for, and will not be corrected. This does not have any effect when the filter is set touploads. (Default:false, will use YTM's default behavior of autocorrecting the search.
Returns List of results depending on filter.
resultTypespecifies the type of item (important for default search). Albums, artists and playlists additionally contain abrowseId, corresponding toalbumId,channelIdandplaylistId(browseId=VL+playlistId).
Example list for default search with one result per resultType for brevity.
Normally there are 3 results per resultType and an additional thumbnails key:
[
{
"category": "Top result",
"resultType": "video",
"videoId": "vU05Eksc_iM",
"title": "Wonderwall",
"artists": [
{
"name": "Oasis",
"id": "UCmMUZbaYdNH0bEd1PAlAqsA"
}
],
"views": "1.4M",
"videoType": "MUSIC_VIDEO_TYPE_OMV",
"duration": "4:38",
"duration_seconds": 278
},
{
"category": "Songs",
"resultType": "song",
"videoId": "ZrOKjDZOtkA",
"title": "Wonderwall",
"artists": [
{
"name": "Oasis",
"id": "UCmMUZbaYdNH0bEd1PAlAqsA"
}
],
"album": {
"name": "(What's The Story) Morning Glory? (Remastered)",
"id": "MPREb_9nqEki4ZDpp"
},
"duration": "4:19",
"duration_seconds": 259,
"isExplicit": false,
"feedbackTokens": {
"add": null,
"remove": null
}
},
{
"category": "Albums",
"resultType": "album",
"browseId": "MPREb_IInSY5QXXrW",
"playlistId": "OLAK5uy_kunInnOpcKECWIBQGB0Qj6ZjquxDvfckg",
"title": "(What's The Story) Morning Glory?",
"type": "Album",
"artist": "Oasis",
"year": "1995",
"isExplicit": false
},
{
"category": "Community playlists",
"resultType": "playlist",
"browseId": "VLPLK1PkWQlWtnNfovRdGWpKffO1Wdi2kvDx",
"title": "Wonderwall - Oasis",
"author": "Tate Henderson",
"itemCount": "174"
},
{
"category": "Videos",
"resultType": "video",
"videoId": "bx1Bh8ZvH84",
"title": "Wonderwall",
"artists": [
{
"name": "Oasis",
"id": "UCmMUZbaYdNH0bEd1PAlAqsA"
}
],
"views": "386M",
"duration": "4:38",
"duration_seconds": 278
},
{
"category": "Artists",
"resultType": "artist",
"browseId": "UCmMUZbaYdNH0bEd1PAlAqsA",
"artist": "Oasis",
"shuffleId": "RDAOkjHYJjL1a3xspEyVkhHAsg",
"radioId": "RDEMkjHYJjL1a3xspEyVkhHAsg"
},
{
"category": "Profiles",
"resultType": "profile",
"title": "Taylor Swift Time",
"name": "@TaylorSwiftTime",
"browseId": "UCSCRK7XlVQ6fBdEl00kX6pQ",
"thumbnails": ...
}
]
Implementation
Future<List> search(
String query, {
SearchFilter? filter,
String? scope,
int limit = 20,
bool ignoreSpelling = false,
}) async {
final body = <String, dynamic>{'query': query};
const endpoint = 'search';
final searchResults = <dynamic>[];
const filters = [
'albums',
'artists',
'playlists',
'community_playlists',
'featured_playlists',
'songs',
'videos',
'profiles',
'podcasts',
'episodes',
];
const scopes = ['library', 'uploads'];
if (scope != null && !scopes.contains(scope)) {
throw YTMusicUserError(
'Invalid scope provided. Please use one of the following scopes or leave out the parameter: '
'${scopes.join(', ')}',
);
}
if (scope == 'uploads' && filter != null) {
throw YTMusicUserError(
'No filter can be set when searching uploads. Please unset the filter parameter when scope is set to uploads.',
);
}
if (scope == 'library' &&
filter != null &&
(filter == SearchFilter.community_playlists ||
filter == SearchFilter.featured_playlists)) {
throw YTMusicUserError(
'$filter cannot be set when searching library. Please use one of the following filters or leave out the parameter: '
'${filters.sublist(0, 3).followedBy(filters.sublist(5)).join(', ')}',
);
}
final params = getSearchParams(filter, scope, ignoreSpelling);
if (params != null) {
body['params'] = params;
}
final response = await sendRequest(endpoint, body);
// no results
if (!response.containsKey('contents')) return searchResults;
dynamic results;
if ((response['contents'] as JsonMap).containsKey(
'tabbedSearchResultsRenderer',
)) {
final tabIndex =
(scope == null || filter == null) ? 0 : scopes.indexOf(scope) + 1;
results =
(((((response['contents'] as JsonMap)['tabbedSearchResultsRenderer']
as JsonMap)['tabs']
as List)[tabIndex]
as JsonMap)['tabRenderer']
as JsonMap)['content'];
} else {
results = response['contents'];
}
final sectionList = List<JsonMap>.from(nav(results, SECTION_LIST) as List);
// no results
if (sectionList.length == 1 &&
sectionList.first.containsKey('itemSectionRenderer')) {
return searchResults;
}
// set filter for parser
String? resultType;
final SearchFilter? realFilter;
if (filter != null &&
(filter == SearchFilter.playlists ||
filter == SearchFilter.featured_playlists ||
filter == SearchFilter.community_playlists)) {
realFilter = SearchFilter.playlists;
} else if (scope == 'uploads') {
realFilter = SearchFilter.uploads;
resultType = 'upload';
} else {
realFilter = filter;
}
for (final res in sectionList) {
String? category;
List<JsonMap> shelfContents;
if (res.containsKey('musicCardShelfRenderer')) {
final topResult = parseTopResult(
res['musicCardShelfRenderer'] as JsonMap,
parser.getSearchResultTypes(),
);
searchResults.add(topResult);
shelfContents = List<JsonMap>.from(
(nav(res, [
'musicCardShelfRenderer',
'contents',
], nullIfAbsent: true) ??
[])
as List,
);
if (shelfContents.isEmpty) continue;
// if "more from youtube" is present, remove it - it's not parseable
if (shelfContents.first.containsKey('messageRenderer')) {
category =
nav(shelfContents.removeAt(0), [
'messageRenderer',
...TEXT_RUN_TEXT,
])
as String?;
}
} else if (res.containsKey('musicShelfRenderer')) {
shelfContents = List<JsonMap>.from(
(res['musicShelfRenderer'] as JsonMap)['contents'] as List,
);
category =
nav(res, MUSIC_SHELF + TITLE_TEXT, nullIfAbsent: true) as String?;
// if we know the filter it's easy to set the result type
// unfortunately uploads is modeled as a filter (historical reasons),
// so we take care to not set the result type for that scope
if (realFilter != null && scope != 'uploads') {
resultType =
realFilter.name
.substring(0, realFilter.name.length - 1)
.toLowerCase();
}
} else {
continue;
}
searchResults.addAll(
parseSearchResults(
shelfContents,
resultType: resultType,
category: category,
),
);
if (realFilter != null) {
// if filter is set, there are continuations
Future<JsonMap> requestFunc(dynamic additionalParams) => sendRequest(
endpoint,
body,
additionalParams: additionalParams as String,
);
List parseFunc(contents) => parseSearchResults(
List<JsonMap>.from(contents as List),
resultType: resultType,
category: category,
);
searchResults.addAll(
await getContinuations(
res['musicShelfRenderer'] as JsonMap,
'musicShelfContinuation',
limit - searchResults.length,
requestFunc,
parseFunc,
),
);
}
}
return searchResults;
}