UNPKG

25.1 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6
7function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
8
9var _videoJs = require('video.js');
10
11var _videoJs2 = _interopRequireDefault(_videoJs);
12
13var _playlistLoader = require('./playlist-loader');
14
15var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
16
17var noop = function noop() {};
18
19/**
20 * Convert the properties of an HLS track into an audioTrackKind.
21 *
22 * @private
23 */
24var audioTrackKind_ = function audioTrackKind_(properties) {
25 var kind = properties['default'] ? 'main' : 'alternative';
26
27 if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
28 kind = 'main-desc';
29 }
30
31 return kind;
32};
33
34/**
35 * Pause provided segment loader and playlist loader if active
36 *
37 * @param {SegmentLoader} segmentLoader
38 * SegmentLoader to pause
39 * @param {Object} mediaType
40 * Active media type
41 * @function stopLoaders
42 */
43var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
44 segmentLoader.abort();
45 segmentLoader.pause();
46
47 if (mediaType && mediaType.activePlaylistLoader) {
48 mediaType.activePlaylistLoader.pause();
49 mediaType.activePlaylistLoader = null;
50 }
51};
52
53exports.stopLoaders = stopLoaders;
54/**
55 * Start loading provided segment loader and playlist loader
56 *
57 * @param {PlaylistLoader} playlistLoader
58 * PlaylistLoader to start loading
59 * @param {Object} mediaType
60 * Active media type
61 * @function startLoaders
62 */
63var startLoaders = function startLoaders(playlistLoader, mediaType) {
64 // Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
65 // playlist loader
66 mediaType.activePlaylistLoader = playlistLoader;
67 playlistLoader.load();
68};
69
70exports.startLoaders = startLoaders;
71/**
72 * Returns a function to be called when the media group changes. It performs a
73 * non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
74 * change of group is merely a rendition switch of the same content at another encoding,
75 * rather than a change of content, such as switching audio from English to Spanish.
76 *
77 * @param {String} type
78 * MediaGroup type
79 * @param {Object} settings
80 * Object containing required information for media groups
81 * @return {Function}
82 * Handler for a non-destructive resync of SegmentLoader when the active media
83 * group changes.
84 * @function onGroupChanged
85 */
86var onGroupChanged = function onGroupChanged(type, settings) {
87 return function () {
88 var _settings$segmentLoaders = settings.segmentLoaders;
89 var segmentLoader = _settings$segmentLoaders[type];
90 var mainSegmentLoader = _settings$segmentLoaders.main;
91 var mediaType = settings.mediaTypes[type];
92
93 var activeTrack = mediaType.activeTrack();
94 var activeGroup = mediaType.activeGroup(activeTrack);
95 var previousActiveLoader = mediaType.activePlaylistLoader;
96
97 stopLoaders(segmentLoader, mediaType);
98
99 if (!activeGroup) {
100 // there is no group active
101 return;
102 }
103
104 if (!activeGroup.playlistLoader) {
105 if (previousActiveLoader) {
106 // The previous group had a playlist loader but the new active group does not
107 // this means we are switching from demuxed to muxed audio. In this case we want to
108 // do a destructive reset of the main segment loader and not restart the audio
109 // loaders.
110 mainSegmentLoader.resetEverything();
111 }
112 return;
113 }
114
115 // Non-destructive resync
116 segmentLoader.resyncLoader();
117
118 startLoaders(activeGroup.playlistLoader, mediaType);
119 };
120};
121
122exports.onGroupChanged = onGroupChanged;
123/**
124 * Returns a function to be called when the media track changes. It performs a
125 * destructive reset of the SegmentLoader to ensure we start loading as close to
126 * currentTime as possible.
127 *
128 * @param {String} type
129 * MediaGroup type
130 * @param {Object} settings
131 * Object containing required information for media groups
132 * @return {Function}
133 * Handler for a destructive reset of SegmentLoader when the active media
134 * track changes.
135 * @function onTrackChanged
136 */
137var onTrackChanged = function onTrackChanged(type, settings) {
138 return function () {
139 var _settings$segmentLoaders2 = settings.segmentLoaders;
140 var segmentLoader = _settings$segmentLoaders2[type];
141 var mainSegmentLoader = _settings$segmentLoaders2.main;
142 var mediaType = settings.mediaTypes[type];
143
144 var activeTrack = mediaType.activeTrack();
145 var activeGroup = mediaType.activeGroup(activeTrack);
146 var previousActiveLoader = mediaType.activePlaylistLoader;
147
148 stopLoaders(segmentLoader, mediaType);
149
150 if (!activeGroup) {
151 // there is no group active so we do not want to restart loaders
152 return;
153 }
154
155 if (!activeGroup.playlistLoader) {
156 // when switching from demuxed audio/video to muxed audio/video (noted by no playlist
157 // loader for the audio group), we want to do a destructive reset of the main segment
158 // loader and not restart the audio loaders
159 mainSegmentLoader.resetEverything();
160 return;
161 }
162
163 if (previousActiveLoader === activeGroup.playlistLoader) {
164 // Nothing has actually changed. This can happen because track change events can fire
165 // multiple times for a "single" change. One for enabling the new active track, and
166 // one for disabling the track that was active
167 startLoaders(activeGroup.playlistLoader, mediaType);
168 return;
169 }
170
171 if (segmentLoader.track) {
172 // For WebVTT, set the new text track in the segmentloader
173 segmentLoader.track(activeTrack);
174 }
175
176 // destructive reset
177 segmentLoader.resetEverything();
178
179 startLoaders(activeGroup.playlistLoader, mediaType);
180 };
181};
182
183exports.onTrackChanged = onTrackChanged;
184var onError = {
185 /**
186 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
187 * an error.
188 *
189 * @param {String} type
190 * MediaGroup type
191 * @param {Object} settings
192 * Object containing required information for media groups
193 * @return {Function}
194 * Error handler. Logs warning (or error if the playlist is blacklisted) to
195 * console and switches back to default audio track.
196 * @function onError.AUDIO
197 */
198 AUDIO: function AUDIO(type, settings) {
199 return function () {
200 var segmentLoader = settings.segmentLoaders[type];
201 var mediaType = settings.mediaTypes[type];
202 var blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
203
204 stopLoaders(segmentLoader, mediaType);
205
206 // switch back to default audio track
207 var activeTrack = mediaType.activeTrack();
208 var activeGroup = mediaType.activeGroup();
209 var id = (activeGroup.filter(function (group) {
210 return group['default'];
211 })[0] || activeGroup[0]).id;
212 var defaultTrack = mediaType.tracks[id];
213
214 if (activeTrack === defaultTrack) {
215 // Default track encountered an error. All we can do now is blacklist the current
216 // rendition and hope another will switch audio groups
217 blacklistCurrentPlaylist({
218 message: 'Problem encountered loading the default audio track.'
219 });
220 return;
221 }
222
223 _videoJs2['default'].log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
224
225 for (var trackId in mediaType.tracks) {
226 mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
227 }
228
229 mediaType.onTrackChanged();
230 };
231 },
232 /**
233 * Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
234 * an error.
235 *
236 * @param {String} type
237 * MediaGroup type
238 * @param {Object} settings
239 * Object containing required information for media groups
240 * @return {Function}
241 * Error handler. Logs warning to console and disables the active subtitle track
242 * @function onError.SUBTITLES
243 */
244 SUBTITLES: function SUBTITLES(type, settings) {
245 return function () {
246 var segmentLoader = settings.segmentLoaders[type];
247 var mediaType = settings.mediaTypes[type];
248
249 _videoJs2['default'].log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
250
251 stopLoaders(segmentLoader, mediaType);
252
253 var track = mediaType.activeTrack();
254
255 if (track) {
256 track.mode = 'disabled';
257 }
258
259 mediaType.onTrackChanged();
260 };
261 }
262};
263
264exports.onError = onError;
265var setupListeners = {
266 /**
267 * Setup event listeners for audio playlist loader
268 *
269 * @param {String} type
270 * MediaGroup type
271 * @param {PlaylistLoader|null} playlistLoader
272 * PlaylistLoader to register listeners on
273 * @param {Object} settings
274 * Object containing required information for media groups
275 * @function setupListeners.AUDIO
276 */
277 AUDIO: function AUDIO(type, playlistLoader, settings) {
278 if (!playlistLoader) {
279 // no playlist loader means audio will be muxed with the video
280 return;
281 }
282
283 var tech = settings.tech;
284 var requestOptions = settings.requestOptions;
285 var segmentLoader = settings.segmentLoaders[type];
286
287 playlistLoader.on('loadedmetadata', function () {
288 var media = playlistLoader.media();
289
290 segmentLoader.playlist(media, requestOptions);
291
292 // if the video is already playing, or if this isn't a live video and preload
293 // permits, start downloading segments
294 if (!tech.paused() || media.endList && tech.preload() !== 'none') {
295 segmentLoader.load();
296 }
297 });
298
299 playlistLoader.on('loadedplaylist', function () {
300 segmentLoader.playlist(playlistLoader.media(), requestOptions);
301
302 // If the player isn't paused, ensure that the segment loader is running
303 if (!tech.paused()) {
304 segmentLoader.load();
305 }
306 });
307
308 playlistLoader.on('error', onError[type](type, settings));
309 },
310 /**
311 * Setup event listeners for subtitle playlist loader
312 *
313 * @param {String} type
314 * MediaGroup type
315 * @param {PlaylistLoader|null} playlistLoader
316 * PlaylistLoader to register listeners on
317 * @param {Object} settings
318 * Object containing required information for media groups
319 * @function setupListeners.SUBTITLES
320 */
321 SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
322 var tech = settings.tech;
323 var requestOptions = settings.requestOptions;
324 var segmentLoader = settings.segmentLoaders[type];
325 var mediaType = settings.mediaTypes[type];
326
327 playlistLoader.on('loadedmetadata', function () {
328 var media = playlistLoader.media();
329
330 segmentLoader.playlist(media, requestOptions);
331 segmentLoader.track(mediaType.activeTrack());
332
333 // if the video is already playing, or if this isn't a live video and preload
334 // permits, start downloading segments
335 if (!tech.paused() || media.endList && tech.preload() !== 'none') {
336 segmentLoader.load();
337 }
338 });
339
340 playlistLoader.on('loadedplaylist', function () {
341 segmentLoader.playlist(playlistLoader.media(), requestOptions);
342
343 // If the player isn't paused, ensure that the segment loader is running
344 if (!tech.paused()) {
345 segmentLoader.load();
346 }
347 });
348
349 playlistLoader.on('error', onError[type](type, settings));
350 }
351};
352
353exports.setupListeners = setupListeners;
354var initialize = {
355 /**
356 * Setup PlaylistLoaders and AudioTracks for the audio groups
357 *
358 * @param {String} type
359 * MediaGroup type
360 * @param {Object} settings
361 * Object containing required information for media groups
362 * @function initialize.AUDIO
363 */
364 'AUDIO': function AUDIO(type, settings) {
365 var mode = settings.mode;
366 var hls = settings.hls;
367 var segmentLoader = settings.segmentLoaders[type];
368 var withCredentials = settings.requestOptions.withCredentials;
369 var mediaGroups = settings.master.mediaGroups;
370 var _settings$mediaTypes$type = settings.mediaTypes[type];
371 var groups = _settings$mediaTypes$type.groups;
372 var tracks = _settings$mediaTypes$type.tracks;
373
374 // force a default if we have none or we are not
375 // in html5 mode (the only mode to support more than one
376 // audio track)
377 if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0 || mode !== 'html5') {
378 mediaGroups[type] = { main: { 'default': { 'default': true } } };
379 }
380
381 for (var groupId in mediaGroups[type]) {
382 if (!groups[groupId]) {
383 groups[groupId] = [];
384 }
385
386 for (var variantLabel in mediaGroups[type][groupId]) {
387 var properties = mediaGroups[type][groupId][variantLabel];
388 var playlistLoader = undefined;
389
390 if (properties.resolvedUri) {
391 playlistLoader = new _playlistLoader2['default'](properties.resolvedUri, hls, withCredentials);
392 } else {
393 // no resolvedUri means the audio is muxed with the video when using this
394 // audio track
395 playlistLoader = null;
396 }
397
398 properties = _videoJs2['default'].mergeOptions({ id: variantLabel, playlistLoader: playlistLoader }, properties);
399
400 setupListeners[type](type, properties.playlistLoader, settings);
401
402 groups[groupId].push(properties);
403
404 if (typeof tracks[variantLabel] === 'undefined') {
405 var track = new _videoJs2['default'].AudioTrack({
406 id: variantLabel,
407 kind: audioTrackKind_(properties),
408 enabled: false,
409 language: properties.language,
410 'default': properties['default'],
411 label: variantLabel
412 });
413
414 tracks[variantLabel] = track;
415 }
416 }
417 }
418
419 // setup single error event handler for the segment loader
420 segmentLoader.on('error', onError[type](type, settings));
421 },
422 /**
423 * Setup PlaylistLoaders and TextTracks for the subtitle groups
424 *
425 * @param {String} type
426 * MediaGroup type
427 * @param {Object} settings
428 * Object containing required information for media groups
429 * @function initialize.SUBTITLES
430 */
431 'SUBTITLES': function SUBTITLES(type, settings) {
432 var tech = settings.tech;
433 var hls = settings.hls;
434 var segmentLoader = settings.segmentLoaders[type];
435 var withCredentials = settings.requestOptions.withCredentials;
436 var mediaGroups = settings.master.mediaGroups;
437 var _settings$mediaTypes$type2 = settings.mediaTypes[type];
438 var groups = _settings$mediaTypes$type2.groups;
439 var tracks = _settings$mediaTypes$type2.tracks;
440
441 for (var groupId in mediaGroups[type]) {
442 if (!groups[groupId]) {
443 groups[groupId] = [];
444 }
445
446 for (var variantLabel in mediaGroups[type][groupId]) {
447 if (mediaGroups[type][groupId][variantLabel].forced) {
448 // Subtitle playlists with the forced attribute are not selectable in Safari.
449 // According to Apple's HLS Authoring Specification:
450 // If content has forced subtitles and regular subtitles in a given language,
451 // the regular subtitles track in that language MUST contain both the forced
452 // subtitles and the regular subtitles for that language.
453 // Because of this requirement and that Safari does not add forced subtitles,
454 // forced subtitles are skipped here to maintain consistent experience across
455 // all platforms
456 continue;
457 }
458
459 var properties = mediaGroups[type][groupId][variantLabel];
460
461 properties = _videoJs2['default'].mergeOptions({
462 id: variantLabel,
463 playlistLoader: new _playlistLoader2['default'](properties.resolvedUri, hls, withCredentials)
464 }, properties);
465
466 setupListeners[type](type, properties.playlistLoader, settings);
467
468 groups[groupId].push(properties);
469
470 if (typeof tracks[variantLabel] === 'undefined') {
471 var track = tech.addRemoteTextTrack({
472 id: variantLabel,
473 kind: 'subtitles',
474 enabled: false,
475 language: properties.language,
476 label: variantLabel
477 }, false).track;
478
479 tracks[variantLabel] = track;
480 }
481 }
482 }
483
484 // setup single error event handler for the segment loader
485 segmentLoader.on('error', onError[type](type, settings));
486 },
487 /**
488 * Setup TextTracks for the closed-caption groups
489 *
490 * @param {String} type
491 * MediaGroup type
492 * @param {Object} settings
493 * Object containing required information for media groups
494 * @function initialize['CLOSED-CAPTIONS']
495 */
496 'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
497 var tech = settings.tech;
498 var mediaGroups = settings.master.mediaGroups;
499 var _settings$mediaTypes$type3 = settings.mediaTypes[type];
500 var groups = _settings$mediaTypes$type3.groups;
501 var tracks = _settings$mediaTypes$type3.tracks;
502
503 for (var groupId in mediaGroups[type]) {
504 if (!groups[groupId]) {
505 groups[groupId] = [];
506 }
507
508 for (var variantLabel in mediaGroups[type][groupId]) {
509 var properties = mediaGroups[type][groupId][variantLabel];
510
511 // We only support CEA608 captions for now, so ignore anything that
512 // doesn't use a CCx INSTREAM-ID
513 if (!properties.instreamId.match(/CC\d/)) {
514 continue;
515 }
516
517 // No PlaylistLoader is required for Closed-Captions because the captions are
518 // embedded within the video stream
519 groups[groupId].push(_videoJs2['default'].mergeOptions({ id: variantLabel }, properties));
520
521 if (typeof tracks[variantLabel] === 'undefined') {
522 var track = tech.addRemoteTextTrack({
523 id: properties.instreamId,
524 kind: 'captions',
525 enabled: false,
526 language: properties.language,
527 label: variantLabel
528 }, false).track;
529
530 tracks[variantLabel] = track;
531 }
532 }
533 }
534 }
535};
536
537exports.initialize = initialize;
538/**
539 * Returns a function used to get the active group of the provided type
540 *
541 * @param {String} type
542 * MediaGroup type
543 * @param {Object} settings
544 * Object containing required information for media groups
545 * @return {Function}
546 * Function that returns the active media group for the provided type. Takes an
547 * optional parameter {TextTrack} track. If no track is provided, a list of all
548 * variants in the group, otherwise the variant corresponding to the provided
549 * track is returned.
550 * @function activeGroup
551 */
552var activeGroup = function activeGroup(type, settings) {
553 return function (track) {
554 var masterPlaylistLoader = settings.masterPlaylistLoader;
555 var groups = settings.mediaTypes[type].groups;
556
557 var media = masterPlaylistLoader.media();
558
559 if (!media) {
560 return null;
561 }
562
563 var variants = null;
564
565 if (media.attributes[type]) {
566 variants = groups[media.attributes[type]];
567 }
568
569 variants = variants || groups.main;
570
571 if (typeof track === 'undefined') {
572 return variants;
573 }
574
575 if (track === null) {
576 // An active track was specified so a corresponding group is expected. track === null
577 // means no track is currently active so there is no corresponding group
578 return null;
579 }
580
581 return variants.filter(function (props) {
582 return props.id === track.id;
583 })[0] || null;
584 };
585};
586
587exports.activeGroup = activeGroup;
588var activeTrack = {
589 /**
590 * Returns a function used to get the active track of type provided
591 *
592 * @param {String} type
593 * MediaGroup type
594 * @param {Object} settings
595 * Object containing required information for media groups
596 * @return {Function}
597 * Function that returns the active media track for the provided type. Returns
598 * null if no track is active
599 * @function activeTrack.AUDIO
600 */
601 AUDIO: function AUDIO(type, settings) {
602 return function () {
603 var tracks = settings.mediaTypes[type].tracks;
604
605 for (var id in tracks) {
606 if (tracks[id].enabled) {
607 return tracks[id];
608 }
609 }
610
611 return null;
612 };
613 },
614 /**
615 * Returns a function used to get the active track of type provided
616 *
617 * @param {String} type
618 * MediaGroup type
619 * @param {Object} settings
620 * Object containing required information for media groups
621 * @return {Function}
622 * Function that returns the active media track for the provided type. Returns
623 * null if no track is active
624 * @function activeTrack.SUBTITLES
625 */
626 SUBTITLES: function SUBTITLES(type, settings) {
627 return function () {
628 var tracks = settings.mediaTypes[type].tracks;
629
630 for (var id in tracks) {
631 if (tracks[id].mode === 'showing') {
632 return tracks[id];
633 }
634 }
635
636 return null;
637 };
638 }
639};
640
641exports.activeTrack = activeTrack;
642/**
643 * Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
644 * Closed-Captions) specified in the master manifest.
645 *
646 * @param {Object} settings
647 * Object containing required information for setting up the media groups
648 * @param {SegmentLoader} settings.segmentLoaders.AUDIO
649 * Audio segment loader
650 * @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
651 * Subtitle segment loader
652 * @param {SegmentLoader} settings.segmentLoaders.main
653 * Main segment loader
654 * @param {Tech} settings.tech
655 * The tech of the player
656 * @param {Object} settings.requestOptions
657 * XHR request options used by the segment loaders
658 * @param {PlaylistLoader} settings.masterPlaylistLoader
659 * PlaylistLoader for the master source
660 * @param {String} mode
661 * Mode of the hls source handler. Can be 'auto', 'html5', or 'flash'
662 * @param {HlsHandler} settings.hls
663 * HLS SourceHandler
664 * @param {Object} settings.master
665 * The parsed master manifest
666 * @param {Object} settings.mediaTypes
667 * Object to store the loaders, tracks, and utility methods for each media type
668 * @param {Function} settings.blacklistCurrentPlaylist
669 * Blacklists the current rendition and forces a rendition switch.
670 * @function setupMediaGroups
671 */
672var setupMediaGroups = function setupMediaGroups(settings) {
673 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
674 initialize[type](type, settings);
675 });
676
677 var mediaTypes = settings.mediaTypes;
678 var masterPlaylistLoader = settings.masterPlaylistLoader;
679 var tech = settings.tech;
680 var hls = settings.hls;
681
682 // setup active group and track getters and change event handlers
683 ['AUDIO', 'SUBTITLES'].forEach(function (type) {
684 mediaTypes[type].activeGroup = activeGroup(type, settings);
685 mediaTypes[type].activeTrack = activeTrack[type](type, settings);
686 mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
687 mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
688 });
689
690 // DO NOT enable the default subtitle or caption track.
691 // DO enable the default audio track
692 var audioGroup = mediaTypes.AUDIO.activeGroup();
693 var groupId = (audioGroup.filter(function (group) {
694 return group['default'];
695 })[0] || audioGroup[0]).id;
696
697 mediaTypes.AUDIO.tracks[groupId].enabled = true;
698 mediaTypes.AUDIO.onTrackChanged();
699
700 masterPlaylistLoader.on('mediachange', function () {
701 ['AUDIO', 'SUBTITLES'].forEach(function (type) {
702 return mediaTypes[type].onGroupChanged();
703 });
704 });
705
706 // custom audio track change event handler for usage event
707 var onAudioTrackChanged = function onAudioTrackChanged() {
708 mediaTypes.AUDIO.onTrackChanged();
709 tech.trigger({ type: 'usage', name: 'hls-audio-change' });
710 };
711
712 tech.audioTracks().addEventListener('change', onAudioTrackChanged);
713 tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
714
715 hls.on('dispose', function () {
716 tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
717 tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
718 });
719
720 // clear existing audio tracks and add the ones we just created
721 tech.clearTracks('audio');
722
723 for (var id in mediaTypes.AUDIO.tracks) {
724 tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
725 }
726};
727
728exports.setupMediaGroups = setupMediaGroups;
729/**
730 * Creates skeleton object used to store the loaders, tracks, and utility methods for each
731 * media type
732 *
733 * @return {Object}
734 * Object to store the loaders, tracks, and utility methods for each media type
735 * @function createMediaTypes
736 */
737var createMediaTypes = function createMediaTypes() {
738 var mediaTypes = {};
739
740 ['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
741 mediaTypes[type] = {
742 groups: {},
743 tracks: {},
744 activePlaylistLoader: null,
745 activeGroup: noop,
746 activeTrack: noop,
747 onGroupChanged: noop,
748 onTrackChanged: noop
749 };
750 });
751
752 return mediaTypes;
753};
754exports.createMediaTypes = createMediaTypes;
\No newline at end of file