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 >
0 commit comments