6.5 KiB
6.5 KiB
Task plan — soundcloud provider
Status (2026-07-24): all phases A–E implemented. Everything offline is
verified (unit tests, parsers, server build, fmt/clippy/machete). The live-only
gates — the real client_id scrape (B2/B3), live JSON shapes (B4/B5), and
mp3-HLS play-to-EOS (C4), plus browsing /soundcloud (D4) and tests/live.rs
(E1) — need real SoundCloud access and are left to run on a machine with creds
and audio. See plan/summary.md.
Executes architecture/soundcloud-provider.md against the stubs in
soundclouddy/ and audio-player/src/hls.rs, satisfying the gates in
quality/soundcloud-provider.md. Ordered by dependency. Verification column
names the test(s) / gate(s) each task must satisfy.
Phase A — Provider logic (no network; unit-testable now)
- A1 —
Settings+ accessors. Bounds accessors (search_results,playlist_tracks,call_timeout,hls_deadline) with theDEFAULT_*consts; redactingDebugalready present. (G1;settings_debug_redacts_secrets.) - A2 —
get_lib_rootbuilds root children directly (sync, no network):search+resolve, pluslikes+playlistsifflogged_in. Serve the Root node from here (decision #1) and haveget_lib_node(Root)delegate to it. (G10;root_without_login_has_no_personal_nodes,root_with_login_shows_personal_nodes.) - A3 —
sc_trackmapper ScTrack→protoTrackat/soundcloud/track/<id>,provider_item_id = "track:<id>", ms→Option<u32>. (G12.) - A4 —
search_node+ term store (create/rename/delete, dedup, implicit recreation). (G17;create_rename_delete_search_term.) - A5 —
search_term_nodelists tracks (intonode.tracks, canonical paths, queueable node) and playlists (intonode.children, canonical container paths, queueable). (G13, G12, G16;search_term_lists_tracks_and_playlists_with_canonical_paths.) - A6 —
resolve_node+ URL store (create/rename/delete), andresolve_entry_node: track→node.tracks=[t]; playlist→child container. (G14, G17;resolve_entry_shapes_track_and_playlist,create_resolve_url_entry.) - A7 —
playlist_nodefetch detail, hydrate stub ids in ≤50 batches up toplaylist_tracks,logtruncation. (G15;playlist_node_hydrates_stub_tracks.) - A8 —
likes_node/playlists_node(login only). (G10.) - A9 —
get_urls_for_track/get_metadata_for_trackviaresolve_stream_url/track_detail; non-track path →MalformedPath. (G18;get_urls_for_track_returns_the_m3u8.) - A10 — Download blessing in
get_lib_node(node + children), and typedfetch_errmapping (no panics). (G4, G16;backend_failures_are_typed.) - A11 — Gate:
cargo test -p soundclouddy— all 15 tests pass. (G11, G22.)
Phase B — HTTP seam ScApi (needs live SoundCloud to fully verify)
- B1 —
ScApi::newbuilds thereqwestclient with per-call timeout + user-agent; holdsclient_id/app_versionbehindRwLock, optional OAuth. (G3, G6.) - B2 —
scrape_client_idfree fn — fetch soundcloud.com, find script bundles, regexclient_id+app_version. Unit-test the parser against a captured HTML/JS fixture (no network). (G7; newscrape_parses_fixturetest.) - B3 — Signed GET helper: append
client_id/app_version, OAuth header when present; map status→FetchError(404/401/403); re-scrape once on 401/403 then retry. (G8;reauth_rescrapes_once_then_gives_upover a mock/loopback.) - B4 — Defensive wire DTOs (
#[serde(default)], drop id-less entries) andinto_*converters for track/playlist/resolve/search/transcoding. (G5.) - B5 —
resolve_stream_url: pickhls+audio/mpegtranscoding, GET its url, return the.m3u8;NotStreamablewhen absent. (G18.) - B6 —
hydrate_tracksbatched/tracks?ids=(≤50). (G15.) - B7 —
settings()folds cachedclient_id/app_versionback in. (G9;settings_round_trip_persists_scraped_id.) - B8 —
initparses TOML, buildsScApi, ensures aclient_id(scrape if none), non-fatal on total failure. (G7.)
Phase C — HLS playback in audio-player
- C1 —
parse_media_playlisthand-written line parser (#EXTINF+ URI, resolve relative against base, one-level master fallback). Unit test with a fixture playlist string. (G19; newhls::tests::parses_media_playlist.) - C2 —
HlsStreamstate machine (Stream+SourceStream): fetch segments in order, advance at boundaries, finish after last;reconnectre-opens the current segment; forward-only,content_length None,supports_seek false; total-deadline bound. (G6, G19, G20.) - C3 — Route
.m3u8inopen_sourcetoHlsStream(viaStreamDownload) before the windowed-HTTP path; opus/other sniffing downstream unchanged. Wiremod hls;(already declared). (G21.) - C4 — Gate (integration): an ffmpeg-built mp3-HLS fixture (or a live
track) plays to EOS with no panic; add a
#[ignore]play-to-EOS test. (G19, G20.)
Phase D — Server wiring
- D1 — Workspace dep
soundclouddy = { path = "soundclouddy" }in rootCargo.toml[workspace.dependencies]; add tocrabidy-serverdeps. - D2 —
settings.rs:"soundcloud"inALL_PROVIDERS(→8),ProviderToggles.sc,all(),provider_toggles(). - D3 —
provider.rs:sc_clientfield,sc_owns,sc_provider,build()block readingsoundcloud.toml(non-fatal, round-tripsettings()),get_lib_rootchild gated onis_some(), and a routing arm in each dispatch method. (mirrors abs.) - D4 — Gate:
cargo build -p crabidy-server; browse/soundcloudin the running app (manual//run).
Phase E — Validation & docs
- E1 —
tests/live.rs(#[ignore]): readsSOUNDCLOUD_OAUTH/optionalSOUNDCLOUD_CLIENT_IDfrom env, exercises scrape→search→resolve→stream_url end-to-end; confirms DTOs match live JSON. (G7 live gate.) - E2 — Quality sweep:
cargo fmt,clippy,machete; tickquality/soundcloud-provider.md. (G23.) - E3 —
plan/summary.mdrecords deviations from this plan.
Notes
- Live-verification boundary: Phases A and C1 are fully verifiable offline. B3/B5/C4/D4/E1 need live SoundCloud access (no creds in CI/sandbox) — deliver as code + fixture/mock tests, run the live gate on a real machine.
- Do not commit secrets;
soundcloud.tomlstays out of git.