效果:
实现思路:
1、创建音频上下文。
2、创建音频乐谱分析仪
3、将分析出的数据画到canvas上。
实现步骤:
1、创建音频上下文
this.audioContext = new AudioContext()
2、创建源及乐谱分析仪,并链接
// 创建音频源节点
this.source = this.audioContext.createMediaElementSource(this.audioElement)
// 创建乐谱分析器
this.analyser = this.audioContext.createAnalyser()
// 设置分析精度
this.analyser.fftSize = this.options.fftSize
// 将源链接到分析器
this.source.connect(this.analyser)
//将分析器链接到音频上下文目的地
this.analyser.connect(this.audioContext.destination)
3、绘制到canvas上
// 清除画布
const { width, height } = this.canvasElement
this.ctx.clearRect(0, 0, width, height)
// 让分析器节点将分析出的数据放到存储容器内
this.analyser.getByteFrequencyData(this.dataArray)
// 计算要绘制的区域
const l = this.dataArray.length * (this.options.range / 100)
// 计算每个块的宽度
const barWidth = width / l / 2
this.ctx.fillStyle = this.options.color
for (let i = 0; i < l; i++) {
// 获取每个的数据,此值小于256
const item = this.dataArray[i]
// 计算块高度
const h = (item / 255) * height
// 计算块开始的y
const y = height - h
// 计算块开始的x,对称
const x_r = i * barWidth + width / 2
const x_l = width - x_r
this.ctx.fillRect(x_r, y, barWidth, height)
this.ctx.fillRect(x_l, y, barWidth, height)
}
4、使用requestAnimationFrame重复绘制
this.animateId = requestAnimationFrame(this.draw.bind(this))
完整代码:
music-score.ts
interface IOptionsType {
// audio元素|audio元素选择器|网络音频地址
audio: string | HTMLAudioElement
canvas: string | HTMLCanvasElement
fftSize?: number
color?: string
range?: number
}
export class MpMusicScore {
private options: Partial<IOptionsType> = {
fftSize: 512,
color: '#9adcff',
range: 70
}
private audioElement: HTMLAudioElement
private canvasElement: HTMLCanvasElement
private audioContext: AudioContext
private analyser: AnalyserNode
private source: MediaElementAudioSourceNode
private dataArray: Uint8Array
private animateId: number
private ctx: CanvasRenderingContext2D
private isInitAudio: boolean = false
constructor(options: IOptionsType) {
this.options = { ...this.options, ...options }
this.init()
}
// 初始化参数
private init() {
// 获取音频元素
if (typeof this.options.audio === 'string') {
if (
this.options.audio.includes('//') &&
this.options.audio.endsWith('.mp3')
) {
this.audioElement = new Audio(this.options.audio)
} else {
this.audioElement = document.querySelector(this.options.audio)
}
} else if (this.options.audio instanceof HTMLAudioElement) {
this.audioElement = this.options.audio
}
if (!(this.audioElement instanceof HTMLAudioElement)) {
throw '音频不存在!'
}
// 获取画布元素
if (typeof this.options.canvas === 'string') {
this.canvasElement = document.querySelector(this.options.canvas)
} else if (this.options.canvas instanceof HTMLCanvasElement) {
this.canvasElement = this.options.canvas
}
if (!(this.canvasElement instanceof HTMLCanvasElement)) {
throw '画布不存在!'
}
if (Math.log2(this.options.fftSize) % 1 !== 0) {
throw 'fftSize为2的n次幂!'
}
// 初始化画布及画布大小
this.ctx = this.canvasElement.getContext('2d')
const { clientWidth, clientHeight } = this.canvasElement
this.canvasElement.width = clientWidth
this.canvasElement.height = clientHeight
// 添加绘制事件
this.audioElement.addEventListener('play', this.draw.bind(this))
this.audioElement.addEventListener('pause', this.cancelDraw.bind(this))
}
// 初始化音频
private initAudio() {
if (this.isInitAudio) {
return
}
this.isInitAudio = true
// 创建audio上下文
this.audioContext = new AudioContext()
// 创建音频源节点
this.source = this.audioContext.createMediaElementSource(this.audioElement)
// 创建乐谱分析器
this.analyser = this.audioContext.createAnalyser()
// 设置分析精度
this.analyser.fftSize = this.options.fftSize
// 将源链接到分析器
this.source.connect(this.analyser)
//将分析器链接到音频上下文目的地
this.analyser.connect(this.audioContext.destination)
// 创建乐谱存储容器
this.dataArray = new Uint8Array(this.analyser.frequencyBinCount)
}
// 绘制乐谱
private draw() {
// 初始化Audio
this.initAudio()
this.animateId = requestAnimationFrame(this.draw.bind(this))
// 清除画布
const { width, height } = this.canvasElement
this.ctx.clearRect(0, 0, width, height)
// 让分析器节点将分析出的数据放到存储容器内
this.analyser.getByteFrequencyData(this.dataArray)
// 计算要绘制的区域
const l = this.dataArray.length * (this.options.range / 100)
// 计算每个块的宽度
const barWidth = width / l / 2
this.ctx.fillStyle = this.options.color
for (let i = 0; i < l; i++) {
// 获取每个的数据,此值小于256
const item = this.dataArray[i]
// 计算块高度
const h = (item / 255) * height
// 计算块开始的y
const y = height - h
// 计算块开始的x,对称
const x_r = i * barWidth + width / 2
const x_l = width - x_r
this.ctx.fillRect(x_r, y, barWidth, height)
this.ctx.fillRect(x_l, y, barWidth, height)
}
}
// 取消绘制
private cancelDraw() {
cancelAnimationFrame(this.animateId)
}
// 播放
public play() {
this.audioElement.play()
}
// 暂停
public pause() {
this.audioElement.pause()
}
// 获取audio元素
public getAudioElement() {
return this.audioElement
}
}
使用方法:
接收一个配置对象,有5个可配置属性:
audio(audio元素 | audio元素选择器 | 网络音频地址)、
canvas(canvas元素或选择器)、
fftSize(精度,越大越精细,必须为2的n次幂)、
color(乐谱块颜色)、
range(乐谱的取值,因乐谱后面的频率一般为0,没有显示,可以通过此值,只取前面部分)。
使用案例:
new MpMusicScore({
audio: 'audio',
canvas: 'canvas',
color: '#58ee43',
range: 50,
fftSize: 1024
})
const score = new MpMusicScore({
audio: 'http://127.0.0.1:5173/1.mp3',
canvas: 'canvas',
color: 'red',
range: 75,
fftSize: 2048
})
setTimeout(() => {
score.play()
}, 1000)