MPEG-TS over MSE: TrackBuffer reports 2–3 frame gaps that are not in the source stream

:warning: Before you continue


Before submitting a bug report, please review our troubleshooting documentation at Redirect | /docs/vega/0.24/troubleshoot-overview.html

If you still want to file a bug report, please make sure to fill in all the details below and provide the necessary information.

NOTE: PLEASE ONLY REPORT A SINGLE BUG USING THIS TEMPLATE.

If you’re experiencing multiple issues, please file a separate report for each.


:backhand_index_pointing_right: Bug Description


1. Summary

App Name: M327 PRO

App Link on Amazon Appstore: N/A — currently under development/testing on Vega Virtual Device.

Bug Severity:

  • Impacts operation of app

  • Blocks current development

  • Improvement suggestion

  • Issue with documentation

  • Other

We are seeing repeatable video micro-stutters when playing HLS content through MSE using MPEG-TS SourceBuffers on Amazon Vega.

The source MPEG-TS has continuous video frame timing, but Vega introduces repeated video TrackBuffer gaps equivalent to approximately 2–3 video frames.

During the visible stutters, the Vega media pipeline reports:

No Time update in Playing

At the same time:

media.currentTime stops advancing
totalVideoFrames continues increasing
droppedVideoFrames increases

This indicates that video frames continue to be produced while the media clock temporarily stops progressing.


2. Steps to Reproduce

  1. Create and initialize a VideoPlayer using @amazon-devices/react-native-w3cmedia.

  2. Attach it to a KeplerVideoSurfaceView using the surface handle.

  3. Create a MediaSource.

  4. Add a MPEG-TS SourceBuffer containing H.264 High@L3.1 video and AAC-LC audio:

const sourceBuffer = mediaSource.addSourceBuffer(
  'video/mp2t; codecs="avc1.64001F, mp4a.40.2"',
);

  1. Use:
sourceBuffer.mode = 'segments';

  1. Append MPEG-TS segments from an HLS stream using:
sourceBuffer.appendBuffer(tsSegment);

  1. Start playback and allow it to run continuously.

  2. Observe recurring visible micro-stutters.

  3. Monitor the Vega device logs and media playback counters.

  4. Repeat the same test using:

sourceBuffer.mode = 'sequence';

The problem remains reproducible at essentially the same rate.


3. Observed Behavior

Playback starts normally and audio/video are generally synchronized, but the video repeatedly exhibits short visible stutters.

During those events Vega reports:

No Time update in Playing

Our device-side measurements show:

media.currentTime:
temporarily stops advancing

totalVideoFrames:
continues increasing

droppedVideoFrames:
increases

We also compared the timing of the original MPEG-TS video frames with the timing observed in the Vega media pipeline.

Source stream:

435 / 435 measured frame intervals were consistent
Video rate: 29.97 fps
Expected frame interval: ~33.37 ms

On the Vega device, during the same type of playback:

543 video TrackBuffer gaps were observed.

The gaps correspond to exact multiples of approximately
2–3 video frames.

The equivalent discontinuities were not present in the MPEG-TS source timing.

We also performed a controlled SourceBuffer.mode comparison:

segments:
143.7 gap events / minute

sequence:
144.9 gap events / minute

Therefore, switching to sequence mode did not materially change the issue.


4. Expected Behavior

MPEG-TS media with continuous timestamps should be appended and presented without introducing additional periodic TrackBuffer gaps.

When sufficient media is already buffered and the incoming MPEG-TS timestamps are continuous, we expect:

media.currentTime to progress continuously
video frames to be presented continuously
droppedVideoFrames not to increase periodically because of media-clock stalls

Using the Amazon-recommended sequence mode for MPEG-2 TS/HLS content should also not exhibit the same recurrent TrackBuffer discontinuities if SourceBuffer timestamp handling is contributing to the issue.


4.a Possible Root Cause & Temporary Workaround

We have not identified the exact internal root cause.

The evidence currently points to timing/TrackBuffer handling within the Vega native MPEG-TS/MSE playback pipeline.

We have tested and ruled out several application-level candidates.

Network starvation

Not observed.

The issue occurs while approximately 20 seconds of playable media remain buffered ahead.

Buffer size

Not causal.

The same micro-stutters occur both with a stable ~20-second buffer and with substantially larger buffered ranges.

SourceBuffer append/remove contention

No temporal correlation was found between append/trim operations and the visible stalls.

Video surface lifecycle

The KeplerVideoSurfaceView remains mounted and attached during the stalls.

The surface is not recreated or detached.

Decoder stopping

Not observed.

totalVideoFrames continues increasing during media-clock stalls.

Dropped frames

droppedVideoFrames increases while media.currentTime is stalled.

Channel-switch teardown warnings

Messages such as:

Invalid Sink Type
flush failed
abortPendingReconfiguration

occur during channel teardown/switching, but not during the recurring micro-stutters.

SourceBuffer mode

Both modes were tested:

segments: 143.7 gaps/min
sequence: 144.9 gaps/min

No meaningful improvement was observed.

At this time we do not have a reliable application-side workaround.


5. Logs or crash report

There is no application crash.

Relevant device logs can be provided as a sanitized attachment.

The most relevant native playback message observed during the issue is:

No Time update in Playing

Our diagnostic instrumentation additionally records:

media.currentTime
totalVideoFrames
droppedVideoFrames
corruptedVideoFrames
bufferAhead
SourceBuffer updating state
append activity
trim activity
network request activity
surface generation / attachment state

We can provide sanitized logs containing these signals if required.

No authentication credentials, tokens, media URLs, or provider hostnames will be included.


6. Environment

  • SDK Version: [INSERT OUTPUT OF vega --version]

  • React Native Version: [INSERT EXACT VERSION]

  • App State: Foreground

  • Device: Vega Virtual Device — TV x86_64

  • @amazon-devices/react-native-w3cmedia: [INSERT EXACT VERSION]

  • @amazon-devices/kepler-video-view: [INSERT EXACT VERSION]

OS Information:

Please insert the exact output of:

vega exec vda shell
cat /etc/os-release

[INSERT /etc/os-release OUTPUT HERE]


7. Example Code Snippet / Screenshots / Screengrabs

Minimal playback configuration:

const mediaSource = new MediaSource();

videoPlayer.src = URL.createObjectURL(mediaSource);

mediaSource.addEventListener('sourceopen', () => {
  const sourceBuffer = mediaSource.addSourceBuffer(
    'video/mp2t; codecs="avc1.64001F, mp4a.40.2"',
  );

  sourceBuffer.mode = 'segments';

  // MPEG-TS segments from the HLS playlist are fetched sequentially
  // and appended through the SourceBuffer.
  sourceBuffer.appendBuffer(tsSegment);
});

We also reproduced the same issue after changing only:

sourceBuffer.mode = 'sequence';

No buffer-size, network, append-queue, surface, or playback-timing changes were made during the A/B comparison.


:backhand_index_pointing_right: Playback Issues


This is a Live HLS playback issue using MSE.

Pipeline:

HLS playlist
→ MPEG-TS media segments
→ MediaSource
→ video/mp2t SourceBuffer
→ Vega native MPEG-TS parser
→ VideoPlayer
→ KeplerVideoSurfaceView

The stream plays successfully, but recurrent video micro-stutters occur.

The source itself was inspected separately and showed continuous video frame intervals:

435 / 435 measured intervals consistent
29.97 fps
~33.37 ms/frame

The Vega playback pipeline subsequently reports hundreds of short TrackBuffer gaps corresponding to approximately 2–3 frames.

  • Player SDK: @amazon-devices/react-native-w3cmedia / custom MSE player

  • Player SDK Version: [INSERT EXACT VERSION]

  • Audio Codecs: AAC-LC (mp4a.40.2)

  • Video Codecs: H.264 High@L3.1 (avc1.64001F)

  • Manifest Types: HLS / M3U8

  • Media segments: MPEG-TS

  • Architecture: x86_64 Vega Virtual Device

Q: If applicable, please provide your media/content URL

The production media URL is authenticated/tokenized and cannot be posted publicly.

We can provide a controlled reproduction stream or additional sanitized
stream characteristics privately if required.

Q: Are there any special headers required to reproduce the issue you are facing?

No special application-defined request headers are required beyond the
authentication mechanism used by the media provider.

Additionally please provide the following if possible

The key comparison from our device measurements is:

SOURCE
435 / 435 video frame intervals consistent

VEGA — mode=segments
143.7 TrackBuffer gap events/min

VEGA — mode=sequence
144.9 TrackBuffer gap events/min

During a gap:
media.currentTime → stops temporarily
totalVideoFrames → continues increasing
droppedVideoFrames → increases

We can provide sanitized logs and additional device captures if requested.


:backhand_index_pointing_right: Additional Context


Any Additional Context you would like to provide?

We performed several controlled experiments before filing this report because we wanted to distinguish an application buffering issue from a native playback issue.

The following conditions have already been tested:

Healthy buffered media remains available during stalls.
Network fetches are not starved.
SourceBuffer append/trim operations do not correlate with the stalls.
The video surface remains attached.
The decoder continues producing video frames.
Dropped frames increase when media.currentTime stalls.
The incoming MPEG-TS frame timing is continuous.
Both SourceBuffer "segments" and "sequence" modes reproduce the issue.

We understand that sequence mode is recommended for HLS/MPEG-2 TS playback on Vega, which is why we specifically performed the A/B comparison.

We would appreciate guidance on the following:

  1. Is recurrent No Time update in Playing with MPEG-TS over MSE a known Vega issue?

  2. Are there Vega-specific requirements for MPEG-TS PTS/DTS continuity or TrackBuffer timestamp handling beyond the standard MSE requirements?

  3. Is there additional native media-pipeline logging or instrumentation we can enable to determine exactly where the 2–3-frame TrackBuffer gaps are introduced?

We are happy to provide sanitized device logs and further diagnostics if needed.

Hi @diego_duran

Welcome to the community!!

Your A/B on segments vs. sequence, the source-vs-device frame-interval comparison, and the fact that you’ve already ruled out network starvation, buffer size, append/trim contention, surface lifecycle, and decoder-stop mean this is clearly not an app-level buffering issue and needs investigation at the native media-pipeline level. We’re going to route it there.

To get it in front of the internal teams effectively, could you provide the following:

  1. Environment details (left as placeholders in your report). These are important because you’re on the Vega Virtual Device (x86_64), whose media pipeline and timing can differ from physical Fire TV hardware:
  • vega --version
  • @amazon-devices/react-native-w3cmedia version
  • @amazon-devices/kepler-video-view version
  • React Native version
  • /etc/os-release output
  1. Reproduce on a physical Vega Fire TV device, not just the Virtual Device. VVD timing artifacts don’t always reflect on-device behavior, and confirming whether this reproduces on hardware materially affects triage and priority.
  2. Native pipeline logs. Please capture loggingctl log across a reproduction window so we get the native media traces to line up against your JS-side counters (currentTime / totalVideoFrames / droppedVideoFrames / bufferAhead). Sanitized is fine - please include timestamps for a few of the gap events so the native traces can be correlated.
  3. Your pipeline setup. To make sure the team has full context: are you driving playback through a player library (Shaka / hls.js) adapted per the Porting MSE Players to Vega (Porting Media Source Extension (MSE) Players to Vega - Amazon Vega Docs) guidance, or a fully custom MSE implementation? If custom, please note which of the documented Vega MSE adaptations you’ve applied (Fetch instead of XHR, no ReadableStream, appendBuffer accepting Uint8Array, no web workers, polyfills).
  4. A controlled reproduction stream. Your offer to share a sanitized/controlled repro stream privately would help significantly - a stream the team can reproduce against is often the difference between a fast root-cause and a slow one.

On your three direct questions, I’m taking these to the media-pipeline team along with your report and will follow up on this thread. On the third: loggingctl log is the primary native-pipeline log source, so please include that capture (above) and we’ll confirm whether deeper instrumentation is available.

Thanks again for the exceptional detail - it will help us make the investigation much faster.

Warm Regards,
Ivy

Hi Ivy,

Thank you — that’s exactly the routing we hoped for. All five points are answered below, including the physical-hardware reproduction you asked for. There is one thing I need from you in §3.

1. Environment

vega --version
  Active SDK Version: 0.23.8358
  Vega CLI Version:   1.3.2

@amazon-devices/react-native-w3cmedia   2.1.99
@amazon-devices/react-native-kepler     2.0.9000000000+rn0.72.0
@amazon-devices/kepler-media-controls   1.0.21
@amazon-devices/keplermediadescriptor   1.1.15
@amazon-devices/kepler-media-types      1.0.0
react-native                            0.72.0
hls.js                                  1.6.16  (experiment only, not shipped)

@amazon-devices/kepler-video-view       NOT INSTALLED

On kepler-video-view: we don’t use that package. The video surface is KeplerVideoSurfaceView, imported from @amazon-devices/react-native-w3cmedia, with VideoPlayer from the same package’s headless entry point. If kepler-video-view is the expected integration for this use case, that’s useful to know.

/etc/os-release (Vega Virtual Device) — the physical device’s build is in §2:

OS_VERSION="1.1"
BRANCH_CODE="TV Ship"
BUILD_DESC="OS 1.1 (TV Ship/101251090)"
BUILD_FINGERPRINT="1.0.125109.0(c449c30bb1fb0cc8)/101251090N:user/dev-keys"
BUILD_VARIANT="user"
BUILD_TAGS="dev-keys"
BUILD_DATE="Thu May 28 17:36:14 UTC 2026"

4. Pipeline setup

Fully custom MSE implementation. No Shaka, and hls.js only as a short-lived experiment that never shipped (details in §5).

Adaptations applied, against the Porting MSE Players guidance:

Adaptation Status
fetch instead of XHR Yes — fetch only, no direct XMLHttpRequest
No ReadableStream Confirmed — response.body is unavailable in this runtime, so playback is built entirely on whole-response reads
appendBuffer receives Uint8Array Yes — every append is new Uint8Array(buffer)
No web workers Yes — none anywhere
MSE globals installMediaSourceGlobal() before any MediaSource use

Live playback shape:

mediaSource.duration = Infinity;
const sb = mediaSource.addSourceBuffer(
  'video/mp2t; codecs="avc1.64001F, mp4a.40.2"'
);
sb.mode = 'segments';           // also tested 'sequence' — same gap rate
// one ~6 s TS segment at a time, each append awaiting `updateend`
// buffer trimming issued by the same queue, never concurrent with an append
player.currentTime = player.buffered.start(0) + 0.1;
player.play();

Appends and removals are serialised through a single queue, so appendBuffer and remove never overlap.

5. A second, possibly simpler repro — URL Mode rejects a flat .ts outright

While preparing this I ran a separate check that may be easier for the team to reproduce than the frame-gap issue, because it needs no account and no live stream — a public, finite MPEG-TS file:

HTTP 200 · video/mp2t · Content-Length: 6987924468 · Accept-Ranges: bytes
~651 s · H.264 Constrained Baseline L3.1 1278x544 23.976 fps · AAC-LC

No MSE at all — URL Mode only, one request, no HEAD or preflight:

await player.initialize();
player.setSurfaceHandle(handle);   // succeeds
player.src = FLAT_TS_URL;          // <- fails here
player.play();

Native log:

W3CMEDIA:[MSE_EME] MPBBackend: >>> set_src_uri
W3CMEDIA:[MSE_EME] set_src_uri: MPB Call failed with code: 50004.
W3CMEDIA:[MSE_EME] MPBBackend: <<< set_src_uri
W3CMEDIA:[MSE_EME] MediaPlayerPipelineImpl::prepareForPlayback():
                   set_src_uri(mUri) failed apiStatus=50004
W3CMEDIA:makeMediaError: code = 4

It fails at set_src_uri, before any HTTP request is issued — no bytes are fetched. Never reaches loadedmetadata.

The asymmetry seems worth noting: the same container plays when appended through MSE as video/mp2t, and is rejected when handed to src as a URI.

Is .ts over URL Mode expected to work on Vega, or is MSE the only supported path for MPEG-TS? Knowing that would settle a separate question for us. Happy to share the source URL and the full lifecycle trace directly if useful.

3. loggingctl capture — and a rate limit I can’t get past

Thank you for pointing at this; it was the piece we were missing. On the virtual device it works exactly as you describe, and the TrackBuffer__internal_consistency_check gap lines are dense enough to correlate: roughly 145 gap events per minute of playback.

On physical hardware the same capture comes back almost empty. A 5-minute reproduction window yields 75–84 lines total, with 11–12 surviving max frame duration lines — against ~700 for the equivalent window on the virtual device. The journal is dropping the rest.

What I tried:

loggingctl log -f -v com.m327pro.vega -o short_precise     # 75-84 lines / 5 min
loggingctl config --set-rate com.m327pro.vega 60000        # accepted, no effect

RateLimitBurst appears to be journal-global (300), and the per-process rate override above is accepted but does not lift it for this process.

I deliberately have not compared those 11–12 surviving lines against the virtual device’s ~700 — with that much loss the hardware number would be a measure of the rate limiter, not of the defect.

Is there a supported way to raise or disable the journal rate limit on a physical device for a debugging session? With that, I can post the same timestamp-correlated capture from hardware that I have from the virtual device.

2. Physical hardware — reproduced

The defect reproduces on physical hardware. I have a Fire TV stick running Vega, and repeated the measurement there:

Fire TV (physical)          Vega Virtual Device
OS 1.2 (TV Ship/44)         OS 1.1 (TV Ship/101251090)
release-keys                dev-keys
armv7l                      x86_64

Same app build, same channel class, same 5-minute single-channel session, no zapping. The visible symptom is identical on both: video stutters roughly twice a second while audio stays perfectly smooth.

JS-side counters, 5 minutes on hardware:

Counter Virtual device Fire TV (physical)
droppedVideoFrames 0 0
corruptedVideoFrames 0 0
Intervals with no frame production 0 1 (254 ms)
Media-clock stalls 0 1
Buffer ahead of playhead 5.3–37.1 s 5.3 s minimum, never starved

One caveat on how to read that table, so it isn’t taken for more than it is: those counters are sampled from JS every 250 ms, so they cannot resolve the 67–100 ms gaps the native log reports. They bound the problem rather than measure it. What they establish is that across a 5-minute window in which the stutter is visible perhaps 600 times, the decoder dropped zero frames, produced frames continuously except for a single 254 ms interval, and never ran out of buffered data.

So on both platforms the picture is the same: frames are decoded, none are dropped, the media clock advances, the buffer stays healthy, and the picture still stutters. That is what led us to look at presentation rather than at anything the app controls.

Thanks again,
Diego


Correcting something in my §3 above before it wastes anyone’s time.

I was wrong about the rate limit. I reported that loggingctl config --set-rate was accepted but did not lift the journal limit for our process. It does lift it. Checking the device state directly:

$ loggingctl config --get-rate com.m327pro.vega
{
 "message": "com.m327pro.vega:rate -> 60000",
 "note": [
  "Journal RateLimitBurst value: 60000",
  "DEFAULT:rate -> 300"
 ],
 "status": "success"
}

And a subsequent capture on the same physical device returned 6445 lines where earlier ones returned 75–84. My original measurement was taken before the setting took effect, and I did not re-measure before writing to you. Apologies for the noise — please disregard the request to raise the limit; there is no platform issue there.

With working capture, the hardware numbers

Now that the capture works on hardware, here is a clean 38-second window of live playback on the Fire TV:

 99 x  gap(33000) > max frame duration(20750)
 39 x  gap(34000) > max frame duration(20750)
 13 x  gap(33000) > max frame duration(17000)
  7 x  gap(34000) > max frame duration(17000)
------
158 gap events in 37.9 s  =  ~250/min

Two things stand out against the virtual-device numbers I sent earlier:

The reported gaps are now exactly one frame, not two or three. 33000 and 34000 µs is precisely the 29.97 fps frame interval of the source — the same alternating 33/34 ms cadence I measured in the transport stream itself, with zero irregular intervals across 540 frames.

And max frame duration is reported as 20750 µs — about 48 fps — for content whose real frame interval is 33–34 ms. So the threshold sits below the stream’s normal cadence, and ordinary frame timing trips the consistency check on its own. On the virtual device the same field read 34000 µs, which is consistent with the content, and there the flagged gaps were 2–3 frames.

That seems to sharpen my earlier question 2: how is max frame duration derived? If it can settle at 20750 µs for a 29.97 fps stream, the check would fire on correctly-timed content, which would explain why we see gap events at a rate no dropped-frame counter agrees with (droppedVideoFrames remains 0 throughout).

One caveat I want to state plainly rather than have you find it: this capture is a different channel from the virtual-device measurements, so some of the difference may be content, not platform. I can capture the same stream on both if that comparison would be useful.

Thanks again!

Hi @diego_duran

  • Rate limit: noted, no issue there.
  • max frame duration = 20750 µs (~48 fps) on a 29.97 fps stream: agreed this looks like the threshold sitting below the real cadence, which would trip the check on correctly-timed frames (and droppedVideoFrames staying 0 supports that). We’re taking this question to engineering and will follow up here.
  • Ask: please capture the same stream on both VVD and hardware so we can separate content from platform. Attach both, with the gap / max frame duration lines and droppedVideoFrames.

Warm Regards,
Ivy

Hi @diego_duran,

Here’s the update on your questions - the short version: the gap warnings are diagnostic false positives, not the stutter cause.

1. No Time update in Playing - generic clock-health warning (checks every 250 ms), not TS-specific. Fired once in 5 min on your hardware → not the twice-a-second stutter.

2. “max frame duration = 20750 µs” - confirmed false positive. TS frames carry no duration, so the parser invents one from the DTS gap to the next frame; “max frame duration” is the largest invented value, not the frame rate. Those came out <21 ms vs. real ~33 ms frames, so the threshold sits below one frame and normal spacing trips it. The gap is a bookkeeping mismatch, the check is purely diagnostic (changes nothing in playback), and it re-logs every prior gap on each append - so “~145–250/min” counts log lines, not events. droppedVideoFrames = 0 confirms no real gap.

3. Vega PTS/DTS requirements (worth checking your stream against):

  • First picture in each PES packet must carry a PTS (leading pics without one are dropped/replaced by the first keyframe).
  • DTS must be monotonic; backward or >2-frame-period jumps → treated as discontinuity, drops to next keyframe.
  • Each appendBuffer should end on complete pictures (parser flushes per append).
  • This is why segments vs. sequence made no difference.

Bonus - flat .ts URL-mode 50004: .ts over URL mode is supported. 50004 before any request = either plain http:// blocked by cleartext manifest policy, or pipeline not ready within ~2 s. Try https/add cleartext; if it persists, send the set_src_uri log lines.

The actual stutter is still open - zero dropped frames + continuous decode + healthy buffer → points to presentation, not the app or TS parser. To confirm, please send:

  1. Same stream on VVD + hardware, one comparable window.
  2. Full system journal in those captures (not just app log).
  3. One raw TS segment or ffprobe frame dump (to check interlaced/DTS).
  4. With the raised rate limit, grep for: FrameProcessor_discontinuity_found,

FrameProcessor_drop_frame, gap too big, dts_not_monotonic, pts_outside_gop, plus the config line (shows if video is interlaced).

Thanks again for the rigor - the frame-gap is now ruled out; the remaining stutter is a presentation-side question.
Hope this helps!

Warm regards,
Ivy