feat: add playback seeking
This commit is contained in:
12 files changed
+170
-8
No files matched your search
@@ -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 }),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in new issue
Block a user