getManifest method

Future<StreamManifest> getManifest(
  1. dynamic videoId, {
  2. @Deprecated('Use the ytClient parameter instead passing the proper [YoutubeApiClient]s') bool fullManifest = false,
  3. List<YoutubeApiClient>? ytClients,
  4. bool requireWatchPage = true,
})

Gets the manifest that contains information about available streams in the specified video.

See YoutubeApiClient for all the possible clients that can be set using the ytClients parameter. If ytClients is null the library automatically manages the clients, otherwise only the clients provided are used. Currently by default the YoutubeApiClient.ios clients is used, if the extraction fails the YoutubeApiClient.tv client is used instead.

Note that age restricted videos are no longer support due to the changes in the YouTube API.

If requireWatchPage (default: true) is set to false the watch page is not used to extract the streams (so the process can be faster) but it COULD be less reliable (not tested thoroughly). If the extracted streams require signature decoding for which the watch page is required, the client will automatically fetch the watch page anyways (e.g. YoutubeApiClient.tv).

If the extraction fails an exception is thrown, to diagnose the issue enable the logging from the logging package, and open an issue with the output. For example add at the beginning of your code:

Logger.root.level = Level.FINER;
Logger.root.onRecord.listen((e)  {
  print(e);
   if (e.error != null) {
    print(e.error);
    print(e.stackTrace);
  }
});

Implementation

Future<StreamManifest> getManifest(dynamic videoId,
    {@Deprecated(
        'Use the ytClient parameter instead passing the proper [YoutubeApiClient]s')
    bool fullManifest = false,
    List<YoutubeApiClient>? ytClients,
    bool requireWatchPage = true}) async {
  videoId = VideoId.fromString(videoId);
  final clients = ytClients ?? [YoutubeApiClient.ios];

  final uniqueStreams = LinkedHashSet<StreamInfo>(
    equals: (a, b) {
      if (a.runtimeType != b.runtimeType) return false;
      if (a is AudioStreamInfo && b is AudioStreamInfo) {
        return a.tag == b.tag && a.audioTrack == b.audioTrack;
      }
      return a.tag == b.tag;
    },
    hashCode: (e) {
      if (e is AudioStreamInfo) {
        return e.tag.hashCode ^ e.audioTrack.hashCode;
      }
      return e.tag.hashCode;
    },
  );

  Object? lastException;

  for (final client in clients) {
    _logger.fine(
        'Getting stream manifest for video $videoId with client: ${client.payload['context']['client']['clientName']}');
    try {
      await retry(_httpClient, () async {
        final streams = await _getStreams(videoId,
                ytClient: client, requireWatchPage: requireWatchPage)
            .toList();
        if (streams.isEmpty) {
          throw VideoUnavailableException(
            'Video "$videoId" does not contain any playable streams.',
          );
        }

        final response = await _httpClient.head(streams.first.url);
        if (response.statusCode == 403) {
          throw YoutubeExplodeException(
            'Video $videoId returned 403 (stream: ${streams.first.tag})',
          );
        }
        uniqueStreams.addAll(streams);
      });
    } catch (e, s) {
      _logger.severe(
          'Failed to get stream manifest for video $videoId with client: ${client.payload['context']['client']['clientName']}. Reason: $e\n',
          e,
          s);
      lastException = e;
    }
  }

  // If the user has not provided any client retry with the tv which work also in some restricted videos.
  if (uniqueStreams.isEmpty && ytClients == null) {
    return getManifest(videoId,
        ytClients: [YoutubeApiClient.tv]);
  }
  if (uniqueStreams.isEmpty) {
    throw lastException ??
        VideoUnavailableException(
            'Video "$videoId" has no available streams');
  }
  return StreamManifest(uniqueStreams.toList());
}