在 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

Demo project GitHub 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 中发生变化时,onPauseonResume 没有被调用。 解决方案是添加 2 个可见性事件:losingVisibilitygainVisibility.

为什么它是一个很好的解决方案?

因为您让框架管理片段缓存和生命周期。 我们只是在片段中添加暂停和恢复媒体所需的回调。

一步一步:

下面的代码只是对我的代码的解释。检查 Step*.java 类 以查看完整实施。

  1. YourFragment.java:

    中创建 losingVisibilitygainVisibility 方法
    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. 每次在 YourActivity.java 中选择一个新页面 (onPageSelected) 时调用 losingVisibilitygainVisibility:

    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. 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 个片段对象的方法
  1. 声明一个接口:
interface FragmentLifecycle {
    void onPauseFragment()
}
  1. 在 Fragment 中实现接口。
public void onPauseFragment() {
    if (simpleExoPlayer != null){
        simpleExoPlayer.setPlayWhenReady(false);
    }
}
  1. 将所有片段对象存储在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(位置); }

  2. 在您声明 viewPager 的 activity 中保留一个变量 currentPosition 并实现 ViewPager.OnPageChangeListener

  3. 内部 onPageSelected 方法,

        @Override
        public void onPageSelected(int position) {
            if(mAdapter.getMapItem(currentPosition) != null) (mAdapter.getMapItem(currentPosition)).onPauseFragment();
            currentPosition = position;
        }