18

I have a suite which records the user's webcam and mic. It works great on a webpage, but in a Chrome Extension the line:

navigator.mediaDevices.getUserMedia({video: true, audio: true})
    .then(this.record.bind(this))
    .catch(VidRA.error);

is throwing

NotAllowedError: Failed due to shutdown

I've searched and found almost nothing that might explain this. Has anyone else come across this or does anyone know what I can do about it?

Mitya
  • 33,629
  • 9
  • 60
  • 107
  • 1
    https://crbug.com/160337 – wOxxOm Jun 22 '18 at 15:54
  • D'oh........... – Mitya Jun 22 '18 at 16:05
  • Does this work as a solution as well? [https://stackoverflow.com/questions/63656707/chrome-extension-how-to-access-webcam-and-audio-at-intervals](https://stackoverflow.com/questions/63656707/chrome-extension-how-to-access-webcam-and-audio-at-intervals) –  Feb 21 '21 at 05:04
  • Don't know - not tried it. Don't like that approach so much because it requires the user to go into options and set permissions there. With the below answer, you achieve the same thing without invoking options. – Mitya Feb 21 '21 at 13:25
  • @wOxxOm I am getting a slightly different error, could you plz check: https://stackoverflow.com/questions/72470401/navigator-getusermedia-is-throwing-error-notallowederror-invalid-state – coure2011 Jun 02 '22 at 03:49

3 Answers3

13

Wow, this is a minefield.

Firstly, it seems this is a bug (thanks, @wOxxOm).

So we need to code around it.

Since the background script generates this bug when requesting media access, we need to request it elsewhere. The same error is generated if you try from the popup JS, so that leaves content scripts.

The steps, then, are these:

  • Content script requests access to media devices

  • On success, content script notifies background script

  • On receipt of message, background script requests acccess to media devices; since content script has already succeeded, background script will now also succeed

Crucially, the content script must run in the context of the extension, NOT the current webpage (in the active tab.) That is, the content script must do so via a URL beginning chrome://, generated by chrome.runtime.getURL().

This is so the user is asked for permissions only once, since their decision is remembered on a domain-by-domain basis. Running the script in the context of the extension, not the current webpage, means permission is always asked (and the decision remembered) in the same, extension-based domain, not again for every website domain visited.

My approach was to have a persistent content script (specified in the manifest under content_scripts) whose job, when the extension is opened, is to inject an iframe into the current tab's page.

Into that iframe is loaded a page from the extension, let's call it iframe.html, which is ultimately responsible for requesting access.

Phew...

Mitya
  • 33,629
  • 9
  • 60
  • 107
  • Why did you decide to do the recording in the background script and not the popup script? – Heiki Mar 31 '20 at 10:53
  • Because the popup script dies as soon as the popup closes, and the popup closes when it loses focus. I want the user to be able to record themselves interacting with the screen, and that will trigger popup close. – Mitya Mar 31 '20 at 11:44
  • Got it. I used exactly your solution: I call `navigator.mediaDevices.getUserMedia` first in the content script, then send a message to the background script which then calls the exact same function, but now I get a very similar error to the first one: `DOMException: Failed due to shutdown` from the background script. Any ideas, what could be the issue here? Thanks! – Heiki Mar 31 '20 at 14:34
  • Hmm that definitely rings a bell. I don't recall what that was about - it may be a bug someone knows about on the Chrome developer forums? I definitely saw it a few times but can't recall what, if anything, I did to get rid of it. – Mitya Mar 31 '20 at 16:38
  • Do you remember what you meant by running the content script in the context of the extension - perhaps by adding it under `content_scripts` together with `"matches": [""]`? – Heiki Mar 31 '20 at 18:46
  • I meant that the content script has to run with an address like `chrome://...` rather than running in the context of your web page. To do this, you need to inject it with a URL generated by `chrome.runtime.getURL`. If you want to see how I ultimately got things working, download a Chrome Extension called govidigo (blue logo) and have a root through that :) – Mitya Apr 01 '20 at 10:36
  • I have used content_script mention in https://developer.chrome.com/extensions/content_scripts. But not working for me. If anyone knows then tell me. Thank you in advance. – Akash Shrimali Jul 07 '20 at 05:37
  • @akashshrimali If it's not an exact duplicate of my question, then post it as a question. – Mitya Jul 07 '20 at 10:34
  • @Mitya it is same duplicate of your question. Thank you for your reply. – Akash Shrimali Jul 08 '20 at 06:58
  • @Mitya can u please provide a sample? – AmirHossein Parsapour Nov 13 '20 at 07:20
  • You can see my solution by looking at the code behind my extension [screenROCK](https://screenrock.com/). The crucial part to understand is what I put in my comment, above, of April 1. – Mitya Nov 13 '20 at 10:50
  • It seems like I cannot get at the code in your extension - when I inspect it, it closes abruptly - so I'd like to ask, how do you "inject a script with a url" - do you mean in the context of an IFrame? How can you manipulate a script's context? – notepadNinja Feb 22 '21 at 09:58
  • When you say "closes abruptly" it sounds like you're trying to inspect the popup, which will, as you've discovered, close automatically any time you blur away from it. (This is default Chrome Extension behaviour for popups). What I meant was, find where on your machine the extension has been installed, and study the code files - not the DOM. – Mitya Feb 23 '21 at 11:27
  • @Mitya, I am trying to follow you approach above but still facing issue can you please help me with this https://stackoverflow.com/questions/69011764/getting-domexception-failed-due-to-shutdown-when-trying-to-record-stream-from – GMAC Sep 01 '21 at 10:10
5

Utkanos answer is great but don't forget to add

frame.setAttribute("allow", "microphone; camera");
rgs
  • 51
  • 1
  • 1
-1

Including this in the manifesto.json solved the issue for me.

"web_accessible_resources": ["background.js"]
  • That makes little sense. Background scripts and web-available resources have nothing in common one another. The latter is for allowing assets hosted in your extension to be available to the current webpage, via a `chrome://` URL. – Mitya Feb 23 '21 at 11:25
  • Somehow it bypassed asking for permission for the camera and I was able to use the camera in the background script, this option is no available anymore after the latest chrome update. But it only worked for a good two weeks until the update. –  Mar 09 '21 at 18:31