Mobil Web Video Oynatma

François Beaufort
François Beaufort

Web'de en iyi mobil medya deneyimini nasıl oluşturursunuz? Kolay! Bu durum, 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 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 özellikleri sunan 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.

Daha sonra ele alacağımız video kontrolleri arasında oynatma/duraklatma düğmesi, tam ekran düğmesi, geri ve ileri sarma düğmeleri ve mevcut saat, 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 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

Devam edip kullanıcıların bazı içerikleri kolayca atlayabilmesi için "Geri sarma" ve "İleri sarma" 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 özel akışınızı oluşturmak için bunları birleştirmeniz yeterlidir.

Otomatik tam ekranı önleme

iOS'te, medya oynatmaya başlandığında video öğeleri otomatik olarak tam ekran moduna geçer. Mobil tarayıcılarda medya deneyimimizi mümkün olduğunca özelleştirmeye ve kontrol etmeye çalışıyoruz. Bu nedenle, video öğesinin playsinline özelliğini iPhone'da satır içi oynatmaya zorlamak ve oynatma başladığında tam ekran moduna girmemek için 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üğme tıklandığında tam ekranı açma/kapatma

Otomatik tam ekranı önlediğimize göre, Tam Ekran API ile videonun tam ekran modunu kendimiz yönetmemiz gerekiyor. 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ö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ünü değiştirirken tam ekranı açma/kapatma

Kullanıcı cihazı yatay moda döndürdüğünde, sürükleyici bir deneyim oluşturmak için akıllıca davranıp 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 içeren Screen Orientation API'ye ihtiyacımız var. Bu nedenle, bu ilk aşamalı 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 olarak kilitlemek screen.orientation.lock('landscape') aramak kadar kolaydır. Ancak bunu yalnızca cihaz matchMedia('(orientation: portrait)') ile dikey moddayken ve matchMedia('(max-device-width: 768px)') ile tek elle tutulabilirken yapmalıyız. Aksi takdirde tablet kullanıcıları için iyi bir deneyim olmaz.

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ünün değiştirilmesi durumunda 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 uzayda konumunu ve hareketini ölçen donanımdan bilgi sağlar: yönelimi için jiroskop ve dijital pusula, hızı için ivme ölçer. Cihazın yönünü değiştirdiğini 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ça 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 duraklatılmış bir videonun devam ettirilmesine olanak tanıyan tarayıcı dışında kontroller sunduğundan, bu davranışı yalnızca kullanıcının arka planda oynatmaya izin verildiği durumlarda 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'yi kullanırsanız ücretsiz olarak daha ayrıntılı analizler yapabilirsiniz. Bu API, gözlemlenen bir öğenin tarayıcı görüntü alanına ne zaman girdiğini veya görüntü alanından ne zaman çıktığını size bildirir.

Sayfadaki videonun görünürlüğüne bağlı olarak bir sessize alma düğmesi gösterelim/gizleyelim. Video oynatılıyor ancak şu anda görünmüyorsa sayfanın sağ alt köşesinde, kullanıcıya video sesi üzerinde kontrol imkanı sunan küçük bir sessize alma düğmesi gösterilir. 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);
});

Aynı anda yalnızca bir video oynatabilirsiniz.

Bir sayfada birden fazla video varsa kullanıcının aynı anda birden fazla ses parçası duymak zorunda kalmaması için yalnızca birini oynatıp diğer videoları otomatik olarak duraklatmanızı öneririz.

// 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 sarma veya parça değiştirme gibi medyayla ilgili etkinlikleri yönetmenize olanak tanır. Bu uygulamanın bir örneğini burada bulabilirsiniz.

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 bildirimlerinde her zaman alakalı bilgiler göstermek için bu sayfayı güncellemeniz gerekir.

Web uygulamanızda şarkı listesi varsa kullanıcının "Önceki Parça" ve "Sonraki Parça" simgelerini kullanarak doğrudan medya bildiriminde şarkı 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şlem 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 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 bildirimde her zaman gösterilir ve ilgili etkinlikler tarayıcı tarafından otomatik olarak yönetilir. 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 tüm giyilebilir cihazlarla otomatik olarak senkronize edilir. Ayrıca kilit ekranlarında da gösterilir.

Geri bildirim