Skip to content

Commit 376068f

Browse files
authored
improvements on (audio) track selection (Dash-Industry-Forum#4707)
* use always selectionPriority, use highest efficiency only for video tracks * fix code for selectionPriority; implement heuristic for audio efficiency * small corrections * adding DTS audio channel configs * adding JOC into audio efficiency consideration * provide optionto ignore selectionPriority * split function for video and audio tracks * selectionPriority TS fix
1 parent 0b89d5f commit 376068f

File tree

7 files changed

+415
-57
lines changed

7 files changed

+415
-57
lines changed

index.d.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,8 +1768,9 @@ declare namespace dashjs {
17681768
trackSwitchMode?: {
17691769
video?: TrackSwitchMode;
17701770
audio?: TrackSwitchMode;
1771-
}
1772-
selectionModeForInitialTrack?: TrackSelectionMode
1771+
};
1772+
ignoreSelectionPriority?: boolean;
1773+
selectionModeForInitialTrack?: TrackSelectionMode;
17731774
fragmentRequestTimeout?: number;
17741775
fragmentRequestProgressTimeout?: number;
17751776
manifestRequestTimeout?: number;

src/core/Settings.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ import Events from './events/Events.js';
188188
* audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE,
189189
* video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE
190190
* },
191-
* selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY,
191+
* ignoreSelectionPriority: false,
192+
* selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_HIGHEST_EFFICIENCY,
192193
* fragmentRequestTimeout: 20000,
193194
* fragmentRequestProgressTimeout: -1,
194195
* manifestRequestTimeout: 10000,
@@ -998,14 +999,14 @@ import Events from './events/Events.js';
998999
* - Constants.TRACK_SWITCH_MODE_NEVER_REPLACE
9991000
* Do not replace existing segments in the buffer
10001001
*
1001-
* @property {string} [selectionModeForInitialTrack="highestSelectionPriority"]
1002+
* @property {} [ignoreSelectionPriority: false]
1003+
* 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.
1004+
*
1005+
* @property {string} [selectionModeForInitialTrack="highestEfficiency"]
10021006
* Sets the selection mode for the initial track. This mode defines how the initial track will be selected if no initial media settings are set. If initial media settings are set this parameter will be ignored. Available options are:
10031007
*
10041008
* Possible values
10051009
*
1006-
* - Constants.TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY
1007-
* This mode makes the player select the track with the highest selectionPriority as defined in the manifest. If not selectionPriority is given we fallback to TRACK_SELECTION_MODE_HIGHEST_BITRATE. This mode is a default mode.
1008-
*
10091010
* - Constants.TRACK_SELECTION_MODE_HIGHEST_BITRATE
10101011
* This mode makes the player select the track with a highest bitrate.
10111012
*
@@ -1230,7 +1231,8 @@ function Settings() {
12301231
audio: Constants.TRACK_SWITCH_MODE_ALWAYS_REPLACE,
12311232
video: Constants.TRACK_SWITCH_MODE_NEVER_REPLACE
12321233
},
1233-
selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY,
1234+
ignoreSelectionPriority: false,
1235+
selectionModeForInitialTrack: Constants.TRACK_SELECTION_MODE_HIGHEST_EFFICIENCY,
12341236
fragmentRequestTimeout: 20000,
12351237
fragmentRequestProgressTimeout: -1,
12361238
manifestRequestTimeout: 10000,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/**
2+
* The copyright in this software is being made available under the BSD License,
3+
* included below. This software may be subject to other third party and contributor
4+
* rights, including patent rights, and no such rights are granted under this license.
5+
*
6+
* Copyright (c) 2025, Dash Industry Forum.
7+
* All rights reserved.
8+
*
9+
* Redistribution and use in source and binary forms, with or without modification,
10+
* are permitted provided that the following conditions are met:
11+
* * Redistributions of source code must retain the above copyright notice, this
12+
* list of conditions and the following disclaimer.
13+
* * Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation and/or
15+
* other materials provided with the distribution.
16+
* * Neither the name of Dash Industry Forum nor the names of its
17+
* contributors may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS AND ANY
21+
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23+
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24+
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25+
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSS OF USE, DATA, OR
26+
* PROFITS, OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
27+
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
* POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
33+
34+
// derived from ISO/IEC 23091-3
35+
const _mapping_CICP = {
36+
'0': undefined,
37+
'1': 1,
38+
'2': 2,
39+
'3': 3,
40+
'4': 4,
41+
'5': 5,
42+
'6': 5,
43+
'7': 7,
44+
'8': 2,
45+
'9': 3,
46+
'10': 4,
47+
'11': 6,
48+
'12': 7,
49+
'13': 22,
50+
'14': 7,
51+
'15': 10,
52+
'16': 9,
53+
'17': 11,
54+
'18': 13,
55+
'19': 11,
56+
'20': 13
57+
};
58+
59+
function _countBits(n) {
60+
return n == 0 ? 0 : n.toString(2).match(/1/g).length;
61+
}
62+
63+
function _getNChanFromBitMask(value, masks) {
64+
let nChan = undefined;
65+
let intVal = parseInt('0x' + value, 16);
66+
67+
let singleChannels = intVal & masks[0];
68+
let ChannelPairs = intVal & masks[1];
69+
nChan = _countBits(singleChannels) + 2 * _countBits(ChannelPairs);
70+
71+
return nChan;
72+
}
73+
74+
function _getNChanDolby2011(value) {
75+
if ( value.length !== 4 ) {
76+
return undefined;
77+
}
78+
79+
// see ETSI TS 103190-1, table F.1:
80+
// 0b1111100110001000: single channel flags
81+
// 0b0000011001110000: channel pair flags
82+
return _getNChanFromBitMask(value, [0b1111100110001000, 0b0000011001110000]);
83+
}
84+
85+
function _getNChanDolby2015(value) {
86+
if ( value.length !== 6 ) {
87+
return undefined;
88+
}
89+
90+
if ( value === '800000' ) {
91+
// object audio
92+
return 24;
93+
}
94+
95+
// see ETSI TS 103190-2, table A.27
96+
// 0b001101111000000010: single channel flags
97+
// 0b110010000110111101: channel pair flags
98+
return _getNChanFromBitMask(value, [0b001101111000000010, 0b110010000110111101]);
99+
}
100+
101+
function _getNChanDTSUHD(value) {
102+
if ( value.length > 8 ) {
103+
return undefined;
104+
}
105+
106+
// see ETSI TS 103491, table B-5
107+
// LFE to exclude: 0x00010000 + 0x00000020
108+
return _getNChanFromBitMask(value, [0xFFFEFFDF, 0x00000000]);
109+
}
110+
111+
function getNChanFromAudioChannelConfig(audioChannelConfiguration) {
112+
let nChan = undefined;
113+
114+
if ( !audioChannelConfiguration || !audioChannelConfiguration.schemeIdUri || !audioChannelConfiguration.value ) {
115+
return undefined;
116+
}
117+
118+
const scheme = audioChannelConfiguration['schemeIdUri'];
119+
const value = audioChannelConfiguration['value'];
120+
121+
if (scheme === 'urn:mpeg:dash:23003:3:audio_channel_configuration:2011' || scheme === 'urn:mpeg:mpegB:cicp:ChannelConfiguration') {
122+
// see ISO/IEC 23091-3
123+
nChan = _mapping_CICP[value];
124+
} else if (scheme === 'tag:dolby.com,2014:dash:audio_channel_configuration:2011') {
125+
nChan = _getNChanDolby2011(value);
126+
} else if (scheme === 'tag:dolby.com,2015:dash:audio_channel_configuration:2015') {
127+
nChan = _getNChanDolby2015(value);
128+
} else if (scheme === 'tag:dts.com,2014:dash:audio_channel_configuration:2012') {
129+
nChan = parseInt(value); // per ETSI TS 102 114,table G.2, this includes LFE
130+
} else if (scheme === 'tag:dts.com,2018:uhd:audio_channel_configuration') {
131+
nChan = _getNChanDTSUHD(value);
132+
}
133+
return nChan;
134+
}
135+
136+
export default getNChanFromAudioChannelConfig;

src/streaming/constants/Constants.js

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -194,14 +194,6 @@ export default {
194194
*/
195195
TRACK_SELECTION_MODE_WIDEST_RANGE: 'widestRange',
196196

197-
/**
198-
* @constant {string} TRACK_SELECTION_MODE_WIDEST_RANGE makes the player select the track with the highest selectionPriority as defined in the manifest
199-
* @memberof Constants#
200-
* @static
201-
*/
202-
TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY: 'highestSelectionPriority',
203-
204-
205197
/**
206198
* @constant {string} CMCD_QUERY_KEY specifies the key that is used for the CMCD query parameter.
207199
* @memberof Constants#

src/streaming/controllers/MediaController.js

Lines changed: 82 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,11 @@ import Events from '../../core/events/Events.js';
3333
import EventBus from '../../core/EventBus.js';
3434
import FactoryMaker from '../../core/FactoryMaker.js';
3535
import Debug from '../../core/Debug.js';
36-
import {bcp47Normalize} from 'bcp-47-normalize';
37-
import {extendedFilter} from 'bcp-47-match';
36+
import { bcp47Normalize } from 'bcp-47-normalize';
37+
import { extendedFilter } from 'bcp-47-match';
3838
import MediaPlayerEvents from '../MediaPlayerEvents.js';
3939
import DashConstants from '../../dash/constants/DashConstants.js';
40+
import getNChanFromAudioChannelConfig from '../constants/AudioChannelConfiguration.js';
4041

4142
function MediaController() {
4243

@@ -416,7 +417,7 @@ function MediaController() {
416417
function matchSettingsLang(settings, track) {
417418
try {
418419
return !settings.lang ||
419-
(settings.lang instanceof RegExp) ?
420+
(settings.lang instanceof RegExp) ?
420421
(track.lang.match(settings.lang)) : track.lang !== '' ?
421422
(extendedFilter(track.lang, bcp47Normalize(settings.lang)).length > 0) : false;
422423
} catch (e) {
@@ -565,7 +566,7 @@ function MediaController() {
565566
return result;
566567
}
567568

568-
function getTracksWithHighestEfficiency(trackArr) {
569+
function _getVideoTracksWithHighestEfficiency(trackArr) {
569570
let min = Infinity;
570571
let result = [];
571572
let tmp;
@@ -585,10 +586,64 @@ function MediaController() {
585586
result.push(track);
586587
}
587588
});
589+
return result;
590+
}
591+
592+
function _getAudioTracksWithHighestEfficiency(trackArr) {
593+
let min = Infinity;
594+
let result = [];
588595

596+
// Note:
597+
// we ignore potential AudioChannelConfiguration descriptors assigned to different bitrates=Representations
598+
// since this should not happen per IOP
599+
trackArr.forEach(function (track) {
600+
const tmp = track.audioChannelConfiguration.reduce(function (acc, audioChanCfg) {
601+
const nChan = getNChanFromAudioChannelConfig(audioChanCfg);
602+
return acc + nChan;
603+
}, 0);
604+
let avgChan = tmp / track.audioChannelConfiguration.length;
605+
606+
if (track.hasOwnProperty('supplementalProperties')) {
607+
if (track.supplementalProperties.some(
608+
prop => {
609+
return (prop.schemeIdUri === 'tag:dolby.com,2018:dash:EC3_ExtensionType:2018' && prop.value === 'JOC');
610+
})) {
611+
avgChan = 16;
612+
}
613+
}
614+
615+
// avgChan may be undefined, e.g. when audioChannelConfiguration is absent
616+
if (!avgChan) {
617+
avgChan = 1;
618+
}
619+
620+
let sumEff = track.bitrateList.reduce(function (acc, t) {
621+
const trackEff = t.bandwidth / avgChan;
622+
return acc + trackEff;
623+
}, 0);
624+
let eff = sumEff / track.bitrateList.length;
625+
626+
if (eff < min) {
627+
min = eff;
628+
result = [track];
629+
} else if (eff === min) {
630+
result.push(track);
631+
}
632+
});
589633
return result;
590634
}
591635

636+
function getTracksWithHighestEfficiency(trackArr) {
637+
if (trackArr[0] && (trackArr[0].type === Constants.VIDEO)) {
638+
return _getVideoTracksWithHighestEfficiency(trackArr);
639+
}
640+
else if (trackArr[0] && (trackArr[0].type === Constants.AUDIO)) {
641+
return _getAudioTracksWithHighestEfficiency(trackArr);
642+
}
643+
644+
return trackArr;
645+
}
646+
592647
function getTracksWithWidestRange(trackArr) {
593648
let max = 0;
594649
let result = [];
@@ -630,27 +685,29 @@ function MediaController() {
630685

631686
// Use the track selection function that is defined in the settings
632687
else {
633-
let mode = settings.get().streaming.selectionModeForInitialTrack;
634-
switch (mode) {
635-
case Constants.TRACK_SELECTION_MODE_HIGHEST_SELECTION_PRIORITY:
636-
tmpArr = _trackSelectionModeHighestSelectionPriority(tmpArr);
637-
break;
638-
case Constants.TRACK_SELECTION_MODE_HIGHEST_BITRATE:
639-
tmpArr = _trackSelectionModeHighestBitrate(tmpArr);
640-
break;
641-
case Constants.TRACK_SELECTION_MODE_FIRST_TRACK:
642-
tmpArr = _trackSelectionModeFirstTrack(tmpArr);
643-
break;
644-
case Constants.TRACK_SELECTION_MODE_HIGHEST_EFFICIENCY:
645-
tmpArr = _trackSelectionModeHighestEfficiency(tmpArr);
646-
break;
647-
case Constants.TRACK_SELECTION_MODE_WIDEST_RANGE:
648-
tmpArr = _trackSelectionModeWidestRange(tmpArr);
649-
break;
650-
default:
651-
logger.warn(`Track selection mode ${mode} is not supported. Falling back to TRACK_SELECTION_MODE_FIRST_TRACK`);
652-
tmpArr = _trackSelectionModeFirstTrack(tmpArr);
653-
break;
688+
if (!settings.get().streaming.ignoreSelectionPriority) {
689+
tmpArr = _trackSelectionModeHighestSelectionPriority(tmpArr);
690+
}
691+
if (tmpArr.length > 1) {
692+
let mode = settings.get().streaming.selectionModeForInitialTrack;
693+
switch (mode) {
694+
case Constants.TRACK_SELECTION_MODE_HIGHEST_BITRATE:
695+
tmpArr = _trackSelectionModeHighestBitrate(tmpArr);
696+
break;
697+
case Constants.TRACK_SELECTION_MODE_FIRST_TRACK:
698+
tmpArr = _trackSelectionModeFirstTrack(tmpArr);
699+
break;
700+
case Constants.TRACK_SELECTION_MODE_HIGHEST_EFFICIENCY:
701+
tmpArr = _trackSelectionModeHighestEfficiency(tmpArr);
702+
break;
703+
case Constants.TRACK_SELECTION_MODE_WIDEST_RANGE:
704+
tmpArr = _trackSelectionModeWidestRange(tmpArr);
705+
break;
706+
default:
707+
logger.warn(`Track selection mode ${mode} is not supported. Falling back to TRACK_SELECTION_MODE_FIRST_TRACK`);
708+
tmpArr = _trackSelectionModeFirstTrack(tmpArr);
709+
break;
710+
}
654711
}
655712
}
656713

@@ -794,18 +851,6 @@ function MediaController() {
794851
function _trackSelectionModeHighestSelectionPriority(tracks) {
795852
let tmpArr = getTracksWithHighestSelectionPriority(tracks);
796853

797-
if (tmpArr.length > 1) {
798-
tmpArr = getTracksWithHighestEfficiency(tmpArr);
799-
}
800-
801-
if (tmpArr.length > 1) {
802-
tmpArr = getTracksWithHighestBitrate(tmpArr);
803-
}
804-
805-
if (tmpArr.length > 1) {
806-
tmpArr = getTracksWithWidestRange(tmpArr);
807-
}
808-
809854
return tmpArr;
810855
}
811856

0 commit comments

Comments
 (0)