HLS 切到背景或鎖定螢幕後停止:以復原為優先的診斷指南

診斷 HLS 在切換分頁、切換應用程式、手機鎖定、頁面凍結或瀏覽器捨棄後出現暫停、延遲或復原失敗的問題。

HLS 播放器原本運作正常,使用者切換分頁、切到其他應用程式、鎖定手機或讓裝置閒置後,問題才出現。返回頁面時,畫面可能停住、聲音消失、播放位置遠遠落後直播,或下一次播放清單要求失敗。這些現象看似相同,起因卻可能分屬不同層級:應用程式碼、瀏覽器生命週期管理、作業系統媒體政策、已經過期的直播視窗、授權,或媒體管線。

不要承諾網頁在所有裝置上隱藏後都能繼續播放影片。應把「持續背景播放」和「可靠復原前景播放」視為兩個獨立的產品要求。請只測試自己擁有或獲准檢查的串流,並從分享的記錄中移除簽章 URL、Cookie、權杖、裝置識別碼、IP 位址和私人主機名稱。

*驗證方法 — 2026 年 9 月 24 日:*我們使用 Node.js 24.12.0 執行一個確定性的復原政策測試。合成直播視窗從媒體時間 40–70 推進到 58–88。儲存位置 42 已經過期,因此測試選擇已宣告的直播同步點 76;儲存位置 62 仍可拖曳,因此得到保留。0–300 範圍內的 VOD 位置 120 得到保留;HTTP 403 選擇重新授權,而不是反覆重試媒體;wasDiscarded: true 則選擇重新初始化直播。這只驗證測試中的範圍與決策計算。它沒有測試瀏覽器生命週期轉換、HLS 播放器、媒體解碼、網路、手機鎖定畫面、作業系統背景政策或實體裝置。測試腳本位於 scripts/test-hls-background-recovery.cjs。

準確命名正在測試的轉換

「切到背景就停了」不是可重現的報告。修改程式碼前,應記錄準確的轉換動作和預期行為。

轉換頁面可能觀察到什麼不能據此證明什麼
另一個瀏覽器分頁變成使用中visibilitychange,且 document.visibilityState === "hidden"頁面已凍結、被捨棄,或獲准繼續播放音訊
瀏覽器或應用程式進入背景可見性變化可能是最後一個可靠事件後續卸載類事件一定會執行
螢幕鎖定文件可以變成隱藏狀態每種瀏覽器和作業系統都保留相同的媒體或網路政策
頁面凍結可凍結工作停止執行,包括許多計時器和回呼算繪程序已經銷毀
頁面被捨棄頁面為節省資源而被移除,稍後重新載入JavaScript 在被捨棄時收到事件
元件卸載或路由變更應用程式可能銷毀播放器或清空媒體來源瀏覽器強制執行了背景政策

MDN 說明,切換分頁、將視窗最小化和在行動裝置上切換應用程式都會觸發 visibilitychange。Chrome 的頁面生命週期指南區分隱藏、凍結、終止和捨棄狀態,並提醒在行動裝置上,進入隱藏狀態通常是頁面最後一個能可靠觀察到的轉換。這些是有用的訊號,但不是媒體一定能跨平台繼續播放的保證。

在頁面消失前蒐集證據

在受控版本中加入短期診斷記錄器。記錄單調時間、牆上時鐘時間、可見性、媒體狀態、目前位置、全部可拖曳和已緩衝範圍,以及最後一個播放器/網路事件。不要記錄完整簽章 URL。

function ranges(value) {
  return Array.from(
    { length: value.length },
    (_, index) => ({ start: value.start(index), end: value.end(index) })
  );
}

function snapshot(video, event) {
  console.info('media-lifecycle', {
    event,
    monotonicMs: performance.now(),
    visibility: document.visibilityState,
    paused: video.paused,
    ended: video.ended,
    currentTime: video.currentTime,
    readyState: video.readyState,
    networkState: video.networkState,
    seekable: ranges(video.seekable),
    buffered: ranges(video.buffered),
  });
}

document.addEventListener('visibilitychange', () => snapshot(video, 'visibilitychange'));
for (const name of ['pause', 'play', 'playing', 'waiting', 'stalled', 'suspend', 'emptied', 'error']) {
  video.addEventListener(name, () => snapshot(video, name));
}

如果播放器提供清單、片段、緩衝、層級和嚴重錯誤事件,也應把它們記錄在同一條單調時間軸上。在支援的環境中蒐集 pagehide、pageshow、freeze 和 resume,但絕不能把 unload 當成儲存狀態或遙測資料的唯一位置。

先排除應用程式主動造成的停止

許多背景故障其實由應用程式本身造成。搜尋每一處 pause()、媒體來源移除、播放器 destroy() 或 detachMedia()、路由清理、狀態重設和 visibilitychange 處理常式。檢視隱藏時,框架元件可能被卸載。節能處理常式可能刻意停止載入,卻沒有配套的復原路徑。兩個生命週期監聽器也可能產生競爭:一個恢復播放,另一個又寫回舊的暫停狀態。

為每一次主動轉換記錄原因,不要只記「已暫停」。診斷版本中的堆疊記錄可以找出 pause() 包裝函式或銷毀流程的呼叫端。使用同一條獲准測試的串流,比較最小播放器頁面和完整應用程式。如果最小頁面可以復原而完整應用程式不行,應先檢查應用程式對播放器的管理,再歸咎於 HLS 或 CDN。

返回後的第一項證據可能的邊界下一項受控檢查
播放器執行個體不存在或媒體來源為空元件生命週期或銷毀流程追蹤卸載、destroy、媒體來源指派和狀態復原
媒體暫停,但沒有失敗要求主動暫停、平台政策或使用者意圖遺失記錄事件順序並測試使用者明確觸發的繼續播放
play() 被拒絕瀏覽器播放政策或媒體狀態不可用記錄 Promise 拒絕的名稱和訊息,不要忽略錯誤
播放清單恢復載入,但本文仍舊CDN 快取或暫停的重新載入迴圈比較時間戳記、媒體序號、Age 和重複本文
第一個要求回傳 401/403授權過期或簽章子資源 URL 過期透過核准的流程更新授權
舊片段回傳 404/410儲存的直播位置已離開保留視窗比較新的播放清單視窗和可拖曳範圍
位元組已到達,但解碼沒有恢復媒體管線、時間戳記、編解碼器或不連續點保留播放器錯誤並檢查返回後的第一段媒體

返回後重新讀取時間軸

不要假定進入背景前儲存的位置仍然存在。載入被限速或停止時,滑動直播播放清單仍可能繼續推進。讀取新的媒體播放清單,再蒐集 video.seekable、播放器直播同步點、所選呈現版本、不連續點以及儲存的 currentTime。

對直播而言,應在三種狀態中做出判斷:

  1. 儲存位置仍可拖曳:如果產品支援使用者主動回看 DVR,就保留該位置。
  2. 儲存位置已過期:加入目前範圍內經過驗證的直播同步目標。
  3. 還沒有可靠範圍:重新載入時間軸並等待,不要跳到憑空編造的位置。

對 VOD 而言,只有儲存位置落在目前可拖曳範圍內時才復原它。無論直播或 VOD,都應把目標限制在真實範圍內。MDN 指出,直播媒體可能失去較早內容,所要求的 currentTime 也可能被調整到媒體支援的位置。

function contains(ranges, time) {
  return ranges.some(range => time >= range.start && time <= range.end);
}

function chooseLiveTarget({ savedTime, ranges, liveSyncPosition }) {
  if (!ranges.length) return null;
  if (contains(ranges, savedTime)) return savedTime;

  const latest = ranges.at(-1);
  return Math.min(latest.end, Math.max(latest.start, liveSyncPosition));
}

可用時,應採用播放器文件說明的直播同步點,而不是數學意義上的末端。直播延遲與「回到直播」指南解釋了為什麼安全目標通常要落後最後一個已公布瞬間。如果拖曳後向前或向後跳轉,請參考 HLS 拖曳與 DVR 診斷。

把播放意圖當成狀態,不要靠猜測

頁面變成隱藏狀態前,儲存播放是否由使用者要求、目前位置、內容識別、串流類型、所選軌道、音量和經過去識別化的授權版本。不要把 paused === false 等同於可以永遠自動重新開始:頁面隱藏時,使用者可能已經透過系統媒體控制暫停播放。

返回後:

  1. 只有播放器已銷毀或頁面曾被捨棄時,才重新建立播放器。
  2. 證據顯示授權過期時更新授權;不要對 401/403 循環重試。
  3. 載入目前播放清單並等待真實可拖曳範圍。
  4. 根據產品承諾選擇 VOD、DVR 或直播同步目標。
  5. 只有儲存的使用者意圖仍要求播放時才呼叫 play()。
  6. 處理回傳的 Promise;如果被拒絕,提供明確的「點選繼續」控制項。
  7. 只有收到 playing 且 currentTime 持續前進後,才把復原標記為完成,而不是在方法呼叫後立刻完成。

這樣可以讓復原操作保持冪等。反覆觸發 visibilitychange 或框架重新算繪,不應建立多個 HLS 執行個體、重複監聽器、互相競爭的播放清單重新載入,或循環的播放/暫停呼叫。

區分授權失敗和媒體失敗

裝置閒置時,短期簽章經常過期。主播放清單可能仍在快取中,但下一個媒體播放清單、金鑰、初始化區段或片段所使用的子 URL 已經過期。記錄返回後第一個要求對應的資源類型和去識別化狀態碼。

拖曳或清空媒體緩衝無法修復 401 或 403。應透過服務支援的流程更新工作階段,或取得新授權的播放 URL。絕不能把一個來源的憑證附加到另一個來源、公開簽章 URL,或為了讓背景復原看似成功而削弱存取控制。

如果播放清單是新的,但已公布物件回傳 404,請使用過期播放清單與遺失片段判斷路徑。如果返回後只是要求太慢,HLS 緩衝診斷會分別檢查傳輸量、片段可用性、解碼和播放器狀態。

為真實復原狀態設計介面

介面應區分「已暫停」「正在重新連線」「正在返回直播」「落後直播」和「點選繼續」。頁面仍在使用舊播放清單,或播放位置尚未前進時,不要顯示「直播中」。使用者主動選擇的 DVR 位置只要仍受支援就應保留;另外提供「回到直播」,不要悄悄覆寫它。

如果背景音訊是產品要求,應明確記錄支援的瀏覽器、安裝模式、作業系統、內容類型、使用者手勢要求、鎖定畫面控制項和中斷行為。一個桌面分頁測試成功,並不能證明 iOS Safari、已安裝 PWA、Android Chrome、WebView 或原生應用程式也會成功。

在實體裝置上執行轉換測試矩陣

自動化能驗證決策程式碼和頁面算繪,但平台政策需要實體裝置。使用同一條獲准測試的串流,並讓每次轉換持續足夠久,使直播視窗或簽章確實推進。

測試維度至少記錄的情況
裝置路徑按產品支援範圍測試手機、平板、桌面、已安裝 PWA、WebView 或原生封裝
轉換切換分頁、切換應用程式、鎖定螢幕、解鎖、來電等中斷、瀏覽器程序回收
持續時間短時間返回、超過一個目標長度週期、超過 DVR 視窗、超過 URL 有效期
媒體VOD、滑動直播、EVENT/DVR、含音訊的影片,以及支援時的純音訊
網路網路不變、隱藏期間離線、Wi-Fi 切換行動網路、強制入口網站重新驗證
預期結果繼續播放、安全暫停、復原儲存位置、加入直播同步點,或要求使用者操作

每個測試都應記錄裝置與作業系統版本、瀏覽器或 WebView 版本、播放器版本、轉換時間、實際收到的可見性/生命週期事件、儲存的意圖、前後播放清單序號、可拖曳範圍、返回後的第一個要求、播放 Promise 結果,以及確認進入 playing 所需的時間。沒有實體測試的平台必須標成僅查閱文件或未測試。

圍繞一次返回過程撰寫錯誤報告

一份可執行的報告可以寫成:「媒體時間 42 時,測試手機在直播範圍 40–70 內鎖定螢幕。返回後目前範圍為 58–88,因此 42 已過期。更新後的播放清單要求成功,播放器提供的直播同步點為 76;經過一次使用者授權的播放要求後,播放在 76 進入 playing。」這能指出時間軸過期,而不是只說「行動裝置停止播放」。

報告應包含預期背景行為、儲存的播放意圖、準確的轉換動作、實際觸發的生命週期事件、轉換前後時間軸、去識別化網路證據、播放器管理權變化、Promise 拒絕詳細資料和實體裝置身分。沒有對應證據時,不要聲稱作業系統終止、凍結或捨棄了頁面。

主要參考資料

從使用者實際執行的轉換開始,沿著第一項發生變化的狀態或要求繼續追蹤。頁面消失前儲存播放意圖,返回後重新建立目前時間軸,再復原到受支援的位置,而不是重播過期狀態。即使平台不保證持續背景播放,這套方法仍能讓前景復原變得可測試。