1/*! 2 3 Flowplayer v5.4.3 (Wednesday, 19. June 2013 11:34PM) | flowplayer.org/license 4 5*/ 6!function($) { 7 8/* 9 jQuery.browser for 1.9+ 10 11 We all love feature detection but that's sometimes not enough. 12 13 @author Tero Piirainen 14*/ 15!function($) { 16 17 if (!$.browser) { 18 19 var b = $.browser = {}, 20 ua = navigator.userAgent.toLowerCase(), 21 match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 22 /(safari)[ \/]([\w.]+)/.exec(ua) || 23 /(webkit)[ \/]([\w.]+)/.exec(ua) || 24 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 25 /(msie) ([\w.]+)/.exec(ua) || 26 ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || []; 27 28 if (match[1]) { 29 b[match[1]] = true; 30 b.version = match[2] || "0"; 31 } 32 33 } 34 35}(jQuery); 36// auto-install (any video tag with parent .flowplayer) 37$(function() { 38 if (typeof $.fn.flowplayer == 'function') { 39 $("video").parent(".flowplayer").flowplayer(); 40 } 41}); 42 43var instances = [], 44 extensions = [], 45 UA = window.navigator.userAgent; 46 47 48/* flowplayer() */ 49window.flowplayer = function(fn) { 50 return $.isFunction(fn) ? extensions.push(fn) : 51 typeof fn == 'number' || fn === undefined ? instances[fn || 0] : 52 $(fn).data("flowplayer"); 53}; 54 55$(window).on('beforeunload', function() { 56 $.each(instances, function(i, api) { 57 if (api.conf.splash) { 58 api.unload(); 59 } else { 60 api.bind("error", function () { 61 $(".flowplayer.is-error .fp-message").remove(); 62 }); 63 } 64 }); 65}); 66 67$.extend(flowplayer, { 68 69 version: '5.4.3', 70 71 engine: {}, 72 73 conf: {}, 74 75 support: {}, 76 77 defaults: { 78 79 debug: false, 80 81 // true = forced playback 82 disabled: false, 83 84 // first engine to try 85 engine: 'html5', 86 87 fullscreen: window == window.top, 88 89 // keyboard shortcuts 90 keyboard: true, 91 92 // default aspect ratio 93 ratio: 9 / 16, 94 95 adaptiveRatio: false, 96 97 // scale flash object to video's aspect ratio in normal mode? 98 flashfit: false, 99 100 rtmp: 0, 101 102 splash: false, 103 104 swf: "//releases.flowplayer.org/5.4.3/flowplayer.swf", 105 106 speeds: [0.25, 0.5, 1, 1.5, 2], 107 108 tooltip: true, 109 110 // initial volume level 111 volume: typeof localStorage != "object" ? 1 : localStorage.muted == "true" ? 0 : !isNaN(localStorage.volume) ? localStorage.volume || 1 : 1, 112 113 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#error-codes 114 errors: [ 115 116 // video exceptions 117 '', 118 'Video loading aborted', 119 'Network error', 120 'Video not properly encoded', 121 'Video file not found', 122 123 // player exceptions 124 'Unsupported video', 125 'Skin not found', 126 'SWF file not found', 127 'Subtitles not found', 128 'Invalid RTMP URL', 129 'Unsupported video format. Try installing Adobe Flash.' 130 ], 131 errorUrls: ['','','','','','','','','','', 132 'http://get.adobe.com/flashplayer/' 133 ], 134 playlist: [] 135 136 } 137 138}); 139 140// keep track of players 141var playerCount = 1; 142 143// jQuery plugin 144$.fn.flowplayer = function(opts, callback) { 145 146 if (typeof opts == 'string') opts = { swf: opts } 147 if ($.isFunction(opts)) { callback = opts; opts = {} } 148 149 return !opts && this.data("flowplayer") || this.each(function() { 150 151 // private variables 152 var root = $(this).addClass("is-loading"), 153 conf = $.extend({}, flowplayer.defaults, flowplayer.conf, opts, root.data()), 154 videoTag = $("video", root).addClass("fp-engine").removeAttr("controls"), 155 urlResolver = videoTag.length ? new URLResolver(videoTag) : null, 156 storage = {}, 157 lastSeekPosition, 158 engine; 159 160 if (conf.playlist.length) { // Create initial video tag if called without 161 var preload = videoTag.attr('preload'), placeHolder; 162 if (videoTag.length) videoTag.replaceWith(placeHolder = $('<p />')); 163 videoTag = $('<video />').addClass('fp-engine'); 164 placeHolder ? placeHolder.replaceWith(videoTag) : root.prepe
164nd(videoTag); 165 videoTag.attr('preload', preload); 166 if (typeof conf.playlist[0] === 'string') videoTag.attr('src', conf.playlist[0]); 167 else { 168 $.each(conf.playlist[0], function(i, plObj) { 169 for (var type in plObj) { 170 if (plObj.hasOwnProperty(type)) { 171 videoTag.append($('<source />').attr({type: 'video/' + type, src: plObj[type]})); 172 } 173 } 174 }); 175 } 176 urlResolver = new URLResolver(videoTag); 177 178 } 179 180 //stop old instance 181 var oldApi = root.data('flowplayer'); 182 if (oldApi) oldApi.unload(); 183 184 root.data('fp-player_id', root.data('fp-player_id') || playerCount++); 185 186 try { 187 storage = window.localStorage || storage; 188 } catch(e) {} 189 190 var isRTL = (this.currentStyle && this.currentStyle['direction'] === 'rtl') 191 || (window.getComputedStyle && window.getComputedStyle(this, null).getPropertyValue('direction') === 'rtl'); 192 193 if (isRTL) root.addClass('is-rtl'); 194 195 /*** API ***/ 196 var api = oldApi || { 197 198 // properties 199 conf: conf, 200 currentSpeed: 1, 201 volumeLevel: typeof conf.volume === "undefined" ? storage.volume * 1 : conf.volume, 202 video: {}, 203 204 // states 205 disabled: false, 206 finished: false, 207 loading: false, 208 muted: storage.muted == "true" || conf.muted, 209 paused: false, 210 playing: false, 211 ready: false, 212 splash: false, 213 rtl: isRTL, 214 215 // methods 216 load: function(video, callback) { 217 218 if (api.error || api.loading || api.disabled) return; 219 220 // resolve URL 221 video = urlResolver.resolve(video); 222 $.extend(video, engine.pick(video.sources)); 223 224 if (video.src) { 225 var e = $.Event("load"); 226 root.trigger(e, [api, video, engine]); 227 228 if (!e.isDefaultPrevented()) { 229 engine.load(video); 230 231 // callback 232 if ($.isFunction(video)) callback = video; 233 if (callback) root.one("ready", callback); 234 } else { 235 api.loading = false; 236 } 237 } 238 239 return api; 240 }, 241 242 pause: function(fn) { 243 if (api.ready && !api.seeking && !api.disabled && !api.loading) { 244 engine.pause(); 245 api.one("pause", fn); 246 } 247 return api; 248 }, 249 250 resume: function() { 251 252 if (api.ready && api.paused && !api.disabled) { 253 engine.resume(); 254 255 // Firefox (+others?) does not fire "resume" after finish 256 if (api.finished) { 257 api.trigger("resume"); 258 api.finished = false; 259 } 260 } 261 262 return api; 263 }, 264 265 toggle: function() { 266 return api.ready ? api.paused ? api.resume() : api.pause() : api.load(); 267 }, 268 269 /* 270 seek(1.4) -> 1.4s time 271 seek(true) -> 10% forward 272 seek(false) -> 10% backward 273 */ 274 seek: function(time, callback) { 275 if (api.ready) { 276 277 if (typeof time == "boolean") { 278 var delta = api.video.duration * 0.1; 279 time = api.video.time + (time ? delta : -delta); 280 } 281 time = lastSeekPosition = Math.min(Math.max(time, 0), api.video.duration).toFixed(1); 282 var ev = $.Event('beforeseek'); 283 root.trigger(ev, [api, time]); 284 if (!ev.isDefaultPrevented()) { 285 engine.seek(time); 286 if ($.isFunction(callback)) root.one("seek", callback); 287 } else { 288 api.seeking = false; 289 root.toggleClass("is-seeking", api.seeking); // remove loading indicator 290 } 291 } 292 return api; 293 }, 294 295 /* 296 seekTo(1) -> 10% 297 seekTo(2) -> 20% 298 seekTo(3) -> 30% 299 ... 300 seekTo() -> last position 301 */ 302 seekTo: function(position, fn) { 303 var time = position === undefined ? lastSeekPosition : api.video.duration * 0.1 * position; 304 return api.seek(time, fn); 305 }, 306 307 mute: function(flag) { 308 if (flag === undefined) flag = !api.muted; 309 storage.muted = api.muted = flag; 310 storage.volume = !isNaN(storage.volume) ? storage.volume : conf.volume; // make sure storage has volume 311 api.volume(flag ? 0 : storage.volume, true); 312 api.trigger("mute", flag); 313 return api; 314 }, 315 316 volume: function(level, skipStore) { 317 if (api.ready) { 318 level = Math.min(Math.max(level, 0), 1); 319 if (!skipStore) storage.volume = level; 320 engine.volume(level); 321 } 322 323 return api; 324 }, 325 326 speed: function(val, callback) { 327 328 if (api.ready) { 329 330 // increase / decrease 331 if (typeof val == "boolean") { 332 val = conf.speeds[$.inArray(api.currentSpeed, conf.speeds) + (val ? 1 : -1)] || api.currentSpeed; 333 } 334 335 engine.speed(val); 336 if (callback) root.one("speed", callback); 337 } 338 339 return api; 340 }, 341 342 343 stop: function() { 344 if (api.ready) { 345 api.pause(); 346 api.seek(0, function() { 347 root.trigger("stop"); 348 }); 349 } 350 return api; 351 }, 352 353 unload: function() { 354 if (!root.hasClass("is-embedding")) { 355 356 if (conf.splash) { 357 api.trigger("unload"); 358 engine.unload(); 359 } else { 360 api.stop(); 361 } 362 } 363 return api; 364 }, 365 366 disable: function(flag) { 367 if (flag === undefined) flag = !api.disabled; 368
369 if (flag != api.disabled) { 370 api.disabled = flag; 371 api.trigger("disable", flag); 372 } 373 return api; 374 } 375 376 }; 377 378 api.conf = $.extend(api.conf, conf); 379 380 381 /* event binding / unbinding */ 382 $.each(['bind', 'one', 'unbind'], function(i, key) { 383 api[key] = function(type, fn) { 384 root[key](type, fn); 385 return api; 386 }; 387 }); 388 389 api.trigger = function(event, arg) { 390 root.trigger(event, [api, arg]); 391 return api; 392 }; 393 394 395 /*** Behaviour ***/ 396 if (!root.data('flowplayer')) { // Only bind once 397 root.bind("boot", function() { 398 399 // conf 400 $.each(['autoplay', 'loop', 'preload', 'poster'], function(i, key) { 401 var val = videoTag.attr(key); 402 if (val !== undefined) conf[key] = val ? val : true; 403 }); 404 405 // splash 406 if (conf.splash || root.hasClass("is-splash") || !flowplayer.support.firstframe) { 407 api.forcedSplash = !conf.splash && !root.hasClass("is-splash"); 408 api.splash = conf.splash = conf.autoplay = true; 409 root.addClass("is-splash"); 410 videoTag.attr("preload", "none"); 411 } 412 413 // extensions 414 $.each(extensions, function(i) { 415 this(api, root); 416 }); 417 418 // 1. use the configured engine 419 engine = flowplayer.engine[conf.engine]; 420 if (engine) engine = engine(api, root); 421 422 if (engine.pick(urlResolver.initialSources)) { 423 api.engine = conf.engine; 424 425 // 2. failed -> try another 426 } else { 427 $.each(flowplayer.engine, function(name, impl) { 428 if (name != conf.engine) { 429 engine = this(api, root); 430 if (engine.pick(urlResolver.initialSources)) api.engine = name; 431 return false; 432 } 433 }); 434 } 435 436 // instances 437 instances.push(api); 438 439 // no engine 440 if (!api.engine) return api.trigger("error", { code: flowplayer.support.flashVideo ? 5 : 10 }); 441 442 // start 443 conf.splash ? api.unload() : api.load(); 444 445 // disabled 446 if (conf.disabled) api.disable(); 447 448 //initial volumelevel 449 engine.volume(api.volumeLevel); 450 451 // initial callback 452 root.one("ready", callback); 453 454 455 }).bind("load", function(e, api, video) { 456 457 // unload others 458 if (conf.splash) { 459 $(".flowplayer").filter(".is-ready, .is-loading").not(root).each(function() { 460 var api = $(this).data("flowplayer"); 461 if (api.conf.splash) api.unload(); 462 }); 463 } 464 465 // loading 466 root.addClass("is-loading"); 467 api.loading = true; 468 469 470 }).bind("ready", function(e, api, video) { 471 video.time = 0; 472 api.video = video; 473 474 function notLoading() { 475 root.removeClass("is-loading"); 476 api.loading = false; 477 } 478 479 if (conf.splash) root.one("progress", notLoading); 480 else notLoading(); 481 482 // saved state 483 if (api.muted) api.mute(true); 484 else api.volume(api.volumeLevel); 485 486 487 }).bind("unload", function(e) { 488 if (conf.splash) videoTag.remove(); 489 root.removeClass("is-loading"); 490 api.loading = false; 491 492 493 }).bind("ready unload", function(e) { 494 var is_ready = e.type == "ready"; 495 root.toggleClass("is-splash", !is_ready).toggleClass("is-ready", is_ready); 496 api.ready = is_ready; 497 api.splash = !is_ready; 498 499 500 }).bind("progress", function(e, api, time) { 501 api.video.time = time; 502 503 504 }).bind("speed", function(e, api, val) { 505 api.currentSpeed = val; 506 507 }).bind("volume", function(e, api, level) { 508 api.volumeLevel = Math.round(level * 100) / 100; 509 if (!api.muted) storage.volume = level; 510 else if (level) api.mute(false); 511 512 513 }).bind("beforeseek seek", function(e) { 514 api.seeking = e.type == "beforeseek"; 515 root.toggleClass("is-seeking", api.seeking); 516 517 }).bind("ready pause resume unload finish stop", function(e, _api, video) { 518 519 // PAUSED: pause / finish 520 api.paused = /pause|finish|unload|stop/.test(e.type); 521 522 // SHAKY HACK: first-frame / preload=none 523 if (e.type == "ready") { 524 api.paused = conf.preload == 'none'; 525 if (video) { 526 api.paused = !video.duration || !conf.autoplay && (conf.preload != 'none'); 527 } 528 } 529 530 // the opposite 531 api.playing = !api.paused; 532 533 // CSS classes 534 root.toggleClass("is-paused", api.paused).toggleClass("is-playing", api.playing); 535 536 // sanity check 537 if (!api.load.ed) api.pause(); 538 539 }).bind("finish", function(e) { 540 api.finished = true; 541 542 }).bind("error", function() { 543 videoTag.remove(); 544 }); 545 } 546 547 // boot 548 root.trigger("boot", [api, root]).data("flowplayer", api); 549 550 }); 551 552}; 553 554!function() { 555 556 var parseIpadVersion = function(UA) { 557 var e = /Version\/(\d\.\d)/.exec(UA); 558 if (e && e.length > 1) { 559 return parseFloat(e[1], 10); 560 } 561 return 0; 562 }; 563 564 var s = flowplayer.support, 565 browser = $.browser, 566 video = $("<video loop autoplay preload/>")[0], 567 IS_IE = browser.msie, 568 UA = navigator.userAgent, 569 IS_IPAD = /iPad|MeeGo/.test(UA) && !/CriOS/.test(UA), 570 IS_IPAD_CHROME = /iPad/.test(UA) && /CriOS/.test(UA), 571 IS_IPHONE = /iP(hone|od)/i.test(UA) && !/iPad/.test(UA), 572 IS_ANDROID = /Android/.test(UA) && !/Firefox/.test(UA), 573 IS_ANDROID_FIREFOX = /Android/.test(UA) && /Firefox/.test(UA), 574 IS_SILK = /Silk/.test(UA), 575 IS_WP = /IEMobile/.test(UA), 576 IPAD_VER = IS_IPAD ? parseIpadVersion(UA) : 0, 577 ANDROID_VER = IS_ANDROID ? parseFloat(/Android\ (\d\.\d)/.exec(UA)[1], 10) : 0; 578 $.extend(s, { 579 subtitles: !!video.addTextTrack, 580 fullscreen: !IS_ANDROID && 581 (typeof document.webkitCancelFullScreen == 'function' && !/Mac OS X 10_5.+Version\/5\.0\.\d S
581afari/.test(UA) || 582 document.mozFullScreenEnabled || 583 typeof document.exitFullscreen == 'function'), 584 inlineBlock: !(IS_IE && browser.version < 8), 585 touch: ('ontouchstart' in window), 586 dataload: !IS_IPAD && !IS_IPHONE && !IS_WP, 587 zeropreload: !IS_IE && !IS_ANDROID, // IE supports only preload=metadata 588 volume: !IS_IPAD && !IS_ANDROID && !IS_IPHONE && !IS_SILK && !IS_IPAD_CHROME, 589 cachedVideoTag: !IS_IPAD && !IS_IPHONE && !IS_IPAD_CHROME && !IS_WP, 590 firstframe: !IS_IPHONE && !IS_IPAD && !IS_ANDROID && !IS_SILK && !IS_IPAD_CHROME && !IS_WP && !IS_ANDROID_FIREFOX, 591 inlineVideo: !IS_IPHONE && !IS_SILK && !IS_WP && (!IS_ANDROID || ANDROID_VER >= 3), 592 hlsDuration: !browser.safari || IS_IPAD || IS_IPHONE || IS_IPAD_CHROME, 593 seekable: !IS_IPAD && !IS_IPAD_CHROME 594 }); 595 596 // flashVideo 597 try { 598 var ver = IS_IE ? new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable('$version') : 599 navigator.plugins["Shockwave Flash"].description; 600 601 ver = ver.split(/\D+/); 602 if (ver.length && !ver[0]) ver = ver.slice(1); 603 604 s.flashVideo = ver[0] > 9 || ver[0] == 9 && ver[3] >= 115; 605 606 } catch (ignored) {} 607 try { 608 s.video = !!video.canPlayType; 609 s.video && video.canPlayType('video/mp4'); 610 } catch (e) { 611 s.video = false; 612 } 613 614 // animation 615 s.animation = (function() { 616 var vendors = ['','Webkit','Moz','O','ms','Khtml'], el = $("<p/>")[0]; 617 618 for (var i = 0; i < vendors.length; i++) { 619 if (el.style[vendors[i] + 'AnimationName'] !== 'undefined') return true; 620 } 621 })(); 622 623 624 625}(); 626 627 628/* The most minimal Flash embedding */ 629 630// movie required in opts 631function embed(swf, flashvars) { 632 633 var id = "obj" + ("" + Math.random()).slice(2, 15), 634 tag = '<object class="fp-engine" id="' + id+ '" name="' + id + '" '; 635 636 tag += $.browser.msie ? 'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">' : 637 ' data="' + swf + '" type="application/x-shockwave-flash">'; 638 639 var opts = { 640 width: "100%", 641 height: "100%", 642 allowscriptaccess: "always", 643 wmode: "transparent", 644 quality: "high", 645 flashvars: "", 646 647 // https://github.com/flowplayer/flowplayer/issues/13#issuecomment-9369919 648 movie: swf + ($.browser.msie ? "?" + id : ""), 649 name: id 650 }; 651 652 // flashvars 653 $.each(flashvars, function(key, value) { 654 opts.flashvars += key + "=" + value + "&"; 655 }); 656 657 // parameters 658 $.each(opts, function(key, value) { 659 tag += '<param name="' + key + '" value="'+ value +'"/>'; 660 }); 661 662 tag += "</object>"; 663 664 return $(tag); 665} 666 667 668// Flash is buggy allover 669if (window.attachEvent) { 670 window.attachEvent("onbeforeunload", function() { 671 __flash_savedUnloadHandler = __flash_unloadHandler = function() {}; 672 }); 673} 674 675 676flowplayer.engine.flash = function(player, root) { 677 678 var conf = player.conf, 679 video = player.video, 680 callbackId, 681 objectTag, 682 api; 683 684 var engine = { 685 686 pick: function(sources) { 687 688 if (flowplayer.support.flashVideo) { 689 690 // always pick video/flash first 691 var flash = $.grep(sources, function(source) { return source.type == 'flash'; })[0]; 692 if (flash) return flash; 693 694 for (var i = 0, source; i < sources.length; i++) { 695 source = sources[i]; 696 if (/mp4|flv/.test(source.type)) return source; 697 } 698 } 699 }, 700 701 load: function(video) { 702 703 function escapeURL(url) { 704 return url.replace(/&/g, '%26').replace(/&/g, '%26').replace(/=/g, '%3D'); 705 } 706 707 var html5Tag = $("video", root), 708 url = escapeURL(video.src); 709 is_absolute = /^https?:/.test(url); 710 711 // html5 tag not needed (pause needed for firefox) 712 if (html5Tag.length > 0 && flowplayer.support.video) html5Tag[0].pause(); 713 html5Tag.remove(); 714 715 // convert to absolute 716 if (!is_absolute && !conf.rtmp) url = $("<img/>").attr("src", url)[0].src; 717 718 if (api) { 719 api.__play(url); 720 721 } else { 722 723 callbackId = "fp" + ("" + Math.random()).slice(3, 15); 724 725 var opts = { 726 hostname: conf.embedded ? conf.hostname : location.hostname, 727 url: url, 728 callback: "jQuery."+ callbackId 729 }; 730 if (root.data("origin")) { 731 opts.origin = root.data("origin"); 732 } 733 734 if (is_absolute) delete conf.rtmp; 735 736 // optional conf 737 $.each(['key', 'autoplay', 'preload', 'rtmp', 'loop', 'debug', 'preload', 'splash'], function(i, key) { 738 if (conf[key]) opts[key] = conf[key]; 739 }); 740 741 // issue #376 742 if (opts.rtmp) { 743 opts.rtmp = escapeURL(opts.rtmp); 744 } 745 746 objectTag = embed(conf.swf, opts); 747 748 objectTag.prependTo(root); 749 750 api = objectTag[0]; 751 752 // throw error if no loading occurs 753 setTimeout(function() { 754 try { 755 if (!api.PercentLoaded()) { 756 return root.trigger("error", [player, { code: 7, url: conf.swf }]); 757 } 758 } catch (e) {} 759 }, 5000); 760 761 // listen 762 $[callbackId] = function(type, arg) { 763 764 if (conf.debug && type != "status") console.log("--", type, arg); 765 766 var event = $.Event(type); 767 768 switch (type) { 769 770 // RTMP sends a lot of finish events in vain 771 // case "finish": if (conf.rtmp) return; 772 case "ready": arg = $.extend(video, arg); break; 773 case "click": event.flash = true; break; 774 case "keydown": event.which = arg; break; 775 case "seek": video.time = arg; break; 776 case "buffered": video.buffered = true; break; 777 778 case "status": 779 player.trigger("progress", arg.time); 780 781 if (arg.buffer <= video.bytes && !video.buffered) { 782 video.buffer = arg.buffer / video.bytes * video.duration; 783 player.trigger("buffer", video.buffer); 784 785 } else if (video.buffered) player.trigger("buffered"); 786 787 break; 788 789 } 790 791 // add some delay to that player is truly ready after an event 792 setTimeout(function() { player.trigger(event, arg); }, 1) 793 794 }; 795 796 } 797 798 }, 799 800 // not supported yet 801 speed: $.noop, 802 803 804 unload: function() { 805 api && api.__unload && api.__unload(); 806 delete $[callbackId]; 807 $("object", root).remove(); 808 api = 0; 809 } 810 811 }; 812 813 $.each("pause,resume,seek,volume".split(","), function(i, name) { 814 815 engine[name] = function(arg) { 816 817 if (player.ready) { 818 819 if (name == 'seek' && player.video.time && !player.paused) { 820 player.trigger("beforeseek"); 821 } 822 823 if (arg === undefined) { 824 api["__" + name](); 825 826 } else { 827 api["__" + name](arg); 828 } 829 830 } 831 }; 832 833 }); 834 835 var win = $(window); 836 837 // handle Flash object aspect ratio 838 player.bind("ready fullscreen fullscreen-exit", function(e) { 839 var origH = root.height(), 840 origW = root.width(); 841 if (player.conf.flashfit || /full/.test(e.type)) { 842 843 var fs = player.isFullscreen, 844 truefs = fs && FS_SUPPORT, 845 ie7 = !flowplayer.support.inlineBlock,
846 screenW = fs ? (truefs ? screen.availWidth : win.width()) : origW, 847 screenH = fs ? (truefs ? screen.availHeight : win.height()) : origH, 848 849 // default values for fullscreen-exit without flashfit 850 hmargin = truefs ? screen.width - screen.availWidth : 0, 851 vmargin = truefs ? screen.height - screen.availHeight : 0, 852 objwidth = ie7 ? origW : '', 853 objheight = ie7 ? origH : '', 854 855 aspectratio, dataratio; 856 857 if (player.conf.flashfit || e.type === "fullscreen") { 858 aspectratio = player.video.width / player.video.height, 859 dataratio = player.video.height / player.video.width, 860 objheight = Math.max(dataratio * screenW), 861 objwidth = Math.max(aspectratio * screenH); 862 objheight = objheight > screenH ? objwidth * dataratio : objheight; 863 objheight = Math.min(Math.round(objheight), screenH); 864 objwidth = objwidth > screenW ? objheight * aspectratio : objwidth; 865 objwidth = Math.min(Math.round(objwidth), screenW); 866 vmargin = Math.max(Math.round((screenH + vmargin - objheight) / 2), 0); 867 hmargin = Math.max(Math.round((screenW + hmargin - objwidth) / 2), 0); 868 } 869 870 $("object", root).css({ 871 width: objwidth, 872 height: objheight, 873 marginTop: vmargin, 874 marginLeft: hmargin 875 }); 876 } 877 }); 878 879 return engine; 880 881}; 882 883 884var VIDEO = $('<video/>')[0]; 885 886// HTML5 --> Flowplayer event 887var EVENTS = { 888 889 // fired 890 ended: 'finish', 891 pause: 'pause', 892 play: 'resume', 893 progress: 'buffer', 894 timeupdate: 'progress', 895 volumechange: 'volume', 896 ratechange: 'speed', 897 //seeking: 'beforeseek', 898 seeked: 'seek', 899 // abort: 'resume', 900 901 // not fired 902 loadeddata: 'ready', 903 // loadedmetadata: 0, 904 // canplay: 0, 905 906 // error events 907 // load: 0, 908 // emptied: 0, 909 // empty: 0, 910 error: 'error', 911 dataunavailable: 'error' 912 913}; 914 915function round(val) { 916 return Math.round(val * 100) / 100; 917} 918 919function getType(type) { 920 return /mpegurl/i.test(type) ? "application/x-mpegurl" : "video/" + type; 921} 922 923function canPlay(type) { 924 if (!/^(video|application)/.test(type)) 925 type = getType(type); 926 return !!VIDEO.canPlayType(type).replace("no", ''); 927} 928 929function findFromSourcesByType(sources, type) { 930 var arr = $.grep(sources, function(s) { 931 return s.type === type; 932 }); 933 return arr.length ? arr[0] : null; 934} 935 936var videoTagCache; 937var createVideoTag = function(video) { 938 if (videoTagCache) { 939 return videoTagCache.attr({type: getType(video.type), src: video.src}); 940 } 941 return (videoTagCache = $("<video/>", { 942 src: video.src, 943 type: getType(video.type), 944 'class': 'fp-engine', 945 'autoplay': 'autoplay', 946 preload: 'none', 947 'x-webkit-airplay': 'allow' 948 })); 949} 950 951flowplayer.engine.html5 = function(player, root) { 952 953 var videoTag = $("video", root), 954 support = flowplayer.support, 955 track = $("track", videoTag), 956 conf = player.conf, 957 self, 958 timer, 959 api; 960 961 return self = { 962 963 pick: function(sources) { 964 if (support.video) { 965 if (conf.videoTypePreference) { 966 var mp4source = findFromSourcesByType(sources, conf.videoTypePreference); 967 if (mp4source) return mp4source; 968 } 969 for (var i = 0, source; i < sources.length; i++) { 970 if (canPlay(sources[i].type)) return sources[i]; 971 } 972 } 973 }, 974 975 load: function(video) { 976 977 if (conf.splash && !api) { 978 979 videoTag = createVideoTag(video).prependTo(root); 980 981 if (!support.inlineVideo) { 982 videoTag.css({ 983 position: 'absolute', 984 top: '-9999em' 985 }); 986 } 987 988 if (track.length) videoTag.append(track.attr("default", "")); 989 990 if (conf.loop) videoTag.attr("loop", "loop"); 991 992 api = videoTag[0]; 993 994 } else { 995 996 api = videoTag[0]; 997 var sources = videoTag.find('source'); 998 if (!api.src && sources.length) { 999 api.src = video.src; 1000 sources.remove(); 1001 } 1002 1003 // change of clip 1004 if (player.video.src && video.src != player.video.src) { 1005 videoTag.attr("autoplay", "autoplay"); 1006 api.src = video.src; 1007 1008 // preload=none or no initial "loadeddata" event 1009 } else if (conf.preload == 'none' || !support.dataload) { 1010
1011 if (support.zeropreload) { 1012 player.trigger("ready", video).trigger("pause").one("ready", function() { 1013 root.trigger("resume"); 1014 }); 1015 1016 } else { 1017 player.one("ready", function() { 1018 root.trigger("pause"); 1019 }); 1020 } 1021 } 1022 1023 } 1024 1025 listen(api, $("source", videoTag).add(videoTag), video); 1026 1027 // iPad (+others?) demands load() 1028 if (conf.preload != 'none' || !support.zeropreload || !support.dataload) api.load(); 1029 if (conf.splash) api.load(); 1030 }, 1031 1032 pause: function() { 1033 api.pause(); 1034 }, 1035 1036 resume: function() { 1037 api.play(); 1038 }, 1039 1040 speed: function(val) { 1041 api.playbackRate = val; 1042 }, 1043 1044 seek: function(time) { 1045 try { 1046 var pausedState = player.paused; 1047 api.currentTime = time; 1048 if (pausedState) api.pause(); 1049 } catch (ignored) {} 1050 }, 1051 1052 volume: function(level) { 1053 api.volume = level; 1054 }, 1055 1056 unload: function() { 1057 $("video.fp-engine", root).remove(); 1058 if (!support.cachedVideoTag) videoTagCache = null; 1059 timer = clearInterval(timer); 1060 api = 0; 1061 } 1062 1063 }; 1064 1065 function listen(api, sources, video) { 1066 // listen only once 1067 1068 if (api.listeners && api.listeners.hasOwnProperty(root.data('fp-player_id'))) return; 1069 (api.listeners || (api.listeners = {}))[root.data('fp-player_id')] = true; 1070 1071 sources.bind("error", function(e) { 1072 try { 1073 if (e.originalEvent && $(e.originalEvent.originalTarget).is('img')) return e.preventDefault(); 1074 if (canPlay($(e.target).attr("type"))) { 1075 player.trigger("error", { code: 4 }); 1076 } 1077 } catch (er) { 1078 // Most likely: https://bugzilla.mozilla.org/show_bug.cgi?id=208427 1079 } 1080 }); 1081 1082 $.each(EVENTS, function(type, flow) { 1083 1084 api.addEventListener(type, function(e) { 1085 1086 // safari hack for bad URL (10s before fails) 1087 if (flow == "progress" && e.srcElement && e.srcElement.readyState === 0) { 1088 setTimeout(function() { 1089 if (!player.video.duration) { 1090 flow = "error"; 1091 player.trigger(flow, { code: 4 }); 1092 } 1093 }, 10000); 1094 } 1095 1096 if (conf.debug && !/progress/.test(flow)) console.log(type, "->", flow, e); 1097 1098 // no events if player not ready 1099 if (!player.ready && !/ready|error/.test(flow) || !flow || !$("video", root).length) { return; } 1100 1101 var event = $.Event(flow), arg; 1102 1103 switch (flow) { 1104 1105 case "ready": 1106 1107 arg = $.extend(video, { 1108 duration: api.duration, 1109 width: api.videoWidth, 1110 height: api.videoHeight, 1111 url: api.currentSrc, 1112 src: api.currentSrc 1113 }); 1114 1115 try { 1116 arg.seekable = api.seekable && api.seekable.end(null); 1117 1118 } catch (ignored) {} 1119 1120 // buffer 1121 timer = timer || setInterval(function() { 1122 1123 try { 1124 arg.buffer = api.buffered.end(null); 1125 1126 } catch (ignored) {} 1127 1128 if (arg.buffer) { 1129 if (arg.buffer <= arg.duration && !arg.buffered) { 1130 player.trigger("buffer", e); 1131 1132 } else if (!arg.buffered) { 1133 arg.buffered = true; 1134 player.trigger("buffer", e).trigger("buffered", e); 1135 clearInterval(timer); 1136 timer = 0; 1137 } 1138 } 1139 1140 }, 250); 1141 1142 if (!arg.duration && !support.hlsDuration && type === "loadeddata") { 1143 var durationChanged = function() { 1144 arg.duration = api.duration; 1145 try { 1146 arg.seekable = api.seekable && api.seekable.end(null); 1147 1148 } catch (ignored) {} 1149 player.trigger(event, arg); 1150 api.removeEventListener('durationchange', durationChanged); 1151 }; 1152 api.addEventListener('durationchange', durationChanged); 1153 return; 1154 } 1155 1156 break; 1157 1158 case "progress": case "seek": 1159 1160 var dur = player.video.duration 1161 1162 if (api.currentTime > 0) { 1163 arg = Math.max(api.currentTime, 0); 1164 break; 1165 1166 } else if (flow == 'progress') { 1167 return; 1168 } 1169 1170 1171 case "speed": 1172 arg = round(api.playbackRate); 1173 break; 1174 1175 case "volume": 1176 arg = round(api.volume); 1177 break; 1178 1179 case "error": 1180 try { 1181 arg = (e.srcElement || e.originalTarget).error; 1182 } catch (er) { 1183 // Most likely https://bugzilla.mozilla.org/show_bug.cgi?id=208427 1184 return; 1185 } 1186 } 1187 1188 player.trigger(event, arg); 1189 1190 }, false); 1191 1192 }); 1193 1194 } 1195 1196};var TYPE_RE = /\.(\w{3,4})(\?.*)?$/i; 1197 1198function parseSource(el) { 1199 1200 var src = el.attr("src"), 1201 type = el.attr("type") || "", 1202 suffix = src.split(TYPE_RE)[1]; 1203 1204 type = /mpegurl/.test(type) ? "mpegurl" : type.replace("video/", ""); 1205 1206 return { src: src, suffix: suffix || type, type: type || suffix }; 1207} 1208 1209/* Resolves video object from initial configuration and from load() method */ 1210function URLResolver(videoTag) { 1211 1212 var self = this, 1213 sources = []; 1214 1215 // initial sources 1216 $("source", videoTag).each(function() { 1217 sources.push(parseSource($(this))); 1218 }); 1219 1220 if (!sources.length) sources.push(parseSource(videoTag)); 1221 1222 self.initialSources = sources; 1223 1224 self.resolve = function(video) { 1225 if (!video) return { sources: sources }; 1226 1227 if ($.isArray(video)) { 1228 1229 video = { sources: $.map(video, function(el) { 1230 var type, ret = $.extend({}, el); 1231 $.each(el, function(key, value) { type = key; }); 1232 ret.type = type; 1233 ret.src = el[type]; 1234 delete ret[type]; 1235 return ret; 1236 })}; 1237 1238 } else if (typeof video == 'string') { 1239 1240 video = { src: video, sources: [] }; 1241 1242 $.each(sources, function(i, source) { 1243 if (source.type != 'flash') { 1244 video.sources.push({ 1245 type: source.type, 1246 src: video.src.replace(TYPE_RE, "." + source.suffix + "$2") 1247 }); 1248 } 1249 }); 1250 } 1251 1252 return video; 1253 }; 1254 1255}; 1256/* A minimal jQuery Slider plugin with all goodies */ 1257 1258// skip IE policies 1259// document.ondragstart = function () { return false; }; 1260 1261 1262// execute function every <delay> ms 1263$.throttle = function(fn, delay) { 1264 var locked; 1265 1266 return function () { 1267 if (!locked) { 1268 fn.apply(this, arguments); 1269 locked = 1; 1270 setTimeout(function () { locked = 0; }, delay); 1271 } 1272 }; 1273}; 1274 1275 1276$.fn.slider2 = function(rtl) { 1277 1278 var IS_IPAD = /iPad/.test(navigator.userAgent) && !/CriOS/.test(navigator.userAgent); 1279 1280 return this.each(function() { 1281 1282 var root = $(this), 1283 doc = $(document), 1284 progress = root.children(":last"), 1285 disabled, 1286 offset, 1287 width, 1288 height, 1289 vertical, 1290 size, 1291 maxValue, 1292 max, 1293 skipAnimation = false, 1294 1295 /* private */ 1296 calc = function() { 1297 offset = root.offset(); 1298 width = root.width(); 1299 height = root.height(); 1300 1301 /* exit from fullscreen can mess this up.*/ 1302 // vertical = height > width; 1303 1304 size = vertical ? height : width; 1305 max = toDelta(maxValue); 1306 }, 1307 1308 fire = function(value) {
1309 if (!disabled && value != api.value && (!maxValue || value < maxValue)) { 1310 root.trigger("slide", [ value ]); 1311 api.value = value; 1312 } 1313 }, 1314 1315 mousemove = function(e) { 1316 var pageX = e.pageX; 1317 if (!pageX && e.originalEvent && e.originalEvent.touches && e.originalEvent.touches.length) { 1318 pageX = e.originalEvent.touches[0].pageX; 1319 } 1320 var delta = vertical ? e.pageY - offset.top : pageX - offset.left; 1321 delta = Math.max(0, Math.min(max || size, delta)); 1322 1323 var value = delta / size; 1324 if (vertical) value = 1 - value; 1325 if (rtl) value = 1 - value; 1326 return move(value, 0, true); 1327 }, 1328 1329 move = function(value, speed) { 1330 if (speed === undefined) { speed = 0; } 1331 if (value > 1) value = 1; 1332 1333 var to = (Math.round(value * 1000) / 10) + "%"; 1334 1335 if (!maxValue || value <= maxValue) { 1336 if (!IS_IPAD) progress.stop(); // stop() broken on iPad 1337 if (skipAnimation) { 1338 progress.css('width', to); 1339 } else { 1340 progress.animate(vertical ? { height: to } : { width: to }, speed, "linear"); 1341 } 1342 } 1343 1344 return value; 1345 }, 1346 1347 toDelta = function(value) { 1348 return Math.max(0, Math.min(size, vertical ? (1 - value) * height : value * width)); 1349 }, 1350 1351 /* public */ 1352 api = { 1353 1354 max: function(value) { 1355 maxValue = value; 1356 }, 1357 1358 disable: function(flag) { 1359 disabled = flag; 1360 }, 1361 1362 slide: function(value, speed, fireEvent) { 1363 calc(); 1364 if (fireEvent) fire(value); 1365 move(value, speed); 1366 }, 1367 1368 // Should animation be handled via css 1369 disableAnimation: function(value) { 1370 skipAnimation = value !== false; 1371 } 1372 1373 }; 1374 1375 calc(); 1376 1377 // bound dragging into document 1378 root.data("api", api).bind("mousedown.sld touchstart", function(e) { 1379 e.preventDefault(); 1380 1381 if (!disabled) { 1382 1383 // begin --> recalculate. allows dynamic resizing of the slider 1384 var delayedFire = $.throttle(fire, 100); 1385 calc(); 1386 api.dragging = true; 1387 root.addClass('is-dragging'); 1388 fire(mousemove(e)); 1389 1390 doc.bind("mousemove.sld touchmove", function(e) { 1391 e.preventDefault(); 1392 delayedFire(mousemove(e)); 1393 1394 }).one("mouseup touchend", function() { 1395 api.dragging = false; 1396 root.removeClass('is-dragging'); 1397 doc.unbind("mousemove.sld touchmove"); 1398 }); 1399 1400 } 1401 1402 }); 1403 1404 }); 1405 1406}; 1407 1408function zeropad(val) { 1409 val = parseInt(val, 10); 1410 return val >= 10 ? val : "0" + val; 1411} 1412
1413// display seconds in hh:mm:ss format 1414function format(sec) { 1415 1416 sec = sec || 0; 1417 1418 var h = Math.floor(sec / 3600), 1419 min = Math.floor(sec / 60); 1420 1421 sec = sec - (min * 60); 1422 1423 if (h >= 1) { 1424 min -= h * 60; 1425 return h + ":" + zeropad(min) + ":" + zeropad(sec); 1426 } 1427 1428 return zeropad(min) + ":" + zeropad(sec); 1429} 1430 1431flowplayer(function(api, root) { 1432 1433 var conf = api.conf, 1434 support = flowplayer.support, 1435 hovertimer; 1436 root.find('.fp-ratio,.fp-ui').remove(); 1437 root.addClass("flowplayer").append('\ 1438 <div class="ratio"/>\ 1439 <div class="ui">\ 1440 <div class="waiting"><em/><em/><em/></div>\ 1441 <a class="fullscreen"/>\ 1442 <a class="unload"/>\ 1443 <p class="speed"/>\ 1444 <div class="controls">\ 1445 <a class="play"></a>\ 1446 <div class="timeline">\ 1447 <div class="buffer"/>\ 1448 <div class="progress"/>\ 1449 </div>\ 1450 <div class="volume">\ 1451 <a class="mute"></a>\ 1452 <div class="volumeslider">\ 1453 <div class="volumelevel"/>\ 1454 </div>\ 1455 </div>\ 1456 </div>\ 1457 <div class="time">\ 1458 <em class="elapsed">00:00</em>\ 1459 <em class="remaining"/>\ 1460 <em class="duration">00:00</em>\ 1461 </div>\ 1462 <div class="message"><h2/><p/></div>\ 1463 </div>'.replace(/class="/g, 'class="fp-') 1464 ); 1465 1466 function find(klass) { 1467 return $(".fp-" + klass, root); 1468 } 1469 1470 // widgets 1471 var progress = find("progress"), 1472 buffer = find("buffer"), 1473 elapsed = find("elapsed"), 1474 remaining = find("remaining"), 1475 waiting = find("waiting"), 1476 ratio = find("ratio"), 1477 speed = find("speed"), 1478 durationEl = find("duration"), 1479 origRatio = ratio.css("paddingTop"), 1480 1481 // sliders 1482 timeline = find("timeline").slider2(api.rtl), 1483 timelineApi = timeline.data("api"), 1484 1485 volume = find("volume"), 1486 fullscreen = find("fullscreen"), 1487 volumeSlider = find("volumeslider").slider2(api.rtl), 1488 volumeApi = volumeSlider.data("api"), 1489 noToggle = root.is(".fixed-controls, .no-toggle"); 1490 1491 timelineApi.disableAnimation(root.hasClass('is-touch')); 1492 1493 // aspect ratio 1494 function setRatio(val) { 1495 if ((root.css('width') === '0px' || root.css('height') === '0px') || val !== flowplayer.defaults.ratio) { 1496 if (!parseInt(origRatio, 10)) ratio.css("paddingTop", val * 100 + "%"); 1497 } 1498 if (!support.inlineBlock) $("object", root).height(root.height()); 1499 } 1500 1501 function hover(flag) { 1502 root.toggleClass("is-mouseover", flag).toggleClass("is-mouseout", !flag); 1503 } 1504 1505 // loading... 1506 if (!support.animation) waiting.html("<p>loading …</p>"); 1507 1508 setRatio(conf.ratio); 1509 1510 // no fullscreen in IFRAME 1511 try { 1512 if (!conf.fullscreen) fullscreen.remove(); 1513 1514 } catch (e) { 1515 fullscreen.remove(); 1516 } 1517 1518 1519 api.bind("ready", function() { 1520 1521 var duration = api.video.duration; 1522 1523 timelineApi.disable(api.disabled || !duration); 1524 1525 conf.adaptiveRatio && setRatio(api.video.height / api.video.width); 1526 1527 // initial time & volume 1528 durationEl.add(remaining).html(format(duration)); 1529 1530 // do we need additional space for showing hour 1531 ((duration >= 3600) && root.addClass('is-long')) || root.removeClass('is-long'); 1532 volumeApi.slide(api.volumeLevel); 1533 1534 1535 }).bind("unload", function() { 1536 if (!origRatio) ratio.css("paddingTop", ""); 1537 1538 // buffer 1539 }).bind("buffer", function() { 1540 var video = api.video, 1541 max = video.buffer / video.duration; 1542 1543 if (!video.seekable && support.seekable) timelineApi.max(max); 1544 if (max < 1) buffer.css("width", (max * 100) + "%"); 1545 else buffer.css({ width: '100%' }); 1546 1547 }).bind("speed", function(e, api, val) { 1548 speed.text(val + "x").addClass("fp-hilite"); 1549 setTimeout(function() { speed.removeClass("fp-hilite") }, 1000); 1550 1551 }).bind("buffered", function() { 1552 buffer.css({ width: '100%' }); 1553 timelineApi.max(1); 1554 1555 // progress 1556 }).bind("progress", function() { 1557 1558 var time = api.video.time, 1559 duration = api.video.duration; 1560 1561 if (!timelineApi.dragging) { 1562 timelineApi.slide(time / duration, api.seeking ? 0 : 250); 1563 } 1564 1565 elapsed.html(format(time)); 1566 remaining.html("-" + format(duration - time)); 1567 1568 }).bind("finish resume seek", function(e) { 1569 root.toggleClass("is-finished", e.type == "finish"); 1570 1571 }).bind("stop", function() { 1572 elapsed.html(format(0)); 1573 timelineApi.slide(0, 100); 1574 1575 }).bind("finish", function() { 1576 elapsed.html(format(api.video.duration)); 1577 timelineApi.slide(1, 100); 1578 root.removeClass("is-seeking"); 1579 1580 // misc 1581 }).bind("beforeseek", function() { 1582 progress.stop(); 1583 1584 }).bind("volume", function() { 1585 volumeApi.slide(api.volumeLevel); 1586 1587 1588 }).bind("disable", function() { 1589 var flag = api.disabled; 1590 timelineApi.disable(flag); 1591 volumeApi.disable(flag); 1592 root.toggleClass("is-disabled", api.disabled); 1593 1594 }).bind("mute", function(e, api, flag) { 1595 root.toggleClass("is-muted", flag); 1596 1597 }).bind("error", function(e, api, error) { 1598 root.removeClass("is-loading").addClass("is-error"); 1599 1600 if (error) { 1601 error.message = conf.errors[error.code]; 1602 api.error = true; 1603 1604 var el = $(".fp-message", root); 1605 $("h2", el).text((api.engine || 'html5') + ": " + error.message); 1606 $("p", el).text(error.url || api.video.url || api.video.src || conf.errorUrls[error.code]); 1607 root.unbind("mouseenter click").removeClass("is-mouseover"); 1608 } 1609 1610 1611 // hover 1612 }).bind("mouseenter mouseleave", function(e) { 1613 if (noToggle) return; 1614 1615 var is_over = e.type == "mouseenter", 1616 lastMove; 1617 1618 // is-mouseover/out 1619 hover(is_over); 1620 1621 if (is_over) { 1622 1623 root.bind("pause.x mousemove.x volume.x", function() { 1624 hover(true); 1625 lastMove = new Date; 1626 }); 1627 1628 hovertimer = setInterval(function() { 1629 if (new Date - lastMove > 5000) { 1630 hover(false) 1631 lastMove = new Date; 1632 } 1633 }, 100); 1634 1635 } else { 1636 root.unbind(".x"); 1637 clearInterval(hovertimer); 1638 } 1639 1640 1641 // allow dragging over the player edge 1642 }).bind("mouseleave", function() { 1643 1644 if (timelineApi.dragging || volumeApi.dragging) { 1645 root.addClass("is-mouseover").removeClass("is-mouseout"); 1646 } 1647 1648 // click 1649 }).bind("click.player", function(e) { 1650 if ($(e.target).is(".fp-ui, .fp-engine") || e.flash) { 1651 e.preventDefault(); 1652 return api.toggle(); 1653 } 1654 }); 1655 1656 // poster -> background image 1657 if (conf.poster) root.css("backgroundImage", "url(" + conf.poster + ")"); 1658 1659 var bc = root.css("backgroundColor"), 1660 has_bg = root.css("backgroundImage") != "none" || bc && bc != "rgba(0, 0, 0, 0)" && bc != "transparent";
1661 1662 // is-poster class 1663 if (has_bg && !conf.splash && !conf.autoplay) { 1664 1665 api.bind("ready stop", function() { 1666 root.addClass("is-poster").one("progress", function() { 1667 root.removeClass("is-poster"); 1668 }); 1669 }); 1670 1671 } 1672 1673 // default background color if not present 1674 if (!has_bg && api.forcedSplash) { 1675 root.css("backgroundColor", "#555"); 1676 } 1677 1678 $(".fp-toggle, .fp-play", root).click(api.toggle); 1679 1680 /* controlbar elements */ 1681 $.each(['mute', 'fullscreen', 'unload'], function(i, key) { 1682 find(key).click(function() { 1683 api[key](); 1684 }); 1685 }); 1686 1687 timeline.bind("slide", function(e, val) { 1688 api.seeking = true; 1689 api.seek(val * api.video.duration); 1690 }); 1691 1692 volumeSlider.bind("slide", function(e, val) { 1693 api.volume(val); 1694 }); 1695 1696 // times 1697 find("time").click(function(e) { 1698 $(this).toggleClass("is-inverted"); 1699 }); 1700 1701 hover(noToggle); 1702 1703}); 1704 1705var focused, 1706 focusedRoot, 1707 IS_HELP = "is-help"; 1708 1709 // keyboard. single global listener 1710$(document).bind("keydown.fp", function(e) { 1711 1712 var el = focused, 1713 metaKeyPressed = e.ctrlKey || e.metaKey || e.altKey, 1714 key = e.which, 1715 conf = el && el.conf; 1716 1717 if (!el || !conf.keyboard || el.disabled) return; 1718 1719 // help dialog (shift key not truly required) 1720 if ($.inArray(key, [63, 187, 191, 219]) != -1) { 1721 focusedRoot.toggleClass(IS_HELP); 1722 return false; 1723 } 1724 1725 // close help / unload 1726 if (key == 27 && focusedRoot.hasClass(IS_HELP)) { 1727 focusedRoot.toggleClass(IS_HELP); 1728 return false; 1729 } 1730 1731 if (!metaKeyPressed && el.ready) { 1732 1733 e.preventDefault(); 1734 1735 // slow motion / fast forward 1736 if (e.shiftKey) { 1737 if (key == 39) el.speed(true); 1738 else if (key == 37) el.speed(false); 1739 return; 1740 } 1741 1742 // 1, 2, 3, 4 .. 1743 if (key < 58 && key > 47) return el.seekTo(key - 48); 1744 1745 switch (key) { 1746 case 38: case 75: el.volume(el.volumeLevel + 0.15); break; // volume up 1747 case 40: case 74: el.volume(el.volumeLevel - 0.15); break; // volume down 1748 case 39: case 76: el.seeking = true; el.seek(true); break; // forward 1749 case 37: case 72: el.seeking = true; el.seek(false); break; // backward 1750 case 190: el.seekTo(); break; // to last seek position 1751 case 32: el.toggle(); break; // spacebar 1752 case 70: conf.fullscreen && el.fullscreen(); break; // toggle fullscreen 1753 case 77: el.mute(); break; // mute 1754 case 27: el[el.isFullscreen ? "fullscreen" : "unload"](); break; // esc 1755 } 1756 1757 } 1758 1759}); 1760 1761flowplayer(function(api, root) { 1762 1763 // no keyboard configured 1764 if (!api.conf.keyboard) return; 1765 1766 // hover 1767 root.bind("mouseenter mouseleave", function(e) { 1768 focused = !api.disabled && e.type == 'mouseenter' ? api : 0; 1769 if (focused) focusedRoot = root; 1770 }); 1771 1772 // TODO: add to player-layout.html 1773 root.append('\ 1774 <div class="fp-help">\ 1775 <a class="fp-close"></a>\ 1776 <div class="fp-help-section fp-help-basics">\ 1777 <p><em>space</em>play / pause</p>\ 1778 <p><em>esc</em>stop</p>\ 1779 <p><em>f</em>fullscreen</p>\ 1780 <p><em>shift</em> + <em>←</em><em>→</em>slower / faster <small>(latest Chrome and Safari)</small></p>\ 1781 </div>\ 1782 <div class="fp-help-section">\ 1783 <p><em>↑</em><em>↓</em>volume</p>\ 1784 <p><em>m</em>mute</p>\ 1785 </div>\ 1786 <div class="fp-help-section">\ 1787 <p><em>←</em><em>→</em>seek</p>\ 1788 <p><em> . </em>seek to previous\ 1789 </p><p><em>1</em><em>2</em>…<em>6</em> seek to 10%, 20%, …60% </p>\ 1790 </div>\ 1791 </div>\ 1792 '); 1793 1794 if (api.conf.tooltip) { 1795 $(".fp-ui", root).attr("title", "Hit ? for help").on("mouseout.tip", function() { 1796 $(this).removeAttr("title").off("mouseout.tip"); 1797 }); 1798 } 1799 1800 $(".fp-close", root).click(function() { 1801 root.toggleClass(IS_HELP); 1802 }); 1803 1804}); 1805 1806var VENDOR = $.browser.mozilla ? "moz": "webkit", 1807 FS_ENTER = "fullscreen", 1808 FS_EXIT = "fullscreen-exit", 1809 FULL_PLAYER, 1810 FS_SUPPORT = flowplayer.support.fullscreen, 1811 FS_NATIVE_SUPPORT = typeof document.exitFullscreen == 'function', 1812 ua = navigator.userAgent.toLowerCase(), 1813 IS_SAFARI = /(safari)[ \/]([\w.]+)/.exec(ua) && !/(chrome)[ \/]([\w.]+)/.exec(ua); 1814 1815 1816// esc button 1817$(document).bind(FS_NATIVE_SUPPORT ? "fullscreenchange" : VENDOR + "fullscreenchange", function(e) { 1818 var el = $(document.webkitCurrentFullScreenElement || document.mozFullScreenElement || document.fullscreenElement || e.target); 1819 if (el.length && !FULL_PLAYER) { 1820 FULL_PLAYER = el.trigger(FS_ENTER, [el]); 1821 } else { 1822 FULL_PLAYER.trigger(FS_EXIT, [FULL_PLAYER]); 1823 FULL_PLAYER = null; 1824 } 1825 1826}); 1827 1828 1829flowplayer(function(player, root) { 1830 1831 if (!player.conf.fullscreen) return; 1832 1833 var win = $(window), 1834 fsSeek = {pos: 0, play: false}, 1835 scrollTop; 1836 1837 player.isFullscreen = false;
1838 1839 player.fullscreen = function(flag) { 1840 1841 if (player.disabled) return; 1842 1843 if (flag === undefined) flag = !player.isFullscreen; 1844 1845 if (flag) scrollTop = win.scrollTop(); 1846 1847 if (FS_SUPPORT) { 1848 1849 if (flag) { 1850 if (FS_NATIVE_SUPPORT) { 1851 root[0].requestFullscreen(); 1852 } else { 1853 root[0][VENDOR + 'RequestFullScreen'](Element.ALLOW_KEYBOARD_INPUT); 1854 if (IS_SAFARI && !document.webkitCurrentFullScreenElement && !document.mozFullScreenElement) { // Element.ALLOW_KEYBOARD_INPUT not allowed 1855 root[0][VENDOR + 'RequestFullScreen'](); 1856 } 1857 } 1858 1859 } else { 1860 if (FS_NATIVE_SUPPORT) { 1861 document.exitFullscreen(); 1862 } else { 1863 document[VENDOR + 'CancelFullScreen'](); 1864 } 1865 } 1866 1867 } else { 1868 if (player.engine === "flash" && player.conf.rtmp) 1869 fsSeek = {pos: player.video.time, play: player.playing}; 1870 player.trigger(flag ? FS_ENTER : FS_EXIT, [player]) 1871 } 1872 1873 return player; 1874 }; 1875 1876 var lastClick; 1877 1878 root.bind("mousedown.fs", function() { 1879 if (+new Date - lastClick < 150 && player.ready) player.fullscreen(); 1880 lastClick = +new Date; 1881 }); 1882 1883 player.bind(FS_ENTER, function(e) { 1884 root.addClass("is-fullscreen"); 1885 player.isFullscreen = true; 1886 1887 }).bind(FS_EXIT, function(e) { 1888 root.removeClass("is-fullscreen"); 1889 player.isFullscreen = false; 1890 win.scrollTop(scrollTop); 1891 1892 }).bind("ready", function () { 1893 if (fsSeek.pos && !isNaN(fsSeek.pos)) { 1894 setTimeout(function () { 1895 player.play(); // avoid hang in buffering state 1896 player.seek(fsSeek.pos); 1897 if (!fsSeek.play) { 1898 setTimeout(function () { 1899 player.pause(); 1900 }, 100); 1901 } 1902 fsSeek = {pos: 0, play: false}; 1903 }, 250); 1904 } 1905 }); 1906 1907}); 1908 1909 1910flowplayer(function(player, root) { 1911 1912 var conf = $.extend({ active: 'is-active', advance: true, query: ".fp-playlist a" }, player.conf), 1913 klass = conf.active; 1914 1915 // getters 1916 function els() { 1917 return $(conf.query, root); 1918 } 1919 1920 function active() { 1921 return $(conf.query + "." + klass, root); 1922 } 1923 1924 1925 player.play = function(i) { 1926 if (i === undefined) return player.resume(); 1927 if (typeof i === 'number' && !player.conf.playlist[i]) return player; 1928 else if (typeof i != 'number') player.load.apply(null, arguments); 1929 player.unbind('resume.fromfirst'); // Don't start from beginning if clip explicitely chosen 1930 player.video.index = i; 1931 player.load(typeof player.conf.playlist[i] === 'string' ? 1932 player.conf.playlist[i].toString() : 1933 $.map(player.conf.playlist[i], function(item) { return $.extend({}, item); }) 1934 ); 1935 return player; 1936 }; 1937 1938 player.next = function(e) { 1939 e && e.preventDefault(); 1940 var current = player.video.index; 1941 if (current != -1) { 1942 current = current === player.conf.playlist.length - 1 ? 0 : current + 1; 1943 player.play(current); 1944 } 1945 return player; 1946 }; 1947 1948 player.prev = function(e) { 1949 e && e.preventDefault(); 1950 var current = player.video.index; 1951 if (current != -1) { 1952 current = current === 0 ? player.conf.playlist.length - 1 : current - 1; 1953 player.play(current); 1954 } 1955 return player; 1956 }; 1957 1958 $('.fp-next', root).click(player.next); 1959 $('.fp-prev', root).click(player.prev); 1960 1961 if (conf.advance) { 1962 root.unbind("finish.pl").bind("finish.pl", function(e, player) { 1963 1964 // next clip is found or loop 1965 var next = player.video.index + 1; 1966 if (next < player.conf.playlist.length || conf.loop) { 1967 next = next === player.conf.playlist.length ? 0 : next; 1968 root.removeClass('is-finished'); 1969 setTimeout(function() { // Let other finish callbacks fire first 1970 player.play(next); 1971 }); 1972 1973 // stop to last clip, play button starts from 1:st clip 1974 } else { 1975 root.addClass("is-playing"); // show play button 1976 1977 // If we have multiple items in playlist, start from first 1978 if (player.conf.playlist.length > 1) player.one("resume.fromfirst", function() { 1979 player.play(0); 1980 return false;
1981 }); 1982 } 1983 }); 1984 } 1985 1986 var playlistInitialized = false; 1987 if (player.conf.playlist.length) { // playlist configured by javascript, generate playlist 1988 playlistInitialized = true; 1989 var plEl = root.find('.fp-playlist'); 1990 if (!plEl.length) { 1991 plEl = $('<div class="fp-playlist"></div>'); 1992 var cntrls = $('.fp-next,.fp-prev', root).eq(0).before(plEl); 1993 if (!cntrls.length) $('video', root).after(plEl); 1994 } 1995 plEl.empty(); 1996 $.each(player.conf.playlist, function(i, item) { 1997 var href; 1998 if (typeof item === 'string') { 1999 href = item; 2000 } else { 2001 for (var key in item[0]) { 2002 if (item[0].hasOwnProperty(key)) { 2003 href = item[0][key]; 2004 break; 2005 } 2006 } 2007 } 2008 plEl.append($('<a />').attr({ 2009 href: href, 2010 'data-index': i 2011 })); 2012 }); 2013 } 2014 2015 if (els().length) { 2016 if (!playlistInitialized) { 2017 player.conf.playlist = []; 2018 els().each(function() { 2019 var src = $(this).attr('href'); 2020 $(this).attr('data-index', player.conf.playlist.length); 2021 player.conf.playlist.push(src); 2022 }); 2023 } 2024 2025 /* click -> play */ 2026 root.on("click", conf.query, function(e) { 2027 e.preventDefault(); 2028 var el = $(e.target).closest(conf.query); 2029 var toPlay = Number(el.attr('data-index')); 2030 if (toPlay != -1) { 2031 player.play(toPlay); 2032 } 2033 }); 2034 2035 // playlist wide cuepoint support 2036 var has_cuepoints = els().filter("[data-cuepoints]").length; 2037 2038 // highlight 2039 player.bind("load", function(e, api, video) { 2040 var prev = active().removeClass(klass), 2041 prevIndex = prev.attr('data-index'), 2042 index = video.index = player.video.index || 0, 2043 el = $('a[data-index="' + index + '"]', root).addClass(klass), 2044 is_last = index == player.conf.playlist.length - 1; 2045 // index 2046 root.removeClass("video" + prevIndex).addClass("video" + index).toggleClass("last-video", is_last); 2047 2048 // video properties 2049 video.index = api.video.index = index; 2050 video.is_last = api.video.is_last = is_last; 2051 2052 // cuepoints 2053 if (has_cuepoints) player.cuepoints = el.data("cuepoints"); 2054 2055 2056 // without namespace callback called only once. unknown rason. 2057 }).bind("unload.pl", function() { 2058 active().toggleClass(klass); 2059 2060 }); 2061 2062 } 2063 2064 if (player.conf.playlist.length) { 2065 // disable single clip looping 2066 player.conf.loop = false; 2067 } 2068 2069 2070}); 2071 2072var CUE_RE = / ?cue\d+ ?/; 2073 2074flowplayer(function(player, root) { 2075 2076 var lastTime = 0; 2077 2078 player.cuepoints = player.conf.cuepoints || []; 2079 2080 function setClass(index) { 2081 root[0].className = root[0].className.replace(CUE_RE, " "); 2082 if (index >= 0) root.addClass("cue" + index); 2083 } 2084 2085 player.bind("progress", function(e, api, time) { 2086 2087 // avoid throwing multiple times 2088 if (lastTime && time - lastTime < 0.015) return lastTime = time; 2089 lastTime = time; 2090 2091 var cues = player.cuepoints || []; 2092 2093 for (var i = 0, cue; i < cues.length; i++) { 2094 2095 cue = cues[i]; 2096 if (!isNaN(cue)) cue = { time: cue }; 2097 if (cue.time < 0) cue.time = player.video.duration + cue.time; 2098 cue.index = i; 2099 2100 // progress_interval / 2 = 0.125 2101 if (Math.abs(cue.time - time) < 0.125 * player.currentSpeed) { 2102 setClass(i); 2103 root.trigger("cuepoint", [player, cue]); 2104 } 2105 2106 } 2107 2108 // no CSS class name 2109 }).bind("unload seek", setClass); 2110 2111 if (player.conf.generate_cuepoints) { 2112 2113 player.bind("load", function() { 2114 2115 // clean up cuepoint elements of previous playlist items 2116 $(".fp-cuepoint", root).remove(); 2117 2118 }).bind("ready", function() { 2119 2120 var cues = player.cuepoints || [], 2121 duration = player.video.duration, 2122 timeline = $(".fp-timeline", root).css("overflow", "visible"); 2123 2124 $.each(cues, function(i, cue) { 2125 2126 var time = cue.time || cue; 2127 if (time < 0) time = duration + cue; 2128 2129 var el = $("<a/>").addClass("fp-cuepoint fp-cuepoint" + i) 2130 .css("left", (time / duration * 100) + "%"); 2131 2132 el.appendTo(timeline).mousedown(function() { 2133 player.seek(time); 2134 2135 // preventDefault() doesn't work 2136 return false;
2137 }); 2138 2139 }); 2140 2141 }); 2142 2143 } 2144 2145}); 2146flowplayer(function(player, root, engine) { 2147 2148 var track = $("track", root), 2149 conf = player.conf; 2150 2151 if (flowplayer.support.subtitles) { 2152 2153 player.subtitles = track.length && track[0].track; 2154 2155 // use native when supported 2156 if (conf.nativesubtitles && conf.engine == 'html5') return; 2157 } 2158 2159 // avoid duplicate loads 2160 track.remove(); 2161 2162 var TIMECODE_RE = /^(([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3}) --\> (([0-9]{2}:)?[0-9]{2}:[0-9]{2}[,.]{1}[0-9]{3})(.*)/; 2163 2164 function seconds(timecode) { 2165 var els = timecode.split(':'); 2166 if (els.length == 2) els.unshift(0); 2167 return els[0] * 60 * 60 + els[1] * 60 + parseFloat(els[2].replace(',','.')); 2168 } 2169 2170 player.subtitles = []; 2171 2172 var url = track.attr("src"); 2173 2174 if (!url) return; 2175 2176 $.get(url, function(txt) { 2177 2178 for (var i = 0, lines = txt.split("\n"), len = lines.length, entry = {}, title, timecode, text, cue; i < len; i++) { 2179 2180 timecode = TIMECODE_RE.exec(lines[i]); 2181 2182 if (timecode) { 2183 2184 // title 2185 title = lines[i - 1]; 2186 2187 // text 2188 text = "<p>" + lines[++i] + "</p><br/>"; 2189 while ($.trim(lines[++i]) && i < lines.length) text += "<p>" + lines[i] + "</p><br/>"; 2190 2191 // entry 2192 entry = { 2193 title: title, 2194 startTime: seconds(timecode[1]), 2195 endTime: seconds(timecode[2] || timecode[3]), 2196 text: text 2197 }; 2198 2199 cue = { time: entry.startTime, subtitle: entry }; 2200 2201 player.subtitles.push(entry); 2202 player.cuepoints.push(cue); 2203 player.cuepoints.push({ time: entry.endTime, subtitleEnd: title }); 2204 2205 // initial cuepoint 2206 if (entry.startTime === 0) { 2207 player.trigger("cuepoint", cue); 2208 } 2209 2210 } 2211 2212 } 2213 2214 }).fail(function() { 2215 player.trigger("error", {code: 8, url: url }); 2216 return false; 2217 }); 2218 2219 var wrap = $("<div class='fp-subtitle'/>", root).appendTo(root), 2220 currentPoint; 2221 2222 player.bind("cuepoint", function(e, api, cue) { 2223 2224 if (cue.subtitle) { 2225 currentPoint = cue.index; 2226 wrap.html(cue.subtitle.text).addClass("fp-active"); 2227 2228 } else if (cue.subtitleEnd) { 2229 wrap.removeClass("fp-active"); 2230 currentPoint = cue.index; 2231 } 2232 2233 }).bind("seek", function(e, api, time) { 2234 // Clear future subtitles if seeking backwards 2235 if (currentPoint && player.cuepoints[currentPoint] && player.cuepoints[currentPoint].time > time) { 2236 wrap.removeClass('fp-active'); 2237 currentPoint = null; 2238 } 2239 $.each(player.cuepoints || [], function(i, cue) { 2240 var entry = cue.subtitle; 2241 //Trigger cuepoint if start time before seek position and end time nonexistent or in the future 2242 if (entry && currentPoint != cue.index) { 2243 if (time >= cue.time && (!entry.endTime || time <= entry.endTime)) player.trigger("cuepoint", cue); 2244 } // Also handle cuepoints that act as the removal trigger 2245 else if (cue.subtitleEnd && time >= cue.time && cue.index == currentPoint + 1) player.trigger("cuepoint", cue); 2246 }); 2247 2248 }); 2249 2250}); 2251 2252 2253 2254flowplayer(function(player, root) { 2255 2256 var id = player.conf.analytics, time = 0, last = 0; 2257 2258 if (id) { 2259 2260 // load Analytics script if needed 2261 if (typeof _gat == 'undefined') $.getScript("//google-analytics.com/ga.js"); 2262 2263 function track(e) { 2264 2265 if (time && typeof _gat != 'undefined') { 2266 var tracker = _gat._getTracker(id), 2267 video = player.video; 2268 2269 tracker._setAllowLinker(true); 2270 2271 // http://code.google.com/apis/analytics/docs/tracking/eventTrackerGuide.html 2272 tracker._trackEvent( 2273 "Video / Seconds played", 2274 player.engine + "/" + video.type, 2275 root.attr("title") || video.src.split("/").slice(-1)[0].replace(TYPE_RE, ''), 2276 Math.round(time / 1000) 2277 ); 2278 time = 0; 2279 } 2280 2281 } 2282 2283 player.bind("load unload", track).bind("progress", function() { 2284 2285 if (!player.seeking) { 2286 time += last ? (+new Date - last) : 0; 2287 last = +new Date; 2288 } 2289 2290 }).bind("pause", function() { 2291 last = 0; 2292 }); 2293 2294 $(window).unload(track); 2295 2296 } 2297 2298});var isIeMobile = /IEMobile/.test(UA); 2299if (flowplayer.support.touch || isIeMobile) { 2300 2301 flowplayer(function(player, root) { 2302 var isAndroid = /Android/.test(UA) && !/Firefox/.test(UA) && !/Opera/.test(UA), 2303 isSilk = /Silk/.test(UA); 2304 2305 // custom load for android 2306 if (isAndroid) { 2307 player.conf.videoTypePreference = "mp4"; // Android has problems with webm aspect ratio 2308 var originalLoad = player.load; 2309 player.load = function(video, callback) {
2310 var ret = originalLoad.apply(player, arguments); 2311 player.trigger('ready', player, player.video); 2312 return ret; 2313 }; 2314 } 2315 2316 // hide volume 2317 if (!flowplayer.support.volume) { 2318 root.addClass("no-volume no-mute"); 2319 } 2320 root.addClass("is-touch"); 2321 root.find('.fp-timeline').data('api').disableAnimation(); 2322 2323 // fake mouseover effect with click 2324 var hasMoved = false; 2325 root.bind('touchmove', function() { 2326 hasMoved = true; 2327 }).bind("touchend click", function(e) { 2328 if (hasMoved) { //not intentional, most likely scrolling 2329 hasMoved = false; 2330 return; 2331 } 2332 2333 if (player.playing && !root.hasClass("is-mouseover")) { 2334 root.addClass("is-mouseover").removeClass("is-mouseout"); 2335 return false; 2336 } 2337 2338 if (player.paused && root.hasClass("is-mouseout")) { 2339 player.toggle(); 2340 } 2341 2342 if (player.paused && isIeMobile) { // IE on WP7 need an additional api.play() call 2343 $('video', root)[0].play(); 2344 } 2345 2346 }); 2347 2348 // native fullscreen 2349 if (player.conf.native_fullscreen && ($.browser.webkit || $.browser.safari)) { 2350 player.fullscreen = function() { 2351 var video = $('video', root); 2352 video[0].webkitEnterFullScreen(); 2353 video.one('webkitendfullscreen', function() { 2354 video.prop('controls', true).prop('controls', false); 2355 }); 2356 }; 2357 } 2358 2359 2360 // Android browser gives video.duration == 1 until second 'timeupdate' event 2361 (isAndroid || isSilk) && player.bind("ready", function() { 2362 2363 var video = $('video', root); 2364 video.one('canplay', function() { 2365 video[0].play(); 2366 }); 2367 video[0].play(); 2368 2369 player.bind("progress.dur", function() { 2370 2371 var duration = video[0].duration; 2372 2373 if (duration !== 1) { 2374 player.video.duration = duration; 2375 $(".fp-duration", root).html(format(duration)); 2376 player.unbind("progress.dur"); 2377 } 2378 }); 2379 }); 2380 2381 2382 }); 2383 2384} 2385 2386flowplayer(function(player, root) { 2387 2388 // no embedding 2389 if (player.conf.embed === false) return; 2390 2391 var conf = player.conf, 2392 ui = $(".fp-ui", root), 2393 trigger = $("<a/>", { "class": "fp-embed", title: 'Copy to your site'}).appendTo(ui), 2394 target = $("<div/>", { 'class': 'fp-embed-code'}) 2395 .append("<label>Paste this HTML code on your site to embed.</label><textarea/>").appendTo(ui), 2396 area = $("textarea", target); 2397 2398 player.embedCode = function() { 2399 2400 var video = player.video, 2401 width = video.width || root.width(), 2402 height = video.height || root.height(), 2403 el = $("<div/>", { 'class': 'flowplayer', css: { width: width, height: height }}), 2404 tag = $("<video/>").appendTo(el); 2405 2406 // configuration 2407 $.each(['origin', 'analytics', 'logo', 'key', 'rtmp'], function(i, key) { 2408 if (conf[key]) el.attr("data-" + key, conf[key]); 2409 }); 2410 2411 // sources 2412 $.each(video.sources, function(i, src) { 2413 tag.append($("<source/>", { type: "video/" + src.type, src: src.src })); 2414 }); 2415 2416 var scriptAttrs = { src: "//embed.flowplayer.org/5.4.3/embed.min.js" }; 2417 if ($.isPlainObject(conf.embed)) { 2418 scriptAttrs['data-swf'] = conf.embed.swf; 2419 scriptAttrs['data-library'] = conf.embed.library; 2420 scriptAttrs['src'] = conf.embed.script || scriptAttrs['src']; 2421 if (conf.embed.skin) { scriptAttrs['data-skin'] = conf.embed.skin; } 2422 } 2423 2424 var code = $("<foo/>", scriptAttrs).append(el); 2425 return $("<p/>").append(code).html().replace(/<(\/?)foo/g, "<$1script"); 2426 }; 2427 2428 root.fptip(".fp-embed", "is-embedding"); 2429 2430 area.click(function() { 2431 this.select(); 2432 }); 2433 2434 trigger.click(function() { 2435 area.text(player.embedCode()); 2436 area[0].focus(); 2437 area[0].select(); 2438 }); 2439 2440}); 2441 2442 2443$.fn.fptip = function(trigger, active) { 2444 2445 return this.each(function() { 2446 2447 var root = $(this); 2448 2449 function close() { 2450 root.removeClass(active); 2451 $(document).unbind(".st"); 2452 } 2453 2454 $(trigger || "a", this).click(function(e) { 2455 2456 e.preventDefault(); 2457 2458 root.toggleClass(active); 2459 2460 if (root.hasClass(active)) { 2461 2462 $(document).bind("keydown.st", function(e) { 2463 if (e.which == 27) close(); 2464 2465 // click:close 2466 }).bind("click.st", function(e) { 2467 if (!$(e.target).parents("." + active).length) close(); 2468 }); 2469 } 2470 2471 }); 2472 2473 }); 2474 2475}; 2476 2477}(jQuery); 2478flowplayer(function(e,o){function a(e){var o=n("<a/>")[0];return o.href=e,o.hostname}var n=jQuery,t=e.conf,i=t.swf.indexOf("flowplayer.org")&&t.e&&o.data("origin"),l=i?a(i):location.hostname,r=t.key;if("file:"==location.protocol&&(l="localhost"),e.load.ed=1,t.hostname=l,t.origin=i||location.href,i&&o.addClass("is-embedded"),"string"==typeof r&&(r=r.split(/,\s*/)),r&&"function"==typeof key_check&&key_check(r,l))t.logo&&o.append(n("<a>",{"class":"fp-logo",href:i}).append(n("<img/>",{src:t.logo})));else{var s=n("<a/>").attr("href","http://flowplayer.org").appendTo(o);n(".fp-controls",o),e.bind("pause resume finish unload",function(e,o){var a=o.video.src?o.video.src.indexOf("://my.flowplayer.org"):-1;/pause|resume/.test(e.type)&&"flash"!=o.engine&&4!=a&&5!=a?(s.show().css({position:"absolute",left:16,bottom:36,zIndex:99999,width:100,height:20,backgroundImage:"url("+[".png","logo","/",".net",".cloudfront","d32
2478wqyuo10o653","//"].reverse().join("")+")"}),o.load.ed=s.is(":visible"),o.load.ed||o.pause()):s.hide()})}});
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.