fix #274, replace video MediaPlayer with ExoPlayer

This commit is contained in:
tibbi 2018-06-29 12:00:12 +02:00
parent ebaf64af70
commit 1038ee33d2
3 changed files with 158 additions and 137 deletions

View file

@ -54,6 +54,7 @@ dependencies {
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.12'
implementation 'com.github.chrisbanes:PhotoView:2.1.3'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.google.android.exoplayer:exoplayer-core:2.8.2'
kapt "android.arch.persistence.room:compiler:1.1.1"
implementation "android.arch.persistence.room:runtime:1.1.1"

View file

@ -1,21 +1,31 @@
package com.simplemobiletools.gallery.fragments
import android.annotation.TargetApi
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.SurfaceTexture
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.support.annotation.RequiresApi
import android.util.DisplayMetrics
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.SeekBar
import android.widget.TextView
import com.google.android.exoplayer2.*
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ExtractorMediaSource
import com.google.android.exoplayer2.source.TrackGroupArray
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.trackselection.TrackSelectionArray
import com.google.android.exoplayer2.upstream.DataSource
import com.google.android.exoplayer2.upstream.DataSpec
import com.google.android.exoplayer2.upstream.FileDataSource
import com.google.android.exoplayer2.video.VideoListener
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isJellyBean1Plus
import com.simplemobiletools.gallery.BuildConfig
import com.simplemobiletools.gallery.R
import com.simplemobiletools.gallery.activities.VideoActivity
import com.simplemobiletools.gallery.extensions.*
@ -24,32 +34,28 @@ import com.simplemobiletools.gallery.helpers.MediaSideScroll
import com.simplemobiletools.gallery.models.Medium
import kotlinx.android.synthetic.main.pager_video_item.view.*
import java.io.File
import java.io.IOException
class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSeekBarChangeListener {
class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener {
private val PROGRESS = "progress"
private val MIN_SKIP_LENGTH = 2000
private val PLAY_PAUSE_VISIBLE_ALPHA = 0.8f
private var mMediaPlayer: MediaPlayer? = null
private var mSurfaceView: SurfaceView? = null
private var mSurfaceHolder: SurfaceHolder? = null
private var mTextureView: TextureView? = null
private var mCurrTimeView: TextView? = null
private var mTimerHandler: Handler? = null
private var mSeekBar: SeekBar? = null
private var mTimeHolder: View? = null
private var mView: View? = null
private var mExoPlayer: SimpleExoPlayer? = null
private var mVideoSize = Point(0, 0)
private var mIsPlaying = false
private var mIsDragged = false
private var mIsFullscreen = false
private var mIsFragmentVisible = false
private var mPlayOnPrepare = false
private var wasEncoded = false
private var wasInit = false
private var isPrepared = false
private var mWasInit = false
private var mCurrTime = 0
private var mDuration = 0
private var mEncodedPath = ""
private var mStoredShowExtendedDetails = false
private var mStoredHideExtendedDetails = false
@ -77,7 +83,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN
mView!!.video_play_outline.alpha = if (mIsFullscreen) 0f else 1f
mView!!.video_play_outline.alpha = if (mIsFullscreen) 0f else PLAY_PAUSE_VISIBLE_ALPHA
setupPlayer()
if (savedInstanceState != null) {
@ -85,7 +91,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
checkFullscreen()
wasInit = true
mWasInit = true
mView!!.apply {
brightnessSideScroll = video_brightness_controller
@ -102,6 +108,65 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
video_duration.setOnClickListener { skip(true) }
}
mExoPlayer = ExoPlayerFactory.newSimpleInstance(context, DefaultTrackSelector())
mExoPlayer!!.addListener(object : Player.EventListener {
override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters?) {}
override fun onSeekProcessed() {}
override fun onTracksChanged(trackGroups: TrackGroupArray?, trackSelections: TrackSelectionArray?) {}
override fun onPlayerError(error: ExoPlaybackException?) {
activity?.showErrorToast(error.toString())
}
override fun onLoadingChanged(isLoading: Boolean) {}
override fun onPositionDiscontinuity(reason: Int) {}
override fun onRepeatModeChanged(repeatMode: Int) {}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {}
override fun onTimelineChanged(timeline: Timeline?, manifest: Any?, reason: Int) {}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
when (playbackState) {
Player.STATE_READY -> videoPrepared()
Player.STATE_ENDED -> videoCompleted()
}
}
})
mExoPlayer!!.addVideoListener(object : VideoListener {
override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) {
mVideoSize.x = width
mVideoSize.y = height
setVideoSize()
}
override fun onRenderedFirstFrame() {}
})
val uri = Uri.fromFile(File(medium.path))
val dataSpec = DataSpec(uri)
val fileDataSource = FileDataSource()
try {
fileDataSource.open(dataSpec)
} catch (e: Exception) {
activity?.showErrorToast(e)
}
val factory = DataSource.Factory { fileDataSource }
val audioSource = ExtractorMediaSource(fileDataSource.uri, factory, DefaultExtractorsFactory(), null, null)
mExoPlayer!!.audioStreamType = AudioManager.STREAM_MUSIC
mExoPlayer!!.prepare(audioSource)
medium.path.getVideoResolution()?.apply {
mVideoSize.x = x
mVideoSize.y = y
setVideoSize()
}
return mView
}
@ -157,26 +222,23 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
mView!!.video_play_outline.setOnClickListener { togglePlayPause() }
mSurfaceView = mView!!.video_surface
mSurfaceHolder = mSurfaceView!!.holder
mSurfaceHolder!!.addCallback(this)
mSurfaceView!!.setOnClickListener { toggleFullscreen() }
mTextureView = mView!!.video_surface
mTextureView!!.setOnClickListener { toggleFullscreen() }
mTextureView!!.surfaceTextureListener = this
mView!!.video_holder.setOnClickListener { toggleFullscreen() }
initTimeHolder()
checkExtendedDetails()
initMediaPlayer()
}
override fun setMenuVisibility(menuVisible: Boolean) {
super.setMenuVisibility(menuVisible)
if (mIsFragmentVisible && !menuVisible) {
pauseVideo()
releaseMediaPlayer()
}
mIsFragmentVisible = menuVisible
if (menuVisible && wasInit) {
initMediaPlayer()
if (menuVisible && mWasInit) {
if (context?.config?.autoplayVideos == true) {
playVideo()
}
@ -259,8 +321,8 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
private fun setupTimer() {
activity!!.runOnUiThread(object : Runnable {
override fun run() {
if (mMediaPlayer != null && !mIsDragged && mIsPlaying) {
mCurrTime = mMediaPlayer!!.currentPosition / 1000
if (mExoPlayer != null && !mIsDragged && mIsPlaying) {
mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt()
mSeekBar!!.progress = mCurrTime
mCurrTimeView!!.text = mCurrTime.getFormattedDuration()
}
@ -299,8 +361,6 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
if (activity == null || !isAdded)
return
initMediaPlayer()
mIsPlaying = !mIsPlaying
if (mIsPlaying) {
playVideo()
@ -310,102 +370,59 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
fun playVideo() {
if (mMediaPlayer != null && isPrepared) {
mIsPlaying = true
mMediaPlayer?.start()
} else {
mPlayOnPrepare = true
if (mExoPlayer == null) {
return
}
if (videoEnded()) {
setProgress(0)
}
mIsPlaying = true
mExoPlayer?.playWhenReady = true
mView!!.video_play_outline.setImageDrawable(resources.getDrawable(R.drawable.img_pause_outline_big))
activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun pauseVideo() {
if (mExoPlayer == null) {
return
}
mIsPlaying = false
mMediaPlayer?.pause()
if (!videoEnded()) {
mExoPlayer?.playWhenReady = false
}
mView?.video_play_outline?.setImageDrawable(resources.getDrawable(R.drawable.img_play_outline_big))
activity!!.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
private fun initMediaPlayer() {
if (mMediaPlayer != null || !mIsFragmentVisible) {
return
}
val mediumPath = if (wasEncoded) mEncodedPath else getPathToLoad(medium)
// this workaround is needed for example if the filename contains a colon
val fileUri = if (mediumPath.startsWith("/")) context!!.getFilePublicUri(File(mediumPath), BuildConfig.APPLICATION_ID) else Uri.parse(mediumPath)
try {
mMediaPlayer = MediaPlayer().apply {
setDataSource(context, fileUri)
setDisplay(mSurfaceHolder)
setOnCompletionListener { videoCompleted() }
setOnVideoSizeChangedListener { mediaPlayer, width, height -> setVideoSize() }
setOnPreparedListener { videoPrepared(it) }
setAudioStreamType(AudioManager.STREAM_MUSIC)
prepare()
}
} catch (e: IOException) {
mEncodedPath = Uri.encode(getPathToLoad(medium))
if (wasEncoded) {
releaseMediaPlayer()
} else {
wasEncoded = true
mMediaPlayer = null
initMediaPlayer()
}
} catch (e: Exception) {
releaseMediaPlayer()
}
}
private fun videoEnded() = mExoPlayer!!.currentPosition >= mExoPlayer!!.duration
private fun setProgress(seconds: Int) {
mMediaPlayer!!.seekTo(seconds * 1000)
mExoPlayer!!.seekTo(seconds * 1000L)
mSeekBar!!.progress = seconds
mCurrTimeView!!.text = seconds.getFormattedDuration()
}
private fun addPreviewImage() {
mMediaPlayer!!.start()
mMediaPlayer!!.pause()
}
private fun videoPrepared() {
if (mDuration == 0) {
mDuration = (mExoPlayer!!.duration / 1000).toInt()
setupTimeHolder()
setProgress(mCurrTime)
private fun cleanup() {
pauseVideo()
mCurrTimeView?.text = 0.getFormattedDuration()
releaseMediaPlayer()
mSeekBar?.progress = 0
mTimerHandler?.removeCallbacksAndMessages(null)
mSurfaceView = null
mSurfaceHolder?.removeCallback(this)
mSurfaceHolder = null
}
private fun releaseMediaPlayer() {
mMediaPlayer?.setSurface(null)
mMediaPlayer?.release()
mMediaPlayer = null
}
private fun videoPrepared(mediaPlayer: MediaPlayer) {
isPrepared = true
mDuration = mediaPlayer.duration / 1000
addPreviewImage()
setupTimeHolder()
setProgress(mCurrTime)
if (mIsFragmentVisible && (context!!.config.autoplayVideos || mPlayOnPrepare)) {
playVideo()
if (mIsFragmentVisible && (context!!.config.autoplayVideos)) {
playVideo()
}
}
}
private fun videoCompleted() {
if (!isAdded) {
if (!isAdded || mExoPlayer == null) {
return
}
mCurrTime = (mExoPlayer!!.duration / 1000).toInt()
if (listener?.videoEnded() == false && context!!.config.loopVideos) {
playVideo()
} else {
@ -415,37 +432,42 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
}
override fun surfaceCreated(holder: SurfaceHolder) {
mSurfaceHolder = holder
if (mIsFragmentVisible) {
initMediaPlayer()
}
}
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
if (width != 0 && height != 0 && mSurfaceView != null) {
setVideoSize()
}
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
private fun cleanup() {
pauseVideo()
mCurrTimeView?.text = 0.getFormattedDuration()
releaseMediaPlayer()
mSeekBar?.progress = 0
mTimerHandler?.removeCallbacksAndMessages(null)
mTextureView = null
}
@RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun releaseMediaPlayer() {
mExoPlayer?.stop()
mExoPlayer?.release()
mExoPlayer = null
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?): Boolean {
releaseMediaPlayer()
return false
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
mExoPlayer?.setVideoSurface(Surface(mTextureView!!.surfaceTexture))
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private fun setVideoSize() {
if (mSurfaceHolder == null)
mSurfaceHolder = mSurfaceView!!.holder
if (activity == null || mSurfaceHolder == null || !mSurfaceHolder!!.surface.isValid)
if (activity == null || mTextureView == null)
return
initMediaPlayer()
if (mMediaPlayer == null) {
return
}
val videoProportion = mMediaPlayer!!.videoWidth.toFloat() / mMediaPlayer!!.videoHeight.toFloat()
val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat()
val display = activity!!.windowManager.defaultDisplay
val screenWidth: Int
val screenHeight: Int
@ -462,7 +484,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
val screenProportion = screenWidth.toFloat() / screenHeight.toFloat()
mSurfaceView!!.layoutParams.apply {
mTextureView!!.layoutParams.apply {
if (videoProportion > screenProportion) {
width = screenWidth
height = (screenWidth.toFloat() / videoProportion).toInt()
@ -470,7 +492,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
width = (videoProportion * screenHeight.toFloat()).toInt()
height = screenHeight
}
mSurfaceView!!.layoutParams = this
mTextureView!!.layoutParams = this
}
}
@ -496,15 +518,15 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
private fun skip(forward: Boolean) {
if (mMediaPlayer == null) {
if (mExoPlayer == null) {
return
}
val curr = mMediaPlayer!!.currentPosition
val twoPercents = Math.max(mMediaPlayer!!.duration / 50, MIN_SKIP_LENGTH)
val curr = mExoPlayer!!.currentPosition
val twoPercents = Math.max((mExoPlayer!!.duration / 50).toInt(), MIN_SKIP_LENGTH)
val newProgress = if (forward) curr + twoPercents else curr - twoPercents
val roundProgress = Math.round(newProgress / 1000f)
val limitedProgress = Math.max(Math.min(mMediaPlayer!!.duration / 1000, roundProgress), 0)
val limitedProgress = Math.max(Math.min(mExoPlayer!!.duration.toInt(), roundProgress), 0)
setProgress(limitedProgress)
if (!mIsPlaying) {
togglePlayPause()
@ -512,17 +534,16 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (mMediaPlayer != null && fromUser) {
if (mExoPlayer != null && fromUser) {
setProgress(progress)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
initMediaPlayer()
if (mMediaPlayer == null)
if (mExoPlayer == null)
return
mMediaPlayer!!.pause()
mExoPlayer!!.playWhenReady = false
mIsDragged = true
}
@ -530,7 +551,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
if (!mIsPlaying) {
togglePlayPause()
} else {
mMediaPlayer?.start()
mExoPlayer!!.playWhenReady = true
}
mIsDragged = false
@ -549,7 +570,7 @@ class VideoFragment : ViewPagerFragment(), SurfaceHolder.Callback, SeekBar.OnSee
}
}
mView!!.video_play_outline.animate().alpha(if (isFullscreen) 0f else 1f).start()
mView!!.video_play_outline.animate().alpha(if (isFullscreen) 0f else PLAY_PAUSE_VISIBLE_ALPHA).start()
}
private fun getExtendedDetailsY(height: Int): Float {

View file

@ -6,12 +6,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<SurfaceView
<TextureView
android:id="@+id/video_surface"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="@android:color/transparent"/>
android:layout_centerInParent="true"/>
<com.simplemobiletools.gallery.helpers.MediaSideScroll
android:id="@+id/video_volume_controller"