2 * flowplayer.js 3.0.0-rc2. The Flowplayer API.
4 * This file is part of Flowplayer, http://flowplayer.org
6 * Author: Tero Piirainen, <support@flowplayer.org>
7 * Copyright (c) 2008 Flowplayer Ltd
9 * Released under the MIT License:
10 * http://www.opensource.org/licenses/mit-license.php
12 * Version: 3.0.0-rc2 - Fri Nov 07 2008 16:50:59 GMT-0000 (GMT+00:00)
19 - handling multiple instances
20 - Flowplayer programming API
21 - Flowplayer event model
22 - player loading / unloading
28 /*jslint glovar: true, browser: true */
29 /*global flowplayer, $f */
31 // {{{ private utility methods
35 // write into opera console
36 if (typeof opera == 'object') {
37 opera.postError("$f.fireEvent: " + args.join(" | "));
40 } else if (typeof console == 'object') {
41 console.log("$f.fireEvent", [].slice.call(args));
46 // thanks: http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
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]);
58 // stripped from jQuery, thanks John Resig
59 function each(obj, fn) {
62 var name, i = 0, length = obj.length;
65 if (length === undefined) {
67 if (fn.call(obj[name], name, obj[name]) === false) { break; }
72 for (var value = obj[0];
73 i < length && fn.call( value, i, value ) !== false; value = obj[++i]) {
83 return document.getElementById(id);
87 // used extensively. a very simple implementation.
88 function extend(to, from, skipFuncs) {
90 each(from, function(name, value) {
91 if (!skipFuncs || typeof value != 'function') {
98 // var arr = select("elem.className");
99 function select(query) {
100 var index = query.indexOf(".");
102 var tag = query.substring(0, index) || "*";
103 var klass = query.substring(index + 1, query.length);
105 each(document.getElementsByTagName(tag), function() {
106 if (this.className && this.className.indexOf(klass) != -1) {
114 // fix event inconsistencies across browsers
115 function stopEvent(e) {
116 e = e || window.event;
118 if (e.preventDefault) {
123 e.returnValue = false;
124 e.cancelBubble = true;
129 // push an event listener into existing array of listeners
130 function bind(to, evt, fn) {
131 to[evt] = to[evt] || [];
136 // generates an unique id
138 return "_" + ("" + Math.random()).substring(2, 10);
146 var Clip = function(json, index, player) {
154 // instance variables
155 if (typeof json == 'string') {
159 extend(this, json, true);
162 each(("Start*,MetaData,Pause*,Resume*,Seek*,Stop*,Finish,LastSecond,Update,BufferFull,BufferEmpty").split(","),
165 var evt = "on" + this;
168 if (evt.indexOf("*") != -1) {
169 evt = evt.substring(0, evt.length -1);
170 var before = "onBefore" + evt.substring(2);
172 self[before] = function(fn) {
173 bind(listeners, before, fn);
178 self[evt] = function(fn) {
179 bind(listeners, evt, fn);
184 // set common clip event listeners to player level
187 player[before] = self[before];
190 player[evt] = self[evt];
199 onCuepoint: function(points, fn) {
201 // embedded cuepoints
202 if (arguments.length == 1) {
203 cuepoints.embedded = [null, points];
207 if (typeof points == 'number') {
212 cuepoints[fnId] = [points, fn];
214 if (player.isLoaded()) {
215 player._api().fp_addCuepoints(points, index, fnId);
221 update: function(json) {
224 if (player.isLoaded()) {
225 player._api().fp_updateClip(json, index);
227 var conf = player._config();
228 var clip = (index == -1) ? conf.clip : conf.playlist[index];
229 extend(clip, json, true);
233 // internal event for performing clip tasks. should be made private someday
234 _fireEvent: function(evt, arg1, arg2, target) {
236 if (evt == 'onLoad') {
237 each(cuepoints, function(key, val) {
238 player._api().fp_addCuepoints(val[0], index, key);
243 // target clip we are working against
248 if (evt == 'onCuepoint') {
249 var fn = cuepoints[arg1];
251 return fn[1].call(player, target, arg2);
255 if (evt == 'onMetaData' || evt == 'onUpdate') {
257 extend(target, arg1);
259 if (!target.duration) {
260 target.duration = arg1.metaData.duration;
262 target.fullDuration = arg1.metaData.duration;
267 each(listeners[evt], function() {
268 ret = this.call(player, target, arg1);
276 // get cuepoints from config
277 if (json.onCuepoint) {
278 self.onCuepoint.apply(self, json.onCuepoint);
279 delete json.onCuepoint;
283 each(json, function(key, val) {
284 if (typeof val == 'function') {
285 bind(listeners, key, val);
291 // setup common clip event callbacks for Player object too (shortcuts)
293 player.onCuepoint = this.onCuepoint;
303 var Plugin = function(name, json, player, fn) {
307 var hasMethods = false;
310 extend(listeners, fn);
313 // custom callback functions in configuration
314 each(json, function(key, val) {
315 if (typeof val == 'function') {
316 listeners[key] = val;
321 // core plugin methods
324 animate: function(props, speed, fn) {
329 if (typeof speed == 'function') {
334 if (typeof props == 'string') {
343 listeners[fnId] = fn;
346 if (speed === undefined) { speed = 500; }
347 json = player._api().fp_animate(name, props, speed, fnId);
351 css: function(props, val) {
352 if (val !== undefined) {
357 json = player._api().fp_css(name, props);
363 this.display = 'block';
364 player._api().fp_showPlugin(name);
369 this.display = 'none';
370 player._api().fp_hidePlugin(name);
375 this.display = player._api().fp_togglePlugin(name);
379 fadeTo: function(o, speed, fn) {
381 if (typeof speed == 'function') {
388 listeners[fnId] = fn;
390 this.display = player._api().fp_fadeTo(name, o, speed, fnId);
395 fadeIn: function(speed, fn) {
396 return self.fadeTo(1, speed, fn);
399 fadeOut: function(speed, fn) {
400 return self.fadeTo(0, speed, fn);
403 getName: function() {
408 // internal method not meant to be used by clients
409 _fireEvent: function(evt, arg) {
412 // update plugins properties & methods
413 if (evt == 'onUpdate') {
414 var json = arg || player._api().fp_getPlugin(name);
415 if (!json) { return; }
421 each(json.methods, function() {
422 var method = "" + this;
424 self[method] = function() {
425 var a = [].slice.call(arguments);
426 var ret = player._api().fp_invoke(name, method, a);
427 return ret == 'undefined' ? self : ret;
435 var fn = listeners[evt];
441 // "one-shot" callback
442 if (evt.substring(0, 1) == "_") {
443 delete listeners[evt];
456 function Player(wrapper, params, conf) {
458 // private variables (+ arguments)
473 // {{{ public methods
481 isLoaded: function() {
482 return (api !== null);
485 getParent: function() {
490 if (api) { api.style.height = "0px"; }
495 if (api) { api.style.height = swfHeight + "px"; }
499 isHidden: function() {
500 return api && parseInt(api.style.height, 10) === 0;
506 if (!api && self._fireEvent("onBeforeLoad") !== false) {
508 // unload all instances
509 each(players, function() {
513 html = wrapper.innerHTML;
514 flashembed(wrapper, params, {config: conf});
519 bind(listeners, "onLoad", fn);
528 if (api && html.replace(/\s/g, '') !== '' && !api.fp_isFullscreen() &&
529 self._fireEvent("onBeforeUnload") !== false) {
531 wrapper.innerHTML = html;
532 self._fireEvent("onUnload");
539 getClip: function(index) {
540 if (index === undefined) {
543 return playlist[index];
547 getCommonClip: function() {
551 getPlaylist: function() {
555 getPlugin: function(name) {
556 var plugin = plugins[name];
558 // create plugin if nessessary
559 if (!plugin && self.isLoaded()) {
560 var json = self._api().fp_getPlugin(name);
562 plugin = new Plugin(name, json, self);
563 plugins[name] = plugin;
569 getScreen: function() {
570 return self.getPlugin("screen");
573 getControls: function() {
574 return self.getPlugin("controls");
577 getConfig: function() {
581 getFlashParams: function() {
585 loadPlugin: function(name, url, props, fn) {
587 // properties not supplied
588 if (typeof props == 'function') {
593 // if fn not given, make a fake id so that plugin's onUpdate get's fired
594 var fnId = fn ? makeId() : "_";
595 self._api().fp_loadPlugin(name, url, props, fnId);
600 var p = new Plugin(name, null, self, arg);
606 getState: function() {
607 return api ? api.fp_getState() : -1;
611 play: function(clip) {
614 if (clip !== undefined) {
615 self._api().fp_play(clip);
617 self._api().fp_play();
625 self.load(function() {
633 getVersion: function() {
634 var js = "flowplayer.js 3.0.0-rc2";
636 var ver = api.fp_getVersion();
645 throw "Flowplayer " +self.id()+ " not loaded. Try moving your call to player's onLoad event";
650 _config: function() {
658 each(("Click*,Load*,Unload*,Keypress*,Volume*,Mute*,Unmute*,PlaylistReplace,Fullscreen*,FullscreenExit,Error").split(","),
660 var name = "on" + this;
663 if (name.indexOf("*") != -1) {
664 name = name.substring(0, name.length -1);
665 var name2 = "onBefore" + name.substring(2);
666 self[name2] = function(fn) {
667 bind(listeners, name2, fn);
673 self[name] = function(fn) {
674 bind(listeners, name, fn);
682 each(("pause,resume,mute,unmute,stop,toggle,seek,getStatus,getVolume,setVolume,getTime,isPaused,isPlaying,startBuffering,stopBuffering,isFullscreen,reset").split(","),
686 self[name] = function(arg) {
687 if (!api) { return self; }
688 var ret = (arg === undefined) ? api["fp_" + name]() : api["fp_" + name](arg);
689 return ret == 'undefined' ? self : ret;
697 // {{{ public method: _fireEvent
699 self._fireEvent = function(evt, arg0, arg1, arg2) {
706 if (evt == 'onLoad' && !api) {
708 api = api || el(apiId);
709 swfHeight = api.clientHeight;
711 each(playlist, function() {
712 this._fireEvent("onLoad");
715 each(plugins, function(name, p) {
716 p._fireEvent("onUpdate");
720 commonClip._fireEvent("onLoad");
723 if (evt == 'onContextMenu') {
724 each(conf.contextMenu[arg0], function(key, fn) {
730 if (evt == 'onPluginEvent') {
731 var name = arg0.name || arg0;
732 var p = plugins[name];
735 p._fireEvent("onUpdate", arg0);
743 if (evt == 'onPlaylistReplace') {
746 each(arg0, function() {
747 playlist.push(new Clip(this, index++));
754 if (arg0 === 0 || (arg0 && arg0 >= 0)) {
757 var clip = playlist[arg0];
759 if (!clip || clip._fireEvent(evt, arg1, arg2) !== false) {
761 // clip argument is given for common clip, because it behaves as the target
762 ret = commonClip._fireEvent(evt, arg1, arg2, clip);
768 each(listeners[evt], function() {
769 ret = this.call(self, arg0);
771 // remove cached entry
773 listeners[evt].splice(i, 1);
777 if (ret === false) { return false; }
796 // register this player into global array of instances
800 // flashembed parameters
801 if (typeof params == 'string') {
802 params = {src: params};
806 playerId = wrapper.id || "fp" + makeId();
807 apiId = params.id || playerId + "_api";
809 conf.playerId = playerId;
812 // plain url is given as config
813 if (typeof conf == 'string') {
814 conf = {clip:{url:conf}};
817 // common clip is always there
818 conf.clip = conf.clip || {};
819 commonClip = new Clip(conf.clip, -1, self);
822 // wrapper href as playlist
823 if (wrapper.getAttribute("href")) {
824 conf.playlist = [{url:wrapper.getAttribute("href", 2)}];
828 conf.playlist = conf.playlist || [conf.clip];
831 each(conf.playlist, function() {
835 // clip is an array, we don't allow that
836 if (typeof clip == 'object' && clip.length) {
840 if (!clip.url && typeof clip == 'string') {
844 // populate common clip properties to each clip
845 extend(clip, conf.clip, true);
847 // modify configuration playlist
848 conf.playlist[index] = clip;
850 // populate playlist array
851 clip = new Clip(clip, index, self);
858 each(conf, function(key, val) {
859 if (typeof val == 'function') {
860 bind(listeners, key, val);
867 each(conf.plugins, function(name, val) {
869 plugins[name] = new Plugin(name, val, self);
874 // setup controlbar plugin if not explicitly defined
875 if (!conf.plugins || conf.plugins.controls === undefined) {
876 plugins.controls = new Plugin("controls", null, self);
879 // Flowplayer uses black background by default
880 params.bgcolor = params.bgcolor || "#000000";
883 // setup default settings for express install
884 params.version = params.version || [9,0];
885 params.expressInstall = 'http://www.flowplayer.org/swf/expressinstall.swf';
889 function doClick(e) {
890 if (self._fireEvent("onBeforeClick") !== false) {
896 // defer loading upon click
897 html = wrapper.innerHTML;
898 if (html.replace(/\s/g, '') !== '') {
900 if (wrapper.addEventListener) {
901 wrapper.addEventListener("click", doClick, false);
903 } else if (wrapper.attachEvent) {
904 wrapper.attachEvent("onclick", doClick);
907 // player is loaded upon page load
910 // prevent default action from wrapper (safari problem) loaded
911 if (wrapper.addEventListener) {
912 wrapper.addEventListener("click", stopEvent, false);
921 // possibly defer initialization until DOM get's loaded
922 if (typeof wrapper == 'string') {
923 flashembed.domReady(function() {
924 var node = el(wrapper);
927 throw "Flowplayer cannot access element: " + wrapper;
934 // we have a DOM element so page is already loaded
946 // {{{ flowplayer() & statics
948 // container for player instances
952 // this object is returned when multiple player's are requested
953 function Iterator(arr) {
955 this.length = arr.length;
957 this.each = function(fn) {
961 this.size = function() {
966 // these two variables are the only global variables
967 window.flowplayer = window.$f = function() {
970 var arg = arguments[0];
974 if (!arguments.length) {
975 each(players, function() {
976 if (this.isLoaded()) {
982 return instance || players[0];
985 if (arguments.length == 1) {
988 if (typeof arg == 'number') {
992 // $f(wrapper || 'containerId' || '*');
997 return new Iterator(players);
1000 // $f(wrapper || 'containerId');
1001 each(players, function() {
1002 if (this.id() == arg.id || this.id() == arg || this.getParent() == arg) {
1013 if (arguments.length > 1) {
1015 var swf = arguments[1];
1016 var conf = (arguments.length == 3) ? arguments[2] : {};
1018 if (typeof arg == 'string') {
1020 // select arg by classname
1021 if (arg.indexOf(".") != -1) {
1024 each(select(arg), function() {
1025 instances.push(new Player(this, clone(swf), clone(conf)));
1028 return new Iterator(instances);
1030 // select node by id
1033 return new Player(node !== null ? node : arg, swf, conf);
1037 // arg is a DOM element
1039 return new Player(arg, swf, conf);
1049 // called by Flash External Interface
1050 fireEvent: function(id, evt, a0, a1, a2) {
1052 return p ? p._fireEvent(evt, a0, a1, a2) : null;
1056 // create plugins by modifying Player's prototype
1057 addPlugin: function(name, fn) {
1058 Player.prototype[name] = fn;
1062 // utility methods for plugin developers
1072 //{{{ jQuery support
1074 if (typeof jQuery == 'function') {
1076 jQuery.prototype.flowplayer = function(params, conf) {
1079 if (!arguments.length || typeof arguments[0] == 'number') {
1081 this.each(function() {
1087 return arguments.length ? arr[arguments[0]] : new Iterator(arr);
1090 // create flowplayer instances
1091 return this.each(function() {
1092 $f(this, clone(params), conf ? clone(conf) : {});
1104 * flashembed 0.33. Adobe Flash embedding script
1106 * http://flowplayer.org/tools/flash-embed.html
1108 * Copyright (c) 2008 Tero Piirainen (support@flowplayer.org)
1110 * Released under the MIT License:
1111 * http://www.opensource.org/licenses/mit-license.php
1113 * >> Basically you can do anything you want but leave this header as is <<
1115 * first version 0.01 - 03/11/2008
1116 * version 0.33 - Mon Nov 03 2008 15:37:15 GMT-0000 (GMT+00:00)
1120 //{{{ utility functions
1122 var jQ = typeof jQuery == 'function';
1125 // from "Pro JavaScript techniques" by John Resig
1126 function isDomReady() {
1127 if (domReady.done) { return false; }
1130 if (d && d.getElementsByTagName && d.getElementById && d.body) {
1131 clearInterval(domReady.timer);
1132 domReady.timer = null;
1134 for (var i = 0; i < domReady.ready.length; i++) {
1135 domReady.ready[i].call();
1138 domReady.ready = null;
1139 domReady.done = true;
1143 // if jQuery is present, use it's more effective domReady method
1144 var domReady = jQ ? jQuery : function(f) {
1146 if (domReady.done) {
1150 if (domReady.timer) {
1151 domReady.ready.push(f);
1154 domReady.ready = [f];
1155 domReady.timer = setInterval(isDomReady, 13);
1160 // override extend params function
1161 function extend(to, from) {
1164 if (from.hasOwnProperty(key)) {
1165 to[key] = from[key];
1172 function concatVars(vars) {
1175 for (var key in vars) {
1177 out += [key] + '=' + toString(vars[key]) + '&';
1180 return out.substring(0, out.length -1);
1185 // JSON.toString() function
1186 function toString(obj) {
1188 switch (typeOf(obj)){
1190 obj = obj.replace(new RegExp('(["\\\\])', 'g'), '\\$1');
1192 // flash does not handle %- characters well. transforms "50%" to "50pct" (a dirty hack, I admit)
1193 obj = obj.replace(/^\s?(\d+)%/, "$1pct");
1194 return '"' +obj+ '"';
1197 return '['+ map(obj, function(el) {
1198 return toString(el);
1202 return '"function()"';
1206 for (var prop in obj) {
1207 if (obj.hasOwnProperty(prop)) {
1208 str.push('"'+prop+'":'+ toString(obj[prop]));
1211 return '{'+str.join(',')+'}';
1214 // replace ' --> " and remove spaces
1215 return String(obj).replace(/\s/g, " ").replace(/\'/g, "\"");
1219 // private functions
1220 function typeOf(obj) {
1221 if (obj === null || obj === undefined) { return false; }
1222 var type = typeof obj;
1223 return (type == 'object' && obj.push) ? 'array' : type;
1227 // version 9 bugfix: (http://blog.deconcept.com/2006/07/28/swfobject-143-released/)
1228 if (window.attachEvent) {
1229 window.attachEvent("onbeforeunload", function() {
1230 __flash_unloadHandler = function() {};
1231 __flash_savedUnloadHandler = function() {};
1235 function map(arr, func) {
1237 for (var i in arr) {
1238 if (arr.hasOwnProperty(i)) {
1239 newArr[i] = func(arr[i]);
1248 window.flashembed = function(root, userParams, flashvars) {
1253 function getHTML() {
1256 if (typeof flashvars == 'function') { flashvars = flashvars(); }
1259 // sometimes ie fails to load flash if it's on cache
1260 params.src += ((params.src.indexOf("?") != -1 ? "&" : "?") + Math.random());
1264 if (navigator.plugins && navigator.mimeTypes && navigator.mimeTypes.length) {
1266 html = '<embed type="application/x-shockwave-flash" ';
1269 extend(params, {name:params.id});
1272 for (var key in params) {
1273 if (params[key] !== null) {
1274 html += [key] + '="' +params[key]+ '"\n\t';
1279 html += 'flashvars=\'' + concatVars(flashvars) + '\'';
1282 // thanks Tom Price (07/17/2008)
1288 html = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ';
1289 html += 'width="' + params.width + '" height="' + params.height + '"';
1291 // force id for IE. otherwise embedded Flash object cannot be returned
1292 if (!params.id && document.all) {
1293 params.id = "_" + ("" + Math.random()).substring(5);
1297 html += ' id="' + params.id + '"';
1301 html += '\n\t<param name="movie" value="'+ params.src +'" />';
1303 params.id = params.src = params.width = params.height = null;
1305 for (var k in params) {
1306 if (params[k] !== null) {
1307 html += '\n\t<param name="'+ k +'" value="'+ params[k] +'" />';
1312 html += '\n\t<param name="flashvars" value=\'' + concatVars(flashvars) + '\' />';
1315 html += "</object>";
1333 // very common params
1338 // flashembed specific options
1341 expressInstall:null,
1344 // flashembed defaults
1345 // bgcolor: 'transparent',
1346 allowfullscreen: true,
1347 allowscriptaccess: 'always',
1349 type: 'application/x-shockwave-flash',
1350 pluginspage: 'http://www.adobe.com/go/getflashplayer'
1354 if (typeof userParams == 'string') {
1355 userParams = {src: userParams};
1358 extend(params, userParams);
1360 var version = flashembed.getVersion();
1361 var required = params.version;
1362 var express = params.expressInstall;
1363 var debug = params.debug;
1366 if (typeof root == 'string') {
1367 var el = document.getElementById(root);
1371 domReady(function() {
1372 flashembed(root, userParams, flashvars);
1378 if (!root) { return; }
1382 if (!required || flashembed.isSupported(required)) {
1383 params.onFail = params.version = params.expressInstall = params.debug = null;
1385 // root.innerHTML may cause broplems: http://domscripting.com/blog/display/99
1386 // thanks to: Ryan Rud
1387 // var tmp = document.createElement("extradiv");
1388 // tmp.innerHTML = getHTML();
1389 // root.appendChild(tmp);
1391 root.innerHTML = getHTML();
1394 return root.firstChild;
1396 // custom fail event
1397 } else if (params.onFail) {
1398 var ret = params.onFail.call(params, flashembed.getVersion(), flashvars);
1399 if (ret === true) { root.innerHTML = ret; }
1403 } else if (required && express && flashembed.isSupported([6,65])) {
1405 extend(params, {src: express});
1408 MMredirectURL: location.href,
1409 MMplayerType: 'PlugIn',
1410 MMdoctitle: document.title
1413 root.innerHTML = getHTML();
1418 // minor bug fixed here 08.04.2008 (thanks JRodman)
1420 if (root.innerHTML.replace(/\s/g, '') !== '') {
1421 // custom content was supplied
1425 "<h2>Flash version " + required + " or greater is required</h2>" +
1427 (version[0] > 0 ? "Your version is " + version : "You have no flash plugin installed") +
1429 "<p>Download latest version from <a href='" + params.pluginspage + "'>here</a></p>";
1441 //{{{ static methods
1443 extend(window.flashembed, {
1445 // arr[major, minor, fix]
1446 getVersion: function() {
1448 var version = [0, 0];
1450 if (navigator.plugins && typeof navigator.plugins["Shockwave Flash"] == "object") {
1451 var _d = navigator.plugins["Shockwave Flash"].description;
1452 if (typeof _d != "undefined") {
1453 _d = _d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
1454 var _m = parseInt(_d.replace(/^(.*)\..*$/, "$1"), 10);
1455 var _r = /r/.test(_d) ? parseInt(_d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
1459 } else if (window.ActiveXObject) {
1461 try { // avoid fp 6 crashes
1462 var _a = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
1466 _a = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
1468 _a.AllowScriptAccess = "always"; // throws if fp < 6.47
1471 if (version[0] == 6) { return; }
1474 _a = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
1481 if (typeof _a == "object") {
1482 _d = _a.GetVariable("$version"); // bugs in fp 6.21 / 6.23
1483 if (typeof _d != "undefined") {
1484 _d = _d.replace(/^\S+\s+(.*)$/, "$1").split(",");
1485 version = [parseInt(_d[0], 10), parseInt(_d[2], 10)];
1493 isSupported: function(version) {
1494 var now = flashembed.getVersion();
1495 var ret = (now[0] > version[0]) || (now[0] == version[0] && now[1] >= version[1]);
1501 // returns a String representation from JSON object
1509 // setup jquery support
1512 jQuery.prototype.flashembed = function(params, flashvars) {
1513 return this.each(function() {
1514 flashembed(this, params, flashvars);