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 getDisplayMedia(), Chrome chiede all'utente di condividere una scheda, una finestra o uno schermo con l'app web come video MediaStreamTrack.

Molte app web che utilizzano getDisplayMedia() mostrano all'utente un'anteprima video della superficie acquisita. Ad esempio, le app di videoconferenza spesso trasmettono questo video in streaming agli utenti remoti e lo visualizzano anche su un HTMLVideoElement locale, in modo che l'utente locale possa vedere costantemente un'anteprima di ciò che sta condividendo.

Questa documentazione introduce 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 aumenta lo zoom di una scheda acquisita (demo).

Perché utilizzare il controllo delle aree acquisite?

Tutte le app di videoconferenza presentano lo stesso svantaggio: se l'utente vuole interagire con una scheda o una finestra acquisita, deve passare a quella piattaforma, allontanandosi dall'app di videoconferenza. Ciò presenta alcune sfide:

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

L'API Captured Surface Control risolve questi problemi.

Come faccio a utilizzare il controllo della superficie acquisita?

Per utilizzare correttamente il controllo della superficie acquisita sono necessari alcuni passaggi, ad esempio acquisire esplicitamente una scheda del browser e ottenere l'autorizzazione dell'utente prima di poter scorrere e aumentare lo zoom della scheda acquisita.

Acquisire una scheda del browser

Per iniziare, chiedi all'utente di scegliere una superficie da condividere utilizzando getDisplayMedia() e, nel processo, associa un oggetto CaptureController alla sessione di acquisizione. A breve utilizzeremo questo oggetto per controllare la superficie acquisita.

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

Successivamente, crea 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 una schermata, per il momento non rientra nell'ambito, 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 genera una richiesta di autorizzazione. Se l'utente concede l'autorizzazione, sono consentite ulteriori invocazioni di questi metodi sull'oggetto CaptureController. Se l'utente nega l'autorizzazione, la promessa restituita viene rifiutata.

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

È necessario un gesto dell'utente per mostrare una richiesta di autorizzazione. Solo le chiamate sendWheel() e setZoomLevel() richiedono un gesto dell'utente e solo se è necessario mostrare la richiesta. Se l'utente fa clic su un pulsante per aumentare o diminuire lo zoom nell'app web, questo gesto è scontato; ma 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 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

Utilizzando sendWheel(), un'app di acquisizione può inviare eventi relativi alla rotella di entità scelte in base alle coordinate scelte all'interno dell'area visibile di una scheda. L'evento non è distinguibile per l'app acquisita dall'interazione diretta dell'utente.

Supponendo che l'app di acquisizione utilizzi un elemento <video> denominato "previewTile", il seguente codice mostra come inoltrare gli eventi di invio della rotella 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 explained further 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() prende un dizionario con due insiemi di valori:

  • x e y: le coordinate in cui deve essere inviato l'evento della ruota.
  • wheelDeltaX e wheelDeltaY: le ampiezze degli scorrimenti, in pixel, per gli scorrimenti orizzontali e verticali, rispettivamente. 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 sono presenti tre dimensioni diverse:

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

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

L'app web utilizza translateCoordinates() per tradurre gli offset relativi all'elemento <video> in coordinate all'interno dello spazio delle coordinate della traccia video. Analogamente, il browser eseguirà la conversione tra le dimensioni dei frame acquisiti e le dimensioni della scheda e invierà 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 è già stata interrotta, inclusa l'interruzione in modo 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 in coordinate esterne a [trackSettings.width, trackSettings.height]. Tieni presente che questi valori potrebbero cambiare in modo asincrono, quindi è consigliabile rilevare l'errore e ignorarlo. Tieni presente che 0, 0 in genere non è fuori limite, quindi è sicuro utilizzarlo per chiedere all'utente l'autorizzazione.

Zoom

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

  • getSupportedZoomLevels() restituisce un elenco dei livelli di zoom supportati dal browser, rappresentati come percentuali del "livello di zoom predefinito", definito come 100%. Questo elenco è in ordine crescente 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 in caso di esito positivo. Tieni presente che il livello di zoom non viene reimpostato alla fine della sessione di acquisizione.
  • oncapturedzoomlevelchange ti consente di ascoltare le modifiche del livello di zoom di una scheda acquisita, poiché gli utenti possono modificarlo tramite l'app di acquisizione o tramite l'interazione diretta con la scheda acquisita.

Le chiamate a setZoomLevel() sono soggette a autorizzazione; le chiamate agli altri metodi di zoom di sola lettura sono "libere", così come l'ascolto degli 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.
    // ...
  }
});

Il seguente esempio mostra come reagire alle modifiche del livello di zoom di una scheda acquisita:

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

Rilevamento di funzionalità

Per verificare se l'invio di eventi relativi al mouse è supportato, utilizza:

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

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

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

Attiva il controllo della superficie acquisita

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

Inoltre, questa funzionalità è in fase di prova per le origini a partire da Chrome 122 su computer, il che consente agli sviluppatori di attivarla per i visitatori dei loro siti al fine di raccogliere dati da utenti reali. Per ulteriori informazioni sulle prove delle origini e sul loro funzionamento, consulta Iniziare a utilizzare le prove delle origini.

Sicurezza e privacy

I criteri di autorizzazione "captured-surface-control" ti consentono di gestire il modo in cui l'app di acquisizione e gli iframe di terze parti incorporati hanno accesso al controllo delle aree acquisite. Per comprendere i compromessi in termini di sicurezza, consulta la sezione Considerazioni sulla privacy e sulla sicurezza della pagina informativa sul controllo delle aree acquisite.

Demo

Puoi provare il controllo della superficie acquisita eseguendo la demo su Glitch. Assicurati di controllare il codice sorgente.

Modifiche rispetto alle versioni precedenti di Chrome

Di seguito sono riportate alcune differenze di comportamento principali del controllo delle aree acquisite da tenere presenti:

  • In Chrome 124 e versioni precedenti:
    • L'autorizzazione, se concessa, è limitata alla sessione di acquisizione associata a quel CaptureController, non all'origine di 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 di utilizzo. 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 il controllo delle aree acquisite.

Parlaci del design

C'è qualcosa in merito a Captured Surface Capture che non funziona come previsto? Oppure mancano metodi o proprietà necessari per implementare la tua idea? Hai una domanda o un commento sul modello di sicurezza? Invia una segnalazione relativa alle specifiche nel repository GitHub o aggiungi il tuo parere a una segnalazione esistente.

Problemi con l'implementazione?

Hai trovato un bug nell'implementazione di Chrome? Oppure l'implementazione è diversa dalla specifica? Segnala un bug all'indirizzo https://new.crbug.com. Assicurati di includere il maggior numero di dettagli possibile, nonché le istruzioni per la riproduzione. Glitch è ideale per condividere bug riproducibili.