Device hot-plug, extended-length paths, VB-CABLE detection (Phase 7) - #15
Merged
Conversation
Long paths. Offstream does not write the output file — ffmpeg does, in a separate process with its own manifest — so a longPathAware opt-in of ours would not reach it. The \\?\ prefix does: it travels with the path through ArgumentList into whatever ffmpeg hands to CreateFile. Verified rather than assumed, since the feature rests on it: ffmpeg 8.1 wrote an MP3 to a 298-character destination through a prefixed path, on a machine with the LongPathsEnabled registry switch off. The prefix turns off path normalisation, which is the trap. Windows stops resolving . and .., stops converting / to \, and stops trimming trailing dots and spaces, so a merely untidy path becomes one the filesystem rejects. It is applied only to fully-qualified paths, normalised first, and only when the path is long enough to need it. Extended paths raise the total length and not the component length, which is the part that catches people out. The 260 budget divided across template levels became a 32767 budget dividing into per-level allowances of thousands of characters, which renders folder names NTFS refuses outright — trading "truncated at 260" for "cannot be written at all". The per-level allowance is clamped to 255. One existing test asserted that a deep output root was too long. It is no longer, which is the whole point, so it now asserts the opposite; a companion case keeps the check honest by proving it still fires on a root with genuinely no room left. VB-CABLE detection only, and that is a licence decision rather than an unfinished one. The package is donationware whose readme forbids integrating it into another installation procedure without the author's agreement. The predecessor ships the vendor's setup executables in its own tree, launches them elevated, and displays neither the origin nor the donationware notice the licence asks for. Offstream carries no vendor binaries: it detects the cable by the driver's product name, as the reference does, and sends the user to vb-audio.com when it is absent. Plan open question 9 has to be answered before any other form ships. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Losing the endpoint mid-recording was silent. WASAPI stops delivering buffers, the session keeps believing it is capturing, and the file that lands is however many seconds arrived before the headphones came out. It reports and stops rather than re-routing. Moving a running capture to another endpoint sounds like the helpful thing and is not: the replacement can have a different sample rate and channel count, so the recording in progress would gain a seam mid-file or a tail encoded from a format its header does not describe. Losing the endpoint now ends that recording cleanly and says why; choosing a different device is the user's call, and the next recording picks it up. The two ways of losing an endpoint look different in the notifications, which is the part worth getting right. A pinned device is lost when that exact id goes away. A capture following the default is lost when the default changes — the old device may still exist and still enumerate, but Windows has moved playback elsewhere. That is the more confusing failure because nothing was unplugged, and watching for removals would never catch it. The decision is separated from the notification plumbing: the rules need no audio hardware to exercise, and the plumbing cannot be exercised without it. Callbacks arrive on a system thread from inside the audio stack, so the watcher raises an event and returns — blocking one is a documented way to wedge the endpoint enumerator for the whole process. 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.
Completes Phase 7's remaining three items: device hot-plug handling, extended-length path support, and VB-CABLE detection.
Extended-length paths
Offstream.Core.Naming.LongPath.Extendedprefixes\\?\(or\\?\UNC\) once a path reaches 260 characters. The prefix is used rather than alongPathAwaremanifest because Offstream does not write the file — ffmpeg does, in its own process with its own manifest. The prefix travels with the path throughArgumentListinto whatever ffmpeg hands toCreateFile. Verified rather than assumed: ffmpeg 8.1 wrote an MP3 to a 298-character destination through a prefixed path on a machine withLongPathsEnabledoff.The prefix disables path normalisation, which is the trap — Windows stops resolving
./.., stops converting/, and stops trimming trailing dots and spaces. So it applies only to fully-qualified paths, normalised first, and only when long enough to need it.OutputPathsnow budgets againstLongPath.MaxLength(32767) instead of 260, with the per-level allowance clamped to 255. Extended paths raise the total length, never the NTFS component length; without the clamp the wider budget produces folder names the filesystem refuses outright — trading "truncated at 260" for "cannot be written at all".One existing test asserted a deep output root was too long. It is not any more, which is the feature; it now asserts the opposite, with a companion case proving the check still fires on a root with genuinely no room left.
Audio endpoint hot-plug
AudioEndpointWatcherwrapsIMMNotificationClientand reports arrivals, departures and default changes.LoopbackAudioCapturetakes one optionally and ends the capture when the endpoint it was reading goes away.It reports and stops; it does not re-route. Moving a running capture to another endpoint sounds helpful and is not — the replacement can have a different sample rate and channel count, so the file in progress would gain a seam, or a tail its header does not describe. Losing the endpoint ends that recording cleanly and says why.
The two ways of losing an endpoint look different in the notifications, and
AudioEndpointRelevance.EndsTheCaptureis the piece with the rules in it (separated out so it needs no audio hardware to test): a pinned device is lost when that exact id goes away; a capture following the default is lost when the default changes — the old device may still exist and still enumerate, but Windows has moved playback elsewhere. That is the more confusing failure, because nothing was unplugged and watching for removals would never catch it.Without any of this the loss is silent: WASAPI simply stops delivering and the session keeps believing it is recording. Callbacks arrive on a system thread from inside the audio stack, so the watcher raises an event and returns;
OnDefaultDeviceChangedis filtered to render + multimedia, since Windows raises it per role.VB-CABLE detection
Offstream.Core.Audio.VirtualCablematches the driver's product name among the render endpoints and exposes the vendor's download page when it is absent. Detection only, and that is a licence decision rather than an unfinished one — the package is donationware whose readme forbids integrating it into another installation procedure without the author's agreement. No vendor binaries in this repo. Open question 9 must be answered before any other form ships.Surfacing the absence in the Settings UI is still to do, and is noted as such in the plan.
Testing
969 green (801 Core + 168 UI),
dotnet format --verify-no-changesclean.🤖 Generated with Claude Code