I have the following fragment handling a ViewPager2
which creates fragments (VideoFragment
) on which a video is shown via 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)
}
}
And here is the Fragment class (used by the ViewPager2) showing the videos via ExoPlayer2:
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)
}
}
}
}
My problem is that when I switch between the VideoFragment
s by swiping, the previous Exoplayer
is not released properly so that I can still hear the audio of the first video after starting a 2nd video.
Although I have put a releasePlayer()
method for releasing the player into the appropriate lifecycle methods, it seems to me that ViewPager
does not care about that.
How can I stop/release an ExoPlayer
instance when used with ViewPager
?