小枫音乐播放器接入指南
小枫音乐播放器(XF Music Player)是可嵌入网站和 Web 应用的 HTML5 音乐播放器插件。本指南说明如何使用发布的 npm 包或 CDN 文件,通过 Web Component 标签、JavaScript 实例以及 Vue、React 接入歌单、歌词和主题功能。
同一页面只保留一个活跃播放器宿主,播放状态与音频管理器由实例共享。标签自动挂载与 JS 实例接入二选一;更换 tagName 不会创建相互隔离的并发播放器。重建前等待 destroy(),销毁后创建新控制器,不再挂载旧控制器。
技术栈与优势
播放器核心基于 Lit + Howler + TypeScript 开发。Lit 负责 Web Component、响应式属性、Shadow DOM 渲染和生命周期;Howler 统一处理播放、暂停、进度、音量、切歌与音频资源释放。
- 响应式稳定:配置、歌单、歌词、主题和播放状态自动驱动界面更新。
- 跨框架复用:HTML、Vue、React、Svelte、Angular 与静态页面共享同一套标签和实例 API。
- 音频生命周期可靠:统一回收音频实例、监听器、请求和计时器,降低多实例资源泄漏风险。
- 性能可控:延迟初始化、封面懒加载、请求取消、状态节流与歌词滚动集中管理。
- 生产可维护:公开配置、数据结构、生命周期和错误兜底保持清晰边界。
代码下载地址
npm 安装命令
npm install xf-music-player发布文件
dist/
music-player.min.js
music-player.esm.js
old-music-player.min.js
plugin/
ie-out/
index.js
sakura/
sakura.min.jsmusic-player.min.js:供<script>或 CDN 使用的固定名称 IIFE 产物。music-player.esm.js:供 Vite、Webpack、Rollup 等现代构建工具使用的 ESM 产物。old-music-player.min.js:旧版播放器兼容产物。plugin/:可选的浏览器兼容检测和樱花特效插件。
安装
pnpm add xf-music-player也可以使用 npm 或 yarn:
npm install xf-music-player
yarn add xf-music-player快速使用
方式一:标签自动挂载
适合静态页面、博客、官网、活动页等无需复杂运行时配置的场景。
<xf-music-player
language="zh"
theme="xf-original-theme"
mode="cloud"
api-url="https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26"
environment="production"
remember-playback="true"
play-mode="order"
volume="0.8"></xf-music-player>
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>标签方式只能传递字符串、数字、布尔值这类基础属性。playlist、audioProvider、生命周期钩子等复杂配置请使用 JS 实例化。
方式二:IIFE JS 实例化
适合不使用构建工具,但需要传入歌单、远程接口或后续调用实例 API 的页面。
<div id="player-root"></div>
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>
<script>
const player = new window.XfMusicPlayer.MusicPlayer({
tagName: 'xf-site-player',
language: 'zh',
isMonitoring: false,
mountElement: document.querySelector('#player-root'),
attributes: {
theme: 'xf-original-theme',
mode: 'local',
environment: 'production',
playerWidth: '324px',
bottom: '24px',
songListHeight: '350px',
visibleSongListCount: 4,
rememberPlayback: true,
memoryKey: 'xf-site-player-memory',
autoplay: false,
playMode: 'order',
volume: 0.8,
isAutoPopup: false,
isAutoPlaylist: false,
colorfulLyric: false,
playlist: [
{
id: 'demo-1',
title: 'Demo Song',
artist: 'Xiao Feng',
cover: 'https://player.xfyun.club/img/half.jpg',
src: 'https://player.xfyun.club/mp3/half.mp3'
}
]
}
})
// 由用户点击播放器控件开始播放。
</script>IIFE 产物会暴露 window.XfMusicPlayer.MusicPlayer。
方式三:ESM / npm 引入
适合 Vue、React、Svelte、原生 Vite 等构建型项目。
import { MusicPlayer } from 'xf-music-player'
const player = new MusicPlayer({
tagName: 'xf-app-player',
language: 'zh',
isMonitoring: false,
attributes: {
theme: 'xf-original-theme',
mode: 'cloud',
apiUrl: 'https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26',
environment: 'production',
rememberPlayback: true,
memoryKey: 'xf-player-memory',
playMode: 'order',
volume: 0.8,
isAutoPopup: true,
isAutoPlaylist: false
}
})Vue / React 生命周期接入
在应用根部放置一个播放器拥有者,不要在每个路由或自动挂载标签旁再创建播放器。框架卸载回调不会等待异步清理。将以下应用侧辅助代码保存为 player-session.ts:串行销毁与创建、跳过过期请求,并仅在客户端加载依赖浏览器的包。调用方应处理返回的 Promise;创建或销毁异常会原样交给调用方。Promise 完成只代表本次创建请求完成,不代表音频已加载;初始界面就绪使用 afterRender。
// player-session.ts — application-owned helper, not a player API.
import type { MusicPlayer } from 'xf-music-player'
type PlayerOptions = ConstructorParameters<typeof MusicPlayer>[0]
let player: MusicPlayer | null = null
let pending: Promise<void> = Promise.resolve()
let revision = 0
export function replacePlayer(options: PlayerOptions | null): Promise<void> {
const request = ++revision
const operation = pending.then(async () => {
if (request !== revision) return
if (player) {
await player.destroy()
player = null
}
if (request !== revision || !options) return
const { MusicPlayer } = await import('xf-music-player')
if (request !== revision) return
player = new MusicPlayer(options)
})
// Keep the queue usable; callers still receive and handle the original rejection.
pending = operation.catch(() => {})
return operation
}React:
import { useEffect, useRef } from 'react'
import { replacePlayer } from './player-session'
export default function MusicPlayerWidget() {
const rootRef = useRef<HTMLDivElement | null>(null)
useEffect(() => {
const root = rootRef.current
if (!root) return
void replacePlayer({
tagName: 'xf-app-player',
mountElement: root,
language: 'zh',
attributes: {
mode: 'local',
autoplay: false,
playlist: [{ id: 'demo-1', title: 'Demo Song', src: 'https://player.xfyun.club/mp3/half.mp3' }]
}
}).catch(console.error)
return () => {
void replacePlayer(null).catch(console.error)
}
}, [])
return <div ref={rootRef} className="music-player-root" />
}Vue 3:
<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue'
import { replacePlayer } from './player-session'
const root = ref<HTMLElement | null>(null)
onMounted(() => {
if (!root.value) return
void replacePlayer({
tagName: 'xf-app-player',
mountElement: root.value,
language: 'zh',
attributes: {
mode: 'local',
autoplay: false,
playlist: [{ id: 'demo-1', title: 'Demo Song', src: 'https://player.xfyun.club/mp3/half.mp3' }]
}
}).catch(console.error)
})
onBeforeUnmount(() => {
void replacePlayer(null).catch(console.error)
})
</script>
<template>
<div ref="root" class="music-player-root"></div>
</template>Vue 和 React 示例二选一使用;需要跨页面连续播放时,将这个唯一拥有者放在路由切换区域之外。业务自行发起的 fetch 或动态导入应在卸载时取消,并在每次 await 后检查当前路由/请求是否仍有效;播放器只管理自己发起的任务。
如果你的 CDN 支持 ESM,也可以直接从 CDN URL 导入:
import { MusicPlayer } from 'https://cdn.jsdelivr.net/npm/xf-music-player@latest/music-player.esm.js'CDN 地址
中国大陆推荐使用小枫音乐播放器静态 CDN;jsDelivr 和 unpkg 可作为 npm CDN 备用线路:
<!-- 中国大陆推荐 -->
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script><!-- jsDelivr IIFE -->
<script src="https://cdn.jsdelivr.net/npm/xf-music-player@latest/music-player.min.js"></script><!-- unpkg IIFE -->
<script src="https://unpkg.com/xf-music-player@latest/music-player.min.js"></script>// jsDelivr ESM
import { MusicPlayer } from 'https://cdn.jsdelivr.net/npm/xf-music-player@latest/music-player.esm.js'小枫静态 CDN 的固定地址指向当前稳定版。官网示例使用 jsDelivr 的 @latest 标签;生产项目如需严格锁定构建结果,可将其替换为明确的已发布版本号。
创建选项
interface CreateMusicPlayerTagOptions {
/** 自定义元素标签名,必须包含连字符;非法值会回退为 xf-music-player */
tagName?: string
/** UI 语言 */
language?: 'zh' | 'en'
/** 是否开启右侧系统监控面板,建议仅开发环境开启 */
isMonitoring?: boolean
/** 播放器运行配置,复杂对象和函数都从这里传入 */
attributes?: MusicPlayerAttributes
/** 挂载容器,默认 document.body */
mountElement?: HTMLElement
/** 是否关闭自动挂载,由业务主动调用 mount() */
manualMount?: boolean
/** 生命周期钩子,也可以通过链式 API 后续注册 */
hooks?: MusicPlayerHooks
}国际化语言
播放器当前支持 zh 和 en 两种语言。语言配置不属于 MusicPlayerAttributes,而是播放器实例或播放器标签的顶层配置。
标签方式:
<xf-music-player language="en"></xf-music-player>JS / Vue / React 实例化方式:
const player = new MusicPlayer({
tagName: 'xf-music-player',
language: 'en',
attributes: {
theme: 'xf-original-theme',
mode: 'local',
playlist: []
}
})如果生产环境需要在中英文之间切换,建议销毁当前实例后用新的 language 重新创建播放器,确保错误提示音、按钮文案、歌词占位和提示信息全部同步到目标语言。
await player.destroy()
const nextPlayer = new MusicPlayer({
tagName: 'xf-music-player',
language: 'zh',
attributes: {
theme: 'xf-original-theme',
mode: 'local',
playlist
}
})在线调试页的语言选择会同步影响预览播放器,并在 HTML、JavaScript、Vue、React 配置代码中自动生成对应配置。
手动挂载示例:
const player = new MusicPlayer({
manualMount: true,
mountElement: document.querySelector('#player-root') as HTMLElement,
attributes: {
rememberPlayback: true,
playMode: 'single'
}
})
player
.beforeRender(el => console.log('before render', el))
.afterRender(el => console.log('after render', el))
.mount()歌曲数据格式
interface SongInfo {
id: string | number
title: string
artist?: string
album?: string
cover?: string
format?: string
/** 音频地址;云端数据允许为空,播放时会进入错误提示音兜底流程 */
src?: string | null
duration?: number
lyrics?: string | ILyricItem[]
lyricsUrl?: string | null
lyrics_url?: string | null
}lyrics 支持结构化数组或标准 LRC 字符串;lyricsUrl / lyrics_url 可指向外链 LRC 文本。切歌时播放器会取消旧歌词请求,只应用当前歌曲的歌词结果。
[00:00.00] Song title
[00:05.00] First lyric lineduration 使用秒。format 是可选的格式提示,例如 flac、wav、aiff,适合 Blob URL 或不带扩展名的音频地址;它不会增加浏览器的解码能力。
配置项
以下配置对应 MusicPlayerAttributes,通过 JS / Vue / React 实例化时放入 attributes,通过标签方式接入时使用短横线属性名,例如 rememberPlayback 对应 remember-playback。
左右滑动查看完整表格 · 也可聚焦表格后使用方向键
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
theme | string | xf-original-theme | 内置主题名,也支持 random-theme、auto-theme |
customThemeName | string | - | 从页面样式表提取同名 class/id CSS 变量 |
customThemeStyle | string | - | 注入自定义主题 CSS |
playerWidth | string | 324px | 播放器宽度 |
fontName | string | - | 字体名 |
bottom | string | 2em | 底部距离 |
songListHeight | string | 350px | 歌单展开高度 |
visibleSongListCount | number | 4 | 歌单可视歌曲数量 |
isAutoPopup | boolean | false | 是否自动弹出/展开播放器,只控制界面状态,不触发播放 |
isAutoPlaylist | boolean | false | 是否展开歌单 |
colorfulLyric | boolean | false | 是否启用彩色歌词和封面取色背景 |
audioVisualizer | boolean | false | 是否启用 2D Canvas 实时音频可视化;开启后进度条使用连续波形展示播放进度,底部歌词条同步显示底部对齐的柱状音频背景;多彩歌词开启时每个柱状线使用稳定分色,跨域音频无法分析时会自动降级 |
lazyLoadTimer | number | 0 | 延迟初始化播放器的时间,单位 ms |
lazyLoadAnimationUrl | string | 按主题自动选择 | 主封面和歌单封面加载前展示的动画图 |
mode | cloud | local | cloud | 数据源模式;云端模式使用 apiUrl,本地模式使用 playlist / audioProvider |
apiUrl | string | VITE_API_URL | 云端歌单接口地址,也可通过 api-url 属性自定义;支持 {environment} 占位符 |
environment | development | production | test | production | 当前环境 |
rememberPlayback | boolean | true | 是否记忆播放 |
memoryKey | string | xf-music-player-memory | localStorage 记忆 key |
autoplay | boolean | false | 是否挂载后尝试自动播放,只控制播放启动,不控制播放器弹出 |
playMode | order | single | random | order | 播放模式 |
volume | number | 0.8 | 音量,范围 0-1 |
playlist | SongInfo[] | [] | 本地初始歌单;构建产物不内置 mock 数据 |
audioProvider | MusicAudioProvider | - | 自定义音频接口 |
标签方式只适合字符串、数字、布尔值等基础属性;playlist、audioProvider、生命周期钩子和复杂对象请使用 JS / Vue / React 实例化。
TypeScript 配置类型:
type MusicPlayerAttributes = {
theme?: string
customThemeName?: string
customThemeStyle?: string
playerWidth?: string
fontName?: string
bottom?: string
songListHeight?: string
visibleSongListCount?: number
isAutoPopup?: boolean
isAutoPlaylist?: boolean
colorfulLyric?: boolean
audioVisualizer?: boolean
lazyLoadTimer?: number
lazyLoadAnimationUrl?: string
mode?: 'cloud' | 'local'
apiUrl?: string
environment?: 'development' | 'production' | 'test'
rememberPlayback?: boolean
memoryKey?: string
autoplay?: boolean
playMode?: 'order' | 'single' | 'random'
volume?: number
playlist?: SongInfo[]
audioProvider?: MusicAudioProvider
}播放器内部状态包含标签名、监控状态、语言、播放器配置、播放运行态和播放控制命令。业务侧通常不需要直接访问内部 Store,生产环境优先使用 MusicPlayer 实例 API;在线调试控制台会基于这些状态能力做可视化配置。
type MusicStoreState = {
tagName: string
isMonitoring: boolean
language: 'zh' | 'en'
playerConfig: MusicPlayerAttributes
playback: MusicPlaybackState
command: MusicPlayerCommand | null
setTagName: (val?: string) => void
toggleMonitoring: (val?: boolean) => void
setLanguage: (val: 'zh' | 'en') => void
updatePlayerConfig: (config: Partial<MusicPlayerAttributes>) => void
setPlaylist: (playlist: SongInfo[], currentIndex?: number) => void
updatePlayback: (state: Partial<MusicPlaybackState>) => void
setPlayMode: (mode: 'order' | 'single' | 'random') => void
dispatchCommand: (type: MusicPlayerCommandType, payload?: unknown) => void
clearMusicPlayerInstance: () => void
}播放器插槽
命名插槽接收播放器宿主的直接子节点。这些节点保留在页面 Light DOM 中,页面 CSS 可以直接设置其样式;不需要穿透播放器内部 Shadow DOM。
| 插槽名 | 位置与行为 |
|---|---|
player-default | 位于主题容器内、浮动主体之前,附加业务内容,不替换内置控件;位置和样式由业务 CSS 设置 |
player-content | 替换浮动主体内的默认展开按钮、播放控制和歌单;传入后需要自行实现这些控件 |
player-lyrics | 替换内置底部歌词及全屏歌词交互区域;自定义节点的显示、隐藏和交互由业务管理 |
| 默认插槽 | 未声明 slot 的直接子节点输出到宿主渲染层、核心播放器之前,不属于内置播放控制区 |
直接子节点:四类插槽
先用下面的结构确认插槽位置。mode="local" 避免结构示例请求云端歌单;这四块文字只是占位内容。
<xf-music-player
class="slot-map"
theme="xf-original-theme"
is-auto-popup="true"
language="zh"
mode="local"
player-width="350px"
bottom="3em"
font-name="Google Sans Code"
is-monitoring="false"
colorful-lyric="false"
>
<div class="slot-note">默认插槽</div>
<div slot="player-default" class="slot-banner">播放器默认插槽</div>
<div slot="player-content" class="slot-content">播放器内容插槽</div>
<div slot="player-lyrics" class="slot-lyrics">歌词控制插槽</div>
</xf-music-player>
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>四个节点都直接放在 <xf-music-player> 内,标签位于脚本前。player-content 会替换内置控件与歌单,player-lyrics 会替换内置歌词,因此上面的占位文字不会自动变成播放按钮或同步歌词。只想增加内容时,保留默认插槽与 player-default,移除另外两个节点即可恢复内置界面。
播放器宿主是零尺寸浮动层,不参与正文排版;自定义节点需要显式布局。下面的页面 CSS 直接设置这些业务节点,不影响内置主题。示例把两个附加信息块固定在顶部,自定义歌词条固定在底部,给播放器预留间距。font-name 仅指定字体名称;要使用该字体,页面还需要自行引入字体文件。
.slot-map {
display: block;
color: #273042;
font: 14px/1.5 system-ui, sans-serif;
}
.slot-map .slot-note,
.slot-map .slot-banner {
position: fixed;
left: 12px;
box-sizing: border-box;
width: max-content;
max-width: calc(100vw - 24px);
padding: 8px 12px;
border: 1px solid #dcdfea;
border-radius: 8px;
background: #f7f8fc;
}
.slot-map .slot-note { top: 16px; }
.slot-map .slot-banner { top: 68px; }
.slot-map .slot-content {
box-sizing: border-box;
width: min(350px, calc(100vw - 24px));
padding: 24px;
border: 1px solid #dcdfea;
border-radius: 16px;
background: #fff;
text-align: center;
}
.slot-map .slot-lyrics {
position: fixed;
right: 12px;
bottom: 8px;
left: 12px;
height: 28px;
border-radius: 8px;
background: #28243e;
color: #f8f7ff;
font-size: 12px;
line-height: 28px;
text-align: center;
}实用示例:保留内置播放器,增加控制按钮
下面是同一个独立页面的 HTML、CSS 和 JavaScript 三部分,按顺序一起使用。示例仅使用 player-default 添加两个按钮,保留原有播放器和歌词;与标签自动挂载示例二选一,同一页面不要再创建第二个播放器。
<div id="slot-player-root"></div>
<template id="slot-player-template">
<div slot="player-default" class="slot-toolbar">
<button type="button" data-player-action="toggle">播放 / 暂停</button>
<button type="button" data-player-action="next">下一首</button>
</div>
</template>
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>.slot-demo .slot-toolbar {
position: fixed;
top: 16px;
right: 16px;
display: flex;
flex-wrap: wrap;
gap: 8px;
max-width: calc(100vw - 32px);
padding: 10px;
border: 1px solid #d7dce5;
border-radius: 12px;
background: #ffffff;
color: #1f2937;
font: 14px/1.5 system-ui, sans-serif;
}
.slot-demo .slot-toolbar button {
padding: 6px 10px;
border: 1px solid #c4cbdb;
border-radius: 8px;
background: #f3f5fb;
color: inherit;
font: inherit;
cursor: pointer;
}
.slot-demo .slot-toolbar button:focus-visible {
outline: 2px solid #6254cc;
outline-offset: 2px;
}const root = document.querySelector('#slot-player-root')
const template = document.querySelector('#slot-player-template')
const events = new AbortController()
const player = new window.XfMusicPlayer.MusicPlayer({
tagName: 'xf-slot-demo',
language: 'zh',
mountElement: root,
manualMount: true,
attributes: {
mode: 'local',
isAutoPopup: true,
rememberPlayback: false,
playlist: [{
id: 'slot-demo',
title: 'Demo Song',
src: 'https://player.xfyun.club/mp3/half.mp3'
}]
}
})
player.beforeRender(host => {
host.classList.add('slot-demo')
host.append(template.content.cloneNode(true))
host.querySelector('[data-player-action="toggle"]').addEventListener(
'click', () => player.toggle(), { signal: events.signal }
)
host.querySelector('[data-player-action="next"]').addEventListener(
'click', () => player.next(), { signal: events.signal }
)
}).beforeDestroy(() => events.abort()).mount()
async function disposeSlotPlayer() {
await player.destroy()
}beforeRender 在首次渲染前把插槽节点加入宿主,确保命名插槽被识别。运行中可以更新已存在节点的文字、样式和内容;要增加新的插槽区域,应在创建阶段准备好对应节点。不要使用 host.innerHTML = ... 覆盖已有业务节点。
若使用 player-content 或 player-lyrics,把对应节点也放入模板作为宿主直接子节点,并为其编写 CSS 与事件处理。它们替换而非叠加内置界面;自定义按钮继续调用 player.play()、player.pause()、player.next() 等公开 API,不查询或修改内部 Shadow DOM。
页面退出或重建前调用并等待 disposeSlotPlayer();beforeDestroy 通过 AbortController 移除本示例注册的业务事件。播放器不会自动清理接入方绑定在页面其他节点上的事件。
云端模式
mode 默认是 cloud。未显式配置 apiUrl 时,播放器会读取构建时的 VITE_API_URL:
VITE_API_URL=https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26也可以通过标签或 JS 配置覆盖:
<xf-music-player
mode="cloud"
api-url="https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26"></xf-music-player>new MusicPlayer({
attributes: {
mode: 'cloud',
apiUrl: 'https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26'
}
})云端模式配置有效 apiUrl 且没有自定义 audioProvider 时,播放器先预加载歌单。等待超过 2 秒显示国际化加载提示;超过 10 秒、请求失败或空歌单会显示国际化失败提示。请求失败时,当前宿主渲染可操作的空播放器并保留错误状态,不会立即重复同一次失败预加载。可通过 player.setPlaylist(...) 补入歌单,或更换 apiUrl、environment、audioProvider 加载新数据。已离开的旧宿主不会因请求完成重新出现。内置请求使用 credentials: 'omit';需要业务请求选项时使用 provider。
云端歌单接口需要返回统一结构:
{
"code": 200,
"msg": "success",
"data": [
{
"id": "002qU5aY3Qu24y",
"title": "青花瓷",
"artist": "周杰伦",
"album": "我很忙",
"cover": "https://y.qq.com/music/photo_new/T002R300x300M000002eFUFm2XYZ7z.jpg",
"src": null,
"duration": 239,
"lyrics_url": "https://music.api.xfyun.club/api/v1/music/lyric?platform=qq&songId=002qU5aY3Qu24y"
}
]
}失败结构:
{
"code": 500,
"msg": "服务器错误",
"data": null
}当 code !== 200 时,播放器会读取 msg 作为错误信息。src 可以为空;用户播放这类歌曲时,播放器不会创建空音频实例,而是播放当前语言的错误提示音,提示音结束后切换下一首。连续 3 首失败后停止播放。
如果 apiUrl 中包含 {environment},播放器会自动替换为当前 environment:以下路径表示业务自己的接口,不是 XF API Server 的现成端点。
new MusicPlayer({
attributes: {
mode: 'cloud',
apiUrl: '/api/{environment}/music/list',
environment: 'production'
}
})歌词接口支持普通 LRC 文本,也支持统一 JSON 响应。播放器只读取 data.lyric:
{
"code": 200,
"msg": "success",
"data": {
"lyric": "[00:00.00] 第一行歌词"
}
}XF API Server 接入示例
官网调试页提供 XF API 地址生成器,可根据服务地址、平台和接口参数自动生成 api-url,并支持拉取预览。推荐直接用于播放器云端模式的接口:
GET https://music.api.xfyun.club/api/v1/music/top?platform=netease&topId=3778678GET https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26GET https://music.api.xfyun.club/api/v1/music/playlist-songs?platform=netease&playlistId=3778678GET https://music.api.xfyun.club/api/v1/music/playlist-songs?platform=qq&playlistId=8523075134歌曲数据中的 lyrics_url 会在播放或切歌时请求,播放器只读取歌词接口响应中的 data.lyric:
GET https://music.api.xfyun.club/api/v1/music/lyric?platform=netease&songId=5257138GET https://music.api.xfyun.club/api/v1/music/lyric?platform=qq&songId=002qU5aY3Qu24ytop、playlist-songs 的 data 直接是播放器歌曲数组。search 只返回歌曲基础信息,不包含 src、duration、lyrics_url,不作为调试器的 api-url 生成入口,避免把不可播放的基础歌曲信息写入播放器。
本地模式
mode="local" 不会主动请求 apiUrl,适合离线页面、静态歌单或业务自己控制数据源:
new MusicPlayer({
attributes: {
mode: 'local',
playlist: [
{
id: 'local-1',
title: 'Local Song',
src: '/audio/demo.mp3'
}
]
}
})本地音频元数据
播放器支持 FLAC、WAV、AIFF / AIFF-C、MP3、M4A、Ogg 等格式的元数据读取。解析按文件内容识别,不依赖扩展名;元数据读取成功与浏览器支持播放该编码是两件事,播放器不负责转码。
readAudioMetadata(input):接受File/Blob/ArrayBuffer/Uint8Array,返回AudioMetadata,不创建临时 URL。readAudioFile(file):接受File,返回{ song, metadata, dispose };song可直接传给本地playlist,包含音频 Blob URL、可用封面、歌词和format提示。metadata.format包含可用的duration、sampleRate、numberOfChannels、bitsPerSample、bitrate;metadata.common包含已有标签、picture和lyrics,metadata.native保留原始标签。缺失字段保持缺失,标题缺失时歌曲使用文件名。- 空文件、非音频、无有效音频流或解析失败会抛出错误,并通过
cause保留原始异常;仍可读取的不完整标签可见于metadata.quality.warnings。这不等于全文件解码或完整性验证。
以下函数在用户通过 <input type="file" accept="audio/*,.flac,.wav,.aif,.aiff,.aifc"> 选择文件后调用。调用前先完成旧播放器的销毁;返回的清理函数由页面退出或下次选文件前调用并等待,避免同时预览多个文件。
import { MusicPlayer, readAudioFile, readAudioMetadata } from 'xf-music-player'
async function previewFile(file: File) {
const loaded = await readAudioFile(file)
console.log(loaded.metadata.format, loaded.metadata.common, loaded.metadata.quality.warnings)
let player: MusicPlayer
try {
player = new MusicPlayer({
attributes: { mode: 'local', rememberPlayback: false, playlist: [loaded.song] }
})
} catch (error) {
loaded.dispose()
throw error
}
return async () => {
try { await player.destroy() }
finally { loaded.dispose() }
}
}
async function inspectFile(file: File) {
return readAudioMetadata(file)
}dispose() 可重复调用;仅在歌曲已停止使用后释放音频和封面 Blob URL。临时 URL 不应写入持久歌单,示例关闭记忆播放。普通云端播放不会为了扫描标签而自动下载整个音频文件。使用 IIFE 时对应 window.XfMusicPlayer.readAudioFile / readAudioMetadata;SSR 项目在客户端事件中动态导入这些函数。
自定义音频接口
type MusicSongListPayload = SongInfo[] | { list: SongInfo[] } | { playlist: SongInfo[] } | { songs: SongInfo[] }
interface MusicApiResponse<T> {
code: number
msg?: string
data: T | null
}
type MusicAudioProvider = (
context: MusicAudioProviderContext
) => Promise<MusicSongListPayload | MusicApiResponse<MusicSongListPayload>> | MusicSongListPayload | MusicApiResponse<MusicSongListPayload>
interface MusicAudioProviderContext {
environment: 'development' | 'production' | 'test'
playerConfig: MusicPlayerAttributes
signal?: AbortSignal
}audioProvider 优先级高于内置 mode/apiUrl 逻辑,适合业务需要自行签名、鉴权或组合多接口的场景。它支持直接返回 SongInfo[]、{ list: SongInfo[] },也兼容 { code: 200, msg: 'success', data: SongInfo[] }。播放器会自动过滤危险协议和非法数据;当环境快速切换或组件销毁时,旧请求会被 AbortSignal 取消并且不会覆盖新歌单。
下面使用公开 XF API 展示最小 provider;鉴权或签名应由业务自己的服务处理。将 signal 传给 fetch,并检查 HTTP 状态,不把失败响应伪装成空数组:
const player = new MusicPlayer({
attributes: {
mode: 'local',
environment: 'production',
audioProvider: async ({ signal }) => {
const response = await fetch('https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26', { signal })
if (!response.ok) throw new Error(`Playlist HTTP ${response.status}`)
return response.json()
}
}
})只有消费 signal 的业务网络请求才会实际停止;即使 provider 忽略取消,播放器也会丢弃旧请求结果。业务附加的日志/提示应先检查 signal?.aborted,取消时保留原始异常而不是弹出失败提示或无限重试。真实网络、HTTP 和业务 code 错误仍按失败处理。更新 environment、apiUrl 或 provider 引用会重新加载;本地歌单可直接 player.setPlaylist(nextList),清空使用 player.setPlaylist([])。
媒体权限与 iOS 音量
autoplay: true只尝试自动播放;浏览器用户手势策略或嵌入页面权限仍可能阻止它。isAutoPopup只展开界面,与播放权限无关。推荐由用户点击播放器按钮启动;业务按钮也应在点击回调中调用player.play(),该方法返回控制器而非可等待的播放 Promise。- iframe 接入需结合父页面的
allow="autoplay"和 Permissions Policy 配置;它不保证免除用户手势要求。播放器不录音,也不需要麦克风权限。 - 音频 URL 需可访问并符合页面 HTTPS / CSP 配置;API 与歌词跨域请求需要服务端允许 CORS。封面取色和 Web Audio 分析有额外的跨域读取要求,普通音频可播放不代表可分析。
- iOS/iPadOS 操作音量图标或音量条会显示中英文居中提示,请使用设备音量键或控制中心;不展开无效滑块、不派发音量变更,3 秒内重复操作只提示一次。
volume/setVolume()不保证改变设备实际输出音量。其他平台仍保留音量滑块。
播放交互
- iOS/iPadOS 以外的平台,音量图标点击后只展开或收起音量滑块,静音能力保留给公开 API、调试控制台或业务自定义入口。
- 音量面板支持拖动调节,并实时显示
0%到100%。 - 主封面切歌时会先渲染主题加载图,并保留约 120ms 最小展示时间。
- 进度条支持拖动;拖动开始时暂停音频,拖动中预览时间和歌词,松手后 seek 并播放,即使拖动前处于暂停状态。
- 上一首、下一首、歌单开关和播放模式按钮带前沿防抖。
- 点击当前歌单歌曲会播放或暂停,点击其他歌曲会切换并播放。
- 歌单图片使用懒加载策略,进入视口并停留 150ms 后替换真实图片。
- 播放、暂停、切歌、静音和切换播放模式会触发居中提示;歌单展开/收起不弹出提示。
- 歌词遵循播放生命周期:播放时显示,暂停或停止时隐藏;手动关闭会记忆为持续隐藏,后续切歌不自动重新打开。
- 点击底部歌词条会打开全屏歌词弹窗,点击歌词行会跳转到对应播放时间。
键盘控制
| 按键 | 行为 |
|---|---|
Tab | 切换播放器收缩/展开状态,即 isAutoPopup |
Space | 播放或暂停当前歌曲 |
ArrowLeft | 上一首 |
ArrowRight | 下一首 |
键盘控制会跳过输入框、下拉框、按钮、滑块、contenteditable 和组合输入状态;同时会忽略 Ctrl、Meta、Alt 组合键,避免抢占系统或浏览器原生快捷键。
失败兜底
音频加载或播放失败时,播放器不会直接卡死在当前歌曲,而是按当前语言播放内置错误提示音。错误提示音播放完成后会自动切换下一首;连续 3 首歌曲播放失败时停止自动播放,并通过错误提示告知用户。
首次自动播放受到浏览器策略阻止时会降级为暂停,不自动跳到下一首;这与用户主动操作时遇到真实资源错误不同。异常媒体事件在 15 秒内触发 3 次自动切歌也会熔断,后续用户主动播放或选歌才开启新的恢复周期。业务不应在外围添加无限重试或自动 next() 循环。
记忆播放
rememberPlayback 默认开启;业务显式传入 rememberPlayback: false 或 remember-playback="false" 后会关闭。开启时播放器会在 memoryKey 下保存结构化记忆:
interface MusicMemoryState {
current: MusicMemoryRecord | null
}current 保存当前歌曲的记忆,包括歌曲 id、音频地址、进度、音量、静音状态、播放模式和歌词手动隐藏偏好。
实例 API
player.play()
player.pause()
player.toggle()
player.stop()
player.prev()
player.next()
player.select(0)
player.seek(30)
player.setVolume(0.6)
player.mute(true)
player.setPlayMode('single')
player.setPlaylist(list, 0)
player.setConfig({
theme: 'xf-dark-theme',
songListHeight: '350px',
visibleSongListCount: 4,
lazyLoadAnimationUrl: '/imgs/custom-loader.svg'
})
await player.destroy()生命周期
播放器生命周期分为外层 MusicPlayer 控制器和内部 Web Component 生命周期。接入方只需要使用公开钩子,不需要直接操作内部组件。
destroy() 返回 Promise<void>,同一控制器的重复调用复用同一个 Promise;默认 timer: 0 也为异步销毁。替换实例前必须等待。destroy(options) 的一次性 beforeDestroy 会被等待,注册的生命周期钩子与 afterDestroy 则是同步通知,不是异步屏障。一次性回调抛错时宿主仍会移除,接入方应处理拒绝而非直接继续成功交接分支。替换后停止调用旧控制器:播放指令作用于共享状态。
触发顺序
new MusicPlayer(options):归一化配置、注册自定义元素、创建宿主 DOM。mount():宿主 DOM 插入页面;自动挂载模式下该步骤会在构造后的微任务执行。beforeRender(el):宿主元素连接到页面后触发。- 内部初始化:写入运行配置、解析主题、加载歌单、恢复记忆播放。
afterRender(el):宿主元素首次渲染完成后触发。beforeUpdate(el, changed):响应式属性或状态联动触发 DOM 更新前执行。afterUpdate(el, changed):DOM 更新完成后执行。destroy()或 DOM 移除:触发beforeDestroy(el),随后清理计时器、订阅、音频实例、页面监听和临时资源。afterDestroy():内部清理完成后触发。
钩子注册
player
.beforeRender(el => {})
.afterRender(el => {})
.beforeUpdate((el, changed) => {})
.afterUpdate((el, changed) => {})
.beforeDestroy(el => {})
.afterDestroy(() => {})销毁示例:
await player.destroy({
timer: 300,
beforeDestroy: async el => {
// 一次性销毁前回调。
},
afterDestroy: () => {
// 一次性销毁后回调。
}
})自定义主题
通过 CSS 变量覆盖播放器主题:
.my-player-theme {
--background-color: #fbfffd;
--theme-color: #1e7d58;
--text-color: #7d9e92;
--song-name-color: #4d9678;
--text-deputy-color: #6a8e80;
--text-hover-color: #26996c;
--text-hover-color-rgb: 38, 153, 108;
}player.setConfig({ customThemeName: 'my-player-theme' })内联主题样式同时传入选择器名称与 CSS 文本;使用播放器变量,不依赖页面选择器穿透 Shadow DOM:
player.setConfig({
customThemeName: 'my-player-theme',
customThemeStyle: `.my-player-theme {
--background-color: #fbfffd;
--theme-color: #1e7d58;
--text-color: #45665a;
--song-name-color: #24523f;
--text-deputy-color: #58776a;
--text-hover-color: #26996c;
--text-hover-color-rgb: 38, 153, 108;
}`
})音频可视化
audioVisualizer 默认关闭,可在现有实例上切换,不必重新创建播放器:
player.setConfig({ audioVisualizer: true, colorfulLyric: true })
// 关闭波形后保留普通播放。
player.setConfig({ audioVisualizer: false })开启后以 Canvas 绘制连续波形进度条及底部歌词柱状背景;colorfulLyric 为柱状条使用稳定分色。跨域音频须允许 Web Audio 分析,否则保留普通播放并降级显示。跨域封面须允许读取像素才能取色,否则使用主题背景。关闭可视化后继续使用当前播放器进行普通播放,不需要重新创建播放器。
浏览器兼容性
IIFE / ESM 产物以 ES2020 为编译目标,转换逻辑赋值和类字段;不注入全局 polyfill,仍需 Web Components、BigInt 等运行时与媒体能力。元数据可解析不代表每个浏览器都可播放该编码,请用目标浏览器/WebView 和真实音频格式验证。
推荐使用现代 Chrome、Edge Chromium、Firefox、Safari 以及主流移动端现代浏览器。播放器依赖 customElements、HTMLElement、Shadow DOM、Promise、Symbol、Object.assign、JavaScript 和 HTML5 Audio。
以下环境不支持或不建议用于生产:
- Internet Explorer 全部版本:明确不支持,可使用兼容检测插件引导升级。
- 旧版 Edge Legacy:不建议支持 EdgeHTML 内核版本。
- Android 旧 WebView / 旧 UC / 旧 QQ 浏览器:缺少 Web Components 或基础 ES 能力时无法运行。
- iOS 10 及以下 Safari:Web Components、Shadow DOM 与音频策略支持不稳定。
- 禁用 JavaScript、限制 Web Components 或不支持 HTML5 Audio 的环境:播放器无法初始化或播放。
浏览器插件
兼容检测插件放在播放器主包之前。检测到 IE、Edge Legacy 或关键能力缺失时,会跳转到浏览器升级页:
<script src="/player/plugin/ie-out/index.js"></script>
<script src="/player/music-player.min.js"></script>樱花特效插件可独立使用,与播放器同页时建议放在主包之后:
<script src="/player/music-player.min.js"></script>
<script src="/player/plugin/sakura/sakura.min.js"></script>CDN 地址:
<script src="https://cdn.jsdelivr.net/npm/xf-music-player@latest/plugin/ie-out/index.js"></script>
<script src="https://cdn.jsdelivr.net/npm/xf-music-player@latest/plugin/sakura/sakura.min.js"></script>旧版迁移
新项目推荐使用 music-player.min.js 或 npm 包中的 MusicPlayer 实例 API。旧项目可暂时引入兼容产物:
<script src="/player/old-music-player.min.js"></script>- 新页面使用
<xf-music-player>或new MusicPlayer(...)。 - 旧页面短期保留
old-music-player.min.js,逐步迁移配置、歌单和生命周期。 - 需要完整旧版代码与示例时,查看发布目录中的
xf-MusicPlayer-master/。
生产环境建议
- CDN 引入时锁定明确版本号,不使用不带版本的 latest 地址。
- 复杂数据通过 JS 实例化传入,不建议塞进 HTML 属性。
- 生产环境关闭
isMonitoring。 - 同一页面只保留一个活跃播放器;自定义
tagName必须包含连字符,更换标签名不绕开单实例约束。 - 重新初始化前
await player.destroy();框架清理回调的返回值不构成等待,应由应用共享交接队列串行协调。 - 对远程歌曲、封面、歌词资源配置稳定的 HTTPS 地址。
FAQ
标签引入和 JS 引入怎么选?
静态页面、配置简单时用标签自动挂载;需要动态歌单、远程接口、生命周期钩子或实例 API 时用 JS 实例化。
CDN 和 npm 能同时提供吗?
可以。npm 包用于构建型项目,CDN 地址用于静态页面或低成本接入。两者建议指向同一个版本的打包产物。
源码不公开会影响使用吗?
不会。接入方只需要 npm 包或 CDN 文件,以及本文档里的配置项、数据结构和实例 API。