flutter架 构项目基础

This commit is contained in:
崔宝新
2026-09-04 15:22:20 +08:00
parent 70967229d0
commit 0c5aee59c0
265 changed files with 35484 additions and 0 deletions
+118
View File
@@ -0,0 +1,118 @@
# 【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:相当于"封装好的 `<audio-player url="...">` 组件",内部用 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`;
- 富文本里的 `<audio>` 标签会自动替换成 AudioPlayerCard,见 [rich_text_demo.md](rich_text_demo.md);
- 进阶"列表连续播放":在 AudioPlayerCard 上加 `setAudioSources` + 上一首/下一首(just_audio 原生支持);
- 真实项目:把 `_audioUrl` 换成课程接口返回的音频地址即可,组件层不用动。