feat: progressive music streaming, library-first search, player fixes
Rebuild & Deploy / rebuild (push) Successful in 59s
Rebuild & Deploy / rebuild (push) Successful in 59s
- Stream downloads as they grow: raw best-audio (no mp3 transcode) served chunked from the stream endpoint mid-download, then with native Range once complete. Songs play within seconds of hitting Play. - Fix searched songs never starting: reconcileMedia now wires the stream URL when a track that was preparing gains one; audio_url is stable for the whole lifecycle so the player never reloads mid-playback. - Progress bars now parse yt-dlp stderr (stdout never carried [download]). - Prefer downloaded songs: /media/api/search merges ready library matches ahead of YouTube results; search_music/play_song tooling and the agent prompt tell Hope to always pick in_library=true. - Mute button turns solid red while the mic is muted.
This commit is contained in:
1 parent
d89918fd2a
commit
4f8eb00c9f
7 files changed
+440
-107
No files matched your search
+19
-9
@@ -1207,7 +1207,15 @@ function reconcileMedia(s) {
|
||||
const pendingJob = (s.pending || []).find(j => !track || j.track_id === track.id);
|
||||
updateMediaProgress(track, pendingJob);
|
||||
|
||||
if (track && track.id !== mediaCurrentTrackId) loadMediaTrack(track);
|
||||
if (track && track.id !== mediaCurrentTrackId) {
|
||||
loadMediaTrack(track);
|
||||
} else if (track && track.audio_url && mediaAudio.getAttribute("src") !== track.audio_url) {
|
||||
// The track was loaded before it had a stream (still downloading) and
|
||||
// now has one — wire it up so playback can start without a reload.
|
||||
mediaAudio.src = track.audio_url;
|
||||
mediaAudio.load();
|
||||
updateMediaNowCard(track);
|
||||
}
|
||||
if (!track && mediaCurrentTrackId && s.desired === "stopped") clearMediaAudio();
|
||||
|
||||
applyDesiredMediaState(s, track);
|
||||
@@ -1224,7 +1232,9 @@ function reconcileMedia(s) {
|
||||
|
||||
function applyDesiredMediaState(s, track) {
|
||||
if (s.desired === "playing") {
|
||||
if (track && track.status === "ready" && mediaAudio.src && mediaAudio.paused) {
|
||||
// audio_url presence means a playable stream exists — including a
|
||||
// still-growing download, which plays progressively.
|
||||
if (track && track.audio_url && mediaAudio.src && mediaAudio.paused) {
|
||||
mediaAudio.play().catch((e) => console.warn("playback blocked:", e));
|
||||
}
|
||||
} else if (!mediaAudio.paused) {
|
||||
@@ -1235,14 +1245,14 @@ function applyDesiredMediaState(s, track) {
|
||||
function loadMediaTrack(track) {
|
||||
mediaCurrentTrackId = track.id;
|
||||
updateMediaNowCard(track);
|
||||
if (track.audio_url && (track.kind === "stream" || track.status === "ready")) {
|
||||
if (track.audio_url) {
|
||||
if (mediaAudio.getAttribute("src") !== track.audio_url) {
|
||||
mediaAudio.src = track.audio_url;
|
||||
mediaAudio.load();
|
||||
}
|
||||
} else {
|
||||
// Downloading or failed: no stream yet. The poll flips this to the
|
||||
// real URL once status turns "ready".
|
||||
// Downloading or failed: no stream yet. reconcileMedia wires the
|
||||
// stream URL in via polling as soon as one is available.
|
||||
mediaAudio.removeAttribute("src");
|
||||
mediaAudio.load();
|
||||
updateMediaControls();
|
||||
@@ -1266,7 +1276,9 @@ function updateMediaNowCard(track) {
|
||||
if (track.status === "failed") {
|
||||
mediaChannelEl.textContent = "Download failed — pick another result or try again.";
|
||||
} else if (track.status === "downloading") {
|
||||
mediaChannelEl.textContent = "Preparing…";
|
||||
// Progressive streaming: the track plays while the download runs.
|
||||
mediaChannelEl.textContent = track.channel
|
||||
? `${track.channel} · downloading` : "Downloading…";
|
||||
} else {
|
||||
mediaChannelEl.textContent = track.channel || (track.kind === "stream" ? "Live stream" : "");
|
||||
}
|
||||
@@ -1288,9 +1300,7 @@ function updateMediaProgress(track, job) {
|
||||
mediaProgressWrap.hidden = false;
|
||||
const pct = Math.round(job.progress || 0);
|
||||
mediaProgressFill.style.width = `${pct}%`;
|
||||
const stage = job.stage === "transcoding"
|
||||
? "Re-encoding…"
|
||||
: job.stage === "queued" ? "Queued…" : "Downloading…";
|
||||
const stage = job.stage === "queued" ? "Queued…" : "Downloading…";
|
||||
mediaProgressLabel.textContent = `${stage} ${pct}%`;
|
||||
if (track) updateMediaNowCard(track);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user