Describe the bug
What I'm expecting
After #2100, LocalTrack.waitForDimensions waits for the first video frame before trusting getSettings(), so a portrait front-camera capture on a WebKit device reports portrait dimensions. I would expect that to hold on an iPad as well as an iPhone.
What happens instead
The wait is gated on getBrowser()?.os === 'iOS'. browserParser.ts (line 66) decides iOS versus macOS for Safari by whether the user agent contains mobile/. iPadOS has sent the Macintosh desktop user agent by default since iPadOS 13, so on an iPad Safari getBrowser() returns os: 'macOS' and waitForDimensions takes the old path: it reads getSettings() immediately, with no wait at all (the 10 ms sleep that #2100 replaced was also iOS-gated, so iPads never had even that).
The front camera on an iPad is mounted the same way as on an iPhone and runs the same WebKit capture pipeline, so I would expect the same window where the track reports the sensor frame until the first painted frame. I raised this in a review comment on #2100 but it was outside that PR's scope, so that is why I am opening it here.
I do not have an iPad to measure the window on, so the size of it on iPad is an expectation, not a reading. The gap in the gate is not an expectation, it is in the parser.
Two shapes, and I will send a pull request for whichever you prefer
-
Drop the OS gate. Call waitForFirstVideoFrame on every platform. It is best effort and never throws, it reuses an attached preview element when one exists, and on desktop the first frame arrives within a frame or two, so the cost where the bug does not exist is a few tens of milliseconds on publish. Simplest, and it covers any other WebKit-based capture path that reports the same way.
-
Detect iPadOS. Treat Safari on a Macintosh user agent with navigator.maxTouchPoints > 1 as iOS for this check, either inside browserParser (which would change getBrowser().os for every other iOS check in the SDK, so probably not) or as a local helper used only by waitForDimensions. Narrower, no change on desktop.
My preference is 1, because the wait is cheap wherever frames arrive fast and it removes a UA sniff that will drift again. Happy to do 2 if you would rather keep desktop publish timing untouched.
Either way the PR would come with a unit test in utils.test.ts covering the new path.
Reproduction
Code path, at merge commit e55c21f (main after #2100):
src/utils/browserParser.ts line 66: for Safari, os: ua.includes('mobile/') ? 'iOS' : 'macOS'.
- iPadOS Safari default user agent (Request Desktop Website is on by default since iPadOS 13):
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15. No mobile/, so getBrowser() returns { name: 'Safari', os: 'macOS' }.
src/room/track/LocalTrack.ts waitForDimensions: if (getBrowser()?.os === 'iOS') { await waitForFirstVideoFrame(...) } is skipped, and the loop returns this.dimensions from getSettings() on the first iteration.
To see the same mechanism on an iPhone (where the wait now runs), the measurement page from #2099 is at https://sideways.postbarrel.com and the readings are in that issue: the track reports 1920x1080 for the first 112 to 139 ms on an iPhone 15, then 1080x1920 at the first painted frame.
Logs
No runtime logs for this one: it is a code-path report against main at e55c21f, not a reproduction on a device. The iPhone readings that show the underlying window are in #2099.
System Info
livekit-client: main at e55c21f (the merge of #2100), read from source.
Affected environment: iPadOS Safari with the default (desktop) user agent, any version from iPadOS 13 on. Not measured here, I have no iPad.
Reference environment where the wait now runs correctly: iPhone 15, iOS 26.6, Safari, as measured in #2099.
Severity
annoyance
Additional Information
If someone on the team has an iPad to hand, the page at https://sideways.postbarrel.com prints the track's reported dimensions against the first painted frame, so the size of the window on iPad is a two-minute check. Happy to open the PR before or after that reading, whichever you prefer.
Describe the bug
What I'm expecting
After #2100,
LocalTrack.waitForDimensionswaits for the first video frame before trustinggetSettings(), so a portrait front-camera capture on a WebKit device reports portrait dimensions. I would expect that to hold on an iPad as well as an iPhone.What happens instead
The wait is gated on
getBrowser()?.os === 'iOS'.browserParser.ts(line 66) decidesiOSversusmacOSfor Safari by whether the user agent containsmobile/. iPadOS has sent the Macintosh desktop user agent by default since iPadOS 13, so on an iPad SafarigetBrowser()returnsos: 'macOS'andwaitForDimensionstakes the old path: it readsgetSettings()immediately, with no wait at all (the 10 ms sleep that #2100 replaced was also iOS-gated, so iPads never had even that).The front camera on an iPad is mounted the same way as on an iPhone and runs the same WebKit capture pipeline, so I would expect the same window where the track reports the sensor frame until the first painted frame. I raised this in a review comment on #2100 but it was outside that PR's scope, so that is why I am opening it here.
I do not have an iPad to measure the window on, so the size of it on iPad is an expectation, not a reading. The gap in the gate is not an expectation, it is in the parser.
Two shapes, and I will send a pull request for whichever you prefer
Drop the OS gate. Call
waitForFirstVideoFrameon every platform. It is best effort and never throws, it reuses an attached preview element when one exists, and on desktop the first frame arrives within a frame or two, so the cost where the bug does not exist is a few tens of milliseconds on publish. Simplest, and it covers any other WebKit-based capture path that reports the same way.Detect iPadOS. Treat Safari on a Macintosh user agent with
navigator.maxTouchPoints > 1as iOS for this check, either insidebrowserParser(which would changegetBrowser().osfor every other iOS check in the SDK, so probably not) or as a local helper used only bywaitForDimensions. Narrower, no change on desktop.My preference is 1, because the wait is cheap wherever frames arrive fast and it removes a UA sniff that will drift again. Happy to do 2 if you would rather keep desktop publish timing untouched.
Either way the PR would come with a unit test in
utils.test.tscovering the new path.Reproduction
Code path, at merge commit e55c21f (main after #2100):
src/utils/browserParser.tsline 66: for Safari,os: ua.includes('mobile/') ? 'iOS' : 'macOS'.Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15. Nomobile/, sogetBrowser()returns{ name: 'Safari', os: 'macOS' }.src/room/track/LocalTrack.tswaitForDimensions:if (getBrowser()?.os === 'iOS') { await waitForFirstVideoFrame(...) }is skipped, and the loop returnsthis.dimensionsfromgetSettings()on the first iteration.To see the same mechanism on an iPhone (where the wait now runs), the measurement page from #2099 is at https://sideways.postbarrel.com and the readings are in that issue: the track reports 1920x1080 for the first 112 to 139 ms on an iPhone 15, then 1080x1920 at the first painted frame.
Logs
System Info
Severity
annoyance
Additional Information
If someone on the team has an iPad to hand, the page at https://sideways.postbarrel.com prints the track's reported dimensions against the first painted frame, so the size of the window on iPad is a two-minute check. Happy to open the PR before or after that reading, whichever you prefer.