Fix 8 code-review findings across transcode queue, requests, cleanup, music

- Episode transcode (single + bulk) now accepts storage_type=scan, matching
  stream_episode and the movie-transcode endpoint — Library-Scan-imported
  TV episodes can finally be transcoded, not just streamed.
- retry_job now forwards content_type, fixing retry for failed/cancelled
  episode jobs (was silently treating them as movies and failing again).
- cancel_job now updates the correct collection (movies vs episodes) based
  on content_type instead of always writing to db.movies.
- approve_request atomically claims the request (pending -> approving)
  before contacting Radarr/Sonarr, so a double-click or two concurrent
  admins can't both add the same content; releases the claim back to
  pending on any failure so a fixable error can be retried.
- Music enrichment's update_many now re-checks album_art_url is still
  empty, so it no longer clobbers art an admin manually set via PATCH.
- Library Cleanup's dangling-refs scan now also checks episode_progress
  for rows referencing a deleted episode (previously only watchlist/
  progress against movie_id were checked).
- Settings now surfaces a failed bulk-TMDB-enrich run as an error instead
  of a false "Enrichment done" success summary.
- Music player's isPlaying now syncs from the audio element's native
  play/pause events instead of only being set inside togglePlay/track-
  change, so it can't drift from actual playback state (buffering stalls,
  OS media-key pauses, etc).
This commit is contained in:
Myron Blair
2026-07-26 22:24:09 -05:00
parent 5ac78654d4
commit 5fc37e46e4
4 changed files with 89 additions and 49 deletions
+11 -4
View File
@@ -41,29 +41,36 @@ export const MusicPlayerProvider = ({ children }) => {
const audio = audioRef.current;
if (!current) { audio.pause(); return; }
audio.src = getTrackStreamUrl(current);
audio.play().then(() => setIsPlaying(true)).catch(() => setIsPlaying(false));
audio.play().catch(() => {}); // isPlaying reflects reality via the native play/pause listeners below
}, [current]);
// isPlaying always mirrors the audio element's own state, so it stays correct through anything
// that changes playback outside togglePlay — a stall, an OS media-key pause, autoplay being blocked.
useEffect(() => {
const audio = audioRef.current;
const onTime = () => setProgress({ position: audio.currentTime, duration: audio.duration || 0 });
const onEnded = () => {
if (index >= 0 && index < queue.length - 1) setIndex(index + 1);
else setIsPlaying(false);
};
const onPlay = () => setIsPlaying(true);
const onPause = () => setIsPlaying(false);
audio.addEventListener("timeupdate", onTime);
audio.addEventListener("ended", onEnded);
audio.addEventListener("play", onPlay);
audio.addEventListener("pause", onPause);
return () => {
audio.removeEventListener("timeupdate", onTime);
audio.removeEventListener("ended", onEnded);
audio.removeEventListener("play", onPlay);
audio.removeEventListener("pause", onPause);
};
}, [index, queue]);
const togglePlay = useCallback(() => {
const audio = audioRef.current;
if (!current) return;
if (audio.paused) audio.play().then(() => setIsPlaying(true));
else { audio.pause(); setIsPlaying(false); }
if (audio.paused) audio.play().catch(() => {});
else audio.pause();
}, [current]);
const next = useCallback(() => {
+11 -2
View File
@@ -140,7 +140,11 @@ export default function Settings() {
setEnrich(data);
if (!data.running) {
clearInterval(t);
toast.success(`Enrichment done — ${data.enriched} enriched, ${data.skipped} already complete, ${data.failed} not found`);
if (data.error) {
toast.error(`Enrichment failed: ${data.error}`);
} else {
toast.success(`Enrichment done — ${data.enriched} enriched, ${data.skipped} already complete, ${data.failed} not found`);
}
}
}, 2000);
return () => clearInterval(t);
@@ -210,7 +214,12 @@ export default function Settings() {
<button type="button" onClick={enrichAll} disabled={enrich?.running} className="text-xs uppercase tracking-[0.2em] border border-[#222] hover:border-[#D9381E] hover:text-[#D9381E] text-[#8A8A8A] px-4 py-2 transition-colors disabled:opacity-50" data-testid="enrich-all-button">
{enrich?.running ? `Enriching… ${enrich.done}/${enrich.total}` : "Bulk enrich all movies →"}
</button>
{enrich && !enrich.running && enrich.total > 0 && (
{enrich && !enrich.running && enrich.error && (
<span className="text-xs text-[#fca5a5]" data-testid="enrich-all-error">
Last run failed: {enrich.error}
</span>
)}
{enrich && !enrich.running && !enrich.error && enrich.total > 0 && (
<span className="text-xs text-[#8A8A8A]" data-testid="enrich-all-summary">
Last run: {enrich.enriched} enriched · {enrich.skipped} already complete · {enrich.failed} not found on TMDB
</span>