gt:     int bytesRead = (int) (offset - extrasHolder.offset);
context: /*
   * Copyright (C) 2014 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 org.telegram.messenger.exoplayer.extractor;
  
  import org.telegram.messenger.exoplayer.C;
  import org.telegram.messenger.exoplayer.SampleHolder;
  import org.telegram.messenger.exoplayer.upstream.Allocation;
  import org.telegram.messenger.exoplayer.upstream.Allocator;
  import org.telegram.messenger.exoplayer.upstream.DataSource;
  import org.telegram.messenger.exoplayer.util.Assertions;
  import org.telegram.messenger.exoplayer.util.ParsableByteArray;
  import java.io.EOFException;
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.util.concurrent.LinkedBlockingDeque;
  
  /**
   * A rolling buffer of sample data and corresponding sample information.
   */
  /* package */ final class RollingSampleBuffer {
  
    private static final int INITIAL_SCRATCH_SIZE = 32;
  
    private final Allocator allocator;
    private final int allocationLength;
  
    private final InfoQueue infoQueue;
    private final LinkedBlockingDeque<Allocation> dataQueue;
    private final SampleExtrasHolder extrasHolder;
    private final ParsableByteArray scratch;
  
    // Accessed only by the consuming thread.
    private long totalBytesDropped;
  
    // Accessed only by the loading thread.
    private long totalBytesWritten;
    private Allocation lastAllocation;
    private int lastAllocationOffset;
  
    /**
     * @param allocator An {@link Allocator} from which allocations for sample data can be obtained.
     */
    public RollingSampleBuffer(Allocator allocator) {
      this.allocator = allocator;
      allocationLength = allocator.getIndividualAllocationLength();
      infoQueue = new InfoQueue();
      dataQueue = new LinkedBlockingDeque<>();
      extrasHolder = new SampleExtrasHolder();
      scratch = new ParsableByteArray(INITIAL_SCRATCH_SIZE);
      lastAllocationOffset = allocationLength;
    }
  
    // Called by the consuming thread, but only when there is no loading thread.
  
    /**
     * Clears the buffer, returning all allocations to the allocator.
     */
    public void clear() {
      infoQueue.clear();
  
      allocator.release(dataQueue.toArray(new Allocation[dataQueue.size()]));
      dataQueue.clear();
  
      totalBytesDropped = 0;
      totalBytesWritten = 0;
      lastAllocation = null;
      lastAllocationOffset = allocationLength;
    }
  
    /**
     * Returns the current absolute write index.
     */
    public int getWriteIndex() {
      return infoQueue.getWriteIndex();
    }
  
    /**
     * Discards samples from the write side of the buffer.
     *
     * @param discardFromIndex The absolute index of the first sample to be discarded.
     */
    public void discardUpstreamSamples(int discardFromIndex) {
      totalBytesWritten = infoQueue.discardUpstreamSamples(discardFromIndex);
      dropUpstreamFrom(totalBytesWritten);
    }
  
    /**
     * Discards data from the write side of the buffer. Data is discarded from the specified absolute
     * position. Any allocations that are fully discarded are returned to the allocator.
     *
     * @param absolutePosition The absolute position (inclusive) from which to discard data.
     */
    private void dropUpstreamFrom(long absolutePosition) {
      int relativePosition = (int) (absolutePosition - totalBytesDropped);
      // Calculate the index of the allocation containing the position, and the offset within it.
      int allocationIndex = relativePosition / allocationLength;
      int allocationOffset = relativePosition % allocationLength;
      // We want to discard any allocations after the one at allocationIdnex.
      int allocationDiscardCount = dataQueue.size() - allocationIndex - 1;
      if (allocationOffset == 0) {
        // If the allocation at allocationIndex is empty, we should discard that one too.
        allocationDiscardCount++;
      }
      // Discard the allocations.
      for (int i = 0; i < allocationDiscardCount; i++) {
        allocator.release(dataQueue.removeLast());
      }
      // Update lastAllocation and lastAllocationOffset to reflect the new position.
      lastAllocation = dataQueue.peekLast();
      lastAllocationOffset = allocationOffset == 0 ? allocationLength : allocationOffset;
    }
  
    // Called by the consuming thread.
  
    /**
     * Returns the current absolute read index.
     */
    public int getReadIndex() {
      return infoQueue.getReadIndex();
    }
  
    /**
     * Fills {@code holder} with information about the current sample, but does not write its data.
     * <p>
     * The fields set are {@link SampleHolder#size}, {@link SampleHolder#timeUs} and
     * {@link SampleHolder#flags}.
     *
     * @param holder The holder into which the current sample information should be written.
     * @return True if the holder was filled. False if there is no current sample.
     */
    public boolean peekSample(SampleHolder holder) {
      return infoQueue.peekSample(holder, extrasHolder);
    }
  
    /**
     * Skips the current sample.
     */
    public void skipSample() {
      long nextOffset = infoQueue.moveToNextSample();
      dropDownstreamTo(nextOffset);
    }
  
    /**
     * Attempts to skip to the keyframe before the specified time, if it's present in the buffer.
     *
     * @param timeUs The seek time.
     * @return True if the skip was successful. False otherwise.
     */
    public boolean skipToKeyframeBefore(long timeUs) {
      long nextOffset = infoQueue.skipToKeyframeBefore(timeUs);
      if (nextOffset == -1) {
        return false;
      }
      dropDownstreamTo(nextOffset);
      return true;
    }
  
    /**
     * Reads the current sample, advancing the read index to the next sample.
     *
     * @param sampleHolder The holder into which the current sample should be written.
     * @return True if a sample was read. False if there is no current sample.
     */
    public boolean readSample(SampleHolder sampleHolder) {
      // Write the sample information into the holder and extrasHolder.
      boolean haveSample = infoQueue.peekSample(sampleHolder, extrasHolder);
      if (!haveSample) {
        return false;
      }
  
      // Read encryption data if the sample is encrypted.
      if (sampleHolder.isEncrypted()) {
        readEncryptionData(sampleHolder, extrasHolder);
      }
      // Write the sample data into the holder.
      sampleHolder.ensureSpaceForWrite(sampleHolder.size);
      readData(extrasHolder.offset, sampleHolder.data, sampleHolder.size);
      // Advance the read head.
      long nextOffset = infoQueue.moveToNextSample();
      dropDownstreamTo(nextOffset);
      return true;
    }
  
    /**
     * Reads encryption data for the current sample.
     * <p>
     * The encryption data is written into {@code sampleHolder.cryptoInfo}, and
     * {@code sampleHolder.size} is adjusted to subtract the number of bytes that were read. The
     * same value is added to {@code extrasHolder.offset}.
     *
     * @param sampleHolder The holder into which the encryption data should be written.
     * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
     */
    private void readEncryptionData(SampleHolder sampleHolder, SampleExtrasHolder extrasHolder) {
      long offset = extrasHolder.offset;
  
      // Read the signal byte.
      readData(offset, scratch.data, 1);
      offset++;
      byte signalByte = scratch.data[0];
      boolean subsampleEncryption = (signalByte & 0x80) != 0;
      int ivSize = signalByte & 0x7F;
  
      // Read the initialization vector.
      if (sampleHolder.cryptoInfo.iv == null) {
        sampleHolder.cryptoInfo.iv = new byte[16];
      }
      readData(offset, sampleHolder.cryptoInfo.iv, ivSize);
      offset += ivSize;
  
      // Read the subsample count, if present.
      int subsampleCount;
      if (subsampleEncryption) {
        readData(offset, scratch.data, 2);
        offset += 2;
        scratch.setPosition(0);
        subsampleCount = scratch.readUnsignedShort();
      } else {
        subsampleCount = 1;
      }
  
      // Write the clear and encrypted subsample sizes.
      int[] clearDataSizes = sampleHolder.cryptoInfo.numBytesOfClearData;
      if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
        clearDataSizes = new int[subsampleCount];
      }
      int[] encryptedDataSizes = sampleHolder.cryptoInfo.numBytesOfEncryptedData;
      if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
        encryptedDataSizes = new int[subsampleCount];
      }
      if (subsampleEncryption) {
        int subsampleDataLength = 6 * subsampleCount;
        ensureCapacity(scratch, subsampleDataLength);
        readData(offset, scratch.data, subsampleDataLength);
        offset += subsampleDataLength;
        scratch.setPosition(0);
        for (int i = 0; i < subsampleCount; i++) {
          clearDataSizes[i] = scratch.readUnsignedShort();
          encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
        }
      } else {
        clearDataSizes[0] = 0;
        encryptedDataSizes[0] = sampleHolder.size - (int) (offset - extrasHolder.offset);
      }
  
      // Populate the cryptoInfo.
      sampleHolder.cryptoInfo.set(subsampleCount, clearDataSizes, encryptedDataSizes,
          extrasHolder.encryptionKeyId, sampleHolder.cryptoInfo.iv, C.CRYPTO_MODE_AES_CTR);
  
      // Adjust the offset and size to take into account the bytes read.
  