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.

Viele Webanwendungen, die getDisplayMedia() verwenden, zeigen dem Nutzer eine Videovorschau der erfassten Oberfläche an. 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 einen 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 denselben Nachteil: Wenn der Nutzer mit einem aufgenommenen Tab oder Fenster interagieren möchte, muss er zu dieser Oberfläche wechseln, wodurch er die Videokonferenz-App verlässt. Das stellt einige Herausforderungen dar:

  • 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 Kontrolle 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“?

Die Verwendung der Funktion „Gecapturte Oberfläche steuern“ erfordert einige Schritte. So müssen Sie beispielsweise einen Browsertab explizit erfassen und die Berechtigung des Nutzers einholen, bevor Sie den erfassten Tab scrollen und zoomen können.

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. Dieses Objekt wird bald verwendet, 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;
}

Berechtigungsanfrage

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 nur, wenn die Aufforderung angezeigt werden muss. 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 zuerst eine Schaltfläche „Scrollen starten“ anzubieten, wie im folgenden Beispiel:

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 Ereignis „Rad“ 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():

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 (hier als trackSettings.width und trackSettings.height dargestellt).
  • Die Größe des Tabs.

Die Größe des <video>-Elements liegt vollständig in der Domain der App, die die Daten erfasst, und ist dem Browser nicht bekannt. 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. Der Browser wandelt die Größe der erfassten Frames ebenfalls in die Größe des Tabs um und sendet das Scrollereignis mit einem Offset, der den Erwartungen der Webanwendung entspricht.

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

  • Wenn die Erfassungssitzung noch nicht gestartet oder bereits beendet wurde, einschließlich asynchroner Beendigung, 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

Die Zoomstufe des erfassten Tabs kann über die folgenden CaptureController-Oberflächen geändert werden:

  • 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. Die Zoomstufe wird am Ende der Aufnahmesitzung nicht zurückgesetzt.
  • 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ächen 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.

Diese Funktion wird ab Chrome 122 auf dem Computer in einer Testphase für Ursprünge eingeführt. Entwickler können 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 "captured-surface-control" Berechtigungsrichtlinie können Sie festlegen, wie Ihre App zum Erfassen von Inhalten und eingebettete Iframes von Drittanbietern auf die Steuerelemente für erfasste Oberflächen zugreifen. 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 die Funktion „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 von Ihnen wissen, welche Erfahrungen Sie mit der Funktion „Captured Surface Control“ gemacht haben.

Designbeschreibung

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.