In my last post I talked about Youtube tracking and the importance of measuring video interactions. This post I’m explaining another Video Player API that is pretty big in the SEO world. WISTIA! Just like YouTube there is a great Wistia Player API with tons of functionality and is actually a lot easier to setup and use than YouTubes!
Wistia actually already has Google Analytics integration which is great (and wish more players had that) but I want to customize and implement more advanced solutions so I turn off their tracking and use my own.
In this guide you will learn how to track the following different actions a user might take when watching your videos.
Developers feel free to jump straight to the code repo on github
The Wistia Player API’s allow you to interact with an embedded media players or react to someone doing something on them. Some obvious examples are:
Have a video start playing when the page loads
Show a pop up box when the video reaches the end
Show products related to video selected
Player API’s have been around for a while but haven’t seen that much use of them in the analytics field.
Add the player API script to the page, I usually add to the <head>. Then in the <body> add the <div> to the html of page for the video player to live. Then create the video object and turn off the standard GA tracking. There is lots of different things that you can control in that object. Check out a full list of the options here.
<head> <script type="text/javascript" src="//fast.wistia.net/static/E-v1.js"></script> </head> <body> <div id="wistia_v059er99tw" style="height: 387px; width: 640px;"></div> <script type="text/javascript"> video = Wistia.embed("v059er99tw", {googleAnalytics : false}); </script> </body>
Now in the code we want to wrap everything inside a callback that fires when the player has loaded.
video.hasData(function () { // // wistia tracking code goes here // });
Now that we have our video object we want to bind the other player events so we can listen for them. I am also going to use some different API methods to grab the name of the video. You can see Wistia’s full list here.
Here is a basic script that will track:
I will bind the play, pause, and end events to the video object.
Note: I created the video start event by checking the time when play is hit because i know that when time = 0 it’s the beginning. I also check if the time of the video is the same as the video duration because when a video finishes if someone hits play it will start over but that play event will be associated with the timestamp at the very end of the video.
video.bind('play', function() { if (video.time() == 0 || video.time() >= video.duration()) { ga('track','event','video','video start', video.name()) } else { ga('track','event','video','play', video.name()) }; }) wistiaEmbed.bind('pause',function(event) { ga('track','event','video','pause',video.name()); }); wistiaEmbed.bind("end", function () { ga('track','event','video','end',video.name()); });
Note: video.name() grabs the name of the video. Right now for this basic example I am saving that to the label field.
Ok well you’re in luck because there is a method time() that we can easily add to the following utility functions and I am also going to use Math.round() so I can get cleaner values.
There are different ways to see where people are pausing and playing:
function cleanTime () { return Math.round(video.time()) } function percentViewed () { return Math.round(cleanTime()/video.duration()*100)+"%" } /* OPTION 1: Using the percent of the video This code will track it as percentage */ video.bind('play', function() { if (video.time() == 0 || video.time() >= video.duration()) { ga('track', 'event', 'video: '+video.name(), 'started', 'started'); } else { ga('track', 'event', 'video: '+video.name(), 'play', cleanTime()); } }); video.bind('pause', function(event) { ga('track', 'event', 'video: '+video.name(), 'pause', cleanTime()); }); /* OPTION 2: Using the percent of the video This will track it as percentage (string) like 25% */ video.bind('play', function() { if (video.time() == 0 || video.time() >= video.duration()) { ga('track', 'event', 'video', 'started', video.name()); } else { ga('track', 'event', 'video', 'play', video.name(), percentViewed()); } }); video.bind('pause', function(event) { ga('track', 'event', 'video', 'pause', video.name(), percentViewed(); }); /* OPTION 3: Using the section of the video (like beginning, middle, end) This will track plays and pauses by section of the video it happens in */ function segementSection (time){ var segmentLength = wistiaEmbed.duration()/3; var beginning = segmentLength var middle =segmentLength * 2 var end = segmentLength * 3 if (time <= beginning) { return 'beginning' } else if (time <= middle && time > beginning) { return 'middle' }else if (time > middle) { return 'end' } }; video.bind('play', function() { if (video.time() == 0 || video.time() >= video.duration()) { ga('send', 'event', 'video', 'started', video.name()); } else { ga('send', 'event', 'video', 'play', video.name(), segementSection(cleanTime())); } }); video.bind('pause', function(event) { ga('send', 'event', 'video', 'pause', video.name(), segementSection(cleanTime())); });
Note: there is a percentWatched() method but I don’t like to use it because it doesn’t reset after a video is played. For example, if I watch a video and then I go back and watch it again the percentWatched() method stays at 1.
Tracking the time watched gets tricky and you really have to understand how the player collects the time. At first glance you might think that binding the secondschange player event to the video obj is the best bet.
video.bind("secondchange", function (s) { ga('send','event','video',s + 'time viewed',video.name(),s); }); //WRONG!
WRONG! We don’t want to send an event at every single second that will give us tons of noise that we don’t need. Also, after really understanding how it works I realized it’ll be inaccurate because the time that is returned is the time that is on the player. For example, if you watch 5 seconds then jump to the end and watch the last 5 seconds it’s going to count the total amount of seconds changed, not watched. We want to know seconds the viewer actually watched. For the last example we would want 10 to be returned because the viewer watched first 5 seconds then last 5 seconds. So we need to create a counter.
var alltime = 0; video.bind("secondchange", function (s) { alltime++ ga('send','event','video: ' + video.name(), 'viewed', alltime, 1); }); //WRONG!
STILL BAD because the second change function will increase alltime if you skip ahead or go backwards and we want our time viewed to be accurate so we are going to add some more to it to make sure it is only collecting when someone is playing. Sending hits for every second is too much data so I am going to split it into ~5 second buckets that we can group viewage into, this will change depending on the total duration in your video if it’s a short video you’ll want ~5 second time frames but if it’s a longer one you might want 1 min or 15 min buckets.
//CORRECT var alltime = 0; video.bind("secondchange", function(s) { var timeDif = s - oldTime; var timeflag = true; if (timeDif > 1 || timeDif < 0) { timeflag = false; }; if (alltime % 5 == 0 && alltime != 0) { ga('track','event','video: ' + video.name(), 'viewed', alltime, 1); }; if (timeflag) { alltime++; }; oldTime = s; });
Here is how I’ve altered it to show if people are skipping around. For example, you can now see if people skip to the end or watch a certain section over and over again.
video.bind("secondchange", function(s) { var timeDif = s - oldTime; var timeflag = true; if (timeDif > 1) { ga('track','event','video: ' + video.name(), 'skip ahead', timeDif, 1) timeflag = false; } else if (timeDif < 0) { ga('track','event','video: ' + video.name(), 'skip backwards', timeDif, 1) timeflag = false; }; if (alltime % 5 == 0 && alltime !=0) { ga('track','event','video: ' + video.name(), 'viewed', alltime, 1); }; if (timeflag) { alltime++; }; oldTime = s; });
To use this with GTM you need to set up the proper rules, variables, and events in GTM. You can see how I did it for youtube. It’s pretty much the same process and all the dataLayer object names are the same.
dtmFlag = false; debugFlag = false;
Will trigger normal Universal Google Analytics Tracking method.
dtmFlag = true; debugFlag = false;
Will send data to the dataLayer
dtmFlag = false OR true debugFlag = true;
Will log the data to the console.
video.hasData(function() { var dtmFlag = false; var debugFlag = true; var alltime = 0; var oldTime = 0; function trackEvents(category, action, label, value) { label = label || ''; value = value || 0; if (!debugFlag) { if (!dtmFlag) { ga('send', 'event', category, action, label, value, nonInteraction); } else if (dtmFlag) { dataLayer.push({ 'event': 'wistiaChange', 'eventCategory': category, 'eventAction': action, 'eventLabel': label, 'eventValue': value, }); }; } else { console.log( 'WISTIA DEBUG = ' + debugFlag + '\n name: ' + video.name() + '\n action: ' + action + '\n category: ' + category + '\n label: ' + label + '\n value:' + value + '\n time: ' + video.time() + '\n clean time: ' + cleanTime() + '\n segment: ' + segementSection(cleanTime()) ); }; }; function segementSection(time) { var segmentLength = video.duration() / 3; var beginning = segmentLength var middle = segmentLength * 2 var end = segmentLength * 3 if (time <= beginning) { return 'beginning' } else if (time <= middle && time > beginning) { return 'middle' } else if (time > middle) { return 'end' } }; function cleanTime() { return Math.round(video.time()) }; function percentViewed() { return Math.round(cleanTime() / video.duration() * 100) + "%" }; video.bind('play', function() { if (video.time() == 0 || video.time() >= video.duration()) { trackEvents('video: ' + video.name(), 'start', 'start', 1) } else { trackEvents('video: ' + video.name(), 'play', segementSection(cleanTime()), 1) }; }); video.bind('pause', function() { trackEvents('video: ' + video.name(), 'pause', segementSection(cleanTime()), 1) }); video.bind("end", function() { trackEvents('video: ' + video.name(), 'end', 'end', 3) }); video.bind("secondchange", function(s) { var timeDif = s - oldTime; var timeflag = true; if (timeDif > 1) { trackEvents('video: ' + video.name(), 'skip ahead', timeDif, 1) timeflag = false; } else if (timeDif < 0) { trackEvents('video: ' + video.name(), 'skip backwards', timeDif, 1) timeflag = false; }; if (alltime % 5 == 0 && alltime != 0) { trackEvents('video: ' + video.name(), 'viewed', alltime, 1) }; if (timeflag) { alltime++; }; oldTime = s; }); });
Thanks for reading! Do you have any questions or cool solutions for video tracking? If so, please leave them in the comments or let me know on Twitter @nicomiceli