Skip to content

[Bug]: Resizing a Preview viewport during recording corrupts the exported MP4 #10494

Description

@MansTomb

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/desktop, with the recorder configuration in apps/web.

Steps to reproduce

  1. Serve the attached deterministic fixture locally. It contains readable text, a Start button, an eight-second requestAnimationFrame animation, elapsed time, and a Complete state.
  2. Open it in a new Preview task tab with preview_open.
  3. Set the viewport to 1280×800 CSS pixels with preview_resize.
  4. Start recording with preview_recording_start, then click Start.
  5. At approximately three seconds, resize the same tab to the iphone-12-pro preset, 390×844.
  6. Wait for Complete, then stop with preview_recording_stop.
  7. Decode the exact exported file using the commands below. Repeat the resize case. Compare with separate fixed desktop and fixed mobile recordings.
ffprobe -v error -show_entries stream=width,height,avg_frame_rate:format=duration,size -of json INPUT.mp4
ffmpeg -v error -xerror -i INPUT.mp4 -fps_mode passthrough -enc_time_base 1:1000000 -f null -

Expected behavior

The whole recording remains decodable and preserves the desktop and mobile layouts across the resize.

Actual behavior

Both repeated resize exports fail H.264 decoding. Separate fixed desktop and fixed mobile exports decode successfully. The source changes from 1600×1000 to 488×1054 physical pixels at DPR 1.25, but the avc1 recording does not convey the new decoder parameters.

Reproduced in the installed nightly, then independently against upstream source with an isolated Electron profile. The source verification drives the real recorder, desktop bridge, Preview manager and native webview capture through a temporary test-mounted webview.

Impact

Major degradation or frequent failure. Resizing during recording produces an unusable export.

Version or commit

Installed version 0.0.39-nightly.20260907.1332.

Upstream pingdotgg/t3code at c0d4e95c0d921b5960ad04c7f8abec459ee3c59e.

Environment

Omarchy Quattro, Arch Linux, /etc/os-release reports Omarchy 4.0.2, kernel 7.1.9-arch1-2, Hyprland. Electron 43.4.1, Chromium 150.0.7871.224, embedded Node 24.18.1. Display scaling 1.25, page DPR 1.25, CSS zoom 1. Local verification uses a separate desktop and temporary app data.

Logs or stack traces

The attached decoder log contains H.264 macroblock errors after resizing. Source packet DTS values remain strictly increasing. The explicit FFmpeg output time base above avoids unrelated duplicate-DTS warnings caused by output rounding.

Cause and proposed fix

browserRecording.ts prefers video/mp4;codecs=avc1. Chromium initializes the MP4 track's codec description once. After a resolution change, avc1 does not carry updated SPS/PPS in the video samples. Chromium explicitly recommends avc3 for this case in MediaRecorder's encoding path.

Prefer supported avc3 profiles, retaining the existing WebM fallback and bitrate calculation. This keeps native frame dimensions and carries parameter changes in-band. PR #10495 includes codec-selection coverage and an Electron media regression that decodes the exported file through desktop → mobile → desktop transitions.

Screenshots, recordings, or supporting files

Before resize recording: Original failing MP4

before-resize.mp4

After resize recording: Original fixed MP4

after-resize.mp4
Decoder output
[h264 @ 0x56349cf52e80] top block unavailable for requested intra mode
[h264 @ 0x56349cf52e80] error while decoding MB 31 0
[h264 @ 0x56349cf52e80] top block unavailable for requested intra mode
[h264 @ 0x56349cf52e80] error while decoding MB 23 10
[h264 @ 0x56349ce53880] top block unavailable for requested intra mode
[h264 @ 0x56349ce53880] error while decoding MB 31 0
[h264 @ 0x56349ce53880] top block unavailable for requested intra mode -1
[h264 @ 0x56349ce53880] error while decoding MB 62 9
[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] corrupt decoded frame
[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Error processing packet in decoder: Invalid data found when processing input
[h264 @ 0x56349cf72d40] negative number of zero coeffs at 92 5
[h264 @ 0x56349cf72d40] error while decoding MB 92 5
[h264 @ 0x56349cf92c00] corrupted macroblock 94 5 (total_coeff=-1)
[h264 @ 0x56349cf92c00] error while decoding MB 94 5
[h264 @ 0x56349cf92c00] top block unavailable for requested intra mode
[h264 @ 0x56349cf92c00] error while decoding MB 47 8
[h264 @ 0x56349cd7be40] dquant out of range (-216) at 0 6
[h264 @ 0x56349cd7be40] error while decoding MB 0 6
[h264 @ 0x56349ce03180] mb_type 3583 in P slice too large at 20 6
[h264 @ 0x56349ce03180] error while decoding MB 20 6
[h264 @ 0x56349ce03180] top block unavailable for requested intra mode
[h264 @ 0x56349ce03180] error while decoding MB 78 8
[h264 @ 0x56349cd9b000] negative number of zero coeffs at 92 5
[h264 @ 0x56349cd9b000] error while decoding MB 92 5
[h264 @ 0x56349cdbae00] negative number of zero coeffs at 93 5
[h264 @ 0x56349cdbae00] error while decoding MB 93 5
[h264 @ 0x56349ce339c0] negative number of zero coeffs at 98 5
[h264 @ 0x56349ce339c0] error while decoding MB 98 5
[h264 @ 0x56349ce73740] mb_type 85 in P slice too large at 46 7
[h264 @ 0x56349ce73740] error while decoding MB 46 7
[h264 @ 0x56349ce93600] negative number of zero coeffs at 99 5
[h264 @ 0x56349ce93600] error while decoding MB 99 5
[h264 @ 0x56349ceb34c0] mb_type 284 in P slice too large at 6 6
[h264 @ 0x56349ceb34c0] error while decoding MB 6 6
[h264 @ 0x56349ced3380] cbp too large (363) at 96 5
[h264 @ 0x56349ced3380] error while decoding MB 96 5
[h264 @ 0x56349cef3240] cbp too large (127) at 54 7
[h264 @ 0x56349cef3240] error while decoding MB 54 7
[h264 @ 0x56349cef3240] top block unavailable for requested intra mode
[h264 @ 0x56349cef3240] error while decoding MB 48 8
[h264 @ 0x56349cf13100] cbp too large (272) at 9 6
[h264 @ 0x56349cf13100] error while decoding MB 9 6
[h264 @ 0x56349cf13100] top block unavailable for requested intra mode
[h264 @ 0x56349cf13100] error while decoding MB 10 9
[h264 @ 0x56349cf32fc0] out of range intra chroma pred mode
[h264 @ 0x56349cf32fc0] error while decoding MB 1 6
[h264 @ 0x56349cf32fc0] top block unavailable for requested intra mode
[h264 @ 0x56349cf32fc0] error while decoding MB 10 9
[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Task finished with error code: -1094995529 (Invalid data found when processing input)
[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Terminating thread with return code -1094995529 (Invalid data found when processing input)
Validation results
[
  {
    "file": "app-before/desktop.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc1",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "364500/12271"
      }
    ],
    "format": {
      "duration": "8.181533",
      "size": "168002"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.148233",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-before/mobile.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc1",
        "width": 488,
        "height": 1054,
        "avg_frame_rate": "3540000/122641"
      }
    ],
    "format": {
      "duration": "8.180400",
      "size": "141727"
    },
    "phases": [
      {
        "shape": [
          488,
          1054
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.147100",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-before/resize-1.mp4",
    "decode_exit": 183,
    "streams": [
      {
        "codec_tag_string": "avc1",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "2430000/82453"
      }
    ],
    "format": {
      "duration": "8.252533",
      "size": "188061"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.219233",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-before/resize-2.mp4",
    "decode_exit": 183,
    "streams": [
      {
        "codec_tag_string": "avc1",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "303750/10291"
      }
    ],
    "format": {
      "duration": "8.237567",
      "size": "171847"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.204267",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-after/desktop.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc3",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "455625/15328"
      }
    ],
    "format": {
      "duration": "8.191767",
      "size": "169361"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.158467",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-after/mobile.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc3",
        "width": 488,
        "height": 1054,
        "avg_frame_rate": "7080000/247553"
      }
    ],
    "format": {
      "duration": "8.238100",
      "size": "143144"
    },
    "phases": [
      {
        "shape": [
          488,
          1054
        ],
        "at": "0.000000"
      }
    ],
    "last_frame": "8.204800",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-after/resize-1.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc3",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "2420000/81641"
      }
    ],
    "format": {
      "duration": "8.169600",
      "size": "188283"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      },
      {
        "shape": [
          488,
          1054
        ],
        "at": "3.190133"
      }
    ],
    "last_frame": "8.136300",
    "packet_dts_monotonic": true
  },
  {
    "file": "app-after/resize-2.mp4",
    "decode_exit": 0,
    "streams": [
      {
        "codec_tag_string": "avc3",
        "width": 1600,
        "height": 1000,
        "avg_frame_rate": "7290000/245867"
      }
    ],
    "format": {
      "duration": "8.183600",
      "size": "186695"
    },
    "phases": [
      {
        "shape": [
          1600,
          1000
        ],
        "at": "0.000000"
      },
      {
        "shape": [
          488,
          1054
        ],
        "at": "3.195033"
      }
    ],
    "last_frame": "8.150300",
    "packet_dts_monotonic": true
  }
]
Reproduction fixture, save as index.html and serve locally
<!doctype html><html><head><meta name="viewport" content="width=device-width, initial-scale=1"><title>T3 capture regression fixture</title><style>body{margin:24px;background:#fff;color:#111;font-family:Arial,sans-serif}h1{font-size:32px}p{font-size:16px;line-height:1.6}#track{height:72px;background:#eee;width:100%;position:relative}#block{position:absolute;top:12px;width:48px;height:48px;background:#005bff}#elapsed{font:24px monospace}button{font-size:20px;padding:8px} @media(max-width:600px){body{margin:12px}h1{font-size:28px}}</style></head><body><h1>Readable capture test</h1><p>ABCDEFGHIJKLMNOPQRSTUVWXYZ<br>abcdefghijklmnopqrstuvwxyz<br>0123456789 Il1 O0 Qq 8B</p><button id="start">Start eight-second animation</button><p id="elapsed">0.000 seconds</p><p id="state">Ready</p><div id="track"><div id="block"></div></div><script>document.querySelector('#start').onclick=()=>{const start=performance.now();document.querySelector('#state').textContent='Running';function tick(now){const t=Math.min((now-start)/1000,8);document.querySelector('#elapsed').textContent=t.toFixed(3)+' seconds';document.querySelector('#block').style.left=((document.querySelector('#track').clientWidth-48)*t/8)+'px';if(t<8)requestAnimationFrame(tick);else document.querySelector('#state').textContent='Complete';}requestAnimationFrame(tick);};</script></body></html>

Workaround

Stop recording before changing viewport size and start a separate take afterward.

Capture softness is a separate issue tracked in #9872, with screenshot work proposed in #9916. This report does not attribute soft text to the encoder or claim to repair it. The earlier short recording was stopped early by the caller; it is not evidence of truncation.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions