soundcloud-iframe-analytics
Advanced tools
Comparing version 1.0.4 to 1.0.5
@@ -1,2 +0,2 @@ | ||
!function(n){var e={};function t(o){if(e[o])return e[o].exports;var i=e[o]={i:o,l:!1,exports:{}};return n[o].call(i.exports,i,i.exports,t),i.l=!0,i.exports}t.m=n,t.c=e,t.d=function(n,e,o){t.o(n,e)||Object.defineProperty(n,e,{configurable:!1,enumerable:!0,get:o})},t.r=function(n){Object.defineProperty(n,"__esModule",{value:!0})},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=1)}([function(n,e,t){e.loadScript=t(3),e.loadScriptPromised=t(2)},function(n,e,t){"use strict";t.r(e);var o=t(0),i=!1,r=!0,u=void 0;function c(n,e,t,o){if(r){var c=function(){if(u)return u;"function"==typeof window.gtag?u=a.GlobalSiteTag:"function"==typeof window.ga?u=a.GA:"_gaq"in window&&"function"==typeof window._gaq.push&&(u=a.Legacy);return u||(r=!1,null)}();c&&(i&&console.info('Tracking Event: category "'+n+'" action "'+e+'" label "'+t+'" value "'+o+'"'),c.event(n,e,t,o))}}var a={GlobalSiteTag:{event:function(n,e,t,o){window.gtag("event",e,{event_category:n,event_label:t,value:o})}},GA:{event:function(n,e,t,o){window.ga("send","event",n,e,t)}},Legacy:{event:function(n,e,t,o){window._gaq.push(["_trackEvent",n,e,t])}}},d="https://w.soundcloud.com/player/api.js",f="soundcloud.com",s="SoundCloud";function l(){var n=[];b(document.getElementsByTagName("iframe"),function(e){e.hasAttribute("src")&&e.getAttribute("src").indexOf(f)>-1&&n.push(e)}),0!==n.length&&o.loadScript(d,function(){"SC"in window&&b(n,function(n){!function(n){var e=SC.Widget.Events,t=!1,o="",i={},r=void 0;n.bind(e.READY,function(){}),n.bind(e.ERROR,function(){c(s,"Error",o)}),n.bind(e.PLAY_PROGRESS,function(){t||(t=!0,setTimeout(function(){t=!1,n.getCurrentSound(function(n){o!==n.title&&(o=n.title,i={})})},0===o.length?0:2500))}),n.bind(e.PLAY,function(){n.getCurrentSound(function(n){o=n.title,!(r=p(i,n.title)).started||r.finished?(r.started=!0,r.finished=!1,r.paused=!1,r.scrubbed=!1,c(s,"Playback started",o)):r.paused&&(r.paused=!1,c(s,"Playback resumed",o))})}),n.bind(e.PAUSE,function(){r=p(i,o),n.getCurrentSound(function(n){n.title!==r.id||r.finished||(r.paused=!0,c(s,"Playback paused",o))})}),n.bind(e.SEEK,function(){(r=p(i,o)).paused||r.finished||(r.scrubbed=!0,c(s,"Playback scrubbed",o))}),n.bind(e.FINISH,function(){if(!(r=p(i,o)).finished){r.finished=!0;var n=r.scrubbed?"Played in full with scrubbing":"Played in full";c(s,n,o)}})}(SC.Widget(n))})})}function p(n,e){return n.hasOwnProperty(e)||(n[e]={id:e,started:!1,paused:!1,scrubbed:!1,finished:!1}),n[e]}function b(n,e){for(var t=0,o=n.length;t<o;++t)e(n[t])}document.addEventListener("DOMContentLoaded",function n(){l(),document.removeEventListener("DOMContentLoaded",n)})},function(n,e){n.exports=function(n,e){var t,o,i=document;return new Promise(function(r,u){o=i.createElement("script"),t=i.getElementsByTagName("script")[0],e&&Object.keys(e).forEach(function(n){o[n]=e[n]}),o.async=1,o.src=n,o.onload=function(){r()},o.onerror=function(){u(new Error("failed to load: "+n))},t.parentNode.insertBefore(o,t)})}},function(n,e){n.exports=function(n,e,t){var o,i,r=document;i=r.createElement("script"),o=r.getElementsByTagName("script")[0],t&&Object.keys(t).forEach(function(n){i[n]=t[n]}),i.async=1,i.src=n,i.onload=function(){e()},i.onerror=function(){e(new Error("failed to load: "+n))},o.parentNode.insertBefore(i,o)}}]); | ||
!function(e){var n={};function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},t.p="",t(t.s=1)}([function(e,n,t){n.loadScript=t(3),n.loadScriptPromised=t(2)},function(e,n,t){"use strict";t.r(n);var o=t(0),r=!1,i=!0,u=void 0;function c(e,n,t,o){if(i){var c=function(){if(u)return u;"function"==typeof window.gtag?u=s.GlobalSiteTag:"function"==typeof window.ga?u=s.GA:"_gaq"in window&&"function"==typeof window._gaq.push&&(u=s.Legacy);return u||(i=!1,null)}();c&&(r&&console.info('Tracking Event: category "'+e+'" action "'+n+'" label "'+t+'" value "'+o+'"'),c.event(e,n,t,o))}}var s={GlobalSiteTag:{event:function(e,n,t,o){window.gtag("event",n,{event_category:e,event_label:t,value:o})}},GA:{event:function(e,n,t,o){window.ga("send","event",e,n,t)}},Legacy:{event:function(e,n,t,o){window._gaq.push(["_trackEvent",e,n,t])}}},d="https://w.soundcloud.com/player/api.js",a="soundcloud.com",f="SoundCloud";function p(){var e=[];if(g(document.getElementsByTagName("iframe"),function(n){n.hasAttribute("src")&&n.getAttribute("src").indexOf(a)>-1&&e.push(n)}),0!==e.length){var n=function(){g(e,function(e){!function(e){var n=SC.Widget.Events,t=!1,o="",r=0,i={},u=void 0;e.bind(n.READY,function(){}),e.bind(n.ERROR,function(){c(f,"Error",o)}),e.bind(n.PLAY_PROGRESS,function(n){u&&u.id===n.soundId&&function(e,n){if("number"!=typeof n.relativePosition)return;e.progress=Math.round(100*n.relativePosition);var t=void 0;e.progress>=99&&e.progstep<4?(t="4/4",e.progstep=4):e.progress>=75&&e.progstep<3?(t="3/4",e.progstep=3):e.progress>=50&&e.progstep<2?(t="2/4",e.progstep=2):e.progress>=25&&e.progstep<1&&(t="1/4",e.progstep=1);"string"==typeof t&&(e.scrubbed&&(t+=" with scrubbing"),c(f,"Progress "+t,e.title))}(u,n),t||(t=!0,setTimeout(function(){t=!1,e.getCurrentSound(function(e){r!==e.id&&(o=e.title,r=e.id,i={})})},0===r?0:2500))}),e.bind(n.PLAY,function(){e.getCurrentSound(function(e){o=e.title,r=e.id,!(u=l(i,e.title,e.id)).started||u.finished?(u.started=!0,u.finished=!1,u.paused=!1,u.scrubbed=!1,u.progress=0,u.progstep=0,c(f,"Playback started",o)):u.paused&&(u.paused=!1,c(f,"Playback resumed",o))})}),e.bind(n.PAUSE,function(){u=l(i,o,r),e.getCurrentSound(function(e){e.id!==u.id||u.finished||(u.paused=!0,c(f,"Playback paused",o))})}),e.bind(n.SEEK,function(){(u=l(i,o,r)).scrubbed||u.paused||u.finished||(u.scrubbed=!0,c(f,"Playback scrubbed",o))}),e.bind(n.FINISH,function(){if(!(u=l(i,o,r)).finished){u.finished=!0;var e=u.scrubbed?"Played in full with scrubbing":"Played in full";c(f,e,o)}})}(SC.Widget(e))})};"SC"in window&&"function"==typeof SC.Widget?n():o.loadScript(d,n)}}function l(e,n,t){return e.hasOwnProperty(n)?0===e[n].id&&(e[n].id=t):e[n]={title:n,id:t,started:!1,paused:!1,scrubbed:!1,finished:!1,progress:0,progstep:0},e[n]}function g(e,n){for(var t=0,o=e.length;t<o;++t)n(e[t])}document.addEventListener("DOMContentLoaded",function e(){p(),document.removeEventListener("DOMContentLoaded",e)})},function(e,n){e.exports=function(e,n){var t,o,r=document;return new Promise(function(i,u){o=r.createElement("script"),t=r.getElementsByTagName("script")[0],n&&Object.keys(n).forEach(function(e){o[e]=n[e]}),o.async=1,o.src=e,o.onload=function(){i()},o.onerror=function(){u(new Error("failed to load: "+e))},t.parentNode.insertBefore(o,t)})}},function(e,n){e.exports=function(e,n,t){var o,r,i=document;r=i.createElement("script"),o=i.getElementsByTagName("script")[0],t&&Object.keys(t).forEach(function(e){r[e]=t[e]}),r.async=1,r.src=e,r.onload=function(){n()},r.onerror=function(){n(new Error("failed to load: "+e))},o.parentNode.insertBefore(r,o)}}]); | ||
//# sourceMappingURL=sia.min.js.map |
{ | ||
"name": "soundcloud-iframe-analytics", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "Automated Google Analytics tracking of user interaction on embedded SoundCloud iframes", | ||
@@ -5,0 +5,0 @@ "author": "Igor Zinken", |
@@ -76,9 +76,16 @@ SoundCloud IFRAME Analytics | ||
* _Playback scrubbed_ | ||
* _Progress (num)_ | ||
* _Progress (num) with scrubbing_ | ||
* _Played in full_ | ||
* _Played in full with scrubbing_ | ||
Where starts are counted only once per track (unless it has finished playback). | ||
Scrubbed indicates that the user has dragged the playback to a different point in the track and thus | ||
might have skipped sections. | ||
Where: | ||
* _starts_ are counted only once per track (unless it has finished playback, after which we can treat | ||
it as a new play). | ||
* _scrubbed_ and _with scrubbing_ indicates that the user has dragged the playback to a different point in the track and thus | ||
might have skipped sections. You can use this to determine engagement. _Playback scrubbed_ is tracked only | ||
once (unless track has finished playback and is restarted). | ||
* _progress_ is tracked for every 25 % of the track that has been played, expected values for _(num)_ are: 1/4, 2/4, 3/4 and 4/4 | ||
## Development | ||
@@ -85,0 +92,0 @@ |
@@ -30,12 +30,20 @@ import * as TinyScriptLoader from "tiny-script-loader"; | ||
// load the SoundCloud Widget API | ||
// process all IFrames one by one and add event listeners and Analytics hooks | ||
TinyScriptLoader.loadScript( SOUNDCLOUD_API_URL, () => { | ||
if ( "SC" in window ) { | ||
loop( playlists, ( playlist ) => { | ||
const widget = SC.Widget( playlist ); | ||
attachSoundCloudAnalytics( widget ); | ||
}); | ||
} | ||
}); | ||
const processIFrames = () => { | ||
loop( playlists, ( playlist ) => { | ||
const widget = SC.Widget( playlist ); | ||
attachSoundCloudAnalytics( widget ); | ||
}); | ||
}; | ||
// ensure the SoundCloud Widget API has been loaded | ||
// after which the processing of the IFrames can start | ||
if ( "SC" in window && typeof SC.Widget === "function" ) { | ||
processIFrames(); | ||
} | ||
else { | ||
TinyScriptLoader.loadScript( SOUNDCLOUD_API_URL, processIFrames ); | ||
} | ||
} | ||
@@ -57,3 +65,3 @@ | ||
let hasTimeout = false, currentId = "", tracks = {}, vo; | ||
let hasTimeout = false, currentTrackTitle = "", currentTrackId = 0, tracks = {}, vo; | ||
@@ -72,7 +80,10 @@ // cache the id of the currently playing track as many events in the | ||
widget.bind( ENUM.ERROR, () => { | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Error", currentId ); | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Error", currentTrackTitle ); | ||
}); | ||
widget.bind( ENUM.PLAY_PROGRESS, () => { | ||
widget.bind( ENUM.PLAY_PROGRESS, ( playerData ) => { | ||
if ( vo && vo.id === playerData.soundId ) | ||
setTrackProgress( vo, playerData ); | ||
if ( hasTimeout ) | ||
@@ -93,4 +104,5 @@ return; | ||
if ( currentId !== data.title ) { | ||
currentId = data.title; | ||
if ( currentTrackId !== data.id ) { | ||
currentTrackTitle = data.title; | ||
currentTrackId = data.id; | ||
tracks = {}; | ||
@@ -100,3 +112,3 @@ } | ||
}, ( currentId.length === 0 ) ? 0 : INTERVAL ); | ||
}, ( currentTrackId === 0 ) ? 0 : INTERVAL ); | ||
}); | ||
@@ -107,6 +119,9 @@ | ||
currentId = data.title; | ||
vo = getSoundCloudTrackVO( tracks, data.title ); | ||
currentTrackTitle = data.title; | ||
currentTrackId = data.id; | ||
vo = getSoundCloudTrackVO( tracks, data.title, data.id ); | ||
if ( !vo.started || vo.finished ) { | ||
vo.started = true; | ||
@@ -116,7 +131,10 @@ vo.finished = false; | ||
vo.scrubbed = false; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback started", currentId ); | ||
vo.progress = 0; | ||
vo.progstep = 0; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback started", currentTrackTitle ); | ||
} | ||
else if ( vo.paused ) { | ||
vo.paused = false; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback resumed", currentId ); | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback resumed", currentTrackTitle ); | ||
} | ||
@@ -129,3 +147,3 @@ }); | ||
widget.bind( ENUM.PAUSE, () => { | ||
vo = getSoundCloudTrackVO( tracks, currentId ); | ||
vo = getSoundCloudTrackVO( tracks, currentTrackTitle, currentTrackId ); | ||
@@ -140,5 +158,5 @@ // do async check for current sound, if it is the same then | ||
widget.getCurrentSound(( data ) => { | ||
if ( data.title === vo.id && !vo.finished ) { | ||
if ( data.id === vo.id && !vo.finished ) { | ||
vo.paused = true; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback paused", currentId ); | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback paused", currentTrackTitle ); | ||
} | ||
@@ -148,14 +166,14 @@ }); | ||
widget.bind( ENUM.SEEK, () => { | ||
vo = getSoundCloudTrackVO( tracks, currentId ); | ||
if ( !vo.paused && !vo.finished ) { | ||
vo = getSoundCloudTrackVO( tracks, currentTrackTitle, currentTrackId ); | ||
if ( !vo.scrubbed && !vo.paused && !vo.finished ) { | ||
vo.scrubbed = true; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback scrubbed", currentId ); | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, "Playback scrubbed", currentTrackTitle ); | ||
} | ||
}); | ||
widget.bind( ENUM.FINISH, () => { | ||
vo = getSoundCloudTrackVO( tracks, currentId ); | ||
vo = getSoundCloudTrackVO( tracks, currentTrackTitle, currentTrackId ); | ||
if ( !vo.finished ) { | ||
vo.finished = true; | ||
const event = ( !vo.scrubbed ) ? "Played in full" : "Played in full with scrubbing"; | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, event, currentId ); | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, event, currentTrackTitle ); | ||
} | ||
@@ -175,20 +193,69 @@ }); | ||
* @param {Object} tracks data store for the tracks | ||
* @param {string} id identifier of the track | ||
* @param {string} title of the track | ||
* @param {number} id identifier of the track | ||
* @returns {Object} | ||
*/ | ||
function getSoundCloudTrackVO( tracks, id ) { | ||
function getSoundCloudTrackVO( tracks, title, id ) { | ||
if ( !tracks.hasOwnProperty( id )) { | ||
tracks[ id ] = { | ||
id: id, | ||
started: false, | ||
paused: false, | ||
scrubbed: false, | ||
finished: false | ||
if ( !tracks.hasOwnProperty( title )) { | ||
tracks[ title ] = { | ||
title: title, // track title, used as label for Google Analytics | ||
id: id, // track unique identifier on SoundCloud | ||
started: false, // whether track has started its playback | ||
paused: false, // whether track playback is paused | ||
scrubbed: false, // whether track has been scrubbed during playback | ||
finished: false, // whether track has finished its playback, e.g. reached the end | ||
progress: 0, // current track playback progress (0 - 100 range) | ||
progstep: 0 // the progress that has been tracked so far (4 steps, once every 25%) | ||
}; | ||
} | ||
return tracks[ id ]; | ||
else if ( tracks[ title ].id === 0 ) { | ||
tracks[ title].id = id; | ||
} | ||
return tracks[ title ]; | ||
} | ||
/** | ||
* Get the track playback progress (in percent) | ||
* | ||
* @param {Object} vo SoundCloud Value Object | ||
* @param {Object} playerData progress data Object | ||
*/ | ||
function setTrackProgress( vo, playerData ) { | ||
if ( typeof playerData.relativePosition !== "number" ) | ||
return; | ||
vo.progress = Math.round( playerData.relativePosition * 100 ); | ||
// track once every 25 % | ||
let doTrack = false, msg; | ||
if ( vo.progress >= 99 && vo.progstep < 4 ) { | ||
msg = "4/4"; | ||
vo.progstep = 4; | ||
} | ||
else if ( vo.progress >= 75 && vo.progstep < 3 ) { | ||
msg = "3/4"; | ||
vo.progstep = 3; | ||
} | ||
else if ( vo.progress >= 50 && vo.progstep < 2 ) { | ||
msg = "2/4"; | ||
vo.progstep = 2; | ||
} | ||
else if ( vo.progress >= 25 && vo.progstep < 1 ) { | ||
msg = "1/4"; | ||
vo.progstep = 1; | ||
} | ||
if ( typeof msg === "string" ) { | ||
if ( vo.scrubbed ) { | ||
msg += " with scrubbing"; | ||
} | ||
trackEvent( ANALYTICS_EVENT_CATEGORY, `Progress ${msg}`, vo.title ); | ||
} | ||
} | ||
/** | ||
* Simple forEach() implementation that will | ||
@@ -195,0 +262,0 @@ * go back a few old IE versions... |
Sorry, the diff of this file is not supported yet
53552
482
129