forked from Keelrleek/SoulStream
media card: Windows knows SoulStream is a music player now
Registering with the System Media Transport Controls is what makes an app a media app to the OS rather than a window that happens to make noise. The card that the media-key overlay, the taskbar media flyout and the lock screen all draw now carries our cover, title, artist and album; the hardware media keys reach us with the window unfocused; and the transport buttons on that card drive this app. Verified against the OS's own session list: title/artist/album/albumArtist, type Music, a live scrubber, a present thumbnail, and play disabled while playing. Where each half of it comes from follows where the truth already lives. Status and position are pushed by the backend's player event pump, which already sees every transport change and every position tick — the card is driven by the same events the UI is, so it cannot drift from the window, and it costs no IPC at all. Metadata goes the other way, once per track, published by the player bar: the queue lives in the frontend and so does the derivation the bar displays, and publishing exactly what the bar shows means the two can never disagree. Deriving it a second time in Rust would guarantee that one day they do. Button presses are forwarded as `soulseek:media-button` rather than handled in Rust, because nextTrack knows about shuffle, repeat, the album fill and re-sourcing a track restored from the last session, and none of that is knowable from that side. They run the same functions the on-screen transport runs — the play/pause branch moved into togglePlayPause() so there is one implementation, not two that drift. Proven by driving the app from the OS session: next advanced the queue, pause and play moved the status, previous restarted the track on the >3s rule the app already had. Stop is deliberately disabled: this transport has no stop, only pause, and a dead button on the card is worse than a missing one. Buffering maps to Changing, which is what "finding best source…" honestly is. The thumbnail is the bar's own data URL decoded into an in-memory stream — no temp files — and it is explicitly cleared when a track has no cover, because the previous track's art outliving it is worse than none. The debug handle gains the local playback entry points beside the streaming ones it already had. That is not only convenience: verifying this end to end needs a track playing, and every run that reaches for Soulseek to get one costs a login the server starts ignoring after a burst.
This commit is contained in:
1 parent
f72b12a892
commit
f6eb9ff02f
7 files changed
+439
-7
No files matched your search
@@ -13,7 +13,7 @@
|
||||
getArt,
|
||||
getCatalogArt,
|
||||
playerArtSource,
|
||||
resumePlayback,
|
||||
togglePlayPause,
|
||||
togglePlayerExpanded,
|
||||
showNowPlaying,
|
||||
saveStream,
|
||||
@@ -296,6 +296,31 @@
|
||||
return () => (active = false);
|
||||
});
|
||||
|
||||
/* The OS media card (Windows SMTC — src-tauri/src/smtc.rs). Published from
|
||||
HERE, not from the store: what Windows shows should be what this bar
|
||||
shows, and this is where that is derived — `metaParts` reconciles the
|
||||
item's own artist/album fields against the `sub` split, and `artUrl` is
|
||||
the cover after the local/catalog fork. Deriving either a second time in
|
||||
Rust would be a second set of rules to keep in step.
|
||||
Status and position are NOT sent: the backend's player event pump
|
||||
already has both and drives the card itself, for no IPC. */
|
||||
let lastCard = "";
|
||||
$effect(() => {
|
||||
const title = player.track?.title ?? "";
|
||||
const [artist, album] = metaParts;
|
||||
const art = artUrl;
|
||||
if (!title) return;
|
||||
// Fires twice per track — once when the title lands, again when the
|
||||
// cover finishes resolving — and that is deliberate: the card should
|
||||
// not wait on artwork to say what is playing. Everything past those is
|
||||
// a re-render carrying the same content, dropped here rather than
|
||||
// crossing IPC with a few hundred KB of base64 cover for nothing.
|
||||
const key = `${title} | ||||