התכונה 'תמונה בתוך תמונה' (PiP) מאפשרת למשתמשים לצפות בסרטונים בחלון צף (תמיד מעל חלונות אחרים) כדי שיוכלו לעקוב אחרי מה שהם צופים בו בזמן שהם מבצעים פעולות באתרים או באפליקציות אחרים.
באמצעות Picture-in-Picture Web API, אתם יכולים להפעיל את התכונה 'תמונה בתוך תמונה' בשביל רכיבי וידאו באתר שלכם ולשלוט בהם. אתם יכולים לנסות את התכונה בדוגמה הרשמית שלנו ל'תמונה בתוך תמונה'.
רקע
בספטמבר 2016, הוספנו ל-Safari תמיכה ב'תמונה בתוך תמונה' באמצעות WebKit API ב-macOS Sierra. שישה חודשים לאחר מכן, עם השקת Android O, Chrome התחיל להפעיל סרטונים במצב 'תמונה בתוך תמונה' באופן אוטומטי בניידים באמצעות Android API מקורי. שישה חודשים לאחר מכן הודענו על הכוונה שלנו ליצור Web API, תכונה שתואמת ל-Safari, וליצור סטנדרטיזציה שלו, שתאפשר למפתחי אתרים ליצור את החוויה המלאה של 'תמונה בתוך תמונה' ולשלוט בה. והנה אנחנו!
איך נכנסים לקוד
מעבר למצב 'תמונה בתוך תמונה'
נתחיל בפשטות עם אלמנט וידאו ודרך למשתמש לבצע איתו אינטראקציה, כמו אלמנט לחצן.
<video id="videoElement" src="https://example.com/file.mp4"></video>
<button id="pipButtonElement"></button>
צריך לבקש את התכונה 'תמונה בתוך תמונה' רק בתגובה לתנועה של המשתמש, אף פעם לא בהבטחה שמוחזרת על ידי videoElement.play()
. הסיבה לכך היא שההבטחות עדיין לא מפיצים תנועות של המשתמשים. במקום זאת, צריך לבצע קריאה ל-requestPictureInPicture()
בטיפול בקליק על pipButtonElement
, כפי שמוצג בהמשך. באחריותכם לקבוע מה יקרה אם משתמש ילחץ פעמיים.
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
await videoElement.requestPictureInPicture();
pipButtonElement.disabled = false;
});
כשההתחייבות מתקבלת, Chrome מצמצם את הסרטון לחלון קטן שהמשתמש יכול להעביר ממקום למקום ולמקם מעל חלונות אחרים.
סיימתם. מעולה! אפשר להפסיק לקרוא ולצאת לחופשה שמגיעה לכם. לצערי, זה לא תמיד המצב. ההבטחה עשויה לדחות כל אחת מהסיבות הבאות:
- המערכת לא תומכת ב'תמונה בתוך תמונה'.
- אסור להשתמש בתכונה 'תמונה בתוך תמונה' במסמך בגלל מדיניות ההרשאות המגבילה.
- המטא-נתונים של הסרטון עדיין לא נטענו (
videoElement.readyState === 0
). - קובץ הווידאו מכיל אודיו בלבד.
- המאפיין החדש
disablePictureInPicture
נמצא ברכיב הווידאו. - הקריאה לא בוצעה במטפל באירוע של תנועת משתמש (למשל, לחיצה על לחצן). החל מגרסה 74 של Chrome, האפשרות הזו רלוונטית רק אם עדיין אין רכיב ב'תמונה בתוך תמונה'.
בקטע תמיכה בתכונות שבהמשך מוסבר איך להפעיל או להשבית לחצן על סמך ההגבלות האלה.
נוסיף בלוק try...catch
כדי לתעד את השגיאות האפשריות האלה ולהודיע למשתמש מה קורה.
pipButtonElement.addEventListener('click', async function () {
pipButtonElement.disabled = true;
try {
await videoElement.requestPictureInPicture();
} catch (error) {
// TODO: Show error message to user.
} finally {
pipButtonElement.disabled = false;
}
});
רכיב הווידאו מתנהג באותו אופן גם במצב 'תמונה בתוך תמונה' וגם אם לא: אירועים מופעלים ושיטות הקריאה פועלות. הוא משקף שינויים במצב בחלון 'תמונה בתוך תמונה' (כמו הפעלה, השהיה, דילוג וכו'), ואפשר גם לשנות את המצב באופן פרוגרמטי ב-JavaScript.
יציאה ממצב 'תמונה בתוך תמונה'
עכשיו נגדיר שהלחצן ישמש למעבר ל'תמונה בתוך תמונה' וליציאה ממנה. קודם כל צריך לבדוק אם האובייקט לקריאה בלבד document.pictureInPictureElement
הוא רכיב הווידאו שלנו. אם לא, אנחנו שולחים בקשה להיכנס למצב 'תמונה בתוך תמונה' כפי שמתואר למעלה. אחרת, נבקש ממך לצאת מהשיחה באמצעות הקשה על document.exitPictureInPicture()
. אחרי זה הסרטון יופיע שוב בכרטיסייה המקורית. שימו לב שהשיטה הזו גם מחזירה הבטחה (promise).
...
try {
if (videoElement !== document.pictureInPictureElement) {
await videoElement.requestPictureInPicture();
} else {
await document.exitPictureInPicture();
}
}
...
האזנה לאירועים של 'תמונה בתוך תמונה'
בדרך כלל, מערכות הפעלה מגבילות את התכונה 'תמונה בתוך תמונה' לחלון אחד, ולכן ההטמעה ב-Chrome תואמת לדפוס הזה. כלומר, המשתמשים יכולים להפעיל רק סרטון אחד ב'תמונה בתוך תמונה' בכל פעם. עליכם לצפות מהמשתמשים לצאת ממצב 'תמונה בתוך תמונה' גם אם לא ביקשתם זאת.
פונקציות הטיפול באירועים החדשות enterpictureinpicture
ו-leavepictureinpicture
מאפשרות לנו להתאים אישית את חוויית השימוש למשתמשים. זה יכול להיות כל דבר, החל מעיון
בקטלוג של סרטונים ועד הצגת צ'אט בשידור חי.
videoElement.addEventListener('enterpictureinpicture', function (event) {
// Video entered Picture-in-Picture.
});
videoElement.addEventListener('leavepictureinpicture', function (event) {
// Video left Picture-in-Picture.
// User may have played a Picture-in-Picture video from a different page.
});
התאמה אישית של חלון תמונה בתוך תמונה
ב-Chrome 74 יש תמיכה בלחצנים 'הפעלה/השהיה', 'טראק קודם' ו'טראק הבא' בחלון התצוגה בחלון. אפשר לשלוט בהם באמצעות Media Session API.
כברירת מחדל, לחצן הפעלה/השהיה תמיד מוצג בחלון התמונה בתוך התמונה, אלא אם בסרטון מופעלים אובייקטים של MediaStream (למשל getUserMedia()
, getDisplayMedia()
, canvas.captureStream()
) או שהמשך הזמן של MediaSource מוגדר בסרטון כ-+Infinity
(למשל פיד בשידור חי). כדי לוודא שלחצן ההפעלה/ההשהיה יהיה גלוי תמיד, תוכלו להגדיר את רכיבי ה-handler של פעולות מדיה בסשן מדיה גם לאירועי 'הפעלה' וגם
ל'השהיה', כפי שמפורט בהמשך.
// Show a play/pause button in the Picture-in-Picture window
navigator.mediaSession.setActionHandler('play', function () {
// User clicked "Play" button.
});
navigator.mediaSession.setActionHandler('pause', function () {
// User clicked "Pause" button.
});
הצגת פקדי החלון 'הטראק הקודם' ו'הטראק הבא' דומה. הגדרת מנהלי פעולות של סשן מדיה עבור הפעולות האלה תציג אותן בחלון התמונה בתוך התמונה, ותוכלו לטפל בפעולות האלה.
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" button.
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" button.
});
כדי לראות איך זה עובד, אפשר לנסות את הדוגמה הרשמית של Media Session.
אחזור גודל החלון של 'תמונה בתוך תמונה'
אם אתם רוצים לשנות את איכות הסרטון כשהסרטון נכנס למצב 'תמונה בתוך תמונה' ויוצא ממנו, אתם צריכים לדעת מהו גודל החלון של 'תמונה בתוך תמונה', ולקבל התראה אם משתמש משנה את גודל החלון באופן ידני.
בדוגמה הבאה מוסבר איך לקבל את הרוחב והגובה של חלון 'תמונה בתוך תמונה' כשהוא נוצר או כשמשנים את הגודל שלו.
let pipWindow;
videoElement.addEventListener('enterpictureinpicture', function (event) {
pipWindow = event.pictureInPictureWindow;
console.log(`> Window size is ${pipWindow.width}x${pipWindow.height}`);
pipWindow.addEventListener('resize', onPipWindowResize);
});
videoElement.addEventListener('leavepictureinpicture', function (event) {
pipWindow.removeEventListener('resize', onPipWindowResize);
});
function onPipWindowResize(event) {
console.log(
`> Window size changed to ${pipWindow.width}x${pipWindow.height}`
);
// TODO: Change video quality based on Picture-in-Picture window size.
}
מומלץ לא לקשר ישירות לאירוע שינוי הגודל, כי כל שינוי קטן שנעשה בגודל החלון של התמונה בתוך התמונה יפעיל אירוע נפרד שעלול לגרום לבעיות בביצועים אם מבצעים פעולה יקרה בכל שינוי גודל. במילים אחרות, פעולת שינוי הגודל תפעיל את האירועים שוב ושוב במהירות רבה. כדי לטפל בבעיה הזו מומלץ להשתמש בשיטות נפוצות כמו ויסות נתונים (throttle) והפרדת שלהן.
תמיכה בתכונות
יכול להיות ש-Picture-in-Picture Web API לא נתמך, לכן צריך לזהות את זה כדי לספק שיפור הדרגתי. גם כשהיא נתמכת, המשתמשים יכולים להשבית אותה או להשבית אותה באמצעות מדיניות הרשאות. למרבה המזל, אפשר להשתמש ב-document.pictureInPictureEnabled
, המשתנה הלוגי החדש, כדי לקבוע זאת.
if (!('pictureInPictureEnabled' in document)) {
console.log('The Picture-in-Picture Web API is not available.');
} else if (!document.pictureInPictureEnabled) {
console.log('The Picture-in-Picture Web API is disabled.');
}
מוחל על רכיב לחצן ספציפי בסרטון, כך תוכלו לטפל בחשיפה של הלחצן 'תמונה בתוך תמונה'.
if ('pictureInPictureEnabled' in document) {
// Set button ability depending on whether Picture-in-Picture can be used.
setPipButton();
videoElement.addEventListener('loadedmetadata', setPipButton);
videoElement.addEventListener('emptied', setPipButton);
} else {
// Hide button if Picture-in-Picture is not supported.
pipButtonElement.hidden = true;
}
function setPipButton() {
pipButtonElement.disabled =
videoElement.readyState === 0 ||
!document.pictureInPictureEnabled ||
videoElement.disablePictureInPicture;
}
תמיכה בווידאו של MediaStream
גם אובייקטים של MediaStream שמופעלים בווידאו (למשל getUserMedia()
, getDisplayMedia()
,
canvas.captureStream()
) תומכים ב'תמונה בתוך תמונה' ב-Chrome 71. כלומר, יש לך אפשרות להציג חלון מסוג 'תמונה בתוך תמונה' שמכיל את שידור הווידאו של המשתמש ממצלמת האינטרנט, את הסטרימינג של הווידאו או אפילו רכיב בד ציור. שימו לב שרכיב הווידאו לא חייב להיות מצורף ל-DOM כדי להיכנס למצב 'תמונה בתוך תמונה', כפי שמוצג בהמשך.
הצגת מצלמת האינטרנט של המשתמש בחלון 'תמונה בתוך תמונה'
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getUserMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
הצגת המסך בחלון 'תמונה בתוך תמונה'
const video = document.createElement('video');
video.muted = true;
video.srcObject = await navigator.mediaDevices.getDisplayMedia({video: true});
video.play();
// Later on, video.requestPictureInPicture();
הצגת רכיב של קנבס בחלון 'תמונה בתוך תמונה'
const canvas = document.createElement('canvas');
// Draw something to canvas.
canvas.getContext('2d').fillRect(0, 0, canvas.width, canvas.height);
const video = document.createElement('video');
video.muted = true;
video.srcObject = canvas.captureStream();
video.play();
// Later on, video.requestPictureInPicture();
שילוב של canvas.captureStream()
עם Media Session API מאפשר, למשל, ליצור חלון של פלייליסט אודיו ב-Chrome 74. כדאי לנסות את הדוגמה הרשמית של פלייליסט.
דוגמאות, הדגמות ו-Codelabs
כדאי לעיין בדוגמה הרשמית לתמונה בתוך תמונה כדי לנסות את Picture-in-Picture Web API.
בהמשך יפורסמו הדגמות והדרכות בנושא.
מה השלב הבא?
קודם כול, כדאי לעיין בדף סטטוס ההטמעה כדי לדעת אילו חלקים של ה-API מוטמעים כרגע ב-Chrome ובדפדפנים אחרים.
אלה השינויים הצפויים בעתיד הקרוב:
- מפתחי אתרים יוכלו להוסיף פקדים מותאמים אישית של 'תמונה בתוך תמונה'.
- ממשק Web API חדש יסופק כדי להציג אובייקטים שרירותיים של
HTMLElement
בחלון צף.
תמיכה בדפדפנים
יש תמיכה ב-Picture-in-Picture Web API בדפדפני Chrome, Edge, Opera ו-Safari. פרטים נוספים זמינים ב-MDN.
משאבים
- סטטוס התכונה ב-Chrome: https://www.chromestatus.com/feature/5729206566649856
- באגים בהטמעה של Chrome: https://crbug.com/?q=component:Blink>Media>PictureInPicture
- מפרט ה-Web API של התכונה 'תמונה בתוך תמונה': https://wicg.github.io/picture-in-picture
- בעיות בנושא המפרט: https://github.com/WICG/picture-in-picture/issues
- דוגמה: https://googlechrome.github.io/samples/picture-in-picture/
- מילוי פוליגונים לא רשמי של תמונה בתוך תמונה: https://github.com/gbentaieb/pip-polyfill/
תודה רבה למוניר למורי ולג'ניפר Apacible על העבודה שלהם על 'תמונה בתוך תמונה', ועל העזרה במאמר הזה. תודה רבה לכל מי שהיה מעורב במאמץ התקינה.