androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.73k stars 415 forks source link

ExoPlayer in RecyclerView Not Synchronizing with Swipe. #1587

Closed varun7952 closed 2 months ago

varun7952 commented 3 months ago

I have created a single instance of ExoPlayerto 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 seekTomethod 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

Tried Solutions

Expected Behavior

Actual Behavior

Environment

What would be the solution to this issue?

tianyif commented 3 months ago

@varun7952,

Thanks for reporting! Could you please send the bug report to android-media-github@google.com?

varun7952 commented 3 months ago

@varun7952,

Thanks for reporting! Could you please send the bug report to android-media-github@google.com?

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?

tianyif commented 3 months ago

@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.

varun7952 commented 3 months ago

@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

oceanjules commented 3 months ago

@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:

varun7952 commented 3 months ago

@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.

google-oss-bot commented 2 months ago

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 commented 2 months ago

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.