Level up your Twilio API skills in TwilioQuest, an educational game for Mac, Windows, and Linux. Download Now

メニュー

Expand
Rate this page:

Thanks for rating this page!

We are always striving to improve our documentation quality, and your feedback is valuable to us. How could this documentation serve you better?

Screen Capture

In this guide, we’ll demonstrate how to share your screen using twilio-video.js. Chrome 72+, Firefox 66+ and Safari 12.2+ support the getDisplayMedia API. This can be used to capture the screen directly from the web app. For previous versions of Chrome, you'll need to create an extension. The web application will communicate with this extension to capture the screen.

Chrome (72+), Firefox (66+), Safari (12.2+): Use getDisplayMedia

To share your screen in a Room, use getDisplayMedia() to get the screen's MediaStreamTrack and create a LocalVideoTrack:

const { connect, LocalVideoTrack } = require('twilio-video');

const stream = await navigator.mediaDevices.getDisplayMedia();
const screenTrack = new LocalVideoTrack(stream.getTracks()[0]);

Then, you can either publish the LocalVideoTrack while joining a Room:

const room = await connect(token, {
  name: 'presentation',
  tracks: [screenTrack]
});

or, publish the LocalVideoTrack after joining a Room:

const room = await connect(token, {
  name: 'presentation'
});

room.localParticipant.publishTrack(screenTrack);

Firefox (65-): Use getUserMedia

To share your screen in the Room, use getUserMedia() to get the screen's MediaStreamTrack and create a LocalVideoTrack:

const { connect, LocalVideoTrack } = require('twilio-video');

const stream = await navigator.mediaDevices.getUserMedia({
  mediaSource: 'window'
});

const screenTrack = new LocalVideoTrack(stream.getTracks()[0]);

Then, you can either publish the LocalVideoTrack while joining a Room:

const room = await connect(token, {
  name: 'presentation',
  tracks: [screenTrack]
});

or, publish the LocalVideoTrack after joining a Room:

const room = await connect(token, {
  name: 'presentation'
});

room.localParticipant.publishTrack(screenTrack);

Chrome (71-): Build a Screen Share Extension

Our web app and extension will communicate using message passing. Specifically, our web app will be responsible for sending requests to our extension using Chrome's sendMessage API, and our extension will be responsible for responding to requests raised through Chrome's onMessageExternal event. By convention, every message passed between our web app and extension will be a JSON object containing a type property, and we will use this type property to distinguish different types of messages.

Webアプリケーションのリクエスト

WebアプリケーションではExtensionにリクエストを送信します。

"getUserScreen"リクエスト

スクリーンキャプチャーを有効にしたいので、WebアプリケーションがExtensionに送ることができるもっとも重要なメッセージは、ユーザの画面をキャプチャーするリクエストです。 これらのリクエストを他のタイプのメッセージと区別したいため、typeを "getUserScreen"に相当するよう設定します(メッセージtypeには任意の文字列を選択できますが、"getUserScreen"はブラウザーのgetUserMedia APIとよく似ています)。 また、ChromeではユーザーにDesktopCaptureSourceTypeを指定できるため、別のプロパティーでDesktopCaptureSourceTypesの配列に相当するsourcesを含めることが必要です。 例えば、下記の"getUserScreen"リクエストはユーザーの画面、ウィンドウ、またはたぶへのアクセスについてプロンプトを表示します。

{
  "type": "getUserScreen",
  "sources": ["screen", "window", "tab"]
}

Our web app should expect a success or error message in response.

Extensionレスポンス

Our extension will respond to our web app's requests.

Success Responses

Any time we need to communicate a successful result from our extension, we'll send a message with type equal to "success", and possibly some additional data. For example, if our web app's "getUserScreen" request succeeds, we should include the resulting streamId that Chrome provides us. Assuming Chrome returns us a streamId of "123", we should respond with

{
  "type": "success",
  "streamId": "123"
}

エラーレスポンス

Any time we need to communicate an error from our extension, we'll send a message with type equal to "error" and an error message. For example, if our web app's "getUserScreen" request fails, we should respond with

{
  "type": "error",
  "message": "Failed to get stream ID"
}

Project Structure

このガイドでは、Webアプリケーションと拡張機能の2つのトップレベルのフォルダを持つ次のプロジェクト構造を使用します。

.
├── web-app
   ├── index.html
   └── web-app.js
└── extension
    ├── extension.js
    └── manifest.json

メモ: このガイドを既存のプロジェクトに当てはめる場合は、お好みの構造に調整することができます。

Webアプリケーション

index.html

今回のWebアプリケーションはブラウザーにロードされるので、アプリケーションにはHTMLのエントリーポイントが必要です。 このHTMLファイルには、web-app.jsとtwilio-video.jsを読み込むことが必要です。

web-app.js

ルームに接続し、ユーザーの画面をリクエストするというtwilio-video.jsクライアントの作成用のロジックは、このファイルに収められています。

内線番号

extension.js

Extensionでは、バックグラウンドページでextension.jsを実行します。 このファイルはリクエストの処理を担当します。 バックグラウンドページに関する詳細については、Chromeのドキュメントを参照してください。

manifest.json

Every extension requires a manifest.json file. This file grants our extension access to Chrome's Tab and DesktopCapture APIs and controls which web apps can send messages to our extension. For more information on manifest.json, refer to Chrome's documentation on the manifest file format; otherwise, feel free to tweak the example provided here. Note that we've included "://localhost/" in our manifest.json's "externally_connectable" section. This is useful during development, but you may not want to publish your extension with this value. Consider removing it once you're done developing your extension.

{
  "manifest_version": 2,
  "name": "your-plugin-name",
  "version": "0.10",
  "background": {
    "scripts": ["extension.js"]
  },
  "externally_connectable": {
    "matches": ["*://localhost/*", "*://*.example.com/*"]
  },
  "permissions": [
    "desktopCapture",
    "tabs"
  ]
}

画面をリクエストする

開発中のWebアプリケーションにはChromeのsendMessage APIを使用してExtensionに"getUserScreen"リクエストを送信するヘルパー関数、getUserScreenを定義します。 リクエストが成功すると、streamIdを含む"success"レスポンスが返されます。 レスポンスコールバックはstreamIdgetUserMediaに渡し、ひととおりうまくいった場合は関数はユーザーの画面を表すMediaStreamに解決するプロミスを返します。

/**
 * Get a MediaStream containing a MediaStreamTrack that represents the user's
 * screen.
 * 
 * This function sends a "getUserScreen" request to our Chrome Extension which,
 * if successful, responds with the sourceId of one of the specified sources. We
 * then use the sourceId to call getUserMedia.
 * 
 * @param {Array<DesktopCaptureSourceType>} sources
 * @param {string} extensionId
 * @returns {Promise<MediaStream>} stream
 */
function getUserScreen(sources, extensionId) {
  const request = {
    type: 'getUserScreen',
    sources: sources
  };
  return new Promise((resolve, reject) => {
    chrome.runtime.sendMessage(extensionId, request, response => {
      switch (response && response.type) {
        case 'success':
          resolve(response.streamId);
          break;

        case 'error':
          reject(new Error(error.message));
          break;

        default:
          reject(new Error('Unknown response'));
          break;
      }
    });
  }).then(streamId => {
    return navigator.mediaDevices.getUserMedia({
      video: {
        mandatory: {
          chromeMediaSource: 'desktop',
          chromeMediaSourceId: streamId,
          // You can provide additional constraints. For example,
          maxWidth: 1920,
          maxHeight: 1080,
          maxFrameRate: 10,
          minAspectRatio: 1.77
        }
      }
    });
  });
}

Connecting to a Room with Screen Sharing

Assume for the moment that we know our extension's ID and that we want to request the user's screen, window, or tab. We have all the information we need to call getUserScreen. When the Promise returned by getUserScreen resolves, we need to use the resulting MediaStream to construct the LocalVideoTrack object we intend to use in our Room. Once we've constructed our LocalVideoTrack representing the user's screen, we have two options for publishing it to the Room:

  1. We can provide it in our call to connect, or
  2. We can add it after connecting to the Room using addTrack.

Finally, we'll also want to add a listener for the "stopped" event. If the user stops sharing their screen, the "stopped" event will fire, and we may want to remove the LocalVideoTrack from the Room. We can do this by calling removeTrack.

const { connect, LocalVideoTrack } = require('twilio-video');

// Option 1. Provide the screenLocalTrack when connecting.
async function option1() {
  const stream = await getUserScreen(['window', 'screen', 'tab'], 'your-extension-id');
  const screenLocalTrack = new LocalVideoTrack(stream.getVideoTracks()[0]);

  const room = await connect('my-token', {
    name: 'my-room-name',
    tracks: [screenLocalTrack]
  });

  screenLocalTrack.once('stopped', () => {
    room.localParticipant.removeTrack(screenLocalTrack);
  });

  return room;
}

// Option 2. First connect, and then add screenLocalTrack.
async function option2() {
  const room = await connect('my-token', {
    name: 'my-room-name',
    tracks: []
  });

  const stream = await getUserScreen(['window', 'screen', 'tab'], 'your-extension-id');
  const screenLocalTrack = new LocalVideoTrack(stream.getVideoTracks()[0]);

  screenLocalTrack.once('stopped', () => {
    room.localParticipant.removeTrack(screenLocalTrack);
  });

  room.localParticipant.addTrack(screenLocalTrack);
  return room;
}

リクエストを処理する

今回開発するExtensionは、WebアプリケーションがこのExtensionにメッセージを送信した際に呼び出されるChromeのonMessageExternalイベントをリッスンします。 イベントリスナーでは、リクエストを処理する方法を決定するためのメッセージタイプ(type)を切り替えます。 この例では、"getUserScreen"リクエストのみ関知していますが、認識されないレスポンスに対してdefault caseも含めています。

chrome.runtime.onMessageExternal.addListener((message, sender, sendResponse) => {
  switch (message && message.type) {
    // Our web app sent us a "getUserScreen" request.
    case 'getUserScreen':
      handleGetUserScreenRequest(message.sources, sender.tab, sendResponse);
      break;

    // Our web app sent us a request we don't recognize.
    default:
      handleUnrecognizedRequest(sendResponse);
      break;
  }

  return true;
});

"getUserScreen"リクエスト

Extensionには、"getUserScreen"リクエストに応答するヘルパー関数、handleGetUserScreenRequestを定義します。 この関数はChromeのchooseDesktopMediaAPIをsourcesをともなって呼び出し、リクエストが成功すると、streamIdを含む成功レスポンスを送信します。 その他の場合はエラーレスポンスを返します。

/**
 * Respond to a "getUserScreen" request.
 * @param {Array<DesktopCaptureSourceType>} sources
 * @param {Tab} tab
 * @param {function} sendResponse
 * @returns {void}
 */
function handleGetUserScreenRequest(sources, tab, sendResponse) {
  chrome.desktopCapture.chooseDesktopMedia(sources, tab, streamId => {
    // The user canceled our request.
    if (!streamId) {
      sendResponse({
        type: 'error',
        message: 'Failed to get stream ID'
      });
    }

    // The user accepted our request.
    sendResponse({
      type: 'success',
      streamId: streamId
    });
  });
}

認識されないリクエスト

万全を期すため、関知しないリクエストについても処理します。 メッセージを理解不能なtypeをともなって受信したり、typeそのものが含まれない場合は、ExtensionのhandleUnrecognizedResponse関数は下記のエラーレスポンスを送信します。

{
  "type": "error",
  "message": "Unrecognized request"
}

handleUnrecognizedRequestの実装

/**
 * Respond to an unrecognized request.
 * @param {function} sendResponse
 * @returns {void}
 */
function handleUnrecognizedRequest(sendResponse) {
  sendResponse({
    type: 'error',
    message: 'Unrecognized request'
  });
}

Extensionを発行する

最後に、Webアプリケーションと拡張機能を構築してテストしたら、Webアプリケーションのユーザーが新しい画面キャプチャー機能を利用できるようにChrome Web Storeに拡張機能を公開します。 詳細については、Chromeのドキュメントをご参照ください。

Rate this page:

ヘルプが必要ですか?

誰しもが一度は考える「コーディングって難しい」。そんな時は、お問い合わせフォームから質問してください。 または、Stack Overflow でTwilioタグのついた情報から欲しいものを探してみましょう。