content: /*
   * Copyright (C) 2020 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 androidx.annotation.VisibleForTesting;
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.extractor.SeekPoint;
  import com.google.android.exoplayer2.util.LongArray;
  import com.google.android.exoplayer2.util.Util;
  
  /** MP3 seeker that builds a time-to-byte mapping as the stream is read. */
  /* package */ final class IndexSeeker implements Seeker {
  
    @VisibleForTesting
    /* package */ static final long MIN_TIME_BETWEEN_POINTS_US = C.MICROS_PER_SECOND / 10;
  
    private final long dataEndPosition;
    private final LongArray timesUs;
    private final LongArray positions;
  
    private long durationUs;
  
    public IndexSeeker(long durationUs, long dataStartPosition, long dataEndPosition) {
      this.durationUs = durationUs;
      this.dataEndPosition = dataEndPosition;
      timesUs = new LongArray();
      positions = new LongArray();
      timesUs.add(0L);
      positions.add(dataStartPosition);
    }
  
    @Override
    public long getTimeUs(long position) {
      int targetIndex =
          Util.binarySearchFloor(
              positions, position, /* inclusive= */ true, /* stayInBounds= */ true);
      return timesUs.get(targetIndex);
    }
  
    @Override
    public long getDataEndPosition() {
      return dataEndPosition;
    }
  
    @Override
    public boolean isSeekable() {
      return true;
    }
  
    @Override
    public long getDurationUs() {
      return durationUs;
    }
  
    @Override
    public SeekPoints getSeekPoints(long timeUs) {
      int targetIndex =
          Util.binarySearchFloor(timesUs, timeUs, /* inclusive= */ true, /* stayInBounds= */ true);
      SeekPoint seekPoint = new SeekPoint(timesUs.get(targetIndex), positions.get(targetIndex));
      if (seekPoint.timeUs == timeUs || targetIndex == timesUs.size() - 1) {
        return new SeekPoints(seekPoint);
      } else {
        SeekPoint nextSeekPoint =
            new SeekPoint(timesUs.get(targetIndex + 1), positions.get(targetIndex + 1));
        return new SeekPoints(seekPoint, nextSeekPoint);
      }
    }
  
    /**
     * Adds a seek point to the index if it is sufficiently distant from the other points.
     *
     * <p>Seek points must be added in order.
     *
     * @param timeUs The time corresponding to the seek point to add in microseconds.
     * @param position The position corresponding to the seek point to add in bytes.
     */
    public void maybeAddSeekPoint(long timeUs, long position) {
      if (isTimeUsInIndex(timeUs)) {
        return;
      }
      timesUs.add(timeUs);
      positions.add(position);
    }
  
    /**
     * Returns whether {@code timeUs} (in microseconds) is included in the index.
     *
     * <p>A point is included in the index if it is equal to another point, between 2 points, or
     * sufficiently close to the last point.
     */
    public boolean isTimeUsInIndex(long timeUs) {
      long lastIndexedTimeUs = timesUs.get(timesUs.size() - 1);
      return timeUs - lastIndexedTimeUs < MIN_TIME_BETWEEN_POINTS_US;
    }
  
    /* package */ void setDurationUs(long durationUs) {
      this.durationUs = durationUs;
    }
  }
  