Files
App.K12.Student.Miniapp_Flu…/docs/demos/audio_player_demo.md
T
2026-09-04 15:22:20 +08:00

4.7 KiB
Raw Blame History

【Demo】音频播放(AudioPlayerCard 控制条组件)

源文件:lib/pages/demo/audio_player_demo.dart | 分类:组件封装 一句话:传一个音频地址,得到一个完整控制条 —— 播放/暂停、进度拖动、倍速、循环。


一、这个 Demo 解决什么问题

课程详情页里常有"音频小节"。本页演示 AudioPlayerCard(lib/core/components/audio_player_card.dart)的用法:

const AudioPlayerCard(
  url: _audioUrl,        // 网络源(也可以传 asset 本地源)
  title: '网络测试音频(约 6 分钟)',
  autoPlay: true,        // 进页自动播放
)

核心思想:AudioPlayerCard 只是"控制条",不是整页 —— 它设计成可嵌进任意列表/详情页(列表项、课程小节卡片都能放)。

对照 Vue:相当于"封装好的 <audio-player url="..."> 组件",内部用 just_audio 插件实现。


二、怎么运行 / 演示内容

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),切后台自动暂停——用户听一半切走,回来继续,不用业务页管。


四、关键代码剖析

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;
  • 进阶"列表连续播放":在 AudioPlayerCard 上加 setAudioSources + 上一首/下一首(just_audio 原生支持);
  • 真实项目:把 _audioUrl 换成课程接口返回的音频地址即可,组件层不用动。