├── .npmignore ├── .gitignore ├── .travis.yml ├── .jshintrc ├── .editorconfig ├── LICENSE-Apache-2.0 ├── test ├── index.html └── videojs-resolution-switcher.test.js ├── lib ├── videojs-resolution-switcher.css └── videojs-resolution-switcher.js ├── bower.json ├── package.json ├── examples ├── youtube.html ├── flash.html └── html5.html ├── Gruntfile.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | test/ 3 | *~ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules/ 4 | dist/ 5 | *~ 6 | /.brackets.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4 4 | - 0.12 5 | - 0.10 6 | before_install: npm install -g grunt-cli 7 | install: npm install 8 | sudo: false 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser" : true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "quotmark" : "single", 6 | "trailing" : true, 7 | "undef" : true, 8 | "predef" : [ 9 | "videojs" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /LICENSE-Apache-2.0: -------------------------------------------------------------------------------- 1 | Copyright 2013 Kasper Moskwiak 2 | 3 | Modified by Pierre Kraft 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js videoJsResolutionSwitcher 6 | 7 | 8 | 9 |
10 |
11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /lib/videojs-resolution-switcher.css: -------------------------------------------------------------------------------- 1 | .vjs-resolution-button .vjs-menu-icon:before { 2 | content: '\f110'; 3 | font-family: VideoJS; 4 | font-weight: normal; 5 | font-style: normal; 6 | font-size: 1.8em; 7 | line-height: 1.67em; 8 | } 9 | 10 | .vjs-resolution-button .vjs-resolution-button-label { 11 | font-size: 1em; 12 | line-height: 3em; 13 | position: absolute; 14 | top: 0; 15 | left: 0; 16 | width: 100%; 17 | height: 100%; 18 | text-align: center; 19 | box-sizing: inherit; 20 | } 21 | 22 | .vjs-resolution-button .vjs-menu .vjs-menu-content { 23 | width: 4em; 24 | left: 50%; /* Center the menu, in it's parent */ 25 | margin-left: -2em; /* half of width, to center */ 26 | } 27 | 28 | .vjs-resolution-button .vjs-menu li { 29 | text-transform: none; 30 | font-size: 1em; 31 | } 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vjs-resolution-switcher", 3 | "version": "0.4.2", 4 | "authors": [ 5 | "Kasper Moskwiak " 6 | ], 7 | "description": "Resolution switcher for video.js 5", 8 | "main": [ 9 | "lib/videojs-resolution-switcher.js", 10 | "lib/videojs-resolution-switcher.css" 11 | ], 12 | "moduleType": [ 13 | "amd", 14 | "globals", 15 | "node" 16 | ], 17 | "keywords": [ 18 | "videojs", 19 | "flash", 20 | "video", 21 | "player", 22 | "resolution", 23 | "sources", 24 | "videojs-plugin" 25 | ], 26 | "license": "Apache-2.0", 27 | "homepage": "https://github.com/kmoskwiak/videojs-resolution-switcher", 28 | "ignore": [ 29 | "**/.*", 30 | "node_modules", 31 | "bower_components", 32 | "test", 33 | "tests" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "videojs-resolution-switcher", 3 | "version": "0.4.2", 4 | "main": "./lib/videojs-resolution-switcher.js", 5 | "author": { 6 | "name": "Kasper Moskwiak", 7 | "email": "kasper.moskwiak@gmail.com", 8 | "url": "http://kspr.pl" 9 | }, 10 | "contributors": [ 11 | { 12 | "name": "Pierre Kraft" 13 | } 14 | ], 15 | "description": "Resolution switcher for video.js 5", 16 | "repository": { 17 | "type": "git", 18 | "url": "git@github.com:kmoskwiak/videojs-resolution-switcher.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/kmoskwiak/videojs-resolution-switcher/issues" 22 | }, 23 | "license": "Apache-2.0", 24 | "keywords": [ 25 | "videojs", 26 | "html5", 27 | "flash", 28 | "video", 29 | "player", 30 | "resolution", 31 | "source", 32 | "videojs-plugin" 33 | ], 34 | "scripts": { 35 | "test": "grunt test" 36 | }, 37 | "devDependencies": { 38 | "grunt": "^0.4.5", 39 | "grunt-contrib-clean": "^1.0", 40 | "grunt-contrib-concat": "^1.0", 41 | "grunt-contrib-jshint": "^1.0", 42 | "grunt-contrib-qunit": "^1.1", 43 | "grunt-contrib-uglify": "^1.0", 44 | "grunt-contrib-watch": "^1.0", 45 | "video.js": "^5.8", 46 | "qunitjs": "^1.22", 47 | "videojs-youtube": "^2.0.8" 48 | }, 49 | "peerDependencies": { 50 | "video.js": "^5.8" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/youtube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Resolution Switcher 6 | 7 | 8 | 9 | 25 | 26 | 27 |
28 |

29 | Youtube tech 30 |

31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /test/videojs-resolution-switcher.test.js: -------------------------------------------------------------------------------- 1 | /*! videojs-test - v0.0.0 - 2015-7-26 2 | * Copyright (c) 2015 Kasper Moskwiak 3 | * Licensed under the Apache-2.0 license. */ 4 | (function(window, videojs, qunit) { 5 | 'use strict'; 6 | 7 | var realIsHtmlSupported, 8 | player, 9 | 10 | // local QUnit aliases 11 | // http://api.qunitjs.com/ 12 | 13 | // module(name, {[setup][ ,teardown]}) 14 | module = qunit.module, 15 | // test(name, callback) 16 | test = qunit.test, 17 | // ok(value, [message]) 18 | ok = qunit.ok, 19 | // equal(actual, expected, [message]) 20 | equal = qunit.equal, 21 | // strictEqual(actual, expected, [message]) 22 | strictEqual = qunit.strictEqual, 23 | // deepEqual(actual, expected, [message]) 24 | deepEqual = qunit.deepEqual, 25 | // notEqual(actual, expected, [message]) 26 | notEqual = qunit.notEqual, 27 | // throws(block, [expected], [message]) 28 | throws = qunit.throws; 29 | 30 | module('videojs-test', { 31 | setup: function() { 32 | // force HTML support so the tests run in a reasonable 33 | // environment under phantomjs 34 | videojs.Html5 = videojs.Html5 || {}; 35 | realIsHtmlSupported = videojs.Html5.isSupported; 36 | videojs.Html5.isSupported = function() { 37 | return true; 38 | }; 39 | 40 | // create a video element 41 | var video = document.createElement('video'); 42 | document.querySelector('#qunit-fixture').appendChild(video); 43 | 44 | // create a video.js player 45 | player = videojs(video); 46 | 47 | // initialize the plugin with the default options 48 | player.videoJsResolutionSwitcher(); 49 | }, 50 | teardown: function() { 51 | videojs.Html5.isSupported = realIsHtmlSupported; 52 | } 53 | }); 54 | 55 | test('registers itself', function() { 56 | ok(player.videoJsResolutionSwitcher, 'registered the plugin'); 57 | }); 58 | })(window, window.videojs, window.QUnit); 59 | -------------------------------------------------------------------------------- /examples/flash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Resolution Switcher 6 | 7 | 8 | 9 | 25 | 26 | 27 |
28 |

29 | Use flash 30 |

31 |
32 | 33 | 34 | 35 | 36 | 39 | 40 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 7 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' + 8 | '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author %>;' + 9 | ' Licensed <%= pkg.license %> */\n', 10 | clean: { 11 | files: ['dist'] 12 | }, 13 | concat: { 14 | options: { 15 | banner: '<%= banner %>', 16 | stripBanners: true 17 | }, 18 | dist: { 19 | src: 'lib/**/*.js', 20 | dest: 'dist/<%= pkg.name %>.js' 21 | } 22 | }, 23 | uglify: { 24 | options: { 25 | banner: '<%= banner %>' 26 | }, 27 | dist: { 28 | src: '<%= concat.dist.dest %>', 29 | dest: 'dist/<%= pkg.name %>.min.js' 30 | } 31 | }, 32 | qunit: { 33 | files: 'test/**/*.html' 34 | }, 35 | jshint: { 36 | gruntfile: { 37 | options: { 38 | node: true 39 | }, 40 | src: 'Gruntfile.js' 41 | }, 42 | src: { 43 | options: { 44 | jshintrc: '.jshintrc' 45 | }, 46 | src: ['lib/**/*.js'] 47 | }, 48 | test: { 49 | options: { 50 | jshintrc: '.jshintrc' 51 | }, 52 | src: ['test/**/*.js'] 53 | } 54 | }, 55 | watch: { 56 | gruntfile: { 57 | files: '<%= jshint.gruntfile.src %>', 58 | tasks: ['jshint:gruntfile'] 59 | }, 60 | src: { 61 | files: '<%= jshint.src.src %>', 62 | tasks: ['jshint:src', 'qunit'] 63 | }, 64 | test: { 65 | files: '<%= jshint.test.src %>', 66 | tasks: ['jshint:test', 'qunit'] 67 | } 68 | } 69 | }); 70 | 71 | grunt.loadNpmTasks('grunt-contrib-clean'); 72 | grunt.loadNpmTasks('grunt-contrib-concat'); 73 | grunt.loadNpmTasks('grunt-contrib-uglify'); 74 | grunt.loadNpmTasks('grunt-contrib-qunit'); 75 | grunt.loadNpmTasks('grunt-contrib-jshint'); 76 | grunt.loadNpmTasks('grunt-contrib-watch'); 77 | 78 | grunt.registerTask('default', 79 | ['clean', 80 | 'jshint', 81 | 'qunit', 82 | 'concat', 83 | 'uglify']); 84 | 85 | grunt.registerTask('test', [ 86 | 'jshint' 87 | ]); 88 | }; 89 | -------------------------------------------------------------------------------- /examples/html5.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video.js Resolution Switcher 6 | 7 | 8 | 9 | 25 | 26 | 27 |
28 |

29 | Set sources dynamically 30 |

31 |
32 | 33 | 34 | 35 |
36 |

37 | Set sources inside <video> tag 38 |

39 |
40 | 41 | 47 | 48 | 49 | 50 | 53 | 54 | 92 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Video.js Resolution Switcher [![Build Status](https://travis-ci.org/kmoskwiak/videojs-resolution-switcher.svg?branch=master)](https://travis-ci.org/kmoskwiak/videojs-resolution-switcher) 2 | 3 | Resolution switcher for [video.js v5](https://github.com/videojs/video.js) 4 | 5 | ## Example 6 | 7 | [Working examples](examples) of the plugin you can check out if you're having trouble. Or check out this [demo](https://kmoskwiak.github.io/videojs-resolution-switcher/). 8 | 9 | ## Getting Started 10 | 11 | Install plugin with 12 | 13 | **npm** 14 | ``` 15 | npm i videojs-resolution-switcher 16 | ``` 17 | 18 | or **bower** 19 | ``` 20 | bower install videojs-resolution-switcher 21 | ``` 22 | 23 | 24 | ### Setup sources dynamically: 25 | 26 | ```html 27 | 28 | 29 | 30 | 61 | ``` 62 | 63 | ### Or use `` tags: 64 | 65 | ```html 66 | 67 | 71 | 74 | 75 | ``` 76 | 77 | 78 | ### YouTube tech 79 | 80 | YouTube tech form https://github.com/eXon/videojs-youtube 81 | 82 | ```html 83 | 84 | 85 | 104 | 105 | ``` 106 | 107 | ### Flash tech 108 | 109 | When using flash tech `preload="auto"` is required. 110 | 111 | ## Source options 112 | 113 | Sources can passed to player using `updateSrc` method or `` tag as shown above. Avalible options for each source are: 114 | * label - `String` (required) is shown in menu (ex. 'SD' or '360p') 115 | * res - `Number` is resolution of video used for sorting (ex. 360 or 1080) 116 | 117 | ## Plugin options 118 | 119 | You can pass options to plugin like this: 120 | 121 | ```javascript 122 | 123 | videojs('video', { 124 | controls: true, 125 | muted: true, 126 | width: 1000, 127 | plugins: { 128 | videoJsResolutionSwitcher: { 129 | default: 'low' 130 | } 131 | } 132 | }, function(){ 133 | // this is player 134 | }) 135 | ``` 136 | ### Avalible options: 137 | * default - `{Number}|'low'|'high'` - default resolution. If any `Number` is passed plugin will try to choose source based on `res` parameter. If `low` or `high` is passed, plugin will choose respectively worse or best resolution (if `res` parameter is specified). If `res` parameter is not specified plugin assumes that sources array is sorted from best to worse. 138 | * dynamicLabel - `{Boolean}` - if `true` current label will be displayed in control bar. By default gear icon is displayed. 139 | * customSourcePicker - `{Function}` - custom function for selecting source. 140 | * ui - `{Boolean}` - If set to `false` button will not be displayed in control bar. Default is `true`. 141 | 142 | 143 | ## Methods 144 | 145 | 146 | ### updateSrc([source]) 147 | Returns video.js player object if used as setter. If `source` is not passed it acts like [player.src()](http://docs.videojs.com/docs/api/player.html#Methodssrc) 148 | ```javascript 149 | 150 | // Update video sources 151 | player.updateSrc([ 152 | { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4", label: 'SD' }, 153 | { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4", label: 'HD' }, 154 | { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4", label: '4k' } 155 | ]) 156 | 157 | ``` 158 | #### PARAMETERS: 159 | | name | type | required | description | 160 | |:----:|:----:|:--------:|:-----------:| 161 | | source| array| no | array of sources | 162 | 163 | ### currentResolution([label], [customSourcePicker]) 164 | If used as getter returns current resolution object: 165 | ```javascript 166 | { 167 | label: 'SD', // current label 168 | sources: [ 169 | { type: "video/webm", src: "http://www.example.com/path/to/video.webm", label: 'SD' }, 170 | { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4", label: 'SD' } 171 | ] // array of sources with current label 172 | } 173 | ``` 174 | 175 | If used as setter returns video.js player object. 176 | 177 | 178 | ```javascript 179 | 180 | // Get current resolution 181 | player.currentResolution(); // returns object {label '', sources: []} 182 | 183 | // Set resolution 184 | player.currentResolution('SD'); // returns videojs player object 185 | ``` 186 | #### PARAMETERS: 187 | | name | type | required | description | 188 | |:----:|:----:|:--------:|:-----------:| 189 | | label| string| no | label name | 190 | | customSourcePicker | function | no | cutom function to choose source | 191 | 192 | #### customSourcePicker 193 | If there is more than one source with the same label, player will choose source automatically. This behavior can be changed if `customSourcePicker` is passed. 194 | 195 | `customSourcePicker` must return `player` object. 196 | ```javascript 197 | player.currentResolution('SD', function(_player, _sources, _label){ 198 | return _player.src(_sources[0]); \\ Always select first source in array 199 | }); 200 | ``` 201 | `customSourcePicker` accepst 3 arguments. 202 | 203 | | name | type | required | description | 204 | |:----:|:----:|:--------:|:-----------:| 205 | | player| Object | yes | videojs player object | 206 | | sources | Array | no | array of sources | 207 | | label | String | no | name of label | 208 | 209 | `customSourcePicker` may be passed in options when player is initialized: 210 | ```javascript 211 | 212 | var myCustomSrcPicker = function(_p, _s, _l){ 213 | // select any source you want 214 | return _p.src(selectedSource); 215 | } 216 | 217 | videojs('video', { 218 | controls: true, 219 | muted: true, 220 | width: 1000, 221 | plugins: { 222 | videoJsResolutionSwitcher: { 223 | default: 'low', 224 | customSourcePicker: myCustomSrcPicker 225 | } 226 | } 227 | }, function(){ 228 | // this is player 229 | }) 230 | ``` 231 | 232 | 233 | ### getGroupedSrc() 234 | Returns sources grouped by label, resolution and type. 235 | 236 | 237 | ## Events 238 | 239 | ### resolutionchange `EVENT` 240 | 241 | > Fired when resolution is changed 242 | -------------------------------------------------------------------------------- /lib/videojs-resolution-switcher.js: -------------------------------------------------------------------------------- 1 | /*! videojs-resolution-switcher - 2015-7-26 2 | * Copyright (c) 2016 Kasper Moskwiak 3 | * Modified by Pierre Kraft and Derk-Jan Hartman 4 | * Licensed under the Apache-2.0 license. */ 5 | 6 | (function() { 7 | /* jshint eqnull: true*/ 8 | /* global require */ 9 | 'use strict'; 10 | var videojs = null; 11 | if(typeof window.videojs === 'undefined' && typeof require === 'function') { 12 | videojs = require('video.js'); 13 | } else { 14 | videojs = window.videojs; 15 | } 16 | 17 | (function(window, videojs) { 18 | var videoJsResolutionSwitcher, 19 | defaults = { 20 | ui: true 21 | }; 22 | 23 | /* 24 | * Resolution menu item 25 | */ 26 | var MenuItem = videojs.getComponent('MenuItem'); 27 | var ResolutionMenuItem = videojs.extend(MenuItem, { 28 | constructor: function(player, options){ 29 | options.selectable = true; 30 | // Sets this.player_, this.options_ and initializes the component 31 | MenuItem.call(this, player, options); 32 | this.src = options.src; 33 | 34 | player.on('resolutionchange', videojs.bind(this, this.update)); 35 | } 36 | } ); 37 | ResolutionMenuItem.prototype.handleClick = function(event){ 38 | MenuItem.prototype.handleClick.call(this,event); 39 | this.player_.currentResolution(this.options_.label); 40 | }; 41 | ResolutionMenuItem.prototype.update = function(){ 42 | var selection = this.player_.currentResolution(); 43 | this.selected(this.options_.label === selection.label); 44 | }; 45 | MenuItem.registerComponent('ResolutionMenuItem', ResolutionMenuItem); 46 | 47 | /* 48 | * Resolution menu button 49 | */ 50 | var MenuButton = videojs.getComponent('MenuButton'); 51 | var ResolutionMenuButton = videojs.extend(MenuButton, { 52 | constructor: function(player, options){ 53 | this.label = document.createElement('span'); 54 | options.label = 'Quality'; 55 | // Sets this.player_, this.options_ and initializes the component 56 | MenuButton.call(this, player, options); 57 | this.el().setAttribute('aria-label','Quality'); 58 | this.controlText('Quality'); 59 | 60 | if(options.dynamicLabel){ 61 | videojs.addClass(this.label, 'vjs-resolution-button-label'); 62 | this.el().appendChild(this.label); 63 | }else{ 64 | var staticLabel = document.createElement('span'); 65 | videojs.addClass(staticLabel, 'vjs-menu-icon'); 66 | this.el().appendChild(staticLabel); 67 | } 68 | player.on('updateSources', videojs.bind( this, this.update ) ); 69 | } 70 | } ); 71 | ResolutionMenuButton.prototype.createItems = function(){ 72 | var menuItems = []; 73 | var labels = (this.sources && this.sources.label) || {}; 74 | 75 | // FIXME order is not guaranteed here. 76 | for (var key in labels) { 77 | if (labels.hasOwnProperty(key)) { 78 | menuItems.push(new ResolutionMenuItem( 79 | this.player_, 80 | { 81 | label: key, 82 | src: labels[key], 83 | selected: key === (this.currentSelection ? this.currentSelection.label : false) 84 | }) 85 | ); 86 | } 87 | } 88 | return menuItems; 89 | }; 90 | ResolutionMenuButton.prototype.update = function(){ 91 | this.sources = this.player_.getGroupedSrc(); 92 | this.currentSelection = this.player_.currentResolution(); 93 | this.label.innerHTML = this.currentSelection ? this.currentSelection.label : ''; 94 | return MenuButton.prototype.update.call(this); 95 | }; 96 | ResolutionMenuButton.prototype.buildCSSClass = function(){ 97 | return MenuButton.prototype.buildCSSClass.call( this ) + ' vjs-resolution-button'; 98 | }; 99 | MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton); 100 | 101 | /** 102 | * Initialize the plugin. 103 | * @param {object} [options] configuration for the plugin 104 | */ 105 | videoJsResolutionSwitcher = function(options) { 106 | var settings = videojs.mergeOptions(defaults, options), 107 | player = this, 108 | groupedSrc = {}, 109 | currentSources = {}, 110 | currentResolutionState = {}; 111 | 112 | /** 113 | * Updates player sources or returns current source URL 114 | * @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}] 115 | * @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources 116 | */ 117 | player.updateSrc = function(src){ 118 | //Return current src if src is not given 119 | if(!src){ return player.src(); } 120 | 121 | // Only add those sources which we can (maybe) play 122 | src = src.filter( function(source) { 123 | try { 124 | return ( player.canPlayType( source.type ) !== '' ); 125 | } catch (e) { 126 | // If a Tech doesn't yet have canPlayType just add it 127 | return true; 128 | } 129 | }); 130 | //Sort sources 131 | this.currentSources = src.sort(compareResolutions); 132 | this.groupedSrc = bucketSources(this.currentSources); 133 | // Pick one by default 134 | var chosen = chooseSrc(this.groupedSrc, this.currentSources); 135 | this.currentResolutionState = { 136 | label: chosen.label, 137 | sources: chosen.sources 138 | }; 139 | 140 | player.trigger('updateSources'); 141 | player.setSourcesSanitized(chosen.sources, chosen.label); 142 | player.trigger('resolutionchange'); 143 | return player; 144 | }; 145 | 146 | /** 147 | * Returns current resolution or sets one when label is specified 148 | * @param {String} [label] label name 149 | * @param {Function} [customSourcePicker] custom function to choose source. Takes 2 arguments: sources, label. Must return player object. 150 | * @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter 151 | */ 152 | player.currentResolution = function(label, customSourcePicker){ 153 | if(label == null) { return this.currentResolutionState; } 154 | 155 | // Lookup sources for label 156 | if(!this.groupedSrc || !this.groupedSrc.label || !this.groupedSrc.label[label]){ 157 | return; 158 | } 159 | var sources = this.groupedSrc.label[label]; 160 | // Remember player state 161 | var currentTime = player.currentTime(); 162 | var isPaused = player.paused(); 163 | 164 | // Hide bigPlayButton 165 | if(!isPaused && this.player_.options_.bigPlayButton){ 166 | this.player_.bigPlayButton.hide(); 167 | } 168 | 169 | // Change player source and wait for loadeddata event, then play video 170 | // loadedmetadata doesn't work right now for flash. 171 | // Probably because of https://github.com/videojs/video-js-swf/issues/124 172 | // If player preload is 'none' and then loadeddata not fired. So, we need timeupdate event for seek handle (timeupdate doesn't work properly with flash) 173 | var handleSeekEvent = 'loadeddata'; 174 | if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') { 175 | handleSeekEvent = 'timeupdate'; 176 | } 177 | player 178 | .setSourcesSanitized(sources, label, customSourcePicker || settings.customSourcePicker) 179 | .one(handleSeekEvent, function() { 180 | player.currentTime(currentTime); 181 | player.handleTechSeeked_(); 182 | if(!isPaused){ 183 | // Start playing and hide loadingSpinner (flash issue ?) 184 | player.play().handleTechSeeked_(); 185 | } 186 | player.trigger('resolutionchange'); 187 | }); 188 | return player; 189 | }; 190 | 191 | /** 192 | * Returns grouped sources by label, resolution and type 193 | * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 194 | */ 195 | player.getGroupedSrc = function(){ 196 | return this.groupedSrc; 197 | }; 198 | 199 | player.setSourcesSanitized = function(sources, label, customSourcePicker) { 200 | this.currentResolutionState = { 201 | label: label, 202 | sources: sources 203 | }; 204 | if(typeof customSourcePicker === 'function'){ 205 | return customSourcePicker(player, sources, label); 206 | } 207 | player.src(sources.map(function(src) { 208 | return {src: src.src, type: src.type, res: src.res}; 209 | })); 210 | return player; 211 | }; 212 | 213 | /** 214 | * Method used for sorting list of sources 215 | * @param {Object} a - source object with res property 216 | * @param {Object} b - source object with res property 217 | * @returns {Number} result of comparation 218 | */ 219 | function compareResolutions(a, b){ 220 | if(!a.res || !b.res){ return 0; } 221 | return (+b.res)-(+a.res); 222 | } 223 | 224 | /** 225 | * Group sources by label, resolution and type 226 | * @param {Array} src Array of sources 227 | * @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } } 228 | */ 229 | function bucketSources(src){ 230 | var resolutions = { 231 | label: {}, 232 | res: {}, 233 | type: {} 234 | }; 235 | src.map(function(source) { 236 | initResolutionKey(resolutions, 'label', source); 237 | initResolutionKey(resolutions, 'res', source); 238 | initResolutionKey(resolutions, 'type', source); 239 | 240 | appendSourceToKey(resolutions, 'label', source); 241 | appendSourceToKey(resolutions, 'res', source); 242 | appendSourceToKey(resolutions, 'type', source); 243 | }); 244 | return resolutions; 245 | } 246 | 247 | function initResolutionKey(resolutions, key, source) { 248 | if(resolutions[key][source[key]] == null) { 249 | resolutions[key][source[key]] = []; 250 | } 251 | } 252 | 253 | function appendSourceToKey(resolutions, key, source) { 254 | resolutions[key][source[key]].push(source); 255 | } 256 | 257 | /** 258 | * Choose src if option.default is specified 259 | * @param {Object} groupedSrc {res: { key: [] }} 260 | * @param {Array} src Array of sources sorted by resolution used to find high and low res 261 | * @returns {Object} {res: string, sources: []} 262 | */ 263 | function chooseSrc(groupedSrc, src){ 264 | var selectedRes = settings['default']; // use array access as default is a reserved keyword 265 | var selectedLabel = ''; 266 | if (selectedRes === 'high') { 267 | selectedRes = src[0].res; 268 | selectedLabel = src[0].label; 269 | } else if (selectedRes === 'low' || selectedRes == null || !groupedSrc.res[selectedRes]) { 270 | // Select low-res if default is low or not set 271 | selectedRes = src[src.length - 1].res; 272 | selectedLabel = src[src.length -1].label; 273 | } else if (groupedSrc.res[selectedRes]) { 274 | selectedLabel = groupedSrc.res[selectedRes][0].label; 275 | } 276 | 277 | return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]}; 278 | } 279 | 280 | function initResolutionForYt(player){ 281 | // Map youtube qualities names 282 | var _yts = { 283 | highres: {res: 1080, label: '1080', yt: 'highres'}, 284 | hd1080: {res: 1080, label: '1080', yt: 'hd1080'}, 285 | hd720: {res: 720, label: '720', yt: 'hd720'}, 286 | large: {res: 480, label: '480', yt: 'large'}, 287 | medium: {res: 360, label: '360', yt: 'medium'}, 288 | small: {res: 240, label: '240', yt: 'small'}, 289 | tiny: {res: 144, label: '144', yt: 'tiny'}, 290 | auto: {res: 0, label: 'auto', yt: 'auto'} 291 | }; 292 | // Overwrite default sourcePicker function 293 | var _customSourcePicker = function(_player, _sources, _label){ 294 | // Note that setPlayebackQuality is a suggestion. YT does not always obey it. 295 | player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt); 296 | player.trigger('updateSources'); 297 | return player; 298 | }; 299 | settings.customSourcePicker = _customSourcePicker; 300 | 301 | // Init resolution 302 | player.tech_.ytPlayer.setPlaybackQuality('auto'); 303 | 304 | // This is triggered when the resolution actually changes 305 | player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(event){ 306 | for(var res in _yts) { 307 | if(res.yt === event.data) { 308 | player.currentResolution(res.label, _customSourcePicker); 309 | return; 310 | } 311 | } 312 | }); 313 | 314 | // We must wait for play event 315 | player.one('play', function(){ 316 | var qualities = player.tech_.ytPlayer.getAvailableQualityLevels(); 317 | var _sources = []; 318 | 319 | qualities.map(function(q){ 320 | _sources.push({ 321 | src: player.src().src, 322 | type: player.src().type, 323 | label: _yts[q].label, 324 | res: _yts[q].res, 325 | _yt: _yts[q].yt 326 | }); 327 | }); 328 | 329 | player.groupedSrc = bucketSources(_sources); 330 | var chosen = {label: 'auto', res: 0, sources: player.groupedSrc.label.auto}; 331 | 332 | this.currentResolutionState = { 333 | label: chosen.label, 334 | sources: chosen.sources 335 | }; 336 | 337 | player.trigger('updateSources'); 338 | player.setSourcesSanitized(chosen.sources, chosen.label, _customSourcePicker); 339 | }); 340 | } 341 | 342 | player.ready(function(){ 343 | if( settings.ui ) { 344 | var menuButton = new ResolutionMenuButton(player, settings); 345 | player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_); 346 | player.controlBar.resolutionSwitcher.dispose = function(){ 347 | this.parentNode.removeChild(this); 348 | }; 349 | } 350 | if(player.options_.sources.length > 1){ 351 | // tech: Html5 and Flash 352 | // Create resolution switcher for videos form tag inside