yanchengPowerSupply/node_modules/videojs-contrib-hls/es5/media-groups.js

754 lines
24 KiB
JavaScript

'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _videoJs = require('video.js');
var _videoJs2 = _interopRequireDefault(_videoJs);
var _playlistLoader = require('./playlist-loader');
var _playlistLoader2 = _interopRequireDefault(_playlistLoader);
var noop = function noop() {};
/**
* Convert the properties of an HLS track into an audioTrackKind.
*
* @private
*/
var audioTrackKind_ = function audioTrackKind_(properties) {
var kind = properties['default'] ? 'main' : 'alternative';
if (properties.characteristics && properties.characteristics.indexOf('public.accessibility.describes-video') >= 0) {
kind = 'main-desc';
}
return kind;
};
/**
* Pause provided segment loader and playlist loader if active
*
* @param {SegmentLoader} segmentLoader
* SegmentLoader to pause
* @param {Object} mediaType
* Active media type
* @function stopLoaders
*/
var stopLoaders = function stopLoaders(segmentLoader, mediaType) {
segmentLoader.abort();
segmentLoader.pause();
if (mediaType && mediaType.activePlaylistLoader) {
mediaType.activePlaylistLoader.pause();
mediaType.activePlaylistLoader = null;
}
};
exports.stopLoaders = stopLoaders;
/**
* Start loading provided segment loader and playlist loader
*
* @param {PlaylistLoader} playlistLoader
* PlaylistLoader to start loading
* @param {Object} mediaType
* Active media type
* @function startLoaders
*/
var startLoaders = function startLoaders(playlistLoader, mediaType) {
// Segment loader will be started after `loadedmetadata` or `loadedplaylist` from the
// playlist loader
mediaType.activePlaylistLoader = playlistLoader;
playlistLoader.load();
};
exports.startLoaders = startLoaders;
/**
* Returns a function to be called when the media group changes. It performs a
* non-destructive (preserve the buffer) resync of the SegmentLoader. This is because a
* change of group is merely a rendition switch of the same content at another encoding,
* rather than a change of content, such as switching audio from English to Spanish.
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Handler for a non-destructive resync of SegmentLoader when the active media
* group changes.
* @function onGroupChanged
*/
var onGroupChanged = function onGroupChanged(type, settings) {
return function () {
var _settings$segmentLoaders = settings.segmentLoaders;
var segmentLoader = _settings$segmentLoaders[type];
var mainSegmentLoader = _settings$segmentLoaders.main;
var mediaType = settings.mediaTypes[type];
var activeTrack = mediaType.activeTrack();
var activeGroup = mediaType.activeGroup(activeTrack);
var previousActiveLoader = mediaType.activePlaylistLoader;
stopLoaders(segmentLoader, mediaType);
if (!activeGroup) {
// there is no group active
return;
}
if (!activeGroup.playlistLoader) {
if (previousActiveLoader) {
// The previous group had a playlist loader but the new active group does not
// this means we are switching from demuxed to muxed audio. In this case we want to
// do a destructive reset of the main segment loader and not restart the audio
// loaders.
mainSegmentLoader.resetEverything();
}
return;
}
// Non-destructive resync
segmentLoader.resyncLoader();
startLoaders(activeGroup.playlistLoader, mediaType);
};
};
exports.onGroupChanged = onGroupChanged;
/**
* Returns a function to be called when the media track changes. It performs a
* destructive reset of the SegmentLoader to ensure we start loading as close to
* currentTime as possible.
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Handler for a destructive reset of SegmentLoader when the active media
* track changes.
* @function onTrackChanged
*/
var onTrackChanged = function onTrackChanged(type, settings) {
return function () {
var _settings$segmentLoaders2 = settings.segmentLoaders;
var segmentLoader = _settings$segmentLoaders2[type];
var mainSegmentLoader = _settings$segmentLoaders2.main;
var mediaType = settings.mediaTypes[type];
var activeTrack = mediaType.activeTrack();
var activeGroup = mediaType.activeGroup(activeTrack);
var previousActiveLoader = mediaType.activePlaylistLoader;
stopLoaders(segmentLoader, mediaType);
if (!activeGroup) {
// there is no group active so we do not want to restart loaders
return;
}
if (!activeGroup.playlistLoader) {
// when switching from demuxed audio/video to muxed audio/video (noted by no playlist
// loader for the audio group), we want to do a destructive reset of the main segment
// loader and not restart the audio loaders
mainSegmentLoader.resetEverything();
return;
}
if (previousActiveLoader === activeGroup.playlistLoader) {
// Nothing has actually changed. This can happen because track change events can fire
// multiple times for a "single" change. One for enabling the new active track, and
// one for disabling the track that was active
startLoaders(activeGroup.playlistLoader, mediaType);
return;
}
if (segmentLoader.track) {
// For WebVTT, set the new text track in the segmentloader
segmentLoader.track(activeTrack);
}
// destructive reset
segmentLoader.resetEverything();
startLoaders(activeGroup.playlistLoader, mediaType);
};
};
exports.onTrackChanged = onTrackChanged;
var onError = {
/**
* Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
* an error.
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Error handler. Logs warning (or error if the playlist is blacklisted) to
* console and switches back to default audio track.
* @function onError.AUDIO
*/
AUDIO: function AUDIO(type, settings) {
return function () {
var segmentLoader = settings.segmentLoaders[type];
var mediaType = settings.mediaTypes[type];
var blacklistCurrentPlaylist = settings.blacklistCurrentPlaylist;
stopLoaders(segmentLoader, mediaType);
// switch back to default audio track
var activeTrack = mediaType.activeTrack();
var activeGroup = mediaType.activeGroup();
var id = (activeGroup.filter(function (group) {
return group['default'];
})[0] || activeGroup[0]).id;
var defaultTrack = mediaType.tracks[id];
if (activeTrack === defaultTrack) {
// Default track encountered an error. All we can do now is blacklist the current
// rendition and hope another will switch audio groups
blacklistCurrentPlaylist({
message: 'Problem encountered loading the default audio track.'
});
return;
}
_videoJs2['default'].log.warn('Problem encountered loading the alternate audio track.' + 'Switching back to default.');
for (var trackId in mediaType.tracks) {
mediaType.tracks[trackId].enabled = mediaType.tracks[trackId] === defaultTrack;
}
mediaType.onTrackChanged();
};
},
/**
* Returns a function to be called when a SegmentLoader or PlaylistLoader encounters
* an error.
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Error handler. Logs warning to console and disables the active subtitle track
* @function onError.SUBTITLES
*/
SUBTITLES: function SUBTITLES(type, settings) {
return function () {
var segmentLoader = settings.segmentLoaders[type];
var mediaType = settings.mediaTypes[type];
_videoJs2['default'].log.warn('Problem encountered loading the subtitle track.' + 'Disabling subtitle track.');
stopLoaders(segmentLoader, mediaType);
var track = mediaType.activeTrack();
if (track) {
track.mode = 'disabled';
}
mediaType.onTrackChanged();
};
}
};
exports.onError = onError;
var setupListeners = {
/**
* Setup event listeners for audio playlist loader
*
* @param {String} type
* MediaGroup type
* @param {PlaylistLoader|null} playlistLoader
* PlaylistLoader to register listeners on
* @param {Object} settings
* Object containing required information for media groups
* @function setupListeners.AUDIO
*/
AUDIO: function AUDIO(type, playlistLoader, settings) {
if (!playlistLoader) {
// no playlist loader means audio will be muxed with the video
return;
}
var tech = settings.tech;
var requestOptions = settings.requestOptions;
var segmentLoader = settings.segmentLoaders[type];
playlistLoader.on('loadedmetadata', function () {
var media = playlistLoader.media();
segmentLoader.playlist(media, requestOptions);
// if the video is already playing, or if this isn't a live video and preload
// permits, start downloading segments
if (!tech.paused() || media.endList && tech.preload() !== 'none') {
segmentLoader.load();
}
});
playlistLoader.on('loadedplaylist', function () {
segmentLoader.playlist(playlistLoader.media(), requestOptions);
// If the player isn't paused, ensure that the segment loader is running
if (!tech.paused()) {
segmentLoader.load();
}
});
playlistLoader.on('error', onError[type](type, settings));
},
/**
* Setup event listeners for subtitle playlist loader
*
* @param {String} type
* MediaGroup type
* @param {PlaylistLoader|null} playlistLoader
* PlaylistLoader to register listeners on
* @param {Object} settings
* Object containing required information for media groups
* @function setupListeners.SUBTITLES
*/
SUBTITLES: function SUBTITLES(type, playlistLoader, settings) {
var tech = settings.tech;
var requestOptions = settings.requestOptions;
var segmentLoader = settings.segmentLoaders[type];
var mediaType = settings.mediaTypes[type];
playlistLoader.on('loadedmetadata', function () {
var media = playlistLoader.media();
segmentLoader.playlist(media, requestOptions);
segmentLoader.track(mediaType.activeTrack());
// if the video is already playing, or if this isn't a live video and preload
// permits, start downloading segments
if (!tech.paused() || media.endList && tech.preload() !== 'none') {
segmentLoader.load();
}
});
playlistLoader.on('loadedplaylist', function () {
segmentLoader.playlist(playlistLoader.media(), requestOptions);
// If the player isn't paused, ensure that the segment loader is running
if (!tech.paused()) {
segmentLoader.load();
}
});
playlistLoader.on('error', onError[type](type, settings));
}
};
exports.setupListeners = setupListeners;
var initialize = {
/**
* Setup PlaylistLoaders and AudioTracks for the audio groups
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @function initialize.AUDIO
*/
'AUDIO': function AUDIO(type, settings) {
var mode = settings.mode;
var hls = settings.hls;
var segmentLoader = settings.segmentLoaders[type];
var requestOptions = settings.requestOptions;
var mediaGroups = settings.master.mediaGroups;
var _settings$mediaTypes$type = settings.mediaTypes[type];
var groups = _settings$mediaTypes$type.groups;
var tracks = _settings$mediaTypes$type.tracks;
// force a default if we have none or we are not
// in html5 mode (the only mode to support more than one
// audio track)
if (!mediaGroups[type] || Object.keys(mediaGroups[type]).length === 0 || mode !== 'html5') {
mediaGroups[type] = { main: { 'default': { 'default': true } } };
}
for (var groupId in mediaGroups[type]) {
if (!groups[groupId]) {
groups[groupId] = [];
}
for (var variantLabel in mediaGroups[type][groupId]) {
var properties = mediaGroups[type][groupId][variantLabel];
var playlistLoader = undefined;
if (properties.resolvedUri) {
playlistLoader = new _playlistLoader2['default'](properties.resolvedUri, hls, requestOptions);
} else {
// no resolvedUri means the audio is muxed with the video when using this
// audio track
playlistLoader = null;
}
properties = _videoJs2['default'].mergeOptions({ id: variantLabel, playlistLoader: playlistLoader }, properties);
setupListeners[type](type, properties.playlistLoader, settings);
groups[groupId].push(properties);
if (typeof tracks[variantLabel] === 'undefined') {
var track = new _videoJs2['default'].AudioTrack({
id: variantLabel,
kind: audioTrackKind_(properties),
enabled: false,
language: properties.language,
'default': properties['default'],
label: variantLabel
});
tracks[variantLabel] = track;
}
}
}
// setup single error event handler for the segment loader
segmentLoader.on('error', onError[type](type, settings));
},
/**
* Setup PlaylistLoaders and TextTracks for the subtitle groups
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @function initialize.SUBTITLES
*/
'SUBTITLES': function SUBTITLES(type, settings) {
var tech = settings.tech;
var hls = settings.hls;
var segmentLoader = settings.segmentLoaders[type];
var requestOptions = settings.requestOptions;
var mediaGroups = settings.master.mediaGroups;
var _settings$mediaTypes$type2 = settings.mediaTypes[type];
var groups = _settings$mediaTypes$type2.groups;
var tracks = _settings$mediaTypes$type2.tracks;
for (var groupId in mediaGroups[type]) {
if (!groups[groupId]) {
groups[groupId] = [];
}
for (var variantLabel in mediaGroups[type][groupId]) {
if (mediaGroups[type][groupId][variantLabel].forced) {
// Subtitle playlists with the forced attribute are not selectable in Safari.
// According to Apple's HLS Authoring Specification:
// If content has forced subtitles and regular subtitles in a given language,
// the regular subtitles track in that language MUST contain both the forced
// subtitles and the regular subtitles for that language.
// Because of this requirement and that Safari does not add forced subtitles,
// forced subtitles are skipped here to maintain consistent experience across
// all platforms
continue;
}
var properties = mediaGroups[type][groupId][variantLabel];
properties = _videoJs2['default'].mergeOptions({
id: variantLabel,
playlistLoader: new _playlistLoader2['default'](properties.resolvedUri, hls, requestOptions)
}, properties);
setupListeners[type](type, properties.playlistLoader, settings);
groups[groupId].push(properties);
if (typeof tracks[variantLabel] === 'undefined') {
var track = tech.addRemoteTextTrack({
id: variantLabel,
kind: 'subtitles',
enabled: false,
language: properties.language,
label: variantLabel
}, false).track;
tracks[variantLabel] = track;
}
}
}
// setup single error event handler for the segment loader
segmentLoader.on('error', onError[type](type, settings));
},
/**
* Setup TextTracks for the closed-caption groups
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @function initialize['CLOSED-CAPTIONS']
*/
'CLOSED-CAPTIONS': function CLOSEDCAPTIONS(type, settings) {
var tech = settings.tech;
var mediaGroups = settings.master.mediaGroups;
var _settings$mediaTypes$type3 = settings.mediaTypes[type];
var groups = _settings$mediaTypes$type3.groups;
var tracks = _settings$mediaTypes$type3.tracks;
for (var groupId in mediaGroups[type]) {
if (!groups[groupId]) {
groups[groupId] = [];
}
for (var variantLabel in mediaGroups[type][groupId]) {
var properties = mediaGroups[type][groupId][variantLabel];
// We only support CEA608 captions for now, so ignore anything that
// doesn't use a CCx INSTREAM-ID
if (!properties.instreamId.match(/CC\d/)) {
continue;
}
// No PlaylistLoader is required for Closed-Captions because the captions are
// embedded within the video stream
groups[groupId].push(_videoJs2['default'].mergeOptions({ id: variantLabel }, properties));
if (typeof tracks[variantLabel] === 'undefined') {
var track = tech.addRemoteTextTrack({
id: properties.instreamId,
kind: 'captions',
enabled: false,
language: properties.language,
label: variantLabel
}, false).track;
tracks[variantLabel] = track;
}
}
}
}
};
exports.initialize = initialize;
/**
* Returns a function used to get the active group of the provided type
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Function that returns the active media group for the provided type. Takes an
* optional parameter {TextTrack} track. If no track is provided, a list of all
* variants in the group, otherwise the variant corresponding to the provided
* track is returned.
* @function activeGroup
*/
var activeGroup = function activeGroup(type, settings) {
return function (track) {
var masterPlaylistLoader = settings.masterPlaylistLoader;
var groups = settings.mediaTypes[type].groups;
var media = masterPlaylistLoader.media();
if (!media) {
return null;
}
var variants = null;
if (media.attributes[type]) {
variants = groups[media.attributes[type]];
}
variants = variants || groups.main;
if (typeof track === 'undefined') {
return variants;
}
if (track === null) {
// An active track was specified so a corresponding group is expected. track === null
// means no track is currently active so there is no corresponding group
return null;
}
return variants.filter(function (props) {
return props.id === track.id;
})[0] || null;
};
};
exports.activeGroup = activeGroup;
var activeTrack = {
/**
* Returns a function used to get the active track of type provided
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Function that returns the active media track for the provided type. Returns
* null if no track is active
* @function activeTrack.AUDIO
*/
AUDIO: function AUDIO(type, settings) {
return function () {
var tracks = settings.mediaTypes[type].tracks;
for (var id in tracks) {
if (tracks[id].enabled) {
return tracks[id];
}
}
return null;
};
},
/**
* Returns a function used to get the active track of type provided
*
* @param {String} type
* MediaGroup type
* @param {Object} settings
* Object containing required information for media groups
* @return {Function}
* Function that returns the active media track for the provided type. Returns
* null if no track is active
* @function activeTrack.SUBTITLES
*/
SUBTITLES: function SUBTITLES(type, settings) {
return function () {
var tracks = settings.mediaTypes[type].tracks;
for (var id in tracks) {
if (tracks[id].mode === 'showing') {
return tracks[id];
}
}
return null;
};
}
};
exports.activeTrack = activeTrack;
/**
* Setup PlaylistLoaders and Tracks for media groups (Audio, Subtitles,
* Closed-Captions) specified in the master manifest.
*
* @param {Object} settings
* Object containing required information for setting up the media groups
* @param {SegmentLoader} settings.segmentLoaders.AUDIO
* Audio segment loader
* @param {SegmentLoader} settings.segmentLoaders.SUBTITLES
* Subtitle segment loader
* @param {SegmentLoader} settings.segmentLoaders.main
* Main segment loader
* @param {Tech} settings.tech
* The tech of the player
* @param {Object} settings.requestOptions
* XHR request options used by the segment loaders
* @param {PlaylistLoader} settings.masterPlaylistLoader
* PlaylistLoader for the master source
* @param {String} mode
* Mode of the hls source handler. Can be 'auto', 'html5', or 'flash'
* @param {HlsHandler} settings.hls
* HLS SourceHandler
* @param {Object} settings.master
* The parsed master manifest
* @param {Object} settings.mediaTypes
* Object to store the loaders, tracks, and utility methods for each media type
* @param {Function} settings.blacklistCurrentPlaylist
* Blacklists the current rendition and forces a rendition switch.
* @function setupMediaGroups
*/
var setupMediaGroups = function setupMediaGroups(settings) {
['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
initialize[type](type, settings);
});
var mediaTypes = settings.mediaTypes;
var masterPlaylistLoader = settings.masterPlaylistLoader;
var tech = settings.tech;
var hls = settings.hls;
// setup active group and track getters and change event handlers
['AUDIO', 'SUBTITLES'].forEach(function (type) {
mediaTypes[type].activeGroup = activeGroup(type, settings);
mediaTypes[type].activeTrack = activeTrack[type](type, settings);
mediaTypes[type].onGroupChanged = onGroupChanged(type, settings);
mediaTypes[type].onTrackChanged = onTrackChanged(type, settings);
});
// DO NOT enable the default subtitle or caption track.
// DO enable the default audio track
var audioGroup = mediaTypes.AUDIO.activeGroup();
var groupId = (audioGroup.filter(function (group) {
return group['default'];
})[0] || audioGroup[0]).id;
mediaTypes.AUDIO.tracks[groupId].enabled = true;
mediaTypes.AUDIO.onTrackChanged();
masterPlaylistLoader.on('mediachange', function () {
['AUDIO', 'SUBTITLES'].forEach(function (type) {
return mediaTypes[type].onGroupChanged();
});
});
// custom audio track change event handler for usage event
var onAudioTrackChanged = function onAudioTrackChanged() {
mediaTypes.AUDIO.onTrackChanged();
tech.trigger({ type: 'usage', name: 'hls-audio-change' });
};
tech.audioTracks().addEventListener('change', onAudioTrackChanged);
tech.remoteTextTracks().addEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
hls.on('dispose', function () {
tech.audioTracks().removeEventListener('change', onAudioTrackChanged);
tech.remoteTextTracks().removeEventListener('change', mediaTypes.SUBTITLES.onTrackChanged);
});
// clear existing audio tracks and add the ones we just created
tech.clearTracks('audio');
for (var id in mediaTypes.AUDIO.tracks) {
tech.audioTracks().addTrack(mediaTypes.AUDIO.tracks[id]);
}
};
exports.setupMediaGroups = setupMediaGroups;
/**
* Creates skeleton object used to store the loaders, tracks, and utility methods for each
* media type
*
* @return {Object}
* Object to store the loaders, tracks, and utility methods for each media type
* @function createMediaTypes
*/
var createMediaTypes = function createMediaTypes() {
var mediaTypes = {};
['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach(function (type) {
mediaTypes[type] = {
groups: {},
tracks: {},
activePlaylistLoader: null,
activeGroup: noop,
activeTrack: noop,
onGroupChanged: noop,
onTrackChanged: noop
};
});
return mediaTypes;
};
exports.createMediaTypes = createMediaTypes;