mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +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