Video playback speed control (#222)

* add 2x playback speed option when longpress

* changed/removed the x2 speed to x0.25-x5.0 with swip up/down after longpress

* add visual indicator to playback speed, and possibility to reset speed when clicking

* add setting for default playback speed

* improve swip up/down sensitivity

* revert changes that won't be used

* added the playback system from Fossify Music-Player

* adjust playback speed UI and added background

* fixed playback speed control missing while a specific setting is enabled

* fixed wrong playback speed icon when opening video

* Use small letters for preference key

* Restructure layout and cleanup code

---------

Co-authored-by: Naveen Singh <36371707+naveensingh@users.noreply.github.com>
Co-authored-by: Naveen Singh <snaveen935@gmail.com>
This commit is contained in:
Natolu 2024-09-07 19:46:24 +02:00 committed by GitHub
parent 267fdaf69f
commit ee755ef6de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 335 additions and 25 deletions

View file

@ -1,5 +1,6 @@
package org.fossify.gallery.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.content.pm.ActivityInfo
@ -30,10 +31,13 @@ import org.fossify.commons.extensions.*
import org.fossify.gallery.R
import org.fossify.gallery.databinding.ActivityVideoPlayerBinding
import org.fossify.gallery.extensions.*
import org.fossify.gallery.fragments.PlaybackSpeedFragment
import org.fossify.gallery.helpers.*
import org.fossify.gallery.interfaces.PlaybackSpeedListener
import java.text.DecimalFormat
@UnstableApi
open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListener, TextureView.SurfaceTextureListener {
open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListener, TextureView.SurfaceTextureListener, PlaybackSpeedListener {
private val PLAY_WHEN_READY_DRAG_DELAY = 100L
private var mIsFullscreen = false
@ -181,6 +185,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoCurrTime.setOnClickListener { doSkip(false) }
binding.bottomVideoTimeHolder.videoDuration.setOnClickListener { doSkip(true) }
binding.bottomVideoTimeHolder.videoTogglePlayPause.setOnClickListener { togglePlayPause() }
binding.bottomVideoTimeHolder.videoPlaybackSpeed.setOnClickListener { showPlaybackSpeedPicker() }
binding.videoSurfaceFrame.setOnClickListener { toggleFullscreen() }
binding.videoSurfaceFrame.controller.settings.swallowDoubleTaps = true
@ -256,6 +261,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
.setLoadControl(loadControl)
.build()
.apply {
setPlaybackSpeed(config.playbackSpeed)
setMediaSource(mediaSource)
setAudioAttributes(
AudioAttributes
@ -299,10 +305,13 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
private fun videoPrepared() {
if (!mWasVideoStarted) {
binding.bottomVideoTimeHolder.videoTogglePlayPause.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(config.playbackSpeed)}x"
mDuration = (mExoPlayer!!.duration / 1000).toInt()
binding.bottomVideoTimeHolder.videoSeekbar.max = mDuration
binding.bottomVideoTimeHolder.videoDuration.text = mDuration.getFormattedDuration()
setPosition(mCurrTime)
updatePlaybackSpeed(config.playbackSpeed)
if (config.rememberLastVideoPosition) {
setLastVideoSavedPosition()
@ -468,6 +477,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoPrevFile,
binding.bottomVideoTimeHolder.videoTogglePlayPause,
binding.bottomVideoTimeHolder.videoNextFile,
binding.bottomVideoTimeHolder.videoPlaybackSpeed,
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoSeekbar,
binding.bottomVideoTimeHolder.videoDuration,
@ -480,6 +490,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
arrayOf(
binding.bottomVideoTimeHolder.videoPrevFile,
binding.bottomVideoTimeHolder.videoNextFile,
binding.bottomVideoTimeHolder.videoPlaybackSpeed,
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoDuration,
).forEach {
@ -493,6 +504,27 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
}.start()
}
private fun showPlaybackSpeedPicker() {
val fragment = PlaybackSpeedFragment()
fragment.show(supportFragmentManager, PlaybackSpeedFragment::class.java.simpleName)
fragment.setListener(this)
}
override fun updatePlaybackSpeed(speed: Float) {
val isSlow = speed < 1f
if (isSlow != binding.bottomVideoTimeHolder.videoPlaybackSpeed.tag as? Boolean) {
binding.bottomVideoTimeHolder.videoPlaybackSpeed.tag = isSlow
val drawableId = if (isSlow) R.drawable.ic_playback_speed_slow_vector else R.drawable.ic_playback_speed_vector
binding.bottomVideoTimeHolder.videoPlaybackSpeed
.setCompoundDrawablesRelativeWithIntrinsicBounds(resources.getDrawable(drawableId), null, null, null)
}
@SuppressLint("SetTextI18n")
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(speed)}x"
mExoPlayer?.setPlaybackSpeed(speed)
}
private fun initTimeHolder() {
var right = 0
var bottom = 0

View file

@ -0,0 +1,135 @@
package org.fossify.gallery.fragments
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import org.fossify.commons.extensions.*
import org.fossify.commons.views.MySeekBar
import org.fossify.commons.views.MyTextView
import org.fossify.gallery.R
import org.fossify.gallery.databinding.FragmentPlaybackSpeedBinding
import org.fossify.gallery.extensions.config
import org.fossify.gallery.helpers.Config
import org.fossify.gallery.interfaces.PlaybackSpeedListener
class PlaybackSpeedFragment : BottomSheetDialogFragment() {
private val MIN_PLAYBACK_SPEED = 0.25f
private val MAX_PLAYBACK_SPEED = 3f
private val MAX_PROGRESS = (MAX_PLAYBACK_SPEED * 100 + MIN_PLAYBACK_SPEED * 100).toInt()
private val HALF_PROGRESS = MAX_PROGRESS / 2
private val STEP = 0.05f
private var seekBar: MySeekBar? = null
private var listener: PlaybackSpeedListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.CustomBottomSheetDialogTheme)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val config = requireContext().config
val binding = FragmentPlaybackSpeedBinding.inflate(inflater, container, false)
val background = ResourcesCompat.getDrawable(resources, org.fossify.commons.R.drawable.bottom_sheet_bg, requireContext().theme)
(background as LayerDrawable).findDrawableByLayerId(org.fossify.commons.R.id.bottom_sheet_background)
.applyColorFilter(requireContext().getProperBackgroundColor())
binding.apply {
seekBar = playbackSpeedSeekbar
root.setBackgroundDrawable(background)
requireContext().updateTextColors(playbackSpeedHolder)
playbackSpeedSlow.applyColorFilter(requireContext().getProperTextColor())
playbackSpeedFast.applyColorFilter(requireContext().getProperTextColor())
playbackSpeedSlow.setOnClickListener { reduceSpeed() }
playbackSpeedFast.setOnClickListener { increaseSpeed() }
initSeekbar(playbackSpeedSeekbar, playbackSpeedLabel, config)
}
return binding.root
}
private fun initSeekbar(seekbar: MySeekBar, speedLabel: MyTextView, config: Config) {
val formattedValue = formatPlaybackSpeed(config.playbackSpeed)
speedLabel.text = "${formattedValue}x"
seekbar.max = MAX_PROGRESS
val playbackSpeedProgress = config.playbackSpeedProgress
if (playbackSpeedProgress == -1) {
config.playbackSpeedProgress = HALF_PROGRESS
}
seekbar.progress = config.playbackSpeedProgress
var lastUpdatedProgress = config.playbackSpeedProgress
var lastUpdatedFormattedValue = formattedValue
seekbar.onSeekBarChangeListener { progress ->
val playbackSpeed = getPlaybackSpeed(progress)
if (playbackSpeed.toString() != lastUpdatedFormattedValue) {
lastUpdatedProgress = progress
lastUpdatedFormattedValue = playbackSpeed.toString()
config.playbackSpeed = playbackSpeed
config.playbackSpeedProgress = progress
speedLabel.text = "${formatPlaybackSpeed(playbackSpeed)}x"
listener?.updatePlaybackSpeed(playbackSpeed)
} else {
seekbar.progress = lastUpdatedProgress
}
}
}
private fun getPlaybackSpeed(progress: Int): Float {
var playbackSpeed = when {
progress < HALF_PROGRESS -> {
val lowerProgressPercent = progress / HALF_PROGRESS.toFloat()
val lowerProgress = (1 - MIN_PLAYBACK_SPEED) * lowerProgressPercent + MIN_PLAYBACK_SPEED
lowerProgress
}
progress > HALF_PROGRESS -> {
val upperProgressPercent = progress / HALF_PROGRESS.toFloat() - 1
val upperDiff = MAX_PLAYBACK_SPEED - 1
upperDiff * upperProgressPercent + 1
}
else -> 1f
}
playbackSpeed = Math.min(Math.max(playbackSpeed, MIN_PLAYBACK_SPEED), MAX_PLAYBACK_SPEED)
val stepMultiplier = 1 / STEP
return Math.round(playbackSpeed * stepMultiplier) / stepMultiplier
}
private fun reduceSpeed() {
var currentProgress = seekBar?.progress ?: return
val currentSpeed = requireContext().config.playbackSpeed
while (currentProgress > 0) {
val newSpeed = getPlaybackSpeed(--currentProgress)
if (newSpeed != currentSpeed) {
seekBar!!.progress = currentProgress
break
}
}
}
private fun increaseSpeed() {
var currentProgress = seekBar?.progress ?: return
val currentSpeed = requireContext().config.playbackSpeed
while (currentProgress < MAX_PROGRESS) {
val newSpeed = getPlaybackSpeed(++currentProgress)
if (newSpeed != currentSpeed) {
seekBar!!.progress = currentProgress
break
}
}
}
private fun formatPlaybackSpeed(value: Float) = String.format("%.2f", value)
fun setListener(playbackSpeedListener: PlaybackSpeedListener) {
listener = playbackSpeedListener
}
}

View file

@ -1,5 +1,6 @@
package org.fossify.gallery.fragments
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.graphics.Point
import android.graphics.SurfaceTexture
@ -34,13 +35,15 @@ import org.fossify.gallery.extensions.config
import org.fossify.gallery.extensions.hasNavBar
import org.fossify.gallery.extensions.parseFileChannel
import org.fossify.gallery.helpers.*
import org.fossify.gallery.interfaces.PlaybackSpeedListener
import org.fossify.gallery.models.Medium
import org.fossify.gallery.views.MediaSideScroll
import java.io.File
import java.io.FileInputStream
import java.text.DecimalFormat
@UnstableApi
class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener {
class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, SeekBar.OnSeekBarChangeListener, PlaybackSpeedListener {
private val PROGRESS = "progress"
private var mIsFullscreen = false
@ -94,6 +97,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
bottomVideoTimeHolder.videoDuration.setOnClickListener { skip(true) }
videoHolder.setOnClickListener { toggleFullscreen() }
videoPreview.setOnClickListener { toggleFullscreen() }
bottomVideoTimeHolder.videoPlaybackSpeed.setOnClickListener { showPlaybackSpeedPicker() }
videoSurfaceFrame.controller.settings.swallowDoubleTaps = true
videoPlayOutline.setOnClickListener {
@ -390,6 +394,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
if (mConfig.loopVideos && listener?.isSlideShowActive() == false) {
repeatMode = Player.REPEAT_MODE_ONE
}
setPlaybackSpeed(mConfig.playbackSpeed)
setMediaSource(mediaSource)
setAudioAttributes(
AudioAttributes
@ -521,7 +526,8 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
arrayOf(
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoDuration,
binding.bottomVideoTimeHolder.videoTogglePlayPause
binding.bottomVideoTimeHolder.videoTogglePlayPause,
binding.bottomVideoTimeHolder.videoPlaybackSpeed
).forEach {
it.isClickable = !mIsFullscreen
}
@ -538,6 +544,27 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
}
}
private fun showPlaybackSpeedPicker() {
val fragment = PlaybackSpeedFragment()
childFragmentManager.beginTransaction().add(fragment, fragment::class.java.simpleName).commit()
fragment.setListener(this)
}
override fun updatePlaybackSpeed(speed: Float) {
val isSlow = speed < 1f
if (isSlow != binding.bottomVideoTimeHolder.videoPlaybackSpeed.tag as? Boolean) {
binding.bottomVideoTimeHolder.videoPlaybackSpeed.tag = isSlow
val drawableId = if (isSlow) R.drawable.ic_playback_speed_slow_vector else R.drawable.ic_playback_speed_vector
binding.bottomVideoTimeHolder.videoPlaybackSpeed
.setCompoundDrawablesRelativeWithIntrinsicBounds(resources.getDrawable(drawableId), null, null, null)
}
@SuppressLint("SetTextI18n")
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(speed)}x"
mExoPlayer?.setPlaybackSpeed(speed)
}
private fun getExtendedDetailsY(height: Int): Float {
val smallMargin = context?.resources?.getDimension(org.fossify.commons.R.dimen.small_margin) ?: return 0f
val fullscreenOffset = smallMargin + if (mIsFullscreen) 0 else requireContext().navigationBarHeight
@ -662,6 +689,8 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
if (!mWasVideoStarted) {
binding.videoPlayOutline.beGone()
mPlayPauseButton.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(mConfig.playbackSpeed)}x"
}
mWasVideoStarted = true
@ -736,6 +765,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
mExoPlayer?.seekTo(mPositionAtPause)
mPositionAtPause = 0L
}
updatePlaybackSpeed(mConfig.playbackSpeed)
playVideo()
}
mWasPlayerInited = true

View file

@ -169,6 +169,14 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(MAX_BRIGHTNESS, false)
set(maxBrightness) = prefs.edit().putBoolean(MAX_BRIGHTNESS, maxBrightness).apply()
var playbackSpeed: Float
get() = prefs.getFloat(PLAYBACK_SPEED, 1f)
set(playbackSpeed) = prefs.edit().putFloat(PLAYBACK_SPEED, playbackSpeed).apply()
var playbackSpeedProgress: Int
get() = prefs.getInt(PLAYBACK_SPEED_PROGRESS, -1)
set(playbackSpeedProgress) = prefs.edit().putInt(PLAYBACK_SPEED_PROGRESS, playbackSpeedProgress).apply()
var cropThumbnails: Boolean
get() = prefs.getBoolean(CROP_THUMBNAILS, true)
set(cropThumbnails) = prefs.edit().putBoolean(CROP_THUMBNAILS, cropThumbnails).apply()

View file

@ -19,6 +19,8 @@ const val LOOP_VIDEOS = "loop_videos"
const val OPEN_VIDEOS_ON_SEPARATE_SCREEN = "open_videos_on_separate_screen"
const val ANIMATE_GIFS = "animate_gifs"
const val MAX_BRIGHTNESS = "max_brightness"
const val PLAYBACK_SPEED = "playback_speed"
const val PLAYBACK_SPEED_PROGRESS = "playback_speed_progress"
const val CROP_THUMBNAILS = "crop_thumbnails"
const val SHOW_THUMBNAIL_VIDEO_DURATION = "show_thumbnail_video_duration"
const val SCREEN_ROTATION = "screen_rotation"

View file

@ -0,0 +1,5 @@
package org.fossify.gallery.interfaces
interface PlaybackSpeedListener {
fun updatePlaybackSpeed(speed: Float)
}

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M13.5 5.5c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2zm6.5 7V23h-1V12.5c0-0.28-0.22-0.5-0.5-0.5S18 12.22 18 12.5v1h-1v-0.69c-1.46-0.38-2.7-1.29-3.51-2.52C13.18 11.16 13 12.07 13 13c0 0.23 0.02 0.46 0.03 0.69L15 16.5V23h-2v-5l-1.78-2.54L11 19l-3 4-1.6-1.2L9 18.33V13c0-1.15 0.18-2.29 0.5-3.39L8 10.46V14H6V9.3l5.4-3.07v0.01c0.59-0.31 1.32-0.33 1.94 0.03 0.36 0.21 0.63 0.51 0.8 0.85l0.79 1.67C15.58 10.1 16.94 11 18.5 11c0.83 0 1.5 0.67 1.5 1.5z"/>
</vector>

View file

@ -0,0 +1,3 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
<path android:fillColor="#FFFFFFFF" android:pathData="M13.49 5.48c1.1 0 2-0.9 2-2s-0.9-2-2-2-2 0.9-2 2 0.9 2 2 2zm-3.6 13.9l1-4.4 2.1 2v6h2v-7.5l-2.1-2 0.6-3c1.3 1.5 3.3 2.5 5.5 2.5v-2c-1.9 0-3.5-1-4.3-2.4l-1-1.6c-0.4-0.6-1-1-1.7-1-0.3 0-0.5 0.1-0.8 0.1l-5.2 2.2v4.7h2v-3.4l1.8-0.7-1.6 8.1-4.9-1-0.4 2 7 1.4z"/>
</vector>

View file

@ -1,54 +1,78 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/video_time_holder"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true">
<TextView
android:id="@+id/video_playback_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/big_margin"
android:background="@drawable/darkened_automatic_circle_background"
android:drawablePadding="@dimen/tiny_margin"
android:gravity="center"
android:paddingHorizontal="@dimen/normal_margin"
android:paddingVertical="@dimen/medium_margin"
android:shadowColor="@color/default_background_color"
android:textColor="@android:color/white"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_playback_speed_vector"
app:layout_constraintBottom_toBottomOf="@id/video_toggle_play_pause"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/video_toggle_play_pause"
tools:text="1x"
tools:visibility="visible" />
<ImageView
android:id="@+id/video_prev_file"
android:layout_width="@dimen/video_player_play_pause_size"
android:layout_height="@dimen/video_player_play_pause_size"
android:layout_alignParentStart="true"
android:layout_marginStart="@dimen/small_margin"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_prev_outline_vector"
android:visibility="invisible" />
android:visibility="invisible"
app:layout_constraintEnd_toStartOf="@id/video_toggle_play_pause"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/video_toggle_play_pause"
android:layout_width="@dimen/video_player_play_pause_size"
android:layout_height="@dimen/video_player_play_pause_size"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/activity_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/small_margin"
android:src="@drawable/ic_pause_outline_vector"
android:visibility="invisible" />
android:visibility="invisible"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<ImageView
android:id="@+id/video_next_file"
android:layout_width="@dimen/video_player_play_pause_size"
android:layout_height="@dimen/video_player_play_pause_size"
android:layout_alignParentEnd="true"
android:layout_marginTop="@dimen/activity_margin"
android:layout_marginEnd="@dimen/small_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_next_outline_vector"
android:visibility="invisible" />
android:visibility="invisible"
app:layout_constraintStart_toEndOf="@id/video_toggle_play_pause"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/video_curr_time"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_below="@+id/video_toggle_play_pause"
android:layout_alignTop="@+id/video_seekbar"
android:layout_alignBottom="@+id/video_seekbar"
android:layout_alignParentStart="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center_vertical"
android:paddingLeft="@dimen/activity_margin"
@ -56,26 +80,26 @@
android:shadowColor="@color/default_background_color"
android:shadowRadius="2"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@+id/video_seekbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/video_toggle_play_pause"
app:layout_constraintTop_toTopOf="@+id/video_seekbar"
tools:text="00:00" />
<org.fossify.commons.views.MySeekBar
android:id="@+id/video_seekbar"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_below="@+id/video_toggle_play_pause"
android:layout_toStartOf="@+id/video_duration"
android:layout_toEndOf="@+id/video_curr_time"
android:paddingTop="@dimen/activity_margin"
android:paddingBottom="@dimen/activity_margin" />
android:paddingBottom="@dimen/activity_margin"
app:layout_constraintEnd_toStartOf="@+id/video_duration"
app:layout_constraintStart_toEndOf="@+id/video_curr_time"
app:layout_constraintTop_toBottomOf="@+id/video_toggle_play_pause" />
<TextView
android:id="@+id/video_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/video_toggle_play_pause"
android:layout_alignTop="@+id/video_seekbar"
android:layout_alignBottom="@+id/video_seekbar"
android:layout_alignParentEnd="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:gravity="center_vertical"
android:paddingLeft="@dimen/activity_margin"
@ -83,6 +107,10 @@
android:shadowColor="@color/default_background_color"
android:shadowRadius="2"
android:textColor="@android:color/white"
app:layout_constraintBottom_toBottomOf="@+id/video_seekbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/video_toggle_play_pause"
app:layout_constraintTop_toTopOf="@+id/video_seekbar"
tools:text="00:00" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/playback_speed_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/activity_margin">
<org.fossify.commons.views.MyTextView
android:id="@+id/playback_speed_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/activity_margin"
android:padding="@dimen/medium_margin"
android:text="@string/playback_speed"
android:textSize="@dimen/actionbar_text_size" />
<ImageView
android:id="@+id/playback_speed_slow"
android:layout_width="@dimen/smaller_icon_size"
android:layout_height="@dimen/smaller_icon_size"
android:layout_below="@+id/playback_speed_title"
android:layout_alignStart="@+id/playback_speed_seekbar"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_playback_speed_slow_vector" />
<org.fossify.commons.views.MyTextView
android:id="@+id/playback_speed_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/playback_speed_slow"
android:layout_centerHorizontal="true"
android:gravity="bottom"
android:textSize="@dimen/big_text_size"
tools:text="1.00x" />
<ImageView
android:id="@+id/playback_speed_fast"
android:layout_width="@dimen/smaller_icon_size"
android:layout_height="@dimen/smaller_icon_size"
android:layout_below="@+id/playback_speed_title"
android:layout_alignEnd="@+id/playback_speed_seekbar"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_playback_speed_vector" />
<org.fossify.commons.views.MySeekBar
android:id="@+id/playback_speed_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/playback_speed_slow"
android:layout_marginBottom="@dimen/medium_margin"
android:padding="@dimen/activity_margin" />
</RelativeLayout>

View file

@ -27,4 +27,5 @@
<dimen name="lock_padding">30dp</dimen>
<dimen name="sample_thumbnail_size">180dp</dimen>
<dimen name="directory_picker_dialog_min_height">180dp</dimen>
<dimen name="smaller_icon_size">36dp</dimen>
</resources>

View file

@ -182,6 +182,7 @@
<string name="delete_empty_folders">Delete empty folders after deleting their content</string>
<string name="allow_photo_gestures">Allow controlling photo brightness with vertical gestures</string>
<string name="allow_video_gestures">Allow controlling video volume and brightness with vertical gestures</string>
<string name="playback_speed">Playback speed</string>
<string name="show_media_count">Show folder media count on the main view</string>
<string name="show_extended_details">Show extended details over fullscreen media</string>
<string name="manage_extended_details">Manage extended details</string>

View file

@ -2,6 +2,14 @@
<style name="AppTheme" parent="AppTheme.Base" />
<style name="CustomBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
<item name="bottomSheetStyle">@style/CustomBottomSheetStyle</item>
</style>
<style name="CustomBottomSheetStyle" parent="Widget.Design.BottomSheet.Modal">
<item name="android:background">@android:color/transparent</item>
</style>
<style name="FullScreenTheme.Base" parent="AppTheme">
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowTranslucentNavigation">true</item>