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

François Beaufort
François Beaufort

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

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

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

ผู้ใช้เลื่อนและซูมแท็บที่บันทึกไว้ (เดโม)

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

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

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

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

ข้อความแจ้งสิทธิ์

การเรียกใช้ sendWheel() หรือ setZoomLevel() ครั้งแรกในออบเจ็กต์ CaptureController ที่ระบุจะแสดงข้อความแจ้งสิทธิ์ หากผู้ใช้ให้สิทธิ์ ระบบจะอนุญาตให้เรียกใช้เมธอดเหล่านี้เพิ่มเติมบนออบเจ็กต์ CaptureController นั้น หากผู้ใช้ปฏิเสธสิทธิ์ ระบบจะปฏิเสธ Promise ที่แสดงผล

โปรดทราบว่าออบเจ็กต์ CaptureController จะเชื่อมโยงกับ capture-session ที่เฉพาะเจาะจงเท่านั้น เชื่อมโยงกับ 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)];
}

โปรดทราบว่ามี 3 ขนาดที่แตกต่างกันในโค้ดก่อนหน้านี้ ได้แก่

  • ขนาดขององค์ประกอบ <video>
  • ขนาดของเฟรมที่บันทึกไว้ (แสดงที่นี่เป็น trackSettings.width และ trackSettings.height)
  • ขนาดของแท็บ

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

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

Promise ที่ sendWheel() แสดงผลจะปฏิเสธได้ในกรณีต่อไปนี้

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

ซูม

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

  • getSupportedZoomLevels() จะแสดงรายการระดับการซูมที่เบราว์เซอร์รองรับ ซึ่งแสดงเป็นเปอร์เซ็นต์ของ "ระดับการซูมเริ่มต้น" ซึ่งกำหนดไว้ที่ 100% รายการนี้เพิ่มขึ้นแบบไม่ผันผวนและมีค่า 100
  • getZoomLevel() แสดงผลระดับการซูมปัจจุบันของแท็บ
  • setZoomLevel() ตั้งค่าระดับการซูมของแท็บเป็นค่าจำนวนเต็มที่แสดงใน getSupportedZoomLevels() และแสดงผลลัพธ์เป็นสัญญา (Promise) เมื่อดำเนินการสำเร็จ โปรดทราบว่าระบบจะไม่รีเซ็ตระดับการซูมเมื่อสิ้นสุดเซสชันการจับภาพ
  • 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 ของ Captured Surface Control และเปิดใช้ได้ที่ 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 ไม่พร้อมใช้งาน คุณสามารถใช้ polyfill กับฟีเจอร์นี้ได้โดยใช้ setInterval()

ความคิดเห็น

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

บอกเราเกี่ยวกับการออกแบบ

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

พบปัญหาในการติดตั้งใช้งานใช่ไหม

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