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

实现效果:

未加载部分:

后端接口描述:

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

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

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

html布局

<div class="wave-wrapper"  ref="waveWrapper" >
<div
class="wave-container"
:style="{width: waveContainerWidth=='100%'?'100%':waveContainerWidth+'px'}"
@click="containerClick($event)"
>
<div
id="waveform"
ref="waveform"
class="wave-form"
:style="{width: waveFormWidth=='100%'?'100%':waveFormWidth+'px',left: waveFormLeft+'px'}"
@click.stop
></div>
</div>
</div>

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

// 音频宽:防止音频过短,渲染不完
let dWidth = 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) / 8
that.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) {
let that = this;
let xhr = new XMLHttpRequest();
let reqUrl = location.origin;
xhr.open(
"GET",
`${reqUrl}/storage/api/audio/${this.audioInfo.code}`,
true
);
xhr.responseType = "arraybuffer"; let startBit = this.rangeBit * (segNumber - 1);
let endBit = this.rangeBit * segNumber;
xhr.setRequestHeader("Range", `bytes=${startBit}-${endBit}`); xhr.onload = function() {
if (this.status === 206 || this.status === 304) {
let type = xhr.getResponseHeader("Content-Type");
let blob = new Blob([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();
} else if (!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方法中(主要代码):

        // 记录当断的位置
let pools = that.blobPools; // 第一段
if (currentSeg == 1) {
pools[currentSeg].startPos = 0;
pools[currentSeg].endPos = that.waveFormWidth;
// 预加载第二段
if (segNumbers > 1) {
that.getAudioSeg(2, true);
}
} else if (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方法中做处理(主要代码):

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

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

    /**
* 根据段设置容器的位置,保证波纹在可见区域
* @param segNumber 请求段
*/
setScrollPos(segNumber) {
let n = segNumber ? segNumber : this.currentSeg;
let segNumbers = this.segNumbers;
let end = 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;
}
// 点击的位置记录
let layerX = e.layerX; // 记录当前鼠标点击的绝对位置
let scrollLeft = this.$refs.waveWrapper.scrollLeft;
this.clickWrapperPos = layerX - scrollLeft; // 获取点击的时间点
let currentTime = parseInt(layerX / 20); // 获取字节所在
let { size, duration, bitrate } = this.audioInfo;
let currentBit = (bitrate * currentTime) / 8;
let seg = Math.ceil(currentBit / this.rangeBit); // 因为音乐的动态性,所以请求的段数会存在误差,这个时候更改请求的段数
if (seg == this.currentSeg) {
// let currentMinTime = 60 * (this.currentSeg-1);
// let currentMaxTime = 60 * this.currentSeg;
let average = (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) {
let leftTime = parseFloat(that.waveFormScroll) / 20;
let moveTime = Math.abs(that.currentTime - leftTime);
that.wavesurfer.skip(moveTime); // 指针的位置移动到当时指的clickWrapperPos位置上,体验更好,这里不能改变波纹的位置,需要改变滚动条的位置
that.$nextTick(() => {
let movePos = moveTime * 20;
let disPos = that.clickWrapperPos - movePos;
// 左-
// 右+
let scrollLeft = 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;
});
}

具体的代码含义我就不解释了,好累啊主要是涉及到很多位置的计算。不过好在最后完美实现啦~

基于wavesurfer.js的超大音频的渐进式请求实现的更多相关文章

  1. 应用wavesurfer.js绘制音频波形图小白极速上手总结

    一.简介 1.1  引   人生中第一份工作公司有语音识别业务,需要做一个web网页来整合语音引擎的标注结果和错误率等参数,并提供人工比对的语音标注功能(功能类似于TranscriberAG等),(博 ...

  2. Wavesurfer.js音频播放器插件的使用教程

    Wavesurfer.js是一款基于HTML5 canvas和Web Audio的音频播放器插件,本文主要记录它及其视觉效果插件Regions插件的使用方法. 1.创建实例 引入插件 import W ...

  3. 基于Vue JS, Webpack 以及Material Design的渐进式web应用 [Part 1]

    基于Vue JS, Webpack 以及Material Design的渐进式web应用 [Part 1] 原文:基于Vue JS, Webpack 以及Material Design的渐进式web应 ...

  4. vue解决音频可视化播放,使用wavesurfer.js

    vue解决音频可视化播放,使用wavesurfer.js 上效果:   1.安装wavesurfer  npm install wavesurfer.js 2.在页面导入 import WaveSur ...

  5. 基于 React.js + Redux + Bootstrap 的 Ruby China 示例 (转)

    一直学 REACT + METEOR 但路由部分有点问题,参考一下:基于 React.js + Redux + Bootstrap 的 Ruby China 示例 http://react-china ...

  6. 用jQuery基于原生js封装的轮播

    我发现轮播在很多网站里面都用到过,一个绚丽的轮播可以为网页增色不少,最近闲来无事,也用原生js封装了一个轮播,可能不像网上的插件那么炫,但是也有用心去做.主要用了闭包的思想.需要传递的参数有:图片地址 ...

  7. 基于Three.js的360X180度全景图预览插件

    基于Three.js的360X180度全景图预览插件 时间 2015-08-12 10:01:10  HTML5中国 原文  http://www.html5cn.org/article-8621-1 ...

  8. NodeBB – 基于 Node.js 的开源论坛系统

    NodeBB 是一个更好的论坛平台,专门为现代网络打造.它是免费的,易于使用. NodeBB 论坛软件是基于 Node.js 开发,支持 Redis 或 MongoDB 的数据库.它利用 Web So ...

  9. RSuite 一个基于 React.js 的 Web 组件库

    RSuite http://rsuite.github.io RSuite 是一个基于 React.js 开发的 Web 组件库,参考 Bootstrap 设计,提供其中常用组件,支持响应式布局. 我 ...

随机推荐

  1. 博客搬家到blog.wu8685.com

    博客园算是我最开始来的地方了吧,当时还在学校,为了找工作会看一些理论方面的东西,所以写的都是偏理论的心得. 后来参加了工作,开始忙起来,也就没有时间来更新了.其实忙都是借口,这点还是需要反思的. 大概 ...

  2. ubuntu系统下安装pyspider:解决pyspider启动时不启动phantomjs问题

    问题描述: 在建立第一个虚拟环境时,运行pyspider正常.建立第二个虚拟环境时,运行pyspider再现下面错误.应该是phantomjs没有启动成功. 错误代码:(phantomjs:21507 ...

  3. Java设计模式(7)——装饰者模式

    转载:http://blog.csdn.net/yanbober/article/details/45395747 一.装饰者模式的定义 装饰者( Decorator )模式又叫做包装模式.通过一种对 ...

  4. sqlserver怎么将excel表的数据导入到数据库中

    在数据库初始阶段,我们有些数据在EXCEL中做好之后,需要将EXCEL对应列名(导入后对应数据库表的字段名),对应sheet(改名为导入数据库之后的表名)导入指定数据库, 相当于导入一张表的整个数据. ...

  5. 23 DesignPatterns学习笔记:C++语言实现 --- 2.7 Proxy

    23 DesignPatterns学习笔记:C++语言实现 --- 2.7 Proxy 2016-07-18 (www.cnblogs.com/icmzn) 模式理解

  6. 洛谷 P1967 货车运输(克鲁斯卡尔重构树)

    题目描述 AAA国有nn n座城市,编号从 11 1到n nn,城市之间有 mmm 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在有 qqq 辆货车在运输货物, 司机们想知道每辆车在不超过车 ...

  7. 用TextWriterTraceListener实现log文件记录 (转载)

    log4net之类3方组件确实很方便,但是想写个小小的demo之类的程序,有点用不起啊. 微软自带的TraceListener要实现一个简易的日志帮助类还是很简单的,直接上代码,自己备用,也希望对同样 ...

  8. CDH源代码

    CDH Packaging and Tarball Information http://www.cloudera.com/content/cloudera/en/documentation/core ...

  9. pycharm中安装可以贴图片的Markdown插件

    方法一:(测试成功) 先安装官方推荐的Markdown support插件,再安装Paste images into MarkDown 如果Paste images into MarkDown插件在线 ...

  10. layou split 属性

    layou split:true -  显示侧分栏