mirror of
https://github.com/MioMioOS/mio-plugin-music.git
synced 2026-06-11 03:44:31 +00:00
69776ecec2
2 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
69776ecec2 |
v2.2.1: real lyrics via LRCLIB + stream envelope fix + TimelineView vinyl
Critical fix · adapter stream was silently empty
v2.2.0 parsed the stream subprocess's JSON at the wrong layer. The
`stream` mode wraps every emit as:
{"type":"data","diff":<bool>,"payload":{title,...}}
but Swift was decoding as AdapterStreamPayload directly (the shape
used by `get`, which is flat). Result: every `stream rx` had
title="" because the real data was nested inside payload, so
hasTrack was always false and onUpdate never fired. Users saw
"nothing playing" even while Apple Music was running.
New AdapterStreamEnvelope decodes the wrapper, extracts payload,
and also honours `diff: false` to reset currentInfo before merging
(stale fields from the previous track were otherwise leaking).
Added bootstrap path · cold start with music already playing
When the adapter subprocess is spawned AFTER Apple Music is already
playing, the stream's initial emit can be an empty baseline. A
parallel one-shot `perl adapter.pl get` at spawn+300ms catches the
current track immediately.
Added file-based debug log at /tmp/mio-plugin-music-debug.log
NSLog / os_log are unreliably filtered on macOS 15, and we can't
attach Xcode to a plugin loaded from a signed host. A line-oriented
log at a fixed path is the one channel that's always readable post-
mortem. Lines include stream rx / bootstrap / parse failures.
Real lyrics · LRCLIB integration
- New LyricsService fetches synced lyrics via
https://lrclib.net/api/get (exact) + /search (fallback), parses
LRC format with regex [mm:ss.xx]. In-memory LRU cache (32 entries,
1-hour TTL). Negative-caches "not found" so obscure tracks don't
re-hit the API every render.
- NowPlayingState gains syncedLyrics + currentLyricIndex @Published.
applyAdapterUpdate detects track changes and refreshes lyrics
detached; the 1s playback timer updates currentLyricIndex.
- DesktopLyricsViews replaces the placeholder text with real lyric
text from syncedLyrics[currentLyricIndex ± 1]. Falls back to
sensible dots when no lyrics loaded / instrumentals.
Bonus · robust vinyl spin via TimelineView
withAnimation(.linear.repeatForever) loses the animation when
SwiftUI re-creates the view (window hide/show, style switch).
Replaced with TimelineView driven by wall-clock — angle =
(elapsed * 45°/s) % 360. Smooth across hours, no drift, pauses
correctly via `paused: !isPlaying`.
Borrowed approach from Atoll (github.com/Ebullioscopic/Atoll)
MusicManager.swift:756-935: same LRCLIB endpoints, same LRC regex
shape, same per-second sync model. Credited in LICENSE-THIRD-PARTY.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d5934b06b0 |
v2.1.0: Atoll-style MediaRemoteAdapter — bypass 15.4+ entitlement gate
Ports the MediaRemoteAdapter pattern from Atoll (github.com/Ebullioscopic/Atoll). On macOS 15.4+, Apple gated MRMediaRemoteGetNowPlayingInfo behind a private entitlement, which made our previous MediaRemoteSource return empty dicts and forced us onto slow-path AppleScript polling. This commit bundles Jonas van den Berg's MediaRemoteAdapter.framework (BSD-3-Clause) plus mediaremote-adapter.pl and runs them as a subprocess — the framework links against Apple's MR in a way that skips the caller-side entitlement check, so we get the full now-playing payload (title, artist, album, duration, elapsed, isPlaying, artwork, bundleIdentifier) pushed to us in real time. Bundle additions (~500KB total): - Resources/MediaRemoteAdapter.framework (universal x86_64 + arm64 + arm64e) - Resources/mediaremote-adapter.pl - LICENSE-THIRD-PARTY.md with full BSD-3-Clause attribution New source: MediaRemoteAdapterSource.swift - Spawns /usr/bin/perl with minimal env (PATH + LANG only). - FileHandle.readabilityHandler ingests newline-delimited JSON stream from stdout, parses via Codable AdapterStreamPayload, merges diffs into persistent MediaRemoteInfo so playbackRate-only payloads don't erase title/artist. - Artwork base64 decoded via Data default strategy. - Crash handling: SIGTERM → 500ms → SIGKILL on stop. Auto-restart with exponential backoff (1s/2s/4s), circuit-breaker after 3 crashes within 60s → fall back to legacy chain. - Transport controls (togglePlay/next/prev/seek) via short-lived one-shot `perl adapter.pl send N` subprocesses. send codes: 2=toggle, 4=next, 5=prev. seek takes microseconds. NowPlayingState wiring: - New sticky kind `.mediaRemoteAdapter`, highest priority. - `applyAdapterUpdate(_:)` publishes directly (no router pass). - `routeSources` short-circuits when adapter is sticky + has data — subprocess pushes fresh data on every change, polling would be pure waste. - `adaptivePollInterval()` returns 30s for adapter (safety net only). - `isCandidateLive` + `tryFetch` treat adapter as push-only (returns nil from pull-fetch so the sticky fast-path falls through to parallel probing if subprocess is dead). - `stop()` terminates the subprocess cleanly. - Transport controls route to adapter.sendCommand() / adapter.seek() when it's the sticky source. Build: - build.sh copies Resources/ into Contents/Resources with preserved exec bits on the framework binary + Perl script. - `codesign --force --deep --sign -` re-signs the whole tree ad-hoc so the nested framework inherits our identity and Gatekeeper loads it without complaint. - Bundle grew from 48KB → 1.6MB (zipped 564KB). Acceptable for the latency win: Apple Music track switches now visible <100ms vs prior 800ms adaptive-poll worst case. Security audit (done before bundling): - Perl script: strict + warnings, whitelisted function names, no shell-out, no network I/O, params passed to framework via ENV (no string concat). Safe. - Framework: ad-hoc signed (Identifier com.vandenbe.MediaRemoteAdapter). --deep re-sign with our identity replaces the original ad-hoc cert so signature validation passes locally and in Gatekeeper. - Subprocess runs with PATH=/usr/bin:/bin + LANG only. No inherited secrets. - Explicit Process arguments array — no shell interpolation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |