mirror of
https://github.com/samsonjs/media.git
synced 2026-04-05 11:15:46 +00:00
Publish experimental bandwidth meter classes
PiperOrigin-RevId: 524846153
This commit is contained in:
parent
e0bb23d463
commit
9081c70788
19 changed files with 3812 additions and 0 deletions
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import android.os.Handler;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
|
||||
/** The interface for different bandwidth estimation strategies. */
|
||||
@UnstableApi
|
||||
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);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/** The interface for different bandwidth estimation statistics. */
|
||||
@UnstableApi
|
||||
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();
|
||||
}
|
||||
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||
|
||||
/**
|
||||
* A {@link BandwidthEstimator} that captures a transfer sample each time all parallel transfers
|
||||
* end.
|
||||
*/
|
||||
@UnstableApi
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,834 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.NetworkTypeObserver;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.TransferListener;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.media3.exoplayer.upstream.TimeToFirstByteEstimator;
|
||||
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}.
|
||||
*/
|
||||
@UnstableApi
|
||||
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};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.exoplayer.upstream.experimental.BandwidthEstimator.ESTIMATE_NOT_AVAILABLE;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
/** A {@link BandwidthStatistic} that calculates estimates using an exponential weighted average. */
|
||||
@UnstableApi
|
||||
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 + (1f - factor) * bitrate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getBandwidthEstimate() {
|
||||
return bitrateEstimate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
bitrateEstimate = ESTIMATE_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.upstream.TimeToFirstByteEstimator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Implementation of {@link TimeToFirstByteEstimator} based on exponential weighted average. */
|
||||
@UnstableApi
|
||||
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 + (1d - 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.exoplayer.upstream.SlidingPercentile;
|
||||
import androidx.media3.exoplayer.upstream.TimeToFirstByteEstimator;
|
||||
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.
|
||||
*/
|
||||
@UnstableApi
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.exoplayer.upstream.experimental.BandwidthEstimator.ESTIMATE_NOT_AVAILABLE;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* A {@link BandwidthStatistic} that calculates estimates based on a sliding window weighted
|
||||
* percentile.
|
||||
*/
|
||||
@UnstableApi
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Util.castNonNull;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
|
||||
/**
|
||||
* A {@link BandwidthStatistic} that calculates estimates based on a sliding window weighted
|
||||
* average.
|
||||
*/
|
||||
@UnstableApi
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
|
||||
import android.os.Handler;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.util.Clock;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
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.
|
||||
*/
|
||||
@UnstableApi
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import androidx.media3.common.util.NonNullApi;
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.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.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.media3.test.utils.FakeDataSource;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,734 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.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.media3.common.C;
|
||||
import androidx.media3.common.util.NetworkTypeObserver;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.test.utils.FakeDataSource;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static androidx.media3.exoplayer.upstream.experimental.ExponentialWeightedAverageTimeToFirstByteEstimator.DEFAULT_SMOOTHING_FACTOR;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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= */ .2f));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new PercentileTimeToFirstByteEstimator(
|
||||
/* numberOfSamples= */ -123, /* percentile= */ .2f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructor_invalidPercentile_throwsIllegalArgumentException() {
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() ->
|
||||
new PercentileTimeToFirstByteEstimator(
|
||||
/* numberOfSamples= */ 11, /* percentile= */ .0f));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.upstream.experimental;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* 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 androidx.media3.exoplayer.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.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.upstream.BandwidthMeter;
|
||||
import androidx.media3.test.utils.FakeClock;
|
||||
import androidx.media3.test.utils.FakeDataSource;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue