Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use ExoPlayer in a ListvVew or RecyclerView? #867

Open
NatsumeReiko opened this issue Oct 15, 2015 · 47 comments

Comments

Projects
None yet
@NatsumeReiko
Copy link

commented Oct 15, 2015

I want to use ExoPlayer in a RecyclerView as a part of row item.
I want to make a customer view and wrap the ExoPlayer in that view.

Do you have some advice?

Thank you!

@RikHeijdens

This comment has been minimized.

Copy link
Contributor

commented Oct 15, 2015

You should keep track of a reference to the ExoPlayer and try to reuse the player for every item in the ListView by just loading new media on the player.

@NatsumeReiko

This comment has been minimized.

Copy link
Author

commented Oct 16, 2015

Thank you RikHeijdens.

Do you mean, I should make only one instance of ExoPlayer and switch the video link at properly timing?

What should I do, If I want to play two or three videos at the same time, because the height of the surfaceView is fixed and how many rows will be showed on the screen at the same depends on the height of the devices.

@RikHeijdens

This comment has been minimized.

Copy link
Contributor

commented Oct 16, 2015

If you want to play multiple video's, for instance two at the same time you should instantiate 2 ExoPlayers, with 2 different SurfaceViews. And yes you switch the video as soon as the 'recycled' item moves out of the screen.

@chodison

This comment has been minimized.

Copy link

commented Oct 16, 2015

@RikHeijdens Supports two hard decoding at the same time for the device?

@RikHeijdens

This comment has been minimized.

Copy link
Contributor

commented Oct 16, 2015

On my Nexus 5 I can play up to 6 video's at the same time, however you don't want to instantiate so many players because the performance will degrade pretty quickly after two instances.

@chodison

This comment has been minimized.

Copy link

commented Oct 16, 2015

BTW: In this case, not all devices are supported.

@NatsumeReiko

This comment has been minimized.

Copy link
Author

commented Oct 16, 2015

Thank you for your advice, I decided to try to use only one ExoPlayer. And I will upload some sample code, and hope get more advice.

@NatsumeReiko NatsumeReiko changed the title How to use ExoPlayer in a ListvVew or RecyclerView How to use ExoPlayer in a ListvVew or RecyclerView? Oct 16, 2015

@NatsumeReiko

This comment has been minimized.

Copy link
Author

commented Oct 19, 2015

Here is my source code, although it doesn't work very well, but I think it's the right way to do this.
And hoping and appreciating get more advice to complete this.

In this customized RecyclerView(ExoPlayerVideoRecyclerView), I make only one SurfaceView for video play, and add to the row root view and remove it every time I need.

At this time I got these 3 problems.

  1. The ratio of the video is not correct. The height of the video surface view is fiexed to 200dp, and the width is match the device.
  2. I want to show a progress bar before real play, and how can I set the listener to get the play start event.
  3. I keep getting this alarm: E/OMXMaster: A component of name 'OMX.qcom.audio.decoder.aac' already exists, ignoring this one.

About problem 1, I tried this code to reset the ration, but it doesn't seem work.

MediaCodecVideoTrackRenderer.EventListener

    @Override
    public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
        if (videoFrame != null) {
            videoFrame.setAspectRatio(
                    height == 0 ? 1 : (width * pixelWidthHeightRatio) / height);
        }
    }

From here is the code abstracted form next sample:
https://github.com/NatsumeReiko/ExoPlayerInRecyclerView

public class ExoPlayerVideoRecyclerView extends RecyclerView
        implements AudioCapabilitiesReceiver.Listener, MediaCodecVideoTrackRenderer.EventListener,
        SurfaceHolder.Callback {

    public ExoPlayerVideoRecyclerView(Context context) {
        super(context);
        initialize(context);
    }

    private void initialize(Context context) {
        mainHandler = new Handler();

        appContext = context.getApplicationContext();

        allocator = new DefaultAllocator(BUFFER_SEGMENT_SIZE);
        dataSource =
                new DefaultUriDataSource(appContext,
                        new DefaultBandwidthMeter(mainHandler, null),
                        Util.getUserAgent(appContext, "ExoPlayerDemo"));


        videoSurfaceView = new SurfaceView(appContext);

        videoSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                        getResources().getDimension(R.dimen.exoplayer_video_height)
                        , getResources().getDisplayMetrics())));

        videoSurfaceView.getHolder().addCallback(this);

        CookieHandler currentHandler = CookieHandler.getDefault();
        if (currentHandler != defaultCookieManager) {
            CookieHandler.setDefault(defaultCookieManager);
        }

        audioCapabilitiesReceiver = new AudioCapabilitiesReceiver(appContext, this);
        audioCapabilitiesReceiver.register();

        player = ExoPlayer.Factory.newInstance(2);
        player.addListener(new ExoPlayer.Listener() {
            @Override
            public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
                switch (playbackState) {
                    case ExoPlayer.STATE_BUFFERING:
                        break;
                    case ExoPlayer.STATE_ENDED:
                        player.seekTo(0);
                        break;
                    case ExoPlayer.STATE_IDLE:
                        break;
                    case ExoPlayer.STATE_PREPARING:
                        break;
                    case ExoPlayer.STATE_READY:
                        break;
                    default:
                        break;
                }
            }

            @Override
            public void onPlayWhenReadyCommitted() {
            }

            @Override
            public void onPlayerError(ExoPlaybackException error) {
            }
        });


        addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {

                    play(getPlayTargetPosition());
                }
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
            }
        });
    }

    private void preparePlayer(int position) {

        Uri uri = Uri.parse(videoInfoList.get(position).videoUrl);

        // Build the sample source
        sampleSource =
                new ExtractorSampleSource(uri, dataSource, allocator, 10 * BUFFER_SEGMENT_SIZE);

        // Build the track renderers
        videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
                MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, -1, mainHandler, this, -1);
        audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);

        // Build the ExoPlayer and start playback
        player.prepare(videoRenderer, audioRenderer);

        playVideo();
    }

    //method to really do the play
    private void playVideo() {
        if (surfaceViewViable) {
            player.sendMessage(videoRenderer,
                    MediaCodecVideoTrackRenderer.MSG_SET_SURFACE,
                    videoSurfaceView.getHolder().getSurface());
            player.setPlayWhenReady(true);
        }
    }

        private void releasePlayer() {
        if (player != null) {
            player.release();
            player = null;
        }
    }

    private void removeVideoView(SurfaceView videoView) {

        ViewGroup parent = (ViewGroup) videoView.getParent();

        if (parent == null) {
            return;
        }

        int index = parent.indexOfChild(videoView);
        if (index >= 0) {
            parent.removeViewAt(index);
        }

    }

    private void play(int position) {
        if (position == playPosition) {
            return;
        }

        playPosition = position;
        removeVideoView(videoSurfaceView);

        // get target View position in RecyclerView
        int at = position - ((LinearLayoutManager) getLayoutManager()).findFirstVisibleItemPosition();

        View child = getChildAt(at);
        if (child == null) {
            return;
        }

        ExoPlayerVideoRecyclerViewAdapter.VideoViewHolder holder
                = (ExoPlayerVideoRecyclerViewAdapter.VideoViewHolder) child.getTag();
        if (holder == null) {
            playPosition = DEFAULT_PLAY_POSITION;
            return;
        }
        holder.videoContainer.addView(videoSurfaceView);
        videoFrame = holder.videoContainer;

        preparePlayer(playPosition);
    }
}


public class ExoPlayerVideoRecyclerViewAdapter
        extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

        public ExoPlayerVideoRecyclerViewAdapter(Context appContext, List<VideoInfo> videoInfoList) {
        this.videoInfoList = videoInfoList;
        inflater = LayoutInflater.from(appContext.getApplicationContext());

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new VideoViewHolder(inflater
                .inflate(R.layout.exoplayer_recycler_view_row, parent, false));

    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof VideoViewHolder) {
            setVideoViewHolder((VideoViewHolder) holder);
        }
    }

    private void setVideoViewHolder(VideoViewHolder holder) {
        holder.parent.setTag(holder);
    }

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

    public static class VideoViewHolder extends RecyclerView.ViewHolder {

        AspectRatioFrameLayout videoContainer;
        View parent;

        public VideoViewHolder(View v) {
            super(v);
            parent = v;
            videoContainer = (AspectRatioFrameLayout) v.findViewById(R.id.video_frame);
        }
    }

    public void onRelease() {
        if (videoInfoList != null) {
            videoInfoList.clear();
            videoInfoList = null;
        }
    }

}

@qqli007

This comment has been minimized.

Copy link

commented Nov 19, 2015

@NatsumeReiko ,how to goto fullscreen when click the item of the listview? Do you have any idea?

@jayshah123

This comment has been minimized.

Copy link

commented Apr 30, 2016

I have a use case (for multiple exoplayers) where there can be TextureViews in each page of viewpager, minimum 3 exoplayers would be allocated, one per textureview(in each page). Although I play only a single exoplayer at a time(the one belonging to textureview of focused page) and rest are in paused state,
how does bandwidth/performance etc. get affected ?
(Assume I am using simple MP4 over http - no adaptive streaming)
What would be best practice in such cases?

@xingstarx

This comment has been minimized.

Copy link

commented Dec 5, 2016

@jayshah123 I think you could see this repo https://github.com/xingstarx/InkeVerticalViewPagerLive

I hope that can help you

@mufumbo

This comment has been minimized.

Copy link

commented Dec 16, 2016

@NatsumeReiko that's an interesting solution, but if you move the SurfaceView to another container, you would lose the paused state thumbnail if you need to resume the video while the user scroll. Do you think reusing surfaceView is much more performant than just reusing the player?

@escamoteur

This comment has been minimized.

Copy link

commented Mar 29, 2017

I'm facing almost the same challenge.

So I would like to know if it is feasable to use multiple SurfaceViews in a ListView and just switch the Player between them or is it better to reuse the same surface when I need it?

Thanks
Thomas

@artjomzab

This comment has been minimized.

Copy link

commented May 28, 2017

Check out https://github.com/eneim/Toro

@Sandeeppal1083

This comment has been minimized.

Copy link

commented Jun 1, 2017

NatsumReiko or anyone , please help me in playing Hls Video inside recyclerview , i want to play Hls videos inside recyclerview, Please help me with code, i would be thankful for you sooo much.

@google google deleted a comment from michalliu Jun 19, 2017

@ojw28 ojw28 added the question label Jun 24, 2017

@Gericop

This comment has been minimized.

Copy link

commented Jul 5, 2017

I am facing the same problem with RecyclerView using ExoPlayer 2. I have multiple items representing either an audio or a video player and there might be an unknown number of items (probably more than 1) visible at the same time.
I implemented the player reuse by delegating it to a service so it can keep playing in the background too, which is good. My main problem is that the SimpleExoPlayerView has a lot of restrictions based on whether the player is set, such as it won't display controls nor album artwork if the player is null, and the class is final so I cannot just override what I want... I have the video thumbnails separately available, so it shouldn't be a problem, but now I have to use a separate image view item to show them on top of the player view. Also, I have to display a separate play button on top of these in order to set the player to the current player view and start playing the media.

Another problem I noticed with the player instance reuse is that if 2 items are visible at the same time, item A is playing, and I start playing item B, then item A will be "reset" to a default state with the total time set to 00:00 and the current "thumbnail" image (video) removed along with the controllers, which seems like a really bad UX.

My question is: what are the performance / networking / bandwidth effects of using more than 1 players, but playing only one at the same time (it would use some kind of an "obtain" mechanism by creating new player if there isn't an available one)? So if the user starts playing item B by pressing the play button, item A would pause. I'm afraid that pausing the players would not be enough because they would still keep buffering / holding data, but stopping them resets the current state, which means back to square one.

@eneim

This comment has been minimized.

Copy link
Contributor

commented Aug 7, 2017

Thank @ArtworkAD for mentioning my library :D. FYI @Gericop I have been struggling a long time with the same idea with you. I finally end up with the belief that ExoPlayer instance will not consume your CPU and much of your network as long as you don't ask it to (= calling player.setPlayWhenReady(true)). It may start fetching some meta data at preparing, but it should be fine with just that.

Having a Singleton Player will be scary (good for performance though). You can learn from how Youtube Player API doing so (closed source, yep, but enough study may turn to something I guess).

@mpainenz

This comment has been minimized.

Copy link

commented Nov 13, 2017

Your Toro library @eneim suffers from thread locking and slow performance when scrolling the recycler. Particularly when you fling the RecyclerView.

In my own testing, I have found that using a single player instance is much better. It removed all my issues with thread locking. I guess the players are contending for Codec access, or some other issue is occuring. This happens even if only one player is playing at a time.

The best approach I've found is to:

  1. Generate your own thumbnails. In my case, I am downloading the MP4's I want to play to disk, and using the ThumbnailUtils.createVideoThumbnail function to store the Thumbnail to disk, and display an ImageView in the RecyclerView.

  2. In each ViewHolder, store a separate MediaSource object, and create it during Bind, and release it during the Recycle event.

  3. In each ViewHolder layout, use a separate SimpleExoPlayerView object with no SimpleExoPlayer attached during Bind Time.

  4. Hold a single instance of a SimpleExoPlayer in the Adapter, or somewhere else in your project.

  5. Listen to the RecyclerView's LayoutManager OnScroll event, and in each scroll event, work out which item is in focus and needs to be in a play state. Prepare the player, attach the SimpleExoPlayer to the ViewHolders SimpleExoPlayerView and hide the Thumbnail overlay.

This method is working very well for me, I get a very fast experience.

@eneim

This comment has been minimized.

Copy link
Contributor

commented Nov 13, 2017

You are right about the issue of having multi ExoPlayer instance in the library. Lately I also investigate in the case of using Single/Limited ExoPlayer instances. Your approach gonna be so much helpful. (It turns out that, to make it highly abstraction and easy to integrate, many works need to be done).

@mpainenz

This comment has been minimized.

Copy link

commented Nov 20, 2017

I have also found that you can further increase performance by using a TextureView instead of SimpleExoPlayerView, and only create one TextureView object instead of one per viewholder.

In your Adapter or Activity, store a single TextureView object, and pass it to the ViewHolder that is playing at runtime. Reuse the same TextureView item for each viewholder that is playing.

I cannot believe how much smoother my application runs this way.

@mpainenz

This comment has been minimized.

Copy link

commented Nov 23, 2017

Just an update to TextureView re-use in RecyclerView, it seems to be quite buggy when removing a TextureView from it's parent and moving it to a new View.

If you want to get that working, the better approach is to hold the TextureView inside the Activity view, and overlay the recyclerview on top. It's a difficult approach, but if you move the TextureView between parents, it seems to end up displaying a black screen on resize or program resume.

It's actually still quite fast to have a TextureView for each Viewholder, so that seems to be a simpler option.

@dishantkawatra

This comment has been minimized.

Copy link

commented Jan 31, 2018

Hi, when i switch the exoplayer from recycler view to a dialog with the help of getplayer() and setplayer() method then frames are hang for some sec and sometimes audio is audible but frames are not see please tell me how to resolve this issue with toro

eneim/toro#286

@sandeepyohans

This comment has been minimized.

Copy link

commented Jun 18, 2018

It's this very old issue which is still open. Is there a proper solution for using ExoPlayer is ListView or RecyclerView? A working sample would be great help for a beginner like me. @ojw28 @andrewlewis

@dishantkawatra

This comment has been minimized.

Copy link

commented Jun 22, 2018

@sandeepyohans

This comment has been minimized.

Copy link

commented Jun 25, 2018

@dishantkawatra Sure, will do that.

@sandeepyohans

This comment has been minimized.

Copy link

commented Jun 25, 2018

@dishantkawatra I checked the layout, looks nice. Seems you are displaying a list of thumbnails and on onClick event playing the video in new Activity. I had the thought of doing the same.
screenshot_1529923322

@dishantkawatra

This comment has been minimized.

Copy link

commented Jun 27, 2018

@sandeepyohans

This comment has been minimized.

Copy link

commented Jun 28, 2018

Thanks @dishantkawatra, your help is much appreciated!

@herotha-sompom

This comment has been minimized.

Copy link

commented Jul 24, 2018

@dishantkawatra could you give that source code to me?

@dishantkawatra

This comment has been minimized.

Copy link

commented Jul 24, 2018

@herotha-sompom

This comment has been minimized.

Copy link

commented Jul 25, 2018

@dishantkawatra thanks for your help

@sandeepyohans

This comment has been minimized.

Copy link

commented Jul 25, 2018

@dishantkawatra I am still waiting for your reply.

@herotha-sompom

This comment has been minimized.

Copy link

commented Jul 26, 2018

@dishantkawatra do you send the source code to me yet?

@dishantkawatra

This comment has been minimized.

Copy link

commented Jul 31, 2018

@dishantkawatra

This comment has been minimized.

Copy link

commented Jul 31, 2018

@adapana

This comment has been minimized.

Copy link

commented Sep 29, 2018

@mpainenz

Your Toro library @eneim suffers from thread locking and slow performance when scrolling the recycler. Particularly when you fling the RecyclerView.

In my own testing, I have found that using a single player instance is much better. It removed all my issues with thread locking. I guess the players are contending for Codec access, or some other issue is occuring. This happens even if only one player is playing at a time.

The best approach I've found is to:

  1. Generate your own thumbnails. In my case, I am downloading the MP4's I want to play to disk, and using the ThumbnailUtils.createVideoThumbnail function to store the Thumbnail to disk, and display an ImageView in the RecyclerView.
  2. In each ViewHolder, store a separate MediaSource object, and create it during Bind, and release it during the Recycle event.
  3. In each ViewHolder layout, use a separate SimpleExoPlayerView object with no SimpleExoPlayer attached during Bind Time.
  4. Hold a single instance of a SimpleExoPlayer in the Adapter, or somewhere else in your project.
  5. Listen to the RecyclerView's LayoutManager OnScroll event, and in each scroll event, work out which item is in focus and needs to be in a play state. Prepare the player, attach the SimpleExoPlayer to the ViewHolders SimpleExoPlayerView and hide the Thumbnail overlay.

This method is working very well for me, I get a very fast experience.

Can you provide code for this? I tried but could not manage to make it fully working.

@sanketmthakare

This comment has been minimized.

Copy link

commented Sep 29, 2018

@mpainenz Can you provide code for this? I tried but could not manage to make it fully working.

@sanketmthakare

This comment has been minimized.

Copy link

commented Oct 1, 2018

You should keep track of a reference to the ExoPlayer and try to reuse the player for every item in the ListView by just loading new media on the player.

How can i achieve this? Whenever i create instance in adapter constructor and reuse it, recycle view row became blank.

@droidwave

This comment has been minimized.

Copy link

commented Dec 3, 2018

ExoPlayer in RecyclerView, First we have to write a custom component for ExoPlayer. Get a sample app and source code here (Complete Solution )
https://androidwave.com/exoplayer-in-recyclerview-in-android/

@mpainenz

This comment has been minimized.

Copy link

commented Dec 12, 2018

I've improved apon my initial solutions to this, and now just use a single PlayerView but move it around in the view hierarchy. Much simpler.

@sanketmthakare

This comment has been minimized.

Copy link

commented Dec 13, 2018

@CatalystNZ

This comment has been minimized.

Copy link

commented Dec 19, 2018

Hi @sanketmthakare, if you post a specific question on stackoverflow with a bounty, I can provide code for you. You would need to be quite specific about how you want your layout displayed, and the behavior you want. It's recommended not to play video while scrolling, and not to play video while the Player is being resized.

Try to provide as much information as possible if you, or anyone else, needs help.

@sanketmthakare

This comment has been minimized.

Copy link

commented Dec 26, 2018

@dishantkawatra

This comment has been minimized.

Copy link

commented Dec 27, 2018

Hello sanket

Please use different toro helper it solves your problem
Thanks
Dishant Kawatra

@CatalystNZ

This comment has been minimized.

Copy link

commented Jan 2, 2019

Be sure to try using TextureView instead of Surface View. Often a black flash on the screen occurs because of the underlying surfaceview.

<com.google.android.exoplayer2.ui.PlayerView android:id="@+id/player_view" app:surface_type="texture_view" android:layout_width="match_parent" android:layout_height="match_parent" />

@xesun

This comment has been minimized.

Copy link

commented Feb 18, 2019

Sure, I give you code Tommorow

On Tue 24 Jul, 2018, 4:28 PM herotha-sompom, @.***> wrote: @dishantkawatra https://github.com/dishantkawatra could you give that source code to me? — You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub <#867 (comment)>, or mute the thread https://github.com/notifications/unsubscribe-auth/AbbnyhlnjIwGUcizHO6vCkMJRwKyJzLYks5uJv3GgaJpZM4GPU6m .

@dishantkawatra could you give that source code to me?

@eneim

This comment has been minimized.

Copy link
Contributor

commented Apr 9, 2019

For those who are interested in this topic, this is what I archive lately: reddit article. If you are curious about it, a feature request/issue or just a normal comment is welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.