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