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.
Calls Player. stop() or Player. stop(boolean) .
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.
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 .
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 .
I followed an approach of maintaining HashMap
of fragment objects inside PagerAdapter
interface FragmentLifecycle {
void onPauseFragment()
}
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 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); }
In activity where you have declared viewPager
keep one variable currentPosition
and implement ViewPager.OnPageChangeListener
.
Inside onPageSelected
method,
@Override
public void onPageSelected(int position) {
if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
currentPosition = position;
}
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.
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 );
}
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
.
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.
The below code is just an explanation for my code. Check Step*.java classes to see full implementation.
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();
}
}
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) {
}
});
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);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With