- Changed casting connection timeout to 2 seconds.

- Added ping pong to FCast.
- Removal of DataInputStream and just using raw InputStream.
- Fix slider position crash.
This commit is contained in:
Koen 2024-01-06 23:07:34 +01:00
commit f1860126a7
4 changed files with 62 additions and 25 deletions

View file

@ -1,5 +1,6 @@
package com.futo.platformplayer package com.futo.platformplayer
import android.util.Log
import com.google.common.base.CharMatcher import com.google.common.base.CharMatcher
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
@ -215,17 +216,20 @@ private fun ByteArray.toInetAddress(): InetAddress {
} }
fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? { fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? {
val timeout = 5000 val timeout = 2000
if (addresses.isEmpty()) { if (addresses.isEmpty()) {
return null; return null;
} }
if (addresses.size == 1) { if (addresses.size == 1) {
val socket = Socket()
try { try {
return Socket().apply { this.connect(InetSocketAddress(addresses[0], port), timeout) }; return socket.apply { this.connect(InetSocketAddress(addresses[0], port), timeout) }
} catch (e: Throwable) { } catch (e: Throwable) {
//Ignored. Log.i("getConnectedSocket", "Failed to connect to: ${addresses[0]}", e)
socket.close()
} }
return null; return null;
@ -264,7 +268,7 @@ fun getConnectedSocket(addresses: List<InetAddress>, port: Int): Socket? {
} }
} }
} catch (e: Throwable) { } catch (e: Throwable) {
//Ignore Log.i("getConnectedSocket", "Failed to connect to: $address", e)
} }
}; };

View file

@ -328,6 +328,8 @@ class ChromecastCastingDevice : CastingDevice {
val factory = sslContext.socketFactory; val factory = sslContext.socketFactory;
val address = InetSocketAddress(usedRemoteAddress, port)
//Connection loop //Connection loop
while (_scopeIO?.isActive == true) { while (_scopeIO?.isActive == true) {
Logger.i(TAG, "Connecting to Chromecast."); Logger.i(TAG, "Connecting to Chromecast.");
@ -341,7 +343,7 @@ class ChromecastCastingDevice : CastingDevice {
connectedSocket = null connectedSocket = null
} else { } else {
Logger.i(TAG, "Using new socket.") 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 _socket = factory.createSocket(s, s.inetAddress.hostAddress, s.port, true) as SSLSocket
} }
@ -444,10 +446,11 @@ class ChromecastCastingDevice : CastingDevice {
while (_scopeIO?.isActive == true) { while (_scopeIO?.isActive == true) {
try { try {
sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.tp.heartbeat", pingObject.toString()); sendChannelMessage("sender-0", "receiver-0", "urn:x-cast:com.google.cast.tp.heartbeat", pingObject.toString());
Thread.sleep(5000);
} catch (e: Throwable) { } catch (e: Throwable) {
Log.w(TAG, "Failed to send ping."); Log.w(TAG, "Failed to send ping.");
} }
Thread.sleep(5000);
} }
Logger.i(TAG, "Stopped ping loop."); Logger.i(TAG, "Stopped ping loop.");

View file

@ -27,9 +27,9 @@ import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.math.BigInteger import java.math.BigInteger
import java.net.InetAddress import java.net.InetAddress
import java.net.InetSocketAddress import java.net.InetSocketAddress
@ -82,12 +82,13 @@ class FCastCastingDevice : CastingDevice {
var port: Int = 0; var port: Int = 0;
private var _socket: Socket? = null; private var _socket: Socket? = null;
private var _outputStream: DataOutputStream? = null; private var _outputStream: OutputStream? = null;
private var _inputStream: DataInputStream? = null; private var _inputStream: InputStream? = null;
private var _scopeIO: CoroutineScope? = null; private var _scopeIO: CoroutineScope? = null;
private var _started: Boolean = false; private var _started: Boolean = false;
private var _version: Long = 1; private var _version: Long = 1;
private var _thread: Thread? = null private var _thread: Thread? = null
private var _pingThread: Thread? = null
constructor(name: String, addresses: Array<InetAddress>, port: Int) : super() { constructor(name: String, addresses: Array<InetAddress>, port: Int) : super() {
this.name = name; this.name = name;
@ -241,7 +242,8 @@ class FCastCastingDevice : CastingDevice {
val adrs = addresses ?: return; val adrs = addresses ?: return;
val thread = _thread 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") Log.i(TAG, "(Re)starting thread because the thread has died")
_scopeIO?.let { _scopeIO?.let {
@ -258,7 +260,7 @@ class FCastCastingDevice : CastingDevice {
var connectedSocket: Socket? = null var connectedSocket: Socket? = null
while (_scopeIO?.isActive == true) { while (_scopeIO?.isActive == true) {
try { try {
Log.i(TAG, "getConnectedSocket.") Log.i(TAG, "getConnectedSocket (adrs = [ ${adrs.joinToString(", ")} ], port = ${port}).")
val resultSocket = getConnectedSocket(adrs.toList(), port); val resultSocket = getConnectedSocket(adrs.toList(), port);
@ -279,6 +281,8 @@ class FCastCastingDevice : CastingDevice {
} }
} }
val address = InetSocketAddress(usedRemoteAddress, port)
//Connection loop //Connection loop
while (_scopeIO?.isActive == true) { while (_scopeIO?.isActive == true) {
Logger.i(TAG, "Connecting to FastCast."); Logger.i(TAG, "Connecting to FastCast.");
@ -292,12 +296,12 @@ class FCastCastingDevice : CastingDevice {
connectedSocket = null connectedSocket = null
} else { } else {
Logger.i(TAG, "Using new socket."); 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"); Logger.i(TAG, "Successfully connected to FastCast at $usedRemoteAddress:$port");
_outputStream = DataOutputStream(_socket?.outputStream); _outputStream = _socket?.outputStream;
_inputStream = DataInputStream(_socket?.inputStream); _inputStream = _socket?.inputStream;
} catch (e: IOException) { } catch (e: IOException) {
_socket?.close(); _socket?.close();
Logger.i(TAG, "Failed to connect to FastCast.", e); Logger.i(TAG, "Failed to connect to FastCast.", e);
@ -318,11 +322,13 @@ class FCastCastingDevice : CastingDevice {
try { try {
val inputStream = _inputStream ?: break; val inputStream = _inputStream ?: break;
Log.d(TAG, "Receiving next packet..."); Log.d(TAG, "Receiving next packet...");
val b1 = inputStream.readUnsignedByte();
val b2 = inputStream.readUnsignedByte(); var headerBytesRead = 0
val b3 = inputStream.readUnsignedByte(); while (headerBytesRead < 4) {
val b4 = inputStream.readUnsignedByte(); headerBytesRead += inputStream.read(buffer, headerBytesRead, 4 - headerBytesRead)
val size = ((b4.toLong() shl 24) or (b3.toLong() shl 16) or (b2.toLong() shl 8) or b1.toLong()).toInt(); }
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) { if (size > buffer.size) {
Logger.w(TAG, "Skipping packet that is too large $size bytes.") Logger.w(TAG, "Skipping packet that is too large $size bytes.")
inputStream.skip(size.toLong()); inputStream.skip(size.toLong());
@ -330,7 +336,10 @@ class FCastCastingDevice : CastingDevice {
} }
Log.d(TAG, "Received header indicating $size bytes. Waiting for message."); 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)); val messageBytes = buffer.sliceArray(IntRange(0, size));
Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}."); Log.d(TAG, "Received $size bytes: ${messageBytes.toHexString()}.");
@ -368,7 +377,23 @@ class FCastCastingDevice : CastingDevice {
Logger.i(TAG, "Stopped connection loop."); Logger.i(TAG, "Stopped connection loop.");
connectionState = CastConnectionState.DISCONNECTED; 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 { } else {
Log.i(TAG, "Thread was still alive, not restarted") Log.i(TAG, "Thread was still alive, not restarted")
} }
@ -473,6 +498,7 @@ class FCastCastingDevice : CastingDevice {
_started = false; _started = false;
//TODO: Kill and/or join thread? //TODO: Kill and/or join thread?
_thread = null; _thread = null;
_pingThread = null;
val socket = _socket; val socket = _socket;
val scopeIO = _scopeIO; val scopeIO = _scopeIO;

View file

@ -143,7 +143,9 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
StateCasting.instance.onActiveDeviceDurationChanged.remove(this); StateCasting.instance.onActiveDeviceDurationChanged.remove(this);
StateCasting.instance.onActiveDeviceDurationChanged.subscribe { 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; _device = StateCasting.instance.activeDevice;
@ -185,8 +187,10 @@ class ConnectedCastingDialog(context: Context?) : AlertDialog(context) {
_sliderPosition.valueFrom = 0.0f; _sliderPosition.valueFrom = 0.0f;
_sliderVolume.valueFrom = 0.0f; _sliderVolume.valueFrom = 0.0f;
_sliderVolume.value = d.volume.toFloat().coerceAtLeast(0.0f).coerceAtMost(_sliderVolume.valueTo); _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) { if (d.canSetVolume) {
_layoutVolumeAdjustable.visibility = View.VISIBLE; _layoutVolumeAdjustable.visibility = View.VISIBLE;