Mobil web videosu oynatma

François Beaufort
François Beaufort

Web'de en iyi mobil medya deneyimini nasıl oluşturursunuz? Kolay! Bu, tamamen kullanıcı etkileşimine ve bir web sayfasındaki medyaya verdiğiniz öneme bağlıdır. Kullanıcının ziyaretinin asıl nedeni videoysa kullanıcı deneyiminin etkileyici ve yeniden etkileşim kurmaya teşvik edici olması gerektiği konusunda hepimiz hemfikiriz.

mobil web videosu oynatma

Bu makalede, çok sayıda Web API'si sayesinde medya deneyiminizi aşamalı olarak nasıl geliştireceğinizi ve daha sürükleyici hale getireceğinizi göstereceğim. Bu nedenle, özel kontroller, tam ekran ve arka planda oynatma özellikleriyle basit bir mobil oynatıcı deneyimi oluşturacağız. Örneği hemen deneyebilir ve kodu GitHub depomuzda bulabilirsiniz.

Özel kontroller

HTML Düzeni
Şekil 1.HTML Düzeni

Gördüğünüz gibi, medya oynatıcımız için kullanacağımız HTML düzeni oldukça basit: <div> kök öğesi, <video> medya öğesi ve video kontrollerine ayrılmış bir <div> alt öğe içeriyor.

Daha sonra ele alacağımız video kontrolleri arasında oynat/duraklat düğmesi, tam ekran düğmesi, geri ve ileri sarma düğmeleri ile geçerli zaman, süre ve zaman takibi için bazı öğeler yer alır.

<div id="videoContainer">
  <video id="video" src="file.mp4"></video>
  <div id="videoControls"></div>
</div>

Video meta verilerini okuma

Öncelikle video süresini ve geçerli zamanı ayarlamak ve ilerleme çubuğunu başlatmak için video meta verilerinin yüklenmesini bekleyelim. secondsToTimeCode() işlevi, saniye sayısını "ss:dd:sn" biçiminde bir dizeye dönüştüren, yazdığım özel bir hizmet işlevidir. Bu biçim, bizim durumumuz için daha uygundur.

<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
  })`;
});
yalnızca video meta verileri
Şekil 2. Medya oynatıcı, video meta verilerini gösteriyor

Videoyu oynat/duraklat

Video meta verileri yüklendiğine göre, kullanıcının oynatma durumuna bağlı olarak video.play() ve video.pause() ile videoyu oynatıp duraklatmasına olanak tanıyan ilk düğmemizi ekleyelim.

<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();
  }
});

Video kontrollerimizi click etkinlik işleyicide ayarlamak yerine play ve pause video etkinliklerini kullanırız. Kontrollerimizi etkinlik tabanlı hale getirmek esneklik sağlar (Media Session API ile daha sonra göreceğimiz gibi) ve tarayıcı oynatmaya müdahale ederse kontrollerimizi senkronize tutmamıza olanak tanır. Video oynatılmaya başladığında düğme durumunu "duraklat" olarak değiştirir ve video kontrollerini gizleriz. Video duraklatıldığında düğme durumunu "oynat" olarak değiştirir ve video kontrollerini gösteririz.

video.addEventListener('play', function () {
  playPauseButton.classList.add('playing');
});

video.addEventListener('pause', function () {
  playPauseButton.classList.remove('playing');
});

timeupdate video etkinliği aracılığıyla video currentTime özelliği tarafından belirtilen süre değiştiğinde, görünür durumdalarsa özel kontrollerimizi de güncelleriz.

video.addEventListener('timeupdate', function () {
  if (videoControls.classList.contains('visible')) {
    videoCurrentTime.textContent = secondsToTimeCode(video.currentTime);
    videoProgressBar.style.transform = `scaleX(${
      video.currentTime / video.duration
    })`;
  }
});

Video sona erdiğinde düğme durumunu "oynat" olarak değiştirir, videoyu currentTime 0'a geri ayarlar ve şimdilik video kontrollerini gösteririz. Kullanıcı bir tür "Otomatik Oynatma" özelliğini etkinleştirdiyse otomatik olarak başka bir video yüklemeyi de seçebiliriz.

video.addEventListener('ended', function () {
  playPauseButton.classList.remove('playing');
  video.currentTime = 0;
});

Geri ve ileri sarma

Devam edelim ve kullanıcının bazı içerikleri kolayca atlayabilmesi için "geri sar" ve "ileri sar" düğmeleri ekleyelim.

<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);
});

Daha önce olduğu gibi, bu düğmelerin click etkinlik işleyicilerinde video stilini ayarlamak yerine, video parlaklığını ayarlamak için tetiklenen seeking ve seeked video etkinliklerini kullanacağız. Özel seeking CSS sınıfım filter: brightness(0); kadar basit.

video.addEventListener('seeking', function () {
  video.classList.add('seeking');
});

video.addEventListener('seeked', function () {
  video.classList.remove('seeking');
});

Şimdiye kadar oluşturduğumuz içerikleri aşağıda bulabilirsiniz. Bir sonraki bölümde tam ekran düğmesini uygulayacağız.

Tam ekran

Burada, mükemmel ve sorunsuz bir tam ekran deneyimi oluşturmak için çeşitli web API'lerinden yararlanacağız. Nasıl çalıştığını görmek için örneğe göz atın.

Elbette bunların hepsini kullanmanız gerekmez. Size uygun olanları seçip birleştirerek özel akışınızı oluşturabilirsiniz.

Otomatik tam ekranı engelleme

iOS'te, medya oynatımı başladığında video öğeleri otomatik olarak tam ekran moduna girer. Mobil tarayıcılardaki medya deneyimimizi mümkün olduğunca özelleştirmeye ve kontrol etmeye çalıştığımız için video öğesinin playsinline özelliğini, iPhone'da satır içi oynatılmaya zorlayacak ve oynatma başladığında tam ekran moduna girmeyecek şekilde ayarlamanızı öneririm. Bu işlemin diğer tarayıcılar üzerinde yan etkisi yoktur.

<div id="videoContainer"></div>
  <video id="video" src="file.mp4"></video><strong>playsinline</strong></video>
  <div id="videoControls">...</div>
</div>

Düğme tıklandığında tam ekranı aç/kapat

Otomatik tam ekranı engellediğimiz için videonun tam ekran modunu Fullscreen API ile kendimiz yönetmemiz gerekir. Kullanıcı "tam ekran düğmesini" tıkladığında, tam ekran modu şu anda doküman tarafından kullanılıyorsa document.exitFullscreen() ile tam ekran modundan çıkalım. Aksi takdirde, varsa requestFullscreen() yöntemiyle video kapsayıcısında tam ekran isteğinde bulunun veya yalnızca iOS'te video öğesinde webkitEnterFullscreen() yöntemine geri dönün.

<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);
});

Ekran yönü değiştiğinde tam ekranı aç/kapat

Kullanıcı cihazı yatay moda döndürdüğünde akıllıca davranıp otomatik olarak tam ekran isteğinde bulunarak sürükleyici bir deneyim sunalım. Bunun için Screen Orientation API'yi kullanmamız gerekir. Bu API henüz her yerde desteklenmemektedir ve bazı tarayıcılarda hâlâ ön ekli olarak kullanılmaktadır. Bu nedenle, bu bizim ilk progresif geliştirmemiz olacak.

Nasıl çalışır? Ekran yönünün değiştiğini algıladığımız anda, tarayıcı penceresi yatay moddaysa (yani genişliği yüksekliğinden fazlaysa) tam ekran isteğinde bulunalım. Değilse tam ekrandan çıkalım. Hepsi bu kadar.

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();
    }
  });
}

Düğme tıklandığında ekranı yatay olarak kilitleme

Video, yatay modda daha iyi görüntülenebileceğinden kullanıcı "tam ekran düğmesini" tıkladığında ekranı yatay modda kilitlemek isteyebiliriz. Bu deneyimin en iyi şekilde sunulması için daha önce kullanılan Screen Orientation API ile bazı medya sorgularını birleştireceğiz.

Yatay modda ekranı kilitlemek için screen.orientation.lock('landscape') demeniz yeterli. Ancak bunu yalnızca cihaz matchMedia('(orientation: portrait)') ile portre modundayken ve matchMedia('(max-device-width: 768px)') ile tek elle tutulabiliyorken yapmalıyız. Tablet kullanıcıları için bu iyi bir deneyim olmayacaktır.

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');
  }
}

Cihazın yönü değiştiğinde ekran kilidini açma

Oluşturduğumuz kilit ekranı deneyiminin mükemmel olmadığını fark etmiş olabilirsiniz. Ekran kilitliyken ekran yönü değişikliklerini alamıyoruz.

Bu sorunu düzeltmek için varsa Device Orientation API'yi kullanalım. Bu API, bir cihazın uzaydaki konumunu ve hareketini ölçen donanımdan bilgi sağlar: yönü için jiroskop ve dijital pusula, hızı için ivme ölçer. Cihazın yönünün değiştiğini algıladığımızda, kullanıcı cihazı portre modunda tutuyorsa ve ekran yatay modda kilitliyse screen.orientation.unlock() ile ekranın kilidini açalım.

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,
          );
        }
      }
    },
  );
}

Gördüğünüz gibi, bu tam ekran deneyimi tam olarak aradığımız şey. Bunun nasıl çalıştığını görmek için örneğe göz atın.

Arka planda oynatma

Bir web sayfasının veya web sayfasındaki bir videonun artık görünür olmadığını tespit ettiğinizde analizlerinizi bunu yansıtacak şekilde güncellemek isteyebilirsiniz. Bu, mevcut oynatmayı da etkileyebilir. Örneğin, farklı bir parça seçebilir, parçayı duraklatabilir, hatta kullanıcıya özel düğmeler gösterebilir.

Sayfa görünürlüğü değiştiğinde videoyu duraklatma

Page Visibility API ile bir sayfanın mevcut görünürlüğünü belirleyebilir ve görünürlük değişiklikleri hakkında bildirim alabiliriz. Aşağıdaki kod, sayfa gizlendiğinde videoyu duraklatır. Bu durum, ekran kilidi etkinken veya sekmeler arasında geçiş yaptığınızda meydana gelir.

Çoğu mobil tarayıcı artık duraklatılmış bir videonun devam ettirilmesine olanak tanıyan tarayıcı dışı kontroller sunduğundan, bu davranışı yalnızca kullanıcının arka planda oynatmasına izin veriliyorsa ayarlamanızı öneririz.

document.addEventListener('visibilitychange', function () {
  // Pause video when page is hidden.
  if (document.hidden) {
    video.pause();
  }
});

Video görünürlüğü değiştiğinde mikrofonu kapatma düğmesini göster/gizle

Yeni Intersection Observer API'yi kullanırsanız ücretsiz olarak daha ayrıntılı bilgi edinebilirsiniz. Bu API, gözlemlenen bir öğenin tarayıcının görüntü alanına ne zaman girdiğini veya çıktığını bildirir.

Sayfadaki video görünürlüğüne göre mikrofonu kapatma düğmesini gösterelim/gizleyelim. Video oynatılıyor ancak o anda görünmüyorsa sayfanın sağ alt köşesinde, kullanıcının video sesini kontrol etmesini sağlayan küçük bir mikrofonu kapatma düğmesi gösterilir. volumechange video etkinliği, mikrofonu kapatma düğmesinin stilini güncellemek için kullanılır.

<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);
});

Aynı anda yalnızca bir video oynatma

Bir sayfada birden fazla video varsa yalnızca birini oynatmanızı ve diğerlerini otomatik olarak duraklatmanızı öneririz. Böylece kullanıcı, aynı anda birden fazla ses parçasını dinlemek zorunda kalmaz.

// 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();
  });
}

Medya bildirimlerini özelleştirme

Media Session API ile, oynatılmakta olan videonun meta verilerini sağlayarak medya bildirimlerini de özelleştirebilirsiniz. Ayrıca, bildirimlerden veya medya tuşlarından gelebilecek, arama veya parça değiştirme gibi medyayla ilgili etkinlikleri işlemenize olanak tanır. Bunun nasıl çalıştığını görmek için örneğe göz atın.

Web uygulamanız ses veya video oynatırken bildirim tepsisinde bir medya bildirimi görürsünüz. Android'de Chrome, dokümanın başlığını ve bulabildiği en büyük simge resmini kullanarak uygun bilgileri göstermek için elinden geleni yapar.

Media Session API ile başlık, sanatçı, albüm adı ve poster gibi bazı medya oturumu meta verilerini ayarlayarak bu medya bildirimini nasıl özelleştireceğinizi görelim.

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',
      },
    ],
  });
}

Oynatma işlemi tamamlandıktan sonra, bildirim otomatik olarak kaybolacağı için medya oturumunu "serbest bırakmanız" gerekmez. Oynatma başladığında mevcut navigator.mediaSession.metadata kullanılacağını unutmayın. Bu nedenle, medya bildiriminde her zaman alakalı bilgileri gösterdiğinizden emin olmak için bu bilgileri güncellemeniz gerekir.

Web uygulamanız bir oynatma listesi sağlıyorsa kullanıcının "Önceki Parça" ve "Sonraki Parça" simgeleriyle doğrudan medya bildiriminden oynatma listenizde gezinmesine izin verebilirsiniz.

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
  });
}

Medya işlemi işleyicilerinin kalıcı olacağını unutmayın. Bu, etkinlik işleyici kalıbına çok benzer. Tek fark, bir etkinliğin işlenmesi, tarayıcının varsayılan davranışları durdurması ve bunu web uygulamanızın medya işlemini desteklediğine dair bir sinyal olarak kullanmasıdır. Bu nedenle, uygun işlem işleyiciyi ayarlamadığınız sürece medya işlemi kontrolleri gösterilmez.

Bu arada, medya işlemi işleyicisini kaldırmak, işleyiciyi null değerine atamak kadar kolaydır.

Media Session API, atlanan süreyi kontrol etmek istiyorsanız "Geri Sar" ve "İleri Sar" medya bildirimi simgelerini göstermenize olanak tanır.

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);
  });
}

"Oynat/Duraklat" simgesi her zaman medya bildiriminde gösterilir ve ilgili etkinlikler tarayıcı tarafından otomatik olarak işlenir. Varsayılan davranış herhangi bir nedenle işe yaramazsa "Oynat" ve "Duraklat" medya etkinliklerini yine de işleyebilirsiniz.

Media Session API'nin en iyi özelliklerinden biri, medya meta verilerinin ve kontrollerinin yalnızca bildirim tepsisinde görünür olmamasıdır. Medya bildirimi, eşlenen tüm giyilebilir cihazlarla otomatik olarak senkronize edilir. Ayrıca kilit ekranlarında da gösterilir.

Geri bildirim