Support efficient switching between SimpleExoPlayerView instances

Prior to this change, the only way to switch SimpleExoPlayerView
was to do:

oldView.setPlayer(null);
newView.setPlayer(player);

This would cause the video renderer to have to transition through
oldSurface->noSurface->newSurface, which is inefficient (noSurface
requires platform decoders to be fully released).

After this change we support:

newView.setPlayer(player);
oldView.setPlayer(null);

This results in direct oldSurface->newSurface transitions, which are
seamless on Android M and above. The change also adds some robustness
against developers ending up with strange behavior as a result of
clearing the player from a view in a different ordering than we expect
w.r.t. registering of other listeners.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=154044976
This commit is contained in:
olly 2017-04-24 06:51:42 -07:00 committed by Oliver Woodman
parent 4c0b539054
commit 8e8a6a2994
2 changed files with 138 additions and 23 deletions

View file

@ -239,6 +239,18 @@ public class SimpleExoPlayer implements ExoPlayer {
setVideoSurfaceInternal(surface, false); setVideoSurfaceInternal(surface, false);
} }
/**
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surface The surface to clear.
*/
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
/** /**
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
* rendered. The player will track the lifecycle of the surface automatically. * rendered. The player will track the lifecycle of the surface automatically.
@ -256,6 +268,18 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
} }
/**
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
* rendered if it matches the one passed. Else does nothing.
*
* @param surfaceHolder The surface holder to clear.
*/
public void clearVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) {
setVideoSurfaceHolder(null);
}
}
/** /**
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
* lifecycle of the surface automatically. * lifecycle of the surface automatically.
@ -263,7 +287,17 @@ public class SimpleExoPlayer implements ExoPlayer {
* @param surfaceView The surface view. * @param surfaceView The surface view.
*/ */
public void setVideoSurfaceView(SurfaceView surfaceView) { public void setVideoSurfaceView(SurfaceView surfaceView) {
setVideoSurfaceHolder(surfaceView.getHolder()); setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
/**
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surfaceView The texture view to clear.
*/
public void clearVideoSurfaceView(SurfaceView surfaceView) {
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
} }
/** /**
@ -287,6 +321,18 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
} }
/**
* Clears the {@link TextureView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param textureView The texture view to clear.
*/
public void clearVideoTextureView(TextureView textureView) {
if (textureView != null && textureView == this.textureView) {
setVideoTextureView(null);
}
}
/** /**
* Sets the stream type for audio playback (see {@link C.StreamType} and * Sets the stream type for audio playback (see {@link C.StreamType} and
* {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type
@ -404,6 +450,57 @@ public class SimpleExoPlayer implements ExoPlayer {
videoListener = listener; videoListener = listener;
} }
/**
* Clears the listener receiving video events if it matches the one passed. Else does nothing.
*
* @param listener The listener to clear.
*/
public void clearVideoListener(VideoListener listener) {
if (videoListener == listener) {
videoListener = null;
}
}
/**
* Sets an output to receive text events.
*
* @param output The output.
*/
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
}
/**
* Clears the output receiving text events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearTextOutput(TextRenderer.Output output) {
if (textOutput == output) {
textOutput = null;
}
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public void setMetadataOutput(MetadataRenderer.Output output) {
metadataOutput = output;
}
/**
* Clears the output receiving metadata events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearMetadataOutput(MetadataRenderer.Output output) {
if (metadataOutput == output) {
metadataOutput = null;
}
}
/** /**
* Sets a listener to receive debug events from the video renderer. * Sets a listener to receive debug events from the video renderer.
* *
@ -422,24 +519,6 @@ public class SimpleExoPlayer implements ExoPlayer {
audioDebugListener = listener; audioDebugListener = listener;
} }
/**
* Sets an output to receive text events.
*
* @param output The output.
*/
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
}
/**
* Sets a listener to receive metadata events.
*
* @param output The output.
*/
public void setMetadataOutput(MetadataRenderer.Output output) {
metadataOutput = output;
}
// ExoPlayer implementation // ExoPlayer implementation
@Override @Override

View file

@ -21,6 +21,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -319,6 +321,30 @@ public final class SimpleExoPlayerView extends FrameLayout {
hideController(); hideController();
} }
/**
* Switches the view targeted by a given {@link SimpleExoPlayer}.
*
* @param player The player whose target view is being switched.
* @param oldPlayerView The old view to detach from the player.
* @param newPlayerView The new view to attach to the player.
*/
public static void switchTargetView(@NonNull SimpleExoPlayer player,
@Nullable SimpleExoPlayerView oldPlayerView, @Nullable SimpleExoPlayerView newPlayerView) {
if (oldPlayerView == newPlayerView) {
return;
}
// We attach the new view before detaching the old one because this ordering allows the player
// to swap directly from one surface to another, without transitioning through a state where no
// surface is attached. This is significantly more efficient and achieves a more seamless
// transition when using platform provided video decoders.
if (newPlayerView != null) {
newPlayerView.setPlayer(player);
}
if (oldPlayerView != null) {
oldPlayerView.setPlayer(null);
}
}
/** /**
* Returns the player currently set on this view, or null if no player is set. * Returns the player currently set on this view, or null if no player is set.
*/ */
@ -330,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* assignments are overridden. * assignments are overridden.
* <p>
* To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to
* use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather
* than this method. If you do wish to use this method directly, be sure to attach the player to
* the new view <em>before</em> calling {@code setPlayer(null)} to detach it from the old one.
* This ordering is significantly more efficient and may allow for more seamless transitions.
* *
* @param player The {@link SimpleExoPlayer} to use. * @param player The {@link SimpleExoPlayer} to use.
*/ */
@ -338,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout {
return; return;
} }
if (this.player != null) { if (this.player != null) {
this.player.setTextOutput(null);
this.player.setVideoListener(null);
this.player.removeListener(componentListener); this.player.removeListener(componentListener);
this.player.setVideoSurface(null); this.player.clearTextOutput(componentListener);
this.player.clearVideoListener(componentListener);
if (surfaceView instanceof TextureView) {
this.player.clearVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SurfaceView) {
this.player.clearVideoSurfaceView((SurfaceView) surfaceView);
}
} }
this.player = player; this.player = player;
if (useController) { if (useController) {
@ -357,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoSurfaceView((SurfaceView) surfaceView); player.setVideoSurfaceView((SurfaceView) surfaceView);
} }
player.setVideoListener(componentListener); player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener); player.setTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false); maybeShowController(false);
updateForCurrentTrackSelections(); updateForCurrentTrackSelections();
} else { } else {