Spotify 是全球最受歡迎的音訊串流訂閱服務,不斷致力改善使用者觀看音訊和影片內容的方式。這項服務提供豐富的音樂、Podcast 和有聲書,每天都吸引數百萬名使用者在行動裝置、電腦及其他平台上使用。
Spotify 最近為電腦版和網路播放器用戶端推出了 Spotify Miniplayer。迷你播放器的設計旨在在小巧的小型視窗內提供重要的播放控制項,讓使用者持續存取 Spotify。這項功能一直以來都有求進步,可讓使用者在不同的視窗和應用程式中流暢地進行多工處理,同時在 Spotify 聆聽喜愛的藝人、播放清單和 Podcast。
以下會詳細說明迷你播放器的開發流程,從最初的「畫布入侵」到較容易使用的全新 Document Picture-in-Picture API 上即可。
「畫布入侵」
迷你播放器於 2019 年在 Spotify 的網頁播放器推出,做為駭客專案推出。我們的目標是運用瀏覽器的子母畫面 (PiP) API 的 <video> API,在螢幕不間斷的視窗中顯示專輯封面。不過,這個 API 主要是為影片元素設計,無法顯示專輯封面圖片。為規避此問題,Spotify 會將專輯封面算繪至畫布元素,並使用 HTMLCanvasElement
captureStream()
方法取得即時 MediaStream 物件。接著,這個串流就會做為子母畫面 API 使用的影片來源。這個方法使用 Google Chrome 的「音訊播放清單」範例建立而成。
Spotify 將畫布與 Media Session API 中設定的適當動作處理常式結合,以控制子母畫面視窗中要顯示哪些播放器控制項。使用者可透過浮動式視窗查看專輯封面和播放器控制項,並能同時控製播放方式,同時專心處理其他工作。
這讓 Spotify 擁有基本的迷你播放器。然而,此方法有一些限制:
- 子母畫面視窗中不支援影片字幕。因為根據 Spotify 規定,所有影片都必須顯示字幕,觀眾在影片開始播放後,就會被強制關閉子母畫面視窗。
- 只有在使用者在本機播放時,畫面上才會顯示播放器控制項。Spotify 允許使用 Spotify Connect (及其他通訊協定) 進行遠端播放,因此希望使用者也能控製播放內容
- 無法自訂子母畫面視窗的外觀和風格。Spotify 只能顯示圖片並使用 Chrome 提供的播放器控制項,因而無法新增 Spotify 品牌宣傳元素或其他播放器控制項。
由於缺乏對使用者介面的掌控,也無法在這裡新增 Spotify 專屬功能 (例如對曲目表示喜歡),因此他們認為這種方法不適合電腦版用戶端。
文件子母畫面:迷你播放器的演進
2023 年初,Spotify 越來越重視 Google Chrome 推出新的 API,這個 API 允許在子母畫面視窗中顯示任意 HTML 內容 (稱為 Document Picture-in-Picture API)。Spotify 可以透過這項新功能全面掌控子母畫面視窗的外觀,所以這項開發工作相當令人期待。Spotify 在來源試用期間與 Chrome 團隊合作,開發出以 Document Picture-in-Picture API 為基礎的全新迷你播放器。
你可以開啟 Document PiP API,開啟一個新的一律在頂部視窗來附加元素。由於 Spotify Web Player 是 React 網頁應用程式,因此 Spotify 透過 ReactDOM 的 createPortal()
方法,將自訂元件從主應用程式算繪到子母畫面視窗,以便完全掌控迷你播放器的外觀和功能。
新的 Document Picture-in-Picture API 也能解決 Spotify 先前的問題:
- 子母畫面視窗中的影片是一般的影片元素,可以完整支援字幕。
- 由於你可以完全掌控 UI,即使使用 Spotify Connect 從遠端播放,仍然會顯示播放器控制項。
- Spotify 整合了外觀和風格與播放器控制項,提升了使用者體驗。
- 他們成功在 Spotify 的桌面用戶端支援 Document PiP API,讓數以百萬計的電腦使用者也能享受迷你播放器。
使用 React 建立子母畫面視窗
以下範例說明如何像 Spotify 團隊一樣,在 React 中使用文件子母畫面。您將建立兩個 React 元件:MyFeature
和 PiPContainer
。
MyFeature
元件負責管理子母畫面視窗。這會顯示切換子母畫面視窗的按鈕,並轉譯 PiPContainer
元件。也會訂閱子母畫面視窗的 "pagehide"
事件,在視窗關閉時更新狀態。
const MyFeature = () => {
const [pipWindow, setPiPWindow] = useState<Window | null>(
documentPictureInPicture.window
);
const handleClick = useCallback(async () => {
if (pipWindow) {
pipWindow.close();
} else {
const newWindow = await documentPictureInPicture.requestWindow();
setPiPWindow(newWindow);
}
}, [pipWindow]);
useEffect(() => {
const handleWindowClose = (): void => {
setPiPWindow(null);
};
pipWindow?.addEventListener("pagehide", handleWindowClose);
return () => {
pipWindow?.removeEventListener("pagehide", handleWindowClose);
};
}, [pipWindow]);
return (
<>
<button onClick={handleClick}>
{pipWindow ? "Close PiP Window" : "Open PiP Window"}
</button>
<PiPContainer pipWindow={pipWindow}>Hello World 👋!</PiPContainer>
</>
);
};
PiPContainer
元件使用 ReactDOM 的 createPortal() 方法,將內容算繪到子母畫面視窗。
type Props = PropsWithChildren<{
pipWindow: Window | null;
}>;
const PiPContainer = ({ pipWindow, children }: Props) => {
useEffect(() => {
if (pipWindow) {
cloneStyles(window.document, pipWindow.document);
}
}, [pipWindow]);
return pipWindow ? createPortal(children, pipWindow.document.body) : null;
};
後續步驟
隨著 Spotify 持續發展及革新,他們會繼續致力強化迷你播放器,並計劃進一步修正其功能和使用者體驗。儘管他們還無法承諾推出特定功能,但也很期待迷你播放器未來的可能性。
Document Picture-in-Picture API 具有彈性和控制權,才能打造更直覺易用的迷你播放器。我們希望其他瀏覽器廠商會記下這個 API 帶來的商機,並考慮如何融入其支援。如此一來,無論使用者選用哪種瀏覽器,Spotify 都能持續提供更一致且更優質的體驗。
特別銘謝
感謝所有參與 Spotify 打造迷你播放器的 Spotify 使用者。
另外,Spotify 也感謝 Google Chrome 團隊參與協作,並將 Spotify 提供的意見回饋納入 Document Picture-in-Picture API。