feat(players): keep a video playing in the background on Brave iOS; repeat on the watch page
On iPad Brave, a shared video stopped the moment the browser left the foreground, even with Brave's "Enable Background Audio" on, while an mp3 kept playing. Brave iOS keeps a web <video> going by re-playing one the OS paused (brave-core ios/browser/web/media/resources/media_backgrounding.ts), but it only hooks a <video> present at document start or added as a MutationObserver addedNode that IS a video. React inserts a player's <video> inside its freshly mounted wrapper in one go, so Brave never saw it. lib/browserBackgroundPlay re-inserts a mounted <video> at its own position, once per element: it becomes an added node, the DOM React manages does not change, and playback is not interrupted. The watch page, the downloads library player and the Plex player hand their <video> to it. Measured on the owner's iPad (Brave) with a prototype of the same idea: the video kept playing in another app and with the screen locked. The YouTube modal is not ours to change: its <video> lives in YouTube's cross-origin iframe, and nothing of ours pauses it on backgrounding. The prototype's two audio-element variants (a muted video following an <audio>, and an <audio> taking over on hide) went silent in the background on the iPad and are removed. The watch page gets a Repeat toggle, on by default and remembered per browser (LS.watchRepeat; the page has no account): it sets `loop` on the <video> or the audio-only <audio>.
This commit is contained in:
@@ -11,6 +11,8 @@ vi.mock("react-i18next", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("react-i18next")>();
|
||||
return { ...actual, useTranslation: () => ({ t: (key: string) => key }) };
|
||||
});
|
||||
const expose = vi.hoisted(() => vi.fn());
|
||||
vi.mock("../lib/browserBackgroundPlay", () => ({ exposeVideoToBackgroundPlay: expose }));
|
||||
|
||||
import LibraryPlayer from "./LibraryPlayer";
|
||||
import type { DownloadJob } from "../lib/api";
|
||||
@@ -90,6 +92,11 @@ describe("LibraryPlayer", () => {
|
||||
expect(document.body.querySelector("audio")).toBeNull();
|
||||
});
|
||||
|
||||
it("hands its <video> to the background-play hook, so Brave iOS keeps it playing", () => {
|
||||
render(<LibraryPlayer job={withAsset({ width: 1280, height: 720 })} onClose={() => {}} />);
|
||||
expect(expose).toHaveBeenCalledWith(document.body.querySelector("video"));
|
||||
});
|
||||
|
||||
it("plays an audio-only asset (media_kind='audio') in an <audio> element", () => {
|
||||
render(
|
||||
<LibraryPlayer
|
||||
|
||||
Reference in New Issue
Block a user