apm-html5-player
Advanced tools
Comparing version
@@ -7,4 +7,7 @@ (function (global, factory) { | ||
// Formats an ugly time (in seconds) to a nice readable format | ||
// e.g. 125 > 2:05, or 4226 > 1:10:26 | ||
function toFormatted(timeInSeconds) { | ||
timeInSeconds = Math.round(timeInSeconds); | ||
var formattedTime = ''; | ||
@@ -16,4 +19,6 @@ var formattedMinutes = ''; | ||
var seconds = timeInSeconds - minutes * 60 - hours * 3600; | ||
if (hours !== 0) { | ||
formattedTime = hours + ':'; | ||
if (minutes < 10) { | ||
@@ -27,2 +32,3 @@ formattedMinutes = '0' + minutes; | ||
} | ||
if (seconds < 10) { | ||
@@ -33,10 +39,23 @@ formattedSeconds = '0' + seconds; | ||
} | ||
formattedTime = formattedTime + formattedMinutes + ':' + formattedSeconds; | ||
return formattedTime; | ||
} | ||
// Required modules for page load | ||
// Constructor | ||
// The 'parent' argument passed in is the parent object from Player.js. | ||
// This script is only intended to be used with that Player.js. | ||
var Playlist = function(parent) { | ||
this.player = parent; | ||
// The containing DOM element | ||
this.el = this.player.playlistEl; | ||
}; | ||
// ----------------------------- | ||
// Setup functions | ||
// ----------------------------- | ||
Playlist.prototype.init = function() { | ||
@@ -47,6 +66,9 @@ this.selectElements() | ||
}; | ||
Playlist.prototype.selectElements = function() { | ||
this.itemEls = this.el.querySelectorAll('.js-playlist-item'); | ||
return this; | ||
}; | ||
Playlist.prototype.setNextItem = function() { | ||
@@ -56,17 +78,28 @@ Array.prototype.forEach.call(this.itemEls, function(currentItemEl) { | ||
var nextSrc; | ||
if (!nextItemEl) { | ||
return; | ||
} | ||
nextSrc = nextItemEl.getAttribute('data-src'); | ||
currentItemEl.setAttribute('data-next', nextSrc); | ||
}); | ||
return this; | ||
}; | ||
Playlist.prototype.bindEventHandlers = function() { | ||
var self = this; | ||
Array.prototype.forEach.call(this.itemEls, function(el) { | ||
el.addEventListener('click', self.onItemClick.bind(self)); | ||
}); | ||
return this; | ||
}; | ||
// ----------------------------- | ||
// Event Handlers | ||
// ----------------------------- | ||
Playlist.prototype.onItemClick = function(e) { | ||
@@ -78,2 +111,3 @@ e.preventDefault(); | ||
var artist = targetEl.getAttribute('data-artist'); | ||
this.player.el.setAttribute('data-src', src); | ||
@@ -86,2 +120,7 @@ this.player.loadAudioFromSources(src); | ||
}; | ||
// ----------------------------- | ||
// Helpers | ||
// ----------------------------- | ||
Playlist.prototype.displayPlayedState = function(el) { | ||
@@ -91,2 +130,3 @@ this.removeDisplayStates(); | ||
}; | ||
Playlist.prototype.displayPlayingState = function(el) { | ||
@@ -96,16 +136,22 @@ this.removeBufferingState(el); | ||
}; | ||
Playlist.prototype.displayPausedState = function(el) { | ||
el.classList.add('is-paused'); | ||
}; | ||
Playlist.prototype.removePausedState = function(el) { | ||
el.classList.remove('is-paused'); | ||
}; | ||
Playlist.prototype.displayBufferingState = function(el) { | ||
el.classList.add('is-loading'); | ||
}; | ||
Playlist.prototype.removeBufferingState = function(el) { | ||
el.classList.remove('is-loading'); | ||
}; | ||
Playlist.prototype.removeDisplayStates = function() { | ||
var self = this; | ||
Array.prototype.forEach.call(this.itemEls, function(el) { | ||
@@ -118,2 +164,3 @@ self.removeBufferingState(el); | ||
}; | ||
Playlist.prototype.populatePlayerInfo = function(title, artist) { | ||
@@ -124,2 +171,5 @@ this.player.titleEl.textContent = title; | ||
// import Playlist from './Playlist'; | ||
// Constants | ||
var PLAYING_CLASS = 'is-playing'; | ||
@@ -129,13 +179,33 @@ var PAUSED_CLASS = 'is-paused'; | ||
var MUTED_CLASS = 'is-muted'; | ||
// Constructor | ||
var Player = function(el, options) { | ||
// The containing DOM element | ||
this.el = el; | ||
// An object with player options | ||
this.options = options; | ||
// The playing/paused state of the Player | ||
this.isPlaying = false; | ||
// A variable to store the previous volume the player was set at. | ||
this.storedVolume = 1; | ||
// References to the playlist | ||
this.playlistSelector = this.el.getAttribute('data-playlist'); | ||
this.playlistEl = document.querySelector(this.playlistSelector); | ||
// The playlist module, initialized later | ||
this.playlist; | ||
// Set to true when playlist is initialized | ||
this.hasPlaylist = false; | ||
// Set to true when mousedown on audio timelineEl | ||
this.isSeeking = false; | ||
}; | ||
// ----------------------------- | ||
// Initialized functions | ||
// ----------------------------- | ||
// Initialize the module | ||
Player.prototype.init = function() { | ||
@@ -148,6 +218,11 @@ this.selectElements() | ||
.displayCurrentVolume(); | ||
return this; | ||
}; | ||
// Descendant elements of the containing DOM element | ||
Player.prototype.selectElements = function() { | ||
// The audio element used for playback | ||
this.audioEl = this.el.querySelector('audio'); | ||
// Controls | ||
this.playButtonEls = this.el.querySelectorAll('.js-player-play'); | ||
@@ -166,2 +241,3 @@ this.skipForwardButtonEl = this.el.querySelector('[data-skip-forward]'); | ||
this.muteButtonEl = this.el.querySelector('.js-player-mute'); | ||
// Info | ||
this.durationEl = this.el.querySelector('.js-player-duration'); | ||
@@ -171,7 +247,11 @@ this.currentTimeEl = this.el.querySelector('.js-player-currentTime'); | ||
this.artistEl = this.el.querySelector('.js-player-artist'); | ||
return this; | ||
}; | ||
Player.prototype.getSources = function() { | ||
// Get data-src attribute each time this is called in case the attribute changes | ||
try { | ||
this.sources = JSON.parse( | ||
// decodeURI is used in case any URL characters (like %20 for spaces) are in the string | ||
decodeURI(this.el.getAttribute('data-src')).replace(/'/g, '"') | ||
@@ -181,16 +261,27 @@ ); | ||
if (typeof console !== 'undefined') { | ||
// If the error is anything other than not evaluating to JSON, print to console | ||
var syntaxReg = /^\s*SyntaxError: Unexpected token|^\s*SyntaxError: Unexpected end of JSON input/; | ||
if (!syntaxReg.test(e)) { | ||
// eslint-disable-next-line | ||
console.log(e); | ||
} | ||
} | ||
this.sources = this.el.getAttribute('data-src'); | ||
} | ||
return this; | ||
}; | ||
// Setup and bind event handlers | ||
Player.prototype.bindEventHandlers = function() { | ||
var self = this; | ||
// Click events | ||
// Apply click event to all play buttons | ||
Array.prototype.forEach.call(this.playButtonEls, function(el) { | ||
el.addEventListener('click', self.onPlayClick.bind(self)); | ||
}); | ||
this.skipForwardButtonEl && | ||
@@ -201,2 +292,3 @@ this.skipForwardButtonEl.addEventListener( | ||
); | ||
this.skipBackButtonEl && | ||
@@ -207,2 +299,3 @@ this.skipBackButtonEl.addEventListener( | ||
); | ||
if (this.timelineEl) { | ||
@@ -224,4 +317,7 @@ this.timelineEl.addEventListener( | ||
this.volumeBarEl.addEventListener('click', this.onVolumeClick.bind(this)); | ||
this.muteButtonEl && | ||
this.muteButtonEl.addEventListener('click', this.onMuteClick.bind(this)); | ||
// Audio element events | ||
this.audioEl.addEventListener('play', this.onAudioPlay.bind(this)); | ||
@@ -242,4 +338,6 @@ this.audioEl.addEventListener('pause', this.onAudioPause.bind(this)); | ||
this.audioEl.addEventListener('progress', this.onProgress.bind(this)); | ||
return this; | ||
}; | ||
Player.prototype.initTime = function() { | ||
@@ -249,2 +347,3 @@ this.displayCurrentTime(); | ||
}; | ||
Player.prototype.initPlaylist = function() { | ||
@@ -256,11 +355,26 @@ if (this.playlistEl) { | ||
} | ||
return this; | ||
}; | ||
// ----------------------------- | ||
// Event Handlers | ||
// ----------------------------- | ||
Player.prototype.onPlayClick = function(e) { | ||
e.preventDefault(); | ||
this.handlePlay(); | ||
const button = e.target.closest('.js-player-play'); | ||
button.disabled = true; | ||
setTimeout(() => { | ||
this.handlePlay(); | ||
button.disabled = false; | ||
}, 200); | ||
}; | ||
// handler is separated from actual click event so it can be called | ||
// externally (e.g. from play buttons outside of the player interface) | ||
Player.prototype.handlePlay = function() { | ||
if (this.isPlaying === false) { | ||
if (this.audioEl.readyState === 0) { | ||
// get sources from data attribute in case it changed | ||
this.getSources(); | ||
@@ -277,20 +391,29 @@ this.loadAudioFromSources(this.sources); | ||
}; | ||
Player.prototype.onSkipForwardClick = function(e) { | ||
var targetEl = e.currentTarget; | ||
var seconds = targetEl.getAttribute('data-skip-forward'); | ||
e.preventDefault(); | ||
if (this.audioEl.duration === Infinity) { | ||
return; | ||
} | ||
this.skipForward(seconds); | ||
}; | ||
Player.prototype.onSkipBackClick = function(e) { | ||
var targetEl = e.currentTarget; | ||
var seconds = targetEl.getAttribute('data-skip-back'); | ||
e.preventDefault(); | ||
if (this.audioEl.duration === Infinity) { | ||
return; | ||
} | ||
this.skipBack(seconds); | ||
}; | ||
Player.prototype.onTimelineMousedown = function(e) { | ||
@@ -300,10 +423,15 @@ var targetEl = e.currentTarget; | ||
var seconds = this.getSecondsByClickPosition(targetEl, clickXPosition); | ||
e.preventDefault(e); | ||
this.seekTime(seconds); | ||
this.isSeeking = true; | ||
}; | ||
Player.prototype.onTimelineMouseup = function(e) { | ||
e.preventDefault(e); | ||
this.isSeeking = false; | ||
}; | ||
Player.prototype.onTimelineMousemove = function(e) { | ||
@@ -315,5 +443,7 @@ e.preventDefault(e); | ||
}; | ||
Player.prototype.onVolumeClick = function(e) { | ||
var targetEl = e.currentTarget; | ||
var volume; | ||
if (targetEl.getAttribute('data-volume-direction') === 'h') { | ||
@@ -326,7 +456,11 @@ var clickXPosition = e.pageX; | ||
} | ||
e.preventDefault(); | ||
this.changeVolume(volume); | ||
}; | ||
Player.prototype.onMuteClick = function(e) { | ||
e.preventDefault(); | ||
if (this.audioEl.volume !== 0) { | ||
@@ -338,2 +472,6 @@ this.muteAudio(); | ||
}; | ||
// HTMLMediaElement 'play' event fires when a request | ||
// to play the audio has occurred. Does not necessarily mean the | ||
// audio is actually playing. (see 'playing' event) | ||
Player.prototype.onAudioPlay = function() { | ||
@@ -343,2 +481,4 @@ this.isPlaying = true; | ||
}; | ||
// HTMLMediaElement 'pause' event fires when the audio gets paused | ||
Player.prototype.onAudioPause = function() { | ||
@@ -348,2 +488,5 @@ this.isPlaying = false; | ||
}; | ||
// HTMLMediaElement 'timeupdate' event fires while the audio is playing | ||
// and the current time changes, usually several times per second | ||
Player.prototype.onAudioTimeupdate = function() { | ||
@@ -353,8 +496,17 @@ this.displayCurrentTime(); | ||
}; | ||
// HTMLMediaElement 'waiting' event fires when audio is downloading | ||
// or interrupted, such as when buffering at first play | ||
Player.prototype.onAudioWaiting = function() { | ||
this.displayBufferingState(); | ||
}; | ||
// HTMLMediaElement 'playing' event fires when audio has actually | ||
// begun playing (after being loaded) | ||
Player.prototype.onAudioPlaying = function() { | ||
this.displayPlayingState(); | ||
}; | ||
// HTMLMediaElement 'ended' event fires when audio playback has | ||
// finished for the current file | ||
Player.prototype.onAudioEnded = function() { | ||
@@ -366,5 +518,11 @@ this.displayStoppedState(); | ||
}; | ||
// HTMLMediaElement 'volumechange' event fires when the audio's | ||
// volume changes | ||
Player.prototype.onVolumeChange = function() { | ||
this.displayCurrentVolume(); | ||
}; | ||
// HTMLMediaElement 'onLoadedMetadata' event fires when the audio's | ||
// metadata has been downloaded | ||
Player.prototype.onLoadedMetadata = function() { | ||
@@ -374,19 +532,40 @@ this.displayDuration(); | ||
}; | ||
// HTMLMediaElement 'progress' event fires when any data | ||
// gets downloaded | ||
Player.prototype.onProgress = function() { | ||
this.displayTimeRanges(); | ||
}; | ||
// ----------------------------- | ||
// Audio Element Methods | ||
// ----------------------------- | ||
Player.prototype.loadAudioFromSources = function(sources) { | ||
// Remove the src attribute on the audio element in case it was left in the HTML. | ||
// An empty src attribute will interfere with the inner <source> elements. | ||
this.audioEl.removeAttribute('src'); | ||
// Clear any existing <source> elements | ||
this.audioEl.innerHTML = ''; | ||
if (typeof sources === 'string') { | ||
// Turn a string value into an array with one item | ||
// This is in this function so that loadAudioFromSources() | ||
// can be called externally with a string if needed | ||
sources = sources.split(); | ||
} | ||
this.createSourceEls(sources); | ||
this.audioEl.load(); | ||
}; | ||
Player.prototype.createSourceEls = function(sources) { | ||
var self = this; | ||
// Append <source> elements to the <audio> element | ||
sources.forEach(function(source) { | ||
var sourceUrl; | ||
var sourceType; | ||
// if using a source object, get the type from the object, | ||
// fall back to figure it out based on the filename | ||
if (typeof source === 'object' && !Array.isArray(source)) { | ||
@@ -399,4 +578,7 @@ sourceUrl = source.url; | ||
} | ||
// Generate the html | ||
var sourceEl = document.createElement('source'); | ||
sourceEl.setAttribute('src', sourceUrl); | ||
// Only set a type if sourceType is not null | ||
if (sourceType !== null) { | ||
@@ -408,3 +590,6 @@ sourceEl.setAttribute('type', sourceType); | ||
}; | ||
Player.prototype.getSourceType = function(source) { | ||
// We don't test for only the end of the filename in case | ||
// there is a cache busting string on the end | ||
var aacReg = /\.aac/; | ||
@@ -415,2 +600,3 @@ var mp4Reg = /\.mp4/; | ||
var mp3Reg = /\.mp3/; | ||
if (aacReg.test(source)) { | ||
@@ -430,2 +616,3 @@ return 'audio/aac'; | ||
}; | ||
Player.prototype.sendToNielsen = function(key, value) { | ||
@@ -436,5 +623,8 @@ if (window.nSdkInstance && this.audioEl.duration === Infinity) { | ||
}; | ||
Player.prototype.unloadAudio = function() { | ||
this.isPlaying = false; | ||
// Remove inner <source> elements | ||
this.audioEl.innerHTML = ''; | ||
// Forces the audio element to read the (nonexistent) <source> elements and update | ||
this.audioEl.load(); | ||
@@ -444,2 +634,3 @@ this.displayStoppedState(); | ||
}; | ||
Player.prototype.playAudio = function() { | ||
@@ -449,2 +640,3 @@ this.pauseAllAudio(); | ||
}; | ||
Player.prototype.pauseAudio = function(currentAudio) { | ||
@@ -457,3 +649,5 @@ if (typeof currentAudio !== 'undefined') { | ||
}; | ||
Player.prototype.pauseAllAudio = function() { | ||
// Pause any audio that may already be playing on the page. | ||
var self = this; | ||
@@ -465,6 +659,10 @@ var audioEls = document.querySelectorAll('audio'); | ||
}; | ||
Player.prototype.playNext = function() { | ||
var nextSrc = this.getNextPlaylistSrc(); | ||
var nextItemEl; | ||
if (typeof nextSrc === 'undefined' || nextSrc === null) return; | ||
this.el.setAttribute('data-src', nextSrc); | ||
@@ -479,5 +677,7 @@ nextItemEl = this.findNext(nextSrc); | ||
}; | ||
Player.prototype.findNext = function(src) { | ||
return this.playlistEl.querySelector('li[data-src="' + src + '"]'); | ||
}; | ||
Player.prototype.getSecondsByClickPosition = function(element, clickXPosition) { | ||
@@ -491,13 +691,18 @@ var timelineRect = element.getBoundingClientRect(); | ||
var seconds = Number(time.toFixed()); | ||
return seconds; | ||
}; | ||
Player.prototype.seekTime = function(seconds) { | ||
this.audioEl.currentTime = seconds; | ||
}; | ||
Player.prototype.skipForward = function(seconds) { | ||
this.audioEl.currentTime = this.audioEl.currentTime + Number(seconds); | ||
}; | ||
Player.prototype.skipBack = function(seconds) { | ||
this.audioEl.currentTime = this.audioEl.currentTime - Number(seconds); | ||
}; | ||
Player.prototype.getVolumeByHorizClickPosition = function( | ||
@@ -513,4 +718,6 @@ element, | ||
var volume = Number(percent.toFixed(2)); | ||
return volume; | ||
}; | ||
Player.prototype.getVolumeByVertClickPosition = function( | ||
@@ -527,4 +734,6 @@ element, | ||
var volume = Number(percent.toFixed(2)); | ||
return volume; | ||
}; | ||
Player.prototype.getCurrentPlaylistItem = function() { | ||
@@ -538,2 +747,3 @@ var srcString = this.el.getAttribute('data-src'); | ||
}; | ||
Player.prototype.getNextPlaylistSrc = function() { | ||
@@ -543,10 +753,14 @@ var itemEl = this.getCurrentPlaylistItem(); | ||
}; | ||
Player.prototype.changeVolume = function(volume) { | ||
this.audioEl.volume = volume; | ||
}; | ||
Player.prototype.muteAudio = function() { | ||
this.storedVolume = this.audioEl.volume; | ||
this.displayMutedState(); | ||
this.changeVolume(0); | ||
}; | ||
Player.prototype.unmuteAudio = function() { | ||
@@ -560,5 +774,10 @@ this.displayUnmutedState(); | ||
}; | ||
// Displays the length of the audio file | ||
Player.prototype.displayDuration = function() { | ||
// Exit if no duration element in DOM | ||
if (!this.durationEl) return; | ||
var duration; | ||
if (this.audioEl.duration !== Infinity) { | ||
@@ -569,4 +788,8 @@ duration = toFormatted(this.audioEl.duration); | ||
}; | ||
// Changes the current time numbers while playing | ||
Player.prototype.displayCurrentTime = function() { | ||
// Exit if current time element isn't in DOM | ||
if (!this.currentTimeEl) return; | ||
var currentTime = toFormatted(this.audioEl.currentTime); | ||
@@ -579,15 +802,28 @@ this.currentTimeEl.innerHTML = currentTime; | ||
} | ||
return this; | ||
}; | ||
// Modifies timeline length based on progress | ||
Player.prototype.updateTimelineProgress = function() { | ||
// Exit if there is no timeline in DOM | ||
if (!this.timelineEl) return; | ||
var progress = (this.audioEl.currentTime / this.audioEl.duration) * 100; | ||
this.timelineProgressEl.style.width = progress + '%'; | ||
}; | ||
// Show the portions of the file that have been downloaded | ||
// (i.e. 'buffered') on the timeline | ||
Player.prototype.displayTimeRanges = function() { | ||
// Exit if there is no timeline element | ||
if (!this.timelineBufferedEl) return; | ||
// Exit if audio isn't playing | ||
if (this.isPlaying !== true) return; | ||
// Exit if live audio is playing | ||
if (this.audioEl.duration === Infinity) return; | ||
for (var i = 0; i < this.audioEl.buffered.length; i++) { | ||
var currentBuffer = i; | ||
if (this.audioEl.buffered.start(currentBuffer) < this.audioEl.currentTime) { | ||
@@ -599,2 +835,3 @@ var startX = this.audioEl.buffered.start(currentBuffer); | ||
var timeRangeEls = this.timelineBufferedEl.children; | ||
if (timeRangeEls[currentBuffer]) { | ||
@@ -611,5 +848,8 @@ timeRangeEls[currentBuffer].style.left = posXPercent + '%'; | ||
}; | ||
// Modifies the play/pause button state | ||
Player.prototype.displayPlayedState = function() { | ||
this.el.classList.remove(PAUSED_CLASS); | ||
this.el.classList.add(PLAYING_CLASS); | ||
if (this.hasPlaylist === true) { | ||
@@ -619,5 +859,8 @@ this.playlist.displayPlayedState(this.getCurrentPlaylistItem()); | ||
}; | ||
// Modifies the play/pause button state | ||
Player.prototype.displayPausedState = function() { | ||
this.el.classList.remove(PLAYING_CLASS); | ||
this.el.classList.add(PAUSED_CLASS); | ||
if (this.hasPlaylist === true) { | ||
@@ -627,4 +870,7 @@ this.playlist.displayPausedState(this.getCurrentPlaylistItem()); | ||
}; | ||
// Modifies the timeline | ||
Player.prototype.displayPlayingState = function() { | ||
this.removeBufferingState(); | ||
if (this.hasPlaylist === true) { | ||
@@ -634,2 +880,4 @@ this.playlist.displayPlayingState(this.getCurrentPlaylistItem()); | ||
}; | ||
// Modifies the timeline, button displays paused state | ||
Player.prototype.displayStoppedState = function() { | ||
@@ -639,2 +887,3 @@ this.el.classList.remove(PLAYING_CLASS); | ||
this.removeBufferingState(); | ||
if (this.hasPlaylist === true) { | ||
@@ -644,4 +893,7 @@ this.playlist.removeDisplayStates(); | ||
}; | ||
// Adds buffering styles to timeline | ||
Player.prototype.displayBufferingState = function() { | ||
this.el.classList.add(LOADING_CLASS); | ||
if (this.hasPlaylist === true) { | ||
@@ -651,4 +903,7 @@ this.playlist.displayBufferingState(this.getCurrentPlaylistItem()); | ||
}; | ||
// Removes buffering styles from timeline | ||
Player.prototype.removeBufferingState = function() { | ||
this.el.classList.remove(LOADING_CLASS); | ||
if (this.hasPlaylist === true) { | ||
@@ -658,5 +913,8 @@ this.playlist.removeBufferingState(this.getCurrentPlaylistItem()); | ||
}; | ||
Player.prototype.displayCurrentVolume = function() { | ||
if (!this.volumeBarEl) return; | ||
var volumePercent = this.audioEl.volume * 100; | ||
if (this.audioEl.volume === 0) { | ||
@@ -667,2 +925,3 @@ this.displayMutedState(); | ||
} | ||
if (this.volumeBarEl.getAttribute('data-volume-direction') === 'h') { | ||
@@ -673,7 +932,10 @@ this.currentVolumeEl.style.width = volumePercent + '%'; | ||
} | ||
return this; | ||
}; | ||
Player.prototype.displayMutedState = function() { | ||
this.el.classList.add(MUTED_CLASS); | ||
}; | ||
Player.prototype.displayUnmutedState = function() { | ||
@@ -684,2 +946,3 @@ this.el.classList.remove(MUTED_CLASS); | ||
var NielsenSetup = function() {}; | ||
NielsenSetup.prototype.init = function(params, metadata) { | ||
@@ -691,8 +954,24 @@ window.nielsenMetadataObject = metadata; | ||
/* | ||
* Google Analytics plug-in for HTML5 Player | ||
* | ||
* Example Usage: | ||
* var example_analytics = new HTML5PlayerGoogleAnalytics(); | ||
* example_analytics.init({ version: 'classic' , audio: $('#audio') }); // omit 'version' property to default to 'universal' | ||
* Note Marketplace uses universal anaylics now. | ||
* Conceptually its probably easiest ot think about this piece of code as something that registers | ||
* event listeners (start, end and timeupdate) for the HTML5 audio player. Then we send a Google Analytics event on START, | ||
* FINISH and 25, 50 amd 75% the way through aka Quartiles. | ||
*/ | ||
function HTML5PlayerGoogleAnalytics(args) { | ||
var self = this; | ||
/* Default to Google Analytics Universal API */ | ||
this.ga_version = 'universal'; | ||
//@todo rethink the use of isFirstPlay as a statemachine and make it its own object? | ||
this.isFirstPlay = true; | ||
this.lastsrc = ''; | ||
this.quartile = 0; | ||
function checkSources() { | ||
@@ -702,2 +981,4 @@ self.isFirstPlay = self.audioele.currentSrc === self.lastsrc ? false : true; | ||
} | ||
/* Return category 'Live Audio', 'On-Demand Audio' or in rare instances 'Underwriting' */ | ||
function getCategory() { | ||
@@ -712,2 +993,3 @@ var cat; | ||
} | ||
function audioSrcForReporting() { | ||
@@ -719,8 +1001,11 @@ return self.audioele.currentSrc.replace( | ||
} | ||
function percent_played() { | ||
return self.audioele.currentTime / self.audioele.duration; | ||
} | ||
/* Triggered every 100ms with position update data */ | ||
function onPositionUpdate() { | ||
if (self.audioele.duration === Infinity) { | ||
return; | ||
return; // bail if it is live audio | ||
} | ||
@@ -731,5 +1016,13 @@ var category = getCategory(); | ||
var quartile = Math.floor((percent + 0.02) * 4); | ||
// Track quartiles played for static audio, but not underwriting. | ||
// Log 'QUARTILE-2', and 'QUARTILE-3', but not 'QUARTILE-1', or 'QUARTILE-4'. | ||
// We don't need to log QUARTILE-1 because START is the same thing. | ||
// We don't need to log QUARTILE-4 because FINISHED is the same thing. | ||
if (quartile > self.quartile && percent > 0 && percent !== 1) { | ||
self.quartile = quartile; | ||
if (quartile <= 3) { | ||
//console.log('About to call with: ' ); | ||
//console.log({ category: category, action: 'QUARTILE-'+quartile, label: src}); | ||
trackEvent({ | ||
@@ -743,20 +1036,35 @@ category: category, | ||
} | ||
function onMediaFinished() { | ||
var category = getCategory(); | ||
var src = audioSrcForReporting(); | ||
//console.log('About to call with: ' ); | ||
//console.log({ category: category, action: 'FINISHED', label: src}); | ||
trackEvent({ category: category, action: 'FINISHED', label: src }); | ||
self.isFirstPlay = true; | ||
} | ||
function onPlayPause() { | ||
var category = getCategory(), | ||
src = audioSrcForReporting(); | ||
checkSources(); | ||
// Track only the first 'PLAY' event as 'START' (we don't care about 'PAUSE' status) | ||
if (self.isFirstPlay === true) { | ||
//console.log('About to call with: ' ); | ||
//console.log({ category: category, action: 'START', label: src}); | ||
trackEvent({ category: category, action: 'START', label: src }); | ||
} | ||
} | ||
/* Handle Google Universal or Classic API calls | ||
* @example trackEvent({ category: 'Live Audio' action: 'START', label: meta.identifier }); | ||
* @example trackEvent({ category: 'On-Demand Audio' action: 'FINISHED', label: meta.identifier }); | ||
*/ | ||
function trackEvent(event) { | ||
if (typeof window.ga === 'undefined') { | ||
return; | ||
return; // bail if no google analytics or ad blocker | ||
} | ||
// Test that an object was passed | ||
if (typeof event !== 'object') { | ||
@@ -766,2 +1074,4 @@ console.error('object expected'); | ||
} | ||
// Test that the object contains required attributes | ||
if (typeof event.category === 'undefined') { | ||
@@ -771,2 +1081,3 @@ console.error('event category expected'); | ||
} | ||
if (typeof event.action === 'undefined') { | ||
@@ -776,2 +1087,3 @@ console.error('event action expected'); | ||
} | ||
if (typeof event.label === 'undefined') { | ||
@@ -781,2 +1093,3 @@ console.error('event label expected'); | ||
} | ||
if (self.ga_version === 'universal') { | ||
@@ -797,3 +1110,6 @@ window.ga('send', 'event', event.category, event.action, event.label, { | ||
} | ||
this.init = function(args) { | ||
// Set ga_version to 'universal' or 'classic'. | ||
// Events will be tracked using the proper Javascript API in trackEvent(). | ||
if (typeof args.audio === 'undefined') { | ||
@@ -803,2 +1119,3 @@ console.error('Audio element must be passed in'); | ||
if (typeof args.version !== 'undefined') { | ||
// Otherwise use provided argument, whitelisting options 'universal' or 'classic'. | ||
self.ga_version = | ||
@@ -809,2 +1126,3 @@ args.version === 'universal' || args.version === 'classic' | ||
} | ||
self.audioele = args.audio; | ||
@@ -811,0 +1129,0 @@ self.audioele.addEventListener('timeupdate', onPositionUpdate); |
@@ -30,3 +30,3 @@ { | ||
"title": "APM HTML5 Player", | ||
"version": "1.1.0", | ||
"version": "1.1.1", | ||
"devDependencies": { | ||
@@ -33,0 +33,0 @@ "@babel/cli": "^7.4.4", |
51408
17.01%883
19.32%