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

Flutter中使用on_audio_query查询应用目录音频文件失败

解决on_audio_query指定应用私有目录查询不到歌曲的问题

我开发了一款Flutter应用,允许用户下载歌曲并存入应用私有目录/storage/emulated/0/Android/data/com.zylae/files/downloads,但调用on_audio_query的querySongs方法并添加path参数后,完全查询不到任何歌曲,尝试移除路径开头的斜杠也无效。以下是修改自官方示例的代码:

import 'dart:io';

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

class AudioQ extends StatefulWidget {
  const AudioQ({Key? key}) : super(key: key);

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

class _AudioQState extends State<AudioQ> {
  // Main method.
  final OnAudioQuery _audioQuery = OnAudioQuery();

  // Indicate if application has permission to the library.
  bool _hasPermission = false;

  @override
  void initState() {
    super.initState();
    // (Optinal) Set logging level. By default will be set to 'WARN'.
    //
    // Log will appear on:
    //  * XCode: Debug Console
    //  * VsCode: Debug Console
    //  * Android Studio: Debug and Logcat Console
    LogConfig logConfig = LogConfig(logType: LogType.DEBUG);
    _audioQuery.setLogConfig(logConfig);

    // Check and request for permission.
    checkAndRequestPermissions();
  }

  checkAndRequestPermissions({bool retry = false}) async {
    // The param 'retryRequest' is false, by default.
    _hasPermission = await _audioQuery.checkAndRequest(
      retryRequest: retry,
    );

    // Only call update the UI if application has all required permissions.
    _hasPermission ? setState(() {}) : null;
  }

  @override
  Widget build(BuildContext context) {
    // _audioQuery.querySongs();
    return Scaffold(
      appBar: AppBar(
        title: const Text("OnAudioQueryExample"),
        elevation: 2,
      ),
      body: Center(
        child: !_hasPermission
            ? noAccessToLibraryWidget()
            : FutureBuilder<List<SongModel>>(
                // Default values:
                future: _audioQuery.querySongs(
                  sortType: null,
                  orderType: OrderType.ASC_OR_SMALLER,
                  uriType: UriType.EXTERNAL,
                  ignoreCase: true,
                  path: '/storage/emulated/0/Android/data/com.zylae/files/downloads',
                ),
                builder: (context, item) {
                  // Display error, if any.
                  if (item.hasError) {
                    return Text(item.error.toString());
                  }

                  // Waiting content.
                  if (item.data == null) {
                    return const CircularProgressIndicator();
                  }

                  // 'Library' is empty.
                  if (item.data!.isEmpty) return const Text("Nothing found!");

                  // You can use [item.data!] direct or you can create a:
                  // List<SongModel> songs = item.data!;
                  return ListView.builder(
                    itemCount: item.data!.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(item.data![index].title),
                        subtitle: Text(item.data![index].artist ?? "No Artist"),
                        trailing: const Icon(Icons.arrow_forward_rounded),
                        // This Widget will query/load image.
                        // You can use/create your own widget/method using [queryArtwork].
                        leading: QueryArtworkWidget(
                          controller: _audioQuery,
                          id: item.data![index].id,
                          type: ArtworkType.AUDIO,
                        ),
                      );
                    },
                  );
                },
              ),
      ),
    );
  }

  Widget noAccessToLibraryWidget() {
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(10),
        color: Colors.redAccent.withOpacity(0.5),
      ),
      padding: const EdgeInsets.all(20),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text("Application doesn't have access to the library"),
          const SizedBox(height: 10),
          ElevatedButton(
            onPressed: () => checkAndRequestPermissions(retry: true),
            child: const Text("Allow"),
          ),
        ],
      ),
    );
  }
}

问题原因及解决办法

  • 系统媒体库未索引私有目录文件:Android的应用私有目录(Android/data/包名/)下的文件,默认不会被系统媒体扫描器收录,而on_audio_query是基于系统媒体数据库进行查询的,所以指定该路径无法获取到文件。

  • 手动触发媒体扫描:在歌曲下载完成后,调用媒体扫描接口让系统识别新文件。可以使用flutter_media_scanner插件实现,示例代码:

    import 'package:flutter_media_scanner/flutter_media_scanner.dart';
    
    // 下载完成后调用此方法扫描文件
    Future<void> scanDownloadedSong(String filePath) async {
      await FlutterMediaScanner.scanFile(filePath);
    }
    

    扫描完成后,on_audio_query就能查询到该文件。

  • 权限适配:确保应用已申请对应权限:

    • Android 12及以下:申请READ_EXTERNAL_STORAGE权限
    • Android 13+:申请READ_MEDIA_AUDIO权限
  • 直接读取目录(替代方案):如果不想依赖系统媒体库,可以直接通过Dart的dart:io遍历目标目录,结合audio_metadata插件解析音频信息:

    import 'dart:io';
    import 'package:audio_metadata/audio_metadata.dart';
    
    Future<List<Map<String, dynamic>>> getLocalSongs() async {
      final songDir = Directory('/storage/emulated/0/Android/data/com.zylae/files/downloads');
      if (!await songDir.exists()) return [];
    
      final songFiles = songDir.listSync().where((entity) => 
        entity is File && entity.path.toLowerCase().endsWith('.mp3')
      ).cast<File>();
    
      final songs = <Map<String, dynamic>>[];
      for (var file in songFiles) {
        final metadata = await MetadataRetriever.fromFile(file);
        songs.add({
          'title': metadata.title ?? file.path.split('/').last,
          'artist': metadata.artist ?? 'Unknown',
          'file': file,
        });
      }
      return songs;
    }
    

内容的提问来源于stack exchange,提问作者Kenneth Ben-Boulo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 06:44:55