electron: getDisplayMedia with Chrome 72 throwing Not Allowed

  • Output of node_modules/.bin/electron --version: v5.0.0-nightly.20190107
  • Operating System (Platform and Version): macOS Mojave 10.14.2 (18C54)
  • Output of node_modules/.bin/electron --version on last known working Electron version (if applicable): never

Expected Behavior Using navigator.mediaDevices.getDisplayMedia({ video: true }), a native screen picker should appear.

Actual behavior The following error is thrown: NotAllowedError: Permission denied

Screenshots Expected dialog image

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 18
  • Comments: 33 (11 by maintainers)

Most upvoted comments

I was able to polyfill getDisplayMedia using the desktopCapturer so that I can use the same code in Electron and the Chrome browser. I’ve only tested it in Windows 10. If it’s handy I can wrap it in a npm package with some options to make the implementation easier.

Adding the polyfill to a BrowserWindow

If you want to add the polyfill to a BrowserWindow with an external website or file in it you need to preload it in the session to make it available to the website. You also need to give the BrowserWindow the permission to use the screen. We just allow everything to make it easier by implementing both setPermissionCheckHandler and setPermissionRequestHandler in it’s basic form.

main.js

const { BrowserWindow } = require('electron')

const win = new BrowserWindow({ width: 800, height: 600 })
win.webContents.session.setPreloads([path.join(__dirname, 'preload-get-display-media-polyfill.js')])
win.webContents.session.setPermissionCheckHandler(async (webContents, permission, details) => {
  return true
})
win.webContents.session.setPermissionRequestHandler(async (webContents, permission, callback, details) => {
  callback(true)
})
win.loadURL('https://example.com')

The polyfill

The polyfill overrides the native mediaDevices.getDisplayMedia implementation. The getDisplayMedia override gets the sources from the desktopCapturer and displays them in a list in the body tag. (You can use the <style> tag at the bottom of this post to style it like an overlay.) The user can click on a source and then the getDisplayMedia promise resolves the source.

preload-get-display-media-polyfill.js

const { desktopCapturer } = require('electron')
window.navigator.mediaDevices.getDisplayMedia = () => {
  return new Promise(async (resolve, reject) => {
    try {
      const sources = await desktopCapturer.getSources({ types: ['screen', 'window'] })

      const selectionElem = document.createElement('div')
      selectionElem.classList = 'desktop-capturer-selection'
      selectionElem.innerHTML = `
        <div class="desktop-capturer-selection__scroller">
          <ul class="desktop-capturer-selection__list">
            ${sources.map(({id, name, thumbnail, display_id, appIcon}) => `
              <li class="desktop-capturer-selection__item">
                <button class="desktop-capturer-selection__btn" data-id="${id}" title="${name}">
                  <img class="desktop-capturer-selection__thumbnail" src="${thumbnail.toDataURL()}" />
                  <span class="desktop-capturer-selection__name">${name}</span>
                </button>
              </li>
            `).join('')}
          </ul>
        </div>
      `
      document.body.appendChild(selectionElem)

      document.querySelectorAll('.desktop-capturer-selection__btn')
        .forEach(button => {
          button.addEventListener('click', async () => {
            try {
              const id = button.getAttribute('data-id')
              const source = sources.find(source => source.id === id)
              if(!source) {
                throw new Error(`Source with id ${id} does not exist`)
              }
              
              const stream = await window.navigator.mediaDevices.getUserMedia({
                audio: false,
                video: {
                  mandatory: {
                    chromeMediaSource: 'desktop',
                    chromeMediaSourceId: source.id
                  }
                }
              })
              resolve(stream)

              selectionElem.remove()
            } catch (err) {
              console.error('Error selecting desktop capture source:', err)
              reject(err)
            }
          })
        })
    } catch (err) {
      console.error('Error displaying desktop capture sources:', err)
      reject(err)
    }
  })
}

Some basic styling for the source selection overlay

If you want to quickly test the polyfill here is some basic styling for the selection overlay. But of course you can customize this in any direction. You can add the style tag to the innerHTML of the selectionElem object in the polyfill.

<style>
.desktop-capturer-selection {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  background: rgba(30,30,30,.75);
  color: #fff;
  z-index: 10000000;
  display: flex;
  align-items: center;
  justify-content: center;
}
.desktop-capturer-selection__scroller {
  width: 100%;
  max-height: 100vh;
  overflow-y: auto;
}
.desktop-capturer-selection__list {
  max-width: calc(100% - 100px);
  margin: 50px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  overflow: hidden;
  justify-content: center;
}
.desktop-capturer-selection__item {
  display: flex;
  margin: 4px;
}
.desktop-capturer-selection__btn {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 145px;
  margin: 0;
  border: 0;
  border-radius: 3px;
  padding: 4px;
  background: #252626;
  text-align: left;
  transition: background-color .15s, box-shadow .15s;
}
.desktop-capturer-selection__btn:hover,
.desktop-capturer-selection__btn:focus {
  background: rgba(98,100,167,.8);
}
.desktop-capturer-selection__thumbnail {
  width: 100%;
  height: 81px;
  object-fit: cover;
}
.desktop-capturer-selection__name {
  margin: 6px 0 6px;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow: hidden;
}
</style>

Has anyone successfully used the polyfill mentioned above with contextIsolation enabled? I don’t think this is possible because you have to pass it through the contextBridge which doesn’t seem to be allowing the stream through. Does anyone have a workaround for this?

@WesselKroos I think you should wrap it in an npm package.

Is there any update for this? Would be great to be able to use the new WebRTC API for screensharing.

There is no chance this is supported on Electron unless either of these is met:

  1. Electron implements a picker UI (which they have refused to do so far)
  2. Electron creates a non-standard constraint for getDisplayMedia so the source ID can be passed

Chrome has no intention to deprecate using getUserMedia for desktop capture (all you can do now on Electron) in the near future.

  • Output of node_modules/.bin/electron --version: v5.0.0-nightly.20190107
  • Operating System (Platform and Version): macOS Mojave 10.14.2 (18C54)
  • Output of node_modules/.bin/electron --version on last known working Electron version (if applicable): never

Expected Behavior Using navigator.mediaDevices.getDisplayMedia({ video: true }), a native screen picker should appear.

Actual behavior The following error is thrown: NotAllowedError: Permission denied

Screenshots Expected dialog image

Solution:

https://electronjs.org/docs/api/desktop-capturer https://qiita.com/gtk2k/items/ac08ddeeaf74f3126f81

FYI even with 5.0.0-beta.3 which includes the fix this is still not working throwing NotAllowedError: Permission denied.

For a noob like me, how do I make those changes that @WesselKroos sugests?

ty

@WesselKroos kudos. I had to butcher your code to inject it into 3rd party iframe… but I got it working. Cheers.

@WesselKroos

Awesome! Thanks so much.

No.

Any update ? It’s good if electron support standard WebRTC API.

Any updates on this? Thanks!