diff --git a/fermata/src/main/java/me/aap/fermata/ui/activity/MainActivityDelegate.java b/fermata/src/main/java/me/aap/fermata/ui/activity/MainActivityDelegate.java index 03dfc56c..1a2ea0ff 100644 --- a/fermata/src/main/java/me/aap/fermata/ui/activity/MainActivityDelegate.java +++ b/fermata/src/main/java/me/aap/fermata/ui/activity/MainActivityDelegate.java @@ -4,7 +4,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.provider.Settings.System.SCREEN_BRIGHTNESS; import static android.util.Base64.URL_SAFE; import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -50,7 +49,6 @@ import android.os.Build.VERSION_CODES; import android.os.Bundle; import android.os.OperationCanceledException; -import android.provider.Settings; import android.speech.RecognitionListener; import android.speech.RecognizerIntent; import android.speech.SpeechRecognizer; @@ -167,7 +165,8 @@ public class MainActivityDelegate extends ActivityDelegate private FutureSupplier contentLoading; private boolean barsHidden; private boolean videoMode; - private int brightness = 255; + private VideoView videoModeView; + private int videoBrightness = 255; private SpeechListener speechListener; private VoiceCommandHandler voiceCommandHandler; @@ -594,32 +593,33 @@ public void setBarsHidden(boolean barsHidden) { } public void setVideoMode(boolean videoMode, @Nullable VideoView v) { - if (videoMode == this.videoMode) return; + if (videoMode == this.videoMode) { + if (videoMode && (v != null)) videoModeView = v; + return; + } ControlPanelView cp = getControlPanel(); if (videoMode) { this.videoMode = true; + videoModeView = v; setSystemUiVisibility(); keepScreenOn(true); cp.enableVideoMode(v); } else { this.videoMode = false; + videoModeView = null; setSystemUiVisibility(); keepScreenOn(false); if (cp != null) cp.disableVideoMode(); } - if (!checkMirroringMode(false)) { - MainActivityPrefs p = getPrefs(); + boolean mirroringMode = checkMirroringMode(false); + MainActivityPrefs p = getPrefs(); - if (p.getChangeBrightnessPref()) { - if (videoMode) { - brightness = getBrightness(); - setBrightness(p.getBrightnessPref()); - } else { - setBrightness(brightness); - } - } + if (videoMode && p.getChangeBrightnessPref()) setBrightness(p.getBrightnessPref()); + else setBrightness(255); + + if (!mirroringMode) { if (p.getLandscapeVideoPref()) { if (videoMode) { getAppActivity().setRequestedOrientation(SCREEN_ORIENTATION_SENSOR_LANDSCAPE); @@ -655,15 +655,15 @@ public void keepScreenOn(boolean on) { } public int getBrightness() { - return Settings.System.getInt(getContext().getContentResolver(), SCREEN_BRIGHTNESS, 255); + return videoBrightness; } - public void setBrightness(int br) { - try { - Settings.System.putInt(getContext().getContentResolver(), SCREEN_BRIGHTNESS, br); - } catch (SecurityException ex) { - Log.e(ex, "Failed to change brightness"); - } + public void setBrightness(int brightness) { + videoBrightness = Math.max(0, Math.min(255, brightness)); + VideoView videoView = videoModeView; + if (videoView == null) videoView = getMediaSessionCallback().getVideoView(); + if ((videoView == null) && (body != null)) videoView = body.getVideoView(); + if (videoView != null) videoView.setSoftwareBrightness(videoBrightness); } public boolean isVideoMode() { @@ -1055,15 +1055,12 @@ public void onPreferenceChanged(PreferenceStore store, List { + private static final int BRIGHTNESS_GESTURE_SLOP_DP = 8; private final Set> prefChange = new HashSet<>( Arrays.asList(MediaPrefs.VIDEO_SCALE, MediaPrefs.AUDIO_DELAY, MediaPrefs.AUDIO_DELAY_AA, MediaPrefs.SUB_DELAY)); private SubDrawer subDrawer; + private View brightnessOverlay; private FutureSupplier createSurface = new Promise<>(); + private boolean brightnessGesture; + private boolean brightnessDragging; + private float brightnessStartX; + private float brightnessStartY; + private int brightnessStartValue; + private int brightnessGestureValue; public VideoView(Context context) { this(context, null); @@ -127,11 +135,22 @@ protected void init(Context context) { }); addInfoView(context); + addBrightnessOverlay(context); addOnLayoutChangeListener(this); setLayoutParams(new CircularRevealFrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); setFocusable(true); } + protected void addBrightnessOverlay(Context context) { + brightnessOverlay = new View(context); + brightnessOverlay.setBackgroundColor(Color.BLACK); + brightnessOverlay.setAlpha(0f); + brightnessOverlay.setClickable(false); + brightnessOverlay.setFocusable(false); + brightnessOverlay.setVisibility(GONE); + addView(brightnessOverlay, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); + } + protected void addInfoView(Context context) { VideoInfoView d = new VideoInfoView(context, null); FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT); @@ -199,6 +218,7 @@ public void showVideo(boolean hideTitle) { createSurface.onSuccess(v -> { MainActivityDelegate a = getActivity().peek(); if (a == null) return; + if (a.getPrefs().getChangeBrightnessPref()) setSoftwareBrightness(a.getBrightness()); MediaSessionCallback cb = a.getMediaSessionCallback(); MediaEngine eng = cb.getEngine(); if (eng != null) setSurfaceSize(eng); @@ -207,6 +227,15 @@ public void showVideo(boolean hideTitle) { }); } + public void setSoftwareBrightness(int brightness) { + View overlay = brightnessOverlay; + if (overlay == null) return; + int value = Math.max(0, Math.min(255, brightness)); + float alpha = (255 - value) / 255f; + overlay.setAlpha(alpha); + overlay.setVisibility(alpha == 0f ? GONE : VISIBLE); + } + public void prepareSubDrawer(boolean dbl) { MainActivityDelegate a = getActivity().peek(); if (a == null) return; @@ -396,9 +425,96 @@ public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, @SuppressLint("ClickableViewAccessibility") @Override - public boolean onTouchEvent(@NonNull MotionEvent e) { - MainActivityDelegate a = getActivity().peek(); - return (a != null) && a.interceptTouchEvent(e, this::onTouch); + public boolean onTouchEvent(@NonNull MotionEvent event) { + if (handleBrightnessGesture(event)) return true; + MainActivityDelegate activity = getActivity().peek(); + return (activity != null) && activity.interceptTouchEvent(event, this::onTouch); + } + + public boolean handleBrightnessGesture(@NonNull MotionEvent event) { + MainActivityDelegate activity = getActivity().peek(); + if (activity == null) return false; + + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN -> { + if (event.getPointerCount() == 2) { + brightnessGesture = true; + brightnessDragging = false; + brightnessStartX = getPointerCenterX(event); + brightnessStartY = getPointerCenterY(event); + brightnessStartValue = activity.getBrightness(); + brightnessGestureValue = brightnessStartValue; + } else { + resetBrightnessGesture(); + } + } + case MotionEvent.ACTION_MOVE -> { + if (!brightnessGesture || (event.getPointerCount() < 2)) return false; + + float currentX = getPointerCenterX(event); + float currentY = getPointerCenterY(event); + float deltaX = currentX - brightnessStartX; + float deltaY = currentY - brightnessStartY; + + if (!brightnessDragging) { + int slop = toIntPx(getContext(), BRIGHTNESS_GESTURE_SLOP_DP); + if (Math.abs(deltaY) < slop) return false; + if (Math.abs(deltaX) > Math.abs(deltaY)) { + resetBrightnessGesture(); + return false; + } + + brightnessDragging = true; + if (!activity.getPrefs().getChangeBrightnessPref()) { + activity.getPrefs().applyBooleanPref( + MainActivityPrefs.CHANGE_BRIGHTNESS, true); + } + } + + int height = getHeight(); + if ((height <= 0) && (getParent() instanceof View parent)) { + height = parent.getHeight(); + } + int travel = Math.max(height / 2, 1); + int value = brightnessStartValue - Math.round(deltaY * 255f / travel); + brightnessGestureValue = Math.max(0, Math.min(255, value)); + activity.setBrightness(brightnessGestureValue); + setSoftwareBrightness(activity.getBrightness()); + return true; + } + case MotionEvent.ACTION_POINTER_UP, MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + boolean handled = brightnessDragging; + if (handled) activity.getPrefs().applyIntPref( + MainActivityPrefs.BRIGHTNESS, brightnessGestureValue); + resetBrightnessGesture(); + return handled; + } + } + + return false; + } + + private void resetBrightnessGesture() { + brightnessGesture = false; + brightnessDragging = false; + } + + private static float getPointerCenterX(@NonNull MotionEvent event) { + float center = 0f; + int pointerCount = Math.min(event.getPointerCount(), 2); + for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { + center += event.getX(pointerIndex); + } + return center / pointerCount; + } + + private static float getPointerCenterY(@NonNull MotionEvent event) { + float center = 0f; + int pointerCount = Math.min(event.getPointerCount(), 2); + for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { + center += event.getY(pointerIndex); + } + return center / pointerCount; } @Override diff --git a/fermata/src/main/res/values/strings.xml b/fermata/src/main/res/values/strings.xml index 1dd2be2d..71bba900 100644 --- a/fermata/src/main/res/values/strings.xml +++ b/fermata/src/main/res/values/strings.xml @@ -344,9 +344,9 @@ Left Right Center - Change brightness while playing video + Dim video while playing Video brightness - The brightness can also be changed with two finger scroll + Uses a software overlay and can also be adjusted with two-finger vertical scroll Watched threshold Marked video as watched, if more than this number of percentages has been watched diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeVideoView.java b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeVideoView.java index 6bc5379e..7667888f 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeVideoView.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeVideoView.java @@ -5,11 +5,14 @@ import android.content.Context; import android.util.AttributeSet; import android.view.SurfaceView; +import android.view.View; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import me.aap.fermata.addon.web.R; +import me.aap.fermata.ui.activity.MainActivityDelegate; import me.aap.fermata.ui.view.VideoInfoView; import me.aap.fermata.ui.view.VideoView; @@ -26,6 +29,7 @@ public YoutubeVideoView(Context context, AttributeSet attrs) { protected void init(Context context) { addView(new FrameLayout(context), new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); addInfoView(context); + addBrightnessOverlay(context); } @NonNull @@ -39,4 +43,15 @@ public VideoInfoView getVideoInfoView() { public SurfaceView getSubtitleSurface() { return null; } + + @Override + public void setSoftwareBrightness(int brightness) { + super.setSoftwareBrightness(brightness); + View overlay = MainActivityDelegate.get(getContext()).findViewById(R.id.ytBrightnessOverlay); + if (overlay == null) return; + int value = Math.max(0, Math.min(255, brightness)); + float alpha = (255 - value) / 255f; + overlay.setAlpha(alpha); + overlay.setVisibility(alpha == 0f ? View.GONE : View.VISIBLE); + } } diff --git a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java index 7d4e0902..6213480b 100644 --- a/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java +++ b/modules/web/src/main/java/me/aap/fermata/addon/web/yt/YoutubeWebView.java @@ -10,6 +10,7 @@ import android.content.Context; import android.util.AttributeSet; +import android.view.MotionEvent; import android.webkit.CookieManager; import androidx.annotation.NonNull; @@ -17,11 +18,13 @@ import java.util.List; import me.aap.fermata.BuildConfig; +import me.aap.fermata.addon.web.R; import me.aap.fermata.addon.web.FermataChromeClient; import me.aap.fermata.addon.web.FermataJsInterface; import me.aap.fermata.addon.web.FermataWebView; import me.aap.fermata.media.service.MediaSessionCallback; import me.aap.fermata.ui.activity.MainActivityDelegate; +import me.aap.fermata.ui.view.VideoView; import me.aap.utils.async.FutureSupplier; import me.aap.utils.async.Promise; import me.aap.utils.log.Log; @@ -90,6 +93,13 @@ public void goBack() { super.goBack(); } + @Override + public boolean onTouchEvent(MotionEvent event) { + VideoView videoView = MainActivityDelegate.get(getContext()).findViewById(R.id.ytVideoView); + if ((videoView != null) && videoView.handleBrightnessGesture(event)) return true; + return super.onTouchEvent(event); + } + @Override protected void pageLoaded(String uri) { attachListeners(); diff --git a/modules/web/src/main/res/layout/youtube.xml b/modules/web/src/main/res/layout/youtube.xml index 51f21e17..9e8e9e05 100644 --- a/modules/web/src/main/res/layout/youtube.xml +++ b/modules/web/src/main/res/layout/youtube.xml @@ -7,6 +7,16 @@ android:layout_width="match_parent" android:layout_height="match_parent" /> + +