Skip to content

Add Kaset as a media source - #1362

Open
WGlegola wants to merge 4 commits into
TheBoredTeam:devfrom
WGlegola:feature/kaset-music-source
Open

WGlegola wants to merge 4 commits into
TheBoredTeam:devfrom
WGlegola:feature/kaset-music-source

Conversation

@WGlegola

Copy link
Copy Markdown

What

Adds Kaset (sozercan/kaset) — a native macOS YouTube Music client — as a selectable music source. boring.notch can now display and control Kaset playback (play/pause, next/previous, seek, volume, shuffle, repeat) and toggle the like/heart, the same way it integrates Apple Music and Spotify.

Why

Closes #1167. Kaset users had no way to surface or control playback from the notch. Kaset exposes a full AppleScript suite plus a com.sertacozercan.Kaset.playerInfo distributed notification, so it can be supported cleanly with a dedicated controller.

How

  • New KasetController (modeled on AppleMusicController/SpotifyController), reading state from Kaset's get player info JSON and driving transport/like via its AppleScript commands. State refreshes on a short poll plus Kaset's distributed notification.
  • Registered the source in MediaControllerType, MusicManager.createController, the onboarding + Settings pickers, and added the com.sertacozercan.Kaset apple-events entitlement exception.
  • All @Published state mutation is @MainActor-isolated, emissions are change-gated via PlaybackState: Equatable, and the displayed track is cleared when Kaset isn't running.

Testing

  • ✅ Builds clean (xcodebuild -scheme boringNotch -configuration Debug build → BUILD SUCCEEDED).
  • ✅ Every command, JSON key, and string token verified against Kaset's Kaset.sdef and ScriptCommands.swift (transport + seek; position/duration/volume numeric types; repeating/likeStatus token casing; bundle id com.sertacozercan.Kaset).
  • Manually exercised with Kaset running: metadata + artwork, play/pause/next/previous, seek, volume, shuffle/repeat, like toggle, and clear-on-quit.

Screenshots / recording

2026-06-30 22-15-19

Adds an AppleScript-based KasetController (modeled on AppleMusicController)
so boring.notch can display and control playback from Kaset, a native macOS
YouTube Music client -- play/pause, next/previous, seek, volume, shuffle,
repeat, and the like/heart via Kaset's `like track` command. State is read
from Kaset's `get player info` JSON and refreshed on a short poll plus
Kaset's `com.sertacozercan.Kaset.playerInfo` distributed notification.

Registers the source in MediaControllerType, MusicManager, onboarding,
Settings, and the apple-events entitlement exception. All published state is
mutated on the main actor and emissions are change-gated; the displayed track
is cleared when Kaset isn't running.

Closes TheBoredTeam#1167
@WGlegola

Copy link
Copy Markdown
Author

Related Kaset PR:
sozercan/kaset#333

@Alexander5015

Copy link
Copy Markdown
Member

It seems like there are several competing Kaset implementations, none of which are merged yet into Kaset. Until one is picked by the maintainer and merged, Kaset will not be supported.

wglegola added 3 commits July 2, 2026 16:39
…a timeout

A hung target app (e.g. Kaset with a broken API, but also Music/Spotify) made
NSAppleScript.executeAndReturnError block for up to the Apple Event timeout.
Because it ran via Task.detached on Swift's shared cooperative thread pool, the
2s poll piled up blocked calls and starved the pool, freezing the whole app/notch.

Run the blocking call on a dedicated serial DispatchQueue (isolated from the
cooperative pool, single blocked thread max) and add a caller-side timeout so a
hung app leaves data stale instead of wedging the UI.
…k peek

Kaset is a WKWebView YouTube Music client; its audio is rendered in the
com.apple.WebKit.GPU helper, not its own process. Three consequences fixed:

- Waveform: a process tap on Kaset captures silence, so once capture spun
  up the visualizer switched from the synthetic animation to a flat real
  waveform. Skip real-time capture for out-of-process (webview) sources so
  it keeps the synthetic animation.
- Sneak peek/flip repeating every ~2s: applyPlayerInfo copied the previous
  track's cover forward, so at a track change MusicManager's artwork-gated
  content bookkeeping never advanced and re-fired every poll. Clear the
  carried-forward artwork on track-identity change.
- Intermittent cover: fetchArtworkIfNeeded marked a URL done before the
  fetch, so a failed/non-image fetch (Kaset occasionally reports the YT
  Music homepage URL) never retried. Remember the URL only after a valid
  image decodes; otherwise retry on the next poll.

(cherry picked from commit 1afa6638679ceb61d733ea4ef3edc55e90f0d3de)
Kaset's `get player info` reports artworkURL as the YouTube Music homepage
(https://music.youtube.com/, HTML) for essentially every track, so the cover
fell back to the app icon. It does provide a videoId, so derive the cover from
YouTube's thumbnail endpoint (maxresdefault, falling back to the always-present
hqdefault). Center-crop to a square and re-encode (also validates it decoded)
because thumbnails are 16:9/4:3 and the notch renders cover art with .fit.
A genuine image artworkURL, if Kaset ever provides one, is still tried first.

(cherry picked from commit 748cf01b444aa64f598eb0aaa7804b78d62f0359)
@WGlegola

WGlegola commented Jul 2, 2026

Copy link
Copy Markdown
Author

Added couple of bug fixes

This branch has not been deployed

No deployments
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