Compare commits

..

6 Commits

Author SHA1 Message Date
Kai 11fd27b774 Fix https://github.com/futo-org/grayjay-android/issues/2055
Use app level screen brightness instead of changing the system brightness

This also fixes an issue where we don't correctly restore the auto brightness state of the device

Changelog: changed
2025-06-13 14:51:48 -05:00
Koen J 13100dc38d Minor fix in playback speed setting. 2025-06-12 11:21:00 +02:00
Koen J 5227041398 Added setting for hold playback speed increase. Implemented chromecast playback rate adjustment in range [1, 2]. Implemented hold playback speed increase pill. 2025-06-12 10:33:05 +02:00
Kelvin 8491d4da1a Merge branch 'fix-ump-downloads' into 'master'
Revert downloads patch which broke downloads

See merge request videostreaming/grayjay!122
2025-06-11 16:41:20 +00:00
zvonimir 9bea1563ca Revert downloads patch which broke downloads 2025-06-11 18:36:05 +02:00
Koen J 9e7b936663 Implemented hold to play video at 2x speed gesture. 2025-06-11 17:03:53 +02:00
13 changed files with 210 additions and 106 deletions
@@ -11,7 +11,7 @@ import java.nio.ByteBuffer
import kotlin.random.Random
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
/*
class SyncServerTests {
//private val relayHost = "relay.grayjay.app"
@@ -335,4 +335,4 @@ class SyncServerTests {
class AlwaysAuthorized : IAuthorizable {
override val isAuthorized: Boolean get() = true
}
}*/
@@ -13,7 +13,7 @@ import kotlin.random.Random
import java.io.InputStream
import java.io.OutputStream
import kotlin.time.Duration.Companion.seconds
/*
data class PipeStreams(
val initiatorInput: LittleEndianDataInputStream,
val initiatorOutput: LittleEndianDataOutputStream,
@@ -509,4 +509,4 @@ class Authorized : IAuthorizable {
class Unauthorized : IAuthorizable {
override val isAuthorized: Boolean = false
}
}*/
-1
View File
@@ -14,7 +14,6 @@
<!--<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" tools:ignore="ProtectedPermissions"/>
<application
android:allowBackup="true"
@@ -584,6 +584,24 @@ class Settings : FragmentedStorageFileJson() {
playbackSpeeds.sort();
return playbackSpeeds;
}
@FormField(R.string.hold_playback_speed, FieldForm.DROPDOWN, R.string.hold_playback_speed_description, 27)
@DropdownFieldOptionsId(R.array.hold_playback_speeds)
var holdPlaybackSpeed: Int = 3;
fun getHoldPlaybackSpeed(): Double {
return when(holdPlaybackSpeed) {
0 -> 1.25
1 -> 1.5
2 -> 1.75
3 -> 2.0
4 -> 2.25
5 -> 2.5
6 -> 2.75
7 -> 3.0
else -> 2.0
}
}
}
@FormField(R.string.comments, "group", R.string.comments_description, 6)
@@ -1019,19 +1037,16 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.toggle_full_screen, FieldForm.TOGGLE, R.string.toggle_full_screen_descr, 3)
var toggleFullscreen: Boolean = true;
@FormField(R.string.system_brightness, FieldForm.TOGGLE, R.string.system_brightness_descr, 4)
var useSystemBrightness: Boolean = false;
@FormField(R.string.screen_brightness, FieldForm.TOGGLE, R.string.screen_brightness_desc, 4)
var controlScreenBrightness: Boolean = true;
@FormField(R.string.system_volume, FieldForm.TOGGLE, R.string.system_volume_descr, 5)
var useSystemVolume: Boolean = true;
@FormField(R.string.restore_system_brightness, FieldForm.TOGGLE, R.string.restore_system_brightness_descr, 6)
var restoreSystemBrightness: Boolean = true;
@FormField(R.string.zoom_option, FieldForm.TOGGLE, R.string.zoom_option_descr, 7)
@FormField(R.string.zoom_option, FieldForm.TOGGLE, R.string.zoom_option_descr, 6)
var zoom: Boolean = true;
@FormField(R.string.pan_option, FieldForm.TOGGLE, R.string.pan_option_descr, 8)
@FormField(R.string.pan_option, FieldForm.TOGGLE, R.string.pan_option_descr, 7)
var pan: Boolean = true;
}
@@ -35,7 +35,7 @@ class ChromecastCastingDevice : CastingDevice {
override var usedRemoteAddress: InetAddress? = null;
override var localAddress: InetAddress? = null;
override val canSetVolume: Boolean get() = true;
override val canSetSpeed: Boolean get() = false; //TODO: Implement
override val canSetSpeed: Boolean get() = true;
var addresses: Array<InetAddress>? = null;
var port: Int = 0;
@@ -144,6 +144,23 @@ class ChromecastCastingDevice : CastingDevice {
sendChannelMessage("sender-0", transportId, "urn:x-cast:com.google.cast.media", json);
}
override fun changeSpeed(speed: Double) {
if (invokeInIOScopeIfRequired { changeSpeed(speed) }) return
val speedClamped = speed.coerceAtLeast(1.0).coerceAtLeast(1.0).coerceAtMost(2.0)
setSpeed(speedClamped)
val mediaSessionId = _mediaSessionId ?: return
val transportId = _transportId ?: return
val setSpeedObject = JSONObject().apply {
put("type", "SET_PLAYBACK_RATE")
put("mediaSessionId", mediaSessionId)
put("playbackRate", speedClamped)
put("requestId", _requestId++)
}
sendChannelMessage(sourceId = "sender-0", destinationId = transportId, namespace = "urn:x-cast:com.google.cast.media", json = setSpeedObject.toString())
}
override fun changeVolume(volume: Double) {
if (invokeInIOScopeIfRequired({ changeVolume(volume) })) {
return;
@@ -724,7 +724,7 @@ class VideoDownload {
val t = cue.groupValues[1];
val d = cue.groupValues[2];
val url = foundTemplateUrl.replace("\$Number\$", (indexCounter + 1).toString());
val url = foundTemplateUrl.replace("\$Number\$", (indexCounter).toString());
val data = if(executor != null)
executor.executeRequest("GET", url, null, mapOf());
@@ -455,10 +455,6 @@ class VideoDetailFragment() : MainFragment() {
activity?.enterPictureInPictureMode(params);
}
}
if (isFullscreen) {
viewDetail?.restoreBrightness()
}
}
fun forcePictureInPicture() {
@@ -495,10 +491,6 @@ class VideoDetailFragment() : MainFragment() {
_isActive = true;
_leavingPiP = false;
if (isFullscreen) {
_viewDetail?.saveBrightness()
}
_viewDetail?.let {
Logger.v(TAG, "onResume preventPictureInPicture=false");
it.preventPictureInPicture = false;
@@ -2575,15 +2575,6 @@ class VideoDetailView : ConstraintLayout {
}
}
fun saveBrightness() {
if (Settings.instance.gestureControls.useSystemBrightness) {
_player.gestureControl.saveBrightness()
}
}
fun restoreBrightness() {
_player.gestureControl.restoreBrightness()
}
fun setFullscreen(fullscreen : Boolean) {
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
_player.setFullScreen(fullscreen)
@@ -3219,6 +3210,8 @@ class VideoDetailView : ConstraintLayout {
fun applyFragment(frag: VideoDetailFragment) {
fragment = frag;
_player.fragment = frag
}
@@ -8,7 +8,6 @@ import android.graphics.Matrix
import android.graphics.drawable.Animatable
import android.media.AudioManager
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.LayoutInflater
import android.view.MotionEvent
@@ -24,7 +23,6 @@ import androidx.core.animation.doOnStart
import androidx.core.view.GestureDetectorCompat
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
@@ -39,6 +37,9 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.util.Locale
class GestureControlView : LinearLayout {
@@ -67,8 +68,6 @@ class GestureControlView : LinearLayout {
private val _progressSound: CircularProgressBar;
private var _animatorSound: ObjectAnimator? = null;
private var _brightnessFactor = 1.0f;
private var _originalBrightnessFactor = 1.0f;
private var _originalBrightnessMode: Int = 0;
private var _adjustingBrightness: Boolean = false;
private val _layoutControlsBrightness: FrameLayout;
private val _progressBrightness: CircularProgressBar;
@@ -79,6 +78,9 @@ class GestureControlView : LinearLayout {
private var _adjustingFullscreenDown: Boolean = false;
private var _fullScreenFactorUp = 1.0f;
private var _fullScreenFactorDown = 1.0f;
private val _layoutHoldSpeed: LinearLayout
private val _textHoldFastForward: TextView
private val _imageHoldFastForward: ImageView
private var _scaleGestureDetector: ScaleGestureDetector
private var _scaleFactor = 1.0f
@@ -92,6 +94,11 @@ class GestureControlView : LinearLayout {
private var _surfaceView: View? = null
private var _layoutIndicatorFill: FrameLayout;
private var _layoutIndicatorFit: FrameLayout;
private var _speedHolding = false
private val _speedFormatter = DecimalFormat("#.##", DecimalFormatSymbols(Locale.US)).apply {
roundingMode = java.math.RoundingMode.HALF_UP
}
private val _gestureController: GestureDetectorCompat;
@@ -99,10 +106,13 @@ class GestureControlView : LinearLayout {
val onSeek = Event1<Long>();
val onBrightnessAdjusted = Event1<Float>();
val onBrightnessCleared = Event0();
val onPan = Event2<Float, Float>();
val onZoom = Event1<Float>();
val onSoundAdjusted = Event1<Float>();
val onToggleFullscreen = Event0();
val onSpeedHoldStart = Event0()
val onSpeedHoldEnd = Event0()
var fullScreenGestureEnabled = true
@@ -124,6 +134,9 @@ class GestureControlView : LinearLayout {
_layoutControlsFullscreen = findViewById(R.id.layout_controls_fullscreen);
_layoutIndicatorFill = findViewById(R.id.layout_indicator_fill);
_layoutIndicatorFit = findViewById(R.id.layout_indicator_fit);
_layoutHoldSpeed = findViewById(R.id.layout_controls_increased_speed)
_textHoldFastForward = findViewById(R.id.text_holdFastForward)
_imageHoldFastForward = findViewById(R.id.image_holdFastForward)
_scaleGestureDetector = ScaleGestureDetector(context, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean {
@@ -216,7 +229,20 @@ class GestureControlView : LinearLayout {
return true;
}
override fun onLongPress(p0: MotionEvent) = Unit
override fun onLongPress(p0: MotionEvent) {
if (!_isControlsLocked
&& !_skipping
&& !_adjustingBrightness
&& !_adjustingSound
&& !_adjustingFullscreenUp
&& !_adjustingFullscreenDown
&& !_isPanning
&& !_isZooming) {
_speedHolding = true
showHoldSpeedControls()
onSpeedHoldStart.emit()
}
}
});
_gestureController.setOnDoubleTapListener(object : GestureDetector.OnDoubleTapListener {
@@ -301,6 +327,17 @@ class GestureControlView : LinearLayout {
onPan.emit(_translationX, _translationY)
}
private fun showHoldSpeedControls() {
_layoutHoldSpeed.visibility = View.VISIBLE
_textHoldFastForward.text = _speedFormatter.format(Settings.instance.playback.getHoldPlaybackSpeed()) + "x"
(_imageHoldFastForward.drawable as? Animatable)?.start()
}
private fun hideHoldSpeedControls() {
_layoutHoldSpeed.visibility = View.GONE
(_imageHoldFastForward.drawable as? Animatable)?.stop()
}
fun setupTouchArea(layoutControls: ViewGroup? = null, background: View? = null) {
_layoutControls = layoutControls;
_background = background;
@@ -309,6 +346,12 @@ class GestureControlView : LinearLayout {
override fun onTouchEvent(event: MotionEvent?): Boolean {
val ev = event ?: return super.onTouchEvent(event);
if (ev.action == MotionEvent.ACTION_UP && _speedHolding) {
_speedHolding = false
hideHoldSpeedControls()
onSpeedHoldEnd.emit()
}
cancelHideJob();
if (_skipping) {
@@ -735,60 +778,21 @@ class GestureControlView : LinearLayout {
_animatorBrightness?.start();
}
fun saveBrightness() {
try {
_originalBrightnessMode = android.provider.Settings.System.getInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE)
val brightness = android.provider.Settings.System.getInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS)
_brightnessFactor = brightness / 255.0f;
Log.i(TAG, "Starting brightness brightness: $brightness, _brightnessFactor: $_brightnessFactor, _originalBrightnessMode: $_originalBrightnessMode")
_originalBrightnessFactor = _brightnessFactor
} catch (e: Throwable) {
Settings.instance.gestureControls.useSystemBrightness = false
Settings.instance.save()
UIDialogs.toast(context, "useSystemBrightness disabled due to an error")
}
}
fun restoreBrightness() {
if (Settings.instance.gestureControls.restoreSystemBrightness) {
onBrightnessAdjusted.emit(_originalBrightnessFactor)
if (android.provider.Settings.System.canWrite(context)) {
Log.i(TAG, "Restoring system brightness mode _originalBrightnessMode: $_originalBrightnessMode")
android.provider.Settings.System.putInt(
context.contentResolver,
android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE,
_originalBrightnessMode
)
}
}
}
fun setFullscreen(isFullScreen: Boolean) {
resetZoomPan()
if (isFullScreen) {
if (Settings.instance.gestureControls.useSystemBrightness) {
saveBrightness()
}
onBrightnessCleared.emit()
_brightnessFactor = 1.0f
if (isFullScreen) {
if (Settings.instance.gestureControls.useSystemVolume) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
_soundFactor = currentVolume.toFloat() / maxVolume.toFloat()
}
onBrightnessAdjusted.emit(_brightnessFactor);
onSoundAdjusted.emit(_soundFactor);
} else {
if (Settings.instance.gestureControls.useSystemBrightness) {
restoreBrightness()
} else {
onBrightnessAdjusted.emit(1.0f);
}
//onSoundAdjusted.emit(1.0f);
stopAdjustingBrightness();
stopAdjustingSound();
@@ -18,6 +18,7 @@ import androidx.media3.ui.DefaultTimeBar
import androidx.media3.ui.TimeBar
import com.bumptech.glide.Glide
import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.api.media.models.chapters.IChapter
import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.casting.AirPlayCastingDevice
@@ -58,6 +59,8 @@ class CastView : ConstraintLayout {
private var _inPictureInPicture: Boolean = false;
private var _chapters: List<IChapter>? = null;
private var _currentChapter: IChapter? = null;
private var _speedHoldPrevRate = 1.0
private var _speedHoldWasPlaying = false
val onChapterChanged = Event2<IChapter?, Boolean>();
val onMinimizeClick = Event0();
@@ -87,6 +90,20 @@ class CastView : ConstraintLayout {
_gestureControlView = findViewById(R.id.gesture_control);
_gestureControlView.fullScreenGestureEnabled = false
_gestureControlView.setupTouchArea();
_gestureControlView.onSpeedHoldStart.subscribe {
val d = StateCasting.instance.activeDevice ?: return@subscribe;
_speedHoldWasPlaying = d.isPlaying
_speedHoldPrevRate = d.speed
if (d.canSetSpeed)
d.changeSpeed(Settings.instance.playback.getHoldPlaybackSpeed())
d.resumeVideo()
}
_gestureControlView.onSpeedHoldEnd.subscribe {
val d = StateCasting.instance.activeDevice ?: return@subscribe;
if (!_speedHoldWasPlaying) d.pauseVideo()
d.changeSpeed(_speedHoldPrevRate)
}
_gestureControlView.onSeek.subscribe {
val d = StateCasting.instance.activeDevice ?: return@subscribe;
StateCasting.instance.videoSeekTo(d.expectedCurrentTime + it / 1000);
@@ -13,6 +13,7 @@ import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.TextView
@@ -40,6 +41,7 @@ import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.Event3
import com.futo.platformplayer.formatDuration
import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StatePlayer
@@ -62,6 +64,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
var isFullScreen: Boolean = false
private set;
var fragment: VideoDetailFragment? = null
//Views
private val _root: ConstraintLayout;
private val _videoView: PlayerView;
@@ -117,6 +121,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private var _isControlsLocked: Boolean = false;
private var _speedHoldPrevRate = 1f
private var _speedHoldWasPlaying = false
private val _time_bar_listener: TimeBar.OnScrubListener;
var isFitMode : Boolean = false
@@ -254,6 +261,20 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
gestureControl = findViewById(R.id.gesture_control);
gestureControl.setupTouchArea(_layoutControls, background);
gestureControl.onSpeedHoldStart.subscribe {
exoPlayer?.player?.let { player ->
_speedHoldWasPlaying = player.isPlaying
_speedHoldPrevRate = getPlaybackRate()
setPlaybackRate(Settings.instance.playback.getHoldPlaybackSpeed().toFloat())
player.play()
}
}
gestureControl.onSpeedHoldEnd.subscribe {
exoPlayer?.player?.let { player ->
if (!_speedHoldWasPlaying) player.pause()
setPlaybackRate(_speedHoldPrevRate)
}
}
gestureControl.onSeek.subscribe { seekFromCurrent(it); };
gestureControl.onSoundAdjusted.subscribe {
if (Settings.instance.gestureControls.useSystemVolume) {
@@ -266,17 +287,25 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
};
gestureControl.onToggleFullscreen.subscribe { setFullScreen(!isFullScreen) };
gestureControl.onBrightnessAdjusted.subscribe {
if (Settings.instance.gestureControls.useSystemBrightness) {
setSystemBrightness(it)
if (Settings.instance.gestureControls.controlScreenBrightness) {
setScreenBrightness(it)
} else {
setBrightnessOverlay(it)
if (it == 1.0f) {
_overlay_brightness.visibility = View.GONE;
} else {
_overlay_brightness.visibility = View.VISIBLE;
_overlay_brightness.setBackgroundColor(Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - it)).toArgb());
}
}
};
}
gestureControl.onBrightnessCleared.subscribe {
if (Settings.instance.gestureControls.controlScreenBrightness) {
clearCustomScreenBrightness()
} else {
setBrightnessOverlay(1.0f)
_overlay_brightness.visibility = View.GONE
}
}
gestureControl.onPan.subscribe { x, y ->
_videoView.translationX = x
_videoView.translationY = y
@@ -459,33 +488,26 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
}
}
private fun setBrightnessOverlay(brightness: Float) {
_overlay_brightness.setBackgroundColor(
Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - brightness)).toArgb()
)
}
private fun updateAutoplayButton() {
_control_autoplay.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
_control_autoplay_fullscreen.setColorFilter(ContextCompat.getColor(context, if (StatePlayer.instance.autoplay) com.futo.futopay.R.color.primary else R.color.white))
}
private fun setSystemBrightness(brightness: Float) {
Log.i(TAG, "setSystemBrightness $brightness")
if (android.provider.Settings.System.canWrite(context)) {
Log.i(TAG, "setSystemBrightness canWrite $brightness")
android.provider.Settings.System.putInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE, android.provider.Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL);
android.provider.Settings.System.putInt(context.contentResolver, android.provider.Settings.System.SCREEN_BRIGHTNESS, (brightness * 255.0f).toInt().coerceAtLeast(1).coerceAtMost(255));
} else if (!_promptedForPermissions) {
Log.i(TAG, "setSystemBrightness prompt $brightness")
_promptedForPermissions = true
UIDialogs.showConfirmationDialog(context, "System brightness controls require explicit permission", action = {
openAndroidPermissionsMenu()
})
} else {
Log.i(TAG, "setSystemBrightness no permission?")
//No permissions but already prompted, ignore
}
private fun clearCustomScreenBrightness() {
setScreenBrightness(null)
}
private fun openAndroidPermissionsMenu() {
val intent = Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.setData(Uri.parse("package:" + context.packageName))
context.startActivity(intent)
private fun setScreenBrightness(brightness: Float?) {
val layoutParams: WindowManager.LayoutParams? = fragment?.activity?.window?.attributes
layoutParams?.screenBrightness =
brightness ?: WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
fragment?.activity?.window?.attributes = layoutParams
}
fun updateNextPrevious() {
@@ -195,4 +195,39 @@
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone"/>
<LinearLayout
android:id="@+id/layout_controls_increased_speed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:orientation="horizontal"
android:gravity="center_vertical"
android:background="@drawable/background_pill_black"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="20dp"
android:visibility="gone">
<TextView
android:id="@+id/text_holdFastForward"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="2x"
android:textSize="14dp"
android:textColor="@color/white"
android:fontFamily="@font/inter_regular" />
<ImageView
android:id="@+id/image_holdFastForward"
android:layout_width="wrap_content"
android:layout_height="8dp"
android:adjustViewBounds="true"
app:srcCompat="@drawable/ic_fastforward_animated"
android:layout_marginStart="4dp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
+14 -4
View File
@@ -397,10 +397,8 @@
<string name="brightness_slider_descr">Enable slide gesture to change brightness</string>
<string name="toggle_full_screen">Toggle full screen</string>
<string name="toggle_full_screen_descr">Enable swipe gesture to toggle full screen</string>
<string name="system_brightness">System brightness</string>
<string name="system_brightness_descr">Gesture controls adjust system brightness</string>
<string name="restore_system_brightness">Restore system brightness</string>
<string name="restore_system_brightness_descr">Restore system brightness when exiting full screen</string>
<string name="screen_brightness">Control screen brightness</string>
<string name="screen_brightness_desc">Gesture controls adjust the device screen brightness instead of an overlay filter</string>
<string name="zoom_option">Enable zoom</string>
<string name="zoom_option_descr">Enable two finger pinch zoom gesture</string>
<string name="pan_option">Enable pan</string>
@@ -433,6 +431,8 @@
<string name="min_playback_speed_description">Minimum Available Speed</string>
<string name="max_playback_speed">Maximum Playback Speed</string>
<string name="max_playback_speed_description">Maximum Available Speed</string>
<string name="hold_playback_speed">Hold playback speed</string>
<string name="hold_playback_speed_description">Playback speed when pressing down on the video</string>
<string name="step_playback_speed">Playback Speed Step Size</string>
<string name="step_playback_speed_description">The step size of playback speeds, may not affect higher playback speeds.</string>
<string name="seek_offset_description">Fast-Forward / Fast-Rewind duration</string>
@@ -1106,6 +1106,16 @@
<item>4.0</item>
<item>5.0</item>
</string-array>
<string-array name="hold_playback_speeds">
<item>1.25</item>
<item>1.5</item>
<item>1.75</item>
<item>2.0</item>
<item>2.25</item>
<item>2.5</item>
<item>2.75</item>
<item>3.0</item>
</string-array>
<string-array name="min_playback_speed">
<item>0.25</item>
<item>0.5</item>