From cc2c7abe1ecaa1f0ba85421198ff38a50825ce89 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 11:22:37 +0200 Subject: [PATCH 01/16] setup function calls reset to initialize default attributes value --- .../controllers/ScheduleController.js | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/streaming/controllers/ScheduleController.js b/src/streaming/controllers/ScheduleController.js index 70ecb91e3b..3354250e27 100644 --- a/src/streaming/controllers/ScheduleController.js +++ b/src/streaming/controllers/ScheduleController.js @@ -83,18 +83,8 @@ function ScheduleController(config) { function setup() { log = Debug(context).getInstance().log.bind(instance); - initialRequest = true; - lastInitQuality = NaN; - lastQualityIndex = NaN; - topQualityIndex = {}; - replaceRequestArray = []; - isStopped = true; - playListMetrics = null; - playListTraceMetrics = null; - playListTraceMetricsClosed = true; - isFragmentProcessingInProgress = false; - timeToLoadDelay = 0; - seekTarget = NaN; + + reset(); } @@ -318,7 +308,7 @@ function ScheduleController(config) { } function completeQualityChange(trigger) { - if (playbackController) { + if (playbackController && fragmentModel) { const item = fragmentModel.getRequests({ state: FragmentModel.FRAGMENT_MODEL_EXECUTED, time: playbackController.getTime(), @@ -614,6 +604,14 @@ function ScheduleController(config) { timeToLoadDelay = 0; seekTarget = NaN; playListMetrics = null; + playListTraceMetrics = null; + playListTraceMetricsClosed = true; + initialRequest = true; + lastInitQuality = NaN; + lastQualityIndex = NaN; + topQualityIndex = {}; + replaceRequestArray = []; + isStopped = true; } instance = { From a4a7d892bd0adcaa2510c2d8a372792678cc0004 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 17:08:23 +0200 Subject: [PATCH 02/16] setup function calls reset to initialize default attributes value --- src/streaming/text/TextController.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/streaming/text/TextController.js b/src/streaming/text/TextController.js index 04a17d1986..f3f7e2c5eb 100644 --- a/src/streaming/text/TextController.js +++ b/src/streaming/text/TextController.js @@ -60,7 +60,8 @@ function TextController() { textSourceBuffer = TextSourceBuffer(context).getInstance(); textTracks.initialize(); - allTracksAreDisabled = false; + + reset(); } function setConfig(config) { From c5b298e7fb5c7aa3cb752cff0dd3e46c2ab058c8 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 17:22:27 +0200 Subject: [PATCH 03/16] setup function calls reset to initialize default attributes value --- src/streaming/controllers/AbrController.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/streaming/controllers/AbrController.js b/src/streaming/controllers/AbrController.js index ebe7d7e531..c25851004f 100644 --- a/src/streaming/controllers/AbrController.js +++ b/src/streaming/controllers/AbrController.js @@ -90,19 +90,7 @@ function AbrController() { function setup() { log = debug.log.bind(instance); - autoSwitchBitrate = {video: true, audio: true}; - topQualities = {}; - qualityDict = {}; - bitrateDict = {}; - ratioDict = {}; - abandonmentStateDict = {}; - streamProcessorDict = {}; - switchHistoryDict = {}; - limitBitrateByPortal = false; - usePixelRatioInLimitBitrateByPortal = false; - if (windowResizeEventCalled === undefined) { - windowResizeEventCalled = false; - } + reset(); } @@ -135,7 +123,6 @@ function AbrController() { } function reset() { - log = debug.log.bind(instance); autoSwitchBitrate = {video: true, audio: true}; topQualities = {}; qualityDict = {}; From 59427825077cd7b24fd06d7a63b430254495facd Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 17:35:19 +0200 Subject: [PATCH 04/16] setup function calls reset to initialize default attributes value --- src/streaming/controllers/BufferController.js | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/streaming/controllers/BufferController.js b/src/streaming/controllers/BufferController.js index 7465277aae..821b218cc9 100644 --- a/src/streaming/controllers/BufferController.js +++ b/src/streaming/controllers/BufferController.js @@ -84,18 +84,8 @@ function BufferController(config) { function setup() { log = Debug(context).getInstance().log.bind(instance); - requiredQuality = AbrController.QUALITY_DEFAULT; - isBufferingCompleted = false; - bufferLevel = 0; - criticalBufferLevel = Number.POSITIVE_INFINITY; - maxAppendedIndex = 0; - lastIndex = Number.POSITIVE_INFINITY; - buffer = null; - bufferState = BUFFER_EMPTY; - wallclockTicked = 0; - appendingMediaChunk = false; - isAppendingInProgress = false; - isPruningInProgress = false; + + reset(); } function getBufferControllerType() { @@ -498,6 +488,8 @@ function BufferController(config) { isBufferingCompleted = false; isAppendingInProgress = false; isPruningInProgress = false; + bufferLevel = 0; + wallclockTicked = 0; if (!errored) { sourceBufferController.abort(mediaSource, buffer); From bba1f716c77904b83cb5496a4b2f1308a5a09dee Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 17:58:07 +0200 Subject: [PATCH 05/16] add resetInitialSettings function. It has to be called by setup and reset functions --- src/streaming/controllers/StreamController.js | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/streaming/controllers/StreamController.js b/src/streaming/controllers/StreamController.js index 1db744938c..0fd67495ec 100644 --- a/src/streaming/controllers/StreamController.js +++ b/src/streaming/controllers/StreamController.js @@ -95,18 +95,11 @@ function StreamController() { audioTrackDetected; function setup() { - protectionController = null; - streams = []; timeSyncController = TimeSyncController(context).getInstance(); baseURLController = BaseURLController(context).getInstance(); mediaSourceController = MediaSourceController(context).getInstance(); - autoPlay = true; - isStreamSwitchingInProgress = false; - isPaused = false; - initialPlayback = true; - playListMetrics = null; - hasMediaError = false; - hasInitialisationError = false; + + resetInitialSettings(); } function initialize(autoPl, protData) { @@ -706,8 +699,23 @@ function StreamController() { } } + function resetInitialSettings() { + streams = []; + protectionController = null; + isStreamSwitchingInProgress = false; + activeStream = null; + hasMediaError = false; + hasInitialisationError = false; + videoTrackDetected = undefined; + initialPlayback = true; + isPaused = false; + autoPlay = true; + playListMetrics = null; + } + function reset() { checkSetConfigCall(); + timeSyncController.reset(); flushPlaylistMetrics( @@ -716,13 +724,11 @@ function StreamController() { PlayListTrace.USER_REQUEST_STOP_REASON ); - for (let i = 0, ln = streams.length; i < ln; i++) { + for (let i = 0, ln = streams ? streams.length : 0; i < ln; i++) { let stream = streams[i]; stream.reset(hasMediaError); } - streams = []; - eventBus.off(Events.PLAYBACK_TIME_UPDATED, onPlaybackTimeUpdated, this); eventBus.off(Events.PLAYBACK_SEEKING, onPlaybackSeeking, this); eventBus.off(Events.PLAYBACK_ERROR, onPlaybackError, this); @@ -740,13 +746,6 @@ function StreamController() { manifestLoader.reset(); timelineConverter.reset(); initCache.reset(); - isStreamSwitchingInProgress = false; - activeStream = null; - hasMediaError = false; - hasInitialisationError = false; - videoTrackDetected = undefined; - initialPlayback = true; - isPaused = false; if (mediaSource) { mediaSourceController.detachMediaSource(videoModel); @@ -765,6 +764,7 @@ function StreamController() { } eventBus.trigger(Events.STREAM_TEARDOWN_COMPLETE); + resetInitialSettings(); } function onMetricAdded(e) { From 26b0811ecaa8c39a044e81ceb809b58024f4d0df Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 20 Jun 2017 18:11:55 +0200 Subject: [PATCH 06/16] add resetInitialSettings function. It has to be called by setup and reset functions --- .../controllers/RepresentationController.js | 24 +++++++++---------- src/streaming/models/FragmentModel.js | 13 +++++----- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/dash/controllers/RepresentationController.js b/src/dash/controllers/RepresentationController.js index 414c259c82..73f8aa3336 100644 --- a/src/dash/controllers/RepresentationController.js +++ b/src/dash/controllers/RepresentationController.js @@ -62,10 +62,7 @@ function RepresentationController() { manifestModel; function setup() { - realAdaptation = null; - realAdaptationIndex = -1; - updating = true; - voAvailableRepresentations = []; + resetInitialSettings(); eventBus.on(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, instance); eventBus.on(Events.REPRESENTATION_UPDATED, onRepresentationUpdated, instance); @@ -128,14 +125,7 @@ function RepresentationController() { return currentVoRepresentation; } - function reset() { - - eventBus.off(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, instance); - eventBus.off(Events.REPRESENTATION_UPDATED, onRepresentationUpdated, instance); - eventBus.off(Events.WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, instance); - eventBus.off(Events.BUFFER_LEVEL_UPDATED, onBufferLevelUpdated, instance); - - + function resetInitialSettings() { realAdaptation = null; realAdaptationIndex = -1; updating = true; @@ -149,6 +139,16 @@ function RepresentationController() { dashMetrics = null; } + function reset() { + + eventBus.off(Events.QUALITY_CHANGE_REQUESTED, onQualityChanged, instance); + eventBus.off(Events.REPRESENTATION_UPDATED, onRepresentationUpdated, instance); + eventBus.off(Events.WALLCLOCK_TIME_UPDATED, onWallclockTimeUpdated, instance); + eventBus.off(Events.BUFFER_LEVEL_UPDATED, onBufferLevelUpdated, instance); + + resetInitialSettings(); + } + function updateData(newRealAdaptation, voAdaptation, type) { let quality, averageThroughput; diff --git a/src/streaming/models/FragmentModel.js b/src/streaming/models/FragmentModel.js index f82c9bed1b..f4166f599f 100644 --- a/src/streaming/models/FragmentModel.js +++ b/src/streaming/models/FragmentModel.js @@ -55,9 +55,7 @@ function FragmentModel(config) { loadingRequests; function setup() { - streamProcessor = null; - executedRequests = []; - loadingRequests = []; + resetInitialSettings(); eventBus.on(Events.LOADING_COMPLETED, onLoadingCompleted, instance); } @@ -268,15 +266,18 @@ function FragmentModel(config) { }); } + function resetInitialSettings() { + executedRequests = []; + loadingRequests = []; + } + function reset() { eventBus.off(Events.LOADING_COMPLETED, onLoadingCompleted, this); if (fragmentLoader) { fragmentLoader.reset(); } - - executedRequests = []; - loadingRequests = []; + resetInitialSettings(); } instance = { From 0de902e9a550741bd1d2b0af9d51af3f2b2aa4e0 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 10:49:53 +0200 Subject: [PATCH 07/16] add resetInitialSettings function for DashHandler. It has to be called by setup and reset functions --- src/dash/DashHandler.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/dash/DashHandler.js b/src/dash/DashHandler.js index 83972ffe71..7365fdc4a5 100644 --- a/src/dash/DashHandler.js +++ b/src/dash/DashHandler.js @@ -73,9 +73,8 @@ function DashHandler(config) { function setup() { log = Debug(context).getInstance().log.bind(instance); - index = -1; - currentTime = 0; - earliestTime = NaN; + + resetInitialSettings(); segmentBaseLoader = isWebM(config.mimeType) ? WebmSegmentBaseLoader(context).getInstance() : SegmentBaseLoader(context).getInstance(); segmentBaseLoader.setConfig({ @@ -121,13 +120,24 @@ function DashHandler(config) { return earliestTime; } - function reset() { - segmentsGetter = null; + function getType() { + return type; + } + + function resetInitialSettings() { + index = -1; currentTime = 0; earliestTime = NaN; + type = null; requestedTime = NaN; - index = -1; + isDynamic = null; streamProcessor = null; + segmentsGetter = null; + } + + function reset() { + resetInitialSettings(); + eventBus.off(Events.INITIALIZATION_LOADED, onInitializationLoaded, instance); eventBus.off(Events.SEGMENTS_LOADED, onSegmentsLoaded, instance); } From 587fcb486288b738dae9cef60f05a2d17deb5dc1 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 10:59:49 +0200 Subject: [PATCH 08/16] add resetInitialSettings function for FragmentController. It has to be called by setup and reset functions --- src/streaming/controllers/FragmentController.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/streaming/controllers/FragmentController.js b/src/streaming/controllers/FragmentController.js index e9395f47dc..422590560b 100644 --- a/src/streaming/controllers/FragmentController.js +++ b/src/streaming/controllers/FragmentController.js @@ -53,7 +53,7 @@ function FragmentController( config ) { fragmentModels; function setup() { - fragmentModels = {}; + resetInitialSettings(); eventBus.on(Events.FRAGMENT_LOADING_COMPLETED, onFragmentLoadingCompleted, instance); } @@ -80,14 +80,18 @@ function FragmentController( config ) { return (request && request.type && request.type === HTTPRequest.INIT_SEGMENT_TYPE); } - function reset() { - eventBus.off(Events.FRAGMENT_LOADING_COMPLETED, onFragmentLoadingCompleted, this); + function resetInitialSettings() { for (let model in fragmentModels) { fragmentModels[model].reset(); } fragmentModels = {}; } + function reset() { + eventBus.off(Events.FRAGMENT_LOADING_COMPLETED, onFragmentLoadingCompleted, this); + resetInitialSettings(); + } + function createDataChunk(bytes, request, streamId) { const chunk = new DataChunk(); From d14937a19830944ba25f87ad76ab1ee075e82eee Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 11:43:41 +0200 Subject: [PATCH 09/16] add resetInitialSettings function for StreamProcessor. It has to be called by setup and reset functions --- src/streaming/StreamProcessor.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/streaming/StreamProcessor.js b/src/streaming/StreamProcessor.js index ce589b677e..f0e706ce45 100644 --- a/src/streaming/StreamProcessor.js +++ b/src/streaming/StreamProcessor.js @@ -73,13 +73,11 @@ function StreamProcessor(config) { spExternalControllers; function setup() { - mediaInfoArr = []; - spExternalControllers = []; - liveEdgeFinder = LiveEdgeFinder(context).create({ timelineConverter: timelineConverter, streamProcessor: instance }); + resetInitialSettings(); } function initialize(mediaSource) { @@ -153,6 +151,13 @@ function StreamProcessor(config) { spExternalControllers = []; } + function resetInitialSettings() { + mediaInfoArr = []; + dynamic = null; + mediaInfo = null; + unregisterAllExternalController(); + } + function reset(errored) { indexHandler.reset(); @@ -175,13 +180,10 @@ function StreamProcessor(config) { spExternalControllers.forEach(function (controller) { controller.reset(); }); - unregisterAllExternalController(); - stream = null; - mediaInfo = null; - mediaInfoArr = []; + resetInitialSettings(); type = null; - + stream = null; liveEdgeFinder.reset(); } From fa46bd53c0c09050588b94d7a6cae41e64796fb7 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 11:44:25 +0200 Subject: [PATCH 10/16] add resetInitialSettings function for InsufficientBufferRule. It has to be called by setup and reset functions --- src/streaming/rules/abr/InsufficientBufferRule.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/streaming/rules/abr/InsufficientBufferRule.js b/src/streaming/rules/abr/InsufficientBufferRule.js index 74e0e06444..b578f00551 100644 --- a/src/streaming/rules/abr/InsufficientBufferRule.js +++ b/src/streaming/rules/abr/InsufficientBufferRule.js @@ -133,8 +133,8 @@ function InsufficientBufferRule(config) { } function reset() { - eventBus.off(Events.PLAYBACK_SEEKING, onPlaybackSeeking, instance); resetInitialSettings(); + eventBus.off(Events.PLAYBACK_SEEKING, onPlaybackSeeking, instance); } instance = { From 53f2bf423d1d7eee72ceaf77cc432789e0e8890f Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 11:46:40 +0200 Subject: [PATCH 11/16] setup and reset functions do the same work.... --- src/streaming/models/BaseURLTreeModel.js | 2 +- src/streaming/rules/ThroughputHistory.js | 6 +- src/streaming/rules/abr/BolaAbandonRule.js | 258 +++++++++++++++++++++ 3 files changed, 262 insertions(+), 4 deletions(-) create mode 100644 src/streaming/rules/abr/BolaAbandonRule.js diff --git a/src/streaming/models/BaseURLTreeModel.js b/src/streaming/models/BaseURLTreeModel.js index d1888af146..e9e6f1e6a6 100644 --- a/src/streaming/models/BaseURLTreeModel.js +++ b/src/streaming/models/BaseURLTreeModel.js @@ -54,7 +54,7 @@ function BaseURLTreeModel() { const objectUtils = ObjectUtils(context).getInstance(); function setup() { - root = new Node(); + reset(); } function setConfig(config) { diff --git a/src/streaming/rules/ThroughputHistory.js b/src/streaming/rules/ThroughputHistory.js index f3b3993f17..c29e306162 100644 --- a/src/streaming/rules/ThroughputHistory.js +++ b/src/streaming/rules/ThroughputHistory.js @@ -52,8 +52,7 @@ function ThroughputHistory(config) { latencyDict; function setup() { - throughputDict = {}; - latencyDict = {}; + reset(); } function isCachedResponse(mediaType, latencyMs, downloadTimeMs) { @@ -167,7 +166,8 @@ function ThroughputHistory(config) { } function reset() { - setup(); + throughputDict = {}; + latencyDict = {}; } const instance = { diff --git a/src/streaming/rules/abr/BolaAbandonRule.js b/src/streaming/rules/abr/BolaAbandonRule.js new file mode 100644 index 0000000000..01c1583336 --- /dev/null +++ b/src/streaming/rules/abr/BolaAbandonRule.js @@ -0,0 +1,258 @@ +/** + * 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) 2016, 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. + */ +import SwitchRequest from '../SwitchRequest'; +import FactoryMaker from '../../../core/FactoryMaker'; +import Debug from '../../../core/Debug'; +import BolaRule from './BolaRule'; + +function BolaAbandonRule(config) { + + // do not abandon during the grace period + const GRACE_PERIOD_MS = 500; + const POOR_LATENCY_MS = 200; + + let context = this.context; + let log = Debug(context).getInstance().log; + let dashMetrics = config.dashMetrics; + let metricsModel = config.metricsModel; + + let instance, + abandonDict; + + function setup() { + reset(); + } + + function rememberAbandon(mediaType, index, quality) { + // if this is called, then canStillAbandon(mediaType, index, quality) should have returned true + abandonDict[mediaType] = {index: index, quality: quality}; + } + + function canAbandon(mediaType, index, quality) { + let a = abandonDict[mediaType]; + if (!a) + return true; + return index !== a.index || quality < a.quality; + } + + function shouldAbandon(rulesContext) { + let mediaType = rulesContext.getMediaType(); + let metrics = metricsModel.getReadOnlyMetricsFor(mediaType); + let request = rulesContext.getCurrentRequest(); + let switchRequest = SwitchRequest(context).create(SwitchRequest.NO_CHANGE, {name: BolaAbandonRule.__dashjs_factory_name}); + + if (metrics.BolaState.length === 0) { + // should not arrive here - we shouldn't be downloading a fragment before BOLA is initialized + log('WARNING: executing BolaAbandonRule before initializing BolaRule'); + abandonDict[mediaType] = null; + return switchRequest; + } + + let bolaState = metrics.BolaState[0]._s; + // TODO: does changing bolaState conform to coding style, or should we clone? + + let index = request.index; + let quality = request.quality; + + if (isNaN(index) || quality === 0 || !canAbandon(mediaType, index, quality) || !request.firstByteDate) { + return switchRequest; + } + + let nowMs = Date.now(); + let elapsedTimeMs = nowMs - request.firstByteDate.getTime(); + + let bytesLoaded = request.bytesLoaded; + let bytesTotal = request.bytesTotal; + let bytesRemaining = bytesTotal - bytesLoaded; + let durationS = request.duration; + + let bufferLevel = dashMetrics.getCurrentBufferLevel(metrics) ? dashMetrics.getCurrentBufferLevel(metrics) : 0.0; + let effectiveBufferLevel = bufferLevel + bolaState.placeholderBuffer; + + let estimateThroughput = 8 * bytesLoaded / (0.001 * elapsedTimeMs); // throughput in bits per second + let estimateThroughputBSF = bolaState.bandwidthSafetyFactor * estimateThroughput; + let latencyS = 0.001 * (request.firstByteDate.getTime() - request.requestStartDate.getTime()); + if (latencyS < 0.001 * POOR_LATENCY_MS) { + latencyS = 0.001 * POOR_LATENCY_MS; + } + let estimateTotalTimeS = latencyS + 8 * bytesTotal / estimateThroughputBSF; + + let diagnosticMessage = ''; + if (BolaRule.BOLA_DEBUG) diagnosticMessage = 'index=' + index + ' quality=' + quality + ' bytesLoaded/bytesTotal=' + bytesLoaded + '/' + bytesTotal + ' bufferLevel=' + bufferLevel + ' timeSince1stByte=' + (elapsedTimeMs / 1000).toFixed(3) + ' estThroughput=' + (estimateThroughputBSF / 1000000).toFixed(3) + ' latency=' + latencyS.toFixed(3); + + let estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[0] / bolaState.bitrates[quality]; + let estimateBytesRemainingAfterLatency = bytesRemaining - latencyS * estimateThroughputBSF / 8; + if (estimateBytesRemainingAfterLatency < 1) { + estimateBytesRemainingAfterLatency = 1; + } + + if (elapsedTimeMs < GRACE_PERIOD_MS || + bytesRemaining <= estimateOtherBytesTotal || + bufferLevel > bolaState.bufferTarget || + estimateBytesRemainingAfterLatency <= estimateOtherBytesTotal || + estimateTotalTimeS <= durationS) { + // Do not abandon during first GRACE_PERIOD_MS. + // Do not abandon if we need to download less bytes than the size of the lowest quality fragment. + // Do not abandon if buffer level is above bufferTarget because the schedule controller will not download anything anyway. + // Do not abandon if after latencyS bytesRemaining is estimated to drop below size of lowest quality fragment. + // Do not abandon if fragment takes less than 1 fragment duration to download. + return switchRequest; + } + + // If we abandon, there will be latencyS time before we get first byte at lower quality. + // By that time, the no-abandon option would have downloaded some more, and the buffer level would have depleted some more. + // Introducing this latencyS cushion also helps avoid extra abandonment, especially with close bitrates. + + let effectiveBufferAfterLatency = effectiveBufferLevel - latencyS; + if (effectiveBufferAfterLatency < 0) { + effectiveBufferAfterLatency = 0; + } + + // if we end up abandoning, we should not consider starting a download that would require more bytes than the remaining bytes in currently downloading fragment + let maxDroppedQuality = 0; + while (maxDroppedQuality + 1 < quality && + bytesTotal * bolaState.bitrates[maxDroppedQuality + 1] / bolaState.bitrates[quality] < estimateBytesRemainingAfterLatency) { + + ++maxDroppedQuality; + } + + let newQuality = quality; + + if (bolaState.state === BolaRule.BOLA_STATE_STARTUP) { + // We are not yet using the BOLA buffer rules - use different abandonment logic. + + // if we are here then we failed the test that estimateTotalTimeS <= durationS, so we abandon + + // search for quality that matches the throughput + newQuality = 0; + for (let i = 0; i <= maxDroppedQuality; ++i) { + estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[i] / bolaState.bitrates[quality]; + if (8 * estimateOtherBytesTotal / durationS > estimateThroughputBSF) { + // chunks at quality i or higher need a greater throughput + break; + } + newQuality = i; + } + } else { // bolaState.state === BolaRule.BOLA_STATE_STEADY + // check if we should abandon using BOLA utility criteria + + let score = (bolaState.Vp * (bolaState.utilities[quality] + bolaState.gp) - effectiveBufferAfterLatency) / estimateBytesRemainingAfterLatency; + + for (let i = 0; i <= maxDroppedQuality; ++i) { + estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[i] / bolaState.bitrates[quality]; + let s = (bolaState.Vp * (bolaState.utilities[i] + bolaState.gp) - effectiveBufferAfterLatency) / estimateOtherBytesTotal; + if (s > score) { + newQuality = i; + score = s; + } + } + } + + // Perform check for rebuffer avoidance - now use real buffer level as opposed to effective buffer level. + let safeByteSize = bolaState.rebufferSafetyFactor * estimateThroughput * (bufferLevel - latencyS) / 8; + + if (newQuality === quality && estimateBytesRemainingAfterLatency > safeByteSize) { + newQuality = maxDroppedQuality; + } + + if (newQuality === quality) { + // no change + return switchRequest; + } + + // newQuality < quality, we are abandoning + while (newQuality > 0) { + estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[newQuality] / bolaState.bitrates[quality]; + if (estimateOtherBytesTotal <= safeByteSize) { + break; + } + --newQuality; + } + + // deflate placeholder buffer - we want to be conservative after abandoning + let wantBufferLevel = NaN; + if (newQuality > 0) { + // deflate to point where score for newQuality is just getting better than for (newQuality - 1) + let u = bolaState.utilities[newQuality]; + let u1 = bolaState.utilities[newQuality - 1]; + let s = bolaState.bitrates[newQuality]; + let s1 = bolaState.bitrates[newQuality - 1]; + wantBufferLevel = bolaState.Vp * ((s * u1 - s1 * u) / (s - s1) + bolaState.gp); + } else { + // deflate to point where score for (newQuality + 1) is just getting better than for newQuality + let u = bolaState.utilities[0]; + let u1 = bolaState.utilities[1]; + let s = bolaState.bitrates[0]; + let s1 = bolaState.bitrates[1]; + wantBufferLevel = bolaState.Vp * ((s * u1 - s1 * u) / (s - s1) + bolaState.gp); + // then reduce one fragment duration to be conservative + wantBufferLevel -= durationS; + } + if (effectiveBufferLevel > wantBufferLevel) { + bolaState.placeholderBuffer = wantBufferLevel - bufferLevel; + if (bolaState.placeholderBuffer < 0) + bolaState.placeholderBuffer = 0; + } + + bolaState.lastQuality = newQuality; + metricsModel.updateBolaState(mediaType, bolaState); + + if (BolaRule.BOLA_DEBUG) log('BolaDebug ' + mediaType + ' BolaAbandonRule abandon to ' + newQuality + ' - ' + diagnosticMessage); + + rememberAbandon(mediaType, index, quality); + switchRequest.quality = newQuality; + switchRequest.reason.state = bolaState.state; + switchRequest.reason.throughput = estimateThroughput; + switchRequest.reason.bufferLevel = bufferLevel; + // following entries used for tuning algorithm + switchRequest.reason.bytesLoaded = request.bytesLoaded; + switchRequest.reason.bytesTotal = request.bytesTotal; + switchRequest.reason.elapsedTimeMs = elapsedTimeMs; + + return switchRequest; + } + + function reset() { + abandonDict = {}; + } + + instance = { + shouldAbandon: shouldAbandon, + reset: reset + }; + + setup(); + + return instance; +} + +BolaAbandonRule.__dashjs_factory_name = 'BolaAbandonRule'; +export default FactoryMaker.getClassFactory(BolaAbandonRule); From bd7eace5a2f8b6fe4d580191d78026a741f675fc Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 11:47:28 +0200 Subject: [PATCH 12/16] config object doesn't need to be set in parameters of setup function --- src/streaming/text/TextBufferController.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/streaming/text/TextBufferController.js b/src/streaming/text/TextBufferController.js index f8f4f60342..20e83a708f 100644 --- a/src/streaming/text/TextBufferController.js +++ b/src/streaming/text/TextBufferController.js @@ -41,7 +41,7 @@ function TextBufferController(config) { let instance; - function setup(config) { + function setup() { // according to text type, we create corresponding buffer controller if (config.type === Constants.FRAGMENTED_TEXT) { @@ -152,7 +152,7 @@ function TextBufferController(config) { reset: reset }; - setup(config); + setup(); return instance; } From 6f845c9eec95baff2d52d6e9b506f43d69ddc885 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Wed, 21 Jun 2017 14:51:59 +0200 Subject: [PATCH 13/16] add resetInitialSettings function for Stream. It has to be called by setup and reset functions --- src/streaming/Stream.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/streaming/Stream.js b/src/streaming/Stream.js index 23034f9898..e8fd9146cf 100644 --- a/src/streaming/Stream.js +++ b/src/streaming/Stream.js @@ -73,12 +73,7 @@ function Stream(config) { trackChangedEvent; function setup() { - streamProcessors = []; - isStreamActivated = false; - isMediaInitialized = false; - streamInfo = null; - updateError = {}; - isUpdating = false; + resetInitialSettings(); fragmentController = FragmentController(context).create({ mediaPlayerModel: mediaPlayerModel, @@ -123,7 +118,7 @@ function Stream(config) { * @memberof Stream# */ function deactivate() { - let ln = streamProcessors.length; + let ln = streamProcessors ? streamProcessors.length : 0; for (let i = 0; i < ln; i++) { streamProcessors[i].reset(); } @@ -134,6 +129,13 @@ function Stream(config) { eventBus.off(Events.CURRENT_TRACK_CHANGED, onCurrentTrackChanged, instance); } + function resetInitialSettings() { + deactivate(); + streamInfo = null; + updateError = {}; + isUpdating = false; + } + function reset() { if (playbackController) { @@ -146,7 +148,7 @@ function Stream(config) { fragmentController = null; } - deactivate(); + resetInitialSettings(); mediaController = null; abrController = null; manifestUpdater = null; @@ -155,8 +157,6 @@ function Stream(config) { capabilities = null; log = null; errHandler = null; - isUpdating = false; - updateError = {}; eventBus.off(Events.DATA_UPDATE_COMPLETED, onDataUpdateCompleted, instance); eventBus.off(Events.BUFFERING_COMPLETED, onBufferingCompleted, instance); From 8fc9e31d865239895646e808e66792189f880148 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Fri, 7 Jul 2017 15:05:32 +0200 Subject: [PATCH 14/16] fix merge issue... --- src/dash/DashHandler.js | 6 ------ src/streaming/StreamProcessor.js | 1 - test/unit/streaming.controllers.BufferControllerSpec.js | 2 -- 3 files changed, 9 deletions(-) diff --git a/src/dash/DashHandler.js b/src/dash/DashHandler.js index 7365fdc4a5..9371acfa9a 100644 --- a/src/dash/DashHandler.js +++ b/src/dash/DashHandler.js @@ -120,17 +120,11 @@ function DashHandler(config) { return earliestTime; } - function getType() { - return type; - } - function resetInitialSettings() { index = -1; currentTime = 0; earliestTime = NaN; - type = null; requestedTime = NaN; - isDynamic = null; streamProcessor = null; segmentsGetter = null; } diff --git a/src/streaming/StreamProcessor.js b/src/streaming/StreamProcessor.js index f0e706ce45..11855ab0f4 100644 --- a/src/streaming/StreamProcessor.js +++ b/src/streaming/StreamProcessor.js @@ -153,7 +153,6 @@ function StreamProcessor(config) { function resetInitialSettings() { mediaInfoArr = []; - dynamic = null; mediaInfo = null; unregisterAllExternalController(); } diff --git a/test/unit/streaming.controllers.BufferControllerSpec.js b/test/unit/streaming.controllers.BufferControllerSpec.js index d0546c0f50..fe774c61b5 100644 --- a/test/unit/streaming.controllers.BufferControllerSpec.js +++ b/test/unit/streaming.controllers.BufferControllerSpec.js @@ -288,8 +288,6 @@ describe("BufferController", function () { let buffer = 'testBuffer'; bufferController.setBuffer(buffer); expect(bufferController.getBuffer()).to.equal(buffer); - expect(sourceBufferMock.aborted).to.be.false; - expect(sourceBufferMock.sourceBufferRemoved).to.be.false; bufferController.reset(); expect(sourceBufferMock.aborted).to.be.true; From 31eb6816fa774ac4a4b98c49817f7b3c13c6e48f Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 11 Jul 2017 11:53:47 +0200 Subject: [PATCH 15/16] update TextBufferController unit test --- test/unit/streaming.text.TextBufferController.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/unit/streaming.text.TextBufferController.js b/test/unit/streaming.text.TextBufferController.js index 4c5f29d1e8..7c9f3bf74d 100644 --- a/test/unit/streaming.text.TextBufferController.js +++ b/test/unit/streaming.text.TextBufferController.js @@ -5,14 +5,28 @@ const expect = chai.expect; const context = {}; +class SourceBufferControllerMock { + constructor() { + } + + abort() { + } + + removeSourceBuffer() { + } +} + describe('TextBufferController', function () { let textBufferController; it('should create a buffer of type "BufferController" if type is "fragmentedText"', function () { + let sourceBufferMock = new SourceBufferControllerMock('fragmentedText'); + textBufferController = TextBufferController(context).create({ - type: 'fragmentedText' + type: 'fragmentedText', + sourceBufferController: sourceBufferMock, }); expect(textBufferController.getBufferControllerType()).to.equal('BufferController'); From 27f6cc693d71b7bf869a7354c9cffaaa340648e0 Mon Sep 17 00:00:00 2001 From: Nicolas ANGOT Date: Tue, 11 Jul 2017 11:56:34 +0200 Subject: [PATCH 16/16] fix merge issue --- src/streaming/rules/abr/BolaAbandonRule.js | 258 --------------------- 1 file changed, 258 deletions(-) delete mode 100644 src/streaming/rules/abr/BolaAbandonRule.js diff --git a/src/streaming/rules/abr/BolaAbandonRule.js b/src/streaming/rules/abr/BolaAbandonRule.js deleted file mode 100644 index 01c1583336..0000000000 --- a/src/streaming/rules/abr/BolaAbandonRule.js +++ /dev/null @@ -1,258 +0,0 @@ -/** - * 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) 2016, 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. - */ -import SwitchRequest from '../SwitchRequest'; -import FactoryMaker from '../../../core/FactoryMaker'; -import Debug from '../../../core/Debug'; -import BolaRule from './BolaRule'; - -function BolaAbandonRule(config) { - - // do not abandon during the grace period - const GRACE_PERIOD_MS = 500; - const POOR_LATENCY_MS = 200; - - let context = this.context; - let log = Debug(context).getInstance().log; - let dashMetrics = config.dashMetrics; - let metricsModel = config.metricsModel; - - let instance, - abandonDict; - - function setup() { - reset(); - } - - function rememberAbandon(mediaType, index, quality) { - // if this is called, then canStillAbandon(mediaType, index, quality) should have returned true - abandonDict[mediaType] = {index: index, quality: quality}; - } - - function canAbandon(mediaType, index, quality) { - let a = abandonDict[mediaType]; - if (!a) - return true; - return index !== a.index || quality < a.quality; - } - - function shouldAbandon(rulesContext) { - let mediaType = rulesContext.getMediaType(); - let metrics = metricsModel.getReadOnlyMetricsFor(mediaType); - let request = rulesContext.getCurrentRequest(); - let switchRequest = SwitchRequest(context).create(SwitchRequest.NO_CHANGE, {name: BolaAbandonRule.__dashjs_factory_name}); - - if (metrics.BolaState.length === 0) { - // should not arrive here - we shouldn't be downloading a fragment before BOLA is initialized - log('WARNING: executing BolaAbandonRule before initializing BolaRule'); - abandonDict[mediaType] = null; - return switchRequest; - } - - let bolaState = metrics.BolaState[0]._s; - // TODO: does changing bolaState conform to coding style, or should we clone? - - let index = request.index; - let quality = request.quality; - - if (isNaN(index) || quality === 0 || !canAbandon(mediaType, index, quality) || !request.firstByteDate) { - return switchRequest; - } - - let nowMs = Date.now(); - let elapsedTimeMs = nowMs - request.firstByteDate.getTime(); - - let bytesLoaded = request.bytesLoaded; - let bytesTotal = request.bytesTotal; - let bytesRemaining = bytesTotal - bytesLoaded; - let durationS = request.duration; - - let bufferLevel = dashMetrics.getCurrentBufferLevel(metrics) ? dashMetrics.getCurrentBufferLevel(metrics) : 0.0; - let effectiveBufferLevel = bufferLevel + bolaState.placeholderBuffer; - - let estimateThroughput = 8 * bytesLoaded / (0.001 * elapsedTimeMs); // throughput in bits per second - let estimateThroughputBSF = bolaState.bandwidthSafetyFactor * estimateThroughput; - let latencyS = 0.001 * (request.firstByteDate.getTime() - request.requestStartDate.getTime()); - if (latencyS < 0.001 * POOR_LATENCY_MS) { - latencyS = 0.001 * POOR_LATENCY_MS; - } - let estimateTotalTimeS = latencyS + 8 * bytesTotal / estimateThroughputBSF; - - let diagnosticMessage = ''; - if (BolaRule.BOLA_DEBUG) diagnosticMessage = 'index=' + index + ' quality=' + quality + ' bytesLoaded/bytesTotal=' + bytesLoaded + '/' + bytesTotal + ' bufferLevel=' + bufferLevel + ' timeSince1stByte=' + (elapsedTimeMs / 1000).toFixed(3) + ' estThroughput=' + (estimateThroughputBSF / 1000000).toFixed(3) + ' latency=' + latencyS.toFixed(3); - - let estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[0] / bolaState.bitrates[quality]; - let estimateBytesRemainingAfterLatency = bytesRemaining - latencyS * estimateThroughputBSF / 8; - if (estimateBytesRemainingAfterLatency < 1) { - estimateBytesRemainingAfterLatency = 1; - } - - if (elapsedTimeMs < GRACE_PERIOD_MS || - bytesRemaining <= estimateOtherBytesTotal || - bufferLevel > bolaState.bufferTarget || - estimateBytesRemainingAfterLatency <= estimateOtherBytesTotal || - estimateTotalTimeS <= durationS) { - // Do not abandon during first GRACE_PERIOD_MS. - // Do not abandon if we need to download less bytes than the size of the lowest quality fragment. - // Do not abandon if buffer level is above bufferTarget because the schedule controller will not download anything anyway. - // Do not abandon if after latencyS bytesRemaining is estimated to drop below size of lowest quality fragment. - // Do not abandon if fragment takes less than 1 fragment duration to download. - return switchRequest; - } - - // If we abandon, there will be latencyS time before we get first byte at lower quality. - // By that time, the no-abandon option would have downloaded some more, and the buffer level would have depleted some more. - // Introducing this latencyS cushion also helps avoid extra abandonment, especially with close bitrates. - - let effectiveBufferAfterLatency = effectiveBufferLevel - latencyS; - if (effectiveBufferAfterLatency < 0) { - effectiveBufferAfterLatency = 0; - } - - // if we end up abandoning, we should not consider starting a download that would require more bytes than the remaining bytes in currently downloading fragment - let maxDroppedQuality = 0; - while (maxDroppedQuality + 1 < quality && - bytesTotal * bolaState.bitrates[maxDroppedQuality + 1] / bolaState.bitrates[quality] < estimateBytesRemainingAfterLatency) { - - ++maxDroppedQuality; - } - - let newQuality = quality; - - if (bolaState.state === BolaRule.BOLA_STATE_STARTUP) { - // We are not yet using the BOLA buffer rules - use different abandonment logic. - - // if we are here then we failed the test that estimateTotalTimeS <= durationS, so we abandon - - // search for quality that matches the throughput - newQuality = 0; - for (let i = 0; i <= maxDroppedQuality; ++i) { - estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[i] / bolaState.bitrates[quality]; - if (8 * estimateOtherBytesTotal / durationS > estimateThroughputBSF) { - // chunks at quality i or higher need a greater throughput - break; - } - newQuality = i; - } - } else { // bolaState.state === BolaRule.BOLA_STATE_STEADY - // check if we should abandon using BOLA utility criteria - - let score = (bolaState.Vp * (bolaState.utilities[quality] + bolaState.gp) - effectiveBufferAfterLatency) / estimateBytesRemainingAfterLatency; - - for (let i = 0; i <= maxDroppedQuality; ++i) { - estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[i] / bolaState.bitrates[quality]; - let s = (bolaState.Vp * (bolaState.utilities[i] + bolaState.gp) - effectiveBufferAfterLatency) / estimateOtherBytesTotal; - if (s > score) { - newQuality = i; - score = s; - } - } - } - - // Perform check for rebuffer avoidance - now use real buffer level as opposed to effective buffer level. - let safeByteSize = bolaState.rebufferSafetyFactor * estimateThroughput * (bufferLevel - latencyS) / 8; - - if (newQuality === quality && estimateBytesRemainingAfterLatency > safeByteSize) { - newQuality = maxDroppedQuality; - } - - if (newQuality === quality) { - // no change - return switchRequest; - } - - // newQuality < quality, we are abandoning - while (newQuality > 0) { - estimateOtherBytesTotal = bytesTotal * bolaState.bitrates[newQuality] / bolaState.bitrates[quality]; - if (estimateOtherBytesTotal <= safeByteSize) { - break; - } - --newQuality; - } - - // deflate placeholder buffer - we want to be conservative after abandoning - let wantBufferLevel = NaN; - if (newQuality > 0) { - // deflate to point where score for newQuality is just getting better than for (newQuality - 1) - let u = bolaState.utilities[newQuality]; - let u1 = bolaState.utilities[newQuality - 1]; - let s = bolaState.bitrates[newQuality]; - let s1 = bolaState.bitrates[newQuality - 1]; - wantBufferLevel = bolaState.Vp * ((s * u1 - s1 * u) / (s - s1) + bolaState.gp); - } else { - // deflate to point where score for (newQuality + 1) is just getting better than for newQuality - let u = bolaState.utilities[0]; - let u1 = bolaState.utilities[1]; - let s = bolaState.bitrates[0]; - let s1 = bolaState.bitrates[1]; - wantBufferLevel = bolaState.Vp * ((s * u1 - s1 * u) / (s - s1) + bolaState.gp); - // then reduce one fragment duration to be conservative - wantBufferLevel -= durationS; - } - if (effectiveBufferLevel > wantBufferLevel) { - bolaState.placeholderBuffer = wantBufferLevel - bufferLevel; - if (bolaState.placeholderBuffer < 0) - bolaState.placeholderBuffer = 0; - } - - bolaState.lastQuality = newQuality; - metricsModel.updateBolaState(mediaType, bolaState); - - if (BolaRule.BOLA_DEBUG) log('BolaDebug ' + mediaType + ' BolaAbandonRule abandon to ' + newQuality + ' - ' + diagnosticMessage); - - rememberAbandon(mediaType, index, quality); - switchRequest.quality = newQuality; - switchRequest.reason.state = bolaState.state; - switchRequest.reason.throughput = estimateThroughput; - switchRequest.reason.bufferLevel = bufferLevel; - // following entries used for tuning algorithm - switchRequest.reason.bytesLoaded = request.bytesLoaded; - switchRequest.reason.bytesTotal = request.bytesTotal; - switchRequest.reason.elapsedTimeMs = elapsedTimeMs; - - return switchRequest; - } - - function reset() { - abandonDict = {}; - } - - instance = { - shouldAbandon: shouldAbandon, - reset: reset - }; - - setup(); - - return instance; -} - -BolaAbandonRule.__dashjs_factory_name = 'BolaAbandonRule'; -export default FactoryMaker.getClassFactory(BolaAbandonRule);