Repository navigation
feat(api): stream a track's stems as one zip - #1060
Merged
Merged
Conversation
GET /v1/tracks/{id}/stems/download streams every stem the requester may
download, plus the parent with include_parent=true, as an uncompressed zip.
The zip length is computed up front so the browser shows real progress, and
there is no job, queue or disk involved. Replaces the archiver service.
A node that drops or stalls mid-file is resumed on the next mirror with a
Range request. Files that can't be downloaded are left out. Downloads are
capped per pod and per user.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
dylanjeffers
added a commit
that referenced
this pull request
Oct 23, 2026
Follow-up to #1060, found validating it in prod. Stems indexed with no `orig_filename` often have the upload's filename as their title (`" 🍕DRUMS.wav"`). The zip's fallback took `createFilename`'s `title + ".mp3"`, stripped the `.mp3`, and added the extension from the blob's content type. Andrew Lux's "Alone" zip came out with `DRUMS.wav.wav`, `LEADS.wav.wav` and so on, 11 of 12 entries. Now a trailing audio extension in the title is replaced instead of kept. Tests cover the emoji/whitespace title, case, a different content type, and a title with a dot but no extension. Not changed here: the single-file `/tracks/{id}/download` names the same files `DRUMS.wav.mp3` even though it serves the WAV, because `createFilename` assumes the transcode whenever `orig_filename` is empty. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
dylanjeffers
added a commit
to AudiusProject/apps
that referenced
this pull request
Oct 26, 2026
) > **Depends on AudiusProject/api#1060.** Don't merge until that endpoint is live on api.audius.co, or Download All 404s. ## What Download All stops using the archiver service (create job → poll → fetch zip) and uses the new streaming endpoint `GET /v1/tracks/{id}/stems/download` from api#1060. The API streams the zip with a known `Content-Length`, so there's no job to wait on or poll. - **SDK:** adds `tracks.getTrackStemsDownloadUrl` (minor changeset). - **common:** `useDownloadTrackStems` now signs the request and returns the zip URL. It includes the parent only when it's both `is_downloadable` and accessible, same rule as before. The archive job status/cancel hooks and their query keys are removed. - **web:** new `useDownloadTrackStemsZip` hands the URL to the browser's download manager. It's used by the desktop track page, the mobile-web track page and the contest stems card. The "Zipping files" modal is deleted. - **mobile:** `DownloadTrackArchiveDrawer` downloads the URL straight to the device with `trackDownload.downloadFile`. No zipping step or job polling, and retry/cancel still work. - Removes the `ArchiverService` wiring from the web and mobile SDK setup, and the `ARCHIVE_ENDPOINT` env key. The SDK still exports `ArchiverService`, so this isn't a breaking SDK change. This keeps the one-click "Download All" that apps#14563 (per-stem downloads) would have removed, without the archiver's instability. That PR can be closed if this lands. ## Testing - `tsc` reports no errors in any changed file for common, web or mobile. The remaining errors are in other files and come from node_modules cloned from another checkout. ESLint is clean on every changed file. Committed with `--no-verify`, so the pre-commit hook didn't run. - No unit tests cover these components. - **Not yet clicked through in the app.** That needs api#1060 deployed first. ## Rollout Merge after api#1060 is deployed. Older mobile store builds and desktop versions still call the archiver, so keep the archiver service and the API's `/archive/*` proxy running until they age out. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.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 free
to 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.
What
GET /v1/tracks/{id}/stems/downloadstreams every stem the requester may download, plus the parent withinclude_parent=true, as one zip. It is the replacement for the archiver service (BullMQ + Redis + disk). The clients move over in AudiusProject/apps#14703, which depends on this.Content-Lengthand the browser's download manager shows progress./tracks/{id}/download: gated stems need access, and an owner or manager signature gets their own non-downloadable files viaownerDownloadLink.Range: bytes=<offset>-.X-Accel-Buffering: noso ingress-nginx doesn't spool large zips to disk.inspectCidis split out ofinspectTrackand now has a 10s timeout. It had none.Testing
v1_track_stems_download_test.go, against a fake content node: exact length vs. bytes written with CRCs verified byarchive/zip, resuming on a mirror after the primary drops mid-file, all mirrors failing, zip64 offsets past 4 GiB, filename dedupe and extension fallback, the handler end to end (parent + stems, without parent, missing blob, not found), and an owner getting a non-downloadable stem that a stranger doesn't.writeStemsZipagainst prod content nodes with links signed by api.audius.co: 3 of Andrew Lux's "Alone" stems streamed into a 155 MB zip in 19s, length matched the precomputed value exactly, all CRCs ok. A Range request against a real node (through its 302) also worked../api/...suite:TestFeedScheduledReleasesalso fails on main.TestSearchneeds Elasticsearch locally and wasn't run.Rollout
Additive, and no client calls it until the apps PR ships. Leave the archiver and the
/archive/*proxy running until older mobile store builds and desktop versions stop using them.🤖 Generated with Claude Code