Skip to content

feat: add seek AppleScript command and now-playing notification broad… - #333

Open
WGlegola wants to merge 1 commit into
sozercan:mainfrom
WGlegola:feat/now-playing-broadcast
Open

feat: add seek AppleScript command and now-playing notification broad…#333
WGlegola wants to merge 1 commit into
sozercan:mainfrom
WGlegola:feat/now-playing-broadcast

Conversation

@WGlegola

Copy link
Copy Markdown
Contributor

Description

Adds the AppleScript + notification plumbing that lets external "now playing"
surfaces — menu-bar widgets, Stream Deck, and notch apps such as boring.notch —
mirror and control Kaset playback reactively, without polling the app.

Two additions:

  1. seek AppleScript command — jump to an absolute position (in seconds) in the current track, e.g. tell application "Kaset" to seek 90. This completes the transport surface (alongside the existing play / pause / next / previous /
    set volume commands) so an external scrubber can drive playback position.
  2. NowPlayingBroadcaster — posts a lightweight distributed notification (com.sertacozercan.Kaset.playerInfo) whenever discrete playback state changes (current track, play/pause, like status, shuffle, repeat). Listeners refresh by calling the existing get player info command instead of polling on a timer. High-frequency progress and volume are intentionally excluded so the notification doesn't fire continuously during playback or volume drags.

Both are read/control affordances over already-exposed state; no playback or auth
behavior changes.

Type of Change

  • ✨ New feature (non-breaking change that adds functionality)

Related Issues

Enables external now-playing integrations such as TheBoredTeam/boring.notch#1167
("Kaset as a music source"). No Kaset-side issue.

Changes Made

  • Add SeekCommand (KasetSeekCommand) NSScriptCommandPlayerService.seek(to:); coercion-fails with a clear error on a non-numeric argument.
  • Declare the seek command (code Kastseek, real direct-parameter) in Kaset.sdef.
  • Add NowPlayingBroadcaster (in Services/Player/, beside NowPlayingManager):
    observes PlayerService via withObservationTracking and posts a bundle-id-prefixed distributed notification on discrete state changes. Wired up once in MainWindow's
    .task.
  • Document seek (+ shell example) and the notification contract ("Now Playing Notifications") in docs/applescript.md.
  • Tests: SeekCommand (nil-player + invalid-arg); pin the cross-process get player info contract keys consumed by external surfaces (position, duration,
    artworkURL); pin the broadcaster's notificationName string.

Testing

  • Unit tests pass (swift test --skip KasetUITests — 1551 tests, 132 suites)
  • Manual testing performed
  • UI tested on macOS 26+ (no UI surface in this change)

Manual verification against a live build:

  • get player info returns the full state contract; seek 30 / seek 90 jump the position exactly; like track toggles likednone; toggle shuffle,
    cycle repeat, playpause, next track all work.
  • End-to-end with an external consumer (boring.notch): the notch reflects and toggles like status and updates faster than its fallback poll, confirming the distributed notification is delivered across the process boundary.

Checklist

  • My code follows the project's style guidelines
  • I have run swiftlint --strict && swiftformat . (0 violations, 0 formatting changes)
  • I have added tests that prove my fix/feature works
  • New and existing unit tests pass locally
  • I have updated documentation if needed (docs/applescript.md)
  • I have checked for any performance implications (notification excludes high-frequency progress/volume)
  • My changes generate no new warnings

Additional Notes

  • The like/favourite toggle that motivated this work uses Kaset's existing
    like track command — no change is required here for liking. This PR adds the reactive refresh signal (so an external heart updates promptly instead of on a poll) and the seek transport command.
  • The distributed notification carries no userInfo (the App Sandbox strips it from a sandboxed sender) and uses a bundle-id-prefixed name so the sandbox permits posting; listeners read full state via get player info. It is a bare, change-only trigger, so consumers poll get player info for the initial state and for position/volume.
2026-06-30 22-15-19

@WGlegola

Copy link
Copy Markdown
Contributor Author

Related boring.notch pr:
TheBoredTeam/boring.notch#1362

@WGlegola
WGlegola force-pushed the feat/now-playing-broadcast branch from d0e5ae7 to bce27fc Compare July 1, 2026 13:14
@WGlegola

WGlegola commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

TheBoredTeam/boring.notch#1362 (comment)

Action for making working connection have to be taken from this side. If this solution is accepted prerequisite for merging boring.notch side of implementation would be fulfilled.

Afaik this method is standard on macos. If im not mistaken this is how spotify and similar comunicate their status. Also seems more straight forward than #210.

…cast

Adds a 'seek' AppleScript command (jump to a position in seconds) and a NowPlayingBroadcaster that posts a com.sertacozercan.Kaset.playerInfo distributed notification on playback/track/like changes, so external now-playing surfaces (e.g. boring.notch's Kaset source) can refresh without polling. Updates the AppleScript docs.
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.

1 participant