使用者可透過子母畫面 (PiP) 功能,在浮動式視窗 (一律位於其他視窗頂端) 中觀看影片,讓他們在與其他網站或應用程式互動時,仍能持續觀看影片。
您可以使用Picture-in-Picture Web API,在網站上啟動及控制 Picture-in-Picture 影片元素。歡迎試試我們的官方子母畫面範例。
背景
2016 年 9 月,Safari 透過 macOS Sierra 中的 WebKit API 新增了子母畫面支援功能。六個月後,Chrome 在 Android O 發布後,使用原生 Android API 在行動裝置上自動播放子母畫面影片。六個月後,我們宣布了我們的意圖,要建構並標準化與 Safari 相容的 Web API 功能,讓網路開發人員能夠建立及控制 Picture-in-Picture 的完整體驗。大功告成!
進入程式碼
進入子母畫面模式
我們先從影片元素和使用者與影片互動的方式 (例如按鈕元素) 開始。
<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>
請只在回應使用者手勢時要求子母畫面,絕對不要在 videoElement.play()
傳回的承諾中要求。這是因為承諾尚未傳播使用者手勢。請改為在 pipButtonElement
的點擊事件處理常式中呼叫 requestPictureInPicture()
,如下所示。您必須負責處理使用者點兩次按鈕的情況。
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
await videoElement.requestPictureInPicture();
pipButtonElement.disabled = false;
});
當承諾解析時,Chrome 會將影片縮小成一個小視窗,讓使用者可以移動並將其置於其他視窗上方。
完成後,做得好!您可以停止閱讀,並前往享受難得的假期。但很遺憾,實際情況並非總是如此。承諾可能會拒絕,原因如下:
- 系統不支援子母畫面功能。
- 由於受限的權限政策,文件無法使用 Picture-in-Picture。
- 影片中繼資料尚未載入 (
videoElement.readyState === 0
)。 - 影片檔案僅含音訊。
- 影片元素上會顯示新的
disablePictureInPicture
屬性。 - 呼叫並未在使用者手勢事件處理常式 (例如按鈕點擊) 中發出。自 Chrome 74 版起,只有在子母畫面中沒有元素時,這項設定才適用。
請參閱下方的「功能支援」一節,瞭解如何根據這些限制啟用/停用按鈕。
讓我們新增 try...catch
區塊,擷取這些潛在錯誤,並讓使用者瞭解發生了什麼事。
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
try {
await videoElement.requestPictureInPicture();
} catch (error) {
// TODO: Show error message to user.
} finally {
pipButtonElement.disabled = false;
}
});
無論是否處於子母畫面模式,影片元素的行為都相同:事件會觸發,呼叫方法會運作。它會反映子母畫面視窗中的狀態變更 (例如播放、暫停、快轉等),也可以透過程式設計在 JavaScript 中變更狀態。
退出子母畫面模式
接著,讓按鈕切換進入和退出子母畫面。我們必須先檢查只讀物件 document.pictureInPictureElement
是否為影片元素。如果不是,我們會傳送要求,以便進入上述的子母畫面模式。否則,我們會呼叫 document.exitPictureInPicture()
要求離開,這表示影片會回到原始分頁。請注意,這個方法也會傳回承諾。
...
try {
if (videoElement !== document.pictureInPictureElement) {
await videoElement.requestPictureInPicture();
} else {
await document.exitPictureInPicture();
}
}
...
監聽子母畫面事件
作業系統通常會將 Picture-in-Picture 限制在一個視窗,因此 Chrome 的實作方式會遵循這項模式。也就是說,使用者一次只能播放一個子母畫面影片。即使您未要求使用者退出,他們還是可以退出畫中畫模式。
新的 enterpictureinpicture
和 leavepictureinpicture
事件處理常式可讓我們為使用者打造客製化體驗。包括瀏覽影片目錄,或顯示直播聊天室等。
videoElement.addEventListener('enterpictureinpicture', function (event) {
// Video entered Picture-in-Picture.
});
videoElement.addEventListener('leavepictureinpicture', function (event) {
// Video left Picture-in-Picture.
// User may have played a Picture-in-Picture video from a different page.
});
自訂子母畫面視窗
Chrome 74 支援在分割畫面視窗中顯示的播放/暫停、上一首和下一首曲目按鈕,您可以使用 Media Session API 進行控制。
根據預設,除非影片播放 MediaStream 物件 (例如 getUserMedia()
、getDisplayMedia()
、canvas.captureStream()
),或是影片的 MediaSource 持續時間設為 +Infinity
(例如即時動態),否則系統一律會在畫中畫視窗中顯示播放/暫停按鈕。為確保播放/暫停按鈕一律顯示,請為「Play」和「Pause」媒體事件設定 somesee Media Session 動作處理常式,如下所示。
// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
// User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
// User clicked "Pause" button.
});
顯示「上一曲」和「下一曲」視窗控制項的方式也類似。為這些項目設定媒體工作階段動作處理常式,即可在子母畫面視窗中顯示這些項目,並處理這些動作。
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" button.
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" button.
});
如要查看實際運作情形,請試用官方的媒體工作階段範例。
取得子母畫面視窗大小
如果您想在影片進入及離開子母畫面時調整影片品質,就必須瞭解子母畫面視窗大小,並在使用者手動調整視窗大小時收到通知。
以下範例說明如何在建立或調整子母畫面視窗大小時,取得其寬度和高度。
let pipWindow;
videoElement.addEventListener('enterpictureinpicture', function (event) {
pipWindow = event.pictureInPictureWindow;
console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
pipWindow.addEventListener('resize', onPipWindowResize);
});
videoElement.addEventListener('leavepictureinpicture', function (event) {
pipWindow.removeEventListener('resize', onPipWindowResize);
});
function onPipWindowResize(event) {
console.log(
`> Window size changed to ${pipWindow.width}x${pipWindow.height}`
);
// TODO: Change video quality based on Picture-in-Picture window size.
}
建議您不要直接連結至調整大小事件,因為每次對 Picture-in-Picture 視窗大小進行微調都會觸發個別事件,如果每次調整大小時都執行耗用大量資源的作業,可能會導致效能問題。換句話說,調整大小作業會非常快速地不斷觸發事件。建議您使用常見的技術 (例如節流和去抖動) 來解決這個問題。
功能支援
系統可能不支援 Picture-in-Picture Web API,因此您必須偵測這項情況,才能提供漸進式增強功能。即使系統支援這項功能,使用者仍可能將其關閉,或因權限政策而停用。幸運的是,您可以使用新的布林值 document.pictureInPictureEnabled
來判斷這項資訊。
if (!('pictureInPictureEnabled' in document)) {
console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
console.log('The Picture-in-Picture Web API is disabled.');
}
將此方法套用至影片的特定按鈕元素,即可處理圖片內圖片按鈕的顯示方式。
if ('pictureInPictureEnabled' in document) {
// Set button ability depending on whether Picture-in-Picture can be used.
setPipButton();
videoElement.addEventListener('loadedmetadata', setPipButton);
videoElement.addEventListener('emptied', setPipButton);
} else {
// Hide button if Picture-in-Picture is not supported.
pipButtonElement.hidden = true;
}
function setPipButton() {
pipButtonElement.disabled =
videoElement.readyState === 0 ||
!document.pictureInPictureEnabled ||
videoElement.disablePictureInPicture;
}
MediaStream 影片支援
播放 MediaStream 物件的影片 (例如 getUserMedia()
、getDisplayMedia()
、canvas.captureStream()
) 也支援 Chrome 71 中的子母畫面。也就是說,您可以顯示含有使用者網路攝影機影片串流、顯示影片串流,甚至是畫布元素的子母畫面視窗。請注意,如要進入子母畫面,視訊元素不必附加至 DOM,如下所示。
在子母畫面視窗中顯示使用者的網路攝影機畫面
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
在子母畫面視窗中顯示螢幕畫面
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
在子母畫面視窗中顯示畫布元素
const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);
const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();
// Later on, video.requestPictureInPicture();
將 canvas.captureStream()
與 Media Session API 結合,即可在 Chrome 74 中建立音訊播放清單視窗。請參閱官方音訊播放清單範例。
範例、試用版和程式碼研究室
請查看我們的官方子母畫面範例,試用子母畫面 Web API。
我們會在後續提供相關的示範和程式碼研究室。
後續步驟
首先,請查看導入狀態頁面,瞭解 Chrome 和其他瀏覽器目前已導入哪些 API 部分。
以下說明近期可能發生的異動:
- 網路開發人員可以新增自訂的 Picture-in-Picture 控制項。
- 我們會提供新的 Web API,讓您在浮動視窗中顯示任意
HTMLElement
物件。
瀏覽器支援
Chrome、Edge、Opera 和 Safari 皆支援 Picture-in-Picture Web API。詳情請參閱 MDN。
資源
- Chrome 功能狀態:https://www.chromestatus.com/feature/5729206566649856
- Chrome 實作錯誤:https://crbug.com/?q=component:Blink>Media>PictureInPicture
- Picture-in-Picture Web API 規格:https://wicg.github.io/picture-in-picture
- 規格問題:https://github.com/WICG/picture-in-picture/issues
- 範例:https://googlechrome.github.io/samples/picture-in-picture/
- 非官方子母畫面 polyfill:https://github.com/gbentaieb/pip-polyfill/
感謝 Mounir Lamouri 和 Jennifer Apacible 在開發 Picture-in-Picture 功能和撰寫本文時提供的協助。也要感謝所有參與標準化工作的人員。