parsePlaylistItem function

JsonMap? parsePlaylistItem(
  1. JsonMap data, {
  2. List<List<String>>? menuEntries,
  3. bool isAlbum = false,
})

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;
}