Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions src/components/Player.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ export default function Player() {
<Artwork track={p.current} size={40} />
<span className="mini__meta">
<span className="mini__title">{p.current.title}</span>
<span className="mini__artist">{p.current.artist}</span>
<span className="mini__artist">
{p.missing ? 'Audio unavailable — re-import' : p.current.artist}
</span>
</span>
<span className="mini__controls" onClick={(e) => e.stopPropagation()}>
<button className="iconbtn" onClick={p.toggle} aria-label={p.isPlaying ? 'Pause' : 'Play'}>
<button className="iconbtn" onClick={p.toggle} aria-label={p.isPlaying ? 'Pause' : 'Play'} disabled={p.missing}>
{p.isPlaying ? <PauseIcon /> : <PlayIcon />}
</button>
<button className="iconbtn" onClick={p.next} aria-label="Next"><NextIcon /></button>
Expand Down Expand Up @@ -82,25 +84,31 @@ function NowPlaying({ p, onClose }) {
</button>
</div>

<div className="scrub">
<input
type="range"
min="0"
max={p.duration || 0}
step="0.1"
value={p.progress}
onChange={(e) => p.seek(Number(e.target.value))}
style={{ background: `linear-gradient(to right, var(--accent) ${pct}%, var(--surface-2) ${pct}%)` }}
/>
<div className="scrub__times">
<span>{formatTime(p.progress)}</span>
<span>{formatTime(p.duration)}</span>
{p.missing ? (
<p className="now__missing" role="status">
Audio unavailable — the file for this track is missing. Re-import it to play.
</p>
) : (
<div className="scrub">
<input
type="range"
min="0"
max={p.duration || 0}
step="0.1"
value={p.progress}
onChange={(e) => p.seek(Number(e.target.value))}
style={{ background: `linear-gradient(to right, var(--accent) ${pct}%, var(--surface-2) ${pct}%)` }}
/>
<div className="scrub__times">
<span>{formatTime(p.progress)}</span>
<span>{formatTime(p.duration)}</span>
</div>
</div>
</div>
)}

<div className="transport">
<button className="iconbtn" onClick={p.prev} aria-label="Previous"><PrevIcon /></button>
<button className="playbtn" onClick={p.toggle} aria-label={p.isPlaying ? 'Pause' : 'Play'}>
<button className="playbtn" onClick={p.toggle} aria-label={p.isPlaying ? 'Pause' : 'Play'} disabled={p.missing}>
{p.isPlaying ? <PauseIcon big /> : <PlayIcon big />}
</button>
<button className="iconbtn" onClick={p.next} aria-label="Next"><NextIcon /></button>
Expand Down
2 changes: 2 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,8 @@ body {
box-shadow: 0 8px 24px rgba(233, 162, 59, 0.35);
}
.playbtn:active { transform: scale(0.96); }
.playbtn:disabled, .mini__controls .iconbtn:disabled { opacity: 0.4; cursor: default; box-shadow: none; }
.now__missing { margin: 14px 0; padding: 0 24px; text-align: center; font-size: 13px; color: var(--accent); }

.loopbtn {
display: inline-flex; align-items: center; gap: 8px;
Expand Down
16 changes: 15 additions & 1 deletion src/state/PlayerProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export function PlayerProvider({ children }) {
let cancelled = false
countedRef.current = false
setMissing(false)
// Reset the scrubber immediately so it doesn't show the previous track's
// position/length until the new metadata arrives.
setProgress(0)
setDuration(0)

const revokePrev = () => {
if (objectUrlRef.current) {
Expand All @@ -93,7 +97,17 @@ export function PlayerProvider({ children }) {
if (!url) {
const blob = await getAudioBlob(current.id)
if (!blob) {
if (!cancelled) setMissing(true)
// Bytes are gone (cleared storage / failed import). Stop the element
// so it doesn't keep playing the PREVIOUS track under a now-missing
// `current`, and reset state so the UI can show an honest message.
if (!cancelled) {
audio.pause()
audio.removeAttribute('src')
audio.load()
revokePrev() // release the previous track's blob URL too
setIsPlaying(false)
setMissing(true)
}
return
}
if (cancelled) return
Expand Down
Loading