public final class

DefaultBandwidthMeter

extends java.lang.Object

implements BandwidthMeter, TransferListener

 java.lang.Object

↳androidx.media3.exoplayer.upstream.DefaultBandwidthMeter

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-exoplayer:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

Estimates bandwidth by listening to data transfers.

The bandwidth estimate is calculated using a SlidingPercentile and is updated each time a transfer ends. 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 DefaultBandwidthMeter.Builder.

Summary

Fields
public static final longDEFAULT_INITIAL_BITRATE_ESTIMATE

Default initial bitrate estimate used when the device is offline or the network type cannot be determined, in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_2G

Default initial 2G bitrate estimates in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_3G

Default initial 3G bitrate estimates in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_4G

Default initial 4G bitrate estimates in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA

Default initial 5G-NSA bitrate estimates in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA

Default initial 5G-SA bitrate estimates in bits per second.

public static final <any>DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI

Default initial Wifi bitrate estimate in bits per second.

public static final intDEFAULT_SLIDING_WINDOW_MAX_WEIGHT

Default maximum weight for the sliding window.

Constructors
publicDefaultBandwidthMeter()

Methods
public voidaddEventListener(Handler eventHandler, BandwidthMeter.EventListener eventListener)

public synchronized longgetBitrateEstimate()

public static synchronized DefaultBandwidthMetergetSingletonInstance(Context context)

Returns a singleton instance of a DefaultBandwidthMeter with default configuration.

public TransferListenergetTransferListener()

public synchronized voidonBytesTransferred(DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred)

public synchronized voidonTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork)

public voidonTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork)

public synchronized voidonTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork)

public voidremoveEventListener(BandwidthMeter.EventListener eventListener)

public synchronized voidsetNetworkTypeOverride(int networkType)

Overrides the network type.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI

Default initial Wifi bitrate estimate in bits per second.

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_2G

Default initial 2G bitrate estimates in bits per second.

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_3G

Default initial 3G bitrate estimates in bits per second.

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_4G

Default initial 4G bitrate estimates in bits per second.

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA

Default initial 5G-NSA bitrate estimates in bits per second.

public static final <any> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA

Default initial 5G-SA bitrate estimates in bits per second.

public static final long DEFAULT_INITIAL_BITRATE_ESTIMATE

Default initial bitrate estimate used when the device is offline or the network type cannot be determined, in bits per second.

public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT

Default maximum weight for the sliding window.

Constructors

public DefaultBandwidthMeter()

Deprecated: Use DefaultBandwidthMeter.Builder instead.

Methods

public static synchronized DefaultBandwidthMeter getSingletonInstance(Context context)

Returns a singleton instance of a DefaultBandwidthMeter with default configuration.

Parameters:

context: A .

Returns:

The singleton instance.

public synchronized void setNetworkTypeOverride(int networkType)

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.

Applications should not normally call this method. It is intended for testing purposes.

Parameters:

networkType: The overriding network type.

public synchronized long getBitrateEstimate()

public TransferListener getTransferListener()

public void addEventListener(Handler eventHandler, BandwidthMeter.EventListener eventListener)

public void removeEventListener(BandwidthMeter.EventListener eventListener)

public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork)

public synchronized void onTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork)

public synchronized void onBytesTransferred(DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred)

public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork)

Source

/*
 * Copyright (C) 2016 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;

import android.content.Context;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
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.EventListener.EventDispatcher;
import com.google.common.base.Ascii;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.HashMap;
import java.util.Map;

/**
 * Estimates bandwidth by listening to data transfers.
 *
 * <p>The bandwidth estimate is calculated using a {@link SlidingPercentile} and is updated each
 * time a transfer ends. 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 DefaultBandwidthMeter implements BandwidthMeter, TransferListener {

  /** Default initial Wifi bitrate estimate in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI =
      ImmutableList.of(5_400_000L, 3_300_000L, 2_000_000L, 1_300_000L, 760_000L);

  /** Default initial 2G bitrate estimates in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_2G =
      ImmutableList.of(1_700_000L, 820_000L, 450_000L, 180_000L, 130_000L);

  /** Default initial 3G bitrate estimates in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_3G =
      ImmutableList.of(2_300_000L, 1_300_000L, 1_000_000L, 820_000L, 570_000L);

  /** Default initial 4G bitrate estimates in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_4G =
      ImmutableList.of(3_400_000L, 2_000_000L, 1_400_000L, 1_000_000L, 620_000L);

  /** Default initial 5G-NSA bitrate estimates in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA =
      ImmutableList.of(7_500_000L, 5_200_000L, 3_700_000L, 1_800_000L, 1_100_000L);

  /** Default initial 5G-SA bitrate estimates in bits per second. */
  public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA =
      ImmutableList.of(3_300_000L, 1_900_000L, 1_700_000L, 1_500_000L, 1_200_000L);

  /**
   * 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;

  /** Default maximum weight for the sliding window. */
  public static final int DEFAULT_SLIDING_WINDOW_MAX_WEIGHT = 2000;

  /**
   * 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;

  @Nullable private static DefaultBandwidthMeter singletonInstance;

  /** Builder for a bandwidth meter. */
  public static final class Builder {

    @Nullable private final Context context;

    private Map<Integer, Long> initialBitrateEstimates;
    private int slidingWindowMaxWeight;
    private Clock clock;
    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 == null ? null : context.getApplicationContext();
      initialBitrateEstimates = getInitialBitrateEstimatesForCountry(Util.getCountryCode(context));
      slidingWindowMaxWeight = DEFAULT_SLIDING_WINDOW_MAX_WEIGHT;
      clock = Clock.DEFAULT;
      resetOnNetworkTypeChange = true;
    }

    /**
     * Sets the maximum weight for the sliding window.
     *
     * @param slidingWindowMaxWeight The maximum weight for the sliding window.
     * @return This builder.
     */
    public Builder setSlidingWindowMaxWeight(int slidingWindowMaxWeight) {
      this.slidingWindowMaxWeight = slidingWindowMaxWeight;
      return this;
    }

    /**
     * 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.
     */
    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.
     */
    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.
     */
    public Builder setInitialBitrateEstimate(String countryCode) {
      initialBitrateEstimates =
          getInitialBitrateEstimatesForCountry(Ascii.toUpperCase(countryCode));
      return this;
    }

    /**
     * Sets the clock used to estimate bandwidth from data transfers. Should only be set for testing
     * purposes.
     *
     * @param clock The clock used to estimate bandwidth from data transfers.
     * @return This builder.
     */
    public Builder setClock(Clock clock) {
      this.clock = clock;
      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.
     */
    public Builder setResetOnNetworkTypeChange(boolean resetOnNetworkTypeChange) {
      this.resetOnNetworkTypeChange = resetOnNetworkTypeChange;
      return this;
    }

    /**
     * Builds the bandwidth meter.
     *
     * @return A bandwidth meter with the configured properties.
     */
    public DefaultBandwidthMeter build() {
      return new DefaultBandwidthMeter(
          context,
          initialBitrateEstimates,
          slidingWindowMaxWeight,
          clock,
          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;
    }
  }

  /**
   * Returns a singleton instance of a {@link DefaultBandwidthMeter} with default configuration.
   *
   * @param context A {@link Context}.
   * @return The singleton instance.
   */
  public static synchronized DefaultBandwidthMeter getSingletonInstance(Context context) {
    if (singletonInstance == null) {
      singletonInstance = new DefaultBandwidthMeter.Builder(context).build();
    }
    return singletonInstance;
  }

  private static final int ELAPSED_MILLIS_FOR_ESTIMATE = 2000;
  private static final int BYTES_TRANSFERRED_FOR_ESTIMATE = 512 * 1024;

  private final ImmutableMap<Integer, Long> initialBitrateEstimates;
  private final EventDispatcher eventDispatcher;
  private final SlidingPercentile slidingPercentile;
  private final Clock clock;
  private final boolean resetOnNetworkTypeChange;

  private int streamCount;
  private long sampleStartTimeMs;
  private long sampleBytesTransferred;

  private @C.NetworkType int networkType;
  private long totalElapsedTimeMs;
  private long totalBytesTransferred;
  private long bitrateEstimate;
  private long lastReportedBitrateEstimate;

  private boolean networkTypeOverrideSet;
  private @C.NetworkType int networkTypeOverride;

  /** @deprecated Use {@link Builder} instead. */
  @Deprecated
  public DefaultBandwidthMeter() {
    this(
        /* context= */ null,
        /* initialBitrateEstimates= */ ImmutableMap.of(),
        DEFAULT_SLIDING_WINDOW_MAX_WEIGHT,
        Clock.DEFAULT,
        /* resetOnNetworkTypeChange= */ false);
  }

  private DefaultBandwidthMeter(
      @Nullable Context context,
      Map<Integer, Long> initialBitrateEstimates,
      int maxWeight,
      Clock clock,
      boolean resetOnNetworkTypeChange) {
    this.initialBitrateEstimates = ImmutableMap.copyOf(initialBitrateEstimates);
    this.eventDispatcher = new EventDispatcher();
    this.slidingPercentile = new SlidingPercentile(maxWeight);
    this.clock = clock;
    this.resetOnNetworkTypeChange = resetOnNetworkTypeChange;
    if (context != null) {
      NetworkTypeObserver networkTypeObserver = NetworkTypeObserver.getInstance(context);
      networkType = networkTypeObserver.getNetworkType();
      bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType);
      networkTypeObserver.register(/* listener= */ this::onNetworkTypeChanged);
    } else {
      networkType = C.NETWORK_TYPE_UNKNOWN;
      bitrateEstimate = getInitialBitrateEstimateForNetworkType(C.NETWORK_TYPE_UNKNOWN);
    }
  }

  /**
   * 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() {
    return bitrateEstimate;
  }

  @Override
  public TransferListener getTransferListener() {
    return this;
  }

  @Override
  public void addEventListener(Handler eventHandler, EventListener eventListener) {
    Assertions.checkNotNull(eventHandler);
    Assertions.checkNotNull(eventListener);
    eventDispatcher.addListener(eventHandler, eventListener);
  }

  @Override
  public void removeEventListener(EventListener eventListener) {
    eventDispatcher.removeListener(eventListener);
  }

  @Override
  public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) {
    // Do nothing.
  }

  @Override
  public synchronized void onTransferStart(
      DataSource source, DataSpec dataSpec, boolean isNetwork) {
    if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) {
      return;
    }
    if (streamCount == 0) {
      sampleStartTimeMs = clock.elapsedRealtime();
    }
    streamCount++;
  }

  @Override
  public synchronized void onBytesTransferred(
      DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred) {
    if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) {
      return;
    }
    sampleBytesTransferred += bytesTransferred;
  }

  @Override
  public synchronized void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) {
    if (!isTransferAtFullNetworkSpeed(dataSpec, isNetwork)) {
      return;
    }
    Assertions.checkState(streamCount > 0);
    long nowMs = clock.elapsedRealtime();
    int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs);
    totalElapsedTimeMs += sampleElapsedTimeMs;
    totalBytesTransferred += sampleBytesTransferred;
    if (sampleElapsedTimeMs > 0) {
      float bitsPerSecond = (sampleBytesTransferred * 8000f) / sampleElapsedTimeMs;
      slidingPercentile.addSample((int) Math.sqrt(sampleBytesTransferred), bitsPerSecond);
      if (totalElapsedTimeMs >= ELAPSED_MILLIS_FOR_ESTIMATE
          || totalBytesTransferred >= BYTES_TRANSFERRED_FOR_ESTIMATE) {
        bitrateEstimate = (long) slidingPercentile.getPercentile(0.5f);
      }
      maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate);
      sampleStartTimeMs = nowMs;
      sampleBytesTransferred = 0;
    } // Else any sample bytes transferred will be carried forward into the next sample.
    streamCount--;
  }

  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.bitrateEstimate = getInitialBitrateEstimateForNetworkType(networkType);
    long nowMs = clock.elapsedRealtime();
    int sampleElapsedTimeMs = streamCount > 0 ? (int) (nowMs - sampleStartTimeMs) : 0;
    maybeNotifyBandwidthSample(sampleElapsedTimeMs, sampleBytesTransferred, bitrateEstimate);

    // Reset the remainder of the state.
    sampleStartTimeMs = nowMs;
    sampleBytesTransferred = 0;
    totalBytesTransferred = 0;
    totalElapsedTimeMs = 0;
    slidingPercentile.reset();
  }

  private void maybeNotifyBandwidthSample(
      int elapsedMs, long bytesTransferred, long bitrateEstimate) {
    if (elapsedMs == 0 && bytesTransferred == 0 && bitrateEstimate == lastReportedBitrateEstimate) {
      return;
    }
    lastReportedBitrateEstimate = bitrateEstimate;
    eventDispatcher.bandwidthSample(elapsedMs, bytesTransferred, bitrateEstimate);
  }

  private long getInitialBitrateEstimateForNetworkType(@C.NetworkType int networkType) {
    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 "AE":
        return new int[] {1, 4, 4, 4, 3, 2};
      case "AG":
        return new int[] {2, 3, 1, 2, 2, 2};
      case "AM":
        return new int[] {2, 3, 2, 4, 2, 2};
      case "AR":
        return new int[] {2, 4, 1, 1, 2, 2};
      case "AS":
        return new int[] {2, 2, 2, 3, 2, 2};
      case "AU":
        return new int[] {0, 1, 0, 1, 2, 2};
      case "BE":
        return new int[] {0, 0, 3, 3, 2, 2};
      case "BF":
        return new int[] {4, 3, 4, 3, 2, 2};
      case "BH":
        return new int[] {1, 2, 2, 4, 4, 2};
      case "BJ":
        return new int[] {4, 4, 3, 4, 2, 2};
      case "BN":
        return new int[] {3, 2, 1, 1, 2, 2};
      case "BO":
        return new int[] {1, 3, 3, 2, 2, 2};
      case "BQ":
        return new int[] {1, 2, 2, 0, 2, 2};
      case "BS":
        return new int[] {4, 2, 2, 3, 2, 2};
      case "BT":
        return new int[] {3, 1, 3, 2, 2, 2};
      case "BY":
        return new int[] {0, 1, 1, 3, 2, 2};
      case "BZ":
        return new int[] {2, 4, 2, 2, 2, 2};
      case "CA":
        return new int[] {0, 2, 1, 2, 4, 1};
      case "CD":
        return new int[] {4, 2, 3, 1, 2, 2};
      case "CF":
        return new int[] {4, 2, 3, 2, 2, 2};
      case "CI":
        return new int[] {3, 3, 3, 4, 2, 2};
      case "CK":
        return new int[] {2, 2, 2, 1, 2, 2};
      case "AO":
      case "CM":
        return new int[] {3, 4, 3, 2, 2, 2};
      case "CN":
        return new int[] {2, 0, 2, 2, 3, 1};
      case "CO":
        return new int[] {2, 2, 4, 2, 2, 2};
      case "CR":
        return new int[] {2, 2, 4, 4, 2, 2};
      case "CV":
        return new int[] {2, 3, 1, 0, 2, 2};
      case "CW":
        return new int[] {2, 2, 0, 0, 2, 2};
      case "CY":
        return new int[] {1, 0, 0, 0, 1, 2};
      case "DE":
        return new int[] {0, 0, 2, 2, 1, 2};
      case "DJ":
        return new int[] {4, 1, 4, 4, 2, 2};
      case "DK":
        return new int[] {0, 0, 1, 0, 0, 2};
      case "EC":
        return new int[] {2, 4, 2, 1, 2, 2};
      case "EG":
        return new int[] {3, 4, 2, 3, 2, 2};
      case "ET":
        return new int[] {4, 4, 3, 1, 2, 2};
      case "FI":
        return new int[] {0, 0, 0, 1, 0, 2};
      case "FJ":
        return new int[] {3, 1, 3, 3, 2, 2};
      case "FM":
        return new int[] {3, 2, 4, 2, 2, 2};
      case "FR":
        return new int[] {1, 1, 2, 1, 1, 1};
      case "GA":
        return new int[] {2, 3, 1, 1, 2, 2};
      case "GB":
        return new int[] {0, 0, 1, 1, 2, 3};
      case "GE":
        return new int[] {1, 1, 1, 3, 2, 2};
      case "BB":
      case "FO":
      case "GG":
        return new int[] {0, 2, 0, 0, 2, 2};
      case "GH":
        return new int[] {3, 2, 3, 2, 2, 2};
      case "GN":
        return new int[] {4, 3, 4, 2, 2, 2};
      case "GQ":
        return new int[] {4, 2, 3, 4, 2, 2};
      case "GT":
        return new int[] {2, 3, 2, 1, 2, 2};
      case "AW":
      case "GU":
        return new int[] {1, 2, 4, 4, 2, 2};
      case "BW":
      case "GY":
        return new int[] {3, 4, 1, 0, 2, 2};
      case "HK":
        return new int[] {0, 1, 2, 3, 2, 0};
      case "HU":
        return new int[] {0, 0, 0, 1, 3, 2};
      case "ID":
        return new int[] {3, 2, 3, 3, 3, 2};
      case "ES":
      case "IE":
        return new int[] {0, 1, 1, 1, 2, 2};
      case "IL":
        return new int[] {1, 1, 2, 3, 4, 2};
      case "IM":
        return new int[] {0, 2, 0, 1, 2, 2};
      case "IN":
        return new int[] {1, 1, 3, 2, 4, 3};
      case "IR":
        return new int[] {3, 0, 1, 1, 3, 0};
      case "IT":
        return new int[] {0, 1, 0, 1, 1, 2};
      case "JE":
        return new int[] {3, 2, 1, 2, 2, 2};
      case "DO":
      case "JM":
        return new int[] {3, 4, 4, 4, 2, 2};
      case "JP":
        return new int[] {0, 1, 0, 1, 1, 1};
      case "KE":
        return new int[] {3, 3, 2, 2, 2, 2};
      case "KG":
        return new int[] {2, 1, 1, 1, 2, 2};
      case "KH":
        return new int[] {1, 1, 4, 2, 2, 2};
      case "KR":
        return new int[] {0, 0, 1, 3, 4, 4};
      case "KW":
        return new int[] {1, 1, 0, 0, 0, 2};
      case "AL":
      case "BA":
      case "KY":
        return new int[] {1, 2, 0, 1, 2, 2};
      case "KZ":
        return new int[] {1, 1, 2, 2, 2, 2};
      case "LB":
        return new int[] {3, 2, 1, 4, 2, 2};
      case "AD":
      case "BM":
      case "GL":
      case "LC":
        return new int[] {1, 2, 0, 0, 2, 2};
      case "LK":
        return new int[] {3, 1, 3, 4, 4, 2};
      case "LR":
        return new int[] {3, 4, 4, 3, 2, 2};
      case "LS":
        return new int[] {3, 3, 4, 3, 2, 2};
      case "LU":
        return new int[] {1, 0, 2, 2, 2, 2};
      case "MC":
        return new int[] {0, 2, 2, 0, 2, 2};
      case "JO":
      case "ME":
        return new int[] {1, 0, 0, 1, 2, 2};
      case "MF":
        return new int[] {1, 2, 1, 0, 2, 2};
      case "MG":
        return new int[] {3, 4, 2, 2, 2, 2};
      case "MH":
        return new int[] {3, 2, 2, 4, 2, 2};
      case "ML":
        return new int[] {4, 3, 3, 1, 2, 2};
      case "MM":
        return new int[] {2, 4, 3, 3, 2, 2};
      case "MN":
        return new int[] {2, 0, 1, 2, 2, 2};
      case "MO":
        return new int[] {0, 2, 4, 4, 2, 2};
      case "GF":
      case "GP":
      case "MQ":
        return new int[] {2, 1, 2, 3, 2, 2};
      case "MR":
        return new int[] {4, 1, 3, 4, 2, 2};
      case "EE":
      case "LT":
      case "LV":
      case "MT":
        return new int[] {0, 0, 0, 0, 2, 2};
      case "MU":
        return new int[] {3, 1, 1, 2, 2, 2};
      case "MV":
        return new int[] {3, 4, 1, 4, 2, 2};
      case "MW":
        return new int[] {4, 2, 1, 0, 2, 2};
      case "CG":
      case "MX":
        return new int[] {2, 4, 3, 4, 2, 2};
      case "BD":
      case "MY":
        return new int[] {2, 1, 3, 3, 2, 2};
      case "NA":
        return new int[] {4, 3, 2, 2, 2, 2};
      case "AZ":
      case "NC":
        return new int[] {3, 2, 4, 4, 2, 2};
      case "NG":
        return new int[] {3, 4, 1, 1, 2, 2};
      case "NI":
        return new int[] {2, 3, 4, 3, 2, 2};
      case "NL":
        return new int[] {0, 0, 3, 2, 0, 4};
      case "NO":
        return new int[] {0, 0, 2, 0, 0, 2};
      case "NP":
        return new int[] {2, 1, 4, 3, 2, 2};
      case "NR":
        return new int[] {3, 2, 2, 0, 2, 2};
      case "NZ":
        return new int[] {1, 0, 1, 2, 4, 2};
      case "OM":
        return new int[] {2, 3, 1, 3, 4, 2};
      case "PA":
        return new int[] {1, 3, 3, 3, 2, 2};
      case "PE":
        return new int[] {2, 3, 4, 4, 4, 2};
      case "PF":
        return new int[] {2, 3, 3, 1, 2, 2};
      case "CU":
      case "PG":
        return new int[] {4, 4, 3, 2, 2, 2};
      case "PH":
        return new int[] {2, 2, 3, 3, 3, 2};
      case "PR":
        return new int[] {2, 3, 2, 2, 3, 3};
      case "PS":
        return new int[] {3, 4, 1, 2, 2, 2};
      case "PT":
        return new int[] {0, 1, 0, 0, 2, 2};
      case "PW":
        return new int[] {2, 2, 4, 1, 2, 2};
      case "PY":
        return new int[] {2, 2, 3, 2, 2, 2};
      case "QA":
        return new int[] {2, 4, 2, 4, 4, 2};
      case "RE":
        return new int[] {1, 1, 1, 2, 2, 2};
      case "RO":
        return new int[] {0, 0, 1, 1, 1, 2};
      case "GR":
      case "HR":
      case "MD":
      case "MK":
      case "RS":
        return new int[] {1, 0, 0, 0, 2, 2};
      case "RU":
        return new int[] {0, 0, 0, 1, 2, 2};
      case "RW":
        return new int[] {3, 4, 3, 0, 2, 2};
      case "KI":
      case "KM":
      case "LY":
      case "SB":
        return new int[] {4, 2, 4, 3, 2, 2};
      case "SC":
        return new int[] {4, 3, 0, 2, 2, 2};
      case "SG":
        return new int[] {1, 1, 2, 3, 1, 4};
      case "BG":
      case "CZ":
      case "SI":
        return new int[] {0, 0, 0, 0, 1, 2};
      case "AT":
      case "CH":
      case "IS":
      case "SE":
      case "SK":
        return new int[] {0, 0, 0, 0, 0, 2};
      case "SL":
        return new int[] {4, 3, 4, 1, 2, 2};
      case "AX":
      case "GI":
      case "LI":
      case "MP":
      case "PM":
      case "SJ":
      case "SM":
        return new int[] {0, 2, 2, 2, 2, 2};
      case "HN":
      case "PK":
      case "SO":
        return new int[] {3, 2, 3, 3, 2, 2};
      case "BR":
      case "SR":
        return new int[] {2, 3, 2, 2, 2, 2};
      case "FK":
      case "KP":
      case "MA":
      case "MZ":
      case "ST":
        return new int[] {3, 2, 2, 2, 2, 2};
      case "SV":
        return new int[] {2, 2, 3, 3, 2, 2};
      case "SZ":
        return new int[] {4, 3, 2, 4, 2, 2};
      case "SX":
      case "TC":
        return new int[] {2, 2, 1, 0, 2, 2};
      case "TG":
        return new int[] {3, 3, 2, 0, 2, 2};
      case "TH":
        return new int[] {0, 3, 2, 3, 3, 0};
      case "TJ":
        return new int[] {4, 2, 4, 4, 2, 2};
      case "BI":
      case "DZ":
      case "SY":
      case "TL":
        return new int[] {4, 3, 4, 4, 2, 2};
      case "TM":
        return new int[] {4, 2, 4, 2, 2, 2};
      case "TO":
        return new int[] {4, 2, 3, 3, 2, 2};
      case "TR":
        return new int[] {1, 1, 0, 1, 2, 2};
      case "TT":
        return new int[] {1, 4, 1, 1, 2, 2};
      case "AQ":
      case "ER":
      case "IO":
      case "NU":
      case "SH":
      case "SS":
      case "TV":
        return new int[] {4, 2, 2, 2, 2, 2};
      case "TW":
        return new int[] {0, 0, 0, 0, 0, 0};
      case "GW":
      case "TZ":
        return new int[] {3, 4, 3, 3, 2, 2};
      case "UA":
        return new int[] {0, 3, 1, 1, 2, 2};
      case "IQ":
      case "UG":
        return new int[] {3, 3, 3, 3, 2, 2};
      case "CL":
      case "PL":
      case "US":
        return new int[] {1, 1, 2, 2, 3, 2};
      case "LA":
      case "UY":
        return new int[] {2, 2, 1, 2, 2, 2};
      case "UZ":
        return new int[] {2, 2, 3, 4, 2, 2};
      case "AI":
      case "BL":
      case "CX":
      case "DM":
      case "GD":
      case "MS":
      case "VC":
        return new int[] {1, 2, 2, 2, 2, 2};
      case "SA":
      case "TN":
      case "VG":
        return new int[] {2, 2, 1, 1, 2, 2};
      case "VI":
        return new int[] {1, 2, 1, 3, 2, 2};
      case "VN":
        return new int[] {0, 3, 3, 4, 2, 2};
      case "VU":
        return new int[] {4, 2, 2, 1, 2, 2};
      case "GM":
      case "WF":
        return new int[] {4, 2, 2, 4, 2, 2};
      case "WS":
        return new int[] {3, 1, 2, 1, 2, 2};
      case "XK":
        return new int[] {1, 1, 1, 1, 2, 2};
      case "AF":
      case "HT":
      case "NE":
      case "SD":
      case "SN":
      case "TD":
      case "VE":
      case "YE":
        return new int[] {4, 4, 4, 4, 2, 2};
      case "YT":
        return new int[] {4, 1, 1, 1, 2, 2};
      case "ZA":
        return new int[] {3, 3, 1, 1, 1, 2};
      case "ZM":
        return new int[] {3, 3, 4, 2, 2, 2};
      case "ZW":
        return new int[] {3, 2, 4, 3, 2, 2};
      default:
        return new int[] {2, 2, 2, 2, 2, 2};
    }
  }
}