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

François Beaufort
François Beaufort

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

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

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

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

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

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

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

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

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

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

הפעלה של בקרת משטח הצילום

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

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

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

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

הדגמה (דמו)

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

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

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

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

משוב

צוות Chrome וקהילת תקני האינטרנט מעוניינים לשמוע על החוויות שלכם עם בקרת פני השטח בצילום.

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

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

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

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