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

openedSep 7, 2026byMMansTomb

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.
sh
1ffprobe -v error -show_entries stream=width,height,avg_frame_rate:format=duration,size -of json INPUT.mp4
2ffmpeg -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

https://github.com/user-attachments/assets/18a8bee3-b048-4f03-b048-662ececdeba1

After resize recording: Original fixed MP4

https://github.com/user-attachments/assets/24306a51-ec12-4c44-80b9-05c41d70cf0a

Decoder output
text
1[h264 @ 0x56349cf52e80] top block unavailable for requested intra mode
2[h264 @ 0x56349cf52e80] error while decoding MB 31 0
3[h264 @ 0x56349cf52e80] top block unavailable for requested intra mode
4[h264 @ 0x56349cf52e80] error while decoding MB 23 10
5[h264 @ 0x56349ce53880] top block unavailable for requested intra mode
6[h264 @ 0x56349ce53880] error while decoding MB 31 0
7[h264 @ 0x56349ce53880] top block unavailable for requested intra mode -1
8[h264 @ 0x56349ce53880] error while decoding MB 62 9
9[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] corrupt decoded frame
10[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Error processing packet in decoder: Invalid data found when processing input
11[h264 @ 0x56349cf72d40] negative number of zero coeffs at 92 5
12[h264 @ 0x56349cf72d40] error while decoding MB 92 5
13[h264 @ 0x56349cf92c00] corrupted macroblock 94 5 (total_coeff=-1)
14[h264 @ 0x56349cf92c00] error while decoding MB 94 5
15[h264 @ 0x56349cf92c00] top block unavailable for requested intra mode
16[h264 @ 0x56349cf92c00] error while decoding MB 47 8
17[h264 @ 0x56349cd7be40] dquant out of range (-216) at 0 6
18[h264 @ 0x56349cd7be40] error while decoding MB 0 6
19[h264 @ 0x56349ce03180] mb_type 3583 in P slice too large at 20 6
20[h264 @ 0x56349ce03180] error while decoding MB 20 6
21[h264 @ 0x56349ce03180] top block unavailable for requested intra mode
22[h264 @ 0x56349ce03180] error while decoding MB 78 8
23[h264 @ 0x56349cd9b000] negative number of zero coeffs at 92 5
24[h264 @ 0x56349cd9b000] error while decoding MB 92 5
25[h264 @ 0x56349cdbae00] negative number of zero coeffs at 93 5
26[h264 @ 0x56349cdbae00] error while decoding MB 93 5
27[h264 @ 0x56349ce339c0] negative number of zero coeffs at 98 5
28[h264 @ 0x56349ce339c0] error while decoding MB 98 5
29[h264 @ 0x56349ce73740] mb_type 85 in P slice too large at 46 7
30[h264 @ 0x56349ce73740] error while decoding MB 46 7
31[h264 @ 0x56349ce93600] negative number of zero coeffs at 99 5
32[h264 @ 0x56349ce93600] error while decoding MB 99 5
33[h264 @ 0x56349ceb34c0] mb_type 284 in P slice too large at 6 6
34[h264 @ 0x56349ceb34c0] error while decoding MB 6 6
35[h264 @ 0x56349ced3380] cbp too large (363) at 96 5
36[h264 @ 0x56349ced3380] error while decoding MB 96 5
37[h264 @ 0x56349cef3240] cbp too large (127) at 54 7
38[h264 @ 0x56349cef3240] error while decoding MB 54 7
39[h264 @ 0x56349cef3240] top block unavailable for requested intra mode
40[h264 @ 0x56349cef3240] error while decoding MB 48 8
41[h264 @ 0x56349cf13100] cbp too large (272) at 9 6
42[h264 @ 0x56349cf13100] error while decoding MB 9 6
43[h264 @ 0x56349cf13100] top block unavailable for requested intra mode
44[h264 @ 0x56349cf13100] error while decoding MB 10 9
45[h264 @ 0x56349cf32fc0] out of range intra chroma pred mode
46[h264 @ 0x56349cf32fc0] error while decoding MB 1 6
47[h264 @ 0x56349cf32fc0] top block unavailable for requested intra mode
48[h264 @ 0x56349cf32fc0] error while decoding MB 10 9
49[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Task finished with error code: -1094995529 (Invalid data found when processing input)
50[vist#0:0/h264 @ 0x56349cd4f3c0] [dec:h264 @ 0x56349cd83680] Terminating thread with return code -1094995529 (Invalid data found when processing input)
Validation results
json
1[
2 {
3 "file": "app-before/desktop.mp4",
4 "decode_exit": 0,
5 "streams": [
6 {
7 "codec_tag_string": "avc1",
8 "width": 1600,
9 "height": 1000,
10 "avg_frame_rate": "364500/12271"
11 }
12 ],
13 "format": {
14 "duration": "8.181533",
15 "size": "168002"
16 },
17 "phases": [
18 {
19 "shape": [
20 1600,
21 1000
22 ],
23 "at": "0.000000"
24 }
25 ],
26 "last_frame": "8.148233",
27 "packet_dts_monotonic": true
28 },
29 {
30 "file": "app-before/mobile.mp4",
31 "decode_exit": 0,
32 "streams": [
33 {
34 "codec_tag_string": "avc1",
35 "width": 488,
36 "height": 1054,
37 "avg_frame_rate": "3540000/122641"
38 }
39 ],
40 "format": {
41 "duration": "8.180400",
42 "size": "141727"
43 },
44 "phases": [
45 {
46 "shape": [
47 488,
48 1054
49 ],
50 "at": "0.000000"
51 }
52 ],
53 "last_frame": "8.147100",
54 "packet_dts_monotonic": true
55 },
56 {
57 "file": "app-before/resize-1.mp4",
58 "decode_exit": 183,
59 "streams": [
60 {
61 "codec_tag_string": "avc1",
62 "width": 1600,
63 "height": 1000,
64 "avg_frame_rate": "2430000/82453"
65 }
66 ],
67 "format": {
68 "duration": "8.252533",
69 "size": "188061"
70 },
71 "phases": [
72 {
73 "shape": [
74 1600,
75 1000
76 ],
77 "at": "0.000000"
78 }
79 ],
80 "last_frame": "8.219233",
81 "packet_dts_monotonic": true
82 },
83 {
84 "file": "app-before/resize-2.mp4",
85 "decode_exit": 183,
86 "streams": [
87 {
88 "codec_tag_string": "avc1",
89 "width": 1600,
90 "height": 1000,
91 "avg_frame_rate": "303750/10291"
92 }
93 ],
94 "format": {
95 "duration": "8.237567",
96 "size": "171847"
97 },
98 "phases": [
99 {
100 "shape": [
101 1600,
102 1000
103 ],
104 "at": "0.000000"
105 }
106 ],
107 "last_frame": "8.204267",
108 "packet_dts_monotonic": true
109 },
110 {
111 "file": "app-after/desktop.mp4",
112 "decode_exit": 0,
113 "streams": [
114 {
115 "codec_tag_string": "avc3",
116 "width": 1600,
117 "height": 1000,
118 "avg_frame_rate": "455625/15328"
119 }
120 ],
121 "format": {
122 "duration": "8.191767",
123 "size": "169361"
124 },
125 "phases": [
126 {
127 "shape": [
128 1600,
129 1000
130 ],
131 "at": "0.000000"
132 }
133 ],
134 "last_frame": "8.158467",
135 "packet_dts_monotonic": true
136 },
137 {
138 "file": "app-after/mobile.mp4",
139 "decode_exit": 0,
140 "streams": [
141 {
142 "codec_tag_string": "avc3",
143 "width": 488,
144 "height": 1054,
145 "avg_frame_rate": "7080000/247553"
146 }
147 ],
148 "format": {
149 "duration": "8.238100",
150 "size": "143144"
151 },
152 "phases": [
153 {
154 "shape": [
155 488,
156 1054
157 ],
158 "at": "0.000000"
159 }
160 ],
161 "last_frame": "8.204800",
162 "packet_dts_monotonic": true
163 },
164 {
165 "file": "app-after/resize-1.mp4",
166 "decode_exit": 0,
167 "streams": [
168 {
169 "codec_tag_string": "avc3",
170 "width": 1600,
171 "height": 1000,
172 "avg_frame_rate": "2420000/81641"
173 }
174 ],
175 "format": {
176 "duration": "8.169600",
177 "size": "188283"
178 },
179 "phases": [
180 {
181 "shape": [
182 1600,
183 1000
184 ],
185 "at": "0.000000"
186 },
187 {
188 "shape": [
189 488,
190 1054
191 ],
192 "at": "3.190133"
193 }
194 ],
195 "last_frame": "8.136300",
196 "packet_dts_monotonic": true
197 },
198 {
199 "file": "app-after/resize-2.mp4",
200 "decode_exit": 0,
201 "streams": [
202 {
203 "codec_tag_string": "avc3",
204 "width": 1600,
205 "height": 1000,
206 "avg_frame_rate": "7290000/245867"
207 }
208 ],
209 "format": {
210 "duration": "8.183600",
211 "size": "186695"
212 },
213 "phases": [
214 {
215 "shape": [
216 1600,
217 1000
218 ],
219 "at": "0.000000"
220 },
221 {
222 "shape": [
223 488,
224 1054
225 ],
226 "at": "3.195033"
227 }
228 ],
229 "last_frame": "8.150300",
230 "packet_dts_monotonic": true
231 }
232]
Reproduction fixture, save as index.html and serve locally
html
1<!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.

Existing fix: #10495, already taken over by a maintainer. No duplicate PR opened.

Ran the attached fixture through the available Linux server Browser panel (Chrome 154, DPR 2): two 1280×800 → 390×844 recordings, resizing about three seconds after Start, plus separate fixed desktop and mobile controls. All reached Complete at 8.000 seconds. All four exact exported MP4s passed the issue's FFmpeg decode command with exit 0 and no decoder errors. The resize exports retained a 2560×1600 encoded size. This is the server capture path, not Electron's native webview capture, so it does not disprove the reported desktop failure.

Current main source at ed9b7ca9c5 still prefers avc1 in browserRecording.ts; that checkout was inspected, not run as an Electron app. Desktop verification is unavailable here. Still needed: current-main Electron resize exports and a Safari/iOS playback check of the existing PR's avc3 export, as its maintainer notes request. Leaving this issue open pending that verification.

desktop.mp4 mobile.mp4 resize1.mp4 resize2.mp4