/**
* 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 '../../streaming/constants/Constants.js';
import FactoryMaker from '../../core/FactoryMaker.js';
import MediaPlayerEvents from '../../streaming/MediaPlayerEvents.js';
import {getTimeBasedSegment} from '../utils/SegmentsUtils.js';
function RepresentationController(config) {
config = config || {};
const eventBus = config.eventBus;
const events = config.events;
const abrController = config.abrController;
const dashMetrics = config.dashMetrics;
const playbackController = config.playbackController;
const timelineConverter = config.timelineConverter;
const type = config.type;
const streamInfo = config.streamInfo;
const segmentsController = config.segmentsController;
const isDynamic = config.isDynamic;
let instance,
voAvailableRepresentations,
currentVoRepresentation;
function setup() {
resetInitialSettings();
eventBus.on(MediaPlayerEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChanged, instance);
}
function getStreamId() {
return streamInfo.id;
}
function getType() {
return type;
}
function checkConfig() {
if (!abrController || !dashMetrics || !playbackController || !timelineConverter) {
throw new Error(Constants.MISSING_CONFIG_ERROR);
}
}
function getCurrentRepresentation() {
return currentVoRepresentation;
}
function resetInitialSettings() {
voAvailableRepresentations = [];
}
function reset() {
eventBus.off(MediaPlayerEvents.MANIFEST_VALIDITY_CHANGED, onManifestValidityChanged, instance);
resetInitialSettings();
}
function updateData(availableRepresentations, isFragmented, selectedRepresentationId) {
return new Promise((resolve, reject) => {
voAvailableRepresentations = availableRepresentations;
const selectedRepresentation = getRepresentationById(selectedRepresentationId);
_setCurrentVoRepresentation(selectedRepresentation);
if (type !== Constants.VIDEO && type !== Constants.AUDIO && (type !== Constants.TEXT || !isFragmented)) {
endDataUpdate();
resolve();
return;
}
const promises = [];
for (let i = 0, ln = voAvailableRepresentations.length; i < ln; i++) {
const currentRep = voAvailableRepresentations[i];
promises.push(_updateRepresentation(currentRep));
}
Promise.all(promises)
.then(() => {
_onAllRepresentationsUpdated();
resolve();
})
.catch((e) => {
reject(e);
})
})
}
function _onAllRepresentationsUpdated() {
abrController.setPlaybackQuality(type, streamInfo, currentVoRepresentation);
const dvrInfo = dashMetrics.getCurrentDVRInfo(type);
if (dvrInfo) {
dashMetrics.updateManifestUpdateInfo({ latency: dvrInfo.range.end - playbackController.getTime() });
}
endDataUpdate();
}
function _updateRepresentation(currentRep) {
return new Promise((resolve, reject) => {
const hasInitialization = currentRep.hasInitialization();
const hasSegments = currentRep.hasSegments();
// If representation has initialization and segments information we are done
// otherwise, it means that a request has to be made to get initialization and/or segments information
const promises = [];
promises.push(segmentsController.updateInitData(currentRep, hasInitialization));
promises.push(segmentsController.updateSegmentData(currentRep, hasSegments));
Promise.all(promises)
.then((data) => {
if (data[0] && !data[0].error) {
currentRep = _onInitLoaded(currentRep, data[0]);
}
if (data[1] && !data[1].error) {
currentRep = _onSegmentsLoaded(currentRep, data[1]);
}
currentRep.fragmentDuration = currentRep.segmentDuration ? currentRep.segmentDuration : currentRep.segments && currentRep.segments.length > 0 ? currentRep.segments[0].duration : NaN;
_setMediaFinishedInformation(currentRep);
_onRepresentationUpdated(currentRep);
resolve();
})
.catch((e) => {
reject(e);
});
});
}
function _setMediaFinishedInformation(representation) {
representation.mediaFinishedInformation = segmentsController.getMediaFinishedInformation(representation);
}
function _onInitLoaded(representation, e) {
if (!e || e.error || !e.representation) {
return representation;
}
return e.representation;
}
function _onSegmentsLoaded(representation, e) {
if (!e || e.error) return;
const fragments = e.segments;
const segments = [];
let count = 0;
let i,
len,
s,
seg;
for (i = 0, len = fragments ? fragments.length : 0; i < len; i++) {
s = fragments[i];
seg = getTimeBasedSegment(
timelineConverter,
isDynamic,
representation,
s.startTime,
s.duration,
s.timescale,
s.media,
s.mediaRange,
count);
if (seg) {
segments.push(seg);
seg = null;
count++;
}
}
if (segments.length > 0) {
representation.segments = segments;
}
return representation;
}
function _addRepresentationSwitch(currentRepresentation) {
checkConfig();
const now = new Date();
const currentVideoTimeMs = playbackController.getTime() * 1000;
if (currentRepresentation) {
dashMetrics.addRepresentationSwitch(currentRepresentation.adaptation.type, now, currentVideoTimeMs, currentRepresentation.id);
}
eventBus.trigger(MediaPlayerEvents.REPRESENTATION_SWITCH, {
mediaType: type,
streamId: streamInfo.id,
currentRepresentation,
}, { streamId: streamInfo.id, mediaType: type })
}
function getRepresentationById(id) {
if (!voAvailableRepresentations || voAvailableRepresentations.length === 0) {
return null;
}
const reps = voAvailableRepresentations.filter((rep) => {
return rep.id === id;
})
if (reps.length > 0) {
return reps[0]
}
return null;
}
function endDataUpdate(error) {
eventBus.trigger(events.DATA_UPDATE_COMPLETED,
{
currentRepresentation: currentVoRepresentation,
error: error
},
{ streamId: streamInfo.id, mediaType: type }
);
}
function _onRepresentationUpdated(r) {
let manifestUpdateInfo = dashMetrics.getCurrentManifestUpdate();
let alreadyAdded = false;
let repInfo;
if (manifestUpdateInfo) {
for (let i = 0; i < manifestUpdateInfo.representationInfo.length; i++) {
repInfo = manifestUpdateInfo.representationInfo[i];
if (repInfo.index === r.index && repInfo.mediaType === getType()) {
alreadyAdded = true;
break;
}
}
if (!alreadyAdded) {
dashMetrics.addManifestUpdateRepresentationInfo(r, getType());
}
}
}
/**
* We get the new selected Representation which will not hold the ranges and the segment references in case of SegmentBase.
* In any case use the id to find the right Representation instance in our array of Representations.
* @param newRep
*/
function prepareQualityChange(newRep) {
const voRepresentations = voAvailableRepresentations.filter((rep) => {
return rep.id === newRep.id;
})
if (voRepresentations.length > 0) {
_setCurrentVoRepresentation(voRepresentations[0]);
}
}
function _setCurrentVoRepresentation(value) {
if (!currentVoRepresentation || currentVoRepresentation.id !== value.id) {
_addRepresentationSwitch(value);
}
currentVoRepresentation = value;
}
function onManifestValidityChanged(e) {
if (e.newDuration) {
const representation = getCurrentRepresentation();
if (representation && representation.adaptation.period) {
const period = representation.adaptation.period;
period.duration = e.newDuration;
}
}
}
instance = {
getCurrentRepresentation,
getRepresentationById,
getStreamId,
getType,
prepareQualityChange,
reset,
updateData,
};
setup();
return instance;
}
RepresentationController.__dashjs_factory_name = 'RepresentationController';
export default FactoryMaker.getClassFactory(RepresentationController);