@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.
Files changed (43) hide show
  1. package/dist/ckeditor/ckConfigurator.js +9 -1
  2. package/dist/mediaEditor/mediaEditorComponent.js +5 -3
  3. package/dist/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +46 -25
  4. package/dist/mediaplayer/css/player.css +104 -14
  5. package/dist/mediaplayer/css/player.css.map +1 -1
  6. package/dist/mediaplayer/players/html5.js +767 -0
  7. package/dist/mediaplayer/players/youtube.js +470 -0
  8. package/dist/mediaplayer/players.js +35 -0
  9. package/dist/mediaplayer/support.js +134 -0
  10. package/dist/mediaplayer/utils/reminder.js +198 -0
  11. package/dist/mediaplayer/utils/timeObserver.js +149 -0
  12. package/dist/mediaplayer/youtubeManager.js +177 -0
  13. package/dist/mediaplayer.js +1251 -1912
  14. package/dist/previewer.js +25 -19
  15. package/package.json +1 -1
  16. package/scss/basic.scss +1 -0
  17. package/scss/inc/_jquery.nouislider.scss +254 -0
  18. package/src/ckeditor/ckConfigurator.js +10 -1
  19. package/src/itemButtonList/css/item-button-list.css +225 -0
  20. package/src/itemButtonList/css/item-button-list.css.map +1 -0
  21. package/src/mediaEditor/mediaEditorComponent.js +25 -26
  22. package/src/mediaEditor/plugins/mediaDimension/mediaDimensionComponent.js +83 -63
  23. package/src/mediaplayer/css/player.css +104 -14
  24. package/src/mediaplayer/css/player.css.map +1 -1
  25. package/src/mediaplayer/players/html5.js +564 -0
  26. package/src/mediaplayer/players/youtube.js +323 -0
  27. package/src/mediaplayer/players.js +29 -0
  28. package/src/mediaplayer/scss/player.scss +125 -16
  29. package/src/mediaplayer/support.js +126 -0
  30. package/src/mediaplayer/tpl/audio.tpl +6 -0
  31. package/src/mediaplayer/tpl/player.tpl +11 -32
  32. package/src/mediaplayer/tpl/source.tpl +1 -0
  33. package/src/mediaplayer/tpl/video.tpl +6 -0
  34. package/src/mediaplayer/tpl/youtube.tpl +1 -0
  35. package/src/mediaplayer/utils/reminder.js +184 -0
  36. package/src/mediaplayer/utils/timeObserver.js +143 -0
  37. package/src/mediaplayer/youtubeManager.js +161 -0
  38. package/src/mediaplayer.js +1217 -1901
  39. package/src/previewer.js +40 -33
  40. package/src/searchModal/css/advancedSearch.css +190 -0
  41. package/src/searchModal/css/advancedSearch.css.map +1 -0
  42. package/src/searchModal/css/searchModal.css +506 -0
  43. package/src/searchModal/css/searchModal.css.map +1 -0
@@ -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
- var _reYoutube = /([?&\/]v[=\/])([\w-]+)([&\/]?)/;
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
- var _volumeMin = 0;
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
- var _volumeRange = _volumeMax - _volumeMin;
48
+ const volumeMax = 100;
88
49
 
89
50
  /**
90
- * Threshold (minium requires space above the player) to display the volume
51
+ * Threshold (minimum required space above the player) to display the volume
91
52
  * above the bar.
92
53
  * @type {Number}
93
54
  */
94
- var volumePositionThreshold = 200;
55
+ const volumePositionThreshold = 200;
95
56
 
96
57
  /**
97
58
  * Some default values
98
59
  * @type {Object}
99
- * @private
100
60
  */
101
- var _defaults = {
61
+ const defaults = {
102
62
  type: 'video/mp4',
103
63
  video: {
104
- width: 480,
105
- height: 270,
106
- minWidth: 200,
107
- minHeight: 200
64
+ width: '100%',
65
+ height: 'auto'
108
66
  },
109
67
  audio: {
110
- width: 400,
111
- height: 30,
112
- minWidth: 200,
113
- minHeight: 36
68
+ width: '100%',
69
+ height: 'auto'
70
+ },
71
+ youtube: {
72
+ width: 640,
73
+ height: 360
114
74
  },
115
75
  options: {
116
- volume: Math.floor(_volumeRange * 0.8),
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
- var _ensureNumber = function(value) {
162
- value = parseFloat(value);
163
- return isFinite(value) ? value : 0;
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
- var _leadingZero = function(n, len) {
174
- var value = n.toString();
105
+ const leadingZero = (n, len) => {
106
+ let value = n.toString();
175
107
  while (value.length < len) {
176
- value = '0' + 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
- var _timerFormat = function(time) {
188
- var seconds = Math.floor(time % 60);
189
- var minutes = Math.floor(time / 60) % 60;
190
- var hours = Math.floor(time / 3600);
191
- var parts = [];
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(_leadingZero(minutes, 2));
197
- parts.push(_leadingZero(seconds, 2));
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
- var _needTypeAdjust = function(type) {
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
- var _getAdjustedType = function(source) {
219
- var type = 'video/ogg';
220
- var url = (source && source.src) || source;
221
- var ext = url && url.substr(-4);
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
- var _configToSources = function(config) {
235
- var sources = config.sources || [];
236
- var url = config.url;
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 {HTMLMediaElement} media The media element on which check support
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
- var _checkSupport = function(media, mimeType) {
260
- var support = !!media.canPlayType;
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
- * Support dection
269
- * @type {Object}
270
- * @private
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
- var _support = {
226
+ function mediaplayerFactory(config) {
273
227
  /**
274
- * Checks if the browser can play video and audio
275
- * @param {String} [type] The type of media (audio or video)
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
- canPlay: function canPlay(type, mime) {
280
- if (type) {
281
- switch (type.toLowerCase()) {
282
- case 'audio':
283
- return this.canPlayAudio(mime);
284
- case 'youtube':
285
- case 'video':
286
- return this.canPlayVideo(mime);
287
- default:
288
- return false;
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
- return this.canPlayAudio() && this.canPlayVideo();
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
- * Checks if the browser can play audio
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
- return _checkSupport(this._mediaAudio, mime);
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
- * Checks if the browser can play video
309
- * @param {String} [mime] A media MIME type to check
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
- return _checkSupport(this._mediaVideo, mime);
318
- },
282
+ if (this.$component) {
283
+ this._unbindEvents();
284
+ this._destroySlider(this.$seekSlider);
285
+ this._destroySlider(this.$volumeSlider);
319
286
 
320
- /**
321
- * Checks if the browser allows to control the media playback
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
- * The Youtube API ready state
344
- * @type {Boolean}
345
- */
346
- ready: false,
292
+ return this;
293
+ },
347
294
 
348
- /**
349
- * A list of pending players
350
- * @type {Array}
351
- */
352
- pending: [],
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
- * Add a Youtube player
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
- * Removes a pending Youtube player
375
- * @param {String|jQuery|HTMLElement} elem
376
- * @param {Object} player
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
- * Install a Youtube player. The Youtube API must be ready
389
- * @param {String|jQuery|HTMLElement} elem
390
- * @param {Object} player
391
- * @param {Object} [options]
392
- * @param {Boolean} [options.controls]
393
- */
394
- create: function create(elem, player, options) {
395
- var $elem;
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
- if (!options) {
402
- options = {};
403
- }
326
+ if (renderTo) {
327
+ this.$container = $(renderTo).append(this.$component);
328
+ }
404
329
 
405
- $elem = $(elem);
406
-
407
- new window.YT.Player($elem.get(0), {
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
- * Called when the Youtube API is ready. Should install all pending players.
433
- */
434
- apiReady: function apiReady() {
435
- var self = this;
436
- var pending = this.pending;
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
- this.pending = [];
439
- this.ready = true;
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
- _.forEach(pending, function(args) {
442
- if (args) {
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
- * Checks if the Youtube API is ready to use
450
- * @returns {Boolean}
451
- */
452
- isApiReady: function isApiReady() {
453
- var apiReady = typeof window.YT !== 'undefined' && typeof window.YT.Player !== 'undefined';
454
- if (apiReady && !this.ready) {
455
- _youtubeManager.apiReady();
456
- }
457
- return apiReady;
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
- * Injects the Youtube API into the page
462
- */
463
- injectApi: function injectApi() {
464
- var self = this;
465
- if (!self.isApiReady()) {
466
- window.require(['https://www.youtube.com/iframe_api'], function() {
467
- var check = function() {
468
- if (!self.isApiReady()) {
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
- this.injected = true;
477
- }
478
- };
385
+ this.execute('seek', this.position);
479
386
 
480
- /**
481
- * Defines a player object dedicated to youtube media
482
- * @param {mediaplayer} mediaplayer
483
- * @private
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
- if (mediaplayer) {
501
- player = {
502
- init: function _youtubePlayerInit() {
503
- $media = mediaplayer.$media;
504
- media = null;
505
- destroyed = false;
393
+ return this;
394
+ },
506
395
 
507
- if ($media) {
508
- _youtubeManager.add($media, this, {
509
- controls: mediaplayer.is('nogui')
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
- return !!$media;
514
- },
515
-
516
- onReady: function _youtubePlayerOnReady(event) {
517
- var callbacks = this._callbacks;
407
+ this.execute('play');
518
408
 
519
- media = event.target;
520
- $media = $(media.getIframe());
521
- this._callbacks = null;
409
+ if (!this.is('ready')) {
410
+ this.autoStart = true;
411
+ }
522
412
 
523
- if (!destroyed) {
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
- if (initWidth && initHeight) {
534
- this.setSize(initWidth, initHeight);
535
- }
415
+ if (this.timerId) {
416
+ cancelAnimationFrame(this.timerId);
417
+ }
418
+ }
536
419
 
537
- mediaplayer._onReady();
420
+ return this;
421
+ },
538
422
 
539
- if (callbacks) {
540
- _.forEach(callbacks, function(cb) {
541
- cb();
542
- });
543
- }
544
- } else {
545
- this.destroy();
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
- stopPolling: function _youtubePlayerStopPolling() {
574
- if (interval) {
575
- clearInterval(interval);
576
- interval = null;
434
+ this.execute('pause');
435
+
436
+ if (!this.is('ready')) {
437
+ this.autoStart = false;
577
438
  }
578
- },
439
+ }
579
440
 
580
- startPolling: function _youtubePlayerStartPolling() {
581
- interval = setInterval(function() {
582
- mediaplayer._onTimeUpdate();
583
- }, mediaplayerFactory.youtubePolling);
584
- },
441
+ return this;
442
+ },
585
443
 
586
- destroy: function _youtubePlayerDestroy() {
587
- destroyed = true;
444
+ /**
445
+ * Resumes the media
446
+ * @returns {mediaplayer}
447
+ */
448
+ resume() {
449
+ if (this._canResume()) {
450
+ this.play();
451
+ }
588
452
 
589
- if (media) {
590
- loopEvents(function(ev) {
591
- media.removeEventListener(ev);
592
- });
593
- media.destroy();
594
- } else {
595
- _youtubeManager.remove($media, this);
596
- }
453
+ return this;
454
+ },
597
455
 
598
- this.stopPolling();
456
+ /**
457
+ * Stops the playback
458
+ * @returns {mediaplayer}
459
+ */
460
+ stop() {
461
+ this.loop = false;
462
+ this.execute('stop');
599
463
 
600
- $media = null;
601
- media = null;
602
- },
464
+ if (!this.is('ready')) {
465
+ this.autoStart = false;
466
+ }
603
467
 
604
- getPosition: function _youtubePlayerGetPosition() {
605
- if (media) {
606
- return media.getCurrentTime();
607
- }
608
- return 0;
609
- },
468
+ return this;
469
+ },
610
470
 
611
- getDuration: function _youtubePlayerGetDuration() {
612
- if (media) {
613
- return media.getDuration();
614
- }
615
- return 0;
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
- getVolume: function _youtubePlayerGetVolume() {
619
- var value = 0;
620
- if (media) {
621
- value = (media.getVolume() * _volumeRange) / 100 + _volumeMin;
622
- }
623
- return value;
624
- },
481
+ /**
482
+ * Restarts the media from the beginning
483
+ * @returns {mediaplayer}
484
+ */
485
+ restart() {
486
+ this.play(0);
625
487
 
626
- setVolume: function _youtubePlayerSetVolume(value) {
627
- if (media) {
628
- media.setVolume(((parseFloat(value) - _volumeMin) / _volumeRange) * 100);
629
- }
630
- },
488
+ return this;
489
+ },
631
490
 
632
- setSize: function _youtubePlayerSetSize(width, height) {
633
- if ($media) {
634
- $media.width(width).height(height);
635
- }
636
- if (media) {
637
- media.setSize(width, height);
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
- seek: function _youtubePlayerSeek(value) {
645
- if (media) {
646
- media.seekTo(parseFloat(value), true);
647
- }
648
- },
498
+ return this;
499
+ },
649
500
 
650
- play: function _youtubePlayerPlay() {
651
- if (media) {
652
- media.playVideo();
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
- pause: function _youtubePlayerPause() {
657
- if (media) {
658
- media.pauseVideo();
659
- }
660
- },
513
+ if (!this.is('ready')) {
514
+ this.startMuted = state;
515
+ }
661
516
 
662
- stop: function _youtubePlayerStop() {
663
- if (media) {
664
- media.stopVideo();
665
- mediaplayer._onEnd();
666
- }
667
- },
517
+ return this;
518
+ },
668
519
 
669
- mute: function _youtubePlayerMute(state) {
670
- if (media) {
671
- media[state ? 'mute' : 'unMute']();
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
- isMuted: function _youtubePlayerIsMuted() {
676
- if (media) {
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
- return player;
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
- if (!mediaplayer.is('nogui')) {
753
- $media.removeAttr('controls');
754
- }
541
+ return this;
542
+ },
755
543
 
756
- $media
757
- .on('play' + _ns, function() {
758
- played = true;
759
- mediaplayer._onPlay();
760
- })
761
- .on('pause' + _ns, function() {
762
- mediaplayer._onPause();
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
- return result;
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
- destroy: function _nativePlayerDestroy() {
843
- if ($media) {
844
- $media.off(_ns).attr('controls', '');
845
- }
560
+ /**
561
+ * Gets the duration of the media
562
+ * @returns {Number}
563
+ */
564
+ getDuration() {
565
+ return this.duration;
566
+ },
846
567
 
847
- this.stop();
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
- $media = null;
850
- media = null;
851
- played = false;
852
- },
576
+ /**
577
+ * Gets the type of player
578
+ * @returns {String}
579
+ */
580
+ getType() {
581
+ return this.type;
582
+ },
853
583
 
854
- getPosition: function _nativePlayerGetPosition() {
855
- if (media) {
856
- return media.currentTime;
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
- return 0;
859
- },
594
+ }
595
+ return this.$container;
596
+ },
860
597
 
861
- getDuration: function _nativePlayerGetDuration() {
862
- if (media) {
863
- return media.duration;
864
- }
865
- return 0;
866
- },
598
+ /**
599
+ * Gets the underlying DOM element
600
+ * @returns {jQuery}
601
+ */
602
+ getElement() {
603
+ return this.$component;
604
+ },
867
605
 
868
- getVolume: function _nativePlayerGetVolume() {
869
- var value = 0;
870
- if (media) {
871
- value = parseFloat(media.volume) * _volumeRange + _volumeMin;
872
- }
873
- return value;
874
- },
606
+ /**
607
+ * Gets the list of media
608
+ * @returns {Array}
609
+ */
610
+ getSources() {
611
+ return this.config.sources.slice();
612
+ },
875
613
 
876
- setVolume: function _nativePlayerSetVolume(value) {
877
- if (media) {
878
- media.volume = (parseFloat(value) - _volumeMin) / _volumeRange;
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
- setSize: function _nativePlayerSetSize(width, height) {
883
- if ($media) {
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
- seek: function _nativePlayerSeek(value) {
889
- if (media) {
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
- play: function _nativePlayerPlay() {
898
- if (media) {
899
- media.play();
900
- }
901
- },
633
+ return this;
634
+ },
902
635
 
903
- pause: function _nativePlayerPause() {
904
- if (media) {
905
- media.pause();
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
- stop: function _nativePlayerStop() {
910
- if (media && played) {
911
- media.currentTime = media.duration;
646
+ if (this.is('rendered')) {
647
+ this.player.addMedia(source.src, source.type);
912
648
  }
913
- },
914
649
 
915
- mute: function _nativePlayerMute(state) {
916
- if (media) {
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
- return player;
954
- };
655
+ return this;
656
+ },
955
657
 
956
- /**
957
- * Defines the list of available players
958
- * @type {Object}
959
- * @private
960
- */
961
- var _players = {
962
- audio: _nativePlayer,
963
- video: _nativePlayer,
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
- * Defines a media player object
969
- * @type {Object}
970
- */
971
- var mediaplayer = {
972
- /**
973
- * Initializes the media player
974
- * @param {Object} config
975
- * @param {String} config.type - The type of media to play
976
- * @param {String|Array} config.url - The URL to the media
977
- * @param {String|jQuery|HTMLElement} [config.renderTo] - An optional container in which renders the player
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
- return this;
1022
- },
681
+ return this;
682
+ },
1023
683
 
1024
- /**
1025
- * Uninstalls the media player
1026
- * @returns {mediaplayer}
1027
- */
1028
- destroy: function destroy() {
1029
684
  /**
1030
- * Triggers a destroy event
1031
- * @event mediaplayer#destroy
685
+ * Enables the media player
686
+ * @returns {mediaplayer}
1032
687
  */
1033
- this.trigger('destroy');
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.$component.remove();
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
- * Triggers a render event
1081
- * @event mediaplayer#render
1082
- * @param {jQuery} $component
695
+ * Disables the media player
696
+ * @returns {mediaplayer}
1083
697
  */
1084
- this.trigger('render', this.$component);
1085
-
1086
- return this;
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
- return this;
1108
- },
702
+ return this;
703
+ },
1109
704
 
1110
- /**
1111
- * Plays the media
1112
- * @param {Number} [time] - An optional start position in seconds
1113
- * @returns {mediaplayer}
1114
- */
1115
- play: function play(time) {
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
- if (!this.is('ready')) {
1124
- this.autoStart = true;
1125
- }
712
+ return this;
713
+ },
1126
714
 
1127
- this.loop = !!this.config.loop;
715
+ /**
716
+ * hides the media player
717
+ * @returns {mediaplayer}
718
+ */
719
+ hide() {
720
+ this._toState('hidden');
1128
721
 
1129
- if (this.timerId) {
1130
- cancelAnimationFrame(this.timerId);
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
- this.execute('pause');
1149
-
1150
- if (!this.is('ready')) {
1151
- this.autoStart = false;
1152
- }
1153
- }
1154
-
1155
- return this;
1156
- },
1157
-
1158
- /**
1159
- * Resumes the media
1160
- * @returns {mediaplayer}
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
- * Sets the media source. If a source has been already set, it will be replaced.
1321
- * @param {String|Object} src - The media URL, or an object containing the source and the type
1322
- * @param {Function} [callback] - A function called to provide the added media source object
1323
- * @returns {mediaplayer}
1324
- */
1325
- setSource: function setSource(src, callback) {
1326
- this._getSource(src, function(source) {
1327
- this.config.sources = [source];
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
- if (this.is('rendered')) {
1330
- this.player.setMedia(source.src, source.type);
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 (this.is('rendered')) {
1352
- this.player.addMedia(source.src, source.type);
778
+ if (_.isString(src)) {
779
+ source = {
780
+ src: src
781
+ };
782
+ } else {
783
+ source = _.clone(src);
1353
784
  }
1354
785
 
1355
- if (callback) {
1356
- callback.call(this, source);
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
- this.config.width = width;
1386
- this.config.height = height;
1387
-
1388
- if (this.$component) {
1389
- height -= this.$component.outerHeight() - this.$component.height();
1390
- width -= this.$component.outerWidth() - this.$component.width();
1391
- this.$component.width(width).height(height);
1392
-
1393
- if (!this.is('nogui')) {
1394
- height -= this.$controls.outerHeight();
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
- * Disables the media player
1415
- * @returns {mediaplayer}
1416
- */
1417
- disable: function disable() {
1418
- this._toState('disabled');
1419
- this.trigger('disabled');
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
- return this;
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
- * hides the media player
1436
- * @returns {mediaplayer}
1437
- */
1438
- hide: function hide() {
1439
- this._toState('hidden');
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
- * Ensures the right media type is set
1446
- * @param {String} type
1447
- * @private
1448
- */
1449
- _setType: function _setType(type) {
1450
- if (type.indexOf('youtube') !== -1) {
1451
- this.type = 'youtube';
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
- * Ensures the type is correctly applied
1461
- * @private
1462
- */
1463
- _initType: function _initType() {
1464
- var is = this.config.is;
1465
- is.youtube = 'youtube' === this.type;
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
- * Gets a source descriptor.
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
- if (_.isString(src)) {
1480
- source = {
1481
- src: src
1482
- };
1483
- } else {
1484
- source = _.clone(src);
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
- if (!source.type) {
1492
- mimetype.getResourceType(source.src, function(err, type) {
1493
- if (err) {
1494
- type = _defaults.type;
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
- function done() {
1504
- if (_needTypeAdjust(source.type)) {
1505
- source.type = _getAdjustedType(source);
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
- if (self.is('youtube')) {
1509
- source.id = _extractYoutubeId(source.src);
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
- callback.call(self, source);
1513
- }
1514
- },
1515
-
1516
- /**
1517
- * Ensures the sources are correctly set
1518
- * @param {Function} callback - A function called once all sources have been initialized
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
- triggerEvent = this.trigger;
1548
- this.trigger = function trigger(eventName) {
1549
- if (this.$component) {
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
- * Ensures the right size is set according to the media type
1558
- * @private
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
- this.config.width = _.parseInt(this.config.width) || defaults.width;
1565
- this.config.height = _.parseInt(this.config.height) || defaults.height;
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
- * Initializes the right player instance
1570
- * @private
1571
- */
1572
- _initPlayer: function _initPlayer() {
1573
- var player = _players[this.type];
1574
- var error;
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
- if (_support.canPlay(this.type)) {
1577
- if (_.isFunction(player)) {
1578
- this.player = player(this);
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 (this.player) {
1582
- error = !this.player.init();
995
+ if (vertical) {
996
+ orientation = 'vertical';
997
+ direction = 'rtl';
1583
998
  } else {
1584
- error = true;
999
+ orientation = 'horizontal';
1000
+ direction = 'ltr';
1585
1001
  }
1586
- } else {
1587
- error = true;
1588
- }
1589
1002
 
1590
- this._setState('error', error);
1591
- this._setState('nogui', !_support.canControl());
1592
- this._setState('loading', true);
1593
- },
1594
-
1595
- /**
1596
- * Initializes the player state
1597
- * @private
1598
- */
1599
- _initState: function _initState() {
1600
- var isCORS = false;
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
- * Resets the internals attributes
1616
- * @private
1617
- */
1618
- _reset: function _reset() {
1619
- this.config.is = {};
1620
- this._initType();
1621
-
1622
- this.$component = null;
1623
- this.$container = null;
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
- * Binds events onto the rendered player
1715
- * @private
1716
- */
1717
- _bindEvents: function _bindEvents() {
1718
- var self = this;
1719
- var overing = false;
1028
+ /**
1029
+ * Binds events onto the rendered player
1030
+ * @private
1031
+ */
1032
+ _bindEvents() {
1033
+ let overing = false;
1720
1034
 
1721
- this.$component.on('contextmenu' + _ns, function(event) {
1722
- event.preventDefault();
1723
- });
1035
+ this.$component.on(`contextmenu${ns}`, event => event.preventDefault());
1724
1036
 
1725
- this.$controls.on('click' + _ns, '.action', function(event) {
1726
- var $target = $(event.target);
1727
- var $action = $target.closest('.action');
1728
- var id = $action.data('control');
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
- if (_.isFunction(self[id])) {
1731
- self[id]();
1732
- }
1733
- });
1042
+ if (_.isFunction(this[id])) {
1043
+ this[id]();
1044
+ }
1045
+ });
1734
1046
 
1735
- this.$player.on('click' + _ns, function() {
1736
- if (self.is('playing')) {
1737
- self.pause();
1738
- } else {
1739
- self.play();
1740
- }
1741
- });
1742
-
1743
- this.$seek.on('change' + _ns, function(event, value) {
1744
- self.seek(value, true);
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
- self.$volumeControl.addClass('up');
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
- //close the volume control after 15s
1770
- self.overingTimer = _.delay(function() {
1771
- if (self.$volumeControl) {
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
- * Unbinds events from the rendered player
1786
- * @private
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
- $(document).off(_ns);
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
- * Updates the volume slider
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
- * Updates the displayed volume
1817
- * @param {Number} value
1818
- * @param {*} [internal]
1819
- * @private
1820
- */
1821
- _updateVolume: function _updateVolume(value, internal) {
1822
- this.volume = Math.max(_volumeMin, Math.min(_volumeMax, parseFloat(value)));
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
- * Updates the time slider
1831
- * @param {Number} value
1832
- * @private
1833
- */
1834
- _updatePositionSlider: function _updatePositionSlider(value) {
1835
- if (this.$seekSlider) {
1836
- this.$seekSlider.val(value);
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
- * Updates the time label
1842
- * @param {Number} value
1843
- * @private
1844
- */
1845
- _updatePositionLabel: function _updatePositionLabel(value) {
1846
- if (this.$position) {
1847
- this.$position.text(_timerFormat(value));
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
- * Updates the displayed time position
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
- if (!internal) {
1861
- this._updatePositionSlider(this.position);
1862
- }
1863
- this._updatePositionLabel(this.position);
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
- * Updates the duration slider
1868
- * @param {Number} value
1869
- * @private
1870
- */
1871
- _updateDurationSlider: function _updateDurationSlider(value) {
1872
- if (this.$seekSlider) {
1873
- this._destroySlider(this.$seekSlider);
1874
- this.$seekSlider = null;
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
- if (value && isFinite(value)) {
1878
- this.$seekSlider = this._renderSlider(this.$seek, 0, 0, value);
1879
- this.$seekSlider.attr('disabled', !this.config.canSeek);
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
- * Updates the duration label
1885
- * @param {Number} value
1886
- * @private
1887
- */
1888
- _updateDurationLabel: function _updateDurationLabel(value) {
1889
- if (this.$duration) {
1890
- if (value && isFinite(value)) {
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
- * Updates the displayed duration
1900
- * @param {Number} value
1901
- * @private
1902
- */
1903
- _updateDuration: function _updateDuration(value) {
1904
- this.duration = Math.abs(parseFloat(value));
1905
- this._updateDurationSlider(this.duration);
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
- * Event called when the media is ready
1911
- * @private
1912
- */
1913
- _onReady: function _onReady() {
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
- * Update volume in DBIndex store
1939
- * @param {Number} volume
1940
- * @private
1941
- */
1942
- _storeVolume: function _storeVolume(volume) {
1943
- return store('mediaVolume').then(function(volumeStore) {
1944
- volumeStore.setItem('volume', volume);
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
- * Get volume from DBIndex store
1950
- * @private
1951
- */
1952
- _updateVolumeFromStore: function _updateVolumeFromStore() {
1953
- var self = this;
1954
- return store('mediaVolume')
1955
- .then(function(volumeStore) {
1956
- return volumeStore.getItem('volume');
1957
- })
1958
- .then(function(volume) {
1959
- if (_.isNumber(volume)) {
1960
- self.volume = Math.max(_volumeMin, Math.min(_volumeMax, parseFloat(volume)));
1961
- self.setVolume(self.volume);
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
- * Event called when the media throws unrecoverable error
1968
- * @private
1969
- */
1970
- _onError: function _onError() {
1971
- this._setState('error', true);
1972
- this._setState('loading', false);
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
- * Triggers an unrecoverable media error event
1976
- * @event mediaplayer#error
1237
+ * Event called when the media is ready
1238
+ * @private
1977
1239
  */
1978
- this.trigger('error');
1979
- },
1240
+ _onReady() {
1241
+ if (this.is('error')) {
1242
+ this._setState('error', false);
1243
+ }
1980
1244
 
1981
- /**
1982
- * Event called when the media throws recoverable error
1983
- * @private
1984
- */
1985
- _onRecoverError: function _onRecoverError() {
1986
- this._setState('error', false);
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
- * Triggers a recoverable media error event
1990
- * @event mediaplayer#recovererror
1273
+ * Set max height limit for container
1274
+ * using by old media items with defined height.
1275
+ * @private
1991
1276
  */
1992
- this.trigger('recovererror');
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
- * Event called when the media is played
1997
- * @private
1998
- */
1999
- _onPlay: function _onPlay() {
2000
- this._playingState(true);
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
- * Triggers a media playback event
2004
- * @event mediaplayer#play
1303
+ * Get volume from DBIndex store
1304
+ * @returns {Promise}
1305
+ * @private
2005
1306
  */
2006
- this.trigger('play', this.$media[0]);
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
- * Event called when the media is paused
2011
- * @private
2012
- */
2013
- _onPause: function _onPause() {
2014
- this._playingState(false);
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
- * Triggers a media paused event
2018
- * @event mediaplayer#pause
1334
+ * Event called when the media is played
1335
+ * @private
2019
1336
  */
2020
- this.trigger('pause');
2021
- },
1337
+ _onPlay() {
1338
+ this._playingState(true);
1339
+ this._setState('preview', true);
2022
1340
 
2023
- /**
2024
- * Event called when the media is ended
2025
- * @private
2026
- */
2027
- _onEnd: function _onEnd() {
2028
- this.timesPlayed++;
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
- // disable GUI when the play limit is reached
2033
- if (this._playLimitReached()) {
2034
- this._disableGUI();
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 play limit reached event
2038
- * @event mediaplayer#limitreached
1356
+ * Triggers a media paused event
1357
+ * @event mediaplayer#pause
2039
1358
  */
2040
- this.trigger('limitreached');
2041
- } else if (this.loop) {
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
- * Triggers a media ended event
2050
- * @event mediaplayer#ended
1363
+ * Event called when the media is ended
1364
+ * @private
2051
1365
  */
2052
- this.trigger('ended');
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
- * Event called when the time position has changed
2057
- * @private
2058
- */
2059
- _onTimeUpdate: function _onTimeUpdate() {
2060
- this._updatePosition(this.player.getPosition());
1388
+ /**
1389
+ * Triggers a media ended event
1390
+ * @event mediaplayer#ended
1391
+ */
1392
+ this.trigger('ended');
1393
+ },
2061
1394
 
2062
1395
  /**
2063
- * Triggers a media time update event
2064
- * @event mediaplayer#update
1396
+ * Event called when the playback is playing
1397
+ * @private
2065
1398
  */
2066
- this.trigger('update');
2067
- },
1399
+ _onPlaying() {
1400
+ this._setState('preview', true);
1401
+ this._setState('stalled', false);
1402
+ this._setState('ready', true);
1403
+ },
2068
1404
 
2069
- /**
2070
- * Run a timer to disable the possibility of replaying a media
2071
- * @private
2072
- */
2073
- _replayTimeout: function _replayTimeout() {
2074
- var nowMs = new window.Date().getTime(),
2075
- elapsedSeconds = Math.floor((nowMs - this.replayTimeoutStartMs) / 1000);
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
- this.timerId = requestAnimationFrame(this._replayTimeout.bind(this));
1414
+ /**
1415
+ * Event called when the time position has changed
1416
+ * @private
1417
+ */
1418
+ _onTimeUpdate() {
1419
+ this._updatePosition(this.player.getPosition());
2078
1420
 
2079
- if (elapsedSeconds >= parseInt(this.config.replayTimeout, 10)) {
2080
- this._disableGUI();
2081
- this.disable();
2082
- cancelAnimationFrame(this.timerId);
2083
- }
2084
- },
1421
+ /**
1422
+ * Triggers a media time update event
1423
+ * @event mediaplayer#update
1424
+ */
1425
+ this.trigger('update');
1426
+ },
2085
1427
 
2086
- /**
2087
- * Disable the player GUI
2088
- * @private
2089
- */
2090
- _disableGUI: function disableGUI() {
2091
- this._setState('ready', false);
2092
- this._setState('canplay', false);
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
- * Checks if the media can be played
2106
- * @returns {Boolean}
2107
- * @private
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
- * Checks if the media can be paused
2115
- * @returns {Boolean}
2116
- * @private
2117
- */
2118
- _canPause: function _canPause() {
2119
- return !!this.config.canPause;
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
- * Checks if the media can be sought
2124
- * @returns {Boolean}
2125
- * @private
2126
- */
2127
- _canSeek: function _canSeek() {
2128
- return !!this.config.canSeek;
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
- * Checks if the playback can be resumed
2133
- * @returns {Boolean}
2134
- * @private
2135
- */
2136
- _canResume: function _canResume() {
2137
- return this.is('paused') && this._canPlay();
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
- * Sets the media is in a particular state
2142
- * @param {String} name
2143
- * @param {Boolean} value
2144
- * @returns {mediaplayer}
2145
- */
2146
- _setState: function _setState(name, value) {
2147
- value = !!value;
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
- this.config.is[name] = value;
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
- if (this.$component) {
2152
- this.$component.toggleClass(name, value);
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
- return this;
2156
- },
1503
+ this.config.is[name] = value;
2157
1504
 
2158
- /**
2159
- * Restores the media player from a particular state and resumes the playback
2160
- * @param {String} stateName
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
- return this;
2169
- },
1509
+ return this;
1510
+ },
2170
1511
 
2171
- /**
2172
- * Sets the media player to a particular state and pauses the playback
2173
- * @param {String} stateName
2174
- * @returns {mediaplayer}
2175
- * @private
2176
- */
2177
- _toState: function _toState(stateName) {
2178
- this.pause();
2179
- this._setState(stateName, true);
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
- return this;
2182
- },
1522
+ return this;
1523
+ },
2183
1524
 
2184
- /**
2185
- * Sets the playing state
2186
- * @param {Boolean} state
2187
- * @param {Boolean} [ended]
2188
- * @returns {mediaplayer}
2189
- * @private
2190
- */
2191
- _playingState: function _playingState(state, ended) {
2192
- this._setState('playing', !!state);
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
- return this;
2197
- },
1535
+ return this;
1536
+ },
2198
1537
 
2199
- /**
2200
- * Executes a command onto the media
2201
- * @param {String} command - The name of the command to execute
2202
- * @returns {*}
2203
- * @private
2204
- */
2205
- execute: function execute(command) {
2206
- var ctx = this.player;
2207
- var method = ctx && ctx[command];
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
- if (_.isFunction(method)) {
2210
- return method.apply(ctx, _slice.call(arguments, 1));
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
- * Builds a media player instance
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 _support.canPlay(type, mime);
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 _support.canPlayAudio(mime);
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 _support.canPlayVideo(mime);
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 _support.canControl();
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;