技術探し

JavaScriptを中心に記事を書いていきます :;(∩´﹏`∩);:

Presentation APIを使ってみる

今作っているFusumaというスライドツールにPresentation APIを追加してみました。

blog.hiroppy.me

github.com

Presentation API とは?

developer.mozilla.org

ステータスは 勧告候補 で、今現在はchromeのみ実装されています。
このAPIは、KeynotePowerPointでプレゼンテーションモードにすると他のディスプレイに出力できるようになる機能をブラウザで実現できます。
ディスプレイの他にも、Chrome CastやAirPlay等にも映し出すことも可能です。

とりあえずディスプレイが一枚繋がっているかネットワーク以下にChrome Cast等があれば使えます。

デモ

hiroppy.github.io

  1. chromeで上記のページに行く
  2. 左下のハンバーガーメニューを押して、ロケットアイコンを選択する
  3. 下のスクリーンショットのように表示されるのでディスプレイを選択する(おそらく1が自身になる気がする)
  4. 現在のページがコントローラーになって、選択したディスプレイにフルスクリーンでスライドが表示される

f:id:about_hiroppy:20180821001720p:plain

あとは、コントローラー側で矢印キーを使って操作すれば、レシーバー側(フルスクリーンになったスライド側)のスライドも変わると思います。

いままではどのようにやっていたか?

今までもこのように別画面でフルスクリーンにスライドを表示する実装は出来ていました。
これを実現するためには、localStorageでページ情報と挿入イベントのリッスンを行います。
大きな違いとしては、自動で他のディスプレイにフルスクリーンで表示できる点が大きいです。

今までの場合は、window.openでレシーバー側を表示し、それを他のディスプレイに移し、フルスクリーンにするという手順が必要でした。

これからは、対応していなければlocalStorageの方を使う実装となりました。
また、Presentation APIで表示されたレシーバーからはlocalStorageは参照できません。

Chromeのサンプルコードは以下を参照

googlechrome.github.io

コード

Presentation API と localStorage をまとめたコードは以下を参照

github.com

コントローラー

接続処理

presentation api
async function connect() {
  return new Promise((resolve, reject) => {
    const presentationRequest = new PresentationRequest(['?presenter=view']);

    navigator.presentation.defaultRequest = presentationRequest;

    presentationRequest.addEventListener('connectionavailable', (e) => {
      const presentationConnection = e.connection;
      resolve(presentationConnection); // presentationConnectionからternimate以外の操作を行う
    });

    presentationRequest.start().catch((err) => reject(err));
  });
}
localStorage

localStorageなので特になし

メッセージング

presentation api
presentationConnection.send(JSON.stringify({
  page: 1
}));
localStorage
localStorage.setItem(
  'page',
  JSON.stringify({
    date: Date.now(),
    page: 1
  })
);

レシーバー

接続処理

presentation api

接続という処理はなく、イベントを登録していく感じとなります。
navigator.presentation.receiver.connectionList に接続されているコネクションのリストが入っています。

function addEvent(name, cb) {
  if (navigator.presentation && navigator.presentation.receiver) {
    navigator.presentation.receiver.connectionList.then((list) => {
      list.connections.forEach((connection) => {
        connection.addEventListener(name, cb);
      });

      list.addEventListener('connectionavailable', (event) => {
        cb(event.connection);
      });
    });
  }
}

addEvent('close', (e) => {
  console.log(e);
});
localStorage

localStorageなので特になし

メッセージング

presentation api
navigator.presentation.receiver.connectionList.then((list) => {
  list.connections.forEach((connection) => {
    connection.addEventListener('message', (e) => {
      const page = JSON.parse(e.data).page;
    });
  });
});
localStorage
window.addEventListener('storage', (e) => {
  if (e.key === 'page') {
    const page = JSON.parse(e.newValue).page;

    window.slide.bespoke.slide(page);
  }
});

問題点

  • ライブリロードされると、レシーバーが破棄されるため毎回開き直し
  • レシーバー側では、ショートカットキーが使えず、右クリックから検証を押さないとインスペクターが開けない
  • シークレットウィンドウだと、起動はするがメッセージが送れない
    • UnknownError: Mismatch in incognito status: request = 1, response = 0

とりあえずデバッグが大変めんどくさかったです。

今後

Presentation APIとlocalStorageの互換性があるライブラリを書くかもしれません。
しかし、Chrome Castなどはブラウザの域から外れ、localStorageでは対応できないため完璧な対応はできないと思います。
message周りを抽象化したいなーって実装してて感じました。

すごい便利な機能なので早くほかのブラウザにも入ってほしいと思っています。

github.com