【问题标题】:How to Close ExoPlayer instance in ViewPager2 when swipe occurs发生滑动时如何在 ViewPager2 中关闭 ExoPlayer 实例
【发布时间】:2020-03-14 16:10:30
【问题描述】:

我有以下片段处理 ViewPager2,它创建片段 (VideoFragment),通过 ExoPlayer 在其上显示视频:

private const val IMMERSIVE_FLAG_TIMEOUT = 500L
class VideoGalleryFragment : Fragment() {

    private lateinit var binding: FragmentVideoGalleryBinding
    private lateinit var mediaList: MutableList<File>
    private lateinit var mediaViewPager: ViewPager2

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private val videoGalleryFragmentViewModel by viewModels<VideoGalleryFragmentViewModel> { viewModelFactory }

    private val args by navArgs<VideoGalleryFragmentArgs>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Mark this as a retain fragment, so the lifecycle does not get restarted on config change
        retainInstance = true

        // Get root directory of media
        //outputDirectory = getOutputDirectory(requireContext())
        val rootDirectory = File(args.rootDirectory)


        // Walk through all files in the root directory
        // We reverse the order of the list to present the last photos first
        mediaList = rootDirectory.listFiles { file ->
            VIDEO_EXTENSION_WHITELIST.contains(file.extension.toUpperCase(Locale.ROOT))
        }?.sortedDescending()?.toMutableList() ?: mutableListOf()

    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {

        // Inflate the layout for this fragment
        binding = DataBindingUtil.inflate(inflater, R.layout.fragment_video_gallery, container, false)
        binding.apply {
            lifecycleOwner = viewLifecycleOwner
            viewModel = videoGalleryFragmentViewModel
        }

        return binding.root
    }

    // HERE IS MY VIEWPAGER2 SETUP
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        mediaViewPager = binding.videoViewPager.apply {
            offscreenPageLimit = 2
            adapter = object: FragmentStateAdapter(this@VideoGalleryFragment) {
                override fun getItemCount(): Int = mediaList.size

                override fun createFragment(position: Int): Fragment =
                    VideoFragment.create(
                        mediaList[position]
                    )

            }
            setPageTransformer(DepthPageTransformer())
        }


        }
    }



    override fun onResume() {
        super.onResume()
        /*
        * Before setting full screen flags, we must wait a bit to let UI settle; otherwise, we may
        * be trying to set app to immersive mode before it's ready and the flags do not stick
        * */
        binding.container.postDelayed({
            binding.container.systemUiVisibility =
                FLAGS_FULLSCREEN
        }, IMMERSIVE_FLAG_TIMEOUT)
    }
}

这是通过 ExoPlayer2 显示视频的 Fragment 类(由 ViewPager2 使用):

class VideoFragment : Fragment() {

    private var _binding: FragmentVideoBinding? = null
    // This property is only valid between onCreateView and
    // onDestroyView.
    private val binding get() = _binding!!

    private var player: SimpleExoPlayer? = null

    private var playWhenReady: Boolean? = true
    private var currentWindow: Int? = 0
    private var playbackPosition : Long? = 0
    private var resource: String? = null


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        _binding = FragmentVideoBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val args = arguments ?: return
        args.getString(VideoFragment.VIDEO_FILE_NAME_KEY)?.let {
            resource = it
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        releasePlayer()
        _binding = null
    }

    override fun onStart() {
        super.onStart()
        if(Util.SDK_INT >= 24){
            initializePlayer(resource)
        }
    }

    override fun onResume() {
        super.onResume()
        if((Util.SDK_INT < 24 || player == null)){
            initializePlayer(resource)
        }
    }

    private fun initializePlayer(videoFilePath: String?){

        player = ExoPlayerFactory.newSimpleInstance(activity)




        binding.videoViewGallery.player = player


        videoFilePath?.let {
            val mediaSource: MediaSource =
                ProgressiveMediaSource.Factory(DefaultDataSourceFactory(activity, "exoplayer-mylim"))
                    .createMediaSource(Uri.parse(it))


            playWhenReady?.let { playWhenReady ->
                (player as SimpleExoPlayer).playWhenReady = playWhenReady
            }



            currentWindow?.let { currentWindow ->
                playbackPosition?.let {playbackPosition ->
                    (player as SimpleExoPlayer).seekTo(currentWindow, playbackPosition)
                }
            }



            (player as SimpleExoPlayer).prepare(mediaSource, false, false)
        }
    }

    private fun releasePlayer(){
        player?.stop()
        player?.release()
        player = null
    }

    override fun onPause() {
        super.onPause()
        if(Util.SDK_INT < 24){
            releasePlayer()
        }
    }

    override fun onStop() {
        super.onStop()
        if(Util.SDK_INT >= 24){
            releasePlayer()
        }
    }
    companion object {
        private const val VIDEO_FILE_NAME_KEY = "video_file_name"

        fun create(video: File) = VideoFragment().apply {
            arguments = Bundle().apply {
                putString(VIDEO_FILE_NAME_KEY, video.absolutePath)
            }
        }

    }
}

我的问题是,当我通过滑动在VideoFragments 之间切换时,之前的Exoplayer 没有正确释放,所以在开始第二个视频后我仍然可以听到第一个视频的音频。

虽然我已将用于释放播放器的releasePlayer() 方法放入适当的生命周期方法中,但在我看来ViewPager 并不关心这一点。

当与ViewPager 一起使用时,如何停止/释放ExoPlayer 实例?

【问题讨论】:

    标签: android release exoplayer2.x android-viewpager2 fragment-lifecycle


    【解决方案1】:

    使用 ViewPager2,您的 Fragment 可以在实际显示给用户之前进行预加载。解决问题的一种方法是从那里监听页面更改和releasePlayer(),而不是等待 Fragment 被销毁。在此处查看ViewPager2.OnPageChangeCallback 的文档

    【讨论】:

      【解决方案2】:

      我知道已经晚了但可能会帮助某人,我也遇到了同样的问题。解决方案在于使用 StatePagerAdapter

          class ViewPagerAdapter extends FragmentStatePagerAdapter {
      
          public ViewPagerAdapter(FragmentManager manager) {
              super(manager, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
          }
      
          @Override
          public Fragment getItem(int position) {
              return mFragmentList.get(position);
          }
      
          @Override
          public int getCount() {
              return mFragmentList.size();
          }
      
          public void addFragment(Fragment fragment) {
              mFragmentList.add(fragment);
          }
      
          @Override
          public CharSequence getPageTitle(int position) {
              return mFragmentTitleList.get(position);
          }
      }
      

      我用的是旧的

          class ViewPagerAdapter extends FragmentPagerAdapter {
      
          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) {
              mFragmentList.add(fragment);
          }
      
          @Override
          public CharSequence getPageTitle(int position) {
              return mFragmentTitleList.get(position);
          }
      } 
      

      区别在于父类

      FragmentStatePagerAdapter

       class ViewPagerAdapter extends FragmentStatePagerAdapter {
      
      public ViewPagerAdapter(FragmentManager manager) {
          super(manager, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
      }
      

      FragmentPagerAdapter

      class ViewPagerAdapter extends FragmentPagerAdapter {
      
      public ViewPagerAdapter(FragmentManager manager) {
          super(manager);
      }
      

      FragmentPagerAdapter 这提供了一个现已弃用的方法,如下所示:

               @Override
      public void setUserVisibleHint(boolean isVisibleToUser) {
          super.setUserVisibleHint(isVisibleToUser);
      
          if (isVisibleToUser){
              if (!loaded){
                  loadStatus();
              }
          }
      }
      

      这用于根据片段在 View Pager 中是否对用户可见来管理资源/播放/暂停。 已弃用

      现在如果你想处理片段的资源/状态,你应该使用我上面描述的状态寻呼适配器。实现 StatePagerAdapter 根据对用户的可见性调用片段的 onPause()onResume() 方法。

      所以问题的答案是实现 Adapter Like StatePagerAdapter 并在 Fragment 的 onPause() 方法上暂停播放器并在 onResume() 方法中恢复播放器。

      【讨论】:

        猜你喜欢
        • 2021-10-12
        • 2020-11-23
        • 1970-01-01
        • 2022-09-24
        • 2021-12-26
        • 2019-07-25
        • 2014-09-22
        • 1970-01-01
        相关资源
        最近更新 更多