Compare commits

..

20 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
Kelvin 19c84475db Hotfix playback speed for non-dot locales 2025-06-10 23:27:01 +02:00
Kelvin 4164b1a3f8 Build fix 2025-06-10 19:25:43 +02:00
Kelvin a9dc038190 Build fix 2025-06-10 19:20:50 +02:00
Kelvin 2825db88a5 Minor playback tracker fix, submodules 2025-06-10 18:56:19 +02:00
Kelvin 363099b303 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2025-06-10 17:39:44 +02:00
Kelvin 5e25a5054f Increase max comment length, Fix raw dash downloads ending too early, Fix playback tracker not working for downloaded videos 2025-06-10 17:33:14 +02:00
Kelvin 2bc6127f6b Merge branch 'copy-title' into 'master'
Copy Title

See merge request videostreaming/grayjay!110
2025-06-10 14:41:49 +00:00
Kelvin 064824aedf Merge branch 'copy-playlists' into 'master'
Clone Playlist

See merge request videostreaming/grayjay!111
2025-06-10 14:41:07 +00:00
Kai DeLorenzo 52044edb2e Merge branch 'brightness-fix' into 'master'
Dim Fullscreen Fix

See merge request videostreaming/grayjay!119
2025-06-10 14:38:01 +00:00
Kai fb12073a82 Only save brightness on resume fullscreen if use system brightness is enabled
Changelog: changed
2025-06-10 09:18:28 -05:00
Kai 389798457b navigate to playlist screen after copying
Changelog: changed
2025-06-06 15:57:09 -05:00
Kai dd1c04bea1 make the copied playlist name unique
Changelog: changed
2025-06-06 09:39:09 -05:00
Kai 8e70f1b865 add long tap to copy playing video title
Changelog: added
2025-06-05 23:14:03 -05:00
Kai f86fb0ee44 add functionality to copy playlists
fix https://github.com/futo-org/grayjay-android/issues/2306

Changelog: added
2025-06-05 23:13:05 -05:00
24 changed files with 289 additions and 132 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.toString());
val url = foundTemplateUrl.replace("\$Number\$", (indexCounter).toString());
val data = if(executor != null)
executor.executeRequest("GET", url, null, mapOf());
@@ -10,7 +10,6 @@ import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.*
import com.futo.platformplayer.activities.IWithResultLauncher
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist
import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylistDetails
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
import com.futo.platformplayer.api.media.models.video.SerializedPlatformVideo
import com.futo.platformplayer.constructs.TaskHandler
@@ -165,14 +164,24 @@ class PlaylistFragment : MainFragment() {
};
}
private fun copyPlaylist(playlist: Playlist) {
private fun savePlaylist(playlist: Playlist) {
StatePlaylists.instance.playlistStore.save(playlist)
_fragment.topBar?.assume<NavigationTopBarFragment>()?.setMenuItems(
arrayListOf()
)
UIDialogs.toast("Playlist saved")
}
private fun copyPlaylist(playlist: Playlist) {
var copyNumber = 1
var newName = "${playlist.name} (Copy)"
val playlists = StatePlaylists.instance.playlistStore.getItems()
while (playlists.any { it.name == newName }) {
copyNumber += 1
newName = "${playlist.name} (Copy $copyNumber)"
}
StatePlaylists.instance.playlistStore.save(playlist.makeCopy(newName))
_fragment.navigate<PlaylistsFragment>(withHistory = false)
UIDialogs.toast("Playlist copied")
}
fun onShown(parameter: Any?) {
_taskLoadPlaylist.cancel()
@@ -188,12 +197,14 @@ class PlaylistFragment : MainFragment() {
setButtonExportVisible(false)
setButtonEditVisible(true)
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.id }) {
_fragment.topBar?.assume<NavigationTopBarFragment>()
?.setMenuItems(arrayListOf(Pair(R.drawable.ic_copy) {
_fragment.topBar?.assume<NavigationTopBarFragment>()
?.setMenuItems(arrayListOf(Pair(R.drawable.ic_copy) {
if (StatePlaylists.instance.playlistStore.hasItem { it.id == parameter.id }) {
copyPlaylist(parameter)
}))
}
} else {
savePlaylist(parameter)
}
}))
} else {
setName(null)
setVideos(null, false)
@@ -259,7 +270,7 @@ class PlaylistFragment : MainFragment() {
val playlist = _playlist ?: return
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == playlist.id }) {
UIDialogs.showConfirmationDialog(context, "Playlist must be saved to download", {
copyPlaylist(playlist)
savePlaylist(playlist)
download()
})
return
@@ -292,7 +303,7 @@ class PlaylistFragment : MainFragment() {
val playlist = _playlist ?: return
if (!StatePlaylists.instance.playlistStore.hasItem { it.id == playlist.id }) {
UIDialogs.showConfirmationDialog(context, "Playlist must be saved to edit the name", {
copyPlaylist(playlist)
savePlaylist(playlist)
onEditClick()
})
return
@@ -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;
@@ -2,6 +2,8 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.app.PictureInPictureParams
import android.app.RemoteAction
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
@@ -172,6 +174,7 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import userpackage.Protocol
import java.time.OffsetDateTime
import java.util.Locale
import kotlin.math.abs
import kotlin.math.roundToLong
@@ -408,6 +411,14 @@ class VideoDetailView : ConstraintLayout {
showChaptersUI();
};
_title.setOnLongClickListener {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager;
val clip = ClipData.newPlainText("Video Title", (it as TextView).text);
clipboard.setPrimaryClip(clip);
UIDialogs.toast(context, "Copied", false)
// let other interactions happen based on the touch
false
}
_buttonSubscribe.onSubscribed.subscribe {
_slideUpOverlay = UISlideOverlays.showSubscriptionOptionsOverlay(it, _overlayContainer);
@@ -1399,8 +1410,8 @@ class VideoDetailView : ConstraintLayout {
onVideoChanged.emit(0, 0)
}
val me = this;
if (video is JSVideoDetails) {
val me = this;
fragment.lifecycleScope.launch(Dispatchers.IO) {
try {
//TODO: Implement video.getContentChapters()
@@ -1457,6 +1468,32 @@ class VideoDetailView : ConstraintLayout {
}
};
}
else {
fragment.lifecycleScope.launch(Dispatchers.IO) {
try {
if (!StateApp.instance.privateMode) {
val stopwatch = com.futo.platformplayer.debug.Stopwatch()
var tracker = video.getPlaybackTracker()
Logger.i(TAG, "video.getPlaybackTracker took ${stopwatch.elapsedMs}ms")
if (tracker == null) {
stopwatch.reset()
tracker = StatePlatform.instance.getPlaybackTracker(video.url);
Logger.i(
TAG,
"StatePlatform.instance.getPlaybackTracker took ${stopwatch.elapsedMs}ms"
)
}
if (me.video?.url == video.url && !video.url.isNullOrBlank())
me._playbackTracker = tracker;
} else if (me.video == video)
me._playbackTracker = null;
} catch (ex: Throwable) {
Logger.e(TAG, "Playback tracker failed", ex);
}
}
}
val ref = Models.referenceFromBuffer(video.url.toByteArray())
val extraBytesRef = video.id.value?.let { if (it.isNotEmpty()) it.toByteArray() else null }
@@ -2156,19 +2193,19 @@ class VideoDetailView : ConstraintLayout {
if (canSetSpeed) SlideUpMenuButtonList(this.context, null, "playback_rate").apply {
val playbackSpeeds = Settings.instance.playback.getPlaybackSpeeds();
val format = if(playbackSpeeds.size < 20) "%.2f" else "%.1f";
val playbackLabels = playbackSpeeds.map { String.format(format, it) }.toMutableList();
val playbackLabels = playbackSpeeds.map { String.format(Locale.US, format, it) }.toMutableList();
playbackLabels.add("+");
playbackLabels.add(0, "-");
setButtons(playbackLabels, String.format(format, currentPlaybackRate));
setButtons(playbackLabels, String.format(Locale.US, format, currentPlaybackRate));
onClick.subscribe { v ->
val currentPlaybackSpeed = if (_isCasting) StateCasting.instance.activeDevice?.speed else _player.getPlaybackRate();
var playbackSpeedString = v;
val stepSpeed = Settings.instance.playback.getPlaybackSpeedStep();
if(v == "+")
playbackSpeedString = String.format("%.2f", Math.min((currentPlaybackSpeed?.toDouble() ?: 1.0) + stepSpeed, 5.0)).toString();
playbackSpeedString = String.format(Locale.US, "%.2f", Math.min((currentPlaybackSpeed?.toDouble() ?: 1.0) + stepSpeed, 5.0)).toString();
else if(v == "-")
playbackSpeedString = String.format("%.2f", Math.max(0.1, (currentPlaybackSpeed?.toDouble() ?: 1.0) - stepSpeed)).toString();
playbackSpeedString = String.format(Locale.US, "%.2f", Math.max(0.1, (currentPlaybackSpeed?.toDouble() ?: 1.0) - stepSpeed)).toString();
val newPlaybackSpeed = playbackSpeedString.toDouble();
if (_isCasting) {
val ad = StateCasting.instance.activeDevice ?: return@subscribe
@@ -2176,11 +2213,11 @@ class VideoDetailView : ConstraintLayout {
return@subscribe
}
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format("%.2f", newPlaybackSpeed)})");
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})");
ad.changeSpeed(newPlaybackSpeed)
setSelected(playbackSpeedString);
} else {
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format("%.2f", newPlaybackSpeed)})");
qualityPlaybackSpeedTitle?.setTitle(context.getString(R.string.playback_rate) + " (${String.format(Locale.US, "%.2f", newPlaybackSpeed)})");
_player.setPlaybackRate(playbackSpeedString.toFloat());
setSelected(playbackSpeedString);
}
@@ -2538,13 +2575,6 @@ class VideoDetailView : ConstraintLayout {
}
}
fun saveBrightness() {
_player.gestureControl.saveBrightness()
}
fun restoreBrightness() {
_player.gestureControl.restoreBrightness()
}
fun setFullscreen(fullscreen : Boolean) {
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
_player.setFullScreen(fullscreen)
@@ -3180,6 +3210,8 @@ class VideoDetailView : ConstraintLayout {
fun applyFragment(frag: VideoDetailFragment) {
fragment = frag;
_player.fragment = frag
}
@@ -35,6 +35,9 @@ class Playlist {
this.videos = ArrayList(list);
}
fun makeCopy(newName: String? = null): Playlist {
return Playlist(newName ?: name, videos)
}
companion object {
fun fromV8(config: SourcePluginConfig, obj: V8ValueObject?): Playlist? {
@@ -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() {
+1 -1
View File
@@ -80,7 +80,7 @@
android:isScrollContainer="false"
android:textColor="#CCCCCC"
android:textSize="13sp"
android:maxLines="100"
android:maxLines="150"
app:layout_constraintTop_toBottomOf="@id/text_metadata"
app:layout_constraintLeft_toRightOf="@id/image_thumbnail"
app:layout_constraintRight_toRightOf="parent"
@@ -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>