NEW323550
REGRESSION(? - iOS 26.6): MediaRecorder portrait front camera MP4 has no rotation metadata and the track reports landscape, file plays sideways
https://bugs.webkit.org/show_bug.cgi?id=323550
Summary REGRESSION(? - iOS 26.6): MediaRecorder portrait front camera MP4 has no rota...
Fuad Laguda
Reported 2026-09-06 08:14:39 PDT
Device: iPhone 15, iOS 26.6, Safari. Same page on Android Chrome shows the landscape-reporting track too, but the missing rotation metadata in the recorded file is the WebKit part. Setup A page requests the front camera with getUserMedia, shows it in a <video>, and records with MediaRecorder using video/mp4;codecs=avc1.640028,mp4a.40.2. Phone held upright. Steps to reproduce 1. On iPhone running iOS 26.6, open a page that calls getUserMedia({ video: { facingMode: 'user', width: { ideal: 1920 }, height: { ideal: 1080 } } }). 2. Attach the stream to a <video>. The preview is portrait and correct. 3. Call track.getSettings(): width 1920, height 1080 (the sensor buffer, not the drawn frame). 4. Record with MediaRecorder for a few seconds, stop, save the blob. 5. Play the file anywhere (QuickTime, VLC, ffprobe, or a <video> element). Actual The MP4 plays rotated 90 degrees. ffprobe shows no displaymatrix / rotation side data. The file is the unrotated sensor buffer with nothing telling a player to turn it. Expected Either the recorded frames are rotated to match what the <video> element draws (as bug 290223 did for WebM by rotating before encoding), or the MP4 carries the rotation matrix so players display it upright. Regression note On earlier iOS versions the same code produced an MP4 with a displaymatrix rotation of -90 degrees, which players honoured. On 26.6 that metadata is absent. Apple Developer Forums thread 786803 has other reports of the same, and I have opened thread 844826 with the full detail. Related Bug 290223 (WebM, resolved fixed) established that the fix for WebM was to physically rotate before encoding because the container cannot carry rotation. MP4 can carry it and used to. This report is the MP4 path on 26.6. Also worth noting Requesting portrait dimensions ({ width: 1080, height: 1920 }) returns the wide preset unrotated; only landscape numbers let Safari rotate the preview. And getSettings() reporting the sensor size rather than the drawn frame size means a page cannot discover the rotation from the track at all. Workaround I ship in production Draw one frame of the <video> onto a 16x16 canvas and check which corner is painted to learn the real orientation; if the drawn frame is portrait but the track says landscape, record a canvas stream of the drawn frame instead of the raw track. Standalone reproduction and workaround, MIT: https://github.com/lagudafuadtosin/web-teleprompter (src/lib/camera.ts) Write-up: https://dev.to/lagudafuad/why-your-web-teleprompter-records-sideways-on-iphone-and-the-fix-549m
Attachments
Reduced test case (6.77 KB, text/html)
2026-09-25 10:51 PDT, Fuad Laguda
no flags
Radar WebKit Bug Importer
Comment 1 2026-09-06 14:50:28 PDT
Fuad Laguda
Comment 2 2026-09-07 00:16:22 PDT
Answering the "?" in the title. My own device is an iPhone 15 on iOS 26.6, and that is where I reproduce it. The regression note came from my investigation into whether this was my phone or the iOS version, and it rests on these: 1. Bug 198912 (fixed Oct 2020): the MP4 writer was changed to carry a rotation and mirror transform in the file. 2. Bug 290002, comment from Jean-Yves Avenard on 2025-03-22 (iOS 18.4 beta): "The webm container doesn't allow like mp4 to indicate in the metadata that the video should be rotated." 3. Commit 294257@main (2025-04-29, the WebM fix for bug 290223): "WebM, unlike mp4 doesn't support metadata indicating that a video track should be played with given rotation." 4. Apple Developer Forums thread 786803 (June 2025, iOS 18.5 era): front camera recordings sideways on iOS 18 and above, rear camera fine, and one poster reports the file has no orientation metadata where an earlier version showed displaymatrix rotation of -90. So the last version where MP4 output is documented as carrying rotation metadata is iOS 18.4 (April 2025), and the first reports of it missing are June 2025, front camera specifically. I confirm it on 26.6. If a build between those would help, tell me what to test.
Fuad Laguda
Comment 3 2026-09-07 06:03:53 PDT
Correction to step 1. The request that produces the sideways file is the portrait one, width 1080, height 1920. The landscape request, width 1920, height 1080, records upright on the same device in the same minute. My earlier ffprobe line was also wrong. Redoing it on a fresh sideways take gives a different result, and I think I know what the bug is now. The video stream is 1080x1920, H.264 Constrained Baseline. The frames are already portrait. The same stream carries a displaymatrix of -90 degrees and a rotate=90 tag. A player that honours the matrix, Photos, QuickTime, VLC, rotates an already-upright picture and shows it on its side. So the rotation metadata is not missing. The frames are rotated before encoding and the transform is written into the track header as well, and the two cancel the wrong way. That would fit the April 2025 change that rotates frames before encoding for WebM, if it now applies to MP4 while the MP4 writer still adds the transform. For comparison, a take from the same page and phone with the workaround gives a 608x1080 High profile stream with no displaymatrix, and it plays upright.
justin
Comment 4 2026-09-25 09:16:32 PDT
I tested this on an iPhone 13 mini running iOS [27.0], front camera, phone held upright, recording with MediaRecorder as [video/mp4]. { width: 1080, height: 1920 } gives the same stream you describe: 1080x1920 Constrained Baseline with a −90 displaymatrix. On mine, though, the stored frames aren't upright portrait. Decoded with ffmpeg -noautorotate, the image lies on its side inside the 1080x1920 buffer. The matrix turns it upright, and the file plays as landscape (1920x1080), a zoomed center crop of the preview. So the displayed image is upright, but the output is landscape even though the phone was held upright and portrait was requested. Could you check whether your stored frames are actually upright, or whether the 1080x1920 size is what suggested it? If yours are also on their side, this is the same result: the portrait request produces a landscape crop, rather than frames being rotated twice. For comparison, on the same device, { width: 1920, height: 1080, aspectRatio: 16 / 9 } gives 1920x1080 with a −90 displaymatrix and plays upright in portrait. A request with no width/height gives 640x480, which also plays upright in portrait.
Fuad Laguda
Comment 5 2026-09-25 10:28:24 PDT
Thanks for testing it on iOS 27. You're right, and my earlier comment was wrong about the frames. I went back to the stored frames from my 7 September take (iPhone 15, iOS 26.6, front camera, phone upright, width 1080 height 1920). Decoded without applying the matrix, the 1080x1920 buffer holds the image on its side, the same as yours. With the -90 matrix applied it plays as an upright 1920x1080 landscape picture. So it is not a double rotation. The portrait request produces a landscape recording, and the 1080x1920 size is what misled me. The landscape request (width 1920, height 1080) records upright in portrait on my device too, which matches your comparison. So this reproduces on iOS 26.6 and on iOS 27.0. One more measurement that belongs here, since the title says the track reports landscape. On iOS 26.6 the front camera track's getSettings() returns 1920x1080 for roughly the first 100 to 400 ms after the stream arrives, then changes to 1080x1920 once the first frame is painted. Anything that reads the size inside that window gets landscape, which may be how the recorder ends up with a landscape output. LiveKit's client SDK now waits for the first frame before reading dimensions because of this: https://github.com/livekit/client-sdk-js/issues/2099 (fix merged in https://github.com/livekit/client-sdk-js/pull/2100).
Fuad Laguda
Comment 6 2026-09-25 10:51:02 PDT
Created attachment 481558 [details] Reduced test case Reduced test case attached (webkit-323550-testcase.html). One file, no dependencies. It records 3 seconds from the front camera with MediaRecorder for three requests, plays each file back and shows the size it plays at. Results on iPhone 15, iOS 26.6, phone held upright: Request 1080x1920: getSettings() at getUserMedia 1080x1920, at first frame 1920x1080, recording plays as 1920x1080 landscape (ran it twice, same result) Request 1920x1080: getSettings() at getUserMedia 1920x1080, at first frame 1080x1920, recording plays as 1080x1920 portrait No width or height: getSettings() at getUserMedia 640x480, at first frame 480x640, recording plays as 480x640 portrait The first frame arrived 356 to 451 ms after getUserMedia resolved. So before the first frame, getSettings() returns the size that was requested. After the first frame it returns the size the camera actually delivers, and the recording follows that value. With the portrait request the track switches from 1080x1920 to 1920x1080 and the file comes out landscape. This also corrects the direction in my last comment. The change from 1920x1080 to 1080x1920 that I described is what happens with a landscape request. With a portrait request it goes the other way.
Note You need to log in before you can comment on or make changes to this bug.