PageSourceSearch

https://www.jakesweeney.com/global/media/js/ddc/apis/videoplayer/v2/embed-jwplayer.js

js jakesweeney.com collected 2026-09-26 02:06:42 UTC 52,597 bytes, 1,599 lines download raw bytes

1/**
2*	Embedding code for JWPlayer to use Dealer.com playlists
3*	This gives an API to embed the player with standard jwplayer options or uses data attributes of the target element
4*	The resulting videos are registered in the DDC.Video global object
5*	Copyright (c) 2014 Dealer.com.
6*
7*	Requires:
8*	JQuery 1.5+
9*	JQuery cookie plugin (only if using preroll functionality)
10*
11*	Created: 15.July.2013, Orson Bradford
12*	Last modified: 3.February.2014, Orson Bradford
13*/
14
15/*! Allow CORS header support for ajax requests in IE
16 * jQuery-ajaxTransport-XDomainRequest - v1.0.1 - 2013-10-17
17 * https://github.com/MoonScript/jQuery-ajaxTransport-XDomainRequest
18 * Copyright (c) 2013 Jason Moon (@JSONMOON)
19 * Licensed MIT (/blob/master/LICENSE.txt)
20 */
21(function($){if(!$.support.cors&&$.ajaxTransport&&window.XDomainRequest){var n=/^https?:\/\//i;var o=/^get|post$/i;var p=new RegExp('^'+location.protocol,'i');var q=/text\/html/i;var r=/\/json/i;var s=/\/xml/i;$.ajaxTransport('* text html xml json',function(i,j,k){if(i.crossDomain&&i.async&&o.test(i.type)&&n.test(i.url)&&p.test(i.url)){var l=null;var m=(j.dataType||'').toLowerCase();return{send:function(f,g){l=new XDomainRequest();if(/^\d+$/.test(j.timeout)){l.timeout=j.timeout}l.ontimeout=function(){g(500,'timeout')};l.onload=function(){var a='Content-Length: '+l.responseText.length+'\r\nContent-Type: '+l.contentType;var b={code:200,message:'success'};var c={text:l.responseText};try{if(m==='html'||q.test(l.contentType)){c.html=l.responseText}else if(m==='json'||(m!=='text'&&r.test(l.contentType))){try{c.json=$.parseJSON(l.responseText)}catch(e){b.code=500;b.message='parseerror'}}else if(m==='xml'||(m!=='text'&&s.test(l.contentType))){var d=new ActiveXObject('Microsoft.XMLDOM');d.async=false;try{d.loadXML(l.responseText)}catch(e){d=undefined}if(!d||!d.documentElement||d.getElementsByTagName('parsererror').length){b.code=500;b.message='parseerror';throw'Invalid XML: '+l.responseText;}c.xml=d}}catch(parseMessage){throw parseMessage;}finally{g(b.code,b.message,c,a)}};l.onprogress=function(){};l.onerror=function(){g(500,'error',{text:l.responseText})};var h='';if(j.data){h=($.type(j.data)==='string')?j.data:$.param(j.data)}l.open(i.type,i.url);l.send(h)},abort:function(){if(l){l.abort()}}}}})}})(jQuery);
22
23window.DDC = window.DDC || {};
24
25// ************************************
26// Register as Global Module
27// ************************************
28
29DDC.Video = (function ($) {
30	"use strict";
31	var version = '2.14',
32		// module object to hold externally accessible methods and properties
33		module = {},
34		supportsTouch = 'ontouchstart' in window || navigator.msMaxTouchPoints,
35		ddcHostName = "https://static.dealer.com/",
36		jwPlayerkey = 'CuRkq2mEg9sCOY9v/r3gGjfwpRHO+3kZk713oA==',
37		// Falling back to jwplayer 6.5 until the skin performance has been improved
38		jwplayerPath = ddcHostName + 'global/media/js/ddc/apis/videoplayer/v2/jwplayer/v6.5/jwplayer.js',
39		flashplayerPath = ddcHostName + 'global/media/js/ddc/apis/videoplayer/v2/jwplayer/v6.5/jwplayer.flash.swf',
40		html5playerPath = ddcHostName + 'global/media/js/ddc/apis/videoplayer/v2/jwplayer/v6.5/jwplayer.html5.js',
41		// Optional proxy for cross domain ajax requests using the DDC Request Proxy Portlet
42		// (works for playlists and captions files but not skin xml files because jwplayer can't handle proxy escape chars)
43		// proxyPath = '/proxy.htm?_minify=false&url=',
44		proxyPath = null,
45		prerollPathPattern = window.location.protocol+'//videos2.dealer.com/clients/%INIT%/%ID%/preroll.xml',
46		postrollPathPattern = window.location.protocol+'//videos2.dealer.com/clients/%INIT%/%ID%/postroll.xml',
47		skinsPath = ddcHostName + 'global/media/js/ddc/apis/videoplayer/v2/jwplayer/v6.5/skins/',
48		localeSkins = {
49			'glowddc' : {
50				'en_US' : skinsPath + 'glowddc/glowddc_en.xml',
51				'en_CA' : skinsPath + 'glowddc/glowddc_en.xml',
52				'es_US' : skinsPath + 'glowddc/glowddc_es.xml',
53				'fr_CA' : skinsPath + 'glowddc/glowddc_fr.xml'
54			}
55		},
56		jwDefaults = {
57			primary : (isIE() && isIE() < 10) ? 'flash' : 'html5',
58			flashplayer : flashplayerPath,
59			html5player : html5playerPath,
60			width : '100%',
61			aspectratio: '4:3',
62			stretching : 'uniform',
63			skin : localeSkins.glowddc.en_US
64		},
65		// note these wrapper defaults will take priority over data attributes of target element, so leave undefined to override
66		// keep all lowercase for normalization with element data attributes
67		// see DDCVideoWrapper definition for more details
68		defaults = {
69			target: '.ddcPlayer-holder',
70			targetid : undefined,
71			playerid : undefined,
72			wrapperelement: '<div class="ddc-video-wrapper" />',
73			accountid : undefined,
74			vehicleid : undefined,
75			videourl : undefined,
76			ddcplaylist : undefined,
77			useprepostroll : undefined,
78			prerollplaylist : undefined,
79			postrollplaylist : undefined,
80			locale : undefined, // if set, uses the appropriate version of the localeskin below, rather than from the jwsettings
81			localeskin : 'glowddc',
82			jwplaylist : [],
83			jwsettings: {}
84		},
85		jwPlayerScriptPromise,
86		trackerSettings = {
87			ddc : {
88				viewTypeVideo : 16
89			},
90			ga : {
91				id : 'UA-9899509-3',
92				category : 'Video'
93			}
94		},
95		tracker = {
96			ddc : undefined,
97			ga : undefined
98		};
99		// use absolute paths for third party (CarSales) support
100
101
102	// ************************************
103	// DDC.Video exposed methods and properties
104	// ************************************
105
106	module.version = version;
107	module.jwplayerPath = jwplayerPath;
108	module.proxyPath = proxyPath;
109	module.localeSkins = localeSkins;
110	module.jwDefaults = jwDefaults;
111	module.wrappers = [];
112
113	// hoisted from function definitions below
114	module.init = initEachWrapper;
115	module.embed = embedEachWrapper;
116	module.onReady = onReadyEachWrapper;
117	module.getWrapper = getWrapper;
118	module.getAllWrappers = getAllWrappers;
119	module.getPlayer = getPlayer;
120	module.getAllPlayers = getAllPlayers;
121	module.getAllPlayerIds = getAllPlayerIds;
122
123	/**
124	* Remove all players cleanly when unloading page
125	* Chrome has buffering errors JIRA-31593
126	*/
127	window.onbeforeunload = function () {
128		var allplayers = [];
129		try {
130			allplayers = DDC.Video.getAllPlayers();
131
132			for (var i = 0; i < allplayers.length; i++) {
133				allplayers[i].remove();
134			}
135		} catch (e) {
136			if (window.console && window.console.log) {
137				console.log('DDC.Video unable to clean up players.');
138			}
139		}
140	};
141
142	/**
143	* Simple IE check
144	*/
145	function isIE() {
146		var userAgent = navigator.userAgent.toLowerCase();
147		return (userAgent.indexOf('msie') != -1) ? parseInt(userAgent.
147split('msie')[1], 10) : false;
148	}
149
150	/**
151	* Ajax Load JWPlayer Scripts
152	*/
153	function getJWPlayerScript() {
154		var deferred = $.Deferred();
155
156		// Reference for handlers which depend on jwplayer being available
157		jwPlayerScriptPromise = deferred.promise();
158
159		$.ajax({url: DDC.Video.jwplayerPath,
160			dataType: 'script',
161			cache: true
162		}).fail(function (jqXHR, textStatus, errorThrown) {
163			if (window.console && window.console.log) {
164				console.log('Videoplayer unable to load jwplayer script');
165			}
166			deferred.reject();
167		}).done(function (jqXHR, textStatus) {
168			window.jwplayer.key = jwPlayerkey;
169			deferred.resolve();
170		});
171	}
172
173	/**
174	* Constructor for the wrapper object
175	*
176	* @param Object options An object literal containing key/value pairs used by the Dealer.com JWPlayer wrapper.
177	* @option mixed target A jQuery selector or jQuery object reference of the target element (which will contain the video).
178	* @option string targetid Id for the element which will contain the player ('target' above takes precedence).
179	* @option string playerid id for the player
180	* @option string wrapperelement HTML string to wrap video player with (since jwPlayer API gives no control over internal wrapper elements) 
181	* @option string accountid DDC account id used to get pre/post roll paths
182	* @option string vehicleid DDC vehicle id (currently unused, but included from legacy v8 player)
183	* @option string ddcplaylist Path to a Dealer.com XML playlist file listing video assests for video player.
184	* @option string useprepostroll Flag to indicate whether to try to load pre/post roll playlists.
185	* @option string prerollplaylist Path to a Preroll XML playlist file listing video assests for video player.
186	* @option string postrollplaylist Path to a Postroll XML playlist file listing video assests for video player.
187	* @option string videourl direct URL for a video file; only used if a DDC playlist is not provided.
188	* @option string locale language for text controls: en_US, es_US, en_CA, fr_CA
189	* @option Object jwplaylist A jwPlayer playlist object which will get used in the jwsettings at embed time
190	* @option Object jwsettings A jwPlayer options object (http://www.longtailvideo.com/support/jw-player/28839/embedding-the-player/)
191	*/
192	var DDCVideoWrapper = function (options) {
193		this.options = options;
194		this.playlistPromise = undefined;
195		this.mainPlaylistPromise = undefined;
196		this.prerollPlaylistPromise = undefined;
197		this.postrollPlaylistPromise = undefined;
198		this.hasPreroll  = undefined;
199		this.jwPlayerInstance = undefined;
200		this.playerState = 'created'; // change based on jwPlayer ready state. Either 'created', 'initializing', 'ready'
201	};
202
203	DDCVideoWrapper.prototype.init = init;
204	DDCVideoWrapper.prototype.embed = embed;
205	DDCVideoWrapper.prototype.onEmbed = onEmbed;
206	DDCVideoWrapper.prototype.onReady = onReady;
207
208	/**
209	* Constructor for collection of wrappers created as a batch
210	*
211	* This maps the basic methods 'init', 'embed', 'onReady' to versions of these functions
212	* which iterate over each wrapper in the collection. 'doFnEachWrapper' is a generic function which gets used to do this.
213	*
214	* @param Object options (See DDCVideoWrapper) shared in all wrappers in this collection
215	*/
216	var DDCVideoWrapperCollection = function (options) {
217		this.wrappers = [];
218		this.options = options;
219	};
220
221	DDCVideoWrapperCollection.prototype.addWrapper = addWrapper;
222	DDCVideoWrapperCollection.prototype.getWrapper = getWrapper;
223	DDCVideoWrapperCollection.prototype.init = initEachWrapper;
224	DDCVideoWrapperCollection.prototype.embed = embedEachWrapper;
225	DDCVideoWrapperCollection.prototype.onReady = onReadyEachWrapper;
226	DDCVideoWrapperCollection.prototype.doFnEachWrapper = doFnEachWrapper;
227
228	/**
229	* Put a wrapper into the collection
230	* (either global Video context or for given DDCVideoWrapperCollection)
231	*
232	* @param DDCVideoWrapper wrapperObj wrapper to add
233	*/
234	function addWrapper(wrapperObj) {
235		var wrapperAdded;
236
237		if (wrapperObj instanceof DDCVideoWrapper && !this.getWrapper(wrapperObj.options.targetid)) {
238			this.wrappers.push(wrapperObj);
239			wrapperAdded = wrapperObj;
240		}
241
242		return wrapperAdded;
243	}
244
245	/**
246	* Get a wrapper from the collection by its target id, the player id or a jquery reference
247	* (either in global DDC.Video context or for given DDCVideoWrapperCollection)
248	*
249	* @param mixed reference 
250	*/
251	function getWrapper(reference) {
252
253		var refId,
254			wrapper;
255
256		if (typeof reference === 'string') {
257			refId = reference;
258		} else if (reference instanceof jQuery) {
259			refId = reference.attr('id');
260		}
261
262		if (reference) {
263			for (var i = this.wrappers.length - 1; i >= 0; i--) {
264				if ((this.wrappers[i].options.targetid === refId) || (this.wrappers[i].options.playerid === refId)) {
265					wrapper = this.wrappers[i];
266					break;
267				}
268			}
269		}
270
271		return wrapper;
272	}
273
274	/**
275	* Get array of all wrappers registered globally
276	*
277	*/
278	function getAllWrappers() {
279
280		return this.wrappers;
281	}
282
283	/**
284	* Get a jwplayer instance from id
285	* (either global Video context or for given DDCVideoWrapperCollection)
286	*
287	* @param mixed reference
288	*/
289	function getPlayer(reference) {
290		var refId,
291			wrapper,
292			player;
293
294		if (typeof reference === 'string') {
295			refId = reference;
296		} else if (reference instanceof jQuery) {
297			refId = reference.attr('id');
298		}
299
300		wrapper = this.getWrapper(reference);
301		if (wrapper) {
302			player = wrapper.jwPlayerInstance;
303		}
304
305		// fallback to jwplayer built-in method
306		if (!player && window.jwplayer) {
307			player = window.jwplayer(refId);
308		}
309
310		return player;
311	}
312
313	/**
314	* Get array of all jwplayer instances registered globally
315	*
316	*/
317	function getAllPlayers() {
318		var players = [];
319		for (var i = this.wrappers.length - 1; i >= 0; i--) {
320			if (this.wrappers[i].jwPlayerInstance) {
321				players.push(this.wrappers[i].jwPlayerInstance);
322			}
323		}
324
325		return players;
326	}
327
328	/**
329	* Get array of all jwplayer ids registered globally
330	*
331	*/
332	function getAllPlayerIds() {
333		var playerIds = [];
334		for (var i = this.wrappers.length - 1; i >= 0; i--) {
335			playerIds.push(this.wrappers[i].options.playerid);
336		}
337
338		return playerIds;
339	}
340
341	/**
342	* Wrapper function which routes init method through doFnEachWrapper 
343	*
344	* @param Object options (See DDCVideoWrapper)
345	* @return DDCVideoWrapperCollection
346	*/
347	function initEachWrapper(options) {
348		// ************************************
349		// Init Each Wrapper
350		// ************************************
351		return doFnEachWrapper.call(this, init, options);
352	}
353
354	/**
355	* Wrapper function which routes embed method through doFnEachWrapper 
356	*
357	* @param Object options (See DDCVideoWrapper)
358	* @return DDCVideoWrapperCollection
359	*/
360	function embedEachWrapper(options) {
361		// ************************************
362		// Embed Each Wrapper
363		// ************************************
364		return doFnEachWrapper.call(this, embed, options);
365	}
366
367	/**
368	* This is an abstraction to allow for some reuse between initEachWrapper() and embedEachWrapper()
369	* If these two functions diverge, the stubs above could become distinct methods
370	* 
371	* @param Function fn A DDCVideoWrapper method which to call on each wrapper instance
372	* @param Object options (See DDCVideoWrapper)
373	* @return DDCVideoWrapperCollection current collection, or a new one if not set
374	*/
375	function doFnEachWrapper(fn, options) {
376		var collection,
377			$target;
378
379		// Normalize fn to function reference, if passed as a string
380		if (typeof fn === 'string') {
381			if (DDCVideoWrapper.prototype[fn] && typeof DDCVideoWrapper.prototype[fn] === 'function') {
382				fn = DDCVideoWrapper.prototype[fn];
383			}
384		}
385
386		// Get jwPlayer script if not already loaded
387		if (!window.jwplayer) {
388			getJWPlayerScript();
389		}
390
391		// ************************************
392		// Set up Tracking
393		// ************************************
394
395		if (!tracker.ga && !tracker.ddc) {
396			tracker = configureTracker(tracker, trackerSettings);
397		}
398
399		// ************************************
400		// Instantiate Collection Object
401		// ************************************
402
403		if (this instanceof DDCVideoWrapperCollection) {
404			collection = this;
405			collection.options = mergeOptions(collection.options, options);
406		} else {
407			// populate with defaults
408			options = mergeOptions(defaults, options);
409			collection = new DDCVideoWrapperCollection(options);
410		}
411
412		// ************************************
413		// Acquire $target
414		// ************************************
415
416		$target = acquireTarget(collection.options);
417
418		// ************************************
419		// Delegate Fn calls per target
420		// (init or embed)
421		// ************************************
422
423		if ($target && $target.length > 0) {
424			$target.each(function (i, targetEl) {
425				var itemWrapperInstance,
426					itemOptions = $.extend(true, {}, collection.options),
427					$itemTarget = $(targetEl),
428					// 0 indexed counter for the player Ids: ddc-jwplayer-0, ...
429					defaultPlayerId = ('ddc-jwplayer-' + DDC.Video.wrappers.length);
430
431				// If not set, retrieve or generate unique targetid
432
433				if (!itemOptions.targetid) {
434					if ($itemTarget.attr('id')) {
435						itemOptions.targetid = $itemTarget.attr('id');
436					} else {
437						itemOptions.targetid = defaultPlayerId + '-target';
438						$itemTarget.attr('id', itemOptions.targetid);
439					}
440				}
441
442				// Reset the existing 'target' to be this specific unique element - otherwise the generic selector will remain on these options and it will pick up multiple targets, in the individual video init calls
443				itemOptions.target = $itemTarget;
444
445				// Override an existing wrapper object, if there is one
446				itemWrapperInstance = DDC.Video.getWrapper(itemOptions.targetid);
447
448				// Or create a new one
449				if (!itemWrapperInstance) {
450
451					if (!itemOptions.playerid) {
452						itemOptions.playerid = defaultPlayerId;
453					}
454
455					itemWrapperInstance = new DDCVideoWrapper(itemOptions);
456					collection.addWrapper(itemWrapperInstance);
457					// add to global collection as well. method isn't exposed on DDC.Video, so using call()
458					addWrapper.call(DDC.Video, itemWrapperInstance);
459				}
460
461				// Try to call argument 'fn' (init or embed) on the wrapper instance
462				if (typeof fn === "function") {
463					try {
464						fn.call(itemWrapperInstance, itemOptions);
465					} catch (e) {
466						if (window.console && window.console.log) {
467							console.log('Not able to run function on wrapper');
468						}
469					}
470				}
471			});
472		} else {
473			// No targets. May not exist in DOM yet on init, and could exist later when call embed.
474			// TODO - to be most efficient, playlist ajax calls should be made on init even without $target as long as playlist paths are defined in options (as opposed to in data attribs)
475		}
476
477		return collection;
478	}
479
480	/**
481	* Unlike previous this is NOT a wrapper to doFnEachWrapper
482	* It passes a function which is run on each wrapper's onReady callback 
483	* So the callback function will repeat once per wrapper 
484	*
485	* @param Function fn A user-defined callback function
486	* @return DDCVideoWrapperCollection
487	*/
488	function onReadyEachWrapper(fn) {
489		var collection;
490
491		// ************************************
492		// Instantiate Collection Object
493		// ************************************
494
495		if (this instanceof DDCVideoWrapperCollection) {
496			collection = this;
497		} else {
498			collection = new DDCVideoWrapperCollection();
499			collection.wrappers = [];
500			collection.init();
501		}
502
503		for (var i = this.wrappers.length - 1; i >= 0; i--) {
504			if (collection.wrappers[i] instanceof DDCVideoWrapper) {
505				collection.wrappers[i].onEmbedReadyCallback = fn;
506			}
507		}
508
509		return collection;
510	}
511
512	/**
513	* Extend "Base Options" (usually either collection level options or defaults) with more specific player options
514	*
515	* @param Object baseOptions (See DDCVideoWrapper)
516	* @param Object options (See DDCVideoWrapper)
517	* @return Object merged clone of options merged with base group options
518	*/
519	function mergeOptions(baseOptions, options) {
520		var merged = {};
521
522		if (baseOptions && typeof baseOptions === "object") {
523			baseOptions = normalizeOptions(baseOptions);
524		} else {
525			baseOptions = {};
526		}
527
528		if (options && typeof options === "object") {
529			options = normalizeOptions(options);
530		} else {
531			options = {};
532		}
533
534		// deep extend the remaining options, so as to properly merge nested jwsettings, if any
535		$.extend(true, merged, baseOptions, options);
536
537		return merged;
538	}
539
540	/**
541	* Lowercase all option names to disambiguate and simplify merging
542	* Also catch certain synonymous option names
543	*
544	* @param Object options (See DDCVideoWrapper)
545	* @return Object normOptions normalized values
546	*/
547	function normalizeOptions(options) {
548		var normOptions = {},
549			normOption;
550
551		for (var option in options) {
552			if (options.hasOwnProperty(option)) {
553				// normalize case - all options are lowercase
554				normOption = option.toString().toLowerCase();
555
556				// trap naming variants
557				switch (normOption) {
558
559				case 'autoplay':
560					if (options[option] === true || options[option] === 'true') {
561						normOptions.autostart = true;
562					}
563					break;
564
565				case 'divname':
566					normOptions.targetid = options[option];
567					break;
568
569				case 'height':
570				case 'width':
571					// specify without 'px' units, but '%' is allowed
572					normOptions[normOption] = (options[option].toString().indexOf('%') > 0) ? parseInt(options[option], 10) + '%' : parseInt(options[option], 10).toString();
573					break;
574
575				case 'ddcplaylist':
576				case 'ddcplaylisturl':
577				case 'playlistxmlpath':
578					normOptions.ddcplaylist = getProxiedPath(options[option]);
579					break;
580
581				case 'postrollxmlpath':
582					normOptions.postrollplaylist = getProxiedPath(options[option]);
583					break;
584
585				case 'prerollxmlpath':
586					normOptions.prerollplaylist = getProxiedPath(options[option]);
587					break;
588
589				case 'showcontrols':
590					normOptions.controls = options[option];
591					break;
592
593				case 'allowfullscreen':
594				case 'calltoactionpath':
595				case 'overlaypath':
596				case 'showplaylist':
597				case 'swfid':
598					//unused legacy options
599					break;
600
601				default:
602					normOptions[normOption] = options[option];
603					break;
604				}
605			}
606		}
607
608		return normOptions;
609	}
610
611	/**
612	* Look up label translations
613	*
614	* @param String label
615	* @return String translated
616	*/
617	var getLabel = function (label) {
618		var i18n = (window.DDC.i18n || {}),
619			translated = label;
620
621		if (i18n.getLabel) {
622			translated = DDC.i18n.getLabel(label);
623		}
624
625		if (!translated) {
626			translated = label;
627		}
628
629		return translated;
630	};
631
632	/**
633	* Tease out jwPlayer settings from wrapper options
634	* With fallback to jwDefaults
635	*
636	* @param Object options (See DDCVideoWrapper)
637	* @param Object jwDefaults a jwPlayer settings object
638	* @return Object jwSettings a jwPlayer settings object
639	*/
640	function getJWSettings(options, jwDefaults) {
641		var jwSettings = {},
642			localeSkins;
643
644		// Set the exact skin file for the locale, if available
645		// Proxied skin files won't get handled properly by the jwplayer, so not attemptin to go through proxy
646		if (options.locale && options.localeskin) {
647			// check if the localeSkins have been overridden
648			localeSkins = DDC.Video.localeSkins;
649			if (localeSkins.hasOwnProperty(options.localeskin) && localeSkins[options.localeskin].hasOwnProperty(options.locale)) {
650				options.jwsettings.skin = localeSkins[options.localeskin][options.locale];
651			}
652		}
653
654		// Transfer any wrapper options which apply into the jwplayer settings
655		for (var option in options) {
656			if (option === 'jwsettings') {
657				$.extend(true, jwSettings, options.jwsettings);
658			} else if (option === 'jwplaylist') {
659				jwSettings.playlist = options.jwplaylist;
660			} else {
661				// if option not defined in the defaults, assume we are dealing with a jwPlayer setting and grab it
662				if (options.hasOwnProperty(option) && !defaults.hasOwnProperty(option)) {
663					jwSettings[option] = options[option];
664				}
665			}
666		}
667
668		jwSettings = $.extend(true, {}, jwDefaults, jwSettings);
669
670		return jwSettings;
671	}
672
673	/**
674	* Find the DOM element to attach the video player to  
675	*
676	* @param Object options (See DDCVideoWrapper)
677	* @param Object jwPlayerInstance jwPlayer object
678	* @return jQuery $target
679	*/
680	function acquireTarget(options, wrapperInstance) {
681		var $target = $(),
682			target,
683			targetid,
684			alphaRegex = /[A-Za-z]/;
685
686		if (typeof options == "object") {
687			target = options.target;
688			targetid = options.targetid;
689
690			// if instance already exists, target it
691			if (wrapperInstance && wrapperInstance.jwPlayerInstance) {
692				if (wrapperInstance.options.target instanceof jQuery) {
693					$target = wrapperInstance.options.target;
694				} else {
695					// can't target existing video player
696				}
697			} else if (target instanceof jQuery) {
698				// param is already jQuery reference
699				$target = target;
700			} else if (targetid) {
701				// jwPlayer html5 mode is strict about invalid ids, and will not embed if it's an invalid id
702				if (alphaRegex.test(targetid[0])) {
703					$target = $('#' + targetid);
704				} else {
705					// invalid element id
706				}
707			} else if (target) {
708				// try to select element via jQuery
709				$target = $(target);
710			} else {
711				if (window.console && window.console.log) {
712					console.log('Videoplayer unable to find embed target.');
713				}
714			}
715		}
716
717		return $target;
718	}
719
720	/**
721	* Take options and load playlist files to prepare video for embedding.
722	*
723	* @param Object options See DDCVideoWrapper
724	* @return DDCVideoWrapper
725	*/
726	function init(options) {
727		// return object is a wrapper instance (for chaining methods)
728		var wrapperInstance,
729			$target,
730			simpleFormatObj = {'file' : undefined},
731			prerollPlaylist,
732			mainPlaylist,
733			postrollPlaylist;
734
735		// ************************************
736		// Instantiate Wrapper Object
737		// ************************************
738
739		if (this instanceof DDCVideoWrapperCollection) {
740			this.initAll(options);
741			wrapperInstance = null;
742		} else if (this instanceof DDCVideoWrapper) {
743			wrapperInstance = this;
744			wrapperInstance.options = mergeOptions(this.options, options);
745		} else {
746			// New instance, use the DDCVideoWrapper definition and register in the outer scope list
747			wrapperInstance = new DDCVideoWrapper(options);
748		}
749
750		if (wrapperInstance) {
751			// ************************************
752			// Acquire $target
753			// ************************************
754
755			// Note that it is not critical that the target element be available at init, only at embed
756			// But $target data attribute may hold the video paths extracted by buildPlaylist, so need to check for it
757			$target = acquireTarget(wrapperInstance.options, wrapperInstance);
758
759			// ************************************
760			// Get Any Target Options from Data Attr
761			// ************************************
762
763			// sanity check, jwplayer can only have one target
764			if ($target && $target.length == 1) {
765				// nested mergeOptions statement to ensure proper overrides: manual options > data attributes > wrapper defaults
766				wrapperInstance.options = mergeOptions(wrapperInstance.options, mergeOptions(parseTargetVideoOptions($target), options));
767			}
768
769			// ************************************
770			// Get playlists
771			// ************************************
772
773			if (wrapperInstance.options.videourl) {
774
775				simpleFormatObj.file = wrapperInstance.options.videourl;
776
777				wrapperInstance.options.jwplaylist.push(simpleFormatObj);
778
779			} else if (!wrapperInstance.playlistPromise) {
780				// make sure we haven't already requested the playlists (in case of rapid repeat init calls)
781
782				// MAIN PLAYLIST
783				wrapperInstance.mainPlaylistPromise = loadPlaylist(wrapperInstance.options.ddcplaylist, wrapperInstance.options.locale);
784
785				// PRE / POST ROLL PLAYLISTS
786				if (wrapperInstance.options.useprepostroll || wrapperInstance.options.postrollplaylist || wrapperInstance.options.prerollplaylist) {
787
788					if (!wrapperInstance.options.prerollplaylist) {
789						prerollPlaylist = getProxiedPath(prerollPathPattern.replace('%INIT%', wrapperInstance.options.accountid.slice(0, 1)).replace('%ID%', wrapperInstance.options.accountid));
790						// for example http://videos2.dealer.com/clients/d/ddcdemo/preroll.xml
791					} else {
792						prerollPlaylist = getProxiedPath(wrapperInstance.options.prerollplaylist);
793					}
794
795					if (!wrapperInstance.options.postrollplaylist) {
796						postrollPlaylist = getProxiedPath(postrollPathPattern.replace('%INIT%', wrapperInstance.options.accountid.slice(0, 1)).replace('%ID%', wrapperInstance.options.accountid));
797						// for example http://videos2.dealer.com/clients/d/ddcdemo/postroll.xml
798					} else {
799						postrollPlaylist = getProxiedPath(wrapperInstance.options.postrollplaylist);
800					}
801
802					// PREROLL PLAYLIST
803					if ($.cookie && $.cookie('prerollViewed')) {
804						wrapperInstance.prerollPlaylistPromise = $.Deferred().resolve();
805					} else {
806						wrapperInstance.prerollPlaylistPromise = loadPlaylist(prerollPlaylist, wrapperInstance.options.locale, simpleFormatObj);
807					}
808
809					// POSTROLL PLAYLIST
810					wrapperInstance.postrollPlaylistPromise = loadPlaylist(postrollPlaylist, wrapperInstance.options.locale, simpleFormatObj);
811
812					// When all the promises resolve
813					wrapperInstance.playlistPromise = $.when(wrapperInstance.prerollPlaylistPromise, wrapperInstance.mainPlaylistPromise, wrapperInstance.postrollPlaylistPromise).done(function (preVideoList, mainVideoList, postVideoList) {
814						var playlist = [];
815						if (mainVideoList && mainVideoList.file) {
816							if (preVideoList && preVideoList.file) {
817								preVideoList.image = mainVideoList.image;
818								wrapperInstance.hasPreroll = true;
819								playlist.push(preVideoList);
820							}
821
822							playlist.push(mainVideoList);
823
824							if (postVideoList && postVideoList.file) {
825								playlist.push(postVideoList);
826							}
827						}
828						wrapperInstance.options.jwplaylist = playlist;
829					});
830
831				} else {
832					wrapperInstance.playlistPromise = $.when(wrapperInstance.mainPlaylistPromise).done(function (mainVideoList) {
833						var playlist = [];
834						if (mainVideoList && mainVideoList.file) {
835							playlist.push(mainVideoList);
836							wrapperInstance.options.jwplaylist = playlist;
837						}
838					});
839				}
840			} // end if playlistPromise defined
841		} // end if wrapperInstance
842
843		return wrapperInstance;
844	}
845
846	/**
847	* Utility function to get the path using the proxyPath config (or not, if on same domain)
848	*
849	* @param String path path to transform
850	* @return String proxiedPath
851	*/
852	function getProxiedPath(path) {
853		var proxiedPath;
854
855		if (typeof path == 'string') {
856			if ((path.indexOf('http:') !== 0 && path.indexOf('https:') !== 0) || (path.indexOf(window.location.hostname.toString()) === 0) || !DDC.Video.proxyPath) {
857				// no need to proxy if on same domain
858				proxiedPath = path;
859			} else {
860				proxiedPath = DDC.Video.proxyPath + encodeURIComponent(path);
861			}
862
863			// videos.dealer.com CDN requires the %20 escape, rather than '+' separator
864			proxiedPath = proxiedPath.replace(/\+/g, '%20');
865		}
866
867		return proxiedPath;
868	}
869
870	/**
871	* Utility function to get the original path back out of a proxiedPath
872	*
873	* @param String path path to transform
874	* @param String path proxyPath proxy path to remove from the main path arg 
875	* @return String proxiedPath
876	*/
877	function getOriginalPath(path) {
878		var originalPath;
879
880		if (typeof path == 'string') {
881			if (path.indexOf(DDC.Video.proxyPath) === 0) {
882				originalPath = decodeURIComponent(path.substring(DDC.Video.proxyPath.length, path.length + 1));
883			} else {
884				originalPath = path;
885			}
886
887			// videos.dealer.com CDN requires the %20 escape, rather than '+' separator
888			originalPath = originalPath.replace(/\+/g, '%20');
889		}
890
891		return originalPath;
892	}
893
894	/**
895	* Given an element, try to get the video options out of its attributes
896	* Normalize the options to account for naming variation across systems
897	*
898	* @param Object $target jQuery element
899	* @return Object normOptions
900	*/
901	function parseTargetVideoOptions($target) {
902		var attr,
903			data = {},
904			accountId,
905			autoplay,
906			ddcPlayerInfo,
907			ddcPlayerInfoArray,
908			normOptions = {},
909			normOption;
910
911		// ************************************
912		// Raw Attributes
913		// ************************************
914
915		ddcPlayerInfo = $target.attr('ddcplayerinfo');
916
917		// used to help parse the ddcplayerinfo dash separated values
918		accountId = $target.attr('hyphenatedaccountId');
919
920		// used to fix V8 auto play issue
921		autoplay = $target.attr('autoplay');
922
923		// ************************************
924		// Data Attributes
925		// ************************************
926
927		data = $target.data();
928
929		normOptions = normalizeOptions(data);
930
931		accountId = accountId || normOptions.accountid;
932		ddcPlayerInfo = ddcPlayerInfo || normOptions.ddcplayerinfo;
933		normOptions.autoplay = autoplay || normOptions.autoplay;
934
935		// data-ddcplayerinfo has an internal structure which needs to be parsed:
936		// primary video information, of form: "[ddcplaylist]-[width]-[height]"
937		if (ddcPlayerInfo) {
938			if (accountId) {
939				ddcPlayerInfo = ddcPlayerInfo.replace(accountId, "<siteId>");
940			}
941
942			ddcPlayerInfoArray = ddcPlayerInfo.split('-');
943
944			if (ddcPlayerInfoArray[0]) {
945				normOptions.ddcplaylist = (accountId) ? ddcPlayerInfoArray[0].replace("<siteId>", accountId) : ddcPlayerInfoArray[0];
946				// ddc playlist info is URI Encoded
947				normOptions.ddcplaylist = getProxiedPath(decodeURIComponent(normOptions.ddcplaylist));
948			}
949
950			normOptions.width = $target.get(0).style.width || ddcPlayerInfoArray[1];
951			normOptions.height = $target.get(0).style.height || ddcPlayerInfoArray[2];
952		}
953
954		return normOptions;
955	}
956
957	/**
958	* Make a call to the appropriate playlistUrl, load the xml file and convert it to the playlist format jwPlayer uses
959	*
960	* @param string playlistUrl path to the desired playlist xml
961	* @param string locale which locale to use for UI translation 
962	* @param Object formatObj a customized playlist format object to use instead of the default format
963	* @return Promise playlistPromise jQuery deferred promise object
964	*/
965	function loadPlaylist(playlistUrl, locale, formatObj) {
966		var deferred = $.Deferred(),
967			playlistPromise = deferred.promise();
968
969		if (playlistUrl) {
970			// Ajax load the xml playlist & convert to jwPlayer playlist format
971			// dataType 'text' because need to strip leading whitespace in xml file and custom parse to JSON
972			// the default xml converters fail (due to whitespace issues?)
973			$.ajax({
974				url: ("http:" == playlistUrl.slice(0, 5) ? (window.location.protocol + playlistUrl.slice(5)) : playlistUrl),
975				dataType: 'text'
976				
977			}).fail(function (jqXHR, textStatus, errorThrown) {
978				if (window.console && window.console.log) {
979					console.log('Videoplayer unable to load playlist ' + playlistUrl + ' (' + getOriginalPath(playlistUrl) + '): ' + errorThrown);
980				}
981			}).complete(function (jqXHR, textStatus) {
982				var convertedPlaylist;
983				if (textStatus == "success") {
984					convertedPlaylist = convertDDCPlaylist(jqXHR.responseText, playlistUrl, locale, formatObj);
985					deferred.resolve(convertedPlaylist);
986				} else {
987					// failed, but still allow the promise to resolve because we don't want a failed preroll or postroll to prevent the main video from playing
988					deferred.resolve();
989				}
990			});
991		}
992
993		return playlistPromise;
994	}
995
996	/**
997	* Take plain text return from playlist load and try to convert to XML and parse items
998	*
999	* @param string data Raw xml text from ajax request
1000	* @param string playlistUrl path used to determine the asset path and switch betwen DDC and Carsales playlist formats
1001	* @param Object formatObj a customized playlist format object to use instead of the default format
1002	* @return Object playlist for jwPlayer options
1003	*/
1004	function convertDDCPlaylist(data, playlistUrl, locale, formatObj) {
1005		var dataXML,
1006			assetPath,
1007			vidSeg,
1008			vidLoSource,
1009			vidHiSource,
1010			vidSource,
1011			vidThumb,
1012			vidCaptionSrt,
1013			labelHighQuality = (DDC.i18n) ? getLabel("VIDEOPLAYER_QUALITY_HIGH") : "High",
1014			labelLowQuality = (DDC.i18n) ? getLabel("VIDEOPLAYER_QUALITY_LOW") : "Low",
1015			labelCaptionOn = (DDC.i18n) ? getLabel("VIDEOPLAYER_CAPTIONS_ON") : "On",
1016			format = formatObj || {
1017				file: vidSource,
1018				image: vidThumb,
1019				sources: [{
1020					file: vidHiSource,
1021					label: labelHighQuality
1022				},
1023					{
1024					file: vidLoSource,
1025					label: labelLowQuality
1026				}],
1027				tracks: [{
1028					file: vidCaptionSrt,
1029					label: labelCaptionOn,
1030					kind: "captions"
1031				}]
1032			};
1033
1034		// jwPlayer playlist to return
1035		var playlist = {};
1036
1037		try {
1038			//strip top and bottom whitespace - don't use trim() need to support IE7/IE8
1039			dataXML = $.parseXML(data.replace(/^\s+|\s+$/g, ''));
1040		} catch (e) {
1041			if (window.console && window.console.log) {
1042				console.log('Videoplayer error reading XML file', e, data);
1043			}
1044			dataXML = null;
1045		}
1046		vidSeg = $(dataXML).find('segment');
1047
1048		// ************************************
1049		// Parse XML Playlist
1050		// ************************************
1051
1052		// The playlist XML structure is different, based on CarSales versus DDC
1053		if (playlistUrl.indexOf('vidauth.au') > -1) {
1054			// CarSales Playlist structure - simple de-entitize
1055			if (vidSeg.length > 0) {
1056				vidHiSource = vidSeg.find('h264HighSource').text().replace(/&amp;/g, '&');
1057				vidLoSource = vidSeg.find('h264LowSource').text().replace(/&amp;/g, '&');
1058				vidSource = vidHiSource;
1059				vidThumb = vidSeg.find('largeThumb').text().replace(/&amp;/g, '&');
1060				vidCaptionSrt = ''; // no captions for Carsales format playlists
1061			}
1062		} else {
1063			// DDC default Playlist structure
1064			// get the absolute path to serve the media, since paths not specified in some (virtualTour) playlists
1065
1066			assetPath = getOriginalPath(playlistUrl);
1067			assetPath = assetPath.substring(0, assetPath.lastIndexOf("/") + 1);
1068
1069			if (vidSeg.length > 0) {
1070
1071				vidHiSource = vidSeg.attr('h264_high_src') || vidSeg.attr('flv_high_src') || '';
1072				if (vidHiSource && vidHiSource.indexOf('http:') !== 0 && vidHiSource.indexOf('https:') !== 0) { vidHiSource = assetPath + vidHiSource; }
1073
1074				vidLoSource = vidSeg.attr('h264_low_src') || vidSeg.attr('flv_low_src') || '';
1075				if (vidLoSource && vidLoSource.indexOf('http:') !== 0 && vidLoSource.indexOf('https:') !== 0) { vidLoSource = assetPath + vidLoSource; }
1076
1077				vidSource = vidHiSource || vidLoSource || undefined;
1078
1079				vidThumb = vidSeg.attr('lrgThumb') || '';
1080				if (vidThumb && vidThumb.indexOf('http:') !== 0 && vidThumb.indexOf('https:') !== 0) { vidThumb = assetPath + vidThumb; }
1081
1082				vidCaptionSrt = vidSeg.attr('srt') || '';
1083				// proxy required for captions file
1084				if (vidCaptionSrt) {
1085					if (vidCaptionSrt.indexOf('http:') !== 0 && vidCaptionSrt.indexOf('https:') !== 0) {
1086						vidCaptionSrt = getProxiedPath(assetPath + vidCaptionSrt);
1087					} else {
1088						vidCaptionSrt = getProxiedPath(vidCaptionSrt);
1089					}
1090				} else {
1091					// no captions found
1092				}
1093			}
1094		}
1095
1096		// ************************************
1097		// Construct jwPlayer Playlist
1098		// Based on format object -- roughly
1099		// TODO Could be fine-tuned for custom formats
1100		// ************************************
1101
1102		if (vidSource && format.hasOwnProperty('file')) {
1103			playlist.file = vidSource;
1104		}
1105		if (vidThumb && format.hasOwnProperty('image')) {
1106			playlist.image = vidThumb;
1107		}
1108		if (vidHiSource && vidLoSource && format.hasOwnProperty('sources')) {
1109			playlist.sources = [{
1110					file: vidHiSource,
1111					label: labelHighQuality
1112				},
1113					{
1114					file: vidLoSource,
1115					label: labelLowQuality
1116				}];
1117		}
1118		if (vidCaptionSrt && format.hasOwnProperty('tracks')) {
1119			playlist.tracks = [{
1120					file: vidCaptionSrt,
1121					label: labelCaptionOn,
1122					kind: "captions"
1123				}];
1124		}
1125
1126		return playlist;
1127	}
1128
1129	/**
1130	* embed function to instantiate the video player
1131	* (will call init automatically if needed)
1132	*
1133	* @param Object options See DDCVideoWrapper - will merge these extra options
1134	* @return DDCVideoWrapper
1135	*/
1136	function embed(options) {
1137		var wrapperInstance,
1138			target,
1139			$target,
1140			data,
1141			accountid;
1142
1143		// ************************************
1144		// Instantiate Wrapper Object
1145		// ************************************
1146
1147		if (this instanceof DDCVideoWrapperCollection) {
1148			this.embedAll(options);
1149			wrapperInstance = null;
1150		} else if (this instanceof DDCVideoWrapper) {
1151			wrapperInstance = this;
1152			// if we are overriding init options, re-run init
1153			if (options) {
1154				try {
1155					wrapperInstance.init(options);
1156				} catch (e) {
1157					if (window.console && window.console.log) {
1158						console.log('Videoplayer error initializing', e);
1159					}
1160				}
1161			}
1162		}
1163
1164		if (wrapperInstance) {
1165
1166			// ************************************
1167			// Acquire $target
1168			// ************************************
1169
1170			$target = acquireTarget(wrapperInstance.options, wrapperInstance);
1171
1172			// sanity check, jwplayer can only have one target
1173			if ($target && $target.length == 1) {
1174
1175				// ************************************
1176				// Get Any Target Options from Markup
1177				// ************************************
1178
1179				// nested mergeOptions statement to ensure proper overrides: manual options > data attributes > wrapper defaults
1180				wrapperInstance.options = mergeOptions(wrapperInstance.options, mergeOptions(parseTargetVideoOptions($target), options));
1181
1182				// ************************************
1183				// Embed JWPlayer
1184				// ************************************
1185
1186				// only run embed if playlist is configured and loaded
1187				// init takes care of this asynchronously with the playlist promise objects - make sure they are ready
1188				$.when(jwPlayerScriptPromise, wrapperInstance.playlistPromise).then(function () {
1189					wrapperInstance.onEmbed();
1190				});
1191
1192			} else if ($target && $target.length > 1) {
1193				// if there are multiple targets at this point, something is wrong and must run through init again (TODO)
1194				if (window.console && window.console.log) {
1195					console.log('Videoplayer has multiple conflicting video embed target ids');
1196				}
1197				
1198			} else {
1199				if (window.console && window.console.log) {
1200					console.log('Videoplayer could not find embed target element', $target);
1201				}
1202			}
1203		} else {
1204			// no wrapper
1205		}
1206
1207		return wrapperInstance;
1208	}
1209
1210	/**
1211	* Jquery Success function for the successful return of the jwplayer script (which will now provide jwwrapper in global scope)
1212	*
1213	* jwPlayer automatically embeds when it loads - there is no control over this timing in the API
1214	* so we need to ajax load the script every time we want to embed a video (luckily it gets cached)
1215	*
1216	* @param Function fn a user-defined callback function
1217	*/
1218	function onEmbed() {
1219		var wrapperInstance,
1220			jwSettings,
1221			$placeholder,
1222			player,
1223			playerid,
1224			$target,
1225			playlistPath,
1226			called = {
1227				'play' : false,
1228				'0' : false,
1229				'25' : false,
1230				'50' : false,
1231				'75' : false,
1232				'95' : false,
1233				'100' : false
1234			};
1235
1236		if (this instanceof DDCVideoWrapper && window.jwplayer && this.options.targetid && this.options.playerid) {
1237			wrapperInstance = this;
1238
1239			$target = acquireTarget(wrapperInstance.options, wrapperInstance);
1240			playerid = wrapperInstance.options.playerid;
1241
1242			// If this target already has a jwplayer video in it - remove that cleanly before embedding a new player over it
1243			if (wrapperInstance.jwPlayerInstance && wrapperInstance.jwPlayerInstance.hasOwnProperty('remove')) {
1244				try {
1245					wrapperInstance.jwPlayerInstance.remove();
1246				} catch (e) {
1247					// "Cannot call remove() before player is ready"
1248					// jwplayer will throw this error if the player is not fully instantiated:
1249					wrapperInstance.playerState = 'initializing';
1250				}
1251			}
1252
1253			// ************************************
1254			// INSTANTIATE JWPLAYER
1255			// ************************************
1256
1257			if (wrapperInstance.playerState != 'initializing') {
1258
1259				wrapperInstance.playerState = 'initializing';
1260
1261				// insert a temporary placeholder - which will be replaced by the jwplayer
1262				$placeholder = $('<div />').attr('id', playerid).addClass('jwplayer');
1263				$target.html($placeholder).addClass('jwplayer-loading');
1264
1265				$placeholder.wrap(wrapperInstance.options.wrapperelement);
1266
1267				jwSettings = getJWSettings(wrapperInstance.options, DDC.Video.jwDefaults);
1268
1269				player = wrapperInstance.jwPlayerInstance = window.jwplayer(playerid).setup(jwSettings);
1270
1271				player.onReady(function () {
1272					wrapperInstance.playerState = 'ready';
1273					wrapperInstance.playlistPromise = wrapperInstance.prerollPlaylistPromise = wrapperInstance.postrollPlaylistPromise = null;
1274					// jwplayer flash div wrapper doesn't have a .jwplayer class for some reason, so make it consistent with html version for styling
1275					if (this.getRenderingMode() == 'flash') {
1276						$(this.container).parent().addClass('jwplayer jwplayer-flash');
1277					} else {
1278						$(this.container).addClass('jwplayer jwplayer-html5');
1279					}
1280					$target.removeClass('jwplayer-loading');
1281					if (wrapperInstance.onEmbedReadyCallback && typeof wrapperInstance.onEmbedReadyCallback === 'function') {
1282						try {
1283							wrapperInstance.onEmbedReadyCallback.call(player);
1284						} catch (e) {
1285							if (window.console && window.console.log) {
1286								console.log('Videoplayer error in onReady callback', e);
1287							}
1288						}
1289					}
1290				});
1291
1292				player.onSetupError(function (fallback, message) {
1293					wrapperInstance.playlistPromise = wrapperInstance.prerollPlaylistPromise = wrapperInstance.postrollPlaylistPromise = null;
1294					$('#' + playerid).addClass('jwplayer jwplayer-error');
1295					if (window.console && window.console.log) {
1296						console.log('Player setup error', fallback, message);
1297					}
1298				});
1299
1300				// ************************************
1301				// Item playback fail handler
1302				// ************************************
1303
1304				player.onError(function (message) {
1305					var myPlaylist,
1306						myIndex;
1307
1308					try {
1309						myPlaylist = this.getPlaylist();
1310						myIndex = this.getPlaylistIndex();
1311
1312						if (myPlaylist.length > 1) {
1313							// Remove bad playlist item and reload
1314							myPlaylist.splice(myIndex, 1);
1315							this.load(myPlaylist);
1316							this.play();
1317						}
1318
1319					} catch (e) {
1320						if (window.console && window.console.log) {
1321							console.log('Player error', message);
1322						}
1323					}
1324				});
1325
1326				// ************************************
1327				// Track Events
1328				// ************************************
1329
1330				playlistPath = getOriginalPath(wrapperInstance.options.ddcplaylist);
1331
1332				player.onPlay(function (e) {
1333					var currentPlaylistItem = this.getPlaylistItem(),
1334						currentPlaylistIndex = this.getPlaylistIndex(),
1335						currentPlayElement = this.getContainer().querySelector('video'),
1336						currentPosition = this.getPosition(),
1337						trackingData = {};
1338
1339					try {
1340						if (currentPlaylistIndex === 0 && wrapperInstance.hasPreroll && $.cookie) {
1341							$.cookie('prerollViewed', 'true', {expires: 1});
1342						}
1343						trackingData.eventName = 'play';
1344						trackingData.playlistPath = playlistPath;
1345						trackingData.videoPath = currentPlaylistItem.file;
1346
1347						DDC.pushToGtmDataLayer({
1348							event: "ddc.video.play",
1349							eventData: {
1350								element: currentPlayElement,
1351								eventType: "video-play",
1352								videoSource: trackingData.videoPath,
1353								videoDuration: currentPlayElement.duration,
1354								fileType: typeof trackingData.videoPath === 'string' ? trackingData.videoPath.split('.').pop() : '',
1355								position: currentPosition
1356							}
1357						});
1358
1359						if (!called['play']) {
1360							called['play'] = true;
1361							if (tracker.hasOwnProperty('trackEvent')) {
1362								tracker.trackEvent(trackingData);
1363							}
1364						}
1365					} catch (e) {
1366						if (window.console && window.console.log) {
1367							console.log('tracking error, could not log play');
1368						}
1369					}
1370				});
1371
1372				player.onTime(function (e) {
1373					var currentPlaylistItem,
1374						currentPlayElement,
1375						progress = (e.duration > 0) ? e.position / e.duration : 0,
1376						percentPlayed,
1377						trackingData = {};
1378
1379					try {
1380
1381						if (progress > 0 && !called['0']) {
1382							called['0'] = true;
1383							percentPlayed = '0';
1384						} else if (progress >= 0.25 && !called['25']) {
1385							called['25'] = true;
1386							percentPlayed = '25';
1387						} else if (progress >= 0.50 && !called['50']) {
1388							called['50'] = true;
1389							percentPlayed = '50';
1390						} else if (progress >= 0.75 && !called['75']) {
1391							called['75'] = true;
1392							percentPlayed = '75';
1393						} else if (progress >= 0.95 && !called['95']) {
1394							called['95'] = true;
1395							percentPlayed = '95';
1396						} else if (progress >= 0.99 && !called['100']) {
1397							called['100'] = true;
1398							percentPlayed = '100';
1399						}
1400
1401						if (percentPlayed != undefined) {
1402							currentPlaylistItem = this.getPlaylistItem();
1403							currentPlayElement = this.getContainer().querySelector('video');
1404							trackingData.eventName = 'progress';
1405							trackingData.playlistPath = playlistPath;
1406							trackingData.videoPath = currentPlaylistItem.file;
1407							trackingData.progress = percentPlayed;
1408							if (tracker.hasOwnProperty('trackEvent')) {
1409								tracker.trackEvent(trackingData);
1410							}
1411
1412							if(percentPlayed > 0 && percentPlayed < 100) {
1413								DDC.pushToGtmDataLayer({
1414									event: "ddc.video.milestone",
1415									eventData: {
1416										element: $target,
1417										eventType: "video-play-milestone",
1418										videoSource: trackingData.videoPath,
1419										videoDuration: e.duration,
1420										videoMilestone: percentPlayed,
1421										fileType: typeof trackingData.videoPath === 'string' ? trackingData.videoPath.split('.').pop() : ''
1422									}
1423								});
1424							}
1425						}
1426					} catch (e) {
1427						if (window.console && window.console.log) {
1428							console.log('tracking error, could not log onProgress');
1429						}
1430					}
1431				});
1432
1433				player.onComplete(function (e) {
1434					var currentPlaylistItem = this.getPlaylistItem(),
1435						currentPlayElement = this.getContainer().querySelector('video'),
1436						trackingData = {};
1437
1438					try {
1439						trackingData.eventName = 'play';
1440						trackingData.playlistPath = playlistPath;
1441						trackingData.videoPath = currentPlaylistItem.file;
1442
1443						DDC.pushToGtmDataLayer({
1444							event: "ddc.video.milestone",
1445							eventData: {
1446								element: currentPlayElement,
1447								eventType: "video-play-milestone",
1448								videoSource: trackingData.videoPath,
1449								videoDuration: currentPlayElement.duration,
1450								videoMilestone: "complete",
1451								fileType: typeof trackingData.videoPath === 'string' ? trackingData.videoPath.split('.').pop() : ''
1452							}
1453						});
1454
1455					} catch (e) {
1456						if (window.console && window.console.log) {
1457							console.log('tracking error, could not log video complete');
1458						}
1459					}
1460				});
1461
1462			}
1463
1464		} else {
1465			if (window.console && window.console.log) {
1466				console.log('Videoplayer could not embed into ' + this.options.targetid);
1467			}
1468		}
1469	}
1470
1471	/**
1472	* A function which is run on each wrapper's onReady callback 
1473	*
1474	* @param Function fn A user-defined callback function
1475	*/
1476	function onReady(fn) {
1477		try {
1478			if (typeof fn === 'function' && this instanceof DDCVideoWrapper) {
1479				this.onEmbedReadyCallback = fn;
1480			}
1481		} catch (e) {
1482			if (window.console && window.console.log) {
1483				console.log('Videoplayer could not set up onReady callback', e);
1484			}
1485		}
1486	}
1487
1488	/**
1489	* Set up video specific tracking methods for each type of tracker
1490	*
1491	* @param Object tracker global tracker object
1492	* @treturn Object tracker the configured tracker object, with tracking methods
1493	*/
1494	function configureTracker(tracker, trackerSettings) {
1495
1496		tracker.trackEvent = function (data) {
1497			if (data.hasOwnProperty('eventName')) {
1498				switch (data.eventName) {
1499
1500				case 'play':
1501					if (data.hasOwnProperty('videoPath') && data.hasOwnProperty('playlistPath')) {
1502						if (this.hasOwnProperty('ga')) {
1503							this.gaTrackPlayEvent(data.videoPath, data.playlistPath);
1504						}
1505					}
1506					break;
1507
1508				case 'progress':
1509					if (data.hasOwnProperty('videoPath') && data.hasOwnProperty('playlistPath') && data.hasOwnProperty('progress')) {
1510						if (this.hasOwnProperty('ga')) {
1511							this.gaTrackProgressEvent(data.videoPath, data.playlistPath, data.progress);
1512						}
1513					}
1514					break;
1515
1516				default:
1517					break;
1518				}
1519			}
1520		};
1521
1522		// ************************************
1523		// Google Analytics Tracker
1524		// ************************************
1525
1526		if (tracker.hasOwnProperty('ga')) {
1527			if (DDC.Tracker && DDC.gaManager) {
1528				// DDC.Tracker events automatically tracked by gaManager
1529				delete(tracker.ga);
1530			} else {
1531				// otherwise use this custom GA track code:
1532				configureGATracker(tracker, trackerSettings);
1533			}
1534		}
1535
1536		return tracker;
1537	}
1538
1539	/**
1540	* Set up DDC tracker
1541	*
1542	* @param Object tracker global tracker object
1543	* @treturn Object tracker the configured tracker object, with DDC tracking methods
1544	*/
1545	function configureDDCTracker(tracker, trackerSettings) {
1546		var ddcTrackerCategory = trackerSettings.ddc.viewTypeVideo;
1547		tracker.ddc = DDC.Tracker;
1548		tracker.ddc.deploy();
1549
1550		tracker.ddcTrackPlayEvent = function (videoPath, playlistPath) {
1551			this.ddcTrackProgressEvent(videoPath, playlistPath, '0');
1552		};
1553
1554		tracker.ddcTrackProgressEvent = function (videoPath, playlistPath, progress) {
1555			var viewExtra = videoPath + '|' + playlistPath + '|0|' + progress;
1556			this.ddcTrack(viewExtra);
1557		};
1558
1559		tracker.ddcTrack = function (viewExtra) {
1560			// DDC.Tracker reference
1561			tracker.ddc.track(ddcTrackerCategory, viewExtra);
1562		};
1563
1564		return tracker;
1565	}
1566
1567	/**
1568	* Set up google tracker
1569	*
1570	* @param Object tracker
1571	* @treturn Object tracker the configured tracker object, with google tracking methods
1572	*/
1573	function configureGATracker(tracker, settings) {
1574		var ddcGoogleId = settings.ga.id,
1575			gaCategory = settings.ga.category;
1576
1577		tracker.ga = window._gaq = window._gaq || [];
1578
1579		tracker.gaTrackPlayEvent = function (videoPath, playlistPath) {
1580			this.gaTrack(gaCategory, 'play', playlistPath);
1581		};
1582
1583		tracker.gaTrackProgressEvent = function (videoPath, playlistPath, progress) {
1584			this.gaTrack(gaCategory, 'viewed ' + progress + '%', playlistPath);
1585		};
1586
1587		tracker.gaTrack = function (category, action, opt_label, opt_value, opt_noninteraction) {
1588			tracker.ga.push(function() {
1589				_gat._getTracker(ddcGoogleId)._trackEvent(category, action, opt_label, opt_value, opt_noninteraction);
1590			});
1591		};
1592	}
1593
1594	// ************************************
1595	// Return Public Module Methods and Props
1596	// ************************************
1597	return module;
1598
1599})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.