Auf einem aufgenommenen Tab scrollen und zoomen

François Beaufort
François Beaufort

Über die Screen Capture API ist das Teilen von Tabs, Fenstern und Bildschirmen auf der Webplattform bereits möglich. Wenn eine Web-App getDisplayMedia() aufruft, fordert Chrome den Nutzer auf, einen Tab, ein Fenster oder einen Bildschirm als MediaStreamTrack-Video mit der Web-App zu teilen.

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

In dieser Dokumentation wird die neue Captured Surface Control API in Chrome vorgestellt. Damit können Sie in Ihrer Web-App durch einen erfassten Tab scrollen sowie die Zoomstufe eines erfassten Tabs lesen und schreiben.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
Ein Nutzer scrollt und zoomt auf einem erfassten Tab (Demo).

Vorteile von Captured Surface Control

Alle Videokonferenz-Apps haben denselben Nachteil: Wenn der Nutzer mit einem aufgenommenen Tab oder einem aufgenommenen Fenster interagieren möchte, muss er zu dieser Oberfläche wechseln und die Videokonferenz-App verlassen. Dies bringt einige Herausforderungen mit sich:

  • Der Nutzer kann die aufgenommene App und die Videos von Remote-Nutzern nur dann gleichzeitig sehen, wenn er Bild im Bild oder separate Fenster für die Videokonferenz- und den geteilten Tab verwendet. Auf einem kleineren Bildschirm kann das schwierig sein.
  • Der Nutzer wird dadurch belastet, dass er zwischen der Videokonferenz-App und der aufgenommenen Oberfläche wechseln muss.
  • Der Nutzer verliert den Zugriff auf die Steuerelemente, die von der Videokonferenz-App angezeigt werden, während er sich nicht in der Nähe befindet. z. B. eine eingebettete Chat-App, Emoji-Reaktionen, Benachrichtigungen über Nutzer, die um Teilnahme am Anruf bitten, Multimedia- und Layoutsteuerelemente und andere nützliche Funktionen für Videokonferenzen.
  • Der Vortragende kann die Steuerung nicht an Remote-Teilnehmer delegieren. Das führt zu dem allzu vertrauten Szenario, in dem Remote-Nutzer den Vortragenden bitten, die Folie zu ändern, etwas nach oben und unten zu scrollen oder die Zoomstufe anzupassen.

Die Captured Surface Control API löst diese Probleme.

Wie verwende ich die Oberflächensteuerung?

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

Fordere den Nutzer zuerst auf, eine Oberfläche für die Freigabe mit getDisplayMedia() auszuwählen, und verknüpfe dabei ein CaptureController-Objekt mit der Aufnahmesitzung. Wir werden dieses Objekt bald verwenden, 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 dies vorerst nicht enthalten. 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 Berechtigungsaufforderung ausgelöst. Wenn der Nutzer die Berechtigung erteilt, sind weitere Aufrufe dieser Methoden für das CaptureController-Objekt zulässig. Wenn der Nutzer die Berechtigung ablehnt, wird das zurückgegebene Promise abgelehnt.

CaptureController-Objekte sind eindeutig einer bestimmten capture-session zugeordnet und können keiner anderen Erfassungssitzung zugeordnet werden. Außerdem bleiben sie nicht nach der Navigation auf der Seite erhalten, auf der sie definiert wurden. Erfassungssitzungen überdauern jedoch die Navigation der erfassten Seite.

Eine Nutzergeste ist erforderlich, um dem Nutzer eine Berechtigungsaufforderung anzuzeigen. Nur sendWheel()- und setZoomLevel()-Aufrufe erfordern eine Nutzergeste, und auch nur dann, wenn die Aufforderung angezeigt werden soll. Wenn der Nutzer in der Web-App auf eine Schaltfläche zum Vergrößern oder Verkleinern klickt, ist diese Geste eine gegeben. Wenn in der App jedoch zuerst eine Scroll-Steuerung angeboten werden soll, sollten Entwickler bedenken, dass Scrollen keine Nutzergeste darstellt. Eine Möglichkeit besteht darin, den Nutzenden zunächst eine Funktion zum Scrollen anzubieten. 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 im Darstellungsbereich eines Tabs Radereignisse ihrer gewählten Größenordnung über Koordinaten ihrer Wahl übertragen. Das Ereignis ist von der erfassten App nicht von der direkten Nutzerinteraktion zu unterscheiden.

Unter der Annahme, dass die Erfassungs-App ein <video>-Element namens "previewTile" verwendet, zeigt der folgende Code, wie Radereignisse an den erfassten Tab gesendet werden:

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() verwendet ein Wörterbuch mit zwei Gruppen von Werten:

  • x und y: die Koordinaten, an die das Rad-Ereignis geliefert werden soll.
  • wheelDeltaX und wheelDeltaY: Das Ausmaß des Scrollens in Pixeln für horizontales bzw. vertikales Scrollen. Beachten Sie, dass diese Werte im Vergleich zum ursprünglichen wheel-Ereignis invertiert 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 sind drei verschiedene Größen zu sehen:

  • 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 befindet sich vollständig innerhalb der Browserdomain und ist der Web-App nicht bekannt.

Die Web-App verwendet translateCoordinates(), um die Offsets relativ zum <video>-Element in Koordinaten innerhalb des eigenen Koordinatenbereichs des Videotracks umzuwandeln. 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 zählt auch das asynchrone Beenden der Sitzung, während die sendWheel()-Aktion vom Browser verarbeitet wird.
  • Wenn der Nutzer der App keine Berechtigung zur Verwendung von sendWheel() gewährt hat.
  • Wenn die Erfassungs-App versucht, ein Scroll-Ereignis mit Koordinaten außerhalb von [trackSettings.width, trackSettings.height] bereitzustellen. Da sich diese Werte möglicherweise asynchron ändern können, sollten Sie den Fehler abfangen und ignorieren. Beachten Sie, dass 0, 0 normalerweise nicht außerhalb des gültigen Bereichs liegt. Sie können sie also bedenkenlos verwenden, um den Nutzer um die Berechtigung zu bitten.

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, dargestellt als Prozentsätze der „Standard-Zoomstufe“, die als 100 % definiert ist. Diese Liste erhöht sich kontinuierlich und enthält den Wert 100.
  • getZoomLevel() gibt die aktuelle Zoomstufe des Tabs zurück.
  • setZoomLevel() legt die Zoomstufe des Tabs auf einen beliebigen Ganzzahlwert in getSupportedZoomLevels() fest und gibt ein Promise zurück, wenn die Ausführung erfolgreich ist. Beachten Sie, dass die Zoomstufe am Ende der Aufnahmesitzung nicht zurückgesetzt wird.
  • Mit oncapturedzoomlevelchange können Sie sich die Änderungen der Zoomstufe eines erfassten Tabs anhören, wenn Nutzer die Zoomstufe entweder über die Erfassungs-App oder durch direkte Interaktion mit dem erfassten Tab ändern.

Aufrufe an setZoomLevel() sind durch eine entsprechende Berechtigung gesteuert. -Aufrufe der anderen, schreibgeschützten Zoom-Methoden sind kostenlos, ebenso wie das Überwachen von Ereignissen.

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

Im folgenden Beispiel sehen Sie, wie Sie die Zoomstufe eines erfassten Tabs in einer bestehenden 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 sehen Sie, wie Sie auf Änderungen der Zoomstufe eines erfassten Tabs reagieren:

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

Funktionserkennung

Um zu überprüfen, ob das Senden von Rad-Ereignissen unterstützt wird, verwenden Sie:

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

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

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

Steuerung der Oberfläche aktivieren

Die Captured Surface Control API ist in Chrome auf Computern 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 Erste Schritte mit 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 Vor- und Nachteilen der Sicherheit finden Sie im Abschnitt Überlegungen zu Datenschutz und Sicherheit der Erläuterung der Captured Surface Control.

Demo

Du kannst Captured Surface Control ausprobieren, indem du die Demo auf Glitch ausführst. Sehen Sie sich unbedingt den Quellcode an.

Änderungen im Vergleich zu früheren Chrome-Versionen

Beachten Sie die folgenden wichtigen Verhaltensunterschiede in Bezug auf die Steuerung der Oberfläche:

  • Chrome 124 und niedriger: <ph type="x-smartling-placeholder">
      </ph>
    • Die Berechtigung, sofern sie gewährt wird, bezieht sich auf die Erfassungssitzung, die mit diesem CaptureController verknüpft ist, und nicht auf den Aufnahmeursprung.
  • In Chrome 122: <ph type="x-smartling-placeholder">
      </ph>
    • getZoomLevel() gibt ein Promise 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 gewährt hat. In Chrome 123 und höher wird der Fehlertyp "NotAllowedError" angezeigt.
    • oncapturedzoomlevelchange ist nicht verfügbar. Für diese Funktion kannst du setInterval() verwenden.

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

Gibt es etwas an Captured Surface Capture, das nicht wie erwartet funktioniert? Oder fehlen Methoden oder Eigenschaften, die du zur Umsetzung deiner Idee benötigst? Haben Sie eine Frage oder einen Kommentar zum Sicherheitsmodell? Reichen Sie ein Spezifikationsproblem im GitHub-Repository ein oder fügen Sie Ihre Gedanken zu einem bestehenden Problem hinzu.

Probleme bei der Implementierung?

Haben Sie bei der Implementierung von Chrome einen Fehler gefunden? Oder weicht die Implementierung von der Spezifikation ab? Melden Sie den Fehler unter https://new.crbug.com. Gib so viele Details wie möglich und eine Anleitung zum Reproduzieren an. Glitch eignet sich hervorragend, um reproduzierbare Fehler zu teilen.