Użyj Capture Handle do wsparcia sterowania udostępnianą zakładką!

Spis treści

  1. Wprowadzenie
  2. Implementacja zarządzania prezentacją przy pomocy identyfikacji z Capture Handle
    1. Strona z prezentacją
    2. Strona zarządzająca prezentacją
  3. Kod aplikacji na Github
  4. Klikalna wersja aplikacji na Github Pages
  5. Podsumowanie

Wprowadzenie

Dzięki API getDisplayMedia mamy możliwość udostępniania innym naszego ekranu – dzięki temu możemy np zrobić prezentację online. Jeśli jednak chcemy jednocześnie mieć jakieś notatki do tej prezentacji, musimy je mieć na drugim ekranie by móc z nich skorzystać. Musimy przełączać się między tabem z prezentacją gdzie są slajdy a innym tabem / ekranem gdzie są notatki, co będzie irytować odbiorców.

Z pomocą przychodzi API Capture Handle. Pozwala ono zidentyfikować stronę jaka jest nam udostępniana. Jeśli udostępniana jest właściwa strona możemy zestawić komunikację – dzięki temu czemu możemy z poziomu taba z notatkami sterować slajdami na tabie z prezentacją.

Dokładnie ten przypadek opracujemy w tym artykule – zapraszam do czytania 🙂

Implementacja zarządzania prezentacją przy pomocy identyfikacji z Capture Handle

Strona z prezentacją

Na stronie z prezentacją umieścimy 3 filmy wideo, pomiędzy którymi będziemy się chcieli przełączać. Wszystkie filmy pochodzą z serwisu Pexels, w kodzie można znaleźć dokładne adresy.

Filmy, poza adresem, przyporządkowane też będą mieć tekst związany ze slajdem i notatki. Po co one na stronie prezentacji? Ponieważ przesyłane one będą do aplikacji odpowiedzialnej za przełączanie slajdów:

const videos = [
  {
    src: 'learn.mp4', // https://www.pexels.com/video/a-child-using-a-pencil-writing-on-a-paper-inside-a-classroom-3209663/
    text: 'When I was young I liked to learn in school...',
    notes: 'To tell about doing homework',
  },
  {
    src: 'scientist.mp4', // https://www.pexels.com/video/scientist-looking-through-a-microscope-while-having-a-discussion-with-her-colleague-3196600/
    text: 'Then I have become a scientist....',
    notes: 'To highlight how important job it is',
  },
  {
    src: 'money.mp4', // https://www.pexels.com/video/hands-rich-green-money-3943969/
    text: 'And now I have a lot of money',
    notes: 'To lie to them how much money I make :)',
  },
];

Następnie ustawimy ID którym nasza strona będzie się przedstawiać:

const handleId = 'presentation';

navigator.mediaDevices.setCaptureHandleConfig({
  handle: handleId,
  permittedOrigins: ['*'],
});

W jaki sposób inne strony odczytają to ID w momencie zostanie ona udostępniona? Poprzez funkcję getCaptureHandle dostępną na ścieżce udostępnianego streama:

const stream = await navigator.mediaDevices.getDisplayMedia();
const track = stream.getVideoTracks()[0];
const streamHandle = track.getCaptureHandle();
const handle = streamHandle.handle;
console.log(handle); // 'presentation'

Na koniec pozostaje nam zestawienie ścieżki komunikacji. Użyjemy do tego BroadcastChannel:

const communicationChannel = new BroadcastChannel('presentationChannel');

Do kanału podepniemy reakcję na wiadomości:

communicationChannel.addEventListener('message', listener);

W której to:

Po pierwsze, sprawdzimy czy nadawca przesłał nam swoje id, które powinno być identyczne jak naszej strony prezentacji:

const data = JSON.parse(e.data);
if (data.id !== handleId) {
  return;
}

Gdyż chcemy reagować tylko na wiadomości, które nadaje znany nam nadawca.

Bazując na typie przesłanej wiadomości wystartujemy film będący umownie u nas slajdem bądź przesuniemy się do kolejnego / poprzedniego filmu:

switch (data.type) {
  case 'begin': {
    beginPresentation();
    break;
  }
  case 'previous': {
    presentPrevious();
    break;
  }
  case 'next': {
    presentNext();
    break;
  }
}

Same funkcje beginPresentation, presentPrevious czy presentNext nie są z naszego punktu widzenia istotne. Zawierają jedynie zmianę src elementu video – pamiętaj, że w dalszej części artykułu zamieszczony zostanie link do pełnego kodu aplikacji na Githubie oraz klikalnej wersji na Github Pages.

Na samym końcu wyślemy w eter informacje o aktualnie odtwarzanym elemencie:

const videoObj = videos.find(isCurrentVideo);
communicationChannel.postMessage(JSON.stringify({
  id: handleId,
  ...videoObj,
}));

Dzięki temu strona, która zażądała przełączenia slajdu sama nie musi posiadać żadnych informacji o prezentacji. Wszystkie informacje zostaną jej odesłane.

Strona zarządzająca prezentacją

Do zarządzania prezentacją potrzebujemy ją najpierw udostępnić. Po pobraniu streama pobierzemy za pomocą metody getCaptureHandle identyfikator udostępnianej strony. Jeśli udostępniono nie tą stronę co trzeba, nie będziemy jej podpinać do podglądu:

const handleId = 'presentation';
//....
const stream = await navigator.mediaDevices.getDisplayMedia();
const track = stream.getVideoTracks()[0];
const streamHandle = track.getCaptureHandle();
if (streamHandle.handle !== handleId) {
  alert('Not that tab!');
  return;
}
video.srcObject = stream;
video.play();

Taki stream może zostać udostępniony innym np za pomocą technologii WebRTC.

Zdefiniujmy teraz kanał komunikacji i metodę którą będziemy wołać na przyciskach przełączających slajdy na prezentacji na innym tabie:

const communicationChannel = new BroadcastChannel('presentationChannel');

const sendMessage = type => communicationChannel.postMessage(JSON.stringify({
  id: handleId,
  type,
}));

Metodę podepniemy pod przyciski:

document.querySelector('#begin')
  .addEventListener('click', () => sendMessage('begin'));
document.querySelector('#previous')
  .addEventListener('click', () => sendMessage('previous'));
document.querySelector('#next')
  .addEventListener('click', () => sendMessage('next'));

Dzięki czemu kliknięcie przycisku wyśle do strony z prezentacją odpowiedni typ eventu. Pamiętasz instrukcję switch..case ze strony z prezentacją? Właśnie na te zdarzenia będzie ona reagować.

Tego samego kanału komunikacji użyjemy do odbioru informacji o slajdzie, który właśnie został ustawiony. Odebraną informację wyświetlimy użytkownikowi – dzięki temu, gdy odbiorcy prezentacji widzieć będą prezentację na pełnym ekranie użytkownik w swojej aplikacji będzie widział to, co ma czytać:

communicationChannel.addEventListener('message', e => {
  const data = JSON.parse(e.data);
  if (data.id !== handleId) {
    return;
  }
  document.querySelector('#support').innerHTML = `
    <h3>Presenting ${data.src}</h3>
    <h4>${data.text} <small>[${data.notes}]</small></h4>
  `;
});

Kod aplikacji na Github

Pełen kod aplikacji znajduje się pod adresem

https://github.com/radek-anuszewski/capture-handle

Klikalna wersja aplikacji na Github Pages

Klikalna wersja aplikacji znajduje się pod adresem:

https://radek-anuszewski.github.io/capture-handle/

Podsumowanie

Sterowanie prezentacją z zewnątrz to tylko jeden z przypadków użycia nowego API – łatwo sobie wyobrazić bardziej skomplikowane przypadki użycia. Przykładowo – klient firmy ubezpieczeniowej ma problem z wypełnieniem formularza więc łączy się z przedstawicielem tej firmy, udostępnia mu ekran a przedstawiciel po swojej stronie ma UI do jakiegoś stopnia odwzorowujące ten formularz. API Capture Handle pozwala zestawić prostą komunikację, która będzie transportować dane wpisane w formularzu przez klienta do formularza przedstawiciela firmy ubezpieczeniowej i na odwrót.

API jest na tyle młode, że na dziś (13 czerwca 2022) nie ma swojej strony na MDN czy Caniuse.com 🙂 Wiele wody w Wiśle upłynie zanim będziemy mieli na tyle szerokie wsparcie, by stosować je produkcyjnie – o ile w ogóle twórcy innych przeglądarek niż bazujące na Chromium zaimplementują to API.

Niemniej jednak – jest to kolejne rozszerzenie możliwości świata Web, które warto znać a również dzięki zainteresowaniu nas, developerów, tym API możemy przyspieszyć jego wprowadzenie 🙂