基于wavesurfer.js的超大音频的渐进式请求实现(转)

最近在对超大音频的渐进式请求实现上面消耗了不少时间,主要是因为一对音频的基本原理不太理解,二刚开始的时候太依赖插件,三网上这块的资料找不到只能靠自己摸索。由于交互复杂加上坑比较多,我怕描述不清,这里主要根据问题来做描述(前提你需要对wavesurfer.js有一定的了解)我的这篇博客有做说明:Wavesurfer.js音频播放器插件的使用教程

实现效果:

未加载部分:

后端接口描述:

a、音频主要信息接口:获取总时长、字节数、总字节、音频格式等。

b、 分段请求接口:根据字节参数,传来对应段的音频。

1、如何设置容器的长度,及滚动条的设置?

html布局

waveform是wavesurfer渲染实际分段音频的容器,waveWrapper是音频的容器,这里追溯wavesurfer的源码,可以知道它对音频的解析是 **像素值=秒数*20**;因此从后端获取总时长后,设置waveContainerWidth即可。样式设置为overflow-y:auto。

// 音频宽:防止音频过短,渲染不完letdWidth =Math.round(that.duration *20);that.waveContainerWidth = that.wrapperWidth > dWidth ? that.wrapperWidth : dWidth;

2、音频分几段请求?

// 后台传入的音频信息存储that.audioInfo = res;// 音频时长that.duration =parseInt(res.duration /1000000);// 如果音频的长度大于500s分段请求,每段100s// 1分钟的字节数[平均] = 比特率(bps) * 时长(s)  /  8that.rangeBit =  that.duration >500? (that.audioInfo.bitrate * that.rangeSecond) /8: that.audioInfo.size;// 总段数that.segNumbers =Math.ceil(that.audioInfo.size / that.rangeBit);

3、如何请求音频文件,如何实现预加载?

wavesurfer.js渲染音频的方式之一是根据WebAudio来渲染的。由于后端传给我的文件是arraybuffer的格式,那么这里就需要使用WebAudio读取和解析buffer文件的功能,这些wavesurfer.js内部已实现。只需要将文件传给它就可以了。这里我采用了预加载功能,即每加载一段音频就绘制当段的音频,但同时请求并缓存下一段音频。

/**

    * 获取音频片段

    * @param segNumber 加载第几段

    * @param justCache 仅仅缓存 true 仅缓存不加载

    * @param initLoad 初始加载

    */getAudioSeg(segNumber, justCache, initLoad) {letthat =this;letxhr =newXMLHttpRequest();letreqUrl = location.origin;      xhr.open("GET",`${reqUrl}/storage/api/audio/${this.audioInfo.code}`,true);      xhr.responseType ="arraybuffer";letstartBit =this.rangeBit * (segNumber -1);letendBit =this.rangeBit * segNumber;      xhr.setRequestHeader("Range",`bytes=${startBit}-${endBit}`);      xhr.onload =function(){if(this.status ===206||this.status ===304) {lettype = xhr.getResponseHeader("Content-Type");letblob =newBlob([this.response], {type: type });// 转换成URL并保存that.blobPools[segNumber] = {url: URL.createObjectURL(blob)          };// 第一次加载第一段,并对播放器事件进行绑定if(initLoad) {            that.wavesurfer.load(that.blobPools[segNumber].url);            that.currentSeg =1;// 音频事件绑定that.wavesurferEvt();          }elseif(!justCache) {            that.currentSeg = segNumber;            that.wavesurfer.load(that.blobPools[segNumber].url);          }// 滚动条的位置随着加载的位置移动if(!justCache && that.segNumbers >1) {            that.setScrollPos(segNumber);          }        }      };      xhr.onerror =function(){        that.$message.error("音频加载失败,请重试");        that.progress =false;      };      xhr.send();    }

4、需要绘制的音频怎么创建?

this.wavesurfer = WaveSurfer.create({container:  that.$refs.waveform,waveColor:"#368666",//波纹progressColor:"#6d9e8b",hideScrollbar:false,隐藏波纹的横坐标  cursorColor:"#fff",height:80,responsive:true,scrollParent:true,maxCanvasWidth:50000// canvas的最大值})

5、位置问题(主要的坑都在这里)

1、实际请求获取的音频文件大小跟预计的大小并不是完全符合的。比如我每次想请求100s的视频,根据字节公式算出来字节了,但是实际获取到的音频可能是98s也可能是102s。对应段的音频位置怎么放?这里是在缓存音频文件的时候记录了波纹的实际位置:在wavesurfer的ready方法中(主要代码):

// 记录当断的位置letpools = that.blobPools;// 第一段if(currentSeg ==1) {          pools[currentSeg].startPos =0;          pools[currentSeg].endPos = that.waveFormWidth;// 预加载第二段if(segNumbers >1) {            that.getAudioSeg(2,true);          }        }elseif(currentSeg == that.segNumbers) {// 最后一段pools[currentSeg].startPos =            that.waveContainerWidth - that.waveFormWidth;          pools[currentSeg].endPos = that.waveContainerWidth;console.log(pools);          that.setScrollPos();        }else{// 其他段that.getAudioSeg(currentSeg +1,true);if(pools[currentSeg -1] && pools[currentSeg -1].endPos) {            pools[currentSeg].startPos = pools[currentSeg -1].endPos;            pools[currentSeg].endPos =              pools[currentSeg].startPos + that.waveFormWidth;          }        }

2、我的可见区域就那么大,如果音频绘制的波形大于可见区域,如何在播放的时候自动设置滚动条的位置,把播放的区域显示出来;这里就要在wavesurfer的audioprocess方法中做处理(主要代码):

// 表示的是前面实际播放的letleftTime = that.waveFormScroll            ?parseFloat(that.waveFormScroll) /20:0;// 当前实际的时间that.currentTime =parseInt(res + leftTime);// wave移动的距离letmoveDis =Math.round(res *20);// 滚动条的实际位置letscrollLeft = that.$refs.waveWrapper.scrollLeft;letwaveFormLeft = that.waveFormLeft;letwaveFormWidth = that.waveFormWidth;//waveletwrapperWidth = that.wrapperWidth;// 第一段的时候 moveDis - scrollLeft;// 第二段 waveFormLeft-scrollLeft+moveDisletactualDis;if(waveFormLeft ==0) {            actualDis = moveDis - scrollLeft;          }else{            actualDis = waveFormLeft - scrollLeft + moveDis;          }// 大于位置if(actualDis === wrapperWidth) {letdis =              moveDis >= wrapperWidth                ? waveFormWidth - moveDis                : wrapperWidth - moveDis;            that.$refs.waveWrapper.scrollLeft = scrollLeft + dis;          }

3、加载对应段的时候,如何把渲染出来的波纹放在可视区域?这里写了个公用方法

/**

    * 根据段设置容器的位置,保证波纹在可见区域

    * @param segNumber 请求段

    */setScrollPos(segNumber) {letn = segNumber ? segNumber :this.currentSeg;letsegNumbers =this.segNumbers;letend =this.blobPools[n -1] &&this.blobPools[n -1].endPos;// 最后一段,这里是一个hack,为了防止误差if(n === segNumbers &&this.blobPools[n] &&this.blobPools[n].startPos) {        end =this.blobPools[n].startPos;      }this.waveFormScroll = end ? end : (n -1) *this.wrapperWidth;this.waveFormLeft =this.waveFormScroll;this.$refs.waveWrapper.scrollLeft =this.waveFormScroll;    }

4、当鼠标随机点击未加载音频的位置时,如何保持加载的波纹位置并将波纹的位置进行移动,保证波纹加载后鼠标还在点击的位置上?

/**

    * 随机点击容器

    * @param e 点击的容器e

    */containerClick(e) {if(this.segNumbers ==1||this.progress) {return;      }// 点击的位置记录letlayerX = e.layerX;// 记录当前鼠标点击的绝对位置letscrollLeft =this.$refs.waveWrapper.scrollLeft;this.clickWrapperPos = layerX - scrollLeft;// 获取点击的时间点letcurrentTime =parseInt(layerX /20);// 获取字节所在let{ size, duration, bitrate } =this.audioInfo;letcurrentBit = (bitrate * currentTime) /8;letseg =Math.ceil(currentBit /this.rangeBit);// 因为音乐的动态性,所以请求的段数会存在误差,这个时候更改请求的段数if(seg ==this.currentSeg) {// let currentMinTime = 60 * (this.currentSeg-1);// let currentMaxTime = 60 * this.currentSeg;letaverage = (120*this.currentSeg -this.rangeSecond) /2;        seg = currentTime > average ? seg +1: seg -1;      }this.currentTime = currentTime;// 有缓存数据this.progress =true;if(this.blobPools[seg]) {// 加载缓存数据this.wavesurfer.load(this.blobPools[seg].url);// 更改当前的播放段数this.currentSeg = seg;this.setScrollPos();        }else{this.getAudioSeg(seg);        }// 记录这是点击请求的波纹,在波纹的ready方法中做处理this.fromSeek =true;      }    }

ready方法中加入处理:

// 点击来的if(that.fromSeek) {letleftTime =parseFloat(that.waveFormScroll) /20;letmoveTime =Math.abs(that.currentTime - leftTime);          that.wavesurfer.skip(moveTime);// 指针的位置移动到当时指的clickWrapperPos位置上,体验更好,这里不能改变波纹的位置,需要改变滚动条的位置that.$nextTick(()=>{letmovePos = moveTime *20;letdisPos = that.clickWrapperPos - movePos;// 左-// 右+letscrollLeft = that.$refs.waveWrapper.scrollLeft;if(disPos >0) {              that.$refs.waveWrapper.scrollLeft = scrollLeft - disPos;            }else{              that.$refs.waveWrapper.scrollLeft = scrollLeft +Math.abs(disPos);            }            that.fromSeek =false;            that.clickWrapperPos =0;          });        }

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML标准。 注意:讲述HT...
    kismetajun阅读 28,878评论 1 45
  • 今日体验: 早上一客户打电话说一会过来检查一下,好像有漏油的地方,接车后发现没啥大问题,摸了点胶处理了一下,并且给...
    bbaf70c1705c阅读 211评论 0 0
  • 洋葱没有洋 猫空没有猫 而我没有你
    Anarkh_Mayday阅读 166评论 0 0
  • 今天带着孩子随姐姐一起去给奶奶和四舅拜年,有时孩子比较闹,就直接把手机丢给他,可让他玩美了。 回家的路上,他在车里...
    迷鹿_迷路阅读 245评论 0 2
  • 1、每天健走5公里。完成6/7,七号下夜班没有走。❎ 2、每天奇迹课程学习。OK。✅不够深入,流于形式。下周改进。...
    盛蓝阅读 156评论 0 0

友情链接更多精彩内容