2024 桐庐半程马拉松
00:00:00
时间
0.00
距离(公里)
--:--
配速
--
步频
--
心率 (bpm)
--
配速
步频
|
share-image
ESC

悦音播放器:一个 Flutter 本地音乐播放器的诞生

2026年的大年初一,我躺在甘肃妻子家的炕上,听着窗外此起彼伏的鞭炮声,手里握着手机,却怎么也找不到一个能安静听歌的 App。那一刻,我决定自己动手,写一个属于我自己的音乐播放器。



构建高可扩展音乐流媒体后端:Go+Gin+OSS架构实战

被”绑架”的音乐体验

事情要从大年初一的早晨说起。

2026年的春节,我跟着妻子回甘肃老家过年。西北的冬天格外冷,屋外是零下十几度的寒风,屋内是烧得暖烘烘的炕。这样惬意的环境,最适合窝在被窝里听听音乐。

我打开手机上的 QQ 音乐,搜索了一首想听的老歌。点击播放——

“试听 30 秒,开通会员畅听完整版”

行吧,我认了。现在版权时代,充个会员支持正版也是应该的。于是果断扫码付款,开通了会员。

然而,当我心满意足地准备享受音乐时,噩梦才刚刚开始:

  • 开屏广告:5 秒
  • 播放页横幅广告:虽然小但很碍眼
  • 切歌时的插屏广告:猝不及防
  • 直播入口、社交功能、商城推荐:各种花里胡哨的功能占据了大半个屏幕

我花了钱,却仍然逃不掉广告的轰炸。更过分的是,我的听歌数据被用来推荐各种”可能感兴趣”的内容,算法比我妈还了解我的喜好,却唯独不肯让我安安静静听首歌。

那一刻,我突然很怀念十几年前的 MP3 时代。那时候,一首歌就是一首歌,简单纯粹,没有会员体系,没有算法推荐,没有社交功能。

“既然市面上没有我想要的播放器,那我就自己写一个。”

技术选型:为什么是 Flutter?

作为一个 Android 开发,我第一反应是用原生 Kotlin 来写。但考虑到未来可能要做 iOS 版本,还是决定用跨平台方案。

在 React Native 和 Flutter 之间,我最终选择了 Flutter

原因有三:

  1. 性能接近原生:Flutter 的 Skia 渲染引擎让 UI 动画丝般顺滑,对于音乐播放器这种需要精美交互的 App 来说至关重要。

  2. 一套代码,多端运行:虽然目前只做了 Android 版本,但为未来移植 iOS 预留了可能性。

  3. 生态完善:Flutter 的音频播放生态相当成熟,just_audio + audio_service 的组合可以完美实现后台播放。

最新版本功能(v1.9.59)

更新日志

v1.9.59 (2026-02-22)

  • 🎨 13 种全新主题 - 浅色、深色、樱花粉、深海蓝、森林绿、紫罗兰、日落橙、薄荷青、暗黑红、金色奢华、天空蓝、霓虹紫、跟随系统
  • 🎵 14 种播放器样式 - 黑胶唱片、波形可视化、旋转碟片、极简风格、复古磁带、霓虹脉冲、粒子星云、频谱瀑布、魔法光环、均衡器、涟漪效果、赛博朋克、3D 卡片、CD 盒
  • 玻璃拟态设计 - 所有页面背景采用毛玻璃模糊效果,视觉更沉浸
  • 🔧 修复主题选择器文字对比度问题
  • 🔧 修复 APK 下载后自动安装问题

经过持续的迭代优化,悦音播放器已经新增了许多实用的功能:

🎨 13 种精美主题

悦音现在支持 13 种主题配色,满足不同审美需求:

主题 风格
浅色 / 深色 经典明暗模式
樱花粉 温柔粉嫩的少女风
深海蓝 深邃神秘的海洋风
森林绿 清新自然的护眼风
紫罗兰 优雅浪漫的紫色系
日落橙 温暖活力的夕阳风
薄荷青 清凉舒爽的薄荷风
暗黑红 低调沉稳的暗红系
金色奢华 高贵典雅的金色系
天空蓝 明朗开阔的天空风
霓虹紫 炫酷前卫的霓虹风
跟随系统 自动适配系统主题

🎵 14 种播放器样式

播放器页面现在支持 14 种视觉效果,让听歌成为一种享受:

  1. 黑胶唱片 - 经典旋转唱片 + 金属唱臂
  2. 波形可视化 - 实时音频波形动画
  3. 旋转碟片 - 简洁的旋转 CD 效果
  4. 极简风格 - 纯净无干扰的极简设计
  5. 复古磁带 - 怀旧卡带转动效果
  6. 霓虹脉冲 - 赛博朋克霓虹灯效果
  7. 粒子星云 - 梦幻粒子环绕动画
  8. 频谱瀑布 - 音乐频谱瀑布流
  9. 魔法光环 - 神秘光环扩散效果
  10. 均衡器 - 专业均衡器可视化
  11. 涟漪效果 - 水波涟漪扩散动画
  12. 赛博朋克 - 未来科技风格
  13. 3D 卡片 - 立体翻转卡片效果
  14. CD 盒 - 经典 CD 盒展示效果

✨ 玻璃拟态效果

所有页面背景都采用 BackdropFilter 毛玻璃模糊效果,配合渐变色背景,营造出层次丰富的视觉体验:

  • 底部导航栏 - 半透明毛玻璃
  • 播放列表页 - 动态渐变背景
  • 设置页面 - 统一的玻璃质感

🎨 智能缩略图

无封面歌曲现在会显示精美的渐变色缩略图:

  • 根据歌曲 ID 生成一致的渐变色背景(12 种配色方案)
  • 显示歌曲标题首字母的大字体图标
  • 中大尺寸显示歌曲名称和歌手名称
  • 告别单调的灰色音乐图标

🎵 黑胶唱片播放器

播放页面全新设计了黑胶唱片动画效果:

  • 旋转的黑胶唱片,带有真实的纹路和光泽
  • 金属质感的唱臂,随播放状态移动
  • 中心显示专辑封面,旋转时保持视觉焦点

📝 歌词显示

支持 LRC 格式歌词同步显示:

  • 右滑播放器页面进入歌词界面
  • 歌词自动滚动,当前行高亮显示
  • 支持点击歌词跳转对应播放位置
  • MiniPlayer 左滑也可显示当前歌词

📁 歌单管理

  • 创建自定义歌单,管理音乐收藏
  • 歌单列表显示封面缩略图(自动取第一首歌的封面)
  • 一键添加歌曲到歌单
  • 收藏功能快速访问喜欢的歌曲

🎮 便捷操作

  • 下滑隐藏:播放器页面下滑即可关闭
  • 右滑歌词:播放器页面右滑查看歌词
  • MiniPlayer 歌词:底部迷你播放器左右滑动切换歌词显示
  • 智能更新:自动检测新版本并提示更新

架构设计:简洁而强大

整个项目的架构我遵循了 “简洁优先” 的原则,没有过度设计,也没有用一堆花哨的设计模式。

lib/
├── models/ # 数据模型层
│ ├── song.dart
│ ├── album.dart
│ ├── artist.dart
│ └── playlist.dart
├── services/ # 业务服务层
│ ├── audio_player_service.dart
│ ├── audio_handler.dart
│ ├── media_scanner_service.dart
│ └── playlist_service.dart
├── providers/ # 状态管理层(Provider)
│ ├── player_provider.dart
│ ├── library_provider.dart
│ └── settings_provider.dart
├── screens/ # 页面层
└── widgets/ # 组件层

核心播放引擎:just_audio + audio_service

音频播放是音乐 App 的核心,我选择了 just_audio 作为播放引擎。

class AudioPlayerService {
static final AudioPlayerService _instance = AudioPlayerService._internal();
factory AudioPlayerService() => _instance;
AudioPlayerService._internal();

// 使用 AudioHandler 实现后台播放
MyAudioHandler get _handler => _globalAudioHandler;
AudioPlayer get _player => _globalAudioHandler.player;

// 状态流
Stream<PlayerState> get playerStateStream => _player.playerStateStream;
Stream<Duration> get positionStream => _player.positionStream;

// 播放控制
Future<void> play() => _player.play();
Future<void> pause() => _player.pause();
Future<void> seek(Duration position) => _player.seek(position);
}

just_audio 的优势:

  • 支持多种格式:MP3、AAC、FLAC、WAV、OGG、OPUS 等无损格式
  • 低延迟:播放响应速度快
  • 稳定的后台播放:配合 audio_service 可以实现锁屏控制、通知栏控制

状态管理:Provider 足够用了

项目没有使用 Riverpod 或 Bloc,而是选择了最经典的 Provider

class PlayerProvider extends ChangeNotifier {
Song? _currentSong;
bool _isPlaying = false;
Duration _position = Duration.zero;
Duration _duration = Duration.zero;

// Getters
Song? get currentSong => _currentSong;
bool get isPlaying => _isPlaying;

// 播放控制
Future<void> playSong(Song song) async {
await _audioService.playSong(song);
_currentSong = song;
notifyListeners();
}
}

对于中小型项目来说,Provider 简单、直观、易于调试,完全够用。

媒体扫描:智能过滤非音乐文件

本地音乐播放器最大的挑战之一,是如何从手机存储中准确识别音乐文件,同时过滤掉录音、铃声、微信语音等非音乐内容。

我的解决方案:

class MediaScannerService {
// 需要排除的目录关键词
final List<String> _excludedPaths = [
'call', 'recorder', 'recording', 'recordings',
'voice', 'voices', 'voicerecorder',
'whatsapp', 'wechat', 'qq',
'notifications', 'ringtones', 'alarms', 'podcasts',
'android/data', 'android/obb', '.cache', '.thumbnails',
];

// 支持的音频格式
final List<String> _supportedFormats = [
'mp3', 'aac', 'm4a', 'flac', 'wav', 'ogg', 'opus', 'ape', 'dsd'
];

Future<List<Song>> scanSongs({String? targetPath}) async {
// 使用 on_audio_query 获取设备上的音频文件
final songs = await _audioQuery.querySongs();

return songs
.map((model) => Song.fromSongModel(model))
.where((song) => _isValidSong(song))
.toList();
}

bool _isValidSong(Song song) {
// 排除特定路径
if (_isExcludedPath(song.uri)) return false;

// 过滤时长少于 30 秒的(可能是铃声)
if (song.duration < 30000) return false;

// 检查格式支持
return _supportedFormats.contains(song.fileExtension.toLowerCase());
}
}

这套过滤规则经过实测,可以过滤掉 95% 以上的非音乐音频文件。

界面设计:回归音乐的本质

UI 设计上,我坚持**”减法设计”**理念:

  1. 没有广告:永远不会有任何形式的广告
  2. 没有社交:不做评论、不做分享、不做排行榜
  3. 没有算法推荐:用户自己管理自己的歌单
  4. 沉浸式播放页:黑胶唱片动画,歌词居中显示

主题切换

支持浅色/深色模式切换:

class AppThemeData {
static ThemeData get lightTheme {
return ThemeData(
brightness: Brightness.light,
primaryColor: const Color(0xFF6C63FF),
scaffoldBackgroundColor: const Color(0xFFF8F9FA),
// ...
);
}

static ThemeData get darkTheme {
return ThemeData(
brightness: Brightness.dark,
primaryColor: const Color(0xFF6C63FF),
scaffoldBackgroundColor: const Color(0xFF1A1A2E),
// ...
);
}
}

遇到的坑

1. 后台播放权限

Android 12+ 对后台服务权限收紧,需要在 AndroidManifest.xml 中声明前台服务权限:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>

并且 MainActivity 必须继承 AudioServiceFragmentActivity

class MainActivity: AudioServiceFragmentActivity()

2. 音频焦点处理

当来电、闹钟或其他 App 播放音频时,需要正确处理音频焦点:

final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.music());

3. 专辑封面缓存

音乐文件的内嵌封面读取是耗时操作,需要做内存缓存:

class AlbumArt extends StatefulWidget {
static final Map<String, Uint8List> _artworkCache = {};

// 先查缓存,再异步加载
@override
Widget build(BuildContext context) {
if (_artworkCache.containsKey(widget.id)) {
return Image.memory(_artworkCache[widget.id]!);
}
// 异步加载并缓存...
}
}

最终效果

经过持续的开发和优化,悦音播放器已经成长为一个功能完善的本地音乐播放器。

它可能不如 QQ 音乐、网易云音乐功能丰富,但它:

  • 零广告:没有任何形式的广告
  • 零打扰:没有推送通知,没有社交功能
  • 本地优先:所有歌曲都存在本地,无需联网
  • 开源免费:代码完全开源,任何人都可以审计
  • 智能缩略图:无封面歌曲显示精美渐变色缩略图
  • 歌词支持:LRC 歌词同步显示
  • 歌单管理:自定义歌单,封面缩略图
  • 黑胶唱片:独特的播放器动画效果
  • 多主题支持:13 种配色方案随心切换
  • 多播放器样式:14 种视觉效果满足不同喜好
  • 玻璃拟态:全页面毛玻璃模糊效果

开源地址

项目已开源到 GitHub:

https://github.com/monkey-wenjun/melody_player

欢迎 Star、Fork、提 Issue。

下载安装

版本 下载链接 文件大小
最新版 (v1.9.59) 点击下载 ~52 MB

系统要求

  • Android 版本: Android 5.0 (API 21) 及以上
  • 存储空间: 至少 150 MB 可用空间
  • 权限: 存储权限(用于读取音乐文件)

写在最后

这个项目的诞生,源于一个简单的愿望:我只想安安静静地听首歌。

在算法推荐、社交功能、会员体系越来越复杂的今天,我们似乎已经忘记了音乐最初的模样。

悦音播放器不会替代 QQ 音乐或网易云音乐,它们有各自的定位和受众。但如果你和我一样,只想找一个简单、干净、纯粹的本地音乐播放器,那么欢迎试试悦音。

音乐本应如此简单。


下载地址https://file.awen.me/music/melody_player_v1.9.59.apk

联系方式hi@awen.me

文章作者:阿文
文章链接: https://www.awen.me/post/8f3e2d1a.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
您的评论由 AI 智能审核,一般1分钟内会展示,若不展示请确认你的评论是否符合社区和法律规范
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️