Flutter实现YouTube视频下载:视频损坏、大小异常问题求助
问题排查与修复方案
核心问题分析
你遇到的文件损坏、体积过小问题,本质是直接请求YouTube网页链接下载到的是HTML页面,而非视频文件。YouTube的分享链接指向的是视频播放网页,不是原始的视频资源URL,所以http.get拿到的只是几KB的网页代码,自然无法正常播放。
另外代码还有几个次要问题:
- 未处理用户拒绝存储权限的情况,会导致后续文件写入失败
- 进度条变量
_progress从未更新,下载过程中不会显示进度 - 固定文件名
video.mp4,多次下载会覆盖旧文件
修复步骤与代码实现
1. 引入专业的YouTube解析库
使用youtube_explode_dart库来解析YouTube视频的真实媒体流地址,这是处理YouTube下载的标准方案。在pubspec.yaml中添加依赖:
dependencies: youtube_explode_dart: ^2.0.0 permission_handler: ^10.0.0 path_provider: ^2.0.0 http: ^0.13.0 intl: ^0.18.0
2. 修改核心下载逻辑
以下是修复后的完整代码,包含权限处理、进度更新、唯一文件名生成等优化:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; import 'package:intl/intl.dart'; class DownloadPage extends StatefulWidget { @override _DownloadPageState createState() => _DownloadPageState(); } class _DownloadPageState extends State<DownloadPage> { final TextEditingController _linkController = TextEditingController(); bool _isLoading = false; double _progress = 0; Future<void> _downloadVideo(String link) async { setState(() { _isLoading = true; _progress = 0; }); // 处理存储权限 final status = await Permission.storage.status; if (status.isDenied) { final result = await Permission.storage.request(); if (!result.isGranted) { setState(() => _isLoading = false); _showErrorDialog("存储权限被拒绝,无法下载视频"); return; } } try { // 解析YouTube视频 final yt = YoutubeExplode(); final video = await yt.videos.get(link); final manifest = await yt.videos.streamsClient.getManifest(video.id); // 选择最高质量的MP4视频流 final streamInfo = manifest.muxed.withHighestBitrate(); final streamUrl = streamInfo.url; // 获取存储目录并生成唯一文件名 final directory = await getExternalStorageDirectory(); final timestamp = DateFormat('yyyyMMddHHmmss').format(DateTime.now()); final fileName = '${video.title.replaceAll(RegExp(r'[^\w\s]'), '')}_$timestamp.mp4'; final filePath = '${directory!.path}/$fileName'; // 带进度的下载 final request = await http.get(Uri.parse(streamUrl.toString()), onReceiveProgress: (received, total) { if (total != -1) { setState(() { _progress = (received / total) * 100; }); } }); // 写入文件 final file = File(filePath); await file.writeAsBytes(request.bodyBytes); // 关闭YoutubeExplode客户端 await yt.close(); setState(() => _isLoading = false); _showSuccessDialog(filePath); } catch (e) { setState(() => _isLoading = false); _showErrorDialog("下载失败:${e.toString()}"); } } void _showSuccessDialog(String filePath) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('下载完成'), content: Text('视频已保存至:\n$filePath'), actions: [ ElevatedButton( child: const Text('确定'), onPressed: () => Navigator.of(context).pop(), ), ], ), ); } void _showErrorDialog(String message) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('下载失败'), content: Text(message), actions: [ ElevatedButton( child: const Text('确定'), onPressed: () => Navigator.of(context).pop(), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('下载YouTube视频')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ TextField( controller: _linkController, decoration: const InputDecoration( hintText: '粘贴YouTube视频链接', border: OutlineInputBorder(), ), maxLines: 1, ), const SizedBox(height: 16.0), _isLoading ? Column( children: [ LinearProgressIndicator(value: _progress / 100), const SizedBox(height: 16.0), Text('下载进度:${_progress.toStringAsFixed(1)}%'), ], ) : ElevatedButton( onPressed: () => _downloadVideo(_linkController.text.trim()), child: const Text('开始下载'), ), ], ), ), ); } @override void dispose() { _linkController.dispose(); super.dispose(); } }
关键优化说明
- 视频流解析:通过
youtube_explode_dart获取视频的真实媒体流URL,确保下载的是真正的视频文件 - 权限处理:新增权限请求后的结果判断,避免无权限时的崩溃
- 进度更新:利用
http.get的onReceiveProgress回调实时更新下载进度 - 唯一文件名:基于视频标题和时间戳生成文件名,避免覆盖旧文件
- 错误处理:添加try-catch捕获解析和下载过程中的异常,给出友好提示
内容的提问来源于stack exchange,提问作者M Al
相关产品推荐
相关产品推荐

