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