Flutter Music Player - 设计文档

1. 项目概述

1.1 产品定位

跨平台音乐播放器 MVP,支持 Windows / Linux / macOS / iOS / Android 五大平台。用户可以播放本地音乐文件和在线音乐流,管理播放列表和收藏,并获得原生级别的系统媒体控制集成。

1.2 技术栈

层级 技术选型 版本
框架 Flutter 3.41.5
语言 Dart 3.11.3
状态管理 Riverpod 3.x ^3.3.1 (NotifierProvider API)
路由 GoRouter ^17.1.0
音频引擎 just_audio + audio_service ^0.10.5 / ^0.18.18
本地存储 Hive ^2.2.3
网络 http ^1.6.0
UI 规范 Material Design 3

1.3 代码规模

  • 36 个 Dart 源文件,共计 4,913 行代码
  • flutter analyze 零错误零警告
  • flutter build linux 构建通过

2. 系统架构

2.1 整体架构图

┌─────────────────────────────────────────────────────────────┐
│                    Flutter UI Layer                          │
│         Material 3 · 响应式布局 · 键盘快捷键                    │
│  ┌──────────┬──────────┬───────────┬────────────┬─────────┐ │
│  │ HomePage │ Library  │ SearchPage│ Playlist   │ Player  │ │
│  │          │ Page     │ (多源切换) │ Page       │ Page    │ │
│  └──────────┴──────────┴───────────┴────────────┴─────────┘ │
│                    │  MiniPlayer (全局常驻)  │                │
├─────────────────────────────────────────────────────────────┤
│                  State Layer (Riverpod)                      │
│  ┌───────────────┬────────────────┬──────────────────────┐  │
│  │ Player        │ Library        │ Online               │  │
│  │ Providers     │ Providers      │ Providers            │  │
│  │ (20+ 个)      │ (3 个)         │ (6 个, 含多源切换)    │  │
│  └───────┬───────┴────────┬───────┴──────────┬───────────┘  │
│          │                │    ┌──────────────┤              │
│          │                │    │ MusicProvider│              │
│          │                │    │  Registry    │              │
├──────────┼────────────────┼────┴──────────────┼──────────────┤
│          │         Data / Service Layer        │              │
│  ┌───────▼───────┐ ┌──────▼──────┐ ┌─────────▼─────────┐   │
│  │ AudioHandler  │ │ LocalMusic  │ │ PlaylistService   │   │
│  │ (just_audio   │ │ Service     │ │ (Hive CRUD)       │   │
│  │  + audio_svc) │ │ (文件扫描)   │ │                   │   │
│  └───────┬───────┘ └─────────────┘ └───────────────────┘   │
│          │                                                   │
│  ┌───────▼───────┐  ┌──────────────────────────────────┐    │
│  │ UrlResolver   │  │     MusicProvider (abstract)      │    │
│  │ (URL 解析)    │  │  ┌─────┬────────┬──────┬───────┐  │    │
│  └───────────────┘  │  │Demo │Netease │  QQ  │Spotify│  │    │
│                     │  └─────┴────────┴──────┴───────┘  │    │
│                     └──────────────────────────────────┘    │
│          │                                                   │
│  ┌───────▼───────┐  ┌─────────────────────────┐             │
│  │  AuthService  │  │      http client         │             │
│  │ (Hive tokens) │  │  (Netease/QQ/Spotify)    │             │
│  └───────────────┘  └─────────────────────────┘             │
├──────────────────────────────────────────────────────────────┤
│                      Platform Layer                          │
│  ┌───────────────────────────────────────────────────────┐   │
│  │ Linux: GStreamer  │ Android: ExoPlayer │ iOS: AVFound │   │
│  │ Windows: WinRT    │ macOS: AVFoundation              │   │
│  └───────────────────────────────────────────────────────┘   │
└──────────────────────────────────────────────────────────────┘
 

2.2 目录结构

lib/
├── main.dart                                    # 应用入口
├── core/
│   ├── auth/
│   │   ├── auth_service.dart                    # Hive 令牌存储 (多源认证)
│   │   └── auth_providers.dart                  # AuthService Riverpod Provider
│   ├── router/app_router.dart                   # GoRouter 路由配置
│   ├── theme/app_theme.dart                     # Material 3 主题
│   └── utils/format_duration.dart               # 工具函数
├── features/
│   ├── home/ui/
│   │   ├── home_page.dart                       # 首页(快捷入口)
│   │   └── shell_page.dart                      # 响应式 Shell(导航栏/侧栏)
│   ├── library/
│   │   ├── data/local_music_service.dart         # 本地音乐扫描
│   │   ├── providers/library_providers.dart       # 本地歌曲 Provider
│   │   └── ui/library_page.dart                  # 本地音乐列表
│   ├── online/
│   │   ├── data/
│   │   │   ├── music_provider.dart               # 抽象 MusicProvider 接口
│   │   │   ├── qq_music_api.dart                 # QQ 音乐直连 API 客户端 (685 行)
│   │   │   └── providers/
│   │   │       ├── demo_provider.dart            # Demo 源 (SoundHelix)
│   │   │       ├── netease_provider.dart         # 网易云音乐 API
│   │   │       ├── qq_provider.dart              # QQ 音乐 (直连 u.y.qq.com)
│   │   │       └── spotify_provider.dart         # Spotify Web API
│   │   ├── providers/
│   │   │   ├── music_provider_registry.dart       # 多源注册中心
│   │   │   └── online_providers.dart              # 搜索/热门/源切换 Provider
│   │   └── ui/search_page.dart                    # 搜索页面 (多源 UI)
│   ├── player/
│   │   ├── data/
│   │   │   ├── audio_handler.dart                # just_audio ↔ audio_service 桥接
│   │   │   └── url_resolver.dart                 # 按需 URL 解析 (过期链接刷新)
│   │   ├── providers/player_providers.dart         # 20+ 播放状态 Provider
│   │   └── ui/
│   │       ├── mini_player.dart                   # 底部迷你播放栏
│   │       ├── player_page.dart                   # 全屏播放页
│   │       └── queue_page.dart                    # 播放队列(可拖拽排序)
│   └── playlist/
│       ├── data/playlist_service.dart              # Hive CRUD 服务
│       ├── providers/playlist_providers.dart        # 播放列表 + 收藏 Provider
│       └── ui/
│           ├── playlist_page.dart                  # 播放列表管理
│           └── playlist_detail_page.dart           # 播放列表详情
└── shared/
    ├── models/
    │   ├── song.dart                              # Song 数据模型 (含 source/sourceId)
    │   ├── playlist.dart                          # Playlist 数据模型
    │   └── music_source.dart                      # MusicSource 枚举
    └── widgets/
        ├── song_list_tile.dart                    # 通用歌曲列表项
        ├── source_icon.dart                       # 各源图标 & 标签
        └── keyboard_shortcuts.dart                # 桌面键盘快捷键
 

2.3 分层原则

采用 Feature-First 分层架构,每个功能模块独立包含 data/providers/ui/ 三层:

职责 依赖方向
UI Widget 渲染、用户交互 依赖 Providers
Providers 状态管理、业务逻辑编排 依赖 Data
Data 数据获取(文件系统/网络/数据库) 不依赖上层

shared/ 目录存放跨模块共享的数据模型和通用组件。


3. 核心模块设计

3.1 播放引擎 (Player Engine)

3.1.1 MusicPlayerHandler

文件lib/features/player/data/audio_handler.dart (339 行)

核心类 MusicPlayerHandler 继承 BaseAudioHandler,混入 SeekHandler 和 QueueHandler,实现 just_audio 与 audio_service 的双向桥接。

                        audio_service
                    ┌───────────────────┐
                    │ PlaybackState     │ ──→ 系统通知/锁屏控件
                    │ MediaItem         │ ──→ 蓝牙/耳机按钮
                    │ Queue             │
                    └───────┬───────────┘
                            │ 双向同步
                ┌───────────▼───────────┐
                │  MusicPlayerHandler   │
                │                       │
                │  • play / pause       │
                │  • skipToNext/Prev    │
                │  • seek               │
                │  • loadPlaylist()     │
                │  • setRepeatMode()    │
                │  • setShuffleMode()   │
                └───────────┬───────────┘
                            │
                    ┌───────▼───────┐
                    │  just_audio   │
                    │  AudioPlayer  │
                    └───────────────┘
 

关键实现细节

  1. 状态同步:通过 Rx.combineLatest3 组合 playingStreamprocessingStateStreamplaybackEventStream,实时生成 PlaybackState 广播给系统。

  2. URI 路由_createAudioSource(MediaItem) 根据 URI 前缀自动选择:

    • / 或 file:// → AudioSource.file() (本地文件)
    • http:// / https:// → AudioSource.uri() (网络流)
  3. 队列管理:使用 AudioPlayer 的原生队列 API(addAudioSource/removeAudioSourceAt/insertAudioSource),避免已废弃的 ConcatenatingAudioSource 手动管理。

  4. 时长修正_listenToDuration() 监听实际解码时长,自动更新 MediaItem.duration 并同步回 queue。

3.1.2 Player Providers

文件lib/features/player/providers/player_providers.dart (250 行)

提供 20+ 个 Riverpod Provider,分为三类:

类别 Provider 类型 用途
核心 audioHandlerProvider Provider<MusicPlayerHandler> Handler 单例(main.dart 中 override)
流式 currentMediaItemProvider StreamProvider<MediaItem?> 当前曲目
  playbackStateProvider StreamProvider<PlaybackState> 播放状态
  positionProvider StreamProvider<Duration> 当前位置
  bufferedPositionProvider StreamProvider<Duration> 缓冲位置
  durationProvider StreamProvider<Duration?> 总时长
  queueProvider StreamProvider<List<MediaItem>> 播放队列
派生 isPlayingProvider Provider<bool> 是否正在播放
  currentSongProvider Provider<Song?> 当前 Song 模型
  progressProvider Provider<double> 播放进度 0.0~1.0
  hasNextProvider Provider<bool> 是否有下一曲
  hasPreviousProvider Provider<bool> 是否有上一曲
状态 playModeProvider NotifierProvider<PlayModeNotifier, PlayMode> 播放模式管理

播放模式状态机

sequential ──→ repeatAll ──→ repeatOne ──→ shuffle ──→ sequential
     │              │              │             │
     └── LoopMode.off  LoopMode.all  LoopMode.one  shuffle=true
 

3.2 本地音乐库 (Library)

3.2.1 LocalMusicService

文件lib/features/library/data/local_music_service.dart (75 行)

  • 递归扫描 ~/Music 目录
  • 支持格式:.mp3.flac.wav.ogg.m4a
  • 文件名解析策略:
    • 匹配 Artist - Title.ext 模式 → 自动提取艺术家和标题
    • 不匹配 → 文件名作为标题,艺术家设为 "Unknown Artist"
  • 生成唯一 ID:文件路径 hashCode 的十六进制表示

3.2.2 排序系统

通过 LibrarySortNotifier + sortedLocalSongsProvider 实现三种排序:

  • 按标题 (title)
  • 按艺术家 (artist)
  • 按专辑 (album)

3.3 在线音乐 — 多源架构 (Online Multi-Source)

3.3.1 架构概览

在线音乐模块采用 策略模式 (Strategy Pattern),通过抽象接口 MusicProvider 统一各音乐平台的搜索、播放、详情等操作。新增音乐源只需实现接口并注册到 MusicProviderRegistry

                      MusicProvider (abstract)
                    ┌───────────────────────┐
                    │ + source: MusicSource  │
                    │ + displayName: String  │
                    │ + requiresAuth: bool   │
                    │ + isAuthenticated      │
                    │ + search(query)        │
                    │ + resolvePlayUrl(song) │
                    │ + getSongDetail(id)    │
                    │ + getHotSongs()        │
                    │ + getRecommendations() │
                    └───────┬───────────────┘
              ┌─────────┬───┴────────┬───────────┐
              ▼         ▼            ▼           ▼
        DemoProvider  NeteaseProvider  QQMusic   Spotify
         (内置)       (localhost:3000)  Provider  Provider
                                    (u.y.qq.com   (api.spotify.com)
                                     直连 API)
 

3.3.2 MusicSource 枚举

文件lib/shared/models/music_source.dart

enum MusicSource { local, netease, qq, spotify, demo }
 

每个在线歌曲通过 Song.source 和 Song.sourceId 标记其来源平台和平台侧 ID,确保跨源歌曲不会混淆。

3.3.3 MusicProvider 抽象接口

文件lib/features/online/data/music_provider.dart

方法/属性 类型 说明
source MusicSource 该 Provider 对应的音乐源
displayName String UI 显示名称
requiresAuth bool 是否需要认证才能使用
isAuthenticated Future<bool> 异步检查当前是否已认证
search(query, {limit, offset}) Future<List<Song>> 搜索歌曲
resolvePlayUrl(song) Future<String?> 获取可播放 URL(处理签名过期)
getSongDetail(sourceId) Future<Song?> 通过平台 ID 获取歌曲详情
getHotSongs({limit}) Future<List<Song>> 获取热门/推荐歌曲
getRecommendations({limit}) Future<List<Song>> 获取个性化推荐

3.3.4 四个 Provider 实现

DemoProvider

文件lib/features/online/data/providers/demo_provider.dart (160 行)

从原 OnlineMusicService 迁移而来,内置 8 首 SoundHelix 公共域示例音乐:

sourceId 曲名 艺术家 专辑
demo-1 Ambient Soundscape SoundHelix Demo Collection
demo-2 Electronic Dreams SoundHelix Demo Collection
demo-3 Classical Fusion SoundHelix Instrumental Vibes
demo-4 Jazz Exploration Melody Makers Instrumental Vibes
demo-5 Rock Anthem Melody Makers Power Tracks
demo-6 Chill Lofi Beat LoFi Studio Relaxation
demo-7 Synthwave Runner Retro Synth Neon Nights
demo-8 Acoustic Morning Nature Sounds Relaxation
  • URL 永不过期(SoundHelix 静态资源),resolvePlayUrl() 直接返回 song.uri
  • 搜索支持标题/艺术家/专辑大小写不敏感匹配,模拟 300ms 网络延迟
  • requiresAuth: false
NeteaseProvider

文件lib/features/online/data/providers/netease_provider.dart (224 行)

对接 NeteaseCloudMusicApi 开源项目(需本地部署,默认 localhost:3000)。

API 端点 方法 说明
/search?keywords=... search() 搜索歌曲,返回 result.songs[]
/song/url?id=... resolvePlayUrl() 获取播放 URL(临时签名,会过期)
/song/detail?ids=... getSongDetail() 歌曲详情(ar[]/al/dt 字段)
/top/song?type=0 getHotSongs() 新歌速递
  • 两套独立 JSON 映射器(搜索结果 vs 详情的字段名不同:artists/album vs ar/al
  • requiresAuth: false(公开 API 不强制登录)
QQMusicProvider

文件:

  • lib/features/online/data/qq_music_api.dart (685 行) — 纯 API 客户端层
  • lib/features/online/data/providers/qq_provider.dart (268 行) — MusicProvider 实现

直接对接 QQ 音乐内部 API (u.y.qq.com/cgi-bin/musicu.fcg),无需外部 Python/Node 服务器。这是 QQMusicApi 的纯 Dart 移植。

qq_music_api.dart — 纯 API 客户端

所有请求通过单一 POST 端点发送 JSON body,包含 comm(公共参数)和模块特定请求数据。

方法 功能 说明
search(keyword, {page, limit}) 搜索歌曲 返回 {list, totalnum, curpage, curnum}
querySong(mids) 批量查询歌曲信息 通过 songmid 查询详情
getSongUrls(mids, {credential, quality}) 获取播放 URL 支持 4 种音质,需 vkey 签名
getTryUrl(mid) 获取试听 URL 免登录 30 秒试听
getLyric(mid) 获取歌词 返回 Base64 编码的 LRC
getLyricText(mid) 获取歌词文本 解码后直接返回 UTF-8
getTopCategory() 获取排行榜分类 全部榜单列表
getTopDetail(topId, {limit}) 获取排行榜详情 指定榜单的歌曲列表
getHotkey() 获取热搜关键词 用于搜索推荐
getAlbumCover(mid) 获取专辑封面 从 CDN 获取
getAlbumDetail(mid) 获取专辑详情 含歌曲列表
getAlbumSongs(mid, {limit}) 获取专辑歌曲 分页获取

音质枚举 (QQMusicQuality):

枚举值 文件前缀 格式 说明
mp3_128 M500 .mp3 128 kbps MP3
mp3_320 M800 .mp3 320 kbps MP3(默认)
flac F000 .flac 无损 FLAC
ogg_192 O600 .ogg 192 kbps OGG

认证 (QQMusicCredential):

Cookie 模式认证,通过 musicid(QQ 号或微信 uin)+ musickey(会话密钥)组合。

  • Q_H_L_ 前缀 → QQ 登录 (loginType=2)
  • W_X_ 前缀 → 微信登录 (loginType=1)

认证为可选:搜索、排行榜等公共功能无需登录;高品质流媒体需要凭证。

qq_provider.dart — MusicProvider 集成

MusicProvider 方法 实现 说明
search() QQMusicApi.search() 搜索歌曲,映射为 Song 模型
resolvePlayUrl() getSongUrls() → fallback getTryUrl() 优先完整链接,失败时回退试听
getSongDetail() QQMusicApi.querySong() 通过 songmid 查询
getHotSongs() QQMusicApi.getTopDetail(26) 热歌榜 (topId=26)
getRecommendations() QQMusicApi.getTopDetail(62) 飙升榜 (topId=62)

额外方法:

  • getLyric(mid) — 获取歌词(供未来歌词页使用)

  • getTopCategories() — 获取全部排行榜分类

  • 专辑封面通过 albumMid 拼接 CDN URL:https://y.gtimg.cn/music/photo_new/T002R300x300M000{albumMid}.jpg

  • requiresAuth: false(基本功能免登录)

  • 凭证通过 AuthService 读取:musickey → getToken(qq)musicid → getAuthData(qq, 'musicid')

SpotifyProvider

文件lib/features/online/data/providers/spotify_provider.dart (229 行)

对接 Spotify Web API(直接调用 api.spotify.com,需 OAuth2 token)。

API 端点 方法 说明
/search?q=...&type=track search() 搜索曲目
/tracks/{id} resolvePlayUrl() 返回 preview_url(30 秒预览)
/tracks/{id} getSongDetail() 曲目详情
/recommendations?seed_tracks=... getRecommendations() 个性化推荐
  • requiresAuth: true — 所有 API 调用需 Bearer token
  • MVP 阶段播放限制为 30 秒预览(完整播放需 Spotify SDK)
  • 依赖 AuthService 管理 token 存取

3.3.5 MusicProviderRegistry

文件lib/features/online/providers/music_provider_registry.dart

注册中心在 Riverpod Provider 中初始化,自动注册所有 4 个音乐源:

final musicProviderRegistryProvider = Provider<MusicProviderRegistry>((ref) {
  final authService = ref.watch(authServiceProvider);
  final registry = MusicProviderRegistry()
    ..register(DemoProvider())
    ..register(NeteaseProvider())
    ..register(QQMusicProvider(authService))
    ..register(SpotifyProvider(authService));
  return registry;
});
 

扩展新音乐源只需:① 实现 MusicProvider 子类,② 在 MusicSource 枚举中添加值,③ 在此注册。

3.3.6 URL 解析器 (UrlResolver)

文件lib/features/player/data/url_resolver.dart (33 行)

解决 QQ 音乐/网易云等平台 URL 临时签名过期问题:

播放请求 → UrlResolver.resolve(song)
           ├─ source == local → 直接返回文件路径
           └─ source == online → 调用 provider.resolvePlayUrl()
                                 → 返回最新可用 URL
 

SearchPage 在播放前对所有歌曲调用 UrlResolver,确保传入 AudioHandler 的 URL 是有效的。

3.3.7 认证管理 (AuthService)

文件lib/core/auth/auth_service.dart (60 行)

基于 Hive 的令牌持久化服务,支持多源独立认证:

方法 说明
saveToken(source, token) 保存某源的访问令牌
getToken(source) 获取令牌(同步,null 表示未登录)
saveAuthData(source, data) 保存额外认证数据(refresh token、cookie 等)
getAuthData(source, key) 获取指定认证数据字段
clearAuth(source) 清除某源的全部认证信息(登出)
hasToken(source) 快速检查是否有令牌

存储 key 规则:{source.name} 为主 token,{source.name}_{key} 为附加数据。

3.3.8 搜索 Providers

文件lib/features/online/providers/online_providers.dart (87 行)

Provider 类型 说明
activeSourceProvider NotifierProvider<..., MusicSource> 当前选中的音乐源 Tab
searchQueryProvider NotifierProvider<..., String> 搜索输入文本
searchResultsProvider FutureProvider<List<Song>> 搜索结果(watch source + query)
hotSongsProvider FutureProvider<List<Song>> 热门歌曲(无搜索时展示)
activeSourceProviderInstance Provider<MusicProvider?> 当前源对应的 Provider 实例
sourceNeedsLoginProvider FutureProvider<bool> 是否需要显示登录卡片

搜索仅在 query >= 2 字符时触发,避免过于频繁的请求。切换音乐源自动刷新搜索结果和热门歌曲。

3.3.9 SearchPage 多源 UI

文件lib/features/online/ui/search_page.dart (346 行)

┌──────────────────────────────────────┐
│          Search Online               │ ← AppBar
├──────────────────────────────────────┤
│ [🎵 Demo] [☁ Netease] [♫ QQ] [🎧]  │ ← ChoiceChip 源切换
├──────────────────────────────────────┤
│ 🔍 Search for songs, artists...  ✕  │ ← TextField
├──────────────────────────────────────┤
│                                      │
│  无搜索 → 显示 Hot Songs             │
│  有搜索 → 显示搜索结果               │
│  需登录 → 显示 Login Required 卡片   │
│                                      │
└──────────────────────────────────────┘
 
  • 源切换行使用 SingleChildScrollView 水平滚动的 ChoiceChip
  • 每个 Chip 显示 sourceIcon() + provider displayName
  • 认证门控:对 requiresAuth 的源(如 Spotify),通过 sourceNeedsLoginProvider 异步检查认证状态
  • 播放前通过 UrlResolver 解析所有歌曲 URL,过滤掉无法解析的歌曲

3.4 播放列表与收藏 (Playlist & Favorites)

3.4.1 数据持久化

文件lib/features/playlist/data/playlist_service.dart (120 行)

使用 Hive 作为轻量 NoSQL 存储:

Hive Box Key Value 用途
playlists playlist UUID JSON string (Playlist) 用户播放列表
favorites song ID JSON string (Song) 收藏歌曲

序列化方案:使用 dart:convert 的 jsonEncode/jsonDecode,配合 Song.toJson()/Song.fromJson() 和 Playlist.toJson()/Playlist.fromJson()。不使用 Hive TypeAdapter,避免了 hive_generator 的依赖冲突。

3.4.2 播放列表 CRUD

createPlaylist(name)         → 生成 UUID v4 → 存入 Hive
deletePlaylist(id)           → 从 Hive 删除
renamePlaylist(id, newName)  → 读取 → 修改 → 写回
addSongToPlaylist(id, song)  → 去重检查 → 追加 → 写回
removeSongFromPlaylist(id, songId) → 过滤 → 写回
 

3.4.3 收藏系统

收藏本质上是一个特殊的 "歌曲集合",以 song ID 为 key 独立存储在 favorites box 中:

  • toggleFavorite(song): 存在则删除,不存在则添加
  • isFavorite(songId): O(1) 查询,利用 Hive box 的 containsKey

4. UI 设计

4.1 导航结构

┌─────────────────────────────────────────────┐
│                    App                       │
│  ┌─────────────────────────────────────────┐│
│  │  StatefulShellRoute (IndexedStack)      ││
│  │  ┌─────┬─────────┬────────┬──────────┐ ││
│  │  │Home │ Library │ Search │ Playlists│ ││
│  │  │  /  │/library │/search │/playlists│ ││
│  │  └─────┴─────────┴────────┴──┬───────┘ ││
│  │                               │         ││
│  │                         /playlists/:id  ││
│  └─────────────────────────────────────────┘│
│                                              │
│  /player  ← 全屏播放页(滑入动画,独立导航栈)  │
└─────────────────────────────────────────────┘
 

使用 StatefulShellRoute.indexedStack 确保各 Tab 页面状态独立保持(切换 Tab 不会销毁/重建)。

全屏播放页 /player 使用 parentNavigatorKey 指向根导航器,配合 CustomTransitionPage 实现从底部滑入的动画效果。

4.2 响应式布局

文件lib/features/home/ui/shell_page.dart (104 行)

屏幕宽度 导航形式 MiniPlayer 位置
< 800px (手机/小平板) 底部 NavigationBar NavigationBar 上方
>= 800px (桌面/大平板) 左侧 NavigationRail 底部全宽
移动端布局:                       桌面端布局:
┌──────────────────┐              ┌──┬────────────────┐
│                  │              │  │                │
│     Content      │              │N │    Content     │
│                  │              │a │                │
│                  │              │v │                │
├──────────────────┤              │  │                │
│   MiniPlayer     │              │R │                │
├──────────────────┤              │a │                │
│  NavigationBar   │              │i │                │
│ 🏠  📁  🔍  📋  │              │l │                │
└──────────────────┘              ├──┴────────────────┤
                                  │    MiniPlayer     │
                                  └───────────────────┘
 

4.3 全屏播放页

┌──────────────────────────────────┐
│  ▼ 收起              🎵 队列    │  ← AppBar
│                                  │
│       ┌──────────────────┐       │
│       │                  │       │
│       │   Album Cover    │       │
│       │    300 x 300     │       │
│       │                  │       │
│       └──────────────────┘       │
│                                  │
│         歌曲标题 (headlineSmall)  │
│         艺术家 (titleMedium)     │
│                                  │
│  0:45 ───●────────────── 3:21   │  ← ProgressBar (可拖拽)
│                                  │
│      ⏮       ▶ (FAB)       ⏭   │  ← 主控制
│                                  │
│         🔀          🔁          │  ← 模式切换
│                                  │
│    🔉 ─────────────────── 🔊    │  ← 音量 (仅桌面端)
└──────────────────────────────────┘
 

桌面端独有功能:

  • 音量滑块(StreamBuilder 实时监听 player.volumeStream
  • 仅在 Platform.isLinux || Platform.isMacOS || Platform.isWindows 时显示

4.4 MiniPlayer

┌─────────────────────────────────┐
│ ═══════●═══════════════════════ │  ← 2px 进度条
│ 🎵  歌曲名 - 艺术家       ▶/⏸ │  ← 点击进入全屏
└─────────────────────────────────┘
 
  • 仅当有曲目加载时显示(否则返回 SizedBox.shrink()
  • 点击主区域通过 context.push('/player') 打开全屏播放页

4.5 播放队列

  • ReorderableListView.builder 实现拖拽排序
  • 当前播放项高亮(primaryContainer 背景色)
  • AppBar "Clear Queue" 按钮清空队列

4.6 主题

基于 ColorScheme.fromSeed(seedColor: Colors.deepPurple),自动派生完整的 Material 3 色彩系统:

属性 亮色模式 暗色模式
主色调 Deep Purple 派生 Deep Purple 派生 (darkMode)
Material 3 启用 启用
AppBar Surface 背景 Surface 背景
导航栏 系统默认 M3 系统默认 M3
卡片 圆角 12px 圆角 12px

支持跟随系统自动切换亮色/暗色(ThemeMode.system)。


5. 数据模型

5.1 Song

class Song {
  final String id;                // 唯一标识(本地:路径 hash;在线:{source}-{sourceId})
  final String title;             // 歌曲标题
  final String? artist;           // 艺术家
  final String? album;            // 专辑名
  final Duration? duration;       // 时长
  final String? uri;              // 播放地址(本地路径或 URL)
  final String? artworkUrl;       // 封面图地址
  final bool isLocal;             // 是否本地文件
  final MusicSource source;       // 来源平台 (默认: MusicSource.local)
  final String? sourceId;         // 平台侧原始 ID (用于 API 调用)
}
 

提供与 audio_service.MediaItem 的双向转换:

  • Song.fromMediaItem(MediaItem) — 从系统通知/播放控制反向构建(extras 中恢复 source/sourceId)
  • song.toMediaItem() — 转为 MediaItem 用于播放引擎(source/sourceId 存入 extras)

toJson()/fromJson() 序列化中 source 使用 name 字符串存储,缺失时默认 MusicSource.local,确保向后兼容。

5.2 Playlist

class Playlist {
  final String id;             // UUID v4
  final String name;           // 播放列表名称
  final List<Song> songs;      // 歌曲列表
  final DateTime createdAt;    // 创建时间
}
 

6. 桌面端适配

6.1 键盘快捷键

文件lib/shared/widgets/keyboard_shortcuts.dart (85 行)

按键 功能
Space 播放/暂停
后退 5 秒
前进 5 秒
音量 +0.1
音量 -0.1
N 下一曲
P 上一曲

通过 Focus widget 的 onKeyEvent 实现,仅处理 KeyDownEvent 避免重复触发。

6.2 MPRIS 集成 (Linux)

audio_service 在 Linux 上自动通过 D-Bus 实现 MPRIS 协议,无需额外代码即可支持:

  • 系统媒体通知
  • 桌面环境媒体控件(如 GNOME 顶栏)
  • 蓝牙/耳机媒体按钮

7. 应用启动流程

main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // 1. 初始化 Hive 文件存储
  await Hive.initFlutter();

  // 2. 打开播放列表和收藏的 Hive Box
  await PlaylistService.init();

  // 3. 初始化认证令牌存储
  await AuthService.init();

  // 4. 初始化 audio_service,注册 MusicPlayerHandler
  final audioHandler = await AudioService.init(
    builder: () => MusicPlayerHandler(),
    config: AudioServiceConfig(...),
  );

  // 5. 启动应用,注入 audioHandler 到 Riverpod
  runApp(
    ProviderScope(
      overrides: [audioHandlerProvider.overrideWithValue(audioHandler)],
      child: const MusicPlayerApp(),
    ),
  );
}
 

依赖注入关键点

  • audioHandlerProvider 在定义时抛出 UnimplementedError,必须在 ProviderScope.overrides 中注入实际实例。
  • AuthService.init() 打开 auth_tokens Hive box,必须在 runApp 前完成。
  • MusicProviderRegistry 在首次访问时通过 Riverpod 自动初始化并注册所有 4 个音乐源。

8. 依赖关系总览

# 核心
flutter_riverpod: ^3.3.1      # 状态管理
go_router: ^17.1.0             # 声明式路由
just_audio: ^0.10.5            # 跨平台音频播放
audio_service: ^0.18.18        # 后台播放 + 系统媒体控制
just_audio_background: ^0.0.1  # just_audio ↔ audio_service 桥接

# 数据
hive: ^2.2.3                   # 轻量 NoSQL 存储
hive_flutter: ^1.1.0           # Hive Flutter 适配
uuid: ^4.5.3                   # 播放列表 ID 生成

# 网络
http: ^1.6.0                   # HTTP 客户端 (Netease/QQ/Spotify API)

# UI
audio_video_progress_bar: ^2.0.3  # 音频进度条
cached_network_image: ^3.4.1      # 网络图片缓存

# 工具
rxdart: ^0.28.0                # 响应式流组合
on_audio_query: ^2.9.0         # 音频元数据查询(预留)
path_provider: ^2.1.5          # 平台路径
 

9. 后续演进路线

9.1 短期 (v1.1)

优先级 功能 说明
P0 多源在线 API 对接 ✅ 已完成:Netease / QQ Music / Spotify + 可扩展架构
P0 QQ 音乐直连 API ✅ 已完成:纯 Dart 移植 u.y.qq.com 协议,无需外部服务器
P0 音频元数据读取 使用 on_audio_query 或 id3 包读取 MP3 标签和封面
P0 Netease 后端部署指南 提供 Docker 一键部署文档
P0 Spotify OAuth2 登录流程 实现 PKCE 流程,对接 Login 按钮
P1 歌词显示 LRC 格式逐行高亮同步
P1 搜索历史 Hive 持久化搜索记录

9.2 中期 (v1.5)

优先级 功能 说明
P1 均衡器 just_audio AndroidEqualizer / iOS 原生均衡器
P1 睡眠定时器 定时暂停播放
P2 跨设备同步 通过 Firebase/Supabase 同步播放列表
P2 自定义主题色 用户可选 seed color,或从封面提取主色调

9.3 长期 (v2.0)

优先级 功能 说明
P2 播客支持 RSS 订阅、章节标记、播放速度
P2 CarPlay / Android Auto 车载适配
P3 社交功能 分享歌单、协作播放列表
P3 本地网络播放 DLNA/AirPlay 推送

10. 构建与运行

10.1 开发环境要求

  • Flutter SDK >= 3.41.0
  • Dart SDK >= 3.11.0
  • Linux: GStreamer 开发库 (libgstreamer1.0-dev)
  • macOS: Xcode >= 15
  • Windows: Visual Studio 2022 with Desktop C++ workload

10.2 快速启动

# 获取依赖
flutter pub get

# Linux 桌面运行
flutter run -d linux

# Android 运行
flutter run -d <device_id>

# 构建 Release
flutter build linux
flutter build apk
flutter build ios
flutter build macos
flutter build windows
 

10.3 测试本地音乐

将音乐文件放入 ~/Music 目录,支持格式:.mp3.flac.wav.ogg.m4a

文件名建议使用 艺术家 - 标题.mp3 格式以获得最佳解析效果。

10.4 测试在线搜索

Demo 源(默认,无需配置)

在 Search 页面选择 Demo Music 源,输入以下关键词试听示例音乐:

  • sound — 匹配 SoundHelix 系列
  • jazz — Jazz Exploration
  • chill — Chill Lofi Beat
  • relaxation — 匹配 Relaxation 专辑下的歌曲

网易云音乐

  1. 部署 NeteaseCloudMusicApi
    docker run -p 3000:3000 binaryify/netease_cloud_music_api
     
  2. 在 Search 页面选择 Netease Cloud Music 源即可搜索

QQ 音乐

QQ 音乐使用直连 API,无需部署任何外部服务器。在 Search 页面选择 QQ Music 源即可搜索和试听。

  • 基础功能(搜索、排行榜、试听)无需登录
  • 高品质流媒体需要提供 QQ Music cookie 凭证(musicid + musickey

Spotify

  1. 注册 Spotify Developer 应用获取 Client ID
  2. 完成 OAuth2 认证后,token 将自动保存
  3. 在 Search 页面选择 Spotify 源(MVP 阶段仅支持 30 秒预览)