yt_extractor 0.1.0 copy "yt_extractor: ^0.1.0" to clipboard
yt_extractor: ^0.1.0 copied to clipboard

PlatformAndroid

A Flutter plugin that wraps the NewPipe Extractor. Extract YouTube (and other service) streams, audio, video, metadata, search, comments, channels, playlists and trending content — without any API keys.

yt_extractor #

pub package pub points License: MIT

A Flutter plugin that wraps the NewPipe Extractor (Android) and exposes it over a MethodChannel. Extract streams, audio, video, metadata, search results, comments, channels, playlists and trending content from YouTube — and other services — without any API keys.

Everything runs on a background worker pool, so calling the extractor never blocks the UI thread.

Features #

Feature Method
Initialize the extractor + downloader init
Resolve which service handles a URL getServiceByUrl
List all supported services getServices
Full stream metadata + all streams getStreamInfo
Related streams for a video getRelatedStreams
Paginated search (with content filters) search
Autocomplete suggestions getSuggestions
Paginated comments getComments
Channel info + its tabs and first page getChannel
List a channel's tabs getChannelTabs
Paginated tab content getChannelTab
Playlist info + first page getPlaylist
List kiosks (Trending, Music, …) getKiosks
Paginated kiosk content getKiosk
Fetch the next page of any session loadMore / loadMoreComments
Free a native session early dispose

Requirements #

  • Flutter 3.24+, Dart 3.12+
  • Android only (for now): minSdk 24, the INTERNET permission, core library desugaring, and the JitPack repository.

App setup #

Add the JitPack repository to your Android Gradle files, then the plugin dependency to your Dart pubspec.yaml:

dependencies:
  yt_extractor: ^0.1.0

Your android/app/build.gradle.kts needs JitPack and desugaring:

android {
    compileOptions {
        isCoreLibraryDesugaringEnabled = true
    }
}

dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs_nio:2.1.5")
}
// android/build.gradle.kts
allprojects {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }
    }
}

Getting started #

Create a single YtExtractor instance and reuse it. It is cheap and stateless on the Dart side; native sessions are created per call.

import 'package:yt_extractor/yt_extractor.dart';

final extractor = YtExtractor();

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await extractor.init();

  final service = await extractor.getServiceByUrl(
    'https://youtube.com/watch?v=dQw4w9WgXcQ',
  );
  print('Resolved to service: ${service.name} (id ${service.id})');
}

init() is idempotent — safe to call more than once, and optional: the first extraction call initializes the native side automatically if you haven't. Call it early only if you want initialization on your own schedule (e.g. at app startup).

Extracting a stream #

final info = await extractor.getStreamInfo(
  'https://youtube.com/watch?v=dQw4w9WgXcQ',
);

print(info.name);          // title
print(info.uploaderName);  // channel name
print(info.duration);      // seconds
print(info.viewCount);     // views
info.thumbnailUrl;         // convenience: first thumbnail URL

// Streams: pick the best audio (highest bitrate) and highest video.
final audio = info.bestAudioStream;
final video = info.bestVideoStream;
print('${audio?.averageBitrate} kbps -> ${audio?.url}');
print('${video?.resolution} @ ${video?.fps}fps -> ${video?.url}');

// Chapters/segments (e.g. song sections), when the extractor provides them.
print(info.segments.map((s) => s.title));

// Related videos. Two ways:
// 1. Opt into the related rail on getStreamInfo (one extra network call):
final infoWithRelated = await extractor.getStreamInfo(url, includeRelated: true);
print(infoWithRelated.relatedStreams.length);
// 2. Or fetch them separately, leaving the StreamInfo payload lean:
final related = await extractor.getRelatedStreams(url);

Signed URLs expire (~6h). Don't persist them; re-extract getStreamInfo to get fresh URLs. Note the naming collision: on a StreamInfo, the best audio is info.bestAudioStream; on the AudioStream itself, its own bestAudioStream field holds the URL for the remote media source (e.g. HLS).

Playing audio #

Pass the audio stream URL to any audio player, e.g. just_audio:

final player = AudioPlayer();
await player.setUrl(info.bestAudioStream!.url);
player.play();

Searching #

final results = await extractor.search(
  'lofi hip hop',
  filter: SearchFilter.musicSongs,
);

for (final item in results.items) {
  print('${item.name} — ${item.uploaderName}');
}

// Autocomplete suggestions for a query.
final suggestions = await extractor.getSuggestions('lofi');

Available filters: SearchFilter.all, videos, channels, playlists, musicSongs, musicVideos, musicAlbums, musicPlaylists, musicArtists.

Comments #

final page = await extractor.getComments(videoUrl);

for (final comment in page.items) {
  print('${comment.author}: ${comment.commentText}');
  print('pinned=${comment.isPinned} '
      'hearted=${comment.isHeartedByUploader} '
      'edited=${comment.isEdited}');
}

Every Comment carries flag fields the extractor provides: isPinned, isHeartedByUploader, isEdited, isChannelOwner, isCreatorReply, isUploaderVerified, plus replyCount.

Channels & playlists #

// Channel header + first page of content + its tabs.
final channel = await extractor.getChannel(channelUrl);
print(channel.name);
print('${channel.subscriberCount} subscribers');
for (final tab in channel.tabs) {
  print('tab: ${tab.id}');
}
channel.avatarUrl;   // convenience: first avatar URL
channel.bannerUrl;

// A specific tab's content (Videos, Shorts, Live, Playlists, …).
final videos = await extractor.getChannelTab(channel.tabs.first.url);

// Playlist header + first page of streams.
final playlist = await extractor.getPlaylist(playlistUrl);
print('${playlist.name} by ${playlist.uploaderName}');
// streamCount is -1 when the extractor can't determine it up front —
// paginate with loadMore to discover the real length.
playlist.thumbnailUrl;

For images, use the full lists: channel.avatars, channel.banners, playlist.thumbnails, info.thumbnails. Each YtImage has url, width, height and resolutionLevel; every model also exposes a xxxUrl convenience getter for the first entry.

Services & kiosks #

final services = await extractor.getServices();
for (final s in services) {
  print('${s.id}: ${s.name}');
}

final kiosks = await extractor.getKiosks(); // defaults to YouTube
for (final k in kiosks) {
  print('${k.id} — ${k.name}'); // e.g. "trending — Trending"
}

final trending = await extractor.getKiosk('trending');

Pagination #

Every paged call (search, getChannel, getChannelTab, getPlaylist, getKiosk, getComments) returns a PagedResults<T>:

final page = await extractor.search('lofi');

if (page.hasNext) {
  final next = await extractor.loadMore(page.sessionId);
  // Keep calling loadMore with the *latest* sessionId to page further.
}

// Comments page through loadMoreComments instead.
final nextComments = await extractor.loadMoreComments(commentsPage.sessionId);

// Release the native session early (also evicted on expiry).
await extractor.dispose(page.sessionId);

PagedResults exposes sessionId, items and hasNext. Pass the session from the most recent page each time.

Errors #

All failures throw a typed subclass of YtExtractorException:

Exception Meaning
ReCaptchaException YouTube challenged the request (usually HTTP 429). Wait and retry later.
NetworkException DNS, connection refused, timeouts.
SessionExpiredException Pagination session is missing/expired.
InvalidArgumentException The call was made with bad arguments.
NotFoundException The resource could not be found.
ExtractionException The extractor failed to parse the page.
ExtractorTimeoutException The call exceeded the configured timeout.
PluginNotRegisteredException init() hasn't completed on this platform.
try {
  final info = await extractor.getStreamInfo(url);
} on ReCaptchaException catch (e) {
  // Back off and retry later.
} on NetworkException catch (e) {
  // Offline?
}

The default per-call timeout is 90s; configure it on the constructor:

final extractor = YtExtractor(timeout: const Duration(minutes: 2));

Example app #

The bundled example/ is a complete demo: search with filters and suggestions, kiosk browsing, stream pages with just_audio playback and external video open, channel and playlist pages, and infinite scrolling.

cd example
flutter run

FAQ #

Do I need an API key? No. The extractor scrapes the public web endpoints directly, exactly like the NewPipe app.

Why do stream URLs stop working after a few hours? Signed media URLs expire. Re-run getStreamInfo to refresh them.

Is this a video player? No — it extracts metadata and stream URLs. Pair it with just_audio / media_kit / video_player for playback.

Does it support YouTube Music? Yes for public content — music search filters (SearchFilter.music*) and kiosks hit the same endpoints.

Why is it Android-only? The NewPipe Extractor is a JVM library. Other platforms would need a different transport (e.g. a Dart reimplementation).

Can it bypass age gates / logged-in content? No. It only handles public content; there is no authentication and no po_token handling, so some videos may refuse to extract.

Limitations #

  • Signed URLs expire; re-extract rather than caching forever.
  • No authentication, no age-gate bypass, no po_token support.
  • Rate limiting / captchas (ReCaptchaException) can happen; retry later.
  • Android only.

Contributing #

See CONTRIBUTING.md. Run flutter analyze and flutter test before opening a PR.

License #

MIT — see LICENSE.

1
likes
150
points
17
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin that wraps the NewPipe Extractor. Extract YouTube (and other service) streams, audio, video, metadata, search, comments, channels, playlists and trending content — without any API keys.

Repository (GitHub)
View/report issues
Contributing

Topics

#youtube #music #extractor #streaming #media

License

MIT (license)

Dependencies

equatable, flutter

More

Packages that depend on yt_extractor

Packages that implement yt_extractor