parsePlaylistItem function
Parses item from playlist.
Implementation
JsonMap? parsePlaylistItem(
JsonMap data, {
List<List<String>>? menuEntries,
bool isAlbum = false,
}) {
String? videoId;
String? setVideoId;
dynamic like;
dynamic feedbackTokens;
dynamic libraryStatus;
if (data.containsKey('menu')) {
for (final item in List<JsonMap>.from(nav(data, MENU_ITEMS) as List)) {
if (item.containsKey('menuServiceItemRenderer')) {
final menuService = nav(item, MENU_SERVICE) as JsonMap;
if (menuService.containsKey('playlistEditEndpoint')) {
setVideoId =
nav(menuService, [
'playlistEditEndpoint',
'actions',
0,
'setVideoId',
], nullIfAbsent: true)
as String?;
videoId =
nav(menuService, [
'playlistEditEndpoint',
'actions',
0,
'removedVideoId',
], nullIfAbsent: true)
as String?;
}
}
if (item.containsKey(TOGGLE_MENU)) {
feedbackTokens = parseSongMenuTokens(item);
libraryStatus = parseSongLibraryStatus(item);
}
}
}
if (nav(data, PLAY_BUTTON, nullIfAbsent: true) != null) {
final playBtn = nav(data, PLAY_BUTTON) as JsonMap;
if (playBtn.containsKey('playNavigationEndpoint')) {
videoId =
((playBtn['playNavigationEndpoint'] as JsonMap)['watchEndpoint']
as JsonMap)['videoId']
as String?;
if (data.containsKey('menu')) {
like = nav(data, MENU_LIKE_STATUS, nullIfAbsent: true);
}
}
}
final isAvailable =
!(data['musicItemRendererDisplayPolicy'] ==
'MUSIC_ITEM_RENDERER_DISPLAY_POLICY_GREY_OUT');
final usePresetColumns = !isAvailable || isAlbum ? true : null;
int? titleIndex = usePresetColumns == true ? 0 : null;
int? artistIndex = usePresetColumns == true ? 1 : null;
int? albumIndex = usePresetColumns == true ? 2 : null;
final userChannelIndexes = <int>[];
int? unrecognizedIndex;
for (var index = 0; index < (data['flexColumns'] as List).length; index++) {
final flexColumnItem = getFlexColumnItem(data, index);
final navigationEndpoint = nav(flexColumnItem, [
...TEXT_RUN,
'navigationEndpoint',
], nullIfAbsent: true);
if (navigationEndpoint == null) {
if (nav(flexColumnItem, TEXT_RUN_TEXT, nullIfAbsent: true) != null) {
unrecognizedIndex ??= index;
}
continue;
}
if ((navigationEndpoint as Map).containsKey('watchEndpoint')) {
titleIndex = index;
} else if (navigationEndpoint.containsKey('browseEndpoint')) {
final pageType = nav(navigationEndpoint, [
'browseEndpoint',
'browseEndpointContextSupportedConfigs',
'browseEndpointContextMusicConfig',
'pageType',
]);
if (pageType == 'MUSIC_PAGE_TYPE_ARTIST' ||
pageType == 'MUSIC_PAGE_TYPE_UNKNOWN') {
artistIndex = index;
} else if (pageType == 'MUSIC_PAGE_TYPE_ALBUM') {
albumIndex = index;
} else if (pageType == 'MUSIC_PAGE_TYPE_USER_CHANNEL') {
userChannelIndexes.add(index);
} else if (pageType == 'MUSIC_PAGE_TYPE_NON_MUSIC_AUDIO_TRACK_PAGE') {
titleIndex = index;
}
}
}
artistIndex ??= unrecognizedIndex;
if (artistIndex == null && userChannelIndexes.isNotEmpty) {
artistIndex = userChannelIndexes.last;
}
final title = titleIndex != null ? getItemText(data, titleIndex) : null;
if (title == 'Song deleted') return null;
final artists =
artistIndex != null ? parseSongArtists(data, artistIndex) : null;
final album = albumIndex != null ? parseSongAlbum(data, albumIndex) : null;
final views = isAlbum ? getItemText(data, 2) : null;
String? duration;
if (data.containsKey('fixedColumns')) {
final firstColumn = getFixedColumnItem(data, 0);
duration =
((firstColumn?['text'] as JsonMap?)?['simpleText'] ??
nav(firstColumn, ['text', 'simpleText']) ??
nav(firstColumn, TEXT_RUN_TEXT))
as String?;
}
final thumbnails = nav(data, THUMBNAILS, nullIfAbsent: true);
final isExplicit = nav(data, BADGE_LABEL, nullIfAbsent: true) != null;
final videoType = nav(data, [
...MENU_ITEMS,
0,
MNIR,
'navigationEndpoint',
...NAVIGATION_VIDEO_TYPE,
], nullIfAbsent: true);
final song = <String, dynamic>{
'videoId': videoId,
'title': title,
'artists': artists,
'album': album,
'likeStatus': like,
'inLibrary': libraryStatus,
'thumbnails': thumbnails,
'isAvailable': isAvailable,
'isExplicit': isExplicit,
'videoType': videoType,
'views': views,
};
if (isAlbum) {
song['trackNumber'] =
isAvailable
? int.parse(nav(data, ['index', 'runs', 0, 'text']) as String)
: null;
}
if (duration != null) {
song['duration'] = duration;
song['duration_seconds'] = parseDuration(duration);
}
if (setVideoId != null) song['setVideoId'] = setVideoId;
if (feedbackTokens != null) song['feedbackTokens'] = feedbackTokens;
if (menuEntries != null) {
final menuItems = nav(data, MENU_ITEMS);
for (final menuEntry in menuEntries) {
final items = findObjectsByKey(menuItems as List, menuEntry[0]);
song[menuEntry.last] = items
.map((itm) => nav(itm, menuEntry, nullIfAbsent: true))
.firstWhere((x) => x != null, orElse: () => null);
}
}
return song;
}