Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
4665390f
authored
Apr 17, 2023
by
christosts
Committed by
Rohit Singh
Apr 17, 2023
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Publish experimental bandwidth meter classes
PiperOrigin-RevId: 524846153
parent
2593c5f6
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
3790 additions
and
0 deletions
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/BandwidthEstimator.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/BandwidthStatistic.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/CombinedParallelSampleBandwidthEstimator.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExperimentalBandwidthMeter.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageStatistic.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageTimeToFirstByteEstimator.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/PercentileTimeToFirstByteEstimator.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SlidingPercentileBandwidthStatistic.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SlidingWeightedAverageBandwidthStatistic.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SplitParallelSampleBandwidthEstimator.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/package-info.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/CombinedParallelSampleBandwidthEstimatorTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExperimentalBandwidthMeterTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageStatisticTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageTimeToFirstByteEstimatorTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/PercentileTimeToFirstByteEstimatorTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SlidingPercentileBandwidthStatisticTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SlidingWeightedAverageBandwidthStatisticTest.java
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SplitParallelSampleBandwidthEstimatorTest.java
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/BandwidthEstimator.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
android.os.Handler
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
/** The interface for different bandwidth estimation strategies. */
public
interface
BandwidthEstimator
{
long
ESTIMATE_NOT_AVAILABLE
=
Long
.
MIN_VALUE
;
/**
* Adds an {@link BandwidthMeter.EventListener}.
*
* @param eventHandler A handler for events.
* @param eventListener A listener of events.
*/
void
addEventListener
(
Handler
eventHandler
,
BandwidthMeter
.
EventListener
eventListener
);
/**
* Removes an {@link BandwidthMeter.EventListener}.
*
* @param eventListener The listener to be removed.
*/
void
removeEventListener
(
BandwidthMeter
.
EventListener
eventListener
);
/**
* Called when a transfer is being initialized.
*
* @param source The {@link DataSource} performing the transfer.
*/
void
onTransferInitializing
(
DataSource
source
);
/**
* Called when a transfer starts.
*
* @param source The {@link DataSource} performing the transfer.
*/
void
onTransferStart
(
DataSource
source
);
/**
* Called incrementally during a transfer.
*
* @param source The {@link DataSource} performing the transfer.
* @param bytesTransferred The number of bytes transferred since the previous call to this method
*/
void
onBytesTransferred
(
DataSource
source
,
int
bytesTransferred
);
/**
* Called when a transfer ends.
*
* @param source The {@link DataSource} performing the transfer.
*/
void
onTransferEnd
(
DataSource
source
);
/**
* Returns the bandwidth estimate in bits per second, or {@link #ESTIMATE_NOT_AVAILABLE} if there
* is no estimate available yet.
*/
long
getBandwidthEstimate
();
/**
* Notifies this estimator that a network change has been detected.
*
* @param newBandwidthEstimate The new initial bandwidth estimate based on network type.
*/
void
onNetworkTypeChange
(
long
newBandwidthEstimate
);
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/BandwidthStatistic.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
/** The interface for different bandwidth estimation statistics. */
public
interface
BandwidthStatistic
{
/**
* Adds a transfer sample to the statistic.
*
* @param bytes The number of bytes transferred.
* @param durationUs The duration of the transfer, in microseconds.
*/
void
addSample
(
long
bytes
,
long
durationUs
);
/**
* Returns the bandwidth estimate in bits per second, or {@link
* BandwidthEstimator#ESTIMATE_NOT_AVAILABLE} if there is no estimate available yet.
*/
long
getBandwidthEstimate
();
/**
* Resets the statistic. The statistic should drop all samples and reset to its initial state,
* similar to right after construction.
*/
void
reset
();
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/CombinedParallelSampleBandwidthEstimator.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
android.os.Handler
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.errorprone.annotations.CanIgnoreReturnValue
;
/**
* A {@link BandwidthEstimator} that captures a transfer sample each time all parallel transfers
* end.
*/
public
class
CombinedParallelSampleBandwidthEstimator
implements
BandwidthEstimator
{
/** A builder to create {@link CombinedParallelSampleBandwidthEstimator} instances. */
public
static
class
Builder
{
private
BandwidthStatistic
bandwidthStatistic
;
private
int
minSamples
;
private
long
minBytesTransferred
;
private
Clock
clock
;
/** Creates a new builder instance. */
public
Builder
()
{
bandwidthStatistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
clock
=
Clock
.
DEFAULT
;
}
/**
* Sets the {@link BandwidthStatistic} to be used by the estimator. By default, this is set to a
* {@link SlidingWeightedAverageBandwidthStatistic}.
*
* @param bandwidthStatistic The {@link BandwidthStatistic}.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setBandwidthStatistic
(
BandwidthStatistic
bandwidthStatistic
)
{
checkNotNull
(
bandwidthStatistic
);
this
.
bandwidthStatistic
=
bandwidthStatistic
;
return
this
;
}
/**
* Sets a minimum threshold of samples that need to be taken before the estimator can return a
* bandwidth estimate. By default, this is set to {@code 0}.
*
* @param minSamples The minimum number of samples.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setMinSamples
(
int
minSamples
)
{
checkArgument
(
minSamples
>=
0
);
this
.
minSamples
=
minSamples
;
return
this
;
}
/**
* Sets a minimum threshold of bytes that need to be transferred before the estimator can return
* a bandwidth estimate. By default, this is set to {@code 0}.
*
* @param minBytesTransferred The minimum number of transferred bytes.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setMinBytesTransferred
(
long
minBytesTransferred
)
{
checkArgument
(
minBytesTransferred
>=
0
);
this
.
minBytesTransferred
=
minBytesTransferred
;
return
this
;
}
/**
* Sets the {@link Clock} used by the estimator. By default, this is set to {@link
* Clock#DEFAULT}.
*
* @param clock The {@link Clock} to be used.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
@VisibleForTesting
/* package */
Builder
setClock
(
Clock
clock
)
{
this
.
clock
=
clock
;
return
this
;
}
public
CombinedParallelSampleBandwidthEstimator
build
()
{
return
new
CombinedParallelSampleBandwidthEstimator
(
this
);
}
}
private
final
BandwidthStatistic
bandwidthStatistic
;
private
final
int
minSamples
;
private
final
long
minBytesTransferred
;
private
final
BandwidthMeter
.
EventListener
.
EventDispatcher
eventDispatcher
;
private
final
Clock
clock
;
private
int
streamCount
;
private
long
sampleStartTimeMs
;
private
long
sampleBytesTransferred
;
private
long
bandwidthEstimate
;
private
long
lastReportedBandwidthEstimate
;
private
int
totalSamplesAdded
;
private
long
totalBytesTransferred
;
private
CombinedParallelSampleBandwidthEstimator
(
Builder
builder
)
{
this
.
bandwidthStatistic
=
builder
.
bandwidthStatistic
;
this
.
minSamples
=
builder
.
minSamples
;
this
.
minBytesTransferred
=
builder
.
minBytesTransferred
;
this
.
clock
=
builder
.
clock
;
eventDispatcher
=
new
BandwidthMeter
.
EventListener
.
EventDispatcher
();
bandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
lastReportedBandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
@Override
public
void
addEventListener
(
Handler
eventHandler
,
BandwidthMeter
.
EventListener
eventListener
)
{
eventDispatcher
.
addListener
(
eventHandler
,
eventListener
);
}
@Override
public
void
removeEventListener
(
BandwidthMeter
.
EventListener
eventListener
)
{
eventDispatcher
.
removeListener
(
eventListener
);
}
@Override
public
void
onTransferInitializing
(
DataSource
source
)
{}
@Override
public
void
onTransferStart
(
DataSource
source
)
{
if
(
streamCount
==
0
)
{
sampleStartTimeMs
=
clock
.
elapsedRealtime
();
}
streamCount
++;
}
@Override
public
void
onBytesTransferred
(
DataSource
source
,
int
bytesTransferred
)
{
sampleBytesTransferred
+=
bytesTransferred
;
totalBytesTransferred
+=
bytesTransferred
;
}
@Override
public
void
onTransferEnd
(
DataSource
source
)
{
checkState
(
streamCount
>
0
);
streamCount
--;
if
(
streamCount
>
0
)
{
return
;
}
long
nowMs
=
clock
.
elapsedRealtime
();
long
sampleElapsedTimeMs
=
(
int
)
(
nowMs
-
sampleStartTimeMs
);
if
(
sampleElapsedTimeMs
>
0
)
{
bandwidthStatistic
.
addSample
(
sampleBytesTransferred
,
sampleElapsedTimeMs
*
1000
);
totalSamplesAdded
++;
if
(
totalSamplesAdded
>
minSamples
&&
totalBytesTransferred
>
minBytesTransferred
)
{
bandwidthEstimate
=
bandwidthStatistic
.
getBandwidthEstimate
();
}
maybeNotifyBandwidthSample
(
(
int
)
sampleElapsedTimeMs
,
sampleBytesTransferred
,
bandwidthEstimate
);
sampleBytesTransferred
=
0
;
}
}
@Override
public
long
getBandwidthEstimate
()
{
return
bandwidthEstimate
;
}
@Override
public
void
onNetworkTypeChange
(
long
newBandwidthEstimate
)
{
long
nowMs
=
clock
.
elapsedRealtime
();
int
sampleElapsedTimeMs
=
streamCount
>
0
?
(
int
)
(
nowMs
-
sampleStartTimeMs
)
:
0
;
maybeNotifyBandwidthSample
(
sampleElapsedTimeMs
,
sampleBytesTransferred
,
newBandwidthEstimate
);
bandwidthStatistic
.
reset
();
bandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
sampleStartTimeMs
=
nowMs
;
sampleBytesTransferred
=
0
;
totalSamplesAdded
=
0
;
totalBytesTransferred
=
0
;
}
private
void
maybeNotifyBandwidthSample
(
int
elapsedMs
,
long
bytesTransferred
,
long
bandwidthEstimate
)
{
if
((
bandwidthEstimate
==
ESTIMATE_NOT_AVAILABLE
)
||
(
elapsedMs
==
0
&&
bytesTransferred
==
0
&&
bandwidthEstimate
==
lastReportedBandwidthEstimate
))
{
return
;
}
lastReportedBandwidthEstimate
=
bandwidthEstimate
;
eventDispatcher
.
bandwidthSample
(
elapsedMs
,
bytesTransferred
,
bandwidthEstimate
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExperimentalBandwidthMeter.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
android.content.Context
;
import
android.os.Handler
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.android.exoplayer2.upstream.TimeToFirstByteEstimator
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
com.google.android.exoplayer2.util.NetworkTypeObserver
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.base.Ascii
;
import
com.google.common.collect.ImmutableList
;
import
com.google.common.collect.ImmutableMap
;
import
com.google.errorprone.annotations.CanIgnoreReturnValue
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* An experimental {@link BandwidthMeter} that estimates bandwidth by listening to data transfers.
*
* <p>The initial estimate is based on the current operator's network country code or the locale of
* the user, as well as the network connection type. This can be configured in the {@link Builder}.
*/
public
final
class
ExperimentalBandwidthMeter
implements
BandwidthMeter
,
TransferListener
{
/** Default initial Wifi bitrate estimate in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI
=
ImmutableList
.
of
(
4_400_000L
,
3_200_000L
,
2_300_000L
,
1_600_000L
,
810_000L
);
/** Default initial 2G bitrate estimates in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_2G
=
ImmutableList
.
of
(
1_400_000L
,
990_000L
,
730_000L
,
510_000L
,
230_000L
);
/** Default initial 3G bitrate estimates in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_3G
=
ImmutableList
.
of
(
2_100_000L
,
1_400_000L
,
1_000_000L
,
890_000L
,
640_000L
);
/** Default initial 4G bitrate estimates in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_4G
=
ImmutableList
.
of
(
2_600_000L
,
1_700_000L
,
1_300_000L
,
1_000_000L
,
700_000L
);
/** Default initial 5G-NSA bitrate estimates in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA
=
ImmutableList
.
of
(
5_700_000L
,
3_700_000L
,
2_300_000L
,
1_700_000L
,
990_000L
);
/** Default initial 5G-SA bitrate estimates in bits per second. */
public
static
final
ImmutableList
<
Long
>
DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA
=
ImmutableList
.
of
(
2_800_000L
,
1_800_000L
,
1_400_000L
,
1_100_000L
,
870_000L
);
/**
* Default number of samples to keep in the sliding window for estimating the time to first byte.
*/
public
static
final
int
DEFAULT_TIME_TO_FIRST_BYTE_SAMPLES
=
20
;
/** Default percentile for estimating the time to first byte. */
public
static
final
float
DEFAULT_TIME_TO_FIRST_BYTE_PERCENTILE
=
0.5f
;
/**
* Default initial bitrate estimate used when the device is offline or the network type cannot be
* determined, in bits per second.
*/
public
static
final
long
DEFAULT_INITIAL_BITRATE_ESTIMATE
=
1_000_000
;
/**
* Index for the Wifi group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_WIFI
=
0
;
/**
* Index for the 2G group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_2G
=
1
;
/**
* Index for the 3G group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_3G
=
2
;
/**
* Index for the 4G group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_4G
=
3
;
/**
* Index for the 5G-NSA group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_5G_NSA
=
4
;
/**
* Index for the 5G-SA group index in the array returned by {@link
* #getInitialBitrateCountryGroupAssignment}.
*/
private
static
final
int
COUNTRY_GROUP_INDEX_5G_SA
=
5
;
/** Builder for a bandwidth meter. */
public
static
final
class
Builder
{
private
final
Context
context
;
private
Map
<
Integer
,
Long
>
initialBitrateEstimates
;
private
TimeToFirstByteEstimator
timeToFirstByteEstimator
;
private
BandwidthEstimator
bandwidthEstimator
;
private
boolean
resetOnNetworkTypeChange
;
/**
* Creates a builder with default parameters and without listener.
*
* @param context A context.
*/
public
Builder
(
Context
context
)
{
// Handling of null is for backward compatibility only.
this
.
context
=
context
.
getApplicationContext
();
initialBitrateEstimates
=
getInitialBitrateEstimatesForCountry
(
Util
.
getCountryCode
(
context
));
timeToFirstByteEstimator
=
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
DEFAULT_TIME_TO_FIRST_BYTE_SAMPLES
,
/* percentile= */
DEFAULT_TIME_TO_FIRST_BYTE_PERCENTILE
);
bandwidthEstimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
build
();
resetOnNetworkTypeChange
=
true
;
}
/**
* Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth
* estimate is unavailable.
*
* @param initialBitrateEstimate The initial bitrate estimate in bits per second.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
setInitialBitrateEstimate
(
long
initialBitrateEstimate
)
{
for
(
Integer
networkType
:
initialBitrateEstimates
.
keySet
())
{
setInitialBitrateEstimate
(
networkType
,
initialBitrateEstimate
);
}
return
this
;
}
/**
* Sets the initial bitrate estimate in bits per second that should be assumed when a bandwidth
* estimate is unavailable and the current network connection is of the specified type.
*
* @param networkType The {@link C.NetworkType} this initial estimate is for.
* @param initialBitrateEstimate The initial bitrate estimate in bits per second.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
setInitialBitrateEstimate
(
@C
.
NetworkType
int
networkType
,
long
initialBitrateEstimate
)
{
initialBitrateEstimates
.
put
(
networkType
,
initialBitrateEstimate
);
return
this
;
}
/**
* Sets the initial bitrate estimates to the default values of the specified country. The
* initial estimates are used when a bandwidth estimate is unavailable.
*
* @param countryCode The ISO 3166-1 alpha-2 country code of the country whose default bitrate
* estimates should be used.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
setInitialBitrateEstimate
(
String
countryCode
)
{
initialBitrateEstimates
=
getInitialBitrateEstimatesForCountry
(
Ascii
.
toUpperCase
(
countryCode
));
return
this
;
}
/**
* Sets the {@link TimeToFirstByteEstimator} to be used.
*
* <p>Default is {@link PercentileTimeToFirstByteEstimator} with a sliding window size of {@link
* #DEFAULT_TIME_TO_FIRST_BYTE_SAMPLES} that uses a percentile of {@link
* #DEFAULT_TIME_TO_FIRST_BYTE_PERCENTILE}.
*
* @param timeToFirstByteEstimator The {@link TimeToFirstByteEstimator} to be used.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
setTimeToFirstByteEstimator
(
TimeToFirstByteEstimator
timeToFirstByteEstimator
)
{
this
.
timeToFirstByteEstimator
=
timeToFirstByteEstimator
;
return
this
;
}
/**
* Sets the {@link BandwidthEstimator} used. By default, this is set to a {@link
* SplitParallelSampleBandwidthEstimator} using a {@link
* SlidingWeightedAverageBandwidthStatistic}.
*/
@CanIgnoreReturnValue
public
Builder
setBandwidthEstimator
(
BandwidthEstimator
bandwidthEstimator
)
{
this
.
bandwidthEstimator
=
bandwidthEstimator
;
return
this
;
}
/**
* Sets whether to reset if the network type changes. The default value is {@code true}.
*
* @param resetOnNetworkTypeChange Whether to reset if the network type changes.
* @return This builder.
*/
@CanIgnoreReturnValue
public
Builder
setResetOnNetworkTypeChange
(
boolean
resetOnNetworkTypeChange
)
{
this
.
resetOnNetworkTypeChange
=
resetOnNetworkTypeChange
;
return
this
;
}
/**
* Builds the bandwidth meter.
*
* @return A bandwidth meter with the configured properties.
*/
public
ExperimentalBandwidthMeter
build
()
{
return
new
ExperimentalBandwidthMeter
(
context
,
initialBitrateEstimates
,
timeToFirstByteEstimator
,
bandwidthEstimator
,
resetOnNetworkTypeChange
);
}
private
static
Map
<
Integer
,
Long
>
getInitialBitrateEstimatesForCountry
(
String
countryCode
)
{
int
[]
groupIndices
=
getInitialBitrateCountryGroupAssignment
(
countryCode
);
Map
<
Integer
,
Long
>
result
=
new
HashMap
<>(
/* initialCapacity= */
8
);
result
.
put
(
C
.
NETWORK_TYPE_UNKNOWN
,
DEFAULT_INITIAL_BITRATE_ESTIMATE
);
result
.
put
(
C
.
NETWORK_TYPE_WIFI
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_WIFI
]));
result
.
put
(
C
.
NETWORK_TYPE_2G
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_2G
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_2G
]));
result
.
put
(
C
.
NETWORK_TYPE_3G
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_3G
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_3G
]));
result
.
put
(
C
.
NETWORK_TYPE_4G
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_4G
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_4G
]));
result
.
put
(
C
.
NETWORK_TYPE_5G_NSA
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_5G_NSA
]));
result
.
put
(
C
.
NETWORK_TYPE_5G_SA
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_5G_SA
]));
// Assume default Wifi speed for Ethernet to prevent using the slower fallback.
result
.
put
(
C
.
NETWORK_TYPE_ETHERNET
,
DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI
.
get
(
groupIndices
[
COUNTRY_GROUP_INDEX_WIFI
]));
return
result
;
}
}
private
final
ImmutableMap
<
Integer
,
Long
>
initialBitrateEstimates
;
private
final
TimeToFirstByteEstimator
timeToFirstByteEstimator
;
private
final
BandwidthEstimator
bandwidthEstimator
;
private
final
boolean
resetOnNetworkTypeChange
;
private
@C
.
NetworkType
int
networkType
;
private
long
initialBitrateEstimate
;
private
boolean
networkTypeOverrideSet
;
private
@C
.
NetworkType
int
networkTypeOverride
;
private
ExperimentalBandwidthMeter
(
Context
context
,
Map
<
Integer
,
Long
>
initialBitrateEstimates
,
TimeToFirstByteEstimator
timeToFirstByteEstimator
,
BandwidthEstimator
bandwidthEstimator
,
boolean
resetOnNetworkTypeChange
)
{
this
.
initialBitrateEstimates
=
ImmutableMap
.
copyOf
(
initialBitrateEstimates
);
this
.
timeToFirstByteEstimator
=
timeToFirstByteEstimator
;
this
.
bandwidthEstimator
=
bandwidthEstimator
;
this
.
resetOnNetworkTypeChange
=
resetOnNetworkTypeChange
;
NetworkTypeObserver
networkTypeObserver
=
NetworkTypeObserver
.
getInstance
(
context
);
networkType
=
networkTypeObserver
.
getNetworkType
();
initialBitrateEstimate
=
getInitialBitrateEstimateForNetworkType
(
networkType
);
networkTypeObserver
.
register
(
/* listener= */
this
::
onNetworkTypeChanged
);
}
/**
* Overrides the network type. Handled in the same way as if the meter had detected a change from
* the current network type to the specified network type internally.
*
* <p>Applications should not normally call this method. It is intended for testing purposes.
*
* @param networkType The overriding network type.
*/
public
synchronized
void
setNetworkTypeOverride
(
@C
.
NetworkType
int
networkType
)
{
networkTypeOverride
=
networkType
;
networkTypeOverrideSet
=
true
;
onNetworkTypeChanged
(
networkType
);
}
@Override
public
synchronized
long
getBitrateEstimate
()
{
long
bandwidthEstimate
=
bandwidthEstimator
.
getBandwidthEstimate
();
return
bandwidthEstimate
!=
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
?
bandwidthEstimate
:
initialBitrateEstimate
;
}
@Override
public
long
getTimeToFirstByteEstimateUs
()
{
return
timeToFirstByteEstimator
.
getTimeToFirstByteEstimateUs
();
}
@Override
public
TransferListener
getTransferListener
()
{
return
this
;
}
@Override
public
void
addEventListener
(
Handler
eventHandler
,
EventListener
eventListener
)
{
checkNotNull
(
eventHandler
);
checkNotNull
(
eventListener
);
bandwidthEstimator
.
addEventListener
(
eventHandler
,
eventListener
);
}
@Override
public
void
removeEventListener
(
EventListener
eventListener
)
{
bandwidthEstimator
.
removeEventListener
(
eventListener
);
}
@Override
public
void
onTransferInitializing
(
DataSource
source
,
DataSpec
dataSpec
,
boolean
isNetwork
)
{
if
(!
isTransferAtFullNetworkSpeed
(
dataSpec
,
isNetwork
))
{
return
;
}
timeToFirstByteEstimator
.
onTransferInitializing
(
dataSpec
);
bandwidthEstimator
.
onTransferInitializing
(
source
);
}
@Override
public
synchronized
void
onTransferStart
(
DataSource
source
,
DataSpec
dataSpec
,
boolean
isNetwork
)
{
if
(!
isTransferAtFullNetworkSpeed
(
dataSpec
,
isNetwork
))
{
return
;
}
timeToFirstByteEstimator
.
onTransferStart
(
dataSpec
);
bandwidthEstimator
.
onTransferStart
(
source
);
}
@Override
public
synchronized
void
onBytesTransferred
(
DataSource
source
,
DataSpec
dataSpec
,
boolean
isNetwork
,
int
bytesTransferred
)
{
if
(!
isTransferAtFullNetworkSpeed
(
dataSpec
,
isNetwork
))
{
return
;
}
bandwidthEstimator
.
onBytesTransferred
(
source
,
bytesTransferred
);
}
@Override
public
synchronized
void
onTransferEnd
(
DataSource
source
,
DataSpec
dataSpec
,
boolean
isNetwork
)
{
if
(!
isTransferAtFullNetworkSpeed
(
dataSpec
,
isNetwork
))
{
return
;
}
bandwidthEstimator
.
onTransferEnd
(
source
);
}
private
synchronized
void
onNetworkTypeChanged
(
@C
.
NetworkType
int
networkType
)
{
if
(
this
.
networkType
!=
C
.
NETWORK_TYPE_UNKNOWN
&&
!
resetOnNetworkTypeChange
)
{
// Reset on network change disabled. Ignore all updates except the initial one.
return
;
}
if
(
networkTypeOverrideSet
)
{
networkType
=
networkTypeOverride
;
}
if
(
this
.
networkType
==
networkType
)
{
return
;
}
this
.
networkType
=
networkType
;
if
(
networkType
==
C
.
NETWORK_TYPE_OFFLINE
||
networkType
==
C
.
NETWORK_TYPE_UNKNOWN
||
networkType
==
C
.
NETWORK_TYPE_OTHER
)
{
// It's better not to reset the bandwidth meter for these network types.
return
;
}
// Reset the bitrate estimate and report it, along with any bytes transferred.
this
.
initialBitrateEstimate
=
getInitialBitrateEstimateForNetworkType
(
networkType
);
bandwidthEstimator
.
onNetworkTypeChange
(
initialBitrateEstimate
);
timeToFirstByteEstimator
.
reset
();
}
private
long
getInitialBitrateEstimateForNetworkType
(
@C
.
NetworkType
int
networkType
)
{
@Nullable
Long
initialBitrateEstimate
=
initialBitrateEstimates
.
get
(
networkType
);
if
(
initialBitrateEstimate
==
null
)
{
initialBitrateEstimate
=
initialBitrateEstimates
.
get
(
C
.
NETWORK_TYPE_UNKNOWN
);
}
if
(
initialBitrateEstimate
==
null
)
{
initialBitrateEstimate
=
DEFAULT_INITIAL_BITRATE_ESTIMATE
;
}
return
initialBitrateEstimate
;
}
private
static
boolean
isTransferAtFullNetworkSpeed
(
DataSpec
dataSpec
,
boolean
isNetwork
)
{
return
isNetwork
&&
!
dataSpec
.
isFlagSet
(
DataSpec
.
FLAG_MIGHT_NOT_USE_FULL_NETWORK_SPEED
);
}
/**
* Returns initial bitrate group assignments for a {@code country}. The initial bitrate is a list
* of indices for [Wifi, 2G, 3G, 4G, 5G_NSA, 5G_SA].
*/
private
static
int
[]
getInitialBitrateCountryGroupAssignment
(
String
country
)
{
switch
(
country
)
{
case
"AD"
:
case
"CW"
:
return
new
int
[]
{
2
,
2
,
0
,
0
,
2
,
2
};
case
"AE"
:
return
new
int
[]
{
1
,
4
,
3
,
4
,
4
,
2
};
case
"AG"
:
return
new
int
[]
{
2
,
4
,
3
,
4
,
2
,
2
};
case
"AL"
:
return
new
int
[]
{
1
,
1
,
1
,
3
,
2
,
2
};
case
"AM"
:
return
new
int
[]
{
2
,
3
,
2
,
3
,
2
,
2
};
case
"AO"
:
return
new
int
[]
{
4
,
4
,
4
,
3
,
2
,
2
};
case
"AS"
:
return
new
int
[]
{
2
,
2
,
3
,
3
,
2
,
2
};
case
"AT"
:
return
new
int
[]
{
1
,
2
,
1
,
4
,
1
,
4
};
case
"AU"
:
return
new
int
[]
{
0
,
2
,
1
,
1
,
3
,
0
};
case
"BE"
:
return
new
int
[]
{
0
,
1
,
4
,
4
,
3
,
2
};
case
"BH"
:
return
new
int
[]
{
1
,
3
,
1
,
4
,
4
,
2
};
case
"BJ"
:
return
new
int
[]
{
4
,
4
,
2
,
3
,
2
,
2
};
case
"BN"
:
return
new
int
[]
{
3
,
2
,
0
,
1
,
2
,
2
};
case
"BO"
:
return
new
int
[]
{
1
,
2
,
3
,
2
,
2
,
2
};
case
"BR"
:
return
new
int
[]
{
1
,
1
,
2
,
1
,
1
,
0
};
case
"BW"
:
return
new
int
[]
{
3
,
2
,
1
,
0
,
2
,
2
};
case
"BY"
:
return
new
int
[]
{
1
,
1
,
2
,
3
,
2
,
2
};
case
"CA"
:
return
new
int
[]
{
0
,
2
,
3
,
3
,
3
,
3
};
case
"CH"
:
return
new
int
[]
{
0
,
0
,
0
,
0
,
0
,
3
};
case
"BZ"
:
case
"CK"
:
return
new
int
[]
{
2
,
2
,
2
,
1
,
2
,
2
};
case
"CL"
:
return
new
int
[]
{
1
,
1
,
2
,
1
,
3
,
2
};
case
"CM"
:
return
new
int
[]
{
4
,
3
,
3
,
4
,
2
,
2
};
case
"CN"
:
return
new
int
[]
{
2
,
0
,
4
,
3
,
3
,
1
};
case
"CO"
:
return
new
int
[]
{
2
,
3
,
4
,
2
,
2
,
2
};
case
"CR"
:
return
new
int
[]
{
2
,
4
,
4
,
4
,
2
,
2
};
case
"CV"
:
return
new
int
[]
{
2
,
3
,
0
,
1
,
2
,
2
};
case
"CZ"
:
return
new
int
[]
{
0
,
0
,
2
,
0
,
1
,
2
};
case
"DE"
:
return
new
int
[]
{
0
,
1
,
3
,
2
,
2
,
2
};
case
"DO"
:
return
new
int
[]
{
3
,
4
,
4
,
4
,
4
,
2
};
case
"AZ"
:
case
"BF"
:
case
"DZ"
:
return
new
int
[]
{
3
,
3
,
4
,
4
,
2
,
2
};
case
"EC"
:
return
new
int
[]
{
1
,
3
,
2
,
1
,
2
,
2
};
case
"CI"
:
case
"EG"
:
return
new
int
[]
{
3
,
4
,
3
,
3
,
2
,
2
};
case
"FI"
:
return
new
int
[]
{
0
,
0
,
0
,
2
,
0
,
2
};
case
"FJ"
:
return
new
int
[]
{
3
,
1
,
2
,
3
,
2
,
2
};
case
"FM"
:
return
new
int
[]
{
4
,
2
,
3
,
0
,
2
,
2
};
case
"AI"
:
case
"BB"
:
case
"BM"
:
case
"BQ"
:
case
"DM"
:
case
"FO"
:
return
new
int
[]
{
0
,
2
,
0
,
0
,
2
,
2
};
case
"FR"
:
return
new
int
[]
{
1
,
1
,
2
,
1
,
1
,
2
};
case
"GB"
:
return
new
int
[]
{
0
,
1
,
1
,
2
,
1
,
2
};
case
"GE"
:
return
new
int
[]
{
1
,
0
,
0
,
2
,
2
,
2
};
case
"GG"
:
return
new
int
[]
{
0
,
2
,
1
,
0
,
2
,
2
};
case
"CG"
:
case
"GH"
:
return
new
int
[]
{
3
,
3
,
3
,
3
,
2
,
2
};
case
"GM"
:
return
new
int
[]
{
4
,
3
,
2
,
4
,
2
,
2
};
case
"GN"
:
return
new
int
[]
{
4
,
4
,
4
,
2
,
2
,
2
};
case
"GP"
:
return
new
int
[]
{
3
,
1
,
1
,
3
,
2
,
2
};
case
"GQ"
:
return
new
int
[]
{
4
,
4
,
3
,
3
,
2
,
2
};
case
"GT"
:
return
new
int
[]
{
2
,
2
,
2
,
1
,
1
,
2
};
case
"AW"
:
case
"GU"
:
return
new
int
[]
{
1
,
2
,
4
,
4
,
2
,
2
};
case
"GW"
:
return
new
int
[]
{
4
,
4
,
2
,
2
,
2
,
2
};
case
"GY"
:
return
new
int
[]
{
3
,
0
,
1
,
1
,
2
,
2
};
case
"HK"
:
return
new
int
[]
{
0
,
1
,
1
,
3
,
2
,
0
};
case
"HN"
:
return
new
int
[]
{
3
,
3
,
2
,
2
,
2
,
2
};
case
"ID"
:
return
new
int
[]
{
3
,
1
,
1
,
2
,
3
,
2
};
case
"BA"
:
case
"IE"
:
return
new
int
[]
{
1
,
1
,
1
,
1
,
2
,
2
};
case
"IL"
:
return
new
int
[]
{
1
,
2
,
2
,
3
,
4
,
2
};
case
"IM"
:
return
new
int
[]
{
0
,
2
,
0
,
1
,
2
,
2
};
case
"IN"
:
return
new
int
[]
{
1
,
1
,
2
,
1
,
2
,
1
};
case
"IR"
:
return
new
int
[]
{
4
,
2
,
3
,
3
,
4
,
2
};
case
"IS"
:
return
new
int
[]
{
0
,
0
,
1
,
0
,
0
,
2
};
case
"IT"
:
return
new
int
[]
{
0
,
0
,
1
,
1
,
1
,
2
};
case
"GI"
:
case
"JE"
:
return
new
int
[]
{
1
,
2
,
0
,
1
,
2
,
2
};
case
"JM"
:
return
new
int
[]
{
2
,
4
,
2
,
1
,
2
,
2
};
case
"JO"
:
return
new
int
[]
{
2
,
0
,
1
,
1
,
2
,
2
};
case
"JP"
:
return
new
int
[]
{
0
,
3
,
3
,
3
,
4
,
4
};
case
"KE"
:
return
new
int
[]
{
3
,
2
,
2
,
1
,
2
,
2
};
case
"KH"
:
return
new
int
[]
{
1
,
0
,
4
,
2
,
2
,
2
};
case
"CU"
:
case
"KI"
:
return
new
int
[]
{
4
,
2
,
4
,
3
,
2
,
2
};
case
"CD"
:
case
"KM"
:
return
new
int
[]
{
4
,
3
,
3
,
2
,
2
,
2
};
case
"KR"
:
return
new
int
[]
{
0
,
2
,
2
,
4
,
4
,
4
};
case
"KW"
:
return
new
int
[]
{
1
,
0
,
1
,
0
,
0
,
2
};
case
"BD"
:
case
"KZ"
:
return
new
int
[]
{
2
,
1
,
2
,
2
,
2
,
2
};
case
"LA"
:
return
new
int
[]
{
1
,
2
,
1
,
3
,
2
,
2
};
case
"BS"
:
case
"LB"
:
return
new
int
[]
{
3
,
2
,
1
,
2
,
2
,
2
};
case
"LK"
:
return
new
int
[]
{
3
,
2
,
3
,
4
,
4
,
2
};
case
"LR"
:
return
new
int
[]
{
3
,
4
,
3
,
4
,
2
,
2
};
case
"LU"
:
return
new
int
[]
{
1
,
1
,
4
,
2
,
0
,
2
};
case
"CY"
:
case
"HR"
:
case
"LV"
:
return
new
int
[]
{
1
,
0
,
0
,
0
,
0
,
2
};
case
"MA"
:
return
new
int
[]
{
3
,
3
,
2
,
1
,
2
,
2
};
case
"MC"
:
return
new
int
[]
{
0
,
2
,
2
,
0
,
2
,
2
};
case
"MD"
:
return
new
int
[]
{
1
,
0
,
0
,
0
,
2
,
2
};
case
"ME"
:
return
new
int
[]
{
2
,
0
,
0
,
1
,
1
,
2
};
case
"MH"
:
return
new
int
[]
{
4
,
2
,
1
,
3
,
2
,
2
};
case
"MK"
:
return
new
int
[]
{
2
,
0
,
0
,
1
,
3
,
2
};
case
"MM"
:
return
new
int
[]
{
2
,
2
,
2
,
3
,
4
,
2
};
case
"MN"
:
return
new
int
[]
{
2
,
0
,
1
,
2
,
2
,
2
};
case
"MO"
:
return
new
int
[]
{
0
,
2
,
4
,
4
,
4
,
2
};
case
"KG"
:
case
"MQ"
:
return
new
int
[]
{
2
,
1
,
1
,
2
,
2
,
2
};
case
"MR"
:
return
new
int
[]
{
4
,
2
,
3
,
4
,
2
,
2
};
case
"DK"
:
case
"EE"
:
case
"HU"
:
case
"LT"
:
case
"MT"
:
return
new
int
[]
{
0
,
0
,
0
,
0
,
0
,
2
};
case
"MV"
:
return
new
int
[]
{
3
,
4
,
1
,
3
,
3
,
2
};
case
"MW"
:
return
new
int
[]
{
4
,
2
,
3
,
3
,
2
,
2
};
case
"MX"
:
return
new
int
[]
{
3
,
4
,
4
,
4
,
2
,
2
};
case
"MY"
:
return
new
int
[]
{
1
,
0
,
4
,
1
,
2
,
2
};
case
"NA"
:
return
new
int
[]
{
3
,
4
,
3
,
2
,
2
,
2
};
case
"NC"
:
return
new
int
[]
{
3
,
2
,
3
,
4
,
2
,
2
};
case
"NG"
:
return
new
int
[]
{
3
,
4
,
2
,
1
,
2
,
2
};
case
"NI"
:
return
new
int
[]
{
2
,
3
,
4
,
3
,
2
,
2
};
case
"NL"
:
return
new
int
[]
{
0
,
2
,
3
,
3
,
0
,
4
};
case
"NO"
:
return
new
int
[]
{
0
,
1
,
2
,
1
,
1
,
2
};
case
"NP"
:
return
new
int
[]
{
2
,
1
,
4
,
3
,
2
,
2
};
case
"NR"
:
return
new
int
[]
{
4
,
0
,
3
,
2
,
2
,
2
};
case
"NU"
:
return
new
int
[]
{
4
,
2
,
2
,
1
,
2
,
2
};
case
"NZ"
:
return
new
int
[]
{
1
,
0
,
2
,
2
,
4
,
2
};
case
"OM"
:
return
new
int
[]
{
2
,
3
,
1
,
3
,
4
,
2
};
case
"PA"
:
return
new
int
[]
{
2
,
3
,
3
,
3
,
2
,
2
};
case
"PE"
:
return
new
int
[]
{
1
,
2
,
4
,
4
,
3
,
2
};
case
"AF"
:
case
"PG"
:
return
new
int
[]
{
4
,
3
,
3
,
3
,
2
,
2
};
case
"PH"
:
return
new
int
[]
{
2
,
1
,
3
,
2
,
2
,
0
};
case
"PL"
:
return
new
int
[]
{
2
,
1
,
2
,
2
,
4
,
2
};
case
"PR"
:
return
new
int
[]
{
2
,
0
,
2
,
0
,
2
,
1
};
case
"PS"
:
return
new
int
[]
{
3
,
4
,
1
,
4
,
2
,
2
};
case
"PT"
:
return
new
int
[]
{
1
,
0
,
0
,
0
,
1
,
2
};
case
"PW"
:
return
new
int
[]
{
2
,
2
,
4
,
2
,
2
,
2
};
case
"BL"
:
case
"MF"
:
case
"PY"
:
return
new
int
[]
{
1
,
2
,
2
,
2
,
2
,
2
};
case
"QA"
:
return
new
int
[]
{
1
,
4
,
4
,
4
,
4
,
2
};
case
"RE"
:
return
new
int
[]
{
1
,
2
,
2
,
3
,
1
,
2
};
case
"RO"
:
return
new
int
[]
{
0
,
0
,
1
,
2
,
1
,
2
};
case
"RS"
:
return
new
int
[]
{
2
,
0
,
0
,
0
,
2
,
2
};
case
"RU"
:
return
new
int
[]
{
1
,
0
,
0
,
0
,
3
,
3
};
case
"RW"
:
return
new
int
[]
{
3
,
3
,
1
,
0
,
2
,
2
};
case
"MU"
:
case
"SA"
:
return
new
int
[]
{
3
,
1
,
1
,
2
,
2
,
2
};
case
"CF"
:
case
"SB"
:
return
new
int
[]
{
4
,
2
,
4
,
2
,
2
,
2
};
case
"SC"
:
return
new
int
[]
{
4
,
3
,
1
,
1
,
2
,
2
};
case
"SD"
:
return
new
int
[]
{
4
,
3
,
4
,
2
,
2
,
2
};
case
"SE"
:
return
new
int
[]
{
0
,
1
,
1
,
1
,
0
,
2
};
case
"SG"
:
return
new
int
[]
{
2
,
3
,
3
,
3
,
3
,
3
};
case
"AQ"
:
case
"ER"
:
case
"SH"
:
return
new
int
[]
{
4
,
2
,
2
,
2
,
2
,
2
};
case
"BG"
:
case
"ES"
:
case
"GR"
:
case
"SI"
:
return
new
int
[]
{
0
,
0
,
0
,
0
,
1
,
2
};
case
"IQ"
:
case
"SJ"
:
return
new
int
[]
{
3
,
2
,
2
,
2
,
2
,
2
};
case
"SK"
:
return
new
int
[]
{
1
,
1
,
1
,
1
,
3
,
2
};
case
"GF"
:
case
"PK"
:
case
"SL"
:
return
new
int
[]
{
3
,
2
,
3
,
3
,
2
,
2
};
case
"ET"
:
case
"SN"
:
return
new
int
[]
{
4
,
4
,
3
,
2
,
2
,
2
};
case
"SO"
:
return
new
int
[]
{
3
,
2
,
2
,
4
,
4
,
2
};
case
"SR"
:
return
new
int
[]
{
2
,
4
,
3
,
0
,
2
,
2
};
case
"ST"
:
return
new
int
[]
{
2
,
2
,
1
,
2
,
2
,
2
};
case
"PF"
:
case
"SV"
:
return
new
int
[]
{
2
,
3
,
3
,
1
,
2
,
2
};
case
"SZ"
:
return
new
int
[]
{
4
,
4
,
3
,
4
,
2
,
2
};
case
"TC"
:
return
new
int
[]
{
2
,
2
,
1
,
3
,
2
,
2
};
case
"GA"
:
case
"TG"
:
return
new
int
[]
{
3
,
4
,
1
,
0
,
2
,
2
};
case
"TH"
:
return
new
int
[]
{
0
,
1
,
2
,
1
,
2
,
2
};
case
"DJ"
:
case
"SY"
:
case
"TJ"
:
return
new
int
[]
{
4
,
3
,
4
,
4
,
2
,
2
};
case
"GL"
:
case
"TK"
:
return
new
int
[]
{
2
,
2
,
2
,
4
,
2
,
2
};
case
"TL"
:
return
new
int
[]
{
4
,
2
,
4
,
4
,
2
,
2
};
case
"SS"
:
case
"TM"
:
return
new
int
[]
{
4
,
2
,
2
,
3
,
2
,
2
};
case
"TR"
:
return
new
int
[]
{
1
,
0
,
0
,
1
,
3
,
2
};
case
"TT"
:
return
new
int
[]
{
1
,
4
,
0
,
0
,
2
,
2
};
case
"TW"
:
return
new
int
[]
{
0
,
2
,
0
,
0
,
0
,
0
};
case
"ML"
:
case
"TZ"
:
return
new
int
[]
{
3
,
4
,
2
,
2
,
2
,
2
};
case
"UA"
:
return
new
int
[]
{
0
,
1
,
1
,
2
,
4
,
2
};
case
"LS"
:
case
"UG"
:
return
new
int
[]
{
3
,
3
,
3
,
2
,
2
,
2
};
case
"US"
:
return
new
int
[]
{
1
,
1
,
4
,
1
,
3
,
1
};
case
"TN"
:
case
"UY"
:
return
new
int
[]
{
2
,
1
,
1
,
1
,
2
,
2
};
case
"UZ"
:
return
new
int
[]
{
2
,
2
,
3
,
4
,
3
,
2
};
case
"AX"
:
case
"CX"
:
case
"LI"
:
case
"MP"
:
case
"MS"
:
case
"PM"
:
case
"SM"
:
case
"VA"
:
return
new
int
[]
{
0
,
2
,
2
,
2
,
2
,
2
};
case
"GD"
:
case
"KN"
:
case
"KY"
:
case
"LC"
:
case
"SX"
:
case
"VC"
:
return
new
int
[]
{
1
,
2
,
0
,
0
,
2
,
2
};
case
"VG"
:
return
new
int
[]
{
2
,
2
,
0
,
1
,
2
,
2
};
case
"VI"
:
return
new
int
[]
{
0
,
2
,
1
,
2
,
2
,
2
};
case
"VN"
:
return
new
int
[]
{
0
,
0
,
1
,
2
,
2
,
1
};
case
"VU"
:
return
new
int
[]
{
4
,
3
,
3
,
1
,
2
,
2
};
case
"IO"
:
case
"TV"
:
case
"WF"
:
return
new
int
[]
{
4
,
2
,
2
,
4
,
2
,
2
};
case
"BT"
:
case
"MZ"
:
case
"WS"
:
return
new
int
[]
{
3
,
1
,
2
,
1
,
2
,
2
};
case
"XK"
:
return
new
int
[]
{
1
,
2
,
1
,
1
,
2
,
2
};
case
"BI"
:
case
"HT"
:
case
"MG"
:
case
"NE"
:
case
"TD"
:
case
"VE"
:
case
"YE"
:
return
new
int
[]
{
4
,
4
,
4
,
4
,
2
,
2
};
case
"YT"
:
return
new
int
[]
{
2
,
3
,
3
,
4
,
2
,
2
};
case
"ZA"
:
return
new
int
[]
{
2
,
3
,
2
,
1
,
2
,
2
};
case
"ZM"
:
return
new
int
[]
{
4
,
4
,
4
,
3
,
3
,
2
};
case
"LY"
:
case
"TO"
:
case
"ZW"
:
return
new
int
[]
{
3
,
2
,
4
,
3
,
2
,
2
};
default
:
return
new
int
[]
{
2
,
2
,
2
,
2
,
2
,
2
};
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageStatistic.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
.
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
;
/** A {@link BandwidthStatistic} that calculates estimates using an exponential weighted average. */
public
class
ExponentialWeightedAverageStatistic
implements
BandwidthStatistic
{
/** The default smoothing factor. */
public
static
final
double
DEFAULT_SMOOTHING_FACTOR
=
0.9999
;
private
final
double
smoothingFactor
;
private
long
bitrateEstimate
;
/** Creates an instance with {@link #DEFAULT_SMOOTHING_FACTOR}. */
public
ExponentialWeightedAverageStatistic
()
{
this
(
DEFAULT_SMOOTHING_FACTOR
);
}
/**
* Creates an instance.
*
* @param smoothingFactor The exponential smoothing factor.
*/
public
ExponentialWeightedAverageStatistic
(
double
smoothingFactor
)
{
this
.
smoothingFactor
=
smoothingFactor
;
bitrateEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
@Override
public
void
addSample
(
long
bytes
,
long
durationUs
)
{
long
bitrate
=
bytes
*
8_000_000
/
durationUs
;
if
(
bitrateEstimate
==
ESTIMATE_NOT_AVAILABLE
)
{
bitrateEstimate
=
bitrate
;
return
;
}
// Weight smoothing factor by sqrt(bytes).
double
factor
=
Math
.
pow
(
smoothingFactor
,
Math
.
sqrt
((
double
)
bytes
));
bitrateEstimate
=
(
long
)
(
factor
*
bitrateEstimate
+
(
1
f
-
factor
)
*
bitrate
);
}
@Override
public
long
getBandwidthEstimate
()
{
return
bitrateEstimate
;
}
@Override
public
void
reset
()
{
bitrateEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageTimeToFirstByteEstimator.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.android.exoplayer2.upstream.TimeToFirstByteEstimator
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
/** Implementation of {@link TimeToFirstByteEstimator} based on exponential weighted average. */
public
final
class
ExponentialWeightedAverageTimeToFirstByteEstimator
implements
TimeToFirstByteEstimator
{
/** The default smoothing factor. */
public
static
final
double
DEFAULT_SMOOTHING_FACTOR
=
0.85
;
private
static
final
int
MAX_DATA_SPECS
=
10
;
private
final
LinkedHashMap
<
DataSpec
,
Long
>
initializedDataSpecs
;
private
final
double
smoothingFactor
;
private
final
Clock
clock
;
private
long
estimateUs
;
/** Creates an instance using the {@link #DEFAULT_SMOOTHING_FACTOR}. */
public
ExponentialWeightedAverageTimeToFirstByteEstimator
()
{
this
(
DEFAULT_SMOOTHING_FACTOR
,
Clock
.
DEFAULT
);
}
/**
* Creates an instance.
*
* @param smoothingFactor The exponential weighted average smoothing factor.
*/
public
ExponentialWeightedAverageTimeToFirstByteEstimator
(
double
smoothingFactor
)
{
this
(
smoothingFactor
,
Clock
.
DEFAULT
);
}
/**
* Creates an instance.
*
* @param smoothingFactor The exponential weighted average smoothing factor.
* @param clock The {@link Clock} used for calculating time samples.
*/
@VisibleForTesting
/* package */
ExponentialWeightedAverageTimeToFirstByteEstimator
(
double
smoothingFactor
,
Clock
clock
)
{
this
.
smoothingFactor
=
smoothingFactor
;
this
.
clock
=
clock
;
initializedDataSpecs
=
new
FixedSizeLinkedHashMap
<>(
/* maxSize= */
MAX_DATA_SPECS
);
estimateUs
=
C
.
TIME_UNSET
;
}
@Override
public
long
getTimeToFirstByteEstimateUs
()
{
return
estimateUs
;
}
@Override
public
void
reset
()
{
estimateUs
=
C
.
TIME_UNSET
;
}
@Override
public
void
onTransferInitializing
(
DataSpec
dataSpec
)
{
// Remove to make sure insertion order is updated in case the key already exists.
initializedDataSpecs
.
remove
(
dataSpec
);
initializedDataSpecs
.
put
(
dataSpec
,
Util
.
msToUs
(
clock
.
elapsedRealtime
()));
}
@Override
public
void
onTransferStart
(
DataSpec
dataSpec
)
{
@Nullable
Long
initializationStartUs
=
initializedDataSpecs
.
remove
(
dataSpec
);
if
(
initializationStartUs
==
null
)
{
return
;
}
long
timeToStartSampleUs
=
Util
.
msToUs
(
clock
.
elapsedRealtime
())
-
initializationStartUs
;
if
(
estimateUs
==
C
.
TIME_UNSET
)
{
estimateUs
=
timeToStartSampleUs
;
}
else
{
estimateUs
=
(
long
)
(
smoothingFactor
*
estimateUs
+
(
1
d
-
smoothingFactor
)
*
timeToStartSampleUs
);
}
}
private
static
class
FixedSizeLinkedHashMap
<
K
,
V
>
extends
LinkedHashMap
<
K
,
V
>
{
private
final
int
maxSize
;
public
FixedSizeLinkedHashMap
(
int
maxSize
)
{
this
.
maxSize
=
maxSize
;
}
@Override
protected
boolean
removeEldestEntry
(
Map
.
Entry
<
K
,
V
>
eldest
)
{
return
size
()
>
maxSize
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/PercentileTimeToFirstByteEstimator.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.android.exoplayer2.upstream.SlidingPercentile
;
import
com.google.android.exoplayer2.upstream.TimeToFirstByteEstimator
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
/**
* Implementation of {@link TimeToFirstByteEstimator} that returns a configured percentile of a
* sliding window of collected response times.
*/
public
final
class
PercentileTimeToFirstByteEstimator
implements
TimeToFirstByteEstimator
{
/** The default maximum number of samples. */
public
static
final
int
DEFAULT_MAX_SAMPLES_COUNT
=
10
;
/** The default percentile to return. */
public
static
final
float
DEFAULT_PERCENTILE
=
0.5f
;
private
static
final
int
MAX_DATA_SPECS
=
10
;
private
final
LinkedHashMap
<
DataSpec
,
Long
>
initializedDataSpecs
;
private
final
SlidingPercentile
slidingPercentile
;
private
final
float
percentile
;
private
final
Clock
clock
;
private
boolean
isEmpty
;
/**
* Creates an instance that keeps up to {@link #DEFAULT_MAX_SAMPLES_COUNT} samples and returns the
* {@link #DEFAULT_PERCENTILE} percentile.
*/
public
PercentileTimeToFirstByteEstimator
()
{
this
(
DEFAULT_MAX_SAMPLES_COUNT
,
DEFAULT_PERCENTILE
);
}
/**
* Creates an instance.
*
* @param numberOfSamples The maximum number of samples to be kept in the sliding window.
* @param percentile The percentile for estimating the time to the first byte.
*/
public
PercentileTimeToFirstByteEstimator
(
int
numberOfSamples
,
float
percentile
)
{
this
(
numberOfSamples
,
percentile
,
Clock
.
DEFAULT
);
}
/**
* Creates an instance.
*
* @param numberOfSamples The maximum number of samples to be kept in the sliding window.
* @param percentile The percentile for estimating the time to the first byte.
* @param clock The {@link Clock} to use.
*/
@VisibleForTesting
/* package */
PercentileTimeToFirstByteEstimator
(
int
numberOfSamples
,
float
percentile
,
Clock
clock
)
{
checkArgument
(
numberOfSamples
>
0
&&
percentile
>
0
&&
percentile
<=
1
);
this
.
percentile
=
percentile
;
this
.
clock
=
clock
;
initializedDataSpecs
=
new
FixedSizeLinkedHashMap
<>(
/* maxSize= */
MAX_DATA_SPECS
);
slidingPercentile
=
new
SlidingPercentile
(
/* maxWeight= */
numberOfSamples
);
isEmpty
=
true
;
}
@Override
public
long
getTimeToFirstByteEstimateUs
()
{
return
!
isEmpty
?
(
long
)
slidingPercentile
.
getPercentile
(
percentile
)
:
C
.
TIME_UNSET
;
}
@Override
public
void
reset
()
{
slidingPercentile
.
reset
();
isEmpty
=
true
;
}
@Override
public
void
onTransferInitializing
(
DataSpec
dataSpec
)
{
// Remove to make sure insertion order is updated in case the key already exists.
initializedDataSpecs
.
remove
(
dataSpec
);
initializedDataSpecs
.
put
(
dataSpec
,
Util
.
msToUs
(
clock
.
elapsedRealtime
()));
}
@Override
public
void
onTransferStart
(
DataSpec
dataSpec
)
{
@Nullable
Long
initializationStartUs
=
initializedDataSpecs
.
remove
(
dataSpec
);
if
(
initializationStartUs
==
null
)
{
return
;
}
slidingPercentile
.
addSample
(
/* weight= */
1
,
/* value= */
(
float
)
(
Util
.
msToUs
(
clock
.
elapsedRealtime
())
-
initializationStartUs
));
isEmpty
=
false
;
}
private
static
class
FixedSizeLinkedHashMap
<
K
,
V
>
extends
LinkedHashMap
<
K
,
V
>
{
private
final
int
maxSize
;
public
FixedSizeLinkedHashMap
(
int
maxSize
)
{
this
.
maxSize
=
maxSize
;
}
@Override
protected
boolean
removeEldestEntry
(
Map
.
Entry
<
K
,
V
>
eldest
)
{
return
size
()
>
maxSize
;
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SlidingPercentileBandwidthStatistic.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
.
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
com.google.android.exoplayer2.util.Util
;
import
java.util.ArrayDeque
;
import
java.util.TreeSet
;
/**
* A {@link BandwidthStatistic} that calculates estimates based on a sliding window weighted
* percentile.
*/
public
class
SlidingPercentileBandwidthStatistic
implements
BandwidthStatistic
{
/** The default maximum number of samples. */
public
static
final
int
DEFAULT_MAX_SAMPLES_COUNT
=
10
;
/** The default percentile to return. */
public
static
final
double
DEFAULT_PERCENTILE
=
0.5
;
private
final
int
maxSampleCount
;
private
final
double
percentile
;
private
final
ArrayDeque
<
Sample
>
samples
;
private
final
TreeSet
<
Sample
>
sortedSamples
;
private
double
weightSum
;
private
long
bitrateEstimate
;
/**
* Creates an instance with a maximum of {@link #DEFAULT_MAX_SAMPLES_COUNT} samples, returning the
* {@link #DEFAULT_PERCENTILE}.
*/
public
SlidingPercentileBandwidthStatistic
()
{
this
(
DEFAULT_MAX_SAMPLES_COUNT
,
DEFAULT_PERCENTILE
);
}
/**
* Creates an instance.
*
* @param maxSampleCount The maximum number of samples.
* @param percentile The percentile to return. Must be in the range of [0-1].
*/
public
SlidingPercentileBandwidthStatistic
(
int
maxSampleCount
,
double
percentile
)
{
checkArgument
(
percentile
>=
0
&&
percentile
<=
1
);
this
.
maxSampleCount
=
maxSampleCount
;
this
.
percentile
=
percentile
;
this
.
samples
=
new
ArrayDeque
<>();
this
.
sortedSamples
=
new
TreeSet
<>();
this
.
bitrateEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
@Override
public
void
addSample
(
long
bytes
,
long
durationUs
)
{
while
(
samples
.
size
()
>=
maxSampleCount
)
{
Sample
removedSample
=
samples
.
remove
();
sortedSamples
.
remove
(
removedSample
);
weightSum
-=
removedSample
.
weight
;
}
double
weight
=
Math
.
sqrt
((
double
)
bytes
);
long
bitrate
=
bytes
*
8_000_000
/
durationUs
;
Sample
sample
=
new
Sample
(
bitrate
,
weight
);
samples
.
add
(
sample
);
sortedSamples
.
add
(
sample
);
weightSum
+=
weight
;
bitrateEstimate
=
calculateBitrateEstimate
();
}
@Override
public
long
getBandwidthEstimate
()
{
return
bitrateEstimate
;
}
@Override
public
void
reset
()
{
samples
.
clear
();
sortedSamples
.
clear
();
weightSum
=
0
;
bitrateEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
private
long
calculateBitrateEstimate
()
{
if
(
samples
.
isEmpty
())
{
return
ESTIMATE_NOT_AVAILABLE
;
}
double
targetWeightSum
=
weightSum
*
percentile
;
double
previousPartialWeightSum
=
0
;
long
previousSampleBitrate
=
0
;
double
nextPartialWeightSum
=
0
;
for
(
Sample
sample
:
sortedSamples
)
{
// The percentile position of each sample is the middle of its weight. Hence, we need to add
// half the weight to check whether the target percentile is before or after this sample.
nextPartialWeightSum
+=
sample
.
weight
/
2
;
if
(
nextPartialWeightSum
>=
targetWeightSum
)
{
if
(
previousSampleBitrate
==
0
)
{
return
sample
.
bitrate
;
}
// Interpolate between samples to get an estimate for the target percentile.
double
partialBitrateBetweenSamples
=
(
sample
.
bitrate
-
previousSampleBitrate
)
*
(
targetWeightSum
-
previousPartialWeightSum
)
/
(
nextPartialWeightSum
-
previousPartialWeightSum
);
return
previousSampleBitrate
+
(
long
)
partialBitrateBetweenSamples
;
}
previousSampleBitrate
=
sample
.
bitrate
;
previousPartialWeightSum
=
nextPartialWeightSum
;
nextPartialWeightSum
+=
sample
.
weight
/
2
;
}
return
previousSampleBitrate
;
}
private
static
class
Sample
implements
Comparable
<
Sample
>
{
private
final
long
bitrate
;
private
final
double
weight
;
public
Sample
(
long
bitrate
,
double
weight
)
{
this
.
bitrate
=
bitrate
;
this
.
weight
=
weight
;
}
@Override
public
int
compareTo
(
Sample
other
)
{
return
Util
.
compareLong
(
this
.
bitrate
,
other
.
bitrate
);
}
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SlidingWeightedAverageBandwidthStatistic.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
castNonNull
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.util.Clock
;
import
java.util.ArrayDeque
;
import
java.util.Deque
;
/**
* A {@link BandwidthStatistic} that calculates estimates based on a sliding window weighted
* average.
*/
public
class
SlidingWeightedAverageBandwidthStatistic
implements
BandwidthStatistic
{
/** Represents a bandwidth sample. */
public
static
class
Sample
{
/** The sample bitrate. */
public
final
long
bitrate
;
/** The sample weight. */
public
final
double
weight
;
/**
* The time this sample was added, in milliseconds. Timestamps should come from the same source,
* so that samples can reliably be ordered in time. It is suggested to use {@link
* Clock#elapsedRealtime()}.
*/
public
final
long
timeAddedMs
;
/** Creates a new sample. */
public
Sample
(
long
bitrate
,
double
weight
,
long
timeAddedMs
)
{
this
.
bitrate
=
bitrate
;
this
.
weight
=
weight
;
this
.
timeAddedMs
=
timeAddedMs
;
}
}
/** An interface to decide if samples need to be evicted from the estimator. */
public
interface
SampleEvictionFunction
{
/**
* Whether the sample at the front of the queue needs to be evicted. Called before adding a next
* sample.
*
* @param samples A queue of samples, ordered by {@link Sample#timeAddedMs}. The oldest sample
* is at front of the queue. The queue must not be modified.
*/
boolean
shouldEvictSample
(
Deque
<
Sample
>
samples
);
}
/** Gets a {@link SampleEvictionFunction} that maintains up to {@code maxSamplesCount} samples. */
public
static
SampleEvictionFunction
getMaxCountEvictionFunction
(
long
maxSamplesCount
)
{
return
(
samples
)
->
samples
.
size
()
>=
maxSamplesCount
;
}
/** Gets a {@link SampleEvictionFunction} that maintains samples up to {@code maxAgeMs}. */
public
static
SampleEvictionFunction
getAgeBasedEvictionFunction
(
long
maxAgeMs
)
{
return
getAgeBasedEvictionFunction
(
maxAgeMs
,
Clock
.
DEFAULT
);
}
@VisibleForTesting
/* package */
static
SampleEvictionFunction
getAgeBasedEvictionFunction
(
long
maxAgeMs
,
Clock
clock
)
{
return
(
samples
)
->
{
if
(
samples
.
isEmpty
())
{
return
false
;
}
return
castNonNull
(
samples
.
peek
()).
timeAddedMs
+
maxAgeMs
<
clock
.
elapsedRealtime
();
};
}
/** The default maximum number of samples. */
public
static
final
int
DEFAULT_MAX_SAMPLES_COUNT
=
10
;
private
final
ArrayDeque
<
Sample
>
samples
;
private
final
SampleEvictionFunction
sampleEvictionFunction
;
private
final
Clock
clock
;
private
double
bitrateWeightProductSum
;
private
double
weightSum
;
/** Creates an instance that keeps up to {@link #DEFAULT_MAX_SAMPLES_COUNT} samples. */
public
SlidingWeightedAverageBandwidthStatistic
()
{
this
(
getMaxCountEvictionFunction
(
DEFAULT_MAX_SAMPLES_COUNT
));
}
/**
* Creates an instance.
*
* @param sampleEvictionFunction The {@link SampleEvictionFunction} deciding whether to drop
* samples when new samples are added.
*/
public
SlidingWeightedAverageBandwidthStatistic
(
SampleEvictionFunction
sampleEvictionFunction
)
{
this
(
sampleEvictionFunction
,
Clock
.
DEFAULT
);
}
/**
* Creates an instance.
*
* @param sampleEvictionFunction The {@link SampleEvictionFunction} deciding whether to drop
* samples when new samples are added.
* @param clock The {@link Clock} used.
*/
@VisibleForTesting
/* package */
SlidingWeightedAverageBandwidthStatistic
(
SampleEvictionFunction
sampleEvictionFunction
,
Clock
clock
)
{
this
.
samples
=
new
ArrayDeque
<>();
this
.
sampleEvictionFunction
=
sampleEvictionFunction
;
this
.
clock
=
clock
;
}
@Override
public
void
addSample
(
long
bytes
,
long
durationUs
)
{
while
(
sampleEvictionFunction
.
shouldEvictSample
(
samples
))
{
Sample
sample
=
samples
.
remove
();
bitrateWeightProductSum
-=
sample
.
bitrate
*
sample
.
weight
;
weightSum
-=
sample
.
weight
;
}
double
weight
=
Math
.
sqrt
((
double
)
bytes
);
long
bitrate
=
bytes
*
8_000_000
/
durationUs
;
Sample
sample
=
new
Sample
(
bitrate
,
weight
,
clock
.
elapsedRealtime
());
samples
.
add
(
sample
);
bitrateWeightProductSum
+=
sample
.
bitrate
*
sample
.
weight
;
weightSum
+=
sample
.
weight
;
}
@Override
public
long
getBandwidthEstimate
()
{
if
(
samples
.
isEmpty
())
{
return
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
;
}
return
(
long
)
(
bitrateWeightProductSum
/
weightSum
);
}
@Override
public
void
reset
()
{
samples
.
clear
();
bitrateWeightProductSum
=
0
;
weightSum
=
0
;
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/SplitParallelSampleBandwidthEstimator.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
android.os.Handler
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.util.Clock
;
import
com.google.errorprone.annotations.CanIgnoreReturnValue
;
/**
* A {@link BandwidthEstimator} that captures a transfer sample each time a transfer ends. When
* parallel transfers are happening at the same time, the transferred bytes are aggregated in a
* single sample.
*/
public
class
SplitParallelSampleBandwidthEstimator
implements
BandwidthEstimator
{
/** A builder to create {@link SplitParallelSampleBandwidthEstimator} instances. */
public
static
class
Builder
{
private
BandwidthStatistic
bandwidthStatistic
;
private
int
minSamples
;
private
long
minBytesTransferred
;
private
Clock
clock
;
/** Creates a new builder instance. */
public
Builder
()
{
bandwidthStatistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
clock
=
Clock
.
DEFAULT
;
}
/**
* Sets the {@link BandwidthStatistic} to be used by the estimator. By default, this is set to a
* {@link SlidingWeightedAverageBandwidthStatistic}.
*
* @param bandwidthStatistic The {@link BandwidthStatistic}.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setBandwidthStatistic
(
BandwidthStatistic
bandwidthStatistic
)
{
checkNotNull
(
bandwidthStatistic
);
this
.
bandwidthStatistic
=
bandwidthStatistic
;
return
this
;
}
/**
* Sets a minimum threshold of samples that need to be taken before the estimator can return a
* bandwidth estimate. By default, this is set to {@code 0}.
*
* @param minSamples The minimum number of samples.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setMinSamples
(
int
minSamples
)
{
checkArgument
(
minSamples
>=
0
);
this
.
minSamples
=
minSamples
;
return
this
;
}
/**
* Sets a minimum threshold of bytes that need to be transferred before the estimator can return
* a bandwidth estimate. By default, this is set to {@code 0}.
*
* @param minBytesTransferred The minimum number of transferred bytes.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
public
Builder
setMinBytesTransferred
(
long
minBytesTransferred
)
{
checkArgument
(
minBytesTransferred
>=
0
);
this
.
minBytesTransferred
=
minBytesTransferred
;
return
this
;
}
/**
* Sets the {@link Clock} used by the estimator. By default, this is set to {@link
* Clock#DEFAULT}.
*
* @param clock The {@link Clock} to be used.
* @return This builder for convenience.
*/
@CanIgnoreReturnValue
@VisibleForTesting
/* package */
Builder
setClock
(
Clock
clock
)
{
this
.
clock
=
clock
;
return
this
;
}
public
SplitParallelSampleBandwidthEstimator
build
()
{
return
new
SplitParallelSampleBandwidthEstimator
(
this
);
}
}
private
final
BandwidthStatistic
bandwidthStatistic
;
private
final
int
minSamples
;
private
final
long
minBytesTransferred
;
private
final
Clock
clock
;
private
final
BandwidthMeter
.
EventListener
.
EventDispatcher
eventDispatcher
;
private
int
streamCount
;
private
long
sampleStartTimeMs
;
private
long
sampleBytesTransferred
;
private
long
bandwidthEstimate
;
private
long
lastReportedBandwidthEstimate
;
private
int
totalSamplesAdded
;
private
long
totalBytesTransferred
;
private
SplitParallelSampleBandwidthEstimator
(
Builder
builder
)
{
this
.
bandwidthStatistic
=
builder
.
bandwidthStatistic
;
this
.
minSamples
=
builder
.
minSamples
;
this
.
minBytesTransferred
=
builder
.
minBytesTransferred
;
this
.
clock
=
builder
.
clock
;
eventDispatcher
=
new
BandwidthMeter
.
EventListener
.
EventDispatcher
();
bandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
lastReportedBandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
}
@Override
public
void
addEventListener
(
Handler
eventHandler
,
BandwidthMeter
.
EventListener
eventListener
)
{
eventDispatcher
.
addListener
(
eventHandler
,
eventListener
);
}
@Override
public
void
removeEventListener
(
BandwidthMeter
.
EventListener
eventListener
)
{
eventDispatcher
.
removeListener
(
eventListener
);
}
@Override
public
void
onTransferInitializing
(
DataSource
source
)
{}
@Override
public
void
onTransferStart
(
DataSource
source
)
{
if
(
streamCount
==
0
)
{
sampleStartTimeMs
=
clock
.
elapsedRealtime
();
}
streamCount
++;
}
@Override
public
void
onBytesTransferred
(
DataSource
source
,
int
bytesTransferred
)
{
sampleBytesTransferred
+=
bytesTransferred
;
totalBytesTransferred
+=
bytesTransferred
;
}
@Override
public
void
onTransferEnd
(
DataSource
source
)
{
checkState
(
streamCount
>
0
);
long
nowMs
=
clock
.
elapsedRealtime
();
long
sampleElapsedTimeMs
=
(
int
)
(
nowMs
-
sampleStartTimeMs
);
if
(
sampleElapsedTimeMs
>
0
)
{
bandwidthStatistic
.
addSample
(
sampleBytesTransferred
,
sampleElapsedTimeMs
*
1000
);
totalSamplesAdded
++;
if
(
totalSamplesAdded
>
minSamples
&&
totalBytesTransferred
>
minBytesTransferred
)
{
bandwidthEstimate
=
bandwidthStatistic
.
getBandwidthEstimate
();
}
maybeNotifyBandwidthSample
(
(
int
)
sampleElapsedTimeMs
,
sampleBytesTransferred
,
bandwidthEstimate
);
sampleStartTimeMs
=
nowMs
;
sampleBytesTransferred
=
0
;
}
// Else any sample bytes transferred will be carried forward into the next sample.
streamCount
--;
}
@Override
public
long
getBandwidthEstimate
()
{
return
bandwidthEstimate
;
}
@Override
public
void
onNetworkTypeChange
(
long
newBandwidthEstimate
)
{
long
nowMs
=
clock
.
elapsedRealtime
();
int
sampleElapsedTimeMs
=
streamCount
>
0
?
(
int
)
(
nowMs
-
sampleStartTimeMs
)
:
0
;
maybeNotifyBandwidthSample
(
sampleElapsedTimeMs
,
sampleBytesTransferred
,
newBandwidthEstimate
);
bandwidthStatistic
.
reset
();
bandwidthEstimate
=
ESTIMATE_NOT_AVAILABLE
;
sampleStartTimeMs
=
nowMs
;
sampleBytesTransferred
=
0
;
totalSamplesAdded
=
0
;
totalBytesTransferred
=
0
;
}
private
void
maybeNotifyBandwidthSample
(
int
elapsedMs
,
long
bytesTransferred
,
long
bandwidthEstimate
)
{
if
((
bandwidthEstimate
==
ESTIMATE_NOT_AVAILABLE
)
||
(
elapsedMs
==
0
&&
bytesTransferred
==
0
&&
bandwidthEstimate
==
lastReportedBandwidthEstimate
))
{
return
;
}
lastReportedBandwidthEstimate
=
bandwidthEstimate
;
eventDispatcher
.
bandwidthSample
(
elapsedMs
,
bytesTransferred
,
bandwidthEstimate
);
}
}
library/core/src/main/java/com/google/android/exoplayer2/upstream/experimental/package-info.java
0 → 100644
View file @
4665390f
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@NonNullApi
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
com.google.android.exoplayer2.util.NonNullApi
;
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/CombinedParallelSampleBandwidthEstimatorTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
static
org
.
mockito
.
ArgumentMatchers
.
anyInt
;
import
static
org
.
mockito
.
ArgumentMatchers
.
anyLong
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
never
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
verifyNoMoreInteractions
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
android.os.Handler
;
import
android.os.Looper
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.testutil.FakeClock
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.mockito.Mockito
;
import
org.robolectric.shadows.ShadowLooper
;
/** Unite tests for the {@link CombinedParallelSampleBandwidthEstimator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
CombinedParallelSampleBandwidthEstimatorTest
{
@Test
public
void
builder_setNegativeMinSamples_throws
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
CombinedParallelSampleBandwidthEstimator
.
Builder
().
setMinSamples
(-
1
));
}
@Test
public
void
builder_setNegativeMinBytesTransferred_throws
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
CombinedParallelSampleBandwidthEstimator
.
Builder
().
setMinBytesTransferred
(-
1
));
}
@Test
public
void
transferEvents_singleTransfer_providesOneSample
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
CombinedParallelSampleBandwidthEstimator
estimator
=
new
CombinedParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
Mockito
.
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
DataSource
source
=
new
FakeDataSource
();
estimator
.
onTransferInitializing
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
200
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
ShadowLooper
.
idleMainLooper
();
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
80_000
);
verify
(
eventListener
).
onBandwidthSample
(
20
,
200
,
80_000
);
}
@Test
public
void
transferEvents_twoParallelTransfers_providesOneSample
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
CombinedParallelSampleBandwidthEstimator
estimator
=
new
CombinedParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
Mockito
.
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
DataSource
source1
=
new
FakeDataSource
();
DataSource
source2
=
new
FakeDataSource
();
// At time = 10 ms, source1 starts.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source1
);
estimator
.
onTransferStart
(
source1
);
// At time 20 ms, source1 reports 200 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source1
,
/* bytesTransferred= */
200
);
// At time = 30 ms, source2 starts.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source2
);
estimator
.
onTransferStart
(
source2
);
// At time = 40 ms, both sources report 100 bytes each.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source1
,
/* bytesTransferred= */
100
);
estimator
.
onBytesTransferred
(
source2
,
/* bytesTransferred= */
100
);
// At time = 50 ms, source1 transfer completes. At this point, 400 bytes have been transferred
// in total between times 10 and 50 ms.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source1
);
ShadowLooper
.
idleMainLooper
();
// Verify no update has been made yet.
verify
(
eventListener
,
never
()).
onBandwidthSample
(
anyInt
(),
anyLong
(),
anyLong
());
// At time = 60 ms, source2 reports 160 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source2
,
/* bytesTransferred= */
160
);
// At time = 70 ms second transfer completes. At this time, 160 bytes have been
// transferred between times 50 and 70 ms.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source2
);
ShadowLooper
.
idleMainLooper
();
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
74_666
);
verify
(
eventListener
).
onBandwidthSample
(
60
,
560
,
74_666
);
verifyNoMoreInteractions
(
eventListener
);
}
@Test
public
void
onNetworkTypeChange_notifiesListener
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
CombinedParallelSampleBandwidthEstimator
estimator
=
new
CombinedParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
Mockito
.
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
estimator
.
onNetworkTypeChange
(
100
);
ShadowLooper
.
idleMainLooper
();
verify
(
eventListener
).
onBandwidthSample
(
0
,
0
,
100
);
}
@Test
public
void
minSamplesSet_doesNotReturnEstimateBefore
()
{
FakeDataSource
source
=
new
FakeDataSource
();
FakeClock
fakeClock
=
new
FakeClock
(
0
);
BandwidthStatistic
mockStatistic
=
mock
(
BandwidthStatistic
.
class
);
when
(
mockStatistic
.
getBandwidthEstimate
()).
thenReturn
(
1234L
);
CombinedParallelSampleBandwidthEstimator
estimator
=
new
CombinedParallelSampleBandwidthEstimator
.
Builder
()
.
setBandwidthStatistic
(
mockStatistic
)
.
setMinSamples
(
1
)
.
setClock
(
fakeClock
)
.
build
();
// First sample.
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
// Second sample.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
1234L
);
}
@Test
public
void
minBytesTransferredSet_doesNotReturnEstimateBefore
()
{
FakeDataSource
source
=
new
FakeDataSource
();
FakeClock
fakeClock
=
new
FakeClock
(
0
);
BandwidthStatistic
mockStatistic
=
mock
(
BandwidthStatistic
.
class
);
when
(
mockStatistic
.
getBandwidthEstimate
()).
thenReturn
(
1234L
);
CombinedParallelSampleBandwidthEstimator
estimator
=
new
CombinedParallelSampleBandwidthEstimator
.
Builder
()
.
setBandwidthStatistic
(
mockStatistic
)
.
setMinBytesTransferred
(
500
)
.
setClock
(
fakeClock
)
.
build
();
// First sample transfers 499 bytes.
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
499
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
// Second sample transfers 100 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
1234L
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExperimentalBandwidthMeterTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
android
.
net
.
NetworkInfo
.
State
.
CONNECTED
;
import
static
android
.
net
.
NetworkInfo
.
State
.
DISCONNECTED
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.content.Context
;
import
android.content.Intent
;
import
android.net.ConnectivityManager
;
import
android.net.NetworkInfo
;
import
android.net.NetworkInfo.DetailedState
;
import
android.net.Uri
;
import
android.telephony.TelephonyDisplayInfo
;
import
android.telephony.TelephonyManager
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.android.exoplayer2.util.NetworkTypeObserver
;
import
com.google.android.exoplayer2.util.Util
;
import
java.time.Duration
;
import
java.util.Random
;
import
org.junit.Before
;
import
org.junit.Ignore
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.Shadows
;
import
org.robolectric.annotation.Config
;
import
org.robolectric.shadows.ShadowLooper
;
import
org.robolectric.shadows.ShadowNetworkInfo
;
import
org.robolectric.shadows.ShadowSystemClock
;
import
org.robolectric.shadows.ShadowTelephonyManager
;
/** Unit test for {@link ExperimentalBandwidthMeter}. */
@RunWith
(
AndroidJUnit4
.
class
)
@Config
(
sdk
=
Config
.
ALL_SDKS
)
// Test all SDKs because network detection logic changed over time.
public
final
class
ExperimentalBandwidthMeterTest
{
private
static
final
int
SIMULATED_TRANSFER_COUNT
=
100
;
private
static
final
String
FAST_COUNTRY_ISO
=
"TW"
;
private
static
final
String
SLOW_COUNTRY_ISO
=
"PG"
;
private
TelephonyManager
telephonyManager
;
private
ConnectivityManager
connectivityManager
;
private
NetworkInfo
networkInfoOffline
;
private
NetworkInfo
networkInfoWifi
;
private
NetworkInfo
networkInfo2g
;
private
NetworkInfo
networkInfo3g
;
private
NetworkInfo
networkInfo4g
;
private
NetworkInfo
networkInfo5gSa
;
private
NetworkInfo
networkInfoEthernet
;
@Before
public
void
setUp
()
{
NetworkTypeObserver
.
resetForTests
();
connectivityManager
=
(
ConnectivityManager
)
ApplicationProvider
.
getApplicationContext
()
.
getSystemService
(
Context
.
CONNECTIVITY_SERVICE
);
telephonyManager
=
(
TelephonyManager
)
ApplicationProvider
.
getApplicationContext
().
getSystemService
(
Context
.
TELEPHONY_SERVICE
);
Shadows
.
shadowOf
(
telephonyManager
).
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
networkInfoOffline
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
DISCONNECTED
,
ConnectivityManager
.
TYPE_WIFI
,
/* subType= */
0
,
/* isAvailable= */
false
,
DISCONNECTED
);
networkInfoWifi
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_WIFI
,
/* subType= */
0
,
/* isAvailable= */
true
,
CONNECTED
);
networkInfo2g
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_MOBILE
,
TelephonyManager
.
NETWORK_TYPE_GPRS
,
/* isAvailable= */
true
,
CONNECTED
);
networkInfo3g
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_MOBILE
,
TelephonyManager
.
NETWORK_TYPE_HSDPA
,
/* isAvailable= */
true
,
CONNECTED
);
networkInfo4g
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_MOBILE
,
TelephonyManager
.
NETWORK_TYPE_LTE
,
/* isAvailable= */
true
,
CONNECTED
);
networkInfo5gSa
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_MOBILE
,
TelephonyManager
.
NETWORK_TYPE_NR
,
/* isAvailable= */
true
,
CONNECTED
);
networkInfoEthernet
=
ShadowNetworkInfo
.
newInstance
(
DetailedState
.
CONNECTED
,
ConnectivityManager
.
TYPE_ETHERNET
,
/* subType= */
0
,
/* isAvailable= */
true
,
CONNECTED
);
setNetworkCountryIso
(
"non-existent-country-to-force-default-values"
);
}
@Test
public
void
defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor2G
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeterWifi
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateWifi
=
bandwidthMeterWifi
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter2g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate2g
=
bandwidthMeter2g
.
getBitrateEstimate
();
assertThat
(
initialEstimateWifi
).
isGreaterThan
(
initialEstimate2g
);
}
@Test
public
void
defaultInitialBitrateEstimate_forWifi_isGreaterThanEstimateFor3G
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeterWifi
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateWifi
=
bandwidthMeterWifi
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter3g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate3g
=
bandwidthMeter3g
.
getBitrateEstimate
();
assertThat
(
initialEstimateWifi
).
isGreaterThan
(
initialEstimate3g
);
}
@Test
public
void
defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor2G
()
{
setActiveNetworkInfo
(
networkInfoEthernet
);
ExperimentalBandwidthMeter
bandwidthMeterEthernet
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateEthernet
=
bandwidthMeterEthernet
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter2g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate2g
=
bandwidthMeter2g
.
getBitrateEstimate
();
assertThat
(
initialEstimateEthernet
).
isGreaterThan
(
initialEstimate2g
);
}
@Test
public
void
defaultInitialBitrateEstimate_forEthernet_isGreaterThanEstimateFor3G
()
{
setActiveNetworkInfo
(
networkInfoEthernet
);
ExperimentalBandwidthMeter
bandwidthMeterEthernet
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateEthernet
=
bandwidthMeterEthernet
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter3g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate3g
=
bandwidthMeter3g
.
getBitrateEstimate
();
assertThat
(
initialEstimateEthernet
).
isGreaterThan
(
initialEstimate3g
);
}
@Test
public
void
defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor2G
()
{
setActiveNetworkInfo
(
networkInfo4g
);
ExperimentalBandwidthMeter
bandwidthMeter4g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate4g
=
bandwidthMeter4g
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter2g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate2g
=
bandwidthMeter2g
.
getBitrateEstimate
();
assertThat
(
initialEstimate4g
).
isGreaterThan
(
initialEstimate2g
);
}
@Test
public
void
defaultInitialBitrateEstimate_for4G_isGreaterThanEstimateFor3G
()
{
setActiveNetworkInfo
(
networkInfo4g
);
ExperimentalBandwidthMeter
bandwidthMeter4g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate4g
=
bandwidthMeter4g
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter3g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate3g
=
bandwidthMeter3g
.
getBitrateEstimate
();
assertThat
(
initialEstimate4g
).
isGreaterThan
(
initialEstimate3g
);
}
@Test
public
void
defaultInitialBitrateEstimate_for3G_isGreaterThanEstimateFor2G
()
{
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter3g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate3g
=
bandwidthMeter3g
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter2g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate2g
=
bandwidthMeter2g
.
getBitrateEstimate
();
assertThat
(
initialEstimate3g
).
isGreaterThan
(
initialEstimate2g
);
}
@Test
@Config
(
minSdk
=
31
)
// 5G-NSA detection is supported from API 31.
public
void
defaultInitialBitrateEstimate_for5gNsa_isGreaterThanEstimateFor4g
()
{
setActiveNetworkInfo
(
networkInfo4g
);
ExperimentalBandwidthMeter
bandwidthMeter4g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate4g
=
bandwidthMeter4g
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo4g
,
TelephonyDisplayInfo
.
OVERRIDE_NETWORK_TYPE_NR_NSA
);
ExperimentalBandwidthMeter
bandwidthMeter5gNsa
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate5gNsa
=
bandwidthMeter5gNsa
.
getBitrateEstimate
();
assertThat
(
initialEstimate5gNsa
).
isGreaterThan
(
initialEstimate4g
);
}
@Test
@Config
(
minSdk
=
29
)
// 5G-SA detection is supported from API 29.
public
void
defaultInitialBitrateEstimate_for5gSa_isGreaterThanEstimateFor3g
()
{
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter3g
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate3g
=
bandwidthMeter3g
.
getBitrateEstimate
();
setActiveNetworkInfo
(
networkInfo5gSa
);
ExperimentalBandwidthMeter
bandwidthMeter5gSa
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate5gSa
=
bandwidthMeter5gSa
.
getBitrateEstimate
();
assertThat
(
initialEstimate5gSa
).
isGreaterThan
(
initialEstimate3g
);
}
@Test
public
void
defaultInitialBitrateEstimate_forOffline_isReasonable
()
{
setActiveNetworkInfo
(
networkInfoOffline
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isGreaterThan
(
100_000L
);
assertThat
(
initialEstimate
).
isLessThan
(
50_000_000L
);
}
@Test
public
void
defaultInitialBitrateEstimate_forWifi_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
public
void
defaultInitialBitrateEstimate_forEthernet_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfoEthernet
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
public
void
defaultInitialBitrateEstimate_for2G_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfo2g
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
public
void
defaultInitialBitrateEstimate_for3G_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfo3g
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
public
void
defaultInitialBitrateEstimate_for4g_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfo4g
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
@Config
(
minSdk
=
31
)
// 5G-NSA detection is supported from API 31.
public
void
defaultInitialBitrateEstimate_for5gNsa_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfo4g
,
TelephonyDisplayInfo
.
OVERRIDE_NETWORK_TYPE_NR_NSA
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Ignore
// 5G-SA isn't widespread enough yet to define a slow and fast country for testing.
@Test
@Config
(
minSdk
=
29
)
// 5G-SA detection is supported from API 29.
public
void
defaultInitialBitrateEstimate_for5gSa_forFastCountry_isGreaterThanEstimateForSlowCountry
()
{
setActiveNetworkInfo
(
networkInfo5gSa
);
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFast
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateFast
=
bandwidthMeterFast
.
getBitrateEstimate
();
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
assertThat
(
initialEstimateFast
).
isGreaterThan
(
initialEstimateSlow
);
}
@Test
public
void
initialBitrateEstimateOverwrite_whileConnectedToNetwork_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_whileOffline_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoOffline
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forWifi_whileConnectedToWifi_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_WIFI
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forWifi_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_WIFI
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forEthernet_whileConnectedToEthernet_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoEthernet
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_ETHERNET
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forEthernet_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_WIFI
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for2G_whileConnectedTo2G_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo2g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_2G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for2G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_2G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for3G_whileConnectedTo3G_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo3g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_3G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for3G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_3G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for4G_whileConnectedTo4G_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo4g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_4G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_for4G_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_4G
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
@Config
(
minSdk
=
31
)
// 5G-NSA detection is supported from API 31.
public
void
initialBitrateEstimateOverwrite_for5gNsa_whileConnectedTo5gNsa_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo4g
,
TelephonyDisplayInfo
.
OVERRIDE_NETWORK_TYPE_NR_NSA
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_5G_NSA
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
@Config
(
minSdk
=
31
)
// 5G-NSA detection is supported from API 31.
public
void
initialBitrateEstimateOverwrite_for5gNsa_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo4g
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_5G_NSA
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
@Config
(
minSdk
=
29
)
// 5G-SA detection is supported from API 29.
public
void
initialBitrateEstimateOverwrite_for5gSa_whileConnectedTo5gSa_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfo5gSa
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_5G_SA
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
@Config
(
minSdk
=
29
)
// 5G-SA detection is supported from API 29.
public
void
initialBitrateEstimateOverwrite_for5gSa_whileConnectedToOtherNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_5G_SA
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forOffline_whileOffline_setsInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoOffline
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_OFFLINE
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forOffline_whileConnectedToNetwork_doesNotSetInitialEstimate
()
{
setActiveNetworkInfo
(
networkInfoWifi
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
C
.
NETWORK_TYPE_OFFLINE
,
123456789
)
.
build
();
long
initialEstimate
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimate
).
isNotEqualTo
(
123456789
);
}
@Test
public
void
initialBitrateEstimateOverwrite_forCountry_usesDefaultValuesForCountry
()
{
setNetworkCountryIso
(
SLOW_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterSlow
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateSlow
=
bandwidthMeterSlow
.
getBitrateEstimate
();
setNetworkCountryIso
(
FAST_COUNTRY_ISO
);
ExperimentalBandwidthMeter
bandwidthMeterFastWithSlowOverwrite
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
())
.
setInitialBitrateEstimate
(
SLOW_COUNTRY_ISO
)
.
build
();
long
initialEstimateFastWithSlowOverwrite
=
bandwidthMeterFastWithSlowOverwrite
.
getBitrateEstimate
();
assertThat
(
initialEstimateFastWithSlowOverwrite
).
isEqualTo
(
initialEstimateSlow
);
}
@Test
public
void
networkTypeOverride_updatesBitrateEstimate
()
{
setActiveNetworkInfo
(
networkInfoEthernet
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
initialEstimateEthernet
=
bandwidthMeter
.
getBitrateEstimate
();
bandwidthMeter
.
setNetworkTypeOverride
(
C
.
NETWORK_TYPE_2G
);
long
initialEstimate2g
=
bandwidthMeter
.
getBitrateEstimate
();
assertThat
(
initialEstimateEthernet
).
isGreaterThan
(
initialEstimate2g
);
}
@Test
public
void
networkTypeOverride_doesFullReset
()
{
// Simulate transfers for an ethernet connection.
setActiveNetworkInfo
(
networkInfoEthernet
);
ExperimentalBandwidthMeter
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
long
[]
bitrateEstimatesWithNewInstance
=
simulateTransfers
(
bandwidthMeter
);
// Create a new instance and seed with some transfers.
setActiveNetworkInfo
(
networkInfo2g
);
bandwidthMeter
=
new
ExperimentalBandwidthMeter
.
Builder
(
ApplicationProvider
.
getApplicationContext
()).
build
();
simulateTransfers
(
bandwidthMeter
);
// Override the network type to ethernet and simulate transfers again.
bandwidthMeter
.
setNetworkTypeOverride
(
C
.
NETWORK_TYPE_ETHERNET
);
long
[]
bitrateEstimatesAfterReset
=
simulateTransfers
(
bandwidthMeter
);
// If overriding the network type fully reset the bandwidth meter, we expect the bitrate
// estimates generated during simulation to be the same.
assertThat
(
bitrateEstimatesAfterReset
).
isEqualTo
(
bitrateEstimatesWithNewInstance
);
}
private
void
setActiveNetworkInfo
(
NetworkInfo
networkInfo
)
{
setActiveNetworkInfo
(
networkInfo
,
TelephonyDisplayInfo
.
OVERRIDE_NETWORK_TYPE_NONE
);
}
@SuppressWarnings
(
"StickyBroadcast"
)
private
void
setActiveNetworkInfo
(
NetworkInfo
networkInfo
,
int
networkTypeOverride
)
{
// Set network info in ConnectivityManager and TelephonyDisplayInfo in TelephonyManager.
Shadows
.
shadowOf
(
connectivityManager
).
setActiveNetworkInfo
(
networkInfo
);
if
(
Util
.
SDK_INT
>=
31
)
{
Object
displayInfo
=
ShadowTelephonyManager
.
createTelephonyDisplayInfo
(
networkInfo
.
getType
(),
networkTypeOverride
);
Shadows
.
shadowOf
(
telephonyManager
).
setTelephonyDisplayInfo
(
displayInfo
);
}
// Create a sticky broadcast for the connectivity action because Robolectric isn't replying with
// the current network state if a receiver for this intent is registered.
ApplicationProvider
.
getApplicationContext
()
.
sendStickyBroadcast
(
new
Intent
(
ConnectivityManager
.
CONNECTIVITY_ACTION
));
// Trigger initialization of static network type observer.
NetworkTypeObserver
.
getInstance
(
ApplicationProvider
.
getApplicationContext
());
// Wait until all pending messages are handled and the network initialization is done.
ShadowLooper
.
idleMainLooper
();
}
private
void
setNetworkCountryIso
(
String
countryIso
)
{
Shadows
.
shadowOf
(
telephonyManager
).
setNetworkCountryIso
(
countryIso
);
}
private
static
long
[]
simulateTransfers
(
ExperimentalBandwidthMeter
bandwidthMeter
)
{
long
[]
bitrateEstimates
=
new
long
[
SIMULATED_TRANSFER_COUNT
];
Random
random
=
new
Random
(
/* seed= */
0
);
DataSource
dataSource
=
new
FakeDataSource
();
DataSpec
dataSpec
=
new
DataSpec
(
Uri
.
parse
(
"https://test.com"
));
for
(
int
i
=
0
;
i
<
SIMULATED_TRANSFER_COUNT
;
i
++)
{
bandwidthMeter
.
onTransferInitializing
(
dataSource
,
dataSpec
,
/* isNetwork= */
true
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
random
.
nextInt
(
50
)));
bandwidthMeter
.
onTransferStart
(
dataSource
,
dataSpec
,
/* isNetwork= */
true
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
random
.
nextInt
(
5000
)));
bandwidthMeter
.
onBytesTransferred
(
dataSource
,
dataSpec
,
/* isNetwork= */
true
,
/* bytesTransferred= */
random
.
nextInt
(
5
*
1024
*
1024
));
bandwidthMeter
.
onTransferEnd
(
dataSource
,
dataSpec
,
/* isNetwork= */
true
);
bitrateEstimates
[
i
]
=
bandwidthMeter
.
getBitrateEstimate
();
}
return
bitrateEstimates
;
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageStatisticTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit tests for {@link ExponentialWeightedAverageStatistic}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
ExponentialWeightedAverageStatisticTest
{
@Test
public
void
getBandwidthEstimate_afterConstruction_returnsNoEstimate
()
{
ExponentialWeightedAverageStatistic
statistic
=
new
ExponentialWeightedAverageStatistic
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
@Test
public
void
getBandwidthEstimate_oneSample_returnsEstimate
()
{
ExponentialWeightedAverageStatistic
statistic
=
new
ExponentialWeightedAverageStatistic
();
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
8_000_000
);
}
@Test
public
void
getBandwidthEstimate_multipleSamples_returnsEstimate
()
{
ExponentialWeightedAverageStatistic
statistic
=
new
ExponentialWeightedAverageStatistic
(
/* smoothingFactor= */
0.9999
);
// Transfer bytes are chosen so that their weights (square root) is exactly an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
319545334
);
}
@Test
public
void
getBandwidthEstimate_calledMultipleTimes_returnsSameEstimate
()
{
ExponentialWeightedAverageStatistic
statistic
=
new
ExponentialWeightedAverageStatistic
(
/* smoothingFactor= */
0.9999
);
// Transfer bytes chosen so that their weight (sqrt) is an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
319545334
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
319545334
);
}
@Test
public
void
reset_withSamplesAdded_returnsNoEstimate
()
{
ExponentialWeightedAverageStatistic
statistic
=
new
ExponentialWeightedAverageStatistic
();
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
reset
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/ExponentialWeightedAverageTimeToFirstByteEstimatorTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
.
ExponentialWeightedAverageTimeToFirstByteEstimator
.
DEFAULT_SMOOTHING_FACTOR
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.net.Uri
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.testutil.FakeClock
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit test for {@link ExponentialWeightedAverageTimeToFirstByteEstimator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
ExponentialWeightedAverageTimeToFirstByteEstimatorTest
{
@Test
public
void
timeToFirstByteEstimate_afterConstruction_notAvailable
()
{
ExponentialWeightedAverageTimeToFirstByteEstimator
estimator
=
new
ExponentialWeightedAverageTimeToFirstByteEstimator
();
assertThat
(
estimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
C
.
TIME_UNSET
);
}
@Test
public
void
timeToFirstByteEstimate_afterReset_notAvailable
()
{
FakeClock
clock
=
new
FakeClock
(
0
);
ExponentialWeightedAverageTimeToFirstByteEstimator
estimator
=
new
ExponentialWeightedAverageTimeToFirstByteEstimator
(
DEFAULT_SMOOTHING_FACTOR
,
clock
);
DataSpec
dataSpec
=
new
DataSpec
.
Builder
().
setUri
(
Uri
.
EMPTY
).
build
();
// Initialize and start two transfers.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
dataSpec
);
// Second transfer.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
dataSpec
);
assertThat
(
estimator
.
getTimeToFirstByteEstimateUs
()).
isGreaterThan
(
0
);
estimator
.
reset
();
assertThat
(
estimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
C
.
TIME_UNSET
);
}
@Test
public
void
timeToFirstByteEstimate_afterTwoSamples_returnsEstimate
()
{
FakeClock
clock
=
new
FakeClock
(
0
);
ExponentialWeightedAverageTimeToFirstByteEstimator
estimator
=
new
ExponentialWeightedAverageTimeToFirstByteEstimator
(
DEFAULT_SMOOTHING_FACTOR
,
clock
);
DataSpec
dataSpec
=
new
DataSpec
.
Builder
().
setUri
(
Uri
.
EMPTY
).
build
();
// Initialize and start two transfers.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
dataSpec
);
// Second transfer.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
5
);
estimator
.
onTransferStart
(
dataSpec
);
// (0.85 * 10ms) + (0.15 * 5ms) = 9.25ms => 9250us
assertThat
(
estimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
9250
);
}
@Test
public
void
timeToFirstByteEstimate_withUserDefinedSmoothingFactor_returnsEstimate
()
{
FakeClock
clock
=
new
FakeClock
(
0
);
ExponentialWeightedAverageTimeToFirstByteEstimator
estimator
=
new
ExponentialWeightedAverageTimeToFirstByteEstimator
(
/* smoothingFactor= */
0.9
,
clock
);
DataSpec
dataSpec
=
new
DataSpec
.
Builder
().
setUri
(
Uri
.
EMPTY
).
build
();
// Initialize and start two transfers.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
dataSpec
);
// Second transfer.
estimator
.
onTransferInitializing
(
dataSpec
);
clock
.
advanceTime
(
5
);
estimator
.
onTransferStart
(
dataSpec
);
// (0.9 * 10ms) + (0.1 * 5ms) = 9.5ms => 9500 us
assertThat
(
estimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
9500
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/PercentileTimeToFirstByteEstimatorTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
android.net.Uri
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
java.time.Duration
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.shadows.ShadowSystemClock
;
/** Unit tests for {@link PercentileTimeToFirstByteEstimator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
PercentileTimeToFirstByteEstimatorTest
{
private
PercentileTimeToFirstByteEstimator
percentileTimeToResponseEstimator
;
@Before
public
void
setUp
()
{
percentileTimeToResponseEstimator
=
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
5
,
/* percentile= */
0.5f
);
}
@Test
public
void
constructor_invalidNumberOfSamples_throwsIllegalArgumentException
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
0
,
/* percentile= */
.
2
f
));
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
-
123
,
/* percentile= */
.
2
f
));
}
@Test
public
void
constructor_invalidPercentile_throwsIllegalArgumentException
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
11
,
/* percentile= */
.
0
f
));
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
11
,
/* percentile= */
1.1f
));
}
@Test
public
void
getTimeToRespondEstimateUs_noSamples_returnsTimeUnset
()
{
assertThat
(
percentileTimeToResponseEstimator
.
getTimeToFirstByteEstimateUs
())
.
isEqualTo
(
C
.
TIME_UNSET
);
}
@Test
public
void
getTimeToRespondEstimateUs_medianOfOddNumberOfSamples_returnsCenterSampleValue
()
{
DataSpec
dataSpec
=
new
DataSpec
(
Uri
.
EMPTY
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
10
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
20
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
30
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
40
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
50
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
assertThat
(
percentileTimeToResponseEstimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
30_000
);
}
@Test
public
void
getTimeToRespondEstimateUs_medianOfEvenNumberOfSamples_returnsLastSampleOfFirstHalfValue
()
{
PercentileTimeToFirstByteEstimator
percentileTimeToResponseEstimator
=
new
PercentileTimeToFirstByteEstimator
(
/* numberOfSamples= */
12
,
/* percentile= */
0.5f
);
DataSpec
dataSpec
=
new
DataSpec
(
Uri
.
EMPTY
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
10
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
20
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
30
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
40
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
assertThat
(
percentileTimeToResponseEstimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
20_000
);
}
@Test
public
void
getTimeToRespondEstimateUs_slidingMedian_returnsCenterSampleValue
()
{
DataSpec
dataSpec
=
new
DataSpec
(
Uri
.
EMPTY
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
10
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
20
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
30
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
40
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
50
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
60
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
70
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
assertThat
(
percentileTimeToResponseEstimator
.
getTimeToFirstByteEstimateUs
()).
isEqualTo
(
50_000
);
}
@Test
public
void
reset_clearsTheSlidingWindows
()
{
DataSpec
dataSpec
=
new
DataSpec
(
Uri
.
EMPTY
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
10
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
onTransferInitializing
(
dataSpec
);
ShadowSystemClock
.
advanceBy
(
Duration
.
ofMillis
(
10
));
percentileTimeToResponseEstimator
.
onTransferStart
(
dataSpec
);
percentileTimeToResponseEstimator
.
reset
();
assertThat
(
percentileTimeToResponseEstimator
.
getTimeToFirstByteEstimateUs
())
.
isEqualTo
(
C
.
TIME_UNSET
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SlidingPercentileBandwidthStatisticTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit tests for {@link SlidingPercentileBandwidthStatistic}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
SlidingPercentileBandwidthStatisticTest
{
@Test
public
void
getBandwidthEstimate_afterConstruction_returnsNoEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
@Test
public
void
getBandwidthEstimate_oneSample_returnsEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.5
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
8_000_000
);
}
@Test
public
void
getBandwidthEstimate_multipleSamples_returnsEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.5
);
// Transfer bytes are chosen so that their weights (square root) is exactly an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
176_000_000
);
}
@Test
public
void
getBandwidthEstimate_calledMultipleTimes_returnsSameEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.5
);
// Transfer bytes chosen so that their weight (sqrt) is an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
176_000_000
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
176_000_000
);
}
@Test
public
void
getBandwidthEstimate_afterMoreSamplesThanMaxSamples_usesOnlyMaxSamplesForEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.5
);
// Add 12 samples, the first two should be discarded
statistic
.
addSample
(
/* bytes= */
1_000
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
1_000
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
12_800_000
);
}
@Test
public
void
getBandwidthEstimate_nonMediaPercentile_returnsEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.125
);
// Transfer bytes are chosen so that their weights (square root) is exactly an integer.
statistic
.
addSample
(
/* bytes= */
484
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
54_400_000
);
}
@Test
public
void
reset_withSamplesAdded_returnsNoEstimate
()
{
SlidingPercentileBandwidthStatistic
statistic
=
new
SlidingPercentileBandwidthStatistic
(
/* maxSampleCount= */
10
,
/* percentile= */
0.5
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
reset
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SlidingWeightedAverageBandwidthStatisticTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.testutil.FakeClock
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit tests for {@link SlidingWeightedAverageBandwidthStatistic}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
SlidingWeightedAverageBandwidthStatisticTest
{
@Test
public
void
getBandwidthEstimate_afterConstruction_returnsNoEstimate
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
@Test
public
void
getBandwidthEstimate_oneSample_returnsEstimate
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
8_000_000
);
}
@Test
public
void
getBandwidthEstimate_multipleSamples_returnsEstimate
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
// Transfer bytes are chosen so that their weights (square root) is exactly an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
200252631
);
}
@Test
public
void
getBandwidthEstimate_calledMultipleTimes_returnsSameEstimate
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
// Transfer bytes chosen so that their weight (sqrt) is an integer.
statistic
.
addSample
(
/* bytes= */
400
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
200_252_631
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
200_252_631
);
}
@Test
public
void
defaultConstructor_estimatorKeepsTenSamples
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
// Add 12 samples, the first two should be discarded
statistic
.
addSample
(
/* bytes= */
4
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
9
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
25
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
36
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
49
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
64
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
81
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
100
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
121
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
144
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
169
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
77_600_000
);
}
@Test
public
void
constructorSetsMaxSamples_estimatorKeepsDefinedSamples
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
(
SlidingWeightedAverageBandwidthStatistic
.
getMaxCountEvictionFunction
(
2
),
fakeClock
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
5
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
5
,
/* durationUs= */
10
);
assertThat
(
statistic
.
getBandwidthEstimate
()).
isEqualTo
(
4_000_000
);
}
@Test
public
void
reset_withSamplesAdded_returnsNoEstimate
()
{
SlidingWeightedAverageBandwidthStatistic
statistic
=
new
SlidingWeightedAverageBandwidthStatistic
();
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
statistic
.
reset
();
assertThat
(
statistic
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
}
@Test
public
void
ageBasedSampleEvictionFunction_dropsOldSamples
()
{
// Create an estimator that keeps samples up to 15 seconds old.
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SlidingWeightedAverageBandwidthStatistic
estimator
=
new
SlidingWeightedAverageBandwidthStatistic
(
SlidingWeightedAverageBandwidthStatistic
.
getAgeBasedEvictionFunction
(
15_000
),
fakeClock
);
// Add sample at time = 0.99 seconds.
fakeClock
.
advanceTime
(
999
);
estimator
.
addSample
(
/* bytes= */
10
,
/* durationUs= */
10
);
// Add sample at time = 1 seconds.
fakeClock
.
advanceTime
(
1
);
estimator
.
addSample
(
/* bytes= */
5
,
/* durationUs= */
10
);
// Add sample at time = 5 seconds.
fakeClock
.
advanceTime
(
4_000
);
estimator
.
addSample
(
/* bytes= */
5
,
/* durationUs= */
10
);
// Add sample at time = 16 seconds, first sample should be dropped, but second sample should
// remain.
fakeClock
.
advanceTime
(
11_000
);
estimator
.
addSample
(
/* bytes= */
5
,
/* durationUs= */
10
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
4_000_000
);
}
@Test
public
void
ageBasedSampleEvictionFunction_dropsOldSamples_onlyWhenAddingSamples
()
{
// Create an estimator that keeps samples up to 5 seconds old.
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SlidingWeightedAverageBandwidthStatistic
estimator
=
new
SlidingWeightedAverageBandwidthStatistic
(
SlidingWeightedAverageBandwidthStatistic
.
getAgeBasedEvictionFunction
(
5_000
),
fakeClock
);
// Add sample at time = 0 seconds.
estimator
.
addSample
(
/* bytes= */
16
,
/* durationUs= */
10
);
// Add sample at time = 4 seconds.
fakeClock
.
advanceTime
(
4_000
);
estimator
.
addSample
(
/* bytes= */
9
,
/* durationUs= */
10
);
// Advance clock to 10 seconds, samples should remain
fakeClock
.
advanceTime
(
6_000
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
10_400_000
);
}
}
library/core/src/test/java/com/google/android/exoplayer2/upstream/experimental/SplitParallelSampleBandwidthEstimatorTest.java
0 → 100644
View file @
4665390f
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
upstream
.
experimental
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
assertThrows
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
verify
;
import
static
org
.
mockito
.
Mockito
.
when
;
import
android.os.Handler
;
import
android.os.Looper
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.testutil.FakeClock
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.upstream.BandwidthMeter
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.robolectric.shadows.ShadowLooper
;
/** Unite tests for the {@link SplitParallelSampleBandwidthEstimator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
SplitParallelSampleBandwidthEstimatorTest
{
@Test
public
void
builder_setNegativeMinSamples_throws
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
setMinSamples
(-
1
));
}
@Test
public
void
builder_setNegativeMinBytesTransferred_throws
()
{
assertThrows
(
IllegalArgumentException
.
class
,
()
->
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
setMinBytesTransferred
(-
1
));
}
@Test
public
void
transferEvents_singleTransfer_providesOneSample
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SplitParallelSampleBandwidthEstimator
estimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
DataSource
source
=
new
FakeDataSource
();
estimator
.
onTransferInitializing
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
200
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
ShadowLooper
.
idleMainLooper
();
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
80_000
);
verify
(
eventListener
).
onBandwidthSample
(
20
,
200
,
80_000
);
}
@Test
public
void
transferEvents_twoParallelTransfers_providesTwoSamples
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SplitParallelSampleBandwidthEstimator
estimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
DataSource
source1
=
new
FakeDataSource
();
DataSource
source2
=
new
FakeDataSource
();
// At time = 10 ms, source1 starts.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source1
);
estimator
.
onTransferStart
(
source1
);
// At time 20 ms, source1 reports 200 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source1
,
/* bytesTransferred= */
200
);
// At time = 30 ms, source2 starts.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source2
);
estimator
.
onTransferStart
(
source2
);
// At time = 40 ms, both sources report 100 bytes each.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source1
,
/* bytesTransferred= */
100
);
estimator
.
onBytesTransferred
(
source2
,
/* bytesTransferred= */
100
);
// At time = 50 ms, source1 transfer completes. At this point, 400 bytes have been transferred
// in total between times 10 and 50 ms.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source1
);
ShadowLooper
.
idleMainLooper
();
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
80_000
);
verify
(
eventListener
).
onBandwidthSample
(
40
,
400
,
80_000
);
// At time = 60 ms, source2 reports 160 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source2
,
/* bytesTransferred= */
160
);
// At time = 70 ms second transfer completes. At this time, 160 bytes have been
// transferred between times 50 and 70 ms.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source2
);
ShadowLooper
.
idleMainLooper
();
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
73_801
);
verify
(
eventListener
).
onBandwidthSample
(
20
,
160
,
73_801
);
}
@Test
public
void
onNetworkTypeChange_notifiesListener
()
{
FakeClock
fakeClock
=
new
FakeClock
(
0
);
SplitParallelSampleBandwidthEstimator
estimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
().
setClock
(
fakeClock
).
build
();
BandwidthMeter
.
EventListener
eventListener
=
mock
(
BandwidthMeter
.
EventListener
.
class
);
estimator
.
addEventListener
(
new
Handler
(
Looper
.
getMainLooper
()),
eventListener
);
estimator
.
onNetworkTypeChange
(
100
);
ShadowLooper
.
idleMainLooper
();
verify
(
eventListener
).
onBandwidthSample
(
0
,
0
,
100
);
}
@Test
public
void
minSamplesSet_doesNotReturnEstimateBefore
()
{
FakeDataSource
source
=
new
FakeDataSource
();
FakeClock
fakeClock
=
new
FakeClock
(
0
);
BandwidthStatistic
mockStatistic
=
mock
(
BandwidthStatistic
.
class
);
when
(
mockStatistic
.
getBandwidthEstimate
()).
thenReturn
(
1234L
);
SplitParallelSampleBandwidthEstimator
estimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
()
.
setBandwidthStatistic
(
mockStatistic
)
.
setMinSamples
(
1
)
.
setClock
(
fakeClock
)
.
build
();
// First sample.
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
// Second sample.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
1234L
);
}
@Test
public
void
minBytesTransferredSet_doesNotReturnEstimateBefore
()
{
FakeDataSource
source
=
new
FakeDataSource
();
FakeClock
fakeClock
=
new
FakeClock
(
0
);
BandwidthStatistic
mockStatistic
=
mock
(
BandwidthStatistic
.
class
);
when
(
mockStatistic
.
getBandwidthEstimate
()).
thenReturn
(
1234L
);
SplitParallelSampleBandwidthEstimator
estimator
=
new
SplitParallelSampleBandwidthEstimator
.
Builder
()
.
setBandwidthStatistic
(
mockStatistic
)
.
setMinBytesTransferred
(
500
)
.
setClock
(
fakeClock
)
.
build
();
// First sample transfers 499 bytes.
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
499
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
())
.
isEqualTo
(
BandwidthEstimator
.
ESTIMATE_NOT_AVAILABLE
);
// Second sample transfers 100 bytes.
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferInitializing
(
source
);
estimator
.
onTransferStart
(
source
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onBytesTransferred
(
source
,
/* bytesTransferred= */
100
);
fakeClock
.
advanceTime
(
10
);
estimator
.
onTransferEnd
(
source
);
assertThat
(
estimator
.
getBandwidthEstimate
()).
isEqualTo
(
1234L
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment