You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 19:29:55