v3: native bandcamp playback and a queue in the global player
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
All checks were successful
Build and Publish Docker Image / build-and-push (push) Successful in 43s
The player now holds a queue instead of a single item, so albums play through and single tracks share the same code path. Adds a "stream" item kind for expiring, remotely-hosted sources: Bandcamp signs its mp3 URLs for ~24h, so an item carries the page it came from and re-resolves itself on error or on a stale restored session, falling back to the iframe embed if that fails too. Gated behind GERBEUR_BANDCAMP_PLAYER (default "embed"), injected into a meta tag the same way as site-name/site-emoji. Also: player header gains artwork and a subtitle, volume persists across queue advances and reloads, and cross-origin streams get a plain seek bar rather than a waveform that can never decode. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SbtjNsT5wvuABnfqhegZEJ
This commit is contained in:
130
src/App.css
130
src/App.css
@@ -832,6 +832,37 @@
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
/* Audio whose peaks can't be decoded (cross-origin streams). Boxed to the
|
||||
waveform's height so the player doesn't resize between item kinds, with the
|
||||
bar itself centred inside. */
|
||||
.audio-player-track--stream {
|
||||
height: 48px;
|
||||
background: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.audio-player-track--stream::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 50% 0 auto 0;
|
||||
transform: translateY(-50%);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--color-accent) 12%,
|
||||
var(--color-border) 88%
|
||||
);
|
||||
}
|
||||
.audio-player-track--stream .audio-player-fill {
|
||||
top: 50%;
|
||||
bottom: auto;
|
||||
transform: translateY(-50%);
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.audio-player-track--volume {
|
||||
flex: 1 1 100px;
|
||||
max-width: 120px;
|
||||
@@ -1063,6 +1094,105 @@ a.global-player-title:hover {
|
||||
.global-player.global-player--bandcamp {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
/* ── Global player: queue ── */
|
||||
|
||||
/* The title/artist pair takes the space the bare title used to claim. */
|
||||
.global-player-heading {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.1rem;
|
||||
}
|
||||
.global-player-heading .global-player-title {
|
||||
flex: none;
|
||||
}
|
||||
.global-player-subtitle {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.global-player-artwork {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
flex: none;
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.global-player-transport {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
flex: none;
|
||||
}
|
||||
.global-player-position {
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.global-player-transport .btn--ghost:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: default;
|
||||
}
|
||||
.global-player-status {
|
||||
margin: 0;
|
||||
padding: 0 1rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.global-player-tracks {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 0.5rem 0.5rem;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.global-player-track > button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
width: 100%;
|
||||
padding: 0.35rem 0.5rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-left: 3px solid transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-size: 0.8rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.global-player-track > button:hover {
|
||||
background: var(--color-surface-alt, rgba(127, 127, 127, 0.12));
|
||||
}
|
||||
/* An accent border rather than a filled pill, so no theme has to un-round it. */
|
||||
.global-player-track.is-current > button {
|
||||
border-left-color: var(--color-accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.global-player-track-num {
|
||||
flex: none;
|
||||
min-width: 1.5em;
|
||||
color: var(--color-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.global-player-track-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.global-player-track-dur {
|
||||
flex: none;
|
||||
color: var(--color-text-muted);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.feed-loading-more {
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
|
||||
Reference in New Issue
Block a user