android: a streamed file copied into a folder, the listening port, the dev build's diagnostics

The now-playing sheet's streamed track can be copied into a folder picked
on the phone, as the file it is — the cache written out, nothing downloaded
again, no library row (copy_stream_to_folder, which the desktop has had a
command for and no UI). You → Sharing carries the listening port, what
peers dial when they download from you: unset is the client's own 50300,
the app asks the router to forward it over UPnP and falls back to the
server relaying, and a change applies on the next connect. The dev flavour
gets the desktop's debug overlay as a screen under This phone — CPU,
memory, the network, the stream and its loudness, what the pre-buffer is
fetching, the size of the play history — behind devBuild in util.js, which
is Vite's own DEV flag, so the embedded build the phone keeps has no row
to tap.
This commit is contained in:
KeelrLeek committed 2026-09-13 23:59:47 -05:00
1 parent 1737c5e1b6
commit aff29aba5c
8 files changed
+272 -2

No files matched your search

+20 -1
View File
@@ -214,6 +214,24 @@
: "Nothing to save");
} catch (e) { oops(e); }
}
/* the desktop's copy_stream_to_folder: the cached stream written into a
folder picked on this phone, as the file it is — no re-download, no
tagging, no library row. Nothing leaves the phone, so the Wi-Fi guard
has no say here. A track that has not finished streaming is counted
back as missing, never fetched. */
async function copyToFolder(entries) {
const files = entries.filter((e) => e.t?.stream && e.t.path).map((e) => ({ username: e.t.stream.username, path: e.t.path }));
if (!files.length) return;
try {
const r = await api.pickLibraryFolder();
if (!r?.path) { player.error = "That folder has no path the phone can write to — pick one on this phone's storage."; return; }
const [saved, missing] = await api.copyStreamToFolder(files, r.path);
note(saved ? saved + " file" + (saved === 1 ? "" : "s") + " copied" + (missing ? " · " + missing + " not cached yet" : "")
: "Not cached yet — play it through first");
} catch (e) {
if (!/cancelled/.test(String(e))) oops(e);
}
}
/* a peer's sheet (a raw result's folder, or the browse screen's user) */
function playGroup(g) {
const es = groupEntries(g, 0);
@@ -346,6 +364,7 @@
{#if streamMates(track, album).length > 1}
{@render row("download", "Save this record", streamMates(track, album).length + " tracks streaming from the same record", act(() => saveStreams(streamMates(track, album))))}
{/if}
{@render row("copy", "Copy to a folder…", "the streamed file as it is, into a folder on this phone", act(() => copyToFolder([{ t: track, album }])))}
<div class="sheet-hair"></div>
{/if}
{@render row("play", "Play next", "", act(() => playNext([{ t: track, album }])))}
@@ -487,7 +506,7 @@
{#if a.multiline}
<textarea class="field area" bind:value={promptVal} placeholder={a.placeholder || ""} rows="3" use:focusLater></textarea>
{:else}
<input class="field" bind:value={promptVal} placeholder={a.placeholder || ""} autocomplete="off" spellcheck="false" enterkeyhint="done" onkeydown={(e) => { if (e.key === "Enter") submitPrompt(); }} use:focusLater />
<input class="field" bind:value={promptVal} placeholder={a.placeholder || ""} autocomplete="off" spellcheck="false" inputmode={a.numeric ? "numeric" : undefined} enterkeyhint="done" onkeydown={(e) => { if (e.key === "Enter") submitPrompt(); }} use:focusLater />
{/if}
<div class="prompt-acts">
<button class="btn btn-ghost" onclick={closeHalf}>Cancel</button>