HTML5에서 브라우저 탭을 화면공유하는 경우

지난 몇 년 동안 저는 몇몇 다른 기업에서 브라우저 기술만 사용하여 화면 공유와 유사한 기능을 구현할 수 있도록 지원했습니다. 내 경험에 비추어 볼 때 VNC를 웹 플랫폼 기술에서만 구현 (플러그인 없음)하는 것은 어려운 문제입니다. 고려해야 할 사항도 많고 극복해야 할 과제도 많습니다. 마우스 포인터 위치 전달, 키 입력 전달, 60fps에서 완전한 24비트 색상 재페인트를 달성하는 것 등은 몇 가지 문제들에 불과합니다.

탭 콘텐츠 캡처

기존 화면 공유의 복잡성을 제거하고 브라우저 탭의 콘텐츠를 공유하는 데 집중하면 문제는 a) 현재 상태에서 표시되는 탭을 캡처하고 b) 유선을 통해 해당 '프레임'을 전송하는 문제로 크게 간소화됩니다. 기본적으로 DOM의 스냅샷을 생성하고 이를 공유할 방법이 필요합니다.

공유는 쉽습니다. WebSocket은 다양한 형식 (문자열, JSON, 바이너리)으로 데이터를 전송할 수 있습니다. 스냅샷 부분은 훨씬 더 어려운 문제입니다. html2canvas와 같은 프로젝트에서는 자바스크립트로 브라우저의 렌더링 엔진을 다시 구현하여 HTML 스크린 캡처를 해결했습니다. 또 다른 예로는 오픈소스가 아니지만 Google 피드백이 있습니다. 이러한 유형의 프로젝트는 매우 멋지지만 매우 느립니다. 1fps의 처리량을 얻을 수 있다면 좋았을 것입니다. 60fps보다 훨씬 적은 처리량을 얻을 수 있었을 것입니다.

이 도움말에서는 탭의 '화면 공유'와 관련하여 제가 좋아하는 몇 가지 개념 증명 솔루션을 설명합니다.

메서드 1: 변형 관찰자 + WebSocket

올해 초 +Rafael Weinstein은 탭을 미러링하는 방법을 보여주었습니다. 그의 기법은 Mutation Observers와 WebSocket을 사용합니다.

기본적으로 발표자가 공유하는 탭은 페이지 변경사항을 감시하고 Websocket을 사용하여 뷰어에게 차이를 전송합니다. 사용자가 페이지를 스크롤하거나 페이지와 상호작용할 때 관찰자는 이러한 변경사항을 확인하고 라파엘의 변형 요약 라이브러리를 사용하여 뷰어에게 다시 보고합니다. 이렇게 하면 성능이 유지됩니다. 전체 페이지가 모든 프레임에 전송되는 것은 아닙니다.

라파엘이 동영상에서 지적했듯이 이는 개념 증명에 불과합니다. 하지만 Mutation Observers 같은 최신 플랫폼 기능을 Websockets 같은 이전 기능과 결합하는 좋은 방법이라고 생각합니다.

방법 2: HTMLDocument + 바이너리 WebSocket에서 Blob

다음 방법이 최근에 저에게 떠오른 것입니다. Mutation Observers 접근 방식과 비슷하지만 요약 차이를 전송하는 대신 전체 HTMLDocument의 Blob 클론을 만들어 바이너리 WebSocket을 통해 전송합니다. 설정별 설정 방법은 다음과 같습니다.

  1. 페이지의 모든 URL을 절대 URL로 다시 작성합니다. 이렇게 하면 정적 이미지와 CSS 애셋에 깨진 링크가 포함되는 것을 방지할 수 있습니다.
  2. 페이지의 문서 요소 클론: document.documentElement.cloneNode(true);
  3. 클론을 읽기 전용으로 만들고 선택 불가능하도록 만들고 CSS pointer-events: 'none';user-select:'none';overflow:hidden;를 사용하여 스크롤을 방지합니다.
  4. 페이지의 현재 스크롤 위치를 캡처하여 복제본에 data-* 속성으로 추가합니다.
  5. 복제본의 .outerHTML에서 new Blob()를 만듭니다.

코드는 다음과 같습니다 (전체 소스에서 단순화했습니다).

function screenshotPage() {
    // 1. Rewrite current doc's imgs, css, and script URLs to be absolute before
    // we duplicate. This ensures no broken links when viewing the duplicate.
    urlsToAbsolute(document.images);
    urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
    urlsToAbsolute(document.scripts);

    // 2. Duplicate entire document tree.
    var screenshot = document.documentElement.cloneNode(true);

    // 3. Screenshot should be readyonly, no scrolling, and no selections.
    screenshot.style.pointerEvents = 'none';
    screenshot.style.overflow = 'hidden';
    screenshot.style.userSelect = 'none'; // Note: need vendor prefixes

    // 4. … read on …

    // 5. Create a new .html file from the cloned content.
    var blob = new Blob([screenshot.outerHTML], {type: 'text/html'});

    // Open a popup to new file by creating a blob URL.
    window.open(window.URL.createObjectURL(blob));
}

urlsToAbsolute()에는 상대/스키마 없는 URL을 절대 URL로 재작성하는 간단한 정규식이 포함됩니다. 이는 blob URL의 컨텍스트 (예: 다른 출처)에서 볼 때 이미지, CSS, 글꼴, 스크립트가 중단되지 않도록 하기 위해 필요합니다.

마지막으로 한 가지 조정은 스크롤 지원을 추가하는 것입니다. 발표자가 페이지를 스크롤하면 뷰어도 따라가야 합니다. 이를 위해 현재 scrollXscrollY 위치를 중복 HTMLDocumentdata-* 속성으로 보관합니다. 최종 Blob이 생성되기 전에 페이지 로드 시 실행되는 약간의 JS가 삽입됩니다.

// 4. Preserve current x,y scroll position of this page. See addOnPageLoad().
screenshot.dataset.scrollX = window.scrollX;
screenshot.dataset.scrollY = window.scrollY;

// 4.5. When screenshot loads (e.g. in blob URL), scroll it to the same location
// of this page. Do this by appending a window.onDOMContentLoaded listener
// which pulls out the screenshot (dupe's) saved scrollX/Y state on the DOM.
var script = document.createElement('script');
script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
screenshot.querySelector('body').appendChild(script);

// NOTE: Not to be invoked directly. When the screenshot loads, scroll it
// to the same x,y location of original page.
function addOnPageLoad() {
    window.addEventListener('DOMContentLoaded', function(e) {
    var scrollX = document.documentElement.dataset.scrollX || 0;
    var scrollY = document.documentElement.dataset.scrollY || 0;
    window.scrollTo(scrollX, scrollY);
    });

가짜 스크롤은 원본 페이지의 일부를 스크린샷으로 표시했지만 실제로는 페이지 전체를 복제하고 위치만 변경했을 뿐입니다. #clever

데모

하지만 탭 공유를 위해서는 탭을 지속적으로 캡처하여 시청자에게 전송해야 합니다. 이를 위해 흐름을 보여주는 작은 Node Websocket 서버, 앱, 북마크를 작성했습니다. 코드에 관심이 없는 경우 다음 동영상을 참고하세요.

향후 개선사항

한 가지 최적화는 모든 프레임에서 전체 문서를 복제하지 않는 것입니다. 이는 낭비이며 Mutation Observer 예시가 잘하고 있습니다. 또 다른 개선사항은 urlsToAbsolute()에서 상대적 CSS 배경 이미지를 처리하는 것입니다. 그것은 현재 스크립트에서 고려하지 않습니다.

방법 3: Chrome Extension API + 바이너리 WebSocket

저는 Google I/O 2012에서 브라우저 탭의 콘텐츠를 화면공유하는 또 다른 접근 방식을 시연했습니다. 하지만 이는 속임수입니다. 순수한 HTML5 매직이 아닌 Chrome 확장 프로그램 API가 필요합니다.

이 파일의 소스도 GitHub에 있지만 요점은 다음과 같습니다.

  1. 현재 탭을 .png dataURL로 캡처합니다. Chrome 확장 프로그램에는 이를 위한 API가 있습니다 chrome.tabs.captureVisibleTab().
  2. dataURL을 Blob로 변환합니다. convertDataURIToBlob() 도우미를 참고하세요.
  3. socket.responseType='blob'를 설정하여 바이너리 Websocket을 사용하여 각 Blob (프레임)을 뷰어에 전송합니다.

다음은 현재 탭을 png로 스크린샷으로 만들고 Websocket을 통해 프레임을 전송하는 코드입니다.

var IMG_MIMETYPE = 'images/jpeg'; // Update to image/webp when crbug.com/112957 is fixed.
var IMG_QUALITY = 80; // [0-100]
var SEND_INTERVAL = 250; // ms

var ws = new WebSocket('ws://…', 'dumby-protocol');
ws.binaryType = 'blob';

function captureAndSendTab() {
    var opts = {format: IMG_MIMETYPE, quality: IMG_QUALITY};
    chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
    // captureVisibleTab returns a dataURL. Decode it -> convert to blob -> send.
    ws.send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
    });
}

var intervalId = setInterval(function() {
    if (ws.bufferedAmount == 0) {
    captureAndSendTab();
    }
}, SEND_INTERVAL);

향후 개선사항

이 프레임 속도는 의외로 훌륭하지만 이보다 더 나을 수 있습니다. 한 가지 개선 사항은 dataURL을 Blob으로 변환하는 오버헤드를 제거하는 것입니다. 하지만 chrome.tabs.captureVisibleTab()는 dataURL만 제공합니다. Blob 또는 Typed Array를 반환하면 자체적으로 Blob으로 변환하는 대신 websocket을 통해 직접 이를 전송할 수 있습니다. 그러려면 crbug.com/32498에 별표표시하세요.

방법 4: WebRTC - 진정한 미래

마지막 중요한 사실!

브라우저 화면 공유의 미래는 WebRTC에 의해 실현됩니다. 2012년 8월 14일에 팀은 탭 콘텐츠를 공유하기 위한 WebRTC Tab Content Capture API를 제안했습니다.

이 녀석이 준비될 때까지 메서드 1~3이 남게 돼요.

결론

따라서 오늘날의 웹 기술로 브라우저 탭 공유가 가능합니다.

하지만... 이 표현은 신중하게 생각해야 합니다. 이 도움말의 기법은 훌륭하지만 어떤 식으로든 훌륭한 공유 UX에 미치지 못합니다. 이 모든 것이 WebRTC 탭 콘텐츠 캡처의 노력에 따라 바뀌겠지만 실제로 실제로 구현될 때까지는 브라우저 플러그인이나 여기서 다룬 것과 같은 제한적인 솔루션으로 남게 됩니다.

더 많은 기술이 있나요? 댓글 달기