Skip to content

Commit

Permalink
[feature] Support long press to speed up playback; support swipe on t…
Browse files Browse the repository at this point in the history
…he video to control volume and brightness
  • Loading branch information
SkyD666 committed Feb 18, 2024
1 parent 571a1d9 commit 4f9679e
Show file tree
Hide file tree
Showing 20 changed files with 480 additions and 38 deletions.
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,21 @@
1. **Subscribe to RSS**, Update RSS, **Read** RSS
2. **Download enclosures** (enclosure tags) of **torrent or magnet** links in RSS articles
3. **Play downloaded videos**
4. Support variable playback **speed** and **double-finger rotate and zoom video** screen
5. Support **dark mode**
6. ......
4. Support variable playback **speed**, **long press** to speed up playback
5. **Double-finger** gesture to **rotate and zoom** video
6. **Swipe** on the video to **control volume**, **brightness**, and **playback position**
7. Support **searching** existing **RSS subscription content**
8. Support **dark mode**
9. ......

## 🚧 Todo

1. **Long press** on the video screen for variable playback **speed**
2. **Swipe** on the video screen to **adjust volume**, screen brightness, and **playback position**
3. **Automatically update RSS** subscriptions and **download videos**
4. **Customize player settings**, such as default screen scale, surface type used by the player, and more
5. **Search** existing **RSS subscription content**
6. **Float** video playback **window**
7. **Automatically** play the **next video**
8. **Seeding** downloaded files
9. **Play other videos on the phone**
1. **Automatically update RSS** subscriptions and **download videos**
2. **Customize player settings**, such as default screen scale, surface type used by the player, and more
3. **Float** video playback **window**
4. **Automatically** play the **next video**
5. **Seeding** downloaded files
6. **Play other videos on the phone**

## 🤩 Screenshots

Expand Down
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ android {
minSdk = 24
targetSdk = 34
versionCode = 2
versionName = "1.0-beta02"
versionName = "1.0-beta03"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:name=".App"
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/com/skyd/anivu/ext/ActivityExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.skyd.anivu.ext

import android.app.Activity
import android.provider.Settings

/**
* 获取系统屏幕亮度
*/
fun Activity.getScreenBrightness(): Int? = try {
Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS)
} catch (e: Settings.SettingNotFoundException) {
e.printStackTrace()
null
}
1 change: 1 addition & 0 deletions app/src/main/java/com/skyd/anivu/ext/ContextExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ val Context.activity: Activity
return tryActivity ?: error("can't find activity: $this")
}

@get:JvmName("tryActivity")
val Context.tryActivity: Activity?
get() {
var ctx = this
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/java/com/skyd/anivu/ext/VibratorExt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.skyd.anivu.ext

import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator

fun Vibrator.tickVibrate(duration: Long = 35) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.EFFECT_TICK))
} else {
vibrate(duration)
}
}
122 changes: 122 additions & 0 deletions app/src/main/java/com/skyd/anivu/ui/player/PlayerControlView.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.common.collect.ImmutableList;
import com.skyd.anivu.ext.IOExtKt;
import com.skyd.anivu.ext.ViewExtKt;
Expand Down Expand Up @@ -353,10 +354,20 @@ public interface OnFullScreenModeChangedListener {
@Nullable
private final View backButton;
@Nullable
private final TextView longPressPlaybackSpeedView;
@Nullable
private final TextView seekPreviewView;
@Nullable
private final View resetZoomView;
@Nullable
private final ViewGroup brightnessControlsView;
@Nullable
private final LinearProgressIndicator brightnessProgressView;
@Nullable
private final ViewGroup volumeControlsView;
@Nullable
private final LinearProgressIndicator volumeProgressView;
@Nullable
private final View playbackSpeedButton;
@Nullable
private final View audioTrackButton;
Expand Down Expand Up @@ -548,6 +559,11 @@ public PlayerControlView(
}
isZoom = false;

longPressPlaybackSpeedView = findViewById(com.skyd.anivu.R.id.exo_long_press_playback_speed);
if (longPressPlaybackSpeedView != null) {
longPressPlaybackSpeedView.setVisibility(View.GONE);
}

seekPreviewView = findViewById(com.skyd.anivu.R.id.exo_seek_preview);
if (seekPreviewView != null) {
seekPreviewView.setVisibility(View.GONE);
Expand All @@ -558,6 +574,18 @@ public PlayerControlView(
playbackSpeedButton.setOnClickListener(componentListener);
}

brightnessControlsView = findViewById(com.skyd.anivu.R.id.exo_brightness_controls);
if (brightnessControlsView != null) {
brightnessControlsView.setVisibility(View.GONE);
}
brightnessProgressView = findViewById(com.skyd.anivu.R.id.exo_brightness_progress);

volumeControlsView = findViewById(com.skyd.anivu.R.id.exo_volume_controls);
if (volumeControlsView != null) {
volumeControlsView.setVisibility(View.GONE);
}
volumeProgressView = findViewById(com.skyd.anivu.R.id.exo_volume_progress);

audioTrackButton = findViewById(R.id.exo_audio_track);
if (audioTrackButton != null) {
audioTrackButton.setOnClickListener(componentListener);
Expand Down Expand Up @@ -1072,6 +1100,12 @@ public void onZoomStateChanged(boolean isZoom) {
}
}

public void setLongPressPlaybackSpeedVisibility(int visibility) {
if (longPressPlaybackSpeedView != null) {
longPressPlaybackSpeedView.setVisibility(visibility);
}
}

public boolean updateSeekPreview(long newPositionPos) {
if (seekPreviewView != null && player != null) {
newPositionPos = Math.min(Math.max(newPositionPos, 0), player.getContentDuration());
Expand All @@ -1090,6 +1124,94 @@ public void setSeekPreviewVisibility(int visibility) {
}
}

public boolean updateBrightnessProgress(int progress) {
if (brightnessProgressView != null) {
ImageView icon = findViewById(com.skyd.anivu.R.id.exo_brightness_icon);
if (icon != null) {
if (progress <= 20) {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_low_24);
} else if (progress > 20 && progress <= 60) {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_medium_24);
} else {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_high_24);
}
}
brightnessProgressView.setProgressCompat(progress, false);
return true;
}
return false;
}

private Runnable setBrightnessControlsGoneRunnable = new Runnable() {
@Override
public void run() {
if (brightnessControlsView != null) {
brightnessControlsView.setVisibility(GONE);
}
}
};

public void setBrightnessControlsVisibility(int visibility) {
if (brightnessControlsView != null) {
removeCallbacks(setBrightnessControlsGoneRunnable);
if (visibility == VISIBLE) {
brightnessControlsView.setVisibility(VISIBLE);
} else {
postDelayed(setBrightnessControlsGoneRunnable, 220);
}
}
}

public void setMaxVolume(int max) {
if (volumeProgressView != null) {
volumeProgressView.setMax(max);
}
}

public void setMinVolume(int min) {
if (volumeProgressView != null) {
volumeProgressView.setMin(min);
}
}

public boolean updateVolumeProgress(int progress) {
if (volumeProgressView != null) {
ImageView icon = findViewById(com.skyd.anivu.R.id.exo_volume_icon);
if (icon != null) {
if (progress <= 0) {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_mute_24);
} else if (progress > 0 && progress <= volumeProgressView.getMax() / 2) {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_down_24);
} else {
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_up_24);
}
}
volumeProgressView.setProgressCompat(progress, false);
return true;
}
return false;
}

private Runnable setVolumeControlsGoneRunnable = new Runnable() {
@Override
public void run() {
if (volumeControlsView != null) {
volumeControlsView.setVisibility(GONE);
}
}
};

public void setVolumeControlsVisibility(int visibility) {
if (volumeControlsView != null) {
removeCallbacks(setVolumeControlsGoneRunnable);
if (visibility == VISIBLE) {
volumeControlsView.setVisibility(VISIBLE);
} else {
postDelayed(setVolumeControlsGoneRunnable, 220);
}
}
}

/**
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
* be automatically hidden after this duration of time has elapsed without user input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.animation.DecelerateInterpolator
import androidx.core.animation.addListener
import kotlin.math.abs
Expand Down Expand Up @@ -92,6 +95,13 @@ class PlayerGestureDetector(
mListener.isZoomChanged(false)
}

private var longPressed = false
private val longPressHandler: Handler = Handler(Looper.getMainLooper())
private var longPressedRunnable = Runnable {
longPressed = true
mListener.onLongPress()
}

fun onTouchEvent(event: MotionEvent): Boolean {
var handled = false
val x = event.x
Expand All @@ -104,12 +114,18 @@ class PlayerGestureDetector(
// Log.e("TAG", "onTouchEvent: move down")
singleMoveDownX = x
singleMoveDownY = y

longPressHandler.postDelayed(
longPressedRunnable,
ViewConfiguration.getLongPressTimeout().toLong()
)

handled = true
}
}

MotionEvent.ACTION_POINTER_DOWN -> {
if (singleMove != 2 && event.pointerCount == 2) {
if (!longPressed && singleMove != 2 && event.pointerCount == 2) {
// Log.e("TAG", "onTouchEvent: scale down")
doublePointer = 1
val centerX = getCenterX(event)
Expand All @@ -124,18 +140,24 @@ class PlayerGestureDetector(
}

MotionEvent.ACTION_MOVE -> {
if (singleMove > 0 && event.pointerCount == 1) {
if (longPressed) { // 长按后,即使手指移动,也不再响应其他事件
doublePointer = 0
singleMove = 0
handled = true
} else if (singleMove > 0 && event.pointerCount == 1) {
doublePointer = 0
// Log.e("TAG", "onTouchEvent: move move")
val deltaX = x - singleMoveDownX
val deltaY = y - singleMoveDownY
val absDeltaX = abs(deltaX)
val absDeltaY = abs(deltaY)
handled = if (singleMove == 2 || absDeltaX > 50 || absDeltaY > 50) {
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
singleMove = 2
mListener.onSingleMoving(deltaX, deltaY, x, y)
} else false
} else if (doublePointer == 1 && event.pointerCount == 2) {
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
singleMove = 0
// Log.e("TAG", "onTouchEvent: scale move")
val centerX = getCenterX(event)
Expand Down Expand Up @@ -164,10 +186,14 @@ class PlayerGestureDetector(
}

MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
// 单指滑动了一段距离,第二个手指又落下滑动,然后两个手指抬起
// 这时候应该只响应单指滑动,因为它先产生的。
// 所以这时event.pointerCount可能不1,所以不能加&& event.pointerCount == 1
if (singleMove > 0/* && event.pointerCount == 1*/) {
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
if (longPressed && event.pointerCount == 1) {
handled = mListener.onLongPressUp()
longPressed = false
} else if (singleMove > 0/* && event.pointerCount == 1*/) {
// 单指滑动了一段距离,第二个手指又落下滑动,然后两个手指抬起
// 这时候应该只响应单指滑动,因为它先产生的。
// 所以这时event.pointerCount可能不1,所以不能加&& event.pointerCount == 1
val deltaX = x - singleMoveDownX
val deltaY = y - singleMoveDownY
handled = mListener.onSingleMoved(deltaX, deltaY, x, y)
Expand Down Expand Up @@ -227,6 +253,8 @@ class PlayerGestureDetector(
fun onSingleMoving(deltaX: Float, deltaY: Float, x: Float, y: Float): Boolean = false
fun onSingleMoved(deltaX: Float, deltaY: Float, x: Float, y: Float): Boolean = false
fun isZoomChanged(isZoom: Boolean) {}
fun onLongPressUp(): Boolean = false
fun onLongPress() {}
}

companion object {
Expand Down
Loading

0 comments on commit 4f9679e

Please sign in to comment.