【问题标题】:Stopping Exoplayer onSwipe of ViewPager在 ViewPager 的滑动上停止 Exoplayer
【发布时间】:2018-03-22 21:37:03
【问题描述】:

我正在使用带有单个片段实例的ViewPager,其中我正在显示Media 文件,如图像、视频、音频。

我已经实现了ExoPlayer 来处理VideoAudio 文件。和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 文件将停止播放。

在活动中我尝试添加.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.

【问题讨论】:

  • 如何在 onScrollListener 中获取上一个位置?
  • 另外我看到 getItem 方法每次都返回一个 newInstance 这可能是原因。
  • 初始化时,我正在存储正在显示的GalleryModel 的当前位置。所以假设我的列表中有 30 项,我显示的是第 5 项。然后onScroll 前一个位置将是第 5 位,当前位置将是第 4 位或第 6 位,具体取决于滚动的哪一侧。
  • 获取项目看起来很可疑,我正在用一些代码发布答案,尝试使用 pageAdapter。
  • 我使用的是相同的片段,所以我每次都返回newInstance。您对我们如何改进这一点有任何想法吗?

标签: java android android-viewpager exoplayer


【解决方案1】:

我遵循了在PagerAdapter 中维护片段对象的HashMap 的方法

  1. 声明一个接口:
interface FragmentLifecycle {
    void onPauseFragment()
}
  1. 在 Fragment 中实现接口。
public void onPauseFragment() {
    if (simpleExoPlayer != null){
        simpleExoPlayer.setPlayWhenReady(false);
    }
}
  1. 将所有片段对象存储在HashMap<Integer,Fragment> 中,并以各自的位置作为键。在 PagerAdapter 中声明 hashmap。还声明一种用于从 hashmap 访问片段对象的 getter 方法。例如

    @Override
    public Fragment getItem(int position) {
        ItemViewerFragment fragment = ItemViewerFragment.newInstance(mItems.get(position));
        mFragments.put(position,fragment);
        return fragment;
    }
    

    public ItemViewerFragment getMapItem(int position) { 返回 mFragments.get(位置); }

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

  3. onPageSelected方法内部,

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

【讨论】:

  • 这是一个反模式,getItem() 不会在 Android 在进程死亡后返回后被 FragmentPagerAdapter 调用来获取片段实例。未来地图必然会导致NPE。
【解决方案2】:

从未来开始编辑:你不应该直接在 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));
        }

希望这会有所帮助。

【讨论】:

  • 好的,我会试一试。谢谢!
  • 假设我有 1000 个媒体文件。它会创建 1000 个片段吗? mFragmentList 会这么大吗?
  • 型号列表?
  • 所以是的,它还会创建一个片段列表,但是当我们将 offScreenLimit 更改为 3 时,它将有助于仅维护 3 个实例。
  • 我不这么认为,因为我的适配器中没有任何mFragmentList。每次它离开offscreenLimit 时都会返回新实例。我会对此进行研究以了解到底发生了什么。
【解决方案3】:

自从我使用 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 时暂停播放器,它发生在第二次滑动中。
  • 您是否尝试从 viewPager 的 onPageScrolled 和 onPageScrollStateChanged 回调调用我的回答中提到的 releaseExoplayer()?
  • 是的,我做到了.. 即使onSwipe 当前片段正在停止播放器。 onSwipeBack 到同一片段不会恢复播放器。我在片段的onResume 中调用resumePlaybackFromPreviousPosition
  • 这可能是因为您需要正确重新初始化播放器。您尚未共享用于重新初始化播放器的代码。要记住的第二件事是,您需要在 onSaveInstance() 回调中保留播放器的先前位置,并重新读取片段的 onActivityCreated() 或 onCreateView() 中的值。如果这些事情做的好,理论上问题应该解决了。
  • 为了从这个社区获得最好的支持,总是建议用所有必需的代码 sn-ps 编写简洁的问题。
【解决方案4】:

问题在于,当 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

第三步是添加一个检索缓存片段的方法。为此,我们必须缓存对已创建片段的引用(覆盖instantiateItem)并释放相同的引用(覆盖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);
        }
    }

【讨论】:

    【解决方案5】:

    我知道它为时已晚,但我希望它可以帮助任何人......
    viewPager 使屏幕外片段保持启动..
    所以你有:
    上一个屏幕外片段(开始)--> 可见片段(工作)--> 下一个屏幕外片段(开始)
    解决方案是您可以覆盖 Fragment.setUserVisibleHint() 并处理播放/暂停视频..

    【讨论】:

    • viewPager.setOffscreenPageLimit(0) 即使它设置为 0 仍然查看寻呼机将保留 -1 和 +1 片段。
    • 我已经编辑了我的答案,因为setOffscreenPageLimit() 可以处理的最小和默认屏幕外片段是一个..Fragment.setUserVisibleHint() 是最好的解决方案
    猜你喜欢
    • 2022-09-24
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-10
    • 1970-01-01
    相关资源
    最近更新 更多