Timing

Timestamp overflow in long-running streams: why playback jumps after a day

A channel that has been streaming without interruption for a day develops a fault no test caught: the progress bar shows nonsense, playback leaps to a random position, or the player freezes. The timestamps ran out of room.

What it looks like

Playback that has been running for hours suddenly jumps to an unrelated position, loops over the same few seconds, freezes, or reports a current time that is wildly wrong. It appears on long-running live channels and 24-hour events, never on short on-demand assets, and it is reproducible only by leaving a stream running far longer than any ordinary test.

The arithmetic

Presentation timestamps in MPEG transport streams are a 33-bit field on a 90 kHz clock, which wraps around to zero after roughly 26.5 hours. That wraparound is expected and well defined, and correct players handle it. The trouble starts when a timestamp is stored or calculated in a 32-bit integer somewhere along the chain: a signed 32-bit value on the same clock overflows after about 6.6 hours, an unsigned one after about 13.3 hours, and at that point arithmetic on the position produces garbage.

Fragmented MP4 has a version of the same trap. The decode time field in each fragment can be written as 32 or 64 bits depending on the box version, and a packager that uses the 32-bit form with a 90 kHz timescale will overflow in the same thirteen hours. Players written in JavaScript are mostly safe because numbers there are 64-bit doubles, but native television pipelines, set-top box firmware and some embedded players are not.

  • 33-bit MPEG-TS PTS: wraps at about 26.5 hours by design, must be handled explicitly.
  • Signed 32-bit timestamp arithmetic at 90 kHz: overflows after about 6.6 hours.
  • Unsigned 32-bit fields, including version 0 fragment decode times: about 13.3 hours.
  • Long-running live encoders that never reset their timeline between programmes.

What the packager and player should do

Use 64-bit fields and 64-bit or floating-point arithmetic for every timestamp calculation, on every side. Packagers should write the 64-bit form of the fragment decode time, and encoders for continuous channels should reset the timeline at natural discontinuities - a programme boundary, an ad break - and signal it, so a stream never accumulates a day of timestamps in the first place.

Players should detect wraparound rather than assume monotonic time: a large backwards jump in PTS on a live stream is a wrap, not a seek, and the correction is to keep an offset that makes the timeline continuous. Normalising timestamps relative to the stream start rather than working in absolute values keeps the numbers small on the player side regardless of what the encoder does.

Test for it deliberately

Nobody finds this bug in a ten-minute test run, so the only way to find it before viewers do is to run the stream for longer than the thresholds on the actual target devices - a soak test past the thirteen-hour and twenty-seven-hour marks, with the current position logged throughout. Alternatively, start the encoder timeline just short of the wrap point, so the test crosses it in minutes rather than a day.