Skip to content

Commit

Permalink
Feature capabilities filter (#3512)
Browse files Browse the repository at this point in the history
* WiP: Add CapabilitiesFilter.js to filter unsupported codecs and unsupported EssentialProperties before initialisation

* Fix existing unit tests in streaming.Stream.js

* Filter AdaptationSets and Representations with unsupported EssentialProperty elements

* Fix a bug in the CapabilitiesFilter.js

* Add unit tests for the CapabilitiesFilter

* Fix a bug in the supportsEssentialProperty function

* Move codec comparison to Capabilities.js
  • Loading branch information
dsilhavy authored Jan 18, 2021
1 parent c8403e4 commit 0fc5db8
Show file tree
Hide file tree
Showing 13 changed files with 632 additions and 78 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ declare namespace dashjs {
useAppendWindow?: boolean,
manifestUpdateRetryInterval?: number;
stallThreshold?: number;
filterUnsupportedEssentialProperties?: true
liveCatchup?: {
minDrift?: number;
maxDrift?: number;
Expand Down
3 changes: 3 additions & 0 deletions src/core/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ import {HTTPRequest} from '../streaming/vo/metrics/HTTPRequest';
* downloading the next manifest once the minimumUpdatePeriod time has
* @property {number} [stallThreshold=0.5]
* Stall threshold used in BufferController.js to determine whether a track should still be changed and which buffer range to prune.
* @property {boolean} [filterUnsupportedEssentialProperties=true]
* Enable to filter all the AdaptationSets and Representations which contain an unsupported <EssentialProperty> element
* @property {module:Settings~CachingInfoSettings} [lastBitrateCachingInfo={enabled: true, ttl: 360000}]
* Set to false if you would like to disable the last known bit rate from being stored during playback and used
* to set the initial bit rate for subsequent playback within the expiration window.
Expand Down Expand Up @@ -447,6 +449,7 @@ function Settings() {
useAppendWindow: true,
manifestUpdateRetryInterval: 100,
stallThreshold: 0.5,
filterUnsupportedEssentialProperties: true,
liveCatchup: {
minDrift: 0.02,
maxDrift: 0,
Expand Down
29 changes: 29 additions & 0 deletions src/dash/DashAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,28 @@ function DashAdapter() {
return realAdaptation;
}

/**
* Return all EssentialProperties of a Representation
* @param {object} representation
* @return {array}
*/
function getEssentialPropertiesForRepresentation(representation) {
try {
return dashManifestModel.getEssentialPropertiesForRepresentation(representation);
} catch (e) {
return [];
}
}

/**
* Returns the period by index
* @param {number} index
* @return {object}
*/
function getRealPeriodByIndex(index) {
return dashManifestModel.getRealPeriodForIndex(index, voPeriods[0].mpd.manifest);
}

/**
* Returns all voRepresentations for a given mediaInfo
* @param {object} mediaInfo
Expand Down Expand Up @@ -725,6 +747,10 @@ function DashAdapter() {
return null;
}

function getIsTypeOf(adaptation, type) {
return dashManifestModel.getIsTypeOf(adaptation, type);
}

function reset() {
voPeriods = [];
voAdaptations = {};
Expand Down Expand Up @@ -940,6 +966,8 @@ function DashAdapter() {
getAllMediaInfoForType: getAllMediaInfoForType,
getAdaptationForType: getAdaptationForType,
getRealAdaptation: getRealAdaptation,
getRealPeriodByIndex,
getEssentialPropertiesForRepresentation,
getVoRepresentations: getVoRepresentations,
getEventsFor: getEventsFor,
getEvent: getEvent,
Expand All @@ -950,6 +978,7 @@ function DashAdapter() {
getUTCTimingSources: getUTCTimingSources,
getSuggestedPresentationDelay: getSuggestedPresentationDelay,
getAvailabilityStartTime: getAvailabilityStartTime,
getIsTypeOf,
getIsDynamic: getIsDynamic,
getDuration: getDuration,
getRegularPeriods: getRegularPeriods,
Expand Down
16 changes: 16 additions & 0 deletions src/dash/models/DashManifestModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,19 @@ function DashManifestModel() {
return manifest && manifest.Period_asArray && isInteger(periodIndex) ? manifest.Period_asArray[periodIndex] ? manifest.Period_asArray[periodIndex].AdaptationSet_asArray : [] : [];
}

function getRealPeriods(manifest) {
return manifest && manifest.Period_asArray ? manifest.Period_asArray : [];
}

function getRealPeriodForIndex(index, manifest) {
const realPeriods = getRealPeriods(manifest);
if (realPeriods.length > 0 && isInteger(index)) {
return realPeriods[index];
} else {
return null;
}
}

function getAdaptationForId(id, manifest, periodIndex) {
const realAdaptations = getRealAdaptations(manifest, periodIndex);
let i,
Expand Down Expand Up @@ -1114,6 +1127,8 @@ function DashManifestModel() {
getIndexForAdaptation: getIndexForAdaptation,
getAdaptationForId: getAdaptationForId,
getAdaptationsForType: getAdaptationsForType,
getRealPeriods,
getRealPeriodForIndex,
getCodec: getCodec,
getMimeType: getMimeType,
getKID: getKID,
Expand All @@ -1132,6 +1147,7 @@ function DashManifestModel() {
getRegularPeriods: getRegularPeriods,
getMpd: getMpd,
getEventsForPeriod: getEventsForPeriod,
getEssentialPropertiesForRepresentation,
getEventStreamForAdaptationSet: getEventStreamForAdaptationSet,
getEventStreamForRepresentation: getEventStreamForRepresentation,
getUTCTimingSources: getUTCTimingSources,
Expand Down
54 changes: 36 additions & 18 deletions src/streaming/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import BaseURLController from './controllers/BaseURLController';
import ManifestLoader from './ManifestLoader';
import ErrorHandler from './utils/ErrorHandler';
import Capabilities from './utils/Capabilities';
import CapabilitiesFilter from './utils/CapabilitiesFilter';
import TextTracks from './text/TextTracks';
import RequestModifier from './utils/RequestModifier';
import TextController from './text/TextController';
Expand All @@ -61,7 +62,7 @@ import Settings from '../core/Settings';
import {
getVersionString
}
from './../core/Version';
from './../core/Version';

//Dash
import SegmentBaseController from '../dash/controllers/SegmentBaseController';
Expand All @@ -74,7 +75,7 @@ import {
import BASE64 from '../../externals/base64';
import ISOBoxer from 'codem-isoboxer';
import DashJSError from './vo/DashJSError';
import { checkParameterType } from './utils/SupervisorTools';
import {checkParameterType} from './utils/SupervisorTools';
import ManifestUpdater from './ManifestUpdater';
import URLUtils from '../streaming/utils/URLUtils';
import BoxParser from './utils/BoxParser';
Expand All @@ -84,6 +85,7 @@ import BoxParser from './utils/BoxParser';
* The media types
* @typedef {("video" | "audio" | "text" | "fragmentedText" | "embeddedText" | "image")} MediaType
*/

/* jscs:enable */

/**
Expand All @@ -94,35 +96,35 @@ import BoxParser from './utils/BoxParser';
*/
function MediaPlayer() {
/**
* @constant {string} STREAMING_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized
* @inner
*/
* @constant {string} STREAMING_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized
* @inner
*/
const STREAMING_NOT_INITIALIZED_ERROR = 'You must first call initialize() and set a source before calling this method';
/**
* @constant {string} PLAYBACK_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized
* @inner
*/
* @constant {string} PLAYBACK_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized
* @inner
*/
const PLAYBACK_NOT_INITIALIZED_ERROR = 'You must first call initialize() and set a valid source and view before calling this method';
/**
* @constant {string} ELEMENT_NOT_ATTACHED_ERROR error string thrown when a function is called before the dash.js has received a reference of an HTML5 video element
* @inner
*/
* @constant {string} ELEMENT_NOT_ATTACHED_ERROR error string thrown when a function is called before the dash.js has received a reference of an HTML5 video element
* @inner
*/
const ELEMENT_NOT_ATTACHED_ERROR = 'You must first call attachView() to set the video element before calling this method';
/**
* @constant {string} SOURCE_NOT_ATTACHED_ERROR error string thrown when a function is called before the dash.js has received a valid source stream.
* @inner
*/
* @constant {string} SOURCE_NOT_ATTACHED_ERROR error string thrown when a function is called before the dash.js has received a valid source stream.
* @inner
*/
const SOURCE_NOT_ATTACHED_ERROR = 'You must first call attachSource() with a valid source before calling this method';
/**
* @constant {string} MEDIA_PLAYER_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized.
* @inner
*/
* @constant {string} MEDIA_PLAYER_NOT_INITIALIZED_ERROR error string thrown when a function is called before the dash.js has been fully initialized.
* @inner
*/
const MEDIA_PLAYER_NOT_INITIALIZED_ERROR = 'MediaPlayer not initialized!';

const context = this.context;
const eventBus = EventBus(context).getInstance();
let settings = Settings(context).getInstance();
const debug = Debug(context).getInstance({settings: settings});
const debug = Debug(context).getInstance({ settings: settings });

let instance,
logger,
Expand All @@ -145,6 +147,7 @@ function MediaPlayer() {
errHandler,
baseURLController,
capabilities,
capabilitiesFilter,
streamController,
gapController,
playbackController,
Expand Down Expand Up @@ -195,6 +198,9 @@ function MediaPlayer() {
if (config.capabilities) {
capabilities = config.capabilities;
}
if (config.capabilitiesFilter) {
capabilitiesFilter = config.capabilitiesFilter;
}
if (config.streamController) {
streamController = config.streamController;
}
Expand Down Expand Up @@ -242,6 +248,7 @@ function MediaPlayer() {
if (!capabilities) {
capabilities = Capabilities(context).getInstance();
}

errHandler = ErrorHandler(context).getInstance();

if (!capabilities.supportsMediaSource()) {
Expand Down Expand Up @@ -281,6 +288,10 @@ function MediaPlayer() {
gapController = GapController(context).getInstance();
}

if (!capabilitiesFilter) {
capabilitiesFilter = CapabilitiesFilter(context).getInstance();
}

adapter = DashAdapter(context).getInstance();

manifestModel = ManifestModel(context).getInstance();
Expand Down Expand Up @@ -1959,8 +1970,15 @@ function MediaPlayer() {
streamController = StreamController(context).getInstance();
}

capabilitiesFilter.setConfig({
capabilities,
adapter,
settings
});

streamController.setConfig({
capabilities: capabilities,
capabilitiesFilter,
manifestLoader: manifestLoader,
manifestModel: manifestModel,
mediaPlayerModel: mediaPlayerModel,
Expand Down
59 changes: 5 additions & 54 deletions src/streaming/Stream.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function Stream(config) {
const manifestUpdater = config.manifestUpdater;
const adapter = config.adapter;
const capabilities = config.capabilities;
const capabilitiesFilter = config.capabilitiesFilter;
const errHandler = config.errHandler;
const timelineConverter = config.timelineConverter;
const dashMetrics = config.dashMetrics;
Expand Down Expand Up @@ -86,17 +87,6 @@ function Stream(config) {
isEndedEventSignaled,
trackChangedEvent;

const codecCompatibilityTable = [
{
'codec': 'avc1',
'compatibleCodecs': ['avc3']
},
{
'codec': 'avc3',
'compatibleCodecs': ['avc1']
}
];

function setup() {
debug = Debug(context).getInstance();
logger = debug.getLogger(instance);
Expand Down Expand Up @@ -549,8 +539,7 @@ function Stream(config) {

isUpdating = true;

filterCodecs(Constants.VIDEO);
filterCodecs(Constants.AUDIO);
capabilitiesFilter.filterUnsupportedFeaturesOfPeriod( streamInfo);

if (!element || (element && (/^VIDEO$/i).test(element.nodeName))) {
initializeMediaForType(Constants.VIDEO, mediaSource);
Expand Down Expand Up @@ -582,8 +571,7 @@ function Stream(config) {
function initializeAfterPreload() {
isUpdating = true;
checkConfig();
filterCodecs(Constants.VIDEO);
filterCodecs(Constants.AUDIO);
capabilitiesFilter.filterUnsupportedFeaturesOfPeriod(streamInfo);

isMediaInitialized = true;
isUpdating = false;
Expand All @@ -596,25 +584,6 @@ function Stream(config) {
}
}

function filterCodecs(type) {
const realAdaptation = adapter.getAdaptationForType(streamInfo ? streamInfo.index : null, type, streamInfo);

if (!realAdaptation || !Array.isArray(realAdaptation.Representation_asArray)) return;

// Filter codecs that are not supported
realAdaptation.Representation_asArray = realAdaptation.Representation_asArray.filter((_, i) => {
// keep at least codec from lowest representation
if (i === 0) return true;

const codec = adapter.getCodec(realAdaptation, i, true);
if (!capabilities.supportsCodec(codec)) {
logger.error('[Stream] codec not supported: ' + codec);
return false;
}
return true;
});
}

function checkIfInitializationCompleted() {
const ln = streamProcessors.length;
const hasError = !!updateError.audio || !!updateError.video;
Expand Down Expand Up @@ -766,8 +735,7 @@ function Stream(config) {
addInlineEvents();
}

filterCodecs(Constants.VIDEO);
filterCodecs(Constants.AUDIO);
capabilitiesFilter.filterUnsupportedFeaturesOfPeriod(streamInfo);

for (let i = 0, ln = streamProcessors.length; i < ln; i++) {
let streamProcessor = streamProcessors[i];
Expand Down Expand Up @@ -868,27 +836,10 @@ function Stream(config) {
return oldCodecs.indexOf(newCodec) > -1;
});

const partialCodecMatch = newCodecs.some((newCodec) => oldCodecs.some((oldCodec) => codecRootCompatibleWithCodec(oldCodec, newCodec)));
const partialCodecMatch = newCodecs.some((newCodec) => oldCodecs.some((oldCodec) => capabilities.codecRootCompatibleWithCodec(oldCodec, newCodec)));
return codecMatch || (partialCodecMatch && sameMimeType);
}

// Check if the root of the old codec is the same as the new one, or if it's declared as compatible in the compat table
function codecRootCompatibleWithCodec(codec1, codec2) {
const codecRoot = codec1.split('.')[0];
const rootCompatible = codec2.indexOf(codecRoot) === 0;
let compatTableCodec;
for (let i = 0; i < codecCompatibilityTable.length; i++) {
if (codecCompatibilityTable[i].codec === codecRoot) {
compatTableCodec = codecCompatibilityTable[i];
break;
}
}
if (compatTableCodec) {
return rootCompatible || compatTableCodec.compatibleCodecs.some((compatibleCodec) => codec2.indexOf(compatibleCodec) === 0);
}
return rootCompatible;
}

function setPreloaded(value) {
preloaded = value;
}
Expand Down
Loading

0 comments on commit 0fc5db8

Please sign in to comment.