From 4bd287515fa118b6a337e9af9494596e2055decf Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Tue, 10 Mar 2026 07:56:54 -0700 Subject: [PATCH] feat(spf): basic ManagedMediaSource support for Safari (#843) --- packages/spf/src/dom/features/setup-mediasource.ts | 8 +++----- .../spf/src/dom/features/tests/setup-mediasource.test.ts | 2 +- packages/spf/src/dom/media/mediasource-setup.ts | 4 ++++ packages/spf/src/dom/media/mediasource.d.ts | 9 ++++++++- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/spf/src/dom/features/setup-mediasource.ts b/packages/spf/src/dom/features/setup-mediasource.ts index 45b1c5d7..20c6e07d 100644 --- a/packages/spf/src/dom/features/setup-mediasource.ts +++ b/packages/spf/src/dom/features/setup-mediasource.ts @@ -62,11 +62,9 @@ export function setupMediaSource({ settingUp = true; abortController = new AbortController(); - // Create MediaSource - // Note: ManagedMediaSource (Safari) requires disableRemotePlayback and - // startstreaming/endstreaming event handling that SPF doesn't yet implement. - // Use standard MediaSource until ManagedMediaSource support is added. - const mediaSource = createMediaSource(); + // Prefer ManagedMediaSource when available (Safari 17+). attachMediaSource + // handles the srcObject path and sets disableRemotePlayback automatically. + const mediaSource = createMediaSource({ preferManaged: true }); // Attach to element attachMediaSource(mediaSource, currentOwners.mediaElement!); diff --git a/packages/spf/src/dom/features/tests/setup-mediasource.test.ts b/packages/spf/src/dom/features/tests/setup-mediasource.test.ts index e5dfb3ba..5153938f 100644 --- a/packages/spf/src/dom/features/tests/setup-mediasource.test.ts +++ b/packages/spf/src/dom/features/tests/setup-mediasource.test.ts @@ -106,7 +106,7 @@ describe('setupMediaSource', () => { // Wait for async operation await vi.waitFor(() => { - expect(createMediaSource).toHaveBeenCalledWith(); + expect(createMediaSource).toHaveBeenCalledWith({ preferManaged: true }); }); cleanup(); diff --git a/packages/spf/src/dom/media/mediasource-setup.ts b/packages/spf/src/dom/media/mediasource-setup.ts index 07af9b42..8e9b8d2a 100644 --- a/packages/spf/src/dom/media/mediasource-setup.ts +++ b/packages/spf/src/dom/media/mediasource-setup.ts @@ -88,6 +88,10 @@ export function attachMediaSource(mediaSource: MediaSource, mediaElement: HTMLMe const isManagedMediaSource = supportsManagedMediaSource() && mediaSource instanceof ManagedMediaSource!; if (isManagedMediaSource) { + // ManagedMediaSource requires disableRemotePlayback — without it Safari + // will not fire sourceopen. + (mediaElement as HTMLMediaElement & { disableRemotePlayback: boolean }).disableRemotePlayback = true; + // Use srcObject for ManagedMediaSource (mediaElement as HTMLMediaElement & { srcObject: MediaSource | null }).srcObject = mediaSource; diff --git a/packages/spf/src/dom/media/mediasource.d.ts b/packages/spf/src/dom/media/mediasource.d.ts index 0ef9628d..b37dcaa2 100644 --- a/packages/spf/src/dom/media/mediasource.d.ts +++ b/packages/spf/src/dom/media/mediasource.d.ts @@ -7,7 +7,14 @@ * Provides better lifecycle management for background tabs and picture-in-picture. */ declare global { - interface ManagedMediaSource extends MediaSource {} + interface ManagedMediaSource extends MediaSource { + /** True when the browser wants the app to be streaming data. */ + readonly streaming: boolean; + addEventListener(type: 'startstreaming', listener: EventListener): void; + addEventListener(type: 'endstreaming', listener: EventListener): void; + removeEventListener(type: 'startstreaming', listener: EventListener): void; + removeEventListener(type: 'endstreaming', listener: EventListener): void; + } const ManagedMediaSource: | {