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
- Declare an interface :
interface FragmentLifecycle {
void onPauseFragment()
}
- Implement an interface in Fragment.
public void onPauseFragment() {
if (simpleExoPlayer != null){
simpleExoPlayer.setPlayWhenReady(false);
}
}
Store all the fragment objects in a
HashMap<Integer,Fragment>
with there respective positions as a key. Declare hashmap insidePagerAdapter
. 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); }
In activity where you have declared
viewPager
keep one variablecurrentPosition
and implementViewPager.OnPageChangeListener
.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.
Create
losingVisibility
andgainVisibility
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(); } }
Call
losingVisibility
andgainVisibility
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) { } });
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 (overridingdestroyItem
).
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.