Integration Guide
Xiao Feng Music Player (XF Music Player) is an embeddable HTML5 music player plugin for websites and web applications. This guide covers the published npm package and CDN files, with Web Component, JavaScript, Vue and React integration for playlists, lyrics and themes.
A page has one active player host because playback state and audio management are shared. Choose either automatic tag mounting or a JavaScript owner; changing tagName does not create isolated concurrent players. Await destroy() before creating a replacement, and create a new controller rather than mounting a destroyed one again.
Tech Stack
The player is built with Lit + Howler + TypeScript. Lit provides Web Components, reactive properties, Shadow DOM rendering, and lifecycle control. Howler provides a consistent layer for playback, pause, seek, volume, switching, and audio resource cleanup.
- Stable reactive updates: configuration, playlists, lyrics, themes, and playback state update the UI automatically.
- Cross-framework reuse: HTML, Vue, React, Svelte, Angular, and static pages share the same player tag and instance API.
- Reliable audio lifecycle: audio instances, listeners, requests, and timers are cleaned up consistently.
- Controlled performance: delayed initialization, lazy covers, request cancellation, state throttling, and lyric scrolling are managed in one runtime.
- Maintainable production API: public options, schemas, lifecycle hooks, and error recovery remain clearly separated.
Download Links
npm Install Command
npm install xf-music-playerPublished Files
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: stable-name IIFE bundle for<script>and CDN integration.music-player.esm.js: ESM bundle for Vite, Webpack, Rollup, and modern build tools.old-music-player.min.js: compatibility bundle for legacy player usage.plugin/: optional compatibility detection and sakura effect plugins.
Installation
pnpm add xf-music-playerYou can also use npm or yarn:
npm install xf-music-player
yarn add xf-music-playerQuick Start
Player Tag
Use the player tag for static pages, blogs, documentation sites, and landing pages with simple configuration.
<xf-music-player
language="en"
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>HTML attributes are suitable for strings, numbers, and booleans. Use JavaScript initialization for playlist, audioProvider, hooks, and complex objects.
JavaScript Instance
Use the IIFE build when no bundler is available but you need playlist data or instance methods.
<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: 'en',
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,
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'
}
]
}
})
// Start playback from a user gesture on the player UI.
</script>ESM / npm
Use the ESM build in Vue, React, Svelte, native Vite, or other bundled applications.
import { MusicPlayer } from 'xf-music-player'
const player = new MusicPlayer({
tagName: 'xf-app-player',
language: 'en',
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
}
})Vue / React Lifecycle Integration
Mount one player owner at the application root; do not mount another player in every route or alongside the automatic HTML tag. Framework unmount callbacks do not await asynchronous cleanup. Save the shared helper below as player-session.ts: it serializes teardown and creation, skips stale requests, and imports the browser-only package on the client. Every caller must handle its returned Promise; it reports creation/teardown errors rather than turning them into a successful mount. A resolved Promise means the creation request finished, not that audio is loaded; use afterRender for initial UI readiness.
// 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: 'en',
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: 'en',
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>Use either framework example, not both. Keep this owner outside route transitions when continuous playback is desired. If application code performs additional fetch or dynamic imports, cancel them on unmount and check that the route/request is still current after every await; the player cannot cancel work it did not start.
CDN URLs
Use the Xiao Feng Music Player static CDN in mainland China. jsDelivr and unpkg remain available as npm CDN alternatives:
<!-- Recommended in mainland China -->
<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>import { MusicPlayer } from 'https://cdn.jsdelivr.net/npm/xf-music-player@latest/music-player.esm.js'The fixed Xiao Feng CDN URL serves the current stable release. The documentation uses jsDelivr's @latest tag; for reproducible production builds, replace it with an explicit published version.
Create Options
interface CreateMusicPlayerTagOptions {
/** Custom element tag name. It must contain a hyphen. */
tagName?: string
/** UI language. */
language?: 'zh' | 'en'
/** Enable the monitoring console. Recommended only in development. */
isMonitoring?: boolean
/** Runtime player options. Complex data and functions are passed here. */
attributes?: MusicPlayerAttributes
/** Mount container. Defaults to document.body. */
mountElement?: HTMLElement
/** Disable automatic mounting. Call mount() manually. */
manualMount?: boolean
/** Lifecycle hooks. */
hooks?: MusicPlayerHooks
}Internationalization
The player currently supports zh and en. Set language on the player tag or as a top-level MusicPlayer option.
<xf-music-player language="en"></xf-music-player>const player = new MusicPlayer({
tagName: 'xf-music-player',
language: 'en',
attributes: {
theme: 'xf-original-theme',
mode: 'local',
playlist: []
}
})To switch languages at runtime, await player.destroy() before constructing a new MusicPlayer with the next top-level language; alternatively use the shared replacePlayer helper above. This keeps fallback audio, button text, lyric placeholders, and messages aligned.
Song Data
interface SongInfo {
id: string | number
title: string
artist?: string
album?: string
cover?: string
format?: string
/** Audio URL. Cloud data may leave it empty; playback will use fallback audio. */
src?: string | null
duration?: number
lyrics?: string | ILyricItem[]
lyricsUrl?: string | null
lyrics_url?: string | null
}lyrics supports a structured array or standard LRC text. lyricsUrl / lyrics_url can point to an external LRC file. When switching songs, the player cancels old lyric requests and only applies the latest result.
duration uses seconds. format is an optional format hint such as flac, wav, or aiff, useful for Blob URLs and extensionless audio URLs; it does not add browser codec support.
Options
Use camelCase inside attributes for JavaScript / Vue / React. Use kebab-case on the HTML tag, for example rememberPlayback becomes remember-playback.
Scroll sideways to see all columns · Focus the table and use arrow keys
| Field | Type | Default | Description |
|---|---|---|---|
theme | string | xf-original-theme | Built-in theme name; also supports random-theme and auto-theme. |
customThemeName | string | - | Reads CSS variables from a custom class or id. |
customThemeStyle | string | - | Injects custom theme CSS. |
playerWidth | string | 324px | Player width. |
fontName | string | - | Font family name. |
bottom | string | 2em | Bottom offset. |
songListHeight | string | 350px | Expanded playlist height. |
visibleSongListCount | number | 4 | Visible playlist row count. |
isAutoPopup | boolean | false | Auto popup / expand the player UI only. It does not start playback. |
isAutoPlaylist | boolean | false | Expand the playlist on mount. |
colorfulLyric | boolean | false | Enable colorful lyric and cover-based background. |
audioVisualizer | boolean | false | Enable 2D Canvas real-time audio visualization. When enabled, the progress bar renders as a continuous waveform and the bottom lyric bar shows bottom-aligned animated audio bars. With colorful lyrics enabled, each bar uses a stable distinct color. Cross-origin audio falls back safely. |
lazyLoadTimer | number | 0 | Delay before initializing, in ms. |
lazyLoadAnimationUrl | string | Theme-based | Loading image for cover and playlist images. |
mode | cloud | local | cloud | Data source mode. Cloud mode uses apiUrl; local mode uses playlist / audioProvider. |
apiUrl | string | VITE_API_URL | Cloud playlist API URL. It can also be set with api-url and supports the {environment} placeholder. |
environment | development | production | test | production | Runtime environment. |
rememberPlayback | boolean | true | Enable playback memory. |
memoryKey | string | xf-music-player-memory | localStorage key. |
autoplay | boolean | false | Try to play after mount only. It does not open the player UI. |
playMode | order | single | random | order | Playback mode. |
volume | number | 0.8 | Volume from 0 to 1. |
playlist | SongInfo[] | [] | Local initial playlist. The bundle does not include mock data. |
audioProvider | MusicAudioProvider | - | Custom audio data provider. |
Player Slots
Named slots accept direct children of the player host. Those nodes remain in the page Light DOM, so ordinary page CSS can style them without reaching into the internal Shadow DOM.
| Slot | Location and behavior |
|---|---|
player-default | Adds content inside the theme container, before the floating player box, without replacing built-in controls. Application CSS determines its position and appearance. |
player-content | Replaces the floating box’s built-in expand button, playback controls and playlist. Supply your own controls when using this slot. |
player-lyrics | Replaces the built-in bottom lyric and fullscreen lyric interaction area. The application owns visibility and interaction of its custom nodes. |
| Default slot | Direct children without a slot attribute appear in the host render stage, before the core player; they are not part of the built-in control area. |
Direct children: all four slots
Start with this structure to see where each slot appears. mode="local" keeps this layout example from requesting a cloud playlist; the four text blocks are placeholders.
<xf-music-player
class="slot-map"
theme="xf-original-theme"
is-auto-popup="true"
language="en"
mode="local"
player-width="350px"
bottom="3em"
font-name="Google Sans Code"
is-monitoring="false"
colorful-lyric="false"
>
<div class="slot-note">Default slot</div>
<div slot="player-default" class="slot-banner">Player default slot</div>
<div slot="player-content" class="slot-content">Player content slot</div>
<div slot="player-lyrics" class="slot-lyrics">Player lyrics slot</div>
</xf-music-player>
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>All four nodes are direct children of <xf-music-player>, with the tag before the script. player-content replaces the built-in controls and playlist; player-lyrics replaces the built-in lyrics. Placeholder text does not automatically become playback controls or synced lyrics. To only add content, keep the default slot and player-default, and remove the other two nodes to restore the built-in UI.
The player host is a zero-size floating layer, not part of document flow, so custom nodes need an explicit layout. This page CSS styles only the application nodes, leaving built-in themes untouched. The two added information blocks sit at the top; the custom lyric strip sits at the bottom with space for the player. font-name only names a font; load the font file separately if you want to use it.
.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;
}Working example: add buttons and retain the built-in player
Use the following HTML, CSS and JavaScript together, in order, on one standalone page. This example only uses player-default to add two buttons while retaining the built-in player and lyrics. Choose this JavaScript owner instead of automatic tag mounting; do not create a second player on the same page.
<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">Play / Pause</button>
<button type="button" data-player-action="next">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: 'en',
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 inserts the slot nodes before the first render so the named slots are detected. You can update the text, styles and content of existing nodes at runtime; prepare additional named slot regions during creation. Avoid replacing existing application nodes with host.innerHTML = ....
To use player-content or player-lyrics, add matching nodes to the template as direct children of the host, with their own CSS and event handlers. These slots replace rather than overlay the built-in interface. Custom buttons should call public methods such as player.play(), player.pause() and player.next(), without reading or modifying internal Shadow DOM.
Call and await disposeSlotPlayer() before leaving the page or creating a replacement. The beforeDestroy hook uses AbortController to remove the application listeners in this example. The player does not clean up listeners that your application attaches to other page elements.
Cloud Mode
mode defaults to cloud. If apiUrl is not set explicitly, the player reads the build-time VITE_API_URL value:
VITE_API_URL=https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26You can also override it through HTML attributes or JavaScript options:
<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'
}
})With a valid apiUrl and no custom audioProvider, cloud mode preloads the playlist. A request still pending after 2 seconds shows a localized loading message; a 10-second timeout, failure, or empty playlist shows a localized failure message. On failure, the current host renders an operable empty player with the error retained, without immediately repeating the same failed preload. Replace the playlist via player.setPlaylist(...), or change apiUrl, environment, or audioProvider to load a new source. A stale, disconnected host never reappears when its request finishes. Built-in fetches use credentials: 'omit'; use a provider for application-specific request options.
The cloud playlist API should return this structure:
{
"code": 200,
"msg": "success",
"data": [
{
"id": "002qU5aY3Qu24y",
"title": "Qing Hua Ci",
"artist": "Jay Chou",
"album": "On the Run",
"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"
}
]
}Failure response:
{
"code": 500,
"msg": "Server error",
"data": null
}When code !== 200, the player uses msg as the error message. src may be empty; when users try to play that song, the player does not create an empty audio instance. It plays the localized fallback audio, then moves to the next song. After three consecutive failures, playback stops.
If apiUrl contains {environment}, the player replaces it with the current environment: The following path is an application-owned example, not an XF API Server endpoint.
new MusicPlayer({
attributes: {
mode: 'cloud',
apiUrl: '/api/{environment}/music/list',
environment: 'production'
}
})Lyric URLs can return plain LRC text or the same JSON response shape. The player only reads data.lyric:
{
"code": 200,
"msg": "success",
"data": {
"lyric": "[00:00.00] First lyric line"
}
}XF API Server Examples
The online debugger includes an XF API URL builder. It generates api-url from the API origin, platform, endpoint, and endpoint parameters, and can fetch the result for preview. Recommended endpoints for cloud mode:
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=8523075134When a song contains lyrics_url, the player requests it during playback or song switching and reads only 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 and playlist-songs return a player-ready song array in data. search only returns basic song metadata without src, duration, or lyrics_url, so the debugger does not generate player api-url values from it.
Local Mode
mode="local" does not request apiUrl. Use it for offline pages, static playlists, or when the application controls the data source:
new MusicPlayer({
attributes: {
mode: 'local',
playlist: [
{
id: 'local-1',
title: 'Local Song',
src: '/audio/demo.mp3'
}
]
}
})Local Audio Metadata
The player reads metadata from FLAC, WAV, AIFF / AIFF-C, MP3, M4A, Ogg, and other formats. Detection uses file content rather than the extension. Successful metadata parsing does not guarantee that the browser can decode the audio; the player does not transcode files.
readAudioMetadata(input)acceptsFile/Blob/ArrayBuffer/Uint8Array, returnsAudioMetadata, and creates no temporary URLs.readAudioFile(file)accepts aFileand returns{ song, metadata, dispose }. Passsongto a localplaylist; it includes an audio Blob URL, available cover art and lyrics, and aformathint.metadata.formatincludes availableduration,sampleRate,numberOfChannels,bitsPerSample, andbitrate;metadata.commonholds existing tags,picture, andlyrics, whilemetadata.nativeretains raw tags. Missing fields stay absent; a missing title falls back to the file name insong.- Empty or non-audio input, an invalid audio stream, or a parser failure throws an error with the original
cause. Incomplete but readable tags may appear inmetadata.quality.warnings. This is not full-file decoding or an integrity check.
Call the function below after selection through <input type="file" accept="audio/*,.flac,.wav,.aif,.aiff,.aifc">. Finish destroying any previous player first. Call and await the returned cleanup function on page exit or before selecting another file; do not run multiple previews concurrently.
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() is idempotent. Release the audio and cover Blob URLs only after the song is no longer in use. Temporary URLs should not be saved in a persistent playlist; the example disables playback memory. Normal cloud playback does not automatically download entire audio files to scan tags. IIFE users access window.XfMusicPlayer.readAudioFile / readAudioMetadata; SSR applications should import these functions dynamically from client events.
Audio Provider
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 has higher priority than the built-in mode/apiUrl flow. Use it when your application needs custom signing, authentication, or multiple API calls. It can return SongInfo[], { list: SongInfo[] }, or { code: 200, msg: 'success', data: SongInfo[] }. The player filters unsafe protocols and cancels outdated requests with AbortSignal.
This minimal provider uses the public XF API; keep application authentication or signing in your own service. Pass signal to fetch and check the HTTP status rather than disguising failures as empty arrays:
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()
}
}
})Only application requests that consume signal actually stop. Even if a provider ignores cancellation, the player discards stale results. Any additional application logging or notification should first check signal?.aborted; preserve the original rejection on cancellation instead of displaying a failure or retrying indefinitely. Real network, HTTP, and business code errors still follow the failure path. Changes to environment, apiUrl, or the provider reference reload the source. Replace a local playlist with player.setPlaylist(nextList) and clear it with player.setPlaylist([]).
Media Permissions and iOS Volume
autoplay: trueonly attempts playback; browser gesture policies or embedding permissions can still block it.isAutoPopuponly expands the UI and grants no playback permission. Prefer a user click on the player; application buttons should callplayer.play()inside their click handler. This method returns the controller, not an awaitable playback Promise.- iframe integrations must account for the parent's
allow="autoplay"and Permissions Policy settings; neither guarantees exemption from user-gesture requirements. The player does not record audio and needs no microphone permission. - Audio URLs must be accessible and comply with the page's HTTPS / CSP setup. Cross-origin API and lyric requests need server-side CORS permission. Cover color sampling and Web Audio analysis impose additional cross-origin read requirements; playable audio is not necessarily analyzable audio.
- On iOS / iPadOS, interacting with the volume icon or bar shows a centered Chinese or English message to use device volume buttons or Control Center. It does not open an ineffective slider or dispatch a volume change, and repeated interactions show only one message within 3 seconds.
volume/setVolume()does not guarantee a change to actual device output volume. Other platforms retain the slider.
Playback Interaction
- Outside iOS / iPadOS, the volume button opens or closes the volume panel; public APIs can handle mute behavior when needed.
- The volume slider supports drag control and shows 0% to 100%.
- The cover renders a theme loading image before the real cover during song switches.
- Dragging the progress bar pauses audio and previews the new position; releasing seeks and plays, even when playback was paused before the drag.
- Previous, next, playlist toggle, and play mode actions are guarded against fast repeated clicks.
- Clicking the current playlist item toggles play / pause. Clicking another song switches and plays it.
- Playlist covers use lazy loading and switch to real images after entering the viewport.
- Play, pause, switching songs, mute, and play mode updates can trigger centered messages; opening or closing the playlist does not.
- The bottom lyric follows the playback lifecycle. It hides on pause and shows on play unless the user manually hides it.
Keyboard Control
| Key | Behavior |
|---|---|
Tab | Toggle player collapsed / expanded state, namely isAutoPopup. |
Space | Play or pause the current song. |
ArrowLeft | Previous song. |
ArrowRight | Next song. |
Keyboard control skips inputs, selects, buttons, sliders, contenteditable, composition events, and Ctrl / Meta / Alt shortcuts.
Failure Fallback
When audio loading or playback fails, the player plays a built-in language-specific error audio instead of freezing. After the fallback audio ends, it switches to the next song. If three songs fail consecutively, playback stops and the user receives an error message.
A browser-policy rejection during initial autoplay falls back to paused playback without automatically skipping songs. This differs from a real resource failure during an explicit user action. The abnormal-auto-advance circuit breaker also stops recovery after 3 advances within 15 seconds; a new user playback or track-selection action begins a new recovery period. Do not add unbounded retry or automatic next() loops around the player.
Memory Playback
rememberPlayback is enabled by default. When enabled, the player saves one current-song memory record under memoryKey.
interface MusicMemoryState {
current: MusicMemoryRecord | null
}The current record can include song id, audio URL, progress, volume, muted state, playback mode, and lyric visibility preference.
Instance 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()Lifecycle
The player has an outer MusicPlayer controller lifecycle and an internal Web Component lifecycle. Consumers should use the public hooks and avoid touching internal elements directly.
destroy() returns Promise<void> and repeated calls on the same controller reuse the same Promise. Even the default timer: 0 teardown is asynchronous. Await it before replacing the player; the one-time beforeDestroy callback in destroy(options) is awaited, while registered lifecycle hooks and afterDestroy are synchronous notifications, not async barriers. The host is removed even if the one-time callback rejects; handle that rejection and do not continue a successful-handoff path blindly. Never call methods on the old controller after replacement: playback commands address shared state.
Execution Order
new MusicPlayer(options): normalize options, register the custom element, and create the host element.mount(): insert the host element into the page. Automatic mode runs this after construction.beforeRender(el): runs after the host element is connected.- Internal initialization: apply config, resolve theme, load playlist, and restore memory playback.
afterRender(el): runs after the first render.beforeUpdate(el, changed): runs before reactive property or state-driven DOM updates.afterUpdate(el, changed): runs after DOM updates.destroy()or DOM removal: runsbeforeDestroy(el)and clears timers, subscriptions, audio instances, listeners, and temporary resources.afterDestroy(): runs after cleanup completes.
Hook Registration
player
.beforeRender(el => {})
.afterRender(el => {})
.beforeUpdate((el, changed) => {})
.afterUpdate((el, changed) => {})
.beforeDestroy(el => {})
.afterDestroy(() => {})Destroy example:
await player.destroy({
timer: 300,
beforeDestroy: async el => {
// One-time pre-destroy callback.
},
afterDestroy: () => {
// One-time post-destroy callback.
}
})Custom Themes
Override player theme variables with a custom class:
.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' })For inline theme CSS, provide both the selector name and the CSS text. Use player variables rather than relying on page selectors penetrating 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;
}`
})Audio Visualization
audioVisualizer is off by default. Toggle it on the existing instance without recreating the player:
player.setConfig({ audioVisualizer: true, colorfulLyric: true })
// Keep ordinary playback when the waveform is disabled.
player.setConfig({ audioVisualizer: false })Canvas renders the continuous progress waveform and bottom-lyric audio bars; colorfulLyric gives the bars stable distinct colors. Cross-origin audio needs permission for Web Audio analysis; otherwise ordinary playback is retained with a reduced visualization. Cover color sampling also needs readable cross-origin pixels or it falls back to the theme background. Disabling visualization keeps ordinary playback on the current player without creating a replacement.
Browser Compatibility
IIFE / ESM bundles target ES2020, translating logical assignments and class fields. It does not inject global polyfills: Web Components, BigInt, and required media capabilities must still exist. Metadata parsing support is not a promise of codec playback support on every browser; test the actual browser, WebView, and audio format used by your application.
Use modern Chrome, Edge Chromium, Firefox, Safari, and current mainstream mobile browsers. The player requires customElements, HTMLElement, Shadow DOM, Promise, Symbol, Object.assign, JavaScript, and HTML5 Audio.
The following environments are unsupported or unsuitable for production:
- All Internet Explorer versions: explicitly unsupported; use the compatibility detector to redirect users to an upgrade page.
- Old Edge Legacy: EdgeHTML-based versions are not recommended.
- Old Android WebView / UC Browser / QQ Browser: the player cannot run when Web Components or essential ES features are missing.
- Safari on iOS 10 and earlier: Web Components, Shadow DOM, and audio policies are unreliable.
- Environments without JavaScript, Web Components, or HTML5 Audio: the player cannot initialize or play music.
Browser Plugins
Place the compatibility detector before the player bundle. It redirects IE, Edge Legacy, and browsers missing required capabilities to an upgrade page:
<script src="/player/plugin/ie-out/index.js"></script>
<script src="/player/music-player.min.js"></script>The sakura effect is independent. When used with the player, place it after the main bundle:
<script src="/player/music-player.min.js"></script>
<script src="/player/plugin/sakura/sakura.min.js"></script>CDN URLs:
<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>Legacy Migration
New projects should use music-player.min.js or the npm MusicPlayer instance API. Existing projects can temporarily use the compatibility bundle:
<script src="/player/old-music-player.min.js"></script>- Use
<xf-music-player>ornew MusicPlayer(...)on new pages. - Keep
old-music-player.min.jstemporarily while migrating options, playlists, and lifecycle logic. - Use
xf-MusicPlayer-master/when the complete legacy source and demos are required.
Production Notes
- Lock CDN URLs to an explicit version.
- Use JavaScript / Vue / React initialization for complex objects and functions.
- Disable
isMonitoringin production. - Keep one active player per page. A custom
tagNamemust contain a hyphen; changing the name does not bypass the singleton contract. await player.destroy()before replacement. Framework cleanup callbacks are not an await barrier; serialize handoffs in shared application code.- Use stable HTTPS URLs for audio, cover, and lyric resources.
FAQ
Should I use the player tag or JavaScript initialization?
Use the player tag for static pages and simple attributes. Use JavaScript initialization for dynamic playlists, remote APIs, lifecycle hooks, and instance methods.
Can npm and CDN be provided together?
Yes. npm works for bundled applications, while CDN works for static pages and quick integration. Both should point to the same published version.
Does private source code affect usage?
No. Consumer projects only need the npm package or CDN bundle, plus the public options, data schema, lifecycle hooks, and instance API.