Python实战:极简本地音乐播放器Music nano
发布时间:2026/9/8 11:40:51 锦皓数字建站

如果你也遇到过这种场景——电脑里攒了几个 GB 的本地音乐却不想被在线音乐客户端的开屏广告、会员弹窗和个性推荐打扰或者你正在学 Python想找一个兼顾文件遍历、面向对象、状态管理和“能真正跑起来”的小项目那么 Music nano 是一个非常合适的练手方向。Music nano 是一个极简的本地音乐播放器项目名字里的“nano”借鉴了终端文本编辑器的命名理念功能不多但足够果断只为解决“听本地音乐”这一件事服务。它不追求音效均衡器、歌词滚动、社交分享这类复杂能力而是聚焦在音乐扫描、播放控制、播放模式切换和基础音量管理上。本文将围绕 Music nano 从零到一的完整实现过程展开包含核心模块设计、可运行代码、终端交互命令、GUI 扩展方式以及高频踩坑排查适合 Python 入门到进阶的开发者阅读。学完之后你不只拥有一个能播放本地音乐的迷你工具还会理解如何把一个模糊的需求拆成稳定性强的模块并在真实工程里避开那些容易翻车的细节。1. 背景与核心概念1.1 Music nano 解决什么问题在开始写代码之前先明确项目的边界。市面上的音乐播放器功能很强大但对于只想“双击列表里某一首歌然后专心写代码”的人来说它们往往太重了。Music nano 的定位是一个能快速扫描本地音乐目录、用命令或按钮控制播放、不依赖网络的极简播放器。它解决的核心问题有三个本地音乐组织混乱散落在多个文件夹里的 mp3、wav 文件没有统一入口想播放只能逐个打开。大而全的软件干扰体验很多播放器启动后会自动联网、推荐内容、扫描用户偏好对隐私敏感或追求专注的用户来说反而是负担。练手项目缺乏完整闭环学习 Python 时如果只写几行打印语句很难体会模块设计、异常处理、状态管理这些工程概念。Music nano 虽然小但具备一个完整项目的壳。从技术角度看这个项目用 Python 开发播放能力基于pygame.mixer模块文件扫描和路径处理基于标准库pathlib交互方式先做终端命令行再扩展一个基于tkinter的极简图形界面。依赖非常少对初学者非常友好。1.2 核心功能边界为了避免需求蔓延Music nano 第一版只实现以下功能功能说明目录扫描递归扫描指定目录下支持的音频文件顺序播放默认模式按列表顺序循环播放单曲循环当前歌曲循环播放随机播放从非当前歌曲中随机选择下一首播放控制播放、暂停、继续、停止、上一首、下一首音量控制支持 0 到 100 之间的音量调节歌单展示显示当前播放列表中所有歌曲和当前播放位置音乐库刷新重新扫描目录反映文件变化后面如果你有兴趣可以继续扩展 ID3 信息读取、M3U 播放列表导入、歌词同步等功能但第一版先保持干净。1.3 为什么选用 pygame.mixerPython 中播放音频的方式很多例如playsound、pydub、pygame.mixer等。Music nano 选择pygame.mixer的原因是它是pygame的子模块专注于混音和音乐播放API 稳定。使用简单几行代码就能实现 load、play、pause、stop 等操作。跨平台支持较好Windows、macOS、Linux 都可以使用。需要注意pygame.mixer对音频格式的支持依赖于底层 SDL_mixer 库的编译选项。常见格式中mp3、wav、ogg 通常没有问题flac 的兼容性则要看具体环境。后面常见问题部分会专门说明。2. 环境准备与版本说明2.1 运行环境本项目是一个偏桌面端的 Python 小工具运行环境建议如下操作系统Windows 10/11、macOS 或 Linux 桌面版需要有可用的音频输出设备。Python 版本Python 3.8 及以上。依赖库pygame。终端Windows 推荐使用 PowerShell 或 Windows TerminalLinux/macOS 使用系统自带终端即可。关于 Python 版本这里没有写死某一个具体小版本因为pygame对 3.8 以上的版本支持都比较稳定。如果你的环境是 Python 3.10 或 3.12都可以正常运行。关键是安装pygame时确认依赖版本一致。2.2 安装依赖建议先创建一个虚拟环境避免污染系统全局 Python。mkdir music_nano cd music_nano python -m venv venv激活虚拟环境Windowsvenv\Scripts\activatemacOS / Linuxsource venv/bin/activate然后安装pygamepip install pygame安装完成后可以用下面的命令验证是否安装成功python -c import pygame; print(pygame.version.ver)如果能看到版本号输出说明环境已经就绪。不同环境下pygame的输出版本会不同这里不用刻意追求最新版能够正常导入即可。2.3 项目目录结构为了方便后续扩展Music nano 采用“核心逻辑 入口交互”分离的结构。目录规划如下music_nano/ ├── music/ # 存放测试音乐文件 │ ├── song1.mp3 │ ├── song2.wav │ └── subdir/ │ └── song3.ogg ├── music_nano_player.py # 核心播放模块 ├── main.py # 终端交互入口 ├── gui_main.py # 可选 GUI 扩展 └── requirements.txt # 依赖清单其中music/目录可以理解为你的本地音乐仓库Music nano 会递归扫描这个目录下的所有音频文件。requirements.txt内容很简单pygame2.0.0后面示例代码中我会默认以./music作为音乐目录你也可以在启动时手动指定其他路径。3. 核心模块设计在动手写代码之前把模块边界理清楚很重要。Music nano 的核心代码由三个部分组成音乐库扫描、播放列表管理、播放引擎。3.1 音乐库扫描 MusicLibraryMusicLibrary的职责很简单给定一个目录递归找出所有音频文件保存为播放列表。它不应该关心播放状态也不应该关心用户交互。扫描时需要注意几个细节支持多级子目录所以使用Path.rglob(*)。大小写不敏感.MP3和.mp3都要识别。目录不存在时抛出明确异常方便启动阶段定位问题。扫描结果排序保证每次启动列表顺序一致。3.2 播放列表 PlaylistPlaylist负责播放顺序管理。它接受MusicLibrary扫描出的音频文件列表以及当前播放模式顺序order、单曲single、随机random。这个模块最核心的是“下一首”的计算逻辑。顺序播放很简单索引加一后取模即可单曲循环则直接返回当前索引随机播放需要用随机函数在除当前歌曲之外的候选中挑选。prev上一首在顺序播放模式下是索引减一取模。在随机播放模式下如果存在播放历史则回退到上一首否则随机选择一首。这部分逻辑虽然简单但对理解列表索引和取模运算很有帮助。3.3 播放引擎 MusicNanoMusicNano是“总指挥”它组合MusicLibrary和Playlist对外提供播放、暂停、继续、停止、切歌、音量调节等方法。所有播放能力最终都落到pygame.mixer.music模块。这个模块是全局唯一的音乐播放器接口同一时间只能加载并播放一首歌曲。因此MusicNano内部要维护好当前状态确保暂停状态下不会继续触发自动切歌。3.4 播放状态机设计播放器是一个典型的状态机Music nano 定义了四种状态状态含义idle初始化完成但尚未播放任何歌曲playing音乐正在播放paused音乐暂停stopped音乐停止状态转移关系如下idle调用播放方法后进入playing。playing调用暂停进入paused。paused调用继续回到playing。playing或paused调用停止进入stopped。理解状态机最大的价值在于避免误操作。例如暂停状态下再点“暂停”应该直接忽略停止状态下点“继续”也应该忽略。后面自动切歌的逻辑也必须先判断当前状态否则会踩到一个很隐蔽的坑。4. 完整代码实现下面给出 Music nano 的完整代码。先实现核心播放模块再实现终端交互入口。4.1 核心播放模块 music_nano_player.py文件路径music_nano_player.py包含音乐库扫描、播放列表和播放引擎三个类。 文件路径music_nano_player.py Music nano 核心播放模块 import random from pathlib import Path import pygame class MusicLibrary: 音乐库扫描器负责扫描目录收集可播放音频文件。 SUPPORTED_EXTENSIONS {.mp3, .wav, .ogg, .m4a, .flac} def __init__(self, music_dir): self.music_dir Path(music_dir) self.tracks [] self._scan() def _scan(self): if not self.music_dir.exists(): raise FileNotFoundError(f音乐目录不存在: {self.music_dir}) if not self.music_dir.is_dir(): raise NotADirectoryError(f路径不是目录: {self.music_dir}) for file_path in self.music_dir.rglob(*): if file_path.is_file() and file_path.suffix.lower() in self.SUPPORTED_EXTENSIONS: self.tracks.append(str(file_path)) if not self.tracks: print(当前目录中没有找到可播放的音频文件请检查格式。) def refresh(self): 重新扫描目录用于歌单刷新。 self.tracks [] self._scan() class Playlist: 播放列表管理器支持顺序播放、单曲循环、随机播放。 LOOP_ORDER order LOOP_SINGLE single LOOP_RANDOM random def __init__(self, tracks, loop_modeLOOP_ORDER): if not tracks: raise ValueError(播放列表为空无法初始化播放器。) self.tracks list(tracks) self.loop_mode loop_mode self.current_index 0 self.play_history [] def current(self): 返回当前歌曲路径。 return self.tracks[self.current_index] def next_index(self): 计算下一首歌曲的索引不修改当前位置。 if self.loop_mode self.LOOP_SINGLE: return self.current_index if self.loop_mode self.LOOP_RANDOM: if len(self.tracks) 1: return 0 candidates [i for i in range(len(self.tracks)) if i ! self.current_index] available [i for i in candidates if i not in self.play_history[-3:]] pool available or candidates return random.choice(pool) return (self.current_index 1) % len(self.tracks) def prev_index(self): 计算上一首歌曲的索引。 if self.loop_mode self.LOOP_RANDOM: if self.play_history: return self.play_history.pop() candidates [i for i in range(len(self.tracks)) if i ! self.current_index] return random.choice(candidates) return (self.current_index - 1) % len(self.tracks) class MusicNano: 播放器核心负责加载、播放、暂停、停止、切歌、音量控制。 STATE_IDLE idle STATE_PLAYING playing STATE_PAUSED paused STATE_STOPPED stopped def __init__(self, music_dir, volume0.8): pygame.mixer.init() pygame.mixer.music.set_volume(volume) self.library MusicLibrary(music_dir) self.library.tracks.sort() self.playlist Playlist(self.library.tracks) self.state self.STATE_IDLE self.volume volume def play_current(self): 播放当前索引对应的歌曲。 track_path self.playlist.current() pygame.mixer.music.load(track_path) pygame.mixer.music.play() self.state self.STATE_PLAYING self.playlist.play_history.append(self.playlist.current_index) print(f正在播放: {Path(track_path).name}) def pause(self): if self.state ! self.STATE_PLAYING: return pygame.mixer.music.pause() self.state self.STATE_PAUSED print(已暂停) def resume(self): if self.state ! self.STATE_PAUSED: return pygame.mixer.music.unpause() self.state self.STATE_PLAYING print(继续播放) def stop(self): pygame.mixer.music.stop() self.state self.STATE_STOPPED print(播放停止) def next_track(self, manualTrue): 切换到下一首。manual 表示是否为用户手动触发的切歌。 if not manual and self.playlist.loop_mode Playlist.LOOP_SINGLE: self.play_current() return self.playlist.current_index self.playlist.next_index() self.play_current() def prev_track(self): 切换到上一首。 self.playlist.current_index self.playlist.prev_index() self.play_current() def play_at(self, index): 播放指定索引的歌曲。 if 0 index len(self.playlist.tracks): self.playlist.current_index index self.play_current() def set_volume(self, volume): 设置音量值范围为 0.0 到 1.0。 volume max(0.0, min(1.0, float(volume))) pygame.mixer.music.set_volume(volume) self.volume volume print(f音量设置为 {int(volume * 100)}%) def show_playlist(self): 展示播放列表。 for idx, track in enumerate(self.playlist.tracks): mark - if idx self.playlist.current_index else print(f{idx 1}. {Path(track).name}{mark}) def check_auto_next(self): 检测歌曲是否播放完毕自动切下一首。 if self.state self.STATE_PLAYING and not pygame.mixer.music.get_busy(): self.next_track(manualFalse)这个模块有几个细节值得强调Playlist.next_index()在随机模式下不会立刻修改current_index而是由MusicNano.next_track()统一更新这样职责更清晰。check_auto_next()必须判断self.state self.STATE_PLAYING原因后面常见问题会详细说明。play_history用于随机模式的“上一首”回退避免用户按下上一首时跳到完全无关的歌曲。4.2 终端交互入口 main.py文件路径main.py。这里使用一个后台线程监听用户输入主线程负责检测播放状态并自动切歌。 文件路径main.py Music nano 终端入口 import threading import time from music_nano_player import MusicNano HELP_TEXT Music nano 命令说明 play 播放当前歌曲 pause 暂停播放 resume 继续播放 next / n 下一首 prev / p 上一首 stop 停止播放 list / l 查看播放列表 vol 0-100 设置音量例如 vol 50 mode 查看/切换播放模式order/single/random refresh 重新扫描音乐目录 help 显示帮助 exit / quit 退出程序 def handle_command(cmd, args, player) - bool: 处理用户输入命令返回 False 表示需要退出程序。 if cmd in (play,): player.play_current() elif cmd in (pause,): player.pause() elif cmd in (resume,): player.resume() elif cmd in (next, n): player.next_track(manualTrue) elif cmd in (prev, p): player.prev_track() elif cmd in (stop,): player.stop() elif cmd in (list, l): player.show_playlist() elif cmd vol: try: volume int(args[0]) / 100.0 player.set_volume(volume) except (ValueError, IndexError): print(用法: vol 0-100例如 vol 50) elif cmd mode: if not args: print(f当前模式: {player.playlist.loop_mode}) print(可用模式: order / single / random) else: mode args[0] if mode in (order, single, random): player.playlist.loop_mode mode print(f播放模式切换为: {mode}) else: print(模式不正确可选 order / single / random) elif cmd refresh: player.library.refresh() player.playlist.tracks player.library.tracks if player.playlist.current_index len(player.playlist.tracks): player.playlist.current_index 0 print(音乐库已刷新) elif cmd in (help,): print(HELP_TEXT) elif cmd in (exit, quit): player.stop() return False else: print(未知命令输入 help 查看帮助) return True def main(): music_dir input(请输入音乐目录路径直接回车使用 ./music: ).strip() or ./music try: player MusicNano(music_dir) except Exception as exc: print(f启动失败: {exc}) return player.play_current() print(HELP_TEXT) running True def input_loop(): nonlocal running while running: try: raw input(nano ).strip() except (EOFError, KeyboardInterrupt): print(\n退出 Music nano) player.stop() running False break if not raw: continue parts raw.lower().split() cmd parts[0] args parts[1:] if not handle_command(cmd, args, player): running False break input_thread threading.Thread(targetinput_loop, daemonTrue) input_thread.start() while running: time.sleep(0.5) player.check_auto_next() if __name__ __main__: main()这里使用threading.Thread(targetinput_loop, daemonTrue)启动输入线程好处是主线程可以专心做自动切歌检测。daemonTrue保证了程序退出时不会因为输入线程阻塞而卡住。5. 运行与验证5.1 准备测试音乐在项目根目录下创建music目录放入几首本地的 mp3 或 wav 文件。mkdir music你也可以在music下再创建一个子目录验证递归扫描是否生效。例如mkdir music/album cp /path/to/song.mp3 music/song1.mp3 cp /path/to/song.wav music/album/song2.wav启动时如果直接回车Music nano 会默认扫描./music目录。5.2 启动程序在虚拟环境激活状态下执行python main.py预期输出大致如下请输入音乐目录路径直接回车使用 ./music: 正在播放: song1.mp3 Music nano 命令说明 play 播放当前歌曲 pause 暂停播放 resume 继续播放 next / n 下一首 prev / p 上一首 stop 停止播放 list / l 查看播放列表 vol 0-100 设置音量例如 vol 50 mode 查看/切换播放模式order/single/random refresh 重新扫描音乐目录 help 显示帮助 exit / quit 退出程序 nano 此时第一首歌应该已经开始播放。5.3 命令操作示例在nano 提示符后输入命令nano list 1. song1.mp3 - 2. song2.wav nano next 正在播放: song2.wav nano mode random 播放模式切换为: random nano vol 30 音量设置为 30% nano pause 已暂停 nano resume 继续播放 nano exit 退出 Music nano5.4 命令汇总命令功能示例play播放当前歌曲playpause暂停pauseresume继续播放resumenext / n下一首nprev / p上一首pstop停止播放stoplist / l查看播放列表lvol 0-100设置音量vol 60mode查看或切换播放模式mode randomrefresh重新扫描音乐目录refreshhelp显示帮助helpexit / quit退出程序exit6. 进阶扩展tkinter 极简 GUI终端版本已经具备完整播放能力但对于不擅长敲命令的同学来说界面还是不够友好。接下来扩展一个基于tkinter的极简 GUI 版本。tkinter是 Python 标准库不需要额外安装。6.1 GUI 设计思路GUI 版本的核心诉求是列表展示歌单、点击播放、按钮控制、自动切歌。因此界面包含顶部歌单列表使用tk.Listbox展示所有歌曲名称。底部控制栏上一首、播放、暂停、继续、下一首按钮。自动切歌通过root.after(500, self._poll)定时轮询实现每 500 毫秒调用一次check_auto_next()。6.2 代码实现文件路径gui_main.py。 文件路径gui_main.py Music nano 图形界面入口可选扩展 import tkinter as tk from pathlib import Path from music_nano_player import MusicNano class MusicNanoGUI: def __init__(self, root, player): self.root root self.player player self.root.title(Music nano) self.root.geometry(480x360) self._build_ui() self._poll() def _build_ui(self): self.track_list tk.Listbox(self.root) self.track_list.pack(filltk.BOTH, expandTrue) for track in self.player.playlist.tracks: self.track_list.insert(tk.END, Path(track).name) control_bar tk.Frame(self.root) control_bar.pack(filltk.X) tk.Button(control_bar, text上一首, commandself.player.prev_track).pack(sidetk.LEFT, padx4) tk.Button(control_bar, text播放, commandself._play_selected).pack(sidetk.LEFT, padx4) tk.Button(control_bar, text暂停, commandself.player.pause).pack(sidetk.LEFT, padx4) tk.Button(control_bar, text继续, commandself.player.resume).pack(sidetk.LEFT, padx4) tk.Button(control_bar, text下一首, commandlambda: self.player.next_track(True)).pack(sidetk.LEFT, padx4) def _play_selected(self): selection self.track_list.curselection() if selection: self.player.play_at(selection[0]) def _poll(self): self.player.check_auto_next() self.root.after(500, self._poll) if __name__ __main__: player MusicNano(./music) root tk.Tk() MusicNanoGUI(root, player) root.mainloop()6.3 GUI 使用说明启动 GUIpython gui_main.py窗口出现后双击列表框中的歌曲即可播放。点击“播放”按钮会播放当前选中的歌曲歌曲结束后会自动切到下一首。这个 GUI 版本保留了核心播放器全部逻辑只是把交互层从终端命令换成了界面按钮很好地体现了“逻辑与交互分离”的优点。7. 常见问题与排查思路在实现和运行 Music nano 的过程中下面几个问题比较常见这里整理成表格并在表格后详细说明。问题现象常见原因解决思路启动失败报 ALSA 或音频设备错误服务器/容器没有声卡或音频设备被占用检查音频设备无声卡环境可改用 dummy 音频驱动测试暂停之后歌曲自动跳到下一首get_busy()在暂停状态也会返回 False自动切歌前必须判断self.state是否为PLAYING中文路径或中文歌名乱码Windows 终端编码与 Python 输出编码不一致设置PYTHONIOENCODINGutf-8或使用chcp 65001flac 文件无法播放pygame.mixer对 flac 的支持取决于 SDL_mixer 编译选项优先使用 mp3/wav/ogg高保真场景换其他音频后端扫描结果为空白目录路径写错或没有把文件放到 music 目录打印player.library.music_dir.absolute()检查绝对路径退出后终端卡住输入线程仍阻塞在input()使用daemonTrue并让主循环退出必要时兜底调用os._exit7.1 pygame.mixer 初始化失败pygame.mixer.init()在某些无音频设备的 Linux 服务器上会报类似下面的错误ALSA lib confmisc.c:767:(parse_card) cannot find card 0 pygame.error: ALSA: Couldnt open audio device这种情况通常是环境里没有声卡或音频服务。解决思路本地开发时检查系统音频服务是否正常。如果只是做逻辑验证可以在初始化前设置环境变量export SDL_AUDIODRIVERdummy这样音乐不会真正发声但可以验证播放状态流转逻辑。7.2 暂停后自动跳歌这是一个非常隐蔽的坑。pygame.mixer.music.get_busy()的作用是判断混音器是否正在播放音乐但在暂停状态下它同样返回False。如果你在自动切歌逻辑里只判断not pygame.mixer.music.get_busy()那么用户按下暂停后不到一秒就会被“自动切歌”打断表现为“暂停后自动跳到下一首”。正确做法是像前面代码一样先判断自己的状态if self.state self.STATE_PLAYING and not pygame.mixer.music.get_busy(): self.next_track(manualFalse)只有确实是“播放中”且混音器空闲才认为是歌曲放完了这才符合直觉。7.3 中文路径识别问题Python 3 的pathlib对中文路径支持很好Linux 和 macOS 下通常没有乱码。Windows 下容易出问题的是终端编码当终端代码页不是 UTF-8 时打印中文歌名可能变成乱码。推荐在 Windows PowerShell 中先切换到 UTF-8chcp 65001或者在运行程序前设置环境变量$env:PYTHONIOENCODINGutf-87.4 flac 文件无法播放pygame.mixer对音频格式的支持并不是所有格式都健全。mp3、wav、ogg 最稳妥flac 可能因为 SDL_mixer 编译时未开启 flac 支持而报错。建议第一版把支持的扩展名收敛为.mp3、.wav、.ogg如果你确实要播放 flac 或 m4a可以引入ffmpeg转码或者改用python-vlc、pyglet等音频库。本文的重点是项目结构音频后端可以按实际需求替换。8. 最佳实践与工程建议8.1 明确模块边界Music nano 虽然很小但已经能体现模块化的价值。MusicLibrary负责数据来源Playlist负责播放顺序MusicNano负责播放控制main.py负责终端交互gui_main.py负责图形交互。这样的好处是以后如果想把终端交互全部换成 Web API核心播放器三个类可以完全不动。如果想让音乐库支持数据库索引只需要替换MusicLibrary的实现。8.2 状态管理要严谨播放器最容易出的 bug 都集中在状态判断上。建议所有状态都使用常量字符串不要散落魔法值。每次状态变化时明确打印或记录日志。对“暂停、停止、继续”这类操作做状态前置校验避免 API 被乱调用。这在真实业务里同样重要。比如支付回调、任务调度系统本质也是状态机状态判断错了整个流程就会乱。8.3 异常处理与清理资源音频设备是有限资源程序退出前应该释放pygame.mixer。最简单的方式是在退出时调用pygame.mixer.quit()如果项目继续扩大推荐使用contextlib.closing或将播放器实现为上下文管理器用with语句保证资源释放。8.4 日志代替 print当前代码为了直观使用了很多print。在正式项目中建议换成logging模块。这样既可以在控制台看到关键信息又可以在排查问题时输出到文件且不会因为 print 内容过多影响程序性能。示例import logging logging.basicConfig( levellogging.INFO, format%(asctime)s [%(levelname)s] %(message)s ) logging.info(正在播放: %s, track_name)8.5 路径与数据文件规范音频文件名中尽量不包含特殊字符。如果音乐库里混有大量重复文件扫描时还需要考虑去重策略。更规范的方案是在MusicLibrary._scan()中按文件绝对路径去重seen set() for file_path in self.music_dir.rglob(*): absolute str(file_path.resolve()) if absolute not in seen: seen.add(absolute) self.tracks.append(absolute)这样能防止因为符号链接或重复挂载目录导致同一首歌出现多次。8.6 拓展方向Music nano 的架构天然适合继续扩展读取 ID3 标签和音频时长需要安装mutagen库。支持 M3U 播放列表导入导出。在 GUI 中加入进度条和音量滑条。将核心播放逻辑抽取成 HTTP 服务用手机控制电脑播放。建议先把当前版本跑熟再挑一个方向慢慢加功能不要一次性引入太多依赖。9. 总结与下一步Music nano 是一个麻雀虽小、五脏俱全的本地音乐播放器项目。通过这个项目你可以掌握目录递归扫描、播放列表索引管理、状态机设计和线程化交互也能在实战中理解pygame.mixer的常见陷阱。从工程角度看这个小项目最值得借鉴的是模块划分和状态管理。很多初学者喜欢把所有逻辑写在一个文件里等代码量上来后就很难维护。Music nano 把数据、顺序、播放、交互拆开之后终端版和 GUI 版共用同一套核心播放器扩展成本很低。如果你已经开始动手实现建议按这个顺序验证先跑通终端版确认 mp3 能正常播放再测试暂停、自动切歌、单曲循环和随机播放最后再考虑 GUI 版本。等核心稳定后可以加入更多符合自己听歌习惯的小功能。本地音乐仓库已经建好了剩下的改造空间取决于你的需要。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。