fix(media): loader before peer video/audio plays + accurate error (B3/B22)

Streaming a peer file connects over mesh/Tor before the first frame, so the
player sat blank. Add a loading state:
- PeerFiles video modal: spinner overlay ("Connecting to peer…") until the
  <video> fires playing/canplay; an error overlay on failure instead of a
  silent black box.
- useAudioPlayer: loading flag driven by loadstart/waiting vs canplay/playing;
  GlobalAudioPlayer shows a spinner in the transport button while connecting.
- Fix the misleading audio error "Could not play audio. File Browser may not be
  running." (wrong for peer content) → "Could not play this audio file. The peer
  may be offline…" (B22).

type-check clean; useAudioPlayer tests 10/10.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-06-16 05:45:17 -04:00
co-authored by Claude Opus 4.8
parent 921363542c
commit c481afc7d9
3 changed files with 63 additions and 8 deletions
+39 -6
View File
@@ -234,12 +234,30 @@
</svg>
</button>
<!-- Video element -->
<video
:src="videoPlayerUrl"
class="w-full rounded-xl"
controls
autoplay
/>
<div class="relative">
<video
:src="videoPlayerUrl"
class="w-full rounded-xl bg-black"
controls
autoplay
@playing="videoLoading = false"
@canplay="videoLoading = false"
@error="videoLoading = false; videoError = true"
/>
<!-- Loader while the stream connects over mesh/Tor -->
<div v-if="videoLoading && !videoError" class="absolute inset-0 flex flex-col items-center justify-center gap-3 rounded-xl bg-black/60 pointer-events-none">
<svg class="w-8 h-8 animate-spin text-white/80" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.4 0 0 5.4 0 12h4z" />
</svg>
<span class="text-sm text-white/70">Connecting to peer</span>
</div>
<!-- Error state -->
<div v-if="videoError" class="absolute inset-0 flex flex-col items-center justify-center gap-2 rounded-xl bg-black/70 text-center px-4">
<p class="text-sm text-white/80">Couldn't play this video</p>
<p class="text-xs text-white/50">The peer may be offline, or this preview can't be played. Try downloading it instead.</p>
</div>
</div>
<!-- Info bar -->
<div class="mt-3 flex items-center justify-between">
<div>
@@ -320,6 +338,10 @@ const audioPlayer = useAudioPlayer()
const videoPlayerItem = ref<CatalogItem | null>(null)
const videoPlayerUrl = ref<string | null>(null)
const videoPlayerPaid = ref(false)
// Streaming a peer's file connects over mesh/Tor before the first frame, so
// show a loader until the element can actually play (or errors).
const videoLoading = ref(false)
const videoError = ref(false)
const peerDisplayName = computed(() => {
if (currentPeer.value?.name) return currentPeer.value.name
@@ -604,8 +626,19 @@ function closeVideoPlayer() {
videoPlayerItem.value = null
videoPlayerUrl.value = null
videoPlayerPaid.value = false
videoLoading.value = false
videoError.value = false
}
// Show the loader the moment a video opens; the element's playing/canplay/error
// events clear it.
watch(videoPlayerUrl, (url) => {
if (url) {
videoLoading.value = true
videoError.value = false
}
})
function triggerDownload(base64Data: string, item: CatalogItem) {
const blob = new Blob(
[Uint8Array.from(atob(base64Data), c => c.charCodeAt(0))],