feat: add playback seeking
Backend / Lint i formatowanie (pull_request) Successful in 4s
Backend / Testy i kontrakt API (pull_request) Successful in 4s
Backend / Testowy build obrazu (pull_request) Successful in 11s
Panel / Lint, typy i build (pull_request) Successful in 21s

This commit is contained in:
mmacierzanka committed 2026-08-21 18:53:38 +02:00
1 parent ed13d4af06
commit 7ef0a43693
12 files changed
+170 -8

No files matched your search

+1
View File
@@ -50,6 +50,7 @@ export const api = {
playFile: (filename: string) => post('/api/play_file', { filename }),
pause: () => post('/api/pause'),
resume: () => post('/api/resume'),
seek: (seconds: number) => post<{ seconds: number }>('/api/seek', { seconds }),
skip: () => post('/api/skip'),
stop: () => post('/api/stop'),
setVolume: (volume: number) => post('/api/volume', { volume }),
+15
View File
@@ -476,6 +476,21 @@ body {
border: 1px solid rgba(255, 255, 255, 0.03);
}
.track-progress-slider {
position: absolute;
inset: -7px 0;
width: 100%;
height: 20px;
margin: 0;
opacity: 0;
cursor: pointer;
z-index: 1;
}
.track-progress-slider:disabled {
cursor: default;
}
.progress-bar-bg:hover {
height: 8px;
background: rgba(255, 255, 255, 0.15);
+32 -2
View File
@@ -27,6 +27,9 @@ const { elapsed, duration, percent } = useTrackProgress(toRef(props, 'status'))
const current = computed(() => props.status?.current ?? null)
const playing = computed(() => props.status?.playing ?? false)
const loopMode = computed(() => loopModeOf(props.status))
const seeking = ref(false)
const seekPosition = ref(0)
const displayedElapsed = computed(() => seeking.value ? seekPosition.value : elapsed.value)
/** Podczas przeciągania suwaka pokazujemy wartość lokalną, nie tę z serwera. */
const draggedVolume = ref<number | null>(null)
@@ -92,6 +95,21 @@ async function togglePlayback() {
)
}
function onSeekInput(event: Event) {
seeking.value = true
seekPosition.value = Number((event.target as HTMLInputElement).value)
}
async function commitSeek() {
if (!current.value || duration.value <= 0) return
const position = seekPosition.value
try {
await run(() => api.seek(position), 'Przewinięto utwór', 'Błąd przewijania utworu')
} finally {
seeking.value = false
}
}
const skip = () => run(() => api.skip(), 'Pominięto utwór', 'Błąd pomijania utworu')
const stop = () =>
@@ -163,12 +181,24 @@ async function cycleLoop() {
v-if="current"
class="track-progress-container"
>
<span class="time-text">{{ formatTime(elapsed) }}</span>
<span class="time-text">{{ formatTime(displayedElapsed) }}</span>
<div class="progress-bar-bg">
<div
class="progress-bar-fill"
:style="{ width: `${percent}%` }"
:style="{ width: `${seeking ? (duration > 0 ? (seekPosition / duration) * 100 : 0) : percent}%` }"
/>
<input
type="range"
class="track-progress-slider"
min="0"
:max="duration"
step="0.1"
:value="seeking ? seekPosition : elapsed"
:disabled="duration <= 0"
aria-label="Pozycja utworu"
@input="onSeekInput"
@change="commitSeek"
>
</div>
<span class="time-text">{{ duration > 0 ? formatTime(duration) : 'Live' }}</span>
</div>