mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
9306024d17
11 changed files with 251 additions and 90 deletions
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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 opcodeBytes = ByteArray(1)
|
||||
opcodeBytes[0] = opcode.value
|
||||
outputStream.write(opcodeBytes)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue