צילום וידאו בסטרימינג מכל רכיב

François Beaufort
François Beaufort

באמצעות Screen Capture API, אפשר לצלם את כל הכרטיסייה הנוכחית. Element Capture API מאפשר לכם לצלם ולתעד אלמנט HTML ספציפי. היא הופכת צילום מסך של הכרטיסייה כולה לצילום מסך של עץ משנה ספציפי של DOM, ומתעדת רק את צאצאיו הישירים של רכיב היעד. במילים אחרות, הוא חותך ומסיר גם את התוכן שמסתיר וגם את התוכן שמוסתר.

למה כדאי להשתמש ב-Element Capture?

הבנה של הדרישות של אפליקציית ועידות וידאו יכולה לעזור לכם להבין איפה כדאי להשתמש ב-Element Capture. אם יש לכם אפליקציה לשיחות ועידה בווידאו שמאפשרת לכם להטמיע אפליקציות של צד שלישי ב-iframe, יכול להיות שתרצו מדי פעם לצלם את ה-iframe הזה כסרטון ולהעביר אותו למשתתפים מרוחקים.

צילום מסך של שיחת וידאו ב-Chrome.
אלעד משתמש באפליקציית צד שלישי בשיחת ועידה בווידאו עם פרנסואה.

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

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

צילום מסך של רשימה נפתחת שמסתירה תוכן שרוצים לצלם.
רשימה נפתחת מופיעה מעל התוכן שרוצים לצלם.

התכונה 'לכידת אזור' לא תעזור לכם במקרה הזה. יכול להיות שחלק מהרשימה הנפתחת יוצג במסכים של המשתתפים מרחוק.

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

העובדה שהתכונה 'לכידת אזור' לוכדת חלקים מרכיבים באופן הזה (שנקרא הסתרה של תוכן) יוצרת כמה בעיות:

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

ה-API של Element Capture פותר את כל הבעיות האלה, כי הוא מאפשר לכם לטרגט את הרכיב שאתם רוצים לשתף.

צילום מסך של רכיב היעד ללא רשימה נפתחת בתצוגה.
פרנסואה לא רואה את הרשימה הנפתחת של אלעד.

איך משתמשים ב-Element Capture?

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

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

כדאי לעבור שוב על השלבים הבאים:

מתחילים בכך שמאפשרים למשתמש לצלם את הכרטיסייה הנוכחית.

// Ask the user for permission to start capturing the current tab.
const stream = await navigator.mediaDevices.getDisplayMedia({
 preferCurrentTab: true,
});
const [track] = stream.getVideoTracks();

מגדירים RestrictionTarget על ידי קריאה ל-RestrictionTarget.fromElement() עם רכיב לבחירתכם כקלט.

// Associate captureTarget with a new RestrictionTarget
const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

לאחר מכן קוראים לפונקציה restrictTo() בטראק הווידאו עם RestrictionTarget כקלט. אחרי שההבטחה האחרונה תמומש, כל המסגרות הבאות יוגבלו.

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

// Enjoy! Transmit remotely.

ירידה לפרטים

זיהוי תכונות

כדי לבדוק אם יש תמיכה ב-RestrictionTarget.fromElement(), משתמשים בפקודה:

if ("RestrictionTarget" in self && "fromElement" in RestrictionTarget) {
  // Deriving a restriction target is supported.
}

הפקת RestrictionTarget

מתמקדים באלמנט שנקרא captureTarget. כדי לגזור ממנו RestrictionTarget, קוראים ל-RestrictionTarget.fromElement(captureTarget). אם הפעולה תצליח, ה-Promise שיוחזר יקבל ערך של אובייקט RestrictionTarget חדש. אחרת, אם טבעתם מספר לא סביר של אובייקטים מסוג RestrictionTarget, הבקשה תידחה.

const captureTarget = document.querySelector("#captureTarget");
const restrictionTarget = await RestrictionTarget.fromElement(captureTarget);

בניגוד לאובייקט Element, אובייקט RestrictionTarget הוא serializable. אפשר להעביר אותו למסמך אחר באמצעות Window.postMessage(), למשל.

הגבלה

כשמצלמים כרטיסייה, רצועת הווידאו חושפת את restrictTo(). כשמצלמים את הכרטיסייה הנוכחית, אפשר להתקשר אל restrictTo() עם null או עם כל RestrictionTarget שנגזר מ-Element בתוך הכרטיסייה הנוכחית.

הקריאות ל-restrictTo(restrictionTarget) משנות את טראק הווידאו ללכידה של captureTarget, כאילו הוא צויר בעצמו, באופן עצמאי משאר ה-DOM. גם כל צאצא של captureTarget נכלל בצילום המסך, אבל אחים של captureTarget לא נכללים. התוצאה היא שכל הפריים שמוצג בטראק נראה כאילו הוא נחתך לפי קווי המתאר של captureTarget, וכל תוכן שחוסם או נחסם מוסר.

// Start restricting the self-capture video track using the RestrictionTarget.
await track.restrictTo(restrictionTarget);

קריאות ל-restrictTo(null) מחזירות את הטראק למצב המקורי שלו.

// Stop restricting.
await track.restrictTo(null);

אם הקריאה ל-restrictTo() מצליחה, ה-Promise שמוחזר נפתר כשניתן להבטיח שכל מסגרות הווידאו הבאות יוגבלו ל-captureTarget.

אם הפעולה לא מצליחה, ה-Promise נדחה. שיחה ל-restrictTo() תיכשל בגלל אחת מהסיבות הבאות:

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

שיקולים לגבי צילום עצמי

כשמתבצעת קריאה לאפליקציה getDisplayMedia(), והמשתמש בוחר לצלם את הכרטיסייה של האפליקציה עצמה, אנחנו קוראים לזה 'צילום עצמי'.

השיטה restrictTo() נחשפת בכל טראק וידאו של צילום מסך של כרטיסייה, ולא רק בצילום עצמי. אבל בשלב הזה, התכונה 'צילום רכיבים' מופעלת רק לצילום עצמי. לכן, מומלץ לבדוק אם המשתמש בחר את הכרטיסייה הנוכחית לפני שמנסים להגביל את המעקב. אפשר לעשות זאת באמצעות Capture Handle. אפשר גם לבקש מהדפדפן להציג למשתמש הנחיות לצילום עצמי באמצעות preferCurrentTab.

שקיפות

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

  • יכול להיות שהצבעים ישתנו. יכול להיות שרכיבי יעד שקופים חלקית שמצוירים על רקע בהיר ייראו כהים יותר כשהערוץ אלפא יוסר, ורכיבים שמצוירים על רקע כהה ייראו בהירים יותר.
  • צבעים שהיו בלתי נראים או בלתי מורגשים למשתמש כשהערוץ אלפא הוגדר למקסימום, יופיעו אחרי שהערוץ אלפא יוסר. לדוגמה, אם החלקים השקופים כללו את קוד ה-RGBA‏ rgba(0, 0, 0, 0), יכול להיות שיופיעו אזורים שחורים לא צפויים בפריים שצולם.
צילום מסך של התוצאה של יעד שקוף שאינו מלבני.
הזרם של סרטון יעד הלכידה השקוף שאינו מלבני (מימין) הוא מלבן עם רקע שחור שמכיל עיגול כחול אטום.

יעדים לא כשירים ללכידה

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

כדי לוודא שהאלמנט עומד בדרישות להגבלה, חשוב לוודא שהוא יוצר הקשר משלו לסידור בשכבות. כדי להבטיח זאת, אפשר לציין את מאפיין ה-CSS‏ isolation ולהגדיר אותו לערך isolate.

<div id="captureTarget" style="isolation: isolate;"></iframe>

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

תמיכה בדפדפנים

התכונה 'לכידת רכיבים' זמינה מגרסה Chrome 132 ואילך במחשב בלבד.

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

כדי להבין את הפשרות בנושא אבטחה, כדאי לעיין בקטע שיקולי אבטחה ופרטיות במפרט של Element Capture.

דפדפן Chrome מצייר גבול כחול מסביב לקצוות של הכרטיסיות שצולמו.

הדגמה (דמו)

כדי להתנסות ב'לכידת רכיבים', אפשר להריץ את ההדגמה.

משוב

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

מתארים את העיצוב

האם יש משהו ב-Element Capture שלא פועל כמו שציפית? או שחסרות שיטות או מאפיינים שצריך להטמיע כדי לממש את הרעיון? יש לך שאלה או הערה לגבי מודל האבטחה?

  • אפשר להגיש בקשה לבעיה במפרט במאגר GitHub, או להוסיף את המחשבות שלכם לבעיה קיימת.

בעיה בהטמעה?

מצאתם באג בהטמעה של Chrome? או שההטמעה שונה מהמפרט?

תודות

צילום מאת Paul Skorupskas ב-Unsplash