private void restartFrom(long positionUs) {
   pendingResetPositionUs = positionUs;
   loadingFinished = false;
   if (loader.isLoading()) {
     loader.cancelLoading();
   } else {
     sampleQueue.clear();
     mediaChunks.clear();
     clearCurrentLoadable();
     updateLoadControl();
   }
 }
 private void maybeStartLoading() {
   Chunk currentLoadable = currentLoadableHolder.chunk;
   if (currentLoadable == null) {
     // Nothing to load.
     return;
   }
   currentLoadStartTimeMs = SystemClock.elapsedRealtime();
   if (isMediaChunk(currentLoadable)) {
     BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
     mediaChunk.init(sampleQueue);
     mediaChunks.add(mediaChunk);
     if (isPendingReset()) {
       pendingResetPositionUs = NO_RESET_PENDING;
     }
     notifyLoadStarted(
         mediaChunk.dataSpec.length,
         mediaChunk.type,
         mediaChunk.trigger,
         mediaChunk.format,
         mediaChunk.startTimeUs,
         mediaChunk.endTimeUs);
   } else {
     notifyLoadStarted(
         currentLoadable.dataSpec.length,
         currentLoadable.type,
         currentLoadable.trigger,
         currentLoadable.format,
         -1,
         -1);
   }
   loader.startLoading(currentLoadable, this);
 }
  /**
   * Resumes loading.
   *
   * <p>If the {@link ChunkSource} returns a chunk equivalent to the backed off chunk B, then the
   * loading of B will be resumed. In all other cases B will be discarded and the new chunk will be
   * loaded.
   */
  private void resumeFromBackOff() {
    currentLoadableException = null;

    Chunk backedOffChunk = currentLoadableHolder.chunk;
    if (!isMediaChunk(backedOffChunk)) {
      doChunkOperation();
      discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
      if (currentLoadableHolder.chunk == backedOffChunk) {
        // Chunk was unchanged. Resume loading.
        loader.startLoading(backedOffChunk, this);
      } else {
        // Chunk was changed. Notify that the existing load was canceled.
        notifyLoadCanceled(backedOffChunk.bytesLoaded());
        // Start loading the replacement.
        maybeStartLoading();
      }
      return;
    }

    if (backedOffChunk == mediaChunks.getFirst()) {
      // We're not able to clear the first media chunk, so we have no choice but to continue
      // loading it.
      loader.startLoading(backedOffChunk, this);
      return;
    }

    // The current loadable is the last media chunk. Remove it before we invoke the chunk source,
    // and add it back again afterwards.
    BaseMediaChunk removedChunk = mediaChunks.removeLast();
    Assertions.checkState(backedOffChunk == removedChunk);
    doChunkOperation();
    mediaChunks.add(removedChunk);

    if (currentLoadableHolder.chunk == backedOffChunk) {
      // Chunk was unchanged. Resume loading.
      loader.startLoading(backedOffChunk, this);
    } else {
      // Chunk was changed. Notify that the existing load was canceled.
      notifyLoadCanceled(backedOffChunk.bytesLoaded());
      // This call will remove and release at least one chunk from the end of mediaChunks. Since
      // the current loadable is the last media chunk, it is guaranteed to be removed.
      discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
      clearCurrentLoadableException();
      maybeStartLoading();
    }
  }
 @Override
 public void release() {
   Assertions.checkState(state != STATE_ENABLED);
   if (loader != null) {
     loader.release();
     loader = null;
   }
   state = STATE_IDLE;
 }
 @Override
 public void disable(int track) {
   Assertions.checkState(state == STATE_ENABLED);
   Assertions.checkState(--enabledTrackCount == 0);
   state = STATE_PREPARED;
   try {
     chunkSource.disable(mediaChunks);
   } finally {
     loadControl.unregister(this);
     if (loader.isLoading()) {
       loader.cancelLoading();
     } else {
       sampleQueue.clear();
       mediaChunks.clear();
       clearCurrentLoadable();
       loadControl.trimAllocator();
     }
   }
 }
  private void updateLoadControl() {
    long now = SystemClock.elapsedRealtime();
    long nextLoadPositionUs = getNextLoadPositionUs();
    boolean isBackedOff = currentLoadableException != null;
    boolean loadingOrBackedOff = loader.isLoading() || isBackedOff;

    // If we're not loading or backed off, evaluate the operation if (a) we don't have the next
    // chunk yet and we're not finished, or (b) if the last evaluation was over 2000ms ago.
    if (!loadingOrBackedOff
        && ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
            || (now - lastPerformedBufferOperation > 2000))) {
      // Perform the evaluation.
      lastPerformedBufferOperation = now;
      doChunkOperation();
      boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
      // Update the next load position as appropriate.
      if (currentLoadableHolder.chunk == null) {
        // Set loadPosition to -1 to indicate that we don't have anything to load.
        nextLoadPositionUs = -1;
      } else if (chunksDiscarded) {
        // Chunks were discarded, so we need to re-evaluate the load position.
        nextLoadPositionUs = getNextLoadPositionUs();
      }
    }

    // Update the control with our current state, and determine whether we're the next loader.
    boolean nextLoader =
        loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, loadingOrBackedOff);

    if (isBackedOff) {
      long elapsedMillis = now - currentLoadableExceptionTimestamp;
      if (elapsedMillis >= getRetryDelayMillis(currentLoadableExceptionCount)) {
        resumeFromBackOff();
      }
      return;
    }

    if (!loader.isLoading() && nextLoader) {
      maybeStartLoading();
    }
  }