/**
* The copyright in this software is being made available under the BSD License,
* included below. This software may be subject to other third party and contributor
* rights, including patent rights, and no such rights are granted under this license.
*
* Copyright (c) 2013, Dash Industry Forum.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* * Neither the name of Dash Industry Forum nor the names of its
* contributors may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
import Constants from '../constants/Constants.js';
import DashConstants from '../../dash/constants/DashConstants.js';
import FactoryMaker from '../../core/FactoryMaker.js';
import {
ARRAY_NOT_SUPPORTED_ERROR,
MEDIA_PLAYER_NOT_INITIALIZED_ERROR,
STREAMING_NOT_INITIALIZED_ERROR
} from './MediaPlayerApiErrors.js';
function TrackApi() {
let instance,
state,
mediaPlayer;
function setConfig(config) {
if (!config) {
return;
}
if (config.state) {
state = config.state;
}
if (config.mediaPlayer) {
mediaPlayer = config.mediaPlayer;
}
}
/**
* Returns the average latency computed in the ThroughputController in milliseconds
*
* @param {MediaType} type
* @param {string} calculationMode
* @param {number} sampleSize
* @return {number} value
* @memberof module:MediaPlayer
* @instance
*/
function getAverageLatency(type = Constants.VIDEO, calculationMode = null, sampleSize = NaN) {
return state.throughputController ? state.throughputController.getAverageLatency(type, calculationMode, sampleSize) : 0;
}
/**
* Returns the average throughput computed in the ThroughputController in kbit/s
*
* @param {MediaType} type
* @param {string} calculationMode
* @param {number} sampleSize
* @return {number} value
* @memberof module:MediaPlayer
* @instance
*/
function getAverageThroughput(type = Constants.VIDEO, calculationMode = null, sampleSize = NaN) {
return state.throughputController ? state.throughputController.getAverageThroughput(type, calculationMode, sampleSize) : 0;
}
/**
* Returns the safe average throughput computed in the ThroughputController in kbit/s. The safe average throughput is the average throughput multiplied by bandwidthSafetyFactor
*
* @param {MediaType} type
* @param {string} calculationMode
* @param {number} sampleSize
* @return {number} value
* @memberof module:MediaPlayer
* @instance
*/
function getSafeAverageThroughput(type = Constants.VIDEO, calculationMode = null, sampleSize = NaN) {
return state.throughputController ? state.throughputController.getSafeAverageThroughput(type, calculationMode, sampleSize) : 0;
}
/**
* Returns the raw throughput data without calculating the average. This can be used to calculate the current throughput yourself.
*
* @param {MediaType} type
* @return {Array} value
* @memberof module:MediaPlayer
* @instance
*/
function getRawThroughputData(type = Constants.VIDEO) {
return state.throughputController ? state.throughputController.getRawThroughputData(type) : [];
}
/**
* Gets the current download quality for media type video, audio or images. For video and audio types the ABR
* rules update this value before every new download unless autoSwitchBitrate is set to false. For 'image'
* type, thumbnails, there is no ABR algorithm and quality is set manually.
*
* @param {MediaType} type - 'video', 'audio' or 'image' (thumbnails)
* @returns {Representation | null} the quality index, 0 corresponding to the lowest bitrate
* @memberof module:MediaPlayer
* @see {@link module:MediaPlayer#getCurrentRepresentationForType getCurrentRepresentationForType()}
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function getCurrentRepresentationForType(type) {
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
if (type !== Constants.IMAGE && type !== Constants.VIDEO && type !== Constants.AUDIO) {
return null;
}
const activeStream = mediaPlayer.getActiveStream();
if (!activeStream) {
return null;
}
if (type === Constants.IMAGE) {
const thumbnailController = activeStream.getThumbnailController();
return !thumbnailController ? -1 : thumbnailController.getCurrentTrack();
}
return activeStream.getCurrentRepresentationForType(type);
}
/**
* Sets the current quality for media type instead of letting the ABR Heuristics automatically select it.
* This value will be overwritten by the ABR rules unless autoSwitchBitrate is set to false.
*
* @param {MediaType} type - 'video', 'audio' or 'image'
* @param {number} id , The ID of the Representation
* @param {boolean} forceReplace - true if segments have to be replaced by segments of the new quality
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function setRepresentationForTypeById(type, id, forceReplace = false) {
if (type !== Constants.IMAGE && type !== Constants.VIDEO && type !== Constants.AUDIO) {
return;
}
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
const activeStream = mediaPlayer.getActiveStream();
if (!activeStream) {
return;
}
if (type === Constants.IMAGE) {
const thumbnailController = activeStream.getThumbnailController();
if (thumbnailController) {
thumbnailController.setTrackById(id);
}
} else {
const representation = activeStream.getRepresentationForTypeById(type, id);
if (representation) {
state.abrController.manuallySetPlaybackQuality(type, state.streamController.getActiveStreamInfo(), representation, { forceReplace });
}
}
}
/**
* Sets the current quality for media type instead of letting the ABR Heuristics automatically select it.
* This value will be overwritten by the ABR rules unless autoSwitchBitrate is set to false.
* Note that you need to specify a relative index based on the position of the target entry in the return value of getRepresentationsByType().
* Do NOT use representation.absoluteIndex here as this index was assigned prior to applying any filter function. If you want to select a specific representation then use setRepresentationForTypeById() instead.
*
* @param {MediaType} type - 'video', 'audio' or 'image'
* @param {number} index - the quality index, 0 corresponding to the lowest possible index
* @param {boolean} forceReplace - true if segments have to be replaced by segments of the new quality
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function setRepresentationForTypeByIndex(type, index, forceReplace = false) {
if (type !== Constants.IMAGE && type !== Constants.VIDEO && type !== Constants.AUDIO) {
return;
}
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
const activeStream = mediaPlayer.getActiveStream();
if (!activeStream) {
return;
}
if (type === Constants.IMAGE) {
const thumbnailController = activeStream.getThumbnailController();
if (thumbnailController) {
thumbnailController.setTrackByIndex(index);
}
} else {
const representation = activeStream.getRepresentationForTypeByIndex(type, index);
if (representation) {
state.abrController.manuallySetPlaybackQuality(type, state.streamController.getActiveStreamInfo(), representation, { forceReplace });
}
}
}
/**
* This method returns the list of all available representations for a given media type. The returned list is filtered according to the current ABR rules (e.g. max/min bitrate and limitBitrateByPortal).
* If you want to get the unfiltered list of representations then use getRepresentationsByTypeUnfiltered() instead.
* @param {MediaType} type
* @param {string} streamId
* @returns {Array}
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function getRepresentationsByType(type, streamId = null) {
return _getRepresentations(type, streamId, true);
}
/**
* This method returns the list of all available representations for a given media type. The returned list is unfiltered and settings like max/min bitrate and limitBitrateByPortal are not taken into account.
* If you want to get the filtered list of representations then use getRepresentationsByType() instead.
* @param {MediaType} type
* @param {string} streamId
* @returns {Array}
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function getRepresentationsByTypeUnfiltered(type, streamId = null) {
return _getRepresentations(type, streamId, false);
}
function _getRepresentations(type, streamId, filterBySettings = true) {
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
let stream = streamId ? state.streamController.getStreamById(streamId) : mediaPlayer.getActiveStream();
return stream ? stream.getRepresentationsByType(type, filterBySettings) : [];
}
/**
* This method returns the list of all available tracks for a given media type
* @param {MediaType} type
* @returns {Array} list of {@link MediaInfo}
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function getTracksFor(type) {
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
let streamInfo = state.streamController.getActiveStreamInfo();
if (!streamInfo) {
return [];
}
const tracks = state.mediaController.getTracksFor(type, streamInfo.id);
return tracks.filter((track) => {
return state.protectionController ? state.protectionController.areKeyIdsUsable(track.normalizedKeyIds) : true
})
}
/**
* @param {MediaType} type
* @returns {Object|null} {@link MediaInfo}
*
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function getCurrentTrackFor(type) {
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
let streamInfo = state.streamController.getActiveStreamInfo();
if (streamInfo) {
return state.mediaController.getCurrentTrackFor(type, streamInfo.id);
}
return null
}
/**
* This method allows to set media settings that will be used to pick the initial track. The settings object supports the following properties:
* <ul>
* <li><code>lang</code>: a string primitive, a string object, or a RegExp object to match</li>
* <li><code>index</code>: the index of the track</li>
* <li><code>viewpoint</code>: object <code>{schemeIdUri, value}</code> or value-primitive</li>
* <li><code>audioChannelConfiguration</code>: object <code>{schemeIdUri, value}</code> or value-primitive (assumes schemeIdUri='urn:mpeg:mpegB:cicp:ChannelConfiguration')</li>
* <li><code>accessibility</code>: object <code>{schemeIdUri, value}</code> or value-primitive (assumes schemeIdUri='urn:mpeg:dash:role:2011')</li>
* <li><code>role</code>: object <code>{schemeIdUri, value}</code> or value-primitive (assumes schemeIdUri='urn:mpeg:dash:role:2011')</li>
* <li><code>codec</code>: full codec string as exposed in MediaInfo.codec, e.g. <code>'audio/mp4;codecs="ec-3"'</code>, compared with strict equality</li>
* </ul>
*
* @example
* player.setInitialMediaSettingsFor('audio', {
* lang: 'de',
* role: 'main',
* codec: 'audio/mp4;codecs="ec-3"'
* });
* @param {MediaType} type
* @param {Object} value
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~MEDIA_PLAYER_NOT_INITIALIZED_ERROR MEDIA_PLAYER_NOT_INITIALIZED_ERROR} if called before initialize function
* @instance
*/
function setInitialMediaSettingsFor(type, value) {
if (!state.mediaPlayerInitialized) {
throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR;
}
let sanitizedValue = _sanitizeSettings(value);
state.mediaController.setInitialSettings(type, sanitizedValue);
}
/**
* This method returns the media settings that are used to pick the initial track.
*
* @example
* // Returned object has the following format:
* {
* lang: langValue,
* index: indexValue,
* viewpoint: viewpointValue,
* audioChannelConfiguration: audioChannelConfigurationValue,
* accessibility: accessibilityValue,
* role: roleValue,
* codec: codecValue
* }
* @param {MediaType} type
* @returns {Object}
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~MEDIA_PLAYER_NOT_INITIALIZED_ERROR MEDIA_PLAYER_NOT_INITIALIZED_ERROR} if called before initialize function
* @instance
*/
function getInitialMediaSettingsFor(type) {
if (!state.mediaPlayerInitialized) {
throw MEDIA_PLAYER_NOT_INITIALIZED_ERROR;
}
return state.mediaController.getInitialSettings(type);
}
/**
* @param {MediaInfo} track - instance of {@link MediaInfo}
* @param {boolean} [noSettingsSave] - specify if settings from the track must not be saved for incoming track selection
* @memberof module:MediaPlayer
* @throws {@link module:MediaPlayer~STREAMING_NOT_INITIALIZED_ERROR STREAMING_NOT_INITIALIZED_ERROR} if called before initializePlayback function
* @instance
*/
function setCurrentTrack(track, noSettingsSave = false) {
if (!state.streamingInitialized) {
throw STREAMING_NOT_INITIALIZED_ERROR;
}
const canUseTrack = state.protectionController ? state.protectionController.areKeyIdsUsable(track.normalizedKeyIds) : true
if (!canUseTrack) {
state.logger.error(`Can not switch to track with index ${track.index} because key is not usable`);
return
}
state.mediaController.setTrack(track, { noSettingsSave });
}
/**
* Provide the thumbnail at time position. This can be asynchronous, so you must provide a callback ro retrieve thumbnails informations
* @param {number} time - A relative time, in seconds, based on the return value of the {@link module:MediaPlayer#duration duration()} method is expected
* @param {function} callback - A Callback function provided when retrieving thumbnail the given time position. Thumbnail object is null in case there are is not a thumbnails representation or
* if it doesn't contain a thumbnail for the given time position.
* @memberof module:MediaPlayer
* @instance
*/
function provideThumbnail(time, callback) {
if (typeof callback !== 'function') {
return;
}
if (time < 0) {
callback(null);
return;
}
const s = state.playbackController.getIsDynamic() ? mediaPlayer.getDvrSeekOffset(time) : time;
const stream = state.streamController.getStreamForTime(s);
if (stream === null) {
callback(null);
return;
}
const thumbnailController = stream.getThumbnailController();
if (!thumbnailController) {
callback(null);
return;
}
return thumbnailController.provide(s, callback);
}
function _sanitizeSettings(value) {
const defaults = state.settings.get().streaming.defaultSchemeIdUri;
let output = {};
function __sanitizeDescriptorType(name, val, defaultSchemeIdUri) {
let out = {};
// For an empty string, let's unset the descriptor, i.e. return null
if (val) {
if (val instanceof Array) {
throw ARRAY_NOT_SUPPORTED_ERROR;
} else if (val instanceof Object) {
out.schemeIdUri = val.schemeIdUri ? val.schemeIdUri : '';
out.value = val.value ? val.value : '';
} else {
out.schemeIdUri = defaultSchemeIdUri;
out.value = val;
state.logger.warn('No schemeIdUri provided for ' + name + ', using default \"' + defaultSchemeIdUri + '\"');
}
return out;
}
return null;
}
if (value.id !== undefined) {
output.id = value.id;
}
if (value.lang !== undefined) {
output.lang = value.lang;
}
if (!isNaN(value.index)) {
output.index = value.index;
}
if (value.viewpoint !== undefined) {
output.viewpoint = __sanitizeDescriptorType('viewpoint', value.viewpoint, defaults.viewpoint);
}
if (value.audioChannelConfiguration !== undefined) {
output.audioChannelConfiguration = __sanitizeDescriptorType('audioChannelConfiguration', value.audioChannelConfiguration, defaults.audioChannelConfiguration);
}
if (value.role !== undefined && value.role !== null) {
output.role = __sanitizeDescriptorType('role', value.role, defaults.role);
// conceal misspelled "Main" from earlier MPEG-DASH editions (fixed with 6th edition)
if (output.role.schemeIdUri === Constants.DASH_ROLE_SCHEME_ID && output.role.value === 'Main') {
output.role.value = DashConstants.MAIN;
}
}
if (value.accessibility !== undefined) {
output.accessibility = __sanitizeDescriptorType('accessibility', value.accessibility, defaults.accessibility);
}
if (value.codec !== undefined) {
output.codec = value.codec;
}
return output;
}
instance = {
getAverageLatency,
getAverageThroughput,
getCurrentRepresentationForType,
getCurrentTrackFor,
getInitialMediaSettingsFor,
getRawThroughputData,
getRepresentationsByType,
getRepresentationsByTypeUnfiltered,
getSafeAverageThroughput,
getTracksFor,
provideThumbnail,
setConfig,
setCurrentTrack,
setInitialMediaSettingsFor,
setRepresentationForTypeById,
setRepresentationForTypeByIndex,
};
return instance;
}
TrackApi.__dashjs_factory_name = 'TrackApi';
export default FactoryMaker.getClassFactory(TrackApi);