首页/文章/webrtc

用 Wavesurfer.js 和 React 快速打造你的音频录播功能

2024-03-08
9924 分钟
...

背景

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

介绍

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

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

官网 : wavesurfer.xyz/

github: github.com/katspaugh/w…

实现

 一: 安装依赖创建项目

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

  • Node.js
  • pnpm
  • react

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

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

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

二: 创建 Wavesurfer 组件

先看我们要实现效果:

    1. 插件
import React, { useState, useRef, useEffect, useCallback } from "react";
import { Button, Space, Divider } from "antd";
import WaveSurfer from "wavesurfer.js";
    1. 编写 WaveSurfer hook
const useWavesurfer = (containerRef, options) => {
  const [wavesurfer, setWavesurfer] = useState(null)

  
  
  useEffect(() => {
    if (!containerRef.current) return

    const ws = WaveSurfer.create({
      ...options,
      container: containerRef.current,
    })

    setWavesurfer(ws)

    return () => {
      ws.destroy()
    }
  }, [options, containerRef])

  return wavesurfer
}
    1. 编写 WaveSurferPlayer组件

const WaveSurferPlayer = (props) => {
  const containerRef = useRef()
  const [isPlaying, setIsPlaying] = useState(false)
  const [currentTime, setCurrentTime] = useState(0)
  const wavesurfer = useWavesurfer(containerRef, props)

  
  const onPlayClick = useCallback(() => {
    wavesurfer.isPlaying() ? wavesurfer.pause() : wavesurfer.play()
  }, [wavesurfer])

  
  
  useEffect(() => {
    if (!wavesurfer) return

    setCurrentTime(0)
    setIsPlaying(false)

    const subscriptions = [
      wavesurfer.on('play', () => setIsPlaying(true)),
      wavesurfer.on('pause', () => setIsPlaying(false)),
      wavesurfer.on('timeupdate', (currentTime) => setCurrentTime(currentTime)),
    ]

    return () => {
      subscriptions.forEach((unsub) => unsub())
    }
  }, [wavesurfer])

  return (
    <>
      <div ref={containerRef} style={{ minHeight: '120px' }} />

      <button onClick={onPlayClick} style={{ marginTop: '1em' }}>
        {isPlaying ? 'Pause' : 'Play'}
      </button>

      <p>Seconds played: {currentTime}</p>
    </>
  )
}
    1. 使用
const App = () => {
  const urls = ['/examples/audio/audio.wav', '/examples/audio/stereo.mp3']
  const [audioUrl, setAudioUrl] = useState(urls[0])

  
  const onUrlChange = useCallback(() => {
    urls.reverse()
    setAudioUrl(urls[0])
  }, [])

  
  
  return (
    <>
      <WaveSurferPlayer
        height={100}
        waveColor="rgb(200, 0, 200)"
        progressColor="rgb(100, 0, 100)"
        url={audioUrl}
        plugins={[Timeline.create()]}
      />

      <button onClick={onUrlChange}>Change audio</button>
    </>
  )
}

效果如下:

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

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

三、实现录音

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

    1. 引入插件
import React, { useState, useRef, useEffect } from "react";
import { Radio, Card, Divider, Switch, Space } from "antd";
import WaveSurfer from "wavesurfer.js";
import RecordPlugin from "wavesurfer.js/plugins/recordPlugin";
import dayjs from "dayjs";
    1. 编写组件
const AudioRecorder = (props) => {
    ....
};
export default AudioRecorder;
    1. 初始化录音
const micRef = useRef();
const [record, setRecord] = useState<any>(null);
const [isDisabled, setIsDisabled] = useState(true);
const [value, setValue] = useState("");

useEffect(() => {
    if (!micRef.current) return;
    const wavesurfer = WaveSurfer.create({
            container: micRef.current,
            waveColor: "rgb(200, 0, 200)",
            progressColor: "rgb(100, 0, 100)",
    });

    const record = wavesurfer.registerPlugin(RecordPlugin.create() as any);
    record.on("record-end", async (blob) => {
            const recordedUrl = URL.createObjectURL(blob);
            const duration = await record.getDuration(blob);
            const audio = {
                    url: recordedUrl,
                    type: blob.type.split(";")[0].split("/")[1] || "webm",
                    createdAt: dayjs().format(),
                    duration,
            };
            props.onSetAudios((prevState) => [audio, ...prevState]);
    });
    setRecord(record);
}, [micRef]);


html

<div id="mic" ref={micRef}></div>
    1. 实现打开麦克风

function destroyRecord() {
        setIsDisabled(true);
        record.stopMic();
}


function openRecord() {
        setIsDisabled(false);
        record.startMic();
}
        
function changeMic(checked) {
    checked ? openRecord() : destroyRecord();
}

html

<Space>
    麦克风
    <Switch
            checkedChildren="开启"
            unCheckedChildren="关闭"
            onChange={changeMic}
    />
</Space>
    1. 实现操作 接下来我们实现录音的几个操作: 开始、保存。
function startRecord() {
        setIsDisabled(true);
        record.startRecording().then(() => {
                setIsDisabled(false);
                setValue("start");
        });
}

function stopRecord() {
        if (record.isRecording()) {
                record.stopRecording();
                setValue("stop");
        }
}

html

<Space>
	操作
    <Radio.Group buttonStyle="solid" disabled={isDisabled} value={value}>
            <Radio.Button value="start" onClick={startRecord}>
                    开始
            </Radio.Button>
            <Radio.Button value="stop" onClick={stopRecord}>
                    保存
            </Radio.Button>
            <Radio.Button value="pause" onClick={pauseRecord}>
                    暂停
            </Radio.Button>
            <Radio.Button value="resume" onClick={resumeRecord}>
                    继续
            </Radio.Button>
    </Radio.Group>
</Space>
    1. 实现暂停、继续

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


 * 扩展录音插件
 */

import RecordPlugin, {
	type RecordPluginEvents,
	type RecordPluginOptions,
} from "wavesurfer.js/plugins/record";

export type RecordEvents = RecordPluginEvents & {
	"record-pause": [];
	"record-resume": [];
};

class Record extends RecordPlugin {
	
	
	constructor(options: RecordPluginOptions) {
		super({
			...options,
			audioBitsPerSecond: options.audioBitsPerSecond,
		});
	}

	
	public static create(options?: RecordPluginOptions) {
		return new Record(options || {});
	}
	public getMediaRecorder() {
		
		return this.mediaRecorder;
	}

	
	public pauseRecording() {
		if (this.isRecording()) {
			this.getMediaRecorder()?.pause();
		}
	}

	
	public resumeRecording() {
		if (this.isRecording()) {
			this.getMediaRecorder()?.resume();
		}
	}

	
	public getDuration(blob): Promise<number> {
		return new Promise((resolve, reject) => {
			const audioContext = new AudioContext();
			const reader = new FileReader();

			reader.onload = function () {
				const arrayBuffer = this.result as ArrayBuffer;
				audioContext.decodeAudioData(
					arrayBuffer,
					(buffer) => {
						const duration = Math.round(buffer.duration * 1000);
						resolve(duration);
					},
					(err) => {
						throw new Error("Error getDuration:" + (err as Error).message);
					},
				);
			};

			if (blob) {
				reader.readAsArrayBuffer(blob);
			} else {
				throw new Error("Error blob is empty ");
			}
		});
	}
}

export default Record;

实现的效果如下:

总结

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

Q&A

  • Q: 有源码吗?

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

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
webrtc2024-10-08
什么是 WebRTC
WebRTC(Web 实时通信)是一种可以在浏览器中实现点对点(Peer to Peer)通信的技术标准和开源项目。它使得浏览器和移动应用能够进行实时语音、视频和数据交换,无需安装插件或第三方插件。WebRTC 提供了一组 API,使开发者可以轻松地构建各种实时通信应用程序,如视频会议、语音电话、文件共享等。 基本概念 WebRTC 的核心包括三个主要 API: 1. MediaStream API : 获取摄像头和麦克风的访问权限,并...
学习
webrtc2024-05-08
使用Web RTC实现微信一对一视频通话竟如此简单
随着前端之路的不断探索,当今的前端时代可谓是大前端时代。当今的前端能做的事情太多太多了,这也有弊端就是前端开发人员们需要不断的学习不断的探索。希望大家可以在前端的道路上越走越远。 回到正题,今天想和大家聊一聊 相关的知识, 可以干什么?它主要是做通信相关的业务比如:视频聊天、语音聊天、直播等。 结果展示 移动端 pc端 Web RTC API 这里的 有很多,我这里就只介绍一下实现一对一视频通话所需要使用到的 。 SDP SDP 全称...
学习
AI2026-09-01
Deep Agents 01:何为 Agent Harness,以及如何开始
1、本篇任务:完成一份多步骤、带证据的技术调研 普通客服 Agent 的问题短、工具少、输出即时。技术调研或编码任务会持续很久,产生计划、搜索结果、文件和中间结论。Deep Agents 在 LangChain/LangGraph 之上预装规划、虚拟文件系统、上下文压缩和子 Agent,适合这类开放任务。 本课让 Agent 比较两种向量数据库,并交付一份可验证报告。 2、什么时候需要 Deep Agent 满足以下两项以上再考虑:任务...
学习
AI2026-09-01
Deep Agents 02:子 Agent、虚拟文件系统与长期记忆
1、本篇任务:让主管只看结论,让子 Agent 处理细节 技术调研会产生几十次搜索和大量文件。如果全部进入主管上下文,真正的目标会被噪音淹没。本课用两个子 Agent: 收集证据, 检查结论;主管负责计划与最终合成。 2、什么时候委派,什么时候直接调用工具 适合委派:子任务有多步;需要专门提示或工具;会产生大量中间结果;只需返回有限结论。不适合:一步查询;主管需要全部中间上下文;协调成本超过任务本身。 3、配置专门子 Agent Pyt...
学习
AI2026-09-01
Deep Agents 03:生产化、Sandbox、权限与上线验收
1、本篇任务:让 Deep Agent 在隔离环境中分析代码 只读研究 Agent 风险有限;编码 Agent 需要读写文件、安装依赖和执行测试。本课不讲如何让模型写更漂亮的代码,只讲执行环境、权限、恢复和上线验收。 2、先做威胁模型 资产包括源代码、用户文件、云凭证、生产网络和发布权限;攻击入口包括用户消息、仓库内容、网页、依赖包、MCP 返回和命令输出。 Prompt injection 不是靠一句 system prompt 解决...
学习
AI2026-09-01
LangChain 01:全景、原理与学习路线
1、本篇学完要得到什么 这一篇只解决三个问题:LangChain 到底负责什么;它与 LangGraph、Deep Agents、LangSmith 是什么关系;后面应按什么顺序学习。 贯穿整套课程的项目是“退款政策与订单助手”。它最终能够:回答知识库中的退款规则;查询当前用户的订单;生成结构化答复;对真正的退款操作进行人工审批;断线后恢复;通过评测后发布。 先记住一句话: 模型负责理解与生成,应用负责数据、权限、状态和副作用。 如果把...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录