diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt index 0ec735da383943fec2d049a94bfcd42f13f1e79c..0c83a06dcb55c9e3188f456fd75257c2fc5482cf 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt @@ -1,5 +1,6 @@ package com.futo.platformplayer +import android.util.Log import com.google.common.base.CharMatcher import java.io.ByteArrayOutputStream import java.io.IOException @@ -215,17 +216,20 @@ private fun ByteArray.toInetAddress(): InetAddress { } fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? { - val timeout = 5000 + val timeout = 2000 if (addresses.isEmpty()) { return null; } if (addresses.size == 1) { + val socket = Socket() + try { - return Socket().apply { this.connect(InetSocketAddress(addresses[0], port), timeout) }; + return socket.apply { this.connect(InetSocketAddress(addresses[0], port), timeout) } } catch (e: Throwable) { - //Ignored. + Log.i("getConnectedSocket", "Failed to connect to: ${addresses[0]}", e) + socket.close() } return null; @@ -264,7 +268,7 @@ fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? { } } } catch (e: Throwable) { - //Ignore + Log.i("getConnectedSocket", "Failed to connect to: $address", e) } }; diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 168cddfb674863820dd1085bb77b499fd5d9ccd7..30e912315e359b93ad24f80e5038c393cfe2fff4 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -809,7 +809,27 @@ class Settings : FragmentedStorageFileJson() { var polycentricEnabled: Boolean = true; } - @FormField(R.string.info, FieldForm.GROUP, -1, 19) + @FormField(R.string.gesture_controls, FieldForm.GROUP, -1, 19) + var gestureControls = GestureControls(); + @Serializable + class GestureControls { + @FormField(R.string.volume_slider, FieldForm.TOGGLE, R.string.volume_slider_descr, 1) + var volumeSlider: Boolean = true; + + @FormField(R.string.brightness_slider, FieldForm.TOGGLE, R.string.brightness_slider_descr, 2) + var brightnessSlider: Boolean = true; + + @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 = true; + + @FormField(R.string.system_volume, FieldForm.TOGGLE, R.string.system_volume_descr, 5) + var useSystemVolume: Boolean = true; + } + + @FormField(R.string.info, FieldForm.GROUP, -1, 20) var info = Info(); @Serializable class Info { diff --git a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt index a16cd83d180f90e3fbec4e9254ae13a8b8463401..a0a9695c1282cff91ccb01ea6725cb61ff3aef56 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/ChomecastCastingDevice.kt @@ -328,6 +328,8 @@ class ChromecastCastingDevice : CastingDevice { val factory = sslContext.socketFactory; + val address = InetSocketAddress(usedRemoteAddress, port) + //Connection loop while (_scopeIO?.isActive == true) { Logger.i(TAG, "Connecting to Chromecast."); @@ -341,7 +343,7 @@ class ChromecastCastingDevice : CastingDevice { connectedSocket = null } else { Logger.i(TAG, "Using new socket.") - val s = Socket().apply { this.connect(InetSocketAddress(usedRemoteAddress, port), 5000) } + val s = Socket().apply { this.connect(address, 2000) } _socket = factory.createSocket(s, s.inetAddress.hostAddress, s.port, true) as SSLSocket } @@ -444,10 +446,11 @@ class ChromecastCastingDevice : CastingDevice { while (_scopeIO?.isActive == true) { try { sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.tp.heartbeat", pingObject.toString()); - Thread.sleep(5000); } catch (e: Throwable) { Log.w(TAG, "Failed to send ping."); } + + Thread.sleep(5000); } Logger.i(TAG, "Stopped ping loop."); diff --git a/app/src/main/java/com/futo/platformplayer/casting/FCastCastingDevice.kt b/app/src/main/java/com/futo/platformplayer/casting/FCastCastingDevice.kt index ceb2ee969a2badd56a146ed101bfdfe287d7fd9b..9e12f78c26f11a29d2563263d0abebc475210256 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/FCastCastingDevice.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/FCastCastingDevice.kt @@ -15,6 +15,7 @@ import com.futo.platformplayer.casting.models.FCastSetSpeedMessage import com.futo.platformplayer.casting.models.FCastSetVolumeMessage import com.futo.platformplayer.casting.models.FCastVersionMessage import com.futo.platformplayer.casting.models.FCastVolumeUpdateMessage +import com.futo.platformplayer.ensureNotMainThread import com.futo.platformplayer.getConnectedSocket import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.CastingDeviceInfo @@ -27,9 +28,9 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import java.io.DataInputStream -import java.io.DataOutputStream import java.io.IOException +import java.io.InputStream +import java.io.OutputStream import java.math.BigInteger import java.net.InetAddress import java.net.InetSocketAddress @@ -82,12 +83,15 @@ class FCastCastingDevice : CastingDevice { var port: Int = 0; private var _socket: Socket? = null; - private var _outputStream: DataOutputStream? = null; - private var _inputStream: DataInputStream? = null; + private var _outputStream: OutputStream? = null; + private var _inputStream: InputStream? = null; private var _scopeIO: CoroutineScope? = null; private var _started: Boolean = false; private var _version: Long = 1; private var _thread: Thread? = null + private var _pingThread: Thread? = null + private var _lastPongTime = -1L + private var _outputStreamLock = Object() constructor(name: String, addresses: Array<InetAddress>, port: Int) : super() { this.name = name; @@ -207,7 +211,13 @@ class FCastCastingDevice : CastingDevice { private fun invokeInIOScopeIfRequired(action: () -> Unit): Boolean { if(Looper.getMainLooper().thread == Thread.currentThread()) { - _scopeIO?.launch { action(); } + _scopeIO?.launch { + try { + action(); + } catch (e: Throwable) { + Logger.e(TAG, "Failed to invoke in IO scope.", e) + } + } return true; } @@ -241,7 +251,8 @@ class FCastCastingDevice : CastingDevice { val adrs = addresses ?: return; val thread = _thread - if (thread == null || !thread.isAlive) { + val pingThread = _pingThread + if (_started && (thread == null || !thread.isAlive || pingThread == null || !pingThread.isAlive)) { Log.i(TAG, "(Re)starting thread because the thread has died") _scopeIO?.let { @@ -258,7 +269,7 @@ class FCastCastingDevice : CastingDevice { var connectedSocket: Socket? = null while (_scopeIO?.isActive == true) { try { - Log.i(TAG, "getConnectedSocket.") + Log.i(TAG, "getConnectedSocket (adrs = [ ${adrs.joinToString(", ")} ], port = ${port}).") val resultSocket = getConnectedSocket(adrs.toList(), port); @@ -279,6 +290,8 @@ class FCastCastingDevice : CastingDevice { } } + val address = InetSocketAddress(usedRemoteAddress, port) + //Connection loop while (_scopeIO?.isActive == true) { Logger.i(TAG, "Connecting to FastCast."); @@ -286,20 +299,24 @@ class FCastCastingDevice : CastingDevice { try { _socket?.close() + _inputStream?.close() + _outputStream?.close() if (connectedSocket != null) { Logger.i(TAG, "Using connected socket."); _socket = connectedSocket connectedSocket = null } else { Logger.i(TAG, "Using new socket."); - _socket = Socket().apply { this.connect(InetSocketAddress(usedRemoteAddress, port), 5000) }; + _socket = Socket().apply { this.connect(address, 2000) }; } Logger.i(TAG, "Successfully connected to FastCast at $usedRemoteAddress:$port"); - _outputStream = DataOutputStream(_socket?.outputStream); - _inputStream = DataInputStream(_socket?.inputStream); + _outputStream = _socket?.outputStream; + _inputStream = _socket?.inputStream; } catch (e: IOException) { - _socket?.close(); + _socket?.close() + _inputStream?.close() + _outputStream?.close() Logger.i(TAG, "Failed to connect to FastCast.", e); connectionState = CastConnectionState.CONNECTING; @@ -309,28 +326,38 @@ class FCastCastingDevice : CastingDevice { localAddress = _socket?.localAddress; connectionState = CastConnectionState.CONNECTED; + _lastPongTime = -1L val buffer = ByteArray(4096); Logger.i(TAG, "Started receiving."); - var exceptionOccurred = false; - while (_scopeIO?.isActive == true && !exceptionOccurred) { + while (_scopeIO?.isActive == true) { try { val inputStream = _inputStream ?: break; Log.d(TAG, "Receiving next packet..."); - val b1 = inputStream.readUnsignedByte(); - val b2 = inputStream.readUnsignedByte(); - val b3 = inputStream.readUnsignedByte(); - val b4 = inputStream.readUnsignedByte(); - val size = ((b4.toLong() shl 24) or (b3.toLong() shl 16) or (b2.toLong() shl 8) or b1.toLong()).toInt(); + + var headerBytesRead = 0 + while (headerBytesRead < 4) { + val read = inputStream.read(buffer, headerBytesRead, 4 - headerBytesRead) + if (read == -1) + throw Exception("Stream closed") + headerBytesRead += read + } + + val size = ((buffer[3].toLong() shl 24) or (buffer[2].toLong() shl 16) or (buffer[1].toLong() shl 8) or buffer[0].toLong()).toInt(); if (size > buffer.size) { - Logger.w(TAG, "Skipping packet that is too large $size bytes.") - inputStream.skip(size.toLong()); - continue; + Logger.w(TAG, "Packets larger than $size bytes are not supported.") + break } Log.d(TAG, "Received header indicating $size bytes. Waiting for message."); - inputStream.read(buffer, 0, size); + var bytesRead = 0 + while (bytesRead < size) { + val read = inputStream.read(buffer, bytesRead, size - bytesRead) + if (read == -1) + throw Exception("Stream closed") + bytesRead += read + } val messageBytes = buffer.sliceArray(IntRange(0, size)); Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}."); @@ -344,19 +371,22 @@ class FCastCastingDevice : CastingDevice { try { handleMessage(Opcode.find(opcode), json); } catch (e: Throwable) { - Logger.w(TAG, "Failed to handle message.", e); + Logger.w(TAG, "Failed to handle message.", e) + break } } catch (e: java.net.SocketException) { Logger.e(TAG, "Socket exception while receiving.", e); - exceptionOccurred = true; + break } catch (e: Throwable) { Logger.e(TAG, "Exception while receiving.", e); - exceptionOccurred = true; + break } } try { - _socket?.close(); + _socket?.close() + _inputStream?.close() + _outputStream?.close() Logger.i(TAG, "Socket disconnected."); } catch (e: Throwable) { Logger.e(TAG, "Failed to close socket.", e) @@ -368,7 +398,41 @@ class FCastCastingDevice : CastingDevice { Logger.i(TAG, "Stopped connection loop."); connectionState = CastConnectionState.DISCONNECTED; - }.apply { start() }; + }.apply { start() } + + _pingThread = Thread { + Logger.i(TAG, "Started ping loop.") + + while (_scopeIO?.isActive == true) { + try { + send(Opcode.Ping) + } catch (e: Throwable) { + Log.w(TAG, "Failed to send ping.") + + try { + _socket?.close() + _inputStream?.close() + _outputStream?.close() + } catch (e: Throwable) { + Log.w(TAG, "Failed to close socket.", e) + } + } + + /*if (_lastPongTime != -1L && System.currentTimeMillis() - _lastPongTime > 6000) { + Logger.w(TAG, "Closing socket due to last pong time being larger than 6 seconds.") + + try { + _socket?.close() + } catch (e: Throwable) { + Log.w(TAG, "Failed to close socket.", e) + } + }*/ + + Thread.sleep(2000) + } + + Logger.i(TAG, "Stopped ping loop."); + }.apply { start() } } else { Log.i(TAG, "Thread was still alive, not restarted") } @@ -421,39 +485,44 @@ class FCastCastingDevice : CastingDevice { Logger.i(TAG, "Remote version received: $version") } Opcode.Ping -> send(Opcode.Pong) + Opcode.Pong -> _lastPongTime = System.currentTimeMillis() else -> { } } } private fun send(opcode: Opcode, message: String? = null) { - try { - val data: ByteArray = message?.encodeToByteArray() ?: ByteArray(0) - val size = 1 + data.size - val outputStream = _outputStream - if (outputStream == null) { - Log.w(TAG, "Failed to send $size bytes, output stream is null.") - return - } + ensureNotMainThread() + + synchronized (_outputStreamLock) { + try { + val data: ByteArray = message?.encodeToByteArray() ?: ByteArray(0) + val size = 1 + data.size + val outputStream = _outputStream + if (outputStream == null) { + Log.w(TAG, "Failed to send $size bytes, output stream is null.") + return + } - val serializedSizeLE = ByteArray(4) - serializedSizeLE[0] = (size and 0xff).toByte() - serializedSizeLE[1] = (size shr 8 and 0xff).toByte() - serializedSizeLE[2] = (size shr 16 and 0xff).toByte() - serializedSizeLE[3] = (size shr 24 and 0xff).toByte() - outputStream.write(serializedSizeLE) + val serializedSizeLE = ByteArray(4) + serializedSizeLE[0] = (size and 0xff).toByte() + serializedSizeLE[1] = (size shr 8 and 0xff).toByte() + serializedSizeLE[2] = (size shr 16 and 0xff).toByte() + serializedSizeLE[3] = (size shr 24 and 0xff).toByte() + outputStream.write(serializedSizeLE) - val opcodeBytes = ByteArray(1) - opcodeBytes[0] = opcode.value - outputStream.write(opcodeBytes) + val opcodeBytes = ByteArray(1) + opcodeBytes[0] = opcode.value + outputStream.write(opcodeBytes) - if (data.isNotEmpty()) { - outputStream.write(data) - } + if (data.isNotEmpty()) { + outputStream.write(data) + } - Log.d(TAG, "Sent $size bytes: (opcode: $opcode, body: $message).") - } catch (e: Throwable) { - Log.i(TAG, "Failed to send message.", e) - throw e + Log.d(TAG, "Sent $size bytes: (opcode: $opcode, body: $message).") + } catch (e: Throwable) { + Log.i(TAG, "Failed to send message.", e) + throw e + } } } @@ -473,6 +542,7 @@ class FCastCastingDevice : CastingDevice { _started = false; //TODO: Kill and/or join thread? _thread = null; + _pingThread = null; val socket = _socket; val scopeIO = _scopeIO; @@ -482,6 +552,8 @@ class FCastCastingDevice : CastingDevice { scopeIO.launch { socket.close(); + _inputStream?.close() + _outputStream?.close() connectionState = CastConnectionState.DISCONNECTED; scopeIO.cancel(); Logger.i(TAG, "Cancelled scopeIO with open socket.") diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt index 300b0a665bc8a41b6f1581227a41a2965fa2336f..862f8333f19003e6af9c5b0d3dbfda8c448ea8dd 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectedCastingDialog.kt @@ -143,7 +143,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { StateCasting.instance.onActiveDeviceDurationChanged.remove(this); StateCasting.instance.onActiveDeviceDurationChanged.subscribe { - _sliderPosition.valueTo = it.toFloat().coerceAtLeast(1.0f); + val dur = it.toFloat().coerceAtLeast(1.0f) + _sliderPosition.value = _sliderPosition.value.coerceAtLeast(0.0f).coerceAtMost(dur); + _sliderPosition.valueTo = dur }; _device = StateCasting.instance.activeDevice; @@ -185,8 +187,10 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) { _sliderPosition.valueFrom = 0.0f; _sliderVolume.valueFrom = 0.0f; _sliderVolume.value = d.volume.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderVolume.valueTo); - _sliderPosition.valueTo = d.duration.toFloat().coerceAtLeast(1.0f); - _sliderPosition.value = d.time.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderVolume.valueTo); + + val dur = d.duration.toFloat().coerceAtLeast(1.0f) + _sliderPosition.value = d.time.toFloat().coerceAtLeast(0.0f).coerceAtMost(dur) + _sliderPosition.valueTo = dur if (d.canSetVolume) { _layoutVolumeAdjustable.visibility = View.VISIBLE; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 695edab30ab3159c1317e3773338d9ddca528a8c..a80b1aabd09d7a1ecc778c98d56c868f4d6dc243 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -145,7 +145,6 @@ import com.futo.polycentric.core.Opinion import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.protobuf.ByteString import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -1372,7 +1371,9 @@ class VideoDetailView : ConstraintLayout { val toResume = _videoResumePositionMilliseconds; _videoResumePositionMilliseconds = 0; loadCurrentVideo(toResume); - _player.setGestureSoundFactor(1.0f); + if (!Settings.instance.gestureControls.useSystemVolume) { + _player.setGestureSoundFactor(1.0f); + } updateQueueState(); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt index bb3af1aece5c33dae109e991e79409cb77737008..088655f954df1503c4b0c5c36515c86703e8b15c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt @@ -13,17 +13,17 @@ import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.ImageButton import android.widget.TextView -import com.futo.platformplayer.logging.Logger -import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.R -import com.futo.platformplayer.stores.SearchHistoryStorage import com.futo.platformplayer.Settings import com.futo.platformplayer.UIDialogs -import com.futo.platformplayer.api.media.PlatformID import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 -import com.futo.platformplayer.fragment.mainactivity.main.* +import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragment +import com.futo.platformplayer.fragment.mainactivity.main.SuggestionsFragmentData +import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.SearchType +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.SearchHistoryStorage class SearchTopBarFragment : TopFragment() { private val TAG = "SearchTopBarFragment" @@ -54,11 +54,12 @@ class SearchTopBarFragment : TopFragment() { private val _searchDoneListener = object : TextView.OnEditorActionListener { override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { - if (actionId != EditorInfo.IME_ACTION_DONE) + val isEnterPress = event?.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN + if (actionId != EditorInfo.IME_ACTION_DONE && !isEnterPress) return false - onDone(); - return true; + onDone() + return true } }; diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt index f500a87e6b6db91a6892585fc9eed4bc28b3736e..2fa89c8eef2fdb974c8b83b7031e1582f4a2d878 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/GestureControlView.kt @@ -3,8 +3,10 @@ package com.futo.platformplayer.views.behavior import android.animation.Animator import android.animation.AnimatorSet import android.animation.ObjectAnimator +import android.app.Activity import android.content.Context import android.graphics.drawable.Animatable +import android.media.AudioManager import android.util.AttributeSet import android.view.GestureDetector import android.view.LayoutInflater @@ -19,6 +21,7 @@ import androidx.core.animation.doOnEnd import androidx.core.animation.doOnStart import androidx.core.view.GestureDetectorCompat import com.futo.platformplayer.R +import com.futo.platformplayer.Settings import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger @@ -59,6 +62,7 @@ class GestureControlView : LinearLayout { private val _progressSound: CircularProgressBar; private var _animatorSound: ObjectAnimator? = null; private var _brightnessFactor = 1.0f; + private var _originalBrightnessFactor = 1.0f; private var _adjustingBrightness: Boolean = false; private val _layoutControlsBrightness: FrameLayout; private val _progressBrightness: CircularProgressBar; @@ -126,11 +130,11 @@ class GestureControlView : LinearLayout { val rx = (p0.x + p1.x) / (2 * width); val ry = (p0.y + p1.y) / (2 * height); if (ry > 0.1 && ry < 0.9) { - if (_isFullScreen && rx < 0.2) { + if (Settings.instance.gestureControls.brightnessSlider && _isFullScreen && rx < 0.2) { startAdjustingBrightness(); - } else if (_isFullScreen && rx > 0.8) { + } else if (Settings.instance.gestureControls.volumeSlider && _isFullScreen && rx > 0.8) { startAdjustingSound(); - } else if (fullScreenGestureEnabled && rx in 0.3..0.7) { + } else if (Settings.instance.gestureControls.toggleFullscreen && fullScreenGestureEnabled && rx in 0.3..0.7) { if (_isFullScreen) { startAdjustingFullscreenDown(); } else { @@ -559,10 +563,34 @@ class GestureControlView : LinearLayout { fun setFullscreen(isFullScreen: Boolean) { if (isFullScreen) { + val c = context + if (c is Activity && Settings.instance.gestureControls.useSystemBrightness) { + _brightnessFactor = c.window.attributes.screenBrightness + if (_brightnessFactor == -1.0f) { + _brightnessFactor = android.provider.Settings.System.getInt( + context.contentResolver, + android.provider.Settings.System.SCREEN_BRIGHTNESS + ) / 255.0f; + } + _originalBrightnessFactor = _brightnessFactor + } + + 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 { - onBrightnessAdjusted.emit(1.0f); + val c = context + if (c is Activity && Settings.instance.gestureControls.useSystemBrightness) { + onBrightnessAdjusted.emit(_originalBrightnessFactor); + } else { + onBrightnessAdjusted.emit(1.0f); + } //onSoundAdjusted.emit(1.0f); stopAdjustingBrightness(); stopAdjustingSound(); diff --git a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt index b33c62dab5f4ff47c90255a2449bb666ae08610f..91631f1e2f98631b54f41bec8ce44a548d93ee84 100644 --- a/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/casting/CastView.kt @@ -24,8 +24,8 @@ import com.futo.platformplayer.casting.AirPlayCastingDevice import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event2 +import com.futo.platformplayer.formatDuration import com.futo.platformplayer.states.StatePlayer -import com.futo.platformplayer.toHumanTime import com.futo.platformplayer.views.behavior.GestureControlView import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -252,8 +252,8 @@ class CastView : ConstraintLayout { .load(video.thumbnails.getHQThumbnail()) .placeholder(R.drawable.placeholder_video_thumbnail) .into(_thumbnail); - _textPosition.text = position.toHumanTime(false); - _textDuration.text = video.duration.toHumanTime(false); + _textPosition.text = (position * 1000).formatDuration(); + _textDuration.text = (video.duration * 1000).formatDuration(); _timeBar.setPosition(position); _timeBar.setDuration(video.duration); } @@ -261,7 +261,7 @@ class CastView : ConstraintLayout { @OptIn(UnstableApi::class) fun setTime(ms: Long) { updateCurrentChapter(ms); - _textPosition.text = ms.toHumanTime(true); + _textPosition.text = ms.formatDuration(); _timeBar.setPosition(ms / 1000); StatePlayer.instance.updateMediaSessionPlaybackState(getPlaybackStateCompat(), ms); } diff --git a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt index 7e1d20e22a3f17034307fbb0a8b14284797f1924..2c7020260af5a8debfef3b2694ff657d9afd24f0 100644 --- a/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt +++ b/app/src/main/java/com/futo/platformplayer/views/video/FutoVideoPlayer.kt @@ -1,15 +1,18 @@ package com.futo.platformplayer.views.video +import android.app.Activity import android.content.Context import android.content.res.Resources import android.graphics.Color import android.graphics.drawable.Drawable +import android.media.AudioManager import android.util.AttributeSet import android.util.Log 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 @@ -235,14 +238,29 @@ class FutoVideoPlayer : FutoVideoPlayerBase { gestureControl.setupTouchArea(_layoutControls, background); gestureControl.onSeek.subscribe { seekFromCurrent(it); }; - gestureControl.onSoundAdjusted.subscribe { setVolume(it) }; + gestureControl.onSoundAdjusted.subscribe { + if (Settings.instance.gestureControls.useSystemVolume) { + val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + val maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (it * maxVolume).toInt(), 0) + } else { + setVolume(it) + } + }; gestureControl.onToggleFullscreen.subscribe { setFullScreen(!isFullScreen) }; gestureControl.onBrightnessAdjusted.subscribe { - if (it == 1.0f) { - _overlay_brightness.visibility = View.GONE; + if (context is Activity && Settings.instance.gestureControls.useSystemBrightness) { + val window = context.window + val layout: WindowManager.LayoutParams = window.attributes + layout.screenBrightness = it + window.attributes = layout } else { - _overlay_brightness.visibility = View.VISIBLE; - _overlay_brightness.setBackgroundColor(Color.valueOf(0.0f, 0.0f, 0.0f, (1.0f - it)).toArgb()); + 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()); + } } }; @@ -531,7 +549,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; _videoControls_fullscreen.show(); - videoControls.hide(); + videoControls.hideImmediately(); } else { val lp = background.layoutParams as ConstraintLayout.LayoutParams; @@ -543,7 +561,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase { _videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM; videoControls.show(); - _videoControls_fullscreen.hide(); + _videoControls_fullscreen.hideImmediately(); } fitOrFill(fullScreen); @@ -730,7 +748,6 @@ class FutoVideoPlayer : FutoVideoPlayerBase { } } - fun setGestureSoundFactor(soundFactor: Float) { gestureControl.setSoundFactor(soundFactor); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d4f1b6d18317a8c439090e1263d11fb79a579e03..64923346fac5b4e3e3e27174478029f928ce8e05 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -344,6 +344,17 @@ <string name="get_answers_to_common_questions">Get answers to common questions</string> <string name="give_feedback_on_the_application">Give feedback on the application</string> <string name="info">Info</string> + <string name="gesture_controls">Gesture controls</string> + <string name="volume_slider">Volume slider</string> + <string name="volume_slider_descr">Enable slide gesture to change volume</string> + <string name="brightness_slider">Brightness slider</string> + <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 fullscreen</string> + <string name="system_brightness">System brightness</string> + <string name="system_brightness_descr">Gesture controls adjust system brightness</string> + <string name="system_volume">System volume</string> + <string name="system_volume_descr">Gesture controls adjust system volume</string> <string name="live_chat_webview">Live Chat Webview</string> <string name="full_screen_portrait">Fullscreen portrait</string> <string name="allow_full_screen_portrait">Allow fullscreen portrait</string>