-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Racy devicechange event design has poor interoperability #972
Comments
The spec says a few things:
It seems hard to fix all races and have consistency between implementations. |
I'm OK with adding a My view is that:
|
I'm not clear what problem exists with firing "devicechange" events while an Trying to design the API to protect against bugs in client async code is not going to be something that the API can win. The client's response to the change in devices is likely to be async and I don't think we want to continue to delay "devicechange" events while any async operation is pending. Something that might be helpful to clients would be to specify that |
There are a few other APIs which follow a slightly different pattern than
When applying the same concept to navigator.mediaDevices
.enumerateDevices()
.then((deviceList) => {
// Log the latest list of devices.
console.log(deviceList.devices);
deviceList.onchange = () => {
// Log the list of devices again.
// The list got updated already just before the event fired.
console.log(deviceList.devices);
};
}); The Permissions API is another API which follows this pattern. I know that this would be a breaking change. But maybe there is a way to introduce something like this in a backwards compatible way. |
That's a live state update model I wouldn't recommend for lists. |
The simplest fix here IMHO would be to include the device list in the event: navigator.mediaDevices.ondevicechange = ({devices}) => {
// examine devices and compare against oldDevices to detect changes once available
oldDevices = devices;
} Async problem solved and 100% backwards compatible (modeled on trackEvent.streams). |
I'm OK with this change. |
This was discussed in https://www.w3.org/2023/10/17-webrtc-minutes.html:
|
This issue had an associated resolution in WebRTC April 23 2024 meeting – 23 April 2024 (Racy devicechange event design has poor interoperability in Media Capture and Streams):
|
The devicechange event follows § 7.7. Use plain Events for state, but the "state information in the target object." is not available synchronously, so developers have to the following:
But this is racy. By the time you're finally examining the devices, 100+ milliseconds may have passed. A lot may have happened during that time, including more
devicechange
events, which the spec allows.Theoretical? No. I've instrumented this fiddle from #966 (comment), where I keep AirPods in their case initially, then put them on, in macOS Ventura.
Firefox is fine (baseline):
The first two are me calling enumerateDevices before and after getUserMedia, like you're supposed to, to learn of all devices.
But here's Chrome:
8 events are fired, and they're fired on top of each other, while the application is still processing the previous event(s) (awaiting enumerateDevices), causing interleaved code execution.
This would look even worse if I didn't write special guards against duplicate events, but this is still a nightmare to reason about.
This might look like an implementation bug on Chrome, except here's Safari:
...and so far I haven't even inserted my AirPods yet! This is #966 where Safari fires a
devicechange
event as part of getUserMedia, tricking my fiddle into thinking a device has been inserted.As mentioned previously, my fiddle calls enumerateDevices right after getUserMedia like you're supposed to (and have to in other browsers). But here Safari fires
devicechange
on me, causing my app to again interleave two calls to enumerateDevices, making it hard to reason about. It may even expose it to the race in the OP, since AFAIK there's no spec guarantee that enumerateDevices calls resolve in order.Safari continued:
...the rest here is fairly decent, in that calls are not getting interleaved. But like Chrome there's some funny business going on with the default device. What's that about?
This seems like an interoperability issue. For example: Device switching in Google Meet works in Chrome today but not in Firefox, which I think shows it is possible to write app code that passes a testing matrix in one implementation, yet is not interoperable, unless other browsers implement the same side effects.
This seems like a spec issue. I propose we delay
devicechange
events to not fire while calls toenumerateDevices()
are outstanding.I'll file a separate issue on the non-standard "Default" device that Chrome and Safari add for microphone only.
The text was updated successfully, but these errors were encountered: