PageSourceSearch

https://ripedesign.com/js/flowplayer.js

js ripedesign.com collected 2026-10-08 01:52:29 UTC 33,921 bytes, 1,606 lines download raw bytes

1/*!
2 * flowplayer.js The Flowplayer API
3 *
4 * Copyright 2009-2011 Flowplayer Oy
5 *
6 * This file is part of Flowplayer.
7 *
8 * Flowplayer is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * Flowplayer is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with Flowplayer.  If not, see <http://www.gnu.org/licenses/>.
20 *
21 */
22!function() {
23
24/*
25	FEATURES
26	--------
27	- $f() and flowplayer() functions
28	- handling multiple instances
29	- Flowplayer programming API
30	- Flowplayer event model
31	- player loading / unloading
32	- jQuery support
33*/
34
35
36/*jslint glovar: true, browser: true */
37/*global flowplayer, $f */
38
39// {{{ private utility methods
40
41	function log(args) {
42		console.log("$f.fireEvent", [].slice.call(args));
43	}
44
45
46	// thanks: http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
47	function clone(obj) {
48		if (!obj || typeof obj != 'object') { return obj; }
49		var temp = new obj.constructor();
50		for (var key in obj) {
51			if (obj.hasOwnProperty(key)) {
52				temp[key] = clone(obj[key]);
53			}
54		}
55		return temp;
56	}
57
58	// stripped from jQuery, thanks John Resig
59	function each(obj, fn) {
60		if (!obj) { return; }
61
62		var name, i = 0, length = obj.length;
63
64		// object
65		if (length === undefined) {
66			for (name in obj) {
67				if (fn.call(obj[name], name, obj[name]) === false) { break; }
68			}
69
70		// array
71		} else {
72			for (var value = obj[0];
73				i < length && fn.call( value, i, value ) !== false; value = obj[++i]) {
74			}
75		}
76
77		return obj;
78	}
79
80
81	// convenience
82	function el(id) {
83		return document.getElementById(id);
84	}
85
86
87	// used extensively. a very simple implementation.
88	function extend(to, from, skipFuncs) {
89		if (typeof from != 'object') { return to; }
90
91		if (to && from) {
92			each(from, function(name, value) {
93				if (!skipFuncs || typeof value != 'function') {
94					to[name] = value;
95				}
96			});
97		}
98
99		return to;
100	}
101
102	// var arr = select("elem.className");
103	function select(query) {
104		var index = query.indexOf(".");
105		if (index != -1) {
106			var tag = query.slice(0, index) || "*";
107			var klass = query.slice(index + 1, query.length);
108			var els = [];
109			each(document.getElementsByTagName(tag), function() {
110				if (this.className && this.className.indexOf(klass) != -1) {
111					els.push(this);
112				}
113			});
114			return els;
115		}
116	}
117
118	// fix event inconsistencies across browsers
119	function stopEvent(e) {
120		e = e || window.event;
121
122		if (e.preventDefault) {
123			e.stopPropagation();
124			e.preventDefault();
125
126		} else {
127			e.returnValue = false;
128			e.cancelBubble = true;
129		}
130		return false;
131	}
132
133	// push an event listener into existing array of listeners
134	function bind(to, evt, fn) {
135		to[evt] = to[evt] || [];
136		to[evt].push(fn);
137	}
138
139	// escape & and = in config written into flashvars (issue #21)
140	function queryescape(url) {
141		return url.replace(/&amp;/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D');
142	}
143
144	// generates an unique id
145   function makeId() {
146      return "_" + ("" + Math.random()).slice(2, 10);
147   }
148
149//}}}
150
151
152// {{{ Clip
153
154	var Clip = function(json, index, player) {
155
156		// private variables
157		var self = this,
158			 cuepoints = {},
159			 listeners = {};
160
161		self.index = index;
162
163		// instance variables
164		if (typeof json == 'string') {
165			json = {url:json};
166		}
167
168		extend(this, json, true);
169
170		// event handling
171		each(("Begin*,Start,Pause*,Resume*,Seek*,Stop*,Finish*,LastSecond,Update,BufferFull,BufferEmpty,BufferStop").split(","),
172			function() {
173
174			var evt = "on" + this;
175
176			// before event
177			if (evt.indexOf("*") != -1) {
178				evt = evt.slice(0, evt.length -1);
179				var before = "onBefore" + evt.slice(2);
180
181				self[before] = function(fn) {
182					bind(listeners, before, fn);
183					return self;
184				};
185			}
186
187			self[evt] = function(fn) {
188				bind(listeners, evt, fn);
189				return self;
190			};
191
192
193			// set common clip event listeners to player level
194			if (index == -1) {
195				if (self[before]) {
196					player[before] = self[before];
197				}
198				if (self[evt])  {
199					player[evt] = self[evt];
200				}
201			}
202
203		});
204
205		extend(this, {
206
207			onCuepoint: function(points, fn) {
208
209				// embedded cuepoints
210				if (arguments.length == 1) {
211					cuepoints.embedded = [null, points];
212					return self;
213				}
214
215				if (typeof points == 'number') {
216					points = [points];
217				}
218
219				var fnId = makeId();
220				cuepoints[fnId] = [points, fn];
221
222				if (player.isLoaded()) {
223					player._api().fp_addCuepoints(points, index, fnId);
224				}
225
226				return self;
227			},
228
229			update: function(json) {
230				extend(self, json);
231
232				if (player.isLoaded()) {
233					player._api().fp_updateClip(json, index);
234				}
235				var conf = player.getConfig();
236				var clip = (index == -1) ? conf.clip : conf.playlist[index];
237				extend(clip, json, true);
238			},
239
240
241			// internal event for performing clip tasks. should be made private someday
242			_fireEvent: function(evt, arg1, arg2, target) {
243				if (evt == 'onLoad') {
244					each(cuepoints, function(key, val) {
245						if (val[0]) {
246							player._api().fp_addCuepoints(val[0], index, key);
247						}
248					});
249					return false;
250				}
251
252				// target clip we are working against
253				target = target || self;
254
255				if (evt == 'onCuepoint') {
256					var fn = cuepoints[arg1];
257					if (fn) {
258						return fn[1].call(player, target, arg2);
259					}
260				}
261
262				// 1. clip properties, 2-3. metadata, 4. updates, 5. resumes from nested clip
263                //#148 add onMetaDataChange event to extend clip properties, this was needed to prevent regular updates during stream switching.
264				if (arg1 && "onBeforeBegin,onMetaData,onMetaDataChange,onStart,onUpdate,onResume".indexOf(evt) != -1) {
265					// update clip properties
266					extend(target, arg1);
267
268					if (arg1.metaData) {
269						if (!target.duration) {
270							target.duration = arg1.metaData.duration;
271						} else {
272							target.fullDuration = arg1.metaData.duration;
273						}
274					}
275				}
276
277
278				var ret = true;
279				each(listeners[evt], function() {
280					ret = this.call(player, target, arg1, arg2);
281				});
282				return ret;
283			}
284
285		});
286
287
288		// get cuepoints from config
289		if (json.onCuepoint) {
290			var arg = json.onCuepoint;
291			self.onCuepoint.apply(self, typeof arg == 'function' ? [arg] : arg);
292			delete json.onCuepoint;
293		}
294
295		// get other events
296		each(json, function(key, val) {
297
298			if (typeof val == 'function') {
299				bind(listeners, key, val);
300				delete json[key];
301			}
302
303		});
304
305
306		// setup common clip event callbacks for Player object too (shortcuts)
307		if (index == -1) {
308			player.onCuepoint = this.onCuepoint;
309		}
310
311	};
312
313//}}}
314
315
316// {{{ Plugin
317
318	var Plugin = function(name, json, player, fn) {
319
320		var self = this,
321			 listeners = {},
322			 hasMethods = false;
323
324		if (fn) {
325			extend(listeners, fn);
326		}
327
328		// custom callback functions in configuration
329		each(json, function(key, val) {
330			if (typeof val == 'function') {
331				listeners[key] = val;
332				delete json[key];
333			}
334		});
335
336		// core plugin methods
337		extend(this, {
338
339			// speed and fn are optional
340			animate: function(props, speed, fn) {
341				if (!props) {
342					return self;
343				}
344
345				if (typeof speed == 'function') {
346					fn = speed;
347					speed = 500;
348				}
349
350				if (typeof props == 'string') {
351					var key = props;
352					props = {};
353					props[key] = speed;
354					speed = 500;
355				}
356
357				if (fn) {
358					var fnId = makeId();
359					listeners[fnId] = fn;
360				}
361
362				if (speed === undefined) { speed = 500; }
363				json = player._api().fp_animate(name, props, speed, fnId);
364				return self;
365			},
366
367			css: function(props, val) {
368				if (val !== undefined) {
369					var css = {};
370					css[props] = val;
371					props = css;
372				}
373				json = player._api().fp_css(name, props);
374				extend(self, json);
375				return self;
376			},
377
378			show: function() {
379				this.display = 'block';
380				player._api().fp_showPlugin(name);
381				return self;
382			},
383
384			hide: function() {
385				this.display = 'none';
386				player._api().fp_hidePlugin(name);
387				return self;
388			},
389
390			// toggle between visible / hidden state
391			toggle: function() {
392				this.display = player._api().fp_togglePlugin(name);
393				return self;
394			},
395
396			fadeTo: function(o, speed, fn) {
397
398				if (typeof speed == 'function') {
399					fn = speed;
400					speed = 500;
401				}
402
403				if (fn) {
404					var fnId = makeId();
405					listeners[fnId] = fn;
406				}
407				this.display = player._api().fp_fadeTo(name, o, speed, fnId);
408				this.opacity = o;
409				return self;
410			},
411
412			fadeIn: function(speed, fn) {
413				return self.fadeTo(1, speed, fn);
414			},
415
416			fadeOut: function(speed, fn) {
417				return self.fadeTo(0, speed, fn);
418			},
419
420			getName: function() {
421				return name;
422			},
423
424			getPlayer: function() {
425				return player;
426			},
427
428			// internal method. should be made private some day
429         _fireEvent: function(evt, arg, arg2) {
430
431            // update plugins properties & methods
432            if (evt == 'onUpdate') {
433               var json = player._api().fp_getPlugin(name);
434					if (!json) { return;	}
435
436               extend(self, json);
437               delete self.methods;
438
439               if (!hasMethods) {
440                  each(json.methods, function() {
441                     var method = "" + this;
442
443                     self[method] = function() {
444                        var a = [].slice.call(arguments);
445                        var ret = player._api().fp_invoke(name, method, a);
446                        return ret === 'undefined' || ret === undefined ? self : ret;
447                     };
448                  });
449                  hasMethods = true;
450               }
451            }
452
453            // plugin callbacks
454            var fn = listeners[evt];
455
456			if (fn) {
457				var ret = fn.apply(self, arg);
458
459				// "one-shot" callback
460				if (evt.slice(0, 1) == "_") {
461					delete listeners[evt];
462				}
463
464				return ret;
465            }
466
467            return self;
468         }
469
470		});
471
472	};
473
474
475//}}}
476
477
478function Player(wrapper, params, conf) {
479
480	// private variables (+ arguments)
481	var self = this,
482		api = null,
483		isUnloading = false,
484		html,
485		commonClip,
486		playlist = [],
487		plugins = {},
488		listeners = {},
489		playerId,
490		apiId,
491
492		// n'th player on the page
493		playerIndex,
494
495		// active clip's index number
496		activeIndex,
497
498		swfHeight,
499		wrapperHeight;
500
501
502// {{{ public methods
503
504	extend(self, {
505
506		id: function() {
507			return playerId;
508		},
509
510		isLoaded: function() {
511			return (api !== null && api.fp_play !== undefined && !isUnloading);
512		},
513
514		getParent: function() {
515			return wrapper;
516		},
517
518		hide: function(all) {
519			if (all) { wrapper.style.height = "0px"; }
520			if (self.isLoaded()) { api.style.height = "0px"; }
521			return self;
522		},
523
524		show: function() {
525			wrapper.style.height = wrapperHeight + "px";
526			if (self.isLoaded()) { api.style.height = swfHeight + "px"; }
527			return self;
528		},
529
530		isHidden: function() {
531			return self.isLoaded() && parseInt(api.style.height, 10) === 0;
532		},
533
534		load: function(fn) {
535			if (!self.isLoaded() && self._fireEvent("onBeforeLoad") !== false) {
536				var onPlayersUnloaded = function() {
537					// do not use splash as alternate content for flashembed
538					if (html && !flashembed.isSupported(params.version)) {
539						wrapper.innerHTML = "";
540					}
541
542					// onLoad listener given as argument
543					if (fn) {
544						fn.cached = true;
545						bind(listeners, "onLoad", fn);
546					}
547
548					// install Flash object inside given container
549					flashembed(wrapper, params, {config: conf});
550				};
551
552
553				// unload all instances
554				var unloadedPlayersNb = 0;
555				each(players, function()  {
556					this.unload(function(wasUnloaded) {
557						if ( ++unloadedPlayersNb == players.length ) {
558							onPlayersUnloaded();
559						}
560					});
561				});
562			}
563
564			return self;
565		},
566
567		unload: function(fn) {
568
569			// unload only if in splash state
570			if (html.replace(/\s/g,'') !== '') {
571
572				if (self._fireEvent("onBeforeUnload") === false) {
573					if ( fn ) { fn(false); }
574					return self;
575				}
576
577				isUnloading = true;
578				// try closing
579				try {
580					if (api) {
581						// issue511
582						if (api.fp_isFullscreen()) {
583							api.fp_toggleFullscreen();
584						}
585
586						api.fp_close();
587
588						// fire unload only when API is present
589						self._fireEvent("onUnload");
590					}
591				} catch (error) {}
592
593				var clean = function() {
594					api = null;
595					wrapper.innerHTML = html;
596					isUnloading = false;
597
598					if ( fn ) { fn(true); }
599				};
600
601				if (/WebKit/i.test(navigator.userAgent) && ! /Chrome/i.test(navigator.userAgent)) {
602						setTimeout(clean, 0);
603				} else {
604					clean();
605				}
606			}
607			else if ( fn ) { fn(false); }
608
609			return self;
610
611		},
612
613		getClip: function(index) {
614			if (index === undefined) {
615				index = activeIndex;
616			}
617			return playlist[index];
618		},
619
620
621		getCommonClip: function() {
622			return commonClip;
623		},
624
625		getPlaylist: function() {
626			return playlist;
627		},
628
629      getPlugin: function(name) {
630         var plugin = plugins[name];
631
632			// create plugin if nessessary
633         if (!plugin && self.isLoaded()) {
634				var json = self._api().fp_getPlugin(name);
635				if (json) {
636					plugin = new Plugin(name, json, self);
637					plugins[name] = plugin;
638				}
639         }
640         return plugin;
641      },
642
643		getScreen: function() {
644			return self.getPlugin("screen");
645		},
646
647		getControls: function() {
648			return self.getPlugin("controls")._fireEvent("onUpdate");
649		},
650
651		// 3.2
652		getLogo: function() {
653			try {
654				return self.getPlugin("logo")._fireEvent("onUpdate");
655			} catch (ignored) {}
656		},
657
658		// 3.2
659		getPlay: function() {
660			return self.getPlugin("play")._fireEvent("onUpdate");
661		},
662
663
664		getConfig: function(copy) {
665			return copy ? clone(conf) : conf;
666		},
667
668		getFlashParams: function() {
669			return params;
670		},
671
672		loadPlugin: function(name, url, props, fn) {
673
674			// properties not supplied
675			if (typeof props == 'function') {
676				fn = props;
677				props = {};
678			}
679
680			// if fn not given, make a fake id so that plugin's onUpdate get's fired
681			var fnId = fn ? makeId() : "_";
682			self._api().fp_loadPlugin(name, url, props, fnId);
683
684			// create new plugin
685			var arg = {};
686			arg[fnId] = fn;
687			var p = new Plugin(name, null, self, arg);
688			plugins[name] = p;
689			return p;
690		},
691
692
693		getState: function() {
694			return self.isLoaded() ? api.fp_getState() : -1;
695		},
696
697		// "lazy" play
698		play: function(clip, instream) {
699
700			var p = function() {
701				if (clip !== undefined) {
702					self._api().fp_play(clip, instream);
703				} else {
704					self._api().fp_play();
705				}
706			};
707
708			if (self.isLoaded()) {
709				p();
710			} else if ( isUnloading ) {
711				setTimeout(function() {
712					self.play(clip, instream);
713				}, 50);
714
715			} else {
716				self.load(function() {
717					p();
718				});
719			}
720
721			return self;
722		},
723
724		getVersion: function() {
725			var js = "flowplayer.js @VERSION";
726			if (self.isLoaded()) {
727				var ver = api.fp_getVersion();
728				ver.push(js);
729				return ver;
730			}
731			return js;
732		},
733
734		_api: function() {
735			if (!self.isLoaded()) {
736				throw "Flowplayer " +self.id()+ " not loaded when calling an API method";
737			}
738			return api;
739		},
740
741		setClip: function(clip) {
742			each(clip, function(key, val) {
743				if (typeof val == 'function') {
744					bind(listeners, key, val);
745					delete clip[key];
746				} else if (key == 'onCuepoint'){
747					$f(wrapper).getCommonClip().onCuepoint(clip[key][0], clip[key][1]);
748				}
749			});
750			self.setPlaylist([clip]);
751			return self;
752		},
753
754		getIndex: function() {
755			return playerIndex;
756		},
757
758        bufferAnimate: function(enable) {
759            api.fp_bufferAnimate(enable === undefined || enable);
760            return self;
761        },
762
763		_swfHeight: function() {
764			return api.clientHeight;
765		}
766
767	});
768
769
770	// event handlers
771	each(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,ClipAdd,Fullscreen*,FullscreenExit,Error,MouseOver,MouseOut").split(","),
772		function() {
773			var name = "on" + this;
774
775			// before event
776			if (name.indexOf("*") != -1) {
777				name = name.slice(0, name.length -1);
778				var name2 = "onBefore" + name.slice(2);
779				self[name2] = function(fn) {
780					bind(listeners, name2, fn);
781					return self;
782				};
783			}
784
785			// normal event
786			self[name] = function(fn) {
787				bind(listeners, name, fn);
788				return self;
789			};
790		}
791	);
792
793
794	// core API methods
795	each(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffer
795ing,stopBuffering,isFullscreen,toggleFullscreen,reset,close,setPlaylist,addClip,playFeed,setKeyboardShortcutsEnabled,isKeyboardShortcutsEnabled").split(","),
796		function() {
797			var name = this;
798
799			self[name] = function(a1, a2) {
800				if (!self.isLoaded()) { return self; }
801				var ret = null;
802
803				// two arguments
804				if (a1 !== undefined && a2 !== undefined) {
805					ret = api["fp_" + name](a1, a2);
806
807				} else {
808					ret = (a1 === undefined) ? api["fp_" + name]() : api["fp_" + name](a1);
809
810				}
811
812				return ret === 'undefined' || ret === undefined ? self : ret;
813			};
814		}
815	);
816
817//}}}
818
819
820// {{{ public method: _fireEvent
821
822	self._fireEvent = function(a) {
823
824		if (typeof a == 'string') { a = [a]; }
825
826		var evt = a[0], arg0 = a[1], arg1 = a[2], arg2 = a[3], i = 0;
827		if (conf.debug) { log(a); }
828
829		// internal onLoad
830		if (!self.isLoaded() && evt == 'onLoad' && arg0 == 'player') {
831
832			api = api || el(apiId);
833			swfHeight = self._swfHeight();
834
835			each(playlist, function() {
836				this._fireEvent("onLoad");
837			});
838
839			each(plugins, function(name, p) {
840				p._fireEvent("onUpdate");
841			});
842
843			commonClip._fireEvent("onLoad");
844		}
845
846		// other onLoad events are skipped
847		if (evt == 'onLoad' && arg0 != 'player') { return; }
848
849
850		// "normalize" error handling
851		if (evt == 'onError') {
852			if (typeof arg0 == 'string' || (typeof arg0 == 'number' && typeof arg1 == 'number'))  {
853				arg0 = arg1;
854				arg1 = arg2;
855			}
856		}
857
858
859      if (evt == 'onContextMenu') {
860         each(conf.contextMenu[arg0], function(key, fn)  {
861            fn.call(self);
862         });
863         return;
864      }
865
866		if (evt == 'onPluginEvent' || evt == 'onBeforePluginEvent') {
867			var name = arg0.name || arg0;
868			var p = plugins[name];
869
870			if (p) {
871				p._fireEvent("onUpdate", arg0);
872				return p._fireEvent(arg1, a.slice(3));
873			}
874			return;
875		}
876
877		// replace whole playlist
878		if (evt == 'onPlaylistReplace') {
879			playlist = [];
880			var index = 0;
881			each(arg0, function() {
882				playlist.push(new Clip(this, index++, self));
883			});
884		}
885
886		// insert new clip to the playlist. arg0 = clip, arg1 = index
887		if (evt == 'onClipAdd') {
888
889			// instream clip additions are ignored at this point
890			if (arg0.isInStream) { return; }
891
892			// add new clip into playlist
893			arg0 = new Clip(arg0, arg1, self);
894			playlist.splice(arg1, 0, arg0);
895
896			// increment index variable for the rest of the clips on playlist
897			for (i = arg1 + 1; i < playlist.length; i++) {
898				playlist[i].index++;
899			}
900		}
901
902
903		var ret = true;
904
905		// clip event
906		if (typeof arg0 == 'number' && arg0 < playlist.length) {
907
908			activeIndex = arg0;
909			var clip = playlist[arg0];
910
911			if (clip) {
912				ret = clip._fireEvent(evt, arg1, arg2);
913			}
914
915			if (!clip || ret !== false) {
916				// clip argument is given for common clip, because it behaves as the target
917				ret = commonClip._fireEvent(evt, arg1, arg2, clip);
918			}
919		}
920
921
922		// trigger player event
923		each(listeners[evt], function() {
924			ret = this.call(self, arg0, arg1);
925
926			// remove cached entry
927			if (this.cached) {
928				listeners[evt].splice(i, 1);
929			}
930
931			// break loop
932			if (ret === false) { return false;	 }
933			i++;
934
935		});
936
937		return ret;
938	};
939
940//}}}
941
942
943// {{{ init
944
945   function init() {
946		// replace previous installation
947		if ($f(wrapper)) {
948			$f(wrapper).getParent().innerHTML = "";
949			playerIndex = $f(wrapper).getIndex();
950			players[playerIndex] = self;
951
952		// register this player into global array of instances
953		} else {
954			players.push(self);
955			playerIndex = players.length -1;
956		}
957
958		wrapperHeight = parseInt(wrapper.style.height, 10) || wrapper.clientHeight;
959
960		// playerId
961		playerId = wrapper.id || "fp" + makeId();
962		apiId = params.id || playerId + "_api";
963		params.id = apiId;
964        html = wrapper.innerHTML;
965
966
967		// plain url is given as config
968		if (typeof conf == 'string') {
969			conf = {clip:{url:conf}};
970		}
971
972		conf.playerId = playerId;
973
974		// common clip is always there
975		conf.clip = conf.clip || {};
976
977
978		// wrapper href as common clip's url
979		if (wrapper.getAttribute("href", 2) && !conf.clip.url) {
980			conf.clip.url = wrapper.getAttribute("href", 2);
981		}
982
983		if (conf.clip.url) {
984			conf.clip.url = queryescape(conf.clip.url);
985		}
986
987		commonClip = new Clip(conf.clip, -1, self);
988
989		// playlist
990		conf.playlist = conf.playlist || [conf.clip];
991
992		var index = 0;
993
994		each(conf.playlist, function() {
995
996			var clip = this;
997
998			/* sometimes clip is given as array. this is not accepted. */
999			if (typeof clip == 'object' && clip.length) {
1000				clip = {url: "" + clip};
1001			}
1002
1003			if (clip.url) {
1004				clip.url = queryescape(clip.url);
1005			}
1006
1007			// populate common clip properties to each clip
1008			each(conf.clip, function(key, val) {
1009				if (val !== undefined && clip[key] === undefined && typeof val != 'function') {
1010					clip[key] = val;
1011				}
1012			});
1013
1014			// modify playlist in configuration
1015			conf.playlist[index] = clip;
1016
1017			// populate playlist array
1018			clip = new Clip(clip, index, self);
1019			playlist.push(clip);
1020			index++;
1021		});
1022
1023		// event listeners
1024		each(conf, function(key, val) {
1025			if (typeof val == 'function') {
1026
1027				// common clip event
1028				if (commonClip[key]) {
1029					commonClip[key](val);
1030
1031				// player event
1032				} else {
1033					bind(listeners, key, val);
1034				}
1035
1036				// no need to supply for the Flash component
1037				delete conf[key];
1038			}
1039		});
1040
1041
1042		// plugins
1043		each(conf.plugins, function(name, val) {
1044			if (val) {
1045				plugins[name] = new Plugin(name, val, self);
1046			}
1047		});
1048
1049
1050		// setup controlbar plugin if not explicitly defined
1051		if (!conf.plugins || conf.plugins.controls === undefined) {
1052			plugins.controls = new Plugin("controls", null, self);
1053		}
1054
1055		// setup canvas as plugin
1056		plugins.canvas = new Plugin("canvas", null, self);
1057
1058		html = wrapper.innerHTML;
1059
1060		// click function
1061		function doClick(e) {
1062
1063			// ipad/iPhone --> follow the link if plugin not installed
1064			if (/iPad|iPhone|iPod/i.test(navigator.userAgent) && !/.flv$/i.test(playlist[0].url) && ! checkForIpadSupport() ) {
1065				return true;
1066			}
1067
1068			if (!self.isLoaded() && self._fireEvent("onBeforeClick") !== false) {
1069				self.load();
1070			}
1071			return stopEvent(e);
1072		}
1073
1074        function checkForIpadSupport() {
1075             return self.hasiPadSupport && self.hasiPadSupport();
1076        }
1077
1078		function installPlayer() {
1079			// defer loading upon click
1080			if (html.replace(/\s/g, '') !== '') {
1081
1082				if (wrapper.addEventListener) {
1083					wrapper.addEventListener("click", doClick, false);
1084
1085				} else if (wrapper.attachEvent) {
1086					wrapper.attachEvent("onclick", doClick);
1087				}
1088
1089			// player is loaded upon page load
1090			} else {
1091
1092				// prevent default action from wrapper. (fixes safari problems)
1093                //#195 check if we have ipad support and not block click events
1094				if (wrapper.addEventListener && !checkForIpadSupport()) {
1095					wrapper.addEventListener("click", stopEvent, false);
1096				}
1097				// load player
1098				self.load();
1099			}
1100		}
1101
1102		// now that the player is initialized, wait for the plugin chain to finish
1103		// before actually changing the dom
1104		setTimeout(installPlayer, 0);
1105	}
1106
1107	// possibly defer initialization until DOM get's loaded
1108	if (typeof wrapper == 'string') {
1109		var node = el(wrapper);
1110		if (!node) { throw "Flowplayer cannot access element: " + wrapper; }
1111		wrapper = node;
1112		init();
1113
1114	// we have a DOM element so page is already loaded
1115	} else {
1116		init();
1117	}
1118
1119
1120//}}}
1121
1122
1123}
1124
1125
1126// {{{ flowplayer() & statics
1127
1128// container for player instances
1129var players = [];
1130
1131
1132// this object is returned when multiple player's are requested
1133function Iterator(arr) {
1134
1135	this.length = arr.length;
1136
1137	this.each = function(fn)  {
1138		each(arr, fn);
1139	};
1140
1141	this.size = function() {
1142		return arr.length;
1143	};
1144
1145	var self = this;
1146	for(name in Player.prototype) {
1147	  self[name] = function() {
1148 	    var args = arguments;
1149	    self.each(function() {
1150    	  this[name].apply(this, args);
1151	    });
1152	  };
1153  	}
1154}
1155
1156// these two variables are the only global variables
1157window.flowplayer = window.$f = function() {
1158	var instance = null;
1159	var arg = arguments[0];
1160
1161	// $f()
1162	if (!arguments.length) {
1163		each(players, function() {
1164			if (this.isLoaded())  {
1165				instance = this;
1166				return false;
1167			}
1168		});
1169
1170		return instance || players[0];
1171	}
1172
1173	if (arguments.length == 1) {
1174
1175		// $f(index);
1176		if (typeof arg == 'number') {
1177			return players[arg];
1178
1179
1180		// $f(wrapper || 'containerId' || '*');
1181		} else {
1182
1183			// $f("*");
1184			if (arg == '*') {
1185				return new Iterator(players);
1186			}
1187
1188			// $f(wrapper || 'containerId');
1189			each(players, function() {
1190				if (this.id() == arg.id || this.id() == arg || this.getParent() == arg)  {
1191					instance = this;
1192					return false;
1193				}
1194			});
1195
1196			return instance;
1197		}
1198	}
1199
1200	// instance builder
1201	if (arguments.length > 1) {
1202
1203		// flashembed parameters
1204		var params = arguments[1],
1205			 conf = (arguments.length == 3) ? arguments[2] : {};
1206
1207
1208		if (typeof params == 'string') {
1209			params = {src: params};
1210		}
1211
1212		params = extend({
1213			bgcolor: "#000000",
1214			version: [10, 1],
1215			expressInstall: "http://releases.flowplayer.org/swf/expressinstall.swf",
1216			cachebusting: false
1217
1218		}, params);
1219
1220		if (typeof arg == 'string') {
1221
1222			// select arg by classname
1223			if (arg.indexOf(".") != -1) {
1224				var instances = [];
1225
1226				each(select(arg), function() {
1227					instances.push(new Player(this, clone(params), clone(conf)));
1228				});
1229
1230				return new Iterator(instances);
1231
1232			// select node by id
1233			} else {
1234				var node = el(arg);
1235				return new Player(node !== null ? node : clone(arg), clone(params), clone(conf));
1236			}
1237
1238
1239		// arg is a DOM element
1240		} else if (arg) {
1241			return new Player(arg, clone(params), clone(conf));
1242		}
1243
1244	}
1245
1246	return null;
1247};
1248
1249extend(window.$f, {
1250
1251	// called by Flash External Interface
1252	fireEvent: function() {
1253		var a = [].slice.call(arguments);
1254		var p = $f(a[0]);
1255		return p ? p._fireEvent(a.slice(1)) : null;
1256	},
1257
1258
1259	// create plugins by modifying Player's prototype
1260	addPlugin: function(name, fn) {
1261		Player.prototype[name] = fn;
1262		return $f;
1263	},
1264
1265	// utility methods for plugin developers
1266	each: each,
1267
1268	extend: extend
1269});
1270
1271
1272//}}}
1273
1274
1275//{{{ jQuery support
1276
1277if (typeof jQuery == 'function') {
1278
1279	jQuery.fn.flowplayer = function(params, conf) {
1280
1281		// select instances
1282		if (!arguments.length || typeof arguments[0] == 'number') {
1283			var arr = [];
1284			this.each(function()  {
1285				var p = $f(this);
1286				if (p) {
1287					arr.push(p);
1288				}
1289			});
1290			return arguments.length ? arr[arguments[0]] : new Iterator(arr);
1291		}
1292
1293		// create flowplayer instances
1294		return this.each(function() {
1295			$f(this, clone(params), conf ? clone(conf) : {});
1296		});
1297
1298	};
1299
1300}
1301
1302//}}}
1303
1304
1305}();
1306/**
1307 * Flashembed - New wave Flash embedding
1308 *
1309 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
1310 */
1311!function() {
1312
1313	var IE = document.all,
1314		 URL = 'http://get.adobe.com/flashplayer',
1315		 JQUERY = typeof jQuery == 'function',
1316		 RE = /(\d+)[^\d]+(\d+)[^\d]*(\d*)/,
1317		 GLOBAL_OPTS = {
1318			// very common opts
1319			width: '100%',
1320			height: '100%',
1321			id: "_" + ("" + Math.random()).slice(9),
1322
1323			// flashembed defaults
1324			allowfullscreen: true,
1325			allowscriptaccess: 'always',
1326			quality: 'high',
1327
1328			// flashembed specific options
1329			version: [3, 0],
1330			onFail: null,
1331			expressInstall: null,
1332			w3c: false,
1333			cachebusting: false
1334	};
1335
1336	// version 9 bugfix: (http://blog.deconcept.com/2006/07/28/swfobject-143-released/)
1337	if (window.attachEvent) {
1338		window.attachEvent("onbeforeunload", function() {
1339			__flash_unloadHandler = function() {};
1340			__flash_savedUnloadHandler = function() {};
1341		});
1342	}
1343
1344	// simple extend
1345	function extend(to, from) {
1346		if (from) {
1347			for (var key in from) {
1348				if (from.hasOwnProperty(key)) {
1349					to[key] = from[key];
1350				}
1351			}
1352		}
1353		return to;
1354	}
1355
1356	// used by asString method
1357	function map(arr, func) {
1358		var newArr = [];
1359		for (var i in arr) {
1360			if (arr.hasOwnProperty(i)) {
1361				newArr[i] = func(arr[i]);
1362			}
1363		}
1364		return newArr;
1365	}
1366
1367	window.flashembed = function(root, opts, conf) {
1368
1369		// root must be found / loaded
1370		if (typeof root == 'string') {
1371			root = document.getElementById(root.replace("#", ""));
1372		}
1373
1374		// not found
1375		if (!root) { return; }
1376
1377		if (typeof opts == 'string') {
1378			opts = {src: opts};
1379		}
1380
1381		return new Flash(root, extend(extend({}, GLOBAL_OPTS), opts), conf);
1382	};
1383
1384	// flashembed "static" API
1385	var f = extend(window.flashembed, {
1386
1387		conf: GLOBAL_OPTS,
1388
1389		getVersion: function()  {
1390			var fo, ver, plugin;
1391
1392			try {
1393				plugin = navigator.plugins["Shockwave Flash"];
1394				// Safari 6 reports version even when disabled
1395				// but [0, 0] should be returned (#167)
1396				if (plugin[0].enabledPlugin != null)
1397					ver = plugin.description.slice(16);
1398			} catch(e) {
1399
1400				try  {
1401					fo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
1402					ver = fo && fo.GetVariable("$version");
1403
1404				} catch(err) {
1405                try  {
1406                    fo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
1407                    ver = fo && fo.GetVariable("$version");
1408                } catch(err2) { }
1409				}
1410			}
1411
1412			ver = RE.exec(ver);
1413            return ver ? [1 * ver[1], 1 * ver[(ver[1] * 1 > 9 ? 2 : 3)] * 1] : [0, 0];
1414        },
1415
1416		asString: function(obj) {
1417
1418			if (obj === null || obj === undefined) { return null; }
1419			var type = typeof obj;
1420			if (type == 'object' && obj.push) { type = 'array'; }
1421
1422			switch (type){
1423
1424				case 'string':
1425					obj = obj.replace(new RegExp('(["\\\\])', 'g'), '\\$1');
1426
1427					// flash does not handle %- characters well. transforms "50%" to "50pct" (a dirty hack, I admit)
1428					obj = obj.replace(/^\s?(\d+\.?\d*)%/, "$1pct")
1429
1430					return '"' +obj+ '"';
1431
1432				case 'array':
1433					return '['+ map(obj, function(el) {
1434						return f.asString(el);
1435					}).join(',') +']';
1436
1437				case 'function':
1438					return '"function()"';
1439
1440				case 'object':
1441					var str = [];
1442					for (var prop in obj) {
1443						if (obj.hasOwnProperty(prop)) {
1444							str.push('"'+prop+'":'+ f.asString(obj[prop]));
1445						}
1446					}
1447					return '{'+str.join(',')+'}';
1448			}
1449
1450			// replace ' --> "  and remove spaces
1451			return String(obj).replace(/\s/g, " ").replace(/\'/g, "\"");
1452		},
1453
1454		getHTML: function(opts, conf) {
1455
1456			opts = extend({}, opts);
1457
1458			/******* OBJECT tag and it's attributes *******/
1459			var html = '<object width="' + opts.width +
1460				'" height="' + opts.height +
1461				'" id="' + opts.id +
1462				'" name="' + opts.id + '"';
1463
1464			if (opts.cachebusting) {
1465				opts.src += ((opts.src.indexOf("?") != -1 ? "&" : "?") + Math.random());
1466			}
1467
1468			if (opts.w3c || !IE) {
1469				html += ' data="' +opts.src+ '" type="application/x-shockwave-flash"';
1470			} else {
1471				html += ' classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
1472			}
1473
1474			html += '>';
1475
1476			/******* nested PARAM tags *******/
1477			if (opts.w3c || IE) {
1478				html += '<param name="movie" value="' +opts.src+ '" />';
1479			}
1480
1481			// not allowed params
1482			opts.width = opts.height = opts.id = opts.w3c = opts.src = null;
1483			opts.onFail = opts.version = opts.expressInstall = null;
1484
1485			for (var key in opts) {
1486				if (opts[key]) {
1487					html += '<param name="'+ key +'" value="'+ opts[key] +'" />';
1488				}
1489			}
1490
1491			/******* FLASHVARS *******/
1492			var vars = "";
1493
1494			if (conf) {
1495				for (var k in conf) {
1496					if (conf[k]) {
1497						var val = conf[k];
1498						vars += k +'='+ (/function|object/.test(typeof val) ? f.asString(val) 
1498: val) + '&';
1499					}
1500				}
1501				vars = vars.slice(0, -1);
1502				html += '<param name="flashvars" value=\'' + vars + '\' />';
1503			}
1504
1505			html += "</object>";
1506
1507			return html;
1508		},
1509
1510		isSupported: function(ver) {
1511			return VERSION[0] > ver[0] || VERSION[0] == ver[0] && VERSION[1] >= ver[1];
1512		}
1513
1514	});
1515
1516	var VERSION = f.getVersion();
1517
1518	function Flash(root, opts, conf) {
1519
1520		// version is ok
1521		if (f.isSupported(opts.version)) {
1522			root.innerHTML = f.getHTML(opts, conf);
1523
1524		// express install
1525		} else if (opts.expressInstall && f.isSupported([6, 65])) {
1526			root.innerHTML = f.getHTML(extend(opts, {src: opts.expressInstall}), {
1527				//xss fix #357
1528				MMredirectURL: encodeURIComponent(location.href),
1529				MMplayerType: 'PlugIn',
1530				MMdoctitle: document.title
1531			});
1532
1533		} else {
1534
1535			// fail #2.1 custom content inside container
1536			if (!root.innerHTML.replace(/\s/g, '')) {
1537				root.innerHTML =
1538					"<h2>Flash version " + opts.version + " or greater is required</h2>" +
1539					"<h3>" +
1540						(VERSION[0] > 0 ? "Your version is " + VERSION : "You have no flash plugin installed") +
1541					"</h3>" +
1542
1543					(root.tagName == 'A' ? "<p>Click here to download latest version</p>" :
1544						"<p>Download latest version from <a href='" + URL + "'>here</a></p>");
1545
1546                //#526. allow click through event for flash installation message when using div containers.
1547				if (root.tagName == 'A' || root.tagName == "DIV") {
1548					root.onclick = function() {
1549						location.href = URL;
1550					};
1551				}
1552			}
1553
1554			// onFail
1555			if (opts.onFail) {
1556				var ret = opts.onFail.call(this);
1557				if (typeof ret == 'string') { root.innerHTML = ret; }
1558			}
1559		}
1560
1561		// http://flowplayer.org/forum/8/18186#post-18593
1562		if (IE) {
1563			window[opts.id] = document.getElementById(opts.id);
1564		}
1565
1566		// API methods for callback
1567		extend(this, {
1568
1569			getRoot: function() {
1570				return root;
1571			},
1572
1573			getOptions: function() {
1574				return opts;
1575			},
1576
1577
1578			getConf: function() {
1579				return conf;
1580			},
1581
1582			getApi: function() {
1583				return root.firstChild;
1584			}
1585
1586		});
1587	}
1588
1589	// setup jquery support
1590	if (JQUERY) {
1591
1592		// tools version number
1593		jQuery.tools = jQuery.tools || {version: '@VERSION'};
1594
1595		jQuery.tools.flashembed = {
1596			conf: GLOBAL_OPTS
1597		};
1598
1599		jQuery.fn.flashembed = function(opts, conf) {
1600			return this.each(function() {
1601				$(this).data("flashembed", flashembed(this, opts, conf));
1602			});
1603		};
1604	}
1605
1606}();

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.