Auf einem aufgenommenen Tab scrollen und zoomen

François Beaufort
François Beaufort

Mit der Screen Capture API ist es bereits möglich, Tabs, Fenster und Bildschirme auf der Webplattform zu teilen. Wenn eine Webanwendung getDisplayMedia() aufruft, wird der Nutzer von Chrome aufgefordert, einen Tab, ein Fenster oder einen Bildschirm als MediaStreamTrack-Video mit der Webanwendung zu teilen.

In vielen Web-Apps, für die getDisplayMedia() verwendet wird, wird dem Nutzer eine Videovorschau der aufgenommenen Oberfläche angezeigt. Beispielsweise wird dieses Video in Videokonferenz-Apps häufig an Remote-Nutzer gestreamt und gleichzeitig in einem lokalen HTMLVideoElement gerendert, damit der lokale Nutzer ständig eine Vorschau dessen sieht, was er teilt.

In dieser Dokumentation wird die neue Captured Surface Control API in Chrome vorgestellt. Mit dieser API kann Ihre Webanwendung in einem erfassten Tab scrollen und die Zoomstufe eines erfassten Tabs lesen und schreiben.

Ein Nutzer scrollt und zoomt auf einem erfassten Tab (Demo).

Vorteile der Funktion „Captured Surface Control“

Alle Videokonferenz-Apps haben den gleichen Nachteil: Wenn der Nutzer mit einem aufgenommenen Tab oder Fenster interagieren möchte, muss er zu dieser Oberfläche wechseln, sodass er die Videokonferenz-App nicht mehr nutzen kann. Dies bringt einige Herausforderungen mit sich:

  • Der Nutzer kann die geteilte App und die Videos der Remote-Nutzer nicht gleichzeitig sehen, es sei denn, er verwendet die Funktion Bild im Bild oder separate Fenster nebeneinander für den Tab „Videokonferenz“ und den Tab „Geteilt“. Auf einem kleineren Bildschirm kann das schwierig sein.
  • Der Nutzer muss zwischen der Videokonferenz-App und der erfassten Oberfläche wechseln.
  • Der Nutzer verliert den Zugriff auf die Steuerelemente der Videokonferenz-App, während er nicht aktiv ist. Dazu gehören beispielsweise eine eingebettete Chat-App, Emoji-Reaktionen, Benachrichtigungen über Nutzer, die am Anruf teilnehmen möchten, Multimedia- und Layoutsteuerungen sowie andere nützliche Funktionen für Videokonferenzen.
  • Der Vortragende kann die Steuerung nicht an Remote-Teilnehmer delegieren. Das führt zu dem allzu bekannten Szenario, in dem Remote-Nutzer den Vortragenden bitten, die Folie zu wechseln, ein wenig nach oben und unten zu scrollen oder die Zoomstufe anzupassen.

Die Captured Surface Control API löst diese Probleme.

Wie verwende ich die Funktion „Captured Surface Control“?

Für die erfolgreiche Verwendung von Captured Surface Control sind einige Schritte erforderlich. So muss z. B. ein Browsertab explizit erfasst und der Nutzer die Erlaubnis eingeholt werden, bevor auf dem erfassten Tab gescrollt und gezoomt werden kann.

Browsertab erfassen

Bitten Sie den Nutzer zuerst, eine Oberfläche auszuwählen, die er mit getDisplayMedia() teilen möchte, und ordnen Sie der Aufnahmesitzung ein CaptureController-Objekt zu. Wir verwenden dieses Objekt demnächst, um die erfasste Oberfläche zu steuern.

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

Erstellen Sie als Nächstes eine lokale Vorschau der erfassten Oberfläche in Form eines <video>-Elements:

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

Wenn der Nutzer ein Fenster oder einen Bildschirm freigibt, ist das derzeit nicht möglich. Wenn er jedoch einen Tab freigibt, können wir fortfahren.

const [track] = stream.getVideoTracks();

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

Berechtigungsaufforderung

Beim ersten Aufruf von sendWheel() oder setZoomLevel() für ein bestimmtes CaptureController-Objekt wird eine Berechtigungsanfrage angezeigt. Wenn der Nutzer die Berechtigung erteilt, sind weitere Aufrufe dieser Methoden für dieses CaptureController-Objekt zulässig. Wenn der Nutzer die Berechtigung ablehnt, wird das zurückgegebene Promise abgelehnt.

CaptureController-Objekte sind eindeutig mit einer bestimmten Aufnahmesitzung verknüpft, können nicht mit einer anderen Aufnahmesitzung verknüpft werden und bleiben nicht erhalten, wenn die Seite verlassen wird, auf der sie definiert sind. Erfassungssitzungen überleben jedoch die Navigation auf der erfassten Seite.

Es ist eine Nutzergeste erforderlich, um dem Nutzer eine Berechtigungsanfrage zu zeigen. Nur sendWheel()- und setZoomLevel()-Aufrufe erfordern eine Nutzergeste, und auch nur dann, wenn die Aufforderung angezeigt werden soll. Wenn der Nutzer in der Webanwendung auf eine Schaltfläche zum Heranzoomen oder Herauszoomen klickt, ist diese Touch-Geste gegeben. Wenn die App jedoch zuerst eine Scrollsteuerung anbieten soll, sollten Entwickler bedenken, dass Scrollen keine Touch-Geste darstellt. Eine Möglichkeit besteht darin, dem Nutzer zunächst eine Schaltfläche zum Scrollen zu präsentieren, wie im folgenden Beispiel gezeigt:

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

Scrollen

Mit sendWheel() kann eine Erfassungs-App Raddrehereignisse der gewünschten Größe über beliebige Koordinaten innerhalb des Ansichtsbereichs eines Tabs senden. Das Ereignis ist für die erfasste App nicht von einer direkten Nutzerinteraktion zu unterscheiden.

Angenommen, die App, in der die Aufnahme erfolgt, verwendet ein <video>-Element namens "previewTile". Im folgenden Code wird gezeigt, wie Sie Senderad-Ereignisse an den Tab weiterleiten, auf dem die Aufnahme erfolgt:

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

Die Methode sendWheel() nimmt ein Dictionary mit zwei Werten an:

  • x und y: die Koordinaten, an denen das Raddrehereignis gesendet werden soll.
  • wheelDeltaX und wheelDeltaY: die Größe der Scrollbewegungen in Pixeln, jeweils für horizontale und vertikale Scrollbewegungen. Beachten Sie, dass diese Werte im Vergleich zum ursprünglichen Raddrehereignis umgekehrt sind.

Eine mögliche Implementierung von translateCoordinates() ist:

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

Im Code oben werden drei verschiedene Größen verwendet:

  • Die Größe des <video>-Elements.
  • Die Größe der erfassten Frames (dargestellt als trackSettings.width und trackSettings.height).
  • Die Größe des Tabs.

Die Größe des <video>-Elements befindet sich vollständig innerhalb der Domain der Erfassungs-App und ist dem Browser unbekannt. Die Größe des Tabs liegt vollständig in der Domain des Browsers und ist der Webanwendung nicht bekannt.

In der Webanwendung werden mit translateCoordinates() die Offsets relativ zum <video>-Element in Koordinaten im eigenen Koordinatenraum des Videotracks umgewandelt. Ebenso übersetzt der Browser die Größe der erfassten Frames und die Größe des Tabs und liefert das Scroll-Ereignis mit einem Versatz, der den Erwartungen der Web-App entspricht.

Das von sendWheel() zurückgegebene Promise kann in den folgenden Fällen abgelehnt werden:

  • Die Erfassungssitzung wurde noch nicht gestartet oder bereits beendet. Dazu gehört auch das asynchrone Beenden der Sitzung, während die sendWheel()-Aktion vom Browser verarbeitet wird.
  • Wenn der Nutzer der App nicht die Berechtigung zur Verwendung von sendWheel() erteilt hat.
  • Wenn die App, die die Daten erfasst, versucht, ein Scrollereignis mit Koordinaten zu senden, die außerhalb von [trackSettings.width, trackSettings.height] liegen. Diese Werte können sich asynchron ändern. Daher ist es empfehlenswert, den Fehler zu erfassen und zu ignorieren. Hinweis: 0, 0 ist normalerweise nicht außerhalb des zulässigen Bereichs, sodass Sie den Nutzer sicher um Erlaubnis bitten können.

Zoom

Das Interagieren mit der Zoomstufe des erfassten Tabs erfolgt über die folgenden CaptureController-Oberflächen:

  • getSupportedZoomLevels() gibt eine Liste der vom Browser unterstützten Zoomstufen zurück, die als Prozentsätze der „Standardzoomstufe“ dargestellt werden, die als 100 % definiert ist. Diese Liste ist monoton steigend und enthält den Wert 100.
  • getZoomLevel() gibt die aktuelle Zoomstufe des Tabs zurück.
  • setZoomLevel() legt den Zoomlevel des Tabs auf einen beliebigen Ganzzahlwert in getSupportedZoomLevels() fest und gibt ein Promise zurück, wenn der Vorgang erfolgreich war. Beachten Sie, dass die Zoomstufe am Ende der Aufnahmesitzung nicht zurückgesetzt wird.
  • Mit oncapturedzoomlevelchange können Sie Änderungen der Zoomstufe eines erfassten Tabs erfassen, da Nutzer die Zoomstufe entweder über die App zum Erfassen oder durch direkte Interaktion mit dem erfassten Tab ändern können.

Aufrufe von setZoomLevel() sind durch eine Berechtigung eingeschränkt. Aufrufe der anderen, schreibgeschützten Zoommethoden sind „kostenlos“, ebenso wie das Abhören von Ereignissen.

Im folgenden Beispiel wird gezeigt, wie Sie die Zoomstufe eines erfassten Tabs in einer vorhandenen Aufnahmesitzung erhöhen:

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

Im folgenden Beispiel wird gezeigt, wie Sie auf Änderungen der Zoomstufe eines erfassten Tabs reagieren:

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

Funktionserkennung

So prüfen Sie, ob das Senden von Mausrad-Ereignissen unterstützt wird:

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

So prüfen Sie, ob die Zoomsteuerung unterstützt wird:

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

Steuerelement für erfasste Oberfläche aktivieren

Die Captured Surface Control API ist in Chrome auf dem Computer unter dem Flag „Captured Surface Control“ verfügbar und kann unter chrome://flags/#captured-surface-control aktiviert werden.

Für diese Funktion beginnt ein Ursprungstest mit Chrome 122 auf Computern. So können Entwickler die Funktion für Besucher ihrer Websites aktivieren, um Daten von echten Nutzern zu erheben. Weitere Informationen zu Ursprungstests und ihrer Funktionsweise finden Sie unter Einstieg in Ursprungstests.

Sicherheit und Datenschutz

Mit der Berechtigungsrichtlinie "captured-surface-control" können Sie festlegen, wie Ihre Erfassungs-App und eingebettete iFrames von Drittanbietern Zugriff auf Captured Surface Control haben. Weitere Informationen zu den Sicherheitsabwägungen finden Sie im Abschnitt Datenschutz- und Sicherheitsaspekte der Erläuterung zur Funktion „Gescannte Oberfläche steuern“.

Demo

Sie können Captured Surface Control ausprobieren, indem Sie die Demo auf Glitch ausführen. Sehen Sie sich den Quellcode an.

Änderungen gegenüber früheren Versionen von Chrome

Im Folgenden sind einige wichtige Verhaltensunterschiede bei der Erfassung von Oberflächen zu beachten:

  • In Chrome 124 und niedriger:
    • Die Berechtigung gilt – sofern gewährt – nur für die Aufnahmesitzung, die mit dieser CaptureController verknüpft ist, nicht für den Aufnahmeursprung.
  • In Chrome 122:
    • getZoomLevel() gibt ein Versprechen mit der aktuellen Zoomstufe des Tabs zurück.
    • sendWheel() gibt ein Versprechen zurück, das mit der Fehlermeldung "No permission." abgelehnt wird, wenn der Nutzer der App keine Berechtigung zur Verwendung erteilt hat. Der Fehlertyp ist "NotAllowedError" in Chrome 123 und höher.
    • oncapturedzoomlevelchange ist nicht verfügbar. Sie können diese Funktion mit setInterval() polyfillen.

Feedback

Das Chrome-Team und die Webstandards-Community möchten mehr über Ihre Erfahrungen mit Captured Surface Control erfahren.

Erzähl uns etwas über das Design

Funktioniert die Funktion „Aufgenommene Oberfläche erfassen“ nicht wie erwartet? Oder fehlen Methoden oder Eigenschaften, die Sie für die Implementierung Ihrer Idee benötigen? Haben Sie Fragen oder Kommentare zum Sicherheitsmodell? Melden Sie ein Problem mit der Spezifikation im GitHub-Repository oder fügen Sie Ihre Gedanken zu einem vorhandenen Problem hinzu.

Problem bei der Implementierung?

Haben Sie einen Fehler in der Chrome-Implementierung gefunden? Oder unterscheidet sich die Implementierung von der Spezifikation? Melden Sie den Fehler unter https://new.crbug.com. Geben Sie dabei so viele Details wie möglich an und geben Sie eine Anleitung zur Reproduktion an. Glitch eignet sich hervorragend, um reproduzierbare Fehler zu teilen.