Mobil Web Video 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 sürükleyici ve yeniden etkileşime geçirici olması gerektiği konusunda hepimiz hemfikiriz.

mobil web'de video oynatma

Bu makalede, çok sayıda Web API'si sayesinde medya deneyiminizi nasıl aşamalı bir şekilde geliştireceğinizi ve daha etkileyici hale getireceğinizi göstereceğim. Bu nedenle, özel kontroller, tam ekran ve arka planda oynatma özelliklerine sahip basit bir mobil oynatıcı deneyimi oluşturacağız. Örnek kodunu 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, bir <video> medya öğesi ve video kontrollerine özel bir <div> alt öğesi içerir.

İlerleyen bölümlerde ele alacağımız video kontrolleri şunlardır: oynatma/duraklatma 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.

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

Video meta verilerini okuma

Öncelikle video süresini, mevcut zamanı ayarlamak ve ilerleme çubuğunu başlatmak için video meta verilerinin yüklenmesini bekleyelim. secondsToTimeCode() işlevinin, saniye sayısını "ss:dk:sn" biçiminde bir dizeye dönüştüren, yazdığım özel bir yardımcı program işlevi olduğunu unutmayın. Bu işlev, bizim durumumuza 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. Video meta verilerini gösteren Medya Oynatıcı

Videoyu oynat/duraklat

Video meta verileri yüklendikten sonra, oynatma durumuna bağlı olarak kullanıcının videoyu video.play() ve video.pause() ile oynatmasına ve 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 dinleyicisinde ayarlamak yerine play ve pause video etkinliklerini kullanırız. Kontrollerimizi etkinliğe dayalı hale getirmek esneklik sağlar (daha sonra Media Session API ile 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ştirip video kontrollerini gösteririz.

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

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

Video currentTime özelliği tarafından belirtilen zaman, timeupdate video etkinliği aracılığıyla değiştiğinde, görünür durumdaysa özel denetimlerimizi 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, video currentTime süresini 0 olarak ayarlayıp video kontrollerini gösteririz. Kullanıcı bir tür "otomatik oynatma" özelliğini etkinleştirdiyse başka bir videoyu otomatik olarak yüklemeyi de seçebileceğimizi unutmayın.

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

Geri ve ileri sarma

Kullanıcının kolayca bazı içerikleri kolayca atlayabilmesi için devam edelim ve "geriye 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, video stilini bu düğmelerin click etkinlik dinleyicilerinde 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');
});

Şu ana kadar oluşturduklarımızı aşağıda bulabilirsiniz. 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. Bu özelliğin işleyiş şeklini görmek için örneğe göz atın.

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

Otomatik tam ekranı engelle

iOS'te, medya oynatmaya başlandığında video öğeleri otomatik olarak tam ekran moduna geçer. Mobil tarayıcılardaki medya deneyimimizi mümkün olduğunca uyarlamaya ve kontrol etmeye çalıştığımızdan, video öğesinin playsinline özelliğini, oynatma başladığında iPhone'da satır içinde oynatılmaya ve tam ekran moduna girmemeye zorlayacak şekilde ayarlamanızı öneririz. Bu işlemin diğer tarayıcılar üzerinde hiçbir yan etkisi olmadığını unutmayın.

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

Düğmeye basıldığında tam ekranı açma/kapatma

Artık otomatik tam ekranı engellediğimize göre video için tam ekran modunu Fullscreen API ile yönetmemiz gerekiyor. Kullanıcı "tam ekran düğmesini" tıkladığında, tam ekran modu doküman tarafından kullanılıyorsa document.exitFullscreen() ile tam ekran modundan çıkalım. Aksi takdirde, varsa requestFullscreen() yöntemini kullanarak video kapsayıcıda tam ekran isteğinde bulunun veya yalnızca iOS'te video öğesinde webkitEnterFullscreen() değerini kullanı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 modda döndürdüğünde, bu konuda dikkatli olalım ve etkileyici bir deneyim oluşturmak için otomatik olarak tam ekran isteğinde bulunalım. Bunun için henüz her yerde desteklenmeyen ve bazı tarayıcılarda hâlâ ön ek gerektiren Screen Orientation API'ye ihtiyacımız var. Dolayısıyla, bu ilk ilerleme geliştirmemiz olacak.

Nasıl çalışır? Ekran yönünün değiştiğini algıladıktan sonra, tarayıcı penceresi yatay moddaysa (yani genişliği yüksekliğinden büyükse) tam ekran isteğinde bulunalım. Aksi takdirde 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 modda kilitle

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

Ekranı yatay modda kilitlemek screen.orientation.lock('landscape') aramak kadar kolaydır. Ancak bu, tablet kullanıcıları için iyi bir deneyim olmayacağından yalnızca cihaz matchMedia('(orientation: portrait)') ile dikey moddayken ve matchMedia('(max-device-width: 768px)') ile tek elle tutulabilirken yapılmalıdı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 yönü değiştiğinde ekranın kilidini açma

Ekran kilitliyken ekran yön değişikliklerini almadığımız için yeni oluşturduğumuz kilit ekranı deneyiminin mükemmel olmadığını fark etmiş olabilirsiniz.

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. Örneğin, yönü için jiroskop ve dijital pusula, cihazın hızı için ivme ölçer. Cihaz yönü değişikliği algıladığımızda, kullanıcı cihazı dikey modda tutuyorsa ve ekran yatay modda kilitliyse ekranı screen.orientation.unlock() ile kilitleyelim.

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, aradığımız sorunsuz tam ekran deneyimi budur. Bu uygulamanın işleyiş şeklini 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ünmediğini tespit ettiğinizde analizlerinizi bunu yansıtacak şekilde güncelleyebilirsiniz. Bu durum, farklı bir parçayı seçmek, duraklatmak veya kullanıcıya özel düğmeler göstermek gibi mevcut oynatmayı da etkileyebilir.

Sayfa görünürlüğünde değişiklik olduğunda videoyu duraklatma

Sayfa Görünürlüğü API ile bir sayfanın mevcut görünürlüğünü belirleyebilir ve görünürlük değişiklikleri hakkında bildirim alabilirsiniz. Aşağıdaki kod, sayfa gizlendiğinde videoyu duraklatır. Bu durum, ekran kilidi etkinken veya sekme değiştirdiğinizde gerçekleşir.

Çoğu mobil tarayıcı artık tarayıcının dışında duraklatılmış bir videoyu devam ettirmeye olanak tanıyan 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üğünde değişiklik yapıldığında sessize alma düğmesini gösterme/gizleme

Yeni Intersection Observer API'sini kullanırsanız hiçbir ücret ödemeden daha da ayrıntılı veriler oluşturabilirsiniz. Bu API, gözlemlenen bir öğenin tarayıcının görüntü alanına ne zaman girdiğini veya görüntü alanından ne zaman çıktığını size bildirir.

Sayfadaki video görünürlüğüne dayalı olarak bir kapatma düğmesini gösterelim/gizleyelim. Video oynatılıyor ancak şu anda görünmüyorsa sayfanın sağ alt köşesinde küçük bir kapatma düğmesi gösterilir. Bu düğme, kullanıcının video sesini kontrol edebilmesini sağlar. volumechange video etkinliği, sessize alma düğmesi 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);
});

Tek seferde yalnızca bir video oynatabilirsiniz.

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

// 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 olan arama veya izleme değişiklikleri gibi medya ile ilgili etkinlikleri yönetmenize de olanak tanır. Bu uygulamanın işleyiş şeklini görmek için örneği inceleyin.

Web uygulamanız ses veya video oynatırken bildirim tepsisinde bir medya bildirimi görebilirsiniz. Android'de Chrome, dokümanın başlığını ve bulabileceğ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ığında bildirim otomatik olarak kaybolacağından medya oturumunu "bırakmanız" gerekmez. Oynatma işlemi başladığında mevcut navigator.mediaSession.metadata değerinin kullanılacağını unutmayın. Bu nedenle, medya bildiriminde her zaman alakalı bilgiler gösterildiğinden emin olmak için güncellemeniz gerekir.

Web uygulamanız bir şarkı listesi sunuyorsa kullanıcının "Önceki Parça" ve "Sonraki Parça" simgeleriyle doğrudan medya bildiriminden oynatma listenizde gezinmesine izin vermek isteyebilirsiniz.

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 devam edeceğini unutmayın. Bu, etkinlik dinleyici kalıbına çok benzer. Tek fark, bir etkinliğin işlenmesi durumunda tarayıcının varsayılan davranışı durdurması ve bunu web uygulamanızın medya işlemini desteklediğinin bir sinyali olarak kullanmasıdır. Bu nedenle, uygun işlem işleyiciyi ayarlamadığınız sürece medya işlemi kontrolleri gösterilmez.

Bir medya işlemi işleyicisinin ayarını kaldırmak, null değerine atama işlemi kadar kolaydır.

Media Session API, atlanan süreyi kontrol etmek istiyorsanız "Geri Sar" ve "İleri Sar" medya bildirim 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 medya bildiriminde her zaman 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 denetimlerinin yalnızca bildirim tepsisinde görünmemesidir. Medya bildirimi, eşlenen herhangi bir giyilebilir cihazla otomatik olarak sihirli bir şekilde senkronize edilir. Ayrıca kilit ekranlarında görünür.

Geri bildirim