跳到正文

公共 API 与回调事件

AudioPlayeruseAudioPlayer() 都是 @trsoliu/audio-core 的 React 适配层。回调不是 原生 <audio> DOM 事件的逐条透传,而是根据不可变 AudioSnapshot 的变化调用。业务代码 应以 callback payload 或 Hook 的 snapshot 为播放真相。

导出边界

react-audio-native 导入:

导出类型用途
AudioPlayervalueforwardRef React 播放器组件
useAudioPlayer()functioncallback ref 驱动的 Headless Hook
detectAudioCapabilities()function无 UA 的运行时能力检测
formatMediaTime()function把秒数格式化为 mm:sshh:mm:ss
AudioPlayerPropsAudioPlayerSizeUseAudioPlayerResulttypeReact 适配层类型
core 共享类型typetrack、snapshot、error、Bridge、handle 等类型

完整 type-only 导出为:

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

createAudioController()normalizeInput()normalizeSources()AudioControllerError 只从 @trsoliu/audio-core 导出,React 包不会重新导出这些底层 value:

ts
import { createAudioController } from '@trsoliu/audio-core'
import { AudioPlayer, useAudioPlayer } from 'react-audio-native'

输入模型

AudioSourceAudioSourceInput

ts
interface AudioSource {
  src: string
  type?: string
}

type AudioSourceInput = string | AudioSource

src 可以是一个 URL、一个带 MIME type 的 source,或它们的有序数组。空 URL 会被 忽略,同一 URL 只保留第一次出现的位置。提供 type 时,内核优先选择 HTMLMediaElement.canPlayType() 支持的 source;媒体失败后继续尝试后续 source。

AudioTrack

字段类型必填说明
idstring调用方维护的稳定曲目标识
sourcesAudioSource | readonly AudioSource[]一个或多个有序回退源
titlestring标题与 Media Session metadata
artiststring艺术家与 Media Session metadata
albumstring专辑与 Media Session metadata
artworkMediaImage[]默认封面与 Media Session artwork
downloadNamestring覆盖组件级下载文件名
peaksreadonly number[]调用方提供的波形峰值;播放器不会下载音频计算峰值

非空 tracks 优先于 src。无可用 source 的 track 会被过滤;单独的 src 会被规范化 为 id: 'audio-source' 的单曲播放列表。

<AudioPlayer> props

AudioPlayerProps 继承全部 AudioControllerOptions,再增加展示与 callback props。

输入与播放

prop类型默认值行为
tracksreadonly AudioTrack[]播放列表;非空时覆盖 src
srcAudioSourceInput | readonly AudioSourceInput[]单曲或多格式回退输入
autoplaybooleanfalse挂载或换源后尝试播放;浏览器阻止时报告可恢复的 AUTOPLAY_BLOCKED
preload'none' | 'metadata' | 'auto''metadata'写入原生 audio 的 preload 策略
volumenumber1调用方必须传有限值;有限值限制到 01
mutedbooleanfalse静音状态
playbackRatenumber1调用方必须传有限值;有限值限制到 0.254
repeatMode'off' | 'one' | 'all''off'不循环、单曲循环或列表循环
waitBufferbooleantruetrue 允许跳到已知 duration 内任意位置;false 把 seek 限制到当前最大缓冲终点

协作与宿主集成

prop类型默认值行为
exclusivebooleanfalse仅当两个实例都开启且 group 相同时,新播放实例才暂停另一个实例
groupstring'default'互斥分组;空白字符串按 'default' 处理
mediaSessionbooleanfalse显式开启锁屏 metadata、播放状态与媒体按键处理
bridgeAudioPlayerBridge | nullnull协议无关的宿主事件出口;传 null 或移除 prop 会解绑旧宿主

展示与组合

prop类型默认值行为
nativeControlsbooleanfalse强制使用浏览器原生 controls;能力不足时也自动降级为原生 controls
showCurrentTimebooleantrue显示当前时间与 duration
showVolumebooleantrue显示静音按钮和音量滑杆
showDownloadbooleantrue有 source 时显示下载链接;浏览器决定是否支持 download 和跨域下载
downloadNamestring''默认下载文件名;track 的同名字段优先
size'small' | 'default' | 'large''default'写入根节点 data-size,切换包内尺寸样式
classNamestring追加到根节点的 audio-native class 后
hintReactNode'No playable audio source.'没有可播放 track 时显示
artworkReactNode | ((track) => ReactNode)默认图片固定 artwork 节点,或根据当前 track 渲染
beforeControlsReactNode自定义 controls 内、标准控件之前渲染
afterControlsReactNode自定义 controls 内、标准控件之后渲染

beforeControlsafterControls 是普通 ReactNode,不是 render props;需要命令或状态 时使用 forwarded ref 或 Headless Hook。三个组合点在原生 controls 模式下只有 artwork 仍会渲染,before/after controls 不会渲染。

回调事件

callbackpayload触发条件与注意事项
onStateChange(snapshot: AudioSnapshot) => void每次 PlaybackState 真正变化时调用,包括 loadingbufferingerror
onReady(snapshot: AudioSnapshot) => void状态进入 ready 时调用;通常来自暂停时的 canplay,加载阶段的 play() 被拒绝后也会进入这一可重试状态;不是 loadedmetadata 的别名
onPlay(snapshot: AudioSnapshot) => void状态进入 playing 时调用;从 buffering 恢复也会再次调用
onPause(snapshot: AudioSnapshot) => void状态进入 paused 时调用,包括命令暂停和同组互斥暂停
onEnded(snapshot: AudioSnapshot) => void状态进入 ended 时调用;单曲循环、列表自动前进或列表循环时不会先调用终止 callback
onTimeUpdate(currentTime: number, snapshot: AudioSnapshot) => voidsnapshot 的 currentTime 变化时调用;播放中也会由进度帧同步,频率可能高于原生 timeupdate
onTrackChange(track: AudioTrack | null, trackIndex: number) => void当前 track 的索引或对象发生变化时调用;初始选择请直接读取 snapshot,不要依赖一次“挂载 callback”
onError(error: AudioPlayerError) => void出现新的公开错误时调用;多 source 回退只在没有后续可尝试 source 后报告媒体错误
tsx
<AudioPlayer
  tracks={tracks}
  onStateChange={(snapshot) => setPlaybackState(snapshot.state)}
  onTimeUpdate={(seconds, snapshot) =>
    saveProgress(snapshot.track?.id, seconds)
  }
  onTrackChange={(track, index) => console.log(index, track?.id)}
  onError={(error) => console.error(error.code, error.message)}
/>

callbacks 在 React effect 中根据前后 snapshot 调用,不要依赖它们与浏览器 DOM event 的 同一调用栈。AUTOPLAY_BLOCKED 会调用 onError,但 snapshot 通常保持 readypaused,用户仍可通过手势再次调用 play()

AudioSnapshot

每次通知提供一个新的、顶层冻结的 snapshot:

字段类型说明
statePlaybackState当前状态,见下表
trackAudioTrack | null当前规范化 track
trackIndexnumber当前索引;没有 track 时为 -1
currentTimenumber非负播放位置,单位秒
durationnumber | null有效非负时长;未知、Infinity 或无效时为 null
bufferedreadonly BufferedRange[]{ start, end } 秒区间列表
volumenumber01
mutedboolean是否静音
playbackRatenumber当前倍速
repeatModeRepeatMode当前循环模式
errorAudioPlayerError | null最近公开错误;成功播放或重新加载后清空
state含义
idle尚未附着媒体元素,或当前没有可用输入
loading已选择 source,等待媒体就绪
ready暂停且可重试;通常已收到 canplay,也可能刚从加载阶段的播放拒绝恢复
playing正在播放
paused已暂停
buffering播放期间收到 waitingstalled
ended播放列表到达终点且没有继续循环
error所有可用 source 均失败,或发生其他致命媒体错误

结构化错误

ts
interface AudioPlayerError {
  code: AudioPlayerErrorCode
  message: string
  mediaErrorCode?: number
  cause?: unknown
}
code常见来源建议处理
AUTOPLAY_BLOCKEDplay() 被浏览器的用户手势策略拒绝显示播放按钮,让用户手势重试
SOURCE_NOT_SUPPORTED没有可用 source、原生错误码 4,或 audio ref 尚未附着就调用异步命令更换 source、检查 MIME/type,或先挂载 audio
MEDIA_ABORTED原生媒体错误码 1允许重新加载或换源
NETWORK原生媒体错误码 2提供重试并检查 URL/CORS/网络
DECODE原生媒体错误码 3换用兼容编码或后备 source
UNKNOWN其他 play() 或媒体失败记录 cause,向用户提供重试或后备路径

Imperative ref

tsx
import { useRef } from 'react'
import { AudioPlayer, type AudioPlayerHandle } from 'react-audio-native'

export function Player() {
  const playerRef = useRef<AudioPlayerHandle>(null)

  return (
    <>
      <AudioPlayer ref={playerRef} src="/episode.mp3" />
      <button type="button" onClick={() => void playerRef.current?.play()}>
        Play
      </button>
    </>
  )
}
方法返回值语义
play()Promise<void>请求播放;失败时以结构化 AudioControllerError 拒绝
pause()void暂停当前元素
toggle()Promise<void>根据原生元素的 paused 状态播放或暂停
stop()void暂停并跳回 0
seekTo(seconds)void忽略非有限值,并把位置限制到合法时长;waitBuffer=false 时不能超过最大缓冲终点
skipBy(seconds)void从当前时间相对跳转
selectTrack(index)Promise<void>选择合法整数索引;越界时抛出 RangeError,原先在播或 autoplay 时继续播放
previous()Promise<void>当前时间大于 3 秒时先回到本曲开头,否则切上一曲;all 可从首曲回绕
next()Promise<void>切下一曲;all 可从末曲回绕,否则进入 ended
setVolume(volume)void要求有限数值,并限制到 01
setMuted(muted)void更新静音状态
setPlaybackRate(rate)void要求有限数值,并限制到 0.254
setRepeatMode(mode)void更新 'off' | 'one' | 'all'
getElement()HTMLAudioElement | null获取当前原生元素;SSR 或未挂载时为 null

useAudioPlayer()

ts
interface UseAudioPlayerResult {
  audioRef: RefCallback<HTMLAudioElement>
  snapshot: AudioSnapshot
  controls: AudioPlayerHandle
}

Hook 只在 callback ref 收到真实 HTMLAudioElement 后创建 controller:

tsx
'use client'

import { useAudioPlayer } from 'react-audio-native'

export function HeadlessPlayer() {
  const { audioRef, controls, snapshot } = useAudioPlayer({
    src: '/episode.mp3',
    exclusive: true,
    group: 'podcast',
  })

  return (
    <>
      <audio ref={audioRef} />
      <button type="button" onClick={() => void controls.toggle()}>
        {snapshot.state === 'playing' ? 'Pause' : 'Play'}
      </button>
    </>
  )
}
  • audioRef 必须交给一个真实 <audio>;传入 null 时会销毁旧 controller。
  • snapshot 是当前不可变值;React 重新渲染由 core subscription 驱动。
  • controls 的对象身份稳定。ref 尚未附着时,playtogglepreviousnextselectTrack 返回以 SOURCE_NOT_SUPPORTED 拒绝的 Promise;同步 setter/pause 为安全 no-op,getElement() 返回 null
  • options 变化会更新当前 controller;输入变化会重置到首个有效 track。
  • callback ref 的 attach/detach 负责 StrictMode 重挂载和卸载清理,不残留媒体监听器、 进度帧、互斥注册或 Media Session 所有权。

WebView Bridge Events

组件和 Hook 都接受可选的 bridge

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

<AudioPlayer src="/episode.mp3" bridge={bridge} />
ts
type AudioBridgeEvent =
  | { type: 'statechange'; snapshot: AudioSnapshot }
  | {
      type: 'trackchange'
      snapshot: AudioSnapshot
      track: AudioTrack | null
      trackIndex: number
    }
  | { type: 'error'; snapshot: AudioSnapshot; error: AudioPlayerError }
  • statechange 只在 snapshot.state 改变时发送。
  • trackchange 在当前 track/index 改变时发送;附着已有选中 track 的 controller 时也会 向当前 bridge 发送一次。
  • error 只在出现新的公开错误对象时发送。
  • bridge.emit() 抛出的宿主异常会被隔离,不会中断播放。
  • 删除 bridge prop、改为 undefined/null,或在 Hook options 中传 null,都会在同一 次换源前解绑旧宿主。

Bridge 不规定 WKWebView、Android JavascriptInterface 或 ArkWeb 的通道、序列化、鉴权 协议;这些属于宿主应用边界。

能力检测与降级

detectAudioCapabilities(element?) 返回:

ts
interface AudioRuntimeCapabilities {
  customControls: boolean
  download: boolean
  mediaSession: boolean
  nativeHls: boolean
  pointerEvents: boolean
  touch: boolean
}

SSR 中所有字段为 false。组件只依据能力决定自定义/原生 controls,不解析 UA。Media Session 是 opt-in;原生 HLS 可透传,但不捆绑 HLS 引擎。

具体声明以 npm 包 .d.ts 为最终机器契约。发布门禁还会验证 React 18/19、ESM、CJS、 Next SSR 导入和 npm tarball 内容。