├── .npmignore
├── LICENSE
├── README.md
├── bower.json
├── example.html
├── package.json
├── video-quality-selector.css
└── video-quality-selector.js
/.npmignore:
--------------------------------------------------------------------------------
1 | # Exclude everything except the main files
2 | **/*
3 | !video-quality-selector.css
4 | !video-quality-selector.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Dominic
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Video.js Resolution Selector
2 | Add a resolution selector button to Video.js to allow users to manually adjust the video quality.
3 |
4 | ## Install
5 | You can use bower (`bower install videojs-resolution-selector`), npm (`npm install videojs-resolution-selector`), or simply download the source from this repo. You must be running Video.js 4.7.3 or higher for this plugin to function. You can download the latest source at the [main Video.js repo](https://github.com/videojs/video.js), or you can get production files from [videojs.com](http://videojs.com), or you can use the CDN files.
6 |
7 | ## Usage
8 | Add an extra attribute to your `` elements.
9 | ```html
10 |
14 | ```
15 |
16 | Enable the plugin as described in the [video.js docs](https://github.com/videojs/video.js/blob/v4.5.2/docs/guides/plugins.md#step-3-using-a-plugin). Optionally, you can pass some settings to the plugin:
17 | ```javascript
18 | videojs( '#my-video', { plugins : { resolutionSelector : {
19 | force_types : [ 'video/mp4', 'video/webm' ],
20 | default_res : "480"
21 | } } } );
22 | ```
23 |
24 | `force_types` is an array. The plugin will check each resolution to make sure there is a source of each type at that resolution.
25 |
26 | `default_res` must be a string. You can either specify a single resolution or a comma separated list (e.g. `"480,240"`). When using a list, the first available resolution in the list will be selected by default.
27 |
28 | The plugin also triggers a `changeRes` event on the player instance anytime the resolution is changed, so your code can listen for that and take any desired action on resolution changes:
29 | ```javascript
30 | videojs( '#my-video', { plugins : { resolutionSelector : {} } }, function() {
31 |
32 | var player = this;
33 |
34 | player.on( 'changeRes', function() {
35 |
36 | console.log( 'Current Res is: ' + player.getCurrentRes() );
37 | });
38 | });
39 | ```
40 | The plugin provides a `changeRes` method on the `player` object. You can call it like so (after your player is ready): `player.changeRes( '480' )`.
41 |
42 | ## Simple Example
43 | ```html
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 | ```
60 | Please see example.html for a more advanced example.
61 |
62 | ## Styling the Button
63 | By default, the button will not be visible. You will either need to include the styles from `video-quality-selector.css` (after the default Video.js styles to override them), or use your own icon for the button. To match the rest of the Video.js controls, I recommend using an icon font to style the button, but it's up to you.
64 |
65 | ## Mobile devices
66 | If you want this plugin to work on mobile devices, you need to enable the video.js controls because the native controls are default on iOS and Android.
67 |
68 | ```html
69 |
72 | ```
73 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "videojs-resolution-selector",
3 | "description": "Adds a resolution selector button to Video.js to allow users to manually adjust the video quality.",
4 | "version": "1.6.2",
5 | "main": [
6 | "video-quality-selector.js",
7 | "video-quality-selector.css"
8 | ],
9 | "ignore": [
10 | "**/*"
11 | ],
12 | "dependencies": {
13 | "video.js": "^4.7"
14 | }
15 | }
--------------------------------------------------------------------------------
/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Video.js | Resolution Selector Plugin
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
Example Setup
18 |
19 |
20 |
24 |
25 |
26 |
30 |
31 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "videojs-resolution-selector",
3 | "description": "Adds a resolution selector button to Video.js to allow users to manually adjust the video quality.",
4 | "version": "1.6.2",
5 | "author": "Dominic P",
6 | "license": "MIT",
7 | "main": "video-quality-selector.js",
8 | "style": "video-quality-selector.css",
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/dominic-p/videojs-resolution-selector.git"
12 | },
13 | "dependencies": {},
14 | "bugs": {
15 | "url": "https://github.com/dominic-p/videojs-resolution-selector/issues"
16 | },
17 | "keywords": [
18 | "videojs",
19 | "resolution",
20 | "selector"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/video-quality-selector.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 | /*
3 | You are free to style the button however you wish. I plan to use
4 | an icon from my site's own icon font to make it more visible. These
5 | are just basic styles to make it look ok with plain text.
6 | */
7 |
8 | /* Position the button */
9 | .vjs-res-button {
10 | float: right;
11 | line-height: 3em;
12 | }
13 |
14 | /* Don't show hover effects on title */
15 | ul li.vjs-menu-title.vjs-res-menu-title:hover {
16 | cursor: default;
17 | background-color: transparent;
18 | color: #CCC;
19 | -moz-box-shadow: none;
20 | -webkit-box-shadow: none;
21 | box-shadow: none;
22 | }
23 |
24 | /* Needed to keep text visible in video.js 4.9 */
25 | .vjs-res-button .vjs-control-text {
26 | width: auto;
27 | height: auto;
28 | clip: auto;
29 | }
--------------------------------------------------------------------------------
/video-quality-selector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Video.js Resolution Selector
3 | *
4 | * This plugin for Video.js adds a resolution selector option
5 | * to the toolbar. Usage:
6 | *
7 | *
11 | */
12 |
13 | (function( _V_ ) {
14 |
15 | /***********************************************************************************
16 | * Define some helper functions
17 | ***********************************************************************************/
18 | var methods = {
19 |
20 | /**
21 | * In a future version, this can be made more intelligent,
22 | * but for now, we'll just add a "p" at the end if we are passed
23 | * numbers.
24 | *
25 | * @param (string) res The resolution to make a label for
26 | *
27 | * @returns (string) The label text string
28 | */
29 | res_label : function( res ) {
30 |
31 | return ( /^\d+$/.test( res ) ) ? res + 'p' : res;
32 | }
33 | };
34 |
35 | /***********************************************************************************
36 | * Setup our resolution menu items
37 | ***********************************************************************************/
38 | _V_.ResolutionMenuItem = _V_.MenuItem.extend({
39 |
40 | // Call variable to prevent the resolution change from being called twice
41 | call_count : 0,
42 |
43 | /** @constructor */
44 | init : function( player, options ){
45 |
46 | var touchstart = false;
47 |
48 | // Modify options for parent MenuItem class's init.
49 | options.label = methods.res_label( options.res );
50 | options.selected = ( options.res.toString() === player.getCurrentRes().toString() );
51 |
52 | // Call the parent constructor
53 | _V_.MenuItem.call( this, player, options );
54 |
55 | // Store the resolution as a property
56 | this.resolution = options.res;
57 |
58 | // Register our click and tap handlers
59 | this.on( ['click', 'tap'], this.onClick );
60 |
61 | // Toggle the selected class whenever the resolution changes
62 | player.on( 'changeRes', _V_.bind( this, function() {
63 |
64 | if ( this.resolution == player.getCurrentRes() ) {
65 |
66 | this.selected( true );
67 |
68 | } else {
69 |
70 | this.selected( false );
71 | }
72 |
73 | // Reset the call count
74 | this.call_count = 0;
75 | }));
76 | }
77 | });
78 |
79 | // Handle clicks on the menu items
80 | _V_.ResolutionMenuItem.prototype.onClick = function() {
81 |
82 | // Check if this has already been called
83 | if ( this.call_count > 0 ) { return; }
84 |
85 | // Call the player.changeRes method
86 | this.player().changeRes( this.resolution );
87 |
88 | // Increment the call counter
89 | this.call_count++;
90 | };
91 |
92 | /***********************************************************************************
93 | * Setup our resolution menu title item
94 | ***********************************************************************************/
95 | _V_.ResolutionTitleMenuItem = _V_.MenuItem.extend({
96 |
97 | init : function( player, options ) {
98 |
99 | // Call the parent constructor
100 | _V_.MenuItem.call( this, player, options );
101 |
102 | // No click handler for the menu title
103 | this.off( 'click' );
104 | }
105 | });
106 |
107 | /***********************************************************************************
108 | * Define our resolution selector button
109 | ***********************************************************************************/
110 | _V_.ResolutionSelector = _V_.MenuButton.extend({
111 |
112 | /** @constructor */
113 | init : function( player, options ) {
114 |
115 | // Add our list of available resolutions to the player object
116 | player.availableRes = options.available_res;
117 |
118 | // Call the parent constructor
119 | _V_.MenuButton.call( this, player, options );
120 |
121 | // Set the button text based on the option provided
122 | this.el().firstChild.firstChild.innerHTML = options.buttonText;
123 | }
124 | });
125 |
126 | // Set class for resolution selector button
127 | _V_.ResolutionSelector.prototype.className = 'vjs-res-button';
128 |
129 | // Create a menu item for each available resolution
130 | _V_.ResolutionSelector.prototype.createItems = function() {
131 |
132 | var player = this.player(),
133 | items = [],
134 | current_res;
135 |
136 | // Add the menu title item
137 | items.push( new _V_.ResolutionTitleMenuItem( player, {
138 |
139 | el : _V_.Component.prototype.createEl( 'li', {
140 |
141 | className : 'vjs-menu-title vjs-res-menu-title',
142 | innerHTML : player.localize( 'Quality' )
143 | })
144 | }));
145 |
146 | // Add an item for each available resolution
147 | for ( current_res in player.availableRes ) {
148 |
149 | // Don't add an item for the length attribute
150 | if ( 'length' == current_res ) { continue; }
151 |
152 | items.push( new _V_.ResolutionMenuItem( player, {
153 | res : current_res
154 | }));
155 | }
156 |
157 | // Sort the available resolutions in descending order
158 | items.sort(function( a, b ) {
159 |
160 | if ( typeof a.resolution == 'undefined' ) {
161 |
162 | return -1;
163 |
164 | } else {
165 |
166 | return parseInt( b.resolution ) - parseInt( a.resolution );
167 | }
168 | });
169 |
170 | return items;
171 | };
172 |
173 | /***********************************************************************************
174 | * Register the plugin with videojs, main plugin function
175 | ***********************************************************************************/
176 | _V_.plugin( 'resolutionSelector', function( options ) {
177 |
178 | // Only enable the plugin on HTML5 videos
179 | if ( ! this.el().firstChild.canPlayType ) { return; }
180 |
181 | /*******************************************************************
182 | * Setup variables, parse settings
183 | *******************************************************************/
184 | var player = this,
185 | sources = player.options().sources,
186 | i = sources.length,
187 | j,
188 | found_type,
189 |
190 | // Override default options with those provided
191 | settings = _V_.util.mergeOptions({
192 |
193 | default_res : '', // (string) The resolution that should be selected by default ( '480' or '480,1080,240' )
194 | force_types : false // (array) List of media types. If passed, we need to have source for each type in each resolution or that resolution will not be an option
195 |
196 | }, options || {} ),
197 |
198 | available_res = { length : 0 },
199 | current_res,
200 | resolutionSelector,
201 |
202 | // Split default resolutions if set and valid, otherwise default to an empty array
203 | default_resolutions = ( settings.default_res && typeof settings.default_res == 'string' ) ? settings.default_res.split( ',' ) : [];
204 |
205 | // Get all of the available resoloutions
206 | while ( i > 0 ) {
207 |
208 | i--;
209 |
210 | // Skip sources that don't have data-res attributes
211 | if ( ! sources[i]['data-res'] ) { continue; }
212 |
213 | current_res = sources[i]['data-res'];
214 |
215 | if ( typeof available_res[current_res] !== 'object' ) {
216 |
217 | available_res[current_res] = [];
218 | available_res.length++;
219 | }
220 |
221 | available_res[current_res].unshift( sources[i] );
222 | }
223 |
224 | // Check for forced types
225 | if ( settings.force_types ) {
226 |
227 | // Loop through all available resoultions
228 | for ( current_res in available_res ) {
229 |
230 | // Don't count the length property as a resolution
231 | if ( 'length' == current_res ) { continue; }
232 |
233 | i = settings.force_types.length;
234 | found_types = 0;
235 |
236 | // Loop through all required types
237 | while ( i > 0 ) {
238 |
239 | i--;
240 |
241 | j = available_res[current_res].length;
242 |
243 | // Loop through all available sources in current resolution
244 | while ( j > 0 ) {
245 |
246 | j--;
247 |
248 | // Check if the current source matches the current type we're checking
249 | if ( settings.force_types[i] === available_res[current_res][j].type ) {
250 |
251 | found_types++;
252 | break;
253 | }
254 | }
255 | }
256 |
257 | // If we didn't find sources for all of the required types in the current res, remove it
258 | if ( found_types < settings.force_types.length ) {
259 |
260 | delete available_res[current_res];
261 | available_res.length--;
262 | }
263 | }
264 | }
265 |
266 | // Make sure we have at least 2 available resolutions before we add the button
267 | if ( available_res.length < 2 ) { return; }
268 |
269 | // Loop through the choosen default resolutions if there were any
270 | for ( i = 0; i < default_resolutions.length; i++ ) {
271 |
272 | // Set the video to start out with the first available default res
273 | if ( available_res[default_resolutions[i]] ) {
274 |
275 | player.src( available_res[default_resolutions[i]] );
276 | player.currentRes = default_resolutions[i];
277 | break;
278 | }
279 | }
280 |
281 | /*******************************************************************
282 | * Add methods to player object
283 | *******************************************************************/
284 |
285 | // Make sure we have player.localize() if it's not defined by Video.js
286 | if ( typeof player.localize !== 'function' ) {
287 |
288 | player.localize = function( string ) {
289 |
290 | return string;
291 | };
292 | }
293 |
294 | // Helper function to get the current resolution
295 | player.getCurrentRes = function() {
296 |
297 | if ( typeof player.currentRes !== 'undefined' ) {
298 |
299 | return player.currentRes;
300 |
301 | } else {
302 |
303 | try {
304 |
305 | return res = player.options().sources[0]['data-res'];
306 |
307 | } catch(e) {
308 |
309 | return '';
310 | }
311 | }
312 | };
313 |
314 | // Define the change res method
315 | player.changeRes = function( target_resolution ) {
316 |
317 | var video_el = player.el().firstChild,
318 | is_paused = player.paused(),
319 | current_time = player.currentTime(),
320 | button_nodes,
321 | button_node_count;
322 |
323 | // Do nothing if we aren't changing resolutions or if the resolution isn't defined
324 | if ( player.getCurrentRes() == target_resolution
325 | || ! player.availableRes
326 | || ! player.availableRes[target_resolution] ) { return; }
327 |
328 | // Make sure the loadedmetadata event will fire
329 | if ( 'none' == video_el.preload ) { video_el.preload = 'metadata'; }
330 |
331 | // Change the source and make sure we don't start the video over
332 | player.src( player.availableRes[target_resolution] ).one( 'loadedmetadata', function() {
333 |
334 | player.currentTime( current_time );
335 |
336 | // If the video was paused, don't show the poster image again
337 | player.addClass( 'vjs-has-started' );
338 |
339 | if ( ! is_paused ) { player.play(); }
340 | });
341 |
342 | // Save the newly selected resolution in our player options property
343 | player.currentRes = target_resolution;
344 |
345 | // Make sure the button has been added to the control bar
346 | if ( player.controlBar.resolutionSelector ) {
347 |
348 | button_nodes = player.controlBar.resolutionSelector.el().firstChild.children;
349 | button_node_count = button_nodes.length;
350 |
351 | // Update the button text
352 | while ( button_node_count > 0 ) {
353 |
354 | button_node_count--;
355 |
356 | if ( 'vjs-control-text' == button_nodes[button_node_count].className ) {
357 |
358 | button_nodes[button_node_count].innerHTML = methods.res_label( target_resolution );
359 | break;
360 | }
361 | }
362 | }
363 |
364 | // Update the classes to reflect the currently selected resolution
365 | player.trigger( 'changeRes' );
366 | };
367 |
368 | /*******************************************************************
369 | * Add the resolution selector button
370 | *******************************************************************/
371 |
372 | // Get the starting resolution
373 | current_res = player.getCurrentRes();
374 |
375 | if ( current_res ) { current_res = methods.res_label( current_res ); }
376 |
377 | // Add the resolution selector button
378 | resolutionSelector = new _V_.ResolutionSelector( player, {
379 | buttonText : player.localize( current_res || 'Quality' ),
380 | available_res : available_res
381 | });
382 |
383 | // Add the button to the control bar object and the DOM
384 | player.controlBar.resolutionSelector = player.controlBar.addChild( resolutionSelector );
385 | });
386 |
387 | })( videojs );
--------------------------------------------------------------------------------