Skip to content

Access Document

Xiao Feng Music Player is a stable, convenient, high-performance HTML5 music player plugin for production websites and web applications. Source code can stay private; consumer projects only need the published npm package or CDN files.

Tech StackLit + Howler + TypeScript
Distributionnpm package + CDN files
IntegrationPlayer tag or JavaScript instance

npm Install Command

bash
npm install xf-music-player

Published Files

txt
dist/
  music-player.min.js
  music-player.esm.js
  old-music-player.min.js
  plugin/
    ie-out/
      index.js
    sakura/
      sakura.min.js
  • music-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

bash
pnpm add xf-music-player

You can also use npm or yarn:

bash
npm install xf-music-player
yarn add xf-music-player

Quick Start

Player Tag

Use the player tag for static pages, blogs, documentation sites, and landing pages with simple configuration.

html
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>

<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"
/>

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.

html
<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'
        }
      ]
    }
  })

  player.play()
</script>

ESM / npm

Use the ESM build in Vue, React, Svelte, native Vite, or other bundled applications.

ts
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: '/api/v1/music/list?platform=qq',
    environment: 'production',
    rememberPlayback: true,
    memoryKey: 'xf-player-memory',
    playMode: 'order',
    volume: 0.8,
    isAutoPopup: true
  }
})

React example:

tsx
import { useEffect, useRef } from 'react'
import { MusicPlayer } from 'xf-music-player'

export default function MusicPlayerWidget() {
  const rootRef = useRef<HTMLDivElement | null>(null)

  useEffect(() => {
    const player = new MusicPlayer({
      tagName: 'xf-react-player',
      mountElement: rootRef.current || document.body,
      language: 'en',
      isMonitoring: false,
      attributes: {
        theme: 'xf-original-theme',
        mode: 'local',
        rememberPlayback: true,
        memoryKey: 'xf-react-player-memory',
        playMode: 'order',
        volume: 0.8,
        playlist: []
      }
    })

    return () => {
      player.destroy()
    }
  }, [])

  return <div ref={rootRef} className="music-player-root" />
}

CDN URLs

Use the Xiao Feng Music Player static CDN in mainland China. jsDelivr and unpkg remain available as npm CDN alternatives:

html
<!-- Recommended in mainland China -->
<script src="https://player.xfyun.club/js/music-player/music-player.min.js"></script>
html
<!-- jsDelivr IIFE -->
<script src="https://cdn.jsdelivr.net/npm/xf-music-player@latest/music-player.min.js"></script>
html
<!-- unpkg IIFE -->
<script src="https://unpkg.com/xf-music-player@latest/music-player.min.js"></script>
ts
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 version such as @1.0.3.

Create Options

ts
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.

html
<xf-music-player language="en"/>
ts
const player = new MusicPlayer({
  tagName: 'xf-music-player',
  language: 'en',
  attributes: {
    theme: 'xf-original-theme',
    playlist: []
  }
})

If a production page needs to switch languages at runtime, destroy the current instance and recreate it with the next language so fallback audio, button text, lyrics placeholders, and messages stay aligned.

Song Data

ts
interface SongInfo {
  id: string | number
  title: string
  artist?: string
  album?: string
  cover?: 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 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.

Options

Use camelCase inside attributes for JavaScript / Vue / React. Use kebab-case on the HTML tag, for example rememberPlayback becomes remember-playback.

FieldTypeDefaultDescription
themestringxf-original-themeBuilt-in theme name; also supports random-theme and auto-theme.
customThemeNamestring-Reads CSS variables from a custom class or id.
customThemeStylestring-Injects custom theme CSS.
playerWidthstring324pxPlayer width.
fontNamestring-Font family name.
bottomstring2emBottom offset.
songListHeightstring350pxExpanded playlist height.
visibleSongListCountnumber4Visible playlist row count.
isAutoPopupbooleanfalseAuto popup / expand the player UI only. It does not start playback.
isAutoPlaylistbooleanfalseExpand the playlist on mount.
colorfulLyricbooleanfalseEnable colorful lyric and cover-based background.
audioVisualizerbooleanfalseEnable 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.
lazyLoadTimernumber0Delay before initializing, in ms.
lazyLoadAnimationUrlstringTheme-basedLoading image for cover and playlist images.
modecloud | localcloudData source mode. Cloud mode uses apiUrl; local mode uses playlist / audioProvider.
apiUrlstringVITE_API_URLCloud playlist API URL. It can also be set with api-url and supports the {environment} placeholder.
environmentdevelopment | production | testproductionRuntime environment.
rememberPlaybackbooleantrueEnable playback memory.
memoryKeystringxf-music-player-memorylocalStorage key.
autoplaybooleanfalseTry to play after mount only. It does not open the player UI.
playModeorder | single | randomorderPlayback mode.
volumenumber0.8Volume from 0 to 1.
playlistSongInfo[][]Local initial playlist. The bundle does not include mock data.
audioProviderMusicAudioProvider-Custom audio data provider.

Player Slots

The player is a Web Component, so named slots and the default slot can be placed inside <xf-music-player>. Slot content stays in the page Light DOM, which means consumer styles can target those nodes directly.

html
<xf-music-player
  theme="xf-original-theme"
  is-auto-popup="true"
  language="en"
  player-width="350px"
  bottom="3em"
  font-name="Google Sans Code"
  is-monitoring="false"
  colorful-lyric="false"
>
  <div slot="player-default">Default player slot</div>
  <div slot="player-content">Player content slot</div>
  <div slot="player-lyrics">Lyrics control slot</div>
  <div>Default slot</div>
</xf-music-player>
SlotDescription
player-defaultExtends the default player control area, suitable for lightweight buttons or hints.
player-contentExtends the main player content area for custom business content.
player-lyricsExtends the lyric control area for lyric-related controls or state.
Default slotNodes without a slot attribute are rendered as host default content.

Cloud Mode

mode defaults to cloud. If apiUrl is not set explicitly, the player reads the build-time VITE_API_URL value:

The website project uses API_URL from server/.env for player API examples and the debugger's default endpoint. SITE_URL is reserved for the website origin, static assets, SEO, RSS, and sitemap output, so both values can use different domains.

txt
VITE_API_URL=https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26

You can also override it through HTML attributes or JavaScript options:

html
<xf-music-player
  mode="cloud"
  api-url="https://music.api.xfyun.club/api/v1/music/top?platform=qq&topId=26"
/>
ts
new MusicPlayer({
  attributes: {
    mode: 'cloud',
    apiUrl: '/api/v1/music/list?platform=qq'
  }
})

When a valid apiUrl is configured, the player requests the cloud playlist first and renders the player body and lyrics only after the playlist is ready. If the API is still pending after 2 seconds, the player shows the localized message Cloud songs are loading. Please wait. If the request times out after 10 seconds, fails, or returns an empty playlist, it shows Failed to load player cloud data.

The cloud playlist API should return this structure:

json
{
  "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:

json
{
  "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:

ts
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:

json
{
  "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:

The production API origin below is read from API_URL in server/.env and is configured independently from SITE_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

When 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=002qU5aY3Qu24y

top 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:

ts
new MusicPlayer({
  attributes: {
    mode: 'local',
    playlist: [
      {
        id: 'local-1',
        title: 'Local Song',
        src: '/audio/demo.mp3'
      }
    ]
  }
})

Audio Provider

ts
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.

Playback Interaction

  • 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.
  • The progress bar supports drag seek. Dragging pauses the currently playing audio and resumes after release.
  • 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, playlist toggle, mute, and play mode updates can trigger centered messages.
  • The bottom lyric follows the playback lifecycle. It hides on pause and shows on play unless the user manually hides it.

Keyboard Control

KeyBehavior
TabToggle player collapsed / expanded state, namely isAutoPopup.
SpacePlay or pause the current song.
ArrowLeftPrevious song.
ArrowRightNext 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.

Memory Playback

rememberPlayback is enabled by default. When enabled, the player saves one current-song memory record under memoryKey.

ts
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

ts
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'
})
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.

Execution Order

  1. new MusicPlayer(options): normalize options, register the custom element, and create the host element.
  2. mount(): insert the host element into the page. Automatic mode runs this after construction.
  3. beforeRender(el): runs after the host element is connected.
  4. Internal initialization: apply config, resolve theme, load playlist, and restore memory playback.
  5. afterRender(el): runs after the first render.
  6. beforeUpdate(el, changed): runs before reactive property or state-driven DOM updates.
  7. afterUpdate(el, changed): runs after DOM updates.
  8. destroy() or DOM removal: runs beforeDestroy(el) and clears timers, subscriptions, audio instances, listeners, and temporary resources.
  9. afterDestroy(): runs after cleanup completes.

Hook Registration

ts
player
  .beforeRender(el => {})
  .afterRender(el => {})
  .beforeUpdate((el, changed) => {})
  .afterUpdate((el, changed) => {})
  .beforeDestroy(el => {})
  .afterDestroy(() => {})

Destroy example:

ts
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:

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;
}
ts
player.setConfig({ customThemeName: 'my-player-theme' })

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.

Browser Compatibility

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:

html
<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:

html
<script src="/player/music-player.min.js"></script>
<script src="/player/plugin/sakura/sakura.min.js"></script>

CDN URLs:

html
<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:

html
<script src="/player/old-music-player.min.js"></script>
  • Use <xf-music-player> or new MusicPlayer(...) on new pages.
  • Keep old-music-player.min.js temporarily 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 isMonitoring in production.
  • Use unique custom tagName values when multiple players exist on one page.
  • Call destroy() before reinitializing the player.
  • 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.