Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stopping Exoplayer onSwipe of ViewPager

I am using a ViewPager with single fragment instance in which I am showing Media files like Images, Videos, Audio.

I have implemented ExoPlayer for handling Video & Audio files. And Glide for images.

To avoid the memory leaks I am releasing the ExoPlayer object like this in ItemViewerFragment.java :

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

    @Override
    public void onStart() {
        super.onStart();
        if (player == null && currentGalleryModel != null) {
            initializePlayer();
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (player == null && currentGalleryModel != null) {
            initializePlayer();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        releasePlayer();
    }

    @Override
    public void onStop() {
        super.onStop();
        releasePlayer();
    }

And in onViewCreated() I am initializing view like this :

 @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        if (currentGalleryModel.isVideo() || currentGalleryModel.isAudio()) {

            simpleExoPlayerView.setVisibility(View.VISIBLE);
            imageView.setVisibility(View.GONE);

            initializePlayer();
        } else if (currentGalleryModel.isImage() || currentGalleryModel.isGif()) {
            simpleExoPlayerView.setVisibility(View.GONE);
            imageView.setVisibility(View.VISIBLE);


            Glide.with(this)
                    .load(currentGalleryModel.getFilePath())
                    .placeholder(android.R.color.black)
                    .fitCenter()
                    .diskCacheStrategy(DiskCacheStrategy.NONE)
                    .into(imageView);

        }
    }

I am using FragmentStatePagerAdapeter. This is the getItem method :

@Override
public Fragment getItem(int position) {
    return ItemViewerFragment.newInstance(mItems.get(position));
}

I am not able to detect the onPause of the fragment on first swipe of Viewpager. On second swipe video/audio files are stopped playing.

In activity I have tried adding .addOnPageChangeListener :

@Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

     try {
        ((ItemViewerFragment)mAdapter.getItem(mPreviousPos)).imHiddenNow();
        } catch (Exception e) {
            e.printStackTrace();
        }
        mPreviousPos = position;
    }

And in ItemViewerFragment.java :

public void imHiddenNow(){
      releasePlayer();
    }

Still video/audio keeps on playing.

Here is a Video Link to the Screencast.

Demo project GitHub link.

like image 980
Nilesh Deokar Avatar asked Oct 11 '17 09:10

Nilesh Deokar


People also ask

How do I turn off Exoplayer on Android?

Calls Player. stop() or Player. stop(boolean) .

When should I use ViewPager?

ViewPager in Android is a class that allows the user to flip left and right through pages of data. This class provides the functionality to flip pages in app. It is a widget found in the support library. To use it you'll have to put the element inside your XML layout file that'll contain multiple child views.

How the ViewPager is implemented?

Implement Swipe Views You can create swipe views using AndroidX's ViewPager widget. To use ViewPager and tabs, you need to add a dependency on ViewPager and on Material Components to your project. To insert child views that represent each page, you need to hook this layout to a PagerAdapter .

Can we add activity in ViewPager?

You can't use ViewPager to swipe between Activities . You need to convert each of you five Activities into Fragments , then combine everything in one FragmentActivity with the Adapter you use with ViewPager .


4 Answers

I followed an approach of maintaining HashMap of fragment objects inside PagerAdapter

  1. Declare an interface :
interface FragmentLifecycle {
    void onPauseFragment()
}
  1. Implement an interface in Fragment.
public void onPauseFragment() {
    if (simpleExoPlayer != null){
        simpleExoPlayer.setPlayWhenReady(false);
    }
}
  1. Store all the fragment objects in a HashMap<Integer,Fragment> with there respective positions as a key. Declare hashmap inside PagerAdapter. Also declare one getter method for accessing fragment objects from hashmap. e.g.

    @Override
    public Fragment getItem(int position) {
        ItemViewerFragment fragment = ItemViewerFragment.newInstance(mItems.get(position));
        mFragments.put(position,fragment);
        return fragment;
    }
    

    public ItemViewerFragment getMapItem(int position) { return mFragments.get(position); }

  2. In activity where you have declared viewPager keep one variable currentPosition and implement ViewPager.OnPageChangeListener.

  3. Inside onPageSelected method,

        @Override
        public void onPageSelected(int position) {
            if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
            currentPosition = position;
        }
    
like image 81
Nilesh Deokar Avatar answered Oct 21 '22 14:10

Nilesh Deokar


Edit from the future: you should never hold a reference to Fragment instances directly inside a FragmentPagerAdapter, because it can cause crashes after process death.

Here is the code for the pager adapter:

 class ViewPagerAdapter extends FragmentPagerAdapter {
    private final List<Fragment> mFragmentList = new ArrayList<>();
    private final List<String> mFragmentTitleList = new ArrayList<>();

    public ViewPagerAdapter(FragmentManager manager) {
        super(manager);
    }

    @Override
    public Fragment getItem(int position) {
        return mFragmentList.get(position);
    }

    @Override
    public int getCount() {
        return mFragmentList.size();
    }

    public void addFragment(Fragment fragment, String title) {
        mFragmentList.add(fragment);
        mFragmentTitleList.add(title);
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return mFragmentTitleList.get(position);
    }
}

Here is the scroll Listener:

  viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
          //Stop media here.
        }

        @Override
        public void onPageSelected(int position) {
          //Save your previous position here.
        }

        @Override
        public void onPageScrollStateChanged(int state) {
         
        }
    });

For the media you can use a for Loop and add all the fragments to the list at once and then use this for efficiency :

viewPager.setOffscreenPageLimit(3);

This will make sure only 3 instances of your fragment are available which is enough.

For using single fragment i would suggest you to do it like this:

 public MyFragment() {

}

//This is to send a file to the fragment if you need it.
public static MyFragment newInstance(File file) {
    MyFragment fragment = new MyFragment();
    Bundle bundle = new Bundle();
    bundle.putSerializable("file", file);
    fragment.setArguments(bundle);
    return fragment;
}

Then in the onCreate of Fragment you can retrieve your file like this:

File file;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    file = getArguments().getSerializable("file");
   
}

Now add your fragments to pager like this:

 for (int i = 0; i < totalFiles; i++) {
            viewPagerAdapter.addFragment(MyFragment.newInstance(fileList.get(i));
        }

Hope this helps.

like image 23
Sarthak Gandhi Avatar answered Oct 21 '22 15:10

Sarthak Gandhi


It's been more then a year since I used Exoplayer & I kinda tackled a similar problem. Please note that the APIs have changed a little bit so take the following code just to get an idea on how to implement a potential solution. Please let me know if it doesn't work, I'll look into the APIs further and get back to you.

Coming to the solution:

private int mPlayerCurrentPosition;

private int getCurrentPlayerPosition() {
     return mExoPlayer.getCurrentPosition();
}

// call this from onPause
private void releaseExoplayer() {
     mPlayerCurrentPosition = getPlayerCurrentPosition();
     mExoPlayer.setPlayWhenReady(false);
     mExoPlayer.release(); // this will make the player object eligible for GC
}

private void resumePlaybackFromPreviousPosition(int prevPosition) {
    mExoPlayer.seekTo(mPlayerCurrentPosition );
}
like image 2
Umer Farooq Avatar answered Oct 21 '22 15:10

Umer Farooq


The problem is that onPause and onResume are not called when the fragment visibility changed in ViewPager. The solution is to add 2 visibility events: losingVisibility and gainVisibility.

Why is it a great solution?

Because you keep the framework managing Fragment cache and lifecycle. We just add the callbacks needed to pause and resume our media in our fragment.

Step-by-step:

The below code is just an explanation for my code. Check Step*.java classes to see full implementation.

  1. Create losingVisibility and gainVisibility methods in YourFragment.java:

    public class YourFragment extends Fragment {
    
        /**
          * This method is only used by viewpager because the viewpager doesn't call onPause after
          * changing the fragment
          */
        public void losingVisibility() {
            // IMPLEMENT YOUR PAUSE CODE HERE
            savePlayerState();
            releasePlayer();
        }
    
        /**
          * This method is only used by viewpager because the viewpager doesn't call onPause after
          * changing the fragment
        */
        public void gainVisibility() {
            // IMPLEMENT YOUR RESUME CODE HERE
            loadVideo();
        }
    }
    
  2. Call losingVisibility and gainVisibility every time a new page is selected (onPageSelected) in YourActivity.java:

    mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
        }
    
        @Override
        public void onPageSelected(int position) {
            YourFragment cachedFragmentLeaving = mYourPagerAdapter.getCachedItem(mCurrentItem);
            if (cachedFragmentLeaving != null) {
                cachedFragmentLeaving.losingVisibility();
            }
            mCurrentItem = position;
            YourFragment cachedFragmentEntering = mYourPagerAdapter.getCachedItem(mCurrentItem);
            if (cachedFragmentEntering != null) {
                cachedFragmentEntering.gainVisibility();
            }
        }
    
        @Override
        public void onPageScrollStateChanged(int state) {
        }
    });
    
  3. Add getCachedItem to YourPagerAdapter.java:

The 3rd step is adding a method to retrieve cached fragments. To do it we must cache a reference to a fragment created (overriding instantiateItem) and release the same reference (overriding destroyItem).

    public class YourPagerAdapter extends FragmentStatePagerAdapter {

            private SparseArray<YourFragment> mFragmentsHolded = new SparseArray<>();


        @NonNull
        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Object fragment = super.instantiateItem(container, position);
            if(fragment instanceof StepFragment) {
                mFragmentsHolded.append(position, (StepFragment) fragment);
            }
            return fragment;
        }

        @Override
        public void destroyItem(ViewGroup container, int position, Object object) {
            mFragmentsHolded.delete(position);
            super.destroyItem(container, position, object);
        }

        public YourFragment getCachedItem(int position) {
            return mFragmentsHolded.get(position, null);
        }
    }
like image 1
Erick M. Sprengel Avatar answered Oct 21 '22 14:10

Erick M. Sprengel