1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-25 02:42:10 +02:00

Major rewrite to support Google Closure Compiler Advanced Optimization.

Still in process.
This commit is contained in:
Steve Heffernan 2012-12-30 21:45:50 -08:00
parent a34259860f
commit 688b5053ee
20 changed files with 7043 additions and 3011 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@ projects
node_modules
npm-debug.log
closure-test

202
build/compiler/COPYING Normal file
View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

292
build/compiler/README Normal file
View File

@ -0,0 +1,292 @@
/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// Contents
//
The Closure Compiler performs checking, instrumentation, and
optimizations on JavaScript code. The purpose of this README is to
explain how to build and run the Closure Compiler.
The Closure Compiler requires Java 6 or higher.
http://www.java.com/
//
// Building The Closure Compiler
//
There are three ways to get a Closure Compiler executable.
1) Use one we built for you.
Pre-built Closure binaries can be found at
http://code.google.com/p/closure-compiler/downloads/list
2) Check out the source and build it with Apache Ant.
First, check out the full source tree of the Closure Compiler. There
are instructions on how to do this at the project site.
http://code.google.com/p/closure-compiler/source/checkout
Apache Ant is a cross-platform build tool.
http://ant.apache.org/
At the root of the source tree, there is an Ant file named
build.xml. To use it, navigate to the same directory and type the
command
ant jar
This will produce a jar file called "build/compiler.jar".
3) Check out the source and build it with Eclipse.
Eclipse is a cross-platform IDE.
http://www.eclipse.org/
Under Eclipse's File menu, click "New > Project ..." and create a
"Java Project." You will see an options screen. Give the project a
name, select "Create project from existing source," and choose the
root of the checked-out source tree as the existing directory. Verify
that you are using JRE version 6 or higher.
Eclipse can use the build.xml file to discover rules. When you
navigate to the build.xml file, you will see all the build rules in
the "Outline" pane. Run the "jar" rule to build the compiler in
build/compiler.jar.
//
// Running The Closure Compiler
//
Once you have the jar binary, running the Closure Compiler is straightforward.
On the command line, type
java -jar compiler.jar
This starts the compiler in interactive mode. Type
var x = 17 + 25;
then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux)
and "Enter" again. The Compiler will respond:
var x=42;
The Closure Compiler has many options for reading input from a file,
writing output to a file, checking your code, and running
optimizations. To learn more, type
java -jar compiler.jar --help
You can read more detailed documentation about the many flags at
http://code.google.com/closure/compiler/docs/gettingstarted_app.html
//
// Compiling Multiple Scripts
//
If you have multiple scripts, you should compile them all together with
one compile command.
java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js
The Closure Compiler will concatenate the files in the order they're
passed at the command line.
If you need to compile many, many scripts together, you may start to
run into problems with managing dependencies between scripts. You
should check out the Closure Library. It contains functions for
enforcing dependencies between scripts, and a tool called calcdeps.py
that knows how to give scripts to the Closure Compiler in the right
order.
http://code.google.com/p/closure-library/
//
// Licensing
//
Unless otherwise stated, all source files are licensed under
the Apache License, Version 2.0.
-----
Code under:
src/com/google/javascript/rhino
test/com/google/javascript/rhino
URL: http://www.mozilla.org/rhino
Version: 1.5R3, with heavy modifications
License: Netscape Public License and MPL / GPL dual license
Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an
implementation of JavaScript for the JVM. The JavaScript parser and
the parse tree data structures were extracted and modified
significantly for use by Google's JavaScript compiler.
Local Modifications: The packages have been renamespaced. All code not
relevant to parsing has been removed. A JsDoc parser and static typing
system have been added.
-----
Code in:
lib/rhino
Rhino
URL: http://www.mozilla.org/rhino
Version: Trunk
License: Netscape Public License and MPL / GPL dual license
Description: Mozilla Rhino is an implementation of JavaScript for the JVM.
Local Modifications: Minor changes to parsing JSDoc that usually get pushed
up-stream to Rhino trunk.
-----
Code in:
lib/args4j.jar
Args4j
URL: https://args4j.dev.java.net/
Version: 2.0.16
License: MIT
Description:
args4j is a small Java class library that makes it easy to parse command line
options/arguments in your CUI application.
Local Modifications: None.
-----
Code in:
lib/guava.jar
Guava Libraries
URL: http://code.google.com/p/guava-libraries/
Version: 13.0.1
License: Apache License 2.0
Description: Google's core Java libraries.
Local Modifications: None.
-----
Code in:
lib/jsr305.jar
Annotations for software defect detection
URL: http://code.google.com/p/jsr-305/
Version: svn revision 47
License: BSD License
Description: Annotations for software defect detection.
Local Modifications: None.
-----
Code in:
lib/jarjar.jar
Jar Jar Links
URL: http://jarjar.googlecode.com/
Version: 1.1
License: Apache License 2.0
Description:
A utility for repackaging Java libraries.
Local Modifications: None.
----
Code in:
lib/junit.jar
JUnit
URL: http://sourceforge.net/projects/junit/
Version: 4.10
License: Common Public License 1.0
Description: A framework for writing and running automated tests in Java.
Local Modifications: None.
---
Code in:
lib/protobuf-java.jar
Protocol Buffers
URL: http://code.google.com/p/protobuf/
Version: 2.4.1
License: New BSD License
Description: Supporting libraries for protocol buffers,
an encoding of structured data.
Local Modifications: None
---
Code in:
lib/ant.jar
lib/ant-launcher.jar
URL: http://ant.apache.org/bindownload.cgi
Version: 1.8.1
License: Apache License 2.0
Description:
Ant is a Java based build tool. In theory it is kind of like "make"
without make's wrinkles and with the full portability of pure java code.
Local Modifications: None
---
Code in:
lib/json.jar
URL: http://json.org/java/index.html
Version: JSON version 20090211
License: MIT license
Description:
JSON is a set of java files for use in transmitting data in JSON format.
Local Modifications: None
---
Code in:
tools/maven-ant-tasks-2.1.3.jar
URL: http://maven.apache.org
Version 2.1.3
License: Apache License 2.0
Description:
Maven Ant tasks are used to manage dependencies and to install/deploy to
maven repositories.
Local Modifications: None

BIN
build/compiler/compiler.jar Normal file

Binary file not shown.

View File

@ -92,7 +92,7 @@ Instead of editing this file, I recommend creating your own skin CSS file to be
so you can upgrade to newer versions easier. You can remove all these styles by removing the 'vjs-default-skin' class from the tag. */
/* The default control bar. Created by bar.js */
.vjs-default-skin .vjs-controls {
.vjs-default-skin .vjs-control-bar {
position: absolute;
bottom: 0; /* Distance from the bottom of the box/video. Keep 0. Use height to add more bottom margin. */
left: 0; right: 0; /* 100% width of div */

View File

@ -5,19 +5,15 @@ The Video.js API allows you to interact with the video through Javascript, wheth
Referencing the Player
----------------------
To use the API functions, you need access to the player object. Luckily this is easy to get. You just need to make sure your video tag has an ID. The example embed code has an ID of "example\_video_1". If you have multiple videos on one page, make sure every video tag has a unique ID.
```js
var myPlayer = _V_("example_video_1");
```
(If the player hasn't been initialized yet via the data-setup attribute or another method, this will also initialize the player.)
The player may also be referenced using a DOM element as an argument, so `_V_(document.getElementById('my-video'))` and `_V_('my-video')` are equivalent.
Wait Until the Player is Ready
------------------------------
The time it takes Video.js to set up the video and API will vary depending on the playback technology being used (HTML5 will often be much faster to load than Flash). For that reason we want to use the player's 'ready' function to trigger any code that requires the player's API.
```javascript
_V_("example_video_1").ready(function(){
@ -35,7 +31,6 @@ Now that you have access to a ready player, you can control the video, get value
### play() ###
Start video playback. Returns the player object.
```js
myPlayer.play();
```
@ -43,7 +38,6 @@ myPlayer.play();
### pause() ###
Pause the video playback. Returns the player object
```js
myPlayer.pause();
```
@ -51,7 +45,6 @@ myPlayer.pause();
### paused() ###
Returns false if the video is currently playing, or true otherwise. ()
```js
var isPaused = myPlayer.paused();
var isPlaying = !myPlayer.paused();
@ -62,19 +55,16 @@ var isPlaying = !myPlayer.paused();
The source function updates the video source. There are three types of variables you can pass as the argument.
**URL String**: A URL to the the video file. Use this method if you're sure the current playback technology (HTML5/Flash) can support the source you provide. Currently only MP4 files can be used in both HTML5 and Flash.
```js
myPlayer.src("http://www.example.com/path/to/video.mp4");
```
**Source Object (or element):** A javascript object containing information about the source file. Use this method if you want the player to determine if it can support the file using the type information.
```js
myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
```
**Array of Source Objects:** To provide multiple versions of the source so that it can be played using HTML5 across browsers you can use an array of source objects. Video.js will detect which version is supported and load that file.
```js
myPlayer.src([
{ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
@ -88,7 +78,6 @@ Returns the player object.
### currentTime() ###
Returns the current time of the video in seconds.
```js
var whereYouAt = myPlayer.currentTime();
```
@ -96,7 +85,6 @@ var whereYouAt = myPlayer.currentTime();
### currentTime(seconds) // Type: Integer or Float ###
Seek to the supplied time (seconds). Returns the player object.
```js
myPlayer.currentTime(120); // 2 minutes into the video
```
@ -104,7 +92,6 @@ myPlayer.currentTime(120); // 2 minutes into the video
### duration() ###
Returns the length in time of the video in seconds. NOTE: The video must have started loading before the duration can be known, and in the case of Flash, may not be known until the video starts playing.
```js
var howLongIsThis = myPlayer.duration();
```
@ -112,7 +99,6 @@ var howLongIsThis = myPlayer.duration();
### buffered() ###
Returns a [TimeRange](glossary.md#timerange) object with sections of the video that have been downloaded. If you just want the percent of the video that's been downloaded, use bufferedPercent.
```js
var bufferedTimeRange = myPlayer.buffered(),
@ -132,7 +118,6 @@ firstRangeLength = firstRangeEnd - firstRangeStart;
### bufferedPercent() ###
Returns the percent (as a decimal) of the video that's been downloaded. 0 means none, 1 means all.
```js
var howMuchIsDownloaded = myPlayer.bufferedPercent();
```
@ -140,7 +125,6 @@ var howMuchIsDownloaded = myPlayer.bufferedPercent();
### volume() ###
Returns the current volume of the video as a percent in decimal form. 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
```js
var howLoudIsIt = myPlayer.volume();
```
@ -148,7 +132,6 @@ var howLoudIsIt = myPlayer.volume();
### volume(percentAsDecimal) ###
Set the volume to the supplied percent (as a decimal between 0 and 1).
```js
myPlayer.volume(0.5); // Set volume to half
```
@ -156,7 +139,6 @@ myPlayer.volume(0.5); // Set volume to half
### width() ###
Returns the current width of the video in pixels.
```js
var howWideIsIt = myPlayer.width();
```
@ -164,7 +146,6 @@ var howWideIsIt = myPlayer.width();
### width(pixels) ###
Change the width of the video to the supplied width in pixels. Returns the player object
```js
myPlayer.width(640);
```
@ -172,7 +153,6 @@ myPlayer.width(640);
### height() ###
Returns the current height of the video in pixels.
```js
var howTallIsIt = myPlayer.height();
```
@ -180,7 +160,6 @@ var howTallIsIt = myPlayer.height();
### height(pixels) ###
Change the height of the video to the supplied height in pixels. Returns the player object
```js
myPlayer.height(480);
```
@ -188,7 +167,6 @@ myPlayer.height(480);
### size(width, height) ###
Changes the width and height of the video to the supplied width and height. This is more efficient if you're changing both width and height (only triggers the player's resize event once). Returns the player object.
```js
myPlayer.size(640,480);
```
@ -196,7 +174,6 @@ myPlayer.size(640,480);
### requestFullScreen() ###
Increase the size of the video to full screen. In some browsers, full screen is not supported natively, so it enters full window mode, where the video fills the browser window. In browsers and devices that support native full screen, sometimes the browser's default controls will be shown, and not the Video.js custom skin. This includes most mobile devices (iOS, Android) and older versions of Safari. Returns the player object.
```js
myPlayer.requestFullScreen();
```
@ -204,7 +181,6 @@ myPlayer.requestFullScreen();
### cancelFullScreen() ###
Return the video to its normal size after having been in full screen mode. Returns the player object.
```js
myPlayer.cancelFullScreen();
```
@ -223,7 +199,6 @@ myPlayer.addEvent("eventName", myFunc);
```
You can also remove the listeners later.
```js
myPlayer.removeEvent("eventName", myFunc);
```

View File

@ -1,6 +1,6 @@
Components
===
The Video.js player is built on top of a simple custom UI components architecture. The player class and all control classes extend the Component class, or a subclass of Component.
The Video.js player is built on top of a simple, custom UI components architecture. The player class and all control classes inherit from the Component class, or a subclass of Component.
```js
_V_.Control = _V_.Component.extend({});
@ -9,3 +9,83 @@ _V_.PlayToggle = _V_.Button.extend({});
```
(The Class interface itself is provided using John Resig's [simple class inheritance](http://ejohn.org/blog/simple-javascript-inheritance/) also found in [JSNinja](http://jsninja.com).
The UI component architecture makes it easier to add child components to a parent component and build up an entire user interface, like the controls for the Video.js player.
```js
// Adding a new control to the player
myPlayer.addChild('BigPlayButton');
```
Every component has an associated DOM element, and when you add a child component, it inserts the element of that child into the element of the parent.
```js
myPlayer.addChild('BigPlayButton');
```
Results in:
```html
<!-- Player Element -->
<div class="video-js">
<!-- BigPlayButton Element -->
<div class="vjs-big-play-button"></div>
</div>
```
The actual default component structure of the Video.js player looks something like this:
```
Player
PosterImage
TextTrackDisplay
LoadingSpinner
BigPlayButton
ControlBar
PlayToggle
FullscreenToggle
CurrentTimeDisplay
TimeDivider
DurationDisplay
RemainingTimeDisplay
ProgressControl
SeekBar
LoadProgressBar
PlayProgressBar
SeekHandle
VolumeControl
VolumeBar
VolumeLevel
VolumeHandle
MuteToggle
```
Component Methods
-----------------
### addChild() ###
Add a child component to myComponent. This will also insert the child component's DOM element into myComponent's element.
```js
myComponent.addChild('');
```
myPlayer.addChild('BigPlayButton');
myPlayer.removeChild('BigPlayButton');
myPlayer.getChild('BiPlayButton');
myPlayer.getChildren();
myPlayer.getChildById('biPlayButton');
myPlayer.removeChildById('my-player-big-play-button');
getEl();
getContentEl();
getChildren();
getParent();
#home_player_big-play-button

View File

@ -1,247 +1,606 @@
// Using John Resig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
// (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; _V_.Class = function(){}; _V_.Class.extend = function(prop) { var _super = this.prototype; initializing = true; var prototype = new this(); initializing = false; for (var name in prop) { prototype[name] = typeof prop[name] == "function" && typeof _super[name] == "function" && fnTest.test(prop[name]) ? (function(name, fn){ return function() { var tmp = this._super; this._super = _super[name]; var ret = fn.apply(this, arguments); this._super = tmp; return ret; }; })(name, prop[name]) : prop[name]; } function Class() { if ( !initializing && this.init ) this.init.apply(this, arguments); } Class.prototype = prototype; Class.constructor = Class; Class.extend = arguments.callee; return Class;};})();
(function(){
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
_V_.Class = function(){};
_V_.Class.extend = function(prop) {
var _super = this.prototype;
initializing = true;
var prototype = new this();
initializing = false;
for (var name in prop) {
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
this._super = _super[name];
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
function Class() {
if ( !initializing && this.init ) {
return this.init.apply(this, arguments);
/**
* Player Component - Base class for all UI objects
*/
// Attempting to recreate accessing function form of class.
} else if (!initializing) {
return arguments.callee.prototype.init()
}
}
Class.prototype = prototype;
Class.constructor = Class;
Class.extend = arguments.callee;
return Class;
};
})();
goog.provide('_V_.Component');
/* Player Component- Base class for all UI objects
================================================================================ */
_V_.Component = _V_.Class.extend({
goog.require('_V_');
init: function(player, options){
/**
* Base UI Component class
* @param {Object} player Main Player
* @param {Object=} options
* @constructor
*/
_V_.Component = function(player, options, ready){
this.player = player;
// Allow for overridding default component options
// // Allow for overridding default component options
options = this.options = _V_.merge(this.options || {}, options);
// Create element if one wasn't provided in options
if (options.el) {
this.el = options.el;
} else {
this.el = this.createElement();
// Get ID from options, element, or create using player ID and unique ID
this.id_ = options.id || ((options.el && options.el.id) ? options.el.id : player.id + "_component_" + _V_.guid++);
this.name_ = options.name || null;
// Create element if one wasn't provided in potions
this.el_ = (options.el) ? options.el : this.createEl();
// Add any child components in options
this.initChildren();
this.ready(ready);
// Don't want to trigger ready here or it will before init is actually
// finished for all children that run this constructor
};
/**
* Dispose of the component and all child components.
*/
_V_.Component.prototype.dispose = function(){
// Remove all event listeners.
this.off();
// Dispose all children.
if (this.children_) {
for (var i = this.children_.length - 1; i >= 0; i--) {
this.children_[i].dispose();
};
}
// Add any components in options
this.initComponents();
},
// Remove element from DOM
if (this.el_.parentNode) {
this.el_.parentNode.removeChild(this.el_);
}
destroy: function(){},
// Delete child references
this.children_ = null;
this.childIndex_ = null;
this.childNameIndex_ = null;
createElement: function(type, attrs){
return _V_.createElement(type || "div", attrs);
},
this.el_ = null;
};
buildCSSClass: function(){
// Child classes can include a function that does:
// return "CLASS NAME" + this._super();
return "";
},
/**
* Component options object.
* @type {Object}
* @private
*/
_V_.Component.prototype.options;
initComponents: function(){
/**
* The DOM element for the component.
* @type {Element}
* @private
*/
_V_.Component.prototype.el_;
/**
* Create the component's DOM element.
* @param {String=} tagName Element's node type. e.g. 'div'
* @param {Object=} attributes An object of element attributes that should be set on the element.
* @return {Element}
*/
_V_.Component.prototype.createEl = function(tagName, attributes){
return _V_.createEl(tagName, attributes);
};
/**
* Return the component's DOM element.
* @return {Element}
*/
_V_.Component.prototype.getEl = function(){
return this.el_;
};
/**
* The ID for the component.
* @type {String}
* @private
*/
_V_.Component.prototype.id_;
/**
* Return the component's ID.
* @return {String}
*/
_V_.Component.prototype.getId = function(){
return this.id_;
};
/**
* The name for the component. Often used to reference the component.
* @type {String}
* @private
*/
_V_.Component.prototype.name_;
/**
* Return the component's ID.
* @return {String}
*/
_V_.Component.prototype.getName = function(){
return this.name_;
};
/**
* Array of child components
* @type {Array}
* @private
*/
_V_.Component.prototype.children_;
/**
* Returns array of all child components.
* @return {Array}
*/
_V_.Component.prototype.getChildren = function(){
return this.children_;
}
/**
* Object of child components by ID
* @type {Object}
* @private
*/
_V_.Component.prototype.childIndex_;
/**
* Returns a child component with the provided ID.
* @return {Array}
*/
_V_.Component.prototype.getChildById = function(id){
return this.childIndex_[id];
}
/**
* Object of child components by Name
* @type {Object}
* @private
*/
_V_.Component.prototype.childNameIndex_;
/**
* Returns a child component with the provided ID.
* @return {Array}
*/
_V_.Component.prototype.getChild = function(name){
return this.childNameIndex_[name];
}
/**
* Adds a child component inside this component.
* @param {String|_V_.Component} child The class name or instance of a child to add.
* @param {Object=} options Optional options, including options to be passed to
* children of the child.
* @return {_V_.Component} The child component, because it might be created in this process.
* @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
*/
_V_.Component.prototype.addChild = function(child, options){
var component, componentClass, componentName, componentId;
// If string, create new component with options
if (typeof child === "string") {
componentName = child;
// Make sure options is at least an empty object to protect against errors
options = options || {};
// Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
componentClass = options.componentClass || _V_.uc(componentName);
// Set name through options
options.name = componentName;
// Create a new object & element for this controls set
// If there's no .player, this is a player
// Closure Compiler throws an 'incomplete alias' warning if we use the _V_ variable directly.
// Every class should be exported, so this should never be a problem here.
component = new window['_V_'][componentClass](this.player || this, options);
// child is a component instance
} else {
component = child;
}
componentName = component.getName();
componentId = component.getId();
this.children_ = this.children_ || [];
this.childIndex_ = this.childNameIndex_ || {};
this.childNameIndex_ = this.childNameIndex_ || {};
this.children_.push(component);
if (componentId) {
this.childIndex_[componentId] = component;
}
if (componentName) {
this.childNameIndex_[componentName] = component;
}
// Add the UI object's element to the container div (box)
this.el_.appendChild(component.getEl());
// Return so it can stored on parent object if desired.
return component;
};
_V_.Component.prototype.removeChild = function(component){
if (typeof component === 'string') {
component = this.getChild(component);
}
if (!component || !this.children_) return;
var childFound = false;
for (var i = this.children_.length - 1; i >= 0; i--) {
if (this.children_[i] === component) {
childFound = true;
this.children_.splice(i,1);
break;
}
};
if (!childFound) return;
this.childIndex_[component.id] = null;
this.childNameIndex_[component.name] = null;
var compEl = component.getEl();
if (compEl && compEl.parentNode === this.el_) {
this.el_.removeChild(component.getEl());
}
};
/**
* Initialize default child components from options
*/
_V_.Component.prototype.initChildren = function(){
var options = this.options;
if (options && options.components) {
if (options && options.children) {
var self = this;
// Loop through components and add them to the player
this.eachProp(options.components, function(name, opts){
_V_.eachProp(options.children, function(name, opts){
// Allow for disabling default components
// e.g. _V_.options.components.posterImage = false
if (opts === false) return;
// Allow waiting to add components until a specific event is called
var tempAdd = this.proxy(function(){
var tempAdd = function(){
// Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
this[name] = this.addComponent(name, opts);
});
self[name] = self.addChild(name, opts);
};
if (opts.loadEvent) {
this.one(opts.loadEvent, tempAdd)
// this.one(opts.loadEvent, tempAdd)
} else {
tempAdd();
}
});
}
},
};
// Add child components to this component.
// Will generate a new child component and then append child component's element to this component's element.
// Takes either the name of the UI component class, or an object that contains a name, UI Class, and options.
addComponent: function(name, options){
var component, componentClass;
// If string, create new component with options
if (typeof name == "string") {
// Make sure options is at least an empty object to protect against errors
options = options || {};
// Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
componentClass = options.componentClass || _V_.uc(name);
// Create a new object & element for this controls set
// If there's no .player, this is a player
component = new _V_[componentClass](this.player || this, options);
} else {
component = name;
}
// Add the UI object's element to the container div (box)
this.el.appendChild(component.el);
// Return so it can stored on parent object if desired.
return component;
},
removeComponent: function(component){
this.el.removeChild(component.el);
},
/* Display
================================================================================ */
show: function(){
this.el.style.display = "block";
},
hide: function(){
this.el.style.display = "none";
},
fadeIn: function(){
this.removeClass("vjs-fade-out");
this.addClass("vjs-fade-in");
},
fadeOut: function(){
this.removeClass("vjs-fade-in");
this.addClass("vjs-fade-out");
},
lockShowing: function(){
var style = this.el.style;
style.display = "block";
style.opacity = 1;
style.visiblity = "visible";
},
unlockShowing: function(){
var style = this.el.style;
style.display = "";
style.opacity = "";
style.visiblity = "";
},
addClass: function(classToAdd){
_V_.addClass(this.el, classToAdd);
},
removeClass: function(classToRemove){
_V_.removeClass(this.el, classToRemove);
},
_V_.Component.prototype.buildCSSClass = function(){
// Child classes can include a function that does:
// return "CLASS NAME" + this._super();
return "";
};
/* Events
============================================================================= */
/**
* Add an event listener to this component's element. Context will be the component.
* @param {String} type Event type e.g. 'click'
* @param {Function} fn Event listener
* @return {_V_.Component}
*/
_V_.Component.prototype.on = function(type, fn){
_V_.on(this.el_, type, _V_.bind(this, fn));
return this;
};
/**
* Remove an event listener from the component's element
* @param {String=} type Optional event type. Without type it will remove all listeners.
* @param {Function=} fn Optional event listener. Without fn it will remove all listeners for a type.
* @return {_V_.Component}
*/
_V_.Component.prototype.off = function(type, fn){
_V_.off(this.el_, type, fn);
return this;
};
/**
* Add an event listener to be triggered only once and then removed
* @param {String} type Event type
* @param {Function} fn Event listener
* @return {_V_.Component}
*/
_V_.Component.prototype.one = function(type, fn) {
_V_.one(this.el_, type, _V_.bind(this, fn));
return this;
};
/**
* Trigger an event on an element
* @param {String} type Event type to trigger
* @param {Event|Object} event Event object to be passed to the listener
* @return {_V_.Component}
*/
_V_.Component.prototype.trigger = function(type, event){
_V_.trigger(this.el_, type, event);
return this;
};
/* Ready
================================================================================ */
on: function(type, fn, uid){
_V_.on(this.el, type, _V_.proxy(this, fn));
return this;
},
// Deprecated name for 'on' function
addEvent: function(){ return this.on.apply(this, arguments); },
/**
* Is the component loaded.
* @type {Boolean}
* @private
*/
_V_.Component.prototype.isReady_;
off: function(type, fn){
_V_.off(this.el, type, fn);
return this;
},
// Deprecated name for 'off' function
removeEvent: function(){ return this.off.apply(this, arguments); },
/**
* Trigger ready as soon as initialization is finished.
* Allows for delaying ready. Override on a sub class prototype.
* If you set this.isReadyOnInitFinish_ it will affect all components.
* Specially used when waiting for the Flash player to asynchrnously load.
* @type {Boolean}
* @private
*/
_V_.Component.prototype.isReadyOnInitFinish_ = true;
trigger: function(type, e){
_V_.trigger(this.el, type, e);
return this;
},
// Deprecated name for 'off' function
triggerEvent: function(){ return this.trigger.apply(this, arguments); },
/**
* List of ready listeners
* @type {Array}
* @private
*/
_V_.Component.prototype.readyQueue_;
one: function(type, fn) {
_V_.one(this.el, type, _V_.proxy(this, fn));
return this;
},
/* Ready - Trigger functions when component is ready
================================================================================ */
ready: function(fn){
if (!fn) return this;
if (this.isReady) {
/**
* Bind a listener to the component's ready state.
* Different from event listeners in that if the ready event has already happend
* it will trigger the function immediately.
* @param {Function} fn Ready listener
* @return {_V_.Component}
*/
_V_.Component.prototype.ready = function(fn){
if (fn) {
if (this.isReady_) {
fn.call(this);
} else {
if (this.readyQueue === undefined) {
this.readyQueue = [];
if (this.readyQueue_ === undefined) {
this.readyQueue_ = [];
}
this.readyQueue_.push(fn);
}
this.readyQueue.push(fn);
}
return this;
},
};
triggerReady: function(){
this.isReady = true;
if (this.readyQueue && this.readyQueue.length > 0) {
// Call all functions in ready queue
this.each(this.readyQueue, function(fn){
fn.call(this);
});
/**
* Trigger the ready listeners
* @return {_V_.Component}
*/
_V_.Component.prototype.triggerReady = function(){
this.isReady_ = true;
var readyQueue = this.readyQueue_;
if (readyQueue && readyQueue.length > 0) {
for (var i = 0, j = readyQueue.length; i < j; i++) {
readyQueue[i].call(this)
};
// Reset Ready Queue
this.readyQueue = [];
this.readyQueue_ = [];
// Allow for using event listeners also, in case you want to do something everytime a source is ready.
this.trigger("ready");
}
},
};
/* Utility
================================================================================ */
each: function(arr, fn){ _V_.each.call(this, arr, fn); },
/* Display
============================================================================= */
eachProp: function(obj, fn){ _V_.eachProp.call(this, obj, fn); },
/**
* Add a CSS class name to the component's element
* @param {String} classToAdd Classname to add
* @return {_V_.Component}
*/
_V_.Component.prototype.addClass = function(classToAdd){
_V_.addClass(this.el_, classToAdd);
return this;
};
extend: function(obj){ _V_.merge(this, obj) },
/**
* Remove a CSS class name from the component's element
* @param {String} classToRemove Classname to remove
* @return {_V_.Component}
*/
_V_.Component.prototype.removeClass = function(classToRemove){
_V_.removeClass(this.el_, classToRemove);
return this;
};
// More easily attach 'this' to functions
proxy: function(fn, uid){ return _V_.proxy(this, fn, uid); }
/**
* Show the component element if hidden
* @return {_V_.Component}
*/
_V_.Component.prototype.show = function(){
this.el_.style.display = "block";
return this;
};
});
/**
* Hide the component element if hidden
* @return {_V_.Component}
*/
_V_.Component.prototype.hide = function(){
this.el_.style.display = "none";
return this;
};
/**
* Fade a component in using CSS
* @return {_V_.Component}
*/
_V_.Component.prototype.fadeIn = function(){
this.removeClass("vjs-fade-out");
this.addClass("vjs-fade-in");
return this;
};
/**
* Fade a component out using CSS
* @return {_V_.Component}
*/
_V_.Component.prototype.fadeOut = function(){
this.removeClass("vjs-fade-in");
this.addClass("vjs-fade-out");
return this;
};
/**
* Lock an item in its visible state. To be used with fadeIn/fadeOut.
* @return {_V_.Component}
*/
_V_.Component.prototype.lockShowing = function(){
var style = this.el_.style;
style.display = "block";
style.opacity = 1;
style.visiblity = "visible";
return this;
};
/**
* Unlock an item to be hidden. To be used with fadeIn/fadeOut.
* @return {_V_.Component}
*/
_V_.Component.prototype.unlockShowing = function(){
var style = this.el.style;
style.display = "";
style.opacity = "";
style.visiblity = "";
return this;
};
/**
* If a value is provided it will change the width of the player to that value
* otherwise the width is returned
* http://dev.w3.org/html5/spec/dimension-attributes.html#attr-dim-height
* Video tag width/height only work in pixels. No percents.
* But allowing limited percents use. e.g. width() will return number+%, not computed width
* @param {Number|String=} num Optional width number
* @param {[type]} skipListeners Skip the 'resize' event trigger
* @return {_V_.Component|Number|String} Returns 'this' if dimension was set.
* Otherwise it returns the dimension.
*/
_V_.Component.prototype.width = function(num, skipListeners){
return this.dimension("width", num, skipListeners);
};
/**
* Get or set the height of the player
* @param {Number|String=} num Optional new player height
* @param {Boolean=} skipListeners Optional skip resize event trigger
* @return {_V_.Component|Number|String} The player, or the dimension
*/
_V_.Component.prototype.height = function(num, skipListeners){
return this.dimension("height", num, skipListeners);
};
/**
* Set both width and height at the same time.
* @param {Number|String} width
* @param {Number|String} height
* @return {_V_.Component} The player.
*/
_V_.Component.prototype.dimensions = function(width, height){
// Skip resize listeners on width for optimization
return this.width(width, true).height(height);
};
/**
* Get or set width or height.
* All for an integer, integer + 'px' or integer + '%';
* Known issue: hidden elements. Hidden elements officially have a width of 0.
* So we're defaulting to the style.width value and falling back to computedStyle
* which has the hidden element issue.
* Info, but probably not an efficient fix:
* http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
* @param {String=} widthOrHeight 'width' or 'height'
* @param {Number|String=} num New dimension
* @param {Boolean=} skipListeners Skip resize event trigger
* @return {vjs.Component|Number|String} Return the player if setting a dimension.
* Otherwise it returns the dimension.
*/
_V_.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){
if (num !== undefined) {
// Check if using css width/height (% or px) and adjust
if ((""+num).indexOf("%") !== -1 || (""+num).indexOf("px") !== -1) {
this.el_.style[widthOrHeight] = num;
} else {
this.el_.style[widthOrHeight] = num+"px";
}
// skipListeners allows us to avoid triggering the resize event when setting both width and height
if (!skipListeners) { this.trigger("resize"); }
// Return component
return this;
}
// Not setting a value, so getting it
// Make sure element exists
if (!this.el_) return 0;
// Get dimension value from style
var val = this.el_.style[widthOrHeight];
var pxIndex = val.indexOf("px");
if (pxIndex !== -1) {
// Return the pixel value with no 'px'
return parseInt(val.slice(0,pxIndex), 10);
// No px so using % or no style was set, so falling back to offsetWidth/height
// If component has display:none, offset will return 0
// TODO: handle display:none and no dimension style using px
} else {
return parseInt(this.el_['offset'+_V_.uc(widthOrHeight)], 10);
// ComputedStyle version.
// Only difference is if the element is hidden it will return
// the percent value (e.g. '100%'')
// instead of zero like offsetWidth returns.
// var val = _V_.getComputedStyleValue(this.el_, widthOrHeight);
// var pxIndex = val.indexOf("px");
// if (pxIndex !== -1) {
// return val.slice(0, pxIndex);
// } else {
// return val;
// }
}
};
// /* Utility
// ================================================================================ */
// _V_.Component.prototype.each = function(arr, fn){ _V_.each.call(this, arr, fn); };
// _V_.Component.prototype.eachProp = function(obj, fn){ _V_.eachProp.call(this, obj, fn); };
// _V_.Component.prototype.extend = function(obj){ _V_.merge(this, obj) };
// // More easily attach 'this' to functions
// _V_.Component.prototype.proxy = function(fn, uid){ return _V_.proxy(this, fn, uid); };

1820
src/controls.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
// HTML5 Shiv. Must be in <head> to support older browsers.
document.createElement("video");document.createElement("audio");
var VideoJS = function(id, addOptions, ready){
goog.provide('_V_');
var _V_ = function(id, options, ready){
var tag; // Element of ID
// Allow for element or ID to be passed in
@ -34,19 +36,20 @@ var VideoJS = function(id, addOptions, ready){
// Element may have a player attr referring to an already created player instance.
// If not, set up a new player and return the instance.
return tag.player || new _V_.Player(tag, addOptions, ready);
},
return tag.player || new _V_.Player(tag, options, ready);
};
// Shortcut
_V_ = VideoJS,
VideoJS = _V_;
// CDN Version. Used to target right flash swf.
CDN_VERSION = "GENERATED_CDN_VSN";
VideoJS.players = {};
VideoJS.options = {
/**
* Global Player instance options
* @type {Object}
*/
_V_.options = {
// Default order of fallback technology
techOrder: ["html5","flash"],
// techOrder: ["flash","html5"],
@ -62,54 +65,24 @@ VideoJS.options = {
defaultVolume: 0.00, // The freakin seaguls are driving me crazy!
// Included control sets
components: {
// TODO: just use uppercase Class name
children: {
"mediaLoader": {},
"posterImage": {},
"textTrackDisplay": {},
// "textTrackDisplay": {},
"loadingSpinner": {},
"bigPlayButton": {},
"controlBar": {}
}
// components: [
// "poster",
// "loadingSpinner",
// "bigPlayButton",
// { name: "controlBar", options: {
// components: [
// "playToggle",
// "fullscreenToggle",
// "currentTimeDisplay",
// "timeDivider",
// "durationDisplay",
// "remainingTimeDisplay",
// { name: "progressControl", options: {
// components: [
// { name: "seekBar", options: {
// components: [
// "loadProgressBar",
// "playProgressBar",
// "seekHandle"
// ]}
// }
// ]}
// },
// { name: "volumeControl", options: {
// components: [
// { name: "volumeBar", options: {
// components: [
// "volumeLevel",
// "volumeHandle"
// ]}
// }
// ]}
// },
// "muteToggle"
// ]
// }},
// "subtitlesDisplay"/*, "replay"*/
// ]
};
/**
* Global player list
* @type {Object}
*/
_V_.players = {};
// Set CDN Version of swf
if (CDN_VERSION != "GENERATED_CDN_VSN") {
_V_.options.flash.swf = "http://vjs.zencdn.net/"+CDN_VERSION+"/video-js.swf"

View File

@ -1,308 +1,308 @@
// Event System (J.Resig - Secrets of a JS Ninja http://jsninja.com/ [Go read it, really])
// (Book version isn't completely usable, so fixed some things and borrowed from jQuery where it's working)
// Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
// (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
//
// This should work very similarly to jQuery's events, however it's based off the book version which isn't as
// robust as jquery's, so there's probably some differences.
//
// When you add an event listener using _V_.addEvent,
// it stores the handler function in seperate cache object,
// and adds a generic handler to the element's event,
// along with a unique id (guid) to the element.
_V_.extend({
/**
* Add an event listener to element
* It stores the handler function in a separate cache object
* and adds a generic handler to the element's event,
* along with a unique id (guid) to the element.
* @param {Element|Object} elem Element or object to bind listeners to
* @param {String} type Type of event to bind to.
* @param {Function} fn Event listener.
*/
_V_.on = function(elem, type, fn){
var data = _V_.getData(elem);
// Add an event listener to element
// It stores the handler function in a separate cache object
// and adds a generic handler to the element's event,
// along with a unique id (guid) to the element.
on: function(elem, type, fn){
var data = _V_.getData(elem), handlers;
// We need a place to store all our handler data
if (!data.handlers) data.handlers = {};
// We only need to generate one handler per element
if (data && !data.handler) {
// Our new meta-handler that fixes the event object and the context
data.handler = function(event){
if (!data.handlers[type]) data.handlers[type] = [];
if (!fn.guid) fn.guid = _V_.guid++;
data.handlers[type].push(fn);
if (!data.dispatcher) {
data.disabled = false;
data.dispatcher = function (event){
if (data.disabled) return;
event = _V_.fixEvent(event);
var handlers = _V_.getData(elem).events[event.type];
// Go through and call all the real bound handlers
if (handlers) {
var handlers = data.handlers[event.type];
/* Was making a copy of handlers to protect
* against removal of listeners mid loop.
* Removing for v4 to test if we still need it. */
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
if (handlers) {
var handlersCopy = [];
_V_.each(handlers, function(handler, i){
handlersCopy[i] = handler;
})
for (var i = 0, j = handlers.length; i < j; i++) {
handlersCopy[i] = handlers[i];
}
for (var i = 0, l = handlersCopy.length; i < l; i++) {
handlersCopy[i].call(elem, event);
for (var m = 0, n = handlersCopy.length; m < n; m++) {
handlersCopy[m].call(elem, event);
}
}
};
}
// We need a place to store all our event data
if (!data.events) { data.events = {}; }
// And a place to store the handlers for this event type
handlers = data.events[type];
if (!handlers) {
handlers = data.events[ type ] = [];
// Attach our meta-handler to the element, since one doesn't exist
if (data.handlers[type].length == 1) {
if (document.addEventListener) {
elem.addEventListener(type, data.handler, false);
elem.addEventListener(type, data.dispatcher, false);
} else if (document.attachEvent) {
elem.attachEvent("on" + type, data.handler);
elem.attachEvent("on" + type, data.dispatcher);
}
}
};
if (!fn.guid) { fn.guid = _V_.guid++; }
/**
* Removes event listeners from an element
* @param {Element|Object} elem Object to remove listeners from
* @param {String=} type Type of listener to remove. Don't include to remove all events from element.
* @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
*/
_V_.off = function(elem, type, fn) {
var data = _V_.getData(elem);
handlers.push(fn);
},
// Deprecated name for 'on' function
addEvent: function(){ return _V_.on.apply(this, arguments); },
off: function(elem, type, fn) {
var data = _V_.getData(elem), handlers;
// If no events exist, nothing to unbind
if (!data.events) { return; }
if (!data.handlers) { return; }
// Utility function
var removeType = function(t){
data.handlers[t] = [];
_V_.cleanUpEvents(elem,t);
};
// Are we removing all bound events?
if (!type) {
for (type in data.events) {
_V_.cleanUpEvents(elem, type);
}
for (var t in data.handlers) removeType(t);
return;
}
// And a place to store the handlers for this event type
handlers = data.events[type];
var handlers = data.handlers[type];
// If no handlers exist, nothing to unbind
if (!handlers) { return; }
if (!handlers) return;
// See if we're only removing a single handler
if (fn && fn.guid) {
for (var i = 0; i < handlers.length; i++) {
// We found a match (don't stop here, there could be a couple bound)
if (handlers[i].guid === fn.guid) {
// Remove the handler from the array of handlers
handlers.splice(i--, 1);
// If no listener was provided, remove all listeners for type
if (!fn) {
removeType(type);
return;
}
// We're only removing a single handler
if (fn.guid) {
for (var n = 0; n < handlers.length; n++) {
if (handlers[n].guid === fn.guid) {
handlers.splice(n--, 1);
}
}
}
_V_.cleanUpEvents(elem, type);
},
// Deprecated name for 'on' function
removeEvent: function(){ return _V_.off.apply(this, arguments); },
};
cleanUpEvents: function(elem, type) {
/**
* Clean up the listener cache and dispatchers
* @param {Element|Object} elem Element to clean up
* @param {String} type Type of event to clean up
*/
_V_.cleanUpEvents = function(elem, type) {
var data = _V_.getData(elem);
// Remove the events of a particular type if there are none left
if (data.events[type].length === 0) {
delete data.events[type];
// Remove the events of a particular type if there are none left
if (data.handlers[type].length === 0) {
delete data.handlers[type];
// data.handlers[type] = null;
// Setting to null was causing an error with data.handlers
// Remove the meta-handler from the element
if (document.removeEventListener) {
elem.removeEventListener(type, data.handler, false);
elem.removeEventListener(type, data.dispatcher, false);
} else if (document.detachEvent) {
elem.detachEvent("on" + type, data.handler);
elem.detachEvent("on" + type, data.dispatcher);
}
}
// Remove the events object if there are no types left
if (_V_.isEmpty(data.events)) {
delete data.events;
delete data.handler;
if (_V_.isEmpty(data.handlers)) {
data.handlers = null;
data.dispatcher = null;
data.disabled = null;
}
// Finally remove the expando if there is no data left
if (_V_.isEmpty(data)) {
_V_.removeData(elem);
}
},
};
fixEvent: function(event) {
if (event[_V_.expando]) { return event; }
// store a copy of the original event object
// and "clone" to set read-only properties
var originalEvent = event;
event = new _V_.Event(originalEvent);
/**
* Fix a native event to have standard property values
* @param {Object} event Event object to fix
* @return {Object}
*/
_V_.fixEvent = function(event) {
for ( var i = _V_.Event.props.length, prop; i; ) {
prop = _V_.Event.props[ --i ];
event[prop] = originalEvent[prop];
function returnTrue() { return true; }
function returnFalse() { return false; }
// Test if fixing up is needed
// Used to check if !event.stopPropagation instead of isPropagationStopped
// But native events return true for stopPropagation, but don't have
// other expected methods like isPropagationStopped. Seems to be a problem
// with the Javascript Ninja code. So we're just overriding all events now.
if (!event || !event.isPropagationStopped) {
var old = event || window.event;
// Clone the old object so that we can modify the values event = {};
for (var prop in old) {
event[prop] = old[prop];
}
// Fix target property, if necessary
if (!event.target) { event.target = event.srcElement || document; }
// check if target is a textnode (safari)
if (event.target.nodeType === 3) { event.target = event.target.parentNode; }
// Add relatedTarget, if necessary
if (!event.relatedTarget && event.fromElement) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
// The event occurred on this element
if (!event.target) {
event.target = event.srcElement || document;
}
// Calculate pageX/Y if missing and clientX/Y available
if ( event.pageX == null && event.clientX != null ) {
var eventDocument = event.target.ownerDocument || document,
doc = eventDocument.documentElement,
body = eventDocument.body;
// Handle which other element the event is related to
event.relatedTarget = event.fromElement === event.target ?
event.toElement :
event.fromElement;
event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
// Stop the default browser action
event.preventDefault = function () {
event.returnValue = false;
event.isDefaultPrevented = returnTrue;
};
event.isDefaultPrevented = returnFalse;
// Stop the event from bubbling
event.stopPropagation = function () {
event.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// Stop the event from bubbling and executing other handlers
event.stopImmediatePropagation = function () {
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// Handle mouse position
if (event.clientX != null) {
var doc = document.documentElement, body = document.body;
event.pageX = event.clientX +
(doc && doc.scrollLeft || body && body.scrollLeft || 0) -
(doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY +
(doc && doc.scrollTop || body && body.scrollTop || 0) -
(doc && doc.clientTop || body && body.clientTop || 0);
}
// Add which for key events
if (event.which == null && (event.charCode != null || event.keyCode != null)) {
event.which = event.charCode != null ? event.charCode : event.keyCode;
}
// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
if ( !event.metaKey && event.ctrlKey ) {
event.metaKey = event.ctrlKey;
}
// Add which for click: 1 === left; 2 === middle; 3 === right
// Note: button is not normalized, so don't use it
if ( !event.which && event.button !== undefined ) {
event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));
// Handle key presses
event.which = event.charCode || event.keyCode;
// Fix button for mouse clicks:
// 0 == left; 1 == middle; 2 == right
if (event.button != null) {
event.button = (event.button & 1 ? 0 :
(event.button & 4 ? 1 :
(event.button & 2 ? 2 : 0)));
}
}
// Returns fixed-up instance
return event;
},
};
trigger: function(elem, event) {
var data = _V_.getData(elem),
parent = elem.parentNode || elem.ownerDocument,
type = event.type || event,
handler;
/**
* Trigger an event for an element
* @param {Element|Object} elem Element to trigger an event on
* @param {String} event Type of event to trigger
*/
_V_.trigger = function(elem, event) {
// Fetches element data and a reference to the parent (for bubbling).
var elemData = _V_.getData(elem);
var parent = elem.parentNode || elem.ownerDocument;
// type = event.type || event,
// handler;
if (data) { handler = data.handler }
// If an event name was passed as a string, creates an event out of it
if (typeof event === "string") {
event = { type:event, target:elem };
}
// Normalizes the event properties.
event = _V_.fixEvent(event);
// Added in attion to book. Book code was broke.
event = typeof event === "object" ?
event[_V_.expando] ?
event :
new _V_.Event(type, event) :
new _V_.Event(type);
event.type = type;
if (handler) {
handler.call(elem, event);
// If the passed element has a dispatcher, executes the established handlers.
if (elemData.dispatcher) {
elemData.dispatcher.call(elem, event);
}
// Clean up the event in case it is being reused
event.result = undefined;
event.target = elem;
// Unless explicitly stopped, recursively calls this function to bubble the event up the DOM.
if (parent && !event.isPropagationStopped()) {
_V_.trigger(parent, event);
// Bubble the event up the tree to the document,
// Unless it's been explicitly stopped
// if (parent && !event.isPropagationStopped()) {
// _V_.triggerEvent(parent, event);
//
// // We're at the top document so trigger the default action
// } else if (!parent && !event.isDefaultPrevented()) {
// // log(type);
// var targetData = _V_.getData(event.target);
// // log(targetData);
// var targetHandler = targetData.handler;
// // log("2");
// if (event.target[event.type]) {
// // Temporarily disable the bound handler,
// // don't want to execute it twice
// if (targetHandler) {
// targetData.handler = function(){};
// }
//
// // Trigger the native event (click, focus, blur)
// event.target[event.type]();
//
// // Restore the handler
// if (targetHandler) {
// targetData.handler = targetHandler;
// }
// }
// }
},
// Deprecated name for 'on' function
triggerEvent: function(){ return _V_.trigger.apply(this, arguments); },
// If at the top of the DOM, triggers the default action unless disabled.
} else if (!parent && !event.isDefaultPrevented()) {
var targetData = _V_.getData(event.target);
one: function(elem, type, fn) {
// Checks if the target has a default action for this event.
if (event.target[event.type]) {
// Temporarily disables event dispatching on the target as we have already executed the handler.
targetData.disabled = true;
// Executes the default action.
if (typeof event.target[event.type] === 'function') {
event.target[event.type]();
}
// Re-enables event dispatching.
targetData.disabled = false;
}
}
/* Original version of js ninja events wasn't complete.
* We've since updated to the latest version, but keeping this around
* for now just in case.
*/
// // Added in attion to book. Book code was broke.
// event = typeof event === "object" ?
// event[_V_.expando] ?
// event :
// new _V_.Event(type, event) :
// new _V_.Event(type);
// event.type = type;
// if (handler) {
// handler.call(elem, event);
// }
// // Clean up the event in case it is being reused
// event.result = undefined;
// event.target = elem;
};
/**
* Trigger a listener only once for an event
* @param {Element|Object} elem Element or object to
* @param {[type]} type [description]
* @param {Function} fn [description]
* @return {[type]}
*/
_V_.one = function(elem, type, fn) {
_V_.on(elem, type, function(){
_V_.off(elem, type, arguments.callee)
fn.apply(this, arguments);
});
}
});
// Custom Event object for standardizing event objects between browsers.
_V_.Event = function(src, props){
// Event object
if (src && src.type) {
this.originalEvent = src;
this.type = src.type;
// Events bubbling up the document may have been marked as prevented
// by a handler lower down the tree; reflect the correct value.
this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false ||
src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse;
// Event type
} else {
this.type = src;
}
// Put explicitly provided properties onto the event object
if (props) { _V_.merge(this, props); }
this.timeStamp = (new Date).getTime();
// Mark it as fixed
this[_V_.expando] = true;
};
_V_.Event.prototype = {
preventDefault: function() {
this.isDefaultPrevented = returnTrue;
var e = this.originalEvent;
if (!e) { return; }
// if preventDefault exists run it on the original event
if (e.preventDefault) {
e.preventDefault();
// otherwise set the returnValue property of the original event to false (IE)
} else {
e.returnValue = false;
}
},
stopPropagation: function() {
this.isPropagationStopped = returnTrue;
var e = this.originalEvent;
if (!e) { return; }
// if stopPropagation exists run it on the original event
if (e.stopPropagation) { e.stopPropagation(); }
// otherwise set the cancelBubble property of the original event to true (IE)
e.cancelBubble = true;
},
stopImmediatePropagation: function() {
this.isImmediatePropagationStopped = returnTrue;
this.stopPropagation();
},
isDefaultPrevented: returnFalse,
isPropagationStopped: returnFalse,
isImmediatePropagationStopped: returnFalse
};
_V_.Event.props = "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" ");
function returnTrue(){ return true; }
function returnFalse(){ return false; }

72
src/exports.js Normal file
View File

@ -0,0 +1,72 @@
goog.require('_V_');
goog.require('_V_.Component');
goog.require('_V_.Player');
goog.exportSymbol('_V_.Component', _V_.Component);
goog.exportProperty(_V_.Component.prototype, "dispose", _V_.Component.prototype.dispose);
goog.exportProperty(_V_.Component.prototype, "createEl", _V_.Component.prototype.createEl);
goog.exportProperty(_V_.Component.prototype, "getEl", _V_.Component.prototype.getEl);
goog.exportProperty(_V_.Component.prototype, "addChild", _V_.Component.prototype.addChild);
goog.exportProperty(_V_.Component.prototype, "getChildren", _V_.Component.prototype.getChildren);
goog.exportProperty(_V_.Component.prototype, "on", _V_.Component.prototype.on);
goog.exportProperty(_V_.Component.prototype, "off", _V_.Component.prototype.off);
goog.exportProperty(_V_.Component.prototype, "one", _V_.Component.prototype.one);
goog.exportProperty(_V_.Component.prototype, "trigger", _V_.Component.prototype.trigger);
goog.exportProperty(_V_.Component.prototype, "show", _V_.Component.prototype.show);
goog.exportProperty(_V_.Component.prototype, "hide", _V_.Component.prototype.hide);
goog.exportProperty(_V_.Component.prototype, "width", _V_.Component.prototype.width);
goog.exportProperty(_V_.Component.prototype, "height", _V_.Component.prototype.height);
goog.exportProperty(_V_.Component.prototype, "dimensions", _V_.Component.prototype.dimensions);
goog.exportSymbol('_V_.Player', _V_.Player);
goog.exportSymbol('_V_.MediaLoader', _V_.MediaLoader);
goog.exportSymbol('_V_.PosterImage', _V_.PosterImage);
goog.exportSymbol('_V_.LoadingSpinner', _V_.LoadingSpinner);
goog.exportSymbol('_V_.BigPlayButton', _V_.BigPlayButton);
goog.exportSymbol('_V_.ControlBar', _V_.ControlBar);
goog.exportSymbol('_V_.TextTrackDisplay', _V_.TextTrackDisplay);
goog.exportSymbol('_V_.Control', _V_.Control);
goog.exportSymbol('_V_.ControlBar', _V_.ControlBar);
goog.exportSymbol('_V_.Button', _V_.Button);
goog.exportSymbol('_V_.PlayButton', _V_.PlayButton);
goog.exportSymbol('_V_.PauseButton', _V_.PauseButton);
goog.exportSymbol('_V_.PlayToggle', _V_.PlayToggle);
goog.exportSymbol('_V_.FullscreenToggle', _V_.FullscreenToggle);
goog.exportSymbol('_V_.BigPlayButton', _V_.BigPlayButton);
goog.exportSymbol('_V_.LoadingSpinner', _V_.LoadingSpinner);
goog.exportSymbol('_V_.CurrentTimeDisplay', _V_.CurrentTimeDisplay);
goog.exportSymbol('_V_.DurationDisplay', _V_.DurationDisplay);
goog.exportSymbol('_V_.TimeDivider', _V_.TimeDivider);
goog.exportSymbol('_V_.RemainingTimeDisplay', _V_.RemainingTimeDisplay);
goog.exportSymbol('_V_.Slider', _V_.Slider);
goog.exportSymbol('_V_.ProgressControl', _V_.ProgressControl);
goog.exportSymbol('_V_.SeekBar', _V_.SeekBar);
goog.exportSymbol('_V_.LoadProgressBar', _V_.LoadProgressBar);
goog.exportSymbol('_V_.PlayProgressBar', _V_.PlayProgressBar);
goog.exportSymbol('_V_.SeekHandle', _V_.SeekHandle);
goog.exportSymbol('_V_.VolumeControl', _V_.VolumeControl);
goog.exportSymbol('_V_.VolumeBar', _V_.VolumeBar);
goog.exportSymbol('_V_.VolumeLevel', _V_.VolumeLevel);
goog.exportSymbol('_V_.VolumeHandle', _V_.VolumeHandle);
goog.exportSymbol('_V_.MuteToggle', _V_.MuteToggle);
goog.exportSymbol('_V_.PosterImage', _V_.PosterImage);
goog.exportSymbol('_V_.Menu', _V_.Menu);
goog.exportSymbol('_V_.MenuItem', _V_.MenuItem);
goog.exportSymbol('_V_.MediaTechController', _V_.MediaTechController);
// Can't figure out why export symbol doesn't work with media controllers
goog.exportSymbol('_V_.Html5', _V_.Html5);
// goog.exportProperty(_V_, "Html5", _V_.Html5);
// goog.exportProperty(_V_.Html5, "isSupported", _V_.Html5.isSupported);
// goog.exportProperty(_V_.Html5, "canPlaySource", _V_.Html5.canPlaySource);
goog.exportSymbol('_V_.media', _V_.media);
goog.exportSymbol('_V_.media.html5', _V_.media.html5);
goog.exportProperty(_V_.media.html5, "isSupported", _V_.media.html5.isSupported);
goog.exportProperty(_V_.media.html5, "canPlaySource", _V_.media.html5.canPlaySource);
// goog.exportSymbol('_V_.media.html5.canPlaySource', _V_.media.html5.canPlaySource);
// goog.exportSymbol('_V_.media.html5.isSupported', _V_.media.html5.isSupported);

1569
src/goog.base.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,102 +1,331 @@
_V_.merge = function(obj1, obj2, safe){
// Make sure second object exists
if (!obj2) { obj2 = {}; };
/**
* Creates an element and applies properties.
* @param {String=} tagName Name of tag to be created.
* @param {Object=} properties Element properties to be applied.
* @return {Element}
*/
_V_.createEl = function(tagName, properties){
var el = document.createElement(tagName || 'div');
for (var attrname in obj2){
if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
for (var propName in properties){
if (properties.hasOwnProperty(propName)) {
el[propName] = properties[propName];
// Not remembering why we were checking for dash
// but using setAttribute means you have to use getAttribute
// if (propName.indexOf("-") !== -1) {
// el.setAttribute(propName, properties[propName]);
// } else {
// el[propName] = properties[propName];
// }
}
return obj1;
}
return el;
};
_V_.extend = function(obj){ this.merge(this, obj, true); };
_V_.extend({
tech: {}, // Holder for playback technology settings
controlSets: {}, // Holder for control set definitions
/**
* Uppercase the first letter of a string
* @param {String} string String to be uppercased
* @return {String}
*/
_V_.uc = function(string){
return string.charAt(0).toUpperCase() + string.slice(1);
};
// Device Checks
isIE: function(){ return !+"\v1"; },
isFF: function(){ return !!_V_.ua.match("Firefox") },
isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; },
isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; },
isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); },
iOSVersion: function() {
var match = navigator.userAgent.match(/OS (\d+)_/i);
if (match && match[1]) { return match[1]; }
},
isAndroid: function(){ return navigator.userAgent.match(/Android.*AppleWebKit/i) !== null; },
androidVersion: function() {
var match = navigator.userAgent.match(/Android (\d+)\./i);
if (match && match[1]) { return match[1]; }
},
testVid: document.createElement("video"),
ua: navigator.userAgent,
support: {},
each: function(arr, fn){
if (!arr || arr.length === 0) { return; }
for (var i=0,j=arr.length; i<j; i++) {
fn.call(this, arr[i], i);
}
},
eachProp: function(obj, fn){
/**
* Loop through each property in an object and call a function
* whose arguments are (key,value)
* @param {Object} obj Object of properties
* @param {Function} fn Function to be called on each property.
* @this {*}
*/
_V_.eachProp = function(obj, fn){
if (!obj) { return; }
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
fn.call(this, name, obj[name]);
}
}
},
};
el: function(id){ return document.getElementById(id); },
createElement: function(tagName, attributes){
var el = document.createElement(tagName),
attrname;
for (attrname in attributes){
if (attributes.hasOwnProperty(attrname)) {
if (attrname.indexOf("-") !== -1) {
el.setAttribute(attrname, attributes[attrname]);
/**
* Merge two objects together and return the original.
* @param {[type]} obj1 [description]
* @param {[type]} obj2 [description]
* @param {[type]} safe [description]
* @return {[type]}
*/
_V_.merge = function(obj1, obj2, safe){
// Make sure second object exists
if (!obj2) { return obj1; }
for (var attrname in obj2){
if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
}
return obj1;
};
// _V_.extend = function(obj){ this.merge(this, obj, true); };
/**
* Bind (a.k.a proxy or Context). A simple method for changing the context of a function
It also stores a unique id on the function so it can be easily removed from events
* @param {*} context The object to bind as scope
* @param {Function} fn The function to be bound to a scope
* @param {Number=} uid An optional unique ID for the function to be set
* @return {Function}
*/
_V_.bind = function(context, fn, uid) {
// Make sure the function has a unique ID
if (!fn.guid) { fn.guid = _V_.guid++; }
// Create the new function that changes the context
var ret = function() {
return fn.apply(context, arguments);
}
// Allow for the ability to individualize this function
// Needed in the case where multiple objects might share the same prototype
// IF both items add an event listener with the same function, then you try to remove just one
// it will remove both because they both have the same guid.
// when using this, you need to use the bind method when you remove the listener as well.
ret.guid = (uid) ? uid + "_" + fn.guid : fn.guid;
return ret;
};
/**
* Element Data Store. Allows for binding data to an element without putting it directly on the element.
* Ex. Event listneres are stored here.
* (also from jsninja.com, slightly modified and updated for closure compiler)
* @type {Object}
*/
_V_.cache = {};
/**
* Unique ID for an element or function
* @type {Number}
*/
_V_.guid = 1;
/**
* Unique attribute name to store an element's guid in
* @type {String}
* @constant
*/
_V_.expando = "vdata" + (new Date).getTime();
/**
* Returns the cache object where data for an element is stored
* @param {Element} el Element to store data for.
* @return {Object}
*/
_V_.getData = function(el){
var id = el[_V_.expando];
if (!id) {
id = el[_V_.expando] = _V_.guid++;
_V_.cache[id] = {};
}
return _V_.cache[id];
};
/**
* Delete data for the element from the cache and the guid attr from getElementById
* @param {Element} el Remove data for an element
*/
_V_.removeData = function(el){
var id = el[_V_.expando];
if (!id) { return; }
// Remove all stored data
// Changed to = null
// http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
_V_.cache[id] = null;
// Remove the expando property from the DOM node
try {
delete el[_V_.expando];
} catch(e) {
if (el.removeAttribute) {
el.removeAttribute(_V_.expando);
} else {
el[attrname] = attributes[attrname];
// IE doesn't appear to support removeAttribute on the document element
el[_V_.expando] = null;
}
}
}
return el;
},
};
insertFirst: function(node, parent){
if (parent.firstChild) {
parent.insertBefore(node, parent.firstChild);
} else {
parent.appendChild(node);
_V_.isEmpty = function(obj) {
for (var prop in obj) {
// Inlude null properties as empty.
if (obj[prop] !== null) {
return false;
}
},
}
return true;
};
addClass: function(element, classToAdd){
/**
* Add a CSS class name to an element
* @param {Element} element Element to add class name to
* @param {String} classToAdd Classname to add
*/
_V_.addClass = function(element, classToAdd){
if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) {
element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd;
}
},
};
removeClass: function(element, classToRemove){
/**
* Remove a CSS class name from an element
* @param {Element} element Element to remove from class name
* @param {String} classToAdd Classname to remove
*/
_V_.removeClass = function(element, classToRemove){
if (element.className.indexOf(classToRemove) == -1) { return; }
var classNames = element.className.split(" ");
classNames.splice(classNames.indexOf(classToRemove),1);
element.className = classNames.join(" ");
},
};
// Attempt to block the ability to select text while dragging controls
blockTextSelection: function(){
document.body.focus();
document.onselectstart = function () { return false; };
},
// Turn off text selection blocking
unblockTextSelection: function(){ document.onselectstart = function () { return true; }; },
/**
* Element for testing browser HTML5 video capabilities
* @type {Element}
* @constant
*/
_V_.TEST_VID = document.createElement("video");
// Return seconds as H:MM:SS or M:SS
// Supplying a guide (in seconds) will include enough leading zeros to cover the length of the guide
formatTime: function(seconds, guide) {
/**
* Useragent for browser testing.
* @type {String}
* @constant
*/
_V_.UA = navigator.userAgent;
/**
* Device is an iPhone
* @type {Boolean}
* @constant
*/
_V_.IS_IPHONE = !!navigator.userAgent.match(/iPad/i);
_V_.IS_IPAD = !!navigator.userAgent.match(/iPhone/i);
_V_.IS_IPOD = !!navigator.userAgent.match(/iPod/i);
_V_.IS_IOS = _V_.IS_IPHONE || _V_.IS_IPAD || _V_.IS_IPOD;
_V_.IOS_VERSION = (function(){
var match = navigator.userAgent.match(/OS (\d+)_/i);
if (match && match[1]) { return match[1]; }
})();
_V_.IS_ANDROID = !!navigator.userAgent.match(/Android.*AppleWebKit/i);
_V_.ANDROID_VERSION = (function() {
var match = navigator.userAgent.match(/Android (\d+)\./i);
if (match && match[1]) {
return match[1];
}
return null;
})();
/**
* Get an element's attribute values, as defined on the HTML tag
* Attributs are not the same as properties. They're defined on the tag
* or with setAttribute (which shouldn't be used with HTML)
* This will return true or false for boolean attributes.
* @param {Element} tag Element from which to get tag attributes
* @return {Object}
*/
_V_.getAttributeValues = function(tag){
var obj = {};
// Known boolean attributes
// We can check for matching boolean properties, but older browsers
// won't know about HTML5 boolean attributes that we still read from.
// Bookending with commas to allow for an easy string search.
var knownBooleans = ","+"autoplay,controls,loop,muted,default"+",";
if (tag && tag.attributes && tag.attributes.length > 0) {
var attrs = tag.attributes;
var attrName, attrVal;
for (var i = attrs.length - 1; i >= 0; i--) {
attrName = attrs[i].name;
attrVal = attrs[i].value;
// Check for known booleans
// The matching element property will return a value for typeof
if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(","+attrName+",") !== -1) {
// The value of an included boolean attribute is typically an empty string ("")
// which would equal false if we just check for a false value.
// We also don't want support bad code like autoplay="false"
attrVal = (attrVal !== null) ? true : false;
}
obj[attrName] = attrVal;
};
}
return obj;
};
/**
* Get the computed style value for an element
* From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
* @param {Element} el Element to get style value for
* @param {String} strCssRule Style name
* @return {String} Style value
*/
_V_.getComputedStyleValue = function(el, strCssRule){
var strValue = "";
if(document.defaultView && document.defaultView.getComputedStyle){
strValue = document.defaultView.getComputedStyle(el, "").getPropertyValue(strCssRule);
} else if(el.currentStyle){
strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){
return p1.toUpperCase();
});
strValue = el.currentStyle[strCssRule];
}
return strValue;
};
/**
* Insert an element as the first child node of another
* @param {Element} child Element to insert
* @param {[type]} parent Element to insert child into
*/
_V_.insertFirst = function(child, parent){
if (parent.firstChild) {
parent.insertBefore(child, parent.firstChild);
} else {
parent.appendChild(child);
}
};
/**
* Object to hold browser support information
* @type {Object}
*/
_V_.support = {};
/**
* Shorthand for document.getElementById()
* Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
* @param {String} id Element ID
* @return {Element} Element with supplied ID
*/
_V_.el = function(id){
if (id.indexOf("#") === 0) {
id = id.slice(1);
}
return document.getElementById(id);
};
/**
* Format seconds as a time string, H:MM:SS or M:SS
* Supplying a guide (in seconds) will force a number of leading zeros
* to cover the length of the guide
* @param {Number} seconds Number of seconds to be turned into a string
* @param {Number} guide Number (in seconds) to model the string after
* @return {String} Time formatted as H:MM:SS or M:SS
*/
_V_.formatTime = function(seconds, guide) {
guide = guide || seconds; // Default to using seconds as guide
var s = Math.floor(seconds % 60),
m = Math.floor(seconds / 60 % 60),
@ -115,152 +344,121 @@ _V_.extend({
s = (s < 10) ? "0" + s : s;
return h + m + s;
},
};
uc: function(string){
return string.charAt(0).toUpperCase() + string.slice(1);
},
// Attempt to block the ability to select text while dragging controls
_V_.blockTextSelection = function(){
document.body.focus();
document.onselectstart = function () { return false; };
};
// Turn off text selection blocking
_V_.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };
// Return the relative horizonal position of an event as a value from 0-1
getRelativePosition: function(x, relativeElement){
return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
},
/**
* Trim whitespace from the ends of a string.
* @param {String} string String to trim
* @return {String} Trimmed string
*/
_V_.trim = function(string){
return string.toString().replace(/^\s+/, "").replace(/\s+$/, "");
};
getComputedStyleValue: function(element, style){
return window.getComputedStyle(element, null).getPropertyValue(style);
},
trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); },
round: function(num, dec) {
/**
* Should round off a number to a decimal place
* @param {Number} num Number to round
* @param {Number} dec Number of decimal places to round to
* @return {Number} Rounded number
*/
_V_.round = function(num, dec) {
if (!dec) { dec = 0; }
return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
},
};
isEmpty: function(object) {
for (var prop in object) {
return false;
}
return true;
},
// Mimic HTML5 TimeRange Spec.
createTimeRange: function(start, end){
/**
* Should create a fake TimeRange object
* Mimics an HTML5 time range instance, which has functions that
* return the start and end times for a range
* TimeRanges are returned by the buffered() method
* @param {Number} start Start time in seconds
* @param {Number} end End time in seconds
* @return {Object} Fake TimeRange object
*/
_V_.createTimeRange = function(start, end){
return {
length: 1,
start: function() { return start; },
end: function() { return end; }
};
},
};
/* Element Data Store. Allows for binding data to an element without putting it directly on the element.
Ex. Event listneres are stored here.
(also from jsninja.com)
================================================================================ */
cache: {}, // Where the data is stored
guid: 1, // Unique ID for the element
expando: "vdata" + (new Date).getTime(), // Unique attribute to store element's guid in
// _V_.extend({
// // Device Checks
// isIE: function(){ return !+"\v1"; },
// isFF: function(){ return !!_V_.ua.match("Firefox") },
// Returns the cache object where data for the element is stored
getData: function(elem){
var id = elem[_V_.expando];
if (!id) {
id = elem[_V_.expando] = _V_.guid++;
_V_.cache[id] = {};
}
return _V_.cache[id];
},
// Delete data for the element from the cache and the guid attr from element
removeData: function(elem){
var id = elem[_V_.expando];
if (!id) { return; }
// Remove all stored data
delete _V_.cache[id];
// Remove the expando property from the DOM node
try {
delete elem[_V_.expando];
} catch(e) {
if (elem.removeAttribute) {
elem.removeAttribute(_V_.expando);
} else {
// IE doesn't appear to support removeAttribute on the document element
elem[_V_.expando] = null;
}
}
},
// each: function(arr, fn){
// if (!arr || arr.length === 0) { return; }
// for (var i=0,j=arr.length; i<j; i++) {
// fn.call(this, arr[i], i);
// }
// },
/* Proxy (a.k.a Bind or Context). A simple method for changing the context of a function
It also stores a unique id on the function so it can be easily removed from events
================================================================================ */
proxy: function(context, fn, uid) {
// Make sure the function has a unique ID
if (!fn.guid) { fn.guid = _V_.guid++; }
// // Return the relative horizonal position of an event as a value from 0-1
// getRelativePosition: function(x, relativeElement){
// return Math.max(0, Math.min(1, (x - _V_.findPosX(relativeElement)) / relativeElement.offsetWidth));
// },
// Create the new function that changes the context
var ret = function() {
return fn.apply(context, arguments);
}
// get: function(url, onSuccess, onError){
// // if (netscape.security.PrivilegeManager.enablePrivilege) {
// // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
// // }
// Allow for the ability to individualize this function
// Needed in the case where multiple objects might share the same prototype
// IF both items add an event listener with the same function, then you try to remove just one
// it will remove both because they both have the same guid.
// when using this, you need to use the proxy method when you remove the listener as well.
ret.guid = (uid) ? uid + "_" + fn.guid : fn.guid;
// var local = (url.indexOf("file:") == 0 || (window.location.href.indexOf("file:") == 0 && url.indexOf("http:") == -1));
return ret;
},
get: function(url, onSuccess, onError){
// if (netscape.security.PrivilegeManager.enablePrivilege) {
// netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead");
// if (typeof XMLHttpRequest == "undefined") {
// XMLHttpRequest = function () {
// try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
// try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
// try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
// throw new Error("This browser does not support XMLHttpRequest.");
// };
// }
var local = (url.indexOf("file:") == 0 || (window.location.href.indexOf("file:") == 0 && url.indexOf("http:") == -1));
// var request = new XMLHttpRequest();
if (typeof XMLHttpRequest == "undefined") {
XMLHttpRequest = function () {
try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
throw new Error("This browser does not support XMLHttpRequest.");
};
}
// try {
// request.open("GET", url);
// } catch(e) {
// _V_.log("VideoJS XMLHttpRequest (open)", e);
// // onError(e);
// return false;
// }
var request = new XMLHttpRequest();
// request.onreadystatechange = _V_.proxy(this, function() {
// if (request.readyState == 4) {
// if (request.status == 200 || local && request.status == 0) {
// onSuccess(request.responseText);
// } else {
// if (onError) {
// onError();
// }
// }
// }
// });
try {
request.open("GET", url);
} catch(e) {
_V_.log("VideoJS XMLHttpRequest (open)", e);
// try {
// request.send();
// } catch(e) {
// _V_.log("VideoJS XMLHttpRequest (send)", e);
// if (onError) {
// onError(e);
return false;
}
request.onreadystatechange = _V_.proxy(this, function() {
if (request.readyState == 4) {
if (request.status == 200 || local && request.status == 0) {
onSuccess(request.responseText);
} else {
if (onError) {
onError();
}
}
}
});
try {
request.send();
} catch(e) {
_V_.log("VideoJS XMLHttpRequest (send)", e);
if (onError) {
onError(e);
}
}
},
// }
// }
// },
/* Local Storage
================================================================================ */
setLocalStorage: function(key, value){
_V_.setLocalStorage = function(key, value){
// IE was throwing errors referencing the var anywhere without this
var localStorage = window.localStorage || false;
if (!localStorage) { return; }
@ -273,24 +471,28 @@ _V_.extend({
_V_.log("LocalStorage Error (VideoJS)", e);
}
}
},
};
// Get abosolute version of relative URL. Used to tell flash correct URL.
// http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
getAbsoluteURL: function(url){
/**
* Get abosolute version of relative URL. Used to tell flash correct URL.
* http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
* @param {String} url URL to make absolute
* @return {String} Absolute URL
*/
_V_.getAbsoluteURL = function(url){
// Check if absolute URL
if (!url.match(/^https?:\/\//)) {
// Convert to absolute URL. Flash hosted off-site needs an absolute URL.
url = _V_.createElement('div', {
url = _V_.createEl('div', {
innerHTML: '<a href="'+url+'">x</a>'
}).firstChild.href;
}
return url;
}
};
});
// });
// usage: log('inside coolFunc', this, arguments);
// paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/

243
src/media.html5.js Normal file
View File

@ -0,0 +1,243 @@
goog.provide('_V_.Html5');
goog.provide('_V_.media.html5');
goog.require('_V_.MediaTechController');
/* HTML5 Media Controller - Wrapper for HTML5 Media API
================================================================================ */
/**
* HTML5 Media Controller - Wrapper for HTML5 Media API
* @param {_V_.Player|Object} player
* @param {Object=} options
* @param {Function=} ready
* @constructor
*/
_V_.Html5 = function(player, options, ready){
goog.base(this, player, options, ready);
var source = options.source;
// If the element source is already set, we may have missed the loadstart event, and want to trigger it.
// We don't want to set the source again and interrupt playback.
if (source && this.el_.currentSrc == source.src) {
player.trigger("loadstart");
// Otherwise set the source if one was provided.
} else if (source) {
this.el_.src = source.src;
}
// Chrome and Safari both have issues with autoplay.
// In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
// In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
// This fixes both issues. Need to wait for API, so it updates displays correctly
player.ready(function(){
if (this.options.autoplay && this.paused()) {
this.tag.poster = null; // Chrome Fix. Fixed in Chrome v16.
this.play();
}
});
this.on("click", this.onClick);
this.setupTriggers();
this.triggerReady();
};
goog.inherits(_V_.Html5, _V_.MediaTechController);
_V_.Html5.prototype.dispose = function(){
// this.player.tag = false;
this.removeTriggers();
goog.base(this, 'dispose');
};
_V_.Html5.prototype.createEl = function(){
var player = this.player,
// If possible, reuse original tag for HTML5 playback technology element
el = player.tag,
newEl;
// Check if this browser supports moving the element into the box.
// On the iPhone video will break if you move the element,
// So we have to create a brand new element.
if (!el || _V_.media.html5.support.movingElementInDOM === false) {
// If the original tag is still there, remove it.
if (el) {
player.getEl().removeChild(el);
}
newEl = _V_.createElement("video", {
id: el.id || player.id + "_html5_api",
className: el.className || "vjs-tech"
});
el = newEl;
_V_.insertFirst(el, player.el);
}
// Update specific tag settings, in case they were overridden
var attrs = ["autoplay","preload","loop","muted"];
for (var i = attrs.length - 1; i >= 0; i--) {
var attr = attrs[i];
if (player.options[attr] !== null) {
el[attr] = player.options[attr];
}
};
return el;
// jenniisawesome = true;
};
// Make video events trigger player events
// May seem verbose here, but makes other APIs possible.
_V_.Html5.prototype.setupTriggers = function(){
for (var i = _V_.media.html5.events.length - 1; i >= 0; i--) {
_V_.on(this.el_, _V_.media.html5.events[i], _V_.bind(this.player, this.eventHandler));
};
};
_V_.Html5.prototype.removeTriggers = function(){
for (var i = _V_.media.html5.events.length - 1; i >= 0; i--) {
_V_.off(this.el_, _V_.media.html5.events[i], _V_.bind(this.player, this.eventHandler));
};
// console.log("removeTriggers", _V_.getData(this.el_));
};
_V_.Html5.prototype.eventHandler = function(e){
// console.log('eventHandler', e.type, e, this.el_)
this.trigger(e);
e.stopPropagation();
};
_V_.Html5.prototype.play = function(){ this.el_.play(); };
_V_.Html5.prototype.pause = function(){ this.el_.pause(); };
_V_.Html5.prototype.paused = function(){ return this.el_.paused; };
_V_.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };
_V_.Html5.prototype.setCurrentTime = function(seconds){
try {
this.el_.currentTime = seconds;
} catch(e) {
_V_.log(e, "Video isn't ready. (Video.js)");
// this.warning(VideoJS.warnings.videoNotReady);
}
};
_V_.Html5.prototype.duration = function(){ return this.el_.duration || 0; };
_V_.Html5.prototype.buffered = function(){ return this.el_.buffered; };
_V_.Html5.prototype.volume = function(){ return this.el_.volume; };
_V_.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };
_V_.Html5.prototype.muted = function(){ return this.el_.muted; };
_V_.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };
_V_.Html5.prototype.width = function(){ return this.el_.offsetWidth; };
_V_.Html5.prototype.height = function(){ return this.el_.offsetHeight; };
_V_.Html5.prototype.supportsFullScreen = function(){
if (typeof this.el_.webkitEnterFullScreen == 'function') {
// Seems to be broken in Chromium/Chrome && Safari in Leopard
if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
return true;
}
}
return false;
};
_V_.Html5.prototype.enterFullScreen = function(){
try {
this.el_.webkitEnterFullScreen();
} catch (e) {
if (e.code == 11) {
// this.warning(VideoJS.warnings.videoNotReady);
_V_.log("Video.js: Video not ready.");
}
}
};
_V_.Html5.prototype.exitFullScreen = function(){
try {
this.el_.webkitExitFullScreen();
} catch (e) {
if (e.code == 11) {
// this.warning(VideoJS.warnings.videoNotReady);
_V_.log("Video.js: Video not ready.");
}
}
};
_V_.Html5.prototype.src = function(src){ this.el_.src = src; };
_V_.Html5.prototype.load = function(){ this.el_.load(); };
_V_.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };
_V_.Html5.prototype.preload = function(){ return this.el_.preload; };
_V_.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };
_V_.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };
_V_.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };
_V_.Html5.prototype.loop = function(){ return this.el_.loop; };
_V_.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };
_V_.Html5.prototype.error = function(){ return this.el_.error; };
// networkState: function(){ return this.el_.networkState; },
// readyState: function(){ return this.el_.readyState; },
_V_.Html5.prototype.seeking = function(){ return this.el_.seeking; };
// initialTime: function(){ return this.el_.initialTime; },
// startOffsetTime: function(){ return this.el_.startOffsetTime; },
// played: function(){ return this.el_.played; },
// seekable: function(){ return this.el_.seekable; },
_V_.Html5.prototype.ended = function(){ return this.el_.ended; };
// videoTracks: function(){ return this.el_.videoTracks; },
// audioTracks: function(){ return this.el_.audioTracks; },
// videoWidth: function(){ return this.el_.videoWidth; },
// videoHeight: function(){ return this.el_.videoHeight; },
// textTracks: function(){ return this.el_.textTracks; },
// defaultPlaybackRate: function(){ return this.el_.defaultPlaybackRate; },
// playbackRate: function(){ return this.el_.playbackRate; },
// mediaGroup: function(){ return this.el_.mediaGroup; },
// controller: function(){ return this.el_.controller; },
_V_.Html5.prototype.controls = function(){ return this.player.options.controls; };
_V_.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };
/* HTML5 Support Testing -------------------------------------------------------- */
_V_.media.html5.isSupported = function(){
return !!document.createElement("video").canPlayType;
};
_V_.media.html5.canPlaySource = function(srcObj){
return !!document.createElement("video").canPlayType(srcObj.type);
// TODO: Check Type
// If no Type, check ext
// Check Media Type
};
// List of all HTML5 events (various uses).
_V_.media.html5.events = "loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange".split(",");
// HTML5 Device Fixes ---------------------------------------------------------- //
_V_.media.html5.support = {
// Support for video element specific full screen. (webkitEnterFullScreen, not requestFullscreen which we use on the player div)
// http://developer.apple.com/library/safari/#documentation/AudioVideo/Reference/HTMLVideoElementClassReference/HTMLVideoElement/HTMLVideoElement.html
// Seems to be broken in Chromium/Chrome && Safari in Leopard
fullscreen: (_V_.TEST_VID.webkitEnterFullScreen) ? (!_V_.UA.match("Chrome") && !_V_.UA.match("Mac OS X 10.5") ? true : false) : false,
// In iOS, if you move a video element in the DOM, it breaks video playback.
movingElementInDOM: !_V_.IS_IOS
};
// Android
if (_V_.IS_ANDROID) {
// Override Android 2.2 and less canPlayType method which is broken
if (_V_.ANDROID_VERSION < 3) {
document.createElement("video").constructor.prototype.canPlayType = function(type){
return (type && type.toLowerCase().indexOf("video/mp4") != -1) ? "maybe" : "";
};
}
}

52
src/media.js Normal file
View File

@ -0,0 +1,52 @@
goog.provide('_V_.media');
goog.provide('_V_.MediaTechController');
goog.require('_V_.Component');
/* Media Technology Controller - Base class for media playback technologies
================================================================================ */
/**
* Base class for media (HTML5 Video, Flash) controllers
* @param {_V_.Player|Object} player Central player instance
* @param {Object=} options Options object
* @constructor
*/
_V_.MediaTechController = function(player, options, ready){
goog.base(this, player, options, ready);
// Make playback element clickable
// this.addEvent("click", this.proxy(this.onClick));
// player.triggerEvent("techready");
};
goog.inherits(_V_.MediaTechController, _V_.Component);
// destroy: function(){},
// createElement: function(){},
/**
* Handle a click on the media element. By default will play the media.
*/
_V_.MediaTechController.prototype.onClick = function(){
if (this.player.options.controls) {
if (this.player.paused()) {
this.player.play();
} else {
this.player.pause();
}
}
};
/**
* List of default API methods for any MediaTechController
* @type {String}
*/
_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");
// Create placeholder methods for each that warn when a method isn't supported by the current playback technology
for (var i = _V_.apiMethods.length - 1; i >= 0; i--) {
_V_.MediaTechController.prototype[_V_.apiMethods[i]] = function(){
throw new Error("The '"+_V_.apiMethods[i]+"' method is not available on the playback technology's API");
};
};

File diff suppressed because it is too large Load Diff

View File

@ -1,29 +0,0 @@
/* Playback Technology - Base class for playback technologies
================================================================================ */
_V_.PlaybackTech = _V_.Component.extend({
init: function(player, options){
// this._super(player, options);
// Make playback element clickable
// _V_.addEvent(this.el, "click", _V_.proxy(this, _V_.PlayToggle.prototype.onClick));
// this.addEvent("click", this.proxy(this.onClick));
// player.triggerEvent("techready");
},
// destroy: function(){},
// createElement: function(){},
onClick: function(){
if (this.player.options.controls) {
_V_.PlayToggle.prototype.onClick.call(this);
}
}
});
// Create placeholder methods for each that warn when a method isn't supported by the current playback technology
_V_.apiMethods = "play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted".split(",");
_V_.each(_V_.apiMethods, function(methodName){
_V_.PlaybackTech.prototype[methodName] = function(){
throw new Error("The '"+methodName+"' method is not available on the playback technology's API");
};
});

View File

@ -6,11 +6,10 @@
// Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
// Player Track Functions - Functions add to the player object for easier access to tracks
_V_.merge(_V_.Player.prototype, {
// Add an array of text tracks. captions, subtitles, chapters, descriptions
// Track objects will be stored in the player.textTracks array
addTextTracks: function(trackObjects){
_V_.Player.prototype.addTextTracks = function(trackObjects){
var tracks = this.textTracks = (this.textTracks) ? this.textTracks : [],
i = 0, j = trackObjects.length, track, Kind;
@ -29,17 +28,17 @@ _V_.merge(_V_.Player.prototype, {
// Incase there are mulitple defaulted tracks of the same kind
// Or the user has a set preference of a specific language that should override the default
if (track['default']) {
this.ready(_V_.proxy(track, track.show));
this.ready(_V_.bind(track, track.show));
}
}
// Return the track so it can be appended to the display component
return this;
},
};
// Show a text track
// disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
showTextTrack: function(id, disableSameKind){
_V_.Player.prototype.showTextTrack = function(id, disableSameKind){
var tracks = this.textTracks,
i = 0,
j = tracks.length,
@ -67,16 +66,17 @@ _V_.merge(_V_.Player.prototype, {
}
return this;
}
};
});
// Track Class
// Contains track methods for loading, showing, parsing cues of tracks
_V_.Track = _V_.Component.extend({
init: function(player, options){
this._super(player, options);
/**
* Track Class
* Contains track methods for loading, showing, parsing cues of tracks
* @param {_V_.Player|Object} player
* @param {Object=} options
* @constructor
*/
_V_.Track = function(player, options){
goog.base(this, player, options);
// Apply track info to track object
// Options will often be a track element
@ -121,14 +121,15 @@ _V_.Track = _V_.Component.extend({
// attribute unsigned short mode;
mode: 0
});
},
};
goog.inherits(_V_.Track, _V_.Component);
// Create basic div to hold cue text
createElement: function(){
return this._super("div", {
_V_.Track.prototype.createEl = function(){
return goog.base(this, 'createEl', "div", {
className: "vjs-" + this.kind + " vjs-text-track"
});
},
};
// Show: Mode Showing (2)
// Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
@ -138,33 +139,33 @@ _V_.Track = _V_.Component.extend({
// and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
// The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
// This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
show: function(){
_V_.Track.prototype.show = function(){
this.activate();
this.mode = 2;
// Show element.
this._super();
},
goog.base(this, 'show');
};
// Hide: Mode Hidden (1)
// Indicates that the text track is active, but that the user agent is not actively displaying the cues.
// If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
// The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
hide: function(){
_V_.Track.prototype.hide = function(){
// When hidden, cues are still triggered. Disable to stop triggering.
this.activate();
this.mode = 1;
// Hide element.
this._super();
},
goog.base(this, 'hide');
};
// Disable: Mode Off/Disable (0)
// Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
// No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
disable: function(){
_V_.Track.prototype.disable = function(){
// If showing, hide.
if (this.mode == 2) { this.hide(); }
@ -173,39 +174,39 @@ _V_.Track = _V_.Component.extend({
// Switch Mode to Off
this.mode = 0;
},
};
// Turn on cue tracking. Tracks that are showing OR hidden are active.
activate: function(){
_V_.Track.prototype.activate = function(){
// Load text file if it hasn't been yet.
if (this.readyState == 0) { this.load(); }
// Only activate if not already active.
if (this.mode == 0) {
// Update current cue on timeupdate
// Using unique ID for proxy function so other tracks don't remove listener
this.player.on("timeupdate", this.proxy(this.update, this.id));
// Using unique ID for bind function so other tracks don't remove listener
this.player.on("timeupdate", _V_.bind(this, this.update, this.id));
// Reset cue time on media end
this.player.on("ended", this.proxy(this.reset, this.id));
this.player.on("ended", _V_.bind(this, this.reset, this.id));
// Add to display
if (this.kind == "captions" || this.kind == "subtitles") {
this.player.textTrackDisplay.addComponent(this);
this.player.textTrackDisplay.addChild(this);
}
}
},
};
// Turn off cue tracking.
deactivate: function(){
// Using unique ID for proxy function so other tracks don't remove listener
this.player.off("timeupdate", this.proxy(this.update, this.id));
this.player.off("ended", this.proxy(this.reset, this.id));
_V_.Track.prototype.deactivate = function(){
// Using unique ID for bind function so other tracks don't remove listener
this.player.off("timeupdate", _V_.bind(this, this.update, this.id));
this.player.off("ended", _V_.bind(this, this.reset, this.id));
this.reset(); // Reset
// Remove from display
this.player.textTrackDisplay.removeComponent(this);
},
this.player.textTrackDisplay.removeChild(this);
};
// A readiness state
// One of the following:
@ -221,25 +222,25 @@ _V_.Track = _V_.Component.extend({
//
// Failed to load
// Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
load: function(){
_V_.Track.prototype.load = function(){
// Only load if not loaded yet.
if (this.readyState == 0) {
this.readyState = 1;
_V_.get(this.src, this.proxy(this.parseCues), this.proxy(this.onError));
_V_.get(this.src, _V_.bind(this, this.parseCues), _V_.bind(this, this.onError));
}
},
};
onError: function(err){
_V_.Track.prototype.onError = function(err){
this.error = err;
this.readyState = 3;
this.trigger("error");
},
};
// Parse the WebVTT text format for cue times.
// TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
parseCues: function(srcContent) {
_V_.Track.prototype.parseCues = function(srcContent) {
var cue, time, text,
lines = srcContent.split("\n"),
line = "", id;
@ -290,9 +291,10 @@ _V_.Track = _V_.Component.extend({
this.readyState = 2;
this.trigger("loaded");
},
};
parseCueTime: function(timeText) {
_V_.Track.prototype.parseCueTime = function(timeText) {
var parts = timeText.split(':'),
time = 0,
hours, minutes, other, seconds, ms, flags;
@ -330,10 +332,10 @@ _V_.Track = _V_.Component.extend({
if (ms) { time += ms/1000; }
return time;
},
};
// Update active cues whenever timeupdate events are triggered on the player.
update: function(){
_V_.Track.prototype.update = function(){
if (this.cues.length > 0) {
// Get curent player time
@ -440,10 +442,10 @@ _V_.Track = _V_.Component.extend({
this.trigger("cuechange");
}
}
},
};
// Add cue HTML to display
updateDisplay: function(){
_V_.Track.prototype.updateDisplay = function(){
var cues = this.activeCues,
html = "",
i=0,j=cues.length;
@ -452,91 +454,104 @@ _V_.Track = _V_.Component.extend({
html += "<span class='vjs-tt-cue'>"+cues[i].text+"</span>";
}
this.el.innerHTML = html;
},
this.el_.innerHTML = html;
};
// Set all loop helper values back
reset: function(){
_V_.Track.prototype.reset = function(){
this.nextChange = 0;
this.prevChange = this.player.duration();
this.firstActiveIndex = 0;
this.lastActiveIndex = 0;
}
});
};
// Create specific track types
_V_.CaptionsTrack = _V_.Track.extend({
kind: "captions"
});
/**
* @constructor
*/
_V_.CaptionsTrack = function(){};
goog.inherits(_V_.CaptionsTrack, _V_.Track);
_V_.CaptionsTrack.prototype.kind = "captions";
_V_.SubtitlesTrack = _V_.Track.extend({
kind: "subtitles"
});
/**
* @constructor
*/
_V_.SubtitlesTrack = function(){};
goog.inherits(_V_.SubtitlesTrack, _V_.Track);
_V_.SubtitlesTrack.prototype.kind = "subtitles";
_V_.ChaptersTrack = _V_.Track.extend({
kind: "chapters"
});
/**
* @constructor
*/
_V_.ChaptersTrack = function(){};
goog.inherits(_V_.ChaptersTrack, _V_.Track);
_V_.ChaptersTrack.prototype.kind = "chapters";
/* Text Track Display
================================================================================ */
// Global container for both subtitle and captions text. Simple div container.
_V_.TextTrackDisplay = _V_.Component.extend({
createElement: function(){
return this._super("div", {
/**
* @constructor
*/
_V_.TextTrackDisplay = function()}{};
goog.inherits(_V_.TextTrackDisplay, _V_.Component);
_V_.TextTrackDisplay.prototype.createEl = function(){
return goog.base(this, 'createEl', "div", {
className: "vjs-text-track-display"
});
}
};
});
/* Text Track Menu Items
================================================================================ */
_V_.TextTrackMenuItem = _V_.MenuItem.extend({
init: function(player, options){
/**
* @constructor
*/
_V_.TextTrackMenuItem = function(player, options){
var track = this.track = options.track;
// Modify options for parent MenuItem class's init.
options.label = track.label;
options.selected = track["default"];
this._super(player, options);
goog.base(this, player, options);
this.player.on(track.kind + "trackchange", _V_.proxy(this, this.update));
},
this.player.on(track.kind + "trackchange", _V_.bind(this, this.update));
};
goog.inherits(_V_.TextTrackMenuItem, _V_.MenuItem);
onClick: function(){
this._super();
_V_.TextTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
this.player.showTextTrack(this.track.id, this.track.kind);
},
};
update: function(){
_V_.TextTrackMenuItem.prototype.update = function(){
if (this.track.mode == 2) {
this.selected(true);
} else {
this.selected(false);
}
}
};
});
_V_.OffTextTrackMenuItem = _V_.TextTrackMenuItem.extend({
init: function(player, options){
/**
* @constructor
*/
_V_.OffTextTrackMenuItem = function(player, options){
// Create pseudo track info
// Requires options.kind
options.track = { kind: options.kind, player: player, label: "Off" }
this._super(player, options);
},
goog.base(this, player, options);
};
goog.inherits(_V_.OffTextTrackMenuItem, _V_.TextTrackMenuItem);
onClick: function(){
this._super();
_V_.OffTextTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
this.player.showTextTrack(this.track.id, this.track.kind);
},
};
update: function(){
_V_.OffTextTrackMenuItem.prototype.update = function(){
var tracks = this.player.textTracks,
i=0, j=tracks.length, track,
off = true;
@ -553,25 +568,25 @@ _V_.OffTextTrackMenuItem = _V_.TextTrackMenuItem.extend({
} else {
this.selected(false);
}
}
});
};
/* Captions Button
================================================================================ */
_V_.TextTrackButton = _V_.Button.extend({
init: function(player, options){
this._super(player, options);
/**
* @constructor
*/
_V_.TextTrackButton = function(player, options){
goog.base(this, player, options);
this.menu = this.createMenu();
if (this.items.length === 0) {
this.hide();
}
},
};
goog.inherits(_V_.TextTrackButton, _V_.Button);
createMenu: function(){
_V_.TextTrackButton.prototype.createMenu = function(){
var menu = new _V_.Menu(this.player);
// Add a title list item to the top
@ -591,13 +606,13 @@ _V_.TextTrackButton = _V_.Button.extend({
});
// Add list to element
this.addComponent(menu);
this.addChild(menu);
return menu;
},
};
// Create a menu item for each text track
createItems: function(){
_V_.TextTrackButton.prototype.createItems = function(){
var items = [];
this.each(this.player.textTracks, function(track){
if (track.kind === this.kind) {
@ -607,59 +622,67 @@ _V_.TextTrackButton = _V_.Button.extend({
}
});
return items;
},
};
buildCSSClass: function(){
return this.className + " vjs-menu-button " + this._super();
},
_V_.TextTrackButton.prototype.buildCSSClass = function(){
return this.className + " vjs-menu-button " + goog.base(this, 'buildCSSClass');
};
// Focus - Add keyboard functionality to element
onFocus: function(){
_V_.TextTrackButton.prototype.onFocus = function(){
// Show the menu, and keep showing when the menu items are in focus
this.menu.lockShowing();
// this.menu.el.style.display = "block";
// When tabbing through, the menu should hide when focus goes from the last menu item to the next tabbed element.
_V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", this.proxy(function(){
_V_.one(this.menu.el.childNodes[this.menu.el.childNodes.length - 1], "blur", _V_.bind(this, function(){
this.menu.unlockShowing();
}));
},
};
// Can't turn off list display that we turned on with focus, because list would go away.
onBlur: function(){},
_V_.TextTrackButton.prototype.onBlur = function(){};
onClick: function(){
_V_.TextTrackButton.prototype.onClick = function(){
// When you click the button it adds focus, which will show the menu indefinitely.
// So we'll remove focus when the mouse leaves the button.
// Focus is needed for tab navigation.
this.one("mouseout", this.proxy(function(){
this.one("mouseout", _V_.bind(this, function(){
this.menu.unlockShowing();
this.el.blur();
this.el_.blur();
}));
}
};
});
/**
* @constructor
*/
_V_.CaptionsButton = function(){};
goog.inherits(_V_.CaptionsButton, _V_.TextTrackButton);
_V_.CaptionsButton.prototype.kind = "captions";
_V_.CaptionsButton.prototype.buttonText = "Captions";
_V_.CaptionsButton.prototype.className = "vjs-captions-button";
_V_.CaptionsButton = _V_.TextTrackButton.extend({
kind: "captions",
buttonText: "Captions",
className: "vjs-captions-button"
});
_V_.SubtitlesButton = _V_.TextTrackButton.extend({
kind: "subtitles",
buttonText: "Subtitles",
className: "vjs-subtitles-button"
});
/**
* @constructor
*/
_V_.SubtitlesButton = function(){};
goog.inherits(_V_.SubtitlesButton, _V_.TextTrackButton);
_V_.SubtitlesButton.prototype.kind = "subtitles";
_V_.SubtitlesButton.prototype.buttonText = "Subtitles";
_V_.SubtitlesButton.prototype.className = "vjs-subtitles-button";
// Chapters act much differently than other text tracks
// Cues are navigation vs. other tracks of alternative languages
_V_.ChaptersButton = _V_.TextTrackButton.extend({
kind: "chapters",
buttonText: "Chapters",
className: "vjs-chapters-button",
/**
* @constructor
*/
_V_.ChaptersButton = function(){};
goog.inherits(_V_.ChaptersButton, _V_.TextTrackButton);
_V_.ChaptersButton.prototype.kind = "chapters";
_V_.ChaptersButton.prototype.buttonText = "Chapters";
_V_.ChaptersButton.prototype.className = "vjs-chapters-button";
// Create a menu item for each text track
createItems: function(chaptersTrack){
_V_.ChaptersButton.prototype.createItems = function(chaptersTrack){
var items = [];
this.each(this.player.textTracks, function(track){
@ -671,9 +694,9 @@ _V_.ChaptersButton = _V_.TextTrackButton.extend({
});
return items;
},
};
createMenu: function(){
_V_.ChaptersButton.prototype.createMenu = function(){
var tracks = this.player.textTracks,
i = 0,
j = tracks.length,
@ -685,7 +708,7 @@ _V_.ChaptersButton = _V_.TextTrackButton.extend({
if (track.kind == this.kind && track["default"]) {
if (track.readyState < 2) {
this.chaptersTrack = track;
track.on("loaded", this.proxy(this.createMenu));
track.on("loaded", _V_.bind(this, this.createMenu));
return;
} else {
chaptersTrack = track;
@ -715,25 +738,25 @@ _V_.ChaptersButton = _V_.TextTrackButton.extend({
items.push(mi);
menu.addComponent(mi);
menu.addChild(mi);
}
}
// Add list to element
this.addComponent(menu);
this.addChild(menu);
if (this.items.length > 0) {
this.show();
}
return menu;
}
};
});
_V_.ChaptersTrackMenuItem = _V_.MenuItem.extend({
init: function(player, options){
/**
* @constructor
*/
_V_.ChaptersTrackMenuItem = function(player, options){
var track = this.track = options.track,
cue = this.cue = options.cue,
currentTime = player.currentTime();
@ -741,18 +764,19 @@ _V_.ChaptersTrackMenuItem = _V_.MenuItem.extend({
// Modify options for parent MenuItem class's init.
options.label = cue.text;
options.selected = (cue.startTime <= currentTime && currentTime < cue.endTime);
this._super(player, options);
goog.base(this, player, options);
track.on("cuechange", _V_.proxy(this, this.update));
},
track.on("cuechange", _V_.bind(this, this.update));
};
goog.inherits(_V_.ChaptersTrackMenuItem, _V_.MenuItem);
onClick: function(){
this._super();
_V_.ChaptersTrackMenuItem.prototype.onClick = function(){
goog.base(this, 'onClick');
this.player.currentTime(this.cue.startTime);
this.update(this.cue.startTime);
},
};
update: function(time){
_V_.ChaptersTrackMenuItem.prototype.update = function(time){
var cue = this.cue,
currentTime = this.player.currentTime();
@ -762,12 +786,10 @@ _V_.ChaptersTrackMenuItem = _V_.MenuItem.extend({
} else {
this.selected(false);
}
}
});
};
// Add Buttons to controlBar
_V_.merge(_V_.ControlBar.prototype.options.components, {
_V_.merge(_V_.ControlBar.prototype.options.children, {
"subtitlesButton": {},
"captionsButton": {},
"chaptersButton": {}
@ -775,6 +797,6 @@ _V_.merge(_V_.ControlBar.prototype.options.components, {
// _V_.Cue = _V_.Component.extend({
// init: function(player, options){
// this._super(player, options);
// goog.base(this, player, options);
// }
// });

View File

@ -3,7 +3,7 @@ module("Component");
test('should create an element', function(){
var comp = new _V_.Component({}, {});
ok(comp.el.nodeName);
ok(comp.getEl().nodeName);
});
test('should init child coponents from options', function(){
@ -16,6 +16,18 @@ test('should init child coponents from options', function(){
ok(comp.el.childNodes.length === 1);
});
test('should add a child component', function(){
var comp = new _V_.Component({});
comp.addChild("Component");
ok(comp.el.childNodes.length === 1);
});
test('should show and hide an element', function(){
var comp = new _V_.Component({}, {});