From 4a9f4a603c7eda695dc5d2708c89b0ff71e5b460 Mon Sep 17 00:00:00 2001 From: Francesco Marastoni <49027005+Francesco146@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:55:30 +0000 Subject: [PATCH] feat(YouTube - Overlay buttons): Add `Mute Video` button (#22) * feat(YouTube - Overlay buttons): Add minimal MuteVolume button * feat(YouTube - Overlay buttons): Swap icons when audio is muted or not * feat(YouTube - Overlay buttons): Update icon when user changes the volume * chore: Update button id and add to animation control --------- Co-authored-by: Aaron Veil <70171475+anddea@users.noreply.github.com> --- .../AbstractPreferenceFragment.java | 2 + .../overlaybutton/BottomControlButton.java | 20 ++--- .../patches/overlaybutton/MuteVolume.java | 76 +++++++++++++++++++ .../patches/utils/PlayerControlsPatch.java | 3 + .../youtube/settings/Settings.java | 1 + .../youtube/utils/VideoUtils.java | 42 +++++++--- .../youtube/utils/VolumeChangeReceiver.java | 19 +++++ 7 files changed, 144 insertions(+), 19 deletions(-) create mode 100644 app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/MuteVolume.java create mode 100644 app/src/main/java/app/revanced/integrations/youtube/utils/VolumeChangeReceiver.java diff --git a/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java index 203ed4e5c5..5c77a196cb 100644 --- a/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/shared/settings/preference/AbstractPreferenceFragment.java @@ -25,6 +25,7 @@ import app.revanced.integrations.shared.settings.Setting; import app.revanced.integrations.shared.utils.Logger; import app.revanced.integrations.shared.utils.Utils; +import app.revanced.integrations.youtube.patches.overlaybutton.MuteVolume; @SuppressWarnings({"unused", "deprecation"}) public abstract class AbstractPreferenceFragment extends PreferenceFragment { @@ -281,6 +282,7 @@ public void onResume() { @Override public void onDestroy() { getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(listener); + MuteVolume.destroy(); super.onDestroy(); } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/BottomControlButton.java b/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/BottomControlButton.java index 2f16350ea5..1b35f9cdd9 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/BottomControlButton.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/BottomControlButton.java @@ -1,10 +1,5 @@ package app.revanced.integrations.youtube.patches.overlaybutton; -import static app.revanced.integrations.shared.utils.ResourceUtils.getAnimation; -import static app.revanced.integrations.shared.utils.ResourceUtils.getInteger; -import static app.revanced.integrations.shared.utils.StringRef.str; -import static app.revanced.integrations.shared.utils.Utils.getChildView; - import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.PorterDuff; @@ -13,16 +8,19 @@ import android.view.ViewGroup; import android.view.animation.Animation; import android.widget.ImageView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import app.revanced.integrations.shared.settings.BooleanSetting; +import app.revanced.integrations.shared.utils.Logger; +import app.revanced.integrations.shared.utils.Utils; import java.lang.ref.WeakReference; import java.util.Objects; -import app.revanced.integrations.shared.settings.BooleanSetting; -import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.shared.utils.Utils; +import static app.revanced.integrations.shared.utils.ResourceUtils.getAnimation; +import static app.revanced.integrations.shared.utils.ResourceUtils.getInteger; +import static app.revanced.integrations.shared.utils.StringRef.str; +import static app.revanced.integrations.shared.utils.Utils.getChildView; public abstract class BottomControlButton { private static final Animation fadeIn; @@ -117,6 +115,10 @@ public void changeSelected(boolean selected) { primaryInteractionSetting.save(selected); } + public void changeActivated(boolean activated) { + buttonRef.get().setActivated(activated); + } + public void changeColorFilter() { ImageView imageView = buttonRef.get(); if (imageView == null) return; diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/MuteVolume.java b/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/MuteVolume.java new file mode 100644 index 0000000000..c956a4b403 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/overlaybutton/MuteVolume.java @@ -0,0 +1,76 @@ +package app.revanced.integrations.youtube.patches.overlaybutton; + +import android.content.IntentFilter; +import android.view.View; +import android.view.ViewGroup; +import app.revanced.integrations.shared.utils.Logger; +import app.revanced.integrations.youtube.settings.Settings; +import app.revanced.integrations.youtube.utils.VideoUtils; +import app.revanced.integrations.youtube.utils.VolumeChangeReceiver; + +import static app.revanced.integrations.shared.utils.Utils.getContext; +import static app.revanced.integrations.youtube.utils.VideoUtils.isAudioMuted; + +@SuppressWarnings("unused") +public class MuteVolume extends BottomControlButton { + private static MuteVolume instance; + static VolumeChangeReceiver volumeChangeReceiver = new VolumeChangeReceiver(); + + public MuteVolume(ViewGroup bottomControlsViewGroup) { + super(bottomControlsViewGroup, + "mute_volume_button", + Settings.OVERLAY_BUTTON_MUTE_VOLUME, + view -> { + VideoUtils.toggleMuteVolume(); + if (instance != null) + instance.changeActivated(!isAudioMuted()); + }, + null + ); + // Set the initial state of the button + this.changeActivated(!isAudioMuted()); + + // Register the volume change receiver to update the button state when the volume is changed + IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION"); + getContext().registerReceiver(volumeChangeReceiver, filter); + } + + public static void initialize(View ViewGroup) { + try { + if (ViewGroup instanceof ViewGroup bottomControlsViewGroup) { + instance = new MuteVolume(bottomControlsViewGroup); + } + } catch (Exception e) { + Logger.printException(() -> "initialize failure", e); + } + } + + public static void changeVisibility(boolean visible, boolean animation) { + MuteVolume muteVolume = instance; + if (muteVolume != null) + muteVolume.setVisibility(visible, animation); + } + + public static void changeVisibilityNegatedImmediate() { + MuteVolume muteVolume = instance; + if (muteVolume != null) + muteVolume.setVisibilityNegatedImmediate(); + } + + // not used + public static void notifyVolumeChange() { + // TODO: not sure if this is implementable + // ideally we would want to change the button state when the volume is changed by the user + // by calling this method on VolumeKeysController.handleVolumeKeyEvent(). However, that method + // is not run if the volume is changed by the user in the YouTube app. A possible solution + // would be to use a global listener to detect volume changes, but I'm not sure if that's possible. + Logger.printInfo(() -> "Volume changed"); + if (instance != null) + instance.changeActivated(!isAudioMuted()); + } + + public static void destroy() { + Logger.printInfo(() -> "Destroying MuteVolume"); + getContext().unregisterReceiver(volumeChangeReceiver); + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PlayerControlsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PlayerControlsPatch.java index 467c9cd258..3c9d7df7a7 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PlayerControlsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PlayerControlsPatch.java @@ -27,6 +27,7 @@ public static void initializeOverlayButtons(View bottomControlsViewGroup) { // CopyVideoUrl.initialize(bottomControlsViewGroup); // CopyVideoUrlTimestamp.initialize(bottomControlsViewGroup); // ExternalDownload.initialize(bottomControlsViewGroup); + // MuteVolume.initialize(bottomControlsViewGroup); // SpeedDialog.initialize(bottomControlsViewGroup); // TimeOrderedPlaylist.initialize(bottomControlsViewGroup); } @@ -59,6 +60,7 @@ private static void changeVisibility(boolean showing, boolean animation) { // CopyVideoUrl.changeVisibility(showing, animation); // CopyVideoUrlTimestamp.changeVisibility(showing, animation); // ExternalDownload.changeVisibility(showing, animation); + // MuteVolume.changeVisibility(showing, animation); // SpeedDialog.changeVisibility(showing, animation); // TimeOrderedPlaylist.changeVisibility(showing, animation); @@ -107,6 +109,7 @@ private static void changeVisibilityNegatedImmediately() { // CopyVideoUrl.changeVisibilityNegatedImmediate(); // CopyVideoUrlTimestamp.changeVisibilityNegatedImmediate(); // ExternalDownload.changeVisibilityNegatedImmediate(); + // MuteVolume.changeVisibilityNegatedImmediate(); // SpeedDialog.changeVisibilityNegatedImmediate(); // TimeOrderedPlaylist.changeVisibilityNegatedImmediate(); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index f189577b05..beeb100b4b 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -322,6 +322,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting OVERLAY_BUTTON_ALWAYS_REPEAT = new BooleanSetting("revanced_overlay_button_always_repeat", FALSE); public static final BooleanSetting OVERLAY_BUTTON_COPY_VIDEO_URL = new BooleanSetting("revanced_overlay_button_copy_video_url", FALSE); public static final BooleanSetting OVERLAY_BUTTON_COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_overlay_button_copy_video_url_timestamp", FALSE); + public static final BooleanSetting OVERLAY_BUTTON_MUTE_VOLUME = new BooleanSetting("revanced_overlay_button_mute_volume", FALSE); public static final BooleanSetting OVERLAY_BUTTON_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_overlay_button_external_downloader", FALSE); public static final BooleanSetting OVERLAY_BUTTON_SPEED_DIALOG = new BooleanSetting("revanced_overlay_button_speed_dialog", TRUE); public static final BooleanSetting OVERLAY_BUTTON_TIME_ORDERED_PLAYLIST = new BooleanSetting("revanced_overlay_button_time_ordered_playlist", FALSE); diff --git a/app/src/main/java/app/revanced/integrations/youtube/utils/VideoUtils.java b/app/src/main/java/app/revanced/integrations/youtube/utils/VideoUtils.java index 7e0fa2366e..592b016a26 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/utils/VideoUtils.java +++ b/app/src/main/java/app/revanced/integrations/youtube/utils/VideoUtils.java @@ -1,22 +1,13 @@ package app.revanced.integrations.youtube.utils; -import static app.revanced.integrations.shared.utils.StringRef.str; -import static app.revanced.integrations.youtube.patches.video.PlaybackSpeedPatch.userSelectedPlaybackSpeed; -import static app.revanced.integrations.youtube.settings.preference.ExternalDownloaderPreference.checkPackageIsEnabled; - import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.AlertDialog; import android.content.Context; +import android.media.AudioManager; import android.util.Log; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - -import java.time.Duration; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicBoolean; - import app.revanced.integrations.shared.settings.BooleanSetting; import app.revanced.integrations.shared.settings.StringSetting; import app.revanced.integrations.shared.utils.IntentUtils; @@ -24,6 +15,15 @@ import app.revanced.integrations.youtube.patches.video.CustomPlaybackSpeedPatch; import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.shared.VideoInformation; +import app.revanced.integrations.youtube.swipecontrols.controller.AudioVolumeController; + +import java.time.Duration; +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicBoolean; + +import static app.revanced.integrations.shared.utils.StringRef.str; +import static app.revanced.integrations.youtube.patches.video.PlaybackSpeedPatch.userSelectedPlaybackSpeed; +import static app.revanced.integrations.youtube.settings.preference.ExternalDownloaderPreference.checkPackageIsEnabled; @SuppressWarnings("unused") public class VideoUtils extends IntentUtils { @@ -32,6 +32,28 @@ public class VideoUtils extends IntentUtils { private static final StringSetting externalDownloaderPackageName = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME; private static final AtomicBoolean isExternalDownloaderLaunched = new AtomicBoolean(false); + public static AudioVolumeController audioVolumeController = new AudioVolumeController(getContext(), AudioManager.STREAM_MUSIC); + private static Integer previousVolumeLevel = 0; + + + public static void toggleMuteVolume() { + int currentVolume = audioVolumeController.getVolume(); + if (currentVolume > 0) { + // Mute the volume + audioVolumeController.setVolume(0); + // save the current volume level to restore later + previousVolumeLevel = currentVolume; + } else { + // Unmute the volume - restore the previous volume level + audioVolumeController.setVolume( + previousVolumeLevel > 0 ? previousVolumeLevel : audioVolumeController.getMaxVolume() + ); + } + } + + public static boolean isAudioMuted() { + return audioVolumeController.getVolume() == 0; + } public static void copyUrl(boolean withTimestamp) { StringBuilder builder = new StringBuilder("https://youtu.be/"); diff --git a/app/src/main/java/app/revanced/integrations/youtube/utils/VolumeChangeReceiver.java b/app/src/main/java/app/revanced/integrations/youtube/utils/VolumeChangeReceiver.java new file mode 100644 index 0000000000..fa5ff05bdc --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/utils/VolumeChangeReceiver.java @@ -0,0 +1,19 @@ +package app.revanced.integrations.youtube.utils; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import app.revanced.integrations.youtube.patches.overlaybutton.MuteVolume; + +/** + * Receiver to notify the MuteVolume button when the volume is changed + */ +public class VolumeChangeReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if ("android.media.VOLUME_CHANGED_ACTION".equals(action)) { + MuteVolume.notifyVolumeChange(); + } + } +} \ No newline at end of file