캡처된 탭 스크롤 및 확대/축소

François Beaufort
François Beaufort

Screen Capture API를 사용하여 웹 플랫폼에서 이미 탭, 창 및 화면을 공유할 수 있습니다. 웹 앱에서 getDisplayMedia()를 호출하면 Chrome은 사용자에게 탭, 창 또는 화면을 웹 앱과 MediaStreamTrack 동영상으로 공유하라는 메시지를 표시합니다.

getDisplayMedia()를 사용하는 많은 웹 앱에서는 캡처된 노출 영역의 동영상 미리보기를 사용자에게 표시합니다. 예를 들어 화상 회의 앱은 이 동영상을 원격 사용자에게 스트리밍하는 동시에 로컬 HTMLVideoElement에도 렌더링하기 때문에 로컬 사용자는 공유 중인 콘텐츠의 미리보기를 계속 볼 수 있습니다.

이 문서에서는 Chrome의 새로운 Captured Surface Control API를 도입합니다. 이 API를 사용하면 웹 앱에서 캡처된 탭을 스크롤하고 캡처된 탭의 확대/축소 수준을 읽고 쓸 수 있습니다.

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
사용자가 캡처된 탭을 스크롤하고 확대/축소합니다 (데모).

캡처된 노출 영역 제어를 사용해야 하는 이유

모든 화상 회의 앱에는 동일한 단점이 있습니다. 사용자가 캡처된 탭이나 창과 상호작용하려면 해당 표시 경로로 전환하여 화상 회의 앱에서 나가야 합니다. 여기에는 다음과 같은 문제가 있습니다.

  • 사용자가 화상 회의 탭과 공유 탭에 PIP 모드 또는 별도의 나란히 창을 사용하지 않는 한 캡처된 앱과 원격 사용자의 동영상을 동시에 볼 수 없습니다. 작은 화면에서는 이 작업이 어려울 수 있습니다.
  • 사용자는 화상 회의 앱과 캡처된 표시 경로 사이를 이동해야 하는 부담을 느낍니다.
  • 사용자가 화상 회의 앱을 사용할 수 없는 동안에는 화상 회의 앱에서 노출하는 컨트롤에 액세스할 수 없습니다. 예를 들어 삽입된 채팅 앱, 이모티콘 반응, 통화 참여 요청 사용자 관련 알림, 멀티미디어 및 레이아웃 제어, 기타 유용한 화상 회의 기능이 있습니다.
  • 발표자는 원격 참여자에게 제어 권한을 위임할 수 없습니다. 이로 인해 원격 사용자가 발표자에게 슬라이드를 변경하거나, 위아래로 약간 스크롤하거나, 확대/축소 수준을 조정하도록 요청하는 매우 익숙한 시나리오로 이어집니다.

Captured Surface Control API는 이러한 문제를 해결합니다.

캡처된 노출 영역 컨트롤을 사용하려면 어떻게 해야 하나요?

캡처된 표면 제어를 성공적으로 사용하려면 캡처된 탭을 스크롤하고 확대/축소하기 전에 브라우저 탭을 명시적으로 캡처하고 사용자로부터 권한을 얻는 등의 몇 가지 단계를 거쳐야 합니다.

브라우저 탭 캡처

먼저 getDisplayMedia()를 사용하여 공유할 영역을 선택하라는 메시지를 사용자에게 표시하고 이 과정에서 CaptureController 객체를 캡처 세션과 연결합니다. 이 객체를 사용하여 캡처된 표면을 곧 제어할 것입니다.

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

그런 다음 캡처된 노출 영역의 로컬 미리보기를 <video> 요소 형식으로 생성합니다.

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

사용자가 창이나 화면을 공유하는 것은 현재로서는 지원되지 않습니다. 하지만 사용자가 탭을 공유하기로 하면 계속 진행할 수 있습니다.

const [track] = stream.getVideoTracks();

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

권한 메시지

지정된 CaptureController 객체에서 sendWheel() 또는 setZoomLevel()를 처음 호출하면 권한 메시지가 표시됩니다. 사용자가 권한을 부여하면 CaptureController 객체에서 이러한 메서드를 추가로 호출할 수 있습니다. 사용자가 권한을 거부하면 반환된 프로미스가 거부됩니다.

CaptureController 객체는 특정 capture-session과 고유하게 연결되고 다른 캡처 세션과 연결될 수 없으며 객체가 정의된 페이지를 탐색한 후에는 유지되지 않습니다. 하지만 캡처 세션은 캡처된 페이지의 탐색 후에도 유지됩니다.

사용자에게 권한 메시지를 표시하려면 사용자 동작이 필요합니다. 메시지를 표시해야 하는 경우에만 sendWheel()setZoomLevel() 호출에 사용자 동작이 필요합니다. 사용자가 웹 앱에서 확대 또는 축소 버튼을 클릭하면 해당 사용자 동작이 주어집니다. 그러나 앱에서 먼저 스크롤 제어를 제공하려는 경우 개발자는 스크롤이 사용자 동작을 구성하지 않습니다. 한 가지 방법은 먼저 사용자에게 '스크롤 시작'을 제안하는 것입니다. 버튼에 대해 자세히 알아보세요.

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

스크롤

sendWheel()를 사용하면 캡처 앱이 탭의 표시 영역 내에서 선택한 좌표를 통해 선택한 크기의 휠 이벤트를 전달할 수 있습니다. 이벤트가 캡처된 앱과 직접적인 사용자 상호작용을 구별할 수 없습니다.

캡처 앱에서 "previewTile"라는 <video> 요소를 사용한다고 가정하면 다음 코드는 전송 휠 이벤트를 캡처된 탭으로 전달하는 방법을 보여줍니다.

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

sendWheel() 메서드는 두 가지 값 집합이 있는 딕셔너리를 사용합니다.

  • xy: 휠 이벤트가 게재될 좌표입니다.
  • wheelDeltaXwheelDeltaY: 각각 가로 및 세로 스크롤의 스크롤 크기(픽셀)입니다. 이 값은 원래 바퀴 이벤트에 비해 반전됩니다.

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

앞선 코드에는 세 가지 크기가 있습니다.

  • <video> 요소의 크기
  • 캡처된 프레임의 크기입니다 (여기서는 trackSettings.widthtrackSettings.height로 표시됨).
  • 탭의 크기입니다.

<video> 요소의 크기는 캡처 앱의 도메인 내에 완전히 포함되며 브라우저에서 알 수 없습니다. 탭의 크기는 완전히 브라우저 도메인 내에 있으며 웹 앱에서는 알 수 없습니다.

웹 앱은 translateCoordinates()를 사용하여 <video> 요소를 기준으로 한 오프셋을 동영상 트랙의 자체 좌표 공간 내의 좌표로 변환합니다. 마찬가지로 브라우저는 캡처된 프레임의 크기와 탭 크기 간에 변환하고 웹 앱의 예상에 해당하는 오프셋으로 스크롤 이벤트를 전달합니다.

다음과 같은 경우 sendWheel()에서 반환된 프로미스가 거부될 수 있습니다.

  • 캡처 세션이 아직 시작되지 않았거나 이미 중지된 경우(브라우저에서 sendWheel() 작업을 처리하는 동안 비동기식 중지 포함)
  • 사용자가 sendWheel() 사용 권한을 앱에 부여하지 않은 경우
  • 캡처 중인 앱이 [trackSettings.width, trackSettings.height] 외부의 좌표에서 스크롤 이벤트를 전달하려고 시도하는 경우 이 값은 비동기식으로 변경될 수 있으므로 오류를 포착하여 무시하는 것이 좋습니다. (0, 0는 일반적으로 범위를 벗어나지 않으므로 사용자에게 권한을 요청하는 데 사용하는 것이 안전합니다.)

확대/축소

캡처된 탭의 확대/축소 수준과 상호작용은 다음 CaptureController 영역을 통해 이루어집니다.

  • getSupportedZoomLevels()는 브라우저에서 지원하는 확대/축소 수준의 목록을 반환하며, 이는 100%로 정의된 '기본 확대/축소 수준'의 백분율로 표시됩니다. 이 목록은 단조 증가하며 값 100을 포함합니다.
  • getZoomLevel()은 탭의 현재 확대/축소 수준을 반환합니다.
  • setZoomLevel()는 탭의 확대/축소 수준을 getSupportedZoomLevels()에 있는 정수 값으로 설정하고 성공 시 프로미스를 반환합니다. 확대/축소 수준은 캡처 세션이 끝날 때 재설정되지 않습니다.
  • 사용자가 캡처 앱을 통해 또는 캡처된 탭과의 직접 상호작용을 통해 확대/축소 수준을 변경할 수 있으므로 oncapturedzoomlevelchange를 사용하면 캡처된 탭의 확대/축소 수준 변경사항을 들을 수 있습니다.

setZoomLevel() 호출은 권한에 의해 관리됩니다. 다른 읽기 전용 확대/축소 메서드에 대한 호출과 이벤트 수신 대기는 '무료'입니다.

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

다음 예는 기존 캡처 세션에서 캡처된 탭의 확대/축소 수준을 높이는 방법을 보여줍니다.

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

다음 예는 캡처된 탭의 확대/축소 수준 변경에 반응하는 방법을 보여줍니다.

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

특성 감지

휠 이벤트 전송이 지원되는지 확인하려면 다음을 사용합니다.

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

확대/축소 제어가 지원되는지 확인하려면 다음을 사용하세요.

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

캡처된 노출 영역 제어 사용 설정

Captured Surface Control API는 데스크톱의 Chrome에서 캡처된 Surface Control 플래그 뒤를 통해 사용할 수 있으며 chrome://flags/#captured-surface-control에서 사용 설정할 수 있습니다.

또한 이 기능은 데스크톱용 Chrome 122부터 오리진 트라이얼이 시작됩니다. 오리진 트라이얼이 시작되면 개발자는 사이트 방문자가 실제 사용자로부터 데이터를 수집할 수 있도록 이 기능을 사용 설정할 수 있습니다. 오리진 트라이얼 및 작동 방식에 대한 자세한 내용은 오리진 트라이얼 시작하기를 참고하세요.

보안 및 개인 정보 보호

"captured-surface-control" 권한 정책을 사용하면 캡처 앱 및 삽입된 서드 파티 iframe이 캡처된 Surface Control에 액세스하는 방식을 관리할 수 있습니다. 보안 장단점을 이해하려면 캡처된 Surface Control 설명의 개인 정보 보호 및 보안 고려사항 섹션을 확인하세요.

데모

Glitch에서 데모를 실행하여 Captured Surface Control을 사용해 볼 수 있습니다. 소스 코드를 확인하세요.

이전 버전의 Chrome에서 변경된 사항

다음은 캡처된 노출 영역 컨트롤에 관해 알아야 할 몇 가지 주요 동작 차이입니다.

  • Chrome 124 이하: <ph type="x-smartling-placeholder">
      </ph>
    • 권한이 부여된 경우 권한은 캡처 출처가 아닌 해당 CaptureController와 연결된 캡처 세션으로 범위가 지정됩니다.
  • Chrome 122의 경우: <ph type="x-smartling-placeholder">
      </ph>
    • getZoomLevel()는 탭의 현재 확대/축소 수준과 함께 프로미스를 반환합니다.
    • sendWheel()는 사용자가 앱에 사용 권한을 부여하지 않은 경우 "No permission." 오류 메시지와 함께 거부된 프로미스를 반환합니다. Chrome 123 이상에서 오류 유형은 "NotAllowedError"입니다.
    • oncapturedzoomlevelchange님이 전화를 받을 수 없습니다. setInterval()를 사용하여 이 지형지물을 폴리필할 수 있습니다.

의견

Chrome팀과 웹 표준 커뮤니티는 여러분의 Captured Surface Control 사용 경험에 관한 의견을 듣고자 합니다.

디자인에 대해 알려주세요.

캡처된 표면 캡처에서 예상대로 작동하지 않는 부분이 있나요? 아니면 아이디어를 구현하는 데 필요한 메서드나 속성이 누락되었나요? 보안 모델에 대한 질문이나 의견이 있습니까? GitHub 저장소에서 사양 문제를 신고하거나 기존 문제에 의견을 추가하세요.

구현에 문제가 있습니까?

Chrome 구현에서 버그를 발견하셨나요? 아니면 구현이 사양과 다른가요? https://new.crbug.com에서 버그를 신고합니다. 재현 방법에 대한 안내와 함께 세부정보를 최대한 많이 포함하세요. Glitch는 재현 가능한 버그를 공유하는 데 효과적입니다.