handle seeking in unbuffered tracks
Hunter
Tuesday, October 28th, 2025 at 2:41 PM
57
58 audio.addEventListener('pause', () => {
59 stopProgressBar();
- // Only show "Paused" if the user actually paused (not during track transitions)
- if (!audio.ended) {
60 + // Only show "Paused" if the user actually paused (not during track transitions or seeking)
61 + if (!audio.ended && !isSeeking) {
62 const song = songs[currentSongIndex];
63 updateCurrentSongDisplay(`Paused: ${song.artist} - ${song.title}`);
64 }
233 }
234
235 function updateProgressBar() {
- if (audio.duration && !isDragging) {
236 + if (audio.duration && !isDragging && !isSeeking) {
237 const currentTime = audio.currentTime;
238 const duration = audio.duration;
239 const progressPercentage = (currentTime / duration) * 100;
259 return clickPercentage;
260 }
261
262 +let isSeeking = false;
263 +let targetSeekTime = null;
264 +
265 function applySeek(clickPercentage) {
266 if (!playerReady) return;
267
274 audio.addEventListener('loadedmetadata', function setInitialTime() {
275 const duration = audio.duration;
276 const seekTime = duration * clickPercentage;
- audio.currentTime = seekTime;
277 + attemptSeekWithRetry(seekTime, clickPercentage);
278 prePlaySeekTime = seekTime;
279 audio.removeEventListener('loadedmetadata', setInitialTime);
280 }, { once: true });
281 } else if (audio.duration) {
282 const duration = audio.duration;
283 const seekTime = duration * clickPercentage;
- audio.currentTime = seekTime;
284 + attemptSeekWithRetry(seekTime, clickPercentage);
285 prePlaySeekTime = seekTime;
286 }
287 }
288
289 +function isTimeBuffered(time) {
290 + // Check if the given time is within any buffered time range
291 + for (let i = 0; i < audio.buffered.length; i++) {
292 + if (time >= audio.buffered.start(i) && time <= audio.buffered.end(i)) {
293 + return true;
294 + }
295 + }
296 + return false;
297 +}
298 +
299 +function attemptSeekWithRetry(seekTime, targetPercentage) {
300 + targetSeekTime = seekTime;
301 + isSeeking = true;
302 +
303 + // Lock the progress bar at the target position
304 + progressBar.style.setProperty('--progress', targetPercentage * 100);
305 +
306 + const wasPlaying = !audio.paused;
307 +
308 + // Try to seek
309 + audio.currentTime = seekTime;
310 +
311 + // Handler to check if we reached the target after seeking completes
312 + function checkSeekSuccess() {
313 + // Allow small tolerance for floating point comparison
314 + if (Math.abs(audio.currentTime - targetSeekTime) > 0.5) {
315 + // Browser clamped to buffered range - need to wait for more data
316 + // Now pause and show seeking status
317 + if (wasPlaying) {
318 + audio.pause();
319 + }
320 + const song = songs[currentSongIndex];
321 + updateCurrentSongDisplay(`Seeking: ${song.artist} - ${song.title}`);
322 + continueSeekingToTarget(wasPlaying);
323 + } else {
324 + // Successfully reached target immediately (was already buffered)
325 + isSeeking = false;
326 + targetSeekTime = null;
327 + // No need to update display - the seek was instant and playback continues normally
328 + }
329 + }
330 +
331 + audio.addEventListener('seeked', checkSeekSuccess, { once: true });
332 +}
333 +
334 +function continueSeekingToTarget(wasPlaying) {
335 + const song = songs[currentSongIndex];
336 +
337 + // Keep showing seeking status
338 + updateCurrentSongDisplay(`Seeking: ${song.artist} - ${song.title}`);
339 +
340 + // Handler for when more data loads
341 + function retrySeek() {
342 + if (!isSeeking || targetSeekTime === null) {
343 + return; // Seeking was cancelled
344 + }
345 +
346 + audio.currentTime = targetSeekTime;
347 +
348 + // Check again after this seek completes
349 + function checkAgain() {
350 + if (!isSeeking || targetSeekTime === null) {
351 + return;
352 + }
353 +
354 + if (Math.abs(audio.currentTime - targetSeekTime) > 0.5) {
355 + // Still not there, keep trying
356 + continueSeekingToTarget(wasPlaying);
357 + } else {
358 + // Success!
359 + isSeeking = false;
360 + targetSeekTime = null;
361 +
362 + if (wasPlaying) {
363 + audio.play();
364 + } else {
365 + updateCurrentSongDisplay(`Paused: ${song.artist} - ${song.title}`);
366 + }
367 + }
368 + }
369 +
370 + audio.addEventListener('seeked', checkAgain, { once: true });
371 + }
372 +
373 + // Wait for more data to load, then try again
374 + audio.addEventListener('progress', retrySeek, { once: true });
375 +
376 + // Also set a timeout fallback in case progress doesn't fire
377 + setTimeout(() => {
378 + if (isSeeking && targetSeekTime !== null && Math.abs(audio.currentTime - targetSeekTime) > 0.5) {
379 + retrySeek();
380 + }
381 + }, 1000);
382 +}
383 +
384 function onProgressMouseDown(event) {
385 if (!playerReady) return;
386 isDragging = true;