/** * Ensure global ga Google Analytics queue has been initialized. * @type {Array} */ // var ga = ga || function(){}; function merge(stream1, stream2) { return stream1.merge(stream2); } function eventStream(eventName) { return jQuery(window).asEventStream(eventName); } // A map to player state codes from their human-readable sting equivalents. // https://developers.google.com/youtube/js_api_reference var PLAYER_CODES = { beginning: -1, completed: 0, play: 1, pause: 2, buffer: 3, video: 5 }; var DECAY = 5000; // milliseconds var EVENT_NAMES = ["focus", "click", "scroll", "mousemove", "touchstart", "touchend", "touchcancel", "touchleave", "touchmove"]; var URL = [location.protocol, '//', location.host, location.pathname].join(''); var playerEventStream = new Bacon.Bus(); var isPlaying = playerEventStream .scan({}, function(states, event) { var update = {}; var id = event.target.getIframe().id; update[id] = event.data; return _.assign(states, update); }) .map(_.values) .map(_.partialRight(_.contains, PLAYER_CODES.play)); var playerEvents = function(id) { var events = new Bacon.Bus(); var player = new YT.Player(id, { events: { 'onReady': onPlayerReady, 'onStateChange': onPlayerStateChange } }); return events; }; var isFocused = eventStream("focus").map(true) .merge(eventStream("blur").map(false)) .toProperty(true); var streams = _.map(EVENT_NAMES, eventStream); var interactionStream = _.reduce(streams, merge); var activityStream = interactionStream.merge(playerEventStream); var recentlyActive = activityStream .map(true) .flatMapLatest(function() { return Bacon.once(true).merge(Bacon.once(false).delay(DECAY)); }) .toProperty(false); var isActive = (recentlyActive.and(isFocused)).or(isPlaying); var secondsActive = Bacon.mergeAll(isActive.changes(), isActive.sample(1000)) .map(function(isActive) { return { isActive: isActive, timestamp: new Date().getTime() }; }) .slidingWindow(2,2) .filter(function(span) { return span[0].isActive; }) .map(function(span) { return span[1].timestamp - span[0].timestamp; }) .scan(0, function(x,y) { return x + y;}) .map(function(x) { return x / 1000; }) // milliseconds .map(Math.floor) .skipDuplicates(); var sendGaEvent = secondsActive .map(function(x) { if(!(x % 5)){ return x;} }); //////////////////////////////////////// // this is included from youtube.ga.js //////////////////////////////////////// var current_player; function onPlayerReady(event) { yt_vid_url = event.target.getVideoUrl(); yt_id = extractParamFromUri(yt_vid_url, 'v'); yt_video_title = yt_vid_url; // just in case we can't grab the title in time yt_api_url = "//apps.opensocietyfoundations.org/shield/yt/" + yt_id + "/" jQuery.getJSON(yt_api_url, function(data) { yt_video_title = data.items[0].snippet.title; }); // save player for reference later current_player = event.target; // Reattach events playerEventStream.plug(playerEvents("player")); if (typeof time_track != "undefined"){ clearTimeout(time_track); } timer_count = 0; played = 0; } // when player state changes var time_track; var played = 0; function onPlayerStateChange(event) { playerEventStream.push(event); if (event.data == YT.PlayerState.PLAYING && !YT.PlayerState.ENDED) { time_track = setInterval(gaTrackDuration, 1000 ); } else { clearTimeout(time_track); } // track plays if (event.data == 1 && played == 0) { gaTrackEvent('Video','Play', yt_video_title, true); gaTrackEvent('video play', yt_video_title, URL, true); opTrackVideo('videoPlay'); played = 1; } // track completes if (event.data == 0) { gaTrackEvent('Video','End', yt_video_title, true); gaTrackEvent('video end', yt_video_title, URL, true); opTrackVideo('videoEnd'); } } function opTrackVideo(aAct) { if (typeof window.optimizely != "undefined"){ window['optimizely'].push(["trackEvent", aAct]); } } var timer_count = 0; function gaTrackDuration() { timer_count++; if(timer_count % 10 == 0 && (timer_count <= 600)){ gaTrackEvent('Video', 'Duration', yt_video_title + ': ' + timer_count, true); gaTrackEvent('video duration', timer_count, URL, true); } } jQuery(function() { // Include You Tube API if we have a video on page if (jQuery('iframe[src*="youtube"]#player').length > 0){ var player = { loadAPI: function() { if (typeof(YT) == 'undefined' || typeof(YT.Player) == 'undefined') { window.onYouTubePlayerAPIReady = function() { player.loadPlayer(); }; jQuery.getScript('https://www.youtube.com/player_api'); } else { player.loadPlayer(); } }, loadPlayer: function() { // Create YT player and attach events playerEventStream.plug(playerEvents("player")); } }; player.loadAPI(); // stop player if switching videos jQuery('.vids a').click( function(event) { if (typeof(current_player) != 'undefined'){ current_player.stopVideo(); } }); } // Send ga event for event 5 seconds active on page sendGaEvent.onValue(function(x) { if(x && x <= 600){ gaTrackEvent('attention', x, URL, true); } }); });