(trackId)
| 511 | } |
| 512 | |
| 513 | async function loadTrackIntoStudio(trackId) { |
| 514 | let track = tracks[trackId]; |
| 515 | if (!track) return; |
| 516 | if (track.status === "unavailable") { |
| 517 | showError("This track's audio is no longer available. Re-upload to restore it."); |
| 518 | return; |
| 519 | } |
| 520 | const hadStoredAudio = Boolean(track.audioStems?.length); |
| 521 | const token = ++_loadTrackToken; |
| 522 | |
| 523 | // Start peaks fetch immediately — runs in parallel with job-data fetch so it |
| 524 | // resolves before wireUpAudio calls Multitrack.create. This prevents peaks.json |
| 525 | // from competing with stem WAV fetches for Safari's 6-connection-per-origin limit. |
| 526 | const peaksPromise = fetch(`/api/jobs/${trackId}/stems/peaks.json`) |
| 527 | .then((r) => (r.ok ? r.json() : {})) |
| 528 | .catch(() => ({})); |
| 529 | |
| 530 | // Always fetch fresh state so server-side changes (sections, analysis, stems) |
| 531 | // are reflected — cached localStorage data can be stale. |
| 532 | try { |
| 533 | const res = await fetch(`/api/jobs/${trackId}`); |
| 534 | if (token !== _loadTrackToken) return; |
| 535 | if (res.ok) { |
| 536 | const state = await res.json(); |
| 537 | track = stateMetadataToTrack(state, track); |
| 538 | tracks[trackId] = track; |
| 539 | saveState(); |
| 540 | } else if (res.status === 404) { |
| 541 | track = { ...track, status: "unavailable" }; |
| 542 | tracks[trackId] = track; |
| 543 | saveState(); |
| 544 | updateTrackStatus(trackId, "unavailable"); |
| 545 | showError("This track's audio is no longer available. Re-upload to restore it."); |
| 546 | return; |
| 547 | } |
| 548 | } catch (e) { console.warn("[catalog] server sync failed, using stored track:", e); } |
| 549 | |
| 550 | if (token !== _loadTrackToken) return; |
| 551 | // A reprocessing track may still carry the previous extraction's stems |
| 552 | // (hadStoredAudio), but it isn't ready — loading it would replace the live |
| 553 | // job-progress overlay with stale audio. Leave the progress UI in place. |
| 554 | if (PROCESSING_STATUSES.has(track.status)) return; |
| 555 | if (!track.audioStems?.length) return; |
| 556 | if (track.status !== "done" && !hadStoredAudio) return; |
| 557 | applyStoredStemSelection(track); |
| 558 | setCurrentTrack(trackId); |
| 559 | |
| 560 | const urlInput = document.getElementById("url"); |
| 561 | if (urlInput && track.sourceUrl) { |
| 562 | urlInput.value = track.sourceUrl.startsWith("local:") |
| 563 | ? track.sourceUrl.slice(6) |
| 564 | : track.sourceUrl; |
| 565 | } |
| 566 | |
| 567 | applyTrackInfoToPanel(track); |
| 568 | wireUpAudio(trackId, track.audioStems, track.duration || 0, track.thumb, track.mixUrl ?? null, track.title || "", peaksPromise, track.hasVideo ?? false); |
| 569 | initSections(trackId, track.sections, track.duration || 0); |
| 570 | } |
no test coverage detected