Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 15 additions & 13 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Offstream has not shipped a release yet; everything below is work towards the first one.
Because Offstream succeeds **Spytify** (.NET Framework 4.6.1 / WinForms) rather than starting
from nothing, the `Changed`, `Removed` and `Fixed` entries are written against that predecessor
— they say how Offstream differs from the app it replaces. `docs/MODERNIZATION-PLAN.md` is the
phase plan these entries follow.

## [Unreleased]

## [0.1.0] - 2026-08-15

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.**
Expand All @@ -28,6 +33,15 @@ phase plan these entries follow.
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-email` would be the obvious way to do that and is deliberately not requested —
Spotify removed the `email` field 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-contained `win-x64` zip and its SHA-256 to a
GitHub release; the changelog becomes the release notes. Nothing in the repository records a
Expand Down Expand Up @@ -329,18 +343,6 @@ phase plan these entries follow.
harness nothing built on, carried in the solution and the layout docs as though it were part of
the app. `docs/decisions/0001-phase-0-retarget-spike.md` keeps the findings.

### Added

- **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 said which it was, so the only way to find out was
to read the logs. Costs one scope, `user-read-private`, and existing installs show nothing
here until they sign in again, since a refresh token predating the scope cannot read the
profile. 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-email` would be the
obvious way to do that and is deliberately not requested — Spotify removed the `email` field
in its late-2024 cull, so that permission now covers data the endpoint no longer returns.

### Fixed

- **Pressing record with Spotify paused, then pressing play, recorded nothing.** The level meter
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ Set by the user on 2026-08-13 and binding like the constraints above. Most were
- **Endpoints, parameters and field names come from the [OpenAPI schema](https://developer.spotify.com/reference/web-api/open-api-schema.yaml).** Never guessed. In practice `SpotifyAPI.Web` is the typed SDK and nothing here hand-rolls HTTP; check the schema before adding a call, not after.
- **[Authorization Code with PKCE](https://developer.spotify.com/documentation/web-api/tutorials/code-pkce-flow) for user data.** Client Credentials only for public non-user data. **Implicit Grant is banned** — it is deprecated. A desktop app is a public client, so there is no Client Secret to protect and none may be introduced.
- **Redirect URIs are HTTPS**, except `http://127.0.0.1` for local development. Never bare `localhost` (Spotify rejects it), never wildcards. `SpotifyAuthOptions.DefaultRedirectUri` is the one in use and a test pins its shape.
- **Request the minimum [scopes](https://developer.spotify.com/documentation/web-api/concepts/scopes) the shipped features need, never a scope for a feature that might arrive later.** Offstream makes exactly two calls: `/me/player/currently-playing`, which needs `user-read-currently-playing`, and `/albums/{id}`, which needs no user scope. That is the whole list, and `SpotifyAuthOptionsTests` fails the build if it grows. A scope requested ahead of its feature is a permission the user grants for nothing, on a consent screen where the spare lines look identical to the load-bearing one.
- **Request the minimum [scopes](https://developer.spotify.com/documentation/web-api/concepts/scopes) the shipped features need, never a scope for a feature that might arrive later.** Offstream makes exactly three calls: `/me/player/currently-playing`, which needs `user-read-currently-playing`; `/albums/{id}`, which needs no user scope; and `/me`, which puts the signed-in account's name on the Settings page and needs `user-read-private`. Two scopes, and `SpotifyAuthOptionsTests` fails the build if the list grows. `user-read-email` would identify an account unambiguously and is deliberately **not** requested: Spotify removed the `email` field in its late-2024 cull, so the permission now covers data the endpoint no longer returns. `SpotifyAuthOptions.DefaultScopes` carries the full reasoning and is the place to change it. A scope requested ahead of its feature is a permission the user grants for nothing, on a consent screen where the spare lines look identical to the load-bearing one.
- **Tokens are stored protected, refreshed, and abandoned when dead.** DPAPI covers the refresh token; the access token never reaches disk. Spotify rotates the refresh token on every renewal, so the replacement must be persisted or a long-running install silently stops working. A **401** means the refresh token itself is gone — clear it and put the user back through sign-in rather than retrying it forever. Only 401: treating a rate limit or an outage that way would sign the user out over a transient fault.
- **On 429, honour `Retry-After` exactly; back off exponentially only where there is no such instruction.** Guessing shorter is what gets an application throttled harder. Never retry in a tight loop. `SpotifyRetryHandler` is the implementation — note that `SpotifyClientConfig.CreateDefault()` attaches **no** retry handler, so a client built without one treats rate limiting as fatal.
- **Log throttling at `Warning`.** The Record page's activity log shows Information and above, so anything quieter is invisible to everyone who has not gone looking. Transient 5xx stays at `Information` — it usually clears, and promoting it makes the Problems filter too noisy to read.
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ drops into the list below as it is completed.*

Three things worth knowing up front, so nothing is a surprise:

**There's no release yet.** Offstream is still in development, so there is nothing to download — you
build it from source, which is a few downloads and one build, with every step below. The installer
and the release pipeline are built and waiting on a version tag; check
[Releases](https://github.com/revtex/offstream/releases) before assuming this is still true.
**Download it from [Releases](https://github.com/revtex/offstream/releases).** The installer is
per-user and never asks for administrator rights; the zip is the same build if you would rather not
install anything. **Neither is code-signed**, so Windows SmartScreen warns the first time you run
it — choose **More info**, then **Run anyway**, after checking the download against the SHA-256 in
the release notes. Building from source is the other option, and every step is below.

**It records your PC's sound, not Spotify specifically.** By default Offstream captures whatever
your speakers are playing, so a Windows notification chime or a YouTube tab in the background lands
Expand All @@ -46,10 +47,11 @@ in the recording too. Either keep the machine quiet while it records, or install
settings, and record that instead — then only Spotify is captured. Offstream tells you on the
Settings page whether VB-CABLE is installed.

**You need ffmpeg — for now.** It's the free tool Offstream uses to turn the captured audio into
MP3s. One command installs it; see [Getting Offstream running](#getting-offstream-running).
Releases bundle their own copy, so this is only true while building from source. If you have one
installed anyway, or point Offstream at a particular build on the Settings page, yours wins.
**You need ffmpeg only if you build from source.** It's the free tool Offstream uses to turn the
captured audio into MP3s, and releases bundle their own copy in an `ffmpeg` folder beside the
executable. One command installs it otherwise; see
[Getting Offstream running](#getting-offstream-running). If you have one installed anyway, or point
Offstream at a particular build on the Settings page, yours wins over the bundled one.

## Getting Offstream running

Expand Down
Loading