text:
package com.google.android.exoplayer2.offline;

import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;

import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.SparseIntArray;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.RendererCapabilities;
import com.google.android.exoplayer2.RenderersFactory;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoRendererEventListener;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;

/**
 * A helper for initializing and removing downloads.
 *
 *
The helper extracts track information from the media, selects tracks for downloading, and
 * creates {@link DownloadRequest download requests} based on the selected tracks.
 *
 *
A typical usage of DownloadHelper follows these steps:
 *
 *

 *
Build the helper using one of the {@code forMediaItem} methods.
 *
Prepare the helper using {@link #prepare(Callback)} and wait for the callback.
 *
Optional: Inspect the selected tracks using {@link #getMappedTrackInfo(int)} and {@link
 *       #getTrackSelections(int, int)}, and make adjustments using {@link
 *       #clearTrackSelections(int)}, {@link #replaceTrackSelections(int, Parameters)} and {@link
 *       #addTrackSelection(int, Parameters)}.
 *
Create a download request for the selected track using {@link #getDownloadRequest(byte[])}.
 *
Release the helper using {@link #release()}.
 *

 */
public final class DownloadHelper {

  /**
   * Default track selection parameters for downloading, but without any {@link Context}
   * constraints.
   *
   *
If possible, use {@link #getDefaultTrackSelectorParameters(Context)} instead.
   *
   * @see Parameters#DEFAULT_WITHOUT_CONTEXT
   */
  public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT =
      Parameters.DEFAULT_WITHOUT_CONTEXT.buildUpon().setForceHighestSupportedBitrate(true).build();

  /**
   * @deprecated This instance does not have {@link Context} constraints. Use {@link
   *     #getDefaultTrackSelectorParameters(Context)} instead.
   */
  @Deprecated
  public static final Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT =
      DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;

  /**
   * @deprecated This instance does not have {@link Context} constraints. Use {@link
   *     #getDefaultTrackSelectorParameters(Context)} instead.
   */
  @Deprecated
  public static final DefaultTrackSelector.Parameters DEFAULT_TRACK_SELECTOR_PARAMETERS =
      DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT;

  /** Returns the default parameters used for track selection for downloading. */
  public static DefaultTrackSelector.Parameters getDefaultTrackSelectorParameters(Context context) {
    return Parameters.getDefaults(context)
        .buildUpon()
        .setForceHighestSupportedBitrate(true)
        .build();
  }

  /** A callback to be notified when the {@link DownloadHelper} is prepared. */
  public interface Callback {

    /**
     * Called when preparation completes.
     *
     * @param helper The reporting {@link DownloadHelper}.
     */
    void onPrepared(DownloadHelper helper);

    /**
     * Called when preparation fails.
     *
     * @param helper The reporting {@link DownloadHelper}.
     * @param e The error.
     */
    void onPrepareError(DownloadHelper helper, IOException e);
  }

  /** Thrown at an attempt to download live content. */
  public static class LiveContentUnsupportedException extends IOException {}

  /**
   * Extracts renderer capabilities for the renderers created by the provided renderers factory.
   *
   * @param renderersFactory A {@link RenderersFactory}.
   * @return The {@link RendererCapabilities} for each renderer created by the {@code
   *     renderersFactory}.
   */
  public static RendererCapabilities[] getRendererCapabilities(RenderersFactory renderersFactory) {
    Renderer[] renderers =
        renderersFactory.createRenderers(
            Util.createHandlerForCurrentOrMainLooper(),
            new VideoRendererEventListener() {},
            new AudioRendererEventListener() {},
            (cues) -> {},
            (metadata) -> {});
    RendererCapabilities[] capabilities = new RendererCapabilities[renderers.length];
    for (int i = 0; i < renderers.length; i++) {
      capabilities[i] = renderers[i].getCapabilities();
    }
    return capabilities;
  }

  /** @deprecated Use {@link #forMediaItem(Context, MediaItem)} */
  @Deprecated
  public static DownloadHelper forProgressive(Context context, Uri uri) {
    return forMediaItem(context, new MediaItem.Builder().setUri(uri).build());
  }

  /** @deprecated Use {@link #forMediaItem(Context, MediaItem)} */
  @Deprecated
  public static DownloadHelper forProgressive(Context context, Uri uri, @Nullable String cacheKey) {
    return forMediaItem(
        context, new MediaItem.Builder().setUri(uri).setCustomCacheKey(cacheKey).build());
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory)} instead.
   */
  @SuppressWarnings("deprecation")
  @Deprecated
  public static DownloadHelper forDash(
      Context context,
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory) {
    return forDash(
        uri,
        dataSourceFactory,
        renderersFactory,
        /* drmSessionManager= */ null,
        getDefaultTrackSelectorParameters(context));
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory, DrmSessionManager)} instead.
   */
  @Deprecated
  public static DownloadHelper forDash(
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory,
      @Nullable DrmSessionManager drmSessionManager,
      DefaultTrackSelector.Parameters trackSelectorParameters) {
    return forMediaItem(
        new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build(),
        trackSelectorParameters,
        renderersFactory,
        dataSourceFactory,
        drmSessionManager);
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory)} instead.
   */
  @SuppressWarnings("deprecation")
  @Deprecated
  public static DownloadHelper forHls(
      Context context,
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory) {
    return forHls(
        uri,
        dataSourceFactory,
        renderersFactory,
        /* drmSessionManager= */ null,
        getDefaultTrackSelectorParameters(context));
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory, DrmSessionManager)} instead.
   */
  @Deprecated
  public static DownloadHelper forHls(
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory,
      @Nullable DrmSessionManager drmSessionManager,
      DefaultTrackSelector.Parameters trackSelectorParameters) {
    return forMediaItem(
        new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_M3U8).build(),
        trackSelectorParameters,
        renderersFactory,
        dataSourceFactory,
        drmSessionManager);
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory)} instead.
   */
  @SuppressWarnings("deprecation")
  @Deprecated
  public static DownloadHelper forSmoothStreaming(
      Uri uri, DataSource.Factory dataSourceFactory, RenderersFactory renderersFactory) {
    return forSmoothStreaming(
        uri,
        dataSourceFactory,
        renderersFactory,
        /* drmSessionManager= */ null,
        DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT);
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory)} instead.
   */
  @SuppressWarnings("deprecation")
  @Deprecated
  public static DownloadHelper forSmoothStreaming(
      Context context,
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory) {
    return forSmoothStreaming(
        uri,
        dataSourceFactory,
        renderersFactory,
        /* drmSessionManager= */ null,
        getDefaultTrackSelectorParameters(context));
  }

  /**
   * @deprecated Use {@link #forMediaItem(MediaItem, Parameters, RenderersFactory,
   *     DataSource.Factory, DrmSessionManager)} instead.
   */
  @Deprecated
  public static DownloadHelper forSmoothStreaming(
      Uri uri,
      DataSource.Factory dataSourceFactory,
      RenderersFactory renderersFactory,
      @Nullable DrmSessionManager drmSessionManager,
      DefaultTrackSelector.Parameters trackSelectorParameters) {
    return forMediaItem(
        new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_SS).build(),
        trackSelectorParameters,
        renderersFactory,
        dataSourceFactory,
        drmSessionManager);
  }

  /**
   * Creates a {@link DownloadHelper} for the given progressive media item.
   *
   * @param context The context.
   * @param mediaItem A {@link MediaItem}.
   * @return A {@link DownloadHelper} for progressive streams.
   * @throws IllegalStateException If the media item is of type DASH, HLS or SmoothStreaming.
   */
  public static DownloadHelper forMediaItem(Context context, MediaItem mediaItem) {
    Assertions.checkArgument(isProgressive(checkNotNull(mediaItem.localConfiguration)));
    return forMediaItem(
        mediaItem,
        getDefaultTrackSelectorParameters(context),
        /* renderersFactory= */ null,
        /* dataSourceFactory= */ null,
        /* drmSessionManager= */ null);
  }

  /**
   * Creates a {@link DownloadHelper} for the given media item.
   *
   * @param context The context.
   * @param mediaItem A {@link MediaItem}.
   * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
   *     selected.
   * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
   *     streams. This argument is required for adaptive streams and ignored for progressive
   *     streams.
   * @return A {@link DownloadHelper}.
   * @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
   *     SmoothStreaming media items.
   * @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
   */
  public static DownloadHelper forMediaItem(
      Context context,
      MediaItem mediaItem,
      @Nullable RenderersFactory renderersFactory,
      @Nullable DataSource.Factory dataSourceFactory) {
    return forMediaItem(
        mediaItem,
        getDefaultTrackSelectorParameters(context),
        renderersFactory,
        dataSourceFactory,
        /* drmSessionManager= */ null);
  }

  /**
   * Creates a {@link DownloadHelper} for the given media item.
   *
   * @param mediaItem A {@link MediaItem}.
   * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
   *     selected.
   * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
   *     downloading.
   * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
   *     streams. This argument is required for adaptive streams and ignored for progressive
   *     streams.
   * @return A {@link DownloadHelper}.
   * @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
   *     SmoothStreaming media items.
   * @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
   */
  public static DownloadHelper forMediaItem(
      MediaItem mediaItem,
      DefaultTrackSelector.Parameters trackSelectorParameters,
      @Nullable RenderersFactory renderersFactory,
      @Nullable DataSource.Factory dataSourceFactory) {
    return forMediaItem(
        mediaItem,
        trackSelectorParameters,
        renderersFactory,
        dataSourceFactory,
        /* drmSessionManager= */ null);
  }

  /**
   * Creates a {@link DownloadHelper} for the given media item.
   *
   * @param mediaItem A {@link MediaItem}.
   * @param renderersFactory A {@link RenderersFactory} creating the renderers for which tracks are
   *     selected.
   * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
   *     downloading.
   * @param dataSourceFactory A {@link DataSource.Factory} used to load the manifest for adaptive
   *     streams. This argument is required for adaptive streams and ignored for progressive
   *     streams.
   * @param drmSessionManager An optional {@link DrmSessionManager}. Used to help determine which
   *     tracks can be selected.
   * @return A {@link DownloadHelper}.
   * @throws IllegalStateException If the corresponding module is missing for DASH, HLS or
   *     SmoothStreaming media items.
   * @throws IllegalArgumentException If the {@code dataSourceFactory} is null for adaptive streams.
   */
  public static DownloadHelper forMediaItem(
      MediaItem mediaItem,
      DefaultTrackSelector.Parameters trackSelectorParameters,
      @Nullable RenderersFactory renderersFactory,
      @Nullable DataSource.Factory dataSourceFactory,
      @Nullable DrmSessionManager drmSessionManager) {
    boolean isProgressive = isProgressive(checkNotNull(mediaItem.localConfiguration));
    Assertions.checkArgument(isProgressive || dataSourceFactory != null);
    return new DownloadHelper(
        mediaItem,
        isProgressive
            ? null
            : createMediaSourceInternal(
                mediaItem, castNonNull(dataSourceFactory), drmSessionManager),
        trackSelectorParameters,
        renderersFactory != null
            ? getRendererCapabilities(renderersFactory)
            : new RendererCapabilities[0]);
  }

  /**
   * Equivalent to {@link #createMediaSource(DownloadRequest, DataSource.Factory, DrmSessionManager)
   * createMediaSource(downloadRequest, dataSourceFactory, null)}.
   */
  public static MediaSource createMediaSource(
      DownloadRequest downloadRequest, DataSource.Factory dataSourceFactory) {
    return createMediaSource(downloadRequest, dataSourceFactory, /* drmSessionManager= */ null);
  }

  /**
   * Utility method to create a {@link MediaSource} that only exposes the tracks defined in {@code
   * downloadRequest}.
   *
   * @param downloadRequest A {@link DownloadRequest}.
   * @param dataSourceFactory A factory for {@link DataSource}s to read the media.
   * @param drmSessionManager An optional {@link DrmSessionManager} to be passed to the {@link
   *     MediaSource}.
   * @return A {@link MediaSource} that only exposes the tracks defined in {@code downloadRequest}.
   */
  public static MediaSource createMediaSource(
      DownloadRequest downloadRequest,
      DataSource.Factory dataSourceFactory,
      @Nullable DrmSessionManager drmSessionManager) {
    return createMediaSourceInternal(
        downloadRequest.toMediaItem(), dataSourceFactory, drmSessionManager);
  }

  private final MediaItem.LocalConfiguration localConfiguration;
  @Nullable private final MediaSource mediaSource;
  private final DefaultTrackSelector trackSelector;
  private final RendererCapabilities[] rendererCapabilities;
  private final SparseIntArray scratchSet;
  private final Handler callbackHandler;
  private final Timeline.Window window;

  private boolean isPreparedWithMedia;
  private @MonotonicNonNull Callback callback;
  private @MonotonicNonNull MediaPreparer mediaPreparer;
  private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
  private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
  private List<ExoTrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
  private List<ExoTrackSelection> @MonotonicNonNull [][]
      immutableTrackSelectionsByPeriodAndRenderer;

  /**
   * Creates download helper.
   *
   * @param mediaItem The media item.
   * @param mediaSource A {@link MediaSource} for which tracks are selected, or null if no track
   *     selection needs to be made.
   * @param trackSelectorParameters {@link DefaultTrackSelector.Parameters} for selecting tracks for
   *     downloading.
   * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which tracks
   *     are selected.
   */
  public DownloadHelper(
      MediaItem mediaItem,
      @Nullable MediaSource mediaSource,
      DefaultTrackSelector.Parameters trackSelectorParameters,
      RendererCapabilities[] rendererCapabilities) {
    this.localConfiguration = checkNotNull(mediaItem.localConfiguration);
    this.mediaSource = mediaSource;
    this.trackSelector =
        new DefaultTrackSelector(trackSelectorParameters, new DownloadTrackSelection.Factory());
    this.rendererCapabilities = rendererCapabilities;
    this.scratchSet = new SparseIntArray();
    trackSelector.init(/* listener= */ () -> {}, new FakeBandwidthMeter());
    callbackHandler = Util.createHandlerForCurrentOrMainLooper();
    window = new Timeline.Window();
  }

  /**
   * Initializes the helper for starting a download.
   *
   * @param callback A callback to be notified when preparation completes or fails.
   * @throws IllegalStateException If the download helper has already been prepared.
   */
  public void prepare(Callback callback) {
    Assertions.checkState(this.callback == null);
    this.callback = callback;
    if (mediaSource != null) {
      mediaPreparer = new MediaPreparer(mediaSource, /* downloadHelper= */ this);
    } else {
      callbackHandler.post(() -> callback.onPrepared(this));
    }
  }

  /** Releases the helper and all resources it is holding. */
  public void release() {
    if (mediaPreparer != null) {
      mediaPreparer.release();
    }
  }

  /**
   * Returns the manifest, or null if no manifest is loaded. Must not be called until after
   * preparation completes.
   */
  @Nullable
  public Object getManifest() {
    if (mediaSource == null) {
      return null;
    }
    assertPreparedWithMedia();
    return mediaPreparer.timeline.getWindowCount() > 0
        ? mediaPreparer.timeline.getWindow(/* windowIndex= */ 0, window).manifest
        : null;
  }

  /**
   * Returns the number of periods for which media is available. Must not be called until after
   * preparation completes.
   */
  public int getPeriodCount() {
    if (mediaSource == null) {
      return 0;
    }
    assertPreparedWithMedia();
    return trackGroupArrays.length;
  }

  /**
   * Returns the track groups for the given period. Must not be called until after preparation
   * completes.
   *
   *
Use {@link #getMappedTrackInfo(int)} to get the track groups mapped to renderers.
   *
   * @param periodIndex The period index.
   * @return The track groups for the period. May be {@link TrackGroupArray#EMPTY} for single stream
   *     content.
   */
  public TrackGroupArray getTrackGroups(int periodIndex) {
    assertPreparedWithMedia();
    return trackGroupArrays[periodIndex];
  }

  /**
   * Returns the mapped track info for the given period. Must not be called until after preparation
   * completes.
   *
   * @param periodIndex The period index.
   * @return The {@link MappedTrackInfo} for the period.
   */
  public MappedTrackInfo getMappedTrackInfo(int periodIndex) {
    assertPreparedWithMedia();
    return mappedTrackInfos[periodIndex];
  }

  /**
   * Returns all {@link ExoTrackSelection track selections} for a period and renderer. Must not be
   * called until after preparation completes.
   *
   * @param periodIndex The period index.
   * @param rendererIndex The renderer index.
   * @return A list of selected {@link ExoTrackSelection track selections}.
   */
  public List<ExoTrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
    assertPreparedWithMedia();
    return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
  }

  /**
   * Clears the selection of tracks for a period. Must not be called until after preparation
   * completes.
   *
   * @param periodIndex The period index for which track selections are cleared.
   */
  public void clearTrackSelections(int periodIndex) {
    assertPreparedWithMedia();
    for (int i = 0; i < rendererCapabilities.length; i++) {
      trackSelectionsByPeriodAndRenderer[periodIndex][i].clear();
    }
  }

  /**
   * Replaces a selection of tracks to be downloaded. Must not be called until after preparation
   * completes.
   *
   * @param periodIndex The period index for which the track selection is replaced.
   * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
   *     selection of tracks.
   */
  public void replaceTrackSelections(
      int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
    clearTrackSelections(periodIndex);
    addTrackSelection(periodIndex, trackSelectorParameters);
  }

  /**
   * Adds a selection of tracks to be downloaded. Must not be called until after preparation
   * completes.
   *
   * @param periodIndex The period index this track selection is added for.
   * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
   *     selection of tracks.
   */
  public void addTrackSelection(
      int periodIndex, DefaultTrackSelector.Parameters trackSelectorParameters) {
    assertPreparedWithMedia();
    trackSelector.setParameters(trackSelectorParameters);
    runTrackSelection(periodIndex);
  }

  /**
   * Convenience method to add selections of tracks for all specified audio languages. If an audio
   * track in one of the specified languages is not available, the default fallback audio track is
   * used instead. Must not be called until after preparation completes.
   *
   * @param languages A list of audio languages for which tracks should be added to the download
   *     selection, as IETF BCP 47 conformant tags.
   */
  public void addAudioLanguagesToSelection(String... languages) {
    assertPreparedWithMedia();
    for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
      DefaultTrackSelector.ParametersBuilder parametersBuilder =
          DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
      MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
      int rendererCount = mappedTrackInfo.getRendererCount();
      for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
        if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_AUDIO) {
          parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
        }
      }
      for (String language : languages) {
        parametersBuilder.setPreferredAudioLanguage(language);
        addTrackSelection(periodIndex, parametersBuilder.build());
      }
    }
  }

  /**
   * Convenience method to add selections of tracks for all specified text languages. Must not be
   * called until after preparation completes.
   *
   * @param selectUndeterminedTextLanguage Whether a text track with undetermined language should be
   *     selected for downloading if no track with one of the specified {@code languages} is
   *     available.
   * @param languages A list of text languages for which tracks should be added to the download
   *     selection, as IETF BCP 47 conformant tags.
   */
  public void addTextLanguagesToSelection(
      boolean selectUndeterminedTextLanguage, String... languages) {
    assertPreparedWithMedia();
    for (int periodIndex = 0; periodIndex < mappedTrackInfos.length; periodIndex++) {
      DefaultTrackSelector.ParametersBuilder parametersBuilder =
          DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT.buildUpon();
      MappedTrackInfo mappedTrackInfo = mappedTrackInfos[periodIndex];
      int rendererCount = mappedTrackInfo.getRendererCount();
      for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
        if (mappedTrackInfo.getRendererType(rendererIndex) != C.TRACK_TYPE_TEXT) {
          parametersBuilder.setRendererDisabled(rendererIndex, /* disabled= */ true);
        }
      }
      parametersBuilder.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage);
      for (String language : languages) {
        parametersBuilder.setPreferredTextLanguage(language);
        addTrackSelection(periodIndex, parametersBuilder.build());
      }
    }
  }

  /**
   * Convenience method to add a selection of tracks to be downloaded for a single renderer. Must
   * not be called until after preparation completes.
   *
   * @param periodIndex The period index the track selection is added for.
   * @param rendererIndex The renderer index the track selection is added for.
   * @param trackSelectorParameters The {@link DefaultTrackSelector.Parameters} to obtain the new
   *     selection of tracks.
   * @param overrides A list of {@link SelectionOverride SelectionOverrides} to apply to the {@code
   *     trackSelectorParameters}. If empty, {@code trackSelectorParameters} are used as they are.
   */
  public void addTrackSelectionForSingleRenderer(
      int periodIndex,
      int rendererIndex,
      DefaultTrackSelector.Parameters trackSelectorParameters,
      List<SelectionOverride> overrides) {
    assertPreparedWithMedia();
    DefaultTrackSelector.ParametersBuilder builder = trackSelectorParameters.buildUpon();
    for (int i = 0; i < mappedTrackInfos[periodIndex].getRendererCount(); i++) {
      builder.setRendererDisabled(/* rendererIndex= */ i, /* disabled= */ i != rendererIndex);
    }
    if (overrides.isEmpty()) {
      addTrackSelection(periodIndex, builder.build());
    } else {
      TrackGroupArray trackGroupArray = mappedTrackInfos[periodIndex].getTrackGroups(rendererIndex);
      for (int i = 0; i < overrides.size(); i++) {
        builder.setSelectionOverride(rendererIndex, trackGroupArray, overrides.get(i));
        addTrackSelection(periodIndex, builder.build());
      }
    }
  }

  /**
   * Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
   * after preparation completes. The uri of the {@link DownloadRequest} will be used as content id.
   *
   * @param data Application provided data to store in {@link DownloadRequest#data}.
   * @return The built {@link DownloadRequest}.
   */
  public DownloadRequest getDownloadRequest(@Nullable byte[] data) {
    return getDownloadRequest(localConfiguration.uri.toString(), data);
  }

  /**
   * Builds a {@link DownloadRequest} for downloading the selected tracks. Must not be called until
   * after preparation completes.
   *
   * @param id The unique content id.
   * @param data Application provided data to store in {@link DownloadRequest#data}.
   * @return The built {@link DownloadRequest}.
   */
  public DownloadRequest getDownloadRequest(String id, @Nullable byte[] data) {
    DownloadRequest.Builder requestBuilder =
        new DownloadRequest.Builder(id, localConfiguration.uri)
            .setMimeType(localConfiguration.mimeType)
            .setKeySetId(
                localConfiguration.drmConfiguration != null
                    ? localConfiguration.drmConfiguration.getKeySetId()
                    : null)
            .setCustomCacheKey(localConfiguration.customCacheKey)
            .setData(data);
    if (mediaSource == null) {
      return requestBuilder.build();
    }
    assertPreparedWithMedia();
    List<StreamKey> streamKeys = new ArrayList<>();
    List<ExoTrackSelection> allSelections = new ArrayList<>();
    int periodCount = trackSelectionsByPeriodAndRenderer.length;
    for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
      allSelections.clear();
      int rendererCount = trackSelectionsByPeriodAndRenderer[periodIndex].length;
      for (int rendererIndex = 0; rendererIndex < rendererCount; rendererIndex++) {
        allSelections.addAll(trackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]);
      }
      streamKeys.addAll(mediaPreparer.mediaPeriods[periodIndex].getStreamKeys(allSelections));
    }
    return requestBuilder.setStreamKeys(streamKeys).build();
  }

  // Initialization of array of Lists.
  @SuppressWarnings("unchecked")
  private void onMediaPrepared() {
    checkNotNull(mediaPreparer);
    checkNotNull(mediaPreparer.mediaPeriods);
    checkNotNull(mediaPreparer.timeline);
    int periodCount = mediaPreparer.mediaPeriods.length;
    int rendererCount = rendererCapabilities.length;
    trackSelectionsByPeriodAndRenderer =
        (List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
    immutableTrackSelectionsByPeriodAndRenderer =
        (List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
    for (int i = 0; i < periodCount; i++) {
      for (int j = 0; j < rendererCount; j++) {
        trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>();
        immutableTrackSelectionsByPeriodAndRenderer[i][j] =
            Collections.unmodifiableList(trackSelectionsByPeriodAndRenderer[i][j]);
      }
    }
    trackGroupArrays = new TrackGroupArray[periodCount];
    mappedTrackInfos = new MappedTrackInfo[periodCount];
    for (int i = 0; i < periodCount; i++) {
      trackGroupArrays[i] = mediaPreparer.mediaPeriods[i].getTrackGroups();
      TrackSelectorResult trackSelectorResult = runTrackSelection(/* periodIndex= */ i);
      trackSelector.onSelectionActivated(trackSelectorResult.info);
      mappedTrackInfos[i] = checkNotNull(trackSelector.getCurrentMappedTrackInfo());
    }
    setPreparedWithMedia();
    checkNotNull(callbackHandler).post(() -> checkNotNull(callback).onPrepared(this));
  }

  private void onMediaPreparationFailed(IOException error) {
    checkNotNull(callbackHandler).post(() -> checkNotNull(callback).onPrepareError(this, error));
  }

  @RequiresNonNull({
    "trackGroupArrays",
    "mappedTrackInfos",
    "trackSelectionsByPeriodAndRenderer",
    "immutableTrackSelectionsByPeriodAndRenderer",
    "mediaPreparer",
    "mediaPreparer.timeline",
    "mediaPreparer.mediaPeriods"
  })
  private void setPreparedWithMedia() {
    isPreparedWithMedia = true;
  }

  @EnsuresNonNull({
    "trackGroupArrays",
    "mappedTrackInfos",
    "trackSelectionsByPeriodAndRenderer",
    "immutableTrackSelectionsByPeriodAndRenderer",
    "mediaPreparer",
    "mediaPreparer.timeline",
    "mediaPreparer.mediaPeriods"
  })
  @SuppressWarnings("nullness:contracts.postcondition")
  private void assertPreparedWithMedia() {
    Assertions.checkState(isPreparedWithMedia);
  }

  /**
   * Runs the track selection for a given period index with the current parameters. The selected
   * tracks will be added to {@link #trackSelectionsByPeriodAndRenderer}.
   */
  // Intentional reference comparison of track group instances.
  @SuppressWarnings("ReferenceEquality")
  @RequiresNonNull({
    "trackGroupArrays",
    "trackSelectionsByPeriodAndRenderer",
    "mediaPreparer",
    "mediaPreparer.timeline"
  })
  private TrackSelectorResult runTrackSelection(int periodIndex) {
    try {
      TrackSelectorResult trackSelectorResult =
          trackSelector.selectTracks(
              rendererCapabilities,
              trackGroupArrays[periodIndex],
              new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
              mediaPreparer.timeline);
      for (int i = 0; i < trackSelectorResult.length; i++) {
        @Nullable ExoTrackSelection newSelection = trackSelectorResult.selections[i];
        if (newSelection == null) {
          continue;
        }
        List<ExoTrackSelection> existingSelectionList =
            trackSelectionsByPeriodAndRenderer[periodIndex][i];
        boolean mergedWithExistingSelection = false;
        for (int j = 0; j < existingSelectionList.size(); j++) {
          ExoTrackSelection existingSelection = existingSelectionList.get(j);
          if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) {
            // Merge with existing selection.
            scratchSet.clear();
            for (int k = 0; k < existingSelection.length(); k++) {
              scratchSet.put(existingSelection.getIndexInTrackGroup(k), 0);
            }
            for (int k = 0; k < newSelection.length(); k++) {
              scratchSet.put(newSelection.getIndexInTrackGroup(k), 0);
            }
            int[] mergedTracks = new int[scratchSet.size()];
            for (int k = 0; k < scratchSet.size(); k++) {
              mergedTracks[k] = scratchSet.keyAt(k);
            }
            existingSelectionList.set(
                j, new DownloadTrackSelection(existingSelection.getTrackGroup(), mergedTracks));
            mergedWithExistingSelection = true;
            break;
          }
        }
        if (!mergedWithExistingSelection) {
          existingSelectionList.add(newSelection);
        }
      }
      return trackSelectorResult;
    } catch (ExoPlaybackException e) {
      // DefaultTrackSelector does not throw exceptions during track selection.
      throw new UnsupportedOperationException(e);
    }
  }

  private static MediaSource createMediaSourceInternal(
      MediaItem mediaItem,
      DataSource.Factory dataSourceFactory,
      @Nullable DrmSessionManager drmSessionManager) {
    return new DefaultMediaSourceFactory(dataSourceFactory, ExtractorsFactory.EMPTY)
        .setDrmSessionManager(drmSessionManager)
        .createMediaSource(mediaItem);
  }

  private static boolean isProgressive(MediaItem.LocalConfiguration localConfiguration) {
    return Util.inferContentTypeForUriAndMimeType(
            localConfiguration.uri, localConfiguration.mimeType)
        == C.TYPE_OTHER;
  }

  private static final class MediaPreparer
      implements MediaSourceCaller, MediaPeriod.Callback, Handler.Callback {

    private static final int MESSAGE_PREPARE_SOURCE = 0;
    private static final int MESSAGE_CHECK_FOR_FAILURE = 1;
    private static final int MESSAGE_CONTINUE_LOADING = 2;
    private static final int MESSAGE_RELEASE = 3;

    private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED = 0;
    private static final int DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED = 1;

    private final MediaSource mediaSource;
    private final DownloadHelper downloadHelper;
    private final Allocator allocator;
    private final ArrayList<MediaPeriod> pendingMediaPeriods;
    private final Handler downloadHelperHandler;
    private final HandlerThread mediaSourceThread;
    private final Handler mediaSourceHandler;

    public @MonotonicNonNull Timeline timeline;
    public MediaPeriod @MonotonicNonNull [] mediaPeriods;

    private boolean released;

    public MediaPreparer(MediaSource mediaSource, DownloadHelper downloadHelper) {
      this.mediaSource = mediaSource;
      this.downloadHelper = downloadHelper;
      allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE);
      pendingMediaPeriods = new ArrayList<>();
      @SuppressWarnings("nullness:methodref.receiver.bound")
      Handler downloadThreadHandler =
          Util.createHandlerForCurrentOrMainLooper(this::handleDownloadHelperCallbackMessage);
      this.downloadHelperHandler = downloadThreadHandler;
      mediaSourceThread = new HandlerThread("ExoPlayer:DownloadHelper");
      mediaSourceThread.start();
      mediaSourceHandler = Util.createHandler(mediaSourceThread.getLooper(), /* callback= */ this);
      mediaSourceHandler.sendEmptyMessage(MESSAGE_PREPARE_SOURCE);
    }

    public void release() {
      if (released) {
        return;
      }
      released = true;
      mediaSourceHandler.sendEmptyMessage(MESSAGE_RELEASE);
    }

    // Handler.Callback

    @Override
    public boolean handleMessage(Message msg) {
      switch (msg.what) {
        case MESSAGE_PREPARE_SOURCE:
          mediaSource.prepareSource(/* caller= */ this, /* mediaTransferListener= */ null);
          mediaSourceHandler.sendEmptyMessage(MESSAGE_CHECK_FOR_FAILURE);
          return true;
        case MESSAGE_CHECK_FOR_FAILURE:
          try {
            if (mediaPeriods == null) {
              mediaSource.maybeThrowSourceInfoRefreshError();
            } else {
              for (int i = 0; i < pendingMediaPeriods.size(); i++) {
                pendingMediaPeriods.get(i).maybeThrowPrepareError();
              }
            }
            mediaSourceHandler.sendEmptyMessageDelayed(
                MESSAGE_CHECK_FOR_FAILURE, /* delayMillis= */ 100);
          } catch (IOException e) {
            downloadHelperHandler
                .obtainMessage(DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED, /* obj= */ e)
                .sendToTarget();
          }
          return true;
        case MESSAGE_CONTINUE_LOADING:
          MediaPeriod mediaPeriod = (MediaPeriod) msg.obj;
          if (pendingMediaPeriods.contains(mediaPeriod)) {
            mediaPeriod.continueLoading(/* positionUs= */ 0);
          }
          return true;
        case MESSAGE_RELEASE:
          if (mediaPeriods != null) {
            for (MediaPeriod period : mediaPeriods) {
              mediaSource.releasePeriod(period);
            }
          }
          mediaSource.releaseSource(this);
          mediaSourceHandler.removeCallbacksAndMessages(null);
          mediaSourceThread.quit();
          return true;
        default:
          return false;
      }
    }

    // MediaSource.MediaSourceCaller implementation.

    @Override
    public void onSourceInfoRefreshed(MediaSource source, Timeline timeline) {
      if (this.timeline != null) {
        // Ignore dynamic updates.
        return;
      }
      if (timeline.getWindow(/* windowIndex= */ 0, new Timeline.Window()).isLive()) {
        downloadHelperHandler
            .obtainMessage(
                DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED,
                /* obj= */ new LiveContentUnsupportedException())
            .sendToTarget();
        return;
      }
      this.timeline = timeline;
      mediaPeriods = new MediaPeriod[timeline.getPeriodCount()];
      for (int i = 0; i < mediaPeriods.length; i++) {
        MediaPeriod mediaPeriod =
            mediaSource.createPeriod(
                new MediaPeriodId(timeline.getUidOfPeriod(/* periodIndex= */ i)),
                allocator,
                /* startPositionUs= */ 0);
        mediaPeriods[i] = mediaPeriod;
        pendingMediaPeriods.add(mediaPeriod);
      }
      for (MediaPeriod mediaPeriod : mediaPeriods) {
        mediaPeriod.prepare(/* callback= */ this, /* positionUs= */ 0);
      }
    }

    // MediaPeriod.Callback implementation.

    @Override
    public void onPrepared(MediaPeriod mediaPeriod) {
      pendingMediaPeriods.remove(mediaPeriod);
      if (pendingMediaPeriods.isEmpty()) {
        mediaSourceHandler.removeMessages(MESSAGE_CHECK_FOR_FAILURE);
        downloadHelperHandler.sendEmptyMessage(DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED);
      }
    }

    @Override
    public void onContinueLoadingRequested(MediaPeriod mediaPeriod) {
      if (pendingMediaPeriods.contains(mediaPeriod)) {
        mediaSourceHandler.obtainMessage(MESSAGE_CONTINUE_LOADING, mediaPeriod).sendToTarget();
      }
    }

    private boolean handleDownloadHelperCallbackMessage(Message msg) {
      if (released) {
        // Stale message.
        return false;
      }
      switch (msg.what) {
        case DOWNLOAD_HELPER_CALLBACK_MESSAGE_PREPARED:
          downloadHelper.onMediaPrepared();
          return true;
        case DOWNLOAD_HELPER_CALLBACK_MESSAGE_FAILED:
          release();
          downloadHelper.onMediaPreparationFailed((IOException) castNonNull(msg.obj));
          return true;
        default:
          return false;
      }
    }
  }

  private static final class DownloadTrackSelection extends BaseTrackSelection {

    private static final class Factory implements ExoTrackSelection.Factory {

      @Override
      public @NullableType ExoTrackSelection[] createTrackSelections(
          @NullableType Definition[] definitions,
          BandwidthMeter bandwidthMeter,
          MediaPeriodId mediaPeriodId,
          Timeline timeline) {
        @NullableType ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
        for (int i = 0; i < definitions.length; i++) {
          selections[i] =
              definitions[i] == null
                  ? null
                  : new DownloadTrackSelection(definitions[i].group, definitions[i].tracks);
        }
        return selections;
      }
    }

    public DownloadTrackSelection(TrackGroup trackGroup, int[] tracks) {
      super(trackGroup, tracks);
    }

    @Override
    public int getSelectedIndex() {
      return 0;
    }

    @Override
    @C.SelectionReason
    public int getSelectionReason() {
      return C.SELECTION_REASON_UNKNOWN;
    }

    @Override
    @Nullable
    public Object getSelectionData() {
      return null;
    }

    @Override
    public void updateSelectedTrack(
        long playbackPositionUs,
        long bufferedDurationUs,
        long availableDurationUs,
        List<? extends MediaChunk> queue,
        MediaChunkIterator[] mediaChunkIterators) {
      // Do nothing.
    }
  }

  private static final class FakeBandwidthMeter implements BandwidthMeter {

    @Override
    public long getBitrateEstimate() {
      return 0;
    }

    @Override
    @Nullable
    public TransferListener getTransferListener() {
      return null;
    }

    @Override
    public void addEventListener(Handler eventHandler, EventListener eventListener) {
      // Do nothing.
    }

    @Override
    public void removeEventListener(EventListener eventListener) {
      // Do nothing.
    }
  }
}


text:
using System;
using NUnit.Framework;
using FluentAssertions;
using Voltmeter.Tests.Helpers;

namespace Voltmeter.Model
{
    [TestFixture]
    public class ElectricReadingsTests
    {
        const double TOLERANCE = 0.000000000000001;

        static readonly DateTimeOffset T1 = DateTimeOffset.UtcNow;
        static readonly DateTimeOffset T2 = T1.AddSeconds(1);
        static readonly DateTimeOffset T3 = T2.AddSeconds(1);

        [Test]
        public void SetResistance_should_notify_property_change()
        {
            // Assign
            const double R = 4500, S = 2.1;

            var readings = new ElectricReadings(R, S);
            readings.MonitorEvents();

            // Act
            readings.SetResistance(R * 1.1, adjustValues: false);

            // Assert
            readings.ShouldRaisePropertyChangeFor(m => m.Resistance);
        }

        [Test]
        public void SetResistance_without_adjustment_should_updated_only_Resistance()
        {
            // Assign
            const double R = 1100, Rnew = 4500;

            var expectedReadings = new ElectricReadings(R);
            expectedReadings.AddSample(new DataSample(T1, 3.2));
            expectedReadings.AddSample(new DataSample(T2, 3.1));

            var testedReadings = new ElectricReadings(R);
            testedReadings.AddSample(new DataSample(T1, 3.2));
            testedReadings.AddSample(new DataSample(T2, 3.1));

            testedReadings.MonitorEvents();

            // Act
            testedReadings.SetResistance(Rnew, adjustValues: false);

            // Assert
            testedReadings.ShouldBeEquivalentTo(expectedReadings, options => options
                .Excluding(r => r.Resistance));

            testedReadings.Resistance
                .Should().Be(Rnew);
        }

        [Test]
        public void Initial_values_verification()
        {
            // Assign
            const double S = 2.2, R = 2000;

            // Act
            var readings = new ElectricReadings(R, S);

            // Assert
            readings.Voltage
                .Should().BeApproximately(0.0, TOLERANCE);
            readings.VoltageScale
                .Should().BeApproximately(S, TOLERANCE);
            readings.Resistance
                .Should().BeApproximately(R, TOLERANCE);
            readings.Current
                .Should().BeApproximately(0.0, TOLERANCE);
            readings.Power
                .Should().BeApproximately(0.0, TOLERANCE);
            readings.Energy
                .Should().BeApproximately(0.0, TOLERANCE);
            readings.TimeStamp
                .Should().Be(null);
        }

        [Test]
        public void Initial_values_and_Reset_cross_verification()
        {
            // Assign
            const double S = 2.2, R = 2000;

            var resetReadings = new ElectricReadings(R, S);
            resetReadings.AddSample(new DataSample(T1, 4.0));
            resetReadings.AddSample(new DataSample(T2, 3.0));

            // Act
            resetReadings.Reset();
            var initialReadings = new ElectricReadings(R, S);

            // Assert
            resetReadings.ShouldBeEquivalentTo(initialReadings);
        }

        [Test]
        public void Reset_should_notify_property_changes()
        {
            // Assign
            const double V = 3.2, S = 2.0, R = 1500;

            var readings = new ElectricReadings(R, S);
            readings.AddSample(new DataSample(T1, V + 0.4));
            readings.AddSample(new DataSample(T2, V + 0.5));

            readings.MonitorEvents();

            // Act
            readings.Reset();

            // Assert
            readings.ShouldNotRaisePropertyChangeFor(m => m.Resistance);
            readings.ShouldNotRaisePropertyChangeFor(m => m.VoltageScale);

            readings.ShouldRaisePropertyChangeFor(m => m.TimeStamp);
            readings.ShouldRaisePropertyChangeFor(m => m.Voltage);
            readings.ShouldRaisePropertyChangeFor(m => m.Current);
            readings.ShouldRaisePropertyChangeFor(m => m.Power);
            readings.ShouldRaisePropertyChangeFor(m => m.Energy);
        }

        [Test]
        public void SetResistance_readings_adjustment_cross_verification()
        {
            // Assign
            const double R1 = 1700,
                         R2 = 1875;

            const double S = 2.3;

            const double V1 = 3.5,
                         V2 = 3.6;

            var expectedReadings = new ElectricReadings(R1, S);
            expectedReadings.AddSample(new DataSample(T1, V1));
            expectedReadings.AddSample(new DataSample(T2, V2));

            var actualReadings = new ElectricReadings(R2, S);
            actualReadings.AddSample(new DataSample(T1, V1));
            actualReadings.AddSample(new DataSample(T2, V2));

            // Act
            actualReadings.SetResistance(R1, adjustValues: true);

            // Assert
            actualReadings.ShouldBeEquivalentTo(expectedReadings, options => options
                .Using(new DoubleAssertionRule(TOLERANCE)));
        }

        [Test]
        public void SetVoltageScale_should_notify_property_change()
        {
            // Assign
            const double R = 4500, S = 2.1;

            var readings = new ElectricReadings(R, S);
            readings.MonitorEvents();

            // Act
            readings.SetVoltageScale(S * 1.1, adjustValues: false);

            // Assert
            readings.ShouldRaisePropertyChangeFor(m => m.VoltageScale);
        }

        [Test]
        public void SetVoltageScale_readings_adjustment_cross_verification()
        {
            // Assign
            const double R = 1730;

            const double S1 = 2.0,
                         S2 = 10.0;

            const double U1 = 220,
                         U2 = 224;

            var electricReadings = new ElectricReadings(R, S1);
            electricReadings.AddSample(new DataSample(T1, U1 / S1));
            electricReadings.AddSample(new DataSample(T2, U2 / S1));

            var actualReadings = new ElectricReadings(R, S2);
            actualReadings.AddSample(new DataSample(T1, U1 / S1));
            actualReadings.AddSample(new DataSample(T2, U2 / S1));

            // Act
            actualReadings.SetVoltageScale(S1, adjustValues: true);

            // Assert
            actualReadings.ShouldBeEquivalentTo(electricReadings, options => options
                .Using(new DoubleAssertionRule(TOLERANCE)));
        }

        [Test]
        public void SetVoltageScale_without_adjustment_should_update_only_VoltageScale()
        {
            // Assign
            const double R = 1730;

            const double S1 = 3.0,
                         S2 = 10.0;

            const double V1 = 3.1,
                         V2 = 3.2;

            var expectedReadings = new ElectricReadings(R, S1);
            expectedReadings.AddSample(new DataSample(T1, V1));
            expectedReadings.AddSample(new DataSample(T2, V2));

            var actualReadings = new ElectricReadings(R, S1);
            actualReadings.AddSample(new DataSample(T1, V1));
            actualReadings.AddSample(new DataSample(T2, V2));

            // Act
            actualReadings.SetVoltageScale(S2, adjustValues: false);

            // Assert
            actualReadings.ShouldBeEquivalentTo(expectedReadings, options => options
                .Excluding(r => r.VoltageScale));

            actualReadings.VoltageScale
                .Should().Be(S2);
        }

        [Test]
        public void AddSample_on_sequence_calculations_verification()
        {
            // Assign
            const double R = 1700, S = 2.3;

            const double V1 = 2.9,
                         V2 = 3.1,
                         V3 = 3.2;

            const double U1 = V1 * S,
                         U2 = V2 * S,
                         U3 = V3 * S;

            const double I1 = U1 / R,
                         I2 = U2 / R,
                         I3 = U3 / R;

            const double P1 = U1 * I1,
                         P2 = U2 * I2,
                         P3 = U3 * I3;

            double E1 = 0.0,
                   E2 = E1 + (P1 + P2) / 2 * (T2 - T1).TotalSeconds,
                   E3 = E2 + (P2 + P3) / 2 * (T3 - T2).TotalSeconds;

            // Act
            var readings = new ElectricReadings(R, S);
            readings.AddSample(new DataSample(T1, V1));
            readings.AddSample(new DataSample(T2, V2));
            readings.AddSample(new DataSample(T3, V3));

            // Assert
            readings.Resistance
                .Should().BeApproximately(R, TOLERANCE);
            readings.Voltage
                .Should().BeApproximately(U3, TOLERANCE);
            readings.Current
                .Should().BeApproximately(I3, TOLERANCE);
            readings.Power
                .Should().BeApproximately(P3, TOLERANCE);
            readings.Energy
                .Should().BeApproximately(E3, TOLERANCE);
            readings.TimeStamp
                .Should().Be(T3);
        }

        [Test]
        public void AddSample_handles_voltage_as_zero_when_it_is_less_or_equal_than_noise_threshold()
        {
            // Assign
            const double R = 1700, S = 2.3;

            const double noiseThreshold = 1.5;

            const double V1 = 1.1 * noiseThreshold,
                         V2 = 1.0 * noiseThreshold,
                         V3 = 0.9 * noiseThreshold;

            var expectedReadings = new ElectricReadings(R, S) { NoiseThreshold = 0.0 };
            expectedReadings.AddSample(new DataSample(T1, V1));
            expectedReadings.AddSample(new DataSample(T2, 0.0));
            expectedReadings.AddSample(new DataSample(T3, 0.0));

            // Act
            var testedReadings = new ElectricReadings(R, S) { NoiseThreshold = noiseThreshold };
            testedReadings.AddSample(new DataSample(T1, V1));
            testedReadings.AddSample(new DataSample(T2, V2));
            testedReadings.AddSample(new DataSample(T3, V3));

            // Assert
            testedReadings.ShouldBeEquivalentTo(expectedReadings, options => options
                .Excluding(r => r.NoiseThreshold));
        }
    }
}

redpajama_set_name: RedPajamaGithub


text:
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.facebook.presto.verifier.framework;

public enum QueryState
{
    SUCCEEDED,
    FAILED,
    TIMED_OUT,
    FAILED_TO_SETUP,
    NOT_RUN
}


text:
from __future__ import print_function

"""N-dimensional interpolation on data hypercubes.
"""

__author__ = "Robert Nikutta <robert.nikutta@gmail.com>"
__version__ = '20180216' #yyyymmdd

#TODO: update doc strings

import numpy as np
import warnings
from scipy import interpolate, ndimage
import itertools
from copy import copy

# Convert RuntimeWarnings, e.g. division by zero in some array elements, to Exceptions
warnings.simplefilter('error', RuntimeWarning)

class NdimInterpolation:

    """N-dimensional interpolation on data hypercubes.

    Operates on image(index) coordinates. Multi-linear (default) or
    cubic-spline (currently deactivated; needs more testing).
    """

    def __init__(self,data,theta,order=1,mode='log'):

        """Initialize an interpolator object.

        Parameters
        ----------
        data : n-dim array or 1-d array
            Datacube to be interpolated. Sampled on a rectilinear grid
            (it need not be regular!). 'data' is either an
            n-dimensional array (hypercube), or a 1-dimensional
            array. If hypercube, each axis corresponds to one of the
            model parameters, and the index location along each axis
            grows with the parameter value (the parameter values are
            given in `theta`). If 'data' is a 1-d array of values, it
            will be converted into the hypercube format. This means
            that the order of entries in the 1-d array must be as if
            constructed via looping over all axes, i.e.

            .. code:: python

                counter = 0
                for j0 in theta[0]:
                    for j1 in theta[1]:
                        for j2 in theta[2]:
                            ...
                            hypercube[j0,j1,j2,...] = onedarray[counter]
                            counter += 1

        theta : list
            List of lists, each holding in ascending order the unique
            values for one of the axes in `data` hypercube. Example:
            for the CLUMPY models of AGN tori (Nenkova et al. 2008)

              theta = [{i}, {tv}, {q}, {N0}, {sig}, {Y}, {wave}]

            where the {.} are 1-d arrays of unique model parameter
            values, e.g.

              {i} = array([0,10,20,30,40,50,60,70,80,90]) (degrees).

        order : int
            Order of interpolation spline to be used. ``order=1``
            (default) is multi-linear interpolation, ``order=3`` is
            cubic-spline (quite a bit slower, and not necessarily
            better, especially for complicated n-dim
            functions. ``order=1`` is recommended.

        mode : str
            ``log`` is default, and will take log10(data) first, which
            severely improves the interpolation accuracy if the data
            span many orders of magnitude. This is of course only
            applicable if all entries in `data` are greater than
            0. Any string other that ``log`` will keep `data` as-is.

        Returns
        -------
        NdimInterpolation instance.

        Example
        -------
        General way to use ndiminterpolation

        .. code:: python

            # to be written

        """

        self.theta = copy(theta) # list of lists of parameter values, unique, in correct order

        if not isinstance(self.theta,(list,tuple)):
            self.theta = [self.theta]

        shape_ = tuple([len(t) for t in self.theta])

        # determine if data is hypercube or list of 1d arrays
        if shape_ == data.shape:
            self.data_hypercube = data
        else:
            raise Exception("'theta' not compatible with the shape of 'data'.")

        # interpolation orders
        if order in (1,3):
            self.order = order
        else:
            raise Exception("Interpolation spline order not supported! Must be 1 (linear) or 3 (cubic).")

        # interpolate in log10 space?
        self.mode = mode

        # take log10 of 'data' ('y' values)
        if self.mode in ('log','loglog'):
            try:
                self.data_hypercube = np.log10(self.data_hypercube)
            except RuntimeWarning:
                raise Exception("For mode='log' all entries in 'data' must be > 0.")

        # take log10 of 'theta' ('x' values)
        if self.mode == 'loglog':
            for jt,t in enumerate(self.theta):
                try:
                    self.theta[jt] = np.log10(t)
                except:
                    raise # Exception

        # set up n 1-d linear interpolators for all n parameters in theta
        self.ips = [] # list of 1-d interpolator objects
        for t in self.theta:
            self.ips.append(interpolate.interp1d(t,np.linspace(0.,float(t.size-1.),t.size)))

        if self.order == 3:
            print("Evaluating cubic spline coefficients for subsequent use, please wait...")
            self.coeffs = ndimage.spline_filter(self.data_hypercube,order=3)
            print("Done.")

    def get_coords(self,vec):

        """Construct a full 2D matrix of coordinates in pixel-space from a
           vector of coordinates in real space.

        Parameters
        ----------
        vec : tuple

            Tuple of lenght len(self.theta), with each element either
            a single value of theta_i (of the i-th parameter), or
            itself a tuple (of arbitrary length). If an element of vec
            is itself a tuple (of length m), then m*(n-1) combinations
            of all parameters will be added to the resulting 2D matrix
            of coordinates.

        Returns
        -------
        coorinates_pix : 2D array
            2D array of coordinates in pixel space, on which then the
            multi-dim interpolation can be performed.

            Overall, A = np.prod([len(vec_i) for vec_i in vec])
            coordinate sets, for B = len(self.theta) parameters, will
            be generated, i.e. the returned coordinate matrix has
            coordinates.shape = (A,B).

        shape_ : tuple
           The shape tuple to reshape coords_pix with to obtain a
           properly shaped interpolated array.

        Example
        -------
        .. code:: python

            self.parameters
                array(['a', 'b', 'c'])

            [t.size for t in self.theta]
                (3,5,2)

            self.theta
                [array(1.,2.,3.,), array(10.,15,18,24,26), array(100.,126)]

            # vector of real-space coordinate to interpolate self.data_hypercube on
            vec = (1.5,18.,110.)

            # compute pixel-space vector matrix, and shape of resulting array
            coords_pix, shape_ = self.get_coords(vec)

        Old example, rework it:

        .. code:: python

            vec = (0,1,2,3,(0,1,2),(3,4,5),6)
            vectup = [e if isinstance(e,tuple) else (e,) for e in vec]
               [(0,), (1,), (2,), (3,), (0, 1, 2), (3, 4, 5), (6,)]
            coords = np.array([e for e in itertools.product(*vectup)])  # in pixel space
               array([[0, 1, 2, 3, 0, 3, 6],
                      [0, 1, 2, 3, 0, 4, 6],
                      [0, 1, 2, 3, 0, 5, 6],
                      [0, 1, 2, 3, 1, 3, 6],
                      [0, 1, 2, 3, 1, 4, 6],
                      [0, 1, 2, 3, 1, 5, 6],
                      [0, 1, 2, 3, 2, 3, 6],
                      [0, 1, 2, 3, 2, 4, 6],
                      [0, 1, 2, 3, 2, 5, 6]])

        """

        vectup = [e if isinstance(e,tuple) else (e,) for e in vec] # make tuple of vectors

        shape_ = [len(e) for e in vectup] # tuple shape

        # create a fleshed-out mesh of (multi-dim) locations to interpolate `data` at
        coords_real = np.array([e for e in itertools.product(*vectup)])

        columns = coords_real.T.tolist() # transpose

        # convert physical coordinate values to (fractional) pixel-space
        coords_pix = np.array([ self.ips[j](columns[j]) for j in range(len(columns)) ])

        return coords_pix, shape_

    def serialize_vector(self,vector):

        vec = list(vector)

        # sub-vectors can be arrays or lists; convert to tuples
        for j,v in enumerate(vec):
            if isinstance(v,(list,tuple)):
                vec[j] = tuple(v)
            elif isinstance(v,np.ndarray):
                vec[j] = tuple(v.squeeze()) # to allow for 1-d arrays embedded in higher-dims
            else:
                vec[j] = v

        vec = tuple(vec)

        return vec

    def __call__(self,vector):
        """Interpolate in N dimensions, using mapping to image coordinates."""

        if not isinstance(vector,(list,tuple)):
            vector = [vector]

        if self.mode == 'loglog':
            vector = [np.log10(e) for e in vector]

        vec = self.serialize_vector(vector)

        if self.order == 1:
            coords, shape_ = self.get_coords(vec)
            aux = ndimage.map_coordinates(self.data_hypercube,coords,order=1)
            aux = aux.reshape(shape_)
# temporarily disabled order==3, b/c not yet tested
#        elif self.order == 3:
#            aux = ndimage.map_coordinates(self.coeffs,self.get_coords(vector,pivots=pivots),order=3,prefilter=False)

        aux = aux.squeeze() # remove superflous length-one dimensions from result array

        if self.mode in  ('log','loglog'):
            aux = 10.**aux

        return aux