Tracking Youtube Embed Starts, Plays, Pauses, Ends, & Errors with GA & GTM

You think it's good?

Tracking Youtube Embed Starts, Plays, Pauses, Ends, & Errors with GA & GTM

Videos are now the standard for businesses who want to convey their messages to customers in a short concise way. However, they are expensive (well, at least the good ones are) and if you’re pouring money & time into a video for your site it’s important to see how people are using it or when it goes down. This post is going to go over how to approach the YouTube JavaScript API. There are lots of other tutorials and code snippets that can help you track youtube video but I am still seeing sites that aren’t using it AND I believe that it is important to understand how everything works– so this is my attempt to get you up to speed even if you’re not heavily technical. Additionally, I believe my solution is a little different then the other ways video’s have been tracked plus I go over some ways to calculate new metrics and track errors with the video player.

To track video with Google Analytics you need to connect event tracking to the actions that are taken on the player. Most of the mainstream video players allow you to do this with the player API. I will be using the Player API for iframe Embeds and showing how to hook it up with normal GA code and through GTM

Links to the 2 player API’s:

YouTube Player API Reference for iframe Embeds

YouTube JavaScript Player API Reference

Jump to the GTM implementation you can click here but skim through the JS implementation because that is where I explain the functions.

Getting Started with Code

First we need to create a div that will act as the container for us to place the YouTube iFrame in.

<div id='player'></div>

Then with in an HTML script tag we add JavaScript and load the iframe player API code asynchronously.

var tag = document.createElement('script');

tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

Create a new player object with the height (video.h), width(video.w), and VideoID that you want played and declare the events you are going to use. There are other events that are apart of the YouTube API but for this example we are only going to add onStateChange and onError. There are other player events that you can listen for like Player Quality Change and Volume Change, you can read about those here.

var player;
var video = 'YOUR_VIDEO_ID'
    video.h = '390' //video iframe height
    video.w = '640' //video iframe width

function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
        height: video.h,
width: video.w,
videoId: video,
        events: {
            'onStateChange': onPlayerStateChange,
            'onError': onPlayerError
        }
     });
};

About Youtube Player Events

onStateChange

onStateChange tracks when the player switches from different player states like play, pause and end. This is the main method that we will be using for our video tracking. The value that the API passes to your event listener function will specify an integer that corresponds to the new player state.

  • -1 (unstarted)
  • 0 (ended)
  • 1 (playing)
  • 2 (paused)
  • 3 (buffering)
  • 5 (video cued)

You can specify the numeric values above or you can use one of the following namespaced variables:

  • YT.PlayerState.ENDED
  • YT.PlayerState.PLAYING
  • YT.PlayerState.PAUSED
  • YT.PlayerState.BUFFERING
  • YT.PlayerState.CUED

In my examples below I will be using the namespace values.

onError

onError event triggers when their is an error with the youtube iframe player like if the video isn’t loading. This is great for QA and site redesigns knowing which videos are broken on your site.
When the error occurs the API passes a numeric value that represents the error. Below are a list of the errors:

  • 2 – The request contains an invalid parameter value. For example, the videoID is videoId: ‘M7lc1UVf-VE’ that we declared in step 3 is wrong. Make sure it has 11 total characters and none of them are invalid.
  • 100 – The video requested was not found (removed or marked private)
  • 101 – The owner of the requested video does not allow it to be played in embedded players.
  • 150 – This error is the same as 101.

Google Analytics Tracking Functions

This post is going over the basics of video tracking so the code is going to be… well, basic. I am planning to write posts in the future on more complex functions that I’ve created to analyze the video viewing behavior in different ways but I need to clean them up to make sense.

Pause, Play, and End Functions

Here is the first function for onPlayerStateChange I want to call out 2 specific things with the youtube API that I accounted for/modded.

  1. When the player starts playing instead of tracking it as ‘play’ I track it as ‘started’ in order to be able to calculate the completion rate.
  2. In the YT.PlayerState.PAUSED: case I check the time of the pause because I noticed that when the video reaches the end it actually triggers a pause event right before the end event. So if the current time – the duration = 0 then don’t track that pause.
  3. The event label is capturing the video AND the time stamp in the Play and Pause cases and is separated by a pipe ‘ | ‘. This will let us split on the pipe and create 2 data values and then perform some cool calculations and really get a lot more out of this solution.
function onPlayerStateChange(event) {
    switch (event.data) {
        case YT.PlayerState.PLAYING:
            if (cleanTime() == 0) {
                //console.log('started ' + cleanTime());
                ga('send', 'event', 'video', 'started', video);
            } else {
                //console.log('playing ' + cleanTime())
                ga('send', 'event', 'video', 'played', 'v: ' + video + ' | t: ' + cleanTime());
            };
            break;
        case YT.PlayerState.PAUSED:
            if (player.getDuration() - player.getCurrentTime() != 0) {
                //console.log('paused' + ' @ ' + cleanTime());
                ga('send', 'event', 'video', 'paused', 'v: ' + video + ' | t: ' + cleanTime());
            };
            break;
        case YT.PlayerState.ENDED:
            //console.log('ended ');
            ga('send', 'event', 'video', 'ended', video);
            break;
    };
};
//utility
function cleanTime(){
    return Math.round(player.getCurrentTime())
};

Error Tracking

function onPlayerError (event) {
    switch(event.data) {
        case 2:
            //console.log('' + video.id)
            ga('send', 'event', 'video', 'invalid id',video);
            break;
        case 100:
            ga('send', 'event', 'video', 'not found',video);
            break;
        case 101 || 150:
            ga('send', 'event', 'video', 'not allowed',video);
            break;
        };
};

And now you’re tracking your video events! Continue reading if you want to see how it works with DTM.

Now Using Google Tag Manager

If you are using Google Tag Manager you would want to set up 2 tags and 3 macros.

Macros

Create 3 new dataLayer macros, here is an example for eventCategory

2014-12-21_01-21-55

Create one for eventAction and eventLabel the same way.

Tag 1: HTML tag

Create a new HTML Tag with the following rule:

2014-12-21_01-18-54

Copy the following code to the HTML container. Notice that the code pushes all the video events to the dataLayer and then we grab them from the dataLayer and execute the GA tracking.

<script>
//set video values
var video = 'YOUR_VIDEO_ID_HERE'
video.h = '390'
video.w = '640'
var player;
var tag = document.createElement('script');
tag.src = 'http://www.youtube.com/player_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

function onYouTubePlayerAPIReady() {
    player = new YT.Player('player', {
        height: video.h,
        width: video.w,
        videoId: video,
        events: {
            'onStateChange': onPlayerStateChange,
            'onError': onPlayerError
        }
    });
}

function onPlayerStateChange(event) {
    switch (event.data) {
        case YT.PlayerState.PLAYING:
            if (cleanTime() == 0) {
                dataLayer.push({
                    'event': 'youtubeChange',
                    'eventCategory': 'video',
                    'eventAction': 'started',
                    'eventLabel': video
                });
            } else {
                dataLayer.push({
                    'event': 'youtubeChange',
                    'eventCategory': 'video',
                    'eventAction': 'played',
                    'eventLabel': 'v: ' + video + ' | t: ' + cleanTime()
                });
            };
            break;
        case YT.PlayerState.PAUSED:
            if (player.getDuration() - player.getCurrentTime() != 0) {
                dataLayer.push({
                    'event': 'youtubeChange',
                    'eventCategory': 'video',
                    'eventAction': 'paused',
                    'eventLabel': 'v: ' + video + ' | t: ' + cleanTime()
                });
            };
            break;
        case YT.PlayerState.ENDED:
            dataLayer.push({
                'event': 'youtubeChange',
                'eventCategory': 'video',
                'eventAction': 'ended',
                'eventLabel': video
            });
            break;
    };
};

function onPlayerError(event) {
    switch (event.data) {
        case 2:
            dataLayer.push({
                'event': 'youtubeChange',
                'eventCategory': 'video',
                'eventAction': 'invalid id',
                'eventLabel': video
            })
            break;
        case 100:
            dataLayer.push({
                'event': 'youtubeChange',
                'eventCategory': 'video',
                'eventAction': 'not found',
                'eventLabel': video
            })
            break;
        case 101 || 150:
            dataLayer.push({
                'event': 'youtubeChange',
                'eventCategory': 'video',
                'eventAction': 'not allowed',
                'eventLabel': video
            })
            break;
    };
};

function cleanTime() {
    return Math.round(player.getCurrentTime())
};
</script>

Tag 2: Universal Analytics

Create a new Universal Analytics Tag with the following rule:

2015-02-16_21-49-53

Set the track type to event and set the macros that we just made to the values of Event Category, Action, and Label.

2014-12-21_01-35-14

And, boom. You’re tracking your video!

New data that you can Analyze

Once you have that tracking and all the data coming in you can use the Google Analytics Add-on extension to get the data in google sheets and do some cool stuff.

Here are some things you can learn:

  • Total Starts of the video
  • Total Ends of the video
  • Total # of Error’s broken down by type
  • Completion Rate *
    • Video ends event / video starts event.
  • Most frequent times paused and played *
    • For the Most Frequent ones you are going to need to split up the event label which is why I’ve added the | (pipe) in the middle of the label “v: ” + video + ” | t: ” + cleanTime()
      You can split this up in Google Sheets with =split(A3,”|”) if cell A3 contained the video labels. Or if you an excel person you can split them with ‘text to columns’ under the Data Ribbon.

* Note: The last 2 can’t be analyzed in GA right now because it requires making calculations or mods on the data.

I live in the Google Analytics Spreadsheets Add-on and highly suggest you check it out if you don’t have it installed. It’s great for pulling in data and doing your own calculations or customizing the data and dimension names to be more friendly.

What other ways are you using the Youtube Player API, are you tracking other event? Please share in the comments I’d love to other creative uses! And of course let me know if you have any questions or feedback.

Hi, thanks for reading! If you liked this post and saw value in it please consider sharing it. There are share buttons at the top.
Loading Facebook Comments ...