Spotify 是全球最受歡迎的音樂串流訂閱服務,持續致力於改善使用者收聽音訊和觀看影片的方式。該服務提供豐富的音樂、Podcast 和有聲書內容庫,每天透過行動裝置、電腦和其他平台服務數百萬名使用者。
Spotify 最近為電腦和網頁播放器用戶推出了 Spotify Miniplayer。迷你播放器的設計目的,是在頂端提供小型精簡視窗,讓使用者隨時隨地存取 Spotify,並提供必要的播放控制選項。這項功能一直是使用者長期以來的訴求,可讓使用者在 Spotify 上欣賞喜愛的藝人、播放清單和 Podcast 時,在不同視窗和應用程式中順暢地同時處理多項工作。
以下將詳細介紹 Miniplayer 的開發過程,從最初的「畫布駭客攻擊」到以新的 Document Picture-in-Picture API 建構的更進階、更友善的版本。
「畫布駭客攻擊」
最初的迷你播放器是在 2019 年推出,當時是 Spotify 網頁版播放器的駭客專案。目標是使用瀏覽器的 Picture-in-Picture (PiP) API for <video>,在永遠置頂視窗中顯示專輯封面。不過,這個 API 主要用於影片元素,無法顯示專輯封面圖片。Spotify 則是將專輯封面轉譯為畫布元素,並使用 HTMLCanvasElement
captureStream()
方法取得即時 MediaStream 物件,藉此解決這個問題。這個串流會成為 PiP API 使用的影片來源。這個方法是根據 Google Chrome 的「Audio Playlist」範例所設計。
Spotify 將畫布與 Media Session API 中設定的適當動作處理常式結合,以控制在子母畫面視窗中顯示哪些播放器控制項。這麼做可為使用者提供浮動視窗,其中包含專輯封面和播放器控制項,讓使用者在專注於其他工作時,也能控制播放作業。
這讓 Spotify 能提供基本迷你播放器。不過,這種做法有幾項限制:
- 系統不支援在 PiP 視窗中顯示影片字幕。由於 Spotify 必須在所有影片上顯示字幕,因此在影片開始播放後,系統會強制關閉 PiP 視窗。
- 只有在播放內容是本地播放時,才會顯示播放器控制項。Spotify 允許使用 Spotify Connect (和其他通訊協定) 進行遠端播放,並希望使用者也能控制這項播放功能
- 系統不支援自訂 PiP 視窗的外觀和風格。Spotify 只能顯示圖片,並使用 Chrome 提供的播放器控制項,因此無法新增 Spotify 品牌或其他播放器控制項。
由於無法控制使用者介面,也無法在此新增 Spotify 專屬功能 (例如喜歡某首曲目),因此他們認為這種做法不適合用於電腦版用戶端。
文件子母畫面:迷你播放器的演進
2023 年初,Spotify 得知 Google Chrome 重新推出了新的 API,可讓任意 HTML 內容顯示在 PiP 視窗中,也就是所謂的 Document Picture-in-Picture API。這項進展讓 Spotify 感到振奮,因為他們可以完全掌控子母畫面視窗的外觀。Spotify 在 Chrome 原點試驗期間與 Chrome 團隊合作,開發了以 Document Picture-in-Picture API 建構的新 Miniplayer。
您可以使用 Document PiP API 開啟新的一律置頂視窗,並附加元素。由於 Spotify Web Player 是 React 網頁應用程式,Spotify 使用了 ReactDOM 的 createPortal()
方法,將自訂元件轉譯至主要應用程式的 PiP 視窗,讓您可以完全控制迷你播放器的外觀和功能。
新的 Document Picture-in-Picture API 也解決了 Spotify 先前的問題:
- 子母畫面視窗中的影片是一般影片元素,且完全支援字幕。
- 您可以完全控制使用者介面,即使透過 Spotify Connect 遠端播放音樂,也能顯示播放器控制項。
- Spotify 能夠整合自家的外觀和播放器控制項,提升使用者體驗。
- 他們成功為 Spotify 的電腦用戶端支援 Document PiP API,讓數百萬名電腦使用者都能使用迷你播放器。
使用 React 建立子母畫面視窗
以下範例說明如何在 React 中使用文件顯示畫面,就像 Spotify 團隊一樣。您將建立兩個 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 也要感謝 Google Chrome 團隊的合作,以及在 Document Picture-in-Picture API 方面納入 Spotify 的意見回饋。