ExoPlayer icon indicating copy to clipboard operation
ExoPlayer copied to clipboard

How to use ExoPlayer in a ListvVew or RecyclerView?

Open NatsumeReiko opened this issue 10 years ago • 76 comments

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!

NatsumeReiko avatar Oct 15 '15 09:10 NatsumeReiko

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.

RikHeijdens avatar Oct 15 '15 15:10 RikHeijdens

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.

NatsumeReiko avatar Oct 16 '15 01:10 NatsumeReiko

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.

RikHeijdens avatar Oct 16 '15 01:10 RikHeijdens

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

chodison avatar Oct 16 '15 01:10 chodison

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.

RikHeijdens avatar Oct 16 '15 02:10 RikHeijdens

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

chodison avatar Oct 16 '15 02:10 chodison

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 avatar Oct 16 '15 02:10 NatsumeReiko

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

}

NatsumeReiko avatar Oct 19 '15 06:10 NatsumeReiko

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

qqli007 avatar Nov 19 '15 10:11 qqli007

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?

jayshah123 avatar Apr 30 '16 12:04 jayshah123

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

I hope that can help you

xingstarx avatar Dec 05 '16 09:12 xingstarx

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

mufumbo avatar Dec 16 '16 13:12 mufumbo

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

escamoteur avatar Mar 29 '17 18:03 escamoteur

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

artworkad avatar May 28 '17 21:05 artworkad

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.

Sandeeppal1083 avatar Jun 01 '17 08:06 Sandeeppal1083

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.

gregkorossy avatar Jul 05 '17 01:07 gregkorossy

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

eneim avatar Aug 07 '17 00:08 eneim

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.

mpainenz avatar Nov 13 '17 00:11 mpainenz

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

eneim avatar Nov 13 '17 00:11 eneim

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 avatar Nov 20 '17 20:11 mpainenz

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.

mpainenz avatar Nov 23 '17 01:11 mpainenz

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

https://github.com/eneim/toro/issues/286

dishantkawatra avatar Jan 31 '18 11:01 dishantkawatra

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

sandeepyohans avatar Jun 18 '18 08:06 sandeepyohans

Please download BeautyKingdom application then know how to exoplayer is playing if you interested then reply me I will give you code

On Mon 18 Jun, 2018, 2:03 PM Sandeep Yohans, [email protected] wrote:

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 https://github.com/ojw28 @andrewlewis https://github.com/andrewlewis

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/google/ExoPlayer/issues/867#issuecomment-397980844, or mute the thread https://github.com/notifications/unsubscribe-auth/Abbnyos4nyfJpce5DVbuw8Le23OCZ23Kks5t92XcgaJpZM4GPU6m .

dishantkawatra avatar Jun 22 '18 15:06 dishantkawatra

@dishantkawatra Sure, will do that.

sandeepyohans avatar Jun 25 '18 08:06 sandeepyohans

@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

sandeepyohans avatar Jun 25 '18 10:06 sandeepyohans

Hi,

ok i will update you with code how to play a video in next activity with exoplayer.

Thanks Dishant Kawatra Android Developer

On Mon, Jun 25, 2018 at 4:13 PM Sandeep Yohans [email protected] wrote:

@dishantkawatra https://github.com/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. [image: screenshot_1529923322] https://user-images.githubusercontent.com/3971485/41845881-a6552b20-7892-11e8-8cfc-a408fc5fe119.png

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/google/ExoPlayer/issues/867#issuecomment-399909454, or mute the thread https://github.com/notifications/unsubscribe-auth/Abbnyu00QOV4NKgabMA-4tqNVeIhahN-ks5uAL7mgaJpZM4GPU6m .

dishantkawatra avatar Jun 27 '18 04:06 dishantkawatra

Thanks @dishantkawatra, your help is much appreciated!

sandeepyohans avatar Jun 28 '18 04:06 sandeepyohans

@dishantkawatra could you give that source code to me?

herotha-sompom avatar Jul 24 '18 10:07 herotha-sompom

Sure, I give you code Tommorow

On Tue 24 Jul, 2018, 4:28 PM herotha-sompom, [email protected] 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 https://github.com/google/ExoPlayer/issues/867#issuecomment-407366038, or mute the thread https://github.com/notifications/unsubscribe-auth/AbbnyhlnjIwGUcizHO6vCkMJRwKyJzLYks5uJv3GgaJpZM4GPU6m .

dishantkawatra avatar Jul 24 '18 17:07 dishantkawatra