Created
September 9, 2012 15:49
-
-
Save Daphonia/3685187 to your computer and use it in GitHub Desktop.
Google Analytics Tracker Plugin for BC HTML5 Players
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Brightcove HTML5 Player Google Analytics Tracker 1.0.0 Alpha (02 Sep 2012) | |
| * | |
| * REFERENCES: | |
| * Website: | |
| * Source: | |
| * | |
| * AUTHORS: | |
| * Darius Oleskevicius <doleskevicius@brightcove.com> | |
| * | |
| * Permission is hereby granted, free of charge, to any person obtaining a | |
| * copy of this software and associated documentation files (the “Software”), | |
| * to deal in the Software without restriction, including without limitation | |
| * the rights to use, copy, modify, alter, merge, publish, distribute, | |
| * sublicense, and/or sell copies of the Software, and to permit persons to | |
| * whom the Software is furnished to do so, subject to the following conditions: | |
| * | |
| * 1. The permission granted herein does not extend to commercial use of | |
| * the Software by entities primarily engaged in providing online video and | |
| * related services. | |
| * | |
| * 2. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT ANY WARRANTY OF ANY KIND, | |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, SUITABILITY, TITLE, | |
| * NONINFRINGEMENT, OR THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT | |
| * SHALL THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY | |
| * CLAIM, DAMAGES OR OTHER LIABILITY WHATSOEVER, WHETHER IN AN ACTION OF | |
| * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH | |
| * THE SOFTWARE OR THE USE, INABILITY TO USE, OR OTHER DEALINGS IN THE SOFTWARE. | |
| * | |
| * 3. NONE OF THE AUTHORS, CONTRIBUTORS, NOR BRIGHTCOVE SHALL BE RESPONSIBLE | |
| * IN ANY MANNER FOR USE OF THE SOFTWARE. THE SOFTWARE IS PROVIDED FOR YOUR | |
| * CONVENIENCE AND ANY USE IS SOLELY AT YOUR OWN RISK. NO MAINTENANCE AND/OR | |
| * SUPPORT OF ANY KIND IS PROVIDED FOR THE SOFTWARE. | |
| */ | |
| (function(){ | |
| BCHTML5GATracker = {}; | |
| /* | |
| GA account ID can be hardcoded here, or passed in via a parameter on the plugin, | |
| in the embed code for the player: | |
| Plugin Parameter: http://mydomain.com/BCHTML5GATracker.js?accountNumber=UA-123456-0 | |
| */ | |
| BCHTML5GATracker.GAACCOUNT_ID = ""; | |
| BCHTML5GATracker.VERSION = "1.0.0"; | |
| BCHTML5GATracker.REFERRER_URL = "Referrer URL: "; | |
| BCHTML5GATracker.PLAYER_NAME = "Brightcove Player"; | |
| BCHTML5GATracker.PLAYER_LOAD = "Player Load"; | |
| BCHTML5GATracker.VIDEO_LOAD = "Video Load"; | |
| BCHTML5GATracker.MEDIA_BEGIN = "Media Begin"; | |
| BCHTML5GATracker.MEDIA_PAUSE = "Media Pause"; | |
| BCHTML5GATracker.MEDIA_RESUME = "Media Resume"; | |
| BCHTML5GATracker.MEDIA_COMPLETE = "Media Complete"; | |
| BCHTML5GATracker.MEDIA_ABANDONED = "Media Abandoned"; | |
| BCHTML5GATracker.SEEK_FORWARD = "Seeked Forward"; | |
| BCHTML5GATracker.SEEK_BACKWARD = "Seeked Backward"; | |
| BCHTML5GATracker.MILESTONE_25 = "25% Milestone Passed"; | |
| BCHTML5GATracker.MILESTONE_50 = "50% Milestone Passed"; | |
| BCHTML5GATracker.MILESTONE_75 = "75% Milestone Passed"; | |
| var _player, | |
| _experienceModule, | |
| _videoPlayerModule, | |
| _cuePointsModule, | |
| _currentVideo, | |
| _customVideoID, | |
| _currentPosition, | |
| _previousTimestamp, | |
| _timeWatched, | |
| _storedTimeWatched = {}; //stored in milliseconds | |
| _storedTimeWatched.data = {}; | |
| //flags for tracking | |
| var _mediaBegin = false, | |
| _mediaComplete = true, | |
| _mediaPaused = false, | |
| _mediaSeeking = false, | |
| _trackSeekForward = false, | |
| _trackSeekBackward = false; | |
| //GA | |
| var _gaq = _gaq || []; | |
| BCHTML5GATracker.initialize = function(){ | |
| //get reference to player itself | |
| _player = brightcove.api.getExperience(); | |
| //get references to player modules | |
| _videoPlayerModule = _player.getModule(brightcove.api.modules.APIModules.VIDEO_PLAYER); | |
| _experienceModule = _player.getModule(brightcove.api.modules.APIModules.EXPERIENCE); | |
| _cuePointsModule = _player.getModule(brightcove.api.modules.APIModules.CUE_POINTS); | |
| //settup event listeners | |
| BCHTML5GATracker.setupEventListeners(); | |
| //initialise GA | |
| BCHTML5GATracker.initGA(); | |
| //set loaded player | |
| BCHTML5GATracker.setPlayerName(); | |
| //check if a video didn't get a completion tracked | |
| BCHTML5GATracker.checkAbandonedVideo(); | |
| }; | |
| BCHTML5GATracker.setupEventListeners = function(){ | |
| //add event listners | |
| _experienceModule.addEventListener(brightcove.api.events.ExperienceEvent.TEMPLATE_READY, BCHTML5GATracker.onTemplateReady); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.CHANGE, BCHTML5GATracker.onMediaBegin); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.BEGIN, BCHTML5GATracker.onMediaChange); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.PLAY, BCHTML5GATracker.onMediaPlay); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.STOP, BCHTML5GATracker.onMediaStop); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.PROGRESS, BCHTML5GATracker.onMediaProgress); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.SEEK_NOTIFY, BCHTML5GATracker.onMediaSeek); | |
| _videoPlayerModule.addEventListener(brightcove.api.events.MediaEvent.COMPLETE, BCHTML5GATracker.onMediaComplete); | |
| _cuePointsModule.addEventListener(brightcove.api.events.CuePointEvent.CUE, BCHTML5GATracker.onCuePoint); | |
| }; | |
| BCHTML5GATracker.onTemplateReady = function(){ | |
| var referrerURL = document.referrer; | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.PLAYER_LOAD, referrerURL]); | |
| }; | |
| BCHTML5GATracker.onMediaChange = function(){ | |
| _currentVideo = _videoPlayerModule.getCurrentVideo(); | |
| _storedTimeWatched.data.abandonedVideo = _currentVideo; | |
| _customVideoID = BCHTML5GATracker.getLoadedVideo(_currentVideo); | |
| //tracks even if referrer URL is not available | |
| var referrerURL = document.referrer; | |
| var trackingAction = BCHTML5GATracker.REFERRER_URL + referrerURL; | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, trackingAction, BCHTML5GATracker.getLoadedVideo(_currentVideo)]); | |
| BCHTML5GATracker.createCuePoints(_currentVideo); | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.VIDEO_LOAD, BCHTML5GATracker.getLoadedVideo(_currentVideo)]); | |
| _previousTimestamp = new Date().getTime(); | |
| _timeWatched = 0; | |
| }; | |
| BCHTML5GATracker.onMediaBegin = function(){ | |
| if(!_mediaBegin){ | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MEDIA_BEGIN, BCHTML5GATracker.getLoadedVideo(_videoPlayerModule.getCurrentVideo())]); | |
| _previousTimestamp = new Date().getTime(); | |
| _timeWatched = 0; | |
| _mediaComplete = false; | |
| _mediaBegin = true; | |
| } | |
| }; | |
| BCHTML5GATracker.onMediaPlay = function(){ | |
| if(_mediaPaused){ | |
| _mediaPaused = false; | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MEDIA_RESUME, BCHTML5GATracker.getLoadedVideo(_currentVideo)]); | |
| } | |
| }; | |
| BCHTML5GATracker.onMediaStop = function(){ | |
| if(!_mediaComplete && !_mediaPaused){ | |
| _mediaPaused = true; | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MEDIA_PAUSE, BCHTML5GATracker.getLoadedVideo(_currentVideo)]); | |
| } | |
| }; | |
| BCHTML5GATracker.onMediaProgress = function(pEvent){ | |
| _currentPosition = pEvent.position; | |
| //update tracked time | |
| BCHTML5GATracker.updateTrackedTime(); | |
| /* | |
| This will track the media complete event when the user has watched 98% or more of the video. | |
| Why do it this way and not use the Smart Player API's event? The mediaComplete event will | |
| only fire once, so if a video is replayed, it won't fire again. | |
| */ | |
| if(pEvent.position/pEvent.duration > .98 && !_mediaComplete){ | |
| BCHTML5GATracker.onMediaComplete(pEvent); | |
| //empty these since we don't want to track it when someone comes back | |
| _storedTimeWatched.data.abandonedVideo = null; | |
| _storedTimeWatched.data.abandonedTimeWatched = null; | |
| } | |
| //track seek events | |
| if(!_mediaSeeking){ | |
| if(_trackSeekForward){ | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.SEEK_FORWARD, _customVideoID]); | |
| _trackSeekForward = false; | |
| } | |
| if(_trackSeekBackward){ | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.SEEK_BACKWARD, _customVideoID]); | |
| _trackSeekBackward = false; | |
| } | |
| }else{ | |
| _mediaSeeking = false; | |
| } | |
| }; | |
| BCHTML5GATracker.onMediaSeek = function(pEvent){ | |
| if(pEvent.position > _currentPosition){ | |
| _trackSeekForward = true; | |
| }else{ | |
| _trackSeekBackward = true; | |
| } | |
| _mediaSeeking = true; | |
| }; | |
| BCHTML5GATracker.onCuePoint = function(pEvent){ | |
| if(pEvent.cuePoint.type == 2 && pEvent.cuePoint.name == "milestone"){ | |
| switch(pEvent.cuePoint.metadata){ | |
| case "25%": | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MILESTONE_25, _customVideoID]); | |
| break; | |
| case "50%": | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MILESTONE_50, _customVideoID]); | |
| break; | |
| case "75%": | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MILESTONE_75, _customVideoID]); | |
| break; | |
| } | |
| } | |
| }; | |
| BCHTML5GATracker.initGA = function(){ | |
| BCHTML5GATracker.GAACCOUNT_ID = (BCHTML5GATracker.GAACCOUNT_ID == null) ? BCHTML5GATracker.getParamValue('GAID') : BCHTML5GATracker.GAACCOUNT_ID; | |
| if(BCHTML5GATracker.GAACCOUNT_ID == null){ | |
| console.log("The Google Analytics account number has not been defined. This is required for the analytics plugin to function properly."); | |
| }else{ | |
| console.log("GA Account Number: " + BCHTML5GATracker.GAACCOUNT_ID); | |
| _gaq.push(['_setAccount', BCHTML5GATracker.GAACCOUNT_ID]); | |
| _gaq.push(['_trackPageview']); | |
| try{ | |
| // determine whether to include the normal or SSL version | |
| var gaURL = (location.href.indexOf('https') == 0 ? 'https://ssl' : 'http://www'); | |
| gaURL += '.google-analytics.com/ga.js'; | |
| // include the ga.js | |
| $.getScript(gaURL); | |
| }catch(error){ | |
| console.log('Failed to load ga.js:' + error); | |
| } | |
| } | |
| }; | |
| BCHTML5GATracker.getParamValue = function(key){ | |
| //check plugin params for the value | |
| var pluginURL = experienceJSON.data.configuredProperties.plugins.list; | |
| var vars = [], hash; | |
| var hashes = pluginURL.slice(pluginURL.indexOf('?') + 1).split('&'); | |
| for(var i = 0; i < hashes.length; i++){ | |
| hash = hashes[i].split('='); | |
| vars.push(hash[0]); | |
| vars[hash[0]] = hash[1]; | |
| } | |
| return vars[key]; | |
| }; | |
| BCHTML5GATracker.setPlayerName = function(){ | |
| var playerName = unescape(BCHTML5GATracker.getParamValue('PlayerName')); | |
| var playerId = _experienceModule.getExperienceID(); | |
| if(playerName != "undefined"){ | |
| BCHTML5GATracker.PLAYER_NAME = playerName + " ID " + playerId; | |
| }else{ | |
| BCHTML5GATracker.PLAYER_NAME = BCHTML5GATracker.PLAYER_NAME + " ID " + playerId; | |
| } | |
| }; | |
| BCHTML5GATracker.getLoadedVideo = function(videoDTO){ | |
| var loadedVideo = videoDTO.id + " | " + videoDTO.displayName; | |
| return loadedVideo; | |
| }; | |
| BCHTML5GATracker.checkAbandonedVideo = function(){ | |
| if(_storedTimeWatched.data.abandonedVideo && _storedTimeWatched.data.abandonedTimeWatched){ | |
| var customVideoID = getCustomVideoID(_storedTimeWatched.data.abandonedVideo); | |
| var timeWatched = Math.round(_storedTimeWatched.data.abandonedTimeWatched); | |
| console.log("Tracking video that was previously unclosed: " + customVideoID + " : " + timeWatched); | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MEDIA_ABANDONED, customVideoID, timeWatched]); | |
| } | |
| }; | |
| BCHTML5GATracker.updateTrackedTime = function(){ | |
| var currentTimestamp = new Date().getTime(); | |
| var timeElapsed = (currentTimestamp - _previousTimestamp)/1000; | |
| _previousTimestamp = currentTimestamp; | |
| //check if it's more than 2 seconds in case the user paused or changed their local time or something | |
| if(timeElapsed < 2){ | |
| _timeWatched += timeElapsed; | |
| } | |
| //update time watched in case the user bails out before mediaComplete | |
| if(!_mediaComplete){//make sure mediaComplete hasn't fired yet, otherwise it gets set to null and the repopulated: not what we want | |
| _storedTimeWatched.data.abandonedTimeWatched = _timeWatched; //automatically gets flushed when flash player is closed | |
| } | |
| }; | |
| BCHTML5GATracker.onMediaComplete = function(pEvent){ | |
| if(!_mediaComplete){ | |
| _mediaComplete = true; | |
| _mediaBegin = false; | |
| _gaq.push(['_trackEvent', BCHTML5GATracker.PLAYER_NAME, BCHTML5GATracker.MEDIA_COMPLETE, _customVideoID, Math.round(_timeWatched)]); | |
| } | |
| }; | |
| BCHTML5GATracker.createCuePoints = function(pVideo){ | |
| var percent25 = { | |
| type: 2, //chapter cue point | |
| name: "milestone", | |
| metadata: "25%", | |
| time: (pVideo.length/1000) * .25 | |
| }; | |
| var percent50 = { | |
| type: 2, //chapter cue point | |
| name: "milestone", | |
| metadata: "50%", | |
| time: (pVideo.length/1000) * .5 | |
| }; | |
| var percent75 = { | |
| type: 2, //chapter cue point | |
| name: "milestone", | |
| metadata: "75%", | |
| time: (pVideo.length/1000) * .75 | |
| }; | |
| _cuePointsModule.addCuePoints(_currentVideo.id, [percent25, percent50, percent75]); | |
| }; | |
| //initialise the tracker | |
| BCHTML5GATracker.initialize(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment