多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
在[上一篇](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 来源:掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。