From b1bafff68844865cd154d2941f81acb2537ec037 Mon Sep 17 00:00:00 2001 From: Andrey Pavlenko Date: Mon, 1 Jun 2020 03:46:00 +0300 Subject: [PATCH] Gesture support --- depends/utils | 2 +- .../media/service/FermataServiceUiBinder.java | 70 +++++----- .../media/service/MediaSessionCallback.java | 21 +-- .../aap/fermata/ui/view/ControlPanelView.java | 132 ++++++++++++++++-- .../me/aap/fermata/ui/view/VideoView.java | 4 +- 5 files changed, 169 insertions(+), 60 deletions(-) diff --git a/depends/utils b/depends/utils index 8e57d8d5..4ec0a909 160000 --- a/depends/utils +++ b/depends/utils @@ -1 +1 @@ -Subproject commit 8e57d8d53fa4f6258e1993d6c1ec0ac660d3a1f0 +Subproject commit 4ec0a909327d8dbe32fc209f7d826b7e781d2817 diff --git a/fermata/src/main/java/me/aap/fermata/media/service/FermataServiceUiBinder.java b/fermata/src/main/java/me/aap/fermata/media/service/FermataServiceUiBinder.java index 277b8f14..66dd9c05 100644 --- a/fermata/src/main/java/me/aap/fermata/media/service/FermataServiceUiBinder.java +++ b/fermata/src/main/java/me/aap/fermata/media/service/FermataServiceUiBinder.java @@ -4,7 +4,6 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -96,11 +95,6 @@ public MediaSessionCallback getMediaSessionCallback() { return sessionCallback; } - @NonNull - public MediaControllerCompat getMediaController() { - return mediaController; - } - @NonNull public MediaLib getLib() { return getMediaSessionCallback().getMediaLib(); @@ -126,19 +120,12 @@ public void playItem(PlayableItem i) { } public void playItem(PlayableItem i, long pos) { - MediaControllerCompat mediaController = getMediaController(); - if (i.equals(getCurrentItem()) && (pos <= 0)) { - PlaybackStateCompat st = mediaController.getPlaybackState(); - if ((st != null) && (st.getState() == PlaybackStateCompat.STATE_PAUSED)) { - mediaController.getTransportControls().play(); + if (sessionCallback.getPlaybackState().getState() == PlaybackStateCompat.STATE_PAUSED) { + sessionCallback.onPlay(); } - } else if (pos > 0) { - Bundle b = new Bundle(); - b.putLong(MediaSessionCallback.EXTRA_POS, pos); - mediaController.getTransportControls().playFromMediaId(i.getId(), b); } else { - mediaController.getTransportControls().playFromMediaId(i.getId(), null); + sessionCallback.playItem(i, pos); } } @@ -166,24 +153,16 @@ private boolean onPlayPauseButtonLongClick(View v) { public void bindPrevButton(View v) { prevButton = v; - v.setOnClickListener(this::onPrevButtonClick); + v.setOnClickListener(this::onPrevNextButtonClick); v.setOnLongClickListener(this::onPrevNextButtonLongClick); } - private void onPrevButtonClick(View v) { - mediaController.getTransportControls().skipToPrevious(); - } - public void bindNextButton(View v) { nextButton = v; - v.setOnClickListener(this::onNextButtonClick); + v.setOnClickListener(this::onPrevNextButtonClick); v.setOnLongClickListener(this::onPrevNextButtonLongClick); } - private void onNextButtonClick(View v) { - mediaController.getTransportControls().skipToNext(); - } - public void bindRwButton(View v) { rwButton = v; v.setOnClickListener(this::onRwFfButtonClick); @@ -196,6 +175,37 @@ public void bindFfButton(View v) { v.setOnLongClickListener(this::onRwFfButtonLongClick); } + private void onPrevNextButtonClick(View v) { + onPrevNextButtonClick(v == nextButton); + } + + public void onPrevNextButtonClick(boolean next) { + if (next) mediaController.getTransportControls().skipToNext(); + else mediaController.getTransportControls().skipToPrevious(); + } + + private boolean onPrevNextButtonLongClick(View v) { + onPrevNextButtonLongClick(v == nextButton); + return true; + } + + public void onPrevNextButtonLongClick(boolean next) { + MediaSessionCallback cb = getMediaSessionCallback(); + PlaybackControlPrefs pp = cb.getPlaybackControlPrefs(); + cb.rewindFastForward(next, pp.getPrevNextLongTimePref(), + pp.getPrevNextLongTimeUnitPref(), 1); + } + + public void onPrevNextFolderClick(boolean next) { + PlayableItem i = sessionCallback.getCurrentItem(); + if (i == null) return; + MediaLib.BrowsableItem p = i.getParent(); + FutureSupplier f = next ? p.getNextPlayable() : p.getPrevPlayable(); + f.onSuccess(pi -> { + if (pi != null) sessionCallback.playItem(pi, 0); + }); + } + private void onRwFfButtonClick(View v) { onRwFfButtonClick(v == ffButton); } @@ -219,14 +229,6 @@ public void onRwFfButtonLongClick(boolean ff) { cb.rewindFastForward(ff, pp.getRwFfLongTimePref(), pp.getRwFfLongTimeUnitPref(), 1); } - private boolean onPrevNextButtonLongClick(View v) { - MediaSessionCallback cb = getMediaSessionCallback(); - PlaybackControlPrefs pp = cb.getPlaybackControlPrefs(); - cb.rewindFastForward(v == nextButton, pp.getPrevNextLongTimePref(), - pp.getPrevNextLongTimeUnitPref(), 1); - return true; - } - public void bindProgressBar(SeekBar progressBar) { this.progressBar = progressBar; progressBar.setEnabled(false); diff --git a/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java b/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java index e772bed7..c0cfa6d3 100644 --- a/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java +++ b/fermata/src/main/java/me/aap/fermata/media/service/MediaSessionCallback.java @@ -390,7 +390,7 @@ private FutureSupplier play() { case PlaybackStateCompat.STATE_STOPPED: case PlaybackStateCompat.STATE_ERROR: return lib.getLastPlayedItem().then(this::prepareItem).then(i -> { - if (i != null) playItem(i, lib.getLastPlayedPosition(i)); + if (i != null) playPreparedItem(i, lib.getLastPlayedPosition(i)); return completedVoid(); }); case PlaybackStateCompat.STATE_PAUSED: @@ -437,7 +437,7 @@ private FutureSupplier playFromMediaId(String mediaId, Bundle extras) { }).then(this::prepareItem).then(pi -> { if (pi != null) { long pos = (extras == null) ? 0 : extras.getLong(EXTRA_POS, 0); - playItem(pi, pos); + playPreparedItem(pi, pos); } else { String msg = lib.getContext().getResources().getString(R.string.err_failed_to_play, mediaId); Log.w(msg); @@ -568,7 +568,7 @@ private void skipTo(boolean next, PlayableItem i) { b.setState(next ? STATE_SKIPPING_TO_NEXT : STATE_SKIPPING_TO_PREVIOUS, state.getPosition(), state.getPlaybackSpeed()); setPlaybackState(b.build()); - playItem(i, 0); + playPreparedItem(i, 0); } @Override @@ -749,7 +749,7 @@ private FutureSupplier skipToQueueItem(PlayableItem pi, long queueId) { b.setState(PlaybackStateCompat.STATE_SKIPPING_TO_QUEUE_ITEM, state.getPosition(), state.getPlaybackSpeed()); setPlaybackState(b.build()); - playItem(i, 0); + playPreparedItem(i, 0); return completedVoid(); }); } @@ -1030,7 +1030,12 @@ public void onAudioFocusChange(int focusChange) { } } - private void playItem(PlayableItem i, long pos) { + public void playItem(PlayableItem i, long pos) { + playerTask.cancel(); + playerTask = prepareItem(i).onSuccess(pi -> playPreparedItem(i, pos)); + } + + private void playPreparedItem(PlayableItem i, long pos) { MediaEngine eng = getEngine(); if (eng != null) { @@ -1038,15 +1043,15 @@ private void playItem(PlayableItem i, long pos) { if ((current != null) && !current.isExternal()) { eng.getPosition().main().onSuccess(currentPos - -> playItem(eng, i, pos, current, currentPos)); + -> playPreparedItem(eng, i, pos, current, currentPos)); return; } } - playItem(eng, i, pos, null, -1); + playPreparedItem(eng, i, pos, null, -1); } - private void playItem(MediaEngine eng, PlayableItem i, long pos, PlayableItem current, long currentPos) { + private void playPreparedItem(MediaEngine eng, PlayableItem i, long pos, PlayableItem current, long currentPos) { engine = eng = getEngineManager().createEngine(eng, i, this); if (eng == null) { diff --git a/fermata/src/main/java/me/aap/fermata/ui/view/ControlPanelView.java b/fermata/src/main/java/me/aap/fermata/ui/view/ControlPanelView.java index 4c1989be..14cae489 100644 --- a/fermata/src/main/java/me/aap/fermata/ui/view/ControlPanelView.java +++ b/fermata/src/main/java/me/aap/fermata/ui/view/ControlPanelView.java @@ -4,6 +4,7 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; +import android.media.AudioManager; import android.os.Bundle; import android.os.Parcelable; import android.util.AttributeSet; @@ -13,6 +14,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.widget.LinearLayoutCompat; +import androidx.core.view.GestureDetectorCompat; import java.util.List; @@ -43,23 +45,33 @@ import me.aap.utils.ui.UiUtils; import me.aap.utils.ui.menu.OverlayMenu; import me.aap.utils.ui.menu.OverlayMenuItem; +import me.aap.utils.ui.view.GestureListener; import me.aap.utils.ui.view.ImageButton; +import static android.media.AudioManager.ADJUST_LOWER; +import static android.media.AudioManager.ADJUST_RAISE; +import static android.media.AudioManager.FLAG_SHOW_UI; +import static android.media.AudioManager.STREAM_MUSIC; + /** * @author Andrey Pavlenko */ public class ControlPanelView extends LinearLayoutCompat implements MainActivityListener, - PreferenceStore.Listener, OverlayMenu.SelectionHandler { + PreferenceStore.Listener, OverlayMenu.SelectionHandler, GestureListener { private static final byte MASK_VISIBLE = 1; private static final byte MASK_VIDEO_MODE = 2; + private final GestureDetectorCompat gestureDetector; private final ImageButton showHideBars; private PlaybackControlPrefs prefs; private HideTimer hideTimer; private byte mask; + private View gestureSource; + private long scrollStamp; @SuppressLint("PrivateResource") public ControlPanelView(Context context, AttributeSet attrs) { super(context, attrs, R.attr.appControlPanelStyle); + gestureDetector = new GestureDetectorCompat(context, this); setOrientation(VERTICAL); inflate(context, R.layout.control_panel_view, this); @@ -120,15 +132,6 @@ public boolean isActive() { return mask != 0; } - @Override - public boolean onInterceptTouchEvent(MotionEvent e) { - if (hideTimer != null) { - hideTimer = new HideTimer(hideTimer.views); - FermataApplication.get().getHandler().postDelayed(hideTimer, getTouchDelay()); - } - return getActivity().interceptTouchEvent(e, super::onTouchEvent); - } - @Override public void setVisibility(int visibility) { if (visibility == VISIBLE) { @@ -197,12 +200,102 @@ public void disableVideoMode() { setShowHideBarsIcon(a); } - public void onVideoViewTouch(VideoView view) { + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + if (hideTimer != null) { + hideTimer = new HideTimer(hideTimer.views); + FermataApplication.get().getHandler().postDelayed(hideTimer, getTouchDelay()); + } + return getActivity().interceptTouchEvent(e, me -> { + gestureSource = this; + gestureDetector.onTouchEvent(me); + return super.onTouchEvent(me); + }); + } + + @Override + public boolean onSwipeLeft(MotionEvent e1, MotionEvent e2) { + getActivity().getMediaServiceBinder().onPrevNextButtonClick(true); + return true; + } + + @Override + public boolean onSwipeRight(MotionEvent e1, MotionEvent e2) { + getActivity().getMediaServiceBinder().onPrevNextButtonClick(false); + return true; + } + + @Override + public boolean onSwipeUp(MotionEvent e1, MotionEvent e2) { + getActivity().getMediaServiceBinder().onPrevNextFolderClick(false); + return true; + } + + @Override + public boolean onSwipeDown(MotionEvent e1, MotionEvent e2) { + getActivity().getMediaServiceBinder().onPrevNextFolderClick(true); + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + boolean horizontal = Math.abs(distanceX) >= Math.abs(distanceY); + long time = System.currentTimeMillis(); + long diff; + + if (horizontal) { + diff = time - scrollStamp; + if (diff < 100) return true; + scrollStamp = time; + } else { + diff = time + scrollStamp; + if (diff < 100) return true; + scrollStamp = -time; + } + + if (diff > 500) return true; + + if (horizontal) { + FermataServiceUiBinder b = getActivity().getMediaServiceBinder(); + + switch (e2.getPointerCount()) { + case 1: + b.onRwFfButtonClick(distanceX < 0); + break; + case 2: + b.onRwFfButtonLongClick(distanceX < 0); + break; + default: + b.onPrevNextButtonLongClick(distanceX < 0); + break; + } + + onVideoSeek(); + } else { + AudioManager amgr = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); + if (amgr == null) return false; + amgr.adjustStreamVolume(STREAM_MUSIC, (distanceY > 0) ? ADJUST_RAISE : ADJUST_LOWER, FLAG_SHOW_UI); + } + + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (!(gestureSource instanceof VideoView)) return false; + getActivity().getMediaServiceBinder().onPlayPauseButtonClick(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + if (!(gestureSource instanceof VideoView)) return false; + int delay = getTouchDelay(); - if (delay == 0) return; + if (delay == 0) return false; MainActivityDelegate a = getActivity(); - View title = view.getTitle(); + View title = ((VideoView) gestureSource).getTitle(); View fb = a.getFloatingButton(); if (getVisibility() == VISIBLE) { @@ -217,12 +310,23 @@ public void onVideoViewTouch(VideoView view) { hideTimer = new HideTimer(title, fb); App.get().getHandler().postDelayed(hideTimer, delay); } + + return true; + } + + public void onVideoViewTouch(VideoView view, MotionEvent e) { + gestureSource = view; + gestureDetector.onTouchEvent(e); } public void onVideoSeek() { MainActivityDelegate a = getActivity(); VideoView vv = a.getMediaServiceBinder().getMediaSessionCallback().getVideoView(); - if (vv == null) return; + + if (vv == null) { + if (gestureSource instanceof VideoView) vv = (VideoView) gestureSource; + else return; + } View title = vv.getTitle(); View fb = a.getFloatingButton(); diff --git a/fermata/src/main/java/me/aap/fermata/ui/view/VideoView.java b/fermata/src/main/java/me/aap/fermata/ui/view/VideoView.java index c477a4d4..60c1ded2 100644 --- a/fermata/src/main/java/me/aap/fermata/ui/view/VideoView.java +++ b/fermata/src/main/java/me/aap/fermata/ui/view/VideoView.java @@ -280,9 +280,7 @@ public boolean onKeyDown(int keyCode, KeyEvent event) { } private boolean onTouch(@NonNull MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_DOWN) { - getActivity().getControlPanel().onVideoViewTouch(this); - } + getActivity().getControlPanel().onVideoViewTouch(this, e); return true; }