Skip to content

Commit 4f9679e

Browse files
committed
[feature] Support long press to speed up playback; support swipe on the video to control volume and brightness
1 parent 571a1d9 commit 4f9679e

20 files changed

+480
-38
lines changed

README.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,21 +44,21 @@
4444
1. **Subscribe to RSS**, Update RSS, **Read** RSS
4545
2. **Download enclosures** (enclosure tags) of **torrent or magnet** links in RSS articles
4646
3. **Play downloaded videos**
47-
4. Support variable playback **speed** and **double-finger rotate and zoom video** screen
48-
5. Support **dark mode**
49-
6. ......
47+
4. Support variable playback **speed**, **long press** to speed up playback
48+
5. **Double-finger** gesture to **rotate and zoom** video
49+
6. **Swipe** on the video to **control volume**, **brightness**, and **playback position**
50+
7. Support **searching** existing **RSS subscription content**
51+
8. Support **dark mode**
52+
9. ......
5053

5154
## 🚧 Todo
5255

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

6363
## 🤩 Screenshots
6464

app/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ android {
2020
minSdk = 24
2121
targetSdk = 34
2222
versionCode = 2
23-
versionName = "1.0-beta02"
23+
versionName = "1.0-beta03"
2424

2525
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2626

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<uses-permission android:name="android.permission.INTERNET" />
66
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
77
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
8+
<uses-permission android:name="android.permission.VIBRATE" />
89

910
<application
1011
android:name=".App"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.skyd.anivu.ext
2+
3+
import android.app.Activity
4+
import android.provider.Settings
5+
6+
/**
7+
* 获取系统屏幕亮度
8+
*/
9+
fun Activity.getScreenBrightness(): Int? = try {
10+
Settings.System.getInt(contentResolver, Settings.System.SCREEN_BRIGHTNESS)
11+
} catch (e: Settings.SettingNotFoundException) {
12+
e.printStackTrace()
13+
null
14+
}

app/src/main/java/com/skyd/anivu/ext/ContextExt.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ val Context.activity: Activity
2222
return tryActivity ?: error("can't find activity: $this")
2323
}
2424

25+
@get:JvmName("tryActivity")
2526
val Context.tryActivity: Activity?
2627
get() {
2728
var ctx = this
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.skyd.anivu.ext
2+
3+
import android.os.Build
4+
import android.os.VibrationEffect
5+
import android.os.Vibrator
6+
7+
fun Vibrator.tickVibrate(duration: Long = 35) {
8+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
9+
vibrate(VibrationEffect.createOneShot(duration, VibrationEffect.EFFECT_TICK))
10+
} else {
11+
vibrate(duration)
12+
}
13+
}

app/src/main/java/com/skyd/anivu/ui/player/PlayerControlView.java

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
import androidx.recyclerview.widget.LinearLayoutManager;
102102
import androidx.recyclerview.widget.RecyclerView;
103103

104+
import com.google.android.material.progressindicator.LinearProgressIndicator;
104105
import com.google.common.collect.ImmutableList;
105106
import com.skyd.anivu.ext.IOExtKt;
106107
import com.skyd.anivu.ext.ViewExtKt;
@@ -353,10 +354,20 @@ public interface OnFullScreenModeChangedListener {
353354
@Nullable
354355
private final View backButton;
355356
@Nullable
357+
private final TextView longPressPlaybackSpeedView;
358+
@Nullable
356359
private final TextView seekPreviewView;
357360
@Nullable
358361
private final View resetZoomView;
359362
@Nullable
363+
private final ViewGroup brightnessControlsView;
364+
@Nullable
365+
private final LinearProgressIndicator brightnessProgressView;
366+
@Nullable
367+
private final ViewGroup volumeControlsView;
368+
@Nullable
369+
private final LinearProgressIndicator volumeProgressView;
370+
@Nullable
360371
private final View playbackSpeedButton;
361372
@Nullable
362373
private final View audioTrackButton;
@@ -548,6 +559,11 @@ public PlayerControlView(
548559
}
549560
isZoom = false;
550561

562+
longPressPlaybackSpeedView = findViewById(com.skyd.anivu.R.id.exo_long_press_playback_speed);
563+
if (longPressPlaybackSpeedView != null) {
564+
longPressPlaybackSpeedView.setVisibility(View.GONE);
565+
}
566+
551567
seekPreviewView = findViewById(com.skyd.anivu.R.id.exo_seek_preview);
552568
if (seekPreviewView != null) {
553569
seekPreviewView.setVisibility(View.GONE);
@@ -558,6 +574,18 @@ public PlayerControlView(
558574
playbackSpeedButton.setOnClickListener(componentListener);
559575
}
560576

577+
brightnessControlsView = findViewById(com.skyd.anivu.R.id.exo_brightness_controls);
578+
if (brightnessControlsView != null) {
579+
brightnessControlsView.setVisibility(View.GONE);
580+
}
581+
brightnessProgressView = findViewById(com.skyd.anivu.R.id.exo_brightness_progress);
582+
583+
volumeControlsView = findViewById(com.skyd.anivu.R.id.exo_volume_controls);
584+
if (volumeControlsView != null) {
585+
volumeControlsView.setVisibility(View.GONE);
586+
}
587+
volumeProgressView = findViewById(com.skyd.anivu.R.id.exo_volume_progress);
588+
561589
audioTrackButton = findViewById(R.id.exo_audio_track);
562590
if (audioTrackButton != null) {
563591
audioTrackButton.setOnClickListener(componentListener);
@@ -1072,6 +1100,12 @@ public void onZoomStateChanged(boolean isZoom) {
10721100
}
10731101
}
10741102

1103+
public void setLongPressPlaybackSpeedVisibility(int visibility) {
1104+
if (longPressPlaybackSpeedView != null) {
1105+
longPressPlaybackSpeedView.setVisibility(visibility);
1106+
}
1107+
}
1108+
10751109
public boolean updateSeekPreview(long newPositionPos) {
10761110
if (seekPreviewView != null && player != null) {
10771111
newPositionPos = Math.min(Math.max(newPositionPos, 0), player.getContentDuration());
@@ -1090,6 +1124,94 @@ public void setSeekPreviewVisibility(int visibility) {
10901124
}
10911125
}
10921126

1127+
public boolean updateBrightnessProgress(int progress) {
1128+
if (brightnessProgressView != null) {
1129+
ImageView icon = findViewById(com.skyd.anivu.R.id.exo_brightness_icon);
1130+
if (icon != null) {
1131+
if (progress <= 20) {
1132+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_low_24);
1133+
} else if (progress > 20 && progress <= 60) {
1134+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_medium_24);
1135+
} else {
1136+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_brightness_high_24);
1137+
}
1138+
}
1139+
brightnessProgressView.setProgressCompat(progress, false);
1140+
return true;
1141+
}
1142+
return false;
1143+
}
1144+
1145+
private Runnable setBrightnessControlsGoneRunnable = new Runnable() {
1146+
@Override
1147+
public void run() {
1148+
if (brightnessControlsView != null) {
1149+
brightnessControlsView.setVisibility(GONE);
1150+
}
1151+
}
1152+
};
1153+
1154+
public void setBrightnessControlsVisibility(int visibility) {
1155+
if (brightnessControlsView != null) {
1156+
removeCallbacks(setBrightnessControlsGoneRunnable);
1157+
if (visibility == VISIBLE) {
1158+
brightnessControlsView.setVisibility(VISIBLE);
1159+
} else {
1160+
postDelayed(setBrightnessControlsGoneRunnable, 220);
1161+
}
1162+
}
1163+
}
1164+
1165+
public void setMaxVolume(int max) {
1166+
if (volumeProgressView != null) {
1167+
volumeProgressView.setMax(max);
1168+
}
1169+
}
1170+
1171+
public void setMinVolume(int min) {
1172+
if (volumeProgressView != null) {
1173+
volumeProgressView.setMin(min);
1174+
}
1175+
}
1176+
1177+
public boolean updateVolumeProgress(int progress) {
1178+
if (volumeProgressView != null) {
1179+
ImageView icon = findViewById(com.skyd.anivu.R.id.exo_volume_icon);
1180+
if (icon != null) {
1181+
if (progress <= 0) {
1182+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_mute_24);
1183+
} else if (progress > 0 && progress <= volumeProgressView.getMax() / 2) {
1184+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_down_24);
1185+
} else {
1186+
icon.setImageResource(com.skyd.anivu.R.drawable.ic_volume_up_24);
1187+
}
1188+
}
1189+
volumeProgressView.setProgressCompat(progress, false);
1190+
return true;
1191+
}
1192+
return false;
1193+
}
1194+
1195+
private Runnable setVolumeControlsGoneRunnable = new Runnable() {
1196+
@Override
1197+
public void run() {
1198+
if (volumeControlsView != null) {
1199+
volumeControlsView.setVisibility(GONE);
1200+
}
1201+
}
1202+
};
1203+
1204+
public void setVolumeControlsVisibility(int visibility) {
1205+
if (volumeControlsView != null) {
1206+
removeCallbacks(setVolumeControlsGoneRunnable);
1207+
if (visibility == VISIBLE) {
1208+
volumeControlsView.setVisibility(VISIBLE);
1209+
} else {
1210+
postDelayed(setVolumeControlsGoneRunnable, 220);
1211+
}
1212+
}
1213+
}
1214+
10931215
/**
10941216
* Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will
10951217
* be automatically hidden after this duration of time has elapsed without user input.

app/src/main/java/com/skyd/anivu/ui/player/PlayerGestureDetector.kt

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ import android.animation.AnimatorSet
55
import android.animation.ObjectAnimator
66
import android.animation.ValueAnimator
77
import android.os.Build
8+
import android.os.Handler
9+
import android.os.Looper
810
import android.view.MotionEvent
911
import android.view.View
12+
import android.view.ViewConfiguration
1013
import android.view.animation.DecelerateInterpolator
1114
import androidx.core.animation.addListener
1215
import kotlin.math.abs
@@ -92,6 +95,13 @@ class PlayerGestureDetector(
9295
mListener.isZoomChanged(false)
9396
}
9497

98+
private var longPressed = false
99+
private val longPressHandler: Handler = Handler(Looper.getMainLooper())
100+
private var longPressedRunnable = Runnable {
101+
longPressed = true
102+
mListener.onLongPress()
103+
}
104+
95105
fun onTouchEvent(event: MotionEvent): Boolean {
96106
var handled = false
97107
val x = event.x
@@ -104,12 +114,18 @@ class PlayerGestureDetector(
104114
// Log.e("TAG", "onTouchEvent: move down")
105115
singleMoveDownX = x
106116
singleMoveDownY = y
117+
118+
longPressHandler.postDelayed(
119+
longPressedRunnable,
120+
ViewConfiguration.getLongPressTimeout().toLong()
121+
)
122+
107123
handled = true
108124
}
109125
}
110126

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

126142
MotionEvent.ACTION_MOVE -> {
127-
if (singleMove > 0 && event.pointerCount == 1) {
143+
if (longPressed) { // 长按后,即使手指移动,也不再响应其他事件
144+
doublePointer = 0
145+
singleMove = 0
146+
handled = true
147+
} else if (singleMove > 0 && event.pointerCount == 1) {
128148
doublePointer = 0
129149
// Log.e("TAG", "onTouchEvent: move move")
130150
val deltaX = x - singleMoveDownX
131151
val deltaY = y - singleMoveDownY
132152
val absDeltaX = abs(deltaX)
133153
val absDeltaY = abs(deltaY)
134154
handled = if (singleMove == 2 || absDeltaX > 50 || absDeltaY > 50) {
155+
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
135156
singleMove = 2
136157
mListener.onSingleMoving(deltaX, deltaY, x, y)
137158
} else false
138159
} else if (doublePointer == 1 && event.pointerCount == 2) {
160+
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
139161
singleMove = 0
140162
// Log.e("TAG", "onTouchEvent: scale move")
141163
val centerX = getCenterX(event)
@@ -164,10 +186,14 @@ class PlayerGestureDetector(
164186
}
165187

166188
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
167-
// 单指滑动了一段距离,第二个手指又落下滑动,然后两个手指抬起
168-
// 这时候应该只响应单指滑动,因为它先产生的。
169-
// 所以这时event.pointerCount可能不1,所以不能加&& event.pointerCount == 1
170-
if (singleMove > 0/* && event.pointerCount == 1*/) {
189+
longPressHandler.removeCallbacks(longPressedRunnable) // 取消长按监听
190+
if (longPressed && event.pointerCount == 1) {
191+
handled = mListener.onLongPressUp()
192+
longPressed = false
193+
} else if (singleMove > 0/* && event.pointerCount == 1*/) {
194+
// 单指滑动了一段距离,第二个手指又落下滑动,然后两个手指抬起
195+
// 这时候应该只响应单指滑动,因为它先产生的。
196+
// 所以这时event.pointerCount可能不1,所以不能加&& event.pointerCount == 1
171197
val deltaX = x - singleMoveDownX
172198
val deltaY = y - singleMoveDownY
173199
handled = mListener.onSingleMoved(deltaX, deltaY, x, y)
@@ -227,6 +253,8 @@ class PlayerGestureDetector(
227253
fun onSingleMoving(deltaX: Float, deltaY: Float, x: Float, y: Float): Boolean = false
228254
fun onSingleMoved(deltaX: Float, deltaY: Float, x: Float, y: Float): Boolean = false
229255
fun isZoomChanged(isZoom: Boolean) {}
256+
fun onLongPressUp(): Boolean = false
257+
fun onLongPress() {}
230258
}
231259

232260
companion object {

0 commit comments

Comments
 (0)