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:
parent
a34259860f
commit
688b5053ee
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ projects
|
||||
|
||||
node_modules
|
||||
npm-debug.log
|
||||
closure-test
|
||||
|
202
build/compiler/COPYING
Normal file
202
build/compiler/COPYING
Normal 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
292
build/compiler/README
Normal 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
BIN
build/compiler/compiler.jar
Normal file
Binary file not shown.
@ -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 */
|
||||
|
25
docs/api.md
25
docs/api.md
@ -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);
|
||||
```
|
||||
|
@ -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
|
747
src/component.js
747
src/component.js
@ -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
1820
src/controls.js
vendored
File diff suppressed because it is too large
Load Diff
71
src/core.js
71
src/core.js
@ -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"
|
||||
|
460
src/events.js
460
src/events.js
@ -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
72
src/exports.js
Normal 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
1569
src/goog.base.js
Normal file
File diff suppressed because it is too large
Load Diff
598
src/lib.js
598
src/lib.js
@ -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
243
src/media.html5.js
Normal 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
52
src/media.js
Normal 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");
|
||||
};
|
||||
};
|
781
src/player.js
781
src/player.js
File diff suppressed because it is too large
Load Diff
29
src/tech.js
29
src/tech.js
@ -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");
|
||||
};
|
||||
});
|
332
src/tracks.js
332
src/tracks.js
@ -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);
|
||||
// }
|
||||
// });
|
@ -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({}, {});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user