Fixed autoplay after fed download
This commit is contained in:
Generated
+1
-1
@@ -1845,7 +1845,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "furumusic"
|
name = "furumusic"
|
||||||
version = "0.9.1"
|
version = "0.9.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "furumusic"
|
name = "furumusic"
|
||||||
version = "0.9.2"
|
version = "0.9.3"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
description = "Reusable web-app boilerplate: auth, OIDC/SSO, admin panel, user management, i18n, PostgreSQL"
|
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");
|
let dir = storage_root.join("federation");
|
||||||
tokio::fs::create_dir_all(&dir).await?;
|
tokio::fs::create_dir_all(&dir).await?;
|
||||||
let downloaded =
|
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 {
|
if !save {
|
||||||
super::lock(&self.prepared_cache)
|
super::lock(&self.prepared_cache)
|
||||||
.insert(token.clone(), (downloaded.path, downloaded.mime));
|
.insert(token.clone(), (downloaded.path, downloaded.mime));
|
||||||
@@ -306,15 +359,20 @@ impl Federation {
|
|||||||
}
|
}
|
||||||
progress(DownloadProgress {
|
progress(DownloadProgress {
|
||||||
phase: "saving",
|
phase: "saving",
|
||||||
received: 1,
|
received: 0,
|
||||||
total: 1,
|
total: 0,
|
||||||
});
|
});
|
||||||
let track_id = materialize(&pool, &storage_root, &content_id, downloaded).await?;
|
let track_id = materialize(&pool, &storage_root, &content_id, downloaded).await?;
|
||||||
// The normal periodic sync will publish it; this immediate sync keeps
|
// Materialization is the playback boundary: return the local track
|
||||||
// save-on-listen useful to the federation without waiting a minute.
|
// immediately so the browser can replace the pending queue entry and
|
||||||
if let Err(err) = self.sync_now().await {
|
// start it. Publishing must not keep the prepare stream stuck at 100%
|
||||||
tracing::warn!(track_id, "post-import federation publish failed: {err:#}");
|
// 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 {
|
Ok(PreparedTrack {
|
||||||
local_track_id: Some(track_id),
|
local_track_id: Some(track_id),
|
||||||
stream_url: format!("/api/player/stream/{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(
|
async fn download(
|
||||||
service: &music_dht::MusicDhtService,
|
service: &music_dht::MusicDhtService,
|
||||||
owner: EndpointId,
|
owner: EndpointId,
|
||||||
|
|||||||
@@ -2495,6 +2495,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
federationSearch: { loading: false, error: '', artists: [], releases: [], tracks: [] },
|
federationSearch: { loading: false, error: '', artists: [], releases: [], tracks: [] },
|
||||||
artistFederation: { loading: false, error: '', releases: [], tracks: [] },
|
artistFederation: { loading: false, error: '', releases: [], tracks: [] },
|
||||||
federationPreparing: {},
|
federationPreparing: {},
|
||||||
|
federationFailures: {},
|
||||||
trackAvailability: {},
|
trackAvailability: {},
|
||||||
_federatedReleaseCache: {},
|
_federatedReleaseCache: {},
|
||||||
federationLiked: [],
|
federationLiked: [],
|
||||||
@@ -3863,6 +3864,11 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!contentId || sources.length === 0) 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 (this.federationPreparing[contentId]) {
|
||||||
if (options.playAfterPrepare !== false) {
|
if (options.playAfterPrepare !== false) {
|
||||||
this.federationPreparing = {
|
this.federationPreparing = {
|
||||||
@@ -4023,7 +4029,13 @@ document.addEventListener('alpine:init', () => {
|
|||||||
Alpine.store('queue').playRelease([playable], 0);
|
Alpine.store('queue').playRelease([playable], 0);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} 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 {
|
} finally {
|
||||||
const next = { ...this.federationPreparing };
|
const next = { ...this.federationPreparing };
|
||||||
delete next[contentId];
|
delete next[contentId];
|
||||||
@@ -4035,12 +4047,17 @@ document.addEventListener('alpine:init', () => {
|
|||||||
return this.federationPreparing[contentId] || null;
|
return this.federationPreparing[contentId] || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
federationFailure(contentId) {
|
||||||
|
return this.federationFailures[contentId] || null;
|
||||||
|
},
|
||||||
|
|
||||||
federationDownloadTooltip(contentId) {
|
federationDownloadTooltip(contentId) {
|
||||||
const progress = this.federationDownload(contentId);
|
const progress = this.federationDownload(contentId);
|
||||||
if (!progress) return '';
|
if (!progress) return '';
|
||||||
const phases = {
|
const phases = {
|
||||||
checking: 'Checking local library',
|
checking: 'Checking local library',
|
||||||
connecting: 'Connecting to peer',
|
connecting: 'Connecting to peer',
|
||||||
|
discovering: 'Looking for another peer',
|
||||||
downloading: 'Downloading',
|
downloading: 'Downloading',
|
||||||
verifying: 'Verifying content ID',
|
verifying: 'Verifying content ID',
|
||||||
saving: 'Adding to library',
|
saving: 'Adding to library',
|
||||||
|
|||||||
@@ -562,13 +562,17 @@
|
|||||||
<div class="track-row federation-track-row"
|
<div class="track-row federation-track-row"
|
||||||
@dblclick="$store.library.playFederatedTrack(track)">
|
@dblclick="$store.library.playFederatedTrack(track)">
|
||||||
<span class="track-num federation-track-status">
|
<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">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<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="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"/>
|
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</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)">
|
<template x-if="$store.library.federationDownload(track.key.content_id)">
|
||||||
<span class="federation-download-progress"
|
<span class="federation-download-progress"
|
||||||
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
||||||
@@ -905,12 +909,16 @@
|
|||||||
<div class="track-row federation-track-row"
|
<div class="track-row federation-track-row"
|
||||||
@dblclick="$store.library.playFederatedTrack(track)">
|
@dblclick="$store.library.playFederatedTrack(track)">
|
||||||
<span class="track-num federation-track-status">
|
<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">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<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="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</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)">
|
<template x-if="$store.library.federationDownload(track.key.content_id)">
|
||||||
<span class="federation-download-progress"
|
<span class="federation-download-progress"
|
||||||
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
:class="{ indeterminate: !$store.library.federationDownload(track.key.content_id).total }">
|
||||||
@@ -1158,13 +1166,17 @@
|
|||||||
<template x-if="!track.federation_pending">
|
<template x-if="!track.federation_pending">
|
||||||
<span x-text="track.track_number || (idx + 1)"></span>
|
<span x-text="track.track_number || (idx + 1)"></span>
|
||||||
</template>
|
</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">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<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="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"/>
|
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</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)">
|
<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>
|
<span x-text="track.track_number || (idx + 1)"></span>
|
||||||
</template>
|
</template>
|
||||||
@@ -1312,13 +1324,17 @@
|
|||||||
<template x-if="!track.federation_pending || $store.library.isTrackLocal(track._federationTrack)">
|
<template x-if="!track.federation_pending || $store.library.isTrackLocal(track._federationTrack)">
|
||||||
<span x-text="idx + 1"></span>
|
<span x-text="idx + 1"></span>
|
||||||
</template>
|
</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">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<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="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"/>
|
<path d="M2.8 5.8a11 11 0 000 12.4M21.2 5.8a11 11 0 010 12.4"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</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)">
|
<template x-if="track.federation_pending && $store.library.federationDownload(track.content_id)">
|
||||||
<span class="federation-download-progress"
|
<span class="federation-download-progress"
|
||||||
:class="{ indeterminate: !$store.library.federationDownload(track.content_id).total }">
|
:class="{ indeterminate: !$store.library.federationDownload(track.content_id).total }">
|
||||||
@@ -1452,12 +1468,16 @@
|
|||||||
<span class="queue-federation-status federation-track-status"
|
<span class="queue-federation-status federation-track-status"
|
||||||
x-show="item.track.federation_pending"
|
x-show="item.track.federation_pending"
|
||||||
x-cloak>
|
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">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||||
<circle cx="12" cy="12" r="3"/>
|
<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="M5.6 8.5a7.5 7.5 0 000 7M18.4 8.5a7.5 7.5 0 010 7"/>
|
||||||
</svg>
|
</svg>
|
||||||
</template>
|
</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)">
|
<template x-if="$store.library.federationDownload(item.track.content_id)">
|
||||||
<span class="federation-download-progress"
|
<span class="federation-download-progress"
|
||||||
:class="{ indeterminate: !$store.library.federationDownload(item.track.content_id).total }">
|
:class="{ indeterminate: !$store.library.federationDownload(item.track.content_id).total }">
|
||||||
|
|||||||
@@ -2729,6 +2729,26 @@ button.user-stat:hover {
|
|||||||
height: 18px;
|
height: 18px;
|
||||||
color: var(--accent);
|
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 {
|
.federation-download-progress {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user