From ee16445742c609f2e548662260470a643e6ab037 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Thu, 4 Jul 2024 14:12:23 +0200 Subject: [PATCH] Fix/as switch with different codecs (#4524) * WiP: Fix codec switch for AS switching * Put logic for representation switches in dedicated functions inside the BufferController.js and use changeType if possible * Add missing isSupported if MediaCapabilities API is used with content without a EssentialProperty * Add unit tests for checking codec support * Uncomment Media Capabilities API codec check as it seems to fail the CI/CD pipeline * Remove unwanted files * WiP: Improve quality selection algorithm * Add unit tests * Replace invalid sample stream * Additional changes to fix the functional tests * Changes to the Karma execution on Lambdatest * Remove chrome from lambdatest as it fails with timeout * Change Lambdatest config * Run single and smoke tests in same job * Lambdatest config change * Lambdatest config change * Lambdatest config change --- .circleci/config.yml | 21 +- contrib/akamai/controlbar/ControlBar.js | 1 + .../dash-if-reference-player/app/sources.json | 4 +- src/core/Settings.js | 6 +- src/dash/models/DashManifestModel.js | 25 +- src/dash/vo/Representation.js | 4 + src/streaming/MediaPlayer.js | 2 +- src/streaming/StreamProcessor.js | 38 +- src/streaming/controllers/AbrController.js | 81 +- src/streaming/controllers/BufferController.js | 73 +- src/streaming/controllers/MediaController.js | 4 + src/streaming/rules/SwitchRequestHistory.js | 4 +- .../rules/abr/InsufficientBufferRule.js | 4 + src/streaming/utils/CapabilitiesFilter.js | 47 +- .../config/karma.functional.conf.cjs | 5 +- .../test-configurations/lambdatest-smoke.json | 23 +- .../test-configurations/streams/smoke.json | 36 +- test/functional/test/audio/initial-audio.js | 4 +- .../streaming.controllers.AbrController.js | 861 ++++++++++++------ .../streaming/streaming.utils.Capabilities.js | 148 ++- 20 files changed, 989 insertions(+), 402 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2d1e968dd3..0f6f106a5e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -185,6 +185,18 @@ jobs: configfile: lambdatest-smoke - process_test_results + functional-tests-single-and-smoke: + executor: dashjs-executor + steps: + - functional_steps + - run_test_suite: + streamsfile: single + configfile: lambdatest + - run_test_suite: + streamsfile: smoke + configfile: lambdatest-smoke + - process_test_results + functional-tests-full-part-1: executor: dashjs-executor steps: @@ -222,14 +234,7 @@ workflows: branches: ignore: - development # skiping redundant job if already on development - - functional-tests-single: - filters: - branches: - ignore: # as creds are available only for non-forked branches - - /pull\/[0-9]+/ - - functional-tests-smoke: - requires: - - functional-tests-single + - functional-tests-single-and-smoke: filters: branches: ignore: # as creds are available only for non-forked branches diff --git a/contrib/akamai/controlbar/ControlBar.js b/contrib/akamai/controlbar/ControlBar.js index cc0c1336c7..3b219d33ad 100644 --- a/contrib/akamai/controlbar/ControlBar.js +++ b/contrib/akamai/controlbar/ControlBar.js @@ -537,6 +537,7 @@ var ControlBar = function (dashjsMediaPlayer, displayUTCTimeCodes) { contentFunc = function (element, index) { var result = isNaN(index) ? ' Auto Switch' : Math.floor(element.bitrateInKbit) + ' kbps'; result += element && element.width && element.height ? ' (' + element.width + 'x' + element.height + ')' : ''; + result += element && element.codecs ? ' (' + element.codecs + ')' : ''; return result; }; diff --git a/samples/dash-if-reference-player/app/sources.json b/samples/dash-if-reference-player/app/sources.json index ce7c9f0920..34c7ec75aa 100644 --- a/samples/dash-if-reference-player/app/sources.json +++ b/samples/dash-if-reference-player/app/sources.json @@ -307,8 +307,8 @@ "provider": "aws" }, { - "name": "LiveSIM Caminandes 02, Gran Dillama (25fps, 25gop, 2sec, multi MOOF/MDAT, 1080p, KID=1236) v2", - "url": "http://refapp.hbbtv.org/livesim2/02_llamav2/manifest.mpd", + "name": "Main (3 video bitrates, 2 audio languages,3 subtitle languages) + Advertisement, Multiperiod", + "url": "https://refapp.hbbtv.org/videos/multiperiod_v8.php?drm=0&advert=1&emsg=0&video=v1,v2,v3&audiolang=eng,fin&sublang=eng,fin,swe&mup=2&spd=8", "provider": "hbbtv" }, { diff --git a/src/core/Settings.js b/src/core/Settings.js index f26b3f01af..03ef79c39c 100644 --- a/src/core/Settings.js +++ b/src/core/Settings.js @@ -416,7 +416,7 @@ import Events from './events/Events.js'; * That buffered range is likely to have been enqueued for playback. Pruning it causes a flush and reenqueue in WPE and WebKitGTK based browsers. This stresses the video decoder and can cause stuttering on embedded platforms. * @property {boolean} [useChangeTypeForTrackSwitch=true] * If this flag is set to true then dash.js will use the MSE v.2 API call "changeType()" before switching to a different track. - * Note that some platforms might not implement the changeType functio. dash.js is checking for the availability before trying to call it. + * Note that some platforms might not implement the changeType function. dash.js is checking for the availability before trying to call it. * @property {boolean} [mediaSourceDurationInfinity=true] * If this flag is set to true then dash.js will allow `Infinity` to be set as the MediaSource duration otherwise the duration will be set to `Math.pow(2,32)` instead of `Infinity` to allow appending segments indefinitely. * Some platforms such as WebOS 4.x have issues with seeking when duration is set to `Infinity`, setting this flag to false resolve this. @@ -860,7 +860,7 @@ import Events from './events/Events.js'; * This value is used to specify the desired CMCD parameters. Parameters not included in this list are not reported. * @property {Array.} [includeInRequests] * Specifies which HTTP GET requests shall carry parameters. - * + * * If not specified this value defaults to ['segment']. */ @@ -1221,7 +1221,7 @@ function Settings() { } }, droppedFramesRule: { - active: true, + active: false, parameters: { minimumSampleSize: 375, droppedFramesPercentageThreshold: 0.15 diff --git a/src/dash/models/DashManifestModel.js b/src/dash/models/DashManifestModel.js index f811bb5646..7207eb6920 100644 --- a/src/dash/models/DashManifestModel.js +++ b/src/dash/models/DashManifestModel.js @@ -664,7 +664,20 @@ function DashManifestModel() { voRepresentation.scanType = realRepresentation.scanType; } if (realRepresentation.hasOwnProperty(DashConstants.FRAMERATE)) { - voRepresentation.frameRate = realRepresentation[DashConstants.FRAMERATE]; + const frameRate = realRepresentation[DashConstants.FRAMERATE]; + if (isNaN(frameRate) && frameRate.includes('/')) { + const parts = frameRate.split('/'); + if (parts.length === 2) { + const numerator = parseFloat(parts[0]); + const denominator = parseFloat(parts[1]); + + if (!isNaN(numerator) && !isNaN(denominator) && denominator !== 0) { + voRepresentation.frameRate = numerator / denominator; + } + } + } else { + voRepresentation.frameRate = frameRate + } } if (realRepresentation.hasOwnProperty(DashConstants.QUALITY_RANKING)) { voRepresentation.qualityRanking = realRepresentation[DashConstants.QUALITY_RANKING]; @@ -763,8 +776,18 @@ function DashManifestModel() { } } + voRepresentation.essentialProperties = getEssentialPropertiesForRepresentation(realRepresentation); + voRepresentation.supplementalProperties = getSupplementalPropertiesForRepresentation(realRepresentation); voRepresentation.mseTimeOffset = calcMseTimeOffset(voRepresentation); voRepresentation.path = [voAdaptation.period.index, voAdaptation.index, i]; + + if (!isNaN(voRepresentation.width) && !isNaN(voRepresentation.height) && !isNaN(voRepresentation.frameRate)) { + voRepresentation.pixelsPerSecond = Math.max(1, voRepresentation.width * voRepresentation.height * voRepresentation.frameRate) + if (!isNaN(voRepresentation.bandwidth)) { + voRepresentation.bitsPerPixel = voRepresentation.bandwidth / voRepresentation.pixelsPerSecond + } + } + voRepresentations.push(voRepresentation); } } diff --git a/src/dash/vo/Representation.js b/src/dash/vo/Representation.js index 2e1bd3561c..7e8fe41474 100644 --- a/src/dash/vo/Representation.js +++ b/src/dash/vo/Representation.js @@ -44,8 +44,10 @@ class Representation { this.availabilityTimeOffset = 0; this.bandwidth = NaN; this.bitrateInKbit = NaN; + this.bitsPerPixel = NaN; this.codecPrivateData = null; this.codecs = null; + this.essentialProperties = []; this.fragmentDuration = null; this.frameRate = null; this.height = NaN; @@ -57,6 +59,7 @@ class Representation { this.mediaInfo = null; this.mimeType = null; this.mseTimeOffset = NaN; + this.pixelsPerSecond = NaN; this.presentationTimeOffset = 0; this.qualityRanking = NaN; this.range = null; @@ -64,6 +67,7 @@ class Representation { this.segments = null; this.segmentDuration = NaN; this.segmentInfoType = null; + this.supplementalProperties = []; this.startNumber = 1; this.timescale = 1; this.width = NaN; diff --git a/src/streaming/MediaPlayer.js b/src/streaming/MediaPlayer.js index 4386ddb4e3..71b5fc8c96 100644 --- a/src/streaming/MediaPlayer.js +++ b/src/streaming/MediaPlayer.js @@ -2653,7 +2653,7 @@ function MediaPlayer() { if (value.lang) { output.lang = value.lang; } - if (value.index) { + if (!isNaN(value.index)) { output.index = value.index; } if (value.viewpoint) { diff --git a/src/streaming/StreamProcessor.js b/src/streaming/StreamProcessor.js index 9bba192288..dc1418f440 100644 --- a/src/streaming/StreamProcessor.js +++ b/src/streaming/StreamProcessor.js @@ -635,6 +635,7 @@ function StreamProcessor(config) { /** * Called once the StreamProcessor is initialized and when the track is switched. We only have one StreamProcessor per media type. So we need to adjust the mediaInfo once we switch/select a track. * @param {object} newMediaInfo + * @param targetRepresentation */ function selectMediaInfo(newMediaInfo, targetRepresentation = null) { return new Promise((resolve) => { @@ -734,6 +735,7 @@ function StreamProcessor(config) { } function _handleDifferentSwitchTypes(e, newRepresentation) { + // If the switch should occur immediately we need to replace existing stuff in the buffer if (e.reason && e.reason.forceReplace) { _prepareForForceReplacementQualitySwitch(newRepresentation); @@ -761,7 +763,7 @@ function StreamProcessor(config) { function _prepareForForceReplacementQualitySwitch(voRepresentation) { // Abort the current request to avoid inconsistencies and in case a rule such as AbandonRequestRule has forced a quality switch. A quality switch can also be triggered manually by the application. - // If we update the buffer values now, or initialize a request to the new init segment, the currently downloading media segment might "work" with wrong values. + // If we update the buffer values now, or initialize a request to the new init segment, the currently downloading media segment might use wrong values. // Everything that is already in the buffer queue is ok and will be handled by the corresponding function below depending on the switch mode. fragmentModel.abortRequests(); @@ -772,6 +774,7 @@ function StreamProcessor(config) { }, { mediaType: type, streamId: streamInfo.id }); scheduleController.setCheckPlaybackQuality(false); + // Abort appending segments to the buffer. Also adjust the appendWindow as we might have been in the progress of prebuffering stuff. bufferController.prepareForForceReplacementQualitySwitch(voRepresentation) .then(() => { @@ -786,6 +789,20 @@ function StreamProcessor(config) { }); } + function _prepareForAbandonQualitySwitch(voRepresentation) { + bufferController.prepareForAbandonQualitySwitch(voRepresentation) + .then(() => { + fragmentModel.abortRequests(); + shouldRepeatRequest = true; + scheduleController.setCheckPlaybackQuality(false); + scheduleController.startScheduleTimer(); + qualityChangeInProgress = false; + }) + .catch(() => { + qualityChangeInProgress = false; + }) + } + function _prepareForFastQualitySwitch(voRepresentation) { // if we switch up in quality and need to replace existing parts in the buffer we need to adjust the buffer target const time = playbackController.getTime(); @@ -802,7 +819,7 @@ function StreamProcessor(config) { // The new quality is higher than the one we originally requested if (request.bandwidth < voRepresentation.bandwidth && bufferLevel >= safeBufferLevel && abandonmentState === MetricsConstants.ALLOW_LOAD) { - bufferController.updateBufferTimestampOffset(voRepresentation) + bufferController.prepareForFastQualitySwitch(voRepresentation) .then(() => { // Abort the current request to avoid inconsistencies. A quality switch can also be triggered manually by the application. // If we update the buffer values now, or initialize a request to the new init segment, the currently downloading media segment might "work" with wrong values. @@ -819,7 +836,7 @@ function StreamProcessor(config) { }) } - // If we have buffered a higher quality we do not replace anything. We might cancel the current request due to abandon request rule + // If we have buffered a higher quality we do not replace anything. else { _prepareForDefaultQualitySwitch(voRepresentation); } @@ -838,7 +855,7 @@ function StreamProcessor(config) { } - bufferController.updateBufferTimestampOffset(voRepresentation) + bufferController.prepareForDefaultQualitySwitch(voRepresentation) .then(() => { scheduleController.setCheckPlaybackQuality(false); if (currentMediaInfo.segmentAlignment || currentMediaInfo.subSegmentAlignment) { @@ -855,19 +872,6 @@ function StreamProcessor(config) { }) } - function _prepareForAbandonQualitySwitch(voRepresentation) { - bufferController.updateBufferTimestampOffset(voRepresentation) - .then(() => { - fragmentModel.abortRequests(); - shouldRepeatRequest = true; - scheduleController.setCheckPlaybackQuality(false); - scheduleController.startScheduleTimer(); - qualityChangeInProgress = false; - }) - .catch(() => { - qualityChangeInProgress = false; - }) - } /** * We have canceled the download of a fragment and need to adjust the buffer time or reload an init segment diff --git a/src/streaming/controllers/AbrController.js b/src/streaming/controllers/AbrController.js index 0b9ae06af8..b0bd16a36f 100644 --- a/src/streaming/controllers/AbrController.js +++ b/src/streaming/controllers/AbrController.js @@ -225,10 +225,11 @@ function AbrController() { } // If bitrate should be as small as possible return the Representation with the lowest bitrate + const smallestRepresentation = possibleVoRepresentations.reduce((a, b) => { + return a.bandwidth < b.bandwidth ? a : b; + }) if (bitrate <= 0) { - return possibleVoRepresentations.sort((a, b) => { - return a.bandwidth - b.bandwidth; - })[0] + return smallestRepresentation } // Get all Representations that have lower or equal bitrate than our target bitrate @@ -237,10 +238,13 @@ function AbrController() { }); if (!targetRepresentations || targetRepresentations.length === 0) { - return possibleVoRepresentations[0]; + return smallestRepresentation } - return targetRepresentations[targetRepresentations.length - 1]; + return targetRepresentations.reduce((max, curr) => { + return (curr.absoluteIndex > max.absoluteIndex) ? curr : max; + }) + } function getRepresentationByAbsoluteIndex(absoluteIndex, mediaInfo, includeCompatibleMediaInfos = true) { @@ -284,7 +288,7 @@ function AbrController() { }) // Now sort by quality (usually simply by bitrate) - voRepresentations = _sortByCalculatedQualityRank(voRepresentations); + voRepresentations = _sortRepresentationsByQuality(voRepresentations); // Add an absolute index voRepresentations.forEach((rep, index) => { @@ -443,15 +447,17 @@ function AbrController() { } } - /** - * Calculate a quality rank based on bandwidth, codec and qualityRanking. Lower value means better quality. - * @param voRepresentations - * @private - */ - function _sortByCalculatedQualityRank(voRepresentations) { + function _sortRepresentationsByQuality(voRepresentations) { + if (_shouldSortByQualityRankingAttribute(voRepresentations)) { + voRepresentations = _sortByQualityRankingAttribute(voRepresentations) + } else { + voRepresentations = _sortByDefaultParameters(voRepresentations) + } - // All Representations must have a qualityRanking otherwise we ignore it - // QualityRanking only applies to Representations within one AS. If we merged multiple AS based on the adaptation-set-switching-2016 supplemental property we can not apply this logic + return voRepresentations + } + + function _shouldSortByQualityRankingAttribute(voRepresentations) { let firstMediaInfo = null; const filteredRepresentations = voRepresentations.filter((rep) => { if (!firstMediaInfo) { @@ -460,19 +466,46 @@ function AbrController() { return !isNaN(rep.qualityRanking) && adapter.areMediaInfosEqual(firstMediaInfo, rep.mediaInfo); }) - if (filteredRepresentations.length === voRepresentations.length) { - voRepresentations.sort((a, b) => { - return b.qualityRanking - a.qualityRanking; - }) - } else { - voRepresentations.sort((a, b) => { - return a.bandwidth - b.bandwidth; - }) - } + return filteredRepresentations.length === voRepresentations.length + } + + function _sortByQualityRankingAttribute(voRepresentations) { + voRepresentations.sort((a, b) => { + return b.qualityRanking - a.qualityRanking; + }) + + return voRepresentations + } + + + function _sortByDefaultParameters(voRepresentations) { + voRepresentations.sort((a, b) => { + + // In case both Representations are coming from the same MediaInfo then choose the one with the highest resolution and highest bitrate + if (adapter.areMediaInfosEqual(a.mediaInfo, b.mediaInfo)) { + if (!isNaN(a.pixelsPerSecond) && !isNaN(b.pixelsPerSecond) && a.pixelsPerSecond !== b.pixelsPerSecond) { + return a.pixelsPerSecond - b.pixelsPerSecond + } else { + return a.bandwidth - b.bandwidth + } + } + + // In case the Representations are coming from different MediaInfos they might have different codecs. The bandwidth is not a good indicator, use bits per pixel instead + else { + if (!isNaN(a.pixelsPerSecond) && !isNaN(b.pixelsPerSecond) && a.pixelsPerSecond !== b.pixelsPerSecond) { + return a.pixelsPerSecond - b.pixelsPerSecond + } else if (!isNaN(a.bitsPerPixel) && !isNaN(b.bitsPerPixel)) { + return b.bitsPerPixel - a.bitsPerPixel + } else { + return a.bandwidth - b.bandwidth + } + } + }) return voRepresentations } + /** * While fragment loading is in progress we check if we might need to abort the request * @param {object} e @@ -496,6 +529,7 @@ function AbrController() { streamProcessor, currentRequest: e.request, throughputController, + adapter, videoModel }); const switchRequest = abrRulesCollection.shouldAbandonFragment(rulesContext); @@ -618,6 +652,7 @@ function AbrController() { switchRequestHistory, droppedFramesHistory, streamProcessor, + adapter, videoModel }); const switchRequest = abrRulesCollection.getBestPossibleSwitchRequest(rulesContext); diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index d924910f6b..0165eca30f 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -509,6 +509,9 @@ function BufferController(config) { // In any case we need to update the MSE.timeOffset return updateBufferTimestampOffset(voRepresentation) }) + .then(() => { + return changeType(voRepresentation) + }) .then(() => { setIsBufferingCompleted(false); resolve(); @@ -519,18 +522,59 @@ function BufferController(config) { }); } - function prepareForReplacementTrackSwitch(representation) { + function prepareForAbandonQualitySwitch(voRepresentation) { + return new Promise((resolve, reject) => { + updateBufferTimestampOffset(voRepresentation) + .then(() => { + return changeType(voRepresentation) + }) + .then(() => { + resolve() + }) + .catch((e) => { + reject(e); + }); + }); + } + + function prepareForFastQualitySwitch(voRepresentation) { + return new Promise((resolve, reject) => { + updateBufferTimestampOffset(voRepresentation) + .then(() => { + return changeType(voRepresentation) + }) + .then(() => { + resolve() + }) + .catch((e) => { + reject(e); + }); + }); + } + + function prepareForDefaultQualitySwitch(voRepresentation) { + return new Promise((resolve, reject) => { + updateBufferTimestampOffset(voRepresentation) + .then(() => { + return changeType(voRepresentation) + }) + .then(() => { + resolve() + }) + .catch((e) => { + reject(e); + }); + }); + } + + function prepareForReplacementTrackSwitch(selectedRepresentation) { return new Promise((resolve, reject) => { sourceBufferSink.abort() .then(() => { return updateAppendWindow(); }) .then(() => { - if (settings.get().streaming.buffer.useChangeTypeForTrackSwitch) { - return sourceBufferSink.changeType(representation); - } - - return Promise.resolve(); + return changeType(selectedRepresentation) }) .then(() => { return pruneAllSafely(); @@ -549,11 +593,7 @@ function BufferController(config) { return new Promise((resolve, reject) => { updateAppendWindow() .then(() => { - if (settings.get().streaming.buffer.useChangeTypeForTrackSwitch) { - return sourceBufferSink.changeType(selectedRepresentation); - } - - return Promise.resolve(); + return changeType(selectedRepresentation) }) .then(() => { resolve(); @@ -564,6 +604,13 @@ function BufferController(config) { }); } + function changeType(selectedRepresentation) { + if (settings.get().streaming.buffer.useChangeTypeForTrackSwitch) { + return sourceBufferSink.changeType(selectedRepresentation); + } + return Promise.resolve() + } + function pruneAllSafely() { return new Promise((resolve, reject) => { let ranges = getAllRangesWithSafetyFactor(); @@ -1237,6 +1284,7 @@ function BufferController(config) { instance = { appendInitSegmentFromCache, + changeType, clearBuffers, createBufferSink, dischargePreBuffer, @@ -1253,6 +1301,9 @@ function BufferController(config) { getType, hasBufferAtTime, initialize, + prepareForAbandonQualitySwitch, + prepareForDefaultQualitySwitch, + prepareForFastQualitySwitch, prepareForForceReplacementQualitySwitch, prepareForNonReplacementTrackSwitch, prepareForPlaybackSeek, diff --git a/src/streaming/controllers/MediaController.js b/src/streaming/controllers/MediaController.js index dcb53698d2..d8715aadda 100644 --- a/src/streaming/controllers/MediaController.js +++ b/src/streaming/controllers/MediaController.js @@ -767,6 +767,10 @@ function MediaController() { function _trackSelectionModeHighestSelectionPriority(tracks) { let tmpArr = getTracksWithHighestSelectionPriority(tracks); + if (tmpArr.length > 1) { + tmpArr = getTracksWithHighestEfficiency(tmpArr); + } + if (tmpArr.length > 1) { tmpArr = getTracksWithHighestBitrate(tmpArr); } diff --git a/src/streaming/rules/SwitchRequestHistory.js b/src/streaming/rules/SwitchRequestHistory.js index 4557858631..376d84dc32 100644 --- a/src/streaming/rules/SwitchRequestHistory.js +++ b/src/streaming/rules/SwitchRequestHistory.js @@ -42,8 +42,8 @@ function SwitchRequestHistory() { const currentRepresentation = switchRequest.currentRepresentation; const newRepresentation = switchRequest.newRepresentation; - // Don`t compare quality switches between different periods - if (currentRepresentation.mediaInfo.streamInfo.id !== newRepresentation.mediaInfo.streamInfo.id) { + // Don`t compare quality switches between different periods or different AdaptationSets + if (currentRepresentation.mediaInfo.streamInfo.id !== newRepresentation.mediaInfo.streamInfo.id || newRepresentation.mediaInfo.id !== currentRepresentation.mediaInfo.id) { return; } diff --git a/src/streaming/rules/abr/InsufficientBufferRule.js b/src/streaming/rules/abr/InsufficientBufferRule.js index d3a3559447..e6e863a5ee 100644 --- a/src/streaming/rules/abr/InsufficientBufferRule.js +++ b/src/streaming/rules/abr/InsufficientBufferRule.js @@ -102,6 +102,10 @@ function InsufficientBufferRule(config) { const safeThroughput = throughput * settings.get().streaming.abr.rules.insufficientBufferRule.parameters.throughputSafetyFactor; const bitrate = safeThroughput * bufferLevel / fragmentDuration + if (isNaN(bitrate) || bitrate <= 0) { + return switchRequest + } + switchRequest.representation = abrController.getOptimalRepresentationForBitrate(mediaInfo, bitrate, true); switchRequest.reason = { message: '[InsufficientBufferRule]: Limiting maximum bitrate to avoid a buffer underrun.', diff --git a/src/streaming/utils/CapabilitiesFilter.js b/src/streaming/utils/CapabilitiesFilter.js index 182bf68416..96245bec43 100644 --- a/src/streaming/utils/CapabilitiesFilter.js +++ b/src/streaming/utils/CapabilitiesFilter.js @@ -80,12 +80,12 @@ function CapabilitiesFilter() { function _filterUnsupportedAdaptationSetsOfPeriod(period, type) { return new Promise((resolve) => { - + if (!period || !period.AdaptationSet || period.AdaptationSet.length === 0) { resolve(); return; } - + const promises = []; period.AdaptationSet.forEach((as) => { if (adapter.getIsTypeOf(as, type)) { @@ -176,34 +176,26 @@ function CapabilitiesFilter() { // translate ColourPrimaries signaling into capability queries if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['1', '5', '6', '7'].includes(prop.value.toString())) { cfg.colorGamut = Constants.MEDIA_CAPABILITIES_API.COLORGAMUT.SRGB; - } - else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['11', '12'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['11', '12'].includes(prop.value.toString())) { cfg.colorGamut = Constants.MEDIA_CAPABILITIES_API.COLORGAMUT.P3; - } - else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['9'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['9'].includes(prop.value.toString())) { cfg.colorGamut = Constants.MEDIA_CAPABILITIES_API.COLORGAMUT.REC2020; - } - else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['2'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI && ['2'].includes(prop.value.toString())) { cfg.colorGamut = null; - } - else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI) { + } else if (prop.schemeIdUri === Constants.COLOUR_PRIMARIES_SCHEME_ID_URI) { cfg.isSupported = false; } // translate TransferCharacteristics signaling into capability queries if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['1', '6', '13', '14', '15'].includes(prop.value.toString())) { cfg.transferFunction = Constants.MEDIA_CAPABILITIES_API.TRANSFERFUNCTION.SRGB; - } - else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['16'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['16'].includes(prop.value.toString())) { cfg.transferFunction = Constants.MEDIA_CAPABILITIES_API.TRANSFERFUNCTION.PQ; - } - else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['18'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['18'].includes(prop.value.toString())) { cfg.transferFunction = Constants.MEDIA_CAPABILITIES_API.TRANSFERFUNCTION.HLG; - } - else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['2'].includes(prop.value.toString())) { + } else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI && ['2'].includes(prop.value.toString())) { cfg.transferFunction = null; - } - else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI) { + } else if (prop.schemeIdUri === Constants.TRANSFER_CHARACTERISTICS_SCHEME_ID_URI) { cfg.isSupported = false; } } @@ -219,16 +211,13 @@ function CapabilitiesFilter() { for (const prop of representation.EssentialProperty || []) { // translate hdrMetadataType signaling into capability queries - if (prop.schemeIdUri == Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.ST2094_10) { + if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.ST2094_10) { cfg.hdrMetadataType = Constants.MEDIA_CAPABILITIES_API.HDR_METADATATYPE.SMPTE_ST_2094_10; - } - else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.SL_HDR2) { + } else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.SL_HDR2) { cfg.hdrMetadataType = Constants.MEDIA_CAPABILITIES_API.HDR_METADATATYPE.SLHDR2; // Note: This is not specified by W3C - } - else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.ST2094_40) { + } else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI && prop.value === Constants.HDR_METADATA_FORMAT_VALUES.ST2094_40) { cfg.hdrMetadataType = Constants.MEDIA_CAPABILITIES_API.HDR_METADATATYPE.SMPTE_ST_2094_40; - } - else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI) { + } else if (prop.schemeIdUri === Constants.HDR_METADATA_FORMAT_SCHEME_ID_URI) { cfg.isSupported = false; } } @@ -242,13 +231,14 @@ function CapabilitiesFilter() { width: rep.width || null, height: rep.height || null, framerate: rep.frameRate || null, - bitrate: rep.bandwidth || null + bitrate: rep.bandwidth || null, + isSupported: true } if (settings.get().streaming.capabilities.filterVideoColorimetryEssentialProperties) { Object.assign(config, _convertHDRColorimetryToConfig(rep)); } let colorimetrySupported = config.isSupported; - + if (settings.get().streaming.capabilities.filterHDRMetadataFormatEssentialProperties) { Object.assign(config, _convertHDRMetadataFormatToConfig(rep)); } @@ -268,7 +258,8 @@ function CapabilitiesFilter() { return { codec, bitrate, - samplerate + samplerate, + isSupported: true }; } diff --git a/test/functional/config/karma.functional.conf.cjs b/test/functional/config/karma.functional.conf.cjs index 41799b964b..a883000397 100644 --- a/test/functional/config/karma.functional.conf.cjs +++ b/test/functional/config/karma.functional.conf.cjs @@ -110,7 +110,7 @@ module.exports = function (config) { client: { useIframe: false, mocha: { - timeout: 120000 + timeout: 90000 }, testvectors }, @@ -127,7 +127,8 @@ module.exports = function (config) { // enable / disable watching file and executing tests whenever any file changes autoWatch: false, - browserNoActivityTimeout: 120000, + captureTimeout: 600000, + browserNoActivityTimeout: 180000, browserDisconnectTimeout: 20000, browserDisconnectTolerance: 2, diff --git a/test/functional/config/test-configurations/lambdatest-smoke.json b/test/functional/config/test-configurations/lambdatest-smoke.json index 6174bdadef..5ada8e06af 100644 --- a/test/functional/config/test-configurations/lambdatest-smoke.json +++ b/test/functional/config/test-configurations/lambdatest-smoke.json @@ -1,10 +1,10 @@ { "type": "lambdatest", "browsers": [ - "chrome_windows_11" + "edge_windows_11" ], "hostname": "localhost", - "port": 9876, + "port": 9877, "protocol": "http", "customLaunchers": { "chrome_windows_11": { @@ -16,7 +16,24 @@ "console": true, "terminal": true, "network": true, - "pseudoActivityInterval": 5000, + "pseudoActivityInterval": 30000, + "tunnel": true, + "flags": [ + "--disable-web-security", + "--autoplay-policy=no-user-gesture-required", + "--disable-popup-blocking" + ] + }, + "edge_windows_11": { + "base": "WebDriver", + "browserName": "edge", + "platform": "windows 11", + "version": "latest", + "name": "Edge Win 11", + "console": true, + "terminal": true, + "network": true, + "pseudoActivityInterval": 30000, "tunnel": true } }, diff --git a/test/functional/config/test-configurations/streams/smoke.json b/test/functional/config/test-configurations/streams/smoke.json index a2b45a3bc7..8b7fd4e531 100644 --- a/test/functional/config/test-configurations/streams/smoke.json +++ b/test/functional/config/test-configurations/streams/smoke.json @@ -9,28 +9,39 @@ }, "testvectors": [ { - "name": "1080p with PlayReady and Widevine DRM, single key", + "name": "MSS", + "type": "vod", + "url": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest", + "includedTestfiles": [ + "playback/play", + "playback/pause", + "playback/seek", + "playback/seek-to-presentation-time" + ] + }, + { + "name": "Single-period, 1080p, H.264, 5 video, 3 audio, 3 text tracks, CMAF, cbcs encryption, single key, Widevine+PlayReady", "type": "vod", - "url": "https://media.axprod.net/TestVectors/v7-MultiDRM-SingleKey/Manifest_1080p.mpd", + "url": "https://media.axprod.net/TestVectors/Dash/protected_dash_1080p_h264_singlekey/manifest.mpd", "drm": { "com.widevine.alpha": { - "serverURL": "https://drm-widevine-licensing.axtest.net/AcquireLicense", + "serverURL": "https://drm-widevine-licensing.axprod.net/AcquireLicense", "httpRequestHeaders": { - "X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA" + "X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICI0MDYwYTg2NS04ODc4LTQyNjctOWNiZi05MWFlNWJhZTFlNzIiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAid3QzRW51dVI1UkFybjZBRGYxNkNCQT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ.l8PnZznspJ6lnNmfAE9UQV532Ypzt1JXQkvrk8gFSRw" }, "httpTimeout": 5000 }, "com.microsoft.playready": { - "serverURL": "https://drm-playready-licensing.axtest.net/AcquireLicense", + "serverURL": "https://drm-playready-licensing.axprod.net/AcquireLicense", "httpRequestHeaders": { - "X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ2ZXJzaW9uIjoxLCJjb21fa2V5X2lkIjoiYjMzNjRlYjUtNTFmNi00YWUzLThjOTgtMzNjZWQ1ZTMxYzc4IiwibWVzc2FnZSI6eyJ0eXBlIjoiZW50aXRsZW1lbnRfbWVzc2FnZSIsImtleXMiOlt7ImlkIjoiOWViNDA1MGQtZTQ0Yi00ODAyLTkzMmUtMjdkNzUwODNlMjY2IiwiZW5jcnlwdGVkX2tleSI6ImxLM09qSExZVzI0Y3Iya3RSNzRmbnc9PSJ9XX19.4lWwW46k-oWcah8oN18LPj5OLS5ZU-_AQv7fe0JhNjA" + "X-AxDRM-Message": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICI0MDYwYTg2NS04ODc4LTQyNjctOWNiZi05MWFlNWJhZTFlNzIiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAid3QzRW51dVI1UkFybjZBRGYxNkNCQT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ.l8PnZznspJ6lnNmfAE9UQV532Ypzt1JXQkvrk8gFSRw" }, "httpTimeout": 5000 } }, "includedTestfiles": [ "playback/*", - "audio/*", + "audio/initial-audio", "text/*" ] }, @@ -112,17 +123,6 @@ "playback/seek-to-presentation-time" ] }, - { - "name": "MSS", - "type": "vod", - "url": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest", - "includedTestfiles": [ - "playback/play", - "playback/pause", - "playback/seek", - "playback/seek-to-presentation-time" - ] - }, { "name": "DASH-IF Multiperiod Segment Template", "type": "live", diff --git a/test/functional/test/audio/initial-audio.js b/test/functional/test/audio/initial-audio.js index 87e0ae6d83..1841e39ec7 100644 --- a/test/functional/test/audio/initial-audio.js +++ b/test/functional/test/audio/initial-audio.js @@ -42,11 +42,13 @@ Utils.getTestvectorsForTestcase(TESTCASE).forEach((item) => { playerAdapter.destroy(); playerAdapter = initializeDashJsAdapter(item, mpd) playerAdapter.setInitialMediaSettingsFor(Constants.DASH_JS.MEDIA_TYPES.AUDIO, { - lang: track.lang + lang: track.lang, + index: track.index }) await checkIsProgressing(playerAdapter); const currentTrack = playerAdapter.getCurrentTrackFor(Constants.DASH_JS.MEDIA_TYPES.AUDIO); expect(currentTrack.lang).to.be.equal(track.lang); + expect(currentTrack.index).to.be.equal(track.index); } }); diff --git a/test/unit/test/streaming/streaming.controllers.AbrController.js b/test/unit/test/streaming/streaming.controllers.AbrController.js index 64c293a084..13edf9816a 100644 --- a/test/unit/test/streaming/streaming.controllers.AbrController.js +++ b/test/unit/test/streaming/streaming.controllers.AbrController.js @@ -74,290 +74,611 @@ describe('AbrController', function () { eventBus.reset(); }); - it('should return null when attempting to get abandonment state when abandonmentStateDict array is empty', function () { - const state = abrCtrl.getAbandonmentStateFor('1', Constants.AUDIO); - expect(state).to.be.null; - }); + describe('getOptimalRepresentationForBitrate()', function () { + + it('Should return Representation with lowest bitrate when 0 target bitrate provided', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 0); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('Should return Representation with lowest bitrate when all Representations have a higher bitrate than the target bitrate', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 4); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('Should return Representation with suitable bitrate if only one option matches', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 8); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('Should return Representation with suitable bitrate if both options match', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(1); + }) + + it('Should return the Representation with the highest absolute index defined by qualityRanking', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1, + qualityRanking: 1 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + qualityRanking: 0 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('Should return the Representation with the highest pixels per second for same MediaInfo', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1, + pixelsPerSecond: 100 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + pixelsPerSecond: 200 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('Should return the Representation with the highest bandwidth per second for same pixels per second and same MediaInfo ', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1, + pixelsPerSecond: 200 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + pixelsPerSecond: 200 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(1); + }) + + it('Should return the Representation with the highest pixels per second for different MediaInfo', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 10000, + mediaInfo, + id: 1, + pixelsPerSecond: 100 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + pixelsPerSecond: 200 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return false + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + + it('Should return the Representation with the lowest bits per pixel for same pixels per second and different MediaInfo ', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 2000, + mediaInfo, + id: 1, + bitsPerPixel: 10, + pixelsPerSecond: 200 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + bitsPerPixel: 20, + pixelsPerSecond: 200 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return false + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(1); + }) + + it('Should return the Representation with the highest bandwidth when having the same bits per pixel and same pixels per second and different MediaInfo ', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: 10, + bandwidth: 2000, + mediaInfo, + id: 1, + bitsPerPixel: 10, + pixelsPerSecond: 200 + }, + { + bitrateInKbit: 5, + bandwidth: 5000, + mediaInfo, + id: 3, + bitsPerPixel: 10, + pixelsPerSecond: 200 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return false + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, 15); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }) + + it('should return the right Representation in case only bitrate is given', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + const bitrateList = mediaInfo.bitrateList; + + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: bitrateList[0].bandwidth / 1000, + bandwidth: bitrateList[0].bandwidth, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: bitrateList[1].bandwidth / 1000, + bandwidth: bitrateList[1].bandwidth, + mediaInfo, + id: 2 + }, + { + bitrateInKbit: bitrateList[2].bandwidth / 1000, + bandwidth: bitrateList[2].bandwidth, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, bitrateList[2].bandwidth / 1000); + expect(optimalRepresentationForBitrate.id).to.be.equal(3); + }); + }) - it('should return null when calling getQualityForBitrate with no mediaInfo', function () { - const quality = abrCtrl.getOptimalRepresentationForBitrate(undefined, undefined, true); - expect(quality).to.not.exist; - }); + describe('Additional Tests', function () { + it('should return null when attempting to get abandonment state when abandonmentStateDict array is empty', function () { + const state = abrCtrl.getAbandonmentStateFor('1', Constants.AUDIO); + expect(state).to.be.null; + }); - it('should return true if isPlayingAtTopQuality function is called without parameter', function () { - let isPlayingTopQuality = abrCtrl.isPlayingAtTopQuality(); - expect(isPlayingTopQuality).to.be.true; - }); + it('should return null when calling getQualityForBitrate with no mediaInfo', function () { + const quality = abrCtrl.getOptimalRepresentationForBitrate(undefined, undefined, true); + expect(quality).to.not.exist; + }); - it('should switch to a new Representation', function (done) { - const onQualityChange = (e) => { - expect(e.oldRepresentation).to.not.exist; - expect(e.newRepresentation.id).to.be.equal(dummyRepresentations[0].id) - eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange) - done() - } + it('should return true if isPlayingAtTopQuality function is called without parameter', function () { + let isPlayingTopQuality = abrCtrl.isPlayingAtTopQuality(); + expect(isPlayingTopQuality).to.be.true; + }); - eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange, this); + it('should switch to a new Representation', function (done) { + const onQualityChange = (e) => { + expect(e.oldRepresentation).to.not.exist; + expect(e.newRepresentation.id).to.be.equal(dummyRepresentations[0].id) + eventBus.off(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange) + done() + } - abrCtrl.setPlaybackQuality(Constants.VIDEO, dummyMediaInfo.streamInfo, dummyRepresentations[0]); - }); + eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, onQualityChange, this); - it('should ignore an attempt to set a quality value if no streamInfo is provided', function () { - const spy = sinon.spy(); + abrCtrl.setPlaybackQuality(Constants.VIDEO, dummyMediaInfo.streamInfo, dummyRepresentations[0]); + }); - assert.equal(spy.notCalled, true); - eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, spy, this); - abrCtrl.setPlaybackQuality(Constants.VIDEO, null, dummyRepresentations[0]); - }); + it('should ignore an attempt to set a quality value if no streamInfo is provided', function () { + const spy = sinon.spy(); - it('should ignore an attempt to set a quality value if no Representation is provided', function () { - const spy = sinon.spy(); + assert.equal(spy.notCalled, true); + eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, spy, this); + abrCtrl.setPlaybackQuality(Constants.VIDEO, null, dummyRepresentations[0]); + }); - assert.equal(spy.notCalled, true); - eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, spy, this); - abrCtrl.setPlaybackQuality(Constants.VIDEO, dummyMediaInfo.streamInfo, null); - }); + it('should ignore an attempt to set a quality value if no Representation is provided', function () { + const spy = sinon.spy(); - it('should return the right Representations for maxBitrate values', function () { - const mediaInfo = streamProcessor.getMediaInfo(); - const bitrateList = mediaInfo.bitrateList; - - adapterMock.getVoRepresentations = () => { - return [ - { - bitrateInKbit: bitrateList[0].bandwidth / 1000, - mediaInfo, - id: 1 - }, - { - bitrateInKbit: bitrateList[1].bandwidth / 1000, - mediaInfo, - id: 2 - }, - { - bitrateInKbit: bitrateList[2].bandwidth / 1000, - mediaInfo, - id: 3 - } - ] - } - - adapterMock.areMediaInfosEqual = () => { - return true - } - - mediaInfo.streamInfo = streamProcessor.getStreamInfo(); - mediaInfo.type = Constants.VIDEO; - - // Max allowed bitrate in kbps, bandwidth is in bps - const s = { streaming: { abr: { maxBitrate: {} } } }; - s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[0].bandwidth / 1000; - settings.update(s); - let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo, false); - expect(possibleVoRepresentations.length).to.be.equal(1); - expect(possibleVoRepresentations[0].id).to.be.equal(1); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[1].bandwidth / 1000; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(2); - expect(possibleVoRepresentations[1].id).to.be.equal(2); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[2].bandwidth / 1000; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - expect(possibleVoRepresentations[2].id).to.be.equal(3); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(1); - expect(possibleVoRepresentations[0].id).to.be.equal(1); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[1].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(2); - expect(possibleVoRepresentations[1].id).to.be.equal(2); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[2].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - expect(possibleVoRepresentations[2].id).to.be.equal(3); - - s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) - 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - expect(possibleVoRepresentations[2].id).to.be.equal(3); - }); - - it('should return the right Representations for minBitrate values', function () { - const mediaInfo = streamProcessor.getMediaInfo(); - const bitrateList = mediaInfo.bitrateList; - - adapterMock.getVoRepresentations = () => { - return [ - { - bitrateInKbit: bitrateList[0].bandwidth / 1000, - mediaInfo, - id: 1 - }, - { - bitrateInKbit: bitrateList[1].bandwidth / 1000, - mediaInfo, - id: 2 - }, - { - bitrateInKbit: bitrateList[2].bandwidth / 1000, - mediaInfo, - id: 3 - } - ] - } - - adapterMock.areMediaInfosEqual = () => { - return true - } - - mediaInfo.streamInfo = streamProcessor.getStreamInfo(); - mediaInfo.type = Constants.VIDEO; - - // Min allowed bitrate in kbps, bandwidth is in bps - const s = { streaming: { abr: { minBitrate: {} } } }; - s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[0].bandwidth / 1000; - settings.update(s); - let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - - s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[1].bandwidth / 1000; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(2); - - s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[2].bandwidth / 1000; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(1); - - s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(2); - - s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[1].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(1); - - s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[2].bandwidth / 1000) + 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - - s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) - 1; - settings.update(s); - possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(3); - }); + assert.equal(spy.notCalled, true); + eventBus.on(MediaPlayerEvents.QUALITY_CHANGE_REQUESTED, spy, this); + abrCtrl.setPlaybackQuality(Constants.VIDEO, dummyMediaInfo.streamInfo, null); + }); - it('should configure initial bitrate for video type', function () { - domStorageMock.setSavedBitrateSettings(Constants.VIDEO, 50); + it('should return the right Representations for maxBitrate values', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + const bitrateList = mediaInfo.bitrateList; + + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: bitrateList[0].bandwidth / 1000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: bitrateList[1].bandwidth / 1000, + mediaInfo, + id: 2 + }, + { + bitrateInKbit: bitrateList[2].bandwidth / 1000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + // Max allowed bitrate in kbps, bandwidth is in bps + const s = { streaming: { abr: { maxBitrate: {} } } }; + s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[0].bandwidth / 1000; + settings.update(s); + let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo, false); + expect(possibleVoRepresentations.length).to.be.equal(1); + expect(possibleVoRepresentations[0].id).to.be.equal(1); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[1].bandwidth / 1000; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(2); + expect(possibleVoRepresentations[1].id).to.be.equal(2); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = bitrateList[2].bandwidth / 1000; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + expect(possibleVoRepresentations[2].id).to.be.equal(3); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(1); + expect(possibleVoRepresentations[0].id).to.be.equal(1); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[1].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(2); + expect(possibleVoRepresentations[1].id).to.be.equal(2); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[2].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + expect(possibleVoRepresentations[2].id).to.be.equal(3); + + s.streaming.abr.maxBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) - 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + expect(possibleVoRepresentations[2].id).to.be.equal(3); + }); - let initialBitrateFor = abrCtrl.getInitialBitrateFor(Constants.VIDEO); - expect(initialBitrateFor).to.equal(50); - }); + it('should return the right Representations for minBitrate values', function () { + const mediaInfo = streamProcessor.getMediaInfo(); + const bitrateList = mediaInfo.bitrateList; + + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: bitrateList[0].bandwidth / 1000, + mediaInfo, + id: 1 + }, + { + bitrateInKbit: bitrateList[1].bandwidth / 1000, + mediaInfo, + id: 2 + }, + { + bitrateInKbit: bitrateList[2].bandwidth / 1000, + mediaInfo, + id: 3 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + // Min allowed bitrate in kbps, bandwidth is in bps + const s = { streaming: { abr: { minBitrate: {} } } }; + s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[0].bandwidth / 1000; + settings.update(s); + let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + + s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[1].bandwidth / 1000; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(2); + + s.streaming.abr.minBitrate[Constants.VIDEO] = bitrateList[2].bandwidth / 1000; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(1); + + s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(2); + + s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[1].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(1); + + s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[2].bandwidth / 1000) + 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + + s.streaming.abr.minBitrate[Constants.VIDEO] = (bitrateList[0].bandwidth / 1000) - 1; + settings.update(s); + possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(3); + }); - it('should configure initial bitrate for text type', function () { - let initialBitrateFor = abrCtrl.getInitialBitrateFor(Constants.TEXT); - expect(initialBitrateFor).to.be.NaN; - }); + it('should configure initial bitrate for video type', function () { + domStorageMock.setSavedBitrateSettings(Constants.VIDEO, 50); - it('should return the appropriate possible Representations if limitBitrateByPortal is enabled', function () { - videoModelMock.getVideoElementSize = () => { - return { elementWidth: 800 } - }; - const s = { streaming: { abr: { limitBitrateByPortal: true } } }; - settings.update(s); - - const mediaInfo = streamProcessor.getMediaInfo(); - const bitrateList = mediaInfo.bitrateList; - - adapterMock.getVoRepresentations = () => { - return [ - { - bitrateInKbit: bitrateList[0].bandwidth / 1000, - bandwidth: bitrateList[0].bandwidth, - mediaInfo, - id: 1, - width: 640 - }, - { - bitrateInKbit: bitrateList[1].bandwidth / 1000, - bandwidth: bitrateList[1].bandwidth, - mediaInfo, - id: 2, - width: 720 - }, - { - bitrateInKbit: bitrateList[2].bandwidth / 1000, - bandwidth: bitrateList[2].bandwidth, - mediaInfo, - id: 3, - width: 1920 - } - ] - } - - adapterMock.areMediaInfosEqual = () => { - return true - } - - mediaInfo.streamInfo = streamProcessor.getStreamInfo(); - mediaInfo.type = Constants.VIDEO; - - let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); - expect(possibleVoRepresentations.length).to.be.equal(2); - }); + let initialBitrateFor = abrCtrl.getInitialBitrateFor(Constants.VIDEO); + expect(initialBitrateFor).to.equal(50); + }); - it('should return an appropriate Representation when calling getOptimalRepresentationForBitrate', function () { - const mediaInfo = streamProcessor.getMediaInfo(); - const bitrateList = mediaInfo.bitrateList; - - adapterMock.getVoRepresentations = () => { - return [ - { - bitrateInKbit: bitrateList[0].bandwidth / 1000, - bandwidth: bitrateList[0].bandwidth, - mediaInfo, - id: 1 - }, - { - bitrateInKbit: bitrateList[1].bandwidth / 1000, - bandwidth: bitrateList[1].bandwidth, - mediaInfo, - id: 2 - }, - { - bitrateInKbit: bitrateList[2].bandwidth / 1000, - bandwidth: bitrateList[2].bandwidth, - mediaInfo, - id: 3 - } - ] - } - - adapterMock.areMediaInfosEqual = () => { - return true - } - - mediaInfo.streamInfo = streamProcessor.getStreamInfo(); - mediaInfo.type = Constants.VIDEO; - - let optimalRepresentationForBitrate = abrCtrl.getOptimalRepresentationForBitrate(mediaInfo, bitrateList[2].bandwidth / 1000); - expect(optimalRepresentationForBitrate.id).to.be.equal(3); - }); + it('should configure initial bitrate for text type', function () { + let initialBitrateFor = abrCtrl.getInitialBitrateFor(Constants.TEXT); + expect(initialBitrateFor).to.be.NaN; + }); + it('should return the appropriate possible Representations if limitBitrateByPortal is enabled', function () { + videoModelMock.getVideoElementSize = () => { + return { elementWidth: 800 } + }; + const s = { streaming: { abr: { limitBitrateByPortal: true } } }; + settings.update(s); + + const mediaInfo = streamProcessor.getMediaInfo(); + const bitrateList = mediaInfo.bitrateList; + + adapterMock.getVoRepresentations = () => { + return [ + { + bitrateInKbit: bitrateList[0].bandwidth / 1000, + bandwidth: bitrateList[0].bandwidth, + mediaInfo, + id: 1, + width: 640 + }, + { + bitrateInKbit: bitrateList[1].bandwidth / 1000, + bandwidth: bitrateList[1].bandwidth, + mediaInfo, + id: 2, + width: 720 + }, + { + bitrateInKbit: bitrateList[2].bandwidth / 1000, + bandwidth: bitrateList[2].bandwidth, + mediaInfo, + id: 3, + width: 1920 + } + ] + } + + adapterMock.areMediaInfosEqual = () => { + return true + } + + mediaInfo.streamInfo = streamProcessor.getStreamInfo(); + mediaInfo.type = Constants.VIDEO; + + let possibleVoRepresentations = abrCtrl.getPossibleVoRepresentationsFilteredBySettings(mediaInfo); + expect(possibleVoRepresentations.length).to.be.equal(2); + }); + }) }); diff --git a/test/unit/test/streaming/streaming.utils.Capabilities.js b/test/unit/test/streaming/streaming.utils.Capabilities.js index 82ca45db60..54880a07ec 100644 --- a/test/unit/test/streaming/streaming.utils.Capabilities.js +++ b/test/unit/test/streaming/streaming.utils.Capabilities.js @@ -2,7 +2,7 @@ import Capabilities from '../../../../src/streaming/utils/Capabilities.js'; import Settings from '../../../../src/core/Settings.js'; import DescriptorType from '../../../../src/dash/vo/DescriptorType.js'; -import { expect } from 'chai'; +import {expect} from 'chai'; let settings; let capabilities; @@ -78,6 +78,7 @@ describe('Capabilities', function () { capabilities.setConfig({ settings: settings }); + settings.reset(); }); describe('supports EssentialProperty', function () { @@ -181,11 +182,17 @@ describe('Capabilities', function () { props.push(...[{ schemeIdUri: 'tag:dashif.org:scheme:value:test' }]); settings.update({ streaming: { capabilities: { supportedEssentialProperties: props } } }) - let res = capabilities.supportsEssentialProperty({ schemeIdUri: 'tag:dashif.org:scheme:value:test', value: '' }); + let res = capabilities.supportsEssentialProperty({ + schemeIdUri: 'tag:dashif.org:scheme:value:test', + value: '' + }); expect(res).to.be.true; res = capabilities.supportsEssentialProperty({ schemeIdUri: 'tag:dashif.org:scheme:value:test' }); expect(res).to.be.true; - res = capabilities.supportsEssentialProperty({ schemeIdUri: 'tag:dashif.org:scheme:value:test', value: '5' }); + res = capabilities.supportsEssentialProperty({ + schemeIdUri: 'tag:dashif.org:scheme:value:test', + value: '5' + }); expect(res).to.be.true; }); @@ -193,32 +200,149 @@ describe('Capabilities', function () { let res = capabilities.supportsEssentialProperty(EssentialPropertyHDR); expect(res).to.be.false; - settings.update({ streaming: { capabilities: { useMediaCapabilitiesApi: true, filterVideoColorimetryEssentialProperties:true } } }); + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true, + filterVideoColorimetryEssentialProperties: true + } + } + }); res = capabilities.supportsEssentialProperty(EssentialPropertyHDR); expect(res).to.be.true; }); - + it('should return true if MediaCapabilities-check is enabled, even if value is unknown for HDR-Format schemeIdUri', function () { let res = capabilities.supportsEssentialProperty(EssentialPropertyHDRFormat); expect(res).to.be.false; - - settings.update({ streaming: { capabilities: { useMediaCapabilitiesApi: true, filterVideoColorimetryEssentialProperties:true } } }); + + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true, + filterVideoColorimetryEssentialProperties: true + } + } + }); res = capabilities.supportsEssentialProperty(EssentialPropertyHDRFormat); expect(res).to.be.false; - - settings.update({ streaming: { capabilities: { useMediaCapabilitiesApi: true, filterHDRMetadataFormatEssentialProperties:true } } }); + + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true, + filterHDRMetadataFormatEssentialProperties: true + } + } + }); res = capabilities.supportsEssentialProperty(EssentialPropertyHDRFormat); expect(res).to.be.true; }); - + it('should return true for unspecified TransferFunction if MediaCapabilities-check is enabled', function () { let res = capabilities.supportsEssentialProperty(EssentialPropertyPrivateTransferFunction); expect(res).to.be.false; - - settings.update({ streaming: { capabilities: { useMediaCapabilitiesApi: true, filterVideoColorimetryEssentialProperties:true } } }); + + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true, + filterVideoColorimetryEssentialProperties: true + } + } + }); res = capabilities.supportsEssentialProperty(EssentialPropertyPrivateTransferFunction); expect(res).to.be.true; }); }); + + describe('supportsCodec', function () { + it('should return true for supported codec using MediaSource.isTypeSupported', function (done) { + const config = { + codec: 'video/mp4;codecs="avc1.64001f"', + width: 320, + height: 180 + } + + capabilities.supportsCodec(config, 'video') + .then((result) => { + expect(result).to.be.true + done() + }) + .catch((e) => { + done(e) + }) + }) + + it('should filter unsupported codec using MediaSource.isTypeSupported', function (done) { + const config = { + codec: 'video/mp4;codecs="vvvvc1.64001f"', + width: 320, + height: 180 + } + + capabilities.supportsCodec(config, 'video') + .then((result) => { + expect(result).to.be.false + done() + }) + .catch((e) => { + done(e) + }) + }) + + /* + it('should return true for supported codec using the MediaCapabilitiesAPI', function (done) { + const config = { + codec: 'video/mp4;codecs="avc1.64001f"', + width: 320, + height: 180, + bitrate: 5000, + framerate: 25 + } + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true + } + } + }) + capabilities.supportsCodec(config, 'video') + .then((result) => { + expect(result).to.be.true + done() + }) + .catch((e) => { + done(e) + }) + }) + */ + + it('should filter unsupported codec using MediaSource.isTypeSupported', function (done) { + const config = { + codec: 'video/mp4;codecs="vvvvc1.64001f"', + width: 320, + height: 180, + bitrate: 5000, + framerate: 25 + } + + settings.update({ + streaming: { + capabilities: { + useMediaCapabilitiesApi: true + } + } + }) + capabilities.supportsCodec(config, 'video') + .then((result) => { + expect(result).to.be.false + done() + }) + .catch((e) => { + done(e) + }) + }) + }) });