一、前言
上期推荐了文本标注poplar-annotation用法,这期针对音视频标注推荐wavesurfer.js库;
Wavesurfer.js 是一个基于Web Audio API 和HTML5 Canvas的开源音频可视化库,用于创建可交互、可定制的波形。同时拥有众多插件库。
二、demo效果
可以实现音视频播放暂停、指定区域播放循环暂停、创建标注区域、标注区域文字编辑、标注区域拖拉拽、时间交互效果等;
三、官网
1.WaveSurfer基础属性
import WaveSurfer from 'wavesurfer.js'
const wavesurfer = WaveSurfer.create({
container: document.body,//容器
waveColor: 'rgb(200, 0, 200)',//设置波形的颜色
progressColor: 'rgb(100, 0, 100)',//设置进度条的颜色。
url: '/examples/audio/demo.wav',//音频文件的 URL
})
//点击事件
wavesurfer.on('click', () => {
wavesurfer.play()
})
2.相关配置option
{
"container": "body",
"height": 128,
"width": 300,
"splitChannels": false,
"normalize": false,
"waveColor": "#ff4e00",
"progressColor": "#dd5e98",
"cursorColor": "#ddd5e9",
"cursorWidth": 2,
"barWidth": null,
"barGap": null,
"barRadius": null,
"barHeight": null,
"barAlign": "",
"minPxPerSec": 1,
"fillParent": true,
"url": "/wavesurfer-code/examples/audio/audio.wav",
"mediaControls": true,
"autoplay": false,
"interact": true,
"dragToSeek": false,
"hideScrollbar": false,
"audioRate": 1,
"autoScroll": true,
"autoCenter": true,
"sampleRate": 8000
}
属性名 | 默认值 | 类型 | 描述 |
---|---|---|---|
container | body | String | 用于指定播放器容器的 CSS 选择器或元素。body 表示播放器将占据整个文档的主体部分。 |
height | 128 | Number | 播放器的高度。 |
width | 300 | Number | 播放器的宽度。 |
splitChannels | false | Boolean | 是否使用分裂声道模式。如果设置为 true ,则播放器将显示双声道音频的两个声道,每个声道有自己的音量控制。 |
normalize | false | Boolean | 是否使用归一化模式。如果设置为 true ,则播放器将尝试平衡音频的音量,使所有音频片段听起来同样响亮。 |
waveColor | #ff4e00 | String | 波形的颜色,使用十六进制颜色代码。 |
progressColor | #dd5e98 | String | 进度条颜色,使用十六进制颜色代码。 |
cursorColor | #ddd5e9 | String | 光标颜色,使用十六进制颜色代码。 |
cursorWidth | 2 | Number | 光标的宽度。 |
barWidth | null | Number, String | 音量条的宽度。如果设置为 null ,则音量条将自动调整大小以匹配播放器的宽度。 |
barGap | null | Number | 音量条之间的间隙。如果设置为 null ,则间隙将自动计算。 |
barRadius | null | Number | 音量条的圆角半径。如果设置为 null ,则音量条将是矩形的。 |
barHeight | null | Number | 音量条的高度。如果设置为 null ,则音量条的高度将根据播放器的高度和 barGap 参数自动计算。 |
barAlign | "" | String | 音量条的对齐方式。可以是 "center" 、"left" 或 "right" 。默认为 “”,表示不对齐。 |
minPxPerSec | 1 | Number | 音频可视化波形 1 秒对应的最小像素数。 |
fillParent | true | Boolean | 是否填满其父容器。如果设置为 true ,则播放器将填满其父容器的全部空间。 |
url | /wavesurfer-code/examples/audio/audio.wav | String | 音频文件的 URL。 |
mediaControls | true | Boolean | 是否显示媒体控制。如果设置为 true ,则播放器将显示播放/暂停、音量控制等按钮。 |
autoplay | false | Boolean | 是否自动播放。如果设置为 true ,则音频文件在加载后将自动播放。 |
interact | true | Boolean | 是否允许交互。如果设置为 true ,则用户可以通过点击或者拖动波形来控制播放进度。 |
dragToSeek | false | Boolean | 是否允许通过拖拽波形来定位播放进度。如果设置为 true ,则用户可以通过拖拽波形来定位播放进度。 |
hideScrollbar | false | Boolean | 是否隐藏滚动条。如果设置为 true ,则播放器的滚动条将被隐藏。 |
audioRate | 1 | Number | 音频播放的速率。1 表示正常速率,0.5 表示一半的速率,2 表示两倍的速率。 |
autoScroll | true | Boolean | 是否自动滚动。如果设置为 true ,则波形图将自动滚动以跟随播放进度。 |
autoCenter | true | Boolean | 是否自动居中。如果设置为 true ,则当前播放位置将始终保持在视图的中心。 |
sampleRate | 8000 | Number | 音频的采样率,用于音频的播放和绘制。 |
3.事件
在WaveSurfer.js中,有多种事件可以监听,以便在特定动作发生时执行相应的操作。以下是一些常见的WaveSurfer事件:
- ready:当WaveSurfer准备就绪时触发。
- audioprocess:在音频处理过程中不断触发。
- finish:当音频播放完成时触发。
- interaction:当用户与波形图进行交互时触发。
- seek:当用户在波形图上拖动进度条时触发。
- zoom:当用户放大或缩小波形图时触发。
- mousedown:当鼠标在波形图上按下时触发。
- mouseup:当鼠标在波形图上释放时触发。
- mouseenter:当鼠标进入波形图区域时触发。
- mouseleave:当鼠标离开波形图区域时触发。
- scroll:当用户在波形图上滚动时触发。
- timeupdate:在音频播放过程中,当时间更新时触发。
- play:当音频开始播放时触发。
- pause:当音频暂停时触发。
- volumechange:当音频音量改变时触发。
- click : 当用户点击波形图时触发。
- doubleclick : 当用户双击波形图时触发。
你可以通过调用wavesurfer.on(event, handler)方法来监听这些事件,并在事件触发时执行相应的处理函数。
import WaveSurfer from 'wavesurfer.js'
const wavesurfer = WaveSurfer.create({
container: document.body,
waveColor: 'rgb(200, 0, 200)',
progressColor: 'rgb(100, 0, 100)',
})
/** 在音频加载过程中,load事件在音频开始加载时触发 */
wavesurfer.on('load', (url) => {
console.log('Load', url)
})
/** loading事件则会在加载过程中持续触发,显示加载的百分比。这使用户能够跟踪音频文件的加载进度 */
wavesurfer.on('loading', (percent) => {
console.log('Loading', percent + '%')
//Loading 31% Loading 70% Loading 100%
})
/** decode事件在音频解码完成后触发,显示解码后的持续时间。 */
wavesurfer.on('decode', (duration) => {
console.log('Decode', duration + 's')
//Decode 26.386688s
})
/** ready事件表示音频已经解码并且准备好播放了,同样显示其时长。 */
wavesurfer.on('ready', (duration) => {
console.log('Ready', duration + 's')
//Ready 26.386688s
})
/**重绘完成 */
wavesurfer.on('redrawcomplete', () => {
console.log('Redraw began')
})
/**重绘完成 */
wavesurfer.on('redrawcomplete', () => {
console.log('Redraw complete')
})
/** 当音频播放时 */
wavesurfer.on('play', () => {
console.log('Play')
})
/** 当音频停止时 */
wavesurfer.on('pause', () => {
console.log('Pause')
})
/**当音频播放结束时*/
wavesurfer.on('finish', () => {
console.log('Finish')
})
/**timeupdate事件在音频播放过程中持续触发,显示当前播放时间,这对于显示播放进度条的当前位置非常有用。 */
wavesurfer.on('timeupdate', (currentTime) => {
console.log('Time', currentTime + 's')
})
/**当用户停止拖动进度条,`seeking`事件会显示用户跳转到的时间点。这个事件与用户寻求不同的播放位置相关。*/
wavesurfer.on('seeking', (currentTime) => {
console.log('Seeking', currentTime + 's')
})
/** 用户与波形图的交互会触发`interaction`事件,这个事件传递了用户交互后的新时间点。 */
wavesurfer.on('interaction', (newTime) => {
console.log('Interaction', newTime + 's')
})
/** 当用户点击波形图时触发,提供了用户与音频可视化直接交互的反馈。 */
wavesurfer.on('click', (relativeX) => {
console.log('Click', relativeX)
})
/** 当用户拖动波形图时,提供了用户与音频可视化直接交互的反馈。 */
wavesurfer.on('drag', (relativeX) => {
console.log('Drag', relativeX)
})
/** `scroll`事件在用户滚动(平移)波形图时触发,显示可视区域的开始和结束时间。 */
wavesurfer.on('scroll', (visibleStartTime, visibleEndTime) => {
console.log('Scroll', visibleStartTime + 's', visibleEndTime + 's')
})
/** `zoom`事件在缩放级别改变时触发,显示当前的缩放级别(以像素/秒为单位)。 */
wavesurfer.on('zoom', (minPxPerSec) => {
console.log('Zoom', minPxPerSec + 'px/s')
})
/** `destroy`事件在波形图被销毁之前触发,允许开发者执行任何清理操作。*/
wavesurfer.on('destroy', () => {
console.log('Destroy')
})
/**加载音频文件*/
wavesurfer.load('/examples/audio/audio.wav')
// 通过`input`类型的范围滑块实现了对音频缩放的控制。在`once('decode')`回调函数中,它等待音频解码完成,然后获取滑块元素。用户移动滑块时,`input`事件触发,更新`WaveSurfer`实例的缩放级别,从而改变波形的显示分辨率。此外,页面上的一个按钮被添加了点击事件监听器,用于播放和暂停音频。
wavesurfer.once('decode', () => {
const slider = document.querySelector('input[type="range"]')
slider.addEventListener('input', (e) => {
const minPxPerSec = e.target.valueAsNumber
wavesurfer.zoom(minPxPerSec)
})
document.querySelector('button').addEventListener('click', () => {
wavesurfer.playPause()
})
})
4. Plugins插件
4.1 区域插件
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
4.2 时间轴插件
import TimelinePlugin from 'wavesurfer.js/dist/plugins/timeline.esm.js'
4.3 时间交互插件
import Hover from 'wavesurfer.js/dist/plugins/hover.esm.js'
4.4 其他插件
四、使用
1.下载
-
npm install wavesurfer.js
-
或者引入(不推荐)外部地址不安全
<script src="https://unpkg.com/wavesurfer.js"></script>
2.WaveSurfer 挂载、创建、销毁
useWavesurfer
的Hook封装
import React, { useState, useEffect } from "react";
import WaveSurfer from "wavesurfer.js";
const useWavesurfer = (containerRef, options) => {
const [wavesurfer, setWavesurfer] = useState(null)
useEffect(() => {
if (!containerRef.current) return
const ws = WaveSurfer.create({
...options,//WaveSurfer的配置项
container: containerRef.current,//WaveSurfer需要挂载的容器也是dom节点
})
setWavesurfer(ws)
//销毁
return () => {
ws.destroy()
}
}, [options, containerRef])
return wavesurfer
}
export default useWavesurfer
3.WaveSurferPlayer
组件
import React, { useState, useRef, useEffect, useCallback } from "react";
import useWavesurfer from './useWavesurfer.js'
const WaveSurferPlayer = ({plugins}) => {
const regions=plugins[0]
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相关事件
wavesurfer.on('play', () => setIsPlaying(true)),
wavesurfer.on('pause', () => setIsPlaying(false)),
wavesurfer.on('timeupdate', (currentTime) => setCurrentTime(currentTime)),
wavesurfer.on('ready',()=>{
regions.enableDragSelection({
color: 'rgba(255, 0, 0, 0.1)',
resize:true,//是否可以缩放拉伸
drag:true,//是否可以拖拽
})
regions.on('region-created', (region) => {
console.log('region-created', region)
})
regions.on('region-clicked', (region, e) => {
e.stopPropagation()
if(!region)return
region.remove()
console.log('region-remove', region)
})
}),
wavesurfer.on('finish', () => {
wavesurfer.setTime(0)
})
]
return () => {
//在 useEffect 的清理阶段(即返回的函数),遍历 subscriptions 数组,其中包含了所有的事件监听器。对每一个监听器调用 unsub(),移除事件监听器,确保内存得到释放。
subscriptions.forEach((unsub) => unsub())
}
}, [wavesurfer])
return (
<>
<div ref={containerRef} style={{ minHeight: '120px' }} />
<button onClick={onPlayClick} style={{ marginTop: '50px',marginLeft:'600px' }}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<p>Seconds played: {currentTime}</p>
</>
)
}
export default WaveSurferPlayer
每个区域的创建,对应着一个’region-created’,包含id,起始时间,结束时间等;
4.使用组件
import WaveSurferPlayer from './WaveSurferPlayer.js'
import React, { useState } from "react";
/**regions区域交互插件 */
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
/**Timeline时间轴插件 */
import Timeline from 'wavesurfer.js/dist/plugins/timeline.esm.js'
/**Hover时间交互插件 */
import Hover from 'wavesurfer.js/dist/plugins/hover.esm.js'
function App() {
const [audioUrl, setAudioUrl] = useState(require('./music01.mp3'))//require绝对路径
return <WaveSurferPlayer
height={200}
waveColor='#A8DBA8'
progressColor='#3B8686'
url={audioUrl}
backend="MediaElement"
plugins={
[RegionsPlugin.create(),
Timeline.create({}),
Hover.create({
lineColor: '#000',
lineWidth: 2,
labelBackground: '#555',
labelColor: '#fff',
labelSize: '11px',
}),
]}
/>
}
export default App
五、相关问题及解决
1.WaveSurfer.create()重复创建、重复挂载的问题
记得要使用WaveSurfer的destroy属性销毁
//销毁
return () => {
ws.destroy()
}
2.资源路径url地址引入的问题
记得引入绝对路径
require('./music01.mp3')
3.插件引入的地址
一定要看官网的地址,很多博主引入的地址都是老版本的