music_player
队列式原生音乐播放器、锁屏控制和播放恢复。
队列式原生音乐播放器:维护当前歌曲、播放队列、播放模式、进度、错误状态、锁屏 Now Playing、控制中心按钮和上次播放恢复。
边界:music_player播放你自己的 URL / 本地音频队列;music 是 Apple Music / MusicKit;avplayer 是低层单媒体播放器;now_playing 只发布元数据。播放、切歌、恢复和系统控制属于副作用,放在按钮回调、页面初始化或用户确认流程里,不要在 AppUIbody()中反复调用。
#模块概览
| 项 | 说明 |
|---|---|
| 导入 | import music_player |
| 适合做什么 | 音乐 MiniApp、歌单播放器、搜索结果播放、后台音乐播放 |
| 不适合做什么 | Apple Music 资料库控制、短音效、AppUI 内嵌视频 |
| 状态归属 | 宿主全局播放器服务 |
| 系统集成 | 可选 Now Playing、控制中心 / 锁屏按钮、后台音频 |
| 恢复 | restore() 恢复上次队列、当前歌曲、进度和播放模式 |
#快速开始
已复制
import music_player
songs = [
{
"title": "Demo Track",
"artist": "PythonIDE",
"url": "https://example.com/demo.mp3",
"artwork_url": "https://example.com/cover.jpg",
}
]
music_player.set_queue(songs, start_index=0)
music_player.play()
print(music_player.state())
如果队列里的歌曲已经带有真实音频 URL,可以让宿主提前准备后面的几首,降低下一曲等待:
已复制
music_player.set_queue(songs, start_index=0, autoplay=True, preload_count=3)
music_player.prefetch_next(count=3)
preload_count/prefetch_next()只预加载已经有url的音频。搜索平台 ID、聚合源 ID 或需要签名换链的歌曲,应先在你的 MiniApp 后台解析成真实 URL,再交给music_player。
如果不想显示锁屏卡片或接管控制中心按钮,在播放前关闭:
已复制
music_player.configure(
now_playing=False,
remote_commands=False,
background=False,
)
#AppUI 示例
UI 完全由你自己写,播放器只提供能力:
已复制
import json
import appui
import music_player
songs = [
{
"title": "Night Demo",
"artist": "PythonIDE",
"url": "https://example.com/night.mp3",
"artwork_url": "https://example.com/night.jpg",
},
{
"title": "Morning Demo",
"artist": "PythonIDE",
"url": "https://example.com/morning.mp3",
},
]
state = appui.State(
title="未播放",
artist="",
status="ready",
elapsed="0:00",
mode="sequence",
)
def handle_player_event(payload):
data = json.loads(payload or "{}")
current = data.get("current") or {}
state.title = current.get("title") or current.get("name") or "未播放"
state.artist = current.get("artist") or ""
state.status = data.get("state", "ready")
seconds = int(data.get("elapsed") or 0)
state.elapsed = f"{seconds // 60}:{seconds % 60:02d}"
music_player.on_event(
handle_player_event,
events=["ready", "play", "pause", "current", "ended", "progress", "remote_command"],
)
def load_and_play():
music_player.configure(now_playing=True, remote_commands=True, background=True)
music_player.set_queue(songs, start_index=0)
music_player.play()
def toggle_mode():
next_mode = "repeat_all" if state.mode == "sequence" else "shuffle" if state.mode == "repeat_all" else "sequence"
state.mode = next_mode
music_player.set_play_mode(next_mode)
def body():
return appui.NavigationStack(
appui.VStack([
appui.Text(state.title).font("title2").bold(),
appui.Text(state.artist or "—").foreground_color("secondaryLabel"),
appui.Text(f"{state.status} · {state.elapsed}").font("caption"),
appui.HStack([
appui.Button("上一首", action=music_player.previous),
appui.Button("播放", action=load_and_play).button_style("bordered_prominent"),
appui.Button("暂停", action=music_player.pause),
appui.Button("下一首", action=music_player.next),
], spacing=10),
appui.Button(f"模式: {state.mode}", action=toggle_mode),
], spacing=14)
.padding()
.navigation_title("音乐播放器")
)
appui.run(body, state=state)
#歌曲字段
每首歌至少需要 url:
| 字段 | 说明 |
|---|---|
url | 音频 URL、本地路径或 file:// |
title / name | 歌曲标题 |
artist / singer | 艺术家 |
album | 专辑名 |
duration | 可选时长;真实时长准备好后以播放器为准 |
artwork_path | 本地封面路径 |
artwork_url / cover / pic | 远程封面 URL,宿主会缓存后用于 Now Playing |
id | 业务侧歌曲 ID,可用于你的列表选中状态 |
#API 参考
#配置
configure(now_playing=None, remote_commands=None, background=None, persist=None, auto_advance=None, progress_interval=None, can_next=None, can_previous=None)
| 参数 | 说明 |
|---|---|
now_playing | 是否同步锁屏 / 控制中心元数据 |
remote_commands | 是否响应系统播放、暂停、上一首、下一首、seek |
background | 是否配置后台播放音频会话 |
persist | 是否保存队列、进度和播放模式 |
auto_advance | 播放结束后是否按模式自动切歌 |
progress_interval | 进度事件间隔,最小 0.25 秒 |
can_next / can_previous | 覆盖系统按钮可用状态 |
#队列
| API | 说明 |
|---|---|
set_queue(songs, start_index=0, play_mode=None, autoplay=False, preload_count=0) | 替换队列并加载当前歌曲;preload_count 会预备后面 N 首已解析 URL |
queue() | 返回 {queue, current_index, count} |
current() | 返回当前歌曲 |
add(song, play_next=False, autoplay=False) | 追加或插到下一首 |
remove(index) | 删除歌曲 |
move(from_index, to_index) | 移动歌曲 |
clear() | 停止并清空队列 |
prepare(song, index=None) | 预备一首已有真实 url 的歌曲,降低之后切到它时的加载时间 |
prefetch_next(count=3) | 按当前队列和播放模式预备后面几首 |
preload_state() | 返回预加载池状态:count、preload_count、items |
#播放
| API | 说明 |
|---|---|
play() | 播放当前歌曲 |
pause() | 暂停 |
toggle() | 播放 / 暂停切换 |
stop() | 停止并回到 0 秒 |
next() | 下一首 |
previous() | 上一首;当前进度超过 3 秒时先回到开头 |
seek(seconds) | 跳转 |
set_play_mode(mode) | sequence / repeat_all / repeat_one / shuffle |
set_volume(volume) | 0.0–1.0 |
set_rate(rate) | 0.25–3.0 |
#状态与恢复
| API | 说明 |
|---|---|
state() | 返回完整状态:state、playing、current、queue、elapsed、duration、error 等 |
restore(autoplay=False) | 恢复上次队列和进度,默认不自动播放 |
#事件
on_event(callback, events=None) 订阅事件。callback 可以是 Python 函数或 callback id;函数收到 JSON 字符串。
常用事件:
| 事件 | 说明 |
|---|---|
ready | 当前歌曲可播放 |
play / pause / stop | 播放状态变化 |
current / queue | 当前歌曲或队列变化 |
ended | 当前歌曲结束 |
progress | 进度更新 |
preload | 发起预加载 |
prepared | 某首预加载歌曲已准备好 |
item_failed | 某首预加载歌曲准备失败 |
seek | 跳转 |
error | 播放错误 |
restore | 恢复完成 |
remote_command | 锁屏 / 控制中心按钮触发 |
已复制
import json
import music_player
def on_player_event(payload):
event = json.loads(payload)
print(event["event"], event.get("state"), event.get("elapsed"))
music_player.on_event(on_player_event, events=["play", "pause", "remote_command"])
#和相关模块的分工
| 需求 | 用哪个 |
|---|---|
| 自己的音乐队列、锁屏按钮、后台播放 | music_player |
| 播放单个 URL 音频 / 视频 | avplayer |
| AppUI 内嵌视频控件 | appui.PlayerController + appui.VideoPlayer |
| 只发布锁屏标题 / 封面 / 进度 | now_playing |
| Apple Music 资料库 / MusicKit | music |
| 短音效 | sound |
| 大文件后台下载 | background_download |
#常见错误
| 错误写法 | 后果 | 修正 |
|---|---|---|
在 body() 里 set_queue() 或 play() | 刷新时反复重载和播放 | 放到按钮回调或一次性初始化 |
用 music 播放自己的 MP3 URL | 模块不匹配 | 用 music_player 或 avplayer |
| 每个页面自己维护队列 | 页面关闭后状态容易散 | 用 music_player.state() / restore() |
不处理 error 事件 | 网络或格式失败时 UI 卡住 | 订阅 error 并显示失败状态 |
| 不想锁屏显示但没配置 | 控制中心出现播放器卡片 | 播放前 configure(now_playing=False, remote_commands=False) |
#预期效果
运行后,歌曲队列由宿主全局播放器维护;关闭页面再恢复时可通过 restore() 找回上次队列和进度。启用系统集成时,锁屏和控制中心会显示歌曲信息,并把系统按钮回调给 miniapp。