HLS Seek Jumps Back or Live DVR Will Not Rewind: A Timeline-First Diagnosis

Diagnose HLS seek snap-back, expired live DVR positions, missing segments, keyframe alignment, Range responses, and player live-latency correction.

A viewer drags the scrubber backward, the control briefly shows the requested time, and playback jumps somewhere else. On video on demand, the same symptom may appear as a seek that stalls or resumes several seconds away. These outcomes are not one bug. The requested time may be outside the current live window, unavailable on the origin, adjusted to a decodable point, or deliberately corrected by the player.

This guide follows the timeline, playlist, requests, and player events in that order. Test only streams you own or are authorized to inspect. Remove signed URLs, cookies, tokens, viewer identifiers, and IP addresses before sharing evidence.

Verification method — September 22, 2026: We ran a Node.js 24.12.0 loopback fixture with two synthetic live-playlist snapshots. Both windows were 30 seconds long; EXT-X-MEDIA-SEQUENCE advanced from 100 to 103, so sequence 101 existed in the first snapshot but not the second. An EVENT fixture retained sequences 0–7. A VOD fixture ended with EXT-X-ENDLIST, and its controlled media endpoint returned 206 Partial Content, Content-Range: bytes 4-7/16, and the four requested bytes. This verifies only playlist-window arithmetic and the fixture's HTTP Range response. It does not run a browser media pipeline, decode media, inspect keyframes, reproduce a real player's snap-back, or test a phone, television, DRM system, or CDN. The fixture is stored at scripts/test-hls-seek-window-fixtures.cjs.

Define the symptom before changing the player

Record the requested time, the position actually reached, and the evidence between them. “Seeking is broken” hides several different checkpoints.

ObservationWhat it establishesWhat remains unknown
Scrubber moved to the requested pointThe interface accepted an inputThe media element or player accepted that time
seeking event firedThe media element began a seek operationThe requested media is available or decodable
Playlist reloaded after the actionThe player refreshed timeline informationThe target still belongs to the advertised window
Target segment returned 200/206Some bytes were deliveredThey contain the right timeline and a usable decode start
seeked event firedThe seek operation endedThe result exactly equals the requested time
Playback moved near the live edgeA correction or fallback occurredWhether the player, browser, or application caused it

Capture the player version, browser or native playback path, stream type, requested currentTime, currentTime after seeked, seekable ranges, buffered ranges, live latency, selected rendition, and the first request after the action. Do not rely on the scrubber label alone.

Read seekable before reading duration

For a media element, duration and seekable answer different questions. duration describes the media timeline as the browser currently understands it. seekable is a TimeRanges object describing positions currently available for seeking. A live timeline can start above zero, and expired content may no longer be obtainable.

Record all ranges rather than assuming there is one:

function snapshotMedia(video) {
  const ranges = (timeRanges) => Array.from(
    { length: timeRanges.length },
    (_, index) => ({ start: timeRanges.start(index), end: timeRanges.end(index) })
  );

  return {
    currentTime: video.currentTime,
    duration: video.duration,
    seekable: ranges(video.seekable),
    buffered: ranges(video.buffered),
    readyState: video.readyState,
  };
}

Save this snapshot before assigning currentTime, when seeking fires, and after seeked. MDN notes that setting currentTime requests a seek only when that media time is available, and that live content can expire from the buffer. It also notes that the resulting position can be adjusted to a time the media supports.

If a requested time is below the first seekable.start(0) or beyond the last seekable end, clamp the user interface to the current ranges or explain that the position has expired. Do not present an hour-long progress bar merely because the event began an hour ago if the current DVR window contains only the last five minutes.

Identify LIVE, EVENT, and VOD behavior

The playlist type determines what history can remain available, but the tag alone does not prove the server still has every object.

Playlist behaviorExpected mutationPractical seek boundary
Sliding live playlist, no ENDLISTOld segment URIs may leave as new ones arriveCurrent advertised window, subject to actual object availability
EXT-X-PLAYLIST-TYPE:EVENTNew segments may be appended; existing playlist content is not removedFrom retained event start through the current edge
EXT-X-PLAYLIST-TYPE:VOD with ENDLISTPlaylist is staticFull declared presentation, if every referenced object remains available
Ended playlist without the expected historyDepends on how it was producedOnly the segments still referenced and served

RFC 8216 requires a sliding live playlist that removes segments to advance EXT-X-MEDIA-SEQUENCE consistently. It also requires at least three target durations to remain when a non-ended playlist removes old entries. Apple documents EVENT playlists as append-only and useful when viewers need to seek back to the beginning of an event.

Our fixture's sequence 101 was a valid target in snapshot A and absent from snapshot B. That does not prove how a specific player reacts; it proves that a seek label derived from an earlier snapshot can point outside a later playlist window.

Compare consecutive playlist snapshots

Save the media playlist before the seek and again immediately after it. For each snapshot, record:

  1. Request time, final URL, cache status, Age, and cache-control headers.
  2. EXT-X-MEDIA-SEQUENCE, EXT-X-TARGETDURATION, and EXT-X-ENDLIST.
  3. First and last segment URI and the sum of EXTINF durations.
  4. EXT-X-PROGRAM-DATE-TIME where the presentation uses it.
  5. Discontinuity tags and EXT-X-DISCONTINUITY-SEQUENCE.
  6. Selected variant, alternate audio, subtitles, and whether their timelines cover the same target.

Do not compare two cached copies and call the window stable. Conversely, do not translate a sequence number directly into a wall-clock time. RFC 8216 says different renditions may have independent media sequence numbers; align them by relative playlist timeline, discontinuity information, or an unambiguous program-date mapping.

If old segments leave the playlist, the server still has retention obligations for in-progress clients under RFC 8216. An origin or CDN deleting them too early can interrupt playback even before a viewer attempts a new backward seek. The stale playlist versus missing segment guide explains how to distinguish an old manifest from a newly advertised object that returns 404.

Follow the first request after the seek

Preserve the Network log, perform one seek, and identify the first changed or failed request. Reloading the master playlist is less informative than the exact media playlist, initialization section, key, or segment selected after the action.

First evidence after the seekLikely investigation areaNext controlled check
No request and currentTime is clampedTarget outside seekable, application guard, or browser adjustmentLog requested value and ranges at assignment time
Playlist refreshes and starts at a later sequenceLive window advancedCompare both snapshots and recompute the UI range
Old segment returns 404/410Retention, origin cleanup, CDN policy, or expired URLCheck server retention and request time without exposing credentials
Segment returns 401/403Authorization or signed-child expiryCompare expiry and credential scope with a working segment
Byte Range returns the wrong status or bytesOrigin/CDN Range handling or object mutationReissue the exact authorized Range request and inspect headers
Bytes arrive, then demux/decode failsContainer, initialization, timestamps, encryption, or codecCapture player error details and inspect authorized media
Seek completes at a nearby timeDecode boundary or supported-position adjustmentCompare requested time, reached time, and keyframe layout

A successful status does not prove the correct object arrived. Confirm content type, byte count, final URL, and permitted response sample. A CDN challenge or HTML login can return 200. Follow our developer-tools request guide for a privacy-safe capture workflow.

Separate byte ranges from timeline ranges

Range: bytes=... is an HTTP request for part of an object. video.seekable is a media timeline. They are not interchangeable. An HLS presentation may use separate segment files, EXT-X-BYTERANGE, fMP4 initialization sections, or server behavior that causes different HTTP access patterns.

Our VOD fixture confirmed a coherent 206 response for one synthetic byte request. For a real presentation, verify the actual request's Content-Range, total object length, returned byte count, content type, cache behavior, and object identity. A server that ignores Range may still work for some client paths, while an inconsistent partial response can break others; diagnose the observed path instead of requiring 206 universally.

If URLs are signed, confirm that a seek does not request an older object after its signature has expired. Never copy a live signed URL into a public report.

Account for keyframes and discontinuities

Video decoding cannot necessarily begin from every arbitrary frame. A player may start at or near a decodable random-access point, so an exact UI request can resolve to a nearby media time. Large or irregular keyframe spacing can make the adjustment visible. Inspect the actual encoded media with approved tools; the playlist's segment duration alone does not reveal every decode boundary.

Discontinuities add another timeline boundary. Compare discontinuity tags across video and alternate renditions, initialization changes, media timestamps, keys, and the target's position relative to an ad break or encoder restart. RFC 8216 uses EXT-X-DISCONTINUITY-SEQUENCE to help synchronize renditions when earlier discontinuities leave a live window.

Test just before and after each known discontinuity. If video seeks but alternate audio becomes silent, use the alternate-audio diagnosis rather than treating it as a generic scrubber issue.

Check player live-edge correction explicitly

Some players intentionally move playback forward when latency becomes too large. The hls.js API documents maxLatency as the distance from the live edge beyond which playback seeks forward to liveSyncPosition. That correction can look like an unwanted jump if the application exposes a target outside its supported latency policy.

Record hls.liveSyncPosition, estimated latency, configured live sync and maximum latency values, and the event sequence around the jump. Compare behavior with documented defaults and your explicit configuration. Do not disable correction blindly: first decide whether the product promises a DVR seek or low-latency live viewing, because those goals require different windows and controls.

Also check application code for its own “Go Live” logic, periodic currentTime assignments, state synchronization, or a stale React/UI value that overwrites the user's seek. A player-level correction and an application-level assignment need different fixes.

Test a small, reproducible matrix

Run the same authorized presentation through each officially supported path:

  • Native HLS and JavaScript/MSE where applicable.
  • VOD, sliding live, and EVENT/DVR fixtures with known retention.
  • A target inside the current range, exactly at its beginning, and just outside it.
  • A seek near a keyframe boundary and near a declared discontinuity.
  • Low and high variants, alternate audio, and subtitles.
  • Desktop and physical mobile or television devices you actually support.
  • Normal network, delayed playlist refresh, and a controlled expired-segment case.

A narrow desktop viewport is not a phone media pipeline. Label untested devices as untested. If the stream buffers generally rather than only after a seek, use the broader HLS buffering diagnosis.

Write the report around one requested time

An actionable report might say: “At 09:14:22, the viewer requested 128.4 seconds. seekable was 141.0–171.0. The next playlist began at media sequence 103; the earlier snapshot contained sequence 101, but the new one did not. The application then set playback to the current live sync point.” That separates UI, availability, and correction.

Include the requested and reached times, all seekable ranges, playlist snapshots, first post-seek request, selected rendition, sequence and discontinuity information, keyframe evidence if inspected, player configuration, browser/player versions, and sanitized request IDs. Do not claim a keyframe or CDN cause without the corresponding evidence.

Primary references

Start with the range the player can seek now, not the range the interface remembers. Then compare playlist snapshots, follow the first post-seek request, and distinguish decode alignment from deliberate live-edge correction. The first boundary that rejects or changes the target identifies the next owner.