# audioplay **Repository Path**: nirenxing/audioplay ## Basic Information - **Project Name**: audioplay - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-09-02 - **Last Updated**: 2025-09-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Flutter音频播放应用架构 一个完整的Flutter音频播放应用架构,提供了高度抽象和可扩展的音频播放解决方案。 ## 🎯 核心特性 - **抽象接口设计**:统一的音频数据源和播放器接口,支持多种实现 - **工厂模式**:动态创建和切换不同的数据源和播放器 - **依赖注入**:基于服务定位器的依赖管理 - **插件化架构**:支持运行时注册新的数据源和播放器类型 - **完整异常处理**:统一的异常体系和错误处理 - **状态管理友好**:提供响应式的事件流,便于与状态管理库集成 - **测试友好**:支持模拟实现,便于单元测试和集成测试 ## 🏗️ 架构概览 ### 核心组件 1. **AudioItem** - 音频项基础信息模型 2. **AudioCollection** - 音频列表分组模型 3. **PlaybackProgress** - 播放进度信息模型 4. **PlayerState** - 播放器状态枚举 5. **AudioDataSource** - 音频数据源抽象接口 6. **AudioPlayer** - 音频播放器抽象接口 ### 工厂和服务 - **AudioDataSourceFactory** - 数据源工厂 - **AudioPlayerFactory** - 播放器工厂 - **ServiceLocator** - 服务定位器 - **AppInitializer** - 应用初始化器 ### 默认实现 - **MockDataSource** - 模拟数据源(用于开发测试) - **LocalDataSource** - 本地音频数据源 - **MockAudioPlayer** - 模拟播放器(用于开发测试) - **JustAudioPlayer** - just_audio播放器实现框架 ## 🚀 快速开始 ### 1. 应用导航流程 应用采用三级导航结构: 1. **首页列表** - 显示音频集合(专辑/分类) 2. **专辑页面** - 显示专辑内的音频列表 3. **播放器页面** - 音频播放控制和播放列表管理 #### 导航流程示例: ``` 首页 → 点击"小说故事" → 专辑页面 → 点击具体音频 → 开始播放并跳转到播放器 ``` ### 2. 基本使用 ```dart import 'package:flutter/material.dart'; import 'package:audio_play/audio_play.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 初始化音频应用架构 await AudioPlayApp.initialize({ 'defaultDataSource': 'mock', 'defaultPlayer': 'mock', 'enableLogging': true, }); runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: AudioHomePage(), ); } } // 首页显示音频集合列表 class AudioHomePage extends StatefulWidget { @override _AudioHomePageState createState() => _AudioHomePageState(); } class _AudioHomePageState extends State { List _collections = []; @override void initState() { super.initState(); _loadCollections(); } Future _loadCollections() async { try { final dataSource = AudioPlayApp.dataSource; final collections = await dataSource.fetchHomeCollections(); setState(() { _collections = collections; }); } catch (e) { print('加载集合失败: $e'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('音频播放演示')), body: ListView.builder( itemCount: _collections.length, itemBuilder: (context, index) { final collection = _collections[index]; return ListTile( title: Text(collection.title), subtitle: Text(collection.description ?? ''), trailing: Text('${collection.totalCount}首'), onTap: () { // 导航到专辑页面 Navigator.push( context, MaterialPageRoute( builder: (context) => CollectionPage(collection: collection), ), ); }, ); }, ), ); } } // 专辑页面显示具体音频列表 class CollectionPage extends StatefulWidget { final AudioCollection collection; const CollectionPage({required this.collection}); @override _CollectionPageState createState() => _CollectionPageState(); } class _CollectionPageState extends State { List _audioItems = []; @override void initState() { super.initState(); _loadAudioList(); } Future _loadAudioList() async { try { final dataSource = AudioPlayApp.dataSource; final items = await dataSource.fetchAudioList(widget.collection.id); setState(() { _audioItems = items; }); } catch (e) { print('加载音频列表失败: $e'); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(widget.collection.title)), body: ListView.builder( itemCount: _audioItems.length, itemBuilder: (context, index) { final item = _audioItems[index]; return ListTile( title: Text(item.title), subtitle: Text('${item.artist} • ${item.formattedDuration}'), onTap: () async { // 播放音频并跳转到播放器 final player = AudioPlayApp.player; await player.setPlaylist(_audioItems, initialIndex: index); await player.play(item); Navigator.push( context, MaterialPageRoute(builder: (context) => PlayerPage()), ); }, ); }, ), ); } } ``` ### 2. 使用喜马拉雅数据源 应用已经集成了喜马拉雅API,可以直接使用: ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); // 使用喜马拉雅数据源 await AudioPlayApp.initialize({ 'defaultDataSource': 'ximalaya', // 使用喜马拉雅 'defaultPlayer': 'mock', 'enableLogging': true, }); runApp(MyApp()); } ``` #### 在应用中切换数据源 应用提供了数据源切换功能: 1. 点击首页右上角的设置按钮 2. 选择要使用的数据源: - **mock** - 模拟数据源(用于测试) - **local** - 本地音频文件 - **ximalaya** - 喜马拉雅在线音频 3. 应用会自动重新初始化并加载新数据源的内容 #### 喜马拉雅API特点 - **首页推荐**:获取喜马拉雅的推荐专辑列表 - **专辑详情**:获取专辑内的音频列表 - **音频播放**:获取音频的播放链接和详细信息 - **自动获取详情**:点击播放时自动获取音频的完整信息 ### 3. 自定义数据源 如果需要集成其他音频平台,可以实现自定义数据源: ```dart // 实现自定义数据源 class CustomDataSource implements AudioDataSource { @override String get sourceType => 'custom'; @override String get displayName => '自定义平台'; @override Future> fetchHomeCollections() async { // 实现自定义API调用逻辑 // ... } // 实现其他必需方法... } // 注册自定义数据源 void main() async { WidgetsFlutterBinding.ensureInitialized(); // 注册自定义数据源 DataSourceManager.registerType( 'custom', (config) => CustomDataSource(config), ); await AudioPlayApp.initialize(); runApp(MyApp()); } ``` ### 3. 播放器事件监听 ```dart class PlayerWidget extends StatefulWidget { @override _PlayerWidgetState createState() => _PlayerWidgetState(); } class _PlayerWidgetState extends State { late AudioPlayer _player; PlayerState _state = PlayerState.idle; PlaybackProgress _progress = PlaybackProgress.initial(); @override void initState() { super.initState(); _player = AudioPlayApp.player; _setupListeners(); } void _setupListeners() { // 监听播放状态变化 _player.stateStream.listen((state) { setState(() { _state = state; }); }); // 监听播放进度 _player.progressStream.listen((progress) { setState(() { _progress = progress; }); }); // 监听当前播放项变化 _player.currentItemStream.listen((item) { print('当前播放: ${item?.title}'); }); // 监听错误 _player.errorStream.listen((error) { print('播放错误: $error'); }); } @override Widget build(BuildContext context) { return Column( children: [ Text('状态: ${_state.displayName}'), LinearProgressIndicator(value: _progress.progressPercentage), Text(_progress.progressDisplay), Row( children: [ IconButton( onPressed: _state.canPlay ? () => _player.resume() : null, icon: Icon(Icons.play_arrow), ), IconButton( onPressed: _state.canPause ? () => _player.pause() : null, icon: Icon(Icons.pause), ), IconButton( onPressed: _state.canStop ? () => _player.stop() : null, icon: Icon(Icons.stop), ), ], ), ], ); } } ``` ## 📁 项目结构 ``` lib/ ├── audio_play.dart # 主导出文件 ├── core/ # 核心组件 │ ├── app_initializer.dart # 应用初始化器 │ └── service_locator.dart # 服务定位器 ├── exceptions/ # 异常处理 │ └── audio_exceptions.dart # 音频相关异常 ├── factories/ # 工厂类 │ ├── audio_data_source_factory.dart │ └── audio_player_factory.dart ├── implementations/ # 默认实现 │ ├── data_sources/ # 数据源实现 │ │ ├── mock_data_source.dart │ │ └── local_data_source.dart │ └── players/ # 播放器实现 │ ├── mock_audio_player.dart │ └── just_audio_player.dart ├── models/ # 数据模型 │ ├── audio_collection.dart │ ├── audio_item.dart │ ├── playback_progress.dart │ └── player_state.dart ├── services/ # 服务接口 │ ├── audio_data_source.dart │ └── audio_player.dart └── main.dart # 示例应用 ``` ## 🔧 配置选项 ### 初始化配置 ```dart await AudioPlayApp.initialize({ // 数据源配置 'defaultDataSource': 'mock', // 默认数据源类型 'enableDataSourceCache': true, // 启用数据源缓存 'dataSourceTimeout': 30000, // 数据源超时时间(ms) // 播放器配置 'defaultPlayer': 'mock', // 默认播放器类型 'enablePlayerSingleton': true, // 启用播放器单例模式 'autoInitializePlayer': true, // 自动初始化播放器 // 调试配置 'enableLogging': true, // 启用日志 'logLevel': 'debug', // 日志级别 // 性能配置 'maxCacheSize': 100, // 最大缓存大小 'networkTimeout': 15000, // 网络超时时间(ms) 'maxRetryAttempts': 3, // 最大重试次数 }); ``` ## 🧪 测试支持 ### 单元测试示例 ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:audio_play/audio_play.dart'; void main() { group('AudioPlayer Tests', () { late AudioPlayer player; setUp(() async { // 重置服务定位器 await AudioPlayApp.reset(); // 初始化测试环境 await AudioPlayApp.initialize({ 'defaultPlayer': 'mock', 'enableLogging': false, }); player = AudioPlayApp.player; await player.init(); }); tearDown(() async { await player.dispose(); await AudioPlayApp.reset(); }); test('should initialize player', () { expect(player.isInitialized, isTrue); expect(player.currentState, equals(PlayerState.idle)); }); test('should play audio item', () async { final audioItem = AudioItem( id: 'test_001', title: 'Test Audio', artist: 'Test Artist', audioUrl: 'https://example.com/test.mp3', duration: 180000, sourceType: 'test', ); await player.play(audioItem); expect(player.currentItem, equals(audioItem)); expect(player.currentState, equals(PlayerState.playing)); }); }); } ``` ## 📚 API文档 ### AudioItem 音频项基础信息模型,包含音频的所有元数据。 **主要属性:** - `id`: 唯一标识符 - `title`: 标题 - `artist`: 艺术家/作者 - `audioUrl`: 音频流链接 - `duration`: 总时长(毫秒) - `sourceType`: 数据源类型 ### AudioCollection 音频列表分组模型,用于组织音频内容。 **主要属性:** - `id`: 分组ID - `title`: 分组标题 - `items`: 音频项列表 - `type`: 推荐类型/分类 ### AudioDataSource 音频数据源抽象接口,定义数据获取的标准方法。 **主要方法:** - `fetchHomeCollections()`: 获取首页推荐列表 - `fetchAudioList()`: 获取分组明细列表 - `getAudioDetail()`: 获取单个音频详情 - `searchAudio()`: 搜索音频内容 ### AudioPlayer 音频播放器抽象接口,定义播放控制的标准方法。 **主要方法:** - `play()`: 播放指定音频 - `pause()`: 暂停播放 - `resume()`: 恢复播放 - `seek()`: 跳转进度 - `stop()`: 停止播放 **事件流:** - `progressStream`: 播放进度流 - `stateStream`: 播放状态流 - `currentItemStream`: 当前播放项流 ## 🔮 扩展计划 1. **更多数据源实现** - 喜马拉雅API集成 - 懒人听书API集成 - Spotify API集成 - 网易云音乐API集成 2. **更多播放器实现** - audioplayers插件集成 - 原生播放器集成 - 流媒体播放器优化 3. **高级功能** - 播放历史记录 - 收藏和播放列表管理 - 离线下载支持 - 音频均衡器 - 睡眠定时器 4. **状态管理集成** - Riverpod集成示例 - Bloc集成示例 - Provider集成示例 ## 🤝 贡献指南 欢迎提交Issue和Pull Request来改进这个项目。 ### 开发环境设置 1. 克隆项目 ```bash git clone cd audio_play ``` 2. 安装依赖 ```bash flutter pub get ``` 3. 运行示例 ```bash flutter run ``` 4. 运行测试 ```bash flutter test ``` ## 📄 许可证 本项目采用MIT许可证。详见[LICENSE](LICENSE)文件。