Kawałek Kodu

Jeśli chodzi o wpisy na tym blogu, to jestem w 100% pewien, że czytacie je od deski do deski. Ale są tacy (nie Wy!), którzy oglądając filmiki na różnych stronach często przewijają je na koniec, aby obejrzeć tylko to co ich interesuje. Czyli oszukiwanko.

Niedawno przy projekcie, przy którym współpracowałem zaszła potrzeba wykrywania zaawansowania obejrzenia prezentacji wideo. W zależności od tego czy został przekroczony pewien próg procentowy, użytkownik dostawał punkt lub nie. Pokażę Ci dziś jak wykryć taki postęp zakładając, że wideo można przewijać w przód lub w tył w trakcie oglądania. Oczywiście obejrzenie tego samego fragmentu nie będzie zaliczone.

Ach, te procenty!

Jak się mają nasze założenia do całości filmu? Najprościej będzie to omówić na przykładzie. Załóżmy, że nasz film ma 100 sekund i chcemy wykryć 10% obejrzenia. W tym przypadku 10% będzie stanowić 10 sekund filmu. Ale przy powyższych założeniach nie musi to być ciągły fragment. Może to być 10 różnych fragmentów po 1 sekundzie, czy też 2 fragmenty po 5 sekund, albo fragment 3 sekundowy oraz 7 sekundowy. Może też być tak, że obejrzano fragment 5 sekundowy, potem przewinięto do 2 sekundy tego fragmentu i obejrzano znów 5 sekund. W tym przypadku postęp będzie tylko 7% (7 sekund), bo objerzano 5 sekund i 2 sekundy nowego fragmentu, a my nie zaliczamy powtórnego obejrzenia.

Na poniższym rysunku jest przykład kiedy użytkownik najpierw obejrzał fragment 15% (65%-80%), następnie 10% (20%-30%), a potem przewinął do fragmentu 60%-70% i obejrzał znów 10%, przy czym w ostatnim fragmencie 5% zostało obejrzane wcześniej. Sumarycznie obejrzał więc 15%+10%+5%=30%.

Jak wykryć kto idzie na łatwiznę?

O ile założenia wydają się proste, to wyjaśnie mogło spowodować, że problem wydaje się bardziej skomplikowany niż wydawał się na początku. Zaraz Cię przekonam, że wcale tak nie jest. Aby udało nam się zrealizować cel będziemy potrzebować plik wideo oraz API do niego, które obsługuje przynajmniej zdarzenie ontimeupdate oraz onended. I jeszcze jedna ważna rzecz. Zdarzenie ontimeupdate powinno nas informować o postępie jak najczęściej, szczególnie kiedy filmiki są krótkie, najlepiej co 1%.

W zależności od użytego API możemy mieć informację o postępie w postaci procentowej (API JS dla Vimeo) lub w postaci sekundowej (standardowe Audio/Video API), albo jeszcze gorzej jak w przypadku API YT, ale o tym dalej. W przypadku tej drugiej musimy przeliczyć aktualny czas na procenty (znając długość całego wideo możemy to zrobić).

Mając teraz możliwość zapisania każdego procentu postępu, możemy zapisać maksymalnie 100 wartości. A co będzie jeśli ktoś obejrzy założone 60%? Tablica będzie mieć tylko 60 elementów (w tych indeksach, które odnotowaliśmy dzięki ontimeupdate). Wystarczy więc sprawdzić tylko długość tablicy pod względem zadanego minimalnego postępu i gotowe! Gdyby ktoś chciał oszukiwać i po kilku początkowych sekundach przewinął na koniec, to pomimo odnotowania postępu 100% nasza tablica będzie krótsza. Może wyglądać tak: 1, 2, 3, 4, 5, 98, 99, 100. Czyli będzie zawierać 8 elementów co oznacza 8% całości filmu, bo przecież nie interesują nas konkretne wartości w tablicy, ale ilość tych wartości.

Najpierw zajmijmy się API Vimeo.

<iframe src="https://player.vimeo.com/video/5295530?byline=0" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>

+

window.addEventListener('load', function() {
/* tablica postępu */
  let progress = [];

  const iframe = document.querySelector('iframe');

/* inicjalizujemy API Vimeo */
  const player = new Vimeo.Player(iframe);

/* przy postępie zapisujemy do tablicy procent
   pod warunkiem, że wcześniej takiego postępu
   nie zapisywaliśmy
*/
  player.on('timeupdate', function(e) {
    const currentPercent = Math.ceil(e.percent * 100);
    if (progress.indexOf(currentPercent) === -1) {
      progress.push(currentPercent);
    }
  });
/* kiedy wideo się skończy
   sprawdzamy długość tablicy
*/
  player.on('ended', function() {
    if (progress.length >= 60) {
      alert('Obejrzałeś minimum 60% filmu!');
    } else {
      alert('Nie obejrzałeś minimum 60% filmu!');
    }
  });
});

W przypadku API Vimeo dostajemy informację o postępie procentowym, we właściwości percent zdarzenia ontimeupdate.

Mamy jeszcze API YT. Tu jest problem, bo nie mamy możliwości odczytywania postępu. Istnieje natomiast zdarzenie onStateChange, które informuje nas między innymi o fakcie odtwarzania oraz zakończenia całości. Ze względu na te ograniczenia będziemy musieli wykorzystać funkcję setInterval, która będzie startować za każdym razem kiedy dostaniemy informację o odtwarzaniu (czyli po wciśnięciu play na starcie lub play po pauzie). W niej wykorzystamy natomiast metody getDuration oraz getCurrentTime, które zwracają odpowiednio czas całości oraz aktualny moment odtwarzania. Przy podawaniu źródła dla IFRAME należy pamiętać, aby dodać parametr enablejsapi, ustawiony na 1.

<iframe width="560" height="315" src="https://www.youtube.com/embed/SAXU--BJWYY?enablejsapi=1" frameborder="0" allowfullscreen></iframe>

+

/* tablica postępu */
let progress = [];
const iframe = document.querySelector("iframe");

let player;
let timer;

/* funkcja wywoływana przez YT API,
   kiedy to jest dostępne na stronie
   i możemy zacząć z niego korzystać
*/
function onYouTubeIframeAPIReady() {
/* inicjujemy player podając
   za argument element IFRAME,
   interesuje nas tylko jedno
   zdarzenie
*/
  player = new YT.Player(iframe, {
    events: {
      onStateChange: onPlayerStateChange
    }
  });
}

/* handler osługi zdarzenia
   onStateChange,
   tu podobnie jak wcześniej
   zapisujemy postęp wideo,
   ale obliczamy go znając aktualny
   postęp i całkowity czas trwania
*/
function onPlayerStateChange(event) {
  const length = player.getDuration();
/* czy aktualny przeszliśmy
   w stan odtwarzania?
*/
  if (event.data == YT.PlayerState.PLAYING && length > 0) {
/* jeśli wcześniej inicjowaliśmy
   timer to go czyścimy
*/
    if (timer) {
      clearInterval(timer);
    }
/* ustawiamy nowy timer co 10 ms,
   a w nim obliczamy postęp
   na podstawie aktualnego czasu
   i długości wideo
*/
    timer = setInterval(function() {
      const currentTime = player.getCurrentTime();
      const currentPercent = Math.ceil((currentTime / length) * 100);
      if (progress.indexOf(currentPercent) === -1) {
        progress.push(currentPercent);
      }
    }, 10);
  } else if (event.data == YT.PlayerState.ENDED) {
/* jeśli wideo zakończyło się,
   czyścimy timer
*/
    clearInterval(timer);
    if (progress.length >= 60) {
      alert("Obejrzałeś minimum 60% filmu!");
    } else {
      alert("Nie obejrzałeś minimum 60% filmu!");
    }
  }
}

Jeśli chodzi o natywne Video API, to podobnie jak w przypadku YT API, musimy radzić sobie obliczając postęp na własną rękę, choć tu jest łatwiej, bo nie musimy używać setInterval.

<video src="file_example_MP4_480_1_5MG.mp4" controls/>

+

window.addEventListener('load', function() {
/* tablica postępu */
  let progress = [];

/* pobieramy nasz obiekt Video */
  const video = document.querySelector('video');

/* tu podobnie jak w YT API
   zapisujemy postęp wideo
*/
  video.addEventListener('timeupdate', function(e) {
    const length = video.duration;
    const currentTime = video.currentTime;

    const currentPercent = Math.ceil(currentTime / length * 100);
    if (progress.indexOf(currentPercent) === -1) {
      progress.push(currentPercent);
    }
  });

  video.addEventListener('ended', function() {
    if (progress.length >= 60) {
      alert('Obejrzałeś minimum 60% filmu!');
    } else {
      alert('Nie obejrzałeś minimum 60% filmu!');
    }
  });
});

Ostatni przykład może również posłużyć do badania postępu odsłuchania pliku audio, ponieważ Audio API w HTML ma wiele wspólnych metod.

To już koniec wpisu. Nie ma co przewijać na dół, chyba, że zainteresował Cię na tyle, aby przewinąć do góry.

 

Przydatne linki:
Vimeo JS API
YouTube API dla Iframe 
Obsługa audio na stronie www