videojs_rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);