有了之前的原理,现在我们只需要拿到音频的数据即可。

模板

添加一个 canvas 来承载音频图像。

  1. <div class="music-page { $music ?'show':'hidden' } ">
  2. <Back/>
  3. <div>
  4. <ul class="main {list?'hidden':'show'}">
  5. {#each files as file, index}
  6. <li on:click="$set({index})">{handleName(file)}</li>
  7. {/each}
  8. </ul>
  9. <div class="main play-canvs {list?'show':'hidden'}" on:click="toggleList()">
  10. <canvas ref:box width="400" height="400"></canvas>
  11. </div>
  12. <div class="control">
  13. <div class="title" on:click="toggleList()">{title}</div>
  14. <div class="flex">
  15. <button on:click="file()"><img src="{staticPath + "plus-circle.png"}" width="25" height="25" alt="add"></button>
  16. <button on:click="play()"><img src="{staticPath + "play-circle.png"}" width="25" height="25" alt="play"></button>
  17. <button on:click="pause()"><img src="{staticPath + "time-out.png"}" width="25" height="25" alt="stop"></button>
  18. <button on:click="prev()"><img src="{staticPath + "left-circle.png"}" width="25" height="25" alt="prev"></button>
  19. <button on:click="next()"><img src="{staticPath +"right-circle.png"}" width="25" height="25" alt="next"></button>
  20. </div>
  21. </div>
  22. </div>
  23. </div>

修改 oncreate 钩子函数,创建音频编辑器,和分析器。

  1. this.ac = new AudioContext()
  2. this.analyser = this.ac.createAnalyser()

读取音频数据

修改播放和停止方法,这里我就没有对音量进行处理了,对于音量需要另外一个 GainNode 处理器。通过编辑器的 createMediaElementSource 从 dom 里面读取音频数据,连接 analyser 最终连接到 this.ac.destination 目的地。

this.analyser.frequencyBinCount 是数据长度。 this.analyser.getByteFrequencyData(this.datas) 读取数据到 this.datas 中,由于 this.datasUint8 所以是 0 - 255 的范围。

 在停止的时候要通过 requestAnimationFrame 取消动画帧。

  1. play() {
  2. if (!this.player.currentSrc) {
  3. const {
  4. files,
  5. index
  6. } = this.store.get()
  7. this.set({
  8. title: handleName(files[index])
  9. })
  10. this.player.src = "file://" + files[index]
  11. }
  12. this.player.play()
  13. this.audioSource = this.ac.createMediaElementSource(this.player)
  14. this.audioSource.connect(this.analyser)
  15. this.analyser.connect(this.ac.destination)
  16. const animate = () => {
  17. this.datas = new Uint8Array(this.analyser.frequencyBinCount)
  18. this.analyser.getByteFrequencyData(this.datas)
  19. this.animation()
  20. this.rid = requestAnimationFrame(animate)
  21. }
  22. animate()
  23. },
  24. pause() {
  25. this.audioSource.disconnect(this.analyser);
  26. this.audioSource.disconnect(this.ac.destination);
  27. if(this.rid) {
  28. cancelAnimationFrame(rid)
  29. }
  30. this.player.pause()
  31. },

核心

代码基本都是从上一节来的,加了一个 step 步进,即取值间的距离,然后又计算了一下列数。

  1. animation(){
  2. const cvs = this.refs.box
  3. const ctx = cvs.getContext('2d')
  4. const height = cvs.height
  5. const width = cvs.width
  6. let w = 20
  7. let h = 10
  8. let margin = 5 // 间隔
  9. let maxSize = 24 // 最高方块
  10. let widthSize = Math.ceil(width / w + margin) // 列数
  11. let GreenHightArray = new Array(widthSize + 1).fill(height)
  12. let v = (h + margin) * 1 // 下降速度
  13. let lines = Math.ceil(height / (h + margin)) // 计算行数
  14. const step = 10 // 步进,按比例取其中的值
  15. const Run = () => {
  16. ctx.clearRect(0, 0, width, height)
  17. for (let j = 0; j <= widthSize; j++) {
  18. let val = (this.datas[j * step])
  19. let currentSize = Math.ceil( (val / 255 ) * maxSize)
  20. let g = ctx.createLinearGradient(0, height, 0 , 0)
  21. g.addColorStop(0, '#233142')
  22. g.addColorStop(0.6, '#455d7a')
  23. g.addColorStop(0.8, '#f95959')
  24. ctx.fillStyle = g
  25. ctx.fillRect(j * w + margin * j, height - (h + margin) * currentSize, w, (h + margin) * currentSize)
  26. // 绿块逻辑
  27. ctx.fillStyle = '#08c299'
  28. let currentGreenHight = height - currentSize * (h + margin) // 制高点 y 坐标。
  29. if (GreenHightArray[j] + v > currentGreenHight) {
  30. GreenHightArray[j] = currentGreenHight
  31. ctx.fillRect(j * w + margin * j, GreenHightArray[j], w, h + margin)
  32. } else {
  33. GreenHightArray[j] += v
  34. ctx.fillRect(j * w + margin * j, GreenHightArray[j], w, h + margin)
  35. }
  36. }
  37. for (var i = 0; i <= lines; i++) {
  38. ctx.clearRect(0, height - i * (h + margin) , width, margin)
  39. }
  40. setTimeout(Run, 500)
  41. }
  42. Run()
  43. },

嵌入到应用中 - 图1