media icon indicating copy to clipboard operation
media copied to clipboard

ExoPlayer in RecyclerView Not Synchronizing with Swipe.

Open varun7952 opened this issue 1 year ago • 6 comments

I have created a single instance of ExoPlayer to be used throughout a RecyclerView in my app (a chat screen). When a user clicks on a video thumbnail, I pass a list of URIs to the ExoPlayer class:

List<MediaItem> mediaItem = new ArrayList<>();
        for (String s: videoUri){
           mediaItem.add(MediaItem.fromUri(s));
        }
player.setMediaItems(mediaItem);

When ExoPlayer start playing a video in recyclerView, it works fine when using the next and previous buttons of ExoPlayer. However, the problem starts when I swipe for next or previous video RecyclerView. I tried using the seekTo method of ExoPlayer to move to the next/previous video, but after implementing seekto, on swiping only the audio plays next or previous video's audio also I don't see any controls or video.

RecyclerView class

public class Onclick_MediaViewer extends Fragment  {
	int currentPlayingPosition = -1;
    int pos;
    ArrayList<String> galleryItems;
    PlayerView video;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        galleryItems = new ArrayList<>();
        return inflater.inflate(R.layout.onclick_mediaviewer,container,false);

    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        RecyclerView recyclerView = view.findViewById(R.id.mediaViewerRecycler);
        layoutManager = new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false);
        SnapHelper snapHelper = new PagerSnapHelper();
        recyclerView.setLayoutManager(layoutManager);
        snapHelper.attachToRecyclerView(recyclerView);
        recyclerView.addItemDecoration(new LinePagerIndicatorDecoration());
        Media_OnclickAdapter adapter = new Media_OnclickAdapter();
        recyclerView.setHasFixedSize(true);
        recyclerView.setAdapter(adapter);
        
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE){
                    //Handle new video
                    int position = layoutManager.findFirstCompletelyVisibleItemPosition();
                    Log.d(TAG, "onScrollStateChanged: "+position);
                    
                    if (position != RecyclerView.NO_POSITION && position != currentPlayingPosition) {
                        currentPlayingPosition = position;
                        ExoplayerSingle_Instance.getInstance().playItemAtPosition(position);
                    }
                    
                }
            }
        });


    }

    private class Media_OnclickAdapter extends RecyclerView.Adapter<Media_OnclickAdapter.Viewholder>{


        @NonNull
        @Override
        public Viewholder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(getActivity()).inflate(R.layout.onclick_mediaviewer_video,parent,false);
            return new Viewholder(view);
        }

        @Override
        public void onBindViewHolder(@NonNull Viewholder holder, int position) {
            if (!ExoplayerSingle_Instance.getInstance().playerHasItem()){
                    
                    ExoplayerSingle_Instance.getInstance()
                            .prepareExoPlayer(getActivity(),video,galleryItems);
                }
        }

        @Override
        public int getItemCount() {
            return galleryItems.size();
        }

       

        private class Viewholder extends RecyclerView.ViewHolder {
            public Viewholder(@NonNull View itemView) {
                super(itemView);
                video = itemView.findViewById(R.id.onclickMediaViewer_Video);

            }
        }
    }

}

ExoplayerSingle_Instance Class

public class ExoplayerSingle_Instance {

    private static final String TAG = "### Exoplayer ###";

    private static ExoplayerSingle_Instance instance;
    private ExoPlayer player;
    private boolean isPlayerPlaying;

    private ExoplayerSingle_Instance() {
        // Private constructor to prevent instantiation
    }

    public static synchronized ExoplayerSingle_Instance getInstance() {
        if (instance == null) {
            instance = new ExoplayerSingle_Instance();
        }
        return instance;
    }

    public void prepareExoPlayer(Context context, PlayerView exoPlayerView, ArrayList<String> videoUri) {
        if (context == null || exoPlayerView == null) {
            return;
        }

        if (player == null) {
            player = new ExoPlayer.Builder(context).build();
        }
        exoPlayerView.setPlayer(player);
        player.clearMediaItems();
        List<MediaItem> mediaItem = new ArrayList<>();
        for (String s: videoUri){
           mediaItem.add(MediaItem.fromUri(s));
        }
        player.setMediaItems(mediaItem);
        player.prepare();
        player.play();

    }

    public void stopPlayback() {
        if (player != null) {
            player.setPlayWhenReady(false);
            player.stop(); // Reset the player without releasing it
        }
    }

    public void goToBackground() {
        if (player != null) {
            isPlayerPlaying = player.getPlayWhenReady();
            player.setPlayWhenReady(false);
        }
    }

    public void goToForeground() {
        if (player != null) {
            player.setPlayWhenReady(isPlayerPlaying);
        }
    }

    public void releaseVideoPlayer() {
        if (player != null) {
            player.release();
        }
        player = null;
    }

    public boolean playerHasItem(){
        if (player != null && player.getMediaItemCount() > 0){
            Log.d(TAG, "playerHasItem: "+player.getMediaItemCount());
            return true;
        }
        return false;
    }

    public void playItemAtPosition(int position) {
        if (player != null) {
            player.seekTo(position, C.TIME_UNSET);
            player.play();
        }
    }

Problem

  • When swiping left/right item in the RecyclerView, only the audio plays without video or controls.
  • Using seekTo method of ExoPlayer to move to the next/previous video which move but does not display video controls.

Tried Solutions

  • Looked at official documentation and StackOverflow, but no valid solution found.

Expected Behavior

  • On swiping to the next/previous item in the RecyclerView, the video and its controls should be visible and work properly as i can see with exoplayer previous/next buttons .

Actual Behavior

  • Only audio plays on swiping, and the video controls are not visible.

Environment

  • Android Studio
  • implementation "androidx.media3:media3-exoplayer:1.1.1"
  • implementation "androidx.media3:media3-exoplayer-dash:1.1.1"
  • implementation "androidx.media3:media3-ui:1.1.1"

What would be the solution to this issue?

varun7952 avatar Aug 02 '24 01:08 varun7952

@varun7952,

Thanks for reporting! Could you please send the bug report to [email protected]?

tianyif avatar Aug 02 '24 16:08 tianyif

@varun7952,

Thanks for reporting! Could you please send the bug report to [email protected]?

I am not sure whether its bug or issue with my code. Could you please let me know that my code is ok and its a bug?

varun7952 avatar Aug 02 '24 20:08 varun7952

@varun7952, we can't give 1:1 support for solving app specific issues. In the absence of bug report, we don't know it's a problem with our library neither, thus we cannot provide helpful answers I'm afraid.

tianyif avatar Aug 02 '24 21:08 tianyif

@varun7952, we can't give 1:1 support for solving app specific issues. In the absence of bug report, we don't know it's a problem with our library neither, thus we cannot provide helpful answers I'm afraid.

Yes not asking for the 1:1 support but exoplayer in recyclerview is the common use for applications, just need to know if i am doing anything wrong in changing media by seekTo option of exoplayer or there is any other method i can use to change next/previous track in my case

varun7952 avatar Aug 02 '24 22:08 varun7952

@varun7952,

  1. seekTo is a proper approach here, so you don't have to worry about that
  2. The fact that the audio is playing indicates that, at least on the Player side, the playback works as expected
  3. Missing video + controls means that rendering to the surface is the issue

I think the root cause could hide somewhere in your prepareExoPlayer method. It seems to be doing a lot and I don't know if all of those actions are needed in onBindViewHolder().

You might want to try: https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java#L608-L609

Alternatively, add more logging around exoPlayerView.setPlayer(player) to see what the state of the player is at that point and if any previous views still have reference to the player?

Perhaps your private class Viewholder extends RecyclerView.ViewHolder could also be modified:

  • private class Viewholder extends RecyclerView.ViewHolder implements View.OnAttachStateChangeListener
  • itemView.addOnAttachStateChangeListener(this)
  • implement onViewAttachedToWindow and onViewDetachedFromWindow to setup and release the Player properly

oceanjules avatar Aug 13 '24 21:08 oceanjules

@varun7952,

1. `seekTo` is a proper approach here, so you don't have to worry about that

2. The fact that the audio is playing indicates that, at least on the `Player` side, the playback works as expected

3. Missing video + controls means that rendering to the surface is the issue

I think the root cause could hide somewhere in your prepareExoPlayer method. It seems to be doing a lot and I don't know if all of those actions are needed in onBindViewHolder().

You might want to try:

https://github.com/androidx/media/blob/b01c6ffcb3fca3d038476dab5d3bc9c9f2010781/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java#L608-L609

Alternatively, add more logging around exoPlayerView.setPlayer(player) to see what the state of the player is at that point and if any previous views still have reference to the player?

Perhaps your private class Viewholder extends RecyclerView.ViewHolder could also be modified:

* `private class Viewholder extends RecyclerView.ViewHolder implements View.OnAttachStateChangeListener`

* `itemView.addOnAttachStateChangeListener(this)`

* implement `onViewAttachedToWindow` and `onViewDetachedFromWindow` to setup and release the `Player` properly

Thanks for the suggestions and pointing things out. i will test these and update with my code and log again.

varun7952 avatar Aug 18 '24 07:08 varun7952

Hey @varun7952. We need more information to resolve this issue but there hasn't been an update in 14 weekdays. I'm marking the issue as stale and if there are no new updates in the next 7 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

google-oss-bot avatar Sep 05 '24 01:09 google-oss-bot

Since there haven't been any recent updates here, I am going to close this issue.

@varun7952 if you're still experiencing this problem and want to continue the discussion just leave a comment here and we are happy to re-open this.

google-oss-bot avatar Sep 16 '24 01:09 google-oss-bot