getWatchPlaylist method
Get a watch list of tracks. This watch playlist appears when you press play on a track in YouTube Music.
Please note that the INDIFFERENT likeStatus of tracks returned by this
endpoint may be either INDIFFERENT or DISLIKE, due to ambiguous data
returned by YouTube Music.
videoIdvideoIdof the played video.playlistIdplaylistIdof the played playlist or album.limitMinimum number of watch playlist items to return.radioGet a radio playlist (changes each time).shuffleShuffle the input playlist. Only works when theplaylistIdparameter is set at the same time. Does not work ifradio=true.
Returns List of watch playlist items. The counterpart key is optional and only appears if a song has a corresponding video counterpart (UI song/video switcher).
Example:
{
"tracks": [
{
"videoId": "9mWr4c_ig54",
"title": "Foolish Of Me (feat. Jonathan Mendelsohn)",
"length": "3:07",
"thumbnail": [
{
"url": "https://lh3.googleusercontent.com/ulK2YaLtOW0PzcN7ufltG6e4ae3WZ9Bvg8CCwhe6LOccu1lCKxJy2r5AsYrsHeMBSLrGJCNpJqXgwczk=w60-h60-l90-rj",
"width": 60,
"height": 60
}...
],
"feedbackTokens": {
"add": "AB9zfpIGg9XN4u2iJ...",
"remove": "AB9zfpJdzWLcdZtC..."
},
"likeStatus": "INDIFFERENT",
"videoType": "MUSIC_VIDEO_TYPE_ATV",
"artists": [
{
"name": "Seven Lions",
"id": "UCYd2yzYRx7b9FYnBSlbnknA"
},
{
"name": "Jason Ross",
"id": "UCVCD9Iwnqn2ipN9JIF6B-nA"
},
{
"name": "Crystal Skies",
"id": "UCTJZESxeZ0J_M7JXyFUVmvA"
}
],
"album": {
"name": "Foolish Of Me",
"id": "MPREb_C8aRK1qmsDJ"
},
"year": "2020",
"counterpart": {
"videoId": "E0S4W34zFMA",
"title": "Foolish Of Me [ABGT404] (feat. Jonathan Mendelsohn)",
"length": "3:07",
"thumbnail": [...],
"feedbackTokens": null,
"likeStatus": "LIKE",
"artists": [
{
"name": "Jason Ross",
"id": null
},
{
"name": "Seven Lions",
"id": null
},
{
"name": "Crystal Skies",
"id": null
}
],
"views": "6.6K"
}
}
],...
"playlistId": "RDAMVM4y33h81phKU",
"lyrics": "MPLYt_HNNclO0Ddoc-17"
}
Implementation
Future<JsonMap> getWatchPlaylist({
String? videoId,
String? playlistId,
int limit = 25,
bool radio = false,
bool shuffle = false,
}) async {
final body = <String, dynamic>{
'enablePersistentPlaylistPanel': true,
'isAudioOnly': true,
'tunerSettingValue': 'AUTOMIX_SETTING_NORMAL',
};
if (videoId == null && playlistId == null) {
throw YTMusicUserError(
'You must provide either a video id, a playlist id, or both',
);
}
if (videoId != null) {
body['videoId'] = videoId;
playlistId ??= 'RDAMVM$videoId';
if (!radio && !shuffle) {
body['watchEndpointMusicSupportedConfigs'] = {
'watchEndpointMusicConfig': {
'hasPersistentPlaylistPanel': true,
'musicVideoType': 'MUSIC_VIDEO_TYPE_ATV',
},
};
}
}
var isPlaylist = false;
if (playlistId != null) {
final playlistIdValidated = validatePlaylistId(playlistId);
isPlaylist =
playlistIdValidated.startsWith('PL') ||
playlistIdValidated.startsWith('OLA');
body['playlistId'] = playlistIdValidated;
}
if (shuffle && playlistId != null) {
body['params'] = 'wAEB8gECKAE%3D';
}
if (radio) {
body['params'] = 'wAEB';
}
const endpoint = 'next';
final response = await sendRequest(endpoint, body);
final watchNextRenderer = nav(response, [
'contents',
'singleColumnMusicWatchNextResultsRenderer',
'tabbedRenderer',
'watchNextTabbedResultsRenderer',
]);
final lyricsBrowseId = getTabBrowseId(watchNextRenderer as JsonMap, 1);
final relatedBrowseId = getTabBrowseId(watchNextRenderer, 2);
final results =
nav(watchNextRenderer, [
...TAB_CONTENT,
'musicQueueRenderer',
'content',
'playlistPanelRenderer',
], nullIfAbsent: true)
as JsonMap?;
if (results == null) {
var msg = 'No content returned by the server.';
if (playlistId != null) {
msg +=
'\nEnsure you have access to $playlistId - a private playlist may cause this.';
}
throw YTMusicServerError(msg);
}
final playlistIterable = (results['contents'] as List)
.map(
(x) => nav(x, [
'playlistPanelVideoRenderer',
...NAVIGATION_PLAYLIST_ID,
], nullIfAbsent: true),
)
.where((x) => x != null && x != false);
final playlist = playlistIterable.isEmpty ? null : playlistIterable.first;
final tracks = parseWatchPlaylist(
List<JsonMap>.from(results['contents'] as List),
);
if (results.containsKey('continuations')) {
Future<JsonMap> requestFunc(dynamic additionalParams) => sendRequest(
endpoint,
body,
additionalParams: additionalParams as String,
);
List parseFunc(dynamic contents) =>
parseWatchPlaylist(List<JsonMap>.from(contents as List));
tracks.addAll(
await getContinuations(
results,
'playlistPanelContinuation',
limit - tracks.length,
requestFunc,
parseFunc,
additionalParams: isPlaylist ? '' : 'Radio',
),
);
}
return {
'tracks': tracks,
'playlistId': playlist,
'lyrics': lyricsBrowseId,
'related': relatedBrowseId,
};
}