個人blog 原文連接
最近公司迭代的項目中,新增了音頻播放功能,填了很多音頻的坑,總結一下:vue
交互需求:ios
固然還有一些隱性需求:git
實現效果以下圖所示:(固然,這只是項目用到的一部分,項目中還有其餘頁面也用到了這個組件,那麼就更考驗組件的健壯性和可拓展性了。)github
咱們對音頻的操做,一般是先獲取這個音頻 DOM Element,經過對它的操做,實現想要達到的效果,若是你只是設定一個audio
這樣單薄的ref名稱,恐怕會有些問題,所以我給每一個音頻設定了一個惟一的ref名稱。chrome
<template> <audio :src="audioSrc" :ref='uniqueId' :data-key="uniqueId" hidden></audio> </template> <script> export default { data () { return { // uniqueId() 是隨機生成字符串的方法 uniqueId: uniqueId(), audioSrc: '', } }, computed: { audioElement () { return this.$refs[this.uniqueId] } }, } </script>
注意到上面的代碼,我在給 audio 添加屬性的時候,多添加了一個 data-key
的屬性,那就是爲了暫停其餘語音而使用的,做爲我要操做頁面其餘音頻而設置的標識:segmentfault
// 暫停其餘語音的方法 pauseOthers (except) { var audios = document.querySelectorAll('audio') ;[].forEach.call(audios, audio => { if (audio.dataset['key'] !== except.uniqueId) { audio.pause() } }) } // 調用的時候 this.pauseOthers (this)
我想這就是項目坑點之一,由於音頻src並非上傳語音就返回的,上傳語音只返回了語音id,咱們須要經過id再去異步請求一次,才能獲取到src。後端
基於這樣的前提,播放操做作了兩點考慮(單例模式思惟):promise
具體實現:瀏覽器
togglePlay()
事件判斷audioSrc
是否有值bash
audioSrc
,監聽音頻 canplay
canplay
(這邊有一個坑點,後面會提到)爲何相關事件的綁定放在 canplay
中? 否則你可能會出現下面的報錯:
Uncaught (in promise) DOMException: The element has no supported sources.
因此,答應我,基於audio播放的 事件 或是 屬性 ,都放在 canplay
的回調以後。
相關事件包括(本組件中):
timeupdate
: 控制進度條展現pause
: 監聽按鈕 播放/暫停 樣式currentTime
: 控制進度拖動或者中止語音error
: 監聽播放錯誤按鈕的樣式經過設置一個變量做爲狀態值,paly()
和 pause()
的時候分別改變狀態值。
其它具體邏輯上文描述比較清楚,再也不贅述。
currentTime
設置爲0currentTime
值,並設置currentTime
兩個操做都涉及到了currentTime
的設置,咱們在這裏遇到了兩個坑:
設置currentTime繼續播放
一開始仍然覺得是後端的鍋,由於當我靜態設置一個 audioSrc 的時候,是沒有問題的,可是當我動態設置,就會出現這樣的問題:不管我是播放狀態仍是暫停狀態,設置到相對應的currentTime
都會繼續播放。
經過排查,發現當我設置currentTime
會再次觸發一次 canplay
事件, canplay
的回調是綁定播放的相關操做,所以會繼續播放。
解決辦法,溫習了一遍addEventListener的語法,綁定canplay
事件最多隻調用一次。
this.audioElement.addEventListener('canplay', () => { // ...相關操做 }, { once: true })
音頻的打斷包括兩種狀況:
destroyed
第一種狀況,解綁相關事件,釋放內存。
第二種狀況,具體描述一下:
當用戶從新上傳新的語音,不論此時語音暫停仍是播放狀態,都應該中止。
咱們經過 watch
監聽 id (上傳返回來的音頻id),當id變化的時候,將 audioSrc
清空,以避免播放舊的音頻內容。
然而,僅僅這樣是不夠的,若是監聽 error
事件,就會發現報錯,解決的辦法仍是解綁相關事件,即,咱們在 canplay
回調中的綁定的相關事件,讓audio恢復初始狀態,等到下一次播放的時候,須要從新請求新的src,回到上面播放的部分。
在解決問題的過程當中,也查詢到了一些實用的知識點,雖然在應用中沒有運用到,可是這些知識點看起來彷佛挺有用的,爲了下次遇到其餘坑能快速找到解決辦法,先把這些知識點記錄下來。
canplay
與 canplaythrough
辨析canplay
事件。canplaythrough
事件。HTMLMediaElement.play()
返回 Promise<video>
或 <audio>
的 play()
返回一個 Promise
,若是播放成功,Promise狀態變成fulfilled
,若是播放失敗,狀態變爲rejected
並提供錯誤信息。
var playPromise = document.querySelector('video').play(); // In browsers that don’t yet support this functionality, // playPromise won’t be defined. if (playPromise !== undefined) { playPromise.then(function() { // Automatic playback started! }).catch(function(error) { // Automatic playback failed. // Show a UI element to let the user manually start playback. }); }
412 通常是由於服務器的 If-Unmodified-Since 或 If-None-Match 未實現
// 解決辦法 media.addEventListener('error', function (e) { var date = new Date(); var milliSecs = date.getMilliseconds(); var curr_src = $(media[0]).attr('src'); var curr_src_arr = curr_src.split("?"); var new_src = curr_src_arr[0]+"?t="+milliSecs; $(media[0]).attr('src',new_src); $(media[0]).find('source').attr('src',new_src); media[0].load(); }, false);
暫時完。
後續若是測試妹妹發現了什麼bug,我會繼續填坑記錄滴。