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

François Beaufort
François Beaufort

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

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

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

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

פריסת HTML
תרשים 1.פריסת HTML

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

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

כמו קודם, במקום לשנות את סגנון הסרטון ברכיבי ה-event listener של 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
  });
}

חשוב לזכור שה-handlers של פעולות המדיה יישארו. זה דומה מאוד לדפוס של רכיב event listener, אבל הטיפול באירוע אומר שהדפדפן מפסיק לבצע את פעולת ברירת המחדל ומשתמש בזה כאות לכך שאפליקציית האינטרנט תומכת בפעולת המדיה. לכן, פקדי הפעולות של המדיה לא יוצגו אלא אם תגדירו את ה-action handler המתאים.

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

משוב