# 【Demo】音频播放(AudioPlayerCard 控制条组件) > 源文件:`lib/pages/demo/audio_player_demo.dart` | 分类:组件封装 > 一句话:传一个音频地址,得到一个完整控制条 —— 播放/暂停、进度拖动、倍速、循环。 --- ## 一、这个 Demo 解决什么问题 课程详情页里常有"音频小节"。本页演示 `AudioPlayerCard`(`lib/core/components/audio_player_card.dart`)的用法: ```dart const AudioPlayerCard( url: _audioUrl, // 网络源(也可以传 asset 本地源) title: '网络测试音频(约 6 分钟)', autoPlay: true, // 进页自动播放 ) ``` 核心思想:**AudioPlayerCard 只是"控制条",不是整页** —— 它设计成可嵌进任意列表/详情页(列表项、课程小节卡片都能放)。 > 对照 Vue:相当于"封装好的 `` 组件",内部用 just_audio 插件实现。 --- ## 二、怎么运行 / 演示内容 ```dart runApp(const MaterialApp(home: AudioPlayerDemoPage())); ``` - 进页自动播放(网络流约 6 分钟); - 体验:播放/暂停、拖动进度(网络流拖动后出现"缓冲中 X%")、倍速切换、循环; - 切后台自动暂停(组件内部处理,页面无需操心)。 > 代码里注释掉的本地源 `assets/audio/sample-30s.mp3`:30 秒本地音频,seek 秒响应(本地文件跳播无需重新缓冲)。 --- ## 三、核心知识点(新手向) ### 1. 网络音频能否"拖动进度",取决于服务器是否支持 HTTP Range 音频拖到中间播放,播放器会向服务器请求**字节范围**(Range 请求)。支持则秒跳,不支持则跳不了: ``` curl -s -D - -o NUL -H "Range: bytes=0-1023" <音频url> ``` | 响应头 | 含义 | |---|---| | `HTTP/1.1 206 Partial Content` | ✅ 支持,可拖动 | | `HTTP/1.1 200 OK` | ❌ 不支持,seek 会被重置回起点 | > 本项目踩过的坑:samplelib.com 实测**不支持 Range**(返回 200),点进度条后位置被重置回起点; > 而且第二次打开有系统缓存 → 看似正常(会误导排查方向!)。现用 SoundHelix(curl 实测 206)。 > **接真实课程音频前,务必先 curl 验收服务器的 Range 支持。** ### 2. 网络流 seek 后的"缓冲中 X%" 进度条上的**浅色区域 = 已缓冲部分**;网络流 seek 后会有一次"缓冲中 X%"——这是 ExoPlayer 的正常机制,不是卡死。 本地文件没有这个问题,所以本地 demo 拖动"秒响应"。 ### 3. 切后台自动暂停 组件内部监听应用生命周期(AppLifecycleListener),切后台自动暂停——用户听一半切走,回来继续,不用业务页管。 --- ## 四、关键代码剖析 ```dart class AudioPlayerDemoPage extends StatelessWidget { const AudioPlayerDemoPage({super.key}); // 【临时调试】网络源:观察 seek 后进度条的"缓冲区域 + 缓冲中 X%" static const String _audioUrl = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('音频播放'), centerTitle: true), body: Padding( padding: EdgeInsets.all(16.w), child: Column( children: [ const AudioPlayerCard( // ← 一行引入,全部能力由组件提供 url: _audioUrl, title: '网络测试音频(约 6 分钟)', autoPlay: true, ), ... ], ), ), ); } } ``` 页面本身是 `StatelessWidget`——**音频状态全部在组件内部**(状态封装是组件化的精髓)。 --- ## 五、踩坑记录 1. **服务器不支持 HTTP Range 时 seek 会被重置**,且第二次打开有系统缓存会"假装正常"——验收必须用 curl 看 `206`; 2. AudioPlayerCard 是**控制条组件,不是整页**——别拿它当页面用,它设计来嵌进列表/详情; 3. 网络流 seek 后有"缓冲中 X%"是正常机制(ExoPlayer),浅色区域 = 已缓冲; 4. 本地 asset 源与网络 url 源二选一传参(`asset:` 或 `url:`),别两个都传; 5. 切后台自动暂停是组件内部处理的,业务页不要重复实现。 --- ## 六、扩展思考 - 组件内部用 just_audio 插件:想了解播放器原理(音源切换、事件流)直接读 `lib/core/components/audio_player_card.dart`; - 富文本里的 `