[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
時會促發,表示整個媒體檔案都可以播放而不會中斷(假設下載的速度至少維持和當前相同)。另外,當在 paused 和 playing 之間切換時被觸發(實測結果並不一定)。
常用事件
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 檔是屬於哪一種 cues-subtitles
,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),如果你沒有使用正確的編解碼器,那麼影片將無法順利播放。
Vorbis 和 H.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
,可以參考此連結的作法。
Safari 有關的問題
- 在 Safari 的
<video>
標籤中加入空的<track>
標籤,有可能會使得影片無法正常播放。 - 在 iOS(iPad)上的 Safari,網頁初次載入影片時只會觸發
loadedmetadata
而不會觸發loadeddata
和後續的其他事件,其他事件需要等待使用者按下播放後才會開始觸發。 - 在 iOS 上的 Safari 用
window.safari
會是undefined
。
⚠️ Mac 和 iPad 上的 Safari 會有稍微不一樣的行為。
參考
- Video and Audio Content @ MDN > Learn web development > Learning HTML: Guides and tutorials > Multimedia and Embedding > Video and audio content
- HTML Media Element @ MDN > Web technology for developers > Web APIs
- [WEB] Media Streams and Stream API @ PJCHENder Notes
- Video and Audio APIs @ MDN - Learn web development
- Media Events @ MDN
- HTML Video Element @ MDN
- [JS30] Day11: Custom HTML Video Player @ PJCHENder Notes