使用flutter_downloader无法下载Ins/Facebook视频且已下载视频无法播放
解决Flutter中flutter_downloader下载Facebook/Instagram视频失败或无法播放的问题
核心问题分析
你当前的代码直接使用网页URL发起下载,但Facebook/Instagram的网页链接指向的是页面内容,而非真实的视频源文件地址,导致下载的是网页HTML而非视频;此外还存在请求头缺失、权限处理不严谨、文件名逻辑冗余等问题,共同引发下载失败或视频无法播放的情况。
解决方案
1. 依赖准备
先添加必要的解析和网络依赖到pubspec.yaml:
dependencies: flutter: sdk: flutter html: ^0.15.4 # 用于解析网页内容提取视频URL dio: ^5.4.0+1 # 网络请求 flutter_downloader: ^1.11.6 permission_handler: ^11.0.1 # 权限申请 path_provider: ^2.1.2 # 获取存储路径
2. 提取真实视频源URL
通过网页解析获取平台隐藏的真实视频地址:
import 'package:html/parser.dart' show parse; Future<String?> _getRealVideoUrl(String webUrl) async { try { final response = await Dio().get(webUrl, options: Options( headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36' } )); final document = parse(response.data); // 处理Instagram视频/Reels if (webUrl.contains('instagram.com')) { // 优先取OG标签的视频地址 final ogVideo = document.querySelector('meta[property="og:video"]'); if (ogVideo != null) return ogVideo.attributes['content']; // 动态内容 fallback:从JS脚本提取 final scripts = document.querySelectorAll('script'); for (var script in scripts) { if (script.text.contains('video_url')) { final match = RegExp(r'"video_url":"([^"]+)"').firstMatch(script.text); if (match != null) return Uri.decodeFull(match.group(1)!); } } } // 处理Facebook视频 if (webUrl.contains('facebook.com')) { final ogVideo = document.querySelector('meta[property="og:video"]'); if (ogVideo != null) return ogVideo.attributes['content']; final videoTag = document.querySelector('video'); if (videoTag != null) return videoTag.attributes['src']; } return null; } catch (e) { print('视频地址解析失败: $e'); return null; } }
3. 修正下载逻辑
替换原有_requestDownload方法,先获取真实视频URL再发起下载:
Future<void> _requestDownload(TaskInfo task) async { final webUrl = task.url!; if (!_permissionReady) { await _requestPermissions(); if (!_permissionReady) return; } // 获取真实视频源 final realVideoUrl = await _getRealVideoUrl(webUrl); if (realVideoUrl == null) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('解析失败'), content: const Text('无法获取视频真实下载地址,请检查URL有效性'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); return; } try { // 获取正确的文件类型 final headResponse = await Dio().head(realVideoUrl, options: Options( headers: {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'} )); final contentType = headResponse.headers['content-type']?.first; final fileExtension = _getFileExtension(contentType); final fileName = '${DateTime.now().millisecondsSinceEpoch}$fileExtension'; // 发起下载请求 task.taskId = await FlutterDownloader.enqueue( url: realVideoUrl, savedDir: _localPath!, fileName: fileName, saveInPublicStorage: _saveInPublicStorage, showNotification: true, openFileFromNotification: true, headers: {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36'}, ); } catch (e) { print('下载请求失败: $e'); showDialog( context: context, builder: (context) => AlertDialog( title: const Text('下载失败'), content: Text('请求视频资源出错: $e'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))], ), ); } }
4. 完善权限与路径处理
确保存储权限和路径逻辑正确:
import 'package:permission_handler/permission_handler.dart'; import 'package:path_provider/path_provider.dart'; import 'dart:io'; bool _permissionReady = false; String? _localPath; @override void initState() { super.initState(); _prepare(); } Future<void> _prepare() async { await _requestPermissions(); _localPath = await _findLocalPath(); setState(() {}); } Future<void> _requestPermissions() async { if (Platform.isAndroid) { final status = await Permission.storage.request(); _permissionReady = status.isGranted; } else if (Platform.isIOS) { final status = await Permission.photosAddOnly.request(); _permissionReady = status.isGranted; } } Future<String> _findLocalPath() async { final directory = Platform.isAndroid ? await getExternalStorageDirectory() : await getApplicationDocumentsDirectory(); return directory!.path; }
5. 优化URL验证逻辑
支持不带www的移动端域名:
bool isValidVideoUrl(String url) { final Uri? uri = Uri.tryParse(url); if (uri == null || !['http', 'https'].contains(uri.scheme)) return false; final String host = uri.host.toLowerCase(); return host.contains('instagram.com') || host.contains('tiktok.com') || host.contains('reels.com') || host.contains('facebook.com'); }
关键注意事项
- 反爬规避:必须添加
User-Agent请求头模拟浏览器,否则会被平台拦截请求。 - 动态内容兼容:部分视频由JS动态加载,若HTML解析失败,可考虑用WebView加载页面后通过JS注入提取视频URL。
- 版权合规:下载视频需严格遵守平台使用条款及版权法规,避免法律风险。
内容的提问来源于stack exchange,提问作者umair mudassar
相关产品推荐
相关产品推荐

