背景

最近正在做一个开源的项目—pear-rec,pear-rec 是一个跨平台的截图、录屏、录音、录像软件。截图功能上篇文章已经讲过了,如果没有看过的可以去看看这篇文章————手把手教你,用electron实现截图软件,并且项目的框架搭建也可以参考上一篇文章,开发到现在我想要一个音乐(录音)预览、播放功能,说干就干,下面我介绍一下我的开发过程。

介绍

实现音频的录音和播放已经是许多应用程序的基础功能。这可能包括社交媒体平台,音乐创作软件,甚至是一些教育应用。在本教程中,我们将引导你如何使用 Wavesurfer.jsReact 实现一个基本的音频录音与播放功能。

Wavesurfer.js 是一个开源的可视化音频库,它能够创建可交互的音频波形。这个库能够选择、播放或者暂停音频,同时也能进行音频的前后快翻转,同时也支持很多不同的音频格式。

官网 : wavesurfer.xyz/

github: github.com/katspaugh/w…

实现

 一: 安装依赖创建项目

在开始之前,我们需要安装一些依赖项。请确保您已经安装了以下软件:

  • Node.js
  • pnpm
  • react

在项目的根目录下打开终端,并执行以下命令来初始化一个 React 应用。在这里我就不详细讲解了,不会的可以去看我的文章。

然后安装 wavesurfer.js 和录音的库:

 1npm install --save wavesurfer.js
 2import WaveSurfer from 'wavesurfer.js'

二: 创建 Wavesurfer 组件

先看我们要实现效果:

    1. 插件
 1import React, { useState, useRef, useEffect, useCallback } from "react";
 2import { Button, Space, Divider } from "antd";
 3import WaveSurfer from "wavesurfer.js";
    1. 编写 WaveSurfer hook
 1const useWavesurfer = (containerRef, options) => {
 2  const [wavesurfer, setWavesurfer] = useState(null)
 3
 4  
 5  
 6  useEffect(() => {
 7    if (!containerRef.current) return
 8
 9    const ws = WaveSurfer.create({
10      ...options,
11      container: containerRef.current,
12    })
13
14    setWavesurfer(ws)
15
16    return () => {
17      ws.destroy()
18    }
19  }, [options, containerRef])
20
21  return wavesurfer
22}
    1. 编写 WaveSurferPlayer组件
 1
 2const WaveSurferPlayer = (props) => {
 3  const containerRef = useRef()
 4  const [isPlaying, setIsPlaying] = useState(false)
 5  const [currentTime, setCurrentTime] = useState(0)
 6  const wavesurfer = useWavesurfer(containerRef, props)
 7
 8  
 9  const onPlayClick = useCallback(() => {
10    wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play()
11  }, [wavesurfer])
12
13  
14  
15  useEffect(() => {
16    if (!wavesurfer) return
17
18    setCurrentTime(0)
19    setIsPlaying(false)
20
21    const subscriptions = [
22      wavesurfer.on('play', () => setIsPlaying(true)),
23      wavesurfer.on('pause', () => setIsPlaying(false)),
24      wavesurfer.on('timeupdate', (currentTime) => setCurrentTime(currentTime)),
25    ]
26
27    return () => {
28      subscriptions.forEach((unsub) => unsub())
29    }
30  }, [wavesurfer])
31
32  return (
33    <>
34      <div ref={containerRef} style={{ minHeight: '120px' }} />
35
36      <button onClick={onPlayClick} style={{ marginTop: '1em' }}>
37        {isPlaying ? 'Pause' : 'Play'}
38      </button>
39
40      <p>Seconds played: {currentTime}</p>
41    </>
42  )
43}
    1. 使用
 1const App = () => {
 2  const urls = ['/examples/audio/audio.wav', '/examples/audio/stereo.mp3']
 3  const [audioUrl, setAudioUrl] = useState(urls[0])
 4
 5  
 6  const onUrlChange = useCallback(() => {
 7    urls.reverse()
 8    setAudioUrl(urls[0])
 9  }, [])
10
11  
12  
13  return (
14    <>
15      <WaveSurferPlayer
16        height={100}
17        waveColor="rgb(200, 0, 200)"
18        progressColor="rgb(100, 0, 100)"
19        url={audioUrl}
20        plugins={[Timeline.create()]}
21      />
22
23      <button onClick={onUrlChange}>Change audio</button>
24    </>
25  )
26}
27

效果如下:

这样的效果还是有点简单,我实现的最终效果如下:

可以自由播放、暂停、显示当前播放位置和选取播放位置、快进、后退。。。

三、实现录音

我们要实现的录音效果大概是这样: 因为wavesurfer.js有带有录音插件,所以我们可以直接用,话不多说我们实现吧!

    1. 引入插件
 1import React, { useState, useRef, useEffect } from "react";
 2import { Radio, Card, Divider, Switch, Space } from "antd";
 3import WaveSurfer from "wavesurfer.js";
 4import RecordPlugin from "wavesurfer.js/plugins/recordPlugin";
 5import dayjs from "dayjs";
    1. 编写组件
 1const AudioRecorder = (props) => {
 2    ....
 3};
 4export default AudioRecorder;
    1. 初始化录音
 1const micRef = useRef();
 2const [record, setRecord] = useState<any>(null);
 3const [isDisabled, setIsDisabled] = useState(true);
 4const [value, setValue] = useState("");
 5
 6useEffect(() => {
 7    if (!micRef.current) return;
 8    const wavesurfer = WaveSurfer.create({
 9            container: micRef.current,
10            waveColor: "rgb(200, 0, 200)",
11            progressColor: "rgb(100, 0, 100)",
12    });
13
14    const record = wavesurfer.registerPlugin(RecordPlugin.create() as any);
15    record.on("record-end", async (blob) => {
16            const recordedUrl = URL.createObjectURL(blob);
17            const duration = await record.getDuration(blob);
18            const audio = {
19                    url: recordedUrl,
20                    type: blob.type.split(";")[0].split("/")[1] || "webm",
21                    createdAt: dayjs().format(),
22                    duration,
23            };
24            props.onSetAudios((prevState) => [audio, ...prevState]);
25    });
26    setRecord(record);
27}, [micRef]);
28
29

html

 1<div id="mic" ref={micRef}></div>
    1. 实现打开麦克风
 1
 2function destroyRecord() {
 3        setIsDisabled(true);
 4        record.stopMic();
 5}
 6
 7
 8function openRecord() {
 9        setIsDisabled(false);
10        record.startMic();
11}
12        
13function changeMic(checked) {
14    checked ? openRecord() : destroyRecord();
15}

html

 1<Space>
 2    麦克风
 3    <Switch
 4            checkedChildren="开启"
 5            unCheckedChildren="关闭"
 6            onChange={changeMic}
 7    />
 8</Space>
    1. 实现操作 接下来我们实现录音的几个操作: 开始、保存。
 1function startRecord() {
 2        setIsDisabled(true);
 3        record.startRecording().then(() => {
 4                setIsDisabled(false);
 5                setValue("start");
 6        });
 7}
 8
 9function stopRecord() {
10        if (record.isRecording()) {
11                record.stopRecording();
12                setValue("stop");
13        }
14}
15

html

 1<Space>
 2	操作
 3    <Radio.Group buttonStyle="solid" disabled={isDisabled} value={value}>
 4            <Radio.Button value="start" onClick={startRecord}>
 5                    开始
 6            </Radio.Button>
 7            <Radio.Button value="stop" onClick={stopRecord}>
 8                    保存
 9            </Radio.Button>
10            <Radio.Button value="pause" onClick={pauseRecord}>
11                    暂停
12            </Radio.Button>
13            <Radio.Button value="resume" onClick={resumeRecord}>
14                    继续
15            </Radio.Button>
16    </Radio.Group>
17</Space>
    1. 实现暂停、继续

因为插件不支持暂停、继续。所以我们要对插件进行扩展。代码如下:

 1
 2 * 扩展录音插件
 3 */
 4
 5import RecordPlugin, {
 6	type RecordPluginEvents,
 7	type RecordPluginOptions,
 8} from "wavesurfer.js/plugins/record";
 9
10export type RecordEvents = RecordPluginEvents & {
11	"record-pause": [];
12	"record-resume": [];
13};
14
15class Record extends RecordPlugin {
16	
17	
18	constructor(options: RecordPluginOptions) {
19		super({
20			...options,
21			audioBitsPerSecond: options.audioBitsPerSecond,
22		});
23	}
24
25	
26	public static create(options?: RecordPluginOptions) {
27		return new Record(options || {});
28	}
29	public getMediaRecorder() {
30		
31		return this.mediaRecorder;
32	}
33
34	
35	public pauseRecording() {
36		if (this.isRecording()) {
37			this.getMediaRecorder()?.pause();
38		}
39	}
40
41	
42	public resumeRecording() {
43		if (this.isRecording()) {
44			this.getMediaRecorder()?.resume();
45		}
46	}
47
48	
49	public getDuration(blob): Promise<number> {
50		return new Promise((resolve, reject) => {
51			const audioContext = new AudioContext();
52			const reader = new FileReader();
53
54			reader.onload = function () {
55				const arrayBuffer = this.result as ArrayBuffer;
56				audioContext.decodeAudioData(
57					arrayBuffer,
58					(buffer) => {
59						const duration = Math.round(buffer.duration * 1000);
60						resolve(duration);
61					},
62					(err) => {
63						throw new Error("Error getDuration:" + (err as Error).message);
64					},
65				);
66			};
67
68			if (blob) {
69				reader.readAsArrayBuffer(blob);
70			} else {
71				throw new Error("Error blob is empty ");
72			}
73		});
74	}
75}
76
77export default Record;
78

实现的效果如下:

总结

本文详细介绍了如何使用 Wavesurfer.jsReact 创建一个基本的音频录音与播放器。首先,我们创建了一个新的 React 项目,并安装了必需的 Wavesurfer.js 包。然后我们定义了一个 WaveSurferPlayer 组件,并通过 WaveSurfer.create() 创建了一个 wavesurfer 实例。最后,我们加载并播放了音频文件,并在组件卸载时销毁了 wavesurfer 实例。本教程简明易懂,有助于读者快速掌握如何利用 Wavesurfer.jsReact 搭建音频播放器,也让读者了解到 前端 在处理音频方面的强大能力。

Q&A

  • Q: 有源码吗?

当然有,地址如下:pear-rec,有兴趣的话可以大家一起探讨,同时也欢迎大家forkstar

个人笔记记录 2021 ~ 2025