flutter_ytdlp_plugin 2.0.0 copy "flutter_ytdlp_plugin: ^2.0.0" to clipboard
flutter_ytdlp_plugin: ^2.0.0 copied to clipboard

A Flutter plugin for YouTube video streaming using yt-dlp. Supports fetching video formats, related videos, and more.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_ytdlp_plugin/flutter_ytdlp_plugin.dart';

void main() {
  runApp(const YouTubeDLPApp());
}

class YouTubeDLPApp extends StatelessWidget {
  const YouTubeDLPApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'YouTube DLP Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const YouTubeDLPHomePage(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class YouTubeDLPHomePage extends StatefulWidget {
  const YouTubeDLPHomePage({super.key});

  @override
  State<YouTubeDLPHomePage> createState() => _YouTubeDLPHomePageState();
}

class _YouTubeDLPHomePageState extends State<YouTubeDLPHomePage> {
  // Current video ID or URL being processed
  String _videoId =
      'dQw4w9WgXcQ'; // Default: Rick Astley - Never Gonna Give You Up

  // Console output buffer
  final List<String> _consoleOutput = [];
  final ScrollController _scrollController = ScrollController();

  // Quality and codec preferences
  String _videoQuality = '1080p';
  int _audioBitrate = 192;
  String? _audioCodec;
  String? _videoCodec;

  // Loading state
  bool _isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('YouTube DLP Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            onPressed: _showSettingsDialog,
            tooltip: 'Change settings',
          ),
        ],
      ),
      body: Column(
        children: [
          // Console-like output area
          Expanded(
            child: Container(
              padding: const EdgeInsets.all(8),
              color: Colors.black,
              child: ListView.builder(
                controller: _scrollController,
                itemCount: _consoleOutput.length,
                itemBuilder: (context, index) {
                  return SelectableText(
                    _consoleOutput[index],
                    style: const TextStyle(
                      color: Colors.white,
                      fontFamily: 'Monospace',
                      fontSize: 14,
                    ),
                  );
                },
              ),
            ),
          ),

          // Current video info
          Container(
            padding: const EdgeInsets.all(8),
            color: Colors.grey[200],
            child: Row(
              children: [
                Expanded(
                  child: Text(
                    'Current Video: $_videoId',
                    overflow: TextOverflow.ellipsis,
                  ),
                ),
                IconButton(
                  icon: const Icon(Icons.edit),
                  onPressed: _changeVideoId,
                  tooltip: 'Change video',
                ),
              ],
            ),
          ),

          // Action buttons
          Padding(
            padding: const EdgeInsets.all(8),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              alignment: WrapAlignment.center,
              children: [
                ElevatedButton.icon(
                  icon: const Icon(Icons.check_circle),
                  label: const Text('Check Status'),
                  onPressed: _checkStatus,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.videocam),
                  label: const Text('Get Video'),
                  onPressed: _getVideoStreams,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.audiotrack),
                  label: const Text('Get Audio'),
                  onPressed: _getAudioStreams,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.all_inclusive),
                  label: const Text('Get Unified'),
                  onPressed: _getUnifiedStreams,
                ),
                ElevatedButton.icon(
                  icon: const Icon(Icons.clear),
                  label: const Text('Clear Console'),
                  onPressed: _clearConsole,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  // Add a line to the console output
  void _log(String message, {bool isError = false}) {
    final timestamp = DateTime.now().toString().split(' ')[1].split('.')[0];
    final prefix = isError ? '[ERROR]' : '[INFO]';
    setState(() {
      _consoleOutput.add('$timestamp $prefix $message');
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _scrollController.animateTo(
          _scrollController.position.maxScrollExtent,
          duration: const Duration(milliseconds: 300),
          curve: Curves.easeOut,
        );
      });
    });
  }

  // Clear the console output
  void _clearConsole() {
    setState(() {
      _consoleOutput.clear();
    });
  }

  // Change the current video ID
  Future<void> _changeVideoId() async {
    final controller = TextEditingController(text: _videoId);

    final newId = await showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Change Video ID/URL'),
        content: TextField(
          controller: controller,
          autofocus: true,
          decoration: const InputDecoration(
            labelText: 'YouTube Video ID or URL',
            hintText: 'e.g. dQw4w9WgXcQ',
          ),
          onSubmitted: (value) => Navigator.pop(context, value),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          TextButton(
            onPressed: () {
              if (controller.text.isNotEmpty) {
                Navigator.pop(context, controller.text);
              } else {
                Navigator.pop(context);
              }
            },
            child: const Text('OK'),
          ),
        ],
      ),
    );

    if (newId != null && newId.isNotEmpty) {
      setState(() {
        _videoId = newId.trim();
      });
      _log('Changed video ID to: $_videoId');
    }

    controller.dispose();
  }

  // Show settings dialog for quality and codec preferences
  Future<void> _showSettingsDialog() async {
    final videoQualities = [
      '144p',
      '240p',
      '360p',
      '480p',
      '720p',
      '1080p',
      '1440p',
      '2160p',
      '4320p',
    ];
    final audioBitrates = [64, 128, 192, 256, 320];
    final codecs = ['None', 'opus', 'mp4a.40.2', 'aac', 'vp9', 'avc1', 'av01'];

    String? newVideoQuality = _videoQuality;
    int? newAudioBitrate = _audioBitrate;
    String? newAudioCodec = _audioCodec;
    String? newVideoCodec = _videoCodec;

    await showDialog(
      context: context,
      builder: (context) {
        return StatefulBuilder(
          builder: (context, setState) {
            return AlertDialog(
              title: const Text('Stream Preferences'),
              content: SingleChildScrollView(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    // Video quality dropdown
                    DropdownButtonFormField<String>(
                      value: newVideoQuality,
                      items: videoQualities
                          .map(
                            (q) => DropdownMenuItem(value: q, child: Text(q)),
                          )
                          .toList(),
                      onChanged: (value) => setState(() {
                        newVideoQuality = value;
                      }),
                      decoration: const InputDecoration(
                        labelText: 'Video Quality',
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Audio bitrate dropdown
                    DropdownButtonFormField<int>(
                      value: newAudioBitrate,
                      items: audioBitrates
                          .map(
                            (b) => DropdownMenuItem(
                              value: b,
                              child: Text('$b kbps'),
                            ),
                          )
                          .toList(),
                      onChanged: (value) => setState(() {
                        newAudioBitrate = value;
                      }),
                      decoration: const InputDecoration(
                        labelText: 'Audio Bitrate',
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Audio codec dropdown
                    DropdownButtonFormField<String?>(
                      value: newAudioCodec,
                      items: [
                        DropdownMenuItem(
                          value: null,
                          child: const Text('None (auto)'),
                        ),
                        ...codecs
                            .where(
                              (c) =>
                                  c != 'None' &&
                                  ['opus', 'mp4a.40.2', 'aac'].contains(c),
                            )
                            .map(
                              (c) => DropdownMenuItem(
                                value: c == 'None' ? null : c,
                                child: Text(c),
                              ),
                            )
                            .toList(),
                      ],
                      onChanged: (value) => setState(() {
                        newAudioCodec = value;
                      }),
                      decoration: const InputDecoration(
                        labelText: 'Audio Codec',
                      ),
                    ),
                    const SizedBox(height: 16),

                    // Video codec dropdown
                    DropdownButtonFormField<String?>(
                      value: newVideoCodec,
                      items: [
                        DropdownMenuItem(
                          value: null,
                          child: const Text('None (auto)'),
                        ),
                        ...codecs
                            .where(
                              (c) =>
                                  c != 'None' &&
                                  ['vp9', 'avc1', 'av01'].contains(c),
                            )
                            .map(
                              (c) => DropdownMenuItem(
                                value: c == 'None' ? null : c,
                                child: Text(c),
                              ),
                            )
                            .toList(),
                      ],
                      onChanged: (value) => setState(() {
                        newVideoCodec = value;
                      }),
                      decoration: const InputDecoration(
                        labelText: 'Video Codec',
                      ),
                    ),
                  ],
                ),
              ),
              actions: [
                TextButton(
                  onPressed: () => Navigator.pop(context),
                  child: const Text('Cancel'),
                ),
                TextButton(
                  onPressed: () {
                    setState(() {
                      _videoQuality = newVideoQuality ?? '1080p';
                      _audioBitrate = newAudioBitrate ?? 192;
                      _audioCodec = newAudioCodec;
                      _videoCodec = newVideoCodec;
                    });
                    Navigator.pop(context);
                    _log('Updated preferences:');
                    _log('  Video Quality: $_videoQuality');
                    _log('  Audio Bitrate: $_audioBitrate kbps');
                    _log('  Audio Codec: ${_audioCodec ?? 'auto'}');
                    _log('  Video Codec: ${_videoCodec ?? 'auto'}');
                  },
                  child: const Text('Save'),
                ),
              ],
            );
          },
        );
      },
    );
  }

  // Check video status
  Future<void> _checkStatus() async {
    if (_isLoading) return;
    _isLoading = true;
    _log('Checking status for video: $_videoId');

    try {
      final status = await FlutterYtdlpPlugin.checkStatus(_videoId);
      _log('Status result:');
      _log('  Available: ${status['available']}');
      _log('  Status: ${status['status']}');
      if (status['error'] != null) {
        _log('  Error: ${status['error']}', isError: true);
      }
    } catch (e) {
      _log('Error checking status: $e', isError: true);
    } finally {
      _isLoading = false;
    }
  }

  // Get video streams
  Future<void> _getVideoStreams() async {
    if (_isLoading) return;
    _isLoading = true;
    _log('Getting video streams for: $_videoId');
    _log('Preferred quality: $_videoQuality');
    if (_videoCodec != null) {
      _log('Preferred video codec: $_videoCodec');
    }

    try {
      final streams = await FlutterYtdlpPlugin.getVideoStreams(
        _videoId,
        quality: _videoQuality,
      );

      if (streams.isEmpty) {
        _log('No video streams found', isError: true);
        return;
      }

      _log('Found ${streams.length} video stream(s)');
      for (final stream in streams) {
        _log('  Stream:');
        _log('    URL: ${_truncateUrl(stream['url'])}');
        _log('    Format: ${stream['ext']}');
        _log('    Resolution: ${stream['resolution']}');
        _log('    Codec: ${stream['codec']}');
        _log('    Bitrate: ${stream['bitrate']} kbps');
        if (stream['filesize'] != null) {
          _log('    Size: ${_formatFileSize(stream['filesize'])}');
        }
      }
    } catch (e) {
      _log('Error getting video streams: $e', isError: true);
    } finally {
      _isLoading = false;
    }
  }

  // Get audio streams
  Future<void> _getAudioStreams() async {
    if (_isLoading) return;
    _isLoading = true;
    _log('Getting audio streams for: $_videoId');
    _log('Preferred bitrate: $_audioBitrate kbps');
    if (_audioCodec != null) {
      _log('Preferred audio codec: $_audioCodec');
    }

    try {
      final streams = await FlutterYtdlpPlugin.getAudioStreams(
        _videoId,
        bitrate: _audioBitrate,
        codec: _audioCodec,
      );

      if (streams.isEmpty) {
        _log('No audio streams found', isError: true);
        return;
      }

      _log('Found ${streams.length} audio stream(s)');
      for (final stream in streams) {
        _log('  Stream:');
        _log('    URL: ${_truncateUrl(stream['url'])}');
        _log('    Format: ${stream['ext']}');
        _log('    Codec: ${stream['codec']}');
        _log('    Bitrate: ${stream['bitrate']} kbps');
        if (stream['filesize'] != null) {
          _log('    Size: ${_formatFileSize(stream['filesize'])}');
        }
      }
    } catch (e) {
      _log('Error getting audio streams: $e', isError: true);
    } finally {
      _isLoading = false;
    }
  }

  // Get unified streams (video + audio)
  Future<void> _getUnifiedStreams() async {
    if (_isLoading) return;
    _isLoading = true;
    _log('Getting unified streams for: $_videoId');
    _log('Video quality: $_videoQuality');
    _log('Audio bitrate: $_audioBitrate kbps');
    if (_videoCodec != null) _log('Video codec: $_videoCodec');
    if (_audioCodec != null) _log('Audio codec: $_audioCodec');

    try {
      final result = await FlutterYtdlpPlugin.getMuxedStreams(
        _videoId,
        videoQuality: _videoQuality,
        audioBitrate: _audioBitrate,
        videoCodec: _videoCodec,
        audioCodec: _audioCodec,
      );

      _log('Video duration: ${result['duration']} seconds');

      if (result['video'] != null) {
        final videoStreams = result['video'] as List;
        _log('Found ${videoStreams.length} video stream(s)');
        for (final stream in videoStreams) {
          _log('  Video Stream:');
          _log('    URL: ${_truncateUrl(stream['url'])}');
          _log('    Format: ${stream['ext']}');
          _log('    Resolution: ${stream['resolution']}');
          _log('    Codec: ${stream['codec']}');
          _log('    Bitrate: ${stream['bitrate']} kbps');
          if (stream['filesize'] != null) {
            _log('    Size: ${_formatFileSize(stream['filesize'])}');
          }
        }
      }

      if (result['audio'] != null) {
        final audioStreams = result['audio'] as List;
        _log('Found ${audioStreams.length} audio stream(s)');
        for (final stream in audioStreams) {
          _log('  Audio Stream:');
          _log('    URL: ${_truncateUrl(stream['url'])}');
          _log('    Format: ${stream['ext']}');
          _log('    Codec: ${stream['codec']}');
          _log('    Bitrate: ${stream['bitrate']} kbps');
          if (stream['filesize'] != null) {
            _log('    Size: ${_formatFileSize(stream['filesize'])}');
          }
        }
      }
    } catch (e) {
      _log('Error getting unified streams: $e', isError: true);
    } finally {
      _isLoading = false;
    }
  }

  // Helper to truncate long URLs for display
  String _truncateUrl(String url) {
    if (url.length <= 50) return url;
    return '${url.substring(0, 20)}...${url.substring(url.length - 20)}';
  }

  // Helper to format file size
  String _formatFileSize(int bytes) {
    if (bytes < 1024) return '$bytes B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
    if (bytes < 1024 * 1024 * 1024) {
      return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
    }
    return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(1)} GB';
  }
}
4
likes
0
points
49
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for YouTube video streaming using yt-dlp. Supports fetching video formats, related videos, and more.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_ytdlp_plugin

Packages that implement flutter_ytdlp_plugin