@oat-sa/tao-core-ui 1.5.4 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ckeditor/ckConfigurator.js +9 -1
- package/dist/mediaEditor/mediaEditorComponent.js +5 -3
- package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +46 -25
- package/dist/mediaplayer/css/player.css +104 -14
- package/dist/mediaplayer/css/player.css.map +1 -1
- package/dist/mediaplayer/players/html5.js +767 -0
- package/dist/mediaplayer/players/youtube.js +470 -0
- package/dist/mediaplayer/players.js +35 -0
- package/dist/mediaplayer/support.js +134 -0
- package/dist/mediaplayer/utils/reminder.js +198 -0
- package/dist/mediaplayer/utils/timeObserver.js +149 -0
- package/dist/mediaplayer/youtubeManager.js +177 -0
- package/dist/mediaplayer.js +1251 -1912
- package/dist/previewer.js +25 -19
- package/package.json +1 -1
- package/scss/basic.scss +1 -0
- package/scss/inc/_jquery.nouislider.scss +254 -0
- package/src/ckeditor/ckConfigurator.js +10 -1
- package/src/itemButtonList/css/item-button-list.css +225 -0
- package/src/itemButtonList/css/item-button-list.css.map +1 -0
- package/src/mediaEditor/mediaEditorComponent.js +25 -26
- package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +83 -63
- package/src/mediaplayer/css/player.css +104 -14
- package/src/mediaplayer/css/player.css.map +1 -1
- package/src/mediaplayer/players/html5.js +564 -0
- package/src/mediaplayer/players/youtube.js +323 -0
- package/src/mediaplayer/players.js +29 -0
- package/src/mediaplayer/scss/player.scss +125 -16
- package/src/mediaplayer/support.js +126 -0
- package/src/mediaplayer/tpl/audio.tpl +6 -0
- package/src/mediaplayer/tpl/player.tpl +11 -32
- package/src/mediaplayer/tpl/source.tpl +1 -0
- package/src/mediaplayer/tpl/video.tpl +6 -0
- package/src/mediaplayer/tpl/youtube.tpl +1 -0
- package/src/mediaplayer/utils/reminder.js +184 -0
- package/src/mediaplayer/utils/timeObserver.js +143 -0
- package/src/mediaplayer/youtubeManager.js +161 -0
- package/src/mediaplayer.js +1217 -1901
- package/src/previewer.js +40 -33
- package/src/searchModal/css/advancedSearch.css +190 -0
- package/src/searchModal/css/advancedSearch.css.map +1 -0
- package/src/searchModal/css/searchModal.css +506 -0
- package/src/searchModal/css/searchModal.css.map +1 -0
package/src/mediaplayer.js
CHANGED
|
@@ -13,10 +13,7 @@
|
|
|
13
13
|
* along with this program; if not, write to the Free Software
|
|
14
14
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
15
15
|
*
|
|
16
|
-
* Copyright (c) 2015 (original work) Open Assessment Technologies SA ;
|
|
17
|
-
*/
|
|
18
|
-
/**
|
|
19
|
-
* @author Jean-Sébastien Conan <jean-sebastien.conan@vesperiagroup.com>
|
|
16
|
+
* Copyright (c) 2015-2021 (original work) Open Assessment Technologies SA ;
|
|
20
17
|
*/
|
|
21
18
|
|
|
22
19
|
import $ from 'jquery';
|
|
@@ -26,141 +23,77 @@ import UrlParser from 'util/urlParser';
|
|
|
26
23
|
import eventifier from 'core/eventifier';
|
|
27
24
|
import mimetype from 'core/mimetype';
|
|
28
25
|
import store from 'core/store';
|
|
26
|
+
import support from 'ui/mediaplayer/support';
|
|
27
|
+
import players from 'ui/mediaplayer/players';
|
|
29
28
|
import playerTpl from 'ui/mediaplayer/tpl/player';
|
|
30
29
|
import 'ui/mediaplayer/css/player.css';
|
|
31
30
|
import 'nouislider';
|
|
32
31
|
|
|
33
|
-
/**
|
|
34
|
-
* Enable the debug mode
|
|
35
|
-
* @type {boolean}
|
|
36
|
-
* @private
|
|
37
|
-
*/
|
|
38
|
-
var _debugMode = false;
|
|
39
|
-
|
|
40
32
|
/**
|
|
41
33
|
* CSS namespace
|
|
42
34
|
* @type {String}
|
|
43
|
-
* @private
|
|
44
|
-
*/
|
|
45
|
-
var _ns = '.mediaplayer';
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* A Regex to extract ID from Youtube URLs
|
|
49
|
-
* @type {RegExp}
|
|
50
|
-
* @private
|
|
51
35
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* A Regex to detect Apple mobile browsers
|
|
56
|
-
* @type {RegExp}
|
|
57
|
-
* @private
|
|
58
|
-
*/
|
|
59
|
-
var _reAppleMobiles = /ip(hone|od)/i;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Array slice method needed to slice arguments
|
|
63
|
-
* @type {Function}
|
|
64
|
-
* @private
|
|
65
|
-
*/
|
|
66
|
-
var _slice = [].slice;
|
|
36
|
+
const ns = '.mediaplayer';
|
|
67
37
|
|
|
68
38
|
/**
|
|
69
39
|
* Minimum value of the volume
|
|
70
40
|
* @type {Number}
|
|
71
|
-
* @private
|
|
72
41
|
*/
|
|
73
|
-
|
|
42
|
+
const volumeMin = 0;
|
|
74
43
|
|
|
75
44
|
/**
|
|
76
45
|
* Maximum value of the volume
|
|
77
46
|
* @type {Number}
|
|
78
|
-
* @private
|
|
79
|
-
*/
|
|
80
|
-
var _volumeMax = 100;
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Range value of the volume
|
|
84
|
-
* @type {Number}
|
|
85
|
-
* @private
|
|
86
47
|
*/
|
|
87
|
-
|
|
48
|
+
const volumeMax = 100;
|
|
88
49
|
|
|
89
50
|
/**
|
|
90
|
-
* Threshold (
|
|
51
|
+
* Threshold (minimum required space above the player) to display the volume
|
|
91
52
|
* above the bar.
|
|
92
53
|
* @type {Number}
|
|
93
54
|
*/
|
|
94
|
-
|
|
55
|
+
const volumePositionThreshold = 200;
|
|
95
56
|
|
|
96
57
|
/**
|
|
97
58
|
* Some default values
|
|
98
59
|
* @type {Object}
|
|
99
|
-
* @private
|
|
100
60
|
*/
|
|
101
|
-
|
|
61
|
+
const defaults = {
|
|
102
62
|
type: 'video/mp4',
|
|
103
63
|
video: {
|
|
104
|
-
width:
|
|
105
|
-
height:
|
|
106
|
-
minWidth: 200,
|
|
107
|
-
minHeight: 200
|
|
64
|
+
width: '100%',
|
|
65
|
+
height: 'auto'
|
|
108
66
|
},
|
|
109
67
|
audio: {
|
|
110
|
-
width:
|
|
111
|
-
height:
|
|
112
|
-
|
|
113
|
-
|
|
68
|
+
width: '100%',
|
|
69
|
+
height: 'auto'
|
|
70
|
+
},
|
|
71
|
+
youtube: {
|
|
72
|
+
width: 640,
|
|
73
|
+
height: 360
|
|
114
74
|
},
|
|
115
75
|
options: {
|
|
116
|
-
volume:
|
|
76
|
+
volume: 80,
|
|
117
77
|
startMuted: false,
|
|
118
78
|
maxPlays: 0,
|
|
119
79
|
replayTimeout: 0,
|
|
120
80
|
canPause: true,
|
|
121
81
|
canSeek: true,
|
|
122
82
|
loop: false,
|
|
123
|
-
autoStart: false
|
|
83
|
+
autoStart: false,
|
|
84
|
+
preview: true,
|
|
85
|
+
debug: false
|
|
124
86
|
}
|
|
125
87
|
};
|
|
126
88
|
|
|
127
|
-
/**
|
|
128
|
-
* A list of MIME types with codec declaration
|
|
129
|
-
* @type {Object}
|
|
130
|
-
* @private
|
|
131
|
-
*/
|
|
132
|
-
var _mimeTypes = {
|
|
133
|
-
// video
|
|
134
|
-
'video/webm': 'video/webm; codecs="vp8, vorbis"',
|
|
135
|
-
'video/mp4': 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"',
|
|
136
|
-
'video/ogg': 'video/ogg; codecs="theora, vorbis"',
|
|
137
|
-
// audio
|
|
138
|
-
'audio/mpeg': 'audio/mpeg;',
|
|
139
|
-
'audio/mp4': 'audio/mp4; codecs="mp4a.40.5"',
|
|
140
|
-
'audio/ogg': 'audio/ogg; codecs="vorbis"',
|
|
141
|
-
'audio/wav': 'audio/wav; codecs="1"'
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Extracts the ID of a Youtube video from an URL
|
|
146
|
-
* @param {String} url
|
|
147
|
-
* @returns {String}
|
|
148
|
-
* @private
|
|
149
|
-
*/
|
|
150
|
-
var _extractYoutubeId = function(url) {
|
|
151
|
-
var res = _reYoutube.exec(url);
|
|
152
|
-
return (res && res[2]) || url;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
89
|
/**
|
|
156
90
|
* Ensures a value is a number
|
|
157
91
|
* @param {Number|String} value
|
|
158
92
|
* @returns {Number}
|
|
159
|
-
* @private
|
|
160
93
|
*/
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
return isFinite(
|
|
94
|
+
const ensureNumber = value => {
|
|
95
|
+
const floatValue = parseFloat(value);
|
|
96
|
+
return isFinite(floatValue) ? floatValue : 0;
|
|
164
97
|
};
|
|
165
98
|
|
|
166
99
|
/**
|
|
@@ -168,12 +101,11 @@ var _ensureNumber = function(value) {
|
|
|
168
101
|
* @param {Number} n
|
|
169
102
|
* @param {Number} len
|
|
170
103
|
* @returns {String}
|
|
171
|
-
* @private
|
|
172
104
|
*/
|
|
173
|
-
|
|
174
|
-
|
|
105
|
+
const leadingZero = (n, len) => {
|
|
106
|
+
let value = n.toString();
|
|
175
107
|
while (value.length < len) {
|
|
176
|
-
value =
|
|
108
|
+
value = `0${value}`;
|
|
177
109
|
}
|
|
178
110
|
return value;
|
|
179
111
|
};
|
|
@@ -182,19 +114,18 @@ var _leadingZero = function(n, len) {
|
|
|
182
114
|
* Formats a time value to string
|
|
183
115
|
* @param {Number} time
|
|
184
116
|
* @returns {String}
|
|
185
|
-
* @private
|
|
186
117
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
118
|
+
const timerFormat = time => {
|
|
119
|
+
const seconds = Math.floor(time % 60);
|
|
120
|
+
const minutes = Math.floor(time / 60) % 60;
|
|
121
|
+
const hours = Math.floor(time / 3600);
|
|
122
|
+
const parts = [];
|
|
192
123
|
|
|
193
124
|
if (hours) {
|
|
194
125
|
parts.push(hours);
|
|
195
126
|
}
|
|
196
|
-
parts.push(
|
|
197
|
-
parts.push(
|
|
127
|
+
parts.push(leadingZero(minutes, 2));
|
|
128
|
+
parts.push(leadingZero(seconds, 2));
|
|
198
129
|
|
|
199
130
|
return parts.join(':');
|
|
200
131
|
};
|
|
@@ -203,9 +134,8 @@ var _timerFormat = function(time) {
|
|
|
203
134
|
* Checks if a type needs to be adjusted
|
|
204
135
|
* @param {String} type
|
|
205
136
|
* @returns {Boolean}
|
|
206
|
-
* @private
|
|
207
137
|
*/
|
|
208
|
-
|
|
138
|
+
const needTypeAdjust = type => {
|
|
209
139
|
return 'string' === typeof type && type.indexOf('application') === 0;
|
|
210
140
|
};
|
|
211
141
|
|
|
@@ -213,12 +143,11 @@ var _needTypeAdjust = function(type) {
|
|
|
213
143
|
* Adjust bad type by apllying heuristic on URI
|
|
214
144
|
* @param {Object|String} source
|
|
215
145
|
* @returns {String}
|
|
216
|
-
* @private
|
|
217
146
|
*/
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
147
|
+
const getAdjustedType = source => {
|
|
148
|
+
let type = 'video/ogg';
|
|
149
|
+
const url = (source && source.src) || source;
|
|
150
|
+
const ext = url && url.substr(-4);
|
|
222
151
|
if (ext === '.ogg' || ext === '.oga') {
|
|
223
152
|
type = 'audio/ogg';
|
|
224
153
|
}
|
|
@@ -229,11 +158,10 @@ var _getAdjustedType = function(source) {
|
|
|
229
158
|
* Extract a list of media sources from a config object
|
|
230
159
|
* @param {Object} config
|
|
231
160
|
* @returns {Array}
|
|
232
|
-
* @private
|
|
233
161
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
162
|
+
const configToSources = config => {
|
|
163
|
+
let sources = config.sources || [];
|
|
164
|
+
let url = config.url;
|
|
237
165
|
|
|
238
166
|
if (!_.isArray(sources)) {
|
|
239
167
|
sources = [sources];
|
|
@@ -251,1998 +179,1393 @@ var _configToSources = function(config) {
|
|
|
251
179
|
|
|
252
180
|
/**
|
|
253
181
|
* Checks if the browser can play media
|
|
254
|
-
* @param {
|
|
255
|
-
* @param {String} [mimeType] An optional MIME type to precise the support
|
|
182
|
+
* @param {String} sizeProps Width or Height
|
|
256
183
|
* @returns {Boolean}
|
|
257
|
-
* @private
|
|
258
184
|
*/
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (mimeType && support) {
|
|
262
|
-
support = !!media.canPlayType(_mimeTypes[mimeType] || mimeType).replace(/no/, '');
|
|
263
|
-
}
|
|
264
|
-
return support;
|
|
185
|
+
const isResponsiveSize = sizeProps => {
|
|
186
|
+
return /%/.test(sizeProps) || sizeProps === 'auto';
|
|
265
187
|
};
|
|
266
188
|
|
|
267
189
|
/**
|
|
268
|
-
*
|
|
269
|
-
* @
|
|
270
|
-
* @
|
|
190
|
+
* Builds a media player instance
|
|
191
|
+
* @param {Object} config
|
|
192
|
+
* @param {String} config.type - The type of media to play, say `audio`, `video`, or `youtube`. The default is `video`.
|
|
193
|
+
* It might also contain the MIME type of the media as a shorthand.
|
|
194
|
+
* @param {String|Array} [config.url] - The URL to the media. If several media are proposed as alternatives,
|
|
195
|
+
* please look at the `sources` option instead.
|
|
196
|
+
* @param {String} [config.mimeType] - The MIME type of the media. If omitted, the player will try to extract it
|
|
197
|
+
* from the `type` property, otherwise it will request the server to get the content-type.
|
|
198
|
+
* @param {Array} [config.sources] - A list of URL if several media can be proposed. Each entry may be either a
|
|
199
|
+
* string (single URL), or an object containing both the URL and the MIME type ({src: string, type: string}).
|
|
200
|
+
* @param {String|jQuery|HTMLElement} [config.renderTo] - An optional container in which renders the player
|
|
201
|
+
* @param {Boolean} [config.canSeek] - The player allows to reach an arbitrary position within the media using the duration bar
|
|
202
|
+
* @param {Boolean} [config.loop] - The media will be played continuously
|
|
203
|
+
* @param {Boolean} [config.canPause] - The player can be paused
|
|
204
|
+
* @param {Boolean} [config.startMuted] - The player should be initially muted
|
|
205
|
+
* @param {Boolean} [config.autoStart] - The player starts as soon as it is displayed
|
|
206
|
+
* @param {Number} [config.autoStartAt] - The time position at which the player should start
|
|
207
|
+
* @param {Number} [config.maxPlays] - Sets a few number of plays (default: infinite)
|
|
208
|
+
* @param {Number} [config.replayTimeout] - disable the possibility to replay a media after this timeout, in seconds (default: 0)
|
|
209
|
+
* @param {Number} [config.volume] - Sets the sound volume (default: 80)
|
|
210
|
+
* @param {Number} [config.width] - Sets the width of the player (default: depends on media type)
|
|
211
|
+
* @param {Number} [config.height] - Sets the height of the player (default: depends on media type)
|
|
212
|
+
* @param {Boolean} [config.preview] - Enables the media preview (load media metadata)
|
|
213
|
+
* @param {Boolean} [config.debug] - Enables the debug mode
|
|
214
|
+
* @param {number} [config.config.stalledDetectionDelay] - The delay before considering a media is stalled
|
|
215
|
+
* @event render - Event triggered when the player is rendering
|
|
216
|
+
* @event error - Event triggered when the player throws an unrecoverable error
|
|
217
|
+
* @event ready - Event triggered when the player is fully ready
|
|
218
|
+
* @event play - Event triggered when the playback is starting
|
|
219
|
+
* @event update - Event triggered while the player is playing
|
|
220
|
+
* @event pause - Event triggered when the playback is paused
|
|
221
|
+
* @event ended - Event triggered when the playback is ended
|
|
222
|
+
* @event limitreached - Event triggered when the play limit has been reached
|
|
223
|
+
* @event destroy - Event triggered when the player is destroying
|
|
224
|
+
* @returns {mediaplayer}
|
|
271
225
|
*/
|
|
272
|
-
|
|
226
|
+
function mediaplayerFactory(config) {
|
|
273
227
|
/**
|
|
274
|
-
*
|
|
275
|
-
* @
|
|
276
|
-
* @param {String} [mime] A media MIME type to check
|
|
277
|
-
* @returns {Boolean}
|
|
228
|
+
* Defines a media player object
|
|
229
|
+
* @type {Object}
|
|
278
230
|
*/
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
231
|
+
const mediaplayer = {
|
|
232
|
+
/**
|
|
233
|
+
* Initializes the media player
|
|
234
|
+
* @param {Object} config
|
|
235
|
+
* @returns {mediaplayer}
|
|
236
|
+
*/
|
|
237
|
+
init(config) {
|
|
238
|
+
// load the config set, discard null values in order to allow defaults to be set
|
|
239
|
+
this.config = _.omit(config || {}, value => typeof value === 'undefined' || value === null);
|
|
240
|
+
_.defaults(this.config, defaults.options);
|
|
241
|
+
if (!this.config.mimeType && 'string' === typeof this.config.type && this.config.type.indexOf('/') > 0) {
|
|
242
|
+
this.config.mimeType = this.config.type;
|
|
289
243
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
244
|
+
this._setType(this.config.type || defaults.type);
|
|
245
|
+
|
|
246
|
+
this._reset();
|
|
247
|
+
this._updateVolumeFromStore();
|
|
248
|
+
this._initEvents();
|
|
249
|
+
this._initSources(() => {
|
|
250
|
+
if (!this.is('youtube')) {
|
|
251
|
+
_.forEach(this.config.sources, source => {
|
|
252
|
+
if (source && source.type && source.type.indexOf('audio') === 0) {
|
|
253
|
+
this._setType(source.type);
|
|
254
|
+
this._initType();
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
if (this.config.renderTo) {
|
|
260
|
+
_.defer(() => this.render());
|
|
261
|
+
}
|
|
262
|
+
});
|
|
293
263
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
* @param {String} [mime] A media MIME type to check
|
|
297
|
-
* @returns {Boolean}
|
|
298
|
-
*/
|
|
299
|
-
canPlayAudio: function canPlayAudio(mime) {
|
|
300
|
-
if (!this._mediaAudio) {
|
|
301
|
-
this._mediaAudio = document.createElement('audio');
|
|
302
|
-
}
|
|
264
|
+
return this;
|
|
265
|
+
},
|
|
303
266
|
|
|
304
|
-
|
|
305
|
-
|
|
267
|
+
/**
|
|
268
|
+
* Uninstalls the media player
|
|
269
|
+
* @returns {mediaplayer}
|
|
270
|
+
*/
|
|
271
|
+
destroy() {
|
|
272
|
+
/**
|
|
273
|
+
* Triggers a destroy event
|
|
274
|
+
* @event mediaplayer#destroy
|
|
275
|
+
*/
|
|
276
|
+
this.trigger('destroy');
|
|
306
277
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
* @returns {Boolean}
|
|
311
|
-
*/
|
|
312
|
-
canPlayVideo: function canPlayVideo(mime) {
|
|
313
|
-
if (!this._mediaVideo) {
|
|
314
|
-
this._mediaVideo = document.createElement('video');
|
|
315
|
-
}
|
|
278
|
+
if (this.player) {
|
|
279
|
+
this.player.destroy();
|
|
280
|
+
}
|
|
316
281
|
|
|
317
|
-
|
|
318
|
-
|
|
282
|
+
if (this.$component) {
|
|
283
|
+
this._unbindEvents();
|
|
284
|
+
this._destroySlider(this.$seekSlider);
|
|
285
|
+
this._destroySlider(this.$volumeSlider);
|
|
319
286
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
* @returns {Boolean}
|
|
323
|
-
*/
|
|
324
|
-
canControl: function canControl() {
|
|
325
|
-
return !_reAppleMobiles.test(navigator.userAgent);
|
|
326
|
-
}
|
|
327
|
-
};
|
|
287
|
+
this.$component.remove();
|
|
288
|
+
}
|
|
328
289
|
|
|
329
|
-
|
|
330
|
-
* A local manager for Youtube players.
|
|
331
|
-
* Relies on https://developers.google.com/youtube/iframe_api_reference
|
|
332
|
-
* @type {Object}
|
|
333
|
-
* @private
|
|
334
|
-
*/
|
|
335
|
-
var _youtubeManager = {
|
|
336
|
-
/**
|
|
337
|
-
* The Youtube API injection state
|
|
338
|
-
* @type {Boolean}
|
|
339
|
-
*/
|
|
340
|
-
injected: false,
|
|
290
|
+
this._reset();
|
|
341
291
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
* @type {Boolean}
|
|
345
|
-
*/
|
|
346
|
-
ready: false,
|
|
292
|
+
return this;
|
|
293
|
+
},
|
|
347
294
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Renders the media player according to the media type
|
|
297
|
+
* @param {String|jQuery|HTMLElement} [to]
|
|
298
|
+
* @returns {mediaplayer}
|
|
299
|
+
*/
|
|
300
|
+
render(to) {
|
|
301
|
+
const renderTo = to || this.config.renderTo || this.$container;
|
|
353
302
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
* @param {String|jQuery|HTMLElement} elem
|
|
357
|
-
* @param {Object} player
|
|
358
|
-
* @param {Object} [options]
|
|
359
|
-
* @param {Boolean} [options.controls]
|
|
360
|
-
*/
|
|
361
|
-
add: function add(elem, player, options) {
|
|
362
|
-
if (this.ready) {
|
|
363
|
-
this.create(elem, player, options);
|
|
364
|
-
} else {
|
|
365
|
-
this.pending.push([elem, player, options]);
|
|
366
|
-
|
|
367
|
-
if (!this.injected) {
|
|
368
|
-
this.injectApi();
|
|
303
|
+
if (this.$component) {
|
|
304
|
+
this.destroy();
|
|
369
305
|
}
|
|
370
|
-
}
|
|
371
|
-
},
|
|
372
306
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
remove: function remove(elem, player) {
|
|
379
|
-
var pending = this.pending;
|
|
380
|
-
_.forEach(pending, function(args, idx) {
|
|
381
|
-
if (args && elem === args[0] && player === args[1]) {
|
|
382
|
-
pending[idx] = null;
|
|
307
|
+
this._initState();
|
|
308
|
+
this._buildDom();
|
|
309
|
+
if (this.config.preview) {
|
|
310
|
+
this._updateDuration(0);
|
|
311
|
+
this._updatePosition(0);
|
|
383
312
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (!this.ready) {
|
|
398
|
-
return this.add(elem, player, options);
|
|
399
|
-
}
|
|
313
|
+
this._bindEvents();
|
|
314
|
+
this._playingState(false, true);
|
|
315
|
+
this._initPlayer();
|
|
316
|
+
this._initSize();
|
|
317
|
+
|
|
318
|
+
// Resize for old items with defined height to avoid big jump
|
|
319
|
+
if (this.config.height && this.config.height !== 'auto') {
|
|
320
|
+
this.resize('100%', 'auto');
|
|
321
|
+
} else {
|
|
322
|
+
this.resize(this.config.width, this.config.height);
|
|
323
|
+
}
|
|
324
|
+
this.config.is.rendered = true;
|
|
400
325
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
326
|
+
if (renderTo) {
|
|
327
|
+
this.$container = $(renderTo).append(this.$component);
|
|
328
|
+
}
|
|
404
329
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
height: $elem.width(),
|
|
409
|
-
width: $elem.height(),
|
|
410
|
-
videoId: $elem.data('videoId'),
|
|
411
|
-
playerVars: {
|
|
412
|
-
//hd: true,
|
|
413
|
-
autoplay: 0,
|
|
414
|
-
controls: options.controls ? 1 : 0,
|
|
415
|
-
rel: 0,
|
|
416
|
-
showinfo: 0,
|
|
417
|
-
wmode: 'transparent',
|
|
418
|
-
modestbranding: 1,
|
|
419
|
-
disablekb: 1,
|
|
420
|
-
playsinline: 1,
|
|
421
|
-
enablejsapi: 1,
|
|
422
|
-
origin: location.hostname
|
|
423
|
-
},
|
|
424
|
-
events: {
|
|
425
|
-
onReady: player.onReady.bind(player),
|
|
426
|
-
onStateChange: player.onStateChange.bind(player)
|
|
330
|
+
// add class if it is stalled
|
|
331
|
+
if (this.is('stalled')) {
|
|
332
|
+
this._setState('stalled', true);
|
|
427
333
|
}
|
|
428
|
-
});
|
|
429
|
-
},
|
|
430
334
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Triggers a render event
|
|
337
|
+
* @event mediaplayer#render
|
|
338
|
+
* @param {jQuery} $component
|
|
339
|
+
*/
|
|
340
|
+
this.trigger('render', this.$component);
|
|
341
|
+
|
|
342
|
+
return this;
|
|
343
|
+
},
|
|
437
344
|
|
|
438
|
-
|
|
439
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Reloads media player after it was stalled
|
|
347
|
+
*/
|
|
348
|
+
reload() {
|
|
349
|
+
/**
|
|
350
|
+
* Triggers a reload event
|
|
351
|
+
* @event mediaplayer#reload
|
|
352
|
+
*/
|
|
353
|
+
this.trigger('reload');
|
|
440
354
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
self.create.apply(self, args);
|
|
355
|
+
if (this.player) {
|
|
356
|
+
this.player.recover();
|
|
444
357
|
}
|
|
445
|
-
|
|
446
|
-
|
|
358
|
+
this._setState('stalled', false);
|
|
359
|
+
this.setInitialStates();
|
|
360
|
+
},
|
|
447
361
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
362
|
+
/**
|
|
363
|
+
* Set initial states
|
|
364
|
+
*/
|
|
365
|
+
setInitialStates() {
|
|
366
|
+
if (!this.is('stalled')) {
|
|
367
|
+
this._setState('ready', true);
|
|
368
|
+
}
|
|
369
|
+
this._setState('canplay', true);
|
|
370
|
+
this._setState('canpause', this.config.canPause);
|
|
371
|
+
this._setState('canseek', this.config.canSeek);
|
|
372
|
+
this._setState('loading', false);
|
|
373
|
+
},
|
|
459
374
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
setTimeout(check, 100);
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
check();
|
|
473
|
-
});
|
|
474
|
-
}
|
|
375
|
+
/**
|
|
376
|
+
* Sets the start position inside the media
|
|
377
|
+
* @param {Number} time - The start position in seconds
|
|
378
|
+
* @param {*} [internal] - Internal use
|
|
379
|
+
* @returns {mediaplayer}
|
|
380
|
+
*/
|
|
381
|
+
seek(time, internal) {
|
|
382
|
+
if (this._canPlay()) {
|
|
383
|
+
this._updatePosition(time, internal);
|
|
475
384
|
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
};
|
|
385
|
+
this.execute('seek', this.position);
|
|
479
386
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
var _youtubePlayer = function(mediaplayer) {
|
|
486
|
-
var $media;
|
|
487
|
-
var media;
|
|
488
|
-
var player;
|
|
489
|
-
var interval;
|
|
490
|
-
var destroyed;
|
|
491
|
-
var initWidth, initHeight;
|
|
492
|
-
|
|
493
|
-
function loopEvents(callback) {
|
|
494
|
-
_.forEach(
|
|
495
|
-
['onStateChange', 'onPlaybackQualityChange', 'onPlaybackRateChange', 'onError', 'onApiChange'],
|
|
496
|
-
callback
|
|
497
|
-
);
|
|
498
|
-
}
|
|
387
|
+
if (!this.is('ready')) {
|
|
388
|
+
this.autoStartAt = this.position;
|
|
389
|
+
}
|
|
390
|
+
this.loop = !!this.config.loop;
|
|
391
|
+
}
|
|
499
392
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
init: function _youtubePlayerInit() {
|
|
503
|
-
$media = mediaplayer.$media;
|
|
504
|
-
media = null;
|
|
505
|
-
destroyed = false;
|
|
393
|
+
return this;
|
|
394
|
+
},
|
|
506
395
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
396
|
+
/**
|
|
397
|
+
* Plays the media
|
|
398
|
+
* @param {Number} [time] - An optional start position in seconds
|
|
399
|
+
* @returns {mediaplayer}
|
|
400
|
+
*/
|
|
401
|
+
play(time) {
|
|
402
|
+
if (this._canPlay()) {
|
|
403
|
+
if (typeof time !== 'undefined') {
|
|
404
|
+
this.seek(time);
|
|
511
405
|
}
|
|
512
406
|
|
|
513
|
-
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
onReady: function _youtubePlayerOnReady(event) {
|
|
517
|
-
var callbacks = this._callbacks;
|
|
407
|
+
this.execute('play');
|
|
518
408
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
409
|
+
if (!this.is('ready')) {
|
|
410
|
+
this.autoStart = true;
|
|
411
|
+
}
|
|
522
412
|
|
|
523
|
-
|
|
524
|
-
if (_debugMode) {
|
|
525
|
-
// install debug logger
|
|
526
|
-
loopEvents(function(ev) {
|
|
527
|
-
media.addEventListener(ev, function(e) {
|
|
528
|
-
window.console.log(ev, e);
|
|
529
|
-
});
|
|
530
|
-
});
|
|
531
|
-
}
|
|
413
|
+
this.loop = !!this.config.loop;
|
|
532
414
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
415
|
+
if (this.timerId) {
|
|
416
|
+
cancelAnimationFrame(this.timerId);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
536
419
|
|
|
537
|
-
|
|
420
|
+
return this;
|
|
421
|
+
},
|
|
538
422
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
onStateChange: function _youtubePlayerOnStateChange(event) {
|
|
550
|
-
this.stopPolling();
|
|
551
|
-
|
|
552
|
-
if (!destroyed) {
|
|
553
|
-
switch (event.data) {
|
|
554
|
-
// ended
|
|
555
|
-
case 0:
|
|
556
|
-
mediaplayer._onEnd();
|
|
557
|
-
break;
|
|
558
|
-
|
|
559
|
-
// playing
|
|
560
|
-
case 1:
|
|
561
|
-
mediaplayer._onPlay();
|
|
562
|
-
this.startPolling();
|
|
563
|
-
break;
|
|
564
|
-
|
|
565
|
-
// paused
|
|
566
|
-
case 2:
|
|
567
|
-
mediaplayer._onPause();
|
|
568
|
-
break;
|
|
569
|
-
}
|
|
423
|
+
/**
|
|
424
|
+
* Pauses the media
|
|
425
|
+
* @param {Number} [time] - An optional time position in seconds
|
|
426
|
+
* @returns {mediaplayer}
|
|
427
|
+
*/
|
|
428
|
+
pause(time) {
|
|
429
|
+
if (this._canPause()) {
|
|
430
|
+
if (typeof time !== 'undefined') {
|
|
431
|
+
this.seek(time);
|
|
570
432
|
}
|
|
571
|
-
},
|
|
572
433
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
434
|
+
this.execute('pause');
|
|
435
|
+
|
|
436
|
+
if (!this.is('ready')) {
|
|
437
|
+
this.autoStart = false;
|
|
577
438
|
}
|
|
578
|
-
}
|
|
439
|
+
}
|
|
579
440
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
mediaplayer._onTimeUpdate();
|
|
583
|
-
}, mediaplayerFactory.youtubePolling);
|
|
584
|
-
},
|
|
441
|
+
return this;
|
|
442
|
+
},
|
|
585
443
|
|
|
586
|
-
|
|
587
|
-
|
|
444
|
+
/**
|
|
445
|
+
* Resumes the media
|
|
446
|
+
* @returns {mediaplayer}
|
|
447
|
+
*/
|
|
448
|
+
resume() {
|
|
449
|
+
if (this._canResume()) {
|
|
450
|
+
this.play();
|
|
451
|
+
}
|
|
588
452
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
media.removeEventListener(ev);
|
|
592
|
-
});
|
|
593
|
-
media.destroy();
|
|
594
|
-
} else {
|
|
595
|
-
_youtubeManager.remove($media, this);
|
|
596
|
-
}
|
|
453
|
+
return this;
|
|
454
|
+
},
|
|
597
455
|
|
|
598
|
-
|
|
456
|
+
/**
|
|
457
|
+
* Stops the playback
|
|
458
|
+
* @returns {mediaplayer}
|
|
459
|
+
*/
|
|
460
|
+
stop() {
|
|
461
|
+
this.loop = false;
|
|
462
|
+
this.execute('stop');
|
|
599
463
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
464
|
+
if (!this.is('ready')) {
|
|
465
|
+
this.autoStart = false;
|
|
466
|
+
}
|
|
603
467
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
return media.getCurrentTime();
|
|
607
|
-
}
|
|
608
|
-
return 0;
|
|
609
|
-
},
|
|
468
|
+
return this;
|
|
469
|
+
},
|
|
610
470
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Starts the media
|
|
473
|
+
* @returns {mediaplayer}
|
|
474
|
+
*/
|
|
475
|
+
start() {
|
|
476
|
+
this._setState('preview', true);
|
|
477
|
+
this._setState('loading', true);
|
|
478
|
+
this.play();
|
|
479
|
+
},
|
|
617
480
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
},
|
|
481
|
+
/**
|
|
482
|
+
* Restarts the media from the beginning
|
|
483
|
+
* @returns {mediaplayer}
|
|
484
|
+
*/
|
|
485
|
+
restart() {
|
|
486
|
+
this.play(0);
|
|
625
487
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
media.setVolume(((parseFloat(value) - _volumeMin) / _volumeRange) * 100);
|
|
629
|
-
}
|
|
630
|
-
},
|
|
488
|
+
return this;
|
|
489
|
+
},
|
|
631
490
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
} else {
|
|
639
|
-
initWidth = width;
|
|
640
|
-
initHeight = height;
|
|
641
|
-
}
|
|
642
|
-
},
|
|
491
|
+
/**
|
|
492
|
+
* Rewind the media to the beginning
|
|
493
|
+
* @returns {mediaplayer}
|
|
494
|
+
*/
|
|
495
|
+
rewind() {
|
|
496
|
+
this.seek(0);
|
|
643
497
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
media.seekTo(parseFloat(value), true);
|
|
647
|
-
}
|
|
648
|
-
},
|
|
498
|
+
return this;
|
|
499
|
+
},
|
|
649
500
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Mutes the media
|
|
503
|
+
* @param {Boolean} [state] - A flag to set the mute state (default: true)
|
|
504
|
+
* @returns {mediaplayer}
|
|
505
|
+
*/
|
|
506
|
+
mute(state) {
|
|
507
|
+
if (typeof state === 'undefined') {
|
|
508
|
+
state = true;
|
|
509
|
+
}
|
|
510
|
+
this.execute('mute', state);
|
|
511
|
+
this._setState('muted', state);
|
|
655
512
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
},
|
|
513
|
+
if (!this.is('ready')) {
|
|
514
|
+
this.startMuted = state;
|
|
515
|
+
}
|
|
661
516
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
media.stopVideo();
|
|
665
|
-
mediaplayer._onEnd();
|
|
666
|
-
}
|
|
667
|
-
},
|
|
517
|
+
return this;
|
|
518
|
+
},
|
|
668
519
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
520
|
+
/**
|
|
521
|
+
* Restore the sound of the media after a mute
|
|
522
|
+
* @returns {mediaplayer}
|
|
523
|
+
*/
|
|
524
|
+
unmute() {
|
|
525
|
+
this.mute(false);
|
|
674
526
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
return media.isMuted();
|
|
678
|
-
}
|
|
679
|
-
return false;
|
|
680
|
-
},
|
|
681
|
-
|
|
682
|
-
addMedia: function _youtubePlayerSetMedia(url) {
|
|
683
|
-
var id = _extractYoutubeId(url);
|
|
684
|
-
var cb =
|
|
685
|
-
id &&
|
|
686
|
-
function() {
|
|
687
|
-
media.cueVideoById(id);
|
|
688
|
-
};
|
|
689
|
-
if (cb) {
|
|
690
|
-
if (media) {
|
|
691
|
-
cb();
|
|
692
|
-
} else {
|
|
693
|
-
this._callbacks = this._callbacks || [];
|
|
694
|
-
this._callbacks.push(cb);
|
|
695
|
-
}
|
|
696
|
-
return true;
|
|
697
|
-
}
|
|
698
|
-
return false;
|
|
699
|
-
},
|
|
700
|
-
|
|
701
|
-
setMedia: function _youtubePlayerSetMedia(url) {
|
|
702
|
-
var id = _extractYoutubeId(url);
|
|
703
|
-
var cb =
|
|
704
|
-
id &&
|
|
705
|
-
function() {
|
|
706
|
-
media.loadVideoById(id);
|
|
707
|
-
};
|
|
708
|
-
if (cb) {
|
|
709
|
-
if (media) {
|
|
710
|
-
cb();
|
|
711
|
-
} else {
|
|
712
|
-
this._callbacks = [cb];
|
|
713
|
-
}
|
|
714
|
-
return true;
|
|
715
|
-
}
|
|
716
|
-
return false;
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
}
|
|
527
|
+
return this;
|
|
528
|
+
},
|
|
720
529
|
|
|
721
|
-
|
|
722
|
-
|
|
530
|
+
/**
|
|
531
|
+
* Sets the sound volume of the media being played
|
|
532
|
+
* @param {Number} value - A value between 0 and 100
|
|
533
|
+
* @param {*} [internal] - Internal use
|
|
534
|
+
* @returns {mediaplayer}
|
|
535
|
+
*/
|
|
536
|
+
setVolume(value, internal) {
|
|
537
|
+
this._updateVolume(value, internal);
|
|
723
538
|
|
|
724
|
-
|
|
725
|
-
* Defines a player object dedicated to native player
|
|
726
|
-
* @param {mediaplayer} mediaplayer
|
|
727
|
-
* @private
|
|
728
|
-
*/
|
|
729
|
-
var _nativePlayer = function(mediaplayer) {
|
|
730
|
-
var $media;
|
|
731
|
-
var media;
|
|
732
|
-
var player;
|
|
733
|
-
var played;
|
|
734
|
-
|
|
735
|
-
if (mediaplayer) {
|
|
736
|
-
player = {
|
|
737
|
-
init: function _nativePlayerInit() {
|
|
738
|
-
var result = false;
|
|
739
|
-
var mediaElem;
|
|
740
|
-
|
|
741
|
-
$media = mediaplayer.$media;
|
|
742
|
-
media = null;
|
|
743
|
-
played = false;
|
|
744
|
-
|
|
745
|
-
if ($media) {
|
|
746
|
-
mediaElem = $media.get(0);
|
|
747
|
-
if (mediaElem && mediaElem.canPlayType) {
|
|
748
|
-
media = mediaElem;
|
|
749
|
-
result = true;
|
|
750
|
-
}
|
|
539
|
+
this.execute('setVolume', this.volume);
|
|
751
540
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
541
|
+
return this;
|
|
542
|
+
},
|
|
755
543
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
})
|
|
764
|
-
.on('ended' + _ns, function() {
|
|
765
|
-
played = false;
|
|
766
|
-
mediaplayer._onEnd();
|
|
767
|
-
})
|
|
768
|
-
.on('timeupdate' + _ns, function() {
|
|
769
|
-
mediaplayer._onTimeUpdate();
|
|
770
|
-
})
|
|
771
|
-
.on('loadstart', function() {
|
|
772
|
-
if (media.networkState === HTMLMediaElement.NETWORK_NO_SOURCE) {
|
|
773
|
-
mediaplayer._onError();
|
|
774
|
-
}
|
|
775
|
-
})
|
|
776
|
-
.on('error' + _ns, function() {
|
|
777
|
-
if (media.networkState === HTMLMediaElement.NETWORK_NO_SOURCE) {
|
|
778
|
-
mediaplayer._onError();
|
|
779
|
-
} else {
|
|
780
|
-
mediaplayer._onRecoverError();
|
|
781
|
-
|
|
782
|
-
// recover from playing error
|
|
783
|
-
if (
|
|
784
|
-
media.networkState === HTMLMediaElement.NETWORK_LOADING &&
|
|
785
|
-
mediaplayer.is('playing')
|
|
786
|
-
) {
|
|
787
|
-
mediaplayer.render();
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
})
|
|
791
|
-
.on('loadedmetadata' + _ns, function() {
|
|
792
|
-
if (mediaplayer.is('error')) {
|
|
793
|
-
mediaplayer._onRecoverError();
|
|
794
|
-
}
|
|
795
|
-
mediaplayer._onReady();
|
|
796
|
-
});
|
|
797
|
-
|
|
798
|
-
if (_debugMode) {
|
|
799
|
-
// install debug logger
|
|
800
|
-
_.forEach(
|
|
801
|
-
[
|
|
802
|
-
'abort',
|
|
803
|
-
'canplay',
|
|
804
|
-
'canplaythrough',
|
|
805
|
-
'canshowcurrentframe',
|
|
806
|
-
'dataunavailable',
|
|
807
|
-
'durationchange',
|
|
808
|
-
'emptied',
|
|
809
|
-
'empty',
|
|
810
|
-
'ended',
|
|
811
|
-
'error',
|
|
812
|
-
'loadedfirstframe',
|
|
813
|
-
'loadedmetadata',
|
|
814
|
-
'loadstart',
|
|
815
|
-
'pause',
|
|
816
|
-
'play',
|
|
817
|
-
'progress',
|
|
818
|
-
'ratechange',
|
|
819
|
-
'seeked',
|
|
820
|
-
'seeking',
|
|
821
|
-
'suspend',
|
|
822
|
-
'timeupdate',
|
|
823
|
-
'volumechange',
|
|
824
|
-
'waiting'
|
|
825
|
-
],
|
|
826
|
-
function(ev) {
|
|
827
|
-
$media.on(ev + _ns, function(e) {
|
|
828
|
-
window.console.log(
|
|
829
|
-
e.type,
|
|
830
|
-
$media && $media.find('source').attr('src'),
|
|
831
|
-
media && media.networkState
|
|
832
|
-
);
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
544
|
+
/**
|
|
545
|
+
* Gets the sound volume applied to the media being played
|
|
546
|
+
* @returns {Number} Returns a value between 0 and 100
|
|
547
|
+
*/
|
|
548
|
+
getVolume() {
|
|
549
|
+
return this.volume;
|
|
550
|
+
},
|
|
838
551
|
|
|
839
|
-
|
|
840
|
-
|
|
552
|
+
/**
|
|
553
|
+
* Gets the current displayed position inside the media
|
|
554
|
+
* @returns {Number}
|
|
555
|
+
*/
|
|
556
|
+
getPosition() {
|
|
557
|
+
return this.position;
|
|
558
|
+
},
|
|
841
559
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
560
|
+
/**
|
|
561
|
+
* Gets the duration of the media
|
|
562
|
+
* @returns {Number}
|
|
563
|
+
*/
|
|
564
|
+
getDuration() {
|
|
565
|
+
return this.duration;
|
|
566
|
+
},
|
|
846
567
|
|
|
847
|
-
|
|
568
|
+
/**
|
|
569
|
+
* Gets the number of times the media has been played
|
|
570
|
+
* @returns {Number}
|
|
571
|
+
*/
|
|
572
|
+
getTimesPlayed() {
|
|
573
|
+
return this.timesPlayed;
|
|
574
|
+
},
|
|
848
575
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
576
|
+
/**
|
|
577
|
+
* Gets the type of player
|
|
578
|
+
* @returns {String}
|
|
579
|
+
*/
|
|
580
|
+
getType() {
|
|
581
|
+
return this.type;
|
|
582
|
+
},
|
|
853
583
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
584
|
+
/**
|
|
585
|
+
* Gets the DOM container
|
|
586
|
+
* @returns {jQuery}
|
|
587
|
+
*/
|
|
588
|
+
getContainer() {
|
|
589
|
+
if (!this.$container && this.$component) {
|
|
590
|
+
let $container = this.$component.parent();
|
|
591
|
+
if ($container.length) {
|
|
592
|
+
this.$container = $container;
|
|
857
593
|
}
|
|
858
|
-
|
|
859
|
-
|
|
594
|
+
}
|
|
595
|
+
return this.$container;
|
|
596
|
+
},
|
|
860
597
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
598
|
+
/**
|
|
599
|
+
* Gets the underlying DOM element
|
|
600
|
+
* @returns {jQuery}
|
|
601
|
+
*/
|
|
602
|
+
getElement() {
|
|
603
|
+
return this.$component;
|
|
604
|
+
},
|
|
867
605
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
606
|
+
/**
|
|
607
|
+
* Gets the list of media
|
|
608
|
+
* @returns {Array}
|
|
609
|
+
*/
|
|
610
|
+
getSources() {
|
|
611
|
+
return this.config.sources.slice();
|
|
612
|
+
},
|
|
875
613
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
614
|
+
/**
|
|
615
|
+
* Sets the media source. If a source has been already set, it will be replaced.
|
|
616
|
+
* @param {String|Object} src - The media URL, or an object containing the source and the type
|
|
617
|
+
* @param {Function} [callback] - A function called to provide the added media source object
|
|
618
|
+
* @returns {mediaplayer}
|
|
619
|
+
*/
|
|
620
|
+
setSource(src, callback) {
|
|
621
|
+
this._getSource(src, source => {
|
|
622
|
+
this.config.sources = [source];
|
|
881
623
|
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
$media.width(width).height(height);
|
|
624
|
+
if (this.is('rendered')) {
|
|
625
|
+
this.player.setMedia(source.src, source.type);
|
|
885
626
|
}
|
|
886
|
-
},
|
|
887
627
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
media.currentTime = parseFloat(value);
|
|
891
|
-
if (!played) {
|
|
892
|
-
this.play();
|
|
893
|
-
}
|
|
628
|
+
if (callback) {
|
|
629
|
+
callback.call(this, source);
|
|
894
630
|
}
|
|
895
|
-
}
|
|
631
|
+
});
|
|
896
632
|
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
media.play();
|
|
900
|
-
}
|
|
901
|
-
},
|
|
633
|
+
return this;
|
|
634
|
+
},
|
|
902
635
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Adds a media source.
|
|
638
|
+
* @param {String|Object} src - The media URL, or an object containing the source and the type
|
|
639
|
+
* @param {Function} [callback] - A function called to provide the added media source object
|
|
640
|
+
* @returns {mediaplayer}
|
|
641
|
+
*/
|
|
642
|
+
addSource(src, callback) {
|
|
643
|
+
this._getSource(src, source => {
|
|
644
|
+
this.config.sources.push(source);
|
|
908
645
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
media.currentTime = media.duration;
|
|
646
|
+
if (this.is('rendered')) {
|
|
647
|
+
this.player.addMedia(source.src, source.type);
|
|
912
648
|
}
|
|
913
|
-
},
|
|
914
649
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
media.muted = !!state;
|
|
650
|
+
if (callback) {
|
|
651
|
+
callback.call(this, source);
|
|
918
652
|
}
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
isMuted: function _nativePlayerIsMuted() {
|
|
922
|
-
if (media) {
|
|
923
|
-
return !!media.muted;
|
|
924
|
-
}
|
|
925
|
-
return false;
|
|
926
|
-
},
|
|
927
|
-
|
|
928
|
-
addMedia: function _nativePlayerSetMedia(url, type) {
|
|
929
|
-
type = type || _defaults.type;
|
|
930
|
-
if (media) {
|
|
931
|
-
if (!_checkSupport(media, type)) {
|
|
932
|
-
return false;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
if (url && $media) {
|
|
937
|
-
$media.append('<source src="' + url + '" type="' + (_mimeTypes[type] || type) + '" />');
|
|
938
|
-
return true;
|
|
939
|
-
}
|
|
940
|
-
return false;
|
|
941
|
-
},
|
|
942
|
-
|
|
943
|
-
setMedia: function _nativePlayerSetMedia(url, type) {
|
|
944
|
-
if ($media) {
|
|
945
|
-
$media.empty();
|
|
946
|
-
return this.addMedia(url, type);
|
|
947
|
-
}
|
|
948
|
-
return false;
|
|
949
|
-
}
|
|
950
|
-
};
|
|
951
|
-
}
|
|
653
|
+
});
|
|
952
654
|
|
|
953
|
-
|
|
954
|
-
}
|
|
655
|
+
return this;
|
|
656
|
+
},
|
|
955
657
|
|
|
956
|
-
/**
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
youtube: _youtubePlayer
|
|
965
|
-
};
|
|
658
|
+
/**
|
|
659
|
+
* Tells if the media is in a particular state
|
|
660
|
+
* @param {String} state
|
|
661
|
+
* @returns {Boolean}
|
|
662
|
+
*/
|
|
663
|
+
is(state) {
|
|
664
|
+
return !!this.config.is[state];
|
|
665
|
+
},
|
|
966
666
|
|
|
967
|
-
/**
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
* @param {Boolean} [config.loop] - The media will be played continuously
|
|
979
|
-
* @param {Boolean} [config.canPause] - The play can be paused
|
|
980
|
-
* @param {Boolean} [config.canSeek] - The player allows to reach an arbitrary position within the media using the duration bar
|
|
981
|
-
* @param {Boolean} [config.startMuted] - The player should be initially muted
|
|
982
|
-
* @param {Boolean} [config.autoStart] - The player starts as soon as it is displayed
|
|
983
|
-
* @param {Number} [config.autoStartAt] - The time position at which the player should start
|
|
984
|
-
* @param {Number} [config.maxPlays] - Sets a few number of plays (default: infinite)
|
|
985
|
-
* @param {Number} [config.replayTimeout] - disable the possibility to replay a media after this timeout, in seconds (default: 0)
|
|
986
|
-
* @param {Number} [config.volume] - Sets the sound volume (default: 80)
|
|
987
|
-
* @param {Number} [config.width] - Sets the width of the player (default: depends on media type)
|
|
988
|
-
* @param {Number} [config.height] - Sets the height of the player (default: depends on media type)
|
|
989
|
-
* @returns {mediaplayer}
|
|
990
|
-
*/
|
|
991
|
-
init: function init(config) {
|
|
992
|
-
var self = this;
|
|
993
|
-
|
|
994
|
-
// load the config set, discard null values in order to allow defaults to be set
|
|
995
|
-
this.config = _.omit(config || {}, function(value) {
|
|
996
|
-
return typeof value === 'undefined' || value === null;
|
|
997
|
-
});
|
|
998
|
-
_.defaults(this.config, _defaults.options);
|
|
999
|
-
this._setType(this.config.type || _defaults.type);
|
|
1000
|
-
|
|
1001
|
-
this._reset();
|
|
1002
|
-
this._updateVolumeFromStore();
|
|
1003
|
-
this._initEvents();
|
|
1004
|
-
this._initSources(function() {
|
|
1005
|
-
if (!self.is('youtube')) {
|
|
1006
|
-
_.each(self.config.sources, function(source) {
|
|
1007
|
-
if (source && source.type && source.type.indexOf('audio') === 0) {
|
|
1008
|
-
self._setType(source.type);
|
|
1009
|
-
self._initType();
|
|
1010
|
-
return false;
|
|
1011
|
-
}
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
if (self.config.renderTo) {
|
|
1015
|
-
_.defer(function() {
|
|
1016
|
-
self.render();
|
|
1017
|
-
});
|
|
667
|
+
/**
|
|
668
|
+
* Changes the size of the player
|
|
669
|
+
* @param {Number} width
|
|
670
|
+
* @param {Number} height
|
|
671
|
+
* @returns {mediaplayer}
|
|
672
|
+
*/
|
|
673
|
+
resize(width, height) {
|
|
674
|
+
if ((isResponsiveSize(width) && !isResponsiveSize(height)) || this.is('youtube')) {
|
|
675
|
+
// responsive width height should be auto
|
|
676
|
+
// for youtube iframe height is limited by ration
|
|
677
|
+
height = 'auto';
|
|
1018
678
|
}
|
|
1019
|
-
|
|
679
|
+
this.execute('setSize', width, height);
|
|
1020
680
|
|
|
1021
|
-
|
|
1022
|
-
|
|
681
|
+
return this;
|
|
682
|
+
},
|
|
1023
683
|
|
|
1024
|
-
/**
|
|
1025
|
-
* Uninstalls the media player
|
|
1026
|
-
* @returns {mediaplayer}
|
|
1027
|
-
*/
|
|
1028
|
-
destroy: function destroy() {
|
|
1029
684
|
/**
|
|
1030
|
-
*
|
|
1031
|
-
* @
|
|
685
|
+
* Enables the media player
|
|
686
|
+
* @returns {mediaplayer}
|
|
1032
687
|
*/
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
if (this.player) {
|
|
1036
|
-
this.player.destroy();
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
if (this.$component) {
|
|
1040
|
-
this._unbindEvents();
|
|
1041
|
-
this._destroySlider(this.$seekSlider);
|
|
1042
|
-
this._destroySlider(this.$volumeSlider);
|
|
688
|
+
enable() {
|
|
689
|
+
this._fromState('disabled');
|
|
1043
690
|
|
|
1044
|
-
this
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
this._reset();
|
|
1048
|
-
|
|
1049
|
-
return this;
|
|
1050
|
-
},
|
|
1051
|
-
|
|
1052
|
-
/**
|
|
1053
|
-
* Renders the media player according to the media type
|
|
1054
|
-
* @param {String|jQuery|HTMLElement} [to]
|
|
1055
|
-
* @returns {mediaplayer}
|
|
1056
|
-
*/
|
|
1057
|
-
render: function render(to) {
|
|
1058
|
-
var renderTo = to || this.config.renderTo || this.$container;
|
|
1059
|
-
|
|
1060
|
-
if (this.$component) {
|
|
1061
|
-
this.destroy();
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
this._initState();
|
|
1065
|
-
this._buildDom();
|
|
1066
|
-
this._updateDuration(0);
|
|
1067
|
-
this._updatePosition(0);
|
|
1068
|
-
this._bindEvents();
|
|
1069
|
-
this._playingState(false, true);
|
|
1070
|
-
this._initPlayer();
|
|
1071
|
-
this._initSize();
|
|
1072
|
-
this.resize(this.config.width, this.config.height);
|
|
1073
|
-
this.config.is.rendered = true;
|
|
1074
|
-
|
|
1075
|
-
if (renderTo) {
|
|
1076
|
-
this.$container = $(renderTo).append(this.$component);
|
|
1077
|
-
}
|
|
691
|
+
return this;
|
|
692
|
+
},
|
|
1078
693
|
|
|
1079
694
|
/**
|
|
1080
|
-
*
|
|
1081
|
-
* @
|
|
1082
|
-
* @param {jQuery} $component
|
|
695
|
+
* Disables the media player
|
|
696
|
+
* @returns {mediaplayer}
|
|
1083
697
|
*/
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
},
|
|
1088
|
-
|
|
1089
|
-
/**
|
|
1090
|
-
* Sets the start position inside the media
|
|
1091
|
-
* @param {Number} time - The start position in seconds
|
|
1092
|
-
* @param {*} [internal] - Internal use
|
|
1093
|
-
* @returns {mediaplayer}
|
|
1094
|
-
*/
|
|
1095
|
-
seek: function seek(time, internal) {
|
|
1096
|
-
if (this._canPlay()) {
|
|
1097
|
-
this._updatePosition(time, internal);
|
|
1098
|
-
|
|
1099
|
-
this.execute('seek', this.position);
|
|
1100
|
-
|
|
1101
|
-
if (!this.is('ready')) {
|
|
1102
|
-
this.autoStartAt = this.position;
|
|
1103
|
-
}
|
|
1104
|
-
this.loop = !!this.config.loop;
|
|
1105
|
-
}
|
|
698
|
+
disable() {
|
|
699
|
+
this._toState('disabled');
|
|
700
|
+
this.trigger('disabled');
|
|
1106
701
|
|
|
1107
|
-
|
|
1108
|
-
|
|
702
|
+
return this;
|
|
703
|
+
},
|
|
1109
704
|
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
if (this._canPlay()) {
|
|
1117
|
-
if (typeof time !== 'undefined') {
|
|
1118
|
-
this.seek(time);
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
this.execute('play');
|
|
705
|
+
/**
|
|
706
|
+
* Shows the media player
|
|
707
|
+
* @returns {mediaplayer}
|
|
708
|
+
*/
|
|
709
|
+
show() {
|
|
710
|
+
this._fromState('hidden');
|
|
1122
711
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
}
|
|
712
|
+
return this;
|
|
713
|
+
},
|
|
1126
714
|
|
|
1127
|
-
|
|
715
|
+
/**
|
|
716
|
+
* hides the media player
|
|
717
|
+
* @returns {mediaplayer}
|
|
718
|
+
*/
|
|
719
|
+
hide() {
|
|
720
|
+
this._toState('hidden');
|
|
1128
721
|
|
|
1129
|
-
|
|
1130
|
-
|
|
722
|
+
return this;
|
|
723
|
+
},
|
|
724
|
+
/**
|
|
725
|
+
* get media original size
|
|
726
|
+
* @returns {Object}
|
|
727
|
+
*/
|
|
728
|
+
getMediaOriginalSize() {
|
|
729
|
+
if (this.is('youtube')) {
|
|
730
|
+
return defaults.youtube;
|
|
1131
731
|
}
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
return this;
|
|
1135
|
-
},
|
|
1136
|
-
|
|
1137
|
-
/**
|
|
1138
|
-
* Pauses the media
|
|
1139
|
-
* @param {Number} [time] - An optional time position in seconds
|
|
1140
|
-
* @returns {mediaplayer}
|
|
1141
|
-
*/
|
|
1142
|
-
pause: function pause(time) {
|
|
1143
|
-
if (this._canPause()) {
|
|
1144
|
-
if (typeof time !== 'undefined') {
|
|
1145
|
-
this.seek(time);
|
|
732
|
+
if (this.is('video') && this.player) {
|
|
733
|
+
return this.player.getMediaSize();
|
|
1146
734
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
*/
|
|
1162
|
-
resume: function resume() {
|
|
1163
|
-
if (this._canResume()) {
|
|
1164
|
-
this.play();
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
return this;
|
|
1168
|
-
},
|
|
1169
|
-
|
|
1170
|
-
/**
|
|
1171
|
-
* Stops the playback
|
|
1172
|
-
* @returns {mediaplayer}
|
|
1173
|
-
*/
|
|
1174
|
-
stop: function stop() {
|
|
1175
|
-
this.loop = false;
|
|
1176
|
-
this.execute('stop');
|
|
1177
|
-
|
|
1178
|
-
if (!this.is('ready')) {
|
|
1179
|
-
this.autoStart = false;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
return this;
|
|
1183
|
-
},
|
|
1184
|
-
|
|
1185
|
-
/**
|
|
1186
|
-
* Restarts the media from the beginning
|
|
1187
|
-
* @returns {mediaplayer}
|
|
1188
|
-
*/
|
|
1189
|
-
restart: function restart() {
|
|
1190
|
-
this.play(0);
|
|
1191
|
-
|
|
1192
|
-
return this;
|
|
1193
|
-
},
|
|
1194
|
-
|
|
1195
|
-
/**
|
|
1196
|
-
* Rewind the media to the beginning
|
|
1197
|
-
* @returns {mediaplayer}
|
|
1198
|
-
*/
|
|
1199
|
-
rewind: function rewind() {
|
|
1200
|
-
this.seek(0);
|
|
1201
|
-
|
|
1202
|
-
return this;
|
|
1203
|
-
},
|
|
1204
|
-
|
|
1205
|
-
/**
|
|
1206
|
-
* Mutes the media
|
|
1207
|
-
* @param {Boolean} [state] - A flag to set the mute state (default: true)
|
|
1208
|
-
* @returns {mediaplayer}
|
|
1209
|
-
*/
|
|
1210
|
-
mute: function mute(state) {
|
|
1211
|
-
if (typeof state === 'undefined') {
|
|
1212
|
-
state = true;
|
|
1213
|
-
}
|
|
1214
|
-
this.execute('mute', state);
|
|
1215
|
-
this._setState('muted', state);
|
|
1216
|
-
|
|
1217
|
-
if (!this.is('ready')) {
|
|
1218
|
-
this.startMuted = state;
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
return this;
|
|
1222
|
-
},
|
|
1223
|
-
|
|
1224
|
-
/**
|
|
1225
|
-
* Restore the sound of the media after a mute
|
|
1226
|
-
* @returns {mediaplayer}
|
|
1227
|
-
*/
|
|
1228
|
-
unmute: function unmute() {
|
|
1229
|
-
this.mute(false);
|
|
1230
|
-
|
|
1231
|
-
return this;
|
|
1232
|
-
},
|
|
1233
|
-
|
|
1234
|
-
/**
|
|
1235
|
-
* Sets the sound volume of the media being played
|
|
1236
|
-
* @param {Number} value - A value between 0 and 100
|
|
1237
|
-
* @param {*} [internal] - Internal use
|
|
1238
|
-
* @returns {mediaplayer}
|
|
1239
|
-
*/
|
|
1240
|
-
setVolume: function setVolume(value, internal) {
|
|
1241
|
-
this._updateVolume(value, internal);
|
|
1242
|
-
|
|
1243
|
-
this.execute('setVolume', this.volume);
|
|
1244
|
-
|
|
1245
|
-
return this;
|
|
1246
|
-
},
|
|
1247
|
-
|
|
1248
|
-
/**
|
|
1249
|
-
* Gets the sound volume applied to the media being played
|
|
1250
|
-
* @returns {Number} Returns a value between 0 and 100
|
|
1251
|
-
*/
|
|
1252
|
-
getVolume: function getVolume() {
|
|
1253
|
-
return this.volume;
|
|
1254
|
-
},
|
|
1255
|
-
|
|
1256
|
-
/**
|
|
1257
|
-
* Gets the current displayed position inside the media
|
|
1258
|
-
* @returns {Number}
|
|
1259
|
-
*/
|
|
1260
|
-
getPosition: function getPosition() {
|
|
1261
|
-
return this.position;
|
|
1262
|
-
},
|
|
1263
|
-
|
|
1264
|
-
/**
|
|
1265
|
-
* Gets the duration of the media
|
|
1266
|
-
* @returns {Number}
|
|
1267
|
-
*/
|
|
1268
|
-
getDuration: function getDuration() {
|
|
1269
|
-
return this.duration;
|
|
1270
|
-
},
|
|
1271
|
-
|
|
1272
|
-
/**
|
|
1273
|
-
* Gets the number of times the media has been played
|
|
1274
|
-
* @returns {Number}
|
|
1275
|
-
*/
|
|
1276
|
-
getTimesPlayed: function getTimesPlayed() {
|
|
1277
|
-
return this.timesPlayed;
|
|
1278
|
-
},
|
|
1279
|
-
|
|
1280
|
-
/**
|
|
1281
|
-
* Gets the type of player
|
|
1282
|
-
* @returns {String}
|
|
1283
|
-
*/
|
|
1284
|
-
getType: function getType() {
|
|
1285
|
-
return this.type;
|
|
1286
|
-
},
|
|
1287
|
-
|
|
1288
|
-
/**
|
|
1289
|
-
* Gets the DOM container
|
|
1290
|
-
* @returns {jQuery}
|
|
1291
|
-
*/
|
|
1292
|
-
getContainer: function getContainer() {
|
|
1293
|
-
var $container;
|
|
1294
|
-
if (!this.$container && this.$component) {
|
|
1295
|
-
$container = this.$component.parent();
|
|
1296
|
-
if ($container.length) {
|
|
1297
|
-
this.$container = $container;
|
|
735
|
+
return {};
|
|
736
|
+
},
|
|
737
|
+
/**
|
|
738
|
+
* Ensures the right media type is set
|
|
739
|
+
* @param {String} type
|
|
740
|
+
* @private
|
|
741
|
+
*/
|
|
742
|
+
_setType(type) {
|
|
743
|
+
if (type.indexOf('youtube') !== -1) {
|
|
744
|
+
this.type = 'youtube';
|
|
745
|
+
} else if (type.indexOf('audio') === 0) {
|
|
746
|
+
this.type = 'audio';
|
|
747
|
+
} else {
|
|
748
|
+
this.type = 'video';
|
|
1298
749
|
}
|
|
1299
|
-
}
|
|
1300
|
-
return this.$container;
|
|
1301
|
-
},
|
|
1302
|
-
|
|
1303
|
-
/**
|
|
1304
|
-
* Gets the underlying DOM element
|
|
1305
|
-
* @returns {jQuery}
|
|
1306
|
-
*/
|
|
1307
|
-
getElement: function getElement() {
|
|
1308
|
-
return this.$component;
|
|
1309
|
-
},
|
|
1310
|
-
|
|
1311
|
-
/**
|
|
1312
|
-
* Gets the list of media
|
|
1313
|
-
* @returns {Array}
|
|
1314
|
-
*/
|
|
1315
|
-
getSources: function getSources() {
|
|
1316
|
-
return this.config.sources.slice();
|
|
1317
|
-
},
|
|
750
|
+
},
|
|
1318
751
|
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
752
|
+
/**
|
|
753
|
+
* Ensures the type is correctly applied
|
|
754
|
+
* @private
|
|
755
|
+
*/
|
|
756
|
+
_initType() {
|
|
757
|
+
const is = this.config.is;
|
|
758
|
+
is.youtube = 'youtube' === this.type;
|
|
759
|
+
is.video = 'video' === this.type || 'youtube' === this.type;
|
|
760
|
+
is.audio = 'audio' === this.type;
|
|
761
|
+
},
|
|
1328
762
|
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
763
|
+
/**
|
|
764
|
+
* Gets a source descriptor.
|
|
765
|
+
* @param {String|Object} src - The media URL, or an object containing the source and the type
|
|
766
|
+
* @param {Function} callback - A function called to provide the media source object
|
|
767
|
+
*/
|
|
768
|
+
_getSource(src, callback) {
|
|
769
|
+
let source;
|
|
770
|
+
const done = () => {
|
|
771
|
+
if (needTypeAdjust(source.type)) {
|
|
772
|
+
source.type = getAdjustedType(source);
|
|
773
|
+
}
|
|
1332
774
|
|
|
1333
|
-
if (callback) {
|
|
1334
775
|
callback.call(this, source);
|
|
1335
|
-
}
|
|
1336
|
-
});
|
|
1337
|
-
|
|
1338
|
-
return this;
|
|
1339
|
-
},
|
|
1340
|
-
|
|
1341
|
-
/**
|
|
1342
|
-
* Adds a media source.
|
|
1343
|
-
* @param {String|Object} src - The media URL, or an object containing the source and the type
|
|
1344
|
-
* @param {Function} [callback] - A function called to provide the added media source object
|
|
1345
|
-
* @returns {mediaplayer}
|
|
1346
|
-
*/
|
|
1347
|
-
addSource: function addSource(src, callback) {
|
|
1348
|
-
this._getSource(src, function(source) {
|
|
1349
|
-
this.config.sources.push(source);
|
|
776
|
+
};
|
|
1350
777
|
|
|
1351
|
-
if (
|
|
1352
|
-
|
|
778
|
+
if (_.isString(src)) {
|
|
779
|
+
source = {
|
|
780
|
+
src: src
|
|
781
|
+
};
|
|
782
|
+
} else {
|
|
783
|
+
source = _.clone(src);
|
|
1353
784
|
}
|
|
1354
785
|
|
|
1355
|
-
if (
|
|
1356
|
-
|
|
786
|
+
if (!source.type) {
|
|
787
|
+
if (this.is('youtube')) {
|
|
788
|
+
source.type = defaults.type;
|
|
789
|
+
} else if (this.config.mimeType) {
|
|
790
|
+
source.type = this.config.mimeType;
|
|
791
|
+
}
|
|
1357
792
|
}
|
|
1358
|
-
});
|
|
1359
|
-
|
|
1360
|
-
return this;
|
|
1361
|
-
},
|
|
1362
|
-
|
|
1363
|
-
/**
|
|
1364
|
-
* Tells if the media is in a particular state
|
|
1365
|
-
* @param {String} state
|
|
1366
|
-
* @returns {Boolean}
|
|
1367
|
-
*/
|
|
1368
|
-
is: function is(state) {
|
|
1369
|
-
return !!this.config.is[state];
|
|
1370
|
-
},
|
|
1371
|
-
|
|
1372
|
-
/**
|
|
1373
|
-
* Changes the size of the player
|
|
1374
|
-
* @param {Number} width
|
|
1375
|
-
* @param {Number} height
|
|
1376
|
-
* @returns {mediaplayer}
|
|
1377
|
-
*/
|
|
1378
|
-
resize: function resize(width, height) {
|
|
1379
|
-
var type = this.is('video') ? 'video' : 'audio';
|
|
1380
|
-
var defaults = _defaults[type] || _defaults.video;
|
|
1381
|
-
|
|
1382
|
-
width = Math.max(defaults.minWidth, width);
|
|
1383
|
-
height = Math.max(defaults.minHeight, height);
|
|
1384
793
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
794
|
+
if (!source.type) {
|
|
795
|
+
mimetype.getResourceType(source.src, (err, type) => {
|
|
796
|
+
if (err) {
|
|
797
|
+
type = defaults.type;
|
|
798
|
+
}
|
|
799
|
+
source.type = type;
|
|
800
|
+
done();
|
|
801
|
+
});
|
|
802
|
+
} else {
|
|
803
|
+
done();
|
|
1395
804
|
}
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
this.execute('setSize', width, height);
|
|
1399
|
-
|
|
1400
|
-
return this;
|
|
1401
|
-
},
|
|
1402
|
-
|
|
1403
|
-
/**
|
|
1404
|
-
* Enables the media player
|
|
1405
|
-
* @returns {mediaplayer}
|
|
1406
|
-
*/
|
|
1407
|
-
enable: function enable() {
|
|
1408
|
-
this._fromState('disabled');
|
|
1409
|
-
|
|
1410
|
-
return this;
|
|
1411
|
-
},
|
|
805
|
+
},
|
|
1412
806
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
return this;
|
|
1422
|
-
},
|
|
807
|
+
/**
|
|
808
|
+
* Ensures the sources are correctly set
|
|
809
|
+
* @param {Function} callback - A function called once all sources have been initialized
|
|
810
|
+
* @private
|
|
811
|
+
*/
|
|
812
|
+
_initSources(callback) {
|
|
813
|
+
const sources = configToSources(this.config);
|
|
1423
814
|
|
|
1424
|
-
|
|
1425
|
-
* Shows the media player
|
|
1426
|
-
* @returns {mediaplayer}
|
|
1427
|
-
*/
|
|
1428
|
-
show: function show() {
|
|
1429
|
-
this._fromState('hidden');
|
|
815
|
+
this.config.sources = [];
|
|
1430
816
|
|
|
1431
|
-
|
|
1432
|
-
|
|
817
|
+
async.each(
|
|
818
|
+
sources,
|
|
819
|
+
(source, cb) => {
|
|
820
|
+
this.addSource(source, src => cb(null, src));
|
|
821
|
+
},
|
|
822
|
+
callback
|
|
823
|
+
);
|
|
824
|
+
},
|
|
1433
825
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
return this;
|
|
1442
|
-
},
|
|
826
|
+
/**
|
|
827
|
+
* Installs the events manager onto the instance
|
|
828
|
+
* @private
|
|
829
|
+
*/
|
|
830
|
+
_initEvents() {
|
|
831
|
+
eventifier(this);
|
|
1443
832
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
} else if (type.indexOf('audio') === 0) {
|
|
1453
|
-
this.type = 'audio';
|
|
1454
|
-
} else {
|
|
1455
|
-
this.type = 'video';
|
|
1456
|
-
}
|
|
1457
|
-
},
|
|
833
|
+
const triggerEvent = this.trigger;
|
|
834
|
+
this.trigger = function trigger(eventName, ...args) {
|
|
835
|
+
if (this.$component) {
|
|
836
|
+
this.$component.trigger(eventName + ns, ...args);
|
|
837
|
+
}
|
|
838
|
+
return triggerEvent.call(this, eventName, ...args);
|
|
839
|
+
};
|
|
840
|
+
},
|
|
1458
841
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
is.video = 'video' === this.type || 'youtube' === this.type;
|
|
1467
|
-
is.audio = 'audio' === this.type;
|
|
1468
|
-
},
|
|
842
|
+
/**
|
|
843
|
+
* Ensures the right size is set according to the media type
|
|
844
|
+
* @private
|
|
845
|
+
*/
|
|
846
|
+
_initSize() {
|
|
847
|
+
const type = this.is('video') ? 'video' : 'audio';
|
|
848
|
+
const mediaConfig = defaults[type] || defaults.video;
|
|
1469
849
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
* @param {String|Object} src - The media URL, or an object containing the source and the type
|
|
1473
|
-
* @param {Function} callback - A function called to provide the media source object
|
|
1474
|
-
*/
|
|
1475
|
-
_getSource: function _getSource(src, callback) {
|
|
1476
|
-
var self = this;
|
|
1477
|
-
var source;
|
|
850
|
+
this.config.width = this.config.width || mediaConfig.width;
|
|
851
|
+
this.config.height = this.config.height || mediaConfig.height;
|
|
1478
852
|
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
if (this.is('youtube') && !source.type) {
|
|
1488
|
-
source.type = _defaults.type;
|
|
1489
|
-
}
|
|
853
|
+
if ((isResponsiveSize(this.config.width) && !isResponsiveSize(this.config.height)) || this.is('youtube')) {
|
|
854
|
+
// responsive width height should be auto
|
|
855
|
+
// for youtube iframe height is limited by ration
|
|
856
|
+
this.config.height = 'auto';
|
|
857
|
+
}
|
|
858
|
+
},
|
|
1490
859
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
860
|
+
/**
|
|
861
|
+
* Initializes the right player instance
|
|
862
|
+
* @private
|
|
863
|
+
*/
|
|
864
|
+
_initPlayer() {
|
|
865
|
+
const playerFactory = players[this.type];
|
|
866
|
+
let error;
|
|
867
|
+
|
|
868
|
+
if (support.canPlay(this.type)) {
|
|
869
|
+
if (_.isFunction(playerFactory)) {
|
|
870
|
+
const playerConfig = {
|
|
871
|
+
type: this.getType(),
|
|
872
|
+
sources: this.getSources(),
|
|
873
|
+
preview: this.config.preview,
|
|
874
|
+
debug: this.config.debug,
|
|
875
|
+
stalledDetectionDelay: this.config.stalledDetectionDelay
|
|
876
|
+
};
|
|
877
|
+
this.player = playerFactory(this.$player, playerConfig)
|
|
878
|
+
.on('resize', (width, height) => {
|
|
879
|
+
if (this.$component) {
|
|
880
|
+
this.$component.width(width).height(height);
|
|
881
|
+
}
|
|
882
|
+
})
|
|
883
|
+
.on('ready', () => this._onReady())
|
|
884
|
+
.on('play', () => this._onPlay())
|
|
885
|
+
.on('pause', () => this._onPause())
|
|
886
|
+
.on('timeupdate', () => this._onTimeUpdate())
|
|
887
|
+
.on('stalled', () => this._onStalled())
|
|
888
|
+
.on('playing', () => this._onPlaying())
|
|
889
|
+
.on('end', () => this._onEnd())
|
|
890
|
+
.on('error', () => this._onError());
|
|
1495
891
|
}
|
|
1496
|
-
source.type = type;
|
|
1497
|
-
done();
|
|
1498
|
-
});
|
|
1499
|
-
} else {
|
|
1500
|
-
done();
|
|
1501
|
-
}
|
|
1502
892
|
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
893
|
+
if (this.player) {
|
|
894
|
+
error = !this.player.init();
|
|
895
|
+
} else {
|
|
896
|
+
error = true;
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
error = true;
|
|
1506
900
|
}
|
|
1507
901
|
|
|
1508
|
-
|
|
1509
|
-
|
|
902
|
+
this._setState('error', error);
|
|
903
|
+
this._setState('nogui', !support.canControl());
|
|
904
|
+
this._setState('preview', this.config.preview);
|
|
905
|
+
this._setState('loading', !error);
|
|
906
|
+
if (error) {
|
|
907
|
+
this._setState('ready', true);
|
|
908
|
+
this.trigger('ready');
|
|
1510
909
|
}
|
|
910
|
+
},
|
|
1511
911
|
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
* @private
|
|
1520
|
-
*/
|
|
1521
|
-
_initSources: function _initSources(callback) {
|
|
1522
|
-
var self = this;
|
|
1523
|
-
var sources = _configToSources(this.config);
|
|
1524
|
-
|
|
1525
|
-
this.config.sources = [];
|
|
1526
|
-
|
|
1527
|
-
async.each(
|
|
1528
|
-
sources,
|
|
1529
|
-
function(source, cb) {
|
|
1530
|
-
self.addSource(source, function(src) {
|
|
1531
|
-
cb(null, src);
|
|
1532
|
-
});
|
|
1533
|
-
},
|
|
1534
|
-
callback
|
|
1535
|
-
);
|
|
1536
|
-
},
|
|
1537
|
-
|
|
1538
|
-
/**
|
|
1539
|
-
* Installs the events manager onto the instance
|
|
1540
|
-
* @private
|
|
1541
|
-
*/
|
|
1542
|
-
_initEvents: function _initEvents() {
|
|
1543
|
-
var triggerEvent;
|
|
1544
|
-
|
|
1545
|
-
eventifier(this);
|
|
912
|
+
/**
|
|
913
|
+
* Initializes the player state
|
|
914
|
+
* @private
|
|
915
|
+
*/
|
|
916
|
+
_initState() {
|
|
917
|
+
let isCORS = false;
|
|
918
|
+
let page;
|
|
1546
919
|
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
this.$component.trigger(eventName + _ns, _slice.call(arguments, 1));
|
|
920
|
+
if (!this.is('youtube')) {
|
|
921
|
+
page = new UrlParser(window.location);
|
|
922
|
+
isCORS = _.some(this.config.sources, source => !page.sameDomain(source.src));
|
|
1551
923
|
}
|
|
1552
|
-
return triggerEvent.apply(this, arguments);
|
|
1553
|
-
};
|
|
1554
|
-
},
|
|
1555
924
|
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
*/
|
|
1560
|
-
_initSize: function _initSize() {
|
|
1561
|
-
var type = this.is('video') ? 'video' : 'audio';
|
|
1562
|
-
var defaults = _defaults[type] || _defaults.video;
|
|
925
|
+
this._setState('cors', isCORS);
|
|
926
|
+
this._setState('ready', false);
|
|
927
|
+
},
|
|
1563
928
|
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
929
|
+
/**
|
|
930
|
+
* Resets the internals attributes
|
|
931
|
+
* @private
|
|
932
|
+
*/
|
|
933
|
+
_reset() {
|
|
934
|
+
this.config.is = {};
|
|
935
|
+
this._initType();
|
|
936
|
+
|
|
937
|
+
this.$component = null;
|
|
938
|
+
this.$container = null;
|
|
939
|
+
this.$player = null;
|
|
940
|
+
this.$controls = null;
|
|
941
|
+
this.$seek = null;
|
|
942
|
+
this.$seekSlider = null;
|
|
943
|
+
this.$sound = null;
|
|
944
|
+
this.$volume = null;
|
|
945
|
+
this.$volumeControl = null;
|
|
946
|
+
this.$volumeSlider = null;
|
|
947
|
+
this.$position = null;
|
|
948
|
+
this.$duration = null;
|
|
949
|
+
this.player = null;
|
|
950
|
+
|
|
951
|
+
this.duration = 0;
|
|
952
|
+
this.position = 0;
|
|
953
|
+
this.timesPlayed = 0;
|
|
954
|
+
|
|
955
|
+
this.volume = this.config.volume;
|
|
956
|
+
this.autoStart = this.config.autoStart;
|
|
957
|
+
this.autoStartAt = this.config.autoStartAt;
|
|
958
|
+
this.startMuted = this.config.startMuted;
|
|
959
|
+
},
|
|
1567
960
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
961
|
+
/**
|
|
962
|
+
* Builds the DOM content
|
|
963
|
+
* @private
|
|
964
|
+
*/
|
|
965
|
+
_buildDom() {
|
|
966
|
+
const configForTemplate = _.clone(this.config);
|
|
967
|
+
configForTemplate.type = this.type;
|
|
968
|
+
this.$component = $(playerTpl(configForTemplate));
|
|
969
|
+
this.$player = this.$component.find('.player');
|
|
970
|
+
this.$controls = this.$component.find('.controls');
|
|
971
|
+
|
|
972
|
+
this.$seek = this.$controls.find('.seek .slider');
|
|
973
|
+
this.$sound = this.$controls.find('.sound');
|
|
974
|
+
this.$volumeControl = this.$controls.find('.volume');
|
|
975
|
+
this.$volume = this.$controls.find('.volume .slider');
|
|
976
|
+
this.$position = this.$controls.find('[data-control="time-cur"]');
|
|
977
|
+
this.$duration = this.$controls.find('[data-control="time-end"]');
|
|
978
|
+
|
|
979
|
+
this.$volumeSlider = this._renderSlider(this.$volume, this.volume, volumeMin, volumeMax, true);
|
|
980
|
+
},
|
|
1575
981
|
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
982
|
+
/**
|
|
983
|
+
* Renders a slider onto an element
|
|
984
|
+
* @param {jQuery} $elt - The element on which renders the slider
|
|
985
|
+
* @param {Number} [value] - The current value of the slider
|
|
986
|
+
* @param {Number} [min] - The min value of the slider
|
|
987
|
+
* @param {Number} [max] - The max value of the slider
|
|
988
|
+
* @param {Boolean} [vertical] - Tells if the slider must be vertical
|
|
989
|
+
* @returns {jQuery} - Returns the element
|
|
990
|
+
* @private
|
|
991
|
+
*/
|
|
992
|
+
_renderSlider($elt, value, min, max, vertical) {
|
|
993
|
+
let orientation, direction;
|
|
1580
994
|
|
|
1581
|
-
if (
|
|
1582
|
-
|
|
995
|
+
if (vertical) {
|
|
996
|
+
orientation = 'vertical';
|
|
997
|
+
direction = 'rtl';
|
|
1583
998
|
} else {
|
|
1584
|
-
|
|
999
|
+
orientation = 'horizontal';
|
|
1000
|
+
direction = 'ltr';
|
|
1585
1001
|
}
|
|
1586
|
-
} else {
|
|
1587
|
-
error = true;
|
|
1588
|
-
}
|
|
1589
1002
|
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
var page;
|
|
1602
|
-
|
|
1603
|
-
if (!this.is('youtube')) {
|
|
1604
|
-
page = new UrlParser(window.location);
|
|
1605
|
-
isCORS = _.some(this.config.sources, function(source) {
|
|
1606
|
-
return !page.sameDomain(source.src);
|
|
1003
|
+
return $elt.noUiSlider({
|
|
1004
|
+
start: ensureNumber(value) || 0,
|
|
1005
|
+
step: 1,
|
|
1006
|
+
connect: 'lower',
|
|
1007
|
+
orientation: orientation,
|
|
1008
|
+
direction: direction,
|
|
1009
|
+
animate: true,
|
|
1010
|
+
range: {
|
|
1011
|
+
min: ensureNumber(min) || 0,
|
|
1012
|
+
max: ensureNumber(max) || 0
|
|
1013
|
+
}
|
|
1607
1014
|
});
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
this._setState('cors', isCORS);
|
|
1611
|
-
this._setState('ready', false);
|
|
1612
|
-
},
|
|
1015
|
+
},
|
|
1613
1016
|
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
this.$player = null;
|
|
1625
|
-
this.$media = null;
|
|
1626
|
-
this.$controls = null;
|
|
1627
|
-
this.$seek = null;
|
|
1628
|
-
this.$seekSlider = null;
|
|
1629
|
-
this.$sound = null;
|
|
1630
|
-
this.$volume = null;
|
|
1631
|
-
this.$volumeControl = null;
|
|
1632
|
-
this.$volumeSlider = null;
|
|
1633
|
-
this.$position = null;
|
|
1634
|
-
this.$duration = null;
|
|
1635
|
-
this.player = null;
|
|
1636
|
-
|
|
1637
|
-
this.duration = 0;
|
|
1638
|
-
this.position = 0;
|
|
1639
|
-
this.timesPlayed = 0;
|
|
1640
|
-
|
|
1641
|
-
this.volume = this.config.volume;
|
|
1642
|
-
this.autoStart = this.config.autoStart;
|
|
1643
|
-
this.autoStartAt = this.config.autoStartAt;
|
|
1644
|
-
this.startMuted = this.config.startMuted;
|
|
1645
|
-
},
|
|
1646
|
-
|
|
1647
|
-
/**
|
|
1648
|
-
* Builds the DOM content
|
|
1649
|
-
* @private
|
|
1650
|
-
*/
|
|
1651
|
-
_buildDom: function _buildDom() {
|
|
1652
|
-
this.$component = $(playerTpl(this.config));
|
|
1653
|
-
this.$player = this.$component.find('.player');
|
|
1654
|
-
this.$media = this.$component.find('.media');
|
|
1655
|
-
this.$controls = this.$component.find('.controls');
|
|
1656
|
-
|
|
1657
|
-
this.$seek = this.$controls.find('.seek .slider');
|
|
1658
|
-
this.$sound = this.$controls.find('.sound');
|
|
1659
|
-
this.$volumeControl = this.$controls.find('.volume');
|
|
1660
|
-
this.$volume = this.$controls.find('.volume .slider');
|
|
1661
|
-
this.$position = this.$controls.find('[data-control="time-cur"]');
|
|
1662
|
-
this.$duration = this.$controls.find('[data-control="time-end"]');
|
|
1663
|
-
|
|
1664
|
-
this.$volumeSlider = this._renderSlider(this.$volume, this.volume, _volumeMin, _volumeMax, true);
|
|
1665
|
-
},
|
|
1666
|
-
|
|
1667
|
-
/**
|
|
1668
|
-
* Renders a slider onto an element
|
|
1669
|
-
* @param {jQuery} $elt - The element on which renders the slider
|
|
1670
|
-
* @param {Number} [value] - The current value of the slider
|
|
1671
|
-
* @param {Number} [min] - The min value of the slider
|
|
1672
|
-
* @param {Number} [max] - The max value of the slider
|
|
1673
|
-
* @param {Boolean} [vertical] - Tells if the slider must be vertical
|
|
1674
|
-
* @returns {jQuery} - Returns the element
|
|
1675
|
-
* @private
|
|
1676
|
-
*/
|
|
1677
|
-
_renderSlider: function _renderSlider($elt, value, min, max, vertical) {
|
|
1678
|
-
var orientation, direction;
|
|
1679
|
-
|
|
1680
|
-
if (vertical) {
|
|
1681
|
-
orientation = 'vertical';
|
|
1682
|
-
direction = 'rtl';
|
|
1683
|
-
} else {
|
|
1684
|
-
orientation = 'horizontal';
|
|
1685
|
-
direction = 'ltr';
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
return $elt.noUiSlider({
|
|
1689
|
-
start: _ensureNumber(value) || 0,
|
|
1690
|
-
step: 1,
|
|
1691
|
-
connect: 'lower',
|
|
1692
|
-
orientation: orientation,
|
|
1693
|
-
direction: direction,
|
|
1694
|
-
animate: true,
|
|
1695
|
-
range: {
|
|
1696
|
-
min: _ensureNumber(min) || 0,
|
|
1697
|
-
max: _ensureNumber(max) || 0
|
|
1698
|
-
}
|
|
1699
|
-
});
|
|
1700
|
-
},
|
|
1701
|
-
|
|
1702
|
-
/**
|
|
1703
|
-
* Destroys a slider bound to an element
|
|
1704
|
-
* @param {jQuery} $elt
|
|
1705
|
-
* @private
|
|
1706
|
-
*/
|
|
1707
|
-
_destroySlider: function _destroySlider($elt) {
|
|
1708
|
-
if ($elt) {
|
|
1709
|
-
$elt.get(0).destroy();
|
|
1710
|
-
}
|
|
1711
|
-
},
|
|
1017
|
+
/**
|
|
1018
|
+
* Destroys a slider bound to an element
|
|
1019
|
+
* @param {jQuery} $elt
|
|
1020
|
+
* @private
|
|
1021
|
+
*/
|
|
1022
|
+
_destroySlider($elt) {
|
|
1023
|
+
if ($elt) {
|
|
1024
|
+
$elt.get(0).destroy();
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1712
1027
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
var overing = false;
|
|
1028
|
+
/**
|
|
1029
|
+
* Binds events onto the rendered player
|
|
1030
|
+
* @private
|
|
1031
|
+
*/
|
|
1032
|
+
_bindEvents() {
|
|
1033
|
+
let overing = false;
|
|
1720
1034
|
|
|
1721
|
-
|
|
1722
|
-
event.preventDefault();
|
|
1723
|
-
});
|
|
1035
|
+
this.$component.on(`contextmenu${ns}`, event => event.preventDefault());
|
|
1724
1036
|
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1037
|
+
this.$controls.on(`click${ns}`, '.action', event => {
|
|
1038
|
+
const $target = $(event.target);
|
|
1039
|
+
const $action = $target.closest('.action');
|
|
1040
|
+
const id = $action.data('control');
|
|
1729
1041
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1042
|
+
if (_.isFunction(this[id])) {
|
|
1043
|
+
this[id]();
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1734
1046
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
});
|
|
1746
|
-
|
|
1747
|
-
$(document).on('updateVolume' + _ns, function(event, value) {
|
|
1748
|
-
self.setVolume(value);
|
|
1749
|
-
});
|
|
1750
|
-
|
|
1751
|
-
this.$volume.on('change' + _ns, function(event, value) {
|
|
1752
|
-
self.unmute();
|
|
1753
|
-
$(document).trigger('updateVolume' + _ns, value);
|
|
1754
|
-
self.setVolume(value, true);
|
|
1755
|
-
});
|
|
1756
|
-
|
|
1757
|
-
this.$sound.on('mouseover' + _ns, 'a', function() {
|
|
1758
|
-
var position;
|
|
1759
|
-
|
|
1760
|
-
if (!overing && !self.$volumeControl.hasClass('up') && !self.$volumeControl.hasClass('down')) {
|
|
1761
|
-
overing = true;
|
|
1762
|
-
position = self.$controls[0].getBoundingClientRect();
|
|
1763
|
-
if (position && position.top && position.top < volumePositionThreshold) {
|
|
1764
|
-
self.$volumeControl.addClass('down');
|
|
1047
|
+
this.$player.on(`click${ns}`, event => {
|
|
1048
|
+
const $target = $(event.target);
|
|
1049
|
+
const $action = $target.closest('.action');
|
|
1050
|
+
|
|
1051
|
+
// if action was clicked
|
|
1052
|
+
if ($action.length) {
|
|
1053
|
+
const id = $action.data('control');
|
|
1054
|
+
if (_.isFunction(this[id])) {
|
|
1055
|
+
this[id]();
|
|
1056
|
+
}
|
|
1765
1057
|
} else {
|
|
1766
|
-
|
|
1058
|
+
// default action is toggle play
|
|
1059
|
+
if (this.is('playing')) {
|
|
1060
|
+
this.pause();
|
|
1061
|
+
} else {
|
|
1062
|
+
this.play();
|
|
1063
|
+
}
|
|
1767
1064
|
}
|
|
1065
|
+
});
|
|
1768
1066
|
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
self.$volumeControl.removeClass('up down');
|
|
1773
|
-
}
|
|
1774
|
-
overing = false;
|
|
1775
|
-
}, 15000);
|
|
1776
|
-
self.$volumeControl.one('mouseleave' + _ns, function() {
|
|
1777
|
-
self.$volumeControl.removeClass('up down');
|
|
1778
|
-
overing = false;
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
});
|
|
1782
|
-
},
|
|
1067
|
+
this.$seek.on(`change${ns}`, (event, value) => {
|
|
1068
|
+
this.seek(value, true);
|
|
1069
|
+
});
|
|
1783
1070
|
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
*/
|
|
1788
|
-
_unbindEvents: function _unbindEvents() {
|
|
1789
|
-
this.$component.off(_ns);
|
|
1790
|
-
this.$player.off(_ns);
|
|
1791
|
-
this.$controls.off(_ns);
|
|
1792
|
-
this.$seek.off(_ns);
|
|
1793
|
-
this.$volume.off(_ns);
|
|
1794
|
-
|
|
1795
|
-
//if the volume is opened and the player destroyed,
|
|
1796
|
-
//prevent the callback to run
|
|
1797
|
-
if (this.overingTimer) {
|
|
1798
|
-
clearTimeout(this.overingTimer);
|
|
1799
|
-
}
|
|
1071
|
+
$(document).on(`updateVolume${ns}`, (event, value) => {
|
|
1072
|
+
this.setVolume(value);
|
|
1073
|
+
});
|
|
1800
1074
|
|
|
1801
|
-
|
|
1802
|
-
|
|
1075
|
+
this.$volume.on(`change${ns}`, (event, value) => {
|
|
1076
|
+
this.unmute();
|
|
1077
|
+
$(document).trigger(`updateVolume${ns}`, value);
|
|
1078
|
+
this.setVolume(value, true);
|
|
1079
|
+
});
|
|
1803
1080
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
* @param {Number} value
|
|
1807
|
-
* @private
|
|
1808
|
-
*/
|
|
1809
|
-
_updateVolumeSlider: function _updateVolumeSlider(value) {
|
|
1810
|
-
if (this.$volumeSlider) {
|
|
1811
|
-
this.$volumeSlider.val(value);
|
|
1812
|
-
}
|
|
1813
|
-
},
|
|
1081
|
+
this.$sound.on(`mouseover${ns}`, 'a', () => {
|
|
1082
|
+
let position;
|
|
1814
1083
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
this._storeVolume(this.volume);
|
|
1824
|
-
if (!internal) {
|
|
1825
|
-
this._updateVolumeSlider(value);
|
|
1826
|
-
}
|
|
1827
|
-
},
|
|
1084
|
+
if (!overing && !this.$volumeControl.hasClass('up') && !this.$volumeControl.hasClass('down')) {
|
|
1085
|
+
overing = true;
|
|
1086
|
+
position = this.$controls[0].getBoundingClientRect();
|
|
1087
|
+
if (position && position.top && position.top < volumePositionThreshold) {
|
|
1088
|
+
this.$volumeControl.addClass('down');
|
|
1089
|
+
} else {
|
|
1090
|
+
this.$volumeControl.addClass('up');
|
|
1091
|
+
}
|
|
1828
1092
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1093
|
+
//close the volume control after 15s
|
|
1094
|
+
this.overingTimer = _.delay(() => {
|
|
1095
|
+
if (this.$volumeControl) {
|
|
1096
|
+
this.$volumeControl.removeClass('up down');
|
|
1097
|
+
}
|
|
1098
|
+
overing = false;
|
|
1099
|
+
}, 15000);
|
|
1100
|
+
this.$volumeControl.one(`mouseleave${ns}`, () => {
|
|
1101
|
+
this.$volumeControl.removeClass('up down');
|
|
1102
|
+
overing = false;
|
|
1103
|
+
});
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
},
|
|
1839
1107
|
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
this.$
|
|
1848
|
-
|
|
1849
|
-
|
|
1108
|
+
/**
|
|
1109
|
+
* Unbinds events from the rendered player
|
|
1110
|
+
* @private
|
|
1111
|
+
*/
|
|
1112
|
+
_unbindEvents() {
|
|
1113
|
+
this.$component.off(ns);
|
|
1114
|
+
this.$player.off(ns);
|
|
1115
|
+
this.$controls.off(ns);
|
|
1116
|
+
this.$seek.off(ns);
|
|
1117
|
+
this.$volume.off(ns);
|
|
1118
|
+
|
|
1119
|
+
//if the volume is opened and the player destroyed,
|
|
1120
|
+
//prevent the callback to run
|
|
1121
|
+
if (this.overingTimer) {
|
|
1122
|
+
clearTimeout(this.overingTimer);
|
|
1123
|
+
}
|
|
1850
1124
|
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
* @param {Number} value
|
|
1854
|
-
* @param {*} [internal]
|
|
1855
|
-
* @private
|
|
1856
|
-
*/
|
|
1857
|
-
_updatePosition: function _updatePosition(value, internal) {
|
|
1858
|
-
this.position = Math.max(0, Math.min(this.duration, parseFloat(value)));
|
|
1125
|
+
$(document).off(ns);
|
|
1126
|
+
},
|
|
1859
1127
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1128
|
+
/**
|
|
1129
|
+
* Updates the volume slider
|
|
1130
|
+
* @param {Number} value
|
|
1131
|
+
* @private
|
|
1132
|
+
*/
|
|
1133
|
+
_updateVolumeSlider(value) {
|
|
1134
|
+
if (this.$volumeSlider) {
|
|
1135
|
+
this.$volumeSlider.val(value);
|
|
1136
|
+
}
|
|
1137
|
+
},
|
|
1865
1138
|
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
this.
|
|
1874
|
-
this
|
|
1875
|
-
|
|
1139
|
+
/**
|
|
1140
|
+
* Updates the displayed volume
|
|
1141
|
+
* @param {Number} value
|
|
1142
|
+
* @param {*} [internal]
|
|
1143
|
+
* @private
|
|
1144
|
+
*/
|
|
1145
|
+
_updateVolume(value, internal) {
|
|
1146
|
+
this.volume = Math.max(volumeMin, Math.min(volumeMax, parseFloat(value)));
|
|
1147
|
+
this._storeVolume(this.volume);
|
|
1148
|
+
if (!internal) {
|
|
1149
|
+
this._updateVolumeSlider(value);
|
|
1150
|
+
}
|
|
1151
|
+
},
|
|
1876
1152
|
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1153
|
+
/**
|
|
1154
|
+
* Updates the time slider
|
|
1155
|
+
* @param {Number} value
|
|
1156
|
+
* @private
|
|
1157
|
+
*/
|
|
1158
|
+
_updatePositionSlider(value) {
|
|
1159
|
+
if (this.$seekSlider) {
|
|
1160
|
+
this.$seekSlider.val(value);
|
|
1161
|
+
}
|
|
1162
|
+
},
|
|
1882
1163
|
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
this.$duration.text(_timerFormat(value)).show();
|
|
1892
|
-
} else {
|
|
1893
|
-
this.$duration.hide();
|
|
1164
|
+
/**
|
|
1165
|
+
* Updates the time label
|
|
1166
|
+
* @param {Number} value
|
|
1167
|
+
* @private
|
|
1168
|
+
*/
|
|
1169
|
+
_updatePositionLabel(value) {
|
|
1170
|
+
if (this.$position) {
|
|
1171
|
+
this.$position.text(timerFormat(value));
|
|
1894
1172
|
}
|
|
1895
|
-
}
|
|
1896
|
-
},
|
|
1173
|
+
},
|
|
1897
1174
|
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
this._updateDurationLabel(this.duration);
|
|
1907
|
-
},
|
|
1175
|
+
/**
|
|
1176
|
+
* Updates the displayed time position
|
|
1177
|
+
* @param {Number} value
|
|
1178
|
+
* @param {*} [internal]
|
|
1179
|
+
* @private
|
|
1180
|
+
*/
|
|
1181
|
+
_updatePosition(value, internal) {
|
|
1182
|
+
this.position = Math.max(0, Math.min(this.duration || +Infinity, parseFloat(value)));
|
|
1908
1183
|
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
this._updateDuration(this.player.getDuration());
|
|
1915
|
-
this._setState('ready', true);
|
|
1916
|
-
this._setState('canplay', true);
|
|
1917
|
-
this._setState('canpause', this.config.canPause);
|
|
1918
|
-
this._setState('canseek', this.config.canSeek);
|
|
1919
|
-
this._setState('loading', false);
|
|
1920
|
-
|
|
1921
|
-
/**
|
|
1922
|
-
* Triggers a media ready event
|
|
1923
|
-
* @event mediaplayer#ready
|
|
1924
|
-
*/
|
|
1925
|
-
this.trigger('ready');
|
|
1926
|
-
|
|
1927
|
-
// set the initial state
|
|
1928
|
-
this.setVolume(this.volume);
|
|
1929
|
-
this.mute(!!this.startMuted);
|
|
1930
|
-
if (this.autoStartAt) {
|
|
1931
|
-
this.seek(this.autoStartAt);
|
|
1932
|
-
} else if (this.autoStart) {
|
|
1933
|
-
this.play();
|
|
1934
|
-
}
|
|
1935
|
-
},
|
|
1184
|
+
if (!internal && this.duration) {
|
|
1185
|
+
this._updatePositionSlider(this.position);
|
|
1186
|
+
}
|
|
1187
|
+
this._updatePositionLabel(this.position);
|
|
1188
|
+
},
|
|
1936
1189
|
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1190
|
+
/**
|
|
1191
|
+
* Updates the duration slider
|
|
1192
|
+
* @param {Number} value
|
|
1193
|
+
* @private
|
|
1194
|
+
*/
|
|
1195
|
+
_updateDurationSlider(value) {
|
|
1196
|
+
if (this.$seekSlider) {
|
|
1197
|
+
this._destroySlider(this.$seekSlider);
|
|
1198
|
+
this.$seekSlider = null;
|
|
1199
|
+
}
|
|
1947
1200
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1201
|
+
if (value && isFinite(value)) {
|
|
1202
|
+
this.$seekSlider = this._renderSlider(this.$seek, 0, 0, value);
|
|
1203
|
+
this.$seekSlider.attr('disabled', !this.config.canSeek);
|
|
1204
|
+
}
|
|
1205
|
+
},
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Updates the duration label
|
|
1209
|
+
* @param {Number} value
|
|
1210
|
+
* @private
|
|
1211
|
+
*/
|
|
1212
|
+
_updateDurationLabel(value) {
|
|
1213
|
+
if (this.$duration) {
|
|
1214
|
+
if (value && isFinite(value)) {
|
|
1215
|
+
this.$duration.text(timerFormat(value)).show();
|
|
1216
|
+
} else {
|
|
1217
|
+
this.$duration.hide();
|
|
1962
1218
|
}
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1219
|
+
}
|
|
1220
|
+
},
|
|
1965
1221
|
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1222
|
+
/**
|
|
1223
|
+
* Updates the displayed duration
|
|
1224
|
+
* @param {Number|String} value
|
|
1225
|
+
* @private
|
|
1226
|
+
*/
|
|
1227
|
+
_updateDuration(value) {
|
|
1228
|
+
const duration = Math.abs(parseFloat(value));
|
|
1229
|
+
if (duration !== this.duration) {
|
|
1230
|
+
this.duration = duration;
|
|
1231
|
+
this._updateDurationSlider(this.duration);
|
|
1232
|
+
this._updateDurationLabel(this.duration);
|
|
1233
|
+
}
|
|
1234
|
+
},
|
|
1973
1235
|
|
|
1974
1236
|
/**
|
|
1975
|
-
*
|
|
1976
|
-
* @
|
|
1237
|
+
* Event called when the media is ready
|
|
1238
|
+
* @private
|
|
1977
1239
|
*/
|
|
1978
|
-
|
|
1979
|
-
|
|
1240
|
+
_onReady() {
|
|
1241
|
+
if (this.is('error')) {
|
|
1242
|
+
this._setState('error', false);
|
|
1243
|
+
}
|
|
1980
1244
|
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1245
|
+
const duration = this.player.getDuration();
|
|
1246
|
+
const timePreview = this.config.preview || duration;
|
|
1247
|
+
if (timePreview) {
|
|
1248
|
+
this._updateDuration(duration);
|
|
1249
|
+
}
|
|
1250
|
+
this.setInitialStates();
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Triggers a media ready event
|
|
1254
|
+
* @event mediaplayer#ready
|
|
1255
|
+
*/
|
|
1256
|
+
this.trigger('ready');
|
|
1257
|
+
|
|
1258
|
+
// set the initial state
|
|
1259
|
+
this.setVolume(this.volume);
|
|
1260
|
+
this.mute(!!this.startMuted);
|
|
1261
|
+
if (this.autoStartAt) {
|
|
1262
|
+
this.seek(this.autoStartAt);
|
|
1263
|
+
} else if (this.autoStart) {
|
|
1264
|
+
this.play();
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
if (this.config.preview && this.$container && this.config.height && this.config.height !== 'auto') {
|
|
1268
|
+
this._setMaxHeight();
|
|
1269
|
+
}
|
|
1270
|
+
},
|
|
1987
1271
|
|
|
1988
1272
|
/**
|
|
1989
|
-
*
|
|
1990
|
-
*
|
|
1273
|
+
* Set max height limit for container
|
|
1274
|
+
* using by old media items with defined height.
|
|
1275
|
+
* @private
|
|
1991
1276
|
*/
|
|
1992
|
-
|
|
1993
|
-
|
|
1277
|
+
_setMaxHeight() {
|
|
1278
|
+
const $video = this.$container.find('video.video');
|
|
1279
|
+
const controlsHeight = parseInt(window.getComputedStyle(this.$controls[0]).height);
|
|
1280
|
+
const scale = $video.height() / this.config.height;
|
|
1281
|
+
const playerWidth = this.$container.find('.player').width();
|
|
1282
|
+
const videoWidth = $video.width() / scale;
|
|
1283
|
+
|
|
1284
|
+
if (videoWidth > playerWidth) {
|
|
1285
|
+
this.execute('setSize', '100%', 'auto');
|
|
1286
|
+
} else {
|
|
1287
|
+
this.$component.css({ maxHeight: `${this.config.height + controlsHeight}px` });
|
|
1288
|
+
this.execute('setSize', Math.floor(videoWidth), 'auto');
|
|
1289
|
+
}
|
|
1290
|
+
},
|
|
1994
1291
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
1292
|
+
/**
|
|
1293
|
+
* Update volume in DBIndex store
|
|
1294
|
+
* @param {Number} volume
|
|
1295
|
+
* @returns {Promise}
|
|
1296
|
+
* @private
|
|
1297
|
+
*/
|
|
1298
|
+
_storeVolume(volume) {
|
|
1299
|
+
return store('mediaVolume').then(volumeStore => volumeStore.setItem('volume', volume));
|
|
1300
|
+
},
|
|
2001
1301
|
|
|
2002
1302
|
/**
|
|
2003
|
-
*
|
|
2004
|
-
* @
|
|
1303
|
+
* Get volume from DBIndex store
|
|
1304
|
+
* @returns {Promise}
|
|
1305
|
+
* @private
|
|
2005
1306
|
*/
|
|
2006
|
-
|
|
2007
|
-
|
|
1307
|
+
_updateVolumeFromStore() {
|
|
1308
|
+
return store('mediaVolume')
|
|
1309
|
+
.then(volumeStore => volumeStore.getItem('volume'))
|
|
1310
|
+
.then(volume => {
|
|
1311
|
+
if (_.isNumber(volume)) {
|
|
1312
|
+
this.volume = Math.max(volumeMin, Math.min(volumeMax, parseFloat(volume)));
|
|
1313
|
+
this.setVolume(this.volume);
|
|
1314
|
+
}
|
|
1315
|
+
});
|
|
1316
|
+
},
|
|
2008
1317
|
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
1318
|
+
/**
|
|
1319
|
+
* Event called when the media throws unrecoverable error
|
|
1320
|
+
* @private
|
|
1321
|
+
*/
|
|
1322
|
+
_onError() {
|
|
1323
|
+
this._setState('error', true);
|
|
1324
|
+
this._setState('loading', false);
|
|
1325
|
+
|
|
1326
|
+
/**
|
|
1327
|
+
* Triggers an unrecoverable media error event
|
|
1328
|
+
* @event mediaplayer#error
|
|
1329
|
+
*/
|
|
1330
|
+
this.trigger('error');
|
|
1331
|
+
},
|
|
2015
1332
|
|
|
2016
1333
|
/**
|
|
2017
|
-
*
|
|
2018
|
-
* @
|
|
1334
|
+
* Event called when the media is played
|
|
1335
|
+
* @private
|
|
2019
1336
|
*/
|
|
2020
|
-
|
|
2021
|
-
|
|
1337
|
+
_onPlay() {
|
|
1338
|
+
this._playingState(true);
|
|
1339
|
+
this._setState('preview', true);
|
|
2022
1340
|
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
this._playingState(false, true);
|
|
2030
|
-
this._updatePosition(0);
|
|
1341
|
+
/**
|
|
1342
|
+
* Triggers a media playback event
|
|
1343
|
+
* @event mediaplayer#play
|
|
1344
|
+
*/
|
|
1345
|
+
this.trigger('play', this.player && this.player.getMedia());
|
|
1346
|
+
},
|
|
2031
1347
|
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1348
|
+
/**
|
|
1349
|
+
* Event called when the media is paused
|
|
1350
|
+
* @private
|
|
1351
|
+
*/
|
|
1352
|
+
_onPause() {
|
|
1353
|
+
this._playingState(false);
|
|
2035
1354
|
|
|
2036
1355
|
/**
|
|
2037
|
-
* Triggers a
|
|
2038
|
-
* @event mediaplayer#
|
|
1356
|
+
* Triggers a media paused event
|
|
1357
|
+
* @event mediaplayer#pause
|
|
2039
1358
|
*/
|
|
2040
|
-
this.trigger('
|
|
2041
|
-
}
|
|
2042
|
-
this.restart();
|
|
2043
|
-
} else if (parseInt(this.config.replayTimeout, 10) > 0) {
|
|
2044
|
-
this.replayTimeoutStartMs = new window.Date().getTime();
|
|
2045
|
-
this._replayTimeout();
|
|
2046
|
-
}
|
|
1359
|
+
this.trigger('pause');
|
|
1360
|
+
},
|
|
2047
1361
|
|
|
2048
1362
|
/**
|
|
2049
|
-
*
|
|
2050
|
-
* @
|
|
1363
|
+
* Event called when the media is ended
|
|
1364
|
+
* @private
|
|
2051
1365
|
*/
|
|
2052
|
-
|
|
2053
|
-
|
|
1366
|
+
_onEnd() {
|
|
1367
|
+
this.timesPlayed++;
|
|
1368
|
+
this._playingState(false, true);
|
|
1369
|
+
this._updatePosition(0);
|
|
1370
|
+
|
|
1371
|
+
// disable when the play limit is reached
|
|
1372
|
+
if (this._playLimitReached()) {
|
|
1373
|
+
if (!this.is('disabled')) {
|
|
1374
|
+
this.disable();
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Triggers a play limit reached event
|
|
1378
|
+
* @event mediaplayer#limitreached
|
|
1379
|
+
*/
|
|
1380
|
+
this.trigger('limitreached');
|
|
1381
|
+
} else if (this.loop) {
|
|
1382
|
+
this.restart();
|
|
1383
|
+
} else if (parseInt(this.config.replayTimeout, 10) > 0) {
|
|
1384
|
+
this.replayTimeoutStartMs = new window.Date().getTime();
|
|
1385
|
+
this._replayTimeout();
|
|
1386
|
+
}
|
|
2054
1387
|
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
1388
|
+
/**
|
|
1389
|
+
* Triggers a media ended event
|
|
1390
|
+
* @event mediaplayer#ended
|
|
1391
|
+
*/
|
|
1392
|
+
this.trigger('ended');
|
|
1393
|
+
},
|
|
2061
1394
|
|
|
2062
1395
|
/**
|
|
2063
|
-
*
|
|
2064
|
-
* @
|
|
1396
|
+
* Event called when the playback is playing
|
|
1397
|
+
* @private
|
|
2065
1398
|
*/
|
|
2066
|
-
|
|
2067
|
-
|
|
1399
|
+
_onPlaying() {
|
|
1400
|
+
this._setState('preview', true);
|
|
1401
|
+
this._setState('stalled', false);
|
|
1402
|
+
this._setState('ready', true);
|
|
1403
|
+
},
|
|
2068
1404
|
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
1405
|
+
/**
|
|
1406
|
+
* Event called when the playback is stalled
|
|
1407
|
+
* @private
|
|
1408
|
+
*/
|
|
1409
|
+
_onStalled() {
|
|
1410
|
+
this._setState('stalled', true);
|
|
1411
|
+
this._setState('ready', false);
|
|
1412
|
+
},
|
|
2076
1413
|
|
|
2077
|
-
|
|
1414
|
+
/**
|
|
1415
|
+
* Event called when the time position has changed
|
|
1416
|
+
* @private
|
|
1417
|
+
*/
|
|
1418
|
+
_onTimeUpdate() {
|
|
1419
|
+
this._updatePosition(this.player.getPosition());
|
|
2078
1420
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
1421
|
+
/**
|
|
1422
|
+
* Triggers a media time update event
|
|
1423
|
+
* @event mediaplayer#update
|
|
1424
|
+
*/
|
|
1425
|
+
this.trigger('update');
|
|
1426
|
+
},
|
|
2085
1427
|
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
},
|
|
1428
|
+
/**
|
|
1429
|
+
* Run a timer to disable the possibility of replaying a media
|
|
1430
|
+
* @private
|
|
1431
|
+
*/
|
|
1432
|
+
_replayTimeout() {
|
|
1433
|
+
const nowMs = new window.Date().getTime(),
|
|
1434
|
+
elapsedSeconds = Math.floor((nowMs - this.replayTimeoutStartMs) / 1000);
|
|
2094
1435
|
|
|
2095
|
-
|
|
2096
|
-
* Checks if the play limit has been reached
|
|
2097
|
-
* @returns {Boolean}
|
|
2098
|
-
* @private
|
|
2099
|
-
*/
|
|
2100
|
-
_playLimitReached: function _playLimitReached() {
|
|
2101
|
-
return this.config.maxPlays && this.timesPlayed >= this.config.maxPlays;
|
|
2102
|
-
},
|
|
1436
|
+
this.timerId = requestAnimationFrame(this._replayTimeout.bind(this));
|
|
2103
1437
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
_canPlay: function _canPlay() {
|
|
2110
|
-
return this.is('ready') && !this.is('disabled') && !this.is('hidden') && !this._playLimitReached();
|
|
2111
|
-
},
|
|
1438
|
+
if (elapsedSeconds >= parseInt(this.config.replayTimeout, 10)) {
|
|
1439
|
+
this.disable();
|
|
1440
|
+
cancelAnimationFrame(this.timerId);
|
|
1441
|
+
}
|
|
1442
|
+
},
|
|
2112
1443
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
1444
|
+
/**
|
|
1445
|
+
* Checks if the play limit has been reached
|
|
1446
|
+
* @returns {Boolean}
|
|
1447
|
+
* @private
|
|
1448
|
+
*/
|
|
1449
|
+
_playLimitReached() {
|
|
1450
|
+
return this.config.maxPlays && this.timesPlayed >= this.config.maxPlays;
|
|
1451
|
+
},
|
|
2121
1452
|
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
1453
|
+
/**
|
|
1454
|
+
* Checks if the media can be played
|
|
1455
|
+
* @returns {Boolean}
|
|
1456
|
+
* @private
|
|
1457
|
+
*/
|
|
1458
|
+
_canPlay() {
|
|
1459
|
+
return (
|
|
1460
|
+
(this.is('ready') || this.is('stalled')) &&
|
|
1461
|
+
!this.is('disabled') &&
|
|
1462
|
+
!this.is('hidden') &&
|
|
1463
|
+
!this._playLimitReached()
|
|
1464
|
+
);
|
|
1465
|
+
},
|
|
2130
1466
|
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
1467
|
+
/**
|
|
1468
|
+
* Checks if the media can be paused
|
|
1469
|
+
* @returns {Boolean}
|
|
1470
|
+
* @private
|
|
1471
|
+
*/
|
|
1472
|
+
_canPause() {
|
|
1473
|
+
return !!this.config.canPause;
|
|
1474
|
+
},
|
|
2139
1475
|
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
1476
|
+
/**
|
|
1477
|
+
* Checks if the media can be sought
|
|
1478
|
+
* @returns {Boolean}
|
|
1479
|
+
* @private
|
|
1480
|
+
*/
|
|
1481
|
+
_canSeek() {
|
|
1482
|
+
return !!this.config.canSeek;
|
|
1483
|
+
},
|
|
2148
1484
|
|
|
2149
|
-
|
|
1485
|
+
/**
|
|
1486
|
+
* Checks if the playback can be resumed
|
|
1487
|
+
* @returns {Boolean}
|
|
1488
|
+
* @private
|
|
1489
|
+
*/
|
|
1490
|
+
_canResume() {
|
|
1491
|
+
return this.is('paused') && this._canPlay();
|
|
1492
|
+
},
|
|
2150
1493
|
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
1494
|
+
/**
|
|
1495
|
+
* Sets the media is in a particular state
|
|
1496
|
+
* @param {String} name
|
|
1497
|
+
* @param {Boolean} value
|
|
1498
|
+
* @returns {mediaplayer}
|
|
1499
|
+
*/
|
|
1500
|
+
_setState(name, value) {
|
|
1501
|
+
value = !!value;
|
|
2154
1502
|
|
|
2155
|
-
|
|
2156
|
-
},
|
|
1503
|
+
this.config.is[name] = value;
|
|
2157
1504
|
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
* @returns {mediaplayer}
|
|
2162
|
-
* @private
|
|
2163
|
-
*/
|
|
2164
|
-
_fromState: function _fromState(stateName) {
|
|
2165
|
-
this._setState(stateName, false);
|
|
2166
|
-
this.resume();
|
|
1505
|
+
if (this.$component) {
|
|
1506
|
+
this.$component.toggleClass(name, value);
|
|
1507
|
+
}
|
|
2167
1508
|
|
|
2168
|
-
|
|
2169
|
-
|
|
1509
|
+
return this;
|
|
1510
|
+
},
|
|
2170
1511
|
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
1512
|
+
/**
|
|
1513
|
+
* Restores the media player from a particular state and resumes the playback
|
|
1514
|
+
* @param {String} stateName
|
|
1515
|
+
* @returns {mediaplayer}
|
|
1516
|
+
* @private
|
|
1517
|
+
*/
|
|
1518
|
+
_fromState(stateName) {
|
|
1519
|
+
this._setState(stateName, false);
|
|
1520
|
+
this.resume();
|
|
2180
1521
|
|
|
2181
|
-
|
|
2182
|
-
|
|
1522
|
+
return this;
|
|
1523
|
+
},
|
|
2183
1524
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
this._setState('paused', !state);
|
|
2194
|
-
this._setState('ended', !!ended);
|
|
1525
|
+
/**
|
|
1526
|
+
* Sets the media player to a particular state and pauses the playback
|
|
1527
|
+
* @param {String} stateName
|
|
1528
|
+
* @returns {mediaplayer}
|
|
1529
|
+
* @private
|
|
1530
|
+
*/
|
|
1531
|
+
_toState(stateName) {
|
|
1532
|
+
this.pause();
|
|
1533
|
+
this._setState(stateName, true);
|
|
2195
1534
|
|
|
2196
|
-
|
|
2197
|
-
|
|
1535
|
+
return this;
|
|
1536
|
+
},
|
|
2198
1537
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
1538
|
+
/**
|
|
1539
|
+
* Sets the playing state
|
|
1540
|
+
* @param {Boolean} state
|
|
1541
|
+
* @param {Boolean} [ended]
|
|
1542
|
+
* @returns {mediaplayer}
|
|
1543
|
+
* @private
|
|
1544
|
+
*/
|
|
1545
|
+
_playingState(state, ended) {
|
|
1546
|
+
this._setState('playing', !!state);
|
|
1547
|
+
this._setState('paused', !state);
|
|
1548
|
+
this._setState('ended', !!ended);
|
|
2208
1549
|
|
|
2209
|
-
|
|
2210
|
-
|
|
1550
|
+
return this;
|
|
1551
|
+
},
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* Executes a command onto the media
|
|
1555
|
+
* @param {String} command - The name of the command to execute
|
|
1556
|
+
* @param {*} args - additional arguments
|
|
1557
|
+
* @returns {*}
|
|
1558
|
+
* @private
|
|
1559
|
+
*/
|
|
1560
|
+
execute(command, ...args) {
|
|
1561
|
+
if (this.player && 'function' === typeof this.player[command]) {
|
|
1562
|
+
return this.player[command](...args);
|
|
1563
|
+
}
|
|
2211
1564
|
}
|
|
2212
|
-
}
|
|
2213
|
-
};
|
|
1565
|
+
};
|
|
2214
1566
|
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
* @param {Object} config
|
|
2218
|
-
* @param {String} config.type - The type of media to play
|
|
2219
|
-
* @param {String|Array} config.url - The URL to the media
|
|
2220
|
-
* @param {String|jQuery|HTMLElement} [config.renderTo] - An optional container in which renders the player
|
|
2221
|
-
* @param {Boolean} [config.loop] - The media will be played continuously
|
|
2222
|
-
* @param {Boolean} [config.canPause] - The play can be paused
|
|
2223
|
-
* @param {Boolean} [config.startMuted] - The player should be initially muted
|
|
2224
|
-
* @param {Boolean} [config.autoStart] - The player starts as soon as it is displayed
|
|
2225
|
-
* @param {Number} [config.autoStartAt] - The time position at which the player should start
|
|
2226
|
-
* @param {Number} [config.maxPlays] - Sets a few number of plays (default: infinite)
|
|
2227
|
-
* @param {Number} [config.volume] - Sets the sound volume (default: 80)
|
|
2228
|
-
* @param {Number} [config.width] - Sets the width of the player (default: depends on media type)
|
|
2229
|
-
* @param {Number} [config.height] - Sets the height of the player (default: depends on media type)
|
|
2230
|
-
* @event render - Event triggered when the player is rendering
|
|
2231
|
-
* @event error - Event triggered when the player throws an unrecoverable error
|
|
2232
|
-
* @event recovererror - Event triggered when the player throws a recoverable error
|
|
2233
|
-
* @event ready - Event triggered when the player is fully ready
|
|
2234
|
-
* @event play - Event triggered when the playback is starting
|
|
2235
|
-
* @event update - Event triggered while the player is playing
|
|
2236
|
-
* @event pause - Event triggered when the playback is paused
|
|
2237
|
-
* @event ended - Event triggered when the playback is ended
|
|
2238
|
-
* @event limitreached - Event triggered when the play limit has been reached
|
|
2239
|
-
* @event destroy - Event triggered when the player is destroying
|
|
2240
|
-
* @returns {mediaplayer}
|
|
2241
|
-
*/
|
|
2242
|
-
var mediaplayerFactory = function mediaplayerFactory(config) {
|
|
2243
|
-
var player = _.clone(mediaplayer);
|
|
2244
|
-
return player.init(config);
|
|
2245
|
-
};
|
|
1567
|
+
return mediaplayer.init(config);
|
|
1568
|
+
}
|
|
2246
1569
|
|
|
2247
1570
|
/**
|
|
2248
1571
|
* Tells if the browser can play audio and video
|
|
@@ -2251,7 +1574,7 @@ var mediaplayerFactory = function mediaplayerFactory(config) {
|
|
|
2251
1574
|
* @type {Boolean}
|
|
2252
1575
|
*/
|
|
2253
1576
|
mediaplayerFactory.canPlay = function canPlay(type, mime) {
|
|
2254
|
-
return
|
|
1577
|
+
return support.canPlay(type, mime);
|
|
2255
1578
|
};
|
|
2256
1579
|
|
|
2257
1580
|
/**
|
|
@@ -2260,7 +1583,7 @@ mediaplayerFactory.canPlay = function canPlay(type, mime) {
|
|
|
2260
1583
|
* @type {Boolean}
|
|
2261
1584
|
*/
|
|
2262
1585
|
mediaplayerFactory.canPlayAudio = function canPlayAudio(mime) {
|
|
2263
|
-
return
|
|
1586
|
+
return support.canPlayAudio(mime);
|
|
2264
1587
|
};
|
|
2265
1588
|
|
|
2266
1589
|
/**
|
|
@@ -2269,7 +1592,7 @@ mediaplayerFactory.canPlayAudio = function canPlayAudio(mime) {
|
|
|
2269
1592
|
* @type {Boolean}
|
|
2270
1593
|
*/
|
|
2271
1594
|
mediaplayerFactory.canPlayVideo = function canPlayVideo(mime) {
|
|
2272
|
-
return
|
|
1595
|
+
return support.canPlayVideo(mime);
|
|
2273
1596
|
};
|
|
2274
1597
|
|
|
2275
1598
|
/**
|
|
@@ -2277,14 +1600,7 @@ mediaplayerFactory.canPlayVideo = function canPlayVideo(mime) {
|
|
|
2277
1600
|
* @returns {Boolean}
|
|
2278
1601
|
*/
|
|
2279
1602
|
mediaplayerFactory.canControl = function canControl() {
|
|
2280
|
-
return
|
|
1603
|
+
return support.canControl();
|
|
2281
1604
|
};
|
|
2282
1605
|
|
|
2283
|
-
/**
|
|
2284
|
-
* The polling interval used to update the progress bar while playing a YouTube video.
|
|
2285
|
-
* Note : the YouTube API does not provide events to update this progress bar...
|
|
2286
|
-
* @type {Number}
|
|
2287
|
-
*/
|
|
2288
|
-
mediaplayerFactory.youtubePolling = 100;
|
|
2289
|
-
|
|
2290
1606
|
export default mediaplayerFactory;
|