Skip to content

Create generic Now Playing Surfaces interface for notch and bridge integrations #260

Description

@sozercan

This was generated by AI during triage.

Summary

Create a generic Now Playing Surfaces interface so notch-style UI, local bridge integrations, menu bar widgets, and future playback surfaces can plug into one shared model instead of each integration wiring directly into PlayerService, lyrics fetching, WebView polling, settings, and app lifecycle.

Context

Two PRs are currently adding notch-related behavior through separate paths:

Both need the same core inputs: current track metadata, playback state, progress, artwork, like/shuffle/repeat state, volume, and optionally the current synced lyric line. They should not each own bespoke snapshot building, command routing, lyrics fetching, or polling lifecycle.

Proposal

Introduce a shared NowPlayingSurface layer:

  • NowPlayingSnapshot as the canonical read model for external/auxiliary playback surfaces.
  • NowPlayingSnapshotStore or equivalent observable store that derives snapshots from PlayerService and SyncedLyricsService.
  • NowPlayingCommandRouting to route commands like play, pause, seek, volume, next/previous, like/dislike, shuffle, and repeat.
  • NowPlayingSurfaceAdapter for concrete surfaces to start/stop against a narrow context.
  • LyricsPollingCoordinator so multiple lyric-consuming surfaces can share high-frequency WebView polling safely.

Suggested adapter shape:

@MainActor
protocol NowPlayingSurfaceAdapter: AnyObject {
    var descriptor: NowPlayingSurfaceDescriptor { get }

    func start(context: NowPlayingSurfaceContext) async
    func stop() async
}

struct NowPlayingSurfaceContext {
    let snapshots: NowPlayingSnapshotStore
    let commands: any NowPlayingCommandRouting
    let openMainWindow: @MainActor () -> Void
}

Boring Notch Compatibility

Boring Notch should be implemented as a bridge adapter, not as the generic interface itself:

LocalNowPlayingBridgeAdapter
  -> BoringNotchCodec

The codec can translate:

  • NowPlayingSnapshot to the existing Boring Notch-compatible JSON payload.
  • Boring Notch HTTP/WebSocket requests to NowPlayingCommand.

This keeps Boring Notch support compatible with #210 while avoiding Boring Notch-specific concepts in core app lifecycle and settings.

Acceptance Criteria

  • There is one canonical now-playing snapshot used by native notch UI and local bridge integrations.
  • Surface adapters do not directly depend on PlayerService, SyncedLyricsService, SingletonPlayerWebView, or SettingsManager.
  • Music Island / native notch UI can render from NowPlayingSnapshot only.
  • Boring Notch compatibility can be expressed as LocalNowPlayingBridgeAdapter + BoringNotchCodec.
  • Synced lyric fetching and high-frequency WebView polling are coordinated centrally and do not stop incorrectly when one of multiple lyric surfaces disappears.
  • Settings store enabled surfaces generically, instead of adding one boolean per integration.
  • The design preserves Add support for proper integration with boring-notch #210's local API compatibility surface for Boring Notch.

Notes

Relevant existing architecture:

  • PlayerService is the playback source of truth.
  • SyncedLyricsService is the lyric source of truth.
  • ADR-0012 already centralizes synced lyric resolution and polling concerns.
  • ADR-0014 keeps third-party extension behavior user-managed and opt-in.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions