Stopping Exoplayer onSwipe of ViewPager

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

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

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

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.