content: /*
   * Copyright (C) 2018 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.google.android.exoplayer2.extractor.mp3;
  
  import android.util.Pair;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.extractor.SeekPoint;
  import com.google.android.exoplayer2.metadata.id3.MlltFrame;
  import com.google.android.exoplayer2.util.Util;
  
  /** MP3 seeker that uses metadata from an {@link MlltFrame}. */
  /* package */ final class MlltSeeker implements Seeker {
  
    /**
     * Returns an {@link MlltSeeker} for seeking in the stream.
     *
     * @param firstFramePosition The position of the start of the first frame in the stream.
     * @param mlltFrame The MLLT frame with seeking metadata.
     * @param durationUs The stream duration in microseconds, or {@link C#TIME_UNSET} if it is
     *     unknown.
     * @return An {@link MlltSeeker} for seeking in the stream.
     */
    public static MlltSeeker create(long firstFramePosition, MlltFrame mlltFrame, long durationUs) {
      int referenceCount = mlltFrame.bytesDeviations.length;
      long[] referencePositions = new long[1 + referenceCount];
      long[] referenceTimesMs = new long[1 + referenceCount];
      referencePositions[0] = firstFramePosition;
      referenceTimesMs[0] = 0;
      long position = firstFramePosition;
      long timeMs = 0;
      for (int i = 1; i <= referenceCount; i++) {
        position += mlltFrame.bytesBetweenReference + mlltFrame.bytesDeviations[i - 1];
        timeMs += mlltFrame.millisecondsBetweenReference + mlltFrame.millisecondsDeviations[i - 1];
        referencePositions[i] = position;
        referenceTimesMs[i] = timeMs;
      }
      return new MlltSeeker(referencePositions, referenceTimesMs, durationUs);
    }
  
    private final long[] referencePositions;
    private final long[] referenceTimesMs;
    private final long durationUs;
  
    private MlltSeeker(long[] referencePositions, long[] referenceTimesMs, long durationUs) {
      this.referencePositions = referencePositions;
      this.referenceTimesMs = referenceTimesMs;
      // Use the last reference point as the duration if it is unknown, as extrapolating variable
      // bitrate at the end of the stream may give a large error.
      this.durationUs =
          durationUs != C.TIME_UNSET
              ? durationUs
              : Util.msToUs(referenceTimesMs[referenceTimesMs.length - 1]);
    }
  
    @Override
    public boolean isSeekable() {
      return true;
    }
  
    @Override
    public SeekPoints getSeekPoints(long timeUs) {
      timeUs = Util.constrainValue(timeUs, 0, durationUs);
      Pair<Long, Long> timeMsAndPosition =
          linearlyInterpolate(Util.usToMs(timeUs), referenceTimesMs, referencePositions);
      timeUs = Util.msToUs(timeMsAndPosition.first);
      long position = timeMsAndPosition.second;
      return new SeekPoints(new SeekPoint(timeUs, position));
    }
  
    @Override
    public long getTimeUs(long position) {
      Pair<Long, Long> positionAndTimeMs =
          linearlyInterpolate(position, referencePositions, referenceTimesMs);
      return Util.msToUs(positionAndTimeMs.second);
    }
  
    @Override
    public long getDurationUs() {
      return durationUs;
    }
  
    /**
     * Given a set of reference points as coordinates in {@code xReferences} and {@code yReferences}
     * and an x-axis value, linearly interpolates between corresponding reference points to give a
     * y-axis value.
     *
     * @param x The x-axis value for which a y-axis value is needed.
     * @param xReferences x coordinates of reference points.
     * @param yReferences y coordinates of reference points.
     * @return The linearly interpolated y-axis value.
     */
    private static Pair<Long, Long> linearlyInterpolate(
        long x, long[] xReferences, long[] yReferences) {
      int previousReferenceIndex =
          Util.binarySearchFloor(xReferences, x, /* inclusive= */ true, /* stayInBounds= */ true);
      long xPreviousReference = xReferences[previousReferenceIndex];
      long yPreviousReference = yReferences[previousReferenceIndex];
      int nextReferenceIndex = previousReferenceIndex + 1;
      if (nextReferenceIndex == xReferences.length) {
        return Pair.create(xPreviousReference, yPreviousReference);
      } else {
        long xNextReference = xReferences[nextReferenceIndex];
        long yNextReference = yReferences[nextReferenceIndex];
        double proportion =
            xNextReference == xPreviousReference
                ? 0.0
                : ((double) x - xPreviousReference) / (xNextReference - xPreviousReference);
        long y = (long) (proportion * (yNextReference - yPreviousReference)) + yPreviousReference;
        return Pair.create(x, y);
      }
    }
  
    @Override
    public long getDataEndPosition() {
      return C.POSITION_UNSET;
    }
  }
  