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

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

כפי שאפשר לראות, הפריסה של ה-HTML שבה נשתמש בנגן המדיה היא פשוטה למדי: רכיב root <div>
מכיל רכיב מדיה <video>
ורכיב צאצא <div>
שמיועד לפקדי הווידאו.
לחצני הבקרה של הסרטון, שנרחיב עליהם בהמשך, כוללים: לחצן הפעלה/השהיה, לחצן מסך מלא, לחצני דילוג אחורה וקדימה ורכיבים מסוימים שמייצגים את השעה הנוכחית, משך הזמן ומעקב אחר הזמן.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls"></div>
</div>
קריאה של מטא-נתונים של סרטון
קודם כול, נמתין עד שהמטא-נתונים של הסרטון ייטענו כדי להגדיר את משך הסרטון, את השעה הנוכחית ולהפעיל את סרגל ההתקדמות. שימו לב שפונקציית secondsToTimeCode()
היא פונקציית שירות בהתאמה אישית שכתבתי, שממירה מספר שניות למחרוזת בפורמט 'hh:mm:ss' שמתאים יותר במקרה שלנו.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</strong>
</div>
</div>
video.addEventListener('loadedmetadata', function () {
videoDuration.textContent = secondsToTimeCode(video.duration);
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
});

הפעלה או השהיה של סרטון
עכשיו, אחרי שהמטא-נתונים של הסרטון נטענו, נוסיף את הלחצן הראשון שמאפשר למשתמשים להפעיל ולהשהות את הסרטון באמצעות video.play()
ו-video.pause()
, בהתאם למצב ההפעלה שלו.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<strong><button id="playPauseButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
playPauseButton.addEventListener('click', function (event) {
event.stopPropagation();
if (video.paused) {
video.play();
} else {
video.pause();
}
});
במקום לשנות את אמצעי הבקרה של הווידאו ב-event listener click
, אנחנו משתמשים באירועי הווידאו play
ו-pause
. הפיכת אמצעי הבקרה שלנו לאירועים מבוססת עוזרת לנו לשמור על גמישות (כפי שנראה בהמשך עם Media Session API), ומאפשרת לנו לשמור על סנכרון של אמצעי הבקרה אם הדפדפן מתערב בהפעלה.
כשהסרטון מתחיל לפעול, אנחנו משנים את מצב הלחצן ל'השהיה' ומסתירים את לחצני הווידאו. כשהסרטון מושהה, אנחנו פשוט משנים את מצב הלחצן ל'הפעלה' ומציגים את פקדי הסרטון.
video.addEventListener('play', function () {
playPauseButton.classList.add('playing');
});
video.addEventListener('pause', function () {
playPauseButton.classList.remove('playing');
});
כשהזמן שמצוין במאפיין currentTime
של הסרטון משתנה דרך אירוע הווידאו timeupdate
, אנחנו מעדכנים גם את אמצעי הבקרה המותאמים אישית אם הם גלויים.
video.addEventListener('timeupdate', function () {
if (videoControls.classList.contains('visible')) {
videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
videoProgressBar.style.transform = `scaleX(${
video.currentTime / video.duration
})`;
}
});
כשהסרטון מסתיים, אנחנו פשוט משנים את מצב הלחצן ל'הפעלה', מגדירים את currentTime
של הסרטון בחזרה ל-0 ומציגים את לחצני הווידאו בינתיים. חשוב לדעת שאנחנו יכולים גם לבחור לטעון באופן אוטומטי סרטון אחר אם המשתמש הפעיל סוג כלשהו של 'הפעלה אוטומטית'.
video.addEventListener('ended', function () {
playPauseButton.classList.remove('playing');
video.currentTime = 0;
});
דילוג אחורה וקדימה
נמשיך ונוסיף לחצנים של 'דילוג אחורה' ו'דילוג קדימה' כדי שהמשתמשים יוכלו לדלג בקלות על תוכן מסוים.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<strong
><button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button
></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
var skipTime = 10; // Time to skip in seconds
seekForwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
seekBackwardButton.addEventListener('click', function (event) {
event.stopPropagation();
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
כמו קודם, במקום לשנות את סגנון הסרטון במערכי האירועים click
של הלחצנים האלה, נשתמש באירועי הסרטון seeking
ו-seeked
שנורים כדי לשנות את הבהירות של הסרטון. כיתה ה-CSS בהתאמה אישית seeking
שלי פשוטה כמו filter: brightness(0);
.
video.addEventListener('seeking', function () {
video.classList.add('seeking');
});
video.addEventListener('seeked', function () {
video.classList.remove('seeking');
});
בהמשך מפורט מה יצרנו עד עכשיו. בקטע הבא נטמיע את כפתור המסך המלא.
מסך מלא
כאן נשתמש בכמה ממשקי Web API כדי ליצור חוויה מושלמת ומלאה במסך מלא. כדי לראות איך זה עובד, אפשר לעיין בדוגמה.
כמובן שאין צורך להשתמש בכל התכונות האלה. פשוט בוחרים את האפשרויות שמתאימות לכם ומשלבים אותן כדי ליצור תהליך מותאם אישית.
מניעת מעבר אוטומטי למסך מלא
ב-iOS, רכיבי video
עוברים אוטומטית למצב מסך מלא כשהפעלת המדיה מתחילה. אנחנו מנסים להתאים אישית את חוויית המדיה שלנו בדפדפנים לנייד ולשלוט בה ככל האפשר, ולכן מומלץ להגדיר את המאפיין playsinline
של הרכיב video
כדי לאלץ אותו לפעול בתוך הדף ב-iPhone ולא לעבור למצב מסך מלא כשהפעלת ההפעלה מתחילה. חשוב לדעת שאין לכך השפעות לוואי בדפדפנים אחרים.
<div id="videoContainer"></div>
<video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
<div id="videoControls">...</div>
</div>
מעבר למסך מלא או יציאה ממנו בלחיצה על לחצן
עכשיו, אחרי שמונעים את המעבר האוטומטי למסך מלא, צריך לטפל בעצמנו במצב המסך המלא של הסרטון באמצעות Fullscreen API. כשהמשתמש לוחץ על הלחצן 'מסך מלא', אנחנו יוצאים ממצב המסך המלא באמצעות document.exitFullscreen()
אם המסמך נמצא כרגע במצב מסך מלא. אחרת, מבקשים מצב מסך מלא בקונטיינר של הסרטון באמצעות השיטה requestFullscreen()
אם היא זמינה, או עוברים ל-webkitEnterFullscreen()
באלמנט הסרטון רק ב-iOS.
<div id="videoContainer">
<video id="video" src="file.mp4"></video>
<div id="videoControls">
<button id="playPauseButton"></button>
<button id="seekForwardButton"></button>
<button id="seekBackwardButton"></button>
<strong><button id="fullscreenButton"></button></strong>
<div id="videoCurrentTime"></div>
<div id="videoDuration"></div>
<div id="videoProgressBar"></div>
</div>
</div>
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
}
});
function requestFullscreenVideo() {
if (videoContainer.requestFullscreen) {
videoContainer.requestFullscreen();
} else {
video.webkitEnterFullscreen();
}
}
document.addEventListener('fullscreenchange', function () {
fullscreenButton.classList.toggle('active', document.fullscreenElement);
});
החלפת מצב מסך מלא כשכיוון המסך משתנה
כשהמשתמש מסובב את המכשיר למצב לרוחב, כדאי לבקש באופן אוטומטי מסך מלא כדי ליצור חוויה עוצרת נשימה. לשם כך, נצטרך את Screen Orientation API, שעדיין לא נתמך בכל מקום ועדיין מופיע עם קידומת בדפדפנים מסוימים. לכן, זה יהיה השיפור המתקדם הראשון שלנו.
איך זה עובד? ברגע שנזהה שכיוון המסך השתנה, נבקש להציג את הדף במסך מלא אם חלון הדפדפן נמצא במצב אופקי (כלומר, הרוחב שלו גדול מהגובה). אם לא, נצא מהמסך המלא. זה הכול.
if ('orientation' in screen) {
screen.orientation.addEventListener('change', function () {
// Let's request fullscreen if user switches device in landscape mode.
if (screen.orientation.type.startsWith('landscape')) {
requestFullscreenVideo();
} else if (document.fullscreenElement) {
document.exitFullscreen();
}
});
}
מסך הנעילה במצב פריסה לרוחב בלחיצה על כפתור
יכול להיות שצפייה בסרטון תהיה טובה יותר במצב לרוחב, ולכן כדאי לנעול את המסך במצב לרוחב כשהמשתמש לוחץ על הלחצן 'מסך מלא'. כדי להבטיח שהחוויה הזו תהיה הטובה ביותר, נעשה שימוש בשילוב של Screen Orientation API ששימש בעבר ושל כמה שאילתות מדיה.
כדי לנעול את המסך בתצוגה לרוחב, פשוט מקישים על screen.orientation.lock('landscape')
. עם זאת, כדאי לעשות זאת רק כשהמכשיר במצב לאורך עם matchMedia('(orientation: portrait)')
ואפשר להחזיק אותו ביד אחת עם matchMedia('(max-device-width: 768px)')
, כי חוויית המשתמש לא תהיה טובה למשתמשים בטאבלט.
fullscreenButton.addEventListener('click', function (event) {
event.stopPropagation();
if (document.fullscreenElement) {
document.exitFullscreen();
} else {
requestFullscreenVideo();
<strong>lockScreenInLandscape();</strong>;
}
});
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (
matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches
) {
screen.orientation.lock('landscape');
}
}
ביטול נעילת המסך כשמשנים את כיוון המכשיר
יכול להיות ששמתם לב שחוויית מסך הנעילה שיצרנו עכשיו לא מושלמת, כי אנחנו לא מקבלים עדכונים על שינויים בכיוון המסך כשהמסך נעול.
כדי לפתור את הבעיה, נשתמש ב-Device Orientation API אם הוא זמין. ה-API הזה מספק מידע מהחומרה שמודדת את המיקום והתנועה של המכשיר במרחב: ג'ירוסקופ ומצפן דיגיטלי למיקום, וחיישן תאוצה למהירות. כשאנחנו מזהים שינוי בכיוון המכשיר, נאפשר לבטל את נעילת המסך באמצעות screen.orientation.unlock()
אם המשתמש מחזיק את המכשיר בפריסה לאורך והמסך נעול בפריסה לרוחב.
function lockScreenInLandscape() {
if (!('orientation' in screen)) {
return;
}
// Let's force landscape mode only if device is in portrait mode and can be held in one hand.
if (matchMedia('(orientation: portrait) and (max-device-width: 768px)').matches) {
screen.orientation.lock('landscape')
<strong>.then(function() {
listenToDeviceOrientationChanges();
})</strong>;
}
}
function listenToDeviceOrientationChanges() {
if (!('DeviceOrientationEvent' in window)) {
return;
}
var previousDeviceOrientation, currentDeviceOrientation;
window.addEventListener(
'deviceorientation',
function onDeviceOrientationChange(event) {
// event.beta represents a front to back motion of the device and
// event.gamma a left to right motion.
if (Math.abs(event.gamma) > 10 || Math.abs(event.beta) < 10) {
previousDeviceOrientation = currentDeviceOrientation;
currentDeviceOrientation = 'landscape';
return;
}
if (Math.abs(event.gamma) < 10 || Math.abs(event.beta) > 10) {
previousDeviceOrientation = currentDeviceOrientation;
// When device is rotated back to portrait, let's unlock screen orientation.
if (previousDeviceOrientation == 'landscape') {
screen.orientation.unlock();
window.removeEventListener(
'deviceorientation',
onDeviceOrientationChange,
);
}
}
},
);
}
כפי שאתם רואים, זוהי חוויית השימוש חלקה במסך מלא שרצינו. כדי לראות את התכונה בפעולה, אפשר לעיין בדוגמה.
הפעלה ברקע
אם אתם מזהים שדף אינטרנט או סרטון בדף האינטרנט כבר לא גלויים, כדאי לעדכן את ניתוח הנתונים כך שישקף את המצב הזה. הדבר יכול גם להשפיע על ההפעלה הנוכחית, למשל לבחור טראק אחר, להשהות אותו או אפילו להציג למשתמשים לחצנים מותאמים אישית.
השהיה של הסרטון כשהנראות של הדף משתנה
בעזרת Page Visibility API אנחנו יכולים לקבוע את רמת החשיפה הנוכחית של דף ולקבל התראות על שינויים ברמת החשיפה. הקוד הבא משהה את הסרטון כשהדף מוסתר. זה קורה למשל כשנעילה של המסך פעילה או כשעוברים בין כרטיסיות.
מאחר שרוב הדפדפנים לנייד מציעים עכשיו אמצעי בקרה מחוץ לדפדפן שמאפשרים להמשיך את הצפייה בסרטון מושהה, מומלץ להגדיר את ההתנהגות הזו רק אם המשתמש מורשה להפעיל את הסרטון ברקע.
document.addEventListener('visibilitychange', function () {
// Pause video when page is hidden.
if (document.hidden) {
video.pause();
}
});
הצגה/הסתרה של לחצן ההשתקה כשמשנים את הרשאות הגישה של הסרטון
אם משתמשים ב-Intersection Observer API החדש, אפשר לקבל נתונים מפורטים יותר ללא עלות. ה-API הזה מאפשר לכם לדעת מתי רכיב שנצפה נכנס או יוצא מחלון התצוגה של הדפדפן.
נראה או נסתיר את לחצן ההשתקה בהתאם לחשיפה של הסרטון בדף. אם הסרטון פועל אבל לא גלוי כרגע, יוצג לחצן השתקה קטן בפינה השמאלית התחתונה של הדף כדי לתת למשתמש שליטה על צליל הסרטון. אירוע הווידאו volumechange
משמש לעדכון העיצוב של לחצן ההשתקה.
<button id="muteButton"></button>
if ('IntersectionObserver' in window) {
// Show/hide mute button based on video visibility in the page.
function onIntersection(entries) {
entries.forEach(function (entry) {
muteButton.hidden = video.paused || entry.isIntersecting;
});
}
var observer = new IntersectionObserver(onIntersection);
observer.observe(video);
}
muteButton.addEventListener('click', function () {
// Mute/unmute video on button click.
video.muted = !video.muted;
});
video.addEventListener('volumechange', function () {
muteButton.classList.toggle('active', video.muted);
});
הפעלת סרטון אחד בלבד בכל פעם
אם יש יותר מסרטון אחד בדף, מומלץ להפעיל רק סרטון אחד ולהשהות את שאר הסרטונים באופן אוטומטי, כדי שהמשתמשים לא יצטרכו לשמוע כמה טראקים של אודיו שמתנגנים בו-זמנית.
// This array should be initialized once all videos have been added.
var videos = Array.from(document.querySelectorAll('video'));
videos.forEach(function (video) {
video.addEventListener('play', pauseOtherVideosPlaying);
});
function pauseOtherVideosPlaying(event) {
var videosToPause = videos.filter(function (video) {
return !video.paused && video != event.target;
});
// Pause all other videos currently playing.
videosToPause.forEach(function (video) {
video.pause();
});
}
התאמה אישית של התראות מדיה
באמצעות Media Session API, אפשר גם להתאים אישית את ההתראות על מדיה על ידי מתן מטא-נתונים של הסרטון שפועל כרגע. הוא גם מאפשר לטפל באירועים שקשורים למדיה, כמו דילוג או שינוי מסלול, שעשויים להגיע מהתראות או מלחצני מדיה. כדי לראות את התכונה בפעולה, אפשר לעיין בדוגמה.
כשאפליקציית האינטרנט מפעילה אודיו או וידאו, כבר מופיעה התראה על מדיה בחלונית ההתראות. ב-Android, Chrome עושה כמיטב יכולתו כדי להציג מידע מתאים, באמצעות שם המסמך ותמונת הסמל הגדולה ביותר שהוא יכול למצוא.
נראה איך להתאים אישית את ההתראה הזו על מדיה על ידי הגדרת חלק מהמטא-נתונים של סשן המדיה, כמו השם, האומן, שם האלבום ועטיפת האלבום, באמצעות Media Session API.
playPauseButton.addEventListener('click', function(event) {
event.stopPropagation();
if (video.paused) {
video.play()
<strong>.then(function() {
setMediaSession();
});</strong>
} else {
video.pause();
}
});
function setMediaSession() {
if (!('mediaSession' in navigator)) {
return;
}
navigator.mediaSession.metadata = new MediaMetadata({
title: 'Never Gonna Give You Up',
artist: 'Rick Astley',
album: 'Whenever You Need Somebody',
artwork: [
{src: 'https://dummyimage.com/96x96', sizes: '96x96', type: 'image/png'},
{
src: 'https://dummyimage.com/128x128',
sizes: '128x128',
type: 'image/png',
},
{
src: 'https://dummyimage.com/192x192',
sizes: '192x192',
type: 'image/png',
},
{
src: 'https://dummyimage.com/256x256',
sizes: '256x256',
type: 'image/png',
},
{
src: 'https://dummyimage.com/384x384',
sizes: '384x384',
type: 'image/png',
},
{
src: 'https://dummyimage.com/512x512',
sizes: '512x512',
type: 'image/png',
},
],
});
}
בסיום ההפעלה, לא צריך 'לשחרר' את סשן המדיה כי ההתראה תיעלם באופן אוטומטי. חשוב לזכור ש-navigator.mediaSession.metadata
הנוכחי ישמש כשהפעלת ההשמעה תתחיל. לכן, חשוב לעדכן אותו כדי לוודא שתמיד יוצג מידע רלוונטי בהתראה על המדיה.
אם באפליקציית האינטרנט שלכם יש פלייליסט, כדאי לאפשר למשתמש לנווט בפלייליסט ישירות מההתראה על המדיה באמצעות כמה סמלים של 'טראק קודם' ו'טראק הבא'.
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('previoustrack', function () {
// User clicked "Previous Track" media notification icon.
playPreviousVideo(); // load and play previous video
});
navigator.mediaSession.setActionHandler('nexttrack', function () {
// User clicked "Next Track" media notification icon.
playNextVideo(); // load and play next video
});
}
חשוב לזכור שמטפלי הפעולות של המדיה ימשיכו לפעול. הפתרון הזה דומה מאוד לדפוס של מאזין לאירועים, אלא שבטיפול באירוע הדפדפן מפסיק לבצע את התנהגות ברירת המחדל שלו ומשתמש בכך כאות לכך שאפליקציית האינטרנט תומכת בפעולה של המדיה. לכן, לחצני הפעולה של המדיה לא יוצגו אלא אם תגדירו את בורר הפעולות המתאים.
דרך אגב, ביטול ההגדרה של בורר פעולות מדיה קל כמו הקצאה שלו ל-null
.
באמצעות Media Session API אפשר להציג את הסמלים של התראות המדיה 'דילוג אחורה' ו'דילוג קדימה', אם רוצים לשלוט בכמות הזמן שרוצים לדלג.
if ('mediaSession' in navigator) {
let skipTime = 10; // Time to skip in seconds
navigator.mediaSession.setActionHandler('seekbackward', function () {
// User clicked "Seek Backward" media notification icon.
video.currentTime = Math.max(video.currentTime - skipTime, 0);
});
navigator.mediaSession.setActionHandler('seekforward', function () {
// User clicked "Seek Forward" media notification icon.
video.currentTime = Math.min(video.currentTime + skipTime, video.duration);
});
}
הסמל 'הפעלה/השהיה' תמיד מוצג בהתראה על המדיה, והאירועים הקשורים מטופלים באופן אוטומטי על ידי הדפדפן. אם מסיבה כלשהי ההתנהגות שמוגדרת כברירת מחדל לא מתאימה לכם, אתם עדיין יכולים לטפל באירועי מדיה מסוג 'הפעלה' ו'השהיה'.
היתרון של Media Session API הוא שחלונית ההתראות היא לא המקום היחיד שבו אפשר לראות את המטא-נתונים ואת אמצעי הבקרה של המדיה. ההתראה על מדיה מסתנכרנת באופן אוטומטי עם כל מכשיר לבישה מותאם. הוא מופיע גם במסכי הנעילה.