גלילה ושינוי מרחק התצוגה של כרטיסייה שצולמה

François Beaufort
François Beaufort

ניתן כבר לשתף כרטיסיות, חלונות ומסכים בפלטפורמת האינטרנט באמצעות Screen Capture API. כשאפליקציית אינטרנט מפעילה את getDisplayMedia(), Chrome מבקש מהמשתמש לשתף כרטיסייה, חלון או מסך עם אפליקציית האינטרנט כסרטון MediaStreamTrack.

אפליקציות אינטרנט רבות שמשתמשות ב-getDisplayMedia() מציגות למשתמש תצוגה מקדימה של הסרטון במשטח הצילום. לדוגמה, אפליקציות לשיחות ועידה בווידאו ישמיעו לרוב את הסרטון הזה למשתמשים מרוחקים, והן גם מעבדות אותו ל-HTMLVideoElement מקומי, כך שהמשתמש המקומי יראה כל הזמן תצוגה מקדימה של התוכן שהוא משתף.

תיעוד זה מציג את Captured Surface Control API החדש ב-Chrome, שמאפשר לאפליקציית האינטרנט שלך לגלול בכרטיסייה שצולמה, וכן לקרוא ולכתוב את רמת הזום של כרטיסייה שצולמה.

המשתמש גולל ומשנה את מרחק התצוגה בכרטיסייה שצולמה (הדגמה).

למה כדאי להשתמש בתכונה 'בקרת פני שטח שנקלטת'?

לכל האפליקציות לשיחות ועידה בווידאו יש חיסרון זהה: אם המשתמש רוצה ליצור אינטראקציה עם כרטיסייה או חלון שמצולמים, הם צריכים לעבור לפלטפורמה הזו ולהרחיק אותם מאפליקציית שיחות הוועידה בווידאו. יש כמה אתגרים שצריך לעשות:

  • המשתמשים לא יכולים לראות את האפליקציה שצילמתם ואת הסרטונים של משתמשים מרחוק בו-זמנית, אלא אם הם משתמשים בתכונה תמונה בתוך תמונה או בחלונות נפרדים זה לצד זה לכרטיסייה של שיחת הוועידה בווידאו ולכרטיסייה המשותפת. במסכים קטנים יותר, זה עלול להיות קשה.
  • המשתמש לא צריך לדלג בין האפליקציה לשיחות ועידה בווידאו לבין החלונית שהוקלטה.
  • המשתמש מאבד את הגישה לפקדים שגלויים לאפליקציית שיחות הוועידה בווידאו כשהם לא נמצאים בה. לדוגמה, אפליקציית צ'אט מוטמעת, תגובות באמוג'י, התראות על משתמשים שמבקשים להצטרף לשיחה, פקדי מולטימדיה ופריסה ותכונות שימושיות אחרות של שיחות ועידה בווידאו.
  • שיתוף המסך לא יכול להעביר הרשאות ניהול למשתתפים מרוחקים. זהו תרחיש מוכר מדי שבו משתמשים מרחוק מבקשים מהמציג לשנות את השקף, לגלול קצת למעלה ולמטה או לשנות את מרחק התצוגה.

Document 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 הזה. אם המשתמש מסרב לקבל הרשאה, ההבטחה שהוחזרה תידחה.

חשוב לזכור שאובייקטים מסוג 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() מקבלת מילון עם שתי קבוצות של ערכים:

  • 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.
}

הפעל בקרת משטח שנקלטה

ה-Takeout Surface Control API זמין ב-Chrome במחשב מאחורי הסימון של 'בקרת משטחים לתעד', וניתן להפעיל אותו ב-chrome://flags/#captured-surface-control.

התכונה הזו גם נכנסת בגרסת מקור לניסיון החל מ-Chrome 122 למחשב, כך שהמפתחים יכולים להפעיל את התכונה עבור מבקרים באתרים שלהם כדי לאסוף נתונים ממשתמשים אמיתיים. מידע נוסף על גרסאות מקור לגרסאות מקור ועל אופן הפעולה שלהן זמין במאמר תחילת העבודה עם גרסאות מקור.

אבטחה ופרטיות

מדיניות ההרשאות של "captured-surface-control" מאפשרת לכם לנהל את האופן שבו לאפליקציית הצילום ולמסגרות iframe מוטמעות של צד שלישי יש גישה ל-Recorded Surface Control. כדי להבין את החסרונות הקשורים לאבטחה, כדאי לעיין בקטע שיקולי פרטיות ואבטחה בהסבר בנושא בקרת שטחי הפרסום המצולמת.

הדגמה (דמו)

כדי לשחק בתכונה 'בקרת פני שטח שנקלטה', מריצים את ההדגמה ב-Glitch. חשוב לבדוק את קוד המקור.

שינויים מגרסאות קודמות של Chrome

לפניכם כמה הבדלים התנהגותיים עיקריים בבקרת פני השטח שנקלטה, שחשוב שתשימו לב אליהם:

  • ב-Chrome בגרסה 124 ומטה:
    • אם ניתנה הרשאה, היא תוקצה לסשן צילום שמשויך לאותו CaptureController, ולא למקור התיעוד.
  • ב-Chrome 122:
    • getZoomLevel() מחזירה הבטחה עם רמת הזום הנוכחית של הכרטיסייה.
    • sendWheel() מחזירה הבטחה שנדחתה עם הודעת השגיאה "No permission." אם המשתמש לא העניק לאפליקציה הרשאת שימוש. סוג השגיאה הוא "NotAllowedError" ב-Chrome מגרסה 123 ואילך.
    • הדומיין oncapturedzoomlevelchange לא זמין. אפשר למלא את התכונה הזו באמצעות setInterval().

משוב

צוות Chrome וקהילת תקני האינטרנט רוצים לשמוע על החוויות שלכם עם בקרת משטחים (Recordd Surface Control).

נשמח לשמוע על העיצוב

האם יש משהו בצילום פני השטח שנקלט לא פועל כצפוי? או האם חסרים שיטות או מאפיינים שנדרשים לכם כדי ליישם את הרעיון? יש לכם שאלה או הערה לגבי מודל האבטחה? שלחו בעיה במפרט במאגר GitHub או הוסיפו את דעתכם לבעיה קיימת.

נתקלתם בבעיה בהטמעה?

האם מצאת באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט? יש לדווח על באג בכתובת https://new.crbug.com. חשוב לכלול כמה שיותר פרטים, וגם הוראות לשחזור. גליץ' עובד מצוין לשיתוף באגים שניתן לשחזר.