├── .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 [](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