Phase 7: raise the TFM, and make SMTC the primary track source - #13
Merged
Conversation
SMTC lives in Windows.Media.Control, which only exists to bind against when the CsWinRT projections are on, and the projections are switched on by the TFM carrying a version. Phase 7 makes SMTC the primary track source, so this has to land first. 22621 rather than Windows 11's 22000 floor: it is the oldest build still in support, so targeting lower gains nothing. Offstream is Windows 11 only, so this costs no supported user. The plan flagged that raising this changes what the compiler generates around WinRT types and that the hand-rolled IAudioPolicyConfig interop should be re-checked. It is unaffected by construction — it names Windows.Media.Internal.AudioPolicyConfig only as a runtime-class string and otherwise uses ComImport and raw P/Invoke, so there is no WinRT type in the type system for the projections to claim. Verified at runtime rather than argued: it still binds the 21H2 IID and completes a COM round-trip on build 26200. build.ps1 held a second copy of the TFM in its publish path, which went stale the moment this changed and pointed the success message at a directory that no longer existed. It reads the value from Directory.Build.props now, so the next bump cannot desync it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The window title is only readable while Spotify has a window. Minimised to the tray it has none, and detection stopped dead — the recorder could sit idle through a whole session because the user tidied the taskbar. The media session Spotify publishes for the lock screen and volume overlay survives that, so it becomes the primary source. It is also better information. The window title is one string that has to be split back into artist and title on a separator that can legitimately appear inside either of them; SMTC hands over separate fields, and an album with them. Confirmed against the live session, which reported an album no window title carries. "Prefers" means "answers", nothing cleverer: the preferred source wins whenever it returns a track at all, never by being newer or more detailed. An idle answer is still an answer and is not second-guessed — only a null, meaning "I cannot see Spotify", reaches the fallback. Anything more sophisticated means two detectors disagreeing mid-track and a recording whose tags change halfway through. A failing SMTC is treated as a silent one. It is a system service Offstream does not control, so a fault costs the better metadata and never the recording, logged once per transition rather than per poll. Cancellation is exempt — that is the session stopping. Advertisements use the same two rules as the title path, because Spotify announces them the same way to both: the placeholder title, or playing with no artist attached. A paused session is never an ad; the placeholder lingers after playback stops and treating it as one would suppress the next real track. The WinRT call is kept free of decisions — everything decidable sits behind ISmtcSessions in pure code, so all of it is tested without a media session, an audio endpoint, or Spotify installed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 7's first two items. The remaining three — device hot-plug, long-path support, VB-CABLE detection — are not in this PR.
Raise the TFM (the prerequisite)
net10.0-windows→net10.0-windows10.0.22621.0. SMTC lives inWindows.Media.Control, which only exists to bind against once the CsWinRT projections are on, and the version on the TFM is what turns them on. 22621 rather than Windows 11's 22000 floor because it is the oldest build still in support, so targeting lower gains nothing.The plan warned that this changes what the compiler generates around WinRT types and that the hand-rolled
IAudioPolicyConfiginterop should be re-checked. It is unaffected by construction: the routing code namesWindows.Media.Internal.AudioPolicyConfigonly as a runtime-class string, and otherwise usesComImportand raw P/Invoke — there is no WinRT type in the type system for the projections to claim. Verified at runtime rather than argued: it still binds the 21H2 IID and completes a COM round-trip on build 26200.build.ps1held a second copy of the TFM in its publish path and pointed at a directory that stopped existing the moment this changed. It reads the value fromDirectory.Build.propsnow.SMTC as the primary track source
The window title is only readable while Spotify has a window. Minimised to the tray it has none, and detection stopped dead — the recorder could sit idle through a whole session because the user tidied the taskbar.
It is also better information. The title is one string that must be split back into artist and title on a separator that can legitimately appear inside either of them. SMTC hands over separate fields, and an album with them. Confirmed against the live session, which reported an album (
… (Expanded Edition)) that no window title carries."Prefers" means "answers", nothing cleverer. The preferred source wins whenever it returns a track at all — not by being newer, more detailed, or more confident. An idle answer is still an answer and is not second-guessed; only a null, meaning "I cannot see Spotify", reaches the fallback. Anything more sophisticated means two detectors disagreeing mid-track and a recording whose tags change halfway through.
A failing SMTC is treated as a silent one. It is a system service Offstream does not control, so a fault costs the better metadata and never the recording — logged once per transition rather than per poll, since this runs several times a second. Cancellation is exempt: that is the session stopping, and it propagates.
Advertisements use the same two rules as the title path — the placeholder title, or playing with no artist attached — because Spotify announces them the same way to both. A paused session is never an ad: the placeholder lingers after playback stops, and treating it as one would suppress the next real track. An undetected ad is written to the library as a song, which is a worse failure than a missing tag.
The WinRT call is kept free of decisions. Everything decidable sits behind
ISmtcSessions;WindowsSmtcSessionsonly talks to the system. So the mapping and the handover are tested without a media session, an audio endpoint, or Spotify installed. The session manager is fetched once and kept —RequestAsyncis a cross-process call and this is polled several times a second.928 tests green (760 core + 168 UI), up from 906. Twenty-two new, covering the mapping rules, ad detection including the paused case, and every handover branch.
Not verified here
The plan's exit criterion is a clean VM, which I cannot run. What I did verify on this machine: routing still binds under the new TFM, and the real SMTC reader returns Spotify's live session. Worth testing manually: minimise Spotify to the tray and confirm a recording still starts and tags. That is the behaviour this exists for and the one thing a unit test cannot prove.
🤖 Generated with Claude Code