Skip to content

feat(track-log): implement MusicKit event capture and debug logging#17

Merged
flexiondotorg merged 9 commits intomainfrom
track-log
Mar 22, 2026
Merged

feat(track-log): implement MusicKit event capture and debug logging#17
flexiondotorg merged 9 commits intomainfrom
track-log

Conversation

@flexiondotorg
Copy link
Copy Markdown
Member

Summary

Wire MusicKit.js event capture through to main process with debug logging.
The renderer hook injects into music.apple.com, captures track changes and
playback events, and forwards them via IPC to a Player EventEmitter that
logs at debug level. Includes event enum decoding and volume polling for
complete visibility into playback state.

Changes

  • Extract shared getAssetPath() to src/paths.ts
  • Create Player EventEmitter class to receive IPC events and log at debug level
  • Implement MusicKit hook script in assets/musicKitHook.js with all six event listeners
  • Wire hook injection in src/main.ts did-finish-load handler
  • Register six IPC listeners in main process forwarding to player
  • Throttle playbackTimeDidChange logging to 10s intervals (event emits every tick for MPRIS)
  • Decode playback state enum values (0→none, 1→loading, 2→playing, etc.) in logs
  • Decode repeat mode enum values (0→none, 1→one, 2→all) in logs
  • Decode shuffle mode enum values (0→off, 1→songs) in logs
  • Add volume polling (250ms) to detect slider-driven changes that bypass MusicKit setter

Testing

Performed manual testing with just run and just run-debug:

  • Hook injection confirmed via [Sidra] MusicKit hooked successfully in console
  • Track metadata logged on playback (name, artist, album, duration, artwork, genres, track ID, audioTraits, trackNumber, targetBitrate)
  • Null handling verified when queue clears
  • All six events produce debug log lines with decoded enum values
  • Volume adjustments detected via slider in web UI
  • Playback state transitions logged with readable names
  • No private API access (only public item.attributes.* and mk.* properties)
  • just build and just lint pass

Architecture

  • New files: src/paths.ts (shared utility), src/player.ts (event hub), assets/musicKitHook.js (renderer hook)
  • Modified files: src/tray.ts, src/main.ts
  • No dependency changes, no changes to TypeScript config
  • Player is built as a full EventEmitter from the start to support future MPRIS and Discord integration consumers

Constraints Maintained

  • Uses only public, documented MusicKit.js APIs
  • No access to _state internals or undocumented properties
  • Hook injection via standard webContents.executeJavaScript() pattern
  • IPC via standard Electron preload bridge already in place

… hub

- Extract getAssetPath() to src/paths.ts for reuse across modules
- Create Player class in src/player.ts as EventEmitter hub for track
  events
- Update src/tray.ts to import getAssetPath() from shared paths module

These changes establish the foundation for track logging: a centralised
paths utility eliminates duplication, and the Player event hub provides
the integration point for dispatching track change events to logging
consumers.

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Inject IIFE with re-entry guard to prevent duplicate listeners on SPA
  navigation
- Poll for MusicKit availability with 500ms intervals
- Attach six event listeners: playbackStateDidChange,
  nowPlayingItemDidChange, playbackTimeDidChange, repeatModeDidChange,
  shuffleModeDidChange, volumeDidChange
- Expose control object at window.__sidra with nine control methods for
  testing
- IPC bridge implemented (main process wiring)

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Inject musicKitHook.js via executeJavaScript() in did-finish-load
  handler
- Instantiate Player after menu setup completion
- Register six ipcMain.on listeners forwarding track change events to
  player handlers

Completes end-to-end pipeline: renderer hook captures MusicKit events →
IPC forwards payloads → main process player logs and re-emits.

Signed-off-by: Martin Wimpress <code@wimpress.io>
…intervals

- Add lastTimeLogAt instance field to track last log timestamp
- Throttle debug output to max once per 10 seconds while maintaining
  event emission on every tick
- Preserves MPRIS and integration compatibility by keeping events
  unthrottled

Event emission frequency unchanged; only log output is rate-limited to
reduce debug noise during continuous playback.

Signed-off-by: Martin Wimpress <code@wimpress.io>
- Add PLAYBACK_STATES lookup map to decode numeric state values
  (0→none, 1→loading, 2→playing, 3→paused, 4→stopped, 5→ended,
  6→seeking, 7→waiting, 8→stalled, 9→completed)
- Decode state before logging for improved readability
- Preserve original numeric payload for event emission to MPRIS and
  other downstream consumers

Improves log readability without affecting message content or APIs.

Signed-off-by: Martin Wimpress <code@wimpress.io>
…bug logs

- Add REPEAT_MODES and SHUFFLE_MODES lookup maps to translate numeric
  payloads to human-readable strings (repeat: 0→none, 1→one, 2→all;
  shuffle: 0→off, 1→songs)
- Decode values before logging in handleRepeatModeDidChange and
  handleShuffleModeDidChange for improved readability
- Pass original numeric payloads unchanged to this.emit() to preserve
  compatibility with MPRIS and other downstream consumers

Signed-off-by: Martin Wimpress <code@wimpress.io>
The music.apple.com volume slider writes directly to
HTMLMediaElement.volume, bypassing MusicKit's setter where
volumeDidChange would normally fire. Poll mk.volume at 250ms intervals
and emit IPC events when the value changes to catch all volume
adjustments. The existing addEventListener listener is preserved and
continues to work for volume changes that do trigger the event.

Signed-off-by: Martin Wimpress <code@wimpress.io>
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="assets/musicKitHook.js">

<violation number="1" location="assets/musicKitHook.js:51">
P2: Volume changes can be emitted twice because `lastVolume` is not synchronised in the `volumeDidChange` listener, so the poller re-emits the same update.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

When MusicKit's volumeDidChange event fires, the lastVolume guard
variable is now updated before sending the IPC message. This prevents
the 250ms polling fallback from seeing a stale lastVolume value and
emitting a duplicate volume change event.

Previously, rapid volume changes from window.__sidra.setVolume() could
be reported twice: once by the event listener and again by the poller,
because the guard check happened after the IPC send.

Signed-off-by: Martin Wimpress <code@wimpress.io>
…round

- AGENTS.md: Added src/paths.ts to project structure and documented
  volume polling workaround for music.apple.com's slider bypassing
  MusicKit's volumeDidChange event
- docs/SPECIFICATION.md: Added MusicKit.js enum values subsection with
  PlaybackStates (0-9), RepeatMode (0-2), and ShuffleMode (0-1)
  mappings; explained HTMLMediaElement volume event workaround and
  polling fallback; documented audioTraits, bitrate, and codec
  availability caveats

Signed-off-by: Martin Wimpress <code@wimpress.io>
@flexiondotorg flexiondotorg merged commit 61774db into main Mar 22, 2026
11 checks passed
@flexiondotorg flexiondotorg deleted the track-log branch March 22, 2026 03:43
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