Skip to content

Commit

Permalink
MediaInfo: introduce DescriptorType (#4158)
Browse files Browse the repository at this point in the history
* resolve rebase conflicts

* add secure init function for descriptorType

* make use of new DescriptroType class

* fix issue with accessibility

* change getter to array.map functions

* issue #4123: propagate supplProp from Repr to AdaptSet if all equal

* take new DescriptorType into account

* make supplementalProp avail as DescriptorType

* resolve reabse conflicts

* fix TS

* resolve rebase conflicts

* implement first new unit tests

* enable mocha grep with karma runner

* encapsulate mediaInfo test

* a few more tests on Mediainfo from DashAdapter

* add test for DashAdapter.AreMediaInfosEqual

* bugfix comparing content of object

* test supplementalProp on Representations

* added test for viewpoint descriptor

* adding test for trackEquality with DescriptorType

* enhance trackEquality to use DescriptorType

* adding unit tests for DashManifestModel

* coding style: use single space, no alignment

* use strong equality

* use () with constructur invokation

* use plural + camel case

* use DashConstants

* fix new names
  • Loading branch information
stschr committed Apr 24, 2023
1 parent c8ba161 commit 2643952
Show file tree
Hide file tree
Showing 12 changed files with 686 additions and 54 deletions.
20 changes: 15 additions & 5 deletions index.d.ts
Expand Up @@ -145,15 +145,15 @@ declare namespace dashjs {

getLanguageForAdaptation(adaptation: object): string;

getViewpointForAdaptation(adaptation: object): any;
getViewpointForAdaptation(adaptation: object): DescriptorType[];

getRolesForAdaptation(adaptation: object): any[];
getRolesForAdaptation(adaptation: object): DescriptorType[];

getAccessibilityForAdaotation(adaptation: object): any[];
getAccessibilityForAdaptation(adaptation: object): DescriptorType[];

getAudioChannelConfigurationForAdaptation(adaptation: object): any[];
getAudioChannelConfigurationForAdaptation(adaptation: object): DescriptorType[];

getAudioChannelConfigurationForRepresentation(adaptation: object): any[];
getAudioChannelConfigurationForRepresentation(adaptation: object): DescriptorType[];

getRepresentationSortFunction(): (a: object, b: object) => number;

Expand Down Expand Up @@ -426,9 +426,13 @@ declare namespace dashjs {
labels: { text: string, lang?: string }[];
lang: string | null;
viewpoint: any | undefined | null;
viewpointsWithSchemeIdUri: DescriptorType[] | null;
accessibility: any[] | null;
accessibilitiesWithSchemeIdUri: DescriptorType[] | null;
audioChannelConfiguration: any[] | null;
audioChannelConfigurationsWithSchemeIdUri: DescriptorType[] | null;
roles: string[] | null;
rolesWithSchemeIdUri: DescriptorType[] | null;
codec: string | null;
mimeType: string | null;
contentProtection: any | null;
Expand Down Expand Up @@ -545,6 +549,12 @@ declare namespace dashjs {
value: string;
}

export class DescriptorType {
schemeIdUri: string;
value: string;
id: string;
}

/**
* Dash
**/
Expand Down
3 changes: 2 additions & 1 deletion karma.unit.conf.js
Expand Up @@ -35,7 +35,8 @@ module.exports = function (config) {
client: {
useIframe: false,
mocha: {
timeout: 90000
timeout: 90000,
grep: config.grep
}
},

Expand Down
57 changes: 47 additions & 10 deletions src/dash/DashAdapter.js
Expand Up @@ -202,12 +202,16 @@ function DashAdapter() {
const sameId = mInfoOne.id === mInfoTwo.id;
const sameCodec = mInfoOne.codec === mInfoTwo.codec;
const sameViewpoint = mInfoOne.viewpoint === mInfoTwo.viewpoint;
const sameViewpointWithSchemeIdUri = JSON.stringify(mInfoOne.viewpointsWithSchemeIdUri) === JSON.stringify(mInfoTwo.viewpointsWithSchemeIdUri);
const sameLang = mInfoOne.lang === mInfoTwo.lang;
const sameRoles = mInfoOne.roles.toString() === mInfoTwo.roles.toString();
const sameRolesWithSchemeIdUri = JSON.stringify(mInfoOne.rolesWithSchemeIdUri) === JSON.stringify(mInfoTwo.rolesWithSchemeIdUri);
const sameAccessibility = mInfoOne.accessibility.toString() === mInfoTwo.accessibility.toString();
const sameAccessibilityWithSchemeIdUri = JSON.stringify(mInfoOne.accessibilitiesWithSchemeIdUri) === JSON.stringify(mInfoTwo.accessibilitiesWithSchemeIdUri);
const sameAudioChannelConfiguration = mInfoOne.audioChannelConfiguration.toString() === mInfoTwo.audioChannelConfiguration.toString();
const sameAudioChannelConfigurationWithSchemeIdUri = JSON.stringify(mInfoOne.audioChannelConfigurationsWithSchemeIdUri) === JSON.stringify(mInfoTwo.audioChannelConfigurationsWithSchemeIdUri);

return (sameId && sameCodec && sameViewpoint && sameLang && sameRoles && sameAccessibility && sameAudioChannelConfiguration);
return (sameId && sameCodec && sameViewpoint && sameViewpointWithSchemeIdUri && sameLang && sameRoles && sameRolesWithSchemeIdUri && sameAccessibility && sameAccessibilityWithSchemeIdUri && sameAudioChannelConfiguration && sameAudioChannelConfigurationWithSchemeIdUri);
}

function _getAllMediaInfo(manifest, period, streamInfo, adaptations, type, embeddedText) {
Expand Down Expand Up @@ -1035,7 +1039,7 @@ function DashAdapter() {

let mediaInfo = new MediaInfo();
const realAdaptation = adaptation.period.mpd.manifest.Period_asArray[adaptation.period.index].AdaptationSet_asArray[adaptation.index];
let viewpoint;
let viewpoint, acc, acc_rep, roles, accessibility;

mediaInfo.id = adaptation.id;
mediaInfo.index = adaptation.index;
Expand All @@ -1044,11 +1048,15 @@ function DashAdapter() {
mediaInfo.representationCount = dashManifestModel.getRepresentationCount(realAdaptation);
mediaInfo.labels = dashManifestModel.getLabelsForAdaptation(realAdaptation);
mediaInfo.lang = dashManifestModel.getLanguageForAdaptation(realAdaptation);
viewpoint = dashManifestModel.getViewpointForAdaptation(realAdaptation);
mediaInfo.viewpoint = viewpoint ? viewpoint.value : undefined;
mediaInfo.segmentAlignment = dashManifestModel.getSegmentAlignment(realAdaptation);
mediaInfo.subSegmentAlignment = dashManifestModel.getSubSegmentAlignment(realAdaptation);
mediaInfo.accessibility = dashManifestModel.getAccessibilityForAdaptation(realAdaptation).map(function (accessibility) {

viewpoint = dashManifestModel.getViewpointForAdaptation(realAdaptation);
mediaInfo.viewpoint = viewpoint.length ? viewpoint[0].value : undefined;
mediaInfo.viewpointsWithSchemeIdUri = viewpoint;

accessibility = dashManifestModel.getAccessibilityForAdaptation(realAdaptation);
mediaInfo.accessibility = accessibility.map(function (accessibility) {
let accessibilityValue = accessibility.value;
let accessibilityData = accessibilityValue;
if (accessibility.schemeIdUri && (accessibility.schemeIdUri.search('cea-608') >= 0) && typeof (cea608parser) !== 'undefined') {
Expand All @@ -1061,19 +1069,28 @@ function DashAdapter() {
}
return accessibilityData;
});
mediaInfo.accessibilitiesWithSchemeIdUri = accessibility;

mediaInfo.audioChannelConfiguration = dashManifestModel.getAudioChannelConfigurationForAdaptation(realAdaptation).map(function (audioChannelConfiguration) {
acc = dashManifestModel.getAudioChannelConfigurationForAdaptation(realAdaptation);
mediaInfo.audioChannelConfiguration = acc.map(function (audioChannelConfiguration) {
return audioChannelConfiguration.value;
});
mediaInfo.audioChannelConfigurationsWithSchemeIdUri = acc;

if (mediaInfo.audioChannelConfiguration.length === 0 && Array.isArray(realAdaptation.Representation_asArray) && realAdaptation.Representation_asArray.length > 0) {
mediaInfo.audioChannelConfiguration = dashManifestModel.getAudioChannelConfigurationForRepresentation(realAdaptation.Representation_asArray[0]).map(function (audioChannelConfiguration) {
acc_rep = dashManifestModel.getAudioChannelConfigurationForRepresentation(realAdaptation.Representation_asArray[0]);
mediaInfo.audioChannelConfiguration = acc_rep.map(function (audioChannelConfiguration) {
return audioChannelConfiguration.value;
});
mediaInfo.audioChannelConfigurationsWithSchemeIdUri = acc_rep;
}
mediaInfo.roles = dashManifestModel.getRolesForAdaptation(realAdaptation).map(function (role) {

roles = dashManifestModel.getRolesForAdaptation(realAdaptation);
mediaInfo.roles = roles.map(function (role) {
return role.value;
});
mediaInfo.rolesWithSchemeIdUri = roles;

mediaInfo.codec = dashManifestModel.getCodec(realAdaptation);
mediaInfo.mimeType = dashManifestModel.getMimeType(realAdaptation);
mediaInfo.contentProtection = dashManifestModel.getContentProtectionData(realAdaptation);
Expand All @@ -1092,8 +1109,27 @@ function DashAdapter() {
}

mediaInfo.isText = dashManifestModel.getIsText(realAdaptation);
mediaInfo.supplementalProperties = dashManifestModel.getSupplementalProperties(realAdaptation);

mediaInfo.supplementalProperties = dashManifestModel.getSupplementalPropertiesForAdaptation(realAdaptation);
if ( (!mediaInfo.supplementalProperties || Object.keys(mediaInfo.supplementalProperties).length === 0) && Array.isArray(realAdaptation.Representation_asArray) && realAdaptation.Representation_asArray.length > 0) {
let arr = realAdaptation.Representation_asArray.map( repr => {
return dashManifestModel.getSupplementalPropertiesForRepresentation(repr);
});
if ( arr.every( v => JSON.stringify(v) === JSON.stringify(arr[0]) ) ) {
// only output Representation.supplementalProperties to mediaInfo, if they are present on all Representations
mediaInfo.supplementalProperties = arr[0];
}
}
mediaInfo.supplementalPropertiesAsArray = dashManifestModel.getSupplementalPropertiesAsArrayForAdaptation(realAdaptation);
if ( (!mediaInfo.supplementalPropertiesAsArray || mediaInfo.supplementalPropertiesAsArray.length === 0) && Array.isArray(realAdaptation.Representation_asArray) && realAdaptation.Representation_asArray.length > 0) {
let arr = realAdaptation.Representation_asArray.map( repr => {
return dashManifestModel.getSupplementalPropertiesAsArrayForRepresentation(repr);
});
if ( arr.every( v => JSON.stringify(v) === JSON.stringify(arr[0]) ) ) {
// only output Representation.supplementalProperties to mediaInfo, if they are present on all Representations
mediaInfo.supplementalPropertiesAsArray = arr[0];
}
}

mediaInfo.isFragmented = dashManifestModel.getIsFragmented(realAdaptation);
mediaInfo.isEmbedded = false;

Expand All @@ -1109,6 +1145,7 @@ function DashAdapter() {
mediaInfo.isFragmented = false;
mediaInfo.lang = bcp47Normalize(lang);
mediaInfo.roles = ['caption'];
mediaInfo.rolesWithSchemeIdUri = [{schemeIdUri:'urn:mpeg:dash:role:2011', value:'caption'}];
}

function convertVideoInfoToThumbnailInfo(mediaInfo) {
Expand Down
2 changes: 2 additions & 0 deletions src/dash/constants/DashConstants.js
Expand Up @@ -89,6 +89,7 @@ class DashConstants {
this.CONTENT_PROTECTION = 'ContentProtection';
this.ESSENTIAL_PROPERTY = 'EssentialProperty';
this.SUPPLEMENTAL_PROPERTY = 'SupplementalProperty';
this.SUPPLEMENTAL_PROPERTY_ASARRAY = 'SupplementalProperty_asArray';
this.INBAND_EVENT_STREAM = 'InbandEventStream';
this.PRODUCER_REFERENCE_TIME = 'ProducerReferenceTime';
this.INBAND = 'inband';
Expand All @@ -100,6 +101,7 @@ class DashConstants {
this.SUBSET = 'Subset';
this.LANG = 'lang';
this.VIEWPOINT = 'Viewpoint';
this.VIEWPOINT_ASARRAY = 'Viewpoint_asArray';
this.ROLE_ASARRAY = 'Role_asArray';
this.REPRESENTATION_ASARRAY = 'Representation_asArray';
this.PRODUCERREFERENCETIME_ASARRAY = 'ProducerReferenceTime_asArray';
Expand Down
73 changes: 63 additions & 10 deletions src/dash/models/DashManifestModel.js
Expand Up @@ -40,6 +40,7 @@ import BaseURL from '../vo/BaseURL';
import EventStream from '../vo/EventStream';
import ProducerReferenceTime from '../vo/ProducerReferenceTime';
import ContentSteering from '../vo/ContentSteering';
import DescriptorType from '../vo/DescriptorType';
import ObjectUtils from '../../streaming/utils/ObjectUtils';
import URLUtils from '../../streaming/utils/URLUtils';
import FactoryMaker from '../../core/FactoryMaker';
Expand Down Expand Up @@ -230,23 +231,43 @@ function DashManifestModel() {
}

function getViewpointForAdaptation(adaptation) {
return adaptation && adaptation.hasOwnProperty(DashConstants.VIEWPOINT) ? adaptation.Viewpoint : null;
if (!adaptation || !adaptation.hasOwnProperty(DashConstants.VIEWPOINT_ASARRAY) || !adaptation[DashConstants.VIEWPOINT_ASARRAY].length) return [];
return adaptation[DashConstants.VIEWPOINT_ASARRAY].map( viewpoint => {
const vp = new DescriptorType();
return vp.init(viewpoint);
});
}

function getRolesForAdaptation(adaptation) {
return adaptation && adaptation.hasOwnProperty(DashConstants.ROLE_ASARRAY) ? adaptation.Role_asArray : [];
if (!adaptation || !adaptation.hasOwnProperty(DashConstants.ROLE_ASARRAY) || !adaptation[DashConstants.ROLE_ASARRAY].length) return [];
return adaptation[DashConstants.ROLE_ASARRAY].map( role => {
const r = new DescriptorType();
return r.init(role);
});
}

function getAccessibilityForAdaptation(adaptation) {
return adaptation && adaptation.hasOwnProperty(DashConstants.ACCESSIBILITY_ASARRAY) ? adaptation.Accessibility_asArray : [];
if (!adaptation || !adaptation.hasOwnProperty(DashConstants.ACCESSIBILITY_ASARRAY) || !adaptation[DashConstants.ACCESSIBILITY_ASARRAY].length) return [];
return adaptation[DashConstants.ACCESSIBILITY_ASARRAY].map( accessibility => {
const a = new DescriptorType();
return a.init(accessibility);
});
}

function getAudioChannelConfigurationForAdaptation(adaptation) {
return adaptation && adaptation.hasOwnProperty(DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY) ? adaptation.AudioChannelConfiguration_asArray : [];
if (!adaptation || !adaptation.hasOwnProperty(DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY) || !adaptation[DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY].length) return [];
return adaptation[DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY].map( audioChanCfg => {
const acc = new DescriptorType();
return acc.init(audioChanCfg);
});
}

function getAudioChannelConfigurationForRepresentation(representation) {
return representation && representation.hasOwnProperty(DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY) ? representation.AudioChannelConfiguration_asArray : [];
if (!representation || !representation.hasOwnProperty(DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY) || !representation[DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY].length) return [];
return representation[DashConstants.AUDIOCHANNELCONFIGURATION_ASARRAY].map( audioChanCfg => {
const acc = new DescriptorType();
return acc.init(audioChanCfg);
});
}

function getRepresentationSortFunction() {
Expand Down Expand Up @@ -1263,10 +1284,10 @@ function DashManifestModel() {
return serviceDescriptions;
}

function getSupplementalProperties(adaptation) {
function getSupplementalPropertiesForAdaptation(adaptation) {
const supplementalProperties = {};

if (adaptation && adaptation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY)) {
if (adaptation && adaptation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY_ASARRAY)) {
for (const sp of adaptation.SupplementalProperty_asArray) {
if (sp.hasOwnProperty(Constants.SCHEME_ID_URI) && sp.hasOwnProperty(DashConstants.VALUE)) {
supplementalProperties[sp[Constants.SCHEME_ID_URI]] = sp[DashConstants.VALUE];
Expand All @@ -1276,6 +1297,35 @@ function DashManifestModel() {
return supplementalProperties;
}

function getSupplementalPropertiesAsArrayForAdaptation(adaptation) {
if (!adaptation || !adaptation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY_ASARRAY) || !adaptation.SupplementalProperty_asArray.length) return [];
return adaptation.SupplementalProperty_asArray.map( supp => {
const s = new DescriptorType();
return s.init(supp);
});
}

function getSupplementalPropertiesForRepresentation(representation) {
const supplementalProperties = {};

if (representation && representation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY_ASARRAY)) {
for (const sp of representation.SupplementalProperty_asArray) {
if (sp.hasOwnProperty(Constants.SCHEME_ID_URI) && sp.hasOwnProperty(DashConstants.VALUE)) {
supplementalProperties[sp[Constants.SCHEME_ID_URI]] = sp[DashConstants.VALUE];
}
}
}
return supplementalProperties;
}

function getSupplementalPropertiesAsArrayForRepresentation(representation) {
if (!representation || !representation.hasOwnProperty(DashConstants.SUPPLEMENTAL_PROPERTY_ASARRAY) || !representation.SupplementalProperty_asArray.length) return [];
return representation.SupplementalProperty_asArray.map( supp => {
const s = new DescriptorType();
return s.init(supp);
});
}

function setConfig(config) {
if (!config) return;

Expand Down Expand Up @@ -1338,10 +1388,13 @@ function DashManifestModel() {
getSuggestedPresentationDelay,
getAvailabilityStartTime,
getServiceDescriptions,
getSupplementalProperties,
setConfig,
getSegmentAlignment,
getSubSegmentAlignment
getSubSegmentAlignment,
getSupplementalPropertiesForAdaptation,
getSupplementalPropertiesAsArrayForAdaptation,
getSupplementalPropertiesForRepresentation,
getSupplementalPropertiesAsArrayForRepresentation,
setConfig
};

setup();
Expand Down
52 changes: 52 additions & 0 deletions src/dash/vo/DescriptorType.js
@@ -0,0 +1,52 @@
/**
* 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) 2023, 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.
*/
/**
* @class
* @ignore
*/
class DescriptorType {
constructor() {
this.schemeIdUri = null;
this.value = null;
this.id = null;
}

init(data) {
if (data) {
this.schemeIdUri = data.schemeIdUri ? data.schemeIdUri : null;
this.value = data.value ? data.value : null;
this.id = data.id ? data.id : null;
}
return this;
}
}

export default DescriptorType;

0 comments on commit 2643952

Please sign in to comment.