เลื่อนและซูมแท็บที่จับภาพไว้

François Beaufort
François Beaufort

คุณแชร์แท็บ หน้าต่าง และหน้าจอได้แล้วบนแพลตฟอร์มเว็บด้วย Screen Capture API เมื่อเว็บแอปเรียกใช้ getDisplayMedia() Chrome จะแจ้งให้ผู้ใช้แชร์แท็บ หน้าต่าง หรือหน้าจอกับเว็บแอปในรูปแบบวิดีโอ MediaStreamTrack

เว็บแอปจำนวนมากที่ใช้ getDisplayMedia() แสดงตัวอย่างวิดีโอของแพลตฟอร์มที่บันทึกไว้แก่ผู้ใช้ ตัวอย่างเช่น แอปการประชุมทางวิดีโอมักจะสตรีมวิดีโอนี้ไปยังผู้ใช้จากระยะไกล ในขณะเดียวกันก็แสดงผลเป็น HTMLVideoElement ในเครื่องด้วย เพื่อให้ผู้ใช้ในเครื่องได้เห็นตัวอย่างของสิ่งที่แชร์อยู่เสมอ

เอกสารนี้จะแนะนำ API การควบคุมพื้นผิวที่มีการจับภาพใหม่ใน Chrome ซึ่งจะช่วยให้เว็บแอปเลื่อนแท็บที่บันทึกไว้ รวมถึงอ่านและเขียนระดับการซูมของแท็บที่บันทึกได้

ผู้ใช้เลื่อนและซูมแท็บที่บันทึกไว้ (การสาธิต)

เหตุใดจึงควรใช้การควบคุมพื้นผิวที่บันทึก

แอปการประชุมทางวิดีโอทั้งหมดจะมีข้อเสียเหมือนกัน กล่าวคือ หากผู้ใช้ต้องการโต้ตอบกับแท็บหรือหน้าต่างที่บันทึกไว้ ผู้ใช้ต้องเปลี่ยนไปใช้แพลตฟอร์มนั้นและนำออกจากแอปการประชุมทางวิดีโอ ซึ่งมาพร้อมความท้าทายบางอย่าง ดังนี้

  • ผู้ใช้จะไม่เห็นแอปที่บันทึกไว้และวิดีโอของผู้ใช้ระยะไกลพร้อมกัน เว้นแต่จะใช้การแสดงภาพซ้อนภาพหรือแยกหน้าต่างที่เคียงข้างกันสำหรับแท็บการประชุมทางวิดีโอและแท็บที่แชร์ ในหน้าจอขนาดเล็กการดำเนินการนี้อาจทำได้ยาก
  • โดยผู้ใช้ต้องสลับไปมาระหว่างแอปการประชุมทางวิดีโอกับแพลตฟอร์มที่จับภาพไว้
  • ผู้ใช้จะสูญเสียสิทธิ์เข้าถึงการควบคุมที่แอปการประชุมทางวิดีโอแสดงแม้ว่าจะไม่ได้ใช้งานอยู่ก็ตาม ตัวอย่างเช่น แอปแชทแบบฝัง รีแอ็กชันด้วยอีโมจิ การแจ้งเตือนเกี่ยวกับผู้ใช้ที่ขอเข้าร่วมการโทร การควบคุมมัลติมีเดียและเลย์เอาต์ ตลอดจนฟีเจอร์การประชุมทางวิดีโอที่มีประโยชน์อื่นๆ
  • ผู้นำเสนอไม่สามารถมอบสิทธิ์ควบคุมให้กับผู้เข้าร่วมจากระยะไกล ซึ่งนำไปสู่สถานการณ์ที่ผู้นำเสนอคุ้นเคยเกินไป ซึ่งผู้ใช้ระยะไกลจะขอให้ผู้นำเสนอเปลี่ยนสไลด์ เลื่อนขึ้นและลงเล็กน้อย หรือปรับระดับการซูม

Captured Surface Control API จะช่วยแก้ไขปัญหาเหล่านี้ได้

ฉันจะใช้การควบคุมพื้นผิวที่บันทึกได้อย่างไร

การใช้การควบคุมพื้นผิวที่บันทึกสำเร็จมี 2-3 ขั้นตอน เช่น การจับภาพแท็บเบราว์เซอร์อย่างชัดแจ้ง และขออนุญาตจากผู้ใช้ก่อนที่จะเลื่อนและซูมแท็บที่บันทึกไว้ได้

จับภาพแท็บเบราว์เซอร์

เริ่มต้นด้วยการแจ้งให้ผู้ใช้เลือกแพลตฟอร์มที่จะแชร์โดยใช้ 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;
}

ข้อความแจ้งเกี่ยวกับสิทธิ์

การเรียกใช้ sendWheel() หรือ setZoomLevel() ครั้งแรกในออบเจ็กต์ CaptureController ที่ระบุจะสร้างข้อความแจ้งสิทธิ์ หากผู้ใช้ให้สิทธิ์ ระบบจะอนุญาตการเรียกใช้วิธีการเหล่านี้เพิ่มเติมในออบเจ็กต์ 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() แอปจับภาพสามารถแสดงเหตุการณ์ของล้อตามขนาดที่เลือกผ่านพิกัดที่เลือกภายในวิวพอร์ตของแท็บ แอปที่บันทึกไว้ไม่สามารถแยกแยะเหตุการณ์จากการโต้ตอบโดยตรงของผู้ใช้ได้

สมมติว่าแอปจับภาพใช้องค์ประกอบ <video> ชื่อ "previewTile" โค้ดต่อไปนี้แสดงวิธีส่งต่อเหตุการณ์วงล้อไปยังแท็บที่บันทึก

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() จะใช้พจนานุกรมที่มีค่า 2 ชุด ได้แก่

  • x และ y: พิกัดที่จะนำส่งเหตุการณ์ของล้อ
  • wheelDeltaX และ wheelDeltaY: จำนวนการเลื่อนเป็นพิกเซลสำหรับการเลื่อนในแนวนอนและแนวตั้งตามลำดับ โปรดทราบว่าค่าเหล่านี้จะกลับกันเมื่อเทียบกับเหตุการณ์วงล้อเดิม

การติดตั้งใช้งาน 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.width และ trackSettings.height)
  • ขนาดของแท็บ

ขนาดขององค์ประกอบ <video> จะอยู่ภายในโดเมนของแอปจับภาพและเบราว์เซอร์ไม่รู้จัก ขนาดของแท็บทั้งหมดจะอยู่ภายในโดเมนของเบราว์เซอร์และเว็บแอปไม่รู้จัก

เว็บแอปใช้ translateCoordinates() เพื่อแปลออฟเซ็ตที่สัมพันธ์กับองค์ประกอบ <video> ให้เป็นพิกัดภายในพื้นที่พิกัดของแทร็กวิดีโอ ในทำนองเดียวกัน เบราว์เซอร์จะแปลค่าระหว่างขนาดของเฟรมที่จับภาพกับขนาดของแท็บ และส่งเหตุการณ์การเลื่อนในออฟเซ็ตที่สอดคล้องกับความคาดหวังของเว็บแอป

คำมั่นสัญญาที่ sendWheel() ส่งคืนมาอาจถูกปฏิเสธในกรณีต่อไปนี้

  • หากเซสชันการจับภาพยังไม่เริ่มหรือหยุดไปแล้ว ซึ่งรวมถึงการหยุดแบบไม่พร้อมกันขณะที่เบราว์เซอร์จัดการการดำเนินการ sendWheel()
  • หากผู้ใช้ไม่ได้อนุญาตให้แอปใช้ sendWheel()
  • หากแอปจับภาพพยายามส่งเหตุการณ์การเลื่อนในพิกัดที่อยู่นอก [trackSettings.width, trackSettings.height] โปรดทราบว่าค่าเหล่านี้อาจเปลี่ยนแปลงแบบไม่พร้อมกัน จึงเป็นความคิดที่ดีที่จะจับข้อผิดพลาดและเพิกเฉยต่อข้อผิดพลาดนั้น (โปรดทราบว่าโดยปกติแล้ว 0, 0 จะไม่อยู่นอกขอบเขต ดังนั้นจึงสามารถใช้เพื่อแจ้งให้ผู้ใช้อนุญาตได้อย่างปลอดภัย)

ซูม

การโต้ตอบกับระดับการซูมของแท็บที่บันทึกไว้จะทำผ่านส่วน CaptureController ต่อไปนี้

  • getSupportedZoomLevels() จะแสดงรายการระดับการซูมที่เบราว์เซอร์รองรับ ซึ่งแสดงเป็นเปอร์เซ็นต์ของ "ระดับการซูมเริ่มต้น" ซึ่งกำหนดไว้เป็น 100% รายการนี้เพิ่มขึ้นซ้ำๆ และมีค่าเป็น 100
  • getZoomLevel() แสดงระดับการซูมปัจจุบันของแท็บ
  • setZoomLevel() ตั้งค่าระดับการซูมของแท็บเป็นค่าจำนวนเต็มที่มีใน getSupportedZoomLevels() และจะให้ผลลัพธ์กลับมาเมื่อผลลัพธ์สำเร็จ โปรดทราบว่าระดับการซูมจะไม่รีเซ็ตเมื่อสิ้นสุดเซสชันการจับภาพ
  • oncapturedzoomlevelchange ให้คุณฟังการเปลี่ยนแปลงระดับการซูมของแท็บที่บันทึกไว้เนื่องจากผู้ใช้อาจเปลี่ยนระดับการซูมทั้งผ่านแอปจับภาพหรือการโต้ตอบโดยตรงกับแท็บที่บันทึกได้

การโทรไปยัง setZoomLevel() ถูกกั้นด้วยสิทธิ์ ไปยังวิธีการซูมแบบอ่านอย่างเดียวอีกวิธีคือ "ฟรี" เช่นเดียวกับการฟังเหตุการณ์

ตัวอย่างต่อไปนี้แสดงให้เห็นการเพิ่มระดับการซูมของแท็บที่บันทึกในเซสชันการจับภาพที่มีอยู่

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 บนเดสก์ท็อป ซึ่งอยู่หลัง Flag การควบคุมพื้นผิวที่บันทึก และเปิดใช้ได้ที่ chrome://flags/#captured-surface-control

ฟีเจอร์นี้ยังเข้าสู่ช่วงทดลองใช้จากต้นทางโดยเริ่มตั้งแต่ Chrome 122 บนเดสก์ท็อป ซึ่งทำให้นักพัฒนาแอปเปิดใช้ฟีเจอร์นี้ได้สำหรับผู้เข้าชมเว็บไซต์เพื่อรวบรวมข้อมูลจากผู้ใช้จริง โปรดดูข้อมูลเพิ่มเติมเกี่ยวกับช่วงทดลองใช้จากต้นทางและวิธีการทำงานของช่วงทดลองใช้ที่หัวข้อเริ่มต้นใช้งานช่วงทดลองใช้จากต้นทาง

ความปลอดภัยและความเป็นส่วนตัว

นโยบายสิทธิ์ของ "captured-surface-control" ช่วยให้คุณจัดการวิธีที่แอปจับภาพและ iframe ของบุคคลที่สามที่ฝังไว้มีสิทธิ์เข้าถึงการควบคุมพื้นผิวที่บันทึกได้ โปรดดูส่วนข้อควรพิจารณาเกี่ยวกับความเป็นส่วนตัวและความปลอดภัยในวิดีโออธิบายการควบคุมพื้นผิวที่บันทึก

สาธิต

คุณสามารถใช้การควบคุมพื้นผิวที่จับภาพได้โดยเรียกใช้การสาธิตใน Glitch อย่าลืมดูซอร์สโค้ด

การเปลี่ยนแปลงจาก Chrome เวอร์ชันก่อนหน้า

ความแตกต่างด้านพฤติกรรมที่สำคัญๆ เกี่ยวกับการควบคุมพื้นผิวที่บันทึก ที่คุณควรทราบมีดังนี้

  • ใน Chrome 124 และเวอร์ชันก่อนหน้า ให้ทำดังนี้
    • สิทธิ์ (หากได้รับอนุญาต) จะจำกัดอยู่ที่เซสชันการบันทึกที่เชื่อมโยงกับ CaptureController นั้น ไม่ใช่ต้นทางการบันทึก
  • ใน Chrome 122 ให้ทำดังนี้
    • getZoomLevel() ให้การแสดงผลเดียวกับระดับการซูมปัจจุบันของแท็บ
    • sendWheel() จะแสดงผลการสัญญาที่ถูกปฏิเสธพร้อมข้อความแสดงข้อผิดพลาด "No permission." หากผู้ใช้ไม่ได้ให้สิทธิ์แอปในการใช้ ประเภทข้อผิดพลาดคือ "NotAllowedError" ใน Chrome 123 ขึ้นไป
    • oncapturedzoomlevelchange ไม่พร้อมใช้งาน คุณใส่ฟีเจอร์นี้ได้โดยใช้ setInterval()

ความคิดเห็น

ทีม Chrome และชุมชนมาตรฐานเว็บต้องการทราบประสบการณ์ของคุณในการใช้งานการควบคุมพื้นผิวที่บันทึก

บอกให้เราทราบเกี่ยวกับการออกแบบ

มีบางอย่างเกี่ยวกับการจับภาพพื้นผิวที่จับภาพแล้วไม่ทำงานตามที่คุณคาดหวังไหม หรือขาดวิธีการหรือคุณสมบัติที่จำเป็นซึ่งจำเป็นต่อการนำไอเดียของคุณไปปฏิบัติ หากมีคำถามหรือความคิดเห็นเกี่ยวกับรูปแบบการรักษาความปลอดภัย ยื่นข้อมูลจำเพาะในที่เก็บของ GitHub หรือเพิ่มความเห็นเกี่ยวกับปัญหาที่มีอยู่

หากมีปัญหาในการติดตั้งใช้งาน

คุณพบข้อบกพร่องในการติดตั้งใช้งาน Chrome ไหม หรือการติดตั้งใช้งานแตกต่างจากข้อกําหนดหรือไม่ รายงานข้อบกพร่องที่ https://new.crbug.com โปรดใส่รายละเอียดให้มากที่สุดเท่าที่จะเป็นไปได้ รวมถึงวิธีการในการทำซ้ำ Glitch เหมาะสำหรับการแชร์ข้อบกพร่องที่เกิดซ้ำ