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(/&/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.