Extractor - Flutter Video Downloader Plugin

A robust, production-ready Flutter plugin for downloading videos and audio from 1000+ websites using yt-dlp. Built with native Android (Kotlin) implementation.

Platform License

Platform Support

Platform Status Implementation
Android βœ… Fully Supported Native Kotlin + youtubedl-android
iOS πŸ’‘ Open for Contributions See iOS Support below

πŸ“± Screenshots (example app)

Main Screen - Quality Selection Download in Progress Downloads Page
Quality Selection
Browse and select video quality
Download Progress
Real-time progress with logs
Downloads Manager
View completed downloads
Settings - Version Info Settings - Features
Version Information
Check library versions
Features List
All plugin capabilities

✨ Features

Core Functionality

  • πŸŽ₯ Download videos from 1000+ websites (YouTube, Vimeo, Dailymotion, etc.)
  • 🎡 Extract audio with format conversion (MP3, M4A, WAV, FLAC, AAC, OPUS)
  • πŸ“Š Get video information (title, duration, formats, thumbnails, metadata)
  • 🎬 Format selection - Choose quality, resolution, codec
  • πŸ“ Subtitle support - Download and embed subtitles in multiple languages
  • πŸ–ΌοΈ Thumbnail embedding - Embed video thumbnails in audio files
  • πŸ“‹ Metadata embedding - Add title, artist, album info
  • ⚑ Aria2c integration - Faster downloads with external downloader
  • πŸ”„ Progress tracking - Real-time progress updates with ETA
  • ⏸️ Download management - Cancel, pause, resume downloads
  • πŸ”„ Update yt-dlp - Update to latest stable version

Advanced Features

  • 🎯 Download templates - Pre-configured quality presets (Best, 1080p, 720p, 480p, Audio Only, Small Size)
  • 🎨 Custom format strings - Advanced format selection with yt-dlp syntax
  • πŸ”§ Custom yt-dlp options - Pass any yt-dlp command-line option
  • πŸ“¦ Playlist support - Download entire playlists or individual videos
  • 🎭 Chapter support - Preserve video chapters
  • 🌐 Multi-language subtitles - Download subtitles in any language
  • πŸ” Cookie support - Use cookies for authentication (planned)
  • πŸ“± Scoped storage - Android 10+ compliant storage handling

Architecture & Code Quality

  • πŸ—οΈ Clean Architecture - SOLID principles with separation of concerns
  • πŸ”’ Type-safe Communication - Pigeon for compile-time type safety
  • 🧩 Service Layer Pattern - LibraryService, UpdateService, InfoService, DownloadService
  • 🧡 Thread Safety - Proper threading with coroutines (Android)
  • πŸ“ Well Documented - Comprehensive API documentation
  • βœ… Production Ready - Battle-tested architecture

Example App Features

  • 🎨 Material Design 3 - Modern UI with dynamic colors
  • πŸ“± Inline Format Selection - Horizontal scrollable quality cards
  • πŸ“₯ Downloads Manager - View and manage downloaded files
  • πŸ’Ύ Save to Gallery - Export videos to public storage
  • πŸ“Š File Management - View info, share, delete files
  • βš™οΈ Settings Page - Version info, updates, library management
  • πŸŒ“ Dark Mode - Automatic theme switching

πŸ“¦ Installation

Add to your pubspec.yaml:

dependencies:
  extractor: latest

Android Requirements

Minimum SDK version (API 24+):

android {
    defaultConfig {
        minSdk = 24
    }
}

Required: Set extractNativeLibs in AndroidManifest.xml:

<application
    android:extractNativeLibs="true"
    ...>

πŸš€ Quick Start

Initialize

import 'package:extractor/extractor.dart';

final youtubeDL = YoutubeDLFlutter.instance;

// Initialize with FFmpeg and Aria2c
final result = await youtubeDL.initialize(
  enableFFmpeg: true,
  enableAria2c: true,
);

if (result.success) {
  print('Initialized successfully');
} else {
  print('Error: ${result.errorMessage}');
}

Get Video Information

try {
  final info = await youtubeDL.getVideoInfo('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
  
  print('Title: ${info.title}');
  print('Duration: ${info.duration} seconds');
  print('Uploader: ${info.uploader}');
  print('Thumbnail: ${info.thumbnail}');
  print('Available formats: ${info.formats?.length}');
  
  // List all formats
  info.formats?.forEach((format) {
    print('Format: ${format?.formatId} - ${format?.resolution} - ${format?.ext}');
  });
} catch (e) {
  print('Error: $e');
}

Download Video

import 'dart:io';
import 'package:path_provider/path_provider.dart';

// Get download directory
final dir = await getExternalStorageDirectory();
final downloadPath = '${dir!.path}/Downloads';

// Create download request
final request = DownloadRequest(
  url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
  outputPath: downloadPath,
  outputTemplate: '%(title)s.%(ext)s',
  format: 'bestvideo+bestaudio/best', // Best quality
  processId: 'download_${DateTime.now().millisecondsSinceEpoch}',
  embedThumbnail: true,
  embedMetadata: true,
  customOptions: {
    '--downloader': 'libaria2c.so', // Use Aria2c for faster downloads
  },
);

// Start download
try {
  final result = await youtubeDL.download(request);
  
  if (result.status == OperationStatus.success) {
    print('Downloaded to: ${result.outputPath}');
  } else {
    print('Download failed: ${result.errorMessage}');
  }
} catch (e) {
  print('Error: $e');
}

Download Audio Only

final request = DownloadRequest(
  url: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
  outputPath: downloadPath,
  outputTemplate: '%(title)s.%(ext)s',
  extractAudio: true,
  audioFormat: 'mp3',
  audioQuality: 0, // Best quality (0-9, 0 is best)
  embedThumbnail: true,
  embedMetadata: true,
  processId: 'audio_${DateTime.now().millisecondsSinceEpoch}',
);

final result = await youtubeDL.download(request);

Track Download Progress

// Listen to progress updates
youtubeDL.onProgress.listen((progress) {
  print('Process: ${progress.processId}');
  print('Progress: ${progress.progress}%');
  print('ETA: ${progress.eta.inSeconds} seconds');
});

// Listen to state changes
youtubeDL.onStateChanged.listen((state) {
  print('Process: ${state.processId}');
  print('State: ${state.state}'); // started, completed, cancelled
});

// Listen to errors
youtubeDL.onError.listen((error) {
  print('Process: ${error.processId}');
  print('Error: ${error.error}');
});

Cancel Download

final cancelled = await youtubeDL.cancelDownload(processId: 'download_123');
if (cancelled) {
  print('Download cancelled');
}

Update yt-dlp

final result = await youtubeDL.updateYoutubeDL(channel: UpdateChannel.stable);

if (result.status == OperationStatus.success) {
  print('Updated to: ${result.version}');
} else {
  print('Update failed: ${result.errorMessage}');
}

Get Version Information

final versionInfo = await youtubeDL.getVersion();

print('yt-dlp: ${versionInfo.youtubeDlVersion}');
print('FFmpeg: ${versionInfo.ffmpegVersion}');
print('Python: ${versionInfo.pythonVersion}');

πŸ“š Download Templates

Pre-configured quality presets for common use cases:

import 'package:extractor/extractor.dart';

// Best Quality (highest resolution + audio)
final bestQuality = DownloadTemplates.bestQuality(
  url: videoUrl,
  outputPath: downloadPath,
);

// Audio Only (best quality)
final audioOnly = DownloadTemplates.audioOnly(
  url: videoUrl,
  outputPath: downloadPath,
  audioFormat: 'mp3',
);

// 1080p Video
final video1080p = DownloadTemplates.video1080p(
  url: videoUrl,
  outputPath: downloadPath,
);

// 720p Video
final video720p = DownloadTemplates.video720p(
  url: videoUrl,
  outputPath: downloadPath,
);

// 480p Video
final video480p = DownloadTemplates.video480p(
  url: videoUrl,
  outputPath: downloadPath,
);

// Small Size (best video under 100MB)
final smallSize = DownloadTemplates.smallSize(
  url: videoUrl,
  outputPath: downloadPath,
);

// Use template
final result = await youtubeDL.download(bestQuality);

🎯 Advanced Usage

Custom Format Selection

// Download specific format by ID
final request = DownloadRequest(
  url: videoUrl,
  outputPath: downloadPath,
  format: '137+140', // Format IDs from getVideoInfo()
);

// Download best video with height <= 720p
final request = DownloadRequest(
  url: videoUrl,
  outputPath: downloadPath,
  format: 'bestvideo[height<=720]+bestaudio/best[height<=720]',
);

// Download best MP4 video
final request = DownloadRequest(
  url: videoUrl,
  outputPath: downloadPath,
  format: 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]',
);

Subtitle Download

final request = DownloadRequest(
  url: videoUrl,
  outputPath: downloadPath,
  writeSubtitles: true,
  writeAutoSubtitles: true,
  embedSubtitles: true,
  subtitlesLang: 'en,es,fr', // Multiple languages
);

Custom yt-dlp Options

final request = DownloadRequest(
  url: videoUrl,
  outputPath: downloadPath,
  customOptions: {
    '--no-playlist': '', // Don't download playlist
    '--max-downloads': '5', // Limit downloads
    '--rate-limit': '1M', // Limit download speed
    '--retries': '10', // Retry attempts
    '--fragment-retries': '10',
    '--skip-unavailable-fragments': '',
    '--no-mtime': '', // Don't use Last-modified header
    '--no-update': '', // Suppress update warning
  },
);

Playlist Handling

// Download entire playlist
final request = DownloadRequest(
  url: 'https://www.youtube.com/playlist?list=...',
  outputPath: downloadPath,
  noPlaylist: false, // Allow playlist
  outputTemplate: '%(playlist_index)s - %(title)s.%(ext)s',
);

// Download only first video from playlist
final request = DownloadRequest(
  url: 'https://www.youtube.com/playlist?list=...',
  outputPath: downloadPath,
  noPlaylist: true, // Skip playlist
);

πŸ“± Example App

The example app demonstrates all features:

  • Main Page: URL input, video info display, inline format selection
  • Downloads Page: View downloaded files, save to gallery, file management
  • Settings Page: Version info, update yt-dlp, library information

Run the example:

cd example
flutter run

πŸ—οΈ Architecture

Service Layer

ExtractorPlugin (Main)
    └── YoutubeDLManager (Coordinator)
            β”œβ”€β”€ LibraryService (Initialization & Versions)
            β”œβ”€β”€ UpdateService (Binary Updates)
            β”œβ”€β”€ InfoService (Video Information)
            β”œβ”€β”€ DownloadService (Downloads & Progress)
            └── VideoInfoMapper (JSON Mapping)

Communication

  • Pigeon: Type-safe communication between Dart and native code
  • Streams: Real-time progress updates via Dart streams
  • Coroutines: Async operations on Android

πŸ“Š Supported Platforms

Video Platforms (1000+)

YouTube, Vimeo, Dailymotion, Facebook, Instagram, Twitter, TikTok, Reddit, Twitch, SoundCloud, Bandcamp, and many more.

Audio Formats

MP3, M4A, WAV, FLAC, AAC, OPUS, OGG, VORBIS

Video Formats

MP4, MKV, WEBM, AVI, FLV, MOV

Subtitle Formats

SRT, VTT, ASS, LRC

πŸ”§ Troubleshooting

Android

Build Error: NDK version mismatch

// In android/app/build.gradle
android {
    ndkVersion = "27.0.12077973"
}

Build Error: extractNativeLibs

  • Ensure android:extractNativeLibs="true" is set in AndroidManifest.xml
  • This is required for the plugin to extract native libraries

Update Failed

  • Check internet connection
  • Ensure sufficient storage space
  • Try again later (GitHub API rate limits)

Download Failed

  • Verify the URL is supported (check supported sites)
  • Check internet connection
  • Try updating yt-dlp to the latest version
  • Some sites may require cookies or authentication

πŸ’‘ iOS Support

Currently, this plugin only supports Android. iOS implementation is open for contributions!

If you're interested in adding iOS support, here are some approaches to consider:

  1. Rust + FFI: Use Rust with flutter_rust_bridge for cross-platform yt-dlp integration
  2. Native Swift: Port the Android implementation to Swift/Objective-C
  3. Server-side: Implement a backend service for video processing (recommended for App Store apps)

Feel free to open an issue or pull request if you'd like to contribute iOS support!

πŸ“ API Reference

Core Methods

initialize()

Initialize the library with configuration.

Future<InitResult> initialize({
  bool enableFFmpeg = true,
  bool enableAria2c = false,
})

getVideoInfo()

Get video information without downloading.

Future<VideoInfo> getVideoInfo(String url)

Returns: VideoInfo with title, duration, formats, thumbnail, uploader, etc.

download()

Download a video with specified configuration.

Future<DownloadResult> download(DownloadRequest request)

cancelDownload()

Cancel an active download by process ID.

Future<bool> cancelDownload(String processId)

updateYoutubeDL()

Update yt-dlp binary to the latest version.

Future<UpdateResult> updateYoutubeDL({
  UpdateChannel channel = UpdateChannel.stable,
})

getVersion()

Get version information for yt-dlp, FFmpeg, and Python.

Future<VersionInfo> getVersion()

Streams

Listen to real-time updates:

// Progress updates
Stream<DownloadProgress> get onProgress

// State changes (started, completed, cancelled)
Stream<DownloadState> get onStateChanged

// Errors
Stream<DownloadError> get onError

// Logs (yt-dlp output)
Stream<LogMessage> get onLog

Key Models

DownloadRequest

DownloadRequest({
  required String url,
  required String outputPath,
  String? outputTemplate,        // e.g., '%(title)s.%(ext)s'
  String? format,                 // e.g., 'bestvideo+bestaudio/best'
  bool noPlaylist = true,
  bool extractAudio = false,
  String? audioFormat,            // 'mp3', 'm4a', 'wav', etc.
  int? audioQuality,              // 0-9 (0 is best)
  bool embedThumbnail = false,
  bool embedMetadata = false,
  bool embedSubtitles = false,
  String? subtitlesLang,
  bool writeSubtitles = false,
  bool writeAutoSubtitles = false,
  Map<String, String>? customOptions,
  String? processId,
})

VideoInfo

class VideoInfo {
  String? id, title, description;
  String? uploader, uploaderId, uploaderUrl;
  String? channelId, channelUrl;
  int? duration, viewCount, likeCount;
  String? thumbnail, url;
  List<VideoFormat?>? formats;
  String? ext;
  int? width, height, fps;
  String? vcodec, acodec;
}

VideoFormat

class VideoFormat {
  String? formatId, formatNote, ext, url;
  int? width, height, fps, filesize, tbr;
  String? vcodec, acodec, resolution;
}

FormatHelper Utilities

// Get best video/audio formats
FormatHelper.getBestVideo(formats)
FormatHelper.getBestAudio(formats)

// Filter by resolution
FormatHelper.getFormatsByResolution(formats, minHeight, maxHeight)

// Get specific format types
FormatHelper.getAudioFormats(formats)
FormatHelper.getVideoFormats(formats)

// Format utilities
FormatHelper.formatFileSize(bytes)        // "50.00 MB"
FormatHelper.formatResolution(format)     // "1920x1080"
FormatHelper.getFormatDescription(format) // "1080p β€’ mp4 β€’ 50.00 MB"

🀝 Contributing

See CONTRIBUTING.md for contribution guidelines.

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

This plugin uses open-source libraries including youtubedl-android (GPL-3.0). See LICENSE file for third-party acknowledgments.

πŸ™ Credits

πŸ“š Documentation

⚑ Performance

  • Fast Downloads: Aria2c integration for multi-connection downloads
  • Efficient: Lazy extractors for faster startup
  • Optimized: Native code for best performance
  • Scalable: Handle multiple concurrent downloads

πŸ”’ Security

  • Scoped Storage: Android 10+ compliant
  • Permission Minimal: Only requests necessary permissions
  • Type-Safe: Compile-time type checking with Pigeon

πŸ“ˆ Version Information

  • Plugin Version: 1.0.0
  • youtubedl-android: v0.18.1
  • yt-dlp: 2025.11.12 (bundled, updatable)
  • FFmpeg: 6.0 (bundled)
  • Python: 3.8 (bundled)
  • Minimum Android: API 24 (Android 7.0)

Made with ❀️ by Ashish Pipaliya

⭐ Star this repo if you find it useful!

Libraries

extractor