跳至主要内容

[HTML] Media - Video Element

keywords: video, source, media, WebVTT, subtitle, cues, WebAPIs

HTML Element

一般來說,只需要在 <video> 上使用 src 來載入影片即可:

<video src="rabbit320.webm" controls>
<!-- here is fallback content -->
<p>
Your browser doesn't support HTML5 video. Here is a
<a href="rabbit320.webm">link to the video</a> instead.
</p>
</video>

然而為了要解決不同瀏覽器僅支援不同編解碼器(codec),可以在 <video> 中使用 <source> ,讓瀏覽器自動挑選它所支援的:

<video controls>
<source src="rabbit320.mp4" type="video/mp4" />
<source src="rabbit320.webm" type="video/webm" />
<p>
Your browser doesn't support HTML5 video. Here is a
<a href="rabbit320.mp4">link to the video</a> instead.
</p>
</video>

在每一個 <source> 標籤上都含有 type 屬性,雖然則不是必填的,但建議根據 MIME types 加以填寫,瀏覽器將會根據這個屬性值快速過濾適合的選項,否則瀏覽器需要花更多的時間和資源在嘗試影片的播放上。

其他還有些在 <video> 標籤上可以使用的屬性:

<video controls width="400" height="400" autoplay loop muted poster="poster.png">
<source src="rabbit320.mp4" type="video/mp4" />
<source src="rabbit320.webm" type="video/webm" />
<p>
Your browser doesn't support HTML5 video. Here is a
<a href="rabbit320.mp4">link to the video</a> instead.
</p>
</video>
  • autoplay:當頁面其他部分還在加載時就開始播放影音。為了避免對使用者造成困擾,一般會建議不要使用。
  • poster:作為影片開始播放前的截圖。
  • preload:用來緩衝處理大檔,三種值可供使用:none, auto, metadata

HTML Media Element API

HTML Media Element @ MDN > Web technology for developers > Web APIs

屬性(Properties)

取得(getter)

  • HTMLMediaElement.currentSrc取得當前 Media Element 的絕對路徑。
  • HTMLMediaElement.duration取得影片長度(秒),0 則表示無法取得。
  • HTMLMediaElement.paused取得影片是否暫停播放。
  • HTMLMediaElement.readyState:參考 state 的內容
    • 0 表示尚未取得影片資訊
    • 1 表示有足夠的媒體資訊,對影片使用 seek 不會導致錯誤
    • 2 影片的當前時間可以播放,但還不足以播放下一個影格
    • 3 影片的當前時間可以播放,並且已經載入後續更多影格
    • 4 表示可完整播放影片,

取得或設定(getter and setter)

  • HTMLMediaElement.src:取得或設定影片的 src
  • HTMLMediaElement.controls取得或設定要不要顯示 video 播放器的控制列,Boolean
  • HTMLMediaElement.currentTime取得或設定當前的影片所在的播放時間。
  • HTMLMediaElement.muted取得或設定影片是否靜音。
  • HTMLMediaElement.volume:取得或設定影片的音量(0 ~ 1)。
  • HTMLMediaElement.playbackRate取得或設定 播放速率(0 < r <= 16)。

其他

  • HTMLMediaElement.ended取得該影片是否已播放完畢。
  • HTMLMediaElement.defaultMuted取得 Video 元素上是否有 muted 屬性。
  • HTMLMediaElement.error取得 MediaError 物件,沒有錯誤則回傳 null

方法(Methods)

  • HTMLMediaElement.load():重置 media 元素到初始化的狀態,並且從選擇 media source 開始,重新載入以便可以從頭播放。
  • HTMLMediaElement.pause()
  • HTMLMediaElement.play()

事件(Event)

video = document.querySelector('video');

video.addEventListener('loadedmetadata', (e) => console.log('video on loadedmetadata'));
video.addEventListener('canplay', (e) => console.log('video on canplay'));

video.addEventListener('canplaythrough', (e) => {
console.log('video on canplaythrough');
video.controls = true;
});

加載影片事件

依照事件觸發順序排列:

  • loadedmetadata:當媒體檔的 metadata 完成載入時,所有的屬性都包含最多可用的資訊。
  • loadeddata:當第一個 frame 完成載入時促發。
  • canplay:當資料足夠讓謀體可以播放時會促發此事件,至少已有一定數量的 frames。
  • canplaythrough:當 readyState 變成 CAN_PLAY_THROUGH 時會促發,表示整個媒體檔案都可以播放而不會中斷(假設下載的速度至少維持和當前相同)。另外,當在 pausedplaying 之間切換時被觸發(實測結果並不一定)。

常用事件

  • pause:當影片暫停播放
  • play:當影片開始播放
  • ended:當影片播放完畢
  • timeupdate:當 currentTime 變更時會觸發,因此影片播放時會連續觸發。需要 sync 時間軸的話可用。
  • seeking:當設定 currentTime 時,會先觸發 seeking 事件。
  • seeked
  • volumechange

其他事件

尚未清楚了解的事件

  • playing
  • progress
  • ratechange:播放速率改變時
  • seeked
  • seeking

Media Events @ MDN

影片字幕(WebVTT and Track Element)

keywords: WebVTT, track, cues

WebVTT Format

在 HTML5 的 video 中將可以使用 WebVTT<track> 來在影片播放時添加文字。WebVTT 是一種字幕檔的格式,可以定義每段文字在影片的哪個時間點產生,並且可以套用有限的樣式和定位方式。這些文字都被稱作 cues,根據不同的用途可以分成:

  • subtitles(字幕):通常是逐字稿的形式,讓不同語言的使用者都能了解影片的內容。
  • captions(說明文字):在使用者無法聽到聲音的情況下,提供說明或描述聲音。
  • timed descriptions:將文字轉換成聲音,讓使用者在無法閱讀的情況下,可以用聽的。

聽打(transcribe)指的是將聽到的文字寫下來,而寫下來的內容稱作逐字稿(transcript)

一個基本的 WebVTT 檔案會長這樣,副檔名是 .vtt

WEBVTT

1
00:00:22.230 --> 00:00:24.606
This is the first subtitle.

2
00:00:30.739 --> 00:00:34.074
This is the second.

Track Element

要在 HTML 中使用 WebVTT,只需要 <video> 標籤內加上 <track> 標籤,<track> 標籤要記得放在 <source> 之後

<video controls>
<source src="example.mp4" type="video/mp4" />
<source src="example.webm" type="video/webm" />
<track kind="subtitles" src="subtitles_en.vtt" srclang="en" />
</video>

<track> 標籤中有以下屬性可供使用:

  • 使用 kind 屬性來說明這個 vtt 檔是屬於哪一種 cuessubtitles, captions, 或 descriptions
  • 使用 srclang 屬性來告訴瀏覽器這個字幕是用哪種語言。

若有需要實作字幕功能,可參考:

檔案格式(file format)

keywords: container, codec

<audio><video> 元素都提供了不需要外掛即可播放影音的支援。一個影音格式(media format)包含了一個容器(container),而一個容器中可以包含許多不同的影音串流資料(streams),像是影像、音訊、資料和字幕。在影音串流內則包含了編解碼器(codec),指的是 coder-decoder,這是用來壓縮影音資料的演算法。一個 container 只支援特定類型的的編解碼器。

檔案格式像是 MP3, MP4 或 WebM 都稱作 container formats,它們包含許多不同的資料串流(stream)來組成一首完整的音樂或一部完整的影片,其中包括像是音軌(audio track)視軌(video track)詮釋資料(metadata)

  • WebM 包含 Ogg Vorbis/Opus 音軌編解碼器搭配 VP8/VP9 視軌編解碼器。主要由 Firefox 和 Chrome 支援,video/webm, audio/webm
  • MP4 包含 AAC 或 MP3 音軌編解碼器搭配 H.264 視軌編解碼器。主要由 IE 和 Safari 支援。

上述檔案格式之所以存在是因為原始的影音檔相當龐大,所以需要透過編解碼器(codec, coder-decoder)壓縮成可管理的檔案。不同瀏覽器包含不同的編解碼器(Codecs),例如 Vorbis 和 H.264,主要是用來將壓縮過的影音檔轉回二位元資料(binary digits),如果你沒有使用正確的編解碼器,那麼影片將無法順利播放。

VorbisH.264 都是屬於編解碼器(Codecs),用途是將壓縮過的影音檔轉回二位元資料。

延伸閱讀:Media formats for HTML audio and video @ MDN

實作播放器時碰到的問題和情況

指定影片到特定時間點

  • 當要透過 videoElement.currentTime 設定影片時,為了避免瀏覽器過度負擔會有秀抖的情況,記得先將影片透過 pause() 停止後,再切換 currentTime,如此才不會碰到影片播放時的「意外」(例如,明明已經把 isLoading 的狀態設成 false,但仍持續呈現 isLoading 的畫面)。
  • videoElement.currentTime 被改變且影片可以再次播放時,會觸發 canPlayThrough 的事件,這時候可以再根據影片原本的狀態,選擇要不要繼續播放被暫停的影片
// PSEUDO CODE
// 記得在改變 videoElement.currentTime 前先把影片停止
function seek(time) {
if (!this.isReadyStateOk) {
console.warn('not all video ready to play');
return;
}

this.setState({
isLoadingVideo: true,
});

try {
// NOTE: REMEMBER to pause video before seek
Object.values(this.videoElements).forEach((video) => video.pause());

Object.values(this.videoElements).forEach((_video) => {
const video = _video;
video.currentTime = time;
});

this.setState({
currentTime: time,
});
} catch (ex) {
console.warn(ex, 'Video is not ready.');
}
}

// 當 videoElement.currentTime 被改變且影片可以再次播放時,
// 會觸發 canPlayThrough 的事件,這時候可以再根據影片原本的狀態,
// 選擇要不要繼續播放被暫停的影片
function handleCanPlayThrough(e) {
const { isLoadingVideo, isPaused } = this.state;

if (isLoadingVideo && this.isReadyStateOk) {
this.setState({ isLoadingVideo: false }, () => {
// 如果該影片原本就是在播放的情況,則繼續播放
if (isPaused === false) {
setTimeout(() => {
this.play();
}, 200);
}
});
}
}

canPlayThrough 事件和 readyState 在不同瀏覽器的差別

對於較長的影片來說:

  • Chrome 和 Firefox 都會觸發 loadeddata 的事件,但此時 video.readyState 可能是 3 或 4。
  • Chrome 會在可以播放的時候觸發 canplaythrough 的事件,此時 video.readyState 會是 4;Firefox 則不會觸發 canplaythrough 的事件。
  • Firefox 在影片播完後,video.readyState 會改變成 1 或 2。
  • 在 Mac OS 上的 Safari,canplaythrough 的事件比較類似 loadeddata,只有影片初次載入時會被呼叫到,之後觸發瀏覽器的 seek 事件也不會再呼叫到 canplaythrough

visibilityChange 事件在不同瀏覽器的差異

當使用者在瀏覽器內切換頁籤時,不論 Chrome 或 Firefox 都會觸發 visibilityChange 的事件。但某些影片(還不確定原因,也許是編碼或沒有音軌),Chrome 會發生當切換回原本的頁籤,visibilityState 從 hidden 變回 visible 時,會自動開始播放該影片

當使用者透過「Ctrl + Tab」切換系統桌面:

  • 在 Firefox 一樣會觸發 visibilityChange 的事件
  • 在 Chrome 不會觸發 visibilityChange 事件,且可能會自動暫停某些影片(還不確定原因,也許是編碼或沒有音軌),但繼續播放有音軌的影片。

visibilityChange 的時候,影片繼續播放

可以透過監聽 document 的 visibilityChange 事件,一旦使用者切換到不同頁籤時,visibilityState 變成 hidden 時,即暫停影片播放。但某些影片(還不確定原因,也許是編碼或沒有音軌),Chrome 會發生當切換回原本的頁籤,visibilityState 從 hidden 變回 visible 時,會自動開始播放該影片,這種情形在 Firefox 下並不會發生。

動不了影片時

或者可以在 visibilityChange 事件被觸發時,不論是從 visible 變成 hidden,或從 hidden 變回 visible 時,都讓影片暫停(因為自動播放的問題是當 document.visibilityState 從 hidden 變回 visible 時才發現)。

// PSEUDO CODE
// different browser should listen on different event name,
// check MDN for more details, https://goo.gl/2LMnJo
document.addEventListener('webkitvisibilitychange', this.handleVisibilityChange, false);

function handleVisibilityChange() {
// For unknown Chrome issue, some video encoder types will
// cause video autoplay after visibility change to 'visible',
// therefore, pause() the video even when visibility state change back to 'visible'
videoElement.pause();
}

webm 的 duration 會是 infinity

當檔案格式是 webm 時,在影片尚未載完時,webm 的 duration 會是 infinity,這時候有一個方式是把該影片的 currentTime 設成一個很大的數字後,在 ontimeupdate 時去取得該影片實際的 duration,可以參考此連結的作法。

Get video and audio blob duration in HTML5

Safari 有關的問題

  • 在 Safari 的 <video> 標籤中加入空的 <track> 標籤,有可能會使得影片無法正常播放。
  • 在 iOS(iPad)上的 Safari,網頁初次載入影片時只會觸發 loadedmetadata 而不會觸發 loadeddata 和後續的其他事件,其他事件需要等待使用者按下播放後才會開始觸發。
  • 在 iOS 上的 Safari 用 window.safari 會是 undefined

⚠️ Mac 和 iPad 上的 Safari 會有稍微不一樣的行為。

參考