在[上一篇](https://juejin.im/post/5d453bc3e51d4561e36ad9f1)文章中,我們已經獲取到所有的"完整"音頻段落,接下來就要利用這些"完整"的音頻段落,進行音頻分段加載的**最后兩步操作**:
4. wavesurfer 處理之前獲取到的每一小段的 buffer 產生每一小段的波形信息
5. 當wav資源的所有字節都被請求到,并且 buffer 也都被 wavesurfer 處理完畢成波形信息,拼接所有請求段的波形信息,交給 wavesurfer 進行渲染,在渲染的同時,生成波形信息文件上傳到服務端保存,下次再獲取相同的wav資源就直接獲取波形信息文件,避免重復的 decode
#### 如何讓 wavesurfer 擁有只產生波形信息的能力
wavesurfer 是沒有想外提供一個產出波形信息 Peaks 的方法的,所以就需要一點點技巧~
~~~
/**
* Get the correct peaks for current wave view-port and render wave
*
* @private
* @emits WaveSurfer#redraw
*/
drawBuffer() {
const nominalWidth = Math.round(
this.getDuration() *
this.params.minPxPerSec *
this.params.pixelRatio
);
const parentWidth = this.drawer.getWidth();
let width = nominalWidth;
// always start at 0 after zooming for scrolling : issue redraw left part
let start = 0;
let end = Math.max(start + parentWidth, width);
// Fill container
if (
this.params.fillParent &&
(!this.params.scrollParent || nominalWidth < parentWidth)
) {
width = parentWidth;
start = 0;
end = width;
}
let peaks;
if (this.params.partialRender) {
/* something */
} else {
peaks = this.backend.getPeaks(width, start, end);
this.drawer.drawPeaks(peaks, width, start, end);
}
this.fireEvent('redraw', peaks, width);
}
復制代碼
~~~
以上代碼來自 [wavesurfer github 源碼](https://github.com/katspaugh/wavesurfer.js/blob/832e114b7be6436458fc351a57699ba169d08676/src/wavesurfer.js#L1130-L1183),可以看到的是 wavesurfer 在 draweBuffer 過程中得到當前正確的 peaks 信息,是根據當前的渲染容器寬度、minPxPerSec、pixelRatio和資源時長來控制 getPeaks 方法的 width、start、end參數,然后調用 drawer 的 drawPeaks 方法繪制。
根據以上分析以及我們的需求僅僅是得到 peaks 波形信息,所以我們需要借用上面的代碼擴展一下 wavesurfer 方法:
~~~
// 擴展到 WaveSurfer 構造器中方法
// 每一段音頻 buffer 產生 peaks 方法
getPeaks(arraybuffer, callback) {
this.backend.decodeArrayBuffer(
arraybuffer,
buffer => {
if (!this.isDestroyed) {
// https://github.com/katspaugh/wavesurfer.js/blob/832e114b7be6436458fc351a57699ba169d08676/src/wavesurfer.js#L1395-L1396
// decodeArrayBuffer 之后的一個賦值、置空操作。完全模仿
this.backend.buffer = buffer;
this.backend.setPeaks(null);
const nominalWidth = Math.round(
this.getDuration() *
this.params.minPxPerSec *
this.params.pixelRatio
);
const parentWidth = this.drawer.getWidth();
let width = nominalWidth;
let start = 0;
// 此處謹記 end 一定要賦值為 width
// 原本的 let end = Math.max(start + parentWidth, width) 是比較了容器寬度和根據音頻時長等計算出的長度,取最大值。
// 那么會在當前的音頻分段時長大小(例子是2M音頻的時長)所能產生的波形長度小于容器的寬度時
// 出現為了充滿容器下面的 this.backend.getPeaks 方法在實際產生的波形信息后面添加不等位數的 0,從而充滿容器。
// 但是整個大音頻的時長是固定的,根據大音頻時長設定的canvas的個數和寬度已經固定
// 如果分段加載之后最后一段如果出現被補0的情況,在最終合并的完整的波形信息就會超過原本設定的預值,導致擠壓最終產生的波形
let end = width;
if (
this.params.fillParent
&& (!this.params.scrollParent || nominalWidth < parentWidth)
) {
width = parentWidth;
}
const peaks = this.backend.getPeaks(width, start, end);
// 通過回調函數的方式把 peaks 傳遞出去
callback(peaks);
// 清空 arraybuffer 避免占用過多內存
this.arraybuffer = null;
this.backend.buffer = null;
}
},
() => this.fireEvent('error', 'Error decoding audiobuffer')
);
}
// 加載所有的波形信息產生可視化canvas
loadPeaks(peaks) {
this.backend.buffer = null;
this.backend.setPeaks(peaks);
this.drawBuffer();
this.fireEvent('waveform-ready');
this.isReady = true;
}
復制代碼
~~~
增強了 wavesurfer 的能力之后就需要在業務中調用了~~
#### 調用擴展能力,整合波形信息,渲染并上傳保存
~~~
import _ from 'lodash';
import pako from 'pako'; // JS壓縮以及解壓縮三方庫
import WaveSurfer from 'wavesurfer.js';
import requestWav from 'requestWav';
const waveSurfer = null;
const peaksList = [];
const texture = null;
function initWaveSurfer() {
const options = {
container: '#waveform',
backend: 'MediaElement',
fillParent: false, // 重要
height: 200,
barHeight: 10,
normalize: true,
minPxPerSec: 100,
}
waveSurfer = WaveSurfer.create(options);
renderWaveSurfer();
}
function renderWaveSurfer() {
waveSurfer.load(source, [], 'none');
if (!texture) {
decodePeaks();
}
}
function decodePeaks() {
const that = this;
requestWav.loadBlocks('音頻Url', {
loadRangeSucess(data, index) {
// 每一段加載完成之后的回調
peaksList[index - 1] = [];
// 調用擴展的 waveSurfer 方法獲取每一段音頻的 peaks
waveSurfer.getPeaks(data, (peaks) => {
peaksList[index - 1] = peaks;
});
},
loadAllSucess() {
// 所有都加載完之后的回調
let texture = _.flatten(peaksList); // peaksList 降維
if (!texture) {
return;
}
// 按照一定等級進行壓縮 (減少傳輸時間,但是同時需要之后在下載使用波形信息的時候解壓)
waveSurfer.texture = pako.deflate(JSON.stringify(texture), { level: 9, to: 'string' });
// 解壓和壓縮的方法是相反的
// const texture = pako.deflate(JSON.stringify(waveSurfer.texture), { level: 9, to: 'string' });
// 手動置空變量, 避免占用內存過大
texture = null;
// 創建上傳 FormData
const peaksFile = new FormData();
peaksFile.append('sourceUrl', 音頻的URL地址);
// 創建上傳文件 Blob
const blob = new Blob([waveSurfer.texture], { type: 'application/json' });
// 賦值文件名、文件內容
peaksFile.append('sourcePeaks', blob, 'sourcePeaks');
axios({
method: 'post',
url: '上傳地址',
data: peaksFile,
headers: {
'Content-Type': 'multipart/form-data',
},
timeout: 1000000, // 防止文件過大上傳超時,當然不設置也可
});
},
});
}
復制代碼
~~~
至此 5個步驟全部完成,剩下的只有在二次請求相同資源的時候判斷如果已經存儲了當前wav資源的波形信息就不用再一次執行一次 decode 產生波形的操作。
作者:ThoughtZer
鏈接:https://juejin.im/post/5d481cf25188250586752b22
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。