Overview
So I've written a Video.js plugin that reports back to google analytics and our custom reporting.
Each JSONP request we send back to our page is encoded using encodeURIComponent using this method. The first few fire off correctly. It then begins to error with TypeError: '[object Object]' is not a function (evaluating 'encodeURIComponent(p)')
. This only happens in Safari. (I'm on safari 7.0.1 on OSX Mavericks)
I've even tried doing the whole url string using encodeURI
but the samething happens to that function as well.
I've created a [JS FIDDLE][2] to demonstrate the problem. I've been unsuccessful at re-creating it with just some sample code so i've included all the relevant files in the external resources. If it does not do it, re-run the page its happening for me about 85% of the time.
Stepping through the functions
I first add the events to track
this.on('play',onPlay);
this.on('pause',onPause);
When an event fires it is caught by these functions
function onPlay( e ) {
videoData = getVideoData();
doTracking({
'category': videoData.cid,
'action': videoData.vid,
'label': 'Play',
'value': null
});
}
function onPause( e ) {
videoData = getVideoData();
doTracking({
'category': videoData.cid,
'action': videoData.vid,
'label': 'Pause',
'value': getTime()
});
}
Which gets video data from
function getVideoData() {
var src = videojsRef.player().currentSrc();
var srcSplit = src.split('/');
var filename = srcSplit[srcSplit.length-1];
var filenameSplit = filename.split('.');
var cid = filenameSplit[0];
var vid = filenameSplit[1];
var type = filenameSplit[2];
var returnObj = {
'cid': cid,
'vid': vid,
'filename': filename
};
return returnObj;
}
And then calls "doTracking" which is just a helper function that calls both tracking functions.
function doTracking( opt ) {
if ( gaType && bvReady ) { // Are both tracking types initialized
// Send to google
googleTrack( opt );
// Send to BetterVideo
bvTrack( opt );
} else {
queue.push( opt );
}
}
Which calls bvTrack( opt )
function bvTrack( opt ) {
var args = {
pid: playerid,
cid: opt.category,
vcd: opt.action,
a: opt.label,
callback: '{callback}'
};
if ( opt.value !== null ) {
args.val = opt.value;
}
// Heres where the trouble starts
new videojs.JSONP('http://jsfiddle.net/echo/jsonp/?'+serializeToQuery(args), function( response ) {
console.log('[BV Reporting] Tracking Response: ', arguments );
})
}
The data gets serialized in here
function serializeToQuery( obj ) {
var str = [];
console.log( "serializeToQuery", obj );
for(var p in obj) {
if ( obj.hasOwnProperty(p) ) {
console.log( ' property', p, obj[p]);
console.log( ' encodeURIComponent', typeof encodeURIComponent == 'function' ? 'function' : encodeURIComponent );
console.log( ' encoded property', encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
}
return str.join("&");
}
and then passed to the d3.js inspired JSONP (which i believe i found here on SO
videojs.JSONP = function (url, callback) {
var docHead = document.getElementsByTagName('head')[0];
function rand() {
var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
c = '', i = -1;
while (++i < 15) c += chars.charAt(Math.floor(Math.random() * 52));
return c;
}
function create(url) {
var e = url.match(/callback=jsonp.(\w+)/),
c = e ? e[1] : rand();
videojs.JSONP[c] = function(data) {
callback(data);
delete videojs.JSONP[c];
docHead.removeChild(script);
};
return 'videojs.JSONP.' + c;
}
var cb = create(url),
script = document.createElement('script');
script.type = 'text/javascript';
script.src = url.replace(/(\{|%7B)callback(\}|%7D)/, cb);
docHead.appendChild(script)
};
Output
serializeToQuery Object {
a: "Pause"
callback: "{callback}"
cid: "oceans"
pid: "885FA551-A873-4BB9-891A-ABC08CD47D36"
val: 6
vcd: "mp4"
}
property pid 885FA551-A873-4BB9-891A-ABC08CD47D36
encodeURIComponent function
encoded property pid=885FA551-A873-4BB9-891A-ABC08CD47D36
property cid oceans
encodeURIComponent function
encoded property cid=oceans
property vcd mp4
encodeURIComponent function
encoded property vcd=mp4
property a Pause
encodeURIComponent function
encoded property a=Pause
property callback {callback}
encodeURIComponent function
encoded property callback=%7Bcallback%7D
property val 6
encodeURIComponent function
encoded property val=6
But after 2 or 3 JSONP calls, it outputs this:
serializeToQuery Object {
a: "Play"
callback: "{callback}"
cid: "oceans"
pid: "885FA551-A873-4BB9-891A-ABC08CD47D36"
vcd: "mp4"
}
property pid 885FA551-A873-4BB9-891A-ABC08CD47D36
encodeURIComponent Object {
cid: "oceans"
filename: "oceans.mp4"
vid: "mp4"
}
[Error] TypeError: '[object Object]' is not a function (evaluating 'encodeURIComponent(p)')
serializeToQuery (videojs.bvReporting.js, line 531)
bvTrack (videojs.bvReporting.js, line 481)
doTracking (videojs.bvReporting.js, line 329)
onPlay (videojs.bvReporting.js, line 113)
ret (video.dev.js, line 769)
dispatcher (video.dev.js, line 295)
trigger (video.dev.js, line 529)
trigger (video.dev.js, line 1868)
eventHandler (video.dev.js, line 5376)
ret (video.dev.js, line 769)
dispatcher (video.dev.js, line 295)
As you can see encodeURIComponent
is now the last object it was called with.
Any ideas?