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

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

חשוב לזכור שאובייקטים מסוג 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 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(), ומחזיר הבטחה (promise) כשהפעולה מסתיימת בהצלחה. חשוב לדעת שרמת הזום לא מתאפסת בסוף סשן הצילום.
  • oncapturedzoomlevelchange מאפשר לכם להאזין לשינויים ברמת הזום של כרטיסייה שצולמה, כי המשתמשים יכולים לשנות את רמת הזום דרך אפליקציית הצילום או דרך אינטראקציה ישירה עם הכרטיסייה שצולמה.

קריאות ל-setZoomLevel() מוגבלות על ידי הרשאה. קריאות לשיטות אחרות של zoom לקריאה בלבד הן 'חינמיות', וכך גם האזנה לאירועים.

בדוגמה הבאה מוסבר איך להגדיל את רמת הזום של כרטיסייה שצולמה בסשן צילום קיים:

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

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

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

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

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

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

הדגמה (דמו)

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

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

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

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

משוב

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

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

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

בעיה בהטמעה?

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