JavaScriptとTwilio Programmable Videoによる画面共有

July 15, 2020
執筆者
レビュー担当者

Screen Sharing with JavaSCript and Twilio Programmable Video

この記事はMiguel Grinbergこちら(英語)て執筆した記事を日本語化したものです。

Twilio Programmable Videoを用いてWebRTC規格に基づき、カスタマイズされたビデオチャットアプリケーションを構築することができます。この記事では、JavaScriptで構築されたブラウザベースのProgrammable Videoアプリケーションに、画面共有オプションを追加する方法をご紹介します。

Screen Sharing on Twilio Video

チュートリアルの要件

このチュートリアルでは、以前の入門チュートリアルにおいてJavaScriptとPythonで構築したビデオチャットアプリケーションに、画面共有機能を追加します。このアプリケーションをお使いのコンピューターで実行するには、以下の要件を満たす必要があります。

  • Python 3.6以降。お使いのオペレーティングシステムにPythonインタープリターがない場合には、python.orgから、インストーラをダウンロードしてください。
  • 無料または有料のTwilioアカウント。Twilioを使用するのが初めての場合には、今すぐ無料アカウントを取得してください。このリンクを使用すると、アップグレード時に$10受け取ることができます。
  • Twilio Programmable Video JavaScriptライブラリと互換性を持つWebブラウザ(以下のリストを参照)。この要件は、このアプリケーションの構築後、招待されて利用するユーザーにも適用されます。

サポートされるWebブラウザ

このプロジェクトの中心となるビデオとオーディオの機能は、Twilio Programmable Videoから提供されるため、(以下に示す)サポート対象のWebブラウザのいずれかを使用する必要があります。

  • Android: Chrome、Firefox
  • iOS: Safari
  • Linux: Chrome、Firefox
  • macOS: Chrome、Firefox、Safari、Edge
  • Windows: Chrome、Firefox、Edge

ビデオ通話をサポートするブラウザのリストは非常に広範であり、すべてが画面共有トラックを表示できますが、画面共有セッションを起動する機能があるブラウザは、ごく一部に限られています。特に、モバイルブラウザでこの機能を持つものはありません。デスクトップでは、以下のバージョンが必要です。

  • Chrome 72以上
  • Firefox 66以上
  • Safari 12.2以上

サポートされるWebブラウザの最新リストについてはProgrammable Videoのドキュメントを参照してください。特に、この機能をサポートするブラウザのバージョンについては、「Screen Capture」(スクリーンキャプチャ)ページを参照してください。

チュートリアルアプリケーションのインストールと実行

まず、サンプルアプリケーションを設定します。このアプリケーションはGitHubにあります。gitクライアントがインストールされている場合は、以下のようにダウンロードできます。

$ git clone https://github.com/miguelgrinberg/flask-twilio-video

このリポジトリのmasterブランチには、画面共有機能をサポートするすべてのコードがすでに含まれています。このチュートリアルに従いコーディングする予定の場合には、以下のコマンドを使用し、only-video-sharingブランチに切り替えます。

$ git checkout only-video-sharing

gitクライアントがインストールされていない場合は、完全なアプリケーションをzipファイルでダウンロードできます。または、チュートリアルに従いコーディングする場合には、ビデオ通話部分のみをダウンロードします。

Python仮想環境の作成

コードのダウンロードと設定ができたら、仮想環境を作成し、そこにPython依存関係をインストールします。

UnixまたはmacOSシステムを使用している場合は、ターミナルを開き、プロジェクトディレクトリに移動し、以下のコマンドを入力します。

$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install -r requirements.txt

Windowsでチュートリアルを実行する場合には、コマンドプロンプトウィンドウに以下のコマンドを入力します。

$ python -m venv venv
$ venv\Scripts\activate
(venv) $ pip install -r requirements.txt

最後のコマンドでは、pip(Pythonパッケージインストーラ―)を使用し、このアプリケーションにより使用されるPythonパッケージ群をインストールします。パッケージは次のとおりです。

  • Twilio APIと連携するためのTwilio Python Helperライブラリ 
  • Webアプリケーションを作成するためのFlaskフレームワーク
  • .envファイルの内容を環境変数としてインポートするpython-dotenv 
  • アプリケーションの開発バージョンを一時的にインターネットで公開するためのpyngrok

Twilioアカウントの設定

このアプリケーションは、アカウントに関連付けられた認証情報を使用し、Twilioサービスに対して認証を行う必要があります。特に必要になるのが、Account SID、APIキーSID、対応するAPIキーシークレットです。これらの認証情報の設定取得方法が分からない場合には、ビデオ共有チュートリアルの「Twilioアカウントの設定」セクションを参照することをお勧めします。

アプリケーションには、.env.templateというファイルがあり、ここに必要な3つの構成変数が入っています。このファイルのコピーを作成し、.env(ピリオドenv)と名前を付けて、以下のように編集します。

TWILIO_ACCOUNT_SID="<enter your Twilio account SID here>"
TWILIO_API_KEY_SID="<enter your Twilio API key here>"
TWILIO_API_KEY_SECRET="<enter your Twilio API secret here>"

アプリケーションの実行

これで、アプリケーションの実行準備ができました。仮想環境が有効であることを確認したら、以下のコマンドを使用してWebブラウザを起動します。

(venv) $ flask run
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 274-913-316

これでアプリケーションが実行されていますが、同じコンピューターを送信元とするローカル接続を受けることしかできません。一時的な公開URLを割り当てて、電話や別のコンピューターから接続できるようにするため、ngrokを使用します。これはすでに、Python仮想環境の一部としてすでにインストールされています。ngrokを起動するには、2番目のターミナルウィンドウを開き、仮想環境を有効にし(source venv/bin/activateまたはvenv\Scripts\activate、オペレーティングシステムによる)、以下のコマンドを入力します。

(venv) $ ngrok http 5000

2番目のターミナルに、次のような画面が表示されます。

ngrok

ngrokは、サーバーに公開URLを割り当てます。「Forwarding」(転送)キーの値を探して、どのようなものか見てみます。https://で始まるURLを使用します。多くのブラウザは暗号化されていないサイトがカメラやマイクにアクセスすることを許可しないからです。上記のサンプルでは、公開URLはhttps://bbf1b72b.ngrok.ioです。皆さんの値もこれに似たものになりますが、ドメインの最初の部分はngrokを実行するたびに違うものになります。

Flaskサーバーとngrokの両方をコンピューターで実行している間は、ngrokからの公開https:// URLを使用し、別のコンピューターやスマホなどの外部ソースからサーバーに接続できます。

このアプリケーションのさまざまな側面の詳細については、最初のチュートリアルをご覧ください。

getDisplayMedia APIの紹介

ユーザー画面のビデオストリームをキャプチャするには、ブラウザのgetDisplayMedia APIを使用します。ルームがroom変数に格納されている場合には、以下のコードスニペットを使用することにより画面共有セッションを開始し、それをルームに公開できます。

getDisplayMedia()コールは、ユーザーに何を共有するか選択するよう求めます。この選択肢の実装は、Webブラウザにより提供されます。以下はChromeでの例です。

screen preview

ここで、ユーザーが共有するものとして選択できるのは、画面全体、1つのウィンドウ、または1つのブラウザタブです。選択が行われると、ビデオトラックが作成され、通話に公開されます。この時点で、他の参加者全員が、trackSubscribedイベントを受信します。これは、参加者のビデオトラックが公開されるときにアプリケーションに警告するイベントと同じです。

画面共有を停止するには、通話からトラックの公開を停止し、ビデオトラックを停止する必要があります。これは以下のコードで行います。

        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;

Twilio Programmable Video APIによる画面共有の詳細については、ドキュメントをご覧ください。

レイアウトの改善

アプリケーションに画面共有を組み込む前に、ページレイアウトにいくつか変更を加える必要があります。1つは[Share Screen](画面共有)ボタンの追加です。これは、[Join call](通話に参加)ボタンの横に配置します。

participants

現在のレイアウトでは、各参加者はビデオトラックを1つ持ち、その下に名前が表示される設定です。ある参加者が画面共有トラックを1つ追加すると、両方のトラックまたがるように名前の幅が広がります。1参加者が1画面を共有していることを明確にするため、名前を表示する<div>エレメントに背景色を追加します。以下は、ある参加者がカメラのみを共有する場合の例です。

screen sharing - self video

参加者がビデオに加え画面も共有し始めると、名前は、両方のトラックにまたがり、その中央に表示されます。背景色により、誰がどの画面トラックに対応するかが分かりやすくなります。

screen sharing - multi screens

これらの変更を行いましょう。まず、ベースのHTMLページに画面共有ボタンを追加します。以下は、*templates/index.html*ファイルの更新バージョンです。コードのすべてを以下と交換できます。

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
    </head>
    <body>
        <h1>Flask & Twilio Video Conference</h1>
        <form>
            <label for="username">Name: </label>
            <input type="text" name="username" id="username">
            <button id="join_leave">Join call</button>
            <button id="share_screen" disabled>Share screen</button>
        </form>
        <p id="count">Disconnected.</p>
        <div id="container" class="container">
            <div id="local" class="participant"><div></div><div class="label">Me</div></div>
            <!-- more participants will be added dynamically here -->
        </div>

        <script src="//media.twiliocdn.com/sdk/js/video/releases/2.3.0/twilio-video.min.js"></script>
        <script src="{{ url_for('static', filename='app.js') }}"></script>
    </body>
</html>

画面共有ボタンは無効化された状態で追加することに注意してください。これは、この機能を使用するには、その前に通話に参加する必要があるためです。

新しいボタンに加え、クラスlabelを、参加者名を含む<div>エレメントに追加しました。こうすると、CSSファイルで背景色を追加しやすくなります。

以下は、更新されたstatic/styles.cssファイルです。新しいラベル背景色と少々のクリーンアップが行われています。

.container {
    margin-top: 20px;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}
.participant {
    margin-bottom: 5px;
    margin-right: 5px;
}
.participant div {
    text-align: center;
}
.participant div video {
    width: 240px;
    height: 180px;
    background-color: #ccc;
    border: 1px solid black;
}
.participant .label {
    background-color: #ddd;
}

labelクラスも、static/app.jsファイルのparticipantConnected()関数で各参加者に追加する必要があります。

function participantConnected(participant) {
    // ...
    var labelDiv = document.createElement('div');
    labelDiv.setAttribute('class', 'label');
    labelDiv.innerHTML = participant.identity;
    participantDiv.appendChild(labelDiv);
    // ...

画面共有セッションの開始

App.jsファイルで画面共有を実装する準備ができました。ファイルの上部で、新しいボタンのインスタンスを追加し、ローカルの画面共有トラックを保持する新しい変数を追加します。

const shareScreen = document.getElementById('share_screen');
var screenTrack;

次に、一番下の行において、ハンドラーとこのボタンのclickイベントとを関連付けします。

shareScreen.addEventListener('click', shareScreenHandler);

次に、ファイル内の任意の場所に、画面共有ハンドラーを追加します。

function shareScreenHandler() {
    event.preventDefault();
    if (!screenTrack) {
        navigator.mediaDevices.getDisplayMedia().then(stream => {
            screenTrack = new Twilio.Video.LocalVideoTrack(stream.getTracks()[0]);
            room.localParticipant.publishTrack(screenTrack);
            shareScreen.innerHTML = 'Stop sharing';
            screenTrack.mediaStreamTrack.onended = () => { shareScreenHandler() };
        }).catch(() => {
            alert('Could not share the screen.');
        });
    }
    else {
        room.localParticipant.unpublishTrack(screenTrack);
        screenTrack.stop();
        screenTrack = null;
        shareScreen.innerHTML = 'Share screen';
    }
};

screenTrack変数を使用していますが、その目的は、ビデオトラックを保持するというだけでなく、画面共有が有効かどうかを知る方法でもあります。この変数の値がfalseと見なされる場合には、画面共有が有効ではないことが分かるため、上記で説明した方法で、新しいセッションを開始します。さらに、ボタンのラベルを「Stop sharing」(共有停止)に変更します。

また、画面共有トラックにonendedイベントも設定します。一部のブラウザは、画面共有セッションを終わらせる、独自のユーザーインターフェイスを提供しています。Chromeでは、次のような浮動型のウィジェットが表示されます。

screen sharing - notification

[Hide](非表示)ボタンをクリックしてストリームを停止すると、画面共有が終了します。ただし、アプリケーションとTwilio Video APIは画面共有が終了したことを知らないため、フリーズした、すなわち黒い画像のトラックをすべての参加者に提示し続けます。onendedイベントは、ユーザーがこの方法でストリームを終了した場合に、コールバックを受け取る1つの方法です。適切なクリーンアップを行うには、ハンドラー関数にコールバックを送信だけですみます。

最後に設定する変更は、画面共有ボタンの状態です。まず、無効の状態から開始します。参加者が通話に接続したらこのボタンを有効にし、切断時に無効にします。

function connectButtonHandler(event) {
    event.preventDefault();
    if (!connected) {
        // ...
        connect(username).then(() => {
            // ...
            shareScreen.disabled = false;
        }).catch(() => {
        // ...
       });
    }
   else {
        disconnect();
        # ...
        shareScreen.innerHTML = 'Share screen';
        shareScreen.disabled = true;
    }
};

画面共有セッションが開始したら、ラベルを「Stop sharing」(共有の停止)に更新します。参加者の接続が切断されたら、これをリセットする必要があります。

これらの変更を終えると、基本的な画面共有機能は完成です。ngrokとともにアプリケーションを実行し、最低でも2つの異なるブラウザウィンドウ(同じデバイスまたは別のデバイスの)からビデオ通話に接続し、片方からもう片方へ画面共有を試します。

全画面機能の追加

ビデオを共有する際、大きなビデオトラックは重要な問題ではありません。ただし、画面を共有する際には、小さなサムネイルサイズのビデオトラックを表示すると、ほとんどのテキストが読めなくなります。画面共有を使用しやすくするには、クリックするだけでビデオトラックを全画面にできる拡大機能を追加できます。

Programmable Video - Full screen

ビデオトラックを拡大するには、そのトラックに、participantZoomedという新しいCSSクラスを割り当てると同時に、他のトラックにparticipantHiddenクラスを割り当てます。以下は、これらの新しいクラスが割り当てられたstatic/styles.cssファイルです。

.container {
    margin-top: 20px;
    width: 100%;
    display: flex;
    flex-wrap: wrap;
}
.participant {
    margin-bottom: 5px;
    margin-right: 5px;
}
.participant div {
    text-align: center;
}
.participant div video {
    background-color: #ccc;
    border: 1px solid black;
}
.participant div video:not(.participantZoomed) {
    width: 240px;
    height: 180px;
}
.participant .label {
    background-color: #ddd;
}
.participantZoomed {
    position: absolute;
    top: 0px;
    left: 0px;
    width: 100%;
    height: 100%;
}
.participantHidden {
    display: none;
}

次に、clickイベントハンドラーをすべてのトラックに追加する必要があります。ローカルのビデオトラックについては、addLocalVideo()関数を変更します。

function addLocalVideo() {
    Twilio.Video.createLocalVideoTrack().then(track => {
        var video = document.getElementById('local').firstChild;
        var trackElement = track.attach();
        trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
        video.appendChild(trackElement);
    });
};

他の参加者のビデオトラックについては、trackSubscribed()関数にハンドラーを追加します。

function trackSubscribed(div, track) {
    var trackElement = track.attach();
    trackElement.addEventListener('click', () => { zoomTrack(trackElement); });
    div.appendChild(trackElement);
};

以下は、zoomTrack()ハンドラーです。

function zoomTrack(trackElement) {
    if (!trackElement.classList.contains('participantZoomed')) {
        // zoom in
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.add('participantZoomed')
                    }
                    else {
                        track.classList.add('participantHidden')
                    }
                });
                participant.childNodes[1].classList.add('participantHidden');
            }
        });
    }
    else {
        // zoom out
        container.childNodes.forEach(participant => {
            if (participant.className == 'participant') {
                participant.childNodes[0].childNodes.forEach(track => {
                    if (track === trackElement) {
                        track.classList.remove('participantZoomed');
                    }
                    else {
                        track.classList.remove('participantHidden');
                    }
                });
                participant.childNodes[1].classList.remove('participantHidden');
            }
        });
    }
};

拡大の手続きは、container divのすべてのエレメント(通話の参加者)で反復されます。各参加者について、そのトラックで反復されます。participantZoomedは選択されたトラックに適用され、その他のトラックにはparticipantHiddenが適用されます。hiddenクラスも、参加者の名前を保持している<div>エレメントに適用されます。縮小プロセスは、同じプロセスが反対の順序で行われます。

これらの変更から生じる厄介な問題は、現在全画面のトラックが、通話から公開停止された場合です。この場合には、トラックを停止する前に、縮小の手続きを行う必要があります。これはtrackUnsubscribed()ハンドラーで実行できます。

function trackUnsubscribed(track) {
    track.detach().forEach(element => {
        if (element.classList.contains('participantZoomed')) {
            zoomTrack(element);
        }
        element.remove()
    });
};

これで画面共有機能は完成です。

まとめ

このチュートリアルでは、Twilio Programmable Videoアプリケーションに画面共有機能を追加しました。

Chrome、Firefox、Safari以外のブラウザまたはこれらのブラウザの旧バージョンによる画面共有をサポートする必要がある場合には、Phil Nashが便利なチュートリアルを書いています。

皆さんが構築したビデオチャットアプリケーションについてお聞かせください。

Miguel Grinbergは、TwilioのTechnical Content担当Python Developerです。Twilioブログでは読者の皆様のPythonプロジェクトを紹介します。ぜひ、mgrinberg [at] twilio [dot] comまでご連絡ください。