From 6e930c370ed7cccaae476cc200acfe76284ded95 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Tue, 10 Jun 2025 11:02:35 +0000 Subject: [PATCH 01/14] add support for Preselection element parsing and codecs string replacement --- contrib/akamai/controlbar/ControlBar.js | 9 +- src/core/Settings.js | 10 ++ src/dash/DashAdapter.js | 122 ++++++++++++++++ src/dash/constants/DashConstants.js | 4 + src/dash/models/DashManifestModel.js | 143 +++++++++++++++++++ src/dash/parser/DashParser.js | 1 + src/dash/vo/MediaInfo.js | 2 + src/dash/vo/Preselection.js | 46 ++++++ src/streaming/controllers/MediaController.js | 64 +++++---- src/streaming/utils/CapabilitiesFilter.js | 112 +++++++++++++-- test/unit/test/dash/dash.DashAdapter.js | 37 +++++ 11 files changed, 505 insertions(+), 45 deletions(-) create mode 100644 src/dash/vo/Preselection.js diff --git a/contrib/akamai/controlbar/ControlBar.js b/contrib/akamai/controlbar/ControlBar.js index ace916c3d7..8330361681 100644 --- a/contrib/akamai/controlbar/ControlBar.js +++ b/contrib/akamai/controlbar/ControlBar.js @@ -577,7 +577,7 @@ var ControlBar = function (dashjsMediaPlayer, displayUTCTimeCodes) { var info = ''; if (element.lang) { - info += 'Language - ' + element.lang + ' '; + info += 'Language: ' + element.lang + ' '; } if (element.roles && element.roles.length > 0) { @@ -596,6 +596,10 @@ var ControlBar = function (dashjsMediaPlayer, displayUTCTimeCodes) { info += '- Id: ' + element.id + ' '; } + if (element.isPreselection) { + info += '- Preselection'; + } + return label || info }; trackSwitchMenu = createMenu(availableTracks, contentFunc); @@ -785,6 +789,9 @@ var ControlBar = function (dashjsMediaPlayer, displayUTCTimeCodes) { }; var isTracksEqual = function (t1, t2) { + if (!t1 && !t2) return true; + if (!t1 || !t2) return false; + if (t1.isPreselection !== t2.isPreselection) return false; var sameId = t1.id === t2.id; var sameViewpoint = t1.viewpoint === t2.viewpoint; var sameLang = t1.lang === t2.lang; diff --git a/src/core/Settings.js b/src/core/Settings.js index 583ee5c1d6..74ebe036d2 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -189,6 +189,8 @@ import Events from './events/Events.js'; * audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE, * video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE * }, + * includePreselectionsInMediainfo: true, + * includePreselectionsForInitialTrackSelection: false, * ignoreSelectionPriority: false, * prioritizeRoleMain: true, * assumeDefaultRoleAsMain: true, @@ -1007,6 +1009,12 @@ import Events from './events/Events.js'; * - Constants.TRACK_SWITCH_MODE_NEVER_REPLACE * Do not replace existing segments in the buffer * + * @property {} [includePreselectionsInMediainfo: true] + * provides the option to include Preselections in the MediaInfo object + * + * @property {} [includePreselectionsForInitialTrackSelection: false] + * provides the option to include Preselections for initial track selection + * * @property {} [ignoreSelectionPriority: false] * provides the option to disregard any signalled selectionPriority attribute. If disabled and if no initial media settings are set, track selection is accomplished as defined by selectionModeForInitialTrack. * @@ -1246,6 +1254,8 @@ function Settings() { audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE, video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE }, + includePreselectionsInMediainfo: true, + includePreselectionsForInitialTrackSelection: false, ignoreSelectionPriority: false, prioritizeRoleMain: true, assumeDefaultRoleAsMain: true, diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 16796cf4bc..79e66de43b 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -41,6 +41,7 @@ import Representation from './vo/Representation.js'; import {bcp47Normalize} from 'bcp-47-normalize'; import {getId3Frames} from '@svta/common-media-library/id3/getId3Frames.js'; import Constants from '../streaming/constants/Constants.js'; +import Settings from '../core/Settings.js'; /** * @module DashAdapter @@ -51,6 +52,7 @@ function DashAdapter() { let instance, dashManifestModel, patchManifestModel, + settings, voPeriods, constants, cea608parser; @@ -62,6 +64,7 @@ function DashAdapter() { function setup() { dashManifestModel = DashManifestModel(context).getInstance(); patchManifestModel = PatchManifestModel(context).getInstance(); + settings = Settings(context).getInstance(); reset(); } @@ -107,6 +110,12 @@ function DashAdapter() { } const voAdaptations = dashManifestModel.getAdaptationsForPeriod(selectedVoPeriod); + + // the following line seems not to be needed since this function is only used + // - for Thumbnail-tracks + // - to determine timelines (based on one example Representation) + // For both Preselections are not relevant. + // const voPreselections = dashManifestModel.getPreselectionsForPeriod(selectedVoPeriod); let realAdaptation = getMainAdaptationForType(type, streamInfo); if (!realAdaptation) { @@ -247,6 +256,21 @@ function DashAdapter() { } } + if (settings.get().streaming.includePreselectionsInMediainfo) { + const voPreselections = dashManifestModel.getPreselectionsForPeriod(period); + + for (i = 0, ln = voPreselections.length; i < ln; i++) { + const prsl = voPreselections[i]; + + if (prsl.hasOwnProperty('type') && prsl.type === type) { + media = convertPreselectionToMediaInfo(voPreselections[i]); + if (media) { + mediaArr.push(media); + } + } + } + } + return mediaArr; } @@ -410,6 +434,19 @@ function DashAdapter() { } catch (e) { return []; } + } + + /** + * Return all EssentialProperties of a Preselection + * @param {object} preselection + * @return {array} + */ + function getEssentialPropertiesForPreselection(preselection) { + try { + return dashManifestModel.getEssentialPropertiesForRepresentation(preselection); + } catch (e) { + return []; + } } /** @@ -763,6 +800,18 @@ function DashAdapter() { return dashManifestModel.getCodec(adaptation, representationIndex, addResolutionInfo); } + /** + * Returns the codec for a given adaptation set and a given representation id. + * @param {object} preselection + * @param {object} adaptations + * @returns {String} codec + * @memberOf module:DashAdapter + * @instance + */ + function getCodecForPreselection(preselection, adaptations) { + return dashManifestModel.getCodecForPreselection(preselection, adaptations); + } + /** * Returns the framerate of a Representation as number * @param representation @@ -841,6 +890,43 @@ function DashAdapter() { voPeriods = []; } + /** + * Checks if the given Preselection is from the given media type + * @param {object} preselection + * @param {object} adaptation + * @param {string} type + * @return {boolean} + * @memberOf module:DashAdapter + * @instance + */ + function getPreselectionIsTypeOf(preselection, adaptation, type) { + return dashManifestModel.getPreselectionIsTypeOf(preselection, adaptation, type); + } + + /** + * returns the main AdaptationSet of a given Preselection + * @param {object} preselection + * @param {object} adaptation + * @return {object} + * @memberOf module:DashAdapter + * @instance + */ + function getMainAdaptationSetForPreselection(prsl, as) { + return dashManifestModel.getMainAdaptationSetForPreselection(prsl, as); + } + + /** + * returns common properties in form of a Representation of a given Preselection + * @param {object} preselection + * @param {object} adaptation + * @return {object} + * @memberOf module:DashAdapter + * @instance + */ + function getCommonRepresentationForPreselection(prsl, as) { + return dashManifestModel.getCommonRepresentationForPreselection(prsl, as); + } + /** * Checks if the supplied manifest is compatible for application of the supplied patch * @param {object} manifest @@ -1056,6 +1142,37 @@ function DashAdapter() { return mediaInfo; } + function convertPreselectionToMediaInfo(prsl) { + if (!prsl) { + return null; + } + + let mediaInfo = new MediaInfo(); + const realPreselection = prsl.period.mpd.manifest.Period[prsl.period.index].Preselection[prsl.index]; + const realAdaptation = prsl.period.mpd.manifest.Period[prsl.period.index].AdaptationSet; + + // initialize mediaInfo with properties from main adaptation set + const as = dashManifestModel.getAdaptationsForPeriod(prsl.period); + const mainAdaptation = dashManifestModel.getMainAdaptationSetForPreselection(realPreselection, as); + mediaInfo = convertAdaptationToMediaInfo(mainAdaptation); + + mediaInfo.isPreselection = true; + mediaInfo.tag = prsl.tag; + mediaInfo.id = prsl.id; + mediaInfo.codec = dashManifestModel.getCodecForPreselection(realPreselection, realAdaptation); + mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realPreselection); + mediaInfo.labels = dashManifestModel.getLabelsForAdaptation(realPreselection); + mediaInfo.lang = dashManifestModel.getLanguageForAdaptation(realPreselection); + mediaInfo.viewpoint = dashManifestModel.getViewpointForAdaptation(realPreselection); + mediaInfo.accessibility = dashManifestModel.getAccessibilityForAdaptation(realPreselection); + mediaInfo.audioChannelConfiguration = dashManifestModel.getAudioChannelConfigurationForAdaptation(realPreselection); + mediaInfo.roles = dashManifestModel.getRolesForAdaptation(realPreselection); + mediaInfo.essentialProperties = dashManifestModel.getCombinedEssentialPropertiesForAdaptationSet(realPreselection); + mediaInfo.supplementalProperties = dashManifestModel.getCombinedSupplementalPropertiesForAdaptationSet(realPreselection); + + return mediaInfo; + } + function _applyDefaultKeyId(contentProtection) { const keyIds = contentProtection.map(cp => cp.cencDefaultKid).filter(kid => kid !== null); if (keyIds.length) { @@ -1227,9 +1344,11 @@ function DashAdapter() { getBandwidthForRepresentation, getBaseURLsFromElement, getCodec, + getCodecForPreselection, getContentSteering, getDuration, getEssentialPropertiesForAdaptationSet, + getEssentialPropertiesForPreselection, getEssentialPropertiesForRepresentation, getEvent, getEventsFor, @@ -1242,11 +1361,14 @@ function DashAdapter() { getIsTypeOf, getLocation, getMainAdaptationForType, + getMainAdaptationSetForPreselection, + getCommonRepresentationForPreselection, getManifestUpdatePeriod, getMediaInfoForType, getMpd, getPatchLocation, getPeriodById, + getPreselectionIsTypeOf, getProducerReferenceTimes, getPublishTime, getRealAdaptation, diff --git a/src/dash/constants/DashConstants.js b/src/dash/constants/DashConstants.js index e071c243a5..eb71534267 100644 --- a/src/dash/constants/DashConstants.js +++ b/src/dash/constants/DashConstants.js @@ -123,10 +123,13 @@ export default { MPD: 'MPD', MPD_TYPE: 'mpd', MPD_PATCH_TYPE: 'mpdpatch', + ORDER: 'order', ORIGINAL_MPD_ID: 'mpdId', ORIGINAL_PUBLISH_TIME: 'originalPublishTime', PATCH_LOCATION: 'PatchLocation', PERIOD: 'Period', + PRESELECTION: 'Preselection', + PRESELECTION_COMPONENTS: 'preselectionComponents', PRESENTATION_TIME: 'presentationTime', PRESENTATION_TIME_OFFSET: 'presentationTimeOffset', PRO: 'pro', @@ -185,6 +188,7 @@ export default { SUGGESTED_PRESENTATION_DELAY: 'suggestedPresentationDelay', SUPPLEMENTAL_PROPERTY: 'SupplementalProperty', SUPPLEMENTAL_CODECS: 'scte214:supplementalCodecs', + TAG: 'tag', TIMESCALE: 'timescale', TIMESHIFT_BUFFER_DEPTH: 'timeShiftBufferDepth', TTL: 'ttl', diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 2e016310d2..008e412339 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -48,6 +48,7 @@ import MpdLocation from '../vo/MpdLocation.js'; import ObjectUtils from '../../streaming/utils/ObjectUtils.js'; import PatchLocation from '../vo/PatchLocation.js'; import Period from '../vo/Period.js'; +import Preselection from '../vo/Preselection.js'; import ProducerReferenceTime from '../vo/ProducerReferenceTime.js'; import Representation from '../vo/Representation.js'; import URLUtils from '../../streaming/utils/URLUtils.js'; @@ -128,6 +129,25 @@ function DashManifestModel() { return false; } + function getPreselectionIsTypeOf(preselection, adaptations, type) { + if (!preselection) { + throw new Error('preselection is not defined'); + } + + if (!adaptations) { + throw new Error('adaptations is not defined'); + } + + if (!type) { + throw new Error('type is not defined'); + } + + const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); + + let isType = mainAdaptationSet ? getIsTypeOf(mainAdaptationSet, type) : false; + return isType; + } + function getIsFragmented(adaptation) { if (!adaptation) { throw new Error('adaptation is not defined'); @@ -162,6 +182,11 @@ function DashManifestModel() { return getIsTypeOf(adaptation, Constants.TEXT); } + function getIsTextForPreselection(prsl, adaptations) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); + return getIsText(mainAdaptationSet); + } + function getIsMuxed(adaptation) { return getIsTypeOf(adaptation, Constants.MUXED); } @@ -401,10 +426,45 @@ function DashManifestModel() { return codec; } + function getCodecForPreselection(prsl, adaptations, addResolutionInfo) { + let codec = null; + + if (prsl && adaptations ) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); + let mainASCodec = getCodec(mainAdaptationSet,0, addResolutionInfo); + // we just take the subparameters from the first Representation of the main AdaptationSet + + if (prsl.hasOwnProperty(DashConstants.CODECS)) { + let sCodecs = prsl.codecs; + + // Since Preselection elements don't get the @mimeType attribute assigned, this + // copies the media type from the main adaptationSet but takes the codec from + // the preselection. + codec = mainASCodec.replace(/(codecs=")[^"]*(")/, `$1${sCodecs}$2`); + logger.info('Preselection has own codecs-attribute: replacing in MainAdaptationSet (was: ' + mainASCodec + '), new Preselection codec is: ' + codec) + } else { + codec = mainASCodec; + } + } + + return codec; + } + function getMimeType(adaptation) { return adaptation && adaptation.Representation && adaptation.Representation.length > 0 ? adaptation.Representation[0].mimeType : null; } + function getMimeTypeForPreselection(prsl, adaptations) { + let mime = null; + + if (prsl && adaptations ) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); + mime = getMimeType(mainAdaptationSet); + } + + return mime; + } + function getSegmentAlignment(adaptation) { if (adaptation && adaptation.hasOwnProperty(DashConstants.SEGMENT_ALIGNMENT)) { return adaptation[DashConstants.SEGMENT_ALIGNMENT] === 'true' @@ -922,6 +982,82 @@ function DashManifestModel() { return voAdaptations; } + function _getAllAdaptationSetsForPreselection(preselection, adaptations) { + const prslComponentIds = String(preselection.preselectionComponents).split(' '); + return prslComponentIds.map(c => adaptations.find(as => as.id === c)); + } + + function getMainAdaptationSetForPreselection(preselection, adaptations) { + const prslComponentIds = String(preselection.preselectionComponents).split(' '); + return adaptations.find(as => as.id === prslComponentIds[0]); + } + + function getCommonRepresentationForPreselection(preselection, adaptations) { + const mainAS = getMainAdaptationSetForPreselection(preselection, adaptations); + return mainAS.Representation[0]; + } + + function getPreselectionsForPeriod(voPeriod) { + const realPeriod = voPeriod && isInteger(voPeriod.index) ? voPeriod.mpd.manifest.Period[voPeriod.index] : null; + const voPreselections = []; + let voPreselection, + realPreselection, + i; + + if (realPeriod && realPeriod.Preselection) { + for (i = 0; i < realPeriod.Preselection.length; i++) { + realPreselection = realPeriod.Preselection[i]; + voPreselection = new Preselection(); + + const prslAdaptationSets = _getAllAdaptationSetsForPreselection(realPreselection, realPeriod.AdaptationSet); + let allComponentsAvailable = prslAdaptationSets.every(as => !!as); + + // up to now, we only support single-representation preselections, not multi-representation + if (allComponentsAvailable && prslAdaptationSets.length === 1) { + if (realPreselection.hasOwnProperty(DashConstants.ID)) { + voPreselection.id = realPreselection.id; + } + voPreselection.index = i; + voPreselection.period = voPeriod; + + if (realPreselection.hasOwnProperty(DashConstants.TAG)) { + voPreselection.tag = realPreselection.tag; + } + + if (realPreselection.hasOwnProperty(DashConstants.PRESELECTION_COMPONENTS)) { + voPreselection.preselectionComponents = prslAdaptationSets; + } else { + logger.warn('Preselection (index: ' + voPreselection.index + ') missing component(s)'); + } + + if (realPreselection.hasOwnProperty(DashConstants.ORDER)) { + voPreselection.order = realPreselection.order; + } + + if (getIsMuxed(prslAdaptationSets[0])) { + voPreselection.type = Constants.MUXED; + } else if (getIsAudio(prslAdaptationSets[0])) { + voPreselection.type = Constants.AUDIO; + } else if (getIsVideo(prslAdaptationSets[0])) { + voPreselection.type = Constants.VIDEO; + } else if (getIsText(prslAdaptationSets[0])) { + voPreselection.type = Constants.TEXT; + } else if (getIsImage(prslAdaptationSets[0])) { + voPreselection.type = Constants.IMAGE; + } else { + logger.warn('Unknown Preselection stream type'); + } + + voPreselections.push(voPreselection); + } else { + logger.warn('Preselection removed because we don\'t support multi-representation preselections or not all components available.'); + } + } + } + + return voPreselections; + } + function getRegularPeriods(mpd) { const isDynamic = mpd ? getIsDynamic(mpd.manifest) : false; const voPeriods = []; @@ -1530,6 +1666,7 @@ function DashManifestModel() { getBaseURLsFromElement, getBitrateListForAdaptation, getCodec, + getCodecForPreselection, getCombinedEssentialPropertiesForAdaptationSet, getCombinedSupplementalPropertiesForAdaptationSet, getContentProtectionByAdaptation, @@ -1548,14 +1685,20 @@ function DashManifestModel() { getIsDynamic, getIsFragmented, getIsText, + getIsTextForPreselection, getIsTypeOf, getLabelsForAdaptation, getLanguageForAdaptation, getLocation, + getMainAdaptationSetForPreselection, + getCommonRepresentationForPreselection, getManifestUpdatePeriod, getMimeType, + getMimeTypeForPreselection, getMpd, getPatchLocation, + getPreselectionIsTypeOf, + getPreselectionsForPeriod, getProducerReferenceTimesForAdaptation, getPublishTime, getRealPeriodForIndex, diff --git a/src/dash/parser/DashParser.js b/src/dash/parser/DashParser.js index 01183b9478..ba0c7d2b6b 100644 --- a/src/dash/parser/DashParser.js +++ b/src/dash/parser/DashParser.js @@ -62,6 +62,7 @@ const arrayNodes = [ DashConstants.METRICS, DashConstants.REPORTING, DashConstants.PATCH_LOCATION, + DashConstants.PRESELECTION, DashConstants.REPLACE, DashConstants.ADD, DashConstants.REMOVE, diff --git a/src/dash/vo/MediaInfo.js b/src/dash/vo/MediaInfo.js index 924a7b5c0a..d2fd8ff8e4 100644 --- a/src/dash/vo/MediaInfo.js +++ b/src/dash/vo/MediaInfo.js @@ -45,11 +45,13 @@ class MediaInfo { this.index = null; this.isEmbedded = null; this.isFragmented = null; + this.isPreselection = false; this.isText = false; this.labels = null; this.lang = null; this.mimeType = null; this.normalizedKeyIds = new Set(); + this.preselectionComponents = []; this.representationCount = 0; this.roles = null; this.segmentAlignment = false; diff --git a/src/dash/vo/Preselection.js b/src/dash/vo/Preselection.js new file mode 100644 index 0000000000..b01abc87d7 --- /dev/null +++ b/src/dash/vo/Preselection.js @@ -0,0 +1,46 @@ +/** + * 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) 2025, 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 Preselection { + constructor() { + this.period = null; + this.index = -1; + this.type = null; + this.tag = null; + this.order = 'undefined', + this.preselectionComponents = []; + } +} + +export default Preselection; \ No newline at end of file diff --git a/src/streaming/controllers/MediaController.js b/src/streaming/controllers/MediaController.js index ae2e8f84ee..a5ba6c70db 100644 --- a/src/streaming/controllers/MediaController.js +++ b/src/streaming/controllers/MediaController.js @@ -119,56 +119,56 @@ function MediaController() { * @memberof MediaController# */ function setInitialMediaSettingsForType(type, streamInfo) { - let settings = lastSelectedTracks[type] || getInitialSettings(type); + let localSettings = lastSelectedTracks[type] || getInitialSettings(type); const possibleTracks = getTracksFor(type, streamInfo.id); let filteredTracks = []; - if (!settings || Object.keys(settings).length === 0) { - settings = domStorage.getSavedMediaSettings(type); - if (settings) { + if (!localSettings || Object.keys(localSettings).length === 0) { + localSettings = domStorage.getSavedMediaSettings(type); + if (localSettings) { // If the settings are defined locally, do not take codec into account or it'll be too strict. // eg: An audio track should not be selected by codec but merely by lang. - delete settings.codec; + delete localSettings.codec; } - setInitialSettings(type, settings); + setInitialSettings(type, localSettings); } if (!possibleTracks || (possibleTracks.length === 0)) { return; } - if (settings) { + if (!settings.get().streaming.includePreselectionsForInitialTrackSelection) { + // Removing all preselections + filteredTracks = possibleTracks.filter(track => !track.isPreselection); + // Since each preselection always refers at least one AdaptationSet, + // filteredTracks.length will always be > 0 + } else { filteredTracks = Array.from(possibleTracks); + } + + if (localSettings) { logger.info('Filtering ' + filteredTracks.length + ' ' + type + ' tracks based on settings'); - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsId, settings) - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsLang, settings); - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsIndex, settings); - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsViewPoint, settings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsId, localSettings) + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsLang, localSettings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsIndex, localSettings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsViewPoint, localSettings); if (!(type === Constants.AUDIO && !!lastSelectedTracks[type])) { - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsRole, settings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsRole, localSettings); } - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsAccessibility, settings); - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsAudioChannelConfig, settings); - filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsCodec, settings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsAccessibility, localSettings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsAudioChannelConfig, localSettings); + filteredTracks = filterTracksBySettings(filteredTracks, matchSettingsCodec, localSettings); logger.info('Filtering ' + type + ' tracks ended, found ' + filteredTracks.length + ' matching track(s).'); + } + + // More than one possibility + if (filteredTracks.length > 1) { + setTrack(selectInitialTrack(type, filteredTracks)); } - - // We did not apply any filter. We can select from all possible tracks - if (filteredTracks.length === 0) { - setTrack(selectInitialTrack(type, possibleTracks)); - } - - // We have some tracks based on the filtering we did. + // Only one possibility use this one else { - // More than one possibility - if (filteredTracks.length > 1) { - setTrack(selectInitialTrack(type, filteredTracks)); - } - // Only one possibility use this one - else { - setTrack(filteredTracks[0]); - } + setTrack(filteredTracks[0]); } } @@ -361,6 +361,10 @@ function MediaController() { return false; } + if (t1.isPreselection !== t2.isPreselection) { + return false; + } + const sameId = t1.id === t2.id; const sameViewpoint = JSON.stringify(t1.viewpoint) === JSON.stringify(t2.viewpoint); const sameLang = t1.lang === t2.lang; diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index cb17093a72..306de6a198 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -74,6 +74,9 @@ function CapabilitiesFilter() { if (settings.get().streaming.capabilities.filterUnsupportedEssentialProperties) { _filterUnsupportedEssentialProperties(manifest); } + + _removeMultiRepresentationPreselections(manifest); + return _applyCustomFilters(manifest); }) .then(() => { @@ -95,6 +98,7 @@ function CapabilitiesFilter() { manifest.Period .forEach((period) => { _filterUnsupportedAdaptationSetsOfPeriod(period, type); + _filterUnsupportedPreselectionssOfPeriod(period, type); }) } @@ -119,6 +123,32 @@ function CapabilitiesFilter() { }) } + function _filterUnsupportedPreselectionssOfPeriod(period, type) { + if (!period || !period.Preselection || period.Preselection.length === 0) { + return; + } + + period.Preselection = period.Preselection.filter((prsl) => { + if (adapter.getPreselectionIsTypeOf(prsl, period.AdaptationSet, type)) { + const codec = adapter.getCodecForPreselection(prsl, period.AdaptationSet); + let isPrslCodecSupported = true; + if (codec) { + let repr = adapter.getCommonRepresentationForPreselection(prsl, period.AdaptationSet); + + isPrslCodecSupported = _isCodecSupported(type, repr, codec); + } + + if (!isPrslCodecSupported) { + logger.warn(`[CapabilitiesFilter] Preselection@codecs ${codec} not supported. Removing Preselection with ID ${prsl.id}`); + } + + return isPrslCodecSupported; + } else { + return true; + } + }) + } + function _filterUnsupportedRepresentationsOfAdaptation(as, type) { if (!as.Representation || as.Representation.length === 0) { return; @@ -157,8 +187,8 @@ function CapabilitiesFilter() { return isSupplementalCodecSupported } - function _isCodecSupported(type, rep, codec) { - const config = _createConfiguration(type, rep, codec); + function _isCodecSupported(type, rep, codec, prsl_rep) { + const config = _createConfiguration(type, rep, codec, prsl_rep); return capabilities.isCodecSupportedBasedOnTestedConfigurations(config, type); } @@ -185,13 +215,23 @@ function CapabilitiesFilter() { }); } }); + if (period.Preselection && period.Preselection.length) { + period.Preselection.forEach((prsl) => { + if (adapter.getPreselectionIsTypeOf(prsl, period.AdaptationSet, type)) { + const codec = adapter.getCodecForPreselection(prsl, period.AdaptationSet); + const prsl_rep = adapter.getCommonRepresentationForPreselection(prsl, period.AdaptationSet); + + _processCodecToCheck(type, prsl, codec, configurationsSet, configurations, prsl_rep); + } + }); + } }); return configurations; } - function _processCodecToCheck(type, rep, codec, configurationsSet, configurations) { - const config = _createConfiguration(type, rep, codec); + function _processCodecToCheck(type, rep, codec, configurationsSet, configurations, prsl_rep) { + const config = _createConfiguration(type, rep, codec, prsl_rep); const configString = JSON.stringify(config); if (!configurationsSet.has(configString)) { @@ -200,14 +240,14 @@ function CapabilitiesFilter() { } } - function _createConfiguration(type, rep, codec) { + function _createConfiguration(type, rep, codec, prsl_rep) { let config = null; switch (type) { case Constants.VIDEO: - config = _createVideoConfiguration(rep, codec); + config = _createVideoConfiguration(rep, codec, prsl_rep); break; case Constants.AUDIO: - config = _createAudioConfiguration(rep, codec); + config = _createAudioConfiguration(rep, codec, prsl_rep); break; default: return config; @@ -216,15 +256,22 @@ function CapabilitiesFilter() { return _addGenericAttributesToConfig(rep, config); } - function _createVideoConfiguration(rep, codec) { + function _createVideoConfiguration(rep, codec, prsl_rep) { let config = { codec: codec, - width: rep.width || null, - height: rep.height || null, + width: rep ? rep.width || null : null, + height: rep ? rep.height || null : null, framerate: adapter.getFramerate(rep) || null, - bitrate: rep.bandwidth || null, + bitrate: rep ? rep.bandwidth || null : null, isSupported: true } + + if (rep.tagName === DashConstants.Preselection && prsl_rep) { + config.width = prsl_rep.width || null; + config.height = prsl_rep.height || null; + config.bitrate = prsl_rep.bandwidth || null; + } + if (settings.get().streaming.capabilities.filterVideoColorimetryEssentialProperties) { Object.assign(config, _convertHDRColorimetryToConfig(rep)); } @@ -305,9 +352,14 @@ function CapabilitiesFilter() { return cfg; } - function _createAudioConfiguration(rep, codec) { - const samplerate = rep.audioSamplingRate || null; - const bitrate = rep.bandwidth || null; + function _createAudioConfiguration(rep, codec, prsl_rep) { + var samplerate = rep ? rep.audioSamplingRate || null : null; + var bitrate = rep ? rep.bandwidth || null : null; + + if (rep.tagName === DashConstants.PRESELECTION && prsl_rep) { + samplerate = prsl_rep.audioSamplingRate || null; + bitrate = prsl_rep.bandwidth || null; + } return { codec, @@ -351,6 +403,20 @@ function CapabilitiesFilter() { return as.Representation && as.Representation.length > 0; }); + + if (period.Preselection && period.Preselection.length) { + period.Preselection = period.Preselection.filter(prsl => { + const preselectionEssentialProperties = adapter.getEssentialPropertiesForPreselection(prsl); + const doesSupportEssentialProperties = _doesSupportEssentialProperties(preselectionEssentialProperties); + + if (!doesSupportEssentialProperties) { + logger.warn(`[CapabilitiesFilter] removed Preselection (id: ${prsl.id}) with unsupported EssentialProperty`); + return false; + } + + return true; + }) + } }); } @@ -371,6 +437,24 @@ function CapabilitiesFilter() { return true } + function _removeMultiRepresentationPreselections(manifest) { + if (!manifest || !manifest.Period || manifest.Period.length === 0) { + return; + } + + manifest.Period.forEach((period) => { + if (period.Preselection) { + period.Preselection = period.Preselection.filter((prsl) => { + const len = String(prsl.preselectionComponents).split(' ').length; + if (len !== 1) { + logger.warn(`Multi-Representation Preselection (id: ${prsl.id}) removed as not supported.`); + } + return len === 1; + }); + } + }); + } + function _applyCustomFilters(manifest) { if (!manifest || !manifest.Period || manifest.Period.length === 0) { return Promise.resolve(); diff --git a/test/unit/test/dash/dash.DashAdapter.js b/test/unit/test/dash/dash.DashAdapter.js index f400637fcf..45b149d6ec 100644 --- a/test/unit/test/dash/dash.DashAdapter.js +++ b/test/unit/test/dash/dash.DashAdapter.js @@ -64,6 +64,23 @@ const manifest_without_properties = { mediaPresentationDuration: 10, Period: [{ AdaptationSet: [{ id: 0, mimeType: Constants.VIDEO }] }] }; +const manifest_with_preselections = { + loadedTime: new Date(), + mediaPresentationDuration: 10, + Period: [{ + AdaptationSet: [ + { id: '0', mimeType: Constants.VIDEO }, + { id: '1', mimeType: Constants.AUDIO, [DashConstants.REPRESENTATION]: [{id: 101, mimeType: Constants.AUDIO, codecs: 'codec1', bandwidth: 128000}] }, + { id: '2', mimeType: Constants.AUDIO, [DashConstants.REPRESENTATION]: [{id: 102, mimeType: Constants.AUDIO, codecs: 'codec2', bandwidth: 128000}] } + ], + Preselection: [ + { id: '10', preselectionComponents: '1'}, + { id: '11', preselectionComponents: '2', codecs: 'codec3', [DashConstants.ROLE]: [{ schemeIdUri: 'test:scheme', value: 'testvalue' }]}, + { id: '12', preselectionComponents: '1 2'} + ] + }] + +}; const manifest_with_essential_properties = { loadedTime: new Date(), mediaPresentationDuration: 10, @@ -694,6 +711,26 @@ describe('DashAdapter', function () { expect(mediaInfoArray[0].essentialProperties.length).equals(0); }); + it('preselections should be filled', function () { + const mediaInfoArray = dashAdapter.getAllMediaInfoForType({ + id: 'defaultId_0', + index: 0 + }, Constants.AUDIO, manifest_with_preselections); + + expect(mediaInfoArray).to.be.instanceOf(Array); + expect(mediaInfoArray.length).equals(4); + + expect(mediaInfoArray[0].isPreselection).to.be.false; + expect(mediaInfoArray[2].isPreselection).to.be.true; + + expect(mediaInfoArray[2].codec).equals('audio;codecs="codec1"'); + expect(mediaInfoArray[3].codec).equals('audio;codecs="codec3"'); + + expect(mediaInfoArray[3].roles).to.be.instanceOf(Array); + expect(mediaInfoArray[3].roles.length).equals(1); + expect(mediaInfoArray[3].roles[0].value).equals('testvalue'); + }) + it('essential properties should be filled if correctly defined', function () { const mediaInfoArray = dashAdapter.getAllMediaInfoForType({ id: 'defaultId_0', From e2a1e381096a5276cc1c4d12c831c3406f0f4baa Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Mon, 23 Jun 2025 22:36:50 +0200 Subject: [PATCH 02/14] address review comments --- index.d.ts | 2 + src/dash/DashAdapter.js | 35 +++++------ src/dash/models/DashManifestModel.js | 73 +++++++++++++---------- src/dash/vo/MediaInfo.js | 1 - src/dash/vo/Preselection.js | 4 +- src/streaming/utils/CapabilitiesFilter.js | 14 ++--- 6 files changed, 67 insertions(+), 62 deletions(-) diff --git a/index.d.ts b/index.d.ts index 5a3d8d9288..b8854d7ba2 100644 --- a/index.d.ts +++ b/index.d.ts @@ -1771,6 +1771,8 @@ declare namespace dashjs { video?: TrackSwitchMode; audio?: TrackSwitchMode; }; + includePreselectionsInMediainfo?: boolean; + includePreselectionsForInitialTrackSelection?: boolean; ignoreSelectionPriority?: boolean; prioritizeRoleMain?: boolean; assumeDefaultRoleAsMain?: boolean; diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 79e66de43b..212a77b93b 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -110,13 +110,6 @@ function DashAdapter() { } const voAdaptations = dashManifestModel.getAdaptationsForPeriod(selectedVoPeriod); - - // the following line seems not to be needed since this function is only used - // - for Thumbnail-tracks - // - to determine timelines (based on one example Representation) - // For both Preselections are not relevant. - // const voPreselections = dashManifestModel.getPreselectionsForPeriod(selectedVoPeriod); - let realAdaptation = getMainAdaptationForType(type, streamInfo); if (!realAdaptation) { return null; @@ -260,9 +253,9 @@ function DashAdapter() { const voPreselections = dashManifestModel.getPreselectionsForPeriod(period); for (i = 0, ln = voPreselections.length; i < ln; i++) { - const prsl = voPreselections[i]; + const preselection = voPreselections[i]; - if (prsl.hasOwnProperty('type') && prsl.type === type) { + if (preselection.hasOwnProperty('type') && preselection.type === type) { media = convertPreselectionToMediaInfo(voPreselections[i]); if (media) { mediaArr.push(media); @@ -911,8 +904,8 @@ function DashAdapter() { * @memberOf module:DashAdapter * @instance */ - function getMainAdaptationSetForPreselection(prsl, as) { - return dashManifestModel.getMainAdaptationSetForPreselection(prsl, as); + function getMainAdaptationSetForPreselection(preselection, adaptation) { + return dashManifestModel.getMainAdaptationSetForPreselection(preselection, adaptation); } /** @@ -923,8 +916,8 @@ function DashAdapter() { * @memberOf module:DashAdapter * @instance */ - function getCommonRepresentationForPreselection(prsl, as) { - return dashManifestModel.getCommonRepresentationForPreselection(prsl, as); + function getCommonRepresentationForPreselection(preselection, adaptation) { + return dashManifestModel.getCommonRepresentationForPreselection(preselection, adaptation); } /** @@ -1142,23 +1135,23 @@ function DashAdapter() { return mediaInfo; } - function convertPreselectionToMediaInfo(prsl) { - if (!prsl) { + function convertPreselectionToMediaInfo(preselection) { + if (!preselection) { return null; } let mediaInfo = new MediaInfo(); - const realPreselection = prsl.period.mpd.manifest.Period[prsl.period.index].Preselection[prsl.index]; - const realAdaptation = prsl.period.mpd.manifest.Period[prsl.period.index].AdaptationSet; + const realPreselection = preselection.period.mpd.manifest.Period[preselection.period.index].Preselection[preselection.index]; + const realAdaptation = preselection.period.mpd.manifest.Period[preselection.period.index].AdaptationSet; // initialize mediaInfo with properties from main adaptation set - const as = dashManifestModel.getAdaptationsForPeriod(prsl.period); - const mainAdaptation = dashManifestModel.getMainAdaptationSetForPreselection(realPreselection, as); + const adaptation = dashManifestModel.getAdaptationsForPeriod(preselection.period); + const mainAdaptation = dashManifestModel.getMainAdaptationSetForPreselection(realPreselection, adaptation); mediaInfo = convertAdaptationToMediaInfo(mainAdaptation); mediaInfo.isPreselection = true; - mediaInfo.tag = prsl.tag; - mediaInfo.id = prsl.id; + mediaInfo.tag = preselection.tag; + mediaInfo.id = preselection.id; mediaInfo.codec = dashManifestModel.getCodecForPreselection(realPreselection, realAdaptation); mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realPreselection); mediaInfo.labels = dashManifestModel.getLabelsForAdaptation(realPreselection); diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 008e412339..8caa167613 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -144,8 +144,7 @@ function DashManifestModel() { const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); - let isType = mainAdaptationSet ? getIsTypeOf(mainAdaptationSet, type) : false; - return isType; + return mainAdaptationSet ? getIsTypeOf(mainAdaptationSet, type) : false; } function getIsFragmented(adaptation) { @@ -182,8 +181,8 @@ function DashManifestModel() { return getIsTypeOf(adaptation, Constants.TEXT); } - function getIsTextForPreselection(prsl, adaptations) { - const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); + function getIsTextForPreselection(preselection, adaptations) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); return getIsText(mainAdaptationSet); } @@ -426,24 +425,24 @@ function DashManifestModel() { return codec; } - function getCodecForPreselection(prsl, adaptations, addResolutionInfo) { + function getCodecForPreselection(preselection, adaptations, addResolutionInfo) { let codec = null; - if (prsl && adaptations ) { - const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); - let mainASCodec = getCodec(mainAdaptationSet,0, addResolutionInfo); + if (preselection && adaptations ) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); + let mainAsCodec = getCodec(mainAdaptationSet,0, addResolutionInfo); // we just take the subparameters from the first Representation of the main AdaptationSet - if (prsl.hasOwnProperty(DashConstants.CODECS)) { - let sCodecs = prsl.codecs; + if (preselection.hasOwnProperty(DashConstants.CODECS)) { + let sCodecs = preselection.codecs; // Since Preselection elements don't get the @mimeType attribute assigned, this // copies the media type from the main adaptationSet but takes the codec from // the preselection. - codec = mainASCodec.replace(/(codecs=")[^"]*(")/, `$1${sCodecs}$2`); - logger.info('Preselection has own codecs-attribute: replacing in MainAdaptationSet (was: ' + mainASCodec + '), new Preselection codec is: ' + codec) + codec = mainAsCodec.replace(/(codecs=")[^"]*(")/, `$1${sCodecs}$2`); + logger.info('Preselection has own codecs-attribute: replacing in MainAdaptationSet (was: ' + mainAsCodec + '), new Preselection codec is: ' + codec) } else { - codec = mainASCodec; + codec = mainAsCodec; } } @@ -454,11 +453,11 @@ function DashManifestModel() { return adaptation && adaptation.Representation && adaptation.Representation.length > 0 ? adaptation.Representation[0].mimeType : null; } - function getMimeTypeForPreselection(prsl, adaptations) { + function getMimeTypeForPreselection(preselection, adaptations) { let mime = null; - if (prsl && adaptations ) { - const mainAdaptationSet = getMainAdaptationSetForPreselection(prsl, adaptations); + if (preselection && adaptations ) { + const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); mime = getMimeType(mainAdaptationSet); } @@ -535,8 +534,8 @@ function DashManifestModel() { protectionElements = protectionElements.concat(periodProtectionElements); if (period.hasOwnProperty(DashConstants.ADAPTATION_SET) && period[DashConstants.ADAPTATION_SET].length > 0) { - period[DashConstants.ADAPTATION_SET].forEach((as) => { - const curr = _getContentProtectionFromElement(as); + period[DashConstants.ADAPTATION_SET].forEach((adaptation) => { + const curr = _getContentProtectionFromElement(adaptation); protectionElements = protectionElements.concat(curr); }) } @@ -983,16 +982,28 @@ function DashManifestModel() { } function _getAllAdaptationSetsForPreselection(preselection, adaptations) { - const prslComponentIds = String(preselection.preselectionComponents).split(' '); - return prslComponentIds.map(c => adaptations.find(as => as.id === c)); + if (!preselection || !preselection.preselectionComponents || !Array.isArray(adaptations)) { + return undefined; + } + + const preselectionComponentIds = String(preselection.preselectionComponents).split(' '); + return preselectionComponentIds.map(c => adaptations.find(adaptation => adaptation.id === c)); } function getMainAdaptationSetForPreselection(preselection, adaptations) { - const prslComponentIds = String(preselection.preselectionComponents).split(' '); - return adaptations.find(as => as.id === prslComponentIds[0]); + if (!preselection || !preselection.preselectionComponents || !Array.isArray(adaptations)) { + return undefined; + } + + const preselectionComponentIds = String(preselection.preselectionComponents).split(' '); + return adaptations.find(adaptation => adaptation.id === preselectionComponentIds[0]); } function getCommonRepresentationForPreselection(preselection, adaptations) { + if (!preselection || !Array.isArray(adaptations)) { + return undefined; + } + const mainAS = getMainAdaptationSetForPreselection(preselection, adaptations); return mainAS.Representation[0]; } @@ -1009,11 +1020,11 @@ function DashManifestModel() { realPreselection = realPeriod.Preselection[i]; voPreselection = new Preselection(); - const prslAdaptationSets = _getAllAdaptationSetsForPreselection(realPreselection, realPeriod.AdaptationSet); - let allComponentsAvailable = prslAdaptationSets.every(as => !!as); + const preselectionAdaptationSets = _getAllAdaptationSetsForPreselection(realPreselection, realPeriod.AdaptationSet); + let allComponentsAvailable = preselectionAdaptationSets.every(adaptation => !!adaptation); // up to now, we only support single-representation preselections, not multi-representation - if (allComponentsAvailable && prslAdaptationSets.length === 1) { + if (allComponentsAvailable && preselectionAdaptationSets.length === 1) { if (realPreselection.hasOwnProperty(DashConstants.ID)) { voPreselection.id = realPreselection.id; } @@ -1025,7 +1036,7 @@ function DashManifestModel() { } if (realPreselection.hasOwnProperty(DashConstants.PRESELECTION_COMPONENTS)) { - voPreselection.preselectionComponents = prslAdaptationSets; + voPreselection.preselectionComponents = preselectionAdaptationSets; } else { logger.warn('Preselection (index: ' + voPreselection.index + ') missing component(s)'); } @@ -1034,15 +1045,15 @@ function DashManifestModel() { voPreselection.order = realPreselection.order; } - if (getIsMuxed(prslAdaptationSets[0])) { + if (getIsMuxed(preselectionAdaptationSets[0])) { voPreselection.type = Constants.MUXED; - } else if (getIsAudio(prslAdaptationSets[0])) { + } else if (getIsAudio(preselectionAdaptationSets[0])) { voPreselection.type = Constants.AUDIO; - } else if (getIsVideo(prslAdaptationSets[0])) { + } else if (getIsVideo(preselectionAdaptationSets[0])) { voPreselection.type = Constants.VIDEO; - } else if (getIsText(prslAdaptationSets[0])) { + } else if (getIsText(preselectionAdaptationSets[0])) { voPreselection.type = Constants.TEXT; - } else if (getIsImage(prslAdaptationSets[0])) { + } else if (getIsImage(preselectionAdaptationSets[0])) { voPreselection.type = Constants.IMAGE; } else { logger.warn('Unknown Preselection stream type'); diff --git a/src/dash/vo/MediaInfo.js b/src/dash/vo/MediaInfo.js index d2fd8ff8e4..2084b1403d 100644 --- a/src/dash/vo/MediaInfo.js +++ b/src/dash/vo/MediaInfo.js @@ -51,7 +51,6 @@ class MediaInfo { this.lang = null; this.mimeType = null; this.normalizedKeyIds = new Set(); - this.preselectionComponents = []; this.representationCount = 0; this.roles = null; this.segmentAlignment = false; diff --git a/src/dash/vo/Preselection.js b/src/dash/vo/Preselection.js index b01abc87d7..b2d3eb3971 100644 --- a/src/dash/vo/Preselection.js +++ b/src/dash/vo/Preselection.js @@ -38,9 +38,9 @@ class Preselection { this.index = -1; this.type = null; this.tag = null; - this.order = 'undefined', + this.order = 'undefined'; this.preselectionComponents = []; } } -export default Preselection; \ No newline at end of file +export default Preselection; diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index 306de6a198..57427ddb4f 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -98,7 +98,7 @@ function CapabilitiesFilter() { manifest.Period .forEach((period) => { _filterUnsupportedAdaptationSetsOfPeriod(period, type); - _filterUnsupportedPreselectionssOfPeriod(period, type); + _filterUnsupportedPreselectionsOfPeriod(period, type); }) } @@ -123,7 +123,7 @@ function CapabilitiesFilter() { }) } - function _filterUnsupportedPreselectionssOfPeriod(period, type) { + function _filterUnsupportedPreselectionsOfPeriod(period, type) { if (!period || !period.Preselection || period.Preselection.length === 0) { return; } @@ -219,9 +219,9 @@ function CapabilitiesFilter() { period.Preselection.forEach((prsl) => { if (adapter.getPreselectionIsTypeOf(prsl, period.AdaptationSet, type)) { const codec = adapter.getCodecForPreselection(prsl, period.AdaptationSet); - const prsl_rep = adapter.getCommonRepresentationForPreselection(prsl, period.AdaptationSet); + const prslRep = adapter.getCommonRepresentationForPreselection(prsl, period.AdaptationSet); - _processCodecToCheck(type, prsl, codec, configurationsSet, configurations, prsl_rep); + _processCodecToCheck(type, prsl, codec, configurationsSet, configurations, prslRep); } }); } @@ -240,14 +240,14 @@ function CapabilitiesFilter() { } } - function _createConfiguration(type, rep, codec, prsl_rep) { + function _createConfiguration(type, rep, codec, prslRep) { let config = null; switch (type) { case Constants.VIDEO: - config = _createVideoConfiguration(rep, codec, prsl_rep); + config = _createVideoConfiguration(rep, codec, prslRep); break; case Constants.AUDIO: - config = _createAudioConfiguration(rep, codec, prsl_rep); + config = _createAudioConfiguration(rep, codec, prslRep); break; default: return config; From fb7e4c2451214e0a1f98af851e5b7f03ff40b824 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Tue, 24 Jun 2025 07:22:35 +0200 Subject: [PATCH 03/14] one more fix from review --- src/streaming/utils/CapabilitiesFilter.js | 26 +++++++++++------------ 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index 57427ddb4f..0c154b1c61 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -187,8 +187,8 @@ function CapabilitiesFilter() { return isSupplementalCodecSupported } - function _isCodecSupported(type, rep, codec, prsl_rep) { - const config = _createConfiguration(type, rep, codec, prsl_rep); + function _isCodecSupported(type, rep, codec, prslRep) { + const config = _createConfiguration(type, rep, codec, prslRep); return capabilities.isCodecSupportedBasedOnTestedConfigurations(config, type); } @@ -230,8 +230,8 @@ function CapabilitiesFilter() { return configurations; } - function _processCodecToCheck(type, rep, codec, configurationsSet, configurations, prsl_rep) { - const config = _createConfiguration(type, rep, codec, prsl_rep); + function _processCodecToCheck(type, rep, codec, configurationsSet, configurations, prslRep) { + const config = _createConfiguration(type, rep, codec, prslRep); const configString = JSON.stringify(config); if (!configurationsSet.has(configString)) { @@ -256,7 +256,7 @@ function CapabilitiesFilter() { return _addGenericAttributesToConfig(rep, config); } - function _createVideoConfiguration(rep, codec, prsl_rep) { + function _createVideoConfiguration(rep, codec, prslRep) { let config = { codec: codec, width: rep ? rep.width || null : null, @@ -266,10 +266,10 @@ function CapabilitiesFilter() { isSupported: true } - if (rep.tagName === DashConstants.Preselection && prsl_rep) { - config.width = prsl_rep.width || null; - config.height = prsl_rep.height || null; - config.bitrate = prsl_rep.bandwidth || null; + if (rep.tagName === DashConstants.Preselection && prslRep) { + config.width = prslRep.width || null; + config.height = prslRep.height || null; + config.bitrate = prslRep.bandwidth || null; } if (settings.get().streaming.capabilities.filterVideoColorimetryEssentialProperties) { @@ -352,13 +352,13 @@ function CapabilitiesFilter() { return cfg; } - function _createAudioConfiguration(rep, codec, prsl_rep) { + function _createAudioConfiguration(rep, codec, prslRep) { var samplerate = rep ? rep.audioSamplingRate || null : null; var bitrate = rep ? rep.bandwidth || null : null; - if (rep.tagName === DashConstants.PRESELECTION && prsl_rep) { - samplerate = prsl_rep.audioSamplingRate || null; - bitrate = prsl_rep.bandwidth || null; + if (rep.tagName === DashConstants.PRESELECTION && prslRep) { + samplerate = prslRep.audioSamplingRate || null; + bitrate = prslRep.bandwidth || null; } return { From 40e8c4bd05c5ba29265cefb17d61918aad3bec7d Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 4 Jul 2025 21:43:45 +0900 Subject: [PATCH 04/14] unify getProperty functions --- src/dash/DashAdapter.js | 38 ++------- src/dash/models/DashManifestModel.js | 28 +++---- src/streaming/utils/CapabilitiesFilter.js | 6 +- test/unit/mocks/AdapterMock.js | 19 +---- .../dash/dash.models.DashManifestModel.js | 79 +++---------------- 5 files changed, 32 insertions(+), 138 deletions(-) diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 212a77b93b..15212bc7e9 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -404,39 +404,13 @@ function DashAdapter() { } /** - * Return all EssentialProperties of an AdaptationSet - * @param {object} adaptationSet + * Return all EssentialProperties of an AdaptationSet, Representation or Preselection + * @param {object} element * @return {array} */ - function getEssentialPropertiesForAdaptationSet(adaptationSet) { + function getEssentialProperties(element) { try { - return dashManifestModel.getEssentialPropertiesForRepresentation(adaptationSet); - } catch (e) { - return []; - } - } - - /** - * Return all EssentialProperties of a Representation - * @param {object} representation - * @return {array} - */ - function getEssentialPropertiesForRepresentation(representation) { - try { - return dashManifestModel.getEssentialPropertiesForRepresentation(representation); - } catch (e) { - return []; - } - } - - /** - * Return all EssentialProperties of a Preselection - * @param {object} preselection - * @return {array} - */ - function getEssentialPropertiesForPreselection(preselection) { - try { - return dashManifestModel.getEssentialPropertiesForRepresentation(preselection); + return dashManifestModel.getEssentialProperties(element); } catch (e) { return []; } @@ -1340,9 +1314,7 @@ function DashAdapter() { getCodecForPreselection, getContentSteering, getDuration, - getEssentialPropertiesForAdaptationSet, - getEssentialPropertiesForPreselection, - getEssentialPropertiesForRepresentation, + getEssentialProperties, getEvent, getEventsFor, getFramerate, diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 8caa167613..ff4d1921ff 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -85,7 +85,7 @@ function DashManifestModel() { // Check for thumbnail images if (adaptation.Representation && adaptation.Representation.length) { - const essentialProperties = getEssentialPropertiesForRepresentation(adaptation.Representation[0]); + const essentialProperties = getEssentialProperties(adaptation.Representation[0]); if (essentialProperties && essentialProperties.some(essentialProperty => Constants.THUMBNAILS_SCHEME_ID_URIS.indexOf(essentialProperty.schemeIdUri) >= 0)) { return (type === Constants.IMAGE); } @@ -719,30 +719,22 @@ function DashManifestModel() { }); } - function getEssentialPropertiesForAdaptationSet(adaptation) { - return _getProperties(DashConstants.ESSENTIAL_PROPERTY, adaptation); + function getEssentialProperties(element) { + return _getProperties(DashConstants.ESSENTIAL_PROPERTY, element); } function getCombinedEssentialPropertiesForAdaptationSet(adaptation) { return _getCombinedPropertiesForAdaptationSet(DashConstants.ESSENTIAL_PROPERTY, adaptation); } - function getEssentialPropertiesForRepresentation(realRepresentation) { - return _getProperties(DashConstants.ESSENTIAL_PROPERTY, realRepresentation); - } - - function getSupplementalPropertiesForAdaptationSet(adaptation) { - return _getProperties(DashConstants.SUPPLEMENTAL_PROPERTY, adaptation); + function getSupplementalProperties(element) { + return _getProperties(DashConstants.SUPPLEMENTAL_PROPERTY, element); } function getCombinedSupplementalPropertiesForAdaptationSet(adaptation) { return _getCombinedPropertiesForAdaptationSet(DashConstants.SUPPLEMENTAL_PROPERTY, adaptation); } - function getSupplementalPropertiesForRepresentation(representation) { - return _getProperties(DashConstants.SUPPLEMENTAL_PROPERTY, representation); - } - function getRepresentationFor(index, adaptation) { return adaptation && adaptation.Representation && adaptation.Representation.length > 0 && isInteger(index) ? adaptation.Representation[index] : null; @@ -845,8 +837,8 @@ function DashManifestModel() { voRepresentation.segmentInfoType = DashConstants.BASE_URL; } - voRepresentation.essentialProperties = getEssentialPropertiesForRepresentation(realRepresentation); - voRepresentation.supplementalProperties = getSupplementalPropertiesForRepresentation(realRepresentation); + voRepresentation.essentialProperties = getEssentialProperties(realRepresentation); + voRepresentation.supplementalProperties = getSupplementalProperties(realRepresentation); if (segmentInfo) { if (segmentInfo.hasOwnProperty(DashConstants.INITIALIZATION)) { @@ -1685,8 +1677,7 @@ function DashManifestModel() { getContentProtectionByPeriod, getContentSteering, getDuration, - getEssentialPropertiesForAdaptationSet, - getEssentialPropertiesForRepresentation, + getEssentialProperties, getEventStreamForAdaptationSet, getEventStreamForRepresentation, getEventsForPeriod, @@ -1725,8 +1716,7 @@ function DashManifestModel() { getServiceDescriptions, getSubSegmentAlignment, getSuggestedPresentationDelay, - getSupplementalPropertiesForAdaptationSet, - getSupplementalPropertiesForRepresentation, + getSupplementalProperties, getUTCTimingSources, getViewpointForAdaptation, hasProfile, diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index 0c154b1c61..1ce954c629 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -389,7 +389,7 @@ function CapabilitiesFilter() { return true; } - const adaptationSetEssentialProperties = adapter.getEssentialPropertiesForAdaptationSet(as); + const adaptationSetEssentialProperties = adapter.getEssentialProperties(as); const doesSupportEssentialProperties = _doesSupportEssentialProperties(adaptationSetEssentialProperties); if (!doesSupportEssentialProperties) { @@ -397,7 +397,7 @@ function CapabilitiesFilter() { } as.Representation = as.Representation.filter((rep) => { - const essentialProperties = adapter.getEssentialPropertiesForRepresentation(rep); + const essentialProperties = adapter.getEssentialProperties(rep); return _doesSupportEssentialProperties(essentialProperties); }); @@ -406,7 +406,7 @@ function CapabilitiesFilter() { if (period.Preselection && period.Preselection.length) { period.Preselection = period.Preselection.filter(prsl => { - const preselectionEssentialProperties = adapter.getEssentialPropertiesForPreselection(prsl); + const preselectionEssentialProperties = adapter.getEssentialProperties(prsl); const doesSupportEssentialProperties = _doesSupportEssentialProperties(preselectionEssentialProperties); if (!doesSupportEssentialProperties) { diff --git a/test/unit/mocks/AdapterMock.js b/test/unit/mocks/AdapterMock.js index aceffdba58..eeeb411656 100644 --- a/test/unit/mocks/AdapterMock.js +++ b/test/unit/mocks/AdapterMock.js @@ -179,25 +179,12 @@ function AdapterMock() { return supplementalCodecs.split(' ').map((codec) => representation.mimeType + ';codecs="' + codec + '"'); } - this.getEssentialPropertiesForRepresentation = function (realRepresentation) { - if (!realRepresentation || !realRepresentation.EssentialProperty || !realRepresentation.EssentialProperty.length) { + this.getEssentialProperties = function (element) { + if (!element || !element.EssentialProperty || !element.EssentialProperty.length) { return null; } - return realRepresentation.EssentialProperty.map((prop) => { - return { - schemeIdUri: prop.schemeIdUri, - value: prop.value - }; - }); - }; - - this.getEssentialPropertiesForAdaptationSet = function (adaptationSet) { - if (!adaptationSet || !adaptationSet.EssentialProperty || !adaptationSet.EssentialProperty.length) { - return null; - } - - return adaptationSet.EssentialProperty.map((prop) => { + return element.EssentialProperty.map((prop) => { return { schemeIdUri: prop.schemeIdUri, value: prop.value diff --git a/test/unit/test/dash/dash.models.DashManifestModel.js b/test/unit/test/dash/dash.models.DashManifestModel.js index f42817cca8..86c26a2c6f 100644 --- a/test/unit/test/dash/dash.models.DashManifestModel.js +++ b/test/unit/test/dash/dash.models.DashManifestModel.js @@ -144,22 +144,22 @@ describe('DashManifestModel', function () { expect(rolesArray[1].value).equals('Main'); }); - it('should return an empty array when getEssentialPropertiesForAdaptationSet', () => { - const suppPropArray = dashManifestModel.getEssentialPropertiesForAdaptationSet(); + it('should return an empty array when getEssentialProperties', () => { + const suppPropArray = dashManifestModel.getEssentialProperties(); expect(suppPropArray).to.be.instanceOf(Object); expect(suppPropArray).to.be.empty; }); - it('should return an empty array when getEssentialPropertiesForAdaptationSet', () => { - const suppPropArray = dashManifestModel.getEssentialPropertiesForAdaptationSet(); + it('should return an empty array when getEssentialProperties', () => { + const suppPropArray = dashManifestModel.getEssentialProperties(); expect(suppPropArray).to.be.instanceOf(Array); expect(suppPropArray).to.be.empty; }); - it('should return correct array of DescriptorType when getEssentialPropertiesForAdaptationSet is called', () => { - const essPropArray = dashManifestModel.getEssentialPropertiesForAdaptationSet({ + it('should return correct array of DescriptorType when getEssentialProperties is called', () => { + const essPropArray = dashManifestModel.getEssentialProperties({ EssentialProperty: [{ schemeIdUri: 'test.scheme', value: 'testVal' }, { schemeIdUri: 'test.scheme', value: 'test2Val' @@ -175,22 +175,22 @@ describe('DashManifestModel', function () { expect(essPropArray[1].value).equals('test2Val'); }); - it('should return an empty array when getEssentialPropertiesForRepresentation', () => { - const essPropArray = dashManifestModel.getEssentialPropertiesForRepresentation(); + it('should return an empty array when getEssentialProperties', () => { + const essPropArray = dashManifestModel.getEssentialProperties(); expect(essPropArray).to.be.instanceOf(Object); expect(essPropArray).to.be.empty; }); - it('should return an empty array when getEssentialPropertiesForRepresentation', () => { - const essPropArray = dashManifestModel.getEssentialPropertiesForRepresentation(); + it('should return an empty array when getEssentialProperties', () => { + const essPropArray = dashManifestModel.getEssentialProperties(); expect(essPropArray).to.be.instanceOf(Array); expect(essPropArray).to.be.empty; }); - it('should return correct array of DescriptorType when getEssentialPropertiesForRepresentation is called', () => { - const essPropArray = dashManifestModel.getEssentialPropertiesForRepresentation({ + it('should return correct array of DescriptorType when getEssentialProperties is called', () => { + const essPropArray = dashManifestModel.getEssentialProperties({ EssentialProperty: [{ schemeIdUri: 'test.scheme', value: 'testVal' }] }); @@ -200,61 +200,6 @@ describe('DashManifestModel', function () { expect(essPropArray[0].value).equals('testVal'); }); - it('should return an empty array when getSupplementalPropertiesForAdaptationSet', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForAdaptationSet(); - - expect(suppPropArray).to.be.instanceOf(Object); - expect(suppPropArray).to.be.empty; - }); - - it('should return an empty array when getSupplementalPropertiesForAdaptationSet', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForAdaptationSet(); - - expect(suppPropArray).to.be.instanceOf(Array); - expect(suppPropArray).to.be.empty; - }); - - it('should return correct array of DescriptorType when getSupplementalPropertiesForAdaptationSet is called', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForAdaptationSet({ - SupplementalProperty: [{ schemeIdUri: 'test.scheme', value: 'testVal' }, { - schemeIdUri: 'test.scheme', - value: 'test2Val' - }] - }); - - expect(suppPropArray).to.be.instanceOf(Array); - expect(suppPropArray[0]).to.be.instanceOf(DescriptorType); - expect(suppPropArray[0].schemeIdUri).equals('test.scheme'); - expect(suppPropArray[0].value).equals('testVal'); - expect(suppPropArray[1].schemeIdUri).equals('test.scheme'); - expect(suppPropArray[1].value).equals('test2Val'); - }); - - it('should return an empty array when getSupplementalPropertiesForRepresentation', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForRepresentation(); - - expect(suppPropArray).to.be.instanceOf(Object); - expect(suppPropArray).to.be.empty; - }); - - it('should return an empty array when getSupplementalPropertiesForRepresentation', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForRepresentation(); - - expect(suppPropArray).to.be.instanceOf(Array); - expect(suppPropArray).to.be.empty; - }); - - it('should return correct array of DescriptorType when getSupplementalPropertiesForRepresentation is called', () => { - const suppPropArray = dashManifestModel.getSupplementalPropertiesForRepresentation({ - SupplementalProperty: [{ schemeIdUri: 'test.scheme', value: 'testVal' }] - }); - - expect(suppPropArray).to.be.instanceOf(Array); - expect(suppPropArray[0]).to.be.instanceOf(DescriptorType); - expect(suppPropArray[0].schemeIdUri).equals('test.scheme'); - expect(suppPropArray[0].value).equals('testVal'); - }); - it('should return null when getAdaptationForId is called and id, manifest and periodIndex are undefined', () => { const adaptation = dashManifestModel.getAdaptationForId(undefined, undefined, undefined); From 50e44d72398352bd68c8b2d9770aec7ea9382bd0 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Wed, 9 Jul 2025 10:51:08 +0200 Subject: [PATCH 05/14] adding id --- src/dash/vo/AdaptationSet.js | 1 + src/dash/vo/Preselection.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/dash/vo/AdaptationSet.js b/src/dash/vo/AdaptationSet.js index 2c20cebc85..c465550bed 100644 --- a/src/dash/vo/AdaptationSet.js +++ b/src/dash/vo/AdaptationSet.js @@ -36,6 +36,7 @@ class AdaptationSet { constructor() { this.period = null; this.index = -1; + this.id = null; this.type = null; } } diff --git a/src/dash/vo/Preselection.js b/src/dash/vo/Preselection.js index b2d3eb3971..97c70f1858 100644 --- a/src/dash/vo/Preselection.js +++ b/src/dash/vo/Preselection.js @@ -36,6 +36,7 @@ class Preselection { constructor() { this.period = null; this.index = -1; + this.id = null; this.type = null; this.tag = null; this.order = 'undefined'; From e2f4b464068a24b5b17bc9594367ddb99493277b Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 18 Jul 2025 15:03:19 +0200 Subject: [PATCH 06/14] more updates to index.d.ts --- index.d.ts | 50 ++++++++++++++++++++++++++++++++++------- src/dash/DashAdapter.js | 18 +++++++-------- 2 files changed, 51 insertions(+), 17 deletions(-) diff --git a/index.d.ts b/index.d.ts index b8854d7ba2..5018aeb136 100644 --- a/index.d.ts +++ b/index.d.ts @@ -415,6 +415,8 @@ declare namespace dashjs { getCodec(adaptation: object, representationIndex: number, addResolutionInfo: boolean): string; + getCodecForPreselection(preselection: Preselection, adaptations: AdaptationSet[], addResolutionInfo: boolean): string; + getContentProtectionByAdaptation(adaptation: object): any; getContentProtectionByManifest(manifest: object): any[]; @@ -427,7 +429,7 @@ declare namespace dashjs { getEndTimeForLastPeriod(voPeriod: Period): number; - getEssentialPropertiesForRepresentation(realRepresentation: object): { schemeIdUri: string, value: string } + getEssentialProperties(element: object): DescriptorType | []; getEventStreamForAdaptationSet(manifest: object, adaptation: object): EventStream[]; @@ -455,6 +457,8 @@ declare namespace dashjs { getIsText(adaptation: object): boolean; + getIsTextForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): boolean; + getIsTypeOf(adaptation: object, type: string): boolean; getIsVideo(adaptation: object): boolean; @@ -467,12 +471,24 @@ declare namespace dashjs { getLoction(manifest: object): MpdLocation | []; + getMainAdaptationSetForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): AdaptationSet | undefined; + + getCommonRepresentationForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): Representation | undefined; + getManifestUpdatePeriod(manifest: object, latencyOfLastUpdate?: number): number; getMimeType(adaptation: object): object; + getMimeTypeForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): object; + getMpd(manifest: object): Mpd; + getPatchLocation(manifest: object): PatchLocation[]; + + getPreselectionIsTypeOf(preselection: Preselection, adaptations: AdaptationSet[], type: MediaType): boolean; + + getPreselectionsForPeriod(voPeriod: object): Preselection[]; + getPeriodId(realPeriod: Period, i: number): string; getProducerReferenceTimesForAdaptation(adaptation: object): any[]; @@ -509,9 +525,7 @@ declare namespace dashjs { getSuggestedPresentationDelay(mpd: Mpd): any; - getSupplementalPropertiesForAdaptation(adaptation: object): DescriptorType | []; - - getSupplementalPropertiesForRepresentation(representation: Representation): DescriptorType | []; + getSupplementalProperties(element: object): DescriptorType | []; getUTCTimingSources(manifest: object): any[]; @@ -672,6 +686,7 @@ declare namespace dashjs { export class AdaptationSet { period: Period | null; + id: string | null; index: number; type: string | null; } @@ -879,6 +894,7 @@ declare namespace dashjs { index: number | null; isEmbedded: any | null; isFragmented: any | null; + isPreselection: boolean; isText: boolean; labels: { text: string, lang?: string }[]; lang: string | null; @@ -944,6 +960,16 @@ declare namespace dashjs { start: number; } + export interface Preselection { + period: Period | null; + index: number; + id: string | null; + order: string | null; + preselectionComponents: any[]; + tag: string | null; + type: string | null; + } + export interface ProducerReferenceTime { UTCTiming: any; applicationSchme: any; @@ -1056,13 +1082,15 @@ declare namespace dashjs { getCodec(adaptation: object, representationIndex: number, addResolutionInfo: boolean): string; + getCodecForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): string; + + getCommonRepresentationForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): Representation | null; + getContentSteering(manifest: object): object; getDuration(externalManifest?: object): number; - getEssentialPropertiesAdaptationSet(adaptationSet: AdaptationSet): object | []; - - getEssentialPropertiesForRepresentation(representation: Representation): any[]; + getEssentialProperties(element: AdaptationSet | Representation | Preselection): DescriptorType | []; getEvent(eventBox: object, eventStreams: object, mediaStartTime: number, voRepresentation: object): null | Event; @@ -1086,6 +1114,10 @@ declare namespace dashjs { getLocation(manifest: object): MpdLocation[]; + getMainAdaptationForType(type: string, streamInfo: object): object; + + getMainAdaptationSetForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): AdaptationSet | undefined; + getManifestUpdatePeriod(manifest: object, latencyOfLastUpdate?: number): number; getMediaInfoForType(streamInfo: object, type: MediaType): MediaInfo | null; @@ -1096,6 +1128,8 @@ declare namespace dashjs { getPeriodById(id: string): Period | null; + getPreselectionIsTypeOf(preselection: Preselection, adaptations: AdaptationSet[], type: MediaType): boolean; + getProducerReferenceTime(streamInfo: StreamInfo, mediaInfo: MediaInfo): object | []; getPublishTime(manifest: object): number | null; @@ -1112,7 +1146,7 @@ declare namespace dashjs { getSuggestedPresentationDelay(): string; - getSupplementalCodex(representation: Representation): Array; + getSupplementalCodecs(representation: Representation): Array; getUTCTimingSources(): any[]; diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index 15212bc7e9..ab40abd2b0 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -860,38 +860,38 @@ function DashAdapter() { /** * Checks if the given Preselection is from the given media type * @param {object} preselection - * @param {object} adaptation + * @param {array} adaptations * @param {string} type * @return {boolean} * @memberOf module:DashAdapter * @instance */ - function getPreselectionIsTypeOf(preselection, adaptation, type) { - return dashManifestModel.getPreselectionIsTypeOf(preselection, adaptation, type); + function getPreselectionIsTypeOf(preselection, adaptations, type) { + return dashManifestModel.getPreselectionIsTypeOf(preselection, adaptations, type); } /** * returns the main AdaptationSet of a given Preselection * @param {object} preselection - * @param {object} adaptation + * @param {array} adaptations * @return {object} * @memberOf module:DashAdapter * @instance */ - function getMainAdaptationSetForPreselection(preselection, adaptation) { - return dashManifestModel.getMainAdaptationSetForPreselection(preselection, adaptation); + function getMainAdaptationSetForPreselection(preselection, adaptations) { + return dashManifestModel.getMainAdaptationSetForPreselection(preselection, adaptations); } /** * returns common properties in form of a Representation of a given Preselection * @param {object} preselection - * @param {object} adaptation + * @param {array} adaptations * @return {object} * @memberOf module:DashAdapter * @instance */ - function getCommonRepresentationForPreselection(preselection, adaptation) { - return dashManifestModel.getCommonRepresentationForPreselection(preselection, adaptation); + function getCommonRepresentationForPreselection(preselection, adaptations) { + return dashManifestModel.getCommonRepresentationForPreselection(preselection, adaptations); } /** From e2075ea16d038421ca484334dd627f726aa5a737 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Tue, 29 Jul 2025 16:49:25 +0200 Subject: [PATCH 07/14] improve preselections capability check with MediaCapabilitiesAPI --- src/streaming/utils/CapabilitiesFilter.js | 27 ++++++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index 1ce954c629..b1c6ae1bf3 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -253,6 +253,8 @@ function CapabilitiesFilter() { return config; } + config = _addGenericAttributesToConfig(prslRep, config); + return _addGenericAttributesToConfig(rep, config); } @@ -266,10 +268,19 @@ function CapabilitiesFilter() { isSupported: true } - if (rep.tagName === DashConstants.Preselection && prslRep) { - config.width = prslRep.width || null; - config.height = prslRep.height || null; - config.bitrate = prslRep.bandwidth || null; + if (rep.tagName === DashConstants.PRESELECTION && prslRep) { + if (!config.width) { + config.width = prslRep.width || null; + } + if (!config.height) { + config.height = prslRep.height || null; + } + if (!config.bitrate) { + config.bitrate = prslRep.bandwidth || null; + } + if (!config.framerate) { + config.framerate = adapter.getFramerate(prslRep) || null; + } } if (settings.get().streaming.capabilities.filterVideoColorimetryEssentialProperties) { @@ -357,8 +368,12 @@ function CapabilitiesFilter() { var bitrate = rep ? rep.bandwidth || null : null; if (rep.tagName === DashConstants.PRESELECTION && prslRep) { - samplerate = prslRep.audioSamplingRate || null; - bitrate = prslRep.bandwidth || null; + if (!samplerate) { + samplerate = prslRep.audioSamplingRate || null; + } + if (!bitrate) { + bitrate = prslRep.bandwidth || null; + } } return { From 8e9a43d58df3409e9b1360d6a78f7075f71ab17f Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Tue, 29 Jul 2025 17:02:07 +0200 Subject: [PATCH 08/14] missing fix from last commit --- src/streaming/utils/CapabilitiesFilter.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index b1c6ae1bf3..d072650fb4 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -253,7 +253,9 @@ function CapabilitiesFilter() { return config; } - config = _addGenericAttributesToConfig(prslRep, config); + if (prslRep) { + config = _addGenericAttributesToConfig(prslRep, config); + } return _addGenericAttributesToConfig(rep, config); } From 2bb921a8d57f60d9a1224db01cf7fbb8e44b531e Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Wed, 30 Jul 2025 10:38:36 +0200 Subject: [PATCH 09/14] improve robustness --- src/dash/DashAdapter.js | 28 +++++++++++++++------------- src/dash/models/DashManifestModel.js | 4 ++-- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index ab40abd2b0..e06eb12f02 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -1123,19 +1123,21 @@ function DashAdapter() { const mainAdaptation = dashManifestModel.getMainAdaptationSetForPreselection(realPreselection, adaptation); mediaInfo = convertAdaptationToMediaInfo(mainAdaptation); - mediaInfo.isPreselection = true; - mediaInfo.tag = preselection.tag; - mediaInfo.id = preselection.id; - mediaInfo.codec = dashManifestModel.getCodecForPreselection(realPreselection, realAdaptation); - mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realPreselection); - mediaInfo.labels = dashManifestModel.getLabelsForAdaptation(realPreselection); - mediaInfo.lang = dashManifestModel.getLanguageForAdaptation(realPreselection); - mediaInfo.viewpoint = dashManifestModel.getViewpointForAdaptation(realPreselection); - mediaInfo.accessibility = dashManifestModel.getAccessibilityForAdaptation(realPreselection); - mediaInfo.audioChannelConfiguration = dashManifestModel.getAudioChannelConfigurationForAdaptation(realPreselection); - mediaInfo.roles = dashManifestModel.getRolesForAdaptation(realPreselection); - mediaInfo.essentialProperties = dashManifestModel.getCombinedEssentialPropertiesForAdaptationSet(realPreselection); - mediaInfo.supplementalProperties = dashManifestModel.getCombinedSupplementalPropertiesForAdaptationSet(realPreselection); + if (mediaInfo) { + mediaInfo.isPreselection = true; + mediaInfo.tag = preselection.tag; + mediaInfo.id = preselection.id; + mediaInfo.codec = dashManifestModel.getCodecForPreselection(realPreselection, realAdaptation); + mediaInfo.selectionPriority = dashManifestModel.getSelectionPriority(realPreselection); + mediaInfo.labels = dashManifestModel.getLabelsForAdaptation(realPreselection); + mediaInfo.lang = dashManifestModel.getLanguageForAdaptation(realPreselection); + mediaInfo.viewpoint = dashManifestModel.getViewpointForAdaptation(realPreselection); + mediaInfo.accessibility = dashManifestModel.getAccessibilityForAdaptation(realPreselection); + mediaInfo.audioChannelConfiguration = dashManifestModel.getAudioChannelConfigurationForAdaptation(realPreselection); + mediaInfo.roles = dashManifestModel.getRolesForAdaptation(realPreselection); + mediaInfo.essentialProperties = dashManifestModel.getCombinedEssentialPropertiesForAdaptationSet(realPreselection); + mediaInfo.supplementalProperties = dashManifestModel.getCombinedSupplementalPropertiesForAdaptationSet(realPreselection); + } return mediaInfo; } diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index ff4d1921ff..1ca28d9d33 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -974,7 +974,7 @@ function DashManifestModel() { } function _getAllAdaptationSetsForPreselection(preselection, adaptations) { - if (!preselection || !preselection.preselectionComponents || !Array.isArray(adaptations)) { + if (!preselection || !preselection.hasOwnProperty(DashConstants.PRESELECTION_COMPONENTS) || !Array.isArray(adaptations)) { return undefined; } @@ -983,7 +983,7 @@ function DashManifestModel() { } function getMainAdaptationSetForPreselection(preselection, adaptations) { - if (!preselection || !preselection.preselectionComponents || !Array.isArray(adaptations)) { + if (!preselection || !preselection.hasOwnProperty(DashConstants.PRESELECTION_COMPONENTS) || !Array.isArray(adaptations)) { return undefined; } From 332c4e48de448f1534270834cb7f7117eb871c69 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 12 Sep 2025 10:02:39 +0200 Subject: [PATCH 10/14] rename settings element for preselection to MediaInfo mapping --- src/core/Settings.js | 6 +++--- src/dash/DashAdapter.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/Settings.js b/src/core/Settings.js index 74ebe036d2..f865adc2de 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -189,7 +189,7 @@ import Events from './events/Events.js'; * audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE, * video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE * }, - * includePreselectionsInMediainfo: true, + * includePreselectionsInMediainfoArray: true, * includePreselectionsForInitialTrackSelection: false, * ignoreSelectionPriority: false, * prioritizeRoleMain: true, @@ -1009,7 +1009,7 @@ import Events from './events/Events.js'; * - Constants.TRACK_SWITCH_MODE_NEVER_REPLACE * Do not replace existing segments in the buffer * - * @property {} [includePreselectionsInMediainfo: true] + * @property {} [includePreselectionsInMediainfoArray: true] * provides the option to include Preselections in the MediaInfo object * * @property {} [includePreselectionsForInitialTrackSelection: false] @@ -1254,7 +1254,7 @@ function Settings() { audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE, video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE }, - includePreselectionsInMediainfo: true, + includePreselectionsInMediainfoArray: true, includePreselectionsForInitialTrackSelection: false, ignoreSelectionPriority: false, prioritizeRoleMain: true, diff --git a/src/dash/DashAdapter.js b/src/dash/DashAdapter.js index e06eb12f02..16fb3f8d0c 100644 --- a/src/dash/DashAdapter.js +++ b/src/dash/DashAdapter.js @@ -249,7 +249,7 @@ function DashAdapter() { } } - if (settings.get().streaming.includePreselectionsInMediainfo) { + if (settings.get().streaming.includePreselectionsInMediainfoArray) { const voPreselections = dashManifestModel.getPreselectionsForPeriod(period); for (i = 0, ln = voPreselections.length; i < ln; i++) { From 83897ae1cd037bb2f8c107032b1075801f7f736c Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 12 Sep 2025 10:27:27 +0200 Subject: [PATCH 11/14] setting Preselection@id to the default value per spec --- src/dash/vo/Preselection.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dash/vo/Preselection.js b/src/dash/vo/Preselection.js index 97c70f1858..f9b275ba7c 100644 --- a/src/dash/vo/Preselection.js +++ b/src/dash/vo/Preselection.js @@ -36,7 +36,7 @@ class Preselection { constructor() { this.period = null; this.index = -1; - this.id = null; + this.id = 1; this.type = null; this.tag = null; this.order = 'undefined'; From cf716d490bd1a79f0085bd2b548af6f9088da483 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 12 Sep 2025 10:48:55 +0200 Subject: [PATCH 12/14] move code to helper function --- src/dash/models/DashManifestModel.js | 47 +++++++++++++--------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 1ca28d9d33..89f98d10a4 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -936,6 +936,24 @@ function DashManifestModel() { return (periodStart - presentationOffset); } + function _convertType(element) { + if (getIsMuxed(element)) { + return Constants.MUXED; + } else if (getIsAudio(element)) { + return Constants.AUDIO; + } else if (getIsVideo(element)) { + return Constants.VIDEO; + } else if (getIsText(element)) { + return Constants.TEXT; + } else if (getIsImage(element)) { + return Constants.IMAGE; + } else { + logger.warn('Unknown Preselection stream type'); + } + + return null; + } + function getAdaptationsForPeriod(voPeriod) { const realPeriod = voPeriod && isInteger(voPeriod.index) ? voPeriod.mpd.manifest.Period[voPeriod.index] : null; const voAdaptations = []; @@ -953,19 +971,8 @@ function DashManifestModel() { voAdaptationSet.index = i; voAdaptationSet.period = voPeriod; - if (getIsMuxed(realAdaptationSet)) { - voAdaptationSet.type = Constants.MUXED; - } else if (getIsAudio(realAdaptationSet)) { - voAdaptationSet.type = Constants.AUDIO; - } else if (getIsVideo(realAdaptationSet)) { - voAdaptationSet.type = Constants.VIDEO; - } else if (getIsText(realAdaptationSet)) { - voAdaptationSet.type = Constants.TEXT; - } else if (getIsImage(realAdaptationSet)) { - voAdaptationSet.type = Constants.IMAGE; - } else { - logger.warn('Unknown Adaptation stream type'); - } + voAdaptationSet.type = _convertType(realAdaptationSet); + voAdaptations.push(voAdaptationSet); } } @@ -1037,19 +1044,7 @@ function DashManifestModel() { voPreselection.order = realPreselection.order; } - if (getIsMuxed(preselectionAdaptationSets[0])) { - voPreselection.type = Constants.MUXED; - } else if (getIsAudio(preselectionAdaptationSets[0])) { - voPreselection.type = Constants.AUDIO; - } else if (getIsVideo(preselectionAdaptationSets[0])) { - voPreselection.type = Constants.VIDEO; - } else if (getIsText(preselectionAdaptationSets[0])) { - voPreselection.type = Constants.TEXT; - } else if (getIsImage(preselectionAdaptationSets[0])) { - voPreselection.type = Constants.IMAGE; - } else { - logger.warn('Unknown Preselection stream type'); - } + voPreselection.type = _convertType(preselectionAdaptationSets[0]); voPreselections.push(voPreselection); } else { From 9bcb4f3779d95d22e709332348d8c0efd5a13755 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 12 Sep 2025 10:53:32 +0200 Subject: [PATCH 13/14] remove currently unsed code --- src/dash/models/DashManifestModel.js | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index 89f98d10a4..66a125a073 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -181,11 +181,6 @@ function DashManifestModel() { return getIsTypeOf(adaptation, Constants.TEXT); } - function getIsTextForPreselection(preselection, adaptations) { - const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); - return getIsText(mainAdaptationSet); - } - function getIsMuxed(adaptation) { return getIsTypeOf(adaptation, Constants.MUXED); } @@ -453,17 +448,6 @@ function DashManifestModel() { return adaptation && adaptation.Representation && adaptation.Representation.length > 0 ? adaptation.Representation[0].mimeType : null; } - function getMimeTypeForPreselection(preselection, adaptations) { - let mime = null; - - if (preselection && adaptations ) { - const mainAdaptationSet = getMainAdaptationSetForPreselection(preselection, adaptations); - mime = getMimeType(mainAdaptationSet); - } - - return mime; - } - function getSegmentAlignment(adaptation) { if (adaptation && adaptation.hasOwnProperty(DashConstants.SEGMENT_ALIGNMENT)) { return adaptation[DashConstants.SEGMENT_ALIGNMENT] === 'true' @@ -972,7 +956,7 @@ function DashManifestModel() { voAdaptationSet.period = voPeriod; voAdaptationSet.type = _convertType(realAdaptationSet); - + voAdaptations.push(voAdaptationSet); } } @@ -1682,7 +1666,6 @@ function DashManifestModel() { getIsDynamic, getIsFragmented, getIsText, - getIsTextForPreselection, getIsTypeOf, getLabelsForAdaptation, getLanguageForAdaptation, @@ -1691,7 +1674,6 @@ function DashManifestModel() { getCommonRepresentationForPreselection, getManifestUpdatePeriod, getMimeType, - getMimeTypeForPreselection, getMpd, getPatchLocation, getPreselectionIsTypeOf, From d4d11e890af73389a89de6e28cec436edd9d45d2 Mon Sep 17 00:00:00 2001 From: "Schreiner, Stephan" Date: Fri, 12 Sep 2025 15:50:18 +0200 Subject: [PATCH 14/14] remove declarations from index.d.ts, where respective items were removed from code --- index.d.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/index.d.ts b/index.d.ts index 5018aeb136..16fb376005 100644 --- a/index.d.ts +++ b/index.d.ts @@ -457,8 +457,6 @@ declare namespace dashjs { getIsText(adaptation: object): boolean; - getIsTextForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): boolean; - getIsTypeOf(adaptation: object, type: string): boolean; getIsVideo(adaptation: object): boolean; @@ -479,8 +477,6 @@ declare namespace dashjs { getMimeType(adaptation: object): object; - getMimeTypeForPreselection(preselection: Preselection, adaptations: AdaptationSet[]): object; - getMpd(manifest: object): Mpd; getPatchLocation(manifest: object): PatchLocation[];