Scorrimento e zoom di una scheda acquisita

François Beaufort
François Beaufort

La condivisione di schede, finestre e schermate è già possibile sulla piattaforma web con l'API Screen Capture. Quando un'app web chiama il numero getDisplayMedia(), Chrome chiede all'utente di condividere una scheda, una finestra o uno schermo con l'app web sotto forma di video MediaStreamTrack.

Molte app web che utilizzano getDisplayMedia() mostrano all'utente un'anteprima video della piattaforma acquisita. Ad esempio, le app di videoconferenza spesso trasmettono questo video in streaming agli utenti remoti e ne eseguono il rendering anche su un HTMLVideoElement locale, in modo che l'utente locale possa vedere costantemente un'anteprima dei contenuti che ha condiviso.

Questa documentazione presenta la nuova API Captured Surface Control in Chrome, che consente alla tua app web di scorrere una scheda acquisita, nonché di leggere e scrivere il livello di zoom di una scheda acquisita.

Un utente scorre e ingrandisce una scheda acquisita (demo).

Perché usare il controllo della superficie acquisita?

Tutte le app per videoconferenze hanno lo stesso svantaggio: se l'utente vuole interagire con una scheda o una finestra acquisita, deve passare a quella piattaforma e allontanarla dall'app. Ciò presenta alcune difficoltà:

  • L'utente non può vedere contemporaneamente l'app acquisita e i video degli utenti remoti, a meno che non utilizzi Picture in picture o finestre affiancate separate per la scheda per videoconferenze e la scheda condivisa. Su uno schermo più piccolo, questo potrebbe essere difficile.
  • L'utente è più oneroso della necessità di passare dall'app di videoconferenza all'app di videoconferenza.
  • L'utente perde l'accesso ai controlli esposti dall'app di videoconferenza quando è lontano, ad esempio un'app di chat incorporata, reazioni con emoji, notifiche relative alla richiesta di partecipazione agli utenti, controlli multimediali e di layout e altre utili funzionalità di videoconferenza.
  • Il presentatore non può delegare il controllo ai partecipanti remoti. Questo scenario porta a uno scenario fin troppo familiare in cui gli utenti da remoto chiedono al presentatore di cambiare la slide, scorrere un po' verso l'alto e il basso o regolare il livello di zoom.

L'API Captured Surface Control risolve questi problemi.

Come si usa il controllo della superficie acquisita?

L'uso corretto del controllo della superficie acquisita richiede alcuni passaggi, come l'acquisizione esplicita di una scheda del browser e l'ottenimento dell'autorizzazione dall'utente prima di poter scorrere e ingrandire la scheda acquisita.

Acquisisci una scheda del browser

Per iniziare, chiedi all'utente di scegliere una piattaforma da condividere utilizzando getDisplayMedia() e, durante la procedura, associa un oggetto CaptureController alla sessione di acquisizione. Useremo quell'oggetto per controllare la superficie acquisita abbastanza presto.

const controller = new CaptureController();
const stream = await navigator.mediaDevices.getDisplayMedia({ controller });

Quindi, produci un'anteprima locale della superficie acquisita sotto forma di elemento <video>:

const previewTile = document.querySelector('video');
previewTile.srcObject = stream;

Se l'utente sceglie di condividere una finestra o uno schermo, per il momento non rientra nell'ambito di applicazione, ma se sceglie di condividere una scheda, possiamo procedere.

const [track] = stream.getVideoTracks();

if (track.getSettings().displaySurface !== 'browser') {
  // Bail out early if the user didn't pick a tab.
  return;
}

Richiesta di autorizzazione

La prima chiamata di sendWheel() o setZoomLevel() su un determinato oggetto CaptureController produce un prompt di autorizzazione. Se l'utente concede l'autorizzazione, sono consentite ulteriori chiamate di questi metodi sull'oggetto CaptureController in questione. Se l'utente nega l'autorizzazione, la promessa restituita viene rifiutata.

Tieni presente che gli oggetti CaptureController sono associati in modo univoco a una specifica acquisizione-sessione, non possono essere associati a un'altra sessione di acquisizione e non sopravvivono alla navigazione nella pagina in cui sono stati definiti. Tuttavia, le sessioni Capture sopravvivono alla navigazione nella pagina acquisita.

È necessario un gesto dell'utente per mostrargli una richiesta di autorizzazione. Solo le chiamate sendWheel() e setZoomLevel() richiedono un gesto dell'utente e solo se deve essere mostrato il prompt. Se l'utente fa clic su un pulsante per aumentare o diminuire lo zoom nell'app web, il gesto dell'utente è un dato preciso. Se l'app vuole offrire prima il controllo dello scorrimento, gli sviluppatori devono tenere presente che lo scorrimento non costituisce un gesto dell'utente. Una possibilità è offrire prima all'utente un pulsante "Inizia a scorrere", come illustrato nell'esempio seguente:

const startScrollingButton = document.querySelector('button');

startScrollingButton.addEventListener('click', async () => {
  try {
    const noOpWheelAction = {};

    await controller.sendWheel(noOpWheelAction);
    // The user approved the permission prompt.
    // You can now scroll and zoom the captured tab as shown later in the article.
  } catch (error) {
    return; // Permission denied. Bail.
  }
});

Scorri

Con sendWheel(), un'app di acquisizione può mostrare eventi ruota della magnitudine scelta rispetto alle coordinate scelte all'interno dell'area visibile di una scheda. L'evento non è distinguibile dall'interazione diretta dell'utente all'app acquisita.

Supponendo che l'app di acquisizione utilizzi un elemento <video> chiamato "previewTile", il seguente codice mostra come inoltrare gli eventi di invio della ruota alla scheda acquisita:

const previewTile = document.querySelector('video');

previewTile.addEventListener('wheel', async (event) => {
  // Translate the offsets into coordinates which sendWheel() can understand.
  // The implementation of this translation is further explained below.
  const [x, y] = translateCoordinates(event.offsetX, event.offsetY);
  const [wheelDeltaX, wheelDeltaY] = [-event.deltaX, -event.deltaY];

  try {
    // Relay the user's action to the captured tab.
    await controller.sendWheel({ x, y, wheelDeltaX, wheelDeltaY });
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

Il metodo sendWheel() utilizza un dizionario con due insiemi di valori:

  • x e y: le coordinate in cui deve essere consegnato l'evento ruota.
  • wheelDeltaX e wheelDeltaY: l'intensità degli scorrimenti, in pixel, rispettivamente per gli scorrimenti orizzontali e verticali. Tieni presente che questi valori sono invertiti rispetto all'evento ruota originale.

Una possibile implementazione di translateCoordinates() è:

function translateCoordinates(offsetX, offsetY) {
  const previewDimensions = previewTile.getBoundingClientRect();
  const trackSettings = previewTile.srcObject.getVideoTracks()[0].getSettings();

  const x = trackSettings.width * offsetX / previewDimensions.width;
  const y = trackSettings.height * offsetY / previewDimensions.height;

  return [Math.floor(x), Math.floor(y)];
}

Tieni presente che nel codice precedente vengono utilizzate tre diverse dimensioni:

  • Le dimensioni dell'elemento <video>.
  • La dimensione dei frame acquisiti (qui rappresentati come trackSettings.width e trackSettings.height).
  • Le dimensioni della scheda.

Le dimensioni dell'elemento <video> rientrano interamente nel dominio dell'app di acquisizione e non sono note al browser. Le dimensioni della scheda rientrano interamente nel dominio del browser e non sono note all'app web.

L'app web utilizza translateCoordinates() per tradurre gli offset relativi all'elemento <video> in coordinate all'interno dello spazio di coordinate della traccia video. Allo stesso modo, il browser tradurrà le dimensioni dei frame acquisiti e quelle della scheda e restituirà l'evento di scorrimento con un offset corrispondente alle aspettative dell'app web.

La promessa restituita da sendWheel() può essere rifiutata nei seguenti casi:

  • Se la sessione di acquisizione non è ancora iniziata o si è già interrotta, incluso l'arresto asincrono mentre l'azione sendWheel() viene gestita dal browser.
  • Se l'utente non ha concesso all'app l'autorizzazione a utilizzare sendWheel().
  • Se l'app di acquisizione tenta di inviare un evento di scorrimento con coordinate esterne a [trackSettings.width, trackSettings.height]. Tieni presente che questi valori possono cambiare in modo asincrono, quindi ti consigliamo di individuare l'errore e ignorarlo. Tieni presente che 0, 0 in genere non fuoriesce dai limiti, quindi è sicuro utilizzarlo per richiedere l'autorizzazione all'utente.

Zoom

L'interazione con il livello di zoom della scheda acquisita viene eseguita tramite le seguenti piattaforme CaptureController:

  • getSupportedZoomLevels() restituisce un elenco dei livelli di zoom supportati dal browser, rappresentati come percentuali del "livello di zoom predefinito", definito come 100%. Questo elenco aumenta monotonicamente e contiene il valore 100.
  • getZoomLevel() restituisce il livello di zoom corrente della scheda.
  • setZoomLevel() imposta il livello di zoom della scheda su qualsiasi valore intero presente in getSupportedZoomLevels() e restituisce una promessa se l'operazione ha esito positivo. Tieni presente che il livello di zoom non viene reimpostato alla fine della sessione di acquisizione.
  • oncapturedzoomlevelchange ti consente di ascoltare i cambiamenti del livello di zoom di una scheda acquisita perché gli utenti possono cambiarlo tramite l'app di acquisizione o tramite l'interazione diretta con la scheda acquisita.

Le chiamate a setZoomLevel() sono limitate dall'autorizzazione; le chiamate agli altri metodi di zoom di sola lettura sono "senza costi", così come l'ascolto di eventi.

L'esempio seguente mostra come aumentare il livello di zoom di una scheda acquisita in una sessione di acquisizione esistente:

const zoomIncreaseButton = document.getElementById('zoomInButton');

zoomIncreaseButton.addEventListener('click', async (event) => {
  const levels = CaptureController.getSupportedZoomLevels();
  const index = levels.indexOf(controller.getZoomLevel());
  const newZoomLevel = levels[Math.min(index + 1, levels.length - 1)];

  try {
    await controller.setZoomLevel(newZoomLevel);
  } catch (error) {
    // Inspect the error.
    // ...
  }
});

L'esempio seguente mostra come reagire ai cambiamenti del livello di zoom di una scheda acquisita:

controller.addEventListener('capturedzoomlevelchange', (event) => {
  const zoomLevel = controller.getZoomLevel();
  document.querySelector('#zoomLevelLabel').textContent = `${zoomLevel}%`;
});

Rilevamento delle funzionalità

Per verificare se gli eventi della rotellina di invio sono supportati, usa:

if (!!window.CaptureController?.prototype.sendWheel) {
  // CaptureController sendWheel() is supported.
}

Per verificare se il controllo dello zoom è supportato, utilizza:

if (!!window.CaptureController?.prototype.setZoomLevel) {
  // CaptureController setZoomLevel() is supported.
}

Attiva controllo superficie acquisita

L'API Captured Surface Control è disponibile in Chrome su desktop dietro il flag di Captured Surface Control e può essere attivata all'indirizzo chrome://flags/#captured-surface-control.

Inoltre, questa funzionalità sta partecipando a una prova dell'origine a partire da Chrome 122 su computer, che consente agli sviluppatori di abilitare la funzionalità per consentire ai visitatori dei loro siti di raccogliere dati da utenti reali. Consulta la pagina Iniziare a utilizzare le prove dell'origine per saperne di più sulle prove dell'origine e sul loro funzionamento.

Sicurezza e privacy

Le norme di autorizzazione "captured-surface-control" consentono di gestire il modo in cui l'app di acquisizione e gli iframe di terze parti incorporati hanno accesso al controllo della superficie acquisita. Per comprendere i compromessi in termini di sicurezza, consulta la sezione Considerazioni su privacy e sicurezza della sezione esplicativa sul controllo della superficie di acquisizione.

Demo

Puoi giocare con Captured Surface Control eseguendo la demo su Glitch. Assicurati di controllare il codice sorgente.

Modifiche rispetto alle versioni precedenti di Chrome

Di seguito sono riportate alcune principali differenze di comportamento relative al controllo della superficie acquisita che è importante conoscere:

  • In Chrome 124 e versioni precedenti:
    • L'autorizzazione, se concessa, ha come ambito la sessione di acquisizione associata a questo CaptureController, non l'origine dell'acquisizione.
  • In Chrome 122:
    • getZoomLevel() restituisce una promessa con il livello di zoom corrente della scheda.
    • sendWheel() restituisce una promessa rifiutata con il messaggio di errore "No permission." se l'utente non ha concesso all'app l'autorizzazione a utilizzare. Il tipo di errore è "NotAllowedError" in Chrome 123 e versioni successive.
    • oncapturedzoomlevelchange non è disponibile. Puoi eseguire il polyfill di questa funzionalità utilizzando setInterval().

Feedback

Il team di Chrome e la community degli standard web vogliono conoscere le tue esperienze con Captured Surface Control.

Parlaci del design

C'è qualcosa nell'acquisizione della superficie acquisita che non funziona come previsto? Oppure mancano metodi o proprietà di cui hai bisogno per implementare la tua idea? Hai domande o commenti sul modello di sicurezza? Segnala un problema relativo alle specifiche nel repository GitHub o aggiungi le tue opinioni su un problema esistente.

Problemi con l'implementazione?

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalle specifiche? Segnala un bug all'indirizzo https://new.crbug.com. Assicurati di includere il maggior numero di dettagli possibile e le istruzioni per la riproduzione. Glitch è ottimo per la condivisione di bug riproducibili.