Compare commits

...

6 Commits

Author SHA1 Message Date
Koen 898637a616 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-01-06 23:07:50 +01:00
Koen f1860126a7 - Changed casting connection timeout to 2 seconds.
- Added ping pong to FCast.
- Removal of DataInputStream and just using raw InputStream.
- Fix slider position crash.
2024-01-06 23:07:34 +01:00
Kelvin f8402676d7 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-01-06 22:19:38 +01:00
Kelvin cf86ce1ab3 Minor leak fix, login warning support, refs 2024-01-06 22:19:29 +01:00
Koen f4cb1719e0 Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay 2024-01-04 13:38:37 +01:00
Koen 4898cb53ae Fixed tint color. 2024-01-04 13:38:30 +01:00
12 changed files with 98 additions and 46 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)
}
};
@@ -10,6 +10,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.api.media.platforms.js.SourceAuth
import com.futo.platformplayer.api.media.platforms.js.SourcePluginAuthConfig
import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig
@@ -39,6 +40,7 @@ class LoginActivity : AppCompatActivity() {
_textUrl = findViewById(R.id.text_url);
_buttonClose = findViewById(R.id.button_close);
_buttonClose.setOnClickListener {
UIDialogs.toast("Login cancelled", false);
finish();
}
@@ -11,4 +11,5 @@ class SourcePluginAuthConfig(
val userAgent: String? = null,
val loginButton: String? = null,
val domainHeadersToFind: Map<String, List<String>>? = null,
val loginWarning: String? = null
) { }
@@ -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.");
@@ -27,9 +27,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 +82,13 @@ 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
constructor(name: String, addresses: Array<InetAddress>, port: Int) : super() {
this.name = name;
@@ -241,7 +242,8 @@ class FCastCastingDevice : CastingDevice {
val adrs = addresses ?: return;
val thread = _thread
if (thread == null || !thread.isAlive) {
val pingThread = _pingThread
if (thread == null || !thread.isAlive || pingThread == null || !pingThread.isAlive) {
Log.i(TAG, "(Re)starting thread because the thread has died")
_scopeIO?.let {
@@ -258,7 +260,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 +281,8 @@ class FCastCastingDevice : CastingDevice {
}
}
val address = InetSocketAddress(usedRemoteAddress, port)
//Connection loop
while (_scopeIO?.isActive == true) {
Logger.i(TAG, "Connecting to FastCast.");
@@ -292,12 +296,12 @@ class FCastCastingDevice : CastingDevice {
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();
Logger.i(TAG, "Failed to connect to FastCast.", e);
@@ -318,11 +322,13 @@ class FCastCastingDevice : CastingDevice {
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) {
headerBytesRead += inputStream.read(buffer, headerBytesRead, 4 - headerBytesRead)
}
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());
@@ -330,7 +336,10 @@ class FCastCastingDevice : CastingDevice {
}
Log.d(TAG, "Received header indicating $size bytes. Waiting for message.");
inputStream.read(buffer, 0, size);
var bytesRead = 0
while (bytesRead < size) {
bytesRead += inputStream.read(buffer, bytesRead, size - bytesRead)
}
val messageBytes = buffer.sliceArray(IntRange(0, size));
Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}.");
@@ -368,7 +377,23 @@ 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.");
}
Thread.sleep(5000);
}
Logger.i(TAG, "Stopped ping loop.");
}.apply { start() }
} else {
Log.i(TAG, "Thread was still alive, not restarted")
}
@@ -473,6 +498,7 @@ class FCastCastingDevice : CastingDevice {
_started = false;
//TODO: Kill and/or join thread?
_thread = null;
_pingThread = null;
val socket = _socket;
val scopeIO = _scopeIO;
@@ -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;
@@ -354,11 +354,22 @@ class SourceDetailFragment : MainFragment() {
if(config.authentication == null)
return;
LoginActivity.showLogin(StateApp.instance.context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id);
};
if(config.authentication.loginWarning != null) {
UIDialogs.showDialog(context, R.drawable.ic_warning_yellow, "Login Warning",
config.authentication.loginWarning, null, 0,
UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE),
UIDialogs.Action("Login", {
LoginActivity.showLogin(StateApp.instance.context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id);
};
}, UIDialogs.ActionStyle.PRIMARY))
}
else
LoginActivity.showLogin(StateApp.instance.context, config) {
StatePlugins.instance.setPluginAuth(config.id, it);
reloadSource(config.id);
};
}
private fun logoutSource(clear: Boolean = true) {
val config = _config ?: return;
@@ -109,16 +109,6 @@ class SubscriptionsFeedFragment : MainFragment() {
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
StateSubscriptions.instance.onFeedProgress.subscribe(this) { id, progress, total ->
if(subGroup?.id == id)
fragment.lifecycleScope.launch(Dispatchers.Main) {
try {
setProgress(progress, total);
} catch (e: Throwable) {
Logger.e(TAG, "Failed to set progress", e);
}
}
}
StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total ->
};
@@ -174,12 +164,24 @@ class SubscriptionsFeedFragment : MainFragment() {
if (!StateSubscriptions.instance.global.isGlobalUpdating) {
finishRefreshLayoutLoader();
}
StateSubscriptions.instance.onFeedProgress.subscribe(this) { id, progress, total ->
if(subGroup?.id == id)
fragment.lifecycleScope.launch(Dispatchers.Main) {
try {
setProgress(progress, total);
} catch (e: Throwable) {
Logger.e(TAG, "Failed to set progress", e);
}
}
}
}
override fun cleanup() {
super.cleanup()
StateSubscriptions.instance.global.onUpdateProgress.remove(this);
StateSubscriptions.instance.onSubscriptionsChanged.remove(this);
StateSubscriptions.instance.onFeedProgress.remove(this);
}
override val feedStyle: FeedStyle get() = Settings.instance.subscriptions.getSubscriptionsFeedStyle();
+1 -2
View File
@@ -2,8 +2,7 @@
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M120,640L120,560L440,560L440,640L120,640ZM120,480L120,400L600,400L600,480L120,480ZM120,320L120,240L600,240L600,320L120,320ZM640,840L640,520L880,680L640,840Z"/>
@@ -38,12 +38,12 @@
android:id="@+id/dialog_text_details"
android:layout_width="match_parent"
android:textColor="#AAAAAA"
android:fontFamily="@font/inter_regular"
android:fontFamily="@font/inter_light"
android:text=""
android:textAlignment="center"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:textSize="11dp"
android:textSize="13dp"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/dialog_text_code"