@Override
 protected void onEnabled(int track, long positionUs, boolean joining)
     throws ExoPlaybackException {
   super.onEnabled(track, positionUs, joining);
   if (joining && allowedJoiningTimeUs > 0) {
     joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
   }
   frameReleaseTimeHelper.enable();
 }
 @Override
 protected void onDisabled() throws ExoPlaybackException {
   currentWidth = -1;
   currentHeight = -1;
   currentPixelWidthHeightRatio = -1;
   pendingPixelWidthHeightRatio = -1;
   lastReportedWidth = -1;
   lastReportedHeight = -1;
   lastReportedPixelWidthHeightRatio = -1;
   frameReleaseTimeHelper.disable();
   super.onDisabled();
 }
  @Override
  protected boolean processOutputBuffer(
      long positionUs,
      long elapsedRealtimeUs,
      MediaCodec codec,
      ByteBuffer buffer,
      MediaCodec.BufferInfo bufferInfo,
      int bufferIndex,
      boolean shouldSkip) {
    if (shouldSkip) {
      skipOutputBuffer(codec, bufferIndex);
      consecutiveDroppedFrameCount = 0;
      return true;
    }

    if (!renderedFirstFrame) {
      if (Util.SDK_INT >= 21) {
        renderOutputBufferV21(codec, bufferIndex, System.nanoTime());
      } else {
        renderOutputBuffer(codec, bufferIndex);
      }
      consecutiveDroppedFrameCount = 0;
      return true;
    }

    if (getState() != TrackRenderer.STATE_STARTED) {
      return false;
    }

    // Compute how many microseconds it is until the buffer's presentation time.
    long elapsedSinceStartOfLoopUs = (SystemClock.elapsedRealtime() * 1000) - elapsedRealtimeUs;
    long earlyUs = bufferInfo.presentationTimeUs - positionUs - elapsedSinceStartOfLoopUs;

    // Compute the buffer's desired release time in nanoseconds.
    long systemTimeNs = System.nanoTime();
    long unadjustedFrameReleaseTimeNs = systemTimeNs + (earlyUs * 1000);

    // Apply a timestamp adjustment, if there is one.
    long adjustedReleaseTimeNs =
        frameReleaseTimeHelper.adjustReleaseTime(
            bufferInfo.presentationTimeUs, unadjustedFrameReleaseTimeNs);
    earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;

    if (earlyUs < -30000) {
      // We're more than 30ms late rendering the frame.
      dropOutputBuffer(codec, bufferIndex);
      return true;
    }

    if (Util.SDK_INT >= 21) {
      // Let the underlying framework time the release.
      if (earlyUs < 50000) {
        renderOutputBufferV21(codec, bufferIndex, adjustedReleaseTimeNs);
        consecutiveDroppedFrameCount = 0;
        return true;
      }
    } else {
      // We need to time the release ourselves.
      if (earlyUs < 30000) {
        if (earlyUs > 11000) {
          // We're a little too early to render the frame. Sleep until the frame can be rendered.
          // Note: The 11ms threshold was chosen fairly arbitrarily.
          try {
            // Subtracting 10000 rather than 11000 ensures the sleep time will be at least 1ms.
            Thread.sleep((earlyUs - 10000) / 1000);
          } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
          }
        }
        renderOutputBuffer(codec, bufferIndex);
        consecutiveDroppedFrameCount = 0;
        return true;
      }
    }

    // We're either not playing, or it's not time to render the frame yet.
    return false;
  }