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

Flutter实现音频ZIP下载与应用内播放(文件不暴露至系统存储)

Flutter 音频ZIP包下载、提取与播放实现

需求说明

我是Flutter开发新手,当前项目中服务器托管了多份按分类存储音频的ZIP文件。现需实现:用户下载指定ZIP包并提取音频后,可在应用内播放该音频,且音频文件不会显示在手机系统存储中。

基于图片示例修改后的音频处理代码

首先在pubspec.yaml中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  archive: ^3.3.7
  http: ^1.1.0
  path_provider: ^2.1.1
  audioplayers: ^5.2.1 # 新增音频播放依赖

完整实现代码:

import 'package:flutter/material.dart';
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'package:audioplayers/audioplayers.dart'; // 引入音频播放库

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

  final String title = "音频ZIP下载与播放";

  @override
  AudioZipDemoState createState() => AudioZipDemoState();
}

class AudioZipDemoState extends State<AudioZipDemo> {
  bool _downloading = false;
  String? _appDir;
  List<String> _audioPaths = [];
  final AudioPlayer _audioPlayer = AudioPlayer(); // 音频播放器实例
  // 替换为你的目标音频ZIP地址
  final String _zipUrl = 'https://example.com/audio_category.zip';
  final String _localZipName = 'audio.zip';

  @override
  void initState() {
    super.initState();
    _initAppDir();
  }

  // 初始化应用私有存储目录
  Future<void> _initAppDir() async {
    _appDir ??= (await getApplicationDocumentsDirectory()).path;
  }

  // 下载ZIP文件到应用私有目录
  Future<File> _downloadZipFile(String url, String fileName) async {
    final response = await http.Client().get(Uri.parse(url));
    final file = File('$_appDir/$fileName');
    return file.writeAsBytes(response.bodyBytes);
  }

  // 下载并提取ZIP中的音频文件
  Future<void> _downloadAndExtractAudio() async {
    setState(() {
      _downloading = true;
      _audioPaths.clear();
    });

    final zippedFile = await _downloadZipFile(_zipUrl, _localZipName);
    await _extractAudioFromZip(zippedFile);

    setState(() {
      _downloading = false;
    });
  }

  // 解压ZIP并保存音频文件到应用私有目录
  Future<void> _extractAudioFromZip(File zippedFile) async {
    final bytes = zippedFile.readAsBytesSync();
    final archive = ZipDecoder().decodeBytes(bytes);

    for (final file in archive) {
      if (file.isFile && _isAudioFile(file.name)) {
        final filePath = '$_appDir/${file.name}';
        final outFile = File(filePath);
        await outFile.create(recursive: true);
        await outFile.writeAsBytes(file.content);
        _audioPaths.add(filePath);
      }
    }
  }

  // 判断是否为音频文件(可按需扩展格式)
  bool _isAudioFile(String fileName) {
    final audioExtensions = ['.mp3', '.wav', '.aac', '.m4a'];
    return audioExtensions.any(ext => fileName.toLowerCase().endsWith(ext));
  }

  // 播放指定路径的音频
  Future<void> _playAudio(String audioPath) async {
    await _audioPlayer.stop();
    await _audioPlayer.play(DeviceFileSource(audioPath));
  }

  // 构建音频列表UI
  Widget _buildAudioList() {
    return Expanded(
      child: _audioPaths.isEmpty
          ? const Center(child: Text('暂无音频文件'))
          : ListView.builder(
              itemCount: _audioPaths.length,
              itemBuilder: (context, index) {
                final audioName = File(_audioPaths[index]).path.split('/').last;
                return ListTile(
                  title: Text(audioName),
                  trailing: IconButton(
                    icon: const Icon(Icons.play_arrow),
                    onPressed: () => _playAudio(_audioPaths[index]),
                  ),
                );
              },
            ),
    );
  }

  // 下载进度指示器
  Widget _buildProgressIndicator() {
    return Container(
      width: 25,
      height: 25,
      padding: const EdgeInsets.fromLTRB(0, 20, 10, 20),
      child: const CircularProgressIndicator(
        strokeWidth: 3,
        valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
        actions: [
          if (_downloading) _buildProgressIndicator(),
          IconButton(
            icon: const Icon(Icons.file_download),
            onPressed: _downloadAndExtractAudio,
          ),
        ],
      ),
      body: Column(
        children: [
          _buildAudioList(),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _audioPlayer.dispose(); // 释放播放器资源
    super.dispose();
  }
}

关键改动说明

  • 私有存储保障:使用getApplicationDocumentsDirectory()获取应用私有目录,该目录下的文件不会被系统媒体库扫描,满足"音频不显示在系统存储"的要求。
  • 音频播放支持:引入audioplayers库实现本地音频播放,包含播放控制和资源释放逻辑。
  • 文件过滤处理:添加_isAudioFile方法仅提取ZIP中的音频文件,过滤无关文件。
  • UI适配调整:将图片列表替换为音频列表项,显示文件名并添加播放按钮。

内容的提问来源于stack exchange,提问作者joshua

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:09:24