Skip to main content
  1. Blog Post/

Tracking HTML5 Videos with GTM

4 min · 2050 words
Table of Contents

Almost all new browsers support the video playing using HTML5 including mobiles and tablets, so it may be way to embed videos that can be considerated when publishing videos on our pages. HTML5 has support for most of the videos format used on internet, even YouTube has been running an HTML5 version of their site for a long. So we're going to teach you how you can track those videos embed's using the built-in API and Event Listeners. We have setup a working page example at: http://stage.tags.ninja/html5.php Insert an HTML5 video player on a page, is as simple as adding this code:

<video controls="controls" width="320" height="240">
<source src="my_video_name.mp4" type="video/mp4">
</video>

So let's start tracking our videos step by step, what features is going to have this tracking:

    Tracking Flow

      Tags, Rules and Macros

      Tag

      You can scroll down to see a fully comented code for this tag. The tag will have just 1 rule that will check for gtm.dom event from Google Tag Manager, and for the <video> tags presence that will be read using a Macro.

      Rule

      Macro

      Thanks flys to Eliyahu Gusovsky from Analytics Ninja from who I learnt the markers piece of code.

      Source Code

      <script>
      // Let's wrap everything inside a function so variables are not defined as globals 
      (function() {
          // This is gonna our percent buckets ( 10%-90% ) 
          var divisor = 10;
          // We're going to save our players status on this object. 
          var videos_status = {};
          // This is the funcion that is gonna handle the event sent by the player listeners 
          function eventHandler(e) {
              switch (e.type) {
                  // This event type is sent everytime the player updated it's current time, 
                  // We're using for the % of the video played. 
              case 'timeupdate':
                  // Let's set the save the current player's video time in our status object 
                  videos_status[e.target.id].current = Math.round(e.target.currentTime);
                  // We just want to send the percent events once 
                  var pct = Math.floor(100 * videos_status[e.target.id].current / e.target.duration);
                  for (var j in videos_status[e.target.id]._progress_markers) {
                      if (pct >= j && j > videos_status[e.target.id].greatest_marker) {
                          videos_status[e.target.id].greatest_marker = j;
                      }
                  }
                  // current bucket hasn't been already sent to GA?, let's push it to GTM
                  if (videos_status[e.target.id].greatest_marker && !videos_status[e.target.id]._progress_markers[videos_status[e.target.id].greatest_marker]) {
                      videos_status[e.target.id]._progress_markers[videos_status[e.target.id].greatest_marker] = true;
                      dataLayer.push({
                          'event': 'gaEvent',
                          'gaEventCategory': 'HTML5 Video',
                          'gaEventAction': 'Progress %' + videos_status[e.target.id].greatest_marker,
                          // We are using sanitizing the current video src string, and getting just the video name part
                          'gaEventLabel': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1])
                      });
                  }
                  break;
                  // This event is fired when user's click on the play button
              case 'play':
                  dataLayer.push({
                      'event': 'gaEvent',
                      'gaEventCategory': 'HTML5 Video',
                      'gaEventAction': 'Play',
                      'gaEventLabel': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1])
                  });
                  break;
                  // This event is fied when user's click on the pause button
              case 'pause':
                  dataLayer.push({
                      'event': 'gaEvent',
                      'gaEventCategory': 'HTML5 Video',
                      'gaEventAction': 'Pause',
                      'gaEventLabel': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1]),
                      'gaEventValue': videos_status[e.target.id].current
                  });
                  break;
                  // If the user ends playing the video, an Finish video will be pushed ( This equals to % played = 100 )  
              case 'ended':
                  dataLayer.push({
                      'event': 'gaEvent',
                      'gaEventCategory': 'HTML5 Video',
                      'gaEventAction': 'Finished',
                      'gaEventLabel': decodeURIComponent(e.target.currentSrc.split('/')[e.target.currentSrc.split('/').length - 1])
                  });
                  break;
              default:
                  break;
              }
          }
          // We need to configure the listeners
          // Let's grab all the the "video" objects on the current page     
          var videos = document.getElementsByTagName('video');
          for (var i = 0; i < videos.length; i++) {
              // In order to have some id to reference in our status object, we are adding an id to the video objects
              // that don't have an id attribute. 
              var videoTagId;
              if (!videos[i].getAttribute('id')) {
                  // Generate a random alphanumeric string to use is as the id
                  videoTagId = 'html5_video_' + Math.random().toString(36).slice(2);
                  videos[i].setAttribute('id', videoTagId);
              }// Current video has alredy a id attribute, then let's use it <img draggable="false" class="emoji" alt="?" src="https://s.w.org/images/core/emoji/2/svg/1f642.svg">
              else {
                  videoTagId = videos[i].getAttribute('id');
              }
              // Video Status Object declaration  
              videos_status[videoTagId] = {};
              // We'll save the highest percent mark played by the user in the current video.
              videos_status[videoTagId].greatest_marker = 0;
              // Let's set the progress markers, so we can know afterwards which ones have been already sent.
              videos_status[videoTagId]._progress_markers = {};
              for (j = 0; j < 100; j++) {
                  videos_status[videoTagId].progress_point = divisor * Math.floor(j / divisor);
                  videos_status[videoTagId]._progress_markers[videos_status[videoTagId].progress_point] = false;
              }
              // On page DOM, all players currentTime is 0 
              videos_status[videoTagId].current = 0;
              // Now we're setting the event listeners. 
              videos[i].addEventListener("play", eventHandler, false);
              videos[i].addEventListener("pause", eventHandler, false);
              videos[i].addEventListener("ended", eventHandler, false);
              videos[i].addEventListener("timeupdate", eventHandler, false);
          }
      })();
      </script>