@Override
  public int readData(
      int track, long positionUs, MediaFormatHolder formatHolder, SampleHolder sampleHolder) {
    Assertions.checkState(state == STATE_ENABLED);
    downstreamPositionUs = positionUs;

    if (pendingDiscontinuity || isPendingReset()) {
      return NOTHING_READ;
    }

    boolean haveSamples = !sampleQueue.isEmpty();
    BaseMediaChunk currentChunk = mediaChunks.getFirst();
    while (haveSamples
        && mediaChunks.size() > 1
        && mediaChunks.get(1).getFirstSampleIndex() <= sampleQueue.getReadIndex()) {
      mediaChunks.removeFirst();
      currentChunk = mediaChunks.getFirst();
    }

    Format format = currentChunk.format;
    if (!format.equals(downstreamFormat)) {
      notifyDownstreamFormatChanged(format, currentChunk.trigger, currentChunk.startTimeUs);
    }
    downstreamFormat = format;

    if (haveSamples || currentChunk.isMediaFormatFinal) {
      MediaFormat mediaFormat = currentChunk.getMediaFormat();
      if (!mediaFormat.equals(downstreamMediaFormat)) {
        formatHolder.format = mediaFormat;
        formatHolder.drmInitData = currentChunk.getDrmInitData();
        downstreamMediaFormat = mediaFormat;
        return FORMAT_READ;
      }
      // If mediaFormat and downstreamMediaFormat are equal but different objects then the equality
      // check above will have been expensive, comparing the fields in each format. We update
      // downstreamMediaFormat here so that referential equality can be cheaply established during
      // subsequent calls.
      downstreamMediaFormat = mediaFormat;
    }

    if (!haveSamples) {
      if (loadingFinished) {
        return END_OF_STREAM;
      }
      return NOTHING_READ;
    }

    if (sampleQueue.getSample(sampleHolder)) {
      boolean decodeOnly = sampleHolder.timeUs < lastSeekPositionUs;
      sampleHolder.flags |= decodeOnly ? C.SAMPLE_FLAG_DECODE_ONLY : 0;
      onSampleRead(currentChunk, sampleHolder);
      return SAMPLE_READ;
    }

    return NOTHING_READ;
  }
  @Override
  public int readSample(int track, SampleHolder sampleHolder) throws IOException {
    if (sampleHolder.data == null) {
      sampleHolder.size = 0;
      return SampleSource.SAMPLE_READ;
    }

    if (!ffReadSample(pFormatCtx, sampleHolder)) {
      return SampleSource.NOTHING_READ;
    }

    // Log.d(TAG, "PTS: " + sampleHolder.timeUs);
    // Log.d(TAG, "frame start: " + sampleHolder.data.getInt(1));

    return SampleSource.SAMPLE_READ;
  }
  @Override
  protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
    currentPositionUs = positionUs;
    try {
      source.continueBuffering(positionUs);
    } catch (IOException e) {
      throw new ExoPlaybackException(e);
    }

    if (parserHelper.isParsing()) {
      return;
    }

    Subtitle dequeuedSubtitle = null;
    if (subtitle == null) {
      try {
        dequeuedSubtitle = parserHelper.getAndClearResult();
      } catch (IOException e) {
        throw new ExoPlaybackException(e);
      }
    }

    if (subtitle == null && dequeuedSubtitle != null) {
      // We've dequeued a new subtitle. Sync the event index and update the subtitle.
      subtitle = dequeuedSubtitle;
      syncNextEventIndex(positionUs);
      textRendererNeedsUpdate = true;
    } else if (subtitle != null) {
      // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we
      // advance to the next event.
      long nextEventTimeUs = getNextEventTime();
      while (nextEventTimeUs <= positionUs) {
        nextSubtitleEventIndex++;
        nextEventTimeUs = getNextEventTime();
        textRendererNeedsUpdate = true;
      }
      if (nextEventTimeUs == Long.MAX_VALUE) {
        // We've finished processing the subtitle.
        subtitle = null;
      }
    }

    // We don't have a subtitle. Try and read the next one from the source, and if we succeed then
    // sync and set textRendererNeedsUpdate.
    if (!inputStreamEnded && subtitle == null) {
      try {
        SampleHolder sampleHolder = parserHelper.getSampleHolder();
        sampleHolder.clearData();
        int result = source.readData(trackIndex, positionUs, formatHolder, sampleHolder, false);
        if (result == SampleSource.SAMPLE_READ && !sampleHolder.decodeOnly) {
          parserHelper.startParseOperation();
          textRendererNeedsUpdate = false;
        } else if (result == SampleSource.END_OF_STREAM) {
          inputStreamEnded = true;
        }
      } catch (IOException e) {
        throw new ExoPlaybackException(e);
      }
    }

    // Update the text renderer if we're both playing and textRendererNeedsUpdate is set.
    if (textRendererNeedsUpdate && getState() == TrackRenderer.STATE_STARTED) {
      textRendererNeedsUpdate = false;
      if (subtitle == null) {
        clearTextRenderer();
      } else {
        updateTextRenderer(positionUs);
      }
    }
  }