Skip to content

Mac: Spotify pill and a richer Apple Music card - #246

Merged
Louis-CFM merged 9 commits into
Louis-CFM:mainfrom
JhoanG956:feat/mac-spotify-apple-music
Oct 8, 2026
Merged

Louis-CFM merged 9 commits into
Louis-CFM:mainfrom
JhoanG956:feat/mac-spotify-apple-music

Conversation

@JhoanG956

Copy link
Copy Markdown
Contributor

Spotify: a new pill

integration_spotify, GitHub build only like Apple Music (the App Store sandbox would need an Apple Events exception for Spotify).

  • Card: album cover, title, "artist · album", a progress bar you drag or click to seek (elapsed and remaining time), and shuffle · previous · play/pause · next · repeat, plus a small volume slider. Clicking the cover brings Spotify forward.
  • Pill: the track title, play/pause and next on hover, like the Apple Music pill. Mochi dances while it plays, in the notch and on the desktop.
  • Idle states: "Not playing", "Spotify not installed" with a Get Spotify link (Settings shows "Not installed" too), and "Allow Coucou to control Spotify" when Automation is denied.
  • No polling: state comes from Spotify's com.spotify.client.PlaybackStateChanged distributed notification, then one AppleScript read for what the notification lacks (position, shuffle, repeat, volume, artwork URL). The position is extrapolated in the card, with a TimelineView paused unless the card is on screen and playing. The card re-reads the position when it appears, since seeks made in Spotify's own window post no notification.
  • Never launches Spotify: every Apple Event is guarded by a running check.
  • Automation prompt: asked when the user turns the pill on, or first opens its card, not at app launch.
  • Cover: Spotify's artwork url (i.scdn.co). Before Automation is allowed, Spotify's public oEmbed (open.spotify.com/oembed) gives it from the track link. Only *.scdn.co / *.spotifycdn.com images are fetched; the last 12 covers are cached in memory.

Apple Music: the same card

The Apple Music card gets the Spotify layout. Its pill ID, its hover pill and its Automation-denied card are unchanged.

  • Cover from Music itself (raw data of artwork 1). When the track has none (some streams), it's looked up in Apple's public iTunes Search API with the artist and album names; only *.mzstatic.com images are fetched.
  • Seek, shuffle, repeat off → all → one (with the repeat.1 icon), volume, and a favorite heart next to the title (favorited, shown only when Music can favorite the track; it reverts if Music refuses).
  • MusicController reads typed AppleScript results now (numbers, booleans, data) instead of strings only, and follows the same anchor/extrapolation model as Spotify.

Shared pieces

The cover, progress bar, volume slider and buttons live in NowPlayingViews.swift and are used by both cards. Each player keeps its own controller and pill.

Also

  • NSAppleEventsUsageDescription mentions Spotify (project.yml and the regenerated Info.plist). project.pbxproj is left to xcodegen, as on main.
  • Privacy policy (EN/FR): a Spotify paragraph, and the Apple Music paragraph no longer says nothing leaves the computer, since the cover may come from the iTunes Search API. README, SPEC, INTEGRATIONS and CHANGELOG updated.
  • macOS only: the Windows app has no music pill.

Testing

  • Built the GitHub (NotchBuddy) and App Store (CoucouAppStore) schemes.
  • Tested on the Mac with Spotify and Apple Music: track info, cover, seek, controls, volume, favorite, Automation prompt.
  • The Spotify AppleScript and both cover fallbacks (oEmbed, iTunes Search) were also run on their own against live data.

🤖 Generated with Claude Code

Spotify (GitHub build): a new integration_spotify pill. Its card shows the
album cover, title, artist and album, a progress bar you drag or click to
seek, play/pause, previous/next, shuffle, repeat and volume. State comes from
Spotify's com.spotify.client.PlaybackStateChanged notification and AppleScript;
nothing polls and Spotify is never launched by Coucou. The cover comes from
Spotify's `artwork url`, or from its public oEmbed before Automation is allowed.

Apple Music: the card gets the same layout, with the cover (from Music, or
Apple's public iTunes Search API when the track has none), seek, shuffle,
repeat all/one, volume and a favorite heart. The pill ID, the hover pill and
the Automation-denied card are unchanged.

The cover, bar, volume and buttons live in NowPlayingViews.swift and are shared
by both cards; each player keeps its own controller. Mochi dances for either.
Privacy page, README, SPEC, INTEGRATIONS and CHANGELOG updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JhoanG956

Copy link
Copy Markdown
Contributor Author

Hey Louis!

I've got a few PRs open from my fork and their CI is waiting on you to hit "Approve and run" (GitHub blocks workflows from forks until the repo owner says ok):

All of them build on my side (macOS, Linux and Windows on my fork's CI). Whenever you have a minute, no rush. And if you'd like anything changed, just tell me!

Thanks a lot

JhoanG956 and others added 8 commits October 6, 2026 10:29
…anch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into the Spotify / Apple Music branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ternaries and String parameters were shown verbatim, so the help texts,
the idle and denied states and the Settings hint now go through
String(localized:) or LocalizedStringKey, with their 19 strings in the
catalog for the 10 languages.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fy / Apple Music branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Apple Music branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y / Apple Music branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…anch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Louis-CFM added a commit that referenced this pull request Oct 8, 2026
Spotify pill (#246), Apple Music card unchanged
@Louis-CFM
Louis-CFM merged commit 98a6470 into Louis-CFM:main Oct 8, 2026
JhoanG956 pushed a commit to JhoanG956/coucou that referenced this pull request Oct 8, 2026
…sign

Keeps the Spotify pill, its card, the shared now-playing views it uses, the
dance rules and the Spotify docs. Restores MusicController and the Apple
Music card exactly as they ship (CLAUDE.md: never restyle a shipped view
unless asked), and the Apple Music privacy paragraphs and README line that
described the redesign. The project file is left to XcodeGen; the release
notes are written at release time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJm3SRL9k3gBPaVtfPhwcT

Copy link
Copy Markdown
Owner

Merged in #330 — thank you @JhoanG956! The Spotify pill is great: no polling, never launches Spotify, and Automation is only asked when the pill is turned on. 🙌 One thing I left out on purpose: the Apple Music card redesign. A rule of the project is that shipped views don't change look unless that's the goal of the PR, so Apple Music keeps its current card for now.


Generated by Claude Code

@JhoanG956

Copy link
Copy Markdown
Contributor Author

Sounds good! 😎

JhoanG956 pushed a commit to JhoanG956/coucou that referenced this pull request Oct 8, 2026
The Mac has a Spotify pill (Louis-CFM#246) and Mochi dances while music plays; the
Windows and Linux app had neither. Linux has what the Mac gets from
Spotify's notification and AppleScript in MPRIS, Spotify's D-Bus interface,
so the pill comes to Linux first.

Rust (src-tauri/src/spotify.rs, Linux only):
- A small MPRIS client on libdbus (`dbus` 0.9.12, already in Cargo.lock
  through keyring: nothing new is downloaded). While the pill is declared,
  one thread blocks on the session bus and wakes only for Spotify's
  PropertiesChanged and Seeked signals and NameOwnerChanged (Spotify
  starting or quitting): no polling, 0 % CPU when nothing happens.
  Undeclaring the pill pings that thread off the bus.
- Track (id, title, artist, album, length, cover URL), status, position,
  shuffle, loop status and volume, cleaned like the Mac (shortTitle,
  shortArtist). Position isn't signalled: it is read when the status or the
  track changes and when the card shows, and the page runs it on from a
  timestamp, as SpotifyController.position(at:).
- Controls: PlayPause, Next, Previous, SetPosition (Seek on older builds),
  Shuffle, LoopStatus, Volume. Open raises Spotify, else starts `spotify`
  from PATH, the Flatpak or the Snap, else opens the download page.
- Covers: the CSP keeps remote images out of the page, so Rust fetches them
  (Spotify's CDN only, or a local track's file), checks the bytes are an
  image and hands a data URL over; nothing is fetched while paused.

Page:
- `integration_spotify` in the catalog (Mac values), with a new `linux`
  support next to `windows`: offered on Linux only, nothing to set up;
  declared from Settings → Integrations.
- SpotifyPill and SpotifyCardView ported: dancing mini Mochi with
  play/pause and next on hover; cover, title, artist · album, progress with
  seek, shuffle / previous / play-pause / next / repeat, volume; the idle
  card says Not playing or Spotify not installed with Open / Get Spotify.
  Mac strings, in every language already.
- The Mac's BotEngine dance (setDancing, applyDance, happy eyes) in
  mochi/engine.ts, with the Mac's rules: the compact island whenever music
  plays, expanded on the Spotify card, the pill's mini Mochi, and Mochi on
  the desktop (the island sends him `dancing`). Music starting shows the
  hidden island without the peek sound. On Windows nothing reports music,
  so nothing changes there.

Tests: MPRIS parsing from dbus variants and plain values, position
extrapolation, track ids, covers' sources and types (Rust, plus an
end-to-end test against a fake Spotify on a private bus, run with
`dbus-run-session -- cargo test -p coucou --lib spotify -- --ignored`);
the dance math, the dance rules, the catalog's Linux-only filtering, report
handling and the views (Node).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJm3SRL9k3gBPaVtfPhwcT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants