Fixed autoplay after fed download
This commit is contained in:
Generated
+1
-1
@@ -1845,7 +1845,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "furumusic"
|
||||
version = "0.9.1"
|
||||
version = "0.9.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "furumusic"
|
||||
version = "0.9.2"
|
||||
version = "0.9.3"
|
||||
edition = "2024"
|
||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
||||
|
||||
|
||||
@@ -295,7 +295,60 @@ impl Federation {
|
||||
let dir = storage_root.join("federation");
|
||||
tokio::fs::create_dir_all(&dir).await?;
|
||||
let downloaded =
|
||||
download(&service, owner, item_id, &content_id, &dir, &mut progress).await?;
|
||||
match download(&service, owner, item_id, &content_id, &dir, &mut progress).await {
|
||||
Ok(downloaded) => downloaded,
|
||||
Err(primary_error) => {
|
||||
progress(DownloadProgress {
|
||||
phase: "discovering",
|
||||
received: 0,
|
||||
total: 0,
|
||||
});
|
||||
let outcome = service
|
||||
.search_content_id(&content_id)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
anyhow::anyhow!("{primary_error:#}; content lookup also failed: {err}")
|
||||
})?;
|
||||
let mut last_error = primary_error;
|
||||
let mut downloaded = None;
|
||||
for item in outcome
|
||||
.local_results
|
||||
.into_iter()
|
||||
.chain(outcome.network_results)
|
||||
{
|
||||
if item.kind != music_dht::ItemKind::Track
|
||||
|| item.content_id.as_deref() != Some(content_id.as_str())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let candidate_item_id = hex_encode(item.id.as_bytes());
|
||||
if item.owner == owner && candidate_item_id == item_id {
|
||||
continue;
|
||||
}
|
||||
match download(
|
||||
&service,
|
||||
item.owner,
|
||||
&candidate_item_id,
|
||||
&content_id,
|
||||
&dir,
|
||||
&mut progress,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(candidate) => {
|
||||
downloaded = Some(candidate);
|
||||
break;
|
||||
}
|
||||
Err(err) => last_error = err,
|
||||
}
|
||||
}
|
||||
downloaded.ok_or_else(|| {
|
||||
anyhow::anyhow!(
|
||||
"no reachable peer currently provides this track: {last_error:#}"
|
||||
)
|
||||
})?
|
||||
}
|
||||
};
|
||||
if !save {
|
||||
super::lock(&self.prepared_cache)
|
||||
.insert(token.clone(), (downloaded.path, downloaded.mime));
|
||||
@@ -306,15 +359,20 @@ impl Federation {
|
||||
}
|
||||
progress(DownloadProgress {
|
||||
phase: "saving",
|
||||
received: 1,
|
||||
total: 1,
|
||||
received: 0,
|
||||
total: 0,
|
||||
});
|
||||
let track_id = materialize(&pool, &storage_root, &content_id, downloaded).await?;
|
||||
// The normal periodic sync will publish it; this immediate sync keeps
|
||||
// save-on-listen useful to the federation without waiting a minute.
|
||||
if let Err(err) = self.sync_now().await {
|
||||
tracing::warn!(track_id, "post-import federation publish failed: {err:#}");
|
||||
}
|
||||
// Materialization is the playback boundary: return the local track
|
||||
// immediately so the browser can replace the pending queue entry and
|
||||
// start it. Publishing must not keep the prepare stream stuck at 100%
|
||||
// when a federation peer is slow or offline.
|
||||
let federation = std::sync::Arc::clone(self);
|
||||
tokio::spawn(async move {
|
||||
if let Err(err) = federation.sync_now().await {
|
||||
tracing::warn!(track_id, "post-import federation publish failed: {err:#}");
|
||||
}
|
||||
});
|
||||
Ok(PreparedTrack {
|
||||
local_track_id: Some(track_id),
|
||||
stream_url: format!("/api/player/stream/{track_id}"),
|
||||
@@ -362,6 +420,10 @@ fn cache_artwork(federation: &Federation, key: String, artwork: &(Vec<u8>, Strin
|
||||
);
|
||||
}
|
||||
|
||||
fn hex_encode(bytes: &[u8]) -> String {
|
||||
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
|
||||
}
|
||||
|
||||
async fn download(
|
||||
service: &music_dht::MusicDhtService,
|
||||
owner: EndpointId,
|
||||
|
||||
@@ -2495,6 +2495,7 @@ document.addEventListener('alpine:init', () => {
|
||||
federationSearch: { loading: false, error: '', artists: [], releases: [], tracks: [] },
|
||||
artistFederation: { loading: false, error: '', releases: [], tracks: [] },
|
||||
federationPreparing: {},
|
||||
federationFailures: {},
|
||||
trackAvailability: {},
|
||||
_federatedReleaseCache: {},
|
||||
federationLiked: [],
|
||||
@@ -3863,6 +3864,11 @@ document.addEventListener('alpine:init', () => {
|
||||
return;
|
||||
}
|
||||
if (!contentId || sources.length === 0) return;
|
||||
if (this.federationFailures[contentId]) {
|
||||
const failures = { ...this.federationFailures };
|
||||
delete failures[contentId];
|
||||
this.federationFailures = failures;
|
||||
}
|
||||
if (this.federationPreparing[contentId]) {
|
||||
if (options.playAfterPrepare !== false) {
|
||||
this.federationPreparing = {
|
||||
@@ -4023,7 +4029,13 @@ document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('queue').playRelease([playable], 0);
|
||||
}
|
||||
} catch (error) {
|
||||
window.alert(error?.message || 'Federated track download failed');
|
||||
this.federationFailures = {
|
||||
...this.federationFailures,
|
||||
[contentId]: {
|
||||
message: error?.message || 'Federated track is temporarily unavailable',
|
||||
failedAt: Date.now(),
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
const next = { ...this.federationPreparing };
|
||||
delete next[contentId];
|
||||
@@ -4035,12 +4047,17 @@ document.addEventListener('alpine:init', () => {
|
||||
return this.federationPreparing[contentId] || null;
|
||||
},
|
||||
|
||||
federationFailure(contentId) {
|
||||
return this.federationFailures[contentId] || null;
|
||||
},
|
||||
|
||||
federationDownloadTooltip(contentId) {
|
||||
const progress = this.federationDownload(contentId);
|
||||
if (!progress) return '';
|
||||
const phases = {
|
||||
checking: 'Checking local library',
|
||||
connecting: 'Connecting to peer',
|
||||
discovering: 'Looking for another peer',
|
||||
downloading: 'Downloading',
|
||||
verifying: 'Verifying content ID',
|
||||
saving: 'Adding to library',
|
||||
|
||||
@@ -562,13 +562,17 @@
|
||||
<div class="track-row federation-track-row"
|
||||
@dblclick="$store.library.playFederatedTrack(track)">
|
||||
<span class="track-num federation-track-status">
|
||||
<template x-if="!$store.library.federationDownload(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<template x-if="!$store.library.federationDownload(track.key.content_id) && !$store.library.federationFailure(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="$store.library.federationFailure(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<span class="federation-error-badge"
|
||||
:title="$store.library.federationFailure(track.key.content_id).message">!</span>
|
||||
</template>
|
||||
<template x-if="$store.library.federationDownload(track.key.content_id)">
|
||||
<span class="federation-download-progress"
|
||||
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
||||
@@ -905,12 +909,16 @@
|
||||
<div class="track-row federation-track-row"
|
||||
@dblclick="$store.library.playFederatedTrack(track)">
|
||||
<span class="track-num federation-track-status">
|
||||
<template x-if="!$store.library.federationDownload(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<template x-if="!$store.library.federationDownload(track.key.content_id) && !$store.library.federationFailure(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="$store.library.federationFailure(track.key.content_id) && !$store.library.isTrackLocal(track)">
|
||||
<span class="federation-error-badge"
|
||||
:title="$store.library.federationFailure(track.key.content_id).message">!</span>
|
||||
</template>
|
||||
<template x-if="$store.library.federationDownload(track.key.content_id)">
|
||||
<span class="federation-download-progress"
|
||||
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
||||
@@ -1158,13 +1166,17 @@
|
||||
<template x-if="!track.federation_pending">
|
||||
<span x-text="track.track_number || (idx + 1)"></span>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && !$store.library.federationDownload(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<template x-if="track.federation_pending && !$store.library.federationDownload(track.content_id) && !$store.library.federationFailure(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && $store.library.federationFailure(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<span class="federation-error-badge"
|
||||
:title="$store.library.federationFailure(track.content_id).message">!</span>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && !$store.library.federationDownload(track.content_id) && $store.library.isTrackLocal(track._federationTrack)">
|
||||
<span x-text="track.track_number || (idx + 1)"></span>
|
||||
</template>
|
||||
@@ -1312,13 +1324,17 @@
|
||||
<template x-if="!track.federation_pending || $store.library.isTrackLocal(track._federationTrack)">
|
||||
<span x-text="idx + 1"></span>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && !$store.library.federationDownload(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<template x-if="track.federation_pending && !$store.library.federationDownload(track.content_id) && !$store.library.federationFailure(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && $store.library.federationFailure(track.content_id) && !$store.library.isTrackLocal(track._federationTrack)">
|
||||
<span class="federation-error-badge"
|
||||
:title="$store.library.federationFailure(track.content_id).message">!</span>
|
||||
</template>
|
||||
<template x-if="track.federation_pending && $store.library.federationDownload(track.content_id)">
|
||||
<span class="federation-download-progress"
|
||||
:class="{ indeterminate: !$store.library.federationDownload(track.content_id).total }">
|
||||
@@ -1452,12 +1468,16 @@
|
||||
<span class="queue-federation-status federation-track-status"
|
||||
x-show="item.track.federation_pending"
|
||||
x-cloak>
|
||||
<template x-if="!$store.library.federationDownload(item.track.content_id)">
|
||||
<template x-if="!$store.library.federationDownload(item.track.content_id) && !$store.library.federationFailure(item.track.content_id)">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
<path d="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||
</svg>
|
||||
</template>
|
||||
<template x-if="$store.library.federationFailure(item.track.content_id)">
|
||||
<span class="federation-error-badge"
|
||||
:title="$store.library.federationFailure(item.track.content_id).message">!</span>
|
||||
</template>
|
||||
<template x-if="$store.library.federationDownload(item.track.content_id)">
|
||||
<span class="federation-download-progress"
|
||||
:class="{ indeterminate: !$store.library.federationDownload(item.track.content_id).total }">
|
||||
|
||||
@@ -2729,6 +2729,26 @@ button.user-stat:hover {
|
||||
height: 18px;
|
||||
color: var(--accent);
|
||||
}
|
||||
.federation-error-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: 1px solid rgba(235, 105, 105, .7);
|
||||
border-radius: 999px;
|
||||
background: rgba(190, 55, 55, .18);
|
||||
color: #ffb0b0;
|
||||
font-size: 11px;
|
||||
font-weight: 850;
|
||||
line-height: 1;
|
||||
cursor: help;
|
||||
}
|
||||
.queue-federation-status .federation-error-badge {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 10px;
|
||||
}
|
||||
.federation-download-progress {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
|
||||
Reference in New Issue
Block a user