Skip to content

Commit

Permalink
Support efficient switching between SimpleExoPlayerView instances
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ojw28 committed Apr 24, 2017
1 parent 4c0b539 commit 8e8a6a2
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,18 @@ public void setVideoSurface(Surface surface) {
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
* rendered. The player will track the lifecycle of the surface automatically.
Expand All @@ -256,14 +268,36 @@ public void setVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
}
}

/**
* 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());
}

/**
Expand All @@ -287,6 +321,18 @@ public void setVideoTextureView(TextureView textureView) {
}
}

/**
* 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
Expand Down Expand Up @@ -405,30 +451,34 @@ public void setVideoListener(VideoListener listener) {
}

/**
* 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;
}
}

/**
Expand All @@ -440,6 +490,35 @@ 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.
*
* @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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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;
Expand Down Expand Up @@ -319,6 +321,30 @@ public SimpleExoPlayerView(Context context, AttributeSet attrs, int defStyleAttr
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.
*/
Expand All @@ -330,6 +356,12 @@ public SimpleExoPlayer getPlayer() {
* 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.
*/
Expand All @@ -338,10 +370,14 @@ public void setPlayer(SimpleExoPlayer player) {
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) {
Expand All @@ -357,8 +393,8 @@ public void setPlayer(SimpleExoPlayer player) {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false);
updateForCurrentTrackSelections();
} else {
Expand Down

0 comments on commit 8e8a6a2

Please sign in to comment.