Skip to content

Commit 381fa95

Browse files
committed
增加音频波形图
1 parent 2a333d5 commit 381fa95

File tree

5 files changed

+133
-2
lines changed

5 files changed

+133
-2
lines changed

.vitepress/config.mts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ export default defineConfig({
5555
text: 'Demo',
5656
base: '/blog/front/demo/',
5757
items: [
58-
{ text: '视频帧渲染', link: 'video-frame/1/' },
58+
{ text: '视频帧渲染', link: 'video-editor/video-frame/' },
59+
{ text: '音视频波形图渲染', link: 'video-editor/waveform/' },
5960
],
6061
},
6162
{
File renamed without changes.

blog/front/demo/video-frame/1/index.md renamed to blog/front/demo/video-editor/video-frame/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
aside: false
33
---
44

5-
# 视频帧绘制(一)
5+
# 视频帧绘制
66

77
:::codeview
88
<<< ./code.html
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<div class="container">
2+
<h3>先选择一个音频/视频</h3>
3+
<input type="file" accept="video/*,audio/*" id="file">
4+
<h3>渲染波形图</h3>
5+
<div id="waveform-container"></div>
6+
</div>
7+
8+
<script>
9+
const onFileChange = (callback) => {
10+
if (onFileChange._callbacks) onFileChange._callbacks.push(callback);
11+
else (onFileChange._callbacks = [callback]);
12+
};
13+
shadowDocument.querySelector('#file').addEventListener('change', (event) => {
14+
const url = URL.createObjectURL(event.target.files[0]);
15+
onFileChange._callbacks?.forEach((callback) => callback(url));
16+
});
17+
18+
(() => {
19+
class Waveform {
20+
audio = new Audio;
21+
waveformCanvas = document.createElement('canvas');
22+
waveformCtx = this.waveformCanvas.getContext('2d');
23+
waveformContainer = null;
24+
25+
constructor ({ waveformContainer }) {
26+
this.waveformContainer = waveformContainer;
27+
}
28+
29+
async load (url) {
30+
await this.renderWaveform(url);
31+
32+
await new Promise((resolve) => {
33+
this.audio.src = url;
34+
this.audio.load();
35+
const loadedmetadata = () => {
36+
this.audio.removeEventListener('loadedmetadata', loadedmetadata);
37+
resolve(this.audio);
38+
};
39+
this.audio.addEventListener('loadedmetadata', loadedmetadata);
40+
});
41+
}
42+
43+
clear () {
44+
const ctx = this.waveformCtx;
45+
if (!ctx) return;
46+
const { width, height } = this.waveformCanvas.getBoundingClientRect();
47+
ctx.clearRect(0, 0, width, height);
48+
}
49+
50+
// 绘制波形图
51+
async renderWaveform (url) {
52+
const ctx = this.waveformCtx;
53+
if (!ctx || !this.waveformContainer) return;
54+
// 获取音频数据
55+
// const response = await fetch(url);
56+
// const arrayBuffer = await response.arrayBuffer();
57+
// 解码音频
58+
const audioContext = new AudioContext();
59+
const audioBuffer = await audioContext.decodeAudioData(this.audio);
60+
61+
// 获取声道数据
62+
const channelData = audioBuffer.getChannelData(0);
63+
const duration = this.audio.duration;
64+
console.dir(this.audio.duration);
65+
const { width, height } = this.waveformContainer.getBoundingClientRect();
66+
67+
this.waveformCanvas.setAttribute('width', String(width));
68+
this.waveformCanvas.setAttribute('height', String(height));
69+
70+
// 设置背景
71+
ctx.fillStyle = '#434343';
72+
ctx.fillRect(0, 0, width, height);
73+
74+
// 绘制波形
75+
ctx.beginPath();
76+
ctx.lineWidth = 2;
77+
ctx.strokeStyle = '#00a63e';
78+
79+
const step = Math.ceil(channelData.length / width);
80+
const amp = height / 2;
81+
82+
for (let i = 0; i < width; i+=4) {
83+
let min = 1.0;
84+
let max = -1.0;
85+
86+
// 获取该像素点对应的音频数据范围
87+
for (let j = 0; j < step; j++) {
88+
const datum = channelData[(i * step) + j];
89+
if (datum < min) min = datum;
90+
if (datum > max) max = datum;
91+
}
92+
93+
ctx.moveTo(i, (1 + min) * amp);
94+
ctx.lineTo(i, (1 + max) * amp);
95+
}
96+
97+
ctx.stroke();
98+
}
99+
}
100+
// 因为是在shadow环境 所以这里使用了注入进来的 shadowDocument 去获取元素
101+
const waveformContainer = shadowDocument.querySelector('#waveform-container');
102+
103+
onFileChange((url) => {
104+
const waveform = new Waveform({
105+
waveformContainer,
106+
});
107+
waveform.load(url);
108+
});
109+
})();
110+
</script>
111+
112+
<style>
113+
.container {
114+
padding: 20px;
115+
}
116+
#waveform-container {
117+
position: relative;
118+
height: 80px;
119+
background-color: #999;
120+
}
121+
</style>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
aside: false
3+
---
4+
5+
# 音视频波形图绘制
6+
7+
:::codeview
8+
<<< ./code.html
9+
:::

0 commit comments

Comments
 (0)