I'm using ExoPlayer in a RecyclerView.
The way I have it working now is:
- Using only one ExoPlayer instance
- When the RecyclerView item comes into view, it prepares the player and attaches the PlayerView to the layout
- When the RecyclerView item goes out of view, it calls setPlayer(null) and removes the PlayerView from the layout
However, when scrolling through my RecyclerView, each video flashes black very briefly (milliseconds) just before playing, as you can see in the video below:
What am I doing wrong? What can I change to prevent this black screen from flashing before the video plays?
Here are the relevant parts of my RecyclerView adapter:
public class PostAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private SimpleExoPlayer exoPlayer;
private HlsMediaSource.Factory hlsMediaSourceFactory;
public PostAdapter(Context context) {
this.context = context;
this.exoPlayer = new SimpleExoPlayer.Builder(context)
.build();
this.exoPlayer.setVideoTextureView(new TextureView(context));
this.hlsMediaSourceFactory = new HlsMediaSource.Factory(CustomMediaSourceFactory.buildMediaSourceFactory())
.setAllowChunklessPreparation(true); // TODO: Look into this
}
public class PostViewHolder extends RecyclerView.ViewHolder {
public ConstraintLayout videoContainer;
public ImageView videoThumbnail;
public HlsMediaSource hlsMediaSource;
public FrameLayout playerViewContainer;
public PlayerView playerView;
public Player.Listener playerListener;
public PostViewHolder(View v) {
super(v);
videoContainer = v.findViewById(R.id.video_container);
videoThumbnail = v.findViewById(R.id.video_thumbnail);
playerViewContainer = v.findViewById(R.id.player_view_container);
playerView = new PlayerView(context);
playerView.setUseController(false);
playerView.setKeepContentOnPlayerReset(true);
}
public void prepareVideo() {
if (exoPlayer == null) {
return;
}
if (exoPlayer.getCurrentMediaItem() != hlsMediaSource.getMediaItem()) {
playerListener = new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_READY) {
int index = playerViewContainer.indexOfChild(playerView);
if (index == -1) {
// Add the player view to the layout
playerViewContainer.addView(playerView);
playerView.setVisibility(View.VISIBLE);
}
}
}
};
exoPlayer.addListener(playerListener);
exoPlayer.setVolume(0);
exoPlayer.setRepeatMode(Player.REPEAT_MODE_ALL);
// Set the media item to be played.
exoPlayer.setMediaSource(hlsMediaSource);
// Prepare the player
exoPlayer.prepare();
// Attach the player to the view
playerView.setPlayer(exoPlayer);
}
// Play
playVideo();
}
public void playVideo() {
if (exoPlayer != null) {
exoPlayer.play();
}
}
// This is called when the RecyclerView item comes into view
public void attachPlayer() {
// Prepare the video
prepareVideo();
}
// This is called when the RecyclerView item goes out of view
public void detachPlayer() {
playerView.setPlayer(null);
if (playerListener != null) {
exoPlayer.removeListener(playerListener);
}
// Remove the player view from the layout
if (playerView.getParent() != null) {
playerView.setVisibility(View.GONE);
((ViewGroup) playerView.getParent()).removeView(playerView);
}
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
final Post post = (Post) data.get(position);
Video video = post.getVideo();
if (video != null) {
Glide.with(context)
.load(video.getThumbnailUrl())
.diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
.into(holder.videoThumbnail);
if (video.getUrls() != null && video.getUrls().getHls() != null) {
holder.hlsMediaSource = hlsMediaSourceFactory.createMediaSource(MediaItem.fromUri(video.getUrls().getHls()));
}
}
holder.videoContainer.setVisibility(View.VISIBLE);
}
}
And here are the relevant parts of my layout:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/video_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.makeramen.roundedimageview.RoundedImageView
android:id="@+id/video_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/player_view_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Any advice is appreciated!