From 50c6d4d102da3155d8a25477d079f7df38643f10 Mon Sep 17 00:00:00 2001 From: tibbi Date: Wed, 9 Jan 2019 23:39:40 +0100 Subject: [PATCH] adding back some viewpager video playback things --- .../gallery/pro/fragments/VideoFragment.kt | 454 +++++++++++++++++- app/src/main/res/layout/pager_video_item.xml | 6 + 2 files changed, 453 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt index 1112f8920..2e921f978 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/fragments/VideoFragment.kt @@ -2,17 +2,33 @@ package com.simplemobiletools.gallery.pro.fragments import android.content.Intent import android.content.res.Configuration +import android.graphics.Point +import android.graphics.SurfaceTexture +import android.net.Uri import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.os.Handler +import android.util.DisplayMetrics +import android.view.* import android.widget.RelativeLayout +import android.widget.SeekBar +import android.widget.TextView import com.bumptech.glide.Glide +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.TrackSelectionArray +import com.google.android.exoplayer2.upstream.ContentDataSource +import com.google.android.exoplayer2.upstream.DataSource +import com.google.android.exoplayer2.upstream.DataSpec +import com.google.android.exoplayer2.upstream.FileDataSource import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.gallery.pro.R import com.simplemobiletools.gallery.pro.activities.PanoramaVideoActivity +import com.simplemobiletools.gallery.pro.activities.VideoActivity import com.simplemobiletools.gallery.pro.extensions.* import com.simplemobiletools.gallery.pro.helpers.MEDIUM +import com.simplemobiletools.gallery.pro.helpers.MIN_SKIP_LENGTH import com.simplemobiletools.gallery.pro.helpers.PATH import com.simplemobiletools.gallery.pro.models.Medium import com.simplemobiletools.gallery.pro.views.MediaSideScroll @@ -21,15 +37,33 @@ import kotlinx.android.synthetic.main.pager_video_item.view.* import java.io.File import java.io.FileInputStream -class VideoFragment : ViewPagerFragment() { +class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener { + private val PROGRESS = "progress" + private var mIsFullscreen = false private var mWasFragmentInit = false private var mIsPanorama = false + private var mIsFragmentVisible = false + private var mIsPlaying = false + private var mIsDragged = false + private var mWasVideoStarted = false + private var mCurrTime = 0 + private var mDuration = 0 + + private var mTextureView: TextureView? = null + private var mCurrTimeView: TextView? = null + private var mSeekBar: SeekBar? = null + private var mExoPlayer: SimpleExoPlayer? = null + private var mVideoSize = Point(0, 0) + private var mTimerHandler = Handler() private var mStoredShowExtendedDetails = false private var mStoredHideExtendedDetails = false private var mStoredBottomActions = true private var mStoredExtendedDetails = 0 + private var mStoredRememberLastVideoPosition = false + private var mStoredLastVideoPath = "" + private var mStoredLastVideoPosition = 0 private lateinit var mTimeHolder: View private lateinit var mBrightnessSideScroll: MediaSideScroll @@ -42,17 +76,36 @@ class VideoFragment : ViewPagerFragment() { mView = inflater.inflate(R.layout.pager_video_item, container, false).apply { instant_prev_item.setOnClickListener { listener?.goToPrevItem() } instant_next_item.setOnClickListener { listener?.goToNextItem() } + video_curr_time.setOnClickListener { skip(false) } + video_duration.setOnClickListener { skip(true) } video_holder.setOnClickListener { toggleFullscreen() } video_preview.setOnClickListener { toggleFullscreen() } panorama_outline.setOnClickListener { openPanorama() } - video_play_outline.setOnClickListener { launchVideoPlayer() } + video_play_outline.setOnClickListener { + if (context!!.config.openVideosOnSeparateScreen) { + launchVideoPlayer() + } else { + togglePlayPause() + } + } + + mSeekBar = video_seekbar + mSeekBar!!.setOnSeekBarChangeListener(this@VideoFragment) + // adding an empty click listener just to avoid ripple animation at toggling fullscreen + mSeekBar!!.setOnClickListener { } mTimeHolder = video_time_holder + mCurrTimeView = video_curr_time mBrightnessSideScroll = video_brightness_controller mVolumeSideScroll = video_volume_controller if (context.config.allowDownGesture) { - video_preview.setOnTouchListener { v, event -> + video_preview.setOnTouchListener { view, event -> + handleEvent(event) + false + } + + video_surface.setOnTouchListener { view, event -> handleEvent(event) false } @@ -63,10 +116,20 @@ class VideoFragment : ViewPagerFragment() { mMedium = arguments!!.getSerializable(MEDIUM) as Medium Glide.with(context!!).load(mMedium.path).into(mView.video_preview) + // setMenuVisibility is not called at VideoActivity (third party intent) + if (!mIsFragmentVisible && activity is VideoActivity) { + mIsFragmentVisible = true + } + mIsFullscreen = activity!!.window.decorView.systemUiVisibility and View.SYSTEM_UI_FLAG_FULLSCREEN == View.SYSTEM_UI_FLAG_FULLSCREEN initTimeHolder() checkIfPanorama() + mMedium.path.getVideoResolution()?.apply { + mVideoSize.x = x + mVideoSize.y = y + } + if (mIsPanorama) { mView.apply { panorama_outline.beVisible() @@ -78,7 +141,20 @@ class VideoFragment : ViewPagerFragment() { } if (!mIsPanorama) { + setupPlayer() + setupPlayer() + if (savedInstanceState != null) { + mCurrTime = savedInstanceState.getInt(PROGRESS) + } + mWasFragmentInit = true + mExoPlayer = ExoPlayerFactory.newSimpleInstance(context) + mExoPlayer!!.seekParameters = SeekParameters.CLOSEST_SYNC + initExoPlayerListeners() + + if (mVideoSize.x != 0 && mVideoSize.y != 0) { + setVideoSize() + } mView.apply { mBrightnessSideScroll.initialize(activity!!, slide_info, true, container) { x, y -> @@ -88,9 +164,20 @@ class VideoFragment : ViewPagerFragment() { mVolumeSideScroll.initialize(activity!!, slide_info, false, container) { x, y -> video_holder.performClick() } + + video_surface.onGlobalLayout { + if (mIsFragmentVisible && context?.config?.autoplayVideos == true) { + playVideo() + } + } } } + setupVideoDuration() + if (mStoredRememberLastVideoPosition) { + setLastVideoSavedPosition() + } + updateInstantSwitchWidths() return mView } @@ -117,24 +204,164 @@ class VideoFragment : ViewPagerFragment() { override fun onPause() { super.onPause() storeStateVariables() + pauseVideo() + if (mStoredRememberLastVideoPosition && mIsFragmentVisible && mWasVideoStarted) { + saveVideoProgress() + } + } + + override fun onDestroy() { + super.onDestroy() + if (activity?.isChangingConfigurations == false) { + cleanup() + } + } + + override fun setMenuVisibility(menuVisible: Boolean) { + super.setMenuVisibility(menuVisible) + if (mIsFragmentVisible && !menuVisible) { + pauseVideo() + } + + mIsFragmentVisible = menuVisible + if (mWasFragmentInit && menuVisible && context?.config?.autoplayVideos == true) { + playVideo() + } } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) - checkExtendedDetails() + setVideoSize() initTimeHolder() + checkExtendedDetails() updateInstantSwitchWidths() } + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putInt(PROGRESS, mCurrTime) + } + + private fun storeStateVariables() { context!!.config.apply { mStoredShowExtendedDetails = showExtendedDetails mStoredHideExtendedDetails = hideExtendedDetails mStoredExtendedDetails = extendedDetails mStoredBottomActions = bottomActions + mStoredRememberLastVideoPosition = rememberLastVideoPosition + mStoredLastVideoPath = lastVideoPath + mStoredLastVideoPosition = lastVideoPosition } } + private fun setupPlayer() { + if (activity == null) { + return + } + + mTextureView = mView.video_surface + mTextureView!!.setOnClickListener { toggleFullscreen() } + mTextureView!!.surfaceTextureListener = this + + checkExtendedDetails() + } + + private fun saveVideoProgress() { + if (!videoEnded()) { + mStoredLastVideoPosition = mExoPlayer!!.currentPosition.toInt() / 1000 + mStoredLastVideoPath = mMedium.path + } + + context!!.config.apply { + lastVideoPosition = mStoredLastVideoPosition + lastVideoPath = mStoredLastVideoPath + } + } + + private fun setLastVideoSavedPosition() { + if (mStoredLastVideoPath == mMedium.path && mStoredLastVideoPosition > 0) { + setPosition(mStoredLastVideoPosition) + } + } + + private fun setupTimeHolder() { + mSeekBar!!.max = mDuration + mView.video_duration.text = mDuration.getFormattedDuration() + setupTimer() + } + + private fun setupTimer() { + activity!!.runOnUiThread(object : Runnable { + override fun run() { + if (mExoPlayer != null && !mIsDragged && mIsPlaying) { + mCurrTime = (mExoPlayer!!.currentPosition / 1000).toInt() + mSeekBar!!.progress = mCurrTime + mCurrTimeView!!.text = mCurrTime.getFormattedDuration() + } + + mTimerHandler.postDelayed(this, 1000) + } + }) + } + + private fun initExoPlayer() { + val isContentUri = mMedium.path.startsWith("content://") + val uri = if (isContentUri) Uri.parse(mMedium.path) else Uri.fromFile(File(mMedium.path)) + val dataSpec = DataSpec(uri) + val fileDataSource = if (isContentUri) ContentDataSource(context) else 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 = C.STREAM_TYPE_MUSIC + mExoPlayer!!.prepare(audioSource) + } + + private fun initExoPlayerListeners() { + 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?) { + } + + 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 : SimpleExoPlayer.VideoListener { + override fun onVideoSizeChanged(width: Int, height: Int, unappliedRotationDegrees: Int, pixelWidthHeightRatio: Float) { + mVideoSize.x = width + mVideoSize.y = height + setVideoSize() + } + + override fun onRenderedFirstFrame() {} + }) + } + private fun launchVideoPlayer() { listener?.launchViewVideoIntent(mMedium.path) } @@ -204,6 +431,8 @@ class VideoFragment : ViewPagerFragment() { if (!mIsFullscreen) { mTimeHolder.beVisible() } + + mSeekBar!!.setOnSeekBarChangeListener(if (mIsFullscreen) null else this) mTimeHolder.animate().alpha(newAlpha).start() mView.video_details.apply { if (mStoredShowExtendedDetails && isVisible()) { @@ -228,4 +457,215 @@ class VideoFragment : ViewPagerFragment() { } return context!!.realScreenSize.y - height - actionsHeight - fullscreenOffset } + + private fun skip(forward: Boolean) { + if (mExoPlayer == null || mIsPanorama) { + return + } + + 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(mExoPlayer!!.duration.toInt(), roundProgress), 0) + setPosition(limitedProgress) + if (!mIsPlaying) { + togglePlayPause() + } + } + + override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { + if (mExoPlayer != null && fromUser) { + setPosition(progress) + } + } + + override fun onStartTrackingTouch(seekBar: SeekBar) { + if (mExoPlayer == null) + return + + mExoPlayer!!.playWhenReady = false + mIsDragged = true + } + + override fun onStopTrackingTouch(seekBar: SeekBar) { + if (mIsPanorama) { + openPanorama() + return + } + + if (mExoPlayer == null) + return + + if (mIsPlaying) { + mExoPlayer!!.playWhenReady = true + } else { + togglePlayPause() + } + + mIsDragged = false + } + + private fun togglePlayPause() { + if (activity == null || !isAdded) + return + + mIsPlaying = !mIsPlaying + if (mIsPlaying) { + playVideo() + } else { + pauseVideo() + } + } + + fun playVideo() { + if (mExoPlayer == null) { + return + } + + if (mView.video_preview.isVisible()) { + mView.video_preview.beGone() + initExoPlayer() + } + + val wasEnded = videoEnded() + if (wasEnded) { + setPosition(0) + } + + if (mStoredRememberLastVideoPosition) { + setLastVideoSavedPosition() + clearLastVideoSavedProgress() + } + + if (!wasEnded || context?.config?.loopVideos == false) { + mView.video_play_outline.setImageResource(R.drawable.ic_pause) + } + + mWasVideoStarted = true + mIsPlaying = true + mExoPlayer?.playWhenReady = true + activity!!.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun clearLastVideoSavedProgress() { + mStoredLastVideoPosition = 0 + mStoredLastVideoPath = "" + } + + private fun pauseVideo() { + if (mExoPlayer == null) { + return + } + + mIsPlaying = false + if (!videoEnded()) { + mExoPlayer?.playWhenReady = false + } + + mView.video_play_outline?.setImageResource(R.drawable.ic_play) + activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + } + + private fun videoEnded(): Boolean { + val currentPos = mExoPlayer?.currentPosition ?: 0 + val duration = mExoPlayer?.duration ?: 0 + return currentPos != 0L && currentPos >= duration + } + + private fun setPosition(seconds: Int) { + mExoPlayer?.seekTo(seconds * 1000L) + mSeekBar!!.progress = seconds + mCurrTimeView!!.text = seconds.getFormattedDuration() + } + + private fun setupVideoDuration() { + mDuration = mMedium.path.getVideoDuration() + setupTimeHolder() + setPosition(0) + } + + private fun videoPrepared() { + if (mDuration == 0) { + mDuration = (mExoPlayer!!.duration / 1000).toInt() + setupTimeHolder() + setPosition(mCurrTime) + + if (mIsFragmentVisible && (context!!.config.autoplayVideos)) { + playVideo() + } + } + } + + private fun videoCompleted() { + if (!isAdded || mExoPlayer == null) { + return + } + + mCurrTime = (mExoPlayer!!.duration / 1000).toInt() + if (listener?.videoEnded() == false && context!!.config.loopVideos) { + playVideo() + } else { + mSeekBar!!.progress = mSeekBar!!.max + mCurrTimeView!!.text = mDuration.getFormattedDuration() + pauseVideo() + } + } + + private fun cleanup() { + pauseVideo() + mCurrTimeView?.text = 0.getFormattedDuration() + releaseExoPlayer() + mSeekBar?.progress = 0 + mTimerHandler.removeCallbacksAndMessages(null) + mTextureView = null + } + + private fun releaseExoPlayer() { + mExoPlayer?.stop() + Thread { + mExoPlayer?.release() + mExoPlayer = null + }.start() + } + + override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {} + + override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {} + + override fun onSurfaceTextureDestroyed(surface: SurfaceTexture?) = false + + override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) { + Thread { + mExoPlayer?.setVideoSurface(Surface(mTextureView!!.surfaceTexture)) + }.start() + } + + private fun setVideoSize() { + if (activity == null || mTextureView == null) + return + + val videoProportion = mVideoSize.x.toFloat() / mVideoSize.y.toFloat() + val display = activity!!.windowManager.defaultDisplay + val screenWidth: Int + val screenHeight: Int + + val realMetrics = DisplayMetrics() + display.getRealMetrics(realMetrics) + screenWidth = realMetrics.widthPixels + screenHeight = realMetrics.heightPixels + + val screenProportion = screenWidth.toFloat() / screenHeight.toFloat() + + mTextureView!!.layoutParams.apply { + if (videoProportion > screenProportion) { + width = screenWidth + height = (screenWidth.toFloat() / videoProportion).toInt() + } else { + width = (videoProportion * screenHeight.toFloat()).toInt() + height = screenHeight + } + mTextureView!!.layoutParams = this + } + } } diff --git a/app/src/main/res/layout/pager_video_item.xml b/app/src/main/res/layout/pager_video_item.xml index 9537f8997..fc968dfd5 100644 --- a/app/src/main/res/layout/pager_video_item.xml +++ b/app/src/main/res/layout/pager_video_item.xml @@ -11,6 +11,12 @@ android:layout_width="match_parent" android:layout_height="match_parent"/> + +