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

François Beaufort
François Beaufort

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

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

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

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

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

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

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

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 มีความเชื่อมโยงแบบไม่ซ้ำกันกับ การจับภาพ-เซสชัน ที่เฉพาะเจาะจง ไม่สามารถเชื่อมโยงกับเซสชันการจับภาพอื่น และไม่สามารถไปยังส่วนต่างๆ ของหน้าเว็บที่กําหนดไว้ได้ อย่างไรก็ตาม การจับภาพเซสชันจะรอดพ้นจากการนำทางของหน้าที่บันทึกไว้

ต้องมีท่าทางสัมผัสของผู้ใช้เพื่อแสดงข้อความแจ้งสิทธิ์แก่ผู้ใช้ เฉพาะการโทร 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 further explained 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> เป็นพิกัดภายในพื้นที่พิกัดของแทร็กวิดีโอ เบราว์เซอร์จะทำการแปลระหว่างขนาดของเฟรมที่บันทึกและขนาดของแท็บเช่นเดียวกัน และแสดงเหตุการณ์การเลื่อนที่ค่าออฟเซ็ตที่สอดคล้องกับความคาดหวังของเว็บแอป

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

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

Zoom

การโต้ตอบกับระดับการซูมของแท็บที่ถ่ายจะทำผ่านพื้นผิว 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.
}

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

Surface Control API ที่จับภาพมีอยู่ใน Chrome บนเดสก์ท็อป หลัง Flag การควบคุมแพลตฟอร์มที่จับภาพไว้ และเปิดใช้ได้ที่ chrome://flags/#captured-surface-control

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

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

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

ข้อมูลประชากร

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

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

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

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

ความคิดเห็น

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

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

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

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

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