Bug 198040 - getDisplayMedia fails when called from a promise created during a user gesture
Summary: getDisplayMedia fails when called from a promise created during a user gesture
Status: NEW
Alias: None
Product: WebKit
Classification: Unclassified
Component: WebRTC (show other bugs)
Version: Safari Technology Preview
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: Nobody
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2019-05-20 07:46 PDT by philipp.weissensteiner
Modified: 2019-05-24 15:00 PDT (History)
4 users (show)

See Also:


Attachments
getDisplayMedia example (911 bytes, text/html)
2019-05-20 07:46 PDT, philipp.weissensteiner
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description philipp.weissensteiner 2019-05-20 07:46:54 PDT
Created attachment 370251 [details]
getDisplayMedia example

Hi,

Since Safari Tech Preview 82 

> getDisplayMedia must be called from a user gesture handler.

If said handler has an async call to say getUserMedia or enumerateDevices, before
invoking getDisplayMedia, the call will always fail with the above message.

```javascript
//  my user gesture handler is called after a button is clicked
async function myUserGestureHandler() {
  // both will cause a failure
  // const camera = await navigator.mediaDevices.getUserMedia({ video: true });
  const devices = await navigator.mediaDevices.enumerateDevices();

  // calling getUserMedia here works
  const screen = await navigator.mediaDevices.getDisplayMedia({ video: true });
}
```

- Invoking getUserMedia works.
- Recent Firefox and Chrome versions work too.
- Worked in Safari Tech Preview 81.

I've attached an example page demonstrating the issue.
Comment 1 Radar WebKit Bug Importer 2019-05-20 10:31:45 PDT
<rdar://problem/50950989>
Comment 2 youenn fablet 2019-05-20 10:52:38 PDT
Hi Philipp,

I guess a workaround is to do:

const gumPromise = navigator.mediaDevices.getUserMedia({ video: true });
const screenPromise = await anavigator.mediaDevices.getDisplayMedia({ video: true });
const camera = await gumPromise;

Would that work for you?
Or do you want to NOT request getDisplayMedia access if getUserMedia is rejected?

I am also not sure why you want to do enumerateDevices there.


FWIW, it will, in most cases, trigger two prompts to the user in a very short amount of time. I wonder whether this is a good application flow.
Comment 3 philipp.weissensteiner 2019-05-21 02:41:22 PDT
Hi! Thanks for the quick response.

Let's say we want to build a screen sharing app where we mix the
camera and the screen stream.
We'd have a button "share screen" and invoke the two .getMedia
functions in a handler.
Is that handler function not a "user gesture handler"?

My main concern is that "user gesture handler" is ambiguous.
Can you clarify what a user gesture handler is and why the example
code I posted,
doesn't qualify as such?

If I invoke a different Promise instead of getUserMedia or enumarteDevices, e.g:

const examplePromise = () => {
  return new Promise(res => window.setTimeout(() => res(), 500));
};

A subsequent call to getDisplayMedia works just fine, but increasing
the delay to 1s causes the error message too.

The work-around is viable, but I'd it's still consider it a "work-around".
It's not clear why you'd have to invoke gDM before gUM, right?
I'd probably have to add a comment "order is important here!" so I
won't forget it in 6 months :).

(As far as enumarteDevices goes, that was just an example).

Hope that clarifies my issue.
Comment 4 youenn fablet 2019-05-22 16:41:23 PDT
> A subsequent call to getDisplayMedia works just fine, but increasing
> the delay to 1s causes the error message too.

We currently bound the user gesture duration to 1s.

> The work-around is viable, but I'd it's still consider it a "work-around".
> It's not clear why you'd have to invoke gDM before gUM, right?

My example is invoking gum first, then gdm.
It is not waiting for gum response to call gdm response.

The questions might be:
1. Can your application live with just screen, just camera or require both?
2. Do you want to ask the user for gdm in case gum is rejected?

The current design does not allow you to not ask gdm if gum is rejected without another user gesture.

Note though that two prompts that are happening shortly one after the other might be confusing to the user.
A more user friendly flow might be to enter a room and at that time trigger gum or gdm. Then have the user interact with the UI to enable the other one.
Comment 5 philipp.weissensteiner 2019-05-23 03:10:28 PDT
> We currently bound the user gesture duration to 1s.

Thanks for the clarification.

> My example is invoking gum first, then gdm.
> It is not waiting for gum response to call gdm response.

My bad. The point is that the order is important,
because of the user gesture restrictions.

> 1. Can your application live with just screen, just camera or require both?

For this application I'd require both.

> 2. Do you want to ask the user for gdm in case gum is rejected?

No.

You're absolutely right, the current flow could be improved in that way.

I think a clarification for the behaviour might be good.

Maybe something like:

> getDisplayMedia must be called from a user gesture handler. Learn more at:
> https://bugs.webkit.org/show_bug.cgi?id=198040 or whatever the link might be.