android: the repair sheet, browse, every long-press sheet, You's last sections, the lyrics tools — and the walk's audio check made honest
The playlist repair (Find a local match: the library searched by the row's name, a pick relinks it through playlist_relink_item; an unknown row takes the track's identity). A peer's sheet on the raw results (play or save the folder, browse their files, message, buddy) and the Browse screen — the hub's Browse tab: a user's whole share with sizes and totals, a folder played or saved whole; a share can hold thousands of folders, so it filters and renders 80 at a time. "Look up a user" on Community → Buddies. The sheets the phone lacked: a queue row's (Play now, Move to next, Remove from queue), a catalog song's, a catalog card's and the record page's own, a raw file's (in the results and in Browse), a room name's, a buddy's, the now-playing Save (the desktop's Save menu for a streamed track or record). The artwork viewer with pinch zoom, one layer the back gesture peels. Search's recent searches and its spelling suggestion from the names this phone knows. You → Streaming, Downloads, Sharing, Lyrics, Album artwork, and Clear play history; a record's Re-fetch the cover. The lyrics tools on the sheet's face: the offset nudged and kept per track, another LRCLIB candidate chosen, the lookup forgotten and done again (lyrics.svelte.js, shared by the face and the lyrics sheet). The harness: every CDP call times out and the reload step re-attaches (a WebView that stopped answering hung a walk for ten minutes); the audio check counts SoulStream's own tracks' underruns from the mixer dump instead of the thread-wide counters that any app's notification moves — the afternoon's "dropouts" were other apps' sounds, and the browse of a 49,544-file share measured at zero; __settle resets every tab to its root through the nav; an adb step kind; the walks cover all of the above on the mock and on the phone.
This commit is contained in:
1 parent
6e39dc43f3
commit
26928b706d
21 files changed
+1294
-92
No files matched your search
+62
-1
@@ -124,6 +124,14 @@ function plTouch(p) {
|
||||
setTimeout(() => mockEmit("soulseek:playlists-changed", {}), 30);
|
||||
}
|
||||
const plOf = (args) => PL.find((p) => p.id === Number(args?.id ?? args?.playlistId));
|
||||
/* every track has three timed lines; the sheet's tools have something to act on */
|
||||
function mockLyrics(title) {
|
||||
return {
|
||||
status: "synced", source: "lrclib",
|
||||
synced: [{ time_ms: 0, text: "Here is " + (title || "a song") }, { time_ms: 4000, text: "Every word in time" }, { time_ms: 9000, text: "And the last one, late" }],
|
||||
plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false,
|
||||
};
|
||||
}
|
||||
function mockPlaylists(cmd, args) {
|
||||
if (cmd === "list_playlists") return Promise.resolve(PL.map((p) => ({ ...p })));
|
||||
if (cmd === "playlist_items") return new Promise((r) => setTimeout(() => r((PL_ITEMS[Number(args?.id)] || []).map((x) => ({ ...x }))), 80));
|
||||
@@ -177,6 +185,19 @@ function mockPlaylists(cmd, args) {
|
||||
plTouch(p);
|
||||
return Promise.resolve(rows.map((r) => r.id));
|
||||
}
|
||||
if (cmd === "playlist_relink_item") {
|
||||
const p = plOf(args);
|
||||
const it = p ? (PL_ITEMS[p.id] || []).find((x) => x.id === Number(args?.itemId)) : null;
|
||||
if (!it) return Promise.reject(new Error("playlist_relink_item: no such row"));
|
||||
if (args.path != null) { it.path = args.path; it.kind = "local"; it.missing = false; }
|
||||
if (args.artPath != null) it.art_path = args.artPath;
|
||||
if (args.durationSecs != null) it.duration_secs = args.durationSecs;
|
||||
if (args.title != null) it.title = args.title;
|
||||
if (args.artist != null) it.artist = args.artist;
|
||||
if (args.album != null) it.album = args.album;
|
||||
plTouch(p);
|
||||
return Promise.resolve();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -462,6 +483,7 @@ function mockCatalog(cmd, args) {
|
||||
}
|
||||
if (cmd === "cancel_download") return Promise.resolve();
|
||||
if (cmd === "rescan_library") return Promise.resolve();
|
||||
if (cmd === "refresh_all_album_art") return new Promise((r) => setTimeout(() => r(140), 300));
|
||||
if (cmd === "catalog_art") return Promise.resolve(svgArt(200, "D"));
|
||||
if (cmd === "stream_cache_ready") return Promise.resolve(cacheReady.has(args?.path) || (xfer && xfer.path === args?.path && xfer.received >= xfer.total));
|
||||
if (cmd === "preload_stream") {
|
||||
@@ -495,6 +517,17 @@ let downloadSeq = 0;
|
||||
const mockSettings = {
|
||||
download_dir: "C:\\mock\\Library",
|
||||
volume_normalization: true,
|
||||
library_layout: "{artist}\\{album}",
|
||||
shared_folders: [],
|
||||
upload_slots: 2,
|
||||
share_files: 1234,
|
||||
word_timing: false,
|
||||
replace_embedded_art: true,
|
||||
embed_art_max_edge: 1500,
|
||||
write_folder_cover: true,
|
||||
stream_keep_forever: false,
|
||||
stream_retention_days: 30,
|
||||
stream_cache_max_mb: 2048,
|
||||
stream_source_prefs: { max: "any", min: "any", priority: "quality" },
|
||||
download_source_prefs: { max: "any", min: "any", priority: "quality" },
|
||||
};
|
||||
@@ -533,6 +566,25 @@ function mockNet(cmd, args) {
|
||||
later(900, () => mockEmit("soulseek:private-message", { username: args?.username, message: "sure, sending", timestamp: Math.floor(Date.now() / 1000) }));
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (cmd === "slsk_browse_user") {
|
||||
/* a share: two records and a folder of scans, in the browse DTO's shape */
|
||||
const dir = (album, ext, bitrate) => {
|
||||
const folder = "@@shared\\Music\\Daft Punk\\Daft Punk - 2001 - " + album + (ext === "flac" ? " [FLAC]" : " [MP3 " + bitrate + "]");
|
||||
return {
|
||||
name: folder,
|
||||
files: TITLES.slice(0, album === "Discovery" ? 14 : 8).map((t, i) => {
|
||||
const name = String(i + 1).padStart(2, "0") + " - " + t + "." + ext;
|
||||
const duration = 200 + ((i * 37) % 180);
|
||||
return { name, path: folder + "\\" + name, size: ext === "flac" ? duration * 110000 : duration * bitrate * 125, ext, bitrate: ext === "flac" ? null : bitrate, duration };
|
||||
}),
|
||||
};
|
||||
};
|
||||
return new Promise((r) => setTimeout(() => r([
|
||||
dir("Discovery", "flac"),
|
||||
dir("Homework", "mp3", 320),
|
||||
{ name: "@@shared\\Music\\Scans", files: [{ name: "front.jpg", path: "@@shared\\Music\\Scans\\front.jpg", size: 1200000, ext: "jpg", bitrate: null, duration: null }] },
|
||||
]), 600));
|
||||
}
|
||||
if (cmd === "start_search") {
|
||||
const t = ++token;
|
||||
const q = String(args?.query || "");
|
||||
@@ -548,7 +600,16 @@ function mockNet(cmd, args) {
|
||||
const n = args?.buckets || 900;
|
||||
return new Promise((r) => setTimeout(() => r(Array.from({ length: n }, (_, i) => 0.2 + 0.75 * Math.abs(Math.sin(i * 0.23) * Math.cos(i * 0.017)))), 120));
|
||||
}
|
||||
if (cmd === "lyrics_get") return Promise.resolve(null);
|
||||
if (cmd === "lyrics_get") return new Promise((r) => setTimeout(() => r(mockLyrics(args?.title)), 150));
|
||||
if (cmd === "lyrics_search") return new Promise((r) => setTimeout(() => r([
|
||||
{ id: 901, title: args?.title || "Track", artist: args?.artist || "?", album: "The Mock Sessions", synced: true, instrumental: false, durationSecs: 214 },
|
||||
{ id: 902, title: (args?.title || "Track") + " (Live)", artist: args?.artist || "?", album: null, synced: false, instrumental: false, durationSecs: 233 },
|
||||
]), 200));
|
||||
if (cmd === "lyrics_select") return Promise.resolve({ ...mockLyrics(args?.title), source: "lrclib #" + args?.candidateId });
|
||||
if (cmd === "lyrics_set_offset") return Promise.resolve();
|
||||
if (cmd === "lyrics_clear") return Promise.resolve();
|
||||
if (cmd === "clear_play_history") return Promise.resolve();
|
||||
if (cmd === "refresh_album_art") return new Promise((r) => setTimeout(() => r("C:\\mock\\cover.jpg"), 200));
|
||||
if (cmd === "play_local") {
|
||||
/* a file: the engine reports playing after a beat, then the position
|
||||
from where it was asked to start */
|
||||
|
||||
Reference in new issue
Block a user