Commit 8e8a6a29 by olly Committed by Oliver Woodman

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
parent 4c0b5390
......@@ -240,6 +240,18 @@ public class SimpleExoPlayer implements ExoPlayer {
}
/**
* 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
* rendered. The player will track the lifecycle of the surface automatically.
*
......@@ -257,13 +269,35 @@ 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
* lifecycle of the surface automatically.
*
* @param surfaceView The surface view.
*/
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());
}
/**
......@@ -288,6 +322,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
* {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type
* is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}.
......@@ -405,30 +451,34 @@ public class SimpleExoPlayer implements ExoPlayer {
}
/**
* Sets a listener to receive debug events from the video renderer.
* Clears the listener receiving video events if it matches the one passed. Else does nothing.
*
* @param listener The listener.
* @param listener The listener to clear.
*/
public void setVideoDebugListener(VideoRendererEventListener listener) {
videoDebugListener = listener;
public void clearVideoListener(VideoListener listener) {
if (videoListener == listener) {
videoListener = null;
}
}
/**
* Sets a listener to receive debug events from the audio renderer.
* Sets an output to receive text events.
*
* @param listener The listener.
* @param output The output.
*/
public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListener = listener;
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
}
/**
* Sets an output to receive text events.
* Clears the output receiving text events if it matches the one passed. Else does nothing.
*
* @param output The output.
* @param output The output to clear.
*/
public void setTextOutput(TextRenderer.Output output) {
textOutput = output;
public void clearTextOutput(TextRenderer.Output output) {
if (textOutput == output) {
textOutput = null;
}
}
/**
......@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer {
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.
*
* @param listener The listener.
*/
public void setVideoDebugListener(VideoRendererEventListener listener) {
videoDebugListener = listener;
}
/**
* Sets a listener to receive debug events from the audio renderer.
*
* @param listener The listener.
*/
public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListener = listener;
}
// ExoPlayer implementation
@Override
......
......@@ -21,6 +21,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
......@@ -320,6 +322,30 @@ public final class SimpleExoPlayerView extends FrameLayout {
}
/**
* 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.
*/
public SimpleExoPlayer getPlayer() {
......@@ -330,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* 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.
*/
......@@ -338,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout {
return;
}
if (this.player != null) {
this.player.setTextOutput(null);
this.player.setVideoListener(null);
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;
if (useController) {
......@@ -357,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false);
updateForCurrentTrackSelections();
} else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment