Releases: revtex/offstream
Release list
Offstream 0.1.0
Install it with Offstream-0.1.0-setup.exe — a per-user install that never asks
for administrator rights — or take the zip and run it from wherever you unpack it.
Both hold the same build.
This build is not code-signed. Windows SmartScreen will warn the first time you
run it: choose More info and then Run anyway. Verify the download against its
SHA-256 below before you do.
VB-CABLE is not included. Offstream detects it and tells you if it is missing;
install it yourself from https://vb-audio.com/Cable/ if you want to record Spotify
alone rather than everything the machine plays.
ffmpeg n8.1.2-40-g852b0552f0 is included (LGPL-3.0-or-later), unmodified and run as a
separate process. Its source is attached to this release as required, and its licence
travels in the ffmpeg folder beside it.
99253ab73bca124cdec71a9fc2fe4a66d1dbb3ac90217742953dfdf9530fdea2 Offstream-0.1.0-setup.exe
7beafde656b7050c2436a9318d66b8272625a6cd4a329548f20b714ae71b463a Offstream-0.1.0-win-x64.zip
The first release. Everything below is the whole of Offstream rather than a change to it:
phases 0–8 of docs/MODERNIZATION-PLAN.md, from the .NET 10 retarget through to a per-user
installer.
Added
-
The licences are readable from inside the app, not just present as files in the folder.
"Licence and notices" on the Advanced page's Application section opens Offstream's own MIT
licence, the predecessor's copyright notice it carries, the bundled LGPL ffmpeg and its source
offer, TagLib#, and VB-CABLE's origin and donationware terms — with the running build's version
and commit at the top, so a bug report can name the build it came from. The text is embedded in
the executable rather than read from beside it: the MIT and LGPL notices are obliged to travel
with the software, and a loose file does not survive a zip unpacked selectively or a copy of just
the.exe. A test asserts the window still shows whatLICENSEandNOTICEactually say, so the
two cannot drift. -
Credit for the track details, beside the provider that supplies them. Choosing Spotify or
Last.fm now says whose data is being written into the recordings — an attribution Spotify's
Developer Terms require and that the app had never carried anywhere. It is empty when no provider
is selected, because crediting a service the app is not calling would be a false statement. -
The Settings page names which Spotify account is signed in, as display name and account id
— not just that one exists. Recordings go untagged when the signed-in account is not the one
the music plays on, and nothing on screen says which it is, so the only way to find out was to
read the logs. Costs one scope,user-read-private. The id is there because a display name is
not an identifier: two accounts can share one, and telling exactly those apart is what this is
for.user-read-emailwould be the obvious way to do that and is deliberately not requested —
Spotify removed theemailfield in its late-2024 cull, so that permission now covers data the
endpoint no longer returns. -
A release pipeline, with the git tag as the only place a version number lives. Pushing
v1.2.3
builds, tests, publishes, signs and attaches a self-containedwin-x64zip and its SHA-256 to a
GitHub release; the changelog becomes the release notes. Nothing in the repository records a
released version, so a build cannot claim a number the tag disagrees with — a file that has to be
bumped in lockstep with a tag is a file that eventually is not. A malformed tag is rejected before
anything is built, because a tag stops being editable the moment anyone fetches it, and a
prerelease suffix (v1.2.3-rc.1) marks the GitHub release as one so it does not become what
"latest" resolves to. Unreleased builds call themselves0.1.0-devrather than borrowing the last
release's number, which is what turns "which build is this?" into a question with an answer. The
same workflow runs from the Actions tab to exercise the pipeline without spending a version.
Cutting a release is two steps — close## [Unreleased]into## [1.2.3]in a pull request, then
tag — and a tag with no matching section fails in seconds rather than at the end of the pipeline.
Falling back to[Unreleased]would have worked exactly once, and every release after that would
republish the previous one's entries with no fix short of amending a release people had read. -
A per-user installer that never asks for administrator rights. Offstream needs no elevation to
run — routing, session mute and loopback capture were all verified unelevated — so the thing that
installs it asks for none either. An elevation prompt is a decision the user has to make about
software they have not run yet. It installs into%LOCALAPPDATA%\Programs\Offstream, refuses
anything below Windows 11 with a sentence during setup rather than a crash on first run, notices a
running copy through the app's own single-instance mutex instead of failing halfway through
replacing a locked file, and is offered in English and French to match the app.
It is built from the same staged folder the portable zip is made from, so the two downloads
cannot hold different software under one version number, and the installer script is compiled on
every pull request — one that is only compiled when a tag is pushed is one that breaks when a tag
is pushed.
Uninstalling asks about settings and logs rather than guessing. Deleting them silently loses a
Last.fm API key and a Spotify sign-in; keeping them silently is wrong for someone uninstalling
because they are done. Recordings are never in scope: they live outside the install folder and no
uninstaller should reach them. -
ffmpeg travels with the app. Releases carry an unmodified LGPL-3.0 build of ffmpeg in an
ffmpegfolder beside the executable, which is where the locator already looked, so a download
records audio without the user installing anything first. A copy onPATH, or one named on the
Settings page, still wins — bundling is a floor, not a preference. It costs about 45 MB of the
download, which is the price of the app working when it is opened.
The build is pinned by SHA-256, not fetched by name from a moving tag: an encoder that changes
between two builds of the same Offstream version turns a reproducible bug into an unreproducible
one, and a release asset can be replaced after the fact. A mismatch fails the release rather than
quietly shipping something else. The binaries are not committed — 108 MB of someone else's build
does not belong in a git history — sobuild/windows/ffmpeg.jsonrecords what to fetch and
fetch-ffmpeg.ps1fetches it, for the pipeline and forbuild.ps1 -Publish -BundleFfmpegalike.
Onlyffmpeg.exeships;ffprobeis used by the integration tests and by nothing in the app, and
would have added another 108 MB.
The LGPL obligation is met by attaching the source, not by offering it. Every release carries
the FFmpeg source archive for the exact commit the binary was built from, because a written offer
has to outlive whatever was going to host it and this one does not have to. The bundle also carries
ffmpeg's own licence text and aSOURCE.txtnaming that commit, and the vendor binary is never
re-signed or otherwise altered on its way in. -
Signing, wired and waiting.
build/windows/sign.ps1Authenticode-signs whatever it is given,
and when no certificate is configured it says so and exits 0 rather than failing the build.
Offstream has no certificate yet, so every artefact is currently unsigned and Windows SmartScreen
will warn on first run — the release notes say this outright instead of letting users find out,
and ship a SHA-256 as the only integrity check available in the meantime. Building the step now
means acquiring a certificate later is two repository secrets rather than a pipeline change, and it
gets reviewed while nothing depends on it. Timestamping is on by default: without it every
signature stops verifying the day the certificate expires, including on copies installed years
earlier. -
A security policy, and the reporting channel it points at.
SECURITY.mdsays where to send a
vulnerability and what the app actually handles that is worth attention — track metadata being
untrusted input that reaches ffmpeg arguments and file paths, the PKCE sign-in, DPAPI token
storage, and the hand-marshalled COM interop — along with what is deliberate, such as the Last.fm
key sitting in plain text next to a DPAPI-protected refresh token. Private vulnerability
reporting, Dependabot alerts and security updates, and secret scanning with push protection are
enabled on the repository, so a reporter has somewhere private to go and a committed credential
is refused at push time rather than found later. -
CodeQL code scanning on pushes to
main, on pull requests, and weekly, over C#, the workflows
themselves and the one Python script. It runs on Ubuntu withbuild-mode: none: the analysis
reads C# without compiling it, which is what makes scanning a Windows-only WPF app on a Linux
runner possible at all. -
Solution scaffold on .NET 10. SDK-style projects (
Offstream.Core,Offstream.App,
Offstream.Core.Tests,Offstream.UI.Tests,Offstream.FakeSpotify) under an.slnx
solution, central package management, nullable reference types, and analyzers as errors. -
GitHub Actions CI on
windows-latest: build,dotnet format --verify-no-changes,
analyzers as errors, and the test suite with ffmpeg pinned. A parallel job fails any pull
request that does not update this file, since a stale changelog reads as a current one; a
user-invisible change skip...