昨天把播放器切版完成了 (灑花),不過在實作功能以前,先來談談一些基礎知識吧!
const audioPlayer = document.createElement('audio');
if (audioPlayer.canPlayType('audio/mpeg')) {
audioPlayer.setAttribute('src','audiofile.mp3');
}
audioPlayer.play();
audioPlayer.addEventListener('ended', () => console.log('audiofile play ended'));
<audio>
除了提供內建的播放器,我們也可以自己刻一個播放器,再使用 JavaScript 操作 <audio>
,幫我們在背景實現播放功能,對於需求單純、不牽涉到自適應 Stream 等情境就已足夠了。
我們使用 document 新建、取得的 <audio>
element ,實際上是一個 HTMLAudioElement,它是 HTMLMediaElement 的子類別,HTMLMediaElement 提供了一些方法與事件以便操作 element :
method & property | description | parameters | return |
---|---|---|---|
load() | 中斷所有進行中的事件,重置到初始狀態,重新進行音源選擇、讀取、準備從頭播放 | none | undefined |
play() | 開始播放 (resolve: 開始播放 / reject: 其他理由) | none | Promise |
pause() | 暫停播放 | none | none |
canPlayType() | 檢測瀏覽器是否支援該檔案格式 (MIME type) | MIME type 字串, ex: 'audio/mpeg' | "probably" / "maybe" / "" |
captureStream() | 取得即時串流 | none | MediaStream object |
currentTime | 取得、設定當前播放秒數 (sec) | ||
volume | 取得、設定當前音量 (0 - 1) |
load 實際上是重置播放器,並不是一個決定要載入哪一支音源的方法。想要設定音源路徑、決定音源的載入順序,就得使用 src
屬性與 <source>
標籤。
// src
const audioPlayer = document.createElement('audio');
audioPlayer.setAttribute('src', 'audiofile.mp3');
audioPlayer.load();
// source
const audioPlayer = document.createElement('audio');
const source1 = document.createElement('source')
.setAttribute('src', 'audiofile.wav')
.setAttribute('type', 'audio/wav');
const source2 = document.createElement('source')
.setAttribute('src', 'audiofile.mp3')
.setAttribute('type', 'audio/mpeg');
audioPlayer.appendChild(source1);
audioPlayer.appendChild(source2);
audioPlayer.load();
至於 load 會做哪些事情呢?
順序 | element 現況 | 處理 | 觸發的事件 |
---|---|---|---|
1 | 讀取音源中 | 中斷讀取 | abort |
1 | 已完成讀取音源 | 清空 buffer | emptied |
2 | 時間軸位置不在一開始 | 移動到最前面 | timeupdate |
3 | 完成初始化 | 重新 scan、select、load 音源 | loadstart |
4 | 後面與一般 Media Event 順序相同 |
這個方法用來詢問瀏覽器是否支援此檔案格式的播放,回傳值是字串。
value | description |
---|---|
'probably' | 此格式看起來可以播放 |
'maybe' | 不確定,要試播才知道 |
'' | 看起來不行播放 |
第一眼看到的時候不小心笑了,要試試看才知道!? 不曉得有什麼典故,竟然有這種曖昧不明的回應... XD
play 會回傳 Promise (舊版本瀏覽器不會回傳值),當 resolve 就代表開始播放,其他各種原因導致無法播放就會 reject。
const audioPlayer = document.createElement('audio').setAttribute('src', 'audiofile.mp3');
const status = doument.getElementById('player-status');
const btn = doument.getElementById('player-btn');
audioPlayer.load();
audioPlayer.play()
.then(() => {
btn.className = 'pause';
status.innerHTML = 'Playing';
})
.catch(e => {
console.error(e);
btn.className = 'play';
status.innerHTML = 'Stop';
});
至於 Promise reject 的情況,常見的有
error | description |
---|---|
NotAllowedError | 瀏覽器或 OS 不允許播放,像是不允許背景自動播放 |
NotSupportedError | 不支援此檔案格式 |
一般建議先用 canPlayType() 檢查是否支援再播放,以節省瀏覽器 request 數量與流量。
另外,想要取得、設定當前播放的位置,play() 沒辦法傳參數作控制,需要使用 currentTime 來處理。
// get
console.log(audioPlayer.currentTime);
// set
audioPlayer.currentTime = 121;
除了上述的方法以外,我們也可以監聽特定事件來作更細微的處理。
事件分成兩類:Loading 與 Playing。
這些是音源在讀取過程中會觸發的事件,監聽 Loading 事件可以幫助我們在 render 、enable/disable 播放器有更精準的控制。
trigger order | event | description |
---|---|---|
1 | loadstart | 一開始讀取程序 |
2 | durationchange | 讀取部分 metadata,包含音檔總時間,在這個事件之前 duration 都是 NaN |
3 | loadedmetadata | 所有 metadata 讀取完畢 |
4 | loadeddata | 收到音檔的第一個 bit 時觸發,但還沒準備好播放 |
5 | progress | 下載音檔中 |
6 | canplay | 下載的音檔資料量足以開始播放時觸發,仍尚未完全載完 |
7 | canplaythrough | 下載整個音檔完成 |
Loading 過程中,則可能會被這些事件打斷而中止
event | description |
---|---|
suspend | 暫停下載音源 |
abort | 終止下載音源 |
error | 錯誤 |
emptied | 發生錯誤 or load() |
stalled | 音源非預期的無法被取用 |
這些則是播放過程中會觸發的事件,搭配前面的 Method & Property 與監聽 Playing 事件,可以幫助我們處理各種 UI 呈現與互動功能,像是音量控制、時間軸拖移、播放/暫停/播放中斷/停止 ... 等。
event | description |
---|---|
timeupdate | currentTime 屬性改變時觸發,每 250 ms 觸發一次,常用於進度條呈現 |
waiting | 資料不足以繼續播放時觸發 |
playing | waiting 後取得資料足以繼續播放時觸發 |
play | play() 或 autoplay 發生時觸發 |
pause | pause() 發生時觸發 |
ended | 完全播放完畢時觸發 |
volumechange | 音量改變時觸發,包含調整 muted 屬性 |
更詳細的 Media Event,可以參考這份Media Event 列表。
如果想知道播放器常見的互動,分別會觸發哪些事件的話,可以玩玩看這個 Media Event Inspector。
前面介紹完了 HTMLMediaElement ,看著看著發現竟然又有 HTMLAudioElement ,這兩者的差別是什麼呢?事實上,影音本來就不是能完全分割的東西,因此設計規範與瀏覽器實作時, HTMLMediaElement 是 HTMLAudioElement 和 HTMLVideoElement 的父類別,他們的屬性、方法、事件絕大多數都相同,只有少部分相異。像是 <audio>
不支援字幕 WebVTT 的播放,但可以用 <video>
單純播放聲音與字幕一樣。
其實 <audio>
就是 HTMLAudioElement 的實現,而 HTMLMediaElement 和 HTMLAudioElement 幾乎長一樣,唯一的差別是在「指定載入音源」的方法。還記得前面有提到,HTMLMediaElement 只能用 src
、 <source>
指定音源 URI,而 HTMLAudioElement 可以多在 constructor 帶入音源 URI ,長得像下面這樣:
const audioPlayer = new Audio('audiofile.mp3');
audioPlayer.play();
如果需要動態改音源 URI ,修改 HTMLAudioElement 的 src 屬性即可。
const audioPlayer = new Audio('audiofile.mp3');
audioPlayer.play();
audioPlayer.setAttribute('src', 'audiofile2.mp3');
audioPlayer.load();
audioPlayer.play();
今天就先到這邊,明天來正式實作播放器的功能吧!