mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Simplify first frame logic
We currently use 3 different booleans to track the state of the first frame rendering, which implies that there are 8 distinct possible overall states. However, this is actually a staged process and there are only 3 different overall states in the current code. This means it's clearer and easier to reason about if the variables are combined to a single state value. Overall, this should be a complete no-op. State mapping: - rFFAReset=false, rFFAEnable=false, mayRenderFFAEINS=false => FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED - rFFAReset=false and/or rFFAEnable=false, mayRenderFFAEINS=any => FIRST_FRAME_NOT_RENDERED - rFFAReset=true, rFFAEnable=true, mayRenderFFAEINS=any => FIRST_FRAME_RENDERED PiperOrigin-RevId: 552857802
This commit is contained in:
parent
11648e6c8e
commit
79e05ad049
3 changed files with 109 additions and 54 deletions
|
|
@ -1506,6 +1506,36 @@ public final class C {
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
@UnstableApi public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* State of the first frame in a video renderer.
|
||||||
|
*
|
||||||
|
* <p>One of {@link #FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED}, {@link
|
||||||
|
* #FIRST_FRAME_NOT_RENDERED} or {@link #FIRST_FRAME_RENDERED}. The stages are ordered and
|
||||||
|
* comparable, i.e., a value implies that all stages with higher values are not reached yet.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@UnstableApi
|
||||||
|
@IntDef({
|
||||||
|
FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED,
|
||||||
|
FIRST_FRAME_NOT_RENDERED,
|
||||||
|
FIRST_FRAME_RENDERED
|
||||||
|
})
|
||||||
|
public @interface FirstFrameState {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The first frame was not rendered yet, and is only allowed to be rendered if the renderer is
|
||||||
|
* started.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED = 0;
|
||||||
|
|
||||||
|
/** The first frame was not rendered after the last reset, output surface or stream change. */
|
||||||
|
@UnstableApi public static final int FIRST_FRAME_NOT_RENDERED = 1;
|
||||||
|
|
||||||
|
/** The first frame was rendered. */
|
||||||
|
@UnstableApi public static final int FIRST_FRAME_RENDERED = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link Util#usToMs(long)}.
|
* @deprecated Use {@link Util#usToMs(long)}.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_RE
|
||||||
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
|
||||||
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
import static java.lang.Math.min;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
|
@ -136,9 +137,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
private @ReinitializationState int decoderReinitializationState;
|
private @ReinitializationState int decoderReinitializationState;
|
||||||
private boolean decoderReceivedBuffers;
|
private boolean decoderReceivedBuffers;
|
||||||
|
|
||||||
private boolean renderedFirstFrameAfterReset;
|
private @C.FirstFrameState int firstFrameState;
|
||||||
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
|
|
||||||
private boolean renderedFirstFrameAfterEnable;
|
|
||||||
private long initialPositionUs;
|
private long initialPositionUs;
|
||||||
private long joiningDeadlineMs;
|
private long joiningDeadlineMs;
|
||||||
private boolean waitingForFirstSampleInFormat;
|
private boolean waitingForFirstSampleInFormat;
|
||||||
|
|
@ -181,6 +180,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||||
outputMode = C.VIDEO_OUTPUT_MODE_NONE;
|
outputMode = C.VIDEO_OUTPUT_MODE_NONE;
|
||||||
|
firstFrameState = C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseRenderer implementation.
|
// BaseRenderer implementation.
|
||||||
|
|
@ -238,7 +238,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
if (inputFormat != null
|
if (inputFormat != null
|
||||||
&& (isSourceReady() || outputBuffer != null)
|
&& (isSourceReady() || outputBuffer != null)
|
||||||
&& (renderedFirstFrameAfterReset || !hasOutput())) {
|
&& (firstFrameState == C.FIRST_FRAME_RENDERED || !hasOutput())) {
|
||||||
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
// Ready. If we were joining then we've now joined, so clear the joining deadline.
|
||||||
joiningDeadlineMs = C.TIME_UNSET;
|
joiningDeadlineMs = C.TIME_UNSET;
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -276,20 +276,24 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
decoderCounters = new DecoderCounters();
|
decoderCounters = new DecoderCounters();
|
||||||
eventDispatcher.enabled(decoderCounters);
|
eventDispatcher.enabled(decoderCounters);
|
||||||
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
|
firstFrameState =
|
||||||
renderedFirstFrameAfterEnable = false;
|
mayRenderStartOfStream
|
||||||
|
? C.FIRST_FRAME_NOT_RENDERED
|
||||||
|
: C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableMayRenderStartOfStream() {
|
public void enableMayRenderStartOfStream() {
|
||||||
mayRenderFirstFrameAfterEnableIfNotStarted = true;
|
if (firstFrameState == C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED) {
|
||||||
|
firstFrameState = C.FIRST_FRAME_NOT_RENDERED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
|
||||||
inputStreamEnded = false;
|
inputStreamEnded = false;
|
||||||
outputStreamEnded = false;
|
outputStreamEnded = false;
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
initialPositionUs = C.TIME_UNSET;
|
initialPositionUs = C.TIME_UNSET;
|
||||||
consecutiveDroppedFrameCount = 0;
|
consecutiveDroppedFrameCount = 0;
|
||||||
if (decoder != null) {
|
if (decoder != null) {
|
||||||
|
|
@ -320,7 +324,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
inputFormat = null;
|
inputFormat = null;
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED);
|
||||||
try {
|
try {
|
||||||
setSourceDrmSession(null);
|
setSourceDrmSession(null);
|
||||||
releaseDecoder();
|
releaseDecoder();
|
||||||
|
|
@ -854,20 +858,12 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
outputFormat = format;
|
outputFormat = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
long elapsedRealtimeNowUs = msToUs(SystemClock.elapsedRealtime());
|
if (shouldForceRender(earlyUs)) {
|
||||||
long elapsedSinceLastRenderUs = elapsedRealtimeNowUs - lastRenderTimeUs;
|
|
||||||
boolean isStarted = getState() == STATE_STARTED;
|
|
||||||
boolean shouldRenderFirstFrame =
|
|
||||||
!renderedFirstFrameAfterEnable
|
|
||||||
? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
|
|
||||||
: !renderedFirstFrameAfterReset;
|
|
||||||
// TODO: We shouldn't force render while we are joining an ongoing playback.
|
|
||||||
if (shouldRenderFirstFrame
|
|
||||||
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs))) {
|
|
||||||
renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
|
renderOutputBuffer(outputBuffer, presentationTimeUs, outputFormat);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean isStarted = getState() == STATE_STARTED;
|
||||||
if (!isStarted || positionUs == initialPositionUs) {
|
if (!isStarted || positionUs == initialPositionUs) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -889,6 +885,23 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether a buffer or a processed frame should be force rendered. */
|
||||||
|
private boolean shouldForceRender(long earlyUs) {
|
||||||
|
// TODO: We shouldn't force render while we are joining an ongoing playback.
|
||||||
|
boolean isStarted = getState() == STATE_STARTED;
|
||||||
|
switch (firstFrameState) {
|
||||||
|
case C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED:
|
||||||
|
return isStarted;
|
||||||
|
case C.FIRST_FRAME_NOT_RENDERED:
|
||||||
|
return true;
|
||||||
|
case C.FIRST_FRAME_RENDERED:
|
||||||
|
long elapsedSinceLastRenderUs = msToUs(SystemClock.elapsedRealtime()) - lastRenderTimeUs;
|
||||||
|
return isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs);
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasOutput() {
|
private boolean hasOutput() {
|
||||||
return outputMode != C.VIDEO_OUTPUT_MODE_NONE;
|
return outputMode != C.VIDEO_OUTPUT_MODE_NONE;
|
||||||
}
|
}
|
||||||
|
|
@ -897,7 +910,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
// If we know the video size, report it again immediately.
|
// If we know the video size, report it again immediately.
|
||||||
maybeRenotifyVideoSizeChanged();
|
maybeRenotifyVideoSizeChanged();
|
||||||
// We haven't rendered to the new output yet.
|
// We haven't rendered to the new output yet.
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
if (getState() == STATE_STARTED) {
|
if (getState() == STATE_STARTED) {
|
||||||
setJoiningDeadlineMs();
|
setJoiningDeadlineMs();
|
||||||
}
|
}
|
||||||
|
|
@ -905,7 +918,7 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
|
|
||||||
private void onOutputRemoved() {
|
private void onOutputRemoved() {
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onOutputReset() {
|
private void onOutputReset() {
|
||||||
|
|
@ -922,20 +935,19 @@ public abstract class DecoderVideoRenderer extends BaseRenderer {
|
||||||
: C.TIME_UNSET;
|
: C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearRenderedFirstFrame() {
|
private void lowerFirstFrameState(@C.FirstFrameState int firstFrameState) {
|
||||||
renderedFirstFrameAfterReset = false;
|
this.firstFrameState = min(this.firstFrameState, firstFrameState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeNotifyRenderedFirstFrame() {
|
private void maybeNotifyRenderedFirstFrame() {
|
||||||
renderedFirstFrameAfterEnable = true;
|
if (firstFrameState != C.FIRST_FRAME_RENDERED) {
|
||||||
if (!renderedFirstFrameAfterReset) {
|
firstFrameState = C.FIRST_FRAME_RENDERED;
|
||||||
renderedFirstFrameAfterReset = true;
|
|
||||||
eventDispatcher.renderedFirstFrame(output);
|
eventDispatcher.renderedFirstFrame(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeRenotifyRenderedFirstFrame() {
|
private void maybeRenotifyRenderedFirstFrame() {
|
||||||
if (renderedFirstFrameAfterReset) {
|
if (firstFrameState == C.FIRST_FRAME_RENDERED) {
|
||||||
eventDispatcher.renderedFirstFrame(output);
|
eventDispatcher.renderedFirstFrame(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -166,9 +166,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
@Nullable private PlaceholderSurface placeholderSurface;
|
@Nullable private PlaceholderSurface placeholderSurface;
|
||||||
private boolean haveReportedFirstFrameRenderedForCurrentSurface;
|
private boolean haveReportedFirstFrameRenderedForCurrentSurface;
|
||||||
private @C.VideoScalingMode int scalingMode;
|
private @C.VideoScalingMode int scalingMode;
|
||||||
private boolean renderedFirstFrameAfterReset;
|
private @C.FirstFrameState int firstFrameState;
|
||||||
private boolean mayRenderFirstFrameAfterEnableIfNotStarted;
|
|
||||||
private boolean renderedFirstFrameAfterEnable;
|
|
||||||
private long initialPositionUs;
|
private long initialPositionUs;
|
||||||
private long joiningDeadlineMs;
|
private long joiningDeadlineMs;
|
||||||
private long droppedFrameAccumulationStartTimeMs;
|
private long droppedFrameAccumulationStartTimeMs;
|
||||||
|
|
@ -412,6 +410,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
||||||
decodedVideoSize = VideoSize.UNKNOWN;
|
decodedVideoSize = VideoSize.UNKNOWN;
|
||||||
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
||||||
|
firstFrameState = C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED;
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -601,13 +600,17 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
releaseCodec();
|
releaseCodec();
|
||||||
}
|
}
|
||||||
eventDispatcher.enabled(decoderCounters);
|
eventDispatcher.enabled(decoderCounters);
|
||||||
mayRenderFirstFrameAfterEnableIfNotStarted = mayRenderStartOfStream;
|
firstFrameState =
|
||||||
renderedFirstFrameAfterEnable = false;
|
mayRenderStartOfStream
|
||||||
|
? C.FIRST_FRAME_NOT_RENDERED
|
||||||
|
: C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void enableMayRenderStartOfStream() {
|
public void enableMayRenderStartOfStream() {
|
||||||
mayRenderFirstFrameAfterEnableIfNotStarted = true;
|
if (firstFrameState == C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED) {
|
||||||
|
firstFrameState = C.FIRST_FRAME_NOT_RENDERED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -616,7 +619,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
if (videoFrameProcessorManager.isEnabled()) {
|
if (videoFrameProcessorManager.isEnabled()) {
|
||||||
videoFrameProcessorManager.flush();
|
videoFrameProcessorManager.flush();
|
||||||
}
|
}
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
frameReleaseHelper.onPositionReset();
|
frameReleaseHelper.onPositionReset();
|
||||||
lastBufferPresentationTimeUs = C.TIME_UNSET;
|
lastBufferPresentationTimeUs = C.TIME_UNSET;
|
||||||
initialPositionUs = C.TIME_UNSET;
|
initialPositionUs = C.TIME_UNSET;
|
||||||
|
|
@ -641,7 +644,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
public boolean isReady() {
|
public boolean isReady() {
|
||||||
if (super.isReady()
|
if (super.isReady()
|
||||||
&& (!videoFrameProcessorManager.isEnabled() || videoFrameProcessorManager.isReady())
|
&& (!videoFrameProcessorManager.isEnabled() || videoFrameProcessorManager.isReady())
|
||||||
&& (renderedFirstFrameAfterReset
|
&& (firstFrameState == C.FIRST_FRAME_RENDERED
|
||||||
|| (placeholderSurface != null && displaySurface == placeholderSurface)
|
|| (placeholderSurface != null && displaySurface == placeholderSurface)
|
||||||
|| getCodec() == null
|
|| getCodec() == null
|
||||||
|| tunneling)) {
|
|| tunneling)) {
|
||||||
|
|
@ -685,7 +688,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED);
|
||||||
haveReportedFirstFrameRenderedForCurrentSurface = false;
|
haveReportedFirstFrameRenderedForCurrentSurface = false;
|
||||||
tunnelingOnFrameRenderedListener = null;
|
tunnelingOnFrameRenderedListener = null;
|
||||||
try {
|
try {
|
||||||
|
|
@ -801,7 +804,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
// If we know the video size, report it again immediately.
|
// If we know the video size, report it again immediately.
|
||||||
maybeRenotifyVideoSizeChanged();
|
maybeRenotifyVideoSizeChanged();
|
||||||
// We haven't rendered to the new display surface yet.
|
// We haven't rendered to the new display surface yet.
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
if (state == STATE_STARTED) {
|
if (state == STATE_STARTED) {
|
||||||
// Set joining deadline to report MediaCodecVideoRenderer is ready.
|
// Set joining deadline to report MediaCodecVideoRenderer is ready.
|
||||||
setJoiningDeadlineMs();
|
setJoiningDeadlineMs();
|
||||||
|
|
@ -814,7 +817,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
} else {
|
} else {
|
||||||
// The display surface has been removed.
|
// The display surface has been removed.
|
||||||
clearReportedVideoSize();
|
clearReportedVideoSize();
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
if (videoFrameProcessorManager.isEnabled()) {
|
if (videoFrameProcessorManager.isEnabled()) {
|
||||||
videoFrameProcessorManager.clearOutputSurfaceInfo();
|
videoFrameProcessorManager.clearOutputSurfaceInfo();
|
||||||
}
|
}
|
||||||
|
|
@ -1334,17 +1337,28 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
/** Returns whether a buffer or a processed frame should be force rendered. */
|
/** Returns whether a buffer or a processed frame should be force rendered. */
|
||||||
private boolean shouldForceRender(long positionUs, long earlyUs) {
|
private boolean shouldForceRender(long positionUs, long earlyUs) {
|
||||||
|
if (joiningDeadlineMs != C.TIME_UNSET) {
|
||||||
|
// No force rendering during joining.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (positionUs < getOutputStreamOffsetUs()) {
|
||||||
|
// No force rendering if we haven't reached the stream start position.
|
||||||
|
// TODO: b/160461756 - This is a bug because it compares against the offset and not the start
|
||||||
|
// position and also should only be applied when transitioning streams, not after every reset.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
boolean isStarted = getState() == STATE_STARTED;
|
boolean isStarted = getState() == STATE_STARTED;
|
||||||
boolean shouldRenderFirstFrame =
|
switch (firstFrameState) {
|
||||||
!renderedFirstFrameAfterEnable
|
case C.FIRST_FRAME_NOT_RENDERED_ONLY_ALLOWED_IF_STARTED:
|
||||||
? (isStarted || mayRenderFirstFrameAfterEnableIfNotStarted)
|
return isStarted;
|
||||||
: !renderedFirstFrameAfterReset;
|
case C.FIRST_FRAME_NOT_RENDERED:
|
||||||
long elapsedSinceLastRenderUs = msToUs(getClock().elapsedRealtime()) - lastRenderRealtimeUs;
|
return true;
|
||||||
// Don't force output until we joined and the position reached the current stream.
|
case C.FIRST_FRAME_RENDERED:
|
||||||
return joiningDeadlineMs == C.TIME_UNSET
|
long elapsedSinceLastRenderUs = msToUs(getClock().elapsedRealtime()) - lastRenderRealtimeUs;
|
||||||
&& positionUs >= getOutputStreamOffsetUs()
|
return isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs);
|
||||||
&& (shouldRenderFirstFrame
|
default:
|
||||||
|| (isStarted && shouldForceRenderOutputBuffer(earlyUs, elapsedSinceLastRenderUs)));
|
throw new IllegalStateException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1417,7 +1431,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
@Override
|
@Override
|
||||||
protected void onProcessedStreamChange() {
|
protected void onProcessedStreamChange() {
|
||||||
super.onProcessedStreamChange();
|
super.onProcessedStreamChange();
|
||||||
clearRenderedFirstFrame();
|
lowerFirstFrameState(C.FIRST_FRAME_NOT_RENDERED);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1701,8 +1715,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
: C.TIME_UNSET;
|
: C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearRenderedFirstFrame() {
|
private void lowerFirstFrameState(@C.FirstFrameState int firstFrameState) {
|
||||||
renderedFirstFrameAfterReset = false;
|
this.firstFrameState = min(this.firstFrameState, firstFrameState);
|
||||||
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
|
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
|
||||||
// non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and
|
// non-tunneled playback, onQueueInputBuffer for tunneled playback prior to API level 23, and
|
||||||
// OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and
|
// OnFrameRenderedListenerV23.onFrameRenderedListener for tunneled playback on API level 23 and
|
||||||
|
|
@ -1717,9 +1731,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void maybeNotifyRenderedFirstFrame() {
|
/* package */ void maybeNotifyRenderedFirstFrame() {
|
||||||
renderedFirstFrameAfterEnable = true;
|
if (firstFrameState != C.FIRST_FRAME_RENDERED) {
|
||||||
if (!renderedFirstFrameAfterReset) {
|
firstFrameState = C.FIRST_FRAME_RENDERED;
|
||||||
renderedFirstFrameAfterReset = true;
|
|
||||||
eventDispatcher.renderedFirstFrame(displaySurface);
|
eventDispatcher.renderedFirstFrame(displaySurface);
|
||||||
haveReportedFirstFrameRenderedForCurrentSurface = true;
|
haveReportedFirstFrameRenderedForCurrentSurface = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue