Listener do śledzenie filmów vimeo

Strona główna » GTM cHTML » Listener do śledzenie filmów vimeo
Instrukcja użycia

→ Utwórz tag w GTM – Niestandardowy kod HTML
→ Wklej kod listenera do nasłuchiwania zdarzeń
→ Ustaw regułę – Wszystkie strony
→ Sprawdź w trybie podglądu GTM poprawność działania listenera

Kod listenera

<script>
;(function(){
  'use strict';

  var cfg = {
    dataLayerName: 'dataLayer',
    eventName: 'Film w Vimeo',
    progressThresholds: [0,10,25,50,75,90], // 100% wyłącznie na "complete"
    sendPause: true,
    endNearPercent: 98                      // pauzy przy >=98% ignorujemy (autopauza na finish)
  };

  var dl = window[cfg.dataLayerName] = window[cfg.dataLayerName] || [];

  // deduplikacja wg status|percent|time
  var sentCache = {};
  function emitOnce(key, evt){
    if (sentCache[key]) return;
    sentCache[key] = 1;
    dl.push(evt);
    var keys = Object.keys(sentCache);
    if (keys.length > 200) { for (var i=0;i<100;i++) delete sentCache[keys[i]]; }
  }

  function pushEvent(ctx){
    var evt = {
      event: cfg.eventName,
      video_provider: 'vimeo',
      video_title: (ctx.title||'').toLowerCase(),
      video_url: ctx.url || undefined,
      video_duration: typeof ctx.duration==='number' ? ctx.duration : undefined,
      video_status: ctx.status
    };
    if (typeof ctx.percent === 'number'){
      evt.video_progress = ctx.percent;
      evt.video_percent  = ctx.percent + '%';
    }
    if (typeof ctx.currentTime === 'number'){
      evt.video_current_time = Math.round(ctx.currentTime);
    }
    if (typeof ctx.seek_from === 'number') evt.video_seek_from = Math.round(ctx.seek_from);
    if (typeof ctx.seek_to   === 'number') evt.video_seek_to   = Math.round(ctx.seek_to);

    var k = [evt.video_status, evt.video_progress || '', evt.video_current_time || ''].join('|');
    emitOnce(k, evt);
  }

  function uniqSortThresholds(list){
    var map = {}, out = [];
    for (var i=0;i<list.length;i++){
      var v = Number(list[i]); if (isNaN(v)) continue;
      if (v>=0 && v<=100) map[v]=true;
    }
    for (var k in map) if (map.hasOwnProperty(k)) out.push(Number(k));
    out.sort(function(a,b){return a-b;});
    return out;
  }
  var THRESHOLDS = uniqSortThresholds(cfg.progressThresholds);

  function loadVimeoSdk(cb){
    if (window.Vimeo && window.Vimeo.Player) { cb(); return; }
    var s = document.createElement('script');
    s.src = 'https://player.vimeo.com/api/player.js';
    s.async = true;
    s.onload = function(){ cb(); };
    (document.head||document.documentElement).appendChild(s);
  }

  function isVimeoIframe(el){
    return el && el.tagName === 'IFRAME' && /player\.vimeo\.com\/video\/\d+/.test(el.src);
  }

  function trackIframe(iframe){
    if (iframe.__pp_vimeoTracked) return;
    iframe.__pp_vimeoTracked = true;

    var player = new window.Vimeo.Player(iframe);
    var meta = { title:'', duration:undefined, url:iframe.src };

    player.getVideoTitle().then(function(t){ meta.title = t || ''; }).catch(function(){});
    player.getDuration().then(function(d){ if (typeof d==='number') meta.duration = d; }).catch(function(){});
    player.getVideoUrl().then(function(u){ if (u) meta.url = u; }).catch(function(){
      player.getVideoId().then(function(id){ if (id) meta.url = 'https://vimeo.com/'+id; }).catch(function(){});
    });

    // stan sesji odtwarzania
    var fired = {};          // progi progress – raz na sesję; NIE resetujemy przy seek do początku
    var started = false;     // czy trwa sesja
    var endSeq = false;      // po complete blokujemy pause/seek
    var lastPercent = 0;     // z timeupdate
    var lastSeconds = 0;     // z timeupdate (użyte jako seek_from)

    player.on('play', function(){
      // nowa sesja startuje dopiero po "play", więc tu odblokuj i zresetuj liczniki
      if (!started){
        started = true;
        endSeq = false;
        fired = {'0': true};     // progu 0 nie raportujemy
        lastSeconds = 0;
        pushEvent({ status:'start', title:meta.title, duration:meta.duration, url:meta.url, percent:0, currentTime:0 });
      }
    });

    if (THRESHOLDS.length){
      player.on('timeupdate', function(ev){
        var p = Math.floor(((ev && ev.percent) ? ev.percent : 0) * 100);
        lastPercent = p;
        if (ev && typeof ev.seconds === 'number') lastSeconds = ev.seconds;

        // PROGRESS – każdy próg tylko raz na sesję (niezależnie od seeków wstecz/początek)
        for (var i=0;i<THRESHOLDS.length;i++){
          var th = THRESHOLDS[i];
          if (p >= th && !fired[String(th)]){
            fired[String(th)] = true;
            if (th === 0) continue; // nigdy nie wysyłamy 0
            pushEvent({
              status:'progress',
              title:meta.title,
              duration:meta.duration,
              url:meta.url,
              percent:th,
              currentTime:ev.seconds
            });
          }
        }
      });
    }

    // SEEK – nie wysyłaj po complete (Vimeo resetuje do 0 i emituje seeked)
    player.on('seeked', function(ev){
      if (endSeq) return; // po zakończeniu ignoruj wszelkie seeki
      var to = (ev && typeof ev.seconds === 'number') ? ev.seconds : undefined;
      if (!started && (to === 0 || to === undefined)) return; // reset playera → pomiń

      var from = (typeof lastSeconds === 'number') ? lastSeconds : undefined;
      var percent = (typeof meta.duration === 'number' && typeof to === 'number')
        ? Math.floor((to / meta.duration) * 100)
        : undefined;

      pushEvent({
        status:'seek',
        title:meta.title,
        duration:meta.duration,
        url:meta.url,
        percent: percent,
        currentTime: to,
        seek_from: from,
        seek_to: to
      });

      if (typeof to === 'number') lastSeconds = to;
    });

    // PAUSE – filtruj „autopauzę” przy finiszu
    if (cfg.sendPause){
      player.on('pause', function(ev){
        if (endSeq) return;
        if (lastPercent >= cfg.endNearPercent) return; // blisko końca → zwykle autopauza
        pushEvent({ status:'pause', title:meta.title, duration:meta.duration, url:meta.url, currentTime: ev && ev.seconds });
      });
    }

    // COMPLETE – jedyny moment na 100%
    player.on('ended', function(){
      if (endSeq) return;
      endSeq = true;
      lastPercent = 100;
      var d = (typeof meta.duration === 'number' ? meta.duration : undefined);
      pushEvent({ status:'complete', title:meta.title, duration:meta.duration, url:meta.url, percent:100, currentTime:d });
      // zamykamy sesję – kolejne progi/seek do zera nie policzą się
      started = false;
      // fired zostawiamy – i tak nowa sesja zresetuje je w on('play')
    });
  }

  function init(){
    var iframes = document.getElementsByTagName('iframe');
    var list = [];
    for (var i=0;i<iframes.length;i++) if (isVimeoIframe(iframes[i])) list.push(iframes[i]);
    if (!list.length) return;
    loadVimeoSdk(function(){ for (var j=0;j<list.length;j++) trackIframe(list[j]); });
  }

  if (document.readyState === 'loading'){
    document.addEventListener('DOMContentLoaded', init, false);
    window.addEventListener('load', init, false);
  } else { init(); }

  if (window.MutationObserver){
    try{
      var mo = new MutationObserver(function(muts){
        for (var i=0;i<muts.length;i++){
          var nodes = muts[i].addedNodes || [];
          for (var j=0;j<nodes.length;j++){
            var n = nodes[j];
            if (n && n.nodeType===1){
              if (isVimeoIframe(n)) { init(); continue; }
              var q = n.getElementsByTagName ? n.getElementsByTagName('iframe') : [];
              for (var k=0;k<q.length;k++){ if (isVimeoIframe(q[k])) { init(); break; } }
            }
          }
        }
      });
      mo.observe(document.documentElement, { childList:true, subtree:true });
    }catch(e){}
  }
})();
</script>

Dodaj komentarz

Napisz do mnie

Paweł Piekarski
Paweł Piekarski PhD, Analityk eCommerce Marketing oparty na danych

Dane kontaktowe

+48 725 473 745

poczta@pawelpiekarski.pl

linkedin.com/in/pawelpiekarskipl

    Wysyłając wiadomość, zgadzasz się na kontakt i przetwarzanie danych zgodnie z polityką prywatności.