如何在網路上打造最佳行動媒體體驗?非常容易!這一切都取決於使用者參與度,以及您對網頁上媒體的重視程度。我們都同意,如果影片是使用者造訪的關鍵原因,使用者體驗就必須具備身歷其境的效果,並能吸引使用者再次造訪。
本文將說明如何透過大量的 Web API,以漸進方式提升媒體體驗,並創造更身歷其境的體驗。因此,我們將透過自訂控制項、全螢幕和背景播放功能,建構簡單的行動裝置播放器體驗。您可以立即試用範例,並在 GitHub 存放區中找到程式碼。
自訂控制項
如您所見,我們要用於媒體播放器的 HTML 版面配置相當簡單:<div>
根元素包含 <video>
媒體元素,以及專門用於影片控制項的 <div>
子項元素。
我們稍後會介紹影片控制項,包括播放/暫停按鈕、全螢幕按鈕、快轉和倒轉按鈕,以及用於追蹤目前時間、時間長度和時間的元素。
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
讀取影片中繼資料
首先,請等待影片中繼資料載入,以便設定影片時間長度、目前時間,並初始化進度列。請注意,secondsToTimeCode()
函式是我編寫的自訂公用函式,可將秒數轉換為「hh:mm:ss」格式的字串,這在我們的情況下更為合適。
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</strong>
</div>
</div>
video.addEventListener('loadedmetadata', function () {
videoDuration.textContent = secondsToTimeCode(video.duration);
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
});
播放/暫停影片
影片中繼資料已載入,現在我們來新增第一個按鈕,讓使用者根據播放狀態,使用 video.play()
和 video.pause()
播放及暫停影片。
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong><button id="playPauseButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
playPauseButton.addEventListener('click', function (event) {
event.stopPropagation();
if (video.paused) {
video.play();
} else {
video.pause();
}
});
我們不會調整 click
事件監聽器中的影片控制項,而是使用 play
和 pause
影片事件。建立以事件為基礎的控制項有助於提升彈性 (稍後會透過 Media Session API 說明),如果瀏覽器介入播放作業,我們就能讓控制項保持同步。影片開始播放時,我們會將按鈕狀態變更為「暫停」並隱藏影片控制項影片暫停時,我們只需將按鈕狀態變更為「播放」並顯示影片控制項。
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
當影片 currentTime
屬性透過 timeupdate
影片事件變更時,如果自訂控制項可見,我們也會更新這些控制項。
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
影片播放結束時,我們只需將按鈕狀態變更為「播放」,將影片 currentTime
改回 0,並暫時顯示影片控制選項。請注意,如果使用者已啟用某種「自動播放」功能,我們也可以選擇自動載入其他影片。
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
倒轉和快轉
接下來,我們將新增「向後跳躍」和「向前跳躍」按鈕,方便使用者輕鬆略過部分內容。
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<strong
><button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button
></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
var skipTime = 10; // Time to skip in seconds
seekForwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
seekBackwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
如同先前所述,我們會使用觸發的 seeking
和 seeked
影片事件調整影片亮度,而不是在這些按鈕的 click
事件監聽器中調整影片樣式。我的自訂 seeking
CSS 類別和 filter: brightness(0);
一樣簡單。
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
以下是我們目前已建立的內容。我們將在下一節中實作全螢幕按鈕。
全螢幕
我們將利用多個 Web API 打造完美無縫的全螢幕體驗。如要查看實際運作情況,請參閱範例。
當然,您不必使用所有元素。只要選擇符合需求的流程,然後組合起來即可建立自訂流程。
防止自動進入全螢幕模式
在 iOS 上,video
元素會在媒體播放開始時自動進入全螢幕模式。我們會盡可能調整及控制行動瀏覽器的媒體體驗,因此建議您設定 video
元素的 playsinline
屬性,強制在 iPhone 上以內嵌方式播放,並在播放開始時不進入全螢幕模式。請注意,這項設定不會對其他瀏覽器造成任何副作用。
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
按一下按鈕切換全螢幕
我們現在會避免自動進入全螢幕模式,現在我們需要使用全螢幕 API 處理影片的全螢幕模式。當使用者點選「全螢幕按鈕」時,如果文件目前正在使用全螢幕模式,請使用 document.exitFullscreen()
退出全螢幕模式。否則,請使用 requestFullscreen()
方法 (如有) 在影片容器上要求全螢幕模式,或僅在 iOS 上改用影片元素的 webkitEnterFullscreen()
。
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button>
<strong><button id="fullscreenButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
}
});
function requestFullscreenVideo() {
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
} else {
video.webkitEnterFullscreen();
}
}
document.addEventListener('fullscreenchange', function () {
fullscreenButton.classList.toggle('active', document.fullscreenElement);
});
在螢幕方向變更時切換全螢幕
當使用者將裝置旋轉至橫向模式時,我們會聰明地自動要求全螢幕模式,打造沉浸式體驗。為此,我們需要使用 Screen Orientation API,但這個 API 目前尚未在所有地方支援,且在部分瀏覽器中仍會加上前置字串。因此,這是我們第一個漸進式強化功能。
它是怎樣運作的?偵測到螢幕方向變更後,當瀏覽器視窗處於橫向模式 (也就是寬度大於高度) 時,再要求全螢幕。如果不是,請退出全螢幕模式。以上就是清單中的所有項目。
if ('orientation' in screen) {
screen.orientation.addEventListener('change', function () {
// Let's request fullscreen if user switches device in landscape mode.
if (screen.orientation.type.startsWith('landscape')) {
requestFullscreenVideo();
} else if (document.fullscreenElement) {
document.exitFullscreen();
}
});
}
按下按鈕時以橫向鎖定螢幕
由於影片在橫向模式下觀看時可能會更清晰,因此我們可能會在使用者按下「全螢幕按鈕」時,將螢幕鎖定為橫向模式。我們將合併先前使用的 Screen Orientation API 和部分媒體查詢,確保這是最佳體驗。
在橫向模式中鎖定螢幕,就像呼叫 screen.orientation.lock('landscape')
一樣簡單。不過,我們應該只在裝置處於 matchMedia('(orientation: portrait)')
的直向模式,且可以用 matchMedia('(max-device-width: 768px)')
單手握持時,才執行這項操作,因為這對平板電腦使用者來說並非良好體驗。
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
<strong>lockScreenInLandscape();</strong>;
}
});
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (
matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
) {
screen.orientation.lock('landscape');
}
}
在裝置方向變更時解鎖螢幕
您可能已註意到,我們剛建立的螢幕鎖定功能體驗並不完美,因為在螢幕鎖定時不會收到螢幕方向變更。
為解決這個問題,我們要使用 Device Orientation API (如果有的話)。這個 API 會提供硬體測量裝置在空間中的位置和動作的資訊:陀螺儀和數位指南針用於測量方向,加速計用於測量速度。當我們偵測到裝置方向變更時,如果使用者以直向模式握住裝置,且螢幕鎖定在橫向模式,請使用 screen.orientation.unlock()
解鎖螢幕。
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
screen.orientation.lock('landscape')
<strong>.then(function() {
listenToDeviceOrientationChanges();
})</strong>;
}
}
function listenToDeviceOrientationChanges() {
if (!('DeviceOrientationEvent' in window)) {
return;
}
var previousDeviceOrientation, currentDeviceOrientation;
window.addEventListener(
'deviceorientation',
function onDeviceOrientationChange(event) {
// event.beta represents a front to back motion of the device and
// event.gamma a left to right motion.
if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
previousDeviceOrientation = currentDeviceOrientation;
currentDeviceOrientation = 'landscape';
return;
}
if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
previousDeviceOrientation = currentDeviceOrientation;
// When device is rotated back to portrait, let's unlock screen orientation.
if (previousDeviceOrientation == 'landscape') {
screen.orientation.unlock();
window.removeEventListener(
'deviceorientation',
onDeviceOrientationChange,
);
}
}
},
);
}
如您所見,這就是我們想要的流暢全螢幕體驗。如要查看實際運作方式,請參閱範例。
背景播放
如果您發現網頁或網頁中的影片已無法顯示,建議您更新數據分析資料,以反映這項變更。這也可能會影響目前的播放作業,例如選擇其他曲目、暫停播放,甚至向使用者顯示自訂按鈕。
在頁面瀏覽權限變更時暫停影片
透過 Page Visibility API,我們可以判斷網頁目前的瀏覽權限,並收到瀏覽權限變更通知。下方程式碼會在頁面隱藏時暫停影片。例如,當螢幕鎖定功能啟用或切換分頁時,就會發生這種情況。
由於大多數行動瀏覽器現在都會在瀏覽器外提供控制項,可讓使用者繼續播放已暫停的影片,因此建議您只在允許使用者在背景播放影片時,才設定這項行為。
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
在影片瀏覽權限變更時顯示/隱藏靜音按鈕
如果您使用新的 Intersection Observer API,可以免費取得更精細的資料。這個 API 可在偵測到的元素進入或離開瀏覽器的可視區域時通知您。
我們將根據網頁中影片的顯示狀態,顯示/隱藏靜音按鈕。如果影片正在播放但目前無法顯示,頁面右下角會顯示迷你靜音按鈕,讓使用者控制影片音效。volumechange
影片事件會用來更新靜音按鈕樣式。
<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
// Show/hide mute button based on video visibility in the page.
function onIntersection(entries) {
entries.forEach(function (entry) {
muteButton.hidden = video.paused || entry.isIntersecting;
});
}
var observer = new IntersectionObserver(onIntersection);
observer.observe(video);
}
muteButton.addEventListener('click', function () {
// Mute/unmute video on button click.
video.muted = !video.muted;
});
video.addEventListener('volumechange', function () {
muteButton.classList.toggle('active', video.muted);
});
一次只播放一部影片
如果網頁上有多個影片,建議您只播放一部影片,然後自動暫停其他影片,這樣使用者就不必同時聆聽多個音軌播放的內容。
// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));
videos.forEach(function (video) {
video.addEventListener('play', pauseOtherVideosPlaying);
});
function pauseOtherVideosPlaying(event) {
var videosToPause = videos.filter(function (video) {
return !video.paused && video != event.target;
});
// Pause all other videos currently playing.
videosToPause.forEach(function (video) {
video.pause();
});
}
自訂媒體通知
您也可以使用 Media Session API,為目前播放的影片提供中繼資料,以自訂媒體通知。也可讓您處理媒體相關事件,例如尋找或追蹤可能來自通知或媒體鍵的變更。如要瞭解實際操作,請參閱範例。
當您的網路應用程式播放音訊或影片時,您會在通知匣中看到媒體通知。在 Android 上,Chrome 會使用文件標題和可找到的最大圖示圖片,盡可能顯示適當的資訊。
我們將說明如何使用 Media Session API 設定一些媒體工作階段中繼資料 (例如標題、演出者、專輯名稱和圖片),藉此自訂這類媒體通知。
playPauseButton.addEventListener('click', function(event) {
event.stopPropagation();
if (video.paused) {
video.play()
<strong>.then(function() {
setMediaSession();
});</strong>
} else {
video.pause();
}
});
function setMediaSession() {
if (!('mediaSession' in navigator)) {
return;
}
navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
album: 'Whenever You Need Somebody',
artwork: [
{src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
{
src: 'https://dummyimage.com/128x128',
sizes: '128x128',
type: 'image/png',
},
{
src: 'https://dummyimage.com/192x192',
sizes: '192x192',
type: 'image/png',
},
{
src: 'https://dummyimage.com/256x256',
sizes: '256x256',
type: 'image/png',
},
{
src: 'https://dummyimage.com/384x384',
sizes: '384x384',
type: 'image/png',
},
{
src: 'https://dummyimage.com/512x512',
sizes: '512x512',
type: 'image/png',
},
],
});
}
播放完成後,您不必「釋放」媒體工作階段,因為通知會自動消失。請注意,系統會在任何播放作業開始時使用目前的 navigator.mediaSession.metadata
。因此,您需要更新該資訊,確保媒體通知一律顯示相關資訊。
如果您的網頁應用程式提供播放清單,您可能會想讓使用者直接透過媒體通知瀏覽播放清單,並顯示「上一曲」和「下一曲」圖示。
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" media notification icon.
playPreviousVideo(); // load and play previous video
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" media notification icon.
playNextVideo(); // load and play next video
});
}
請注意,媒體操作處理常式會保留。這與事件事件監聽器模式非常相似,但處理事件代表瀏覽器會停止執行任何預設行為,並將此做為網頁應用程式支援媒體動作的信號。因此,除非您設定適當的動作處理常式,否則系統不會顯示媒體動作控制項。
順帶一提,取消設定媒體操作處理常式,就像將其指派給 null
一樣簡單。
如要控制略過的時間長度,您可以使用 Media Session API 顯示「Seek Backward」和「Seek Forward」媒體通知圖示。
if ('mediaSession' in navigator) {
let skipTime = 10; // Time to skip in seconds
navigator.mediaSession.setActionHandler('seekbackward', function () {
// User clicked "Seek Backward" media notification icon.
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
navigator.mediaSession.setActionHandler('seekforward', function () {
// User clicked "Seek Forward" media notification icon.
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
}
媒體通知一律會顯示「播放/暫停」圖示,且瀏覽器會自動處理相關事件。如果預設行為因某些原因無法運作,您仍可處理「播放」和「暫停」媒體事件。
Media Session API 的酷炫之處在於,通知列並非媒體中繼資料和控制項的唯一顯示位置。媒體通知會自動同步至任何已配對的可穿戴式裝置。這類通知也會顯示在螢幕鎖定畫面上。