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 { ...@@ -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 * 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.
* *
...@@ -257,13 +269,35 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -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 * 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.
* *
* @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());
} }
/** /**
...@@ -288,6 +322,18 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -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 * 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
* is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}.
...@@ -405,21 +451,14 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -405,21 +451,14 @@ 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) { public void clearVideoListener(VideoListener listener) {
videoDebugListener = listener; if (videoListener == listener) {
videoListener = null;
} }
/**
* Sets a listener to receive debug events from the audio renderer.
*
* @param listener The listener.
*/
public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListener = listener;
} }
/** /**
...@@ -432,6 +471,17 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -432,6 +471,17 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* 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. * Sets a listener to receive metadata events.
* *
* @param output The output. * @param output The output.
...@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer {
metadataOutput = 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.
*
* @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 // ExoPlayer implementation
@Override @Override
......
...@@ -21,6 +21,8 @@ import android.content.res.Resources; ...@@ -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;
...@@ -320,6 +322,30 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -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. * Returns the player currently set on this view, or null if no player is set.
*/ */
public SimpleExoPlayer getPlayer() { public SimpleExoPlayer getPlayer() {
...@@ -330,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -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 { ...@@ -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 { ...@@ -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 {
......
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