npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

vue-audio-native

v1.0.1

Published

Accessible Vue 3 audio player for modern browsers and WebViews.

Readme

vue-audio-native

An accessible, typed Vue 3 audio player for desktop/mobile browsers and embedded WebViews. It uses the event-driven @trsoliu/audio-core contract for playlists, ordered source fallback, repeat modes, buffered seeking, structured errors, Media Session, optional exclusive groups, and a protocol-neutral host Bridge.

Version 1.x targets Vue >=3.3 <4. Vue 2 applications must stay on the legacy line.

Install

# Vue 3
pnpm add [email protected]

# Vue 2 compatibility line
pnpm add vue-audio-native@legacy

Use vue-audio-native@next only when intentionally validating a prerelease.

Import the standalone package CSS; consumers do not need Tailwind:

import { createApp } from 'vue'
import VueAudioNativePlugin from 'vue-audio-native'
import 'vue-audio-native/style.css'

createApp(App).use(VueAudioNativePlugin).mount('#app')

The plugin registers both <VueAudioNative> and <AudioPlayer>. Named component imports are also supported.

Quick start

<script setup lang="ts">
import { VueAudioNative, type AudioTrack } from 'vue-audio-native'
import 'vue-audio-native/style.css'

const tracks: readonly AudioTrack[] = [
  {
    id: 'episode-1',
    title: 'Episode 1',
    artist: 'Audio Native',
    artwork: [{ src: '/cover.webp', sizes: '512x512', type: 'image/webp' }],
    sources: [
      { src: '/episode-1.ogg', type: 'audio/ogg' },
      { src: '/episode-1.mp3', type: 'audio/mpeg' },
    ],
  },
]

function onError(error: { code: string; message: string }) {
  console.error(error.code, error.message)
}
</script>

<template>
  <VueAudioNative
    :tracks="tracks"
    repeat-mode="all"
    media-session
    @statechange="(snapshot) => console.log(snapshot.state)"
    @error="onError"
  />
</template>

Input priority is fixed: non-empty tracks, then src, then legacy url. The component warns in development when more than one input is provided.

Exports

Runtime exports from vue-audio-native:

  • VueAudioNative and its AudioPlayer alias
  • the default Vue plugin for app.use()
  • useAudioPlayer()
  • detectAudioCapabilities() and formatMediaTime()

Complete type-only exports:

import type {
  AudioPlayerSize,
  UseAudioPlayerOptions,
  UseAudioPlayerResult,
  VueAudioNativeProps,
  AudioArtwork,
  AudioBridgeEvent,
  AudioControllerOptions,
  AudioInput,
  AudioPlayerBridge,
  AudioPlayerError,
  AudioPlayerErrorCode,
  AudioPlayerHandle,
  AudioRuntimeCapabilities,
  AudioSnapshot,
  AudioSource,
  AudioSourceInput,
  AudioTrack,
  BufferedRange,
  PlaybackState,
  PreloadMode,
  RepeatMode,
} from 'vue-audio-native'

For the low-level controller, import directly from @trsoliu/audio-core. The Vue package does not re-export the createAudioController, normalization helpers, or AudioControllerError runtime values.

Inputs

interface AudioSource {
  src: string
  type?: string
}

interface AudioTrack {
  id: string
  title?: string
  artist?: string
  album?: string
  artwork?: MediaImage[]
  sources: AudioSource | readonly AudioSource[]
  downloadName?: string
  peaks?: readonly number[]
}

src accepts a URL, one typed source, or an ordered array of either. Empty URLs are removed and duplicate URLs keep their first position. The core uses canPlayType() to prefer supported MIME types, then tries later sources after media failure. A fatal error is reported only after the final source fails.

Waveforms render only caller-provided peaks; the package never fetches or decodes audio to derive them.

Component props

Input and playback

| Prop | Type | Default | Behavior | | -------------- | ------------------------------------------------- | ------------ | --------------------------------------------------------------------------- | | tracks | readonly AudioTrack[] | — | Playlist; non-empty values override src and url | | src | AudioSourceInput \| readonly AudioSourceInput[] | — | Single item with ordered source fallback | | url | string | '' | Legacy single URL; used only when modern inputs are empty | | autoplay | boolean | false | Attempts playback after mount/input change; policy rejection is recoverable | | preload | 'none' \| 'metadata' \| 'auto' | 'metadata' | Native <audio> preload mode | | volume | number | 1 | Finite values clamp to 01; non-finite values are ignored | | muted | boolean | false | Native muted state | | playbackRate | number | 1 | Finite values clamp to 0.254; non-finite values are ignored | | repeatMode | 'off' \| 'one' \| 'all' | 'off' | End-of-track behavior | | waitBuffer | boolean | true | When false, seeking cannot move beyond the greatest buffered end time |

Integration and presentation

| Prop | Type | Default | Behavior | | ----------------- | --------------------------------- | ------------------- | ----------------------------------------------------------------------------------------- | | exclusive | boolean | false | Pauses peers only when both players are exclusive and share a group | | group | string | 'default' | Exclusive coordination group; blank values become default | | mediaSession | boolean | false | Opts into lock-screen metadata, playback state, and media actions | | nativeControls | boolean | see note | Explicitly forces/hides native controls; when omitted, legacy showControls is used | | showControls | boolean | false | Legacy native-controls option, used only when nativeControls is not explicitly supplied | | showCurrentTime | boolean | true | Shows current time and duration in custom controls | | showVolume | boolean | true | Shows mute and volume controls | | showDownload | boolean | true | Shows a link when a source exists; the browser decides download/cross-origin support | | downloadName | string | '' | Fallback filename; track.downloadName wins | | size | 'small' \| 'default' \| 'large' | 'default' | Selects package sizing through data-size | | hint | string | '暂无有效音频...' | Empty-state text when no tip slot is supplied |

Unsupported custom-control environments always fall back to native <audio controls>, regardless of the requested mode. The component does not expose a bridge prop; use the headless composable for Bridge integration.

Events

Component events describe AudioSnapshot transitions. They are not one-for-one proxies for native media events.

| Event | Payload | When it fires | | ------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | statechange | (snapshot: AudioSnapshot) | Every actual PlaybackState transition, including loading, buffering, and fatal error | | ready | (snapshot: AudioSnapshot) | State enters ready; normally after canplay while paused, or after a play request is rejected during loading; not a metadata alias | | play | (snapshot: AudioSnapshot) | State enters playing; buffering recovery can fire it again without a new user command | | pause | (snapshot: AudioSnapshot) | State enters paused, including programmatic and exclusive-group pauses | | ended | (snapshot: AudioSnapshot) | State enters ended; repeat/automatic playlist advance continues without this terminal transition | | timeupdate | (currentTime: number, snapshot: AudioSnapshot) | Snapshot time changes; active playback updates can be more frequent than native timeupdate | | trackchange | (track: AudioTrack \| null, trackIndex: number) | Selected track/index changes; read the initial track from the snapshot instead of relying on a mount callback | | error | (error: AudioPlayerError) | A new public error appears; native media errors that advance to another source are not emitted |

<VueAudioNative
  src="/episode.mp3"
  @ready="(snapshot) => console.log(snapshot.duration)"
  @timeupdate="(seconds, snapshot) => saveProgress(snapshot.track?.id, seconds)"
  @trackchange="(track, index) => console.log(index, track?.id)"
  @error="(error) => handlePlaybackError(error.code)"
/>

Legacy events

Version 1.x preserves the documented 0.x events:

| Event | Template listener | Payload | Mapping | | --------------- | ----------------- | ----------------------- | ---------------------------------------------------------------------------- | | on-change | @on-change | (playing: boolean) | true on entry to playing; false on entry to paused or ended | | on-timeupdate | @on-timeupdate | (currentTime: number) | Same time change as modern timeupdate, without the snapshot | | on-metadata | @on-metadata | (event: Event) | Raw loadedmetadata or durationchange; may fire more than once per source | | on-audioId | @on-audio-id | (id: string) | Once after mount, for example vue-audio-native-1 |

Use statechange for loading/buffering/error UI; on-change only represents the legacy playing boolean.

Snapshot contract

interface AudioSnapshot {
  state:
    | 'idle'
    | 'loading'
    | 'ready'
    | 'playing'
    | 'paused'
    | 'buffering'
    | 'ended'
    | 'error'
  track: AudioTrack | null
  trackIndex: number
  currentTime: number
  duration: number | null
  buffered: readonly { start: number; end: number }[]
  volume: number
  muted: boolean
  playbackRate: number
  repeatMode: 'off' | 'one' | 'all'
  error: AudioPlayerError | null
}

Snapshots are new, top-level frozen objects. duration is null while unknown/invalid and trackIndex is -1 when no track exists.

AUTOPLAY_BLOCKED can coexist with state: 'ready' or state: 'paused'; it is not fatal. Let the user retry from a gesture. Fatal source failures enter error after all ordered sources are exhausted.

Other error codes are SOURCE_NOT_SUPPORTED, MEDIA_ABORTED, NETWORK, DECODE, and UNKNOWN. Each error contains code, message, optional native mediaErrorCode, and optional cause.

Imperative handle

The component exposes AudioPlayerHandle through a template ref:

<script setup lang="ts">
import { ref } from 'vue'
import { VueAudioNative, type AudioPlayerHandle } from 'vue-audio-native'

const player = ref<AudioPlayerHandle | null>(null)
</script>

<template>
  <VueAudioNative ref="player" src="/episode.mp3" />
  <button type="button" @click="void player?.play()">Play</button>
</template>

| Method | Return | Behavior | | ------------------------------------- | -------------------------- | ---------------------------------------------------------------------------- | | play() | Promise<void> | Requests playback; rejects with a structured controller error | | pause() | void | Pauses playback | | toggle() | Promise<void> | Plays or pauses from the native paused state | | stop() | void | Pauses and seeks to zero | | seekTo(seconds) / skipBy(seconds) | void | Absolute/relative seek under duration and buffer policy | | selectTrack(index) | Promise<void> | Selects a valid integer index; out-of-range values throw RangeError | | previous() / next() | Promise<void> | Playlist navigation; previous restarts the current track when past 3 seconds | | setVolume(value) | void | Clamps to 01 | | setMuted(value) | void | Updates mute state | | setPlaybackRate(value) | void | Clamps to 0.254 | | setRepeatMode(mode) | void | Updates off, one, or all | | getElement() | HTMLAudioElement \| null | Returns the current native element |

Headless composable

useAudioPlayer() accepts an AudioControllerOptions object, ref, computed value, or getter. It returns a shallow audio-element ref, a shallow immutable snapshot ref, and stable controls.

<script setup lang="ts">
import { computed } from 'vue'
import { useAudioPlayer } from 'vue-audio-native'

const source = computed(() => '/episode.mp3')
const { audioRef, controls, snapshot } = useAudioPlayer(() => ({
  src: source.value,
  exclusive: true,
  group: 'podcast',
}))
</script>

<template>
  <audio ref="audioRef" />
  <button type="button" @click="void controls.toggle()">
    {{ snapshot.state === 'playing' ? 'Pause' : 'Play' }}
  </button>
</template>

The composable reacts to input/options changes and destroys its controller with the current Vue effect scope.

WebView Bridge

The headless options accept a protocol-neutral bridge:

import { useAudioPlayer, type AudioPlayerBridge } from 'vue-audio-native'

const bridge: AudioPlayerBridge = {
  emit(event) {
    hostTransport.postMessage(JSON.stringify(event))
  },
}

const player = useAudioPlayer({ src: '/episode.mp3', bridge })

Bridge event types are:

  • { type: 'statechange', snapshot }
  • { type: 'trackchange', snapshot, track, trackIndex }
  • { type: 'error', snapshot, error }

Removing the bridge or passing null detaches the old host before a simultaneous input update. Bridge exceptions are isolated from playback. Host code chooses its own WKWebView, Android JavascriptInterface, ArkWeb, or other transport protocol.

Slots

| Slot | Props | Behavior | | ----------------- | ------------------------ | ---------------------------------------- | | artwork | { track } | Replaces the default first artwork image | | before-controls | { controls, snapshot } | Rendered before custom controls content | | after-controls | { controls, snapshot } | Rendered after custom controls content | | tip | none | Preferred empty-state content | | slotTip | none | Legacy empty-state fallback |

The before/after control slots are not rendered while native controls are active.

Styling

Import vue-audio-native/style.css. It contains no Tailwind Preflight, global utilities, shadcn, Reka, OKLCH, or color-mix() output.

.audio-native {
  --audio-native-background: #0b0f14;
  --audio-native-foreground: #f5f7fa;
  --audio-native-muted: #9aa4b2;
  --audio-native-border: #303846;
  --audio-native-accent: #70f37c;
  --audio-native-accent-foreground: #061008;
}

SSR and browser boundary

Importing the package is SSR safe. Media and DOM objects are used only after Vue mounts a real audio element. Capability detection—not user-agent parsing—selects custom versus native controls. Media Session is opt-in, native HLS is passed through when available, and no HLS engine or audio analysis runtime is bundled.

Full Chinese API documentation is available at https://trsoliu.github.io/vue-audio-native/api/. The published .d.ts files remain the final machine-readable contract.