市面上實(shí)現(xiàn)音頻播放器的庫(kù)有很多,比如wavesurfer.js、howler.js等等,但是都不支持大音頻文件處理,100多M的文件就有可能導(dǎo)致程序崩潰。總之和我目前的需求不太符合,所以打算自己實(shí)現(xiàn)一個(gè)音頻播放器,這樣不管什么需求 在技術(shù)上都可控。下面我們簡(jiǎn)單介紹下
wavesurferJs
、和howlerJs
的實(shí)現(xiàn),然后再講解如何利用audio API實(shí)現(xiàn)自定義語(yǔ)音播放器。
具體資源github下載
wavesurferJs
一開始選擇wavesurferJs
主要是因?yàn)樗囊纛l圖功能。
效果如下:
是不是很漂亮 hh
下面是實(shí)現(xiàn)步驟:
- 初始化
this.playWavesurfer = WaveSurfer.create({
container: '#waveform2',
mediaType: 'audio',
height: 43,
scrollParent: false,
hideScrollbar: true,
waveColor: '#ed6c00',
interact: true,
progressColor: '#dd5e98',
cursorColor: '#ddd5e9',
interact: true,
cursorWidth: 1,
barHeight: 1,
barWidth: 1,
plugins: [
WaveSurfer.microphone.create()
]
});
- 動(dòng)態(tài)加載音頻地址
this.playWavesurfer.load(this.audioUrl);
- 設(shè)置加載loading和完畢后計(jì)算音頻總時(shí)長(zhǎng)
this.playWavesurfer.on('loading', (percent, xhr) => {
this.audioLoadPercent = percent - 1;
})
this.playWavesurfer.on('ready', () => {
this.audioLoading = false;
const duration = this.playWavesurfer.getDuration();
this.duration = this.formatTime(duration);
this.currentTime = this.formatTime(0);
})
- 播放中計(jì)算時(shí)長(zhǎng)
this.playWavesurfer.on('audioprocess', function () {
const duration = that.playWavesurfer.getDuration();
const currentTime = that.playWavesurfer.getCurrentTime();
that.currentTime = that.formatTime(currentTime);
that.duration = that.formatTime(duration);
if (that.currentTime === that.duration) {
that.audioPlayingFlag = false;
}
});
- 播放、暫停
this.playWavesurfer.playPause.bind(this.playWavesurfer)();
- 快進(jìn)、快退
this.playWavesurfer.skip(15);
//this.playWavesurfer.skip(-15);
- 倍數(shù)播放
this.playWavesurfer.setPlaybackRate(value, true);
這樣基本功能大概實(shí)現(xiàn)。
利用howlerJs實(shí)現(xiàn)
- 初始化、動(dòng)態(tài)加載音頻路徑
this.howler = new Howl({
src: [this.audioUrl]
});
- 加載完畢計(jì)算音頻總時(shí)長(zhǎng)
this.howler.on('load', () => {
this.audioLoading = false;
const duration = this.howler.duration();
this.duration = this.formatTime(duration);
this.currentTime = this.formatTime(0);
});
- 播放中獲取當(dāng)前時(shí)間
this.currentTime = this.formatTime(this.howler.seek());
- 播放完畢
this.howler.on('end', () => {
this.audioPlayingFlag = false;
this.siriWave2.stop();
this.currentTime = "00:00:00";
this.progressPercent = 0;
cancelAnimationFrame(this.playTimer);
})
- 快進(jìn)、快退
this.howler.seek(this.howler.seek() + 15);
//this.howler.seek(this.howler.seek() - 15);
- 設(shè)置倍數(shù)播放
this.howler.rate(value);
- 播放、暫停
this.howler.play();
// this.howler.pause();
- 手動(dòng)定位播放時(shí)長(zhǎng)
<div id="waveform2" ref="waveform2" @click="changProgress">
<div class="bar" v-if="!audioLoading&&!audioPlayingFlag"></div>
<div class="progress" :style="{width: `${progressPercent}`}"></div>
</div>
changProgress(e) {
if (this.howler.playing()) {
this.howler.seek((e.offsetX / this.$refs['waveform2'].offsetWidth)*this.howler.duration());
}
},
這樣基本功能大概實(shí)現(xiàn)。
利用audio API實(shí)現(xiàn)播放器
效果圖:
動(dòng)畫庫(kù) 暫時(shí)用的 siriwave.js
先定義audio標(biāo)簽隱藏,可以js里面動(dòng)態(tài)生成
<audio :src="audioUrl" style="display: none;" controls ref="audio"></audio>
this.audio = this.$refs['audio'];
- 獲取音頻url后 動(dòng)態(tài)加載 需要load一下
this.audio.load();
- 音頻加載完畢
this.audio.addEventListener("canplaythrough", () => {
this.audioLoading = false;
console.log('music ready');
}, false);
- 監(jiān)聽可以播放后 計(jì)算音頻時(shí)長(zhǎng)
this.audio.addEventListener("canplay", this.showTime, false);
showTime() {
if (!isNaN(this.audio.duration)) {
this.duration = this.formatTime(this.audio.duration);
this.currentTime = this.formatTime(this.audio.currentTime);
}
},
- 播放中 時(shí)間改變計(jì)算當(dāng)前 時(shí)間
this.audio.addEventListener("timeupdate", this.showTime, true);
- 監(jiān)聽播放事件
this.audio.addEventListener('play', () => {
this.audioPlaying();
}, false);
- 播放完畢
this.audio.addEventListener('ended', () => {
this.audioPlayingFlag = false;
this.siriWave2.stop();
this.currentTime = "00:00:00";
this.progressPercent = 0;
cancelAnimationFrame(this.playTimer);
}, false)
- 前進(jìn)、后退
this.audio.currentTime += 15;
// this.audio.currentTime -= 15;
- 設(shè)置播放倍數(shù)
this.audio.playbackRate = value;
- 播放、暫停
this.audio.play();
// this.audio.pause();
- 音頻定位
<div id="waveform2" ref="waveform2" @click="changProgress">
<div class="bar" v-if="!audioLoading&&!audioPlayingFlag"></div>
<div class="progress" :style="{width: `${progressPercent}`}"></div>
</div>
計(jì)算 定位時(shí)長(zhǎng)
changProgress(e) {
// if (this.audioPlayingFlag) {
this.audio.currentTime = (e.offsetX / this.$refs['waveform2'].offsetWidth)*this.audio.duration;
this.progressPercent = ((this.audio.currentTime/this.audio.duration) * 100) + '%';
// }
},
- siri動(dòng)畫實(shí)現(xiàn)
this.siriWave = new SiriWave({
container: that.$refs['waveform'],
height: 43,
cover: true,
color: '#ed6c00',
speed: 0.03,
amplitude: 1,
frequency: 6
});
開啟動(dòng)畫、停止動(dòng)畫
this.siriWave.start();
// this.siriWave.stop();
這樣基本功能大概實(shí)現(xiàn)。 即使加載再大音頻文件也不會(huì)卡。
踩坑
這里遇到一個(gè)大坑就是 audio自帶,音頻播放定位功能,在外面瀏覽器和vscode主體代碼里面都可以定位,偏偏我在vscode插件里面不可以定位,會(huì)自動(dòng)歸0。翻遍了文檔在MDN上找到這樣一段描述:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Configuring_servers_for_Ogg_media#Handle_HTTP_1.1_byte_range_requests_correctly
Handle HTTP 1.1 byte range requests correctly
In order to support seeking and playing back regions of the media that aren’t yet downloaded, Gecko uses HTTP 1.1 byte-range requests to retrieve the media from the seek target position. In addition, Gecko uses byte-range requests to seek to the end of the media (assuming you serve the Content-Length
header) in order to determine the duration of the media.
Your server should accept the Accept-Ranges
: bytes HTTP header if it can accept byte-range requests. It must return 206: Partial content to all byte range requests; otherwise, browsers can’t be sure you actually support byte range requests.
Your server must also return 206: Partial Content for the request Range: bytes=0- as well.
經(jīng)驗(yàn)證是和response header
有關(guān)的。我通過對(duì)MP3資源set不同的response header
來驗(yàn)證,結(jié)果如下(貌似segmentfault不支持markdown的表格,所以下面排版有點(diǎn)亂。):
ieContent-Type
必須,當(dāng)我設(shè)為audio/mpeg時(shí)才能播放,設(shè)為application/octet-stream不能。Content-Length
必須。和Accept-Ranges無(wú)關(guān)。
chromeContent-Type
無(wú)關(guān),設(shè)為application/octet-stream
可以播放。Content-Length
,Accept-Ranges
必須都有才可更改 currentTime。
也就是說ie需要response header 有正確的Content-Type
,Content-Length
。
chrome需要頭部有Content-Length
和Accept-Ranges
。
然后我想到 vscode插件系統(tǒng)使用了 Service Worker
const headers = {
'Content-Type': entry.mime,
'Content-Length': entry.data.byteLength.toString(),
'Access-Control-Allow-Origin': '*',
};
果然 沒有添加 Accept-Ranges字段
const headers = {
'Content-Type': entry.mime,
'Content-Length': entry.data.byteLength.toString(),
'Access-Control-Allow-Origin': '*',
};
/**
* @author lichangwei
* @description 音頻額外處理 否則無(wú)法調(diào)節(jié)進(jìn)度
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Configuring_servers_for_Ogg_media#Handle_HTTP_1.1_byte_range_requests_correctly
*/
if (entry.mime === 'audio/mpeg') {
headers['Accept-Ranges'] = 'bytes';
}
添加后就可以使用了。
后續(xù)
看了一下印象筆記的語(yǔ)音筆記實(shí)現(xiàn)。
印象筆記是如何避免大文件處理的呢
- 首先錄音過程中繪制的是真的音頻線
- 錄音結(jié)束后是用假的音頻線替代的
其實(shí)錄制過程中實(shí)時(shí)處理音頻數(shù)據(jù)還好,不至于導(dǎo)致瀏覽器崩潰,錄制后生成的音頻文件處理數(shù)據(jù)量太大了,內(nèi)存直接飆升2-3G,所以會(huì)導(dǎo)致程序崩潰文章來源:http://www.zghlxwxcb.cn/news/detail-496157.html
后續(xù)實(shí)現(xiàn)音頻圖文章來源地址http://www.zghlxwxcb.cn/news/detail-496157.html
兄弟萌給個(gè)關(guān)注~
到了這里,關(guān)于Audio API 實(shí)現(xiàn)音頻播放器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!