diff --git a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt
index f057171aa..54681d9d5 100644
--- a/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt
+++ b/app/src/main/kotlin/org/fossify/gallery/activities/VideoPlayerActivity.kt
@@ -16,6 +16,7 @@ import android.util.DisplayMetrics
import android.view.*
import android.widget.RelativeLayout
import android.widget.SeekBar
+import androidx.appcompat.content.res.AppCompatResources
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.ContentDataSource
@@ -60,6 +61,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
private var mVideoSize = Point(0, 0)
private var mTimerHandler = Handler()
private var mPlayWhenReadyHandler = Handler()
+ private var mVolumeController: VolumeController? = null
private var mIgnoreCloseDown = false
@@ -117,6 +119,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoSeekbar.progress = 0
mTimerHandler.removeCallbacksAndMessages(null)
mPlayWhenReadyHandler.removeCallbacksAndMessages(null)
+ mVolumeController?.destroy()
}
}
@@ -186,6 +189,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoDuration.setOnClickListener { doSkip(true) }
binding.bottomVideoTimeHolder.videoTogglePlayPause.setOnClickListener { togglePlayPause() }
binding.bottomVideoTimeHolder.videoPlaybackSpeed.setOnClickListener { showPlaybackSpeedPicker() }
+ binding.bottomVideoTimeHolder.videoToggleMute.setOnClickListener { mVolumeController?.toggleMute() }
binding.videoSurfaceFrame.setOnClickListener { toggleFullscreen() }
binding.videoSurfaceFrame.controller.settings.swallowDoubleTaps = true
@@ -235,6 +239,12 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
}
mDragThreshold = DRAG_THRESHOLD * resources.displayMetrics.density
+ mVolumeController = VolumeController(this) { isMuted ->
+ val icon = if (isMuted) R.drawable.ic_vector_speaker_off else R.drawable.ic_vector_speaker_on
+ binding.bottomVideoTimeHolder.videoToggleMute.setImageDrawable(
+ AppCompatResources.getDrawable(this, icon)
+ )
+ }
}
private fun initExoPlayer() {
@@ -306,6 +316,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
if (!mWasVideoStarted) {
binding.bottomVideoTimeHolder.videoTogglePlayPause.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.beVisible()
+ binding.bottomVideoTimeHolder.videoToggleMute.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(config.playbackSpeed)}x"
mDuration = (mExoPlayer!!.duration / 1000).toInt()
binding.bottomVideoTimeHolder.videoSeekbar.max = mDuration
@@ -478,6 +489,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoTogglePlayPause,
binding.bottomVideoTimeHolder.videoNextFile,
binding.bottomVideoTimeHolder.videoPlaybackSpeed,
+ binding.bottomVideoTimeHolder.videoToggleMute,
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoSeekbar,
binding.bottomVideoTimeHolder.videoDuration,
@@ -491,6 +503,7 @@ open class VideoPlayerActivity : SimpleActivity(), SeekBar.OnSeekBarChangeListen
binding.bottomVideoTimeHolder.videoPrevFile,
binding.bottomVideoTimeHolder.videoNextFile,
binding.bottomVideoTimeHolder.videoPlaybackSpeed,
+ binding.bottomVideoTimeHolder.videoToggleMute,
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoDuration,
).forEach {
diff --git a/app/src/main/kotlin/org/fossify/gallery/fragments/VideoFragment.kt b/app/src/main/kotlin/org/fossify/gallery/fragments/VideoFragment.kt
index ac29c731b..87c1793cf 100644
--- a/app/src/main/kotlin/org/fossify/gallery/fragments/VideoFragment.kt
+++ b/app/src/main/kotlin/org/fossify/gallery/fragments/VideoFragment.kt
@@ -13,6 +13,7 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.SeekBar
import android.widget.TextView
+import androidx.appcompat.content.res.AppCompatResources
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.datasource.ContentDataSource
@@ -84,6 +85,8 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
private lateinit var mPlayPauseButton: ImageView
private lateinit var mSeekBar: SeekBar
+ private var mVolumeController: VolumeController? = null
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val context = requireContext()
val activity = requireActivity()
@@ -98,6 +101,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
videoHolder.setOnClickListener { toggleFullscreen() }
videoPreview.setOnClickListener { toggleFullscreen() }
bottomVideoTimeHolder.videoPlaybackSpeed.setOnClickListener { showPlaybackSpeedPicker() }
+ bottomVideoTimeHolder.videoToggleMute.setOnClickListener { mVolumeController?.toggleMute() }
videoSurfaceFrame.controller.settings.swallowDoubleTaps = true
videoPlayOutline.setOnClickListener {
@@ -240,6 +244,13 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
restoreLastVideoSavedPosition()
}
+ mVolumeController = VolumeController(context) { isMuted ->
+ val icon = if (isMuted) R.drawable.ic_vector_speaker_off else R.drawable.ic_vector_speaker_on
+ binding.bottomVideoTimeHolder.videoToggleMute.setImageDrawable(
+ AppCompatResources.getDrawable(context, icon)
+ )
+ }
+
return mView
}
@@ -527,7 +538,8 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
binding.bottomVideoTimeHolder.videoCurrTime,
binding.bottomVideoTimeHolder.videoDuration,
binding.bottomVideoTimeHolder.videoTogglePlayPause,
- binding.bottomVideoTimeHolder.videoPlaybackSpeed
+ binding.bottomVideoTimeHolder.videoPlaybackSpeed,
+ binding.bottomVideoTimeHolder.videoToggleMute
).forEach {
it.isClickable = !mIsFullscreen
}
@@ -689,6 +701,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
if (!mWasVideoStarted) {
binding.videoPlayOutline.beGone()
mPlayPauseButton.beVisible()
+ binding.bottomVideoTimeHolder.videoToggleMute.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.beVisible()
binding.bottomVideoTimeHolder.videoPlaybackSpeed.text = "${DecimalFormat("#.##").format(mConfig.playbackSpeed)}x"
}
@@ -790,6 +803,7 @@ class VideoFragment : ViewPagerFragment(), TextureView.SurfaceTextureListener, S
private fun cleanup() {
pauseVideo()
releaseExoPlayer()
+ mVolumeController?.destroy()
if (mWasFragmentInit) {
mCurrTimeView.text = 0.getFormattedDuration()
diff --git a/app/src/main/kotlin/org/fossify/gallery/helpers/VolumeController.kt b/app/src/main/kotlin/org/fossify/gallery/helpers/VolumeController.kt
new file mode 100644
index 000000000..e9ed531a2
--- /dev/null
+++ b/app/src/main/kotlin/org/fossify/gallery/helpers/VolumeController.kt
@@ -0,0 +1,58 @@
+package org.fossify.gallery.helpers
+
+import android.content.Context
+import android.database.ContentObserver
+import android.media.AudioManager
+import android.os.Handler
+import android.os.Looper
+import android.provider.Settings
+import org.fossify.gallery.extensions.audioManager
+
+class VolumeController(
+ private val context: Context,
+ private val streamType: Int = AudioManager.STREAM_MUSIC,
+ private val onVolumeChanged: (isMuted: Boolean) -> Unit
+) {
+ private var audioManager = context.audioManager
+ private var savedVolume = audioManager.getStreamMaxVolume(streamType) / 2
+
+ private val currentVolume: Int
+ get() = audioManager.getStreamVolume(streamType)
+
+ private val volumeObserver = object : ContentObserver(Handler(Looper.getMainLooper())) {
+ override fun onChange(selfChange: Boolean) {
+ super.onChange(selfChange)
+ onVolumeChanged(isMuted())
+ }
+ }
+
+ init {
+ context.contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, volumeObserver)
+ onVolumeChanged(isMuted())
+ }
+
+ private fun isMuted() = currentVolume == 0
+
+ private fun mute() {
+ savedVolume = audioManager.getStreamVolume(streamType)
+ audioManager.setStreamVolume(streamType, 0, 0)
+ }
+
+ private fun unmute() {
+ audioManager.setStreamVolume(streamType, savedVolume, 0)
+ }
+
+ fun toggleMute() {
+ if (isMuted()) {
+ unmute()
+ onVolumeChanged(false)
+ } else {
+ mute()
+ onVolumeChanged(true)
+ }
+ }
+
+ fun destroy() {
+ context.contentResolver.unregisterContentObserver(volumeObserver)
+ }
+}
diff --git a/app/src/main/res/drawable/ic_vector_speaker_off.xml b/app/src/main/res/drawable/ic_vector_speaker_off.xml
new file mode 100644
index 000000000..8434fa097
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_speaker_off.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_vector_speaker_on.xml b/app/src/main/res/drawable/ic_vector_speaker_on.xml
new file mode 100644
index 000000000..d52491649
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_speaker_on.xml
@@ -0,0 +1,3 @@
+
+
+
diff --git a/app/src/main/res/layout/bottom_video_time_holder.xml b/app/src/main/res/layout/bottom_video_time_holder.xml
index c374a9370..9b172d17f 100644
--- a/app/src/main/res/layout/bottom_video_time_holder.xml
+++ b/app/src/main/res/layout/bottom_video_time_holder.xml
@@ -9,7 +9,7 @@
+
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index d5e24083d..39d75023c 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -11,6 +11,7 @@
70dp
60dp
60dp
+ 68dp
72dp
64dp
128dp