videojs_rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "rails", "3.1.0.rc4"
4
+ gem "capybara", "~> 1.0.0"
5
+ gem "sqlite3"
6
+
7
+ # Asset template engines
8
+ gem 'sprockets', '2.0.0.beta.10'
9
+ gem 'sass'
10
+ gem 'coffee-script'
11
+ gem 'uglifier'
12
+ gem 'nokogiri'
13
+
14
+ gem 'jquery-rails'
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,22 @@
1
+ <!-- Begin VideoJS -->
2
+ <div class="video-js-box">
3
+ <!-- Using the Video for Everybody Embed Code http://camendesign.com/code/video_for_everybody -->
4
+ <video id="<%=@options[:id]%>" class="<%= @options[:classes] %> video-js" width="<%=@options[:width]%>" height="<%=@options[:height]%>" controls="controls" preload="auto">
5
+
6
+ <source src="<%=@options[:source]%>" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"' />
7
+
8
+ <!-- Flash Fallback. Use any flash video player here. Make sure to keep the vjs-flash-fallback class. -->
9
+ <object id="flash_fallback_1" class="vjs-flash-fallback" width="<%=@options[:width]%>" height="<%=@options[:height]%>" type="application/x-shockwave-flash" data="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf">
10
+ <param name="movie" value="http://releases.flowplayer.org/swf/flowplayer-3.2.1.swf" />
11
+ <param name="allowfullscreen" value="true" />
12
+ <param name="flashvars" value='config={"playlist":["<%=@options[:poster]%>", {"url": "<%=@options[:source]%>","autoPlay":false,"autoBuffering":true}]}' />
13
+
14
+ <!-- Image Fallback. Typically the same as the poster image. -->
15
+ <img src="<%=@options[:poster]%>" width="<%=@options[:width]%>" height="<%=@options[:height]%>" alt="" title="" />
16
+ </object>
17
+ </video>
18
+
19
+ <!-- Download links provided for devices that can't play video in the browser. -->
20
+ <p class="vjs-no-video"><a href="<%=@options[:source]%>">download</a> </p>
21
+ </div>
22
+ <!-- End VideoJS -->
@@ -0,0 +1,2 @@
1
+ require 'videojs_rails/railtie'
2
+ require 'videojs_rails/engine' if defined?(Rails && Rails::VERSION::MAJOR == 3 && Rails::VERSION::MINOR >=1)
@@ -0,0 +1,6 @@
1
+ module VideojsRails
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ require 'videojs_rails/view_helpers'
2
+
3
+ module VideojsRails
4
+ class Railtie < Rails::Railtie
5
+ initializer "videojs_rails.view_helpers" do
6
+ ActionView::Base.send(:include, ViewHelpers)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module VideojsRails
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,9 @@
1
+ module VideojsRails
2
+ module ViewHelpers
3
+ def videojs_rails(*options)
4
+ @options = options.extract_options!
5
+ render "videojs_rails/videojs_rails"
6
+ end
7
+ end
8
+ end
9
+
data/readme.md ADDED
@@ -0,0 +1,53 @@
1
+ # VideoJS for Rails 3.1 Asset Pipeline
2
+
3
+ ## Installation
4
+
5
+ Add to your Gemfile
6
+
7
+ ```ruby
8
+
9
+ gem 'videojs_rails'
10
+
11
+ ```
12
+
13
+ And run bundle to install the library.
14
+
15
+ ```ruby
16
+
17
+ bundle
18
+
19
+ ```
20
+
21
+ Add the resources to your application.js file
22
+
23
+ ```coffeescript
24
+
25
+ # app/assets/javascripts/application.js
26
+ //= require videojs
27
+
28
+ ```
29
+
30
+ You can optionally include skins by requiring in app/assets/stylesheets/application.css
31
+
32
+ ```sass
33
+
34
+ /*
35
+ *= require_self
36
+ *= require videojs
37
+ *= require skins/hu.css
38
+ */
39
+
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```erb
45
+
46
+ <%= videojs_rails source: "http://domain.com/path/to/video.mp4", width:"400px" %>
47
+
48
+ ```
49
+
50
+ ## Resources
51
+ http://videojs.com/
52
+ http://videojs.com/#getting-started
53
+
@@ -0,0 +1,1758 @@
1
+ /*
2
+ VideoJS - HTML5 Video Player
3
+ v2.0.2
4
+
5
+ This file is part of VideoJS. Copyright 2010 Zencoder, Inc.
6
+
7
+ VideoJS is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU Lesser General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ VideoJS is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU Lesser General Public License for more details.
16
+
17
+ You should have received a copy of the GNU Lesser General Public License
18
+ along with VideoJS. If not, see <http://www.gnu.org/licenses/>.
19
+ */
20
+
21
+ // Self-executing function to prevent global vars and help with minification
22
+ (function(window, undefined){
23
+ var document = window.document;
24
+
25
+ // Using jresig's Class implementation http://ejohn.org/blog/simple-javascript-inheritance/
26
+ (function(){var initializing=false, fnTest=/xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; this.JRClass = function(){}; JRClass.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 JRClass() { if ( !initializing && this.init ) this.init.apply(this, arguments); } JRClass.prototype = prototype; JRClass.constructor = JRClass; JRClass.extend = arguments.callee; return JRClass;};})();
27
+
28
+ // Video JS Player Class
29
+ var VideoJS = JRClass.extend({
30
+
31
+ // Initialize the player for the supplied video tag element
32
+ // element: video tag
33
+ init: function(element, setOptions){
34
+
35
+ // Allow an ID string or an element
36
+ if (typeof element == 'string') {
37
+ this.video = document.getElementById(element);
38
+ } else {
39
+ this.video = element;
40
+ }
41
+ // Store reference to player on the video element.
42
+ // So you can acess the player later: document.getElementById("video_id").player.play();
43
+ this.video.player = this;
44
+ this.values = {}; // Cache video values.
45
+ this.elements = {}; // Store refs to controls elements.
46
+
47
+ // Default Options
48
+ this.options = {
49
+ autoplay: false,
50
+ preload: true,
51
+ useBuiltInControls: false, // Use the browser's controls (iPhone)
52
+ controlsBelow: false, // Display control bar below video vs. in front of
53
+ controlsAtStart: false, // Make controls visible when page loads
54
+ controlsHiding: true, // Hide controls when not over the video
55
+ defaultVolume: 0.85, // Will be overridden by localStorage volume if available
56
+ playerFallbackOrder: ["html5", "flash", "links"], // Players and order to use them
57
+ flashPlayer: "htmlObject",
58
+ flashPlayerVersion: false // Required flash version for fallback
59
+ };
60
+ // Override default options with global options
61
+ if (typeof VideoJS.options == "object") { _V_.merge(this.options, VideoJS.options); }
62
+ // Override default & global options with options specific to this player
63
+ if (typeof setOptions == "object") { _V_.merge(this.options, setOptions); }
64
+ // Override preload & autoplay with video attributes
65
+ if (this.getPreloadAttribute() !== undefined) { this.options.preload = this.getPreloadAttribute(); }
66
+ if (this.getAutoplayAttribute() !== undefined) { this.options.autoplay = this.getAutoplayAttribute(); }
67
+
68
+ // Store reference to embed code pieces
69
+ this.box = this.video.parentNode;
70
+ this.linksFallback = this.getLinksFallback();
71
+ this.hideLinksFallback(); // Will be shown again if "links" player is used
72
+
73
+ // Loop through the player names list in options, "html5" etc.
74
+ // For each player name, initialize the player with that name under VideoJS.players
75
+ // If the player successfully initializes, we're done
76
+ // If not, try the next player in the list
77
+ this.each(this.options.playerFallbackOrder, function(playerType){
78
+ if (this[playerType+"Supported"]()) { // Check if player type is supported
79
+ this[playerType+"Init"](); // Initialize player type
80
+ return true; // Stop looping though players
81
+ }
82
+ });
83
+
84
+ // Start Global Listeners - API doesn't exist before now
85
+ this.activateElement(this, "player");
86
+ this.activateElement(this.box, "box");
87
+ },
88
+ /* Behaviors
89
+ ================================================================================ */
90
+ behaviors: {},
91
+ newBehavior: function(name, activate, functions){
92
+ this.behaviors[name] = activate;
93
+ this.extend(functions);
94
+ },
95
+ activateElement: function(element, behavior){
96
+ // Allow passing and ID string
97
+ if (typeof element == "string") { element = document.getElementById(element); }
98
+ this.behaviors[behavior].call(this, element);
99
+ },
100
+ /* Errors/Warnings
101
+ ================================================================================ */
102
+ errors: [], // Array to track errors
103
+ warnings: [],
104
+ warning: function(warning){
105
+ this.warnings.push(warning);
106
+ this.log(warning);
107
+ },
108
+ /* History of errors/events (not quite there yet)
109
+ ================================================================================ */
110
+ history: [],
111
+ log: function(event){
112
+ if (!event) { return; }
113
+ if (typeof event == "string") { event = { type: event }; }
114
+ if (event.type) { this.history.push(event.type); }
115
+ if (this.history.length >= 50) { this.history.shift(); }
116
+ try { console.log(event.type); } catch(e) { try { opera.postError(event.type); } catch(e){} }
117
+ },
118
+ /* Local Storage
119
+ ================================================================================ */
120
+ setLocalStorage: function(key, value){
121
+ if (!localStorage) { return; }
122
+ try {
123
+ localStorage[key] = value;
124
+ } catch(e) {
125
+ if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
126
+ this.warning(VideoJS.warnings.localStorageFull);
127
+ }
128
+ }
129
+ },
130
+ /* Helpers
131
+ ================================================================================ */
132
+ getPreloadAttribute: function(){
133
+ if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload")) {
134
+ var preload = this.video.getAttribute("preload");
135
+ // Only included the attribute, thinking it was boolean
136
+ if (preload === "" || preload === "true") { return "auto"; }
137
+ if (preload === "false") { return "none"; }
138
+ return preload;
139
+ }
140
+ },
141
+ getAutoplayAttribute: function(){
142
+ if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("autoplay")) {
143
+ var autoplay = this.video.getAttribute("autoplay");
144
+ if (autoplay === "false") { return false; }
145
+ return true;
146
+ }
147
+ },
148
+ // Calculates amoutn of buffer is full
149
+ bufferedPercent: function(){ return (this.duration()) ? this.buffered()[1] / this.duration() : 0; },
150
+ // Each that maintains player as context
151
+ // Break if true is returned
152
+ each: function(arr, fn){
153
+ if (!arr || arr.length === 0) { return; }
154
+ for (var i=0,j=arr.length; i<j; i++) {
155
+ if (fn.call(this, arr[i], i)) { break; }
156
+ }
157
+ },
158
+ extend: function(obj){
159
+ for (var attrname in obj) {
160
+ if (obj.hasOwnProperty(attrname)) { this[attrname]=obj[attrname]; }
161
+ }
162
+ }
163
+ });
164
+ VideoJS.player = VideoJS.prototype;
165
+
166
+ ////////////////////////////////////////////////////////////////////////////////
167
+ // Player Types
168
+ ////////////////////////////////////////////////////////////////////////////////
169
+
170
+ /* Flash Object Fallback (Player Type)
171
+ ================================================================================ */
172
+ VideoJS.player.extend({
173
+ flashSupported: function(){
174
+ if (!this.flashElement) { this.flashElement = this.getFlashElement(); }
175
+ // Check if object exists & Flash Player version is supported
176
+ if (this.flashElement && this.flashPlayerVersionSupported()) {
177
+ return true;
178
+ } else {
179
+ return false;
180
+ }
181
+ },
182
+ flashInit: function(){
183
+ this.replaceWithFlash();
184
+ this.element = this.flashElement;
185
+ this.video.src = ""; // Stop video from downloading if HTML5 is still supported
186
+ var flashPlayerType = VideoJS.flashPlayers[this.options.flashPlayer];
187
+ this.extend(VideoJS.flashPlayers[this.options.flashPlayer].api);
188
+ (flashPlayerType.init.context(this))();
189
+ },
190
+ // Get Flash Fallback object element from Embed Code
191
+ getFlashElement: function(){
192
+ var children = this.video.children;
193
+ for (var i=0,j=children.length; i<j; i++) {
194
+ if (children[i].className == "vjs-flash-fallback") {
195
+ return children[i];
196
+ }
197
+ }
198
+ },
199
+ // Used to force a browser to fall back when it's an HTML5 browser but there's no supported sources
200
+ replaceWithFlash: function(){
201
+ // this.flashElement = this.video.removeChild(this.flashElement);
202
+ if (this.flashElement) {
203
+ this.box.insertBefore(this.flashElement, this.video);
204
+ this.video.style.display = "none"; // Removing it was breaking later players
205
+ }
206
+ },
207
+ // Check if browser can use this flash player
208
+ flashPlayerVersionSupported: function(){
209
+ var playerVersion = (this.options.flashPlayerVersion) ? this.options.flashPlayerVersion : VideoJS.flashPlayers[this.options.flashPlayer].flashPlayerVersion;
210
+ return VideoJS.getFlashVersion() >= playerVersion;
211
+ }
212
+ });
213
+ VideoJS.flashPlayers = {};
214
+ VideoJS.flashPlayers.htmlObject = {
215
+ flashPlayerVersion: 9,
216
+ init: function() { return true; },
217
+ api: { // No video API available with HTML Object embed method
218
+ width: function(width){
219
+ if (width !== undefined) {
220
+ this.element.width = width;
221
+ this.box.style.width = width+"px";
222
+ this.triggerResizeListeners();
223
+ return this;
224
+ }
225
+ return this.element.width;
226
+ },
227
+ height: function(height){
228
+ if (height !== undefined) {
229
+ this.element.height = height;
230
+ this.box.style.height = height+"px";
231
+ this.triggerResizeListeners();
232
+ return this;
233
+ }
234
+ return this.element.height;
235
+ }
236
+ }
237
+ };
238
+
239
+
240
+ /* Download Links Fallback (Player Type)
241
+ ================================================================================ */
242
+ VideoJS.player.extend({
243
+ linksSupported: function(){ return true; },
244
+ linksInit: function(){
245
+ this.showLinksFallback();
246
+ this.element = this.video;
247
+ },
248
+ // Get the download links block element
249
+ getLinksFallback: function(){ return this.box.getElementsByTagName("P")[0]; },
250
+ // Hide no-video download paragraph
251
+ hideLinksFallback: function(){
252
+ if (this.linksFallback) { this.linksFallback.style.display = "none"; }
253
+ },
254
+ // Hide no-video download paragraph
255
+ showLinksFallback: function(){
256
+ if (this.linksFallback) { this.linksFallback.style.display = "block"; }
257
+ }
258
+ });
259
+
260
+ ////////////////////////////////////////////////////////////////////////////////
261
+ // Class Methods
262
+ // Functions that don't apply to individual videos.
263
+ ////////////////////////////////////////////////////////////////////////////////
264
+
265
+ // Combine Objects - Use "safe" to protect from overwriting existing items
266
+ VideoJS.merge = function(obj1, obj2, safe){
267
+ for (var attrname in obj2){
268
+ if (obj2.hasOwnProperty(attrname) && (!safe || !obj1.hasOwnProperty(attrname))) { obj1[attrname]=obj2[attrname]; }
269
+ }
270
+ return obj1;
271
+ };
272
+ VideoJS.extend = function(obj){ this.merge(this, obj, true); };
273
+
274
+ VideoJS.extend({
275
+ // Add VideoJS to all video tags with the video-js class when the DOM is ready
276
+ setupAllWhenReady: function(options){
277
+ // Options is stored globally, and added ot any new player on init
278
+ VideoJS.options = options;
279
+ VideoJS.DOMReady(VideoJS.setup);
280
+ },
281
+
282
+ // Run the supplied function when the DOM is ready
283
+ DOMReady: function(fn){
284
+ VideoJS.addToDOMReady(fn);
285
+ },
286
+
287
+ // Set up a specific video or array of video elements
288
+ // "video" can be:
289
+ // false, undefined, or "All": set up all videos with the video-js class
290
+ // A video tag ID or video tag element: set up one video and return one player
291
+ // An array of video tag elements/IDs: set up each and return an array of players
292
+ setup: function(videos, options){
293
+ var returnSingular = false,
294
+ playerList = [],
295
+ videoElement;
296
+
297
+ // If videos is undefined or "All", set up all videos with the video-js class
298
+ if (!videos || videos == "All") {
299
+ videos = VideoJS.getVideoJSTags();
300
+ // If videos is not an array, add to an array
301
+ } else if (typeof videos != 'object' || videos.nodeType == 1) {
302
+ videos = [videos];
303
+ returnSingular = true;
304
+ }
305
+
306
+ // Loop through videos and create players for them
307
+ for (var i=0; i<videos.length; i++) {
308
+ if (typeof videos[i] == 'string') {
309
+ videoElement = document.getElementById(videos[i]);
310
+ } else { // assume DOM object
311
+ videoElement = videos[i];
312
+ }
313
+ playerList.push(new VideoJS(videoElement, options));
314
+ }
315
+
316
+ // Return one or all depending on what was passed in
317
+ return (returnSingular) ? playerList[0] : playerList;
318
+ },
319
+
320
+ // Find video tags with the video-js class
321
+ getVideoJSTags: function() {
322
+ var videoTags = document.getElementsByTagName("video"),
323
+ videoJSTags = [], videoTag;
324
+
325
+ for (var i=0,j=videoTags.length; i<j; i++) {
326
+ videoTag = videoTags[i];
327
+ if (videoTag.className.indexOf("video-js") != -1) {
328
+ videoJSTags.push(videoTag);
329
+ }
330
+ }
331
+ return videoJSTags;
332
+ },
333
+
334
+ // Check if the browser supports video.
335
+ browserSupportsVideo: function() {
336
+ if (typeof VideoJS.videoSupport != "undefined") { return VideoJS.videoSupport; }
337
+ VideoJS.videoSupport = !!document.createElement('video').canPlayType;
338
+ return VideoJS.videoSupport;
339
+ },
340
+
341
+ getFlashVersion: function(){
342
+ // Cache Version
343
+ if (typeof VideoJS.flashVersion != "undefined") { return VideoJS.flashVersion; }
344
+ var version = 0, desc;
345
+ if (typeof navigator.plugins != "undefined" && typeof navigator.plugins["Shockwave Flash"] == "object") {
346
+ desc = navigator.plugins["Shockwave Flash"].description;
347
+ if (desc && !(typeof navigator.mimeTypes != "undefined" && navigator.mimeTypes["application/x-shockwave-flash"] && !navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin)) {
348
+ version = parseInt(desc.match(/^.*\s+([^\s]+)\.[^\s]+\s+[^\s]+$/)[1], 10);
349
+ }
350
+ } else if (typeof window.ActiveXObject != "undefined") {
351
+ try {
352
+ var testObject = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
353
+ if (testObject) {
354
+ version = parseInt(testObject.GetVariable("$version").match(/^[^\s]+\s(\d+)/)[1], 10);
355
+ }
356
+ }
357
+ catch(e) {}
358
+ }
359
+ VideoJS.flashVersion = version;
360
+ return VideoJS.flashVersion;
361
+ },
362
+
363
+ // Browser & Device Checks
364
+ isIE: function(){ return !+"\v1"; },
365
+ isIPad: function(){ return navigator.userAgent.match(/iPad/i) !== null; },
366
+ isIPhone: function(){ return navigator.userAgent.match(/iPhone/i) !== null; },
367
+ isIOS: function(){ return VideoJS.isIPhone() || VideoJS.isIPad(); },
368
+ iOSVersion: function() {
369
+ var match = navigator.userAgent.match(/OS (\d+)_/i);
370
+ if (match && match[1]) { return match[1]; }
371
+ },
372
+ isAndroid: function(){ return navigator.userAgent.match(/Android/i) !== null; },
373
+ androidVersion: function() {
374
+ var match = navigator.userAgent.match(/Android (\d+)\./i);
375
+ if (match && match[1]) { return match[1]; }
376
+ },
377
+
378
+ warnings: {
379
+ // Safari errors if you call functions on a video that hasn't loaded yet
380
+ videoNotReady: "Video is not ready yet (try playing the video first).",
381
+ // Getting a QUOTA_EXCEEDED_ERR when setting local storage occasionally
382
+ localStorageFull: "Local Storage is Full"
383
+ }
384
+ });
385
+
386
+ // Shim to make Video tag valid in IE
387
+ if(VideoJS.isIE()) { document.createElement("video"); }
388
+
389
+ // Expose to global
390
+ window.VideoJS = window._V_ = VideoJS;
391
+
392
+ /* HTML5 Player Type
393
+ ================================================================================ */
394
+ VideoJS.player.extend({
395
+ html5Supported: function(){
396
+ if (VideoJS.browserSupportsVideo() && this.canPlaySource()) {
397
+ return true;
398
+ } else {
399
+ return false;
400
+ }
401
+ },
402
+ html5Init: function(){
403
+ this.element = this.video;
404
+
405
+ this.fixPreloading(); // Support old browsers that used autobuffer
406
+ this.supportProgressEvents(); // Support browsers that don't use 'buffered'
407
+
408
+ // Set to stored volume OR 85%
409
+ this.volume((localStorage && localStorage.volume) || this.options.defaultVolume);
410
+
411
+ // Update interface for device needs
412
+ if (VideoJS.isIOS()) {
413
+ this.options.useBuiltInControls = true;
414
+ this.iOSInterface();
415
+ } else if (VideoJS.isAndroid()) {
416
+ this.options.useBuiltInControls = true;
417
+ this.androidInterface();
418
+ }
419
+
420
+ // Add VideoJS Controls
421
+ if (!this.options.useBuiltInControls) {
422
+ this.video.controls = false;
423
+
424
+ if (this.options.controlsBelow) { _V_.addClass(this.box, "vjs-controls-below"); }
425
+
426
+ // Make a click on th video act as a play button
427
+ this.activateElement(this.video, "playToggle");
428
+
429
+ // Build Interface
430
+ this.buildStylesCheckDiv(); // Used to check if style are loaded
431
+ this.buildAndActivatePoster();
432
+ this.buildBigPlayButton();
433
+ this.buildAndActivateSpinner();
434
+ this.buildAndActivateControlBar();
435
+ this.loadInterface(); // Show everything once styles are loaded
436
+ this.getSubtitles();
437
+ }
438
+ },
439
+ /* Source Managemet
440
+ ================================================================================ */
441
+ canPlaySource: function(){
442
+ // Cache Result
443
+ if (this.canPlaySourceResult) { return this.canPlaySourceResult; }
444
+ // Loop through sources and check if any can play
445
+ var children = this.video.children;
446
+ for (var i=0,j=children.length; i<j; i++) {
447
+ if (children[i].tagName.toUpperCase() == "SOURCE") {
448
+ var canPlay = this.video.canPlayType(children[i].type) || this.canPlayExt(children[i].src);
449
+ if (canPlay == "probably" || canPlay == "maybe") {
450
+ this.firstPlayableSource = children[i];
451
+ this.canPlaySourceResult = true;
452
+ return true;
453
+ }
454
+ }
455
+ }
456
+ this.canPlaySourceResult = false;
457
+ return false;
458
+ },
459
+ // Check if the extention is compatible, for when type won't work
460
+ canPlayExt: function(src){
461
+ if (!src) { return ""; }
462
+ var match = src.match(/\.([^\.]+)$/);
463
+ if (match && match[1]) {
464
+ var ext = match[1].toLowerCase();
465
+ // Android canPlayType doesn't work
466
+ if (VideoJS.isAndroid()) {
467
+ if (ext == "mp4" || ext == "m4v") { return "maybe"; }
468
+ // Allow Apple HTTP Streaming for iOS
469
+ } else if (VideoJS.isIOS()) {
470
+ if (ext == "m3u8") { return "maybe"; }
471
+ }
472
+ }
473
+ return "";
474
+ },
475
+ // Force the video source - Helps fix loading bugs in a handful of devices, like the iPad/iPhone poster bug
476
+ // And iPad/iPhone javascript include location bug. And Android type attribute bug
477
+ forceTheSource: function(){
478
+ this.video.src = this.firstPlayableSource.src; // From canPlaySource()
479
+ this.video.load();
480
+ },
481
+ /* Device Fixes
482
+ ================================================================================ */
483
+ // Support older browsers that used "autobuffer"
484
+ fixPreloading: function(){
485
+ if (typeof this.video.hasAttribute == "function" && this.video.hasAttribute("preload") && this.video.preload != "none") {
486
+ this.video.autobuffer = true; // Was a boolean
487
+ } else {
488
+ this.video.autobuffer = false;
489
+ this.video.preload = "none";
490
+ }
491
+ },
492
+
493
+ // Listen for Video Load Progress (currently does not if html file is local)
494
+ // Buffered does't work in all browsers, so watching progress as well
495
+ supportProgressEvents: function(e){
496
+ _V_.addListener(this.video, 'progress', this.playerOnVideoProgress.context(this));
497
+ },
498
+ playerOnVideoProgress: function(event){
499
+ this.setBufferedFromProgress(event);
500
+ },
501
+ setBufferedFromProgress: function(event){ // HTML5 Only
502
+ if(event.total > 0) {
503
+ var newBufferEnd = (event.loaded / event.total) * this.duration();
504
+ if (newBufferEnd > this.values.bufferEnd) { this.values.bufferEnd = newBufferEnd; }
505
+ }
506
+ },
507
+
508
+ iOSInterface: function(){
509
+ if(VideoJS.iOSVersion() < 4) { this.forceTheSource(); } // Fix loading issues
510
+ if(VideoJS.isIPad()) { // iPad could work with controlsBelow
511
+ this.buildAndActivateSpinner(); // Spinner still works well on iPad, since iPad doesn't have one
512
+ }
513
+ },
514
+
515
+ // Fix android specific quirks
516
+ // Use built-in controls, but add the big play button, since android doesn't have one.
517
+ androidInterface: function(){
518
+ this.forceTheSource(); // Fix loading issues
519
+ _V_.addListener(this.video, "click", function(){ this.play(); }); // Required to play
520
+ this.buildBigPlayButton(); // But don't activate the normal way. Pause doesn't work right on android.
521
+ _V_.addListener(this.bigPlayButton, "click", function(){ this.play(); }.context(this));
522
+ this.positionBox();
523
+ this.showBigPlayButtons();
524
+ },
525
+ /* Wait for styles (TODO: move to _V_)
526
+ ================================================================================ */
527
+ loadInterface: function(){
528
+ if(!this.stylesHaveLoaded()) {
529
+ // Don't want to create an endless loop either.
530
+ if (!this.positionRetries) { this.positionRetries = 1; }
531
+ if (this.positionRetries++ < 100) {
532
+ setTimeout(this.loadInterface.context(this),10);
533
+ return;
534
+ }
535
+ }
536
+ this.hideStylesCheckDiv();
537
+ this.showPoster();
538
+ if (this.video.paused !== false) { this.showBigPlayButtons(); }
539
+ if (this.options.controlsAtStart) { this.showControlBars(); }
540
+ this.positionAll();
541
+ },
542
+ /* Control Bar
543
+ ================================================================================ */
544
+ buildAndActivateControlBar: function(){
545
+ /* Creating this HTML
546
+ <div class="vjs-controls">
547
+ <div class="vjs-play-control">
548
+ <span></span>
549
+ </div>
550
+ <div class="vjs-progress-control">
551
+ <div class="vjs-progress-holder">
552
+ <div class="vjs-load-progress"></div>
553
+ <div class="vjs-play-progress"></div>
554
+ </div>
555
+ </div>
556
+ <div class="vjs-time-control">
557
+ <span class="vjs-current-time-display">00:00</span><span> / </span><span class="vjs-duration-display">00:00</span>
558
+ </div>
559
+ <div class="vjs-volume-control">
560
+ <div>
561
+ <span></span><span></span><span></span><span></span><span></span><span></span>
562
+ </div>
563
+ </div>
564
+ <div class="vjs-fullscreen-control">
565
+ <div>
566
+ <span></span><span></span><span></span><span></span>
567
+ </div>
568
+ </div>
569
+ </div>
570
+ */
571
+
572
+ // Create a div to hold the different controls
573
+ this.controls = _V_.createElement("div", { className: "vjs-controls" });
574
+ // Add the controls to the video's container
575
+ this.box.appendChild(this.controls);
576
+ this.activateElement(this.controls, "controlBar");
577
+ this.activateElement(this.controls, "mouseOverVideoReporter");
578
+
579
+ // Build the play control
580
+ this.playControl = _V_.createElement("div", { className: "vjs-play-control", innerHTML: "<span></span>" });
581
+ this.controls.appendChild(this.playControl);
582
+ this.activateElement(this.playControl, "playToggle");
583
+
584
+ // Build the progress control
585
+ this.progressControl = _V_.createElement("div", { className: "vjs-progress-control" });
586
+ this.controls.appendChild(this.progressControl);
587
+
588
+ // Create a holder for the progress bars
589
+ this.progressHolder = _V_.createElement("div", { className: "vjs-progress-holder" });
590
+ this.progressControl.appendChild(this.progressHolder);
591
+ this.activateElement(this.progressHolder, "currentTimeScrubber");
592
+
593
+ // Create the loading progress display
594
+ this.loadProgressBar = _V_.createElement("div", { className: "vjs-load-progress" });
595
+ this.progressHolder.appendChild(this.loadProgressBar);
596
+ this.activateElement(this.loadProgressBar, "loadProgressBar");
597
+
598
+ // Create the playing progress display
599
+ this.playProgressBar = _V_.createElement("div", { className: "vjs-play-progress" });
600
+ this.progressHolder.appendChild(this.playProgressBar);
601
+ this.activateElement(this.playProgressBar, "playProgressBar");
602
+
603
+ // Create the progress time display (00:00 / 00:00)
604
+ this.timeControl = _V_.createElement("div", { className: "vjs-time-control" });
605
+ this.controls.appendChild(this.timeControl);
606
+
607
+ // Create the current play time display
608
+ this.currentTimeDisplay = _V_.createElement("span", { className: "vjs-current-time-display", innerHTML: "00:00" });
609
+ this.timeControl.appendChild(this.currentTimeDisplay);
610
+ this.activateElement(this.currentTimeDisplay, "currentTimeDisplay");
611
+
612
+ // Add time separator
613
+ this.timeSeparator = _V_.createElement("span", { innerHTML: " / " });
614
+ this.timeControl.appendChild(this.timeSeparator);
615
+
616
+ // Create the total duration display
617
+ this.durationDisplay = _V_.createElement("span", { className: "vjs-duration-display", innerHTML: "00:00" });
618
+ this.timeControl.appendChild(this.durationDisplay);
619
+ this.activateElement(this.durationDisplay, "durationDisplay");
620
+
621
+ // Create the volumne control
622
+ this.volumeControl = _V_.createElement("div", {
623
+ className: "vjs-volume-control",
624
+ innerHTML: "<div><span></span><span></span><span></span><span></span><span></span><span></span></div>"
625
+ });
626
+ this.controls.appendChild(this.volumeControl);
627
+ this.activateElement(this.volumeControl, "volumeScrubber");
628
+
629
+ this.volumeDisplay = this.volumeControl.children[0];
630
+ this.activateElement(this.volumeDisplay, "volumeDisplay");
631
+
632
+ // Crete the fullscreen control
633
+ this.fullscreenControl = _V_.createElement("div", {
634
+ className: "vjs-fullscreen-control",
635
+ innerHTML: "<div><span></span><span></span><span></span><span></span></div>"
636
+ });
637
+ this.controls.appendChild(this.fullscreenControl);
638
+ this.activateElement(this.fullscreenControl, "fullscreenToggle");
639
+ },
640
+ /* Poster Image
641
+ ================================================================================ */
642
+ buildAndActivatePoster: function(){
643
+ this.updatePosterSource();
644
+ if (this.video.poster) {
645
+ this.poster = document.createElement("img");
646
+ // Add poster to video box
647
+ this.box.appendChild(this.poster);
648
+
649
+ // Add poster image data
650
+ this.poster.src = this.video.poster;
651
+ // Add poster styles
652
+ this.poster.className = "vjs-poster";
653
+ this.activateElement(this.poster, "poster");
654
+ } else {
655
+ this.poster = false;
656
+ }
657
+ },
658
+ /* Big Play Button
659
+ ================================================================================ */
660
+ buildBigPlayButton: function(){
661
+ /* Creating this HTML
662
+ <div class="vjs-big-play-button"><span></span></div>
663
+ */
664
+ this.bigPlayButton = _V_.createElement("div", {
665
+ className: "vjs-big-play-button",
666
+ innerHTML: "<span></span>"
667
+ });
668
+ this.box.appendChild(this.bigPlayButton);
669
+ this.activateElement(this.bigPlayButton, "bigPlayButton");
670
+ },
671
+ /* Spinner (Loading)
672
+ ================================================================================ */
673
+ buildAndActivateSpinner: function(){
674
+ this.spinner = _V_.createElement("div", {
675
+ className: "vjs-spinner",
676
+ innerHTML: "<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>"
677
+ });
678
+ this.box.appendChild(this.spinner);
679
+ this.activateElement(this.spinner, "spinner");
680
+ },
681
+ /* Styles Check - Check if styles are loaded (move ot _V_)
682
+ ================================================================================ */
683
+ // Sometimes the CSS styles haven't been applied to the controls yet
684
+ // when we're trying to calculate the height and position them correctly.
685
+ // This causes a flicker where the controls are out of place.
686
+ buildStylesCheckDiv: function(){
687
+ this.stylesCheckDiv = _V_.createElement("div", { className: "vjs-styles-check" });
688
+ this.stylesCheckDiv.style.position = "absolute";
689
+ this.box.appendChild(this.stylesCheckDiv);
690
+ },
691
+ hideStylesCheckDiv: function(){ this.stylesCheckDiv.style.display = "none"; },
692
+ stylesHaveLoaded: function(){
693
+ if (this.stylesCheckDiv.offsetHeight != 5) {
694
+ return false;
695
+ } else {
696
+ return true;
697
+ }
698
+ },
699
+ /* VideoJS Box - Holds all elements
700
+ ================================================================================ */
701
+ positionAll: function(){
702
+ this.positionBox();
703
+ this.positionControlBars();
704
+ this.positionPoster();
705
+ },
706
+ positionBox: function(){
707
+ // Set width based on fullscreen or not.
708
+ if (this.videoIsFullScreen) {
709
+ this.box.style.width = "";
710
+ this.element.style.height="";
711
+ if (this.options.controlsBelow) {
712
+ this.box.style.height = "";
713
+ this.element.style.height = (this.box.offsetHeight - this.controls.offsetHeight) + "px";
714
+ }
715
+ } else {
716
+ this.box.style.width = this.width() + "px";
717
+ this.element.style.height=this.height()+"px";
718
+ if (this.options.controlsBelow) {
719
+ this.element.style.height = "";
720
+ // this.box.style.height = this.video.offsetHeight + this.controls.offsetHeight + "px";
721
+ }
722
+ }
723
+ },
724
+ /* Subtitles
725
+ ================================================================================ */
726
+ getSubtitles: function(){
727
+ var tracks = this.video.getElementsByTagName("TRACK");
728
+ for (var i=0,j=tracks.length; i<j; i++) {
729
+ if (tracks[i].getAttribute("kind") == "subtitles" && tracks[i].getAttribute("src")) {
730
+ this.subtitlesSource = tracks[i].getAttribute("src");
731
+ this.loadSubtitles();
732
+ this.buildSubtitles();
733
+ }
734
+ }
735
+ },
736
+ loadSubtitles: function() { _V_.get(this.subtitlesSource, this.parseSubtitles.context(this)); },
737
+ parseSubtitles: function(subText) {
738
+ var lines = subText.split("\n"),
739
+ line = "",
740
+ subtitle, time, text;
741
+ this.subtitles = [];
742
+ this.currentSubtitle = false;
743
+ this.lastSubtitleIndex = 0;
744
+
745
+ for (var i=0; i<lines.length; i++) {
746
+ line = _V_.trim(lines[i]); // Trim whitespace and linebreaks
747
+ if (line) { // Loop until a line with content
748
+
749
+ // First line - Number
750
+ subtitle = {
751
+ id: line, // Subtitle Number
752
+ index: this.subtitles.length // Position in Array
753
+ };
754
+
755
+ // Second line - Time
756
+ line = _V_.trim(lines[++i]);
757
+ time = line.split(" --> ");
758
+ subtitle.start = this.parseSubtitleTime(time[0]);
759
+ subtitle.end = this.parseSubtitleTime(time[1]);
760
+
761
+ // Additional lines - Subtitle Text
762
+ text = [];
763
+ for (var j=i; j<lines.length; j++) { // Loop until a blank line or end of lines
764
+ line = _V_.trim(lines[++i]);
765
+ if (!line) { break; }
766
+ text.push(line);
767
+ }
768
+ subtitle.text = text.join('<br/>');
769
+
770
+ // Add this subtitle
771
+ this.subtitles.push(subtitle);
772
+ }
773
+ }
774
+ },
775
+
776
+ parseSubtitleTime: function(timeText) {
777
+ var parts = timeText.split(':'),
778
+ time = 0;
779
+ // hours => seconds
780
+ time += parseFloat(parts[0])*60*60;
781
+ // minutes => seconds
782
+ time += parseFloat(parts[1])*60;
783
+ // get seconds
784
+ var seconds = parts[2].split(/\.|,/); // Either . or ,
785
+ time += parseFloat(seconds[0]);
786
+ // add miliseconds
787
+ ms = parseFloat(seconds[1]);
788
+ if (ms) { time += ms/1000; }
789
+ return time;
790
+ },
791
+
792
+ buildSubtitles: function(){
793
+ /* Creating this HTML
794
+ <div class="vjs-subtitles"></div>
795
+ */
796
+ this.subtitlesDisplay = _V_.createElement("div", { className: 'vjs-subtitles' });
797
+ this.box.appendChild(this.subtitlesDisplay);
798
+ this.activateElement(this.subtitlesDisplay, "subtitlesDisplay");
799
+ },
800
+
801
+ /* Player API - Translate functionality from player to video
802
+ ================================================================================ */
803
+ addVideoListener: function(type, fn){ _V_.addListener(this.video, type, fn.rEvtContext(this)); },
804
+
805
+ play: function(){
806
+ this.video.play();
807
+ return this;
808
+ },
809
+ onPlay: function(fn){ this.addVideoListener("play", fn); return this; },
810
+
811
+ pause: function(){
812
+ this.video.pause();
813
+ return this;
814
+ },
815
+ onPause: function(fn){ this.addVideoListener("pause", fn); return this; },
816
+ paused: function() { return this.video.paused; },
817
+
818
+ currentTime: function(seconds){
819
+ if (seconds !== undefined) {
820
+ try { this.video.currentTime = seconds; }
821
+ catch(e) { this.warning(VideoJS.warnings.videoNotReady); }
822
+ this.values.currentTime = seconds;
823
+ return this;
824
+ }
825
+ return this.video.currentTime;
826
+ },
827
+ onCurrentTimeUpdate: function(fn){
828
+ this.currentTimeListeners.push(fn);
829
+ },
830
+
831
+ duration: function(){
832
+ return this.video.duration;
833
+ },
834
+
835
+ buffered: function(){
836
+ // Storing values allows them be overridden by setBufferedFromProgress
837
+ if (this.values.bufferStart === undefined) {
838
+ this.values.bufferStart = 0;
839
+ this.values.bufferEnd = 0;
840
+ }
841
+ if (this.video.buffered && this.video.buffered.length > 0) {
842
+ var newEnd = this.video.buffered.end(0);
843
+ if (newEnd > this.values.bufferEnd) { this.values.bufferEnd = newEnd; }
844
+ }
845
+ return [this.values.bufferStart, this.values.bufferEnd];
846
+ },
847
+
848
+ volume: function(percentAsDecimal){
849
+ if (percentAsDecimal !== undefined) {
850
+ // Force value to between 0 and 1
851
+ this.values.volume = Math.max(0, Math.min(1, parseFloat(percentAsDecimal)));
852
+ this.video.volume = this.values.volume;
853
+ this.setLocalStorage("volume", this.values.volume);
854
+ return this;
855
+ }
856
+ if (this.values.volume) { return this.values.volume; }
857
+ return this.video.volume;
858
+ },
859
+ onVolumeChange: function(fn){ _V_.addListener(this.video, 'volumechange', fn.rEvtContext(this)); },
860
+
861
+ width: function(width){
862
+ if (width !== undefined) {
863
+ this.video.width = width; // Not using style so it can be overridden on fullscreen.
864
+ this.box.style.width = width+"px";
865
+ this.triggerResizeListeners();
866
+ return this;
867
+ }
868
+ return this.video.offsetWidth;
869
+ },
870
+ height: function(height){
871
+ if (height !== undefined) {
872
+ this.video.height = height;
873
+ this.box.style.height = height+"px";
874
+ this.triggerResizeListeners();
875
+ return this;
876
+ }
877
+ return this.video.offsetHeight;
878
+ },
879
+
880
+ supportsFullScreen: function(){
881
+ if(typeof this.video.webkitEnterFullScreen == 'function') {
882
+ // Seems to be broken in Chromium/Chrome
883
+ if (!navigator.userAgent.match("Chrome") && !navigator.userAgent.match("Mac OS X 10.5")) {
884
+ return true;
885
+ }
886
+ }
887
+ return false;
888
+ },
889
+
890
+ html5EnterNativeFullScreen: function(){
891
+ try {
892
+ this.video.webkitEnterFullScreen();
893
+ } catch (e) {
894
+ if (e.code == 11) { this.warning(VideoJS.warnings.videoNotReady); }
895
+ }
896
+ return this;
897
+ },
898
+
899
+ // Turn on fullscreen (window) mode
900
+ // Real fullscreen isn't available in browsers quite yet.
901
+ enterFullScreen: function(){
902
+ if (this.supportsFullScreen()) {
903
+ this.html5EnterNativeFullScreen();
904
+ } else {
905
+ this.enterFullWindow();
906
+ }
907
+ },
908
+
909
+ exitFullScreen: function(){
910
+ if (this.supportsFullScreen()) {
911
+ // Shouldn't be called
912
+ } else {
913
+ this.exitFullWindow();
914
+ }
915
+ },
916
+
917
+ enterFullWindow: function(){
918
+ this.videoIsFullScreen = true;
919
+ // Storing original doc overflow value to return to when fullscreen is off
920
+ this.docOrigOverflow = document.documentElement.style.overflow;
921
+ // Add listener for esc key to exit fullscreen
922
+ _V_.addListener(document, "keydown", this.fullscreenOnEscKey.rEvtContext(this));
923
+ // Add listener for a window resize
924
+ _V_.addListener(window, "resize", this.fullscreenOnWindowResize.rEvtContext(this));
925
+ // Hide any scroll bars
926
+ document.documentElement.style.overflow = 'hidden';
927
+ // Apply fullscreen styles
928
+ _V_.addClass(this.box, "vjs-fullscreen");
929
+ // Resize the box, controller, and poster
930
+ this.positionAll();
931
+ },
932
+
933
+ // Turn off fullscreen (window) mode
934
+ exitFullWindow: function(){
935
+ this.videoIsFullScreen = false;
936
+ document.removeEventListener("keydown", this.fullscreenOnEscKey, false);
937
+ window.removeEventListener("resize", this.fullscreenOnWindowResize, false);
938
+ // Unhide scroll bars.
939
+ document.documentElement.style.overflow = this.docOrigOverflow;
940
+ // Remove fullscreen styles
941
+ _V_.removeClass(this.box, "vjs-fullscreen");
942
+ // Resize the box, controller, and poster to original sizes
943
+ this.positionAll();
944
+ },
945
+
946
+ onError: function(fn){ this.addVideoListener("error", fn); return this; },
947
+ onEnded: function(fn){
948
+ this.addVideoListener("ended", fn); return this;
949
+ }
950
+ });
951
+
952
+ ////////////////////////////////////////////////////////////////////////////////
953
+ // Element Behaviors
954
+ // Tell elements how to act or react
955
+ ////////////////////////////////////////////////////////////////////////////////
956
+
957
+ /* Player Behaviors - How VideoJS reacts to what the video is doing.
958
+ ================================================================================ */
959
+ VideoJS.player.newBehavior("player", function(player){
960
+ this.onError(this.playerOnVideoError);
961
+ // Listen for when the video is played
962
+ this.onPlay(this.playerOnVideoPlay);
963
+ this.onPlay(this.trackCurrentTime);
964
+ // Listen for when the video is paused
965
+ this.onPause(this.playerOnVideoPause);
966
+ this.onPause(this.stopTrackingCurrentTime);
967
+ // Listen for when the video ends
968
+ this.onEnded(this.playerOnVideoEnded);
969
+ // Set interval for load progress using buffer watching method
970
+ // this.trackCurrentTime();
971
+ this.trackBuffered();
972
+ // Buffer Full
973
+ this.onBufferedUpdate(this.isBufferFull);
974
+ },{
975
+ playerOnVideoError: function(event){
976
+ this.log(event);
977
+ this.log(this.video.error);
978
+ },
979
+ playerOnVideoPlay: function(event){ this.hasPlayed = true; },
980
+ playerOnVideoPause: function(event){},
981
+ playerOnVideoEnded: function(event){
982
+ this.currentTime(0);
983
+ this.pause();
984
+ },
985
+
986
+ /* Load Tracking -------------------------------------------------------------- */
987
+ // Buffer watching method for load progress.
988
+ // Used for browsers that don't support the progress event
989
+ trackBuffered: function(){
990
+ this.bufferedInterval = setInterval(this.triggerBufferedListeners.context(this), 500);
991
+ },
992
+ stopTrackingBuffered: function(){ clearInterval(this.bufferedInterval); },
993
+ bufferedListeners: [],
994
+ onBufferedUpdate: function(fn){
995
+ this.bufferedListeners.push(fn);
996
+ },
997
+ triggerBufferedListeners: function(){
998
+ this.isBufferFull();
999
+ this.each(this.bufferedListeners, function(listener){
1000
+ (listener.context(this))();
1001
+ });
1002
+ },
1003
+ isBufferFull: function(){
1004
+ if (this.bufferedPercent() == 1) { this.stopTrackingBuffered(); }
1005
+ },
1006
+
1007
+ /* Time Tracking -------------------------------------------------------------- */
1008
+ trackCurrentTime: function(){
1009
+ if (this.currentTimeInterval) { clearInterval(this.currentTimeInterval); }
1010
+ this.currentTimeInterval = setInterval(this.triggerCurrentTimeListeners.context(this), 100); // 42 = 24 fps
1011
+ this.trackingCurrentTime = true;
1012
+ },
1013
+ // Turn off play progress tracking (when paused or dragging)
1014
+ stopTrackingCurrentTime: function(){
1015
+ clearInterval(this.currentTimeInterval);
1016
+ this.trackingCurrentTime = false;
1017
+ },
1018
+ currentTimeListeners: [],
1019
+ // onCurrentTimeUpdate is in API section now
1020
+ triggerCurrentTimeListeners: function(late, newTime){ // FF passes milliseconds late as the first argument
1021
+ this.each(this.currentTimeListeners, function(listener){
1022
+ (listener.context(this))(newTime || this.currentTime());
1023
+ });
1024
+ },
1025
+
1026
+ /* Resize Tracking -------------------------------------------------------------- */
1027
+ resizeListeners: [],
1028
+ onResize: function(fn){
1029
+ this.resizeListeners.push(fn);
1030
+ },
1031
+ // Trigger anywhere the video/box size is changed.
1032
+ triggerResizeListeners: function(){
1033
+ this.each(this.resizeListeners, function(listener){
1034
+ (listener.context(this))();
1035
+ });
1036
+ }
1037
+ }
1038
+ );
1039
+ /* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
1040
+ ================================================================================ */
1041
+ VideoJS.player.newBehavior("mouseOverVideoReporter", function(element){
1042
+ // Listen for the mouse move the video. Used to reveal the controller.
1043
+ _V_.addListener(element, "mousemove", this.mouseOverVideoReporterOnMouseMove.context(this));
1044
+ // Listen for the mouse moving out of the video. Used to hide the controller.
1045
+ _V_.addListener(element, "mouseout", this.mouseOverVideoReporterOnMouseOut.context(this));
1046
+ },{
1047
+ mouseOverVideoReporterOnMouseMove: function(){
1048
+ this.showControlBars();
1049
+ clearInterval(this.mouseMoveTimeout);
1050
+ this.mouseMoveTimeout = setTimeout(this.hideControlBars.context(this), 4000);
1051
+ },
1052
+ mouseOverVideoReporterOnMouseOut: function(event){
1053
+ // Prevent flicker by making sure mouse hasn't left the video
1054
+ var parent = event.relatedTarget;
1055
+ while (parent && parent !== this.box) {
1056
+ parent = parent.parentNode;
1057
+ }
1058
+ if (parent !== this.box) {
1059
+ this.hideControlBars();
1060
+ }
1061
+ }
1062
+ }
1063
+ );
1064
+ /* Mouse Over Video Reporter Behaviors - i.e. Controls hiding based on mouse location
1065
+ ================================================================================ */
1066
+ VideoJS.player.newBehavior("box", function(element){
1067
+ this.positionBox();
1068
+ _V_.addClass(element, "vjs-paused");
1069
+ this.activateElement(element, "mouseOverVideoReporter");
1070
+ this.onPlay(this.boxOnVideoPlay);
1071
+ this.onPause(this.boxOnVideoPause);
1072
+ },{
1073
+ boxOnVideoPlay: function(){
1074
+ _V_.removeClass(this.box, "vjs-paused");
1075
+ _V_.addClass(this.box, "vjs-playing");
1076
+ },
1077
+ boxOnVideoPause: function(){
1078
+ _V_.removeClass(this.box, "vjs-playing");
1079
+ _V_.addClass(this.box, "vjs-paused");
1080
+ }
1081
+ }
1082
+ );
1083
+ /* Poster Image Overlay
1084
+ ================================================================================ */
1085
+ VideoJS.player.newBehavior("poster", function(element){
1086
+ this.activateElement(element, "mouseOverVideoReporter");
1087
+ this.activateElement(element, "playButton");
1088
+ this.onPlay(this.hidePoster);
1089
+ this.onEnded(this.showPoster);
1090
+ this.onResize(this.positionPoster);
1091
+ },{
1092
+ showPoster: function(){
1093
+ if (!this.poster) { return; }
1094
+ this.poster.style.display = "block";
1095
+ this.positionPoster();
1096
+ },
1097
+ positionPoster: function(){
1098
+ // Only if the poster is visible
1099
+ if (!this.poster || this.poster.style.display == 'none') { return; }
1100
+ this.poster.style.height = this.height() + "px"; // Need incase controlsBelow
1101
+ this.poster.style.width = this.width() + "px"; // Could probably do 100% of box
1102
+ },
1103
+ hidePoster: function(){
1104
+ if (!this.poster) { return; }
1105
+ this.poster.style.display = "none";
1106
+ },
1107
+ // Update poster source from attribute or fallback image
1108
+ // iPad breaks if you include a poster attribute, so this fixes that
1109
+ updatePosterSource: function(){
1110
+ if (!this.video.poster) {
1111
+ var images = this.video.getElementsByTagName("img");
1112
+ if (images.length > 0) { this.video.poster = images[0].src; }
1113
+ }
1114
+ }
1115
+ }
1116
+ );
1117
+ /* Control Bar Behaviors
1118
+ ================================================================================ */
1119
+ VideoJS.player.newBehavior("controlBar", function(element){
1120
+ if (!this.controlBars) {
1121
+ this.controlBars = [];
1122
+ this.onResize(this.positionControlBars);
1123
+ }
1124
+ this.controlBars.push(element);
1125
+ _V_.addListener(element, "mousemove", this.onControlBarsMouseMove.context(this));
1126
+ _V_.addListener(element, "mouseout", this.onControlBarsMouseOut.context(this));
1127
+ },{
1128
+ showControlBars: function(){
1129
+ if (!this.options.controlsAtStart && !this.hasPlayed) { return; }
1130
+ this.each(this.controlBars, function(bar){
1131
+ bar.style.display = "block";
1132
+ });
1133
+ },
1134
+ // Place controller relative to the video's position (now just resizing bars)
1135
+ positionControlBars: function(){
1136
+ this.updatePlayProgressBars();
1137
+ this.updateLoadProgressBars();
1138
+ },
1139
+ hideControlBars: function(){
1140
+ if (this.options.controlsHiding && !this.mouseIsOverControls) {
1141
+ this.each(this.controlBars, function(bar){
1142
+ bar.style.display = "none";
1143
+ });
1144
+ }
1145
+ },
1146
+ // Block controls from hiding when mouse is over them.
1147
+ onControlBarsMouseMove: function(){ this.mouseIsOverControls = true; },
1148
+ onControlBarsMouseOut: function(event){
1149
+ this.mouseIsOverControls = false;
1150
+ }
1151
+ }
1152
+ );
1153
+ /* PlayToggle, PlayButton, PauseButton Behaviors
1154
+ ================================================================================ */
1155
+ // Play Toggle
1156
+ VideoJS.player.newBehavior("playToggle", function(element){
1157
+ if (!this.elements.playToggles) {
1158
+ this.elements.playToggles = [];
1159
+ this.onPlay(this.playTogglesOnPlay);
1160
+ this.onPause(this.playTogglesOnPause);
1161
+ }
1162
+ this.elements.playToggles.push(element);
1163
+ _V_.addListener(element, "click", this.onPlayToggleClick.context(this));
1164
+ },{
1165
+ onPlayToggleClick: function(event){
1166
+ if (this.paused()) {
1167
+ this.play();
1168
+ } else {
1169
+ this.pause();
1170
+ }
1171
+ },
1172
+ playTogglesOnPlay: function(event){
1173
+ this.each(this.elements.playToggles, function(toggle){
1174
+ _V_.removeClass(toggle, "vjs-paused");
1175
+ _V_.addClass(toggle, "vjs-playing");
1176
+ });
1177
+ },
1178
+ playTogglesOnPause: function(event){
1179
+ this.each(this.elements.playToggles, function(toggle){
1180
+ _V_.removeClass(toggle, "vjs-playing");
1181
+ _V_.addClass(toggle, "vjs-paused");
1182
+ });
1183
+ }
1184
+ }
1185
+ );
1186
+ // Play
1187
+ VideoJS.player.newBehavior("playButton", function(element){
1188
+ _V_.addListener(element, "click", this.onPlayButtonClick.context(this));
1189
+ },{
1190
+ onPlayButtonClick: function(event){ this.play(); }
1191
+ }
1192
+ );
1193
+ // Pause
1194
+ VideoJS.player.newBehavior("pauseButton", function(element){
1195
+ _V_.addListener(element, "click", this.onPauseButtonClick.context(this));
1196
+ },{
1197
+ onPauseButtonClick: function(event){ this.pause(); }
1198
+ }
1199
+ );
1200
+ /* Play Progress Bar Behaviors
1201
+ ================================================================================ */
1202
+ VideoJS.player.newBehavior("playProgressBar", function(element){
1203
+ if (!this.playProgressBars) {
1204
+ this.playProgressBars = [];
1205
+ this.onCurrentTimeUpdate(this.updatePlayProgressBars);
1206
+ }
1207
+ this.playProgressBars.push(element);
1208
+ },{
1209
+ // Ajust the play progress bar's width based on the current play time
1210
+ updatePlayProgressBars: function(newTime){
1211
+ var progress = (newTime !== undefined) ? newTime / this.duration() : this.currentTime() / this.duration();
1212
+ if (isNaN(progress)) { progress = 0; }
1213
+ this.each(this.playProgressBars, function(bar){
1214
+ if (bar.style) { bar.style.width = _V_.round(progress * 100, 2) + "%"; }
1215
+ });
1216
+ }
1217
+ }
1218
+ );
1219
+ /* Load Progress Bar Behaviors
1220
+ ================================================================================ */
1221
+ VideoJS.player.newBehavior("loadProgressBar", function(element){
1222
+ if (!this.loadProgressBars) { this.loadProgressBars = []; }
1223
+ this.loadProgressBars.push(element);
1224
+ this.onBufferedUpdate(this.updateLoadProgressBars);
1225
+ },{
1226
+ updateLoadProgressBars: function(){
1227
+ this.each(this.loadProgressBars, function(bar){
1228
+ if (bar.style) { bar.style.width = _V_.round(this.bufferedPercent() * 100, 2) + "%"; }
1229
+ });
1230
+ }
1231
+ }
1232
+ );
1233
+
1234
+ /* Current Time Display Behaviors
1235
+ ================================================================================ */
1236
+ VideoJS.player.newBehavior("currentTimeDisplay", function(element){
1237
+ if (!this.currentTimeDisplays) {
1238
+ this.currentTimeDisplays = [];
1239
+ this.onCurrentTimeUpdate(this.updateCurrentTimeDisplays);
1240
+ }
1241
+ this.currentTimeDisplays.push(element);
1242
+ },{
1243
+ // Update the displayed time (00:00)
1244
+ updateCurrentTimeDisplays: function(newTime){
1245
+ if (!this.currentTimeDisplays) { return; }
1246
+ // Allows for smooth scrubbing, when player can't keep up.
1247
+ var time = (newTime) ? newTime : this.currentTime();
1248
+ this.each(this.currentTimeDisplays, function(dis){
1249
+ dis.innerHTML = _V_.formatTime(time);
1250
+ });
1251
+ }
1252
+ }
1253
+ );
1254
+
1255
+ /* Duration Display Behaviors
1256
+ ================================================================================ */
1257
+ VideoJS.player.newBehavior("durationDisplay", function(element){
1258
+ if (!this.durationDisplays) {
1259
+ this.durationDisplays = [];
1260
+ this.onCurrentTimeUpdate(this.updateDurationDisplays);
1261
+ }
1262
+ this.durationDisplays.push(element);
1263
+ },{
1264
+ updateDurationDisplays: function(){
1265
+ if (!this.durationDisplays) { return; }
1266
+ this.each(this.durationDisplays, function(dis){
1267
+ if (this.duration()) { dis.innerHTML = _V_.formatTime(this.duration()); }
1268
+ });
1269
+ }
1270
+ }
1271
+ );
1272
+
1273
+ /* Current Time Scrubber Behaviors
1274
+ ================================================================================ */
1275
+ VideoJS.player.newBehavior("currentTimeScrubber", function(element){
1276
+ _V_.addListener(element, "mousedown", this.onCurrentTimeScrubberMouseDown.rEvtContext(this));
1277
+ },{
1278
+ // Adjust the play position when the user drags on the progress bar
1279
+ onCurrentTimeScrubberMouseDown: function(event, scrubber){
1280
+ event.preventDefault();
1281
+ this.currentScrubber = scrubber;
1282
+
1283
+ this.stopTrackingCurrentTime(); // Allows for smooth scrubbing
1284
+
1285
+ this.videoWasPlaying = !this.paused();
1286
+ this.pause();
1287
+
1288
+ _V_.blockTextSelection();
1289
+ this.setCurrentTimeWithScrubber(event);
1290
+ _V_.addListener(document, "mousemove", this.onCurrentTimeScrubberMouseMove.rEvtContext(this));
1291
+ _V_.addListener(document, "mouseup", this.onCurrentTimeScrubberMouseUp.rEvtContext(this));
1292
+ },
1293
+ onCurrentTimeScrubberMouseMove: function(event){ // Removeable
1294
+ this.setCurrentTimeWithScrubber(event);
1295
+ },
1296
+ onCurrentTimeScrubberMouseUp: function(event){ // Removeable
1297
+ _V_.unblockTextSelection();
1298
+ document.removeEventListener("mousemove", this.onCurrentTimeScrubberMouseMove, false);
1299
+ document.removeEventListener("mouseup", this.onCurrentTimeScrubberMouseUp, false);
1300
+ if (this.videoWasPlaying) {
1301
+ this.play();
1302
+ this.trackCurrentTime();
1303
+ }
1304
+ },
1305
+ setCurrentTimeWithScrubber: function(event){
1306
+ var newProgress = _V_.getRelativePosition(event.pageX, this.currentScrubber);
1307
+ var newTime = newProgress * this.duration();
1308
+ this.triggerCurrentTimeListeners(0, newTime); // Allows for smooth scrubbing
1309
+ // Don't let video end while scrubbing.
1310
+ if (newTime == this.duration()) { newTime = newTime - 0.1; }
1311
+ this.currentTime(newTime);
1312
+ }
1313
+ }
1314
+ );
1315
+ /* Volume Display Behaviors
1316
+ ================================================================================ */
1317
+ VideoJS.player.newBehavior("volumeDisplay", function(element){
1318
+ if (!this.volumeDisplays) {
1319
+ this.volumeDisplays = [];
1320
+ this.onVolumeChange(this.updateVolumeDisplays);
1321
+ }
1322
+ this.volumeDisplays.push(element);
1323
+ this.updateVolumeDisplay(element); // Set the display to the initial volume
1324
+ },{
1325
+ // Update the volume control display
1326
+ // Unique to these default controls. Uses borders to create the look of bars.
1327
+ updateVolumeDisplays: function(){
1328
+ if (!this.volumeDisplays) { return; }
1329
+ this.each(this.volumeDisplays, function(dis){
1330
+ this.updateVolumeDisplay(dis);
1331
+ });
1332
+ },
1333
+ updateVolumeDisplay: function(display){
1334
+ var volNum = Math.ceil(this.volume() * 6);
1335
+ this.each(display.children, function(child, num){
1336
+ if (num < volNum) {
1337
+ _V_.addClass(child, "vjs-volume-level-on");
1338
+ } else {
1339
+ _V_.removeClass(child, "vjs-volume-level-on");
1340
+ }
1341
+ });
1342
+ }
1343
+ }
1344
+ );
1345
+ /* Volume Scrubber Behaviors
1346
+ ================================================================================ */
1347
+ VideoJS.player.newBehavior("volumeScrubber", function(element){
1348
+ _V_.addListener(element, "mousedown", this.onVolumeScrubberMouseDown.rEvtContext(this));
1349
+ },{
1350
+ // Adjust the volume when the user drags on the volume control
1351
+ onVolumeScrubberMouseDown: function(event, scrubber){
1352
+ // event.preventDefault();
1353
+ _V_.blockTextSelection();
1354
+ this.currentScrubber = scrubber;
1355
+ this.setVolumeWithScrubber(event);
1356
+ _V_.addListener(document, "mousemove", this.onVolumeScrubberMouseMove.rEvtContext(this));
1357
+ _V_.addListener(document, "mouseup", this.onVolumeScrubberMouseUp.rEvtContext(this));
1358
+ },
1359
+ onVolumeScrubberMouseMove: function(event){
1360
+ this.setVolumeWithScrubber(event);
1361
+ },
1362
+ onVolumeScrubberMouseUp: function(event){
1363
+ this.setVolumeWithScrubber(event);
1364
+ _V_.unblockTextSelection();
1365
+ document.removeEventListener("mousemove", this.onVolumeScrubberMouseMove, false);
1366
+ document.removeEventListener("mouseup", this.onVolumeScrubberMouseUp, false);
1367
+ },
1368
+ setVolumeWithScrubber: function(event){
1369
+ var newVol = _V_.getRelativePosition(event.pageX, this.currentScrubber);
1370
+ this.volume(newVol);
1371
+ }
1372
+ }
1373
+ );
1374
+ /* Fullscreen Toggle Behaviors
1375
+ ================================================================================ */
1376
+ VideoJS.player.newBehavior("fullscreenToggle", function(element){
1377
+ _V_.addListener(element, "click", this.onFullscreenToggleClick.context(this));
1378
+ },{
1379
+ // When the user clicks on the fullscreen button, update fullscreen setting
1380
+ onFullscreenToggleClick: function(event){
1381
+ if (!this.videoIsFullScreen) {
1382
+ this.enterFullScreen();
1383
+ } else {
1384
+ this.exitFullScreen();
1385
+ }
1386
+ },
1387
+
1388
+ fullscreenOnWindowResize: function(event){ // Removeable
1389
+ this.positionControlBars();
1390
+ },
1391
+ // Create listener for esc key while in full screen mode
1392
+ fullscreenOnEscKey: function(event){ // Removeable
1393
+ if (event.keyCode == 27) {
1394
+ this.exitFullScreen();
1395
+ }
1396
+ }
1397
+ }
1398
+ );
1399
+ /* Big Play Button Behaviors
1400
+ ================================================================================ */
1401
+ VideoJS.player.newBehavior("bigPlayButton", function(element){
1402
+ if (!this.elements.bigPlayButtons) {
1403
+ this.elements.bigPlayButtons = [];
1404
+ this.onPlay(this.bigPlayButtonsOnPlay);
1405
+ this.onEnded(this.bigPlayButtonsOnEnded);
1406
+ }
1407
+ this.elements.bigPlayButtons.push(element);
1408
+ this.activateElement(element, "playButton");
1409
+ },{
1410
+ bigPlayButtonsOnPlay: function(event){ this.hideBigPlayButtons(); },
1411
+ bigPlayButtonsOnEnded: function(event){ this.showBigPlayButtons(); },
1412
+ showBigPlayButtons: function(){
1413
+ this.each(this.elements.bigPlayButtons, function(element){
1414
+ element.style.display = "block";
1415
+ });
1416
+ },
1417
+ hideBigPlayButtons: function(){
1418
+ this.each(this.elements.bigPlayButtons, function(element){
1419
+ element.style.display = "none";
1420
+ });
1421
+ }
1422
+ }
1423
+ );
1424
+ /* Spinner
1425
+ ================================================================================ */
1426
+ VideoJS.player.newBehavior("spinner", function(element){
1427
+ if (!this.spinners) {
1428
+ this.spinners = [];
1429
+ _V_.addListener(this.video, "loadeddata", this.spinnersOnVideoLoadedData.context(this));
1430
+ _V_.addListener(this.video, "loadstart", this.spinnersOnVideoLoadStart.context(this));
1431
+ _V_.addListener(this.video, "seeking", this.spinnersOnVideoSeeking.context(this));
1432
+ _V_.addListener(this.video, "seeked", this.spinnersOnVideoSeeked.context(this));
1433
+ _V_.addListener(this.video, "canplay", this.spinnersOnVideoCanPlay.context(this));
1434
+ _V_.addListener(this.video, "canplaythrough", this.spinnersOnVideoCanPlayThrough.context(this));
1435
+ _V_.addListener(this.video, "waiting", this.spinnersOnVideoWaiting.context(this));
1436
+ _V_.addListener(this.video, "stalled", this.spinnersOnVideoStalled.context(this));
1437
+ _V_.addListener(this.video, "suspend", this.spinnersOnVideoSuspend.context(this));
1438
+ _V_.addListener(this.video, "playing", this.spinnersOnVideoPlaying.context(this));
1439
+ _V_.addListener(this.video, "timeupdate", this.spinnersOnVideoTimeUpdate.context(this));
1440
+ }
1441
+ this.spinners.push(element);
1442
+ },{
1443
+ showSpinners: function(){
1444
+ this.each(this.spinners, function(spinner){
1445
+ spinner.style.display = "block";
1446
+ });
1447
+ clearInterval(this.spinnerInterval);
1448
+ this.spinnerInterval = setInterval(this.rotateSpinners.context(this), 100);
1449
+ },
1450
+ hideSpinners: function(){
1451
+ this.each(this.spinners, function(spinner){
1452
+ spinner.style.display = "none";
1453
+ });
1454
+ clearInterval(this.spinnerInterval);
1455
+ },
1456
+ spinnersRotated: 0,
1457
+ rotateSpinners: function(){
1458
+ this.each(this.spinners, function(spinner){
1459
+ // spinner.style.transform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)';
1460
+ spinner.style.WebkitTransform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)';
1461
+ spinner.style.MozTransform = 'scale(0.5) rotate('+this.spinnersRotated+'deg)';
1462
+ });
1463
+ if (this.spinnersRotated == 360) { this.spinnersRotated = 0; }
1464
+ this.spinnersRotated += 45;
1465
+ },
1466
+ spinnersOnVideoLoadedData: function(event){ this.hideSpinners(); },
1467
+ spinnersOnVideoLoadStart: function(event){ this.showSpinners(); },
1468
+ spinnersOnVideoSeeking: function(event){ /* this.showSpinners(); */ },
1469
+ spinnersOnVideoSeeked: function(event){ /* this.hideSpinners(); */ },
1470
+ spinnersOnVideoCanPlay: function(event){ /* this.hideSpinners(); */ },
1471
+ spinnersOnVideoCanPlayThrough: function(event){ this.hideSpinners(); },
1472
+ spinnersOnVideoWaiting: function(event){
1473
+ // Safari sometimes triggers waiting inappropriately
1474
+ // Like after video has played, any you play again.
1475
+ this.showSpinners();
1476
+ },
1477
+ spinnersOnVideoStalled: function(event){},
1478
+ spinnersOnVideoSuspend: function(event){},
1479
+ spinnersOnVideoPlaying: function(event){ this.hideSpinners(); },
1480
+ spinnersOnVideoTimeUpdate: function(event){
1481
+ // Safari sometimes calls waiting and doesn't recover
1482
+ if(this.spinner.style.display == "block") { this.hideSpinners(); }
1483
+ }
1484
+ }
1485
+ );
1486
+ /* Subtitles
1487
+ ================================================================================ */
1488
+ VideoJS.player.newBehavior("subtitlesDisplay", function(element){
1489
+ if (!this.subtitleDisplays) {
1490
+ this.subtitleDisplays = [];
1491
+ this.onCurrentTimeUpdate(this.subtitleDisplaysOnVideoTimeUpdate);
1492
+ this.onEnded(function() { this.lastSubtitleIndex = 0; }.context(this));
1493
+ }
1494
+ this.subtitleDisplays.push(element);
1495
+ },{
1496
+ subtitleDisplaysOnVideoTimeUpdate: function(time){
1497
+ // Assuming all subtitles are in order by time, and do not overlap
1498
+ if (this.subtitles) {
1499
+ // If current subtitle should stay showing, don't do anything. Otherwise, find new subtitle.
1500
+ if (!this.currentSubtitle || this.currentSubtitle.start >= time || this.currentSubtitle.end < time) {
1501
+ var newSubIndex = false,
1502
+ // Loop in reverse if lastSubtitle is after current time (optimization)
1503
+ // Meaning the user is scrubbing in reverse or rewinding
1504
+ reverse = (this.subtitles[this.lastSubtitleIndex].start > time),
1505
+ // If reverse, step back 1 becase we know it's not the lastSubtitle
1506
+ i = this.lastSubtitleIndex - (reverse) ? 1 : 0;
1507
+ while (true) { // Loop until broken
1508
+ if (reverse) { // Looping in reverse
1509
+ // Stop if no more, or this subtitle ends before the current time (no earlier subtitles should apply)
1510
+ if (i < 0 || this.subtitles[i].end < time) { break; }
1511
+ // End is greater than time, so if start is less, show this subtitle
1512
+ if (this.subtitles[i].start < time) {
1513
+ newSubIndex = i;
1514
+ break;
1515
+ }
1516
+ i--;
1517
+ } else { // Looping forward
1518
+ // Stop if no more, or this subtitle starts after time (no later subtitles should apply)
1519
+ if (i >= this.subtitles.length || this.subtitles[i].start > time) { break; }
1520
+ // Start is less than time, so if end is later, show this subtitle
1521
+ if (this.subtitles[i].end > time) {
1522
+ newSubIndex = i;
1523
+ break;
1524
+ }
1525
+ i++;
1526
+ }
1527
+ }
1528
+
1529
+ // Set or clear current subtitle
1530
+ if (newSubIndex !== false) {
1531
+ this.currentSubtitle = this.subtitles[newSubIndex];
1532
+ this.lastSubtitleIndex = newSubIndex;
1533
+ this.updateSubtitleDisplays(this.currentSubtitle.text);
1534
+ } else if (this.currentSubtitle) {
1535
+ this.currentSubtitle = false;
1536
+ this.updateSubtitleDisplays("");
1537
+ }
1538
+ }
1539
+ }
1540
+ },
1541
+ updateSubtitleDisplays: function(val){
1542
+ this.each(this.subtitleDisplays, function(disp){
1543
+ disp.innerHTML = val;
1544
+ });
1545
+ }
1546
+ }
1547
+ );
1548
+
1549
+ ////////////////////////////////////////////////////////////////////////////////
1550
+ // Convenience Functions (mini library)
1551
+ // Functions not specific to video or VideoJS and could probably be replaced with a library like jQuery
1552
+ ////////////////////////////////////////////////////////////////////////////////
1553
+
1554
+ VideoJS.extend({
1555
+
1556
+ addClass: function(element, classToAdd){
1557
+ if ((" "+element.className+" ").indexOf(" "+classToAdd+" ") == -1) {
1558
+ element.className = element.className === "" ? classToAdd : element.className + " " + classToAdd;
1559
+ }
1560
+ },
1561
+ removeClass: function(element, classToRemove){
1562
+ if (element.className.indexOf(classToRemove) == -1) { return; }
1563
+ var classNames = element.className.split(/\s+/);
1564
+ classNames.splice(classNames.lastIndexOf(classToRemove),1);
1565
+ element.className = classNames.join(" ");
1566
+ },
1567
+ createElement: function(tagName, attributes){
1568
+ return this.merge(document.createElement(tagName), attributes);
1569
+ },
1570
+
1571
+ // Attempt to block the ability to select text while dragging controls
1572
+ blockTextSelection: function(){
1573
+ document.body.focus();
1574
+ document.onselectstart = function () { return false; };
1575
+ },
1576
+ // Turn off text selection blocking
1577
+ unblockTextSelection: function(){ document.onselectstart = function () { return true; }; },
1578
+
1579
+ // Return seconds as MM:SS
1580
+ formatTime: function(secs) {
1581
+ var seconds = Math.round(secs);
1582
+ var minutes = Math.floor(seconds / 60);
1583
+ minutes = (minutes >= 10) ? minutes : "0" + minutes;
1584
+ seconds = Math.floor(seconds % 60);
1585
+ seconds = (seconds >= 10) ? seconds : "0" + seconds;
1586
+ return minutes + ":" + seconds;
1587
+ },
1588
+
1589
+ // Return the relative horizonal position of an event as a value from 0-1
1590
+ getRelativePosition: function(x, relativeElement){
1591
+ return Math.max(0, Math.min(1, (x - this.findPosX(relativeElement)) / relativeElement.offsetWidth));
1592
+ },
1593
+ // Get an objects position on the page
1594
+ findPosX: function(obj) {
1595
+ var curleft = obj.offsetLeft;
1596
+ while(obj = obj.offsetParent) {
1597
+ curleft += obj.offsetLeft;
1598
+ }
1599
+ return curleft;
1600
+ },
1601
+ getComputedStyleValue: function(element, style){
1602
+ return window.getComputedStyle(element, null).getPropertyValue(style);
1603
+ },
1604
+
1605
+ round: function(num, dec) {
1606
+ if (!dec) { dec = 0; }
1607
+ return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);
1608
+ },
1609
+
1610
+ addListener: function(element, type, handler){
1611
+ if (element.addEventListener) {
1612
+ element.addEventListener(type, handler, false);
1613
+ } else if (element.attachEvent) {
1614
+ element.attachEvent("on"+type, handler);
1615
+ }
1616
+ },
1617
+ removeListener: function(element, type, handler){
1618
+ if (element.removeEventListener) {
1619
+ element.removeEventListener(type, handler, false);
1620
+ } else if (element.attachEvent) {
1621
+ element.detachEvent("on"+type, handler);
1622
+ }
1623
+ },
1624
+
1625
+ get: function(url, onSuccess){
1626
+ if (typeof XMLHttpRequest == "undefined") {
1627
+ XMLHttpRequest = function () {
1628
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e) {}
1629
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (f) {}
1630
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (g) {}
1631
+ //Microsoft.XMLHTTP points to Msxml2.XMLHTTP.3.0 and is redundant
1632
+ throw new Error("This browser does not support XMLHttpRequest.");
1633
+ };
1634
+ }
1635
+ var request = new XMLHttpRequest();
1636
+ request.open("GET",url);
1637
+ request.onreadystatechange = function() {
1638
+ if (request.readyState == 4 && request.status == 200) {
1639
+ onSuccess(request.responseText);
1640
+ }
1641
+ }.context(this);
1642
+ request.send();
1643
+ },
1644
+
1645
+ trim: function(string){ return string.toString().replace(/^\s+/, "").replace(/\s+$/, ""); },
1646
+
1647
+ // DOM Ready functionality adapted from jQuery. http://jquery.com/
1648
+ bindDOMReady: function(){
1649
+ if (document.readyState === "complete") {
1650
+ return VideoJS.onDOMReady();
1651
+ }
1652
+ if (document.addEventListener) {
1653
+ document.addEventListener("DOMContentLoaded", VideoJS.DOMContentLoaded, false);
1654
+ window.addEventListener("load", VideoJS.onDOMReady, false);
1655
+ } else if (document.attachEvent) {
1656
+ document.attachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
1657
+ window.attachEvent("onload", VideoJS.onDOMReady);
1658
+ }
1659
+ },
1660
+
1661
+ DOMContentLoaded: function(){
1662
+ if (document.addEventListener) {
1663
+ document.removeEventListener( "DOMContentLoaded", VideoJS.DOMContentLoaded, false);
1664
+ VideoJS.onDOMReady();
1665
+ } else if ( document.attachEvent ) {
1666
+ if ( document.readyState === "complete" ) {
1667
+ document.detachEvent("onreadystatechange", VideoJS.DOMContentLoaded);
1668
+ VideoJS.onDOMReady();
1669
+ }
1670
+ }
1671
+ },
1672
+
1673
+ // Functions to be run once the DOM is loaded
1674
+ DOMReadyList: [],
1675
+ addToDOMReady: function(fn){
1676
+ if (VideoJS.DOMIsReady) {
1677
+ fn.call(document);
1678
+ } else {
1679
+ VideoJS.DOMReadyList.push(fn);
1680
+ }
1681
+ },
1682
+
1683
+ DOMIsReady: false,
1684
+ onDOMReady: function(){
1685
+ if (VideoJS.DOMIsReady) { return; }
1686
+ if (!document.body) { return setTimeout(VideoJS.onDOMReady, 13); }
1687
+ VideoJS.DOMIsReady = true;
1688
+ if (VideoJS.DOMReadyList) {
1689
+ for (var i=0; i<VideoJS.DOMReadyList.length; i++) {
1690
+ VideoJS.DOMReadyList[i].call(document);
1691
+ }
1692
+ VideoJS.DOMReadyList = null;
1693
+ }
1694
+ }
1695
+ });
1696
+ VideoJS.bindDOMReady();
1697
+
1698
+ // Allows for binding context to functions
1699
+ // when using in event listeners and timeouts
1700
+ Function.prototype.context = function(obj){
1701
+ var method = this,
1702
+ temp = function(){
1703
+ return method.apply(obj, arguments);
1704
+ };
1705
+ return temp;
1706
+ };
1707
+
1708
+ // Like context, in that it creates a closure
1709
+ // But insteaad keep "this" intact, and passes the var as the second argument of the function
1710
+ // Need for event listeners where you need to know what called the event
1711
+ // Only use with event callbacks
1712
+ Function.prototype.evtContext = function(obj){
1713
+ var method = this,
1714
+ temp = function(){
1715
+ var origContext = this;
1716
+ return method.call(obj, arguments[0], origContext);
1717
+ };
1718
+ return temp;
1719
+ };
1720
+
1721
+ // Removeable Event listener with Context
1722
+ // Replaces the original function with a version that has context
1723
+ // So it can be removed using the original function name.
1724
+ // In order to work, a version of the function must already exist in the player/prototype
1725
+ Function.prototype.rEvtContext = function(obj, funcParent){
1726
+ if (this.hasContext === true) { return this; }
1727
+ if (!funcParent) { funcParent = obj; }
1728
+ for (var attrname in funcParent) {
1729
+ if (funcParent[attrname] == this) {
1730
+ funcParent[attrname] = this.evtContext(obj);
1731
+ funcParent[attrname].hasContext = true;
1732
+ return funcParent[attrname];
1733
+ }
1734
+ }
1735
+ return this.evtContext(obj);
1736
+ };
1737
+
1738
+ // jQuery Plugin
1739
+ if (window.jQuery) {
1740
+ (function($) {
1741
+ $.fn.VideoJS = function(options) {
1742
+ this.each(function() {
1743
+ VideoJS.setup(this, options);
1744
+ });
1745
+ return this;
1746
+ };
1747
+ $.fn.player = function() {
1748
+ return this[0].player;
1749
+ };
1750
+ })(jQuery);
1751
+ }
1752
+
1753
+
1754
+ // Expose to global
1755
+ window.VideoJS = window._V_ = VideoJS;
1756
+
1757
+ // End self-executing function
1758
+ })(window);