Skip to content

Example Chrome extension for desktopCapture API (screen sharing).

License

Notifications You must be signed in to change notification settings

wpp/ScreenStream

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Notes

You might not need this anymore!

Recent Chrome versions (70 and up) introduced getDisplayMedia which essentially nullify this project and the need for an extension. You can simply acquire a screen stream with something like this:

navigator.mediaDevices.getDisplayMedia({ audio: false, video: true })
  .then(stream => video.srcObject = stream)
  .catch(handleError);

A working demo can be found here.

This project has been merged into https://github.com/GoogleChrome/webrtc in case something doesn't work have a look over there.

Introduction

This demo app shows you how to use a Chrome extension to access the desktopCapture API in your web-application.

(If you're writing a WebRTC app with screen sharing, and want to avoid sending users to chrome://flags)

Index

Setup

For the Demo to work, you will need to install the extension

  1. Go to chrome://extensions
  2. Check "Developer mode"
  3. Click "Load unpacked extension..."
  4. In the dialog choose the extension folder from the repository

You should see:

NOTE: your ID will differ, that's fine though.

How does it work?

Application (our web-app)

The index.html file contains a "Share screen" button, an empty <video> tag and loads some javascript (app.js). Think of these two files as our "application".

Extension

The extension consists of 4 files:

  1. background.js
  2. content-script.js
  3. manifest.json
  4. icon.png // not important

background.js

holds the main logic of the extension

or in our case, has access to the desktopCapture API. We get access to this API when we ask for permission in manifest.json:

"permissions": [
  "desktopCapture"
]

The background page ("background.js" - chrome generates the related html for us) runs in the extension process and is therefore isolated from our application environment. Meaning that we don't have a direct way to talk to our application. That's why we have the content-script.

1

content-script.js

If your extension needs to interact with web pages, then it needs a content script. A content script is some JavaScript that executes in the context of a page that's been loaded into the browser.

2

The content-script does not have access to variables or functions defined on our page, but it has access to the DOM.

Glueing it together

In order to call navigator.mediaDevices.getUserMedia in app.js, we need a chromeMediaSourceId which we get from our background page.

We have to pass messages through the chain below (left to right):

app.js            |        |content-script.js |      |background.js       | desktopCapture API
------------------|        |------------------|      |--------------------|
window.postMessage|------->|port.postMessage  |----->|port.onMessage------+
                  | window |                  | port |                 get|*streamID*
getUserMedia      |<------ |window.postMessage|<-----|port.postMessage<---+

Lets run through the chain:

When the user clicks on "Share Screen", we post a message to window, because...

window.postMessage({ type: 'SS_UI_REQUEST', text: 'start' }, '*');

the content-script has access to the DOM.

window.addEventListener('message', event => {
  if (event.data.type === 'SS_UI_REQUEST') {
    port.postMessage(event.data);
  }
}, false);

the content-script can also talk to the background page

var port = chrome.runtime.connect(chrome.runtime.id);

the background page is listening on that port,

port.onMessage.addListener(function (msg) {
  if(msg.type === 'SS_UI_REQUEST') {
    requestScreenSharing(port, msg);
  }

gets access to the stream, and sends a message containing the chromeMediaSourceId (streamID) back to the port (the content-script)

function requestScreenSharing(port, msg) {
  desktopMediaRequestId =
  chrome.desktopCapture.chooseDesktopMedia(data_sources, port.sender.tab,
  streamId => {
    msg.type = 'SS_DIALOG_SUCCESS';
    msg.streamId = streamId;
    port.postMessage(msg);
  });
}

the content-script posts it back to app.js

port.onMessage.addListener(msg => {
    window.postMessage(msg, '*');
});

where we finally call navigator.mediaDevices.getUserMedia with the streamID

if (event.data.type && (event.data.type === 'SS_DIALOG_SUCCESS')) {
  startScreenStreamFrom(event.data.streamId);
}

function startScreenStreamFrom(streamId) {
  navigator.mediaDevices
    .getUserMedia({
      audio: false,
      video: {
        mandatory: {
          chromeMediaSource: 'desktop',
          chromeMediaSourceId: streamId
        }
      }
    })
    .then(stream => {
      videoElement = document.getElementById('video');
      videoElement.srcObject = stream;
    })

Please note that the code examples in this README are edited for brevity, complete code is in the corresponding files.

Avoiding a reload after installation

This part was inspired by fippo from &yet.

When the extension is installed, the content-script will not be injected automatically by Chrome. The good news is that background.js will be exectued and we can use it to manually inject the content-script.js in our open pages (tabs):

The relevant code can be found in background.js and looks something like:

chrome.tabs.executeScript(currentTab.id, { file: 'js/content-script.js' }, () => {
  console.log('Injected content-script.');
});

Please note: For this to work, you have to adjust manifest.json by adding "tabs" to the permissions section.

Credits

Thanks to the guys and gals at &yet for talky.io

License

MIT

Releases

No releases published

Packages

No packages published