diff --git a/app/build.gradle b/app/build.gradle
index 047cd8b1a..43298c558 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -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"
diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt
index af7024ff4..6c476a8c2 100644
--- a/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt
+++ b/app/src/main/kotlin/com/simplemobiletools/gallery/fragments/VideoFragment.kt
@@ -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 {
diff --git a/app/src/main/res/layout/pager_video_item.xml b/app/src/main/res/layout/pager_video_item.xml
index b7658ca01..03014aa82 100644
--- a/app/src/main/res/layout/pager_video_item.xml
+++ b/app/src/main/res/layout/pager_video_item.xml
@@ -6,12 +6,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
-
+ android:layout_centerInParent="true"/>