【Flutter for OpenHarmony】Flutter三方库冥想背景音乐的鸿蒙化适配与实战指南
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、为什么我要添加背景音乐?
我是 IntMainJhy,上海某高校大一计算机专业的学生。说起冥想背景音乐这个功能,我想了很久要不要做。
一方面,冥想的时候有背景音乐确实能让人更放松;另一方面,音频播放涉及到原生平台的交互,实现起来比较复杂。
后来室友说了一句话点醒了我:“你做冥想 App,没有背景音乐,那还叫冥想 App 吗?”
于是我就开始研究 Flutter 的音频播放方案。
二、音频播放方案对比
Flutter 中常用的音频播放库有几个:
| 库名 | 优点 | 缺点 |
|---|---|---|
audioplayers | 功能丰富、支持多平台 | 包体积较大 |
just_audio | API 简洁、性能好 | 文档较少 |
flutter_music_player | 专门针对音乐 | 维护不积极 |
| 系统 SoundPool | 适合短音频 | 不适合长音频 |
我最终选择了just_audio,因为它的 API 最简洁,性能也比较好。
三、依赖配置
# pubspec.yamldependencies:just_audio:^0.9.40# 鸿蒙平台支持# just_audio 0.9.40+ 版本已支持鸿蒙四、音频服务实现
// lib/mental_health/services/audio_service.dartimport'package:flutter/foundation.dart';import'package:just_audio/just_audio.dart';/// 冥想音频类型enumMeditationAudioType{rain('雨声','assets/audio/rain.mp3'),forest('森林','assets/audio/forest.mp3'),ocean('海浪','assets/audio/ocean.mp3'),fire('篝火','assets/audio/fire.mp3'),wind('风声','assets/audio/wind.mp3'),night('夜晚','assets/audio/night.mp3'),river('溪流','assets/audio/river.mp3'),birds('鸟鸣','assets/audio/birds.mp3');finalStringname;finalStringassetPath;constMeditationAudioType(this.name,this.assetPath);}/// 冥想音频服务classMeditationAudioService{// 单例模式staticfinalMeditationAudioService_instance=MeditationAudioService._internal();factoryMeditationAudioService()=>_instance;MeditationAudioService._internal();// 播放器AudioPlayer?_player;// 当前播放的音频类型MeditationAudioType?_currentAudio;// 是否正在播放bool _isPlaying=false;// 音量(0.0 - 1.0)double _volume=0.5;// 是否循环播放bool _isLooping=true;// GettersboolgetisPlaying=>_isPlaying;MeditationAudioType?getcurrentAudio=>_currentAudio;doublegetvolume=>_volume;boolgetisLooping=>_isLooping;/// 初始化Future<void>initialize()async{_player=AudioPlayer();_player!.playerStateStream.listen((state){_isPlaying=state.playing;if(kDebugMode){print('Audio state: playing=${state.playing}');}});}/// 播放指定音频Future<void>play(MeditationAudioTypeaudioType)async{if(_player==null){awaitinitialize();}try{_currentAudio=audioType;// 设置循环播放if(_isLooping){await_player!.setLoopMode(LoopMode.one);}// 设置音量await_player!.setVolume(_volume);// 播放音频await_player!.setAsset(audioType.assetPath);await_player!.play();_isPlaying=true;}catch(e){debugPrint('播放音频失败:$e');}}/// 暂停播放Future<void>pause()async{await_player?.pause();_isPlaying=false;}/// 继续播放Future<void>resume()async{await_player?.play();_isPlaying=true;}/// 停止播放Future<void>stop()async{await_player?.stop();_isPlaying=false;_currentAudio=null;}/// 设置音量Future<void>setVolume(double volume)async{_volume=volume.clamp(0.0,1.0);await_player?.setVolume(_volume);}/// 切换循环模式Future<void>setLoopMode(bool loop)async{_isLooping=loop;await_player?.setLoopMode(loop?LoopMode.one:LoopMode.off);}/// 释放资源Future<void>dispose()async{await_player?.dispose();_player=null;}}五、音频选择器 UI
// lib/mental_health/widgets/audio_selector_widget.dartimport'package:flutter/material.dart';import'../services/audio_service.dart';/// 背景音乐选择器classAudioSelectorWidgetextendsStatefulWidget{finalFunction(MeditationAudioType)?onAudioSelected;constAudioSelectorWidget({super.key,this.onAudioSelected,});@overrideState<AudioSelectorWidget>createState()=>_AudioSelectorWidgetState();}class_AudioSelectorWidgetStateextendsState<AudioSelectorWidget>{finalAudioService_audioService=AudioService();MeditationAudioType?_selectedAudio;bool _isPlaying=false;@overrideWidgetbuild(BuildContextcontext){returnContainer(padding:constEdgeInsets.all(16),decoration:BoxDecoration(color:Colors.white,borderRadius:BorderRadius.circular(16),),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[constText('背景音乐',style:TextStyle(fontSize:16,fontWeight:FontWeight.bold,),),constSizedBox(height:4),constText('选择适合的冥想背景音乐',style:TextStyle(fontSize:12,color:Color(0xFF636E72),),),constSizedBox(height:16),// 音频网格GridView.builder(shrinkWrap:true,physics:constNeverScrollableScrollPhysics(),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:4,crossAxisSpacing:12,mainAxisSpacing:12,childAspectRatio:1,),itemCount:MeditationAudioType.values.length,itemBuilder:(context,index){finalaudio=MeditationAudioType.values[index];finalisSelected=_selectedAudio==audio;returnGestureDetector(onTap:()=>_selectAudio(audio),child:AnimatedContainer(duration:constDuration(milliseconds:200),decoration:BoxDecoration(color:isSelected?Theme.of(context).primaryColor.withOpacity(0.1):constColor(0xFFF5F5F5),borderRadius:BorderRadius.circular(12),border:Border.all(color:isSelected?Theme.of(context).primaryColor:Colors.transparent,width:2,),),child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[Icon(_getIconForAudio(audio),color:isSelected?Theme.of(context).primaryColor:constColor(0xFF636E72),),constSizedBox(height:4),Text(audio.name,style:TextStyle(fontSize:10,color:isSelected?Theme.of(context).primaryColor:constColor(0xFF636E72),),),],),),);},),constSizedBox(height:16),// 音量控制Row(children:[constIcon(Icons.volume_down,size:20,color:Color(0xFF636E72)),Expanded(child:Slider(value:0.5,onChanged:(value){_audioService.setVolume(value);},),),constIcon(Icons.volume_up,size:20,color:Color(0xFF636E72)),],),],),);}void_selectAudio(MeditationAudioTypeaudio){setState((){if(_selectedAudio==audio&&_isPlaying){// 再次点击停止_audioService.stop();_isPlaying=false;_selectedAudio=null;}else{// 播放新音频_selectedAudio=audio;_audioService.play(audio);_isPlaying=true;widget.onAudioSelected?.call(audio);}});}IconData_getIconForAudio(MeditationAudioTypeaudio){switch(audio){caseMeditationAudioType.rain:returnIcons.water_drop;caseMeditationAudioType.forest:returnIcons.forest;caseMeditationAudioType.ocean:returnIcons.waves;caseMeditationAudioType.fire:returnIcons.local_fire_department;caseMeditationAudioType.wind:returnIcons.air;caseMeditationAudioType.night:returnIcons.nightlight;caseMeditationAudioType.river:returnIcons.water;caseMeditationAudioType.birds:returnIcons.flutter_dash;}}}六、在冥想页面中使用
// 在 MeditationScreen 中使用classMeditationScreenextendsStatelessWidget{@overrideWidgetbuild(BuildContextcontext){returnScaffold(body:Column(children:[// 音频选择器AudioSelectorWidget(onAudioSelected:(audio){print('选择了:${audio.name}');},),// 其他内容...],),);}}七、鸿蒙平台适配
适配点1:音频资源路径
鸿蒙设备上音频资源的路径可能需要特别处理:
Future<void>play(MeditationAudioTypeaudioType)async{try{// 鸿蒙设备上使用正确的资源路径await_player!.setAsset(audioType.assetPath);await_player!.play();}catch(e){debugPrint('播放失败:$e');// 降级处理:跳过音频}}适配点2:后台播放
鸿蒙设备后台播放音频需要额外配置:
// 在 AndroidManifest.xml 或对应配置中添加后台音频权限八、我的踩坑记录
坑1:音频文件路径错误
报错:
Error: Unable to load asset: assets/audio/rain.mp3原因:音频文件没有添加到pubspec.yaml的 assets 配置中。
解决:
# pubspec.yamlflutter:assets:-assets/images/-assets/icons/-assets/audio/# 添加这一行坑2:音频播放时 App 崩溃
报错:
PlatformException (AudioPlayer)原因:音频文件格式不被支持,或者文件损坏。
解决:
Future<void>play(MeditationAudioTypeaudioType)async{try{await_player!.setAsset(audioType.assetPath);await_player!.play();}catch(e){debugPrint('播放失败,降级处理:$e');// 可以在这里显示一个 toast 告诉用户}}坑3:音量调节不生效
问题:滑动音量条后,音量没有变化。
原因:Slider的onChanged没有正确调用服务方法。
解决:
Slider(value:_audioService.volume,// 使用服务的当前音量onChanged:(value){setState((){});// 触发 UI 更新_audioService.setVolume(value);// 调用服务方法},)九、功能验证清单
| 序号 | 检查项 | 状态 |
|---|---|---|
| 1 | 音频列表显示正确 | ⏳ |
| 2 | 点击播放/停止正常 | ⏳ |
| 3 | 音量调节正常 | ⏳ |
| 4 | 循环播放正常 | ⏳ |
| 5 | 切换音频正常 | ⏳ |
| 6 | 鸿蒙设备播放正常 | ⏳ |
十、大一学生真实学习总结
说实话,音频播放这个功能花了我不少时间。
最大的难点在于:
- 音频资源的准备:需要找合适的音频文件
- 原生平台交互:音频播放涉及到原生代码
- 后台播放:用户锁屏后音频要继续播放
不过just_audio库封装得很好,大部分情况下只需要几行代码就能实现音频播放。
作者:IntMainJhy
创作时间:2026年5月