From aeecc92194d7819a1917f9288c7ba052afcee2a6 Mon Sep 17 00:00:00 2001 From: Steve Heffernan Date: Fri, 9 Mar 2012 17:12:38 -0800 Subject: [PATCH] Refactored tracks to better match HTML5 spec --- design/video-js.css | 28 ++- src/component.js | 38 ++- src/controls.js | 75 ------ src/core.js | 5 +- src/lib.js | 18 +- src/player.js | 20 +- src/tracks.js | 573 ++++++++++++++++++++++++++++++++++++++------ 7 files changed, 570 insertions(+), 187 deletions(-) diff --git a/design/video-js.css b/design/video-js.css index 6f296d670..f1d383ef5 100644 --- a/design/video-js.css +++ b/design/video-js.css @@ -52,9 +52,12 @@ body.vjs-full-window { } /* Text Track Styles */ +.video-js .vjs-text-track-display { + text-align: center; position: absolute; bottom: 4em; left: 1em; right: 1em; +} .video-js .vjs-text-track { - display: none; color: #fff; font-size: 2em; text-align: center; position: absolute; bottom: 2em; left: 1em; right: 1em; - background: rgb(0, 0, 0); background: rgba(0, 0, 0, 0.25); + display: none; color: #fff; font-size: 1.4em; text-align: center; margin-bottom: 0.1em; + background: rgb(0, 0, 0); background: rgba(0, 0, 0, 0.50); } .video-js .vjs-subtitles { color: #fff; } .video-js .vjs-captions { color: #fc6; } @@ -109,8 +112,8 @@ so you can upgrade to newer versions easier. You can remove all these styles by /* Start hidden and with 0 opacity. Opacity is used to fade in modern browsers. */ /* Can't use display block to hide initially because widths of slider handles aren't calculated and avaialbe for positioning correctly. */ - visibility: hidden; - opacity: 0; +/* visibility: hidden; + opacity: 0;*/ } /* General styles for individual controls. */ @@ -447,3 +450,20 @@ div.vjs-loading-spinner .ball8 { opacity: 1.00; position:absolute; left: 6px; to /* Play Icon */ .vjs-default-skin .vjs-captions-control div { background: url('video-js.png') 0px -75px; width: 16px; height: 16px; margin: 0.2em auto 0; padding: 0; } +.vjs-default-skin .vjs-captions-control ul { + display: none; /* Start hidden. Hover will show. */ +} +.vjs-default-skin .vjs-captions-control:hover ul { + display: block; + opacity: 0.8; + list-style: none; padding: 0; margin: 0; + position: absolute; width: 10em; height: 10em; bottom: 2em; + left: -3.5em; /* Width of menu - width of button / 2 */ + background-color: #111; + border: 2px solid #333; + -moz-border-radius: 0.7em; -webkit-border-radius: 1em; border-radius: .5em; + -webkit-box-shadow: 0 2px 4px 0 #000; -moz-box-shadow: 0 2px 4px 0 #000; box-shadow: 0 2px 4px 0 #000; + overflow: auto; +} +.vjs-default-skin .vjs-captions-control ul li { list-style: none; margin: 0 0 2px 0; padding: 0; line-height: 1.5em; font-size: 1.4em; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; background-color: #333; } +.vjs-default-skin .vjs-captions-control ul li:hover { background-color: #ccc; color: #333; } \ No newline at end of file diff --git a/src/component.js b/src/component.js index f97d552ec..b548605b2 100644 --- a/src/component.js +++ b/src/component.js @@ -79,7 +79,8 @@ _V_.Component = _V_.Class.extend({ // Allow waiting to add components until a specific event is called var tempAdd = this.proxy(function(){ - this.addComponent(name, opts); + // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy. + this[name] = this.addComponent(name, opts); }); if (opts.loadEvent) { @@ -95,23 +96,34 @@ _V_.Component = _V_.Class.extend({ // Will generate a new child component and then append child component's element to this component's element. // Takes either the name of the UI component class, or an object that contains a name, UI Class, and options. addComponent: function(name, options){ - var componentClass, component; + var component, componentClass; - // Make sure options is at least an empty object to protect against errors - options = options || {}; + // If string, create new component with options + if (typeof name == "string") { - // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.) - componentClass = options.componentClass || _V_.capitalize(name); + // Make sure options is at least an empty object to protect against errors + options = options || {}; - // Create a new object & element for this controls set - // If there's no .player, this is a player - component = new _V_[componentClass](this.player || this, options); + // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.) + componentClass = options.componentClass || _V_.capitalize(name); + + // Create a new object & element for this controls set + // If there's no .player, this is a player + component = new _V_[componentClass](this.player || this, options); + + } else { + component = name; + } // Add the UI object's element to the container div (box) this.el.appendChild(component.el); - // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy. - this[name] = component; + // Return so it can stored on parent object if desired. + return component; + }, + + removeComponent: function(component){ + this.el.removeChild(component.el); }, /* Display @@ -144,8 +156,8 @@ _V_.Component = _V_.Class.extend({ /* Events ================================================================================ */ - addEvent: function(type, fn){ - return _V_.addEvent(this.el, type, _V_.proxy(this, fn)); + addEvent: function(type, fn, uid){ + return _V_.addEvent(this.el, type, _V_.proxy(this, fn, uid)); }, removeEvent: function(type, fn){ return _V_.removeEvent(this.el, type, fn); diff --git a/src/controls.js b/src/controls.js index ac9c5c197..f1893f79d 100644 --- a/src/controls.js +++ b/src/controls.js @@ -783,79 +783,4 @@ _V_.Poster = _V_.Button.extend({ onClick: function(){ this.player.play(); } -}); - - -/* Text Track Displays -================================================================================ */ -// Create a behavior type for each text track type (subtitlesDisplay, captionsDisplay, etc.). -// Then you can easily do something like. -// player.addBehavior(myDiv, "subtitlesDisplay"); -// And the myDiv's content will be updated with the text change. - -// Base class for all track displays. Should not be instantiated on its own. -_V_.TextTrackDisplay = _V_.Component.extend({ - - init: function(player, options){ - this._super(player, options); - - player.addEvent(this.trackType + "update", _V_.proxy(this, this.update)); - }, - - createElement: function(){ - return this._super("div", { - className: "vjs-" + this.trackType + " vjs-text-track" - }); - }, - - update: function(){ - this.el.innerHTML = this.player.textTrackValue(this.trackType); - } - -}); - -_V_.SubtitlesDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "subtitles" - -}); - -_V_.CaptionsDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "captions" - -}); - -_V_.ChaptersDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "chapters" - -}); - -_V_.DescriptionsDisplay = _V_.TextTrackDisplay.extend({ - - trackType: "descriptions" - -}); - -/* Captions Button -================================================================================ */ -_V_.CaptionsButton = _V_.Button.extend({ - - buttonText: "Captions", - - buildCSSClass: function(){ - return "vjs-captions-control " + this._super(); - }, - - onClick: function(){ - this.player.textTrackValue("captions", "Hi
hi2") - var captionsDisplay = this.player.captionsDisplay; - if (captionsDisplay.el.style.display == "block") { - captionsDisplay.hide(); - } else { - captionsDisplay.show(); - } - } - }); \ No newline at end of file diff --git a/src/core.js b/src/core.js index c2812f0b7..bcfe0a691 100644 --- a/src/core.js +++ b/src/core.js @@ -64,11 +64,10 @@ VideoJS.options = { // Included control sets components: { "poster": {}, + "textTrackDisplay": {}, "loadingSpinner": {}, "bigPlayButton": {}, - "controlBar": {}, - "subtitlesDisplay": {}, - "captionsDisplay": {} + "controlBar": {} } // components: [ diff --git a/src/lib.js b/src/lib.js index ba877e6bc..5e30f4d54 100644 --- a/src/lib.js +++ b/src/lib.js @@ -199,17 +199,25 @@ _V_.extend({ /* Proxy (a.k.a Bind or Context). A simple method for changing the context of a function It also stores a unique id on the function so it can be easily removed from events ================================================================================ */ - proxy: function(context, fn) { - // Make sure the function has a unique ID - if (!fn.guid) { fn.guid = _V_.guid++; } + proxy: function(context, fn, uid) { // Create the new function that changes the context var ret = function() { return fn.apply(context, arguments); - }; + }, + + // Make sure the function has a unique ID + guid = fn.guid || _V_.guid++; + + // Allow for the ability to individualize this function + // Needed in the case where multiple items might share the same prototype function + // IF both items add an event listener with the same function, then you try to remove just one + // it will remove both because they both have the same guid. + // when using this, you need to use the proxy method both times. + if (uid) { guid = uid + "_" + guid } // Give the new function the same ID // (so that they are equivalent and can be easily removed) - ret.guid = fn.guid; + ret.guid = guid; return ret; }, diff --git a/src/player.js b/src/player.js index 54e0c53cb..2cc6f3658 100644 --- a/src/player.js +++ b/src/player.js @@ -83,6 +83,11 @@ _V_.Player = _V_.Component.extend({ }); } + // Tracks defined in tracks.js + if (options.tracks && options.tracks.length > 0) { + this.addTextTracks(options.tracks); + } + // If there are no sources when the player is initialized, // load the first supported playback technology. if (!options.sources || options.sources.length == 0) { @@ -145,15 +150,14 @@ _V_.Player = _V_.Component.extend({ }); } if (c.nodeName == "TRACK") { - options.tracks.push(new _V_.Track({ + options.tracks.push({ src: c.getAttribute("src"), kind: c.getAttribute("kind"), srclang: c.getAttribute("srclang"), label: c.getAttribute("label"), 'default': c.getAttribute("default") !== null, title: c.getAttribute("title") - }, this)); - + }); } } } @@ -789,15 +793,6 @@ _V_.Player = _V_.Component.extend({ return this.techGet("currentSrc") || this.values.src || ""; }, - textTrackValue: function(kind, value){ - if (value !== undefined) { - this.values[kind] = value; - this.triggerEvent(kind+"update"); - return this; - } - return this.values[kind]; - }, - // Attributes/Options preload: function(value){ if (value !== undefined) { @@ -825,7 +820,6 @@ _V_.Player = _V_.Component.extend({ }, controls: function(){ return this.options.controls; }, - textTracks: function(){ return this.options.tracks; }, poster: function(){ return this.techGet("poster"); }, error: function(){ return this.techGet("error"); }, ended: function(){ return this.techGet("ended"); } diff --git a/src/tracks.js b/src/tracks.js index 40e7ef781..30bb58a2e 100644 --- a/src/tracks.js +++ b/src/tracks.js @@ -1,29 +1,203 @@ -_V_.Track = function(attributes, player){ - // Store reference to the parent player - this.player = player; +// Player Extenstions +_V_.merge(_V_.Player.prototype, { - this.src = attributes.src; - this.kind = attributes.kind; - this.srclang = attributes.srclang; - this.label = attributes.label; - this["default"] = attributes["default"]; // 'default' is reserved-ish - this.title = attributes.title; + // Add an array of text tracks. captions, subtitles, chapters, descriptions + addTextTracks: function(trackObjects){ + var tracks = this.textTracks = (this.textTracks) ? this.textTracks : [], + i = 0, + j = trackObjects.length, + track, Kind; - this.cues = []; - this.currentCue = false; - this.lastCueIndex = 0; + for (;i