实现一个基本的音频播放器,该播放器不仅支持基本的播放控制,如播放、暂停、进度显示等等,还具备倍速播放和音高调整等功能
1. 原生音频播放器
原生音频播放器,使用 HTML5 的 Audio 标签,可以播放音频文件,并且可以控制播放、暂停、音量、下载、倍数等等。
原生效果,如下图所示:
如果要去掉下载和倍速功能,可以配置controlsList属性:
nodownload: 禁用音频播放器下载媒体功能。noplaybackrate:禁用音频播放器控制播放速率功能。
// 音频源
const audioSrc = "/audio/mp3Trans14s.mp3";
// 音频元素
const audioRef = useRef<HTMLAudioElement | null>(null);
// 音频播放器
<audio
ref={audioRef}
controls
controlsList="nodownload noplaybackrate"
className="audio"
src={audioSrc}
/>;
2. 自定义播放器
原生音频播放器,尽管提供了基本的播放功能,但在界面和功能上往往显得不足。
因此,对于追求更佳用户体验的应用来说,需要自定义实现,满足更漂亮的UI体验。
左边为原生的,自定义实现效果如右边所示:
主要技术栈:
- React:流行的 JavaScript 库,用于构建用户界面。
- Ant Design:一套企业级 UI 设计语言和 React 组件库,用于快速搭建美观的用户界面。
- Tone.js:一个成熟且功能全面的音频库,特别适合音乐制作和音频处理。
2.1 进度条控制
获取总时长:通过 useEffect 钩子用于监听音频元素的生命周期事件,如 canplay 和 timeupdate,以更新播放状态和播放总时长。
注意:刷新时数据就没了,需要强制加载音频资源,如 audioEle.load()
// 管理音频的总时长
const [allTime, setAllTime] = useState(0);
useEffect(() => {
// 获取音频元素的引用
const audioEle = audioRef.current;
// 1. 定义canplay事件处理器,当音频可以开始播放时触发
const onCanPlay = () => {
if (audioEle) {
// 设置总时长
setAllTime(audioEle.duration);
}
};
// 2. 定义timeupdate事件处理器,当音频播放时间更新时触发
const onTimeUpdate = () => {
if (!audioEle) return;
// 更新当前播放时间
setCurrentTime(audioEle.currentTime);
// 如果当前播放时间等于总时长,设置播放状态为false
if (audioEle.currentTime === audioEle.duration) {
setIsPlay(false);
}
};
// 如果音频元素存在,添加事件监听器
if (audioEle) {
audioEle.addEventListener("canplay", onCanPlay);
audioEle.addEventListener("timeupdate", onTimeUpdate);
// 强制加载音频资源
audioEle.load();
// 返回清除事件监听器的函数,确保组件卸载时移除监听器
return () => {
audioEle?.removeEventListener("canplay", onCanPlay);
audioEle?.removeEventListener("timeupdate", onTimeUpdate);
};
}
}, []);
进度条控制如下,可通过滑动条的changeTime动态修改播放时间
// 定义一个函数来格式化时间显示
const formatSecond = (time: number) => {
const second = Math.floor(time % 60); // 计算秒数
let minute = Math.floor(time / 60); // 计算分钟数
// 返回格式化的时间字符串
return `${minute}:${second >= 10 ? second : `0${second}`}`;
};
// 修改播放时间
const changeTime = (value: number) => {
if (!audioRef.current) return;
// 设置新的播放时间
audioRef.current.currentTime = value;
// 更新当前播放时间状态
setCurrentTime(value);
// 如果新的播放时间等于总时长,更新播放/暂停按钮状态
if (value === audioRef.current.duration) {
setIsPlay(false);
}
};
/* 时常/进度条控制 */
<div className="time">
<div className="time_text">
<span>{formatSecond(currentTime)}</span>
<span className="time_text_split">/</span>
<span>{formatSecond(allTime)}</span>
</div>
<Slider max={allTime} value={currentTime} onChange={changeTime} />
</div>;
当然也可以不使用Ant Design的 Slider 组件,可以用原生的input组件实现:
<input
type="range"
step="0.01"
max={allTime}
value={currentTime}
onChange={(e: any) => changeTime(e.target.value)}
/>
2.1 开始/停止播放
通过 isPlay 管理播放状态,实现播放(play)/暂停(pause)功能
const [isPlay, setIsPlay] = useState(false);
// 播放或者暂停
const pauseOrPlay = () => {
if (!audioRef.current) return;
if (isPlay) {
audioRef.current.pause(); // 暂停音频
setIsPlay(false);
} else {
audioRef.current.play(); // 播放音频
setIsPlay(true);
}
};
/* 播放/暂停按钮 */
<div onClick={pauseOrPlay} className="toggle">
{/* 根据播放状态显示播放或暂停图标 */}
{isPlay ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
</div>;
2.3 音量/禁音控制
音量控制和静音功能通过 volume 和 muted 属性实现,同时使用 Ant Design 的 Slider 组件提供直观的音量调节界面。
const [isMuted, setIsMuted] = useState(false);
const [volume, setVolume] = useState(100);
// 改变音量
const changeVolume = (value: number) => {
if (!audioRef.current) return;
// 设置音量,将百分比转换为0到1之间的值
audioRef.current.volume = value / 100;
setVolume(value);
// 如果音量为0,则设置为静音
setIsMuted(!value);
};
//禁音/取消禁音
const onMuteAudio = () => {
if (!audioRef.current) return;
setIsMuted(!isMuted);
// 设置音频元素的静音状态
audioRef.current.muted = !isMuted;
};
/* 音量控制 */
<Tooltip
overlayClassName="volume-tooltip"
// 显示垂直滑动条作为音量控制器
title={
<Slider vertical value={isMuted ? 0 : volume} onChange={changeVolume} />
}
color="#fff"
>
<div onClick={onMuteAudio} className="volume">
{/* 根据静音状态显示静音或音量开启图标 */}
<img src={isMuted ? icAudioMute : icAudioOpen} alt="禁音" />
</div>
</Tooltip>;
禁音效果:
调整音量效果:
2.4 倍速控制
倍速播放功能通过修改 playbackRate 属性实现,用户可以选择不同的播放速率。
const [playRate, setPlayRate] = useState(1.0);
// 播放倍数
const changePlayRate = (num: number) => {
if (!audioRef.current) return;
// 设置播放速度
audioRef.current.playbackRate = num;
setPlayRate(num);
};
/* 倍速播放 */
<Tooltip
overlayClassName="rate-tooltip"
title={rateList.map((o) => (
<div
key={o.value}
className={`item ${playRate === o.value ? "active" : ""}`}
onClick={() => changePlayPitch(o.value)}
>
{o.label}
</div>
))}
color="#fff"
>
<Button type="link">倍速</Button>
</Tooltip>;
调整倍速效果:
2.5 音高控制
使用了 Tone.js,Tone.js 是一个成熟的音频库,专为创作交互式音乐和复杂的音频应用而设计。它提供了高级别的构建块,如合成器、采样器、音序器和效果器,同时也提供了低级别的构建块,允许你创建自定义的声音处理器和复杂的控制信号。
音高调整是通过 Tone.js 的 PitchShift 节点实现的。当用户选择不同的音高时,会创建一个新的 Player 实例,并通过 PitchShift 节点进行音高变换。
// 使用引入
import * as Tone from "tone";
// 使用 useRef 来保存 Tone.js 中的 Player 和 PitchShift 对象实例
const player = useRef<Tone.Player | null>(null);
const pitchShift = useRef<Tone.PitchShift | null>(null);
// 存储音高的偏移量
const [playPitch, setPlayPitch] = useState(0);
useEffect(() => {
return () => {
// 在组件卸载时清理 Tone.js 的资源,避免内存泄漏
player.current?.dispose();
pitchShift.current?.dispose();
};
}, []);
// 播放音高
const changePlayPitch = async (num: number) => {
// 更新音高偏移量的状态
setPlayPitch(num);
// 已有播放器实例,先停止并断开连接
if (player.current) {
player.current.stop();
player.current.disconnect();
pitchShift.current?.disconnect();
player.current.dispose();
pitchShift.current?.dispose();
}
// 1. 创建 Tone.Player 实例,只在首次调用时创建(用于播放音频文件)
player.current = new Tone.Player(audioSrc);
// 2. 创建 PitchShift 节点,参数为音高偏移量(用于改变音高)
// 0.5-升半个调,1-升一个全音
pitchShift.current = new Tone.PitchShift(num);
// 3. 将 Player 的输出连接到 PitchShift 节点
player.current.connect(pitchShift.current);
// 4. 将 PitchShift 节点的输出连接到音频上下文的目的地
pitchShift.current.toDestination();
// 等待所有音频资源加载完成
await Tone.loaded();
// 开始播放音频
player.current!.start();
};
调整音高效果如下所示:
实际上,React 和 Tone.js 的组合可以支持更复杂的功能,如实时音频分析、音轨混音和多音轨同步等。。。大家感兴趣的话,可以多研究研究。