iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 4
0
Modern Web

Web x Sound - 用 Web 玩轉聲音系列 第 4

Day04 - HTMLMediaElement

  • 分享至 

  • xImage
  •  

用 JavaScript 操作 Audio Element

昨天把播放器切版完成了 (灑花),不過在實作功能以前,先來談談一些基礎知識吧!

HTMLMediaElement

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 等情境就已足夠了。

Method & Property

我們使用 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()

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 順序相同

canPlayType()

這個方法用來詢問瀏覽器是否支援此檔案格式的播放,回傳值是字串。

value description
'probably' 此格式看起來可以播放
'maybe' 不確定,要試播才知道
'' 看起來不行播放

第一眼看到的時候不小心笑了,要試試看才知道!? 不曉得有什麼典故,竟然有這種曖昧不明的回應... XD

play() & currentTime

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;

Event

除了上述的方法以外,我們也可以監聽特定事件來作更細微的處理。
事件分成兩類:Loading 與 Playing。

Loading Events

這些是音源在讀取過程中會觸發的事件,監聽 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 音源非預期的無法被取用

Playing Events

這些則是播放過程中會觸發的事件,搭配前面的 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

HTMLAudioElement

前面介紹完了 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();

今天就先到這邊,明天來正式實作播放器的功能吧!

Reference


上一篇
Day03 - 使用 HTML 播放音檔 - 自製播放器 (1)
下一篇
Day05 - 使用 HTML 播放音檔 - 自製播放器 (2)
系列文
Web x Sound - 用 Web 玩轉聲音13
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言