まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:javascript

Google Fit は Google のヘルスケア関連プラットフォームと、その SDK を提供しています:
https://developers.google.com/fit/

2020062600


Google Fit SDK は Android 標準アプリである Google Fit を操作するものです。簡単に言えば「Android の歩数カウント情報を取得する」ための SDK といえます。

この SDK の1つがブラウザ用の JavaScript で提供されており、ウェブアプリケーションに組み込んで使うことが可能です。データを取得するまでを実際に作ってみたので、その準備手順を含めてまとめてみました。


【Google APIs Console へのプロジェクト登録】
この Google Fit SDK を使う上で最初に必要な作業は Google APIs Console へプロジェクトを登録して Client ID を取得することです(SDK は Client ID を指定して動かすことになります)。
https://console.developers.google.com/flows/enableapi?apiid=fitness&pli=1

Fitness API を使うことになるアプリケーションプロジェクトとして、既存のプロジェクトを流用するか新たにプロジェクトを作成します。そして「使用する API」は「Fitness API」を選択し、「使用する場所」は「ウェブサーバー」を選択、更に「アクセスするデータの種類」は「ユーザーデータ」を選択します。最後に「必要な認証情報」をクリックして、クライアント ID を取得します:
2020062601


次の画面で OAuth 認証用のドメイン登録をします。「承認済みの JavaScript 生成元」と「承認済みのリダイレクト URI」に以下のアプリを動かす URI を指定します。ローカルで動作確認する場合であれば両方に "http://localhost:8080" を追加して保存します※:
2020062602

※以下で紹介するサンプルを https://localhost:8080/ という URL にアクセスして参照する場合の設定です。異なるホスト名やポート番号を使う場合は適宜変更してください。

これで Google APIs Console 側の準備は完了です。



【サンプルアプリケーションで動作確認】
動作確認用の Node.js 向けサンプルアプリケーションを以下に公開しておきました。Node.js がインストール済みの環境であれば実際に動かして動作を確認することが可能です:
https://github.com/dotnsf/google_fit


zip をダウンロードして展開するか、git clone して、自分の PC 内にプロジェクトファイルを展開します。実質的に public/index.html ファイルを 8080 番ポートで公開するだけの Node.js アプリケーションです。

実際に動作させる前に、public/index.html ファイルをテキストエディタで開き、8行目の client_id 変数の文字列値(初期値は 'your_client_id' )を上記で取得したクライアント ID の文字列値に書き換えて保存します:
2020062609


これでサンプルアプリケーションを動作させる準備ができました。以下のコマンドを実行してアプリケーションを稼働状態にします:
$ cd google_fit

$ npm install

$ node app


稼働状態になったアプリケーションは 8080 番ポートでリクエストを待ち受けています。ウェブブラウザで http://localhost:8080/ にアクセスしてみます。正しく動作していると最初に OAuth 認証が実行されるので、Android スマホで利用しているものと同じ Google アカウントでログインします:
2020062604


Google アカウントを選択してログインし、正しく処理が実行されると(このコードでは)2019/03/01 以降の歩数記録を取得します。何種類かの dataSource ごとの記録が表示されるはずです:
2020062608


どれか1つの dataSource をクリックすると、その dataSource に関して更に詳しく日時やその時の歩数が表示されます:
2020062609


↑自分は Android をメインスマホにしていないため、歩数が少ない・・・



実際に使っているコードは OAuth 認証のための authorize 関数と、そのコールバック先で実行している dataSource 一覧のフェッチ、そして各 dataSource ごとの歩数カウントのためのデータセットのフェッチです。JavaScript 自体はかなりシンプルになっていると感じました。



(参考)
https://qiita.com/feb19/items/383848119ba1bdfe5110


人気のテキストエディタである Visual Studio Code(以下 "VSCode")の機能を独自に拡張するプラグインの開発に挑戦してみました。以下で紹介するプラグインの開発は主にこのページにかかれている内容を参照し、参考にさせていただきました。ありがとうございます:
Visual Studio Codeの拡張機能を一通り触って自分用に公開するまで


作った拡張機能は MOTD(Manhole of this day) といいます:
https://marketplace.visualstudio.com/items?itemName=dotnsf.manholeofthisday

2020062305


機能は名前そのままですが、拙作マンホールマップの機能の1つである「今日のマンホール」を VSCode 内に表示する、というマンホーラー向けのものです。

VSCode にこのプラグインをインストールするには左ペインから「拡張機能」を選ぶか、Ctrl + Shift + X を押して拡張機能画面に移動し、検索バーに "manholeofthisday" と入力します("manholeof" あたりまで入力すると、検索候補が一つになります)。そして「インストール」と書かれた箇所をクリックしてインストールします:
2020062301


インストール後、実際に MOTD 機能を利用する場合は Ctrl + Shift + P でコマンドパレットを開き、このパレットに "MOTD" と入力してコマンドを検索し、最後に見つかった MOTD 部分をクリックします:
2020062302


MOTD コマンドを実行した日(月&日)のマンホールがマンホールマップの「本日のマンホール」に記録されて存在していた場合は VSCode 内の新しいタブが一つ追加され、その中で「本日のマンホール」画像と説明が表示されます。なおこの機能の実現には Webview API を使っています:
2020062303


画像部分をクリックすると、ブラウザで該当マンホールのマンホールマップ内ページが開き、より詳しい情報を参照することができる、というものです:
2020062304



この MOTD プラグインのソースコードはこちらで公開しています。開発言語として TypeScript を選ぶこともできましたが、今回は JavaScript で開発しました。この手軽さもハードル低くていいですね:
https://github.com/dotnsf/manholeofthisday

 

このエントリの続きです:
Leaflet.js の UI で画像を表示する(1)


地図操作ライブラリの Leaflet.js の応用で、地図以外のオブジェクトを Leaflet の UI で表示する方法を紹介しています。前回は imageOverlay を使って画像を表示しましたが、今回は canvasOverlay を使ってキャンバス(HTML5 の <canvas>)を操作する方法を紹介します。比較の意味も含めて、表示内容そのものは前回同様に画像としますが、画像を画像のまま表示するのではなく、画像をキャンバスに描画した上で、更にキャンバスに描画を加える、という内容を紹介します。必要に応じて前回の内容も参照しながら見てください。

なお今回も表示対象画像は「いらすとや」「家にいるアマビエ」の画像とします:
amabie_stay_home


また前回紹介した内容は Leaflet.js の標準機能だけで実現しましたが、今回はアドイン機能である L.CanvasLayer を併用して実現します。以下のサイトからあらかじめ L.CanvasLayer.js をダウンロードしておいてください:
https://raw.githubusercontent.com/Sumbera/gLayers.Leaflet/master/L.CanvasLayer.js


ダウンロードできたら以下のような HTML を用意します:
<script src="./L.CanvasLayer.js"></script>

<div id="demoMap"></div>

<script>
//. 空の地図を OpenStreetMap データで表示
map = L.map('demoMap', { dragging: true, zoomControl: true, minZoom: -5, maxZoom: 5, crs: L.CRS.Simple, preferCanvas: true } );

//. 表示する画像のパスとサイズ
var image = {
  url: './3600x3600.png',
  width: 3600,
  height: 3600
};

//. 画像領域を地図に設定
var imageBounds = L.latLngBounds([
  map.unproject( [ 0, image.height ] ),
  map.unproject( [ image.width, 0 ] )
]);
map.fitBounds( imageBounds );
map.setMaxBounds( imageBounds.pad( 0.5 ) );

//. 画像をオーバーレイで設定
L.imageOverlay( image.url, imageBounds ).addTo( map );

//. キャンバスレイヤーを設定
L.canvasLayer().delegate( this ).addTo( map );
      
//. キャンバスの再描画イベントハンドラ
function onDrawLayer( info ) {
  var canvas = info.canvas;
  var ctx = canvas.getContext( '2d' );

  var colors = [
    "rgba(0,0,0,0.2)",
    "rgba(255,0,0,0.2)"
  ];
  var points = [
    [ -1000, 2000 ],
    [ -2000, 2250 ]
  ];

  ctx.clearRect( 0, 0, canvas.width, canvas.height );

  //. ズームレベルに合わせて arc の描画半径を調整
  var zoom = map.getZoom();
  var r = 30 * Math.pow( 2, zoom );

  for( var i = 0; i < points.length; i++ ){
    var point = points[i];
    if( info.bounds.contains( [ point[0], point[1] ] ) ){
      var p = info.layer._map.latLngToContainerPoint( [ point[0], point[1] ] );
      ctx.beginPath();
      ctx.fillStyle = colors[i];
      ctx.arc( p.x, p.y, r, 0, Math.PI * 2 );
      ctx.fill();
      ctx.closePath();
    }
  }
}
</script>



まず L.CanvasLayer.js を読み込みます。そして L.imageOverlay を使って画像(3600x3600.png)をロードするのですが、ここまでは前回紹介したものと同様です。

その後で L.canvasLayer() でキャンバスをレイヤーとして扱います。これでキャンバスの再描画イベントハンドラである onDrawLayer() をカスタマイズすることで画像に更にグラフィックをカスタマイズすることが可能になります。

このコードが実際に動いているワーキングデモページはこちらです:
https://dotnsf.github.io/leaflet_overlay2/


上記 URL をブラウザで開くと、前回同様「家にいるアマビエ」の画像が Leaflet の UI で表示されます:
2020050401


左上のズームコントローラーやスマホのピンチイン/アウト操作で画像が拡大縮小されます。実は前回と少しだけ違う点があることに気付く方はいらっしゃるでしょうか?:
2020050402


よく見ると窓とアマビエの右目尻に丸いオブジェクトが描画されています。これはゴミではなく、画像が表示されている Canvas の上にグラフィックコンテキストを使って(JavaScript で)描画されたものです:
2020050403


画面を更に拡大/縮小しても、ズームレベルに合わせて表示されます:
2020050404


この部分は上記コードの OnDrawLayer 関数内で処理されています。キャンバスが再描画されるタイミングで呼び出されますが、パラメータ(info)の info.canvas で表示されている <canvas> 要素を取得することができます。なので後は info.canvas.getContext( '2d' ) でグラフィックコンテキストを取得した後にここなどを参照して描画すると上述のように元の画像の上に更にグラフィックを追加した上で Leaflet の UI で表示できるようになる、というものです。


<canvas> をある程度使ったことがあると、この方法が使えるのは本当に便利です。自由に描画したキャンバスを Leaflet.js の UI で表示できるようになるため、使いみちが広がります。

かなり自分向けですが、タイトル通りのウェブサービスを作ってみました。


【背景】
まずこのサービスを作った背景です。COVID-19 による在宅勤務やリモート会議が主流となる中で、ZOOM や WebEx などのウェブ会議サービスが多く使われるようになりました。このウェブ会議、サービスによって機能の違いはありますが、概ね以下のような機能を持っています:
・PC やスマホを使い、インターネット経由で仮想的な会議室に入って会議する
・音声は全員でリアルタイムに共有する
フロントカメラを使うことで参加者全員の表情を確認しながら進めることができる
ホスト役の PC 画面を全員で共有しながら会議できる(画面を共有する PC は後から変更可)

video_kaigi


これらの特徴によって、情報共有や伝達事項など「通知」目的のリモート会議であれば目的はほぼ達成できると思っています。通知したい人の画面を使ってプレゼンテーションを行い、その様子を他の参加者が音声と一緒にリアルタイムに確認し、質問なども行うことができるからです。
2020042601


一方、この形式ではまだリモート開催が難しい会議形態もあります。情報共有や伝達事項ではなく、ハンズオントレーニングなど「参加型」目的の場合です。ホスト役の人の画面を使ってハンズオン資料や作業内容を共有するところまでは何も変わりませんし、その様子を見ながら参加者が何の問題もなく作業を進めていける場合や、問題が発生しても自身で解決できる場合は問題になることもありません。Google Drive や Visual Studio Code Live Share のような仕組みを使うことで、全員で1つのファイルを同時に編集することはできます。この形がとれる場合においては音声と併用することでリモートサポートも可能になる範囲だと思っています。

ただローカル PC を使って作業する場合、特に参加者のスキルが必ずしも充分でない場合や、そうでなくても想定外のエラーが発生してしまった場合など、ウェブ会議では解決が途端に難しくなってしまいます。オンラインのウェブ会議ではなく、一箇所に(物理的に)集まって行う形式であれば、その場で本人の画面を覗き込めばエラー内容を確認できます。そのエラー内容が入力ミスなどのケアレスミスであれば正しい入力内容を指摘してあげるだけで解決できます。一方、入力内容が正しいのに想定外のエラーが発生しているようであれば、エラーメッセージやログ内容を参考にググったりして調べることになると思うのですが、画面が見えない限り、その場合分けすらも難しいという問題があります。また「エラーメッセージやログ内容を参考にググったり・・」と書きましたが、ウェブ会議で参加者のエラーメッセージやログ内容を正確に知るのは(その参加者のスキルレベルにもよりますが)かなり面倒です。実際にはエラー画面を共有してもらえればいいのですが、参加者がその手順を理解しているとは限りませんし、PC 画面を参加者全員に知られることに抵抗を感じる人もいると思われます。またその作業中は自分の(ホストの)画面を共有できなくなるので他の参加者の作業が止まってしまう可能性も危惧する必要があります。

更には、そもそもまったくセミナーに付いてこれない人がいて、序盤で躓いたまま全然進められていなかったりした場合でも、ウェブ会議のホスト側ではそのような人が存在していることに気付くことすらかなり困難です。
2020042602


比較的初心者を対象とするようなハンズオン勉強会を行うケースなど、上述のようなケースが想定されるオンライン会議をスムーズに行うには既存のウェブ会議システムだけでは難しいのではないか、、と考えていました。


【解決策の仮説】
上述のようなケースを解決するにはどうすればよいか? この問に対する明確な解答ではないのですが、1つ仮説を考えました:
従来のウェブ会議の逆を行うシステムがあれば解決するのではないか?

つまりホストの画面を参加者全員で共有するのではなく、参加者全員の画面の様子をホストが見れるようになっていれば、参加者側の操作でエラーが起こってもホストからエラーメッセージを確認できるし、作業についていけなくなったとしてもその様子をホスト側が確認できるのではないか?

この内容はあくまで仮説ですが、理屈の上では問題が解決しそうな気もしています。 この仮説を検証するための仕組みとして、ウェブ会議の逆(ホスト以外の参加者全員の画面をホストと共有する)を実現できる検証サービスを作ってみました。まだ使い勝手に難があることも理解していますが、仮説を検証するための機能までは実装できていると思っていて、特別なアプリケーションのインストールも必要としない形で実現できているので検証用に公開します。


【作ったサービス】
公開している URL は以下で紹介しますが、オープンソースとしても公開しています。ソースコードはこちらです:
https://github.com/dotnsf/screen_collect

また、このサービスを利用するには Windows または macOS で比較的最近の Chrome ブラウザを利用している必要があります。後述する MediaDevices インターフェースを使って実装している関係で、このインターフェースをサポートしていないスマホやタブレットのブラウザでは動きません。また他のブラウザでは未検証です。

以下、使い方を紹介します。


【使い方】
最初に、このサービスは実行順を守って動かす必要があります。最初にホスト側が会議室を準備し、その準備後にゲストが参加用 URL にアクセスすることで正しく実行されます(ホストが会議室を準備する前にゲストが URL にアクセスしてもその画面は共有対象になりません)。

まずホスト役の人が対応ブラウザを使って以下の URL にアクセスします(なおこのデモ用サービスでは Basic 認証は ID: admin, PW: password です):
https://screen-collect.mybluemix.net/view?room=XXXXXXXX

XXXXXXXX 部分は会議 ID のようなもので、ユニークな文字列を指定する必要があります(他の人と同じ値を使うと挙動がおかしくなります)。例えば日付や自分の名前を使って、以下の例のような URL を指定してください:
https://screen-collect.mybluemix.net/view?room=dotnsf-20200426

この時点では正しくアクセスできていても画面には何も表示されません(こんな感じの画面になれば正しくアクセスできています):
2020042601


この画面を使ってゲストの画面を共有することになるので、この画面を消さずに残しておいてください。またホストはこの画面をリロードしないよう気をつけてください(上述の順序の都合で、ホスト側ウィンドウをリロードした場合はゲスト側ウィンドウもリロードしてもらう必要があります):

最後にホストからゲストに参加用の URL を(メールやメッセンジャー等で)知らせる必要があります。ゲストへは上記 URL から "view" を除いた文字列を指定して通知してください。上記例の場合だと、この URL を教えることになります:
https://screen-collect.mybluemix.net/?room=dotnsf-20200426


ゲスト全員へ参加用 URL を通知し終わったらホスト側の準備は完了です。

ホスト側の準備が完了後にゲストが通知された URL にアクセスします(ゲストは Basic 認証なしでアクセス可能です)。なお試験的に一台の PC でホストとゲスト両方の画面をテストしたい場合は MediaDevice インターフェースのサポートの都合もあり、ホスト画面を FireFox で、ゲスト画面を Chrome で表示するようにしてください。

するとゲストには以下のような画面が表示されます:
2020042601


Room には URL で指定された会議 ID が表示されています。もしホストが URL を間違えて通知していても、ここで会議 ID を正しく修正して続行することで以下正しく実行されます。

そして Your name .. と書かれたフィールドに参加者の名前(本名でもニックネームでも可。ホストの人がわかる名前)を入力して Start ボタンをクリックします。
2020042602


すると画面には入力した名前が表示されますが、同時に別ウィンドウで画面共有ダイアログが表示されます。ここでゲストは共有範囲を選択します。共有範囲はアプリケーションウィンドウの単位でも、Chrome ブラウザのタブの単位でも、また全画面を選択することも可能です(以下の例では全画面を選択しています):
2020042603


共有範囲を選択したら「共有」ボタンをクリックします:
2020042604


するとゲスト画面では画面共有ダイアログが消え、代わりに小さな共有ウィンドウが表示されます(ゲストはこのウィンドウ画面を消さないよう注意してください):
2020042605


これでゲスト側の準備も完了しました。この後、ゲストは自由にアプリケーションを使ってください(下図ではゲストが Visual Studio Code を起動してプログラミングしている様子にしています):
2020042606


(実際のオンライン勉強会などではホストとゲストが事前にここまで行った上で実際のハンズオン作業に入ってもらう想定です)


ここで改めてホスト側の画面を確認します。さっきまで何も表示されていなかったホスト画面でしたが、ゲストが共有設定を完了すると、ゲストの名前とその画面がパネル表示されているはずです。またゲストの画面は(このデモサービスでは)5秒おきに更新されます:
2020042602


更に別のゲストが共有設定を完了するとパネルが追加され、以下のような感じになります。ホストの1画面内にゲストの画面が追加されて一覧できるようになり、(5秒おきですが)そこで行われているオペレーションの内容も確認できます。パネルはゲストの人数に応じて(共有設定が完了するたびに)追加されていきます:
2020042603


パネル内画面は縮小表示されていますが、画像を選択してクリックするとゲストのスクリーンと同じ縦横比率で拡大して表示されます。これでゲスト側でエラーメッセージなどが表示されていてもホスト側から視認することができるようになりますし、オンラインセミナーに付いてこれなくなって遅れ気味の人もホスト側から確認できるようになる、と思っています:
2020042604


ゲスト側は Chrome のゲストウィンドウを消すか、以下のダイアログの「共有を停止」をクリックすることで画面共有を停止できます:
2020042607


以上がサービスの使い方です。特別なアプリケーションをインストールすることなく、ブラウザだけで複数ユーザーのスクリーン内容をホストに共有する、という「ありそうでなかった※」サービスだと思っています。

※参加者の表情をカメラで映して同時に共有する、という機能は多くのウェブ会議システムに装備されていますが、参加者のスクリーンを共有する機能はなぜか付属していない機能だと思っています。

今後リモート勉強会などであらかじめこの準備をしておくことで、トラブルシューティングやリモートサポートに活用できないかと考えています。また上述のようにソースコードも公開しているので、興味ある方は専用環境を構築してお使いいただいても構いません。

以下、実現方法に関する技術的な補足説明です。興味ある方は引き続きどうぞ。


【どうやって実現しているか】
まず、このサービスはホストもゲストもウェブブラウザだけで(アプリケーションのインストールなしで)実現しています。また HTTP(s) 以外のプロトコルは使っていません。したがって比較的多くの環境で利用できるようになっていると思っています。

で、これを実現している鍵というかキーワードが何度か上述されている MediaDevices インターフェースです。簡単にいうと HTML と JavaScript だけで画面共有とそのために必要な UI を実現する技術です。ただ比較的新しい技術である上に対応している OS やブラウザはかなり少なく、スマホ・タブレット系は 2020/04/26 時点では全滅といっていい状況です(まあスマホでハンズオンセミナーに参加する人も少ないとは思うけど・・・)。MediaDevices インターフェースについては過去にこれをテーマにブログで紹介したこともあるので、詳しくはそちらも参照してください(この時はホスト・ゲストに分けず、1つの HTML 画面内で画面ストリームを取得してそのまま表示する、という内容でした):
MediaDevices インターフェースを使った画面共有

今回上記で紹介したデモはこの応用といえるもので「ゲストで取得した画面ストリームの内容を使い、バックグラウンドで5秒おきにスクリーンショットを撮ってホストに画像をポストし、ホストで受け取った画像を表示する」ということをゲストブラウザ内の JavaScript とホストサーバーの WebSocket を使って実現しています。機能的にはわざわざスクリーンショット(画像)に変換せず、ストリームのまま(動画のまま)ホストに送信する選択肢もあるのですが、さすがに帯域的にもホストの処理内容的にも(そしてデモサーバーの処理能力的にも)厳しそうな感触があったので、意図的に5秒おきにスクリーンショットを送受信する形にしています(5秒という部分は別途専用環境を作る際には変更可能です)。ただこの結果、ネットワーク上のすべてのやり取りを HTTP(s) と(HTTP を拡張した)WebSocket に集約することができ、帯域問題に加えてファイアウォール的な制約をすり抜けやすい形で実現できたという副産物もあります。

もう少し詳しく2点説明します。まずゲスト側で(JavaScript だけで)共有範囲のスクリーンショットを撮る、という操作ですが、これは上述の MediaDevices インターフェースを使って共有アプリケーション画面のストリームを取得した後に、以下のような処理を行っています。

"Your name .." で指定した名前だけが表示されているように見える HTML 画面には、実は非表示属性の付いた <video> 要素と、同じく非表示属性の付いた <canvas> が存在しています:
<body>
  <h3 id="name"><%= name %></h3>

  <!-- Video element (live stream) -->
  <div class="hidetop">
    <video autoplay playsinline id="video" width="640" height="480"></video>
  </div>

  <!-- Canvas element (screenshot) -->
  <div class="hide">
    <canvas id="canvas" width="640" height="480"></canvas>
  </div>
</body>

↑(hidetop クラスと hide クラスでブロックごと非表示指定されているので、ゲストユーザーからは何も見えません)

そして5秒おきに以下の JavaScript 関数 : video2image() が実行されます:
function video2image(){
  if( localStream ){
    var canvas = document.getElementById( 'canvas' );
    var ctx = canvas.getContext( '2d' );

    var video = document.getElementById( 'video' );
    var w = video.offsetWidth;
    var h = video.offsetHeight;

    canvas.setAttribute( "width", w );
    canvas.setAttribute( "height", h );

    ctx.drawImage( video, 0, 0, w, h );
    var png = canvas.toDataURL( 'image/png' );

    //. 画像を通知
    var msg = {
      uuid: uuid,
      room: '<%= room %>',
      image_src: png
    };
    socketio.json.emit( 'image_client', msg );
  }
}

localStream がスクリーン画面のストリームです。このストリームは <video> 要素と接続しています(この辺りは上記のブログの内容を参照ください)。そして <video> の画面を(非表示の) <canvas> にコピーすることでスクリーンショットを取得し、その内容を canvas.toDataURL() で画像データに変換した上で、Socket.IO を使い、ゲストの ID と一緒に WebSocket でホストの image_client イベントに送信しています。これによりバックグラウンド処理で5秒おきにスクリーンショット画像をホストに送る、という処理が実現できています。

そしてホスト側にはスクリーンショット画像を WebSocket で受け取った後(image_client イベント発生時の)の処理が記述されています:
  socketio.on( 'image_client_view', function( msg ){
    var socket_id = msg.uuid; //msg.socket_id;
    if( socket_ids.indexOf( socket_id ) > -1 ){  //. socket_ids == [];
      $('#image_'+socket_id).prop( 'src', msg.image_src );
      $('#image_'+socket_id).prop( 'title', msg.comment );
    }
  });

メッセージの ID でどのゲストからのイベントであるかを識別した上で、その中の image_src を使って画像情報(=スクリーンショット)を取り出し、ホスト画面内の適切な箇所の画像(=対象ゲストのパネル内の画像)を動的に再描画しています。

実際にはこれらの抜粋処理以外にも、これらをつなぐための処理や初期化などをアプリケーション・サーバー側で行ったりしています。詳しい内容については公開しているソースコードを参照ください。大規模な実験を行うようなケースでは別途独自の環境を準備した上で行っていただけると助かります:
https://github.com/dotnsf/screen_collect


【最後に】
こういったサービスを作って試すことで「こういうケースでは使えそう、こういうケースだとまだ○○の問題がある」みたいな形で次のステップが見えてくると思っています。自分的にはある程度のスキルを持った参加者による「もくもく会」のような内容であれば使えそうだという感触を持っていますが、より初心者参加型に近い内容だと、これだけで充分なサポートができるかどうかは不安もあります。

ただそれらが見えてくることで改良点のヒントにもなるだろうし、用途や条件によっては実践で使うこともできるかもしれないと考えています。またこのデモサービスを使ってみていただいた感想や「こういうケースではOKだった/NGだった」のようなフィードバックもいただけると嬉しいです。いずれにせよオンライン勉強会の体験改善は自分にとっても無視できないテーマなので、こういった形で解決できることがあれば引き続き取り組んでいきたいと思っています。


比較的新しい Web API の1つである MediaDevices インターフェースを使って、HTML と JavaScript だけで(PCの)画面共有が実現できるようになりました。とりあえず使ってみるには PC のウェブブラウザ※で以下のサイトにアクセスしてみてください:
https://dotnsf.github.io/display_media_stream/

※対応ブラウザはこちら: 
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getDisplayMedia#Browser_compatibility



対応ブラウザで上記 URL にアクセスすると、画面共有のダイアログが表示されます:
2020033101


何かひとつ(下図の例では「アプリケーションウィンドウ」タブの「タスクマネージャー」)選択して、「共有」ボタンをクリックすると、、
2020033102


選択したアプリケーションのウィンドウ内画面がストリームでウェブブラウザに表示されます。最初に「全画面」を選んでいればウィンドウ全体がブラウザ内に表示されます:
2020033103


この機能を停止するには共有ダイアログの「共有を停止」ボタンをクリックしてください:
2020033104


以下、上記デモのソースコードを紹介します。コードそのものはこちらで公開しています:
https://github.com/dotnsf/display_media_stream


上記デモは実質的に index.html ファイルだけで実現しています。つまり HTML と JavaScript だけで実現しています。まず HTML 部(<body>部)は以下の通り、かなりシンプルです:
<body>
  <!-- Video element (live stream) -->
  <div>
    <video autoplay playsinline id="video" width="640" height="480"></video>
  </div>
</body>

<div> 要素の中に <video> 要素が1つだけ、autoplay 属性と playsinline 属性がついた状態で存在しています。一応初期サイズの指定もしていますが後で JavaScript で修正します。

次に肝となる JavaScript 部はこちらです:
<script>
var localVideo = null;

function gotLocalMediaStream( mediaStream ){
  localVideo.srcObject = mediaStream;
}

function handleLocalMediaStreamError( error ){
  console.log( "navigator.getUserMedia error: ", error );
}


$(function(){
  //. 画面サイズ取得
  var sw = window.parent.screen.width;
  var sh = window.parent.screen.height;
  //sw : sh = x : 480; => x = 480 * sw / sh;
  var x = Math.floor( 480 * sw / sh );
  $('#video').css( { width: x } );

  var mediaStreamConstraints = { video: true };
  localVideo = document.querySelector( "video" );
  navigator.mediaDevices.getDisplayMedia( mediaStreamConstraints ).then( gotLocalMediaStream ).catch( handleLocalMediaStreamError );
});
</script>

実質的には $(function(){ ... }); 部分が最初に実行されます。まずは window.parent.screen にアクセスして実画面のサイズを取得し、その縦横割合に合わせて上述の video 要素をリサイズ(縦は 480 に固定して、横を同割合になるようリサイズ)します。

そしてこの画面共有を実現しているのはこの1行です:
  navigator.mediaDevices.getDisplayMedia( mediaStreamConstraints ).then( gotLocalMediaStream ).catch( handleLocalMediaStreamError );

mediaDevices インターフェースの getDisplayMedia メソッドを、{ video: true } というオブジェクトを引数に実行しています。これがカメラやマイクではなくディスプレイ画面のストリームを取得するための処理で、成功すると gotLocalMediaStream 関数がコールバックされます。

その gotLocalMediaStream 関数は以下のような内容になっています:
<script>
var localVideo = null;

function gotLocalMediaStream( mediaStream ){
  localVideo.srcObject = mediaStream;
}

getDisplayMedia メソッドで取得したメディアストリームを引数にコールバックされ、その値を video 要素として取得済みの localVideo 変数の srcObject 属性に代入しています。これだけで後はディスプレイ画面の動画ストリームが video 要素の中で自動再生(autoplay)されます。


mediaDevices インターフェースや getDisplayMedia メソッドがまだ限られたブラウザ(Chrome, FireFox, Edge)の比較的新しいバージョンでしか使えず、スマホ系ブラウザでは全滅という状況ではあるのですが、ネイティブアプリを使わなくてもブラウザの JavaScript だけでここまでできるようになっていたんですね。video 要素として使えるということはスクリーンショットとかいろいろ応用できそう・・・


このページのトップヘ