content: /*
   * 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 com.google.android.exoplayer2.extractor.ogg;
  
  import com.google.android.exoplayer2.C;
  import com.google.android.exoplayer2.extractor.ExtractorInput;
  import com.google.android.exoplayer2.util.Assertions;
  import com.google.android.exoplayer2.util.ParsableByteArray;
  import java.io.IOException;
  import java.util.Arrays;
  
  /**
   * OGG packet class.
   */
  /* package */ final class OggPacket {
  
    private final OggPageHeader pageHeader = new OggPageHeader();
    private final ParsableByteArray packetArray = new ParsableByteArray(
        new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
  
    private int currentSegmentIndex = C.INDEX_UNSET;
    private int segmentCount;
    private boolean populated;
  
    /**
     * Resets this reader.
     */
    public void reset() {
      pageHeader.reset();
      packetArray.reset();
      currentSegmentIndex = C.INDEX_UNSET;
      populated = false;
    }
  
    /**
     * Reads the next packet of the ogg stream. In case of an {@code IOException} the caller must make
     * sure to pass the same instance of {@code ParsableByteArray} to this method again so this reader
     * can resume properly from an error while reading a continued packet spanned across multiple
     * pages.
     *
     * @param input the {@link ExtractorInput} to read data from.
     * @return {@code true} if the read was successful. {@code false} if the end of the input was
     *    encountered having read no data.
     * @throws IOException thrown if reading from the input fails.
     * @throws InterruptedException thrown if interrupted while reading from input.
     */
    public boolean populate(ExtractorInput input) throws IOException, InterruptedException {
      Assertions.checkState(input != null);
  
      if (populated) {
        populated = false;
        packetArray.reset();
      }
  
      while (!populated) {
        if (currentSegmentIndex < 0) {
          // We're at the start of a page.
          if (!pageHeader.populate(input, true)) {
            return false;
          }
          int segmentIndex = 0;
          int bytesToSkip = pageHeader.headerSize;
          if ((pageHeader.type & 0x01) == 0x01 && packetArray.limit() == 0) {
            // After seeking, the first packet may be the remainder
            // part of a continued packet which has to be discarded.
            bytesToSkip += calculatePacketSize(segmentIndex);
            segmentIndex += segmentCount;
          }
          input.skipFully(bytesToSkip);
          currentSegmentIndex = segmentIndex;
        }
  
        int size = calculatePacketSize(currentSegmentIndex);
        int segmentIndex = currentSegmentIndex + segmentCount;
        if (size > 0) {
          if (packetArray.capacity() < packetArray.limit() + size) {
            packetArray.data = Arrays.copyOf(packetArray.data, packetArray.limit() + size);
          }
          input.readFully(packetArray.data, packetArray.limit(), size);
          packetArray.setLimit(packetArray.limit() + size);
          populated = pageHeader.laces[segmentIndex - 1] != 255;
        }
        // Advance now since we are sure reading didn't throw an exception.
        currentSegmentIndex = segmentIndex == pageHeader.pageSegmentCount ? C.INDEX_UNSET
            : segmentIndex;
      }
      return true;
    }
  
    /**
     * An OGG Packet may span multiple pages. Returns the {@link OggPageHeader} of the last page read,
     * or an empty header if the packet has yet to be populated.
     * <p>
     * Note that the returned {@link OggPageHeader} is mutable and may be updated during subsequent
     * calls to {@link #populate(ExtractorInput)}.
     *
     * @return the {@code PageHeader} of the last page read or an empty header if the packet has yet
     *     to be populated.
     */
    //@VisibleForTesting
    public OggPageHeader getPageHeader() {
      return pageHeader;
    }
  
    /**
     * Returns a {@link ParsableByteArray} containing the packet's payload.
     */
    public ParsableByteArray getPayload() {
      return packetArray;
    }
  
    /**
     * Trims the packet data array.
     */
    public void trimPayload() {
      if (packetArray.data.length == OggPageHeader.MAX_PAGE_PAYLOAD) {
        return;
      }
      packetArray.data = Arrays.copyOf(packetArray.data, Math.max(OggPageHeader.MAX_PAGE_PAYLOAD,
          packetArray.limit()));
    }
  
    /**
     * Calculates the size of the packet starting from {@code startSegmentIndex}.
     *
     * @param startSegmentIndex the index of the first segment of the packet.
     * @return Size of the packet.
     */
    private int calculatePacketSize(int startSegmentIndex) {
      segmentCount = 0;
      int size = 0;
      while (startSegmentIndex + segmentCount < pageHeader.pageSegmentCount) {
        int segmentLength = pageHeader.laces[startSegmentIndex + segmentCount++];
        size += segmentLength;
        if (segmentLength != 255) {
          // packets end at first lace < 255
          break;
        }
      }
      return size;
    }
  
  }
  