在 ViewPager 的滑动上停止 Exoplayer
Stopping Exoplayer onSwipe of ViewPager
我正在使用带有单个片段实例的 ViewPager
,我在其中显示 Media
文件,例如图像、视频、音频。
我已经实现了 ExoPlayer
来处理 Video
& Audio
文件。和 Glide
用于图像。
为了避免内存泄漏,我在 ItemViewerFragment.java
中像这样释放 ExoPlayer
对象:
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();
}
在 onViewCreated()
中,我正在初始化视图,如下所示:
@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);
}
}
我正在使用 FragmentStatePagerAdapeter
。这是 getItem
方法:
@Override
public Fragment getItem(int position) {
return ItemViewerFragment.newInstance(mItems.get(position));
}
我无法在第一次滑动 Viewpager
时检测到片段的 onPause
。第二次滑动 video/audio
文件停止播放。
在 activity 我试过添加 .addOnPageChangeListener
:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
try {
((ItemViewerFragment)mAdapter.getItem(mPreviousPos)).imHiddenNow();
} catch (Exception e) {
e.printStackTrace();
}
mPreviousPos = position;
}
并且在 ItemViewerFragment.java
中:
public void imHiddenNow(){
releasePlayer();
}
还在video/audio
继续玩
这是截屏视频的 Video Link。
从未来编辑:你永远不应该在 FragmentPagerAdapter 中直接持有对 Fragment 实例的引用,因为它会在进程死亡后导致崩溃。
这是寻呼机适配器的代码:
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);
}
}
这里是滚动监听器:
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 循环并一次将所有片段添加到列表中,然后使用它来提高效率:
viewPager.setOffscreenPageLimit(3);
这将确保您的片段只有 3 个实例可用,这就足够了。
对于使用单个片段,我建议您这样做:
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;
}
然后在 Fragment 的 onCreate 中,您可以像这样检索文件:
File file;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
file = getArguments().getSerializable("file");
}
现在像这样将您的片段添加到寻呼机中:
for (int i = 0; i < totalFiles; i++) {
viewPagerAdapter.addFragment(MyFragment.newInstance(fileList.get(i));
}
希望对您有所帮助。
我使用 Exoplayer 已经一年多了,我也解决了一个类似的问题。请注意,API 发生了一些变化,因此使用以下代码只是为了了解如何实施潜在的解决方案。如果它不起作用,请告诉我,我会进一步研究 API 并回复您。
解决方案:
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 );
}
我知道这么晚了,但我希望它能帮助到任何人...
viewPager 保持屏幕外片段启动..
所以你有:
prev offscreen fragment (start) --> visible fragment (working)--> next offscreen fragment (start)
解决方案是您可以覆盖 Fragment.setUserVisibleHint()
并处理 play/pause 视频..
问题是当片段可见性在 ViewPager 中发生变化时,onPause
和 onResume
没有被调用。
解决方案是添加 2 个可见性事件:losingVisibility
和 gainVisibility
.
为什么它是一个很好的解决方案?
因为您让框架管理片段缓存和生命周期。
我们只是在片段中添加暂停和恢复媒体所需的回调。
一步一步:
下面的代码只是对我的代码的解释。检查 Step*.java 类 以查看完整实施。
- 中创建
losingVisibility
和 gainVisibility
方法
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();
}
}
每次在 YourActivity.java 中选择一个新页面 (onPageSelected
) 时调用 losingVisibility
和 gainVisibility
:
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) {
}
});
将getCachedItem
添加到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);
}
}
我采用了在 PagerAdapter
中维护 HashMap
个片段对象的方法
- 声明一个接口:
interface FragmentLifecycle {
void onPauseFragment()
}
- 在 Fragment 中实现接口。
public void onPauseFragment() {
if (simpleExoPlayer != null){
simpleExoPlayer.setPlayWhenReady(false);
}
}
将所有片段对象存储在HashMap<Integer,Fragment>
中,并以各自的位置作为键。在 PagerAdapter
内声明 hashmap。还声明了一个 getter 方法,用于从 hashmap 访问片段对象。例如
@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(位置);
}
在您声明 viewPager
的 activity 中保留一个变量 currentPosition
并实现 ViewPager.OnPageChangeListener
。
内部 onPageSelected
方法,
@Override
public void onPageSelected(int position) {
if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
currentPosition = position;
}
我正在使用带有单个片段实例的 ViewPager
,我在其中显示 Media
文件,例如图像、视频、音频。
我已经实现了 ExoPlayer
来处理 Video
& Audio
文件。和 Glide
用于图像。
为了避免内存泄漏,我在 ItemViewerFragment.java
中像这样释放 ExoPlayer
对象:
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();
}
在 onViewCreated()
中,我正在初始化视图,如下所示:
@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);
}
}
我正在使用 FragmentStatePagerAdapeter
。这是 getItem
方法:
@Override
public Fragment getItem(int position) {
return ItemViewerFragment.newInstance(mItems.get(position));
}
我无法在第一次滑动 Viewpager
时检测到片段的 onPause
。第二次滑动 video/audio
文件停止播放。
在 activity 我试过添加 .addOnPageChangeListener
:
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
try {
((ItemViewerFragment)mAdapter.getItem(mPreviousPos)).imHiddenNow();
} catch (Exception e) {
e.printStackTrace();
}
mPreviousPos = position;
}
并且在 ItemViewerFragment.java
中:
public void imHiddenNow(){
releasePlayer();
}
还在video/audio
继续玩
这是截屏视频的 Video Link。
从未来编辑:你永远不应该在 FragmentPagerAdapter 中直接持有对 Fragment 实例的引用,因为它会在进程死亡后导致崩溃。
这是寻呼机适配器的代码:
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);
}
}
这里是滚动监听器:
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 循环并一次将所有片段添加到列表中,然后使用它来提高效率:
viewPager.setOffscreenPageLimit(3);
这将确保您的片段只有 3 个实例可用,这就足够了。
对于使用单个片段,我建议您这样做:
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;
}
然后在 Fragment 的 onCreate 中,您可以像这样检索文件:
File file;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
file = getArguments().getSerializable("file");
}
现在像这样将您的片段添加到寻呼机中:
for (int i = 0; i < totalFiles; i++) {
viewPagerAdapter.addFragment(MyFragment.newInstance(fileList.get(i));
}
希望对您有所帮助。
我使用 Exoplayer 已经一年多了,我也解决了一个类似的问题。请注意,API 发生了一些变化,因此使用以下代码只是为了了解如何实施潜在的解决方案。如果它不起作用,请告诉我,我会进一步研究 API 并回复您。
解决方案:
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 );
}
我知道这么晚了,但我希望它能帮助到任何人...
viewPager 保持屏幕外片段启动..
所以你有:
prev offscreen fragment (start) --> visible fragment (working)--> next offscreen fragment (start)
解决方案是您可以覆盖 Fragment.setUserVisibleHint()
并处理 play/pause 视频..
问题是当片段可见性在 ViewPager 中发生变化时,onPause
和 onResume
没有被调用。
解决方案是添加 2 个可见性事件:losingVisibility
和 gainVisibility
.
为什么它是一个很好的解决方案?
因为您让框架管理片段缓存和生命周期。 我们只是在片段中添加暂停和恢复媒体所需的回调。
一步一步:
下面的代码只是对我的代码的解释。检查 Step*.java 类 以查看完整实施。
- 中创建
losingVisibility
和gainVisibility
方法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(); } }
每次在 YourActivity.java 中选择一个新页面 (
onPageSelected
) 时调用losingVisibility
和gainVisibility
: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) { } });
将
getCachedItem
添加到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);
}
}
我采用了在 PagerAdapter
HashMap
个片段对象的方法
- 声明一个接口:
interface FragmentLifecycle {
void onPauseFragment()
}
- 在 Fragment 中实现接口。
public void onPauseFragment() {
if (simpleExoPlayer != null){
simpleExoPlayer.setPlayWhenReady(false);
}
}
将所有片段对象存储在
HashMap<Integer,Fragment>
中,并以各自的位置作为键。在PagerAdapter
内声明 hashmap。还声明了一个 getter 方法,用于从 hashmap 访问片段对象。例如@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(位置); }
在您声明
viewPager
的 activity 中保留一个变量currentPosition
并实现ViewPager.OnPageChangeListener
。内部
onPageSelected
方法,@Override public void onPageSelected(int position) { if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment(); currentPosition = position; }