Skip to content

Instantly share code, notes, and snippets.

@nathanwoulfe
Last active August 13, 2017 23:19
Show Gist options
  • Save nathanwoulfe/6d57b35c8e66beb237b649b99284c25f to your computer and use it in GitHub Desktop.
Save nathanwoulfe/6d57b35c8e66beb237b649b99284c25f to your computer and use it in GitHub Desktop.
An example AngularJs directive using the YouTube iFrame API, pushing events into the GTM datalayer
// functional demo at http://www.usc.edu.au/open-day#sessions
(function() {
'use strict'
function hubVideo() {
var video = {
restrict: 'E',
replace: true,
scope: {
youtube: '=',
summary: '=',
timeline: '=',
title: '='
},
template: [
'<div class="video-with-timeline">',
' <div class="embed-responsive embed-responsive-16by9">',
' <div id="player"></div>',
' </div>',
' <h4 ng-if="title && !timeline" ng-bind="title"></h4>',
' <p ng-if="summary && !timeline" ng-bind="summary"></p>',
' <ul class="video-timeline" ng-if="timeline">',
' <li ng-repeat="t in timeline track by $index" ng-class="{\'active\' : $index === activeSlide - 1}" ng-click="scrub(t.time)" title="{{ t.label }}"><span>{{ $index + 1 }}</span></li>',
' </ul> {{ elapsed }}',
'</div>'
].join(''),
link: link
};
function link(scope) {
// ensure dataLayer exists - youtube API is loaded elsewhere and will be available when this code executes
if (!dataLayer) {
dataLayer = [];
}
// var
var time, player, elapsed = 0;
scope.activeSlide = 0;
// track the elapsed time and update the active chapter li element
// the timeline data is provided to the directive in scope.timeline
function startWatch() {
time = setInterval(function() {
elapsed = player.getCurrentTime();
scope.activeSlide = scope.timeline.map(function(v, i) {
var min = i === 0 ? scope.timeline[0].time : v.time;
return elapsed >= min;
}).lastIndexOf(true) + 1;
scope.$apply();
}, 100);
}
// GTM struggles to track dynamic embeds, so let's fire off our own datalayer events
function doGtmStuff(e) {
e['data'] === YT.PlayerState.PLAYING && setTimeout(onPlayerPercent, 1000, e['target']);
var videoData = e.target['getVideoData'](),
label = videoData.video_id + ':' + videoData.title;
// report play button clicks
if (e['data'] === YT.PlayerState.PLAYING && YT.gtmLastAction === 'p') {
dataLayer.push({
event: 'youtube',
action: 'play',
label: label
});
}
YT.gtmLastAction = '';
// report pause button clicks
if (e['data'] === YT.PlayerState.PAUSED) {
dataLayer.push({
event: 'youtube',
action: 'pause',
label: label
});
YT.gtmLastAction = 'p';
}
}
function stopWatch() {
clearTimeout(time);
}
// when something goes wrong, let GTM know about it
function onError(e) {
dataLayer.push({
event: 'error',
action: 'GTM',
label: 'youtube:' + e['target']['src'] + '-' + e['data']
});
}
// report the % played if it matches 0%, 25%, 50%, 75% or completed
// Change the % to more increments 5% (0%, 5%, 10% etc) as the video auto video is long
function onPlayerPercent(e) {
if (e['getPlayerState']() === YT.PlayerState.PLAYING) {
var t = e['getDuration']() - e['getCurrentTime']() <= 1.5 ? 1 : (Math.floor(e['getCurrentTime']() / e['getDuration']() * 20) / 20).toFixed(2);
if (!e['lastP'] || t > e['lastP']) {
var videoData = e['getVideoData'](),
label = videoData.video_id + ':' + videoData.title;
e['lastP'] = t;
dataLayer.push({
event: 'youtube',
action: Math.round(t * 100) + '%',
label: label
});
}
e['lastP'] !== 1 && setTimeout(onPlayerPercent, 1000, e);
}
}
// when everything is loaded, start playing the video
function onPlayerReady(event) {
event.target.playVideo();
YT.gtmLastAction = 'p';
}
// if the video has a timeline, we need to observe the elapsed time to set the correct chapter as active - this happens in startWatch
function onStateChange(e) {
if (scope.timeline) {
if (e.data == YT.PlayerState.PLAYING) {
startWatch();
} else {
stopWatch();
}
}
doGtmStuff(e);
}
// clicking a timeline chapter sets the current play time
scope.scrub = function(time) {
player.seekTo(time);
}
// create the player object with sane default values
// also binds events - we track inside the callback functions
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: scope.youtube,
playerVars: {
'autoplay': 1,
'rel': 0,
'modestbranding': 1,
'showinfo': 0
},
events: {
'onReady': onPlayerReady,
'onStateChange': onStateChange,
'onError': onError
}
});
}
return video;
}
angular.module('app').directive('hubVideo', [hubVideo]);
}());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment