הפעלת סרטון באינטרנט לנייד

François Beaufort
François Beaufort

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

הפעלת סרטון באינטרנט לנייד

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

אמצעי בקרה בהתאמה אישית

פריסת HTML
איור 1.פריסה של HTML

כפי שניתן לראות, הפריסה של ה-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
  })`;
});
מטא-נתונים של סרטון בלבד
איור 2. נגן מדיה שמוצגים בו מטא-נתונים של סרטון

הפעלה או השהיה של סרטון

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

משוב