
react-use 之 useAudio创建audio元素、追踪播放状态并暴露播放控制器的 React Hook 实战指南【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use导读useAudio是 react-use 库中用于封装 HTML5 音频播放能力的核心 Hook它会替你创建一个audio元素实时追踪音频的播放状态时长、音量、缓冲、播放/暂停等并暴露一组简洁的播放控制方法play、pause、mute、seek 等。阅读完本篇你将掌握useAudio的两种调用形式、四元组返回值[audio, state, controls, ref]的完整语义、基于 HTMLMediaElement 事件驱动的状态同步原理以及它在 react-use 仓库中的源码级实现细节从而能够直接用它搭建自定义音频播放器界面。一、useAudio是什么useAudio是一个专门为audio元素设计的 React Hook其职责一句话概括为创建audio元素、追踪其状态并暴露播放控制能力原文Createsaudioelement, tracks its state and exposes playback controls见 docs/useAudio.md。它隶属于 react-use 的 UI 类 Hook。在 react-use 中useAudio与useVideo共享同一套底层实现——它们都来自工厂函数createHTMLMediaHook见 src/useAudio.ts 与 src/useVideo.ts// src/useAudio.ts import createHTMLMediaHook from ./factory/createHTMLMediaHook; const useAudio createHTMLMediaHookHTMLAudioElement(audio); export default useAudio;也就是说useAudio就是调用createHTMLMediaHookHTMLAudioElement(audio)得到的专用 Hook其全部能力均来自 src/factory/createHTMLMediaHook.ts 这个通用媒体 Hook 工厂。库的入口 src/index.ts 中将其以命名导出的形式暴露给使用者export { default as useAudio } from ./useAudio。二、安装与导入useAudio是 react-use 的一部分直接安装 react-use 即可使用npm install react-use # 或 yarn add react-use导入方式import { useAudio } from react-use;仓库的 Storybook 演示stories/useAudio.story.tsx中也使用了与文档完全一致的导入与用法可以作为可运行示例参考。三、基础用法文档示例官方文档给出了一段完整的用法示例我们原样继承并逐行拆解import { useAudio } from react-use; const Demo () { const [audio, state, controls, ref] useAudio({ src: https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3, autoPlay: true, }); return ( div {audio} pre{JSON.stringify(state, null, 2)}/pre button onClick{controls.pause}Pause/button button onClick{controls.play}Play/button br/ button onClick{controls.mute}Mute/button button onClick{controls.unmute}Un-mute/button br/ button onClick{() controls.volume(.1)}Volume: 10%/button button onClick{() controls.volume(.5)}Volume: 50%/button button onClick{() controls.volume(1)}Volume: 100%/button br/ button onClick{() controls.seek(state.time - 5)}-5 sec/button button onClick{() controls.seek(state.time 5)}5 sec/button /div ); };这段代码演示了useAudio的核心工作方式调用useAudio({ src, autoPlay })传入一个普通的 props 对象从返回值中解构出audio要渲染进组件树的audio元素、state实时状态可直接 JSON 序列化展示、controls一组控制函数以及ref底层 DOM 元素引用把{audio}插入到渲染树中——这是状态能同步、控制能生效的前提通过controls.*驱动播放行为暂停/播放、静音/取消静音、设置音量10% / 50% / 100%、基于state.time前后快进/快退 5 秒。四、API 参考返回值四元组详解useAudio支持两种等价调用形式见文档 Reference 部分// 形式一传入 props 对象Hook 内部为你创建 audio 元素 const [audio, state, controls, ref] useAudio(props); // 形式二传入一个现成的 audio React 元素可携带任意子节点与属性 const [audio, state, controls] useAudio(audio {...props}/);从源码src/factory/createHTMLMediaHook.ts可以看到工厂内部通过React.isValidElement(elOrProps)判断入参是 React 元素还是 props 对象若传入的是合法 React 元素则直接使用该元素并取出其props若传入的是普通对象则将其视为 props在内部通过React.createElement(tag, {...})创建audio元素。两种方式殊途同归最终返回相同的四元组。下面逐一展开这四个返回值的语义。4.1audio—— 必须渲染进组件树的audio元素audio是一个 React 元素你必须把它插入到渲染树中的某个位置否则 Hook 拿不到真实的 DOM 元素状态与控制的底层操作将无法工作。文档中的示例为div{audio}/div源码层面工厂会把这个元素以controls: false强制渲染同时合并用户传入的 props 与内部的事件代理也就是说浏览器自带的原生控制条会被关闭——这正是为了让开发者用controls方法自定义播放器 UI。4.2state—— 实时音频状态state追踪音频的当前状态其形状如下文档原文示例{ buffered: [ { start: 0, end: 425.952625 } ], time: 5.244996, duration: 425.952625, paused: false, muted: false, volume: 1, playing: true }各字段含义如下字段类型含义buffered{start, end}[]已缓冲的时间区间数组由TimeRanges解析而来见 src/misc/parseTimeRanges.tstimenumber当前播放位置秒durationnumber音频总时长秒pausedboolean是否处于暂停状态mutedboolean是否静音volumenumber音量范围 01playingboolean是否正在播放中其中playing需要特别注意文档明确指出playing表示音频正在播放且未受网络影响——如果音频开始缓冲buffer数据playing会变为false。也就是说playing与paused并不等价paused描述用户侧是否暂停而playing描述此刻是否真正出声可能因缓冲等待而短暂中断。在源码中state 的初始值定义于 src/factory/createHTMLMediaHook.ts并通过useSetState管理const [state, setState] useSetStateHTMLMediaState({ buffered: [], time: 0, duration: 0, paused: true, muted: false, volume: 1, playing: false, });4.3controls—— 播放控制方法集合controls是一组播放控制方法其 TypeScript 接口如下文档原文interface AudioControls { play: () Promisevoid | void; pause: () void; mute: () void; unmute: () void; volume: (volume: number) void; seek: (time: number) void; }各方法语义play()开始播放返回Promisevoid | voidpause()暂停播放mute()静音unmute()取消静音volume(volume: number)设置音量01seek(time: number)跳转到指定时间秒。4.4ref—— 底层 DOM 元素引用ref是对 HTMLaudio元素的 React 引用通过ref.current访问真实 DOM 元素。文档特别提醒ref.current可能为null——例如元素尚未挂载或你忘记渲染audio时因此访问前应做好空值判断。在测试tests/useAudio.test.ts中正是通过手动给ref.current赋一个document.createElement(audio)来验证mute/unmute/volume控制方法的正确性。4.5props—— 透传所有audio支持的属性最后一个入参props即audio元素接受的所有属性src、autoPlay、loop、crossOrigin、preload等。文档说明 all props thataudioaccepts在源码类型定义中体现为 src/factory/createHTMLMediaHook.ts 的HTMLMediaPropsexport interface HTMLMediaProps extends React.AudioHTMLAttributesany, React.VideoHTMLAttributesany { src: string; }其中src为必填项。注意虽然autoPlay被透传给了元素但真正触发自动播放的逻辑在 Hook 内部见下文第六节因此仅靠autoPlay属性本身在部分浏览器如启用了自动播放策略的 Chrome中未必生效这是实现层面需要理解的一个细节。五、状态如何被追踪事件驱动的同步机制useAudio的实时状态并不是轮询得来的而是监听HTMLMediaElement的原生事件驱动的。工厂在创建元素时会以用户事件 内部代理的方式挂载 8 个事件处理器src/factory/createHTMLMediaHook.tswrapEvent保证用户自己传入的同类事件回调也能先于或后于内部逻辑执行互不干扰事件内部处理器同步到 state 的字段onPlayonPlaypaused: falseonPlayingonPlayingplaying: trueonWaitingonWaitingplaying: false开始缓冲等待onPauseonPausepaused: true, playing: falseonVolumeChangeonVolumeChangemuted、volume读自真实元素onDurationChangeonDurationChangeduration、bufferedonTimeUpdateonTimeUpdatetime读自el.currentTimeonProgressonProgressbuffered读自el.buffered其中buffered字段并非直接透传浏览器的TimeRanges对象而是通过 src/misc/parseTimeRanges.ts 将其解析为易用的{start, end}[]数组export default function parseTimeRanges(ranges) { const result: { start: number; end: number }[] []; for (let i 0; i ranges.length; i) { result.push({ start: ranges.start(i), end: ranges.end(i) }); } return result; }这解释了文档示例中buffered呈现为[{ start: 0, end: 425.952625 }]的原因一个已缓冲区间起点 0 秒、终点与时长一致说明整个音频已缓冲完成。六、控制方法背后的实现细节controls各方法并非简单调用原生 API源码在 src/factory/createHTMLMediaHook.ts 中做了若干健壮性处理值得深入了解6.1play()的 Promise 锁机制部分浏览器如 Chrome的HTMLMediaElement.play()会返回 Promise若在 Promise 尚未 resolve 时再次调用play()或pause()可能抛出异常源码注释引用了 Chromium issue #593273。为此工厂维护了一个lockPlay布尔锁let lockPlay: boolean false; play: () { const el ref.current; if (!el) return undefined; if (!lockPlay) { const promise el.play(); const isPromise typeof promise object; if (isPromise) { lockPlay true; const resetLock () { lockPlay false; }; promise.then(resetLock, resetLock); } return promise; } return undefined; }即play()返回 Promise 时上锁待 Promise 无论成功还是失败都解锁锁未释放期间后续play()/pause()调用会被安全地忽略避免竞态异常。6.2seek()的时间钳制seek(time)会把目标时间钳制在[0, duration]区间内防止越界time Math.min(state.duration, Math.max(0, time)); el.currentTime time;这保证了controls.seek(state.time 5)在接近结尾时不会产生非法跳转。6.3volume()的音量钳制与状态回写volume(volume)同样将入参钳制在[0, 1]在写入el.volume后还会主动setState({ volume })让 state 立即反映新的音量值volume Math.min(1, Math.max(0, volume)); el.volume volume; setState({ volume });6.4mute()/unmute()两者直接读写el.muted属性最终通过onVolumeChange事件把muted状态同步回 state。七、autoPlay的挂载期处理在元素挂载后的useEffect中依赖props.src工厂会做两件事src/factory/createHTMLMediaHook.ts把真实元素的volume、muted、paused初始值回填进 state保证与浏览器实际状态一致若设置了props.autoPlay且元素当前处于暂停状态则调用controls.play()触发自动播放。useEffect(() { const el ref.current!; if (!el) { /* 开发环境下打印错误提示 */ return; } setState({ volume: el.volume, muted: el.muted, paused: el.paused }); if (props.autoPlay el.paused) { controls.play(); } }, [props.src]);注意此处的错误提示在非生产环境NODE_ENV ! production下如果挂载时ref.current为空即没有渲染返回的audio元素控制台会打印一条明确的错误信息useAudio() ref toaudioelement is empty at mount. It seem you have not rendered the audio element...。这条信息对排查为什么 Hook 不工作非常有用——忘渲染{audio}是最常见的误用方式。该行为在测试 tests/useAudio.test.ts 中得到了验证当renderHook只调用 Hook 而不渲染返回的audio元素时console.error恰好被调用一次。八、工程实践建议基于上述原理在实际项目中使用useAudio时有几点建议务必渲染返回的audio元素并让它保持在组件树中可用 CSS 隐藏但不要卸载否则状态不会更新、控制方法全部失效把state视为事件驱动的最新快照time只在timeupdate事件触发时更新做进度条时无需自行轮询若需要更细粒度的时间刷新可结合requestAnimationFrame类 Hook 平滑插值playing与paused分开判断展示加载中/缓冲中状态时应读取playing而不要用paused反推访问ref.current前判空并优先使用controls方法而不是直接操作 DOM以享受钳制、锁等内置保护控制音量后 state 已同步回写UI 上直接展示state.volume即可无需额外维护本地状态。九、与useVideo的关系useAudio与useVideo是同一套工厂的两个实例src/useVideo.ts 中createHTMLMediaHookHTMLVideoElement(video)因此本文所有的 state 字段、controls 接口、事件机制对useVideo同样适用useVideo文档见 docs/useVideo.md。如果你后续要处理视频可以无缝迁移同样的心智模型。十、小结useAudio把 HTML5 音频的元素创建—状态追踪—播放控制三件事封装为一个 Hook返回值四元组[audio, state, controls, ref]分工明确、开箱即用。通过阅读 src/factory/createHTMLMediaHook.ts 的源码可以发现其实时状态完全依赖HTMLMediaElement原生事件驱动控制方法则内建了 Promise 锁、数值钳制等健壮性保护配套测试 tests/useAudio.test.ts 与 Storybook 演示 stories/useAudio.story.tsx 可直接运行验证。理解这些底层机制后你就能放心地基于useAudio构建自定义音频播放器、播客界面或任何需要精确控制音频播放的 React 应用。【免费下载链接】react-useReact Hooks — 项目地址: https://gitcode.com/gh_mirrors/re/react-use创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考