-
Notifications
You must be signed in to change notification settings - Fork 23
/
switch_view.js
361 lines (321 loc) · 12.9 KB
/
switch_view.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
/*! videojs-resolution-switcher - 2015-7-26
* Copyright (c) 2016 Kasper Moskwiak
* Modified by Pierre Kraft
* Licensed under the Apache-2.0 license. */
(function() {
/* jshint eqnull: true*/
/* global require */
'use strict';
var videojs = null;
if(typeof window.videojs === 'undefined' && typeof require === 'function') {
videojs = require('video.js');
} else {
videojs = window.videojs;
}
(function(window, videojs) {
var defaults = {},
videoJsResolutionSwitcher,
currentResolution = {}, // stores current resolution
menuItemsHolder = {}; // stores menuItems
function setSourcesSanitized(player, sources, label, customSourcePicker) {
currentResolution = {
label: label,
sources: sources
};
if(typeof customSourcePicker === 'function'){
return customSourcePicker(player, sources, label);
}
return player.src(sources.map(function(src) {
return {src: src.src, type: src.type, res: src.res};
}));
}
/*
* Resolution menu item
*/
var MenuItem = videojs.getComponent('MenuItem');
var ResolutionMenuItem = videojs.extend(MenuItem, {
constructor: function(player, options, onClickListener, label){
this.onClickListener = onClickListener;
this.label = label;
// Sets this.player_, this.options_ and initializes the component
MenuItem.call(this, player, options);
this.src = options.src;
this.on('click', this.onClick);
this.on('touchstart', this.onClick);
if (options.initialySelected) {
this.showAsLabel();
this.selected(true);
this.addClass('vjs-selected');
}
},
showAsLabel: function() {
// Change menu button label to the label of this item if the menu button label is provided
if(this.label) {
this.label.innerHTML = this.options_.label;
}
},
onClick: function(customSourcePicker){
this.onClickListener(this);
// Remember player state
var currentTime = this.player_.currentTime();
var isPaused = this.player_.paused();
this.showAsLabel();
// add .current class
this.addClass('vjs-selected');
// Hide bigPlayButton
if(!isPaused){
this.player_.bigPlayButton.hide();
}
if(typeof customSourcePicker !== 'function' &&
typeof this.options_.customSourcePicker === 'function'){
customSourcePicker = this.options_.customSourcePicker;
}
// Change player source and wait for loadeddata event, then play video
// loadedmetadata doesn't work right now for flash.
// Probably because of https://github.com/videojs/video-js-swf/issues/124
// 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)
var handleSeekEvent = 'loadeddata';
if(this.player_.techName_ !== 'Youtube' && this.player_.preload() === 'none' && this.player_.techName_ !== 'Flash') {
handleSeekEvent = 'timeupdate';
}
setSourcesSanitized(this.player_, this.src, this.options_.label, customSourcePicker).one(handleSeekEvent, function() {
this.player_.currentTime(currentTime);
this.player_.handleTechSeeked_();
if(!isPaused){
// Start playing and hide loadingSpinner (flash issue ?)
this.player_.play().handleTechSeeked_();
}
this.player_.trigger('resolutionchange');
});
}
});
/*
* Resolution menu button
*/
var MenuButton = videojs.getComponent('MenuButton');
var ResolutionMenuButton = videojs.extend(MenuButton, {
constructor: function(player, options, settings, label){
this.sources = options.sources;
this.label = label;
this.label.innerHTML = options.initialySelectedLabel;
// Sets this.player_, this.options_ and initializes the component
MenuButton.call(this, player, options, settings);
this.controlText('Quality');
if(settings.dynamicLabel){
this.el().appendChild(label);
}else{
var staticLabel = document.createElement('span');
videojs.addClass(staticLabel, 'vjs-resolution-button-staticlabel');
this.el().appendChild(staticLabel);
}
},
createItems: function(){
var menuItems = [];
var labels = (this.sources && this.sources.label) || {};
var onClickUnselectOthers = function(clickedItem) {
menuItems.map(function(item) {
item.selected(item === clickedItem);
item.removeClass('vjs-selected');
});
};
for (var key in labels) {
if (labels.hasOwnProperty(key)) {
menuItems.push(new ResolutionMenuItem(
this.player_,
{
label: key,
src: labels[key],
initialySelected: key === this.options_.initialySelectedLabel,
customSourcePicker: this.options_.customSourcePicker
},
onClickUnselectOthers,
this.label));
// Store menu item for API calls
menuItemsHolder[key] = menuItems[menuItems.length - 1];
}
}
return menuItems;
}
});
/**
* Initialize the plugin.
* @param {object} [options] configuration for the plugin
*/
videoJsResolutionSwitcher = function(options) {
var settings = videojs.mergeOptions(defaults, options),
player = this,
label = document.createElement('span'),
groupedSrc = {};
videojs.addClass(label, 'vjs-resolution-button-label');
/**
* Updates player sources or returns current source URL
* @param {Array} [src] array of sources [{src: '', type: '', label: '', res: ''}]
* @returns {Object|String|Array} videojs player object if used as setter or current source URL, object, or array of sources
*/
player.updateSrc = function(src){
//Return current src if src is not given
if(!src){ return player.src(); }
// Dispose old resolution menu button before adding new sources
if(player.controlBar.resolutionSwitcher){
player.controlBar.resolutionSwitcher.dispose();
delete player.controlBar.resolutionSwitcher;
}
//Sort sources
src = src.sort(compareResolutions);
groupedSrc = bucketSources(src);
var choosen = chooseSrc(groupedSrc, src);
var menuButton = new ResolutionMenuButton(player, { sources: groupedSrc, initialySelectedLabel: choosen.label , initialySelectedRes: choosen.res , customSourcePicker: settings.customSourcePicker}, settings, label);
videojs.addClass(menuButton.el(), 'vjs-resolution-button');
player.controlBar.resolutionSwitcher = player.controlBar.el_.insertBefore(menuButton.el_, player.controlBar.getChild('fullscreenToggle').el_);
player.controlBar.resolutionSwitcher.dispose = function(){
this.parentNode.removeChild(this);
};
return setSourcesSanitized(player, choosen.sources, choosen.label);
};
/**
* Returns current resolution or sets one when label is specified
* @param {String} [label] label name
* @param {Function} [customSourcePicker] custom function to choose source. Takes 3 arguments: player, sources, label. Must return player object.
* @returns {Object} current resolution object {label: '', sources: []} if used as getter or player object if used as setter
*/
player.currentResolution = function(label, customSourcePicker){
if(label == null) { return currentResolution; }
if(menuItemsHolder[label] != null){
menuItemsHolder[label].onClick(customSourcePicker);
}
return player;
};
/**
* Returns grouped sources by label, resolution and type
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/
player.getGroupedSrc = function(){
return groupedSrc;
};
/**
* Method used for sorting list of sources
* @param {Object} a - source object with res property
* @param {Object} b - source object with res property
* @returns {Number} result of comparation
*/
function compareResolutions(a, b){
if(!a.res || !b.res){ return 0; }
return (+b.res)-(+a.res);
}
/**
* Group sources by label, resolution and type
* @param {Array} src Array of sources
* @returns {Object} grouped sources: { label: { key: [] }, res: { key: [] }, type: { key: [] } }
*/
function bucketSources(src){
var resolutions = {
label: {},
res: {},
type: {}
};
src.map(function(source) {
initResolutionKey(resolutions, 'label', source);
initResolutionKey(resolutions, 'res', source);
initResolutionKey(resolutions, 'type', source);
appendSourceToKey(resolutions, 'label', source);
appendSourceToKey(resolutions, 'res', source);
appendSourceToKey(resolutions, 'type', source);
});
return resolutions;
}
function initResolutionKey(resolutions, key, source) {
if(resolutions[key][source[key]] == null) {
resolutions[key][source[key]] = [];
}
}
function appendSourceToKey(resolutions, key, source) {
resolutions[key][source[key]].push(source);
}
/**
* Choose src if option.default is specified
* @param {Object} groupedSrc {res: { key: [] }}
* @param {Array} src Array of sources sorted by resolution used to find high and low res
* @returns {Object} {res: string, sources: []}
*/
function chooseSrc(groupedSrc, src){
var selectedRes = settings['default']; // use array access as default is a reserved keyword
var selectedLabel = '';
if (selectedRes === 'high') {
selectedRes = src[0].res;
selectedLabel = src[0].label;
} else if (selectedRes === 'low' || selectedRes == null) {
// Select low-res if default is low or not set
selectedRes = src[src.length - 1].res;
selectedLabel = src[src.length -1].label;
} else if (groupedSrc.res[selectedRes]) {
selectedLabel = groupedSrc.res[selectedRes][0].label;
}
if(selectedRes === undefined){
return {res: selectedRes, label: selectedLabel, sources: groupedSrc.label[selectedLabel]};
}
return {res: selectedRes, label: selectedLabel, sources: groupedSrc.res[selectedRes]};
}
function initResolutionForYt(player){
// Init resolution
player.tech_.ytPlayer.setPlaybackQuality('default');
// Capture events
player.tech_.ytPlayer.addEventListener('onPlaybackQualityChange', function(){
player.trigger('resolutionchange');
});
// We must wait for play event
player.one('play', function(){
var qualities = player.tech_.ytPlayer.getAvailableQualityLevels();
// Map youtube qualities names
var _yts = {
highres: {res: 1080, label: '1080', yt: 'highres'},
hd1080: {res: 1080, label: '1080', yt: 'hd1080'},
hd720: {res: 720, label: '720', yt: 'hd720'},
large: {res: 480, label: '480', yt: 'large'},
medium: {res: 360, label: '360', yt: 'medium'},
small: {res: 240, label: '240', yt: 'small'},
tiny: {res: 144, label: '144', yt: 'tiny'},
auto: {res: 0, label: 'auto', yt: 'default'}
};
var _sources = [];
qualities.map(function(q){
_sources.push({
src: player.src().src,
type: player.src().type,
label: _yts[q].label,
res: _yts[q].res,
_yt: _yts[q].yt
});
});
groupedSrc = bucketSources(_sources);
// Overwrite defualt sourcePicer function
var _customSourcePicker = function(_player, _sources, _label){
player.tech_.ytPlayer.setPlaybackQuality(_sources[0]._yt);
return player;
};
var choosen = {label: 'auto', res: 0, sources: groupedSrc.label.auto};
var menuButton = new ResolutionMenuButton(player, {
sources: groupedSrc,
initialySelectedLabel: choosen.label,
initialySelectedRes: choosen.res,
customSourcePicker: _customSourcePicker
}, settings, label);
menuButton.el().classList.add('vjs-resolution-button');
player.controlBar.resolutionSwitcher = player.controlBar.addChild(menuButton);
});
}
player.ready(function(){
if(player.options_.sources.length > 1){
// tech: Html5 and Flash
// Create resolution switcher for videos form <source> tag inside <video>
player.updateSrc(player.options_.sources);
}
if(player.techName_ === 'Youtube'){
// tech: YouTube
initResolutionForYt(player);
}
});
};
// register the plugin
videojs.plugin('videoJsResolutionSwitcher', videoJsResolutionSwitcher);
})(window, videojs);
})();