Spotify 如何運用 Picture-in-Picture API 打造 Spotify 迷你播放器

Guido Kessels
Guido Kessels
François Beaufort
François Beaufort

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 Miniplayer 視窗的螢幕截圖。

這讓 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,讓數百萬名電腦使用者都能使用迷你播放器。

新版 Spotify Miniplayer 視窗的螢幕截圖。

使用 React 建立子母畫面視窗

以下範例說明如何在 React 中使用文件顯示畫面,就像 Spotify 團隊一樣。您將建立兩個 React 元件:MyFeaturePiPContainer

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 持續進步及創新,他們仍致力於強化迷你播放器,並計劃進一步改善其功能和使用者體驗。雖然他們尚未能承諾提供特定功能,但對迷你播放器的未來發展感到興奮。

Spotify Miniplayer 視窗的不同形狀螢幕截圖。

有了 Document Picture-in-Picture API,您就能靈活控管應用程式,打造更直覺、更友善的迷你播放器。我們希望其他瀏覽器供應商能注意到這個 API 提供的商機,並考慮納入相關支援。這樣一來,無論使用者選擇哪個瀏覽器,Spotify 都能為所有使用者提供一致且更優質的體驗。

特別銘謝

感謝 Spotify 團隊中所有參與迷你播放器開發的人員。

Spotify 也要感謝 Google Chrome 團隊的合作,以及在 Document Picture-in-Picture API 方面納入 Spotify 的意見回饋。