Faire défiler un onglet capturé et effectuer un zoom

François Beaufort
François Beaufort

Il est déjà possible de partager des onglets, des fenêtres et des écrans sur la plate-forme Web grâce à l'API Screen Capture. Lorsqu'une application Web appelle getDisplayMedia(), Chrome invite l'utilisateur à partager un onglet, une fenêtre ou un écran avec l'application Web sous la forme d'une vidéo MediaStreamTrack.

De nombreuses applications Web qui utilisent getDisplayMedia() présentent à l'utilisateur un aperçu vidéo de la surface capturée. Par exemple, les applications de visioconférence diffusent souvent cette vidéo en streaming auprès d'utilisateurs distants tout en la rendant également dans un HTMLVideoElement local, afin que ces utilisateurs aient constamment un aperçu du contenu qu'ils partagent.

Cette documentation présente la nouvelle API Captured Surface Control dans Chrome, qui permet à votre application Web de faire défiler un onglet capturé et de lire et d'écrire le niveau de zoom d'un onglet capturé.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
Un utilisateur fait défiler un onglet capturé et effectue un zoom (démonstration).

Pourquoi utiliser Captured Surface Control ?

Toutes les applications de visioconférence présentent le même inconvénient: si l'utilisateur souhaite interagir avec un onglet ou une fenêtre capturés, il doit basculer sur cette surface, ce qui l'éloigne de l'application de visioconférence. Cela soulève quelques difficultés:

  • L'utilisateur ne peut pas voir en même temps l'application capturée et les vidéos des utilisateurs distants, sauf s'ils utilisent le mode Picture-in-picture ou des fenêtres côte à côte distinctes pour l'onglet "Visioconférence" et l'onglet partagé. Cela peut être difficile sur un écran de petite taille.
  • L'utilisateur se retrouve contraint de devoir jongler entre l'application de visioconférence et la surface capturée.
  • L'utilisateur perd l'accès aux commandes exposées par l'application de visioconférence lorsqu'il ne s'en sert pas. comme une application de chat intégrée, des réactions emoji, des notifications indiquant aux utilisateurs qui demandent à participer à l'appel, des commandes multimédias et de mise en page, et d'autres fonctionnalités de visioconférence utiles.
  • Le présentateur ne peut pas déléguer le contrôle à des participants à distance. Cela conduit au scénario trop familier où les utilisateurs à distance demandent au présentateur de changer la diapositive, de faire défiler un peu vers le haut et vers le bas ou d'ajuster le niveau de zoom.

L'API Captured Surface Control résout ces problèmes.

Comment utiliser Captured Surface Control ?

Pour utiliser correctement le contrôle de surface capturée, vous devez suivre quelques étapes, comme capturer explicitement un onglet de navigateur et obtenir l'autorisation de l'utilisateur avant de pouvoir faire défiler l'onglet capturé et zoomer dessus.

Capturer un onglet de navigateur

Commencez par inviter l'utilisateur à choisir une surface à partager à l'aide de getDisplayMedia(). Ensuite, associez un objet CaptureController à la session de capture. Nous utiliserons cet objet assez tôt pour contrôler la surface capturée.

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

Ensuite, générez un aperçu local de la surface capturée sous la forme d'un élément <video>:

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

Si l'utilisateur choisit de partager une fenêtre ou un écran, cela n'est pas possible pour le moment. S'il choisit de partager un onglet, nous pouvons continuer.

const [track] = stream.getVideoTracks();

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

Invite concernant l'autorisation

Le premier appel de sendWheel() ou setZoomLevel() sur un objet CaptureController donné génère une invite d'autorisation. Si l'utilisateur donne son autorisation, les appels supplémentaires de ces méthodes sur cet objet CaptureController sont autorisés. Si l'utilisateur refuse l'autorisation, la promesse renvoyée est refusée.

Notez que les objets CaptureController sont associés de manière unique à une capture-session spécifique, ne peuvent pas être associés à une autre session de capture et ne survivent pas à la navigation sur la page où ils sont définis. Toutefois, les sessions de capture survivent à la navigation sur la page capturée.

Un geste de l'utilisateur est nécessaire pour afficher une invite d'autorisation. Seuls les appels sendWheel() et setZoomLevel() nécessitent un geste de l'utilisateur, et uniquement si l'invite doit être affichée. Si l'utilisateur clique sur un bouton de zoom avant ou de zoom arrière dans l'application Web, ce geste est normal. Toutefois, si l'application souhaite d'abord proposer le contrôle du défilement, les développeurs doivent garder à l'esprit que le défilement ne constitue pas un geste de l'utilisateur. Vous pouvez d'abord proposer à l'utilisateur de "commencer à faire défiler la page" , comme dans l'exemple suivant:

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.
  }
});

Faire défiler

À l'aide de sendWheel(), une application de capture peut diffuser des événements de roue de sa magnitude choisie plutôt que des coordonnées de son choix dans la fenêtre d'affichage d'un onglet. Il est impossible de distinguer l'application capturée de l'interaction directe de l'utilisateur.

En supposant que l'application de capture utilise un élément <video> appelé "previewTile", le code suivant montre comment relayer des événements de roue à l'onglet capturé:

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.
    // ...
  }
});

La méthode sendWheel() utilise un dictionnaire contenant deux ensembles de valeurs:

  • x et y: coordonnées du lieu où l'événement "roue" doit être diffusé.
  • wheelDeltaX et wheelDeltaY: magnitudes des défilements horizontaux et verticaux, en pixels. Notez que ces valeurs sont inversées par rapport à l'événement de roue d'origine.

Voici une implémentation possible de 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)];
}

Notez qu'il existe trois tailles différentes dans le code précédent:

  • Taille de l'élément <video>.
  • Taille des images capturées (représentées ici par trackSettings.width et trackSettings.height).
  • Taille de l'onglet.

La taille de l'élément <video> appartient entièrement au domaine de l'application de capture et n'est pas connue du navigateur. La taille de l'onglet dépend entièrement du domaine du navigateur et elle est inconnue de l'application Web.

L'application Web utilise translateCoordinates() pour traduire les décalages par rapport à l'élément <video> en coordonnées dans l'espace de coordonnées de la piste vidéo. Le navigateur effectue également une conversion entre la taille des frames capturés et la taille de l'onglet, et envoie l'événement de défilement à un décalage correspondant aux attentes de l'application Web.

La promesse renvoyée par sendWheel() peut être refusée dans les cas suivants:

  • Si la session de capture n'a pas encore commencé ou s'est déjà arrêtée, y compris l'arrêt asynchrone pendant que l'action sendWheel() est gérée par le navigateur.
  • Si l'utilisateur n'a pas autorisé l'application à utiliser sendWheel().
  • Si l'application de capture tente de générer un événement de défilement avec des coordonnées situées en dehors de [trackSettings.width, trackSettings.height] Notez que ces valeurs peuvent changer de manière asynchrone. Il est donc conseillé de détecter l'erreur et de l'ignorer. Notez que 0, 0 n'est normalement pas hors limites. Vous pouvez donc l'utiliser sans risque pour demander l'autorisation à l'utilisateur.

Zoom

Les interactions avec le niveau de zoom de l'onglet capturé s'effectuent via les surfaces CaptureController suivantes:

  • getSupportedZoomLevels() renvoie une liste des niveaux de zoom acceptés par le navigateur, représentés sous forme de pourcentages du "niveau de zoom par défaut", qui est défini sur 100%. Cette liste augmente de façon monotone et contient la valeur 100.
  • getZoomLevel() renvoie le niveau de zoom actuel de l'onglet.
  • setZoomLevel() définit le niveau de zoom de l'onglet sur n'importe quelle valeur entière présente dans getSupportedZoomLevels() et renvoie une promesse lorsqu'elle aboutit. Notez que le niveau de zoom n'est pas réinitialisé à la fin de la session de capture.
  • oncapturedzoomlevelchange vous permet d'écouter les changements de niveau de zoom d'un onglet capturé, car les utilisateurs peuvent le modifier via l'application de capture ou via une interaction directe avec l'onglet capturé.

Les appels à setZoomLevel() sont contrôlés par une autorisation. les appels vers les autres méthodes de zoom en lecture seule sont "sans frais", tout comme l'écoute d'événements.

<ph type="x-smartling-placeholder">

L'exemple suivant montre comment augmenter le niveau de zoom d'un onglet capturé dans une session de capture existante:

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'exemple suivant montre comment réagir aux changements de niveau de zoom d'un onglet capturé:

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

Détection de caractéristiques

Pour vérifier s'il est possible d'envoyer des événements de roue, utilisez:

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

Pour vérifier si le contrôle du zoom est pris en charge, utilisez:

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

Activer le contrôle de surface capturée

L'API Captured Surface Control est disponible dans Chrome sur ordinateur avec l'indicateur Captured Surface Control, et peut être activée sur chrome://flags/#captured-surface-control.

Cette fonctionnalité fait également l'objet d'une phase d'évaluation à partir de Chrome 122 sur ordinateur, qui permet aux développeurs d'activer la fonctionnalité pour les visiteurs de leurs sites afin de collecter des données auprès d'utilisateurs réels. Pour en savoir plus sur les phases d'évaluation et leur fonctionnement, consultez Premiers pas avec les phases d'évaluation.

Sécurité et confidentialité

Les règles d'autorisation "captured-surface-control" vous permettent de gérer l'accès de votre application de capture et des iFrame tiers intégrés à Captured Surface Control. Pour comprendre les compromis en termes de sécurité, consultez la section Considérations relatives à la confidentialité et à la sécurité de l'explication du contrôle de la surface capturée.

Démo

Vous pouvez jouer avec Captured Surface Control en exécutant la démonstration sur Glitch. N'oubliez pas de consulter le code source.

Modifications par rapport aux versions précédentes de Chrome

Voici quelques différences de comportement majeures concernant Captured Surface Control que vous devez connaître:

  • Dans Chrome 124 et les versions antérieures: <ph type="x-smartling-placeholder">
      </ph>
    • L'autorisation, si elle est accordée, est limitée à la session de capture associée à ce CaptureController, et non à l'origine de capture.
  • Dans Chrome 122: <ph type="x-smartling-placeholder">
      </ph>
    • getZoomLevel() renvoie une promesse avec le niveau de zoom actuel de l'onglet.
    • sendWheel() renvoie une promesse refusée avec le message d'erreur "No permission." si l'utilisateur n'a pas accordé l'autorisation d'utilisation à l'application. Dans Chrome 123 et versions ultérieures, le type d'erreur est "NotAllowedError".
    • oncapturedzoomlevelchange n'est pas disponible. Vous pouvez émuler cette fonctionnalité à l'aide de setInterval().

Commentaires

L'équipe Chrome et la communauté des normes Web souhaitent en savoir plus sur votre expérience avec Captured Surface Control.

Parlez-nous de la conception

Y a-t-il un élément Captured Surface Capture qui ne fonctionne pas comme prévu ? Ou s'il manque des méthodes ou des propriétés dont vous avez besoin pour mettre en œuvre votre idée ? Vous avez une question ou un commentaire sur le modèle de sécurité ? Signalez un problème de spécification dans le dépôt GitHub ou ajoutez vos commentaires à un problème existant.

Vous rencontrez un problème lors de l'implémentation ?

Avez-vous détecté un bug dans l'implémentation de Chrome ? Ou l'implémentation est-elle différente des spécifications ? Signalez un bug sur https://new.crbug.com. Veillez à inclure autant de détails que possible, ainsi que des instructions pour le reproduire. Glitch fonctionne particulièrement bien pour partager des bugs reproductibles.