From 70f36e69e606264a51f28c4be67dd506ea3dbf19 Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 10:15:15 +0200 Subject: [PATCH 1/7] Freeze fix when clicking link in description. --- .../others/PlatformLinkMovementMethod.kt | 47 ++++++++++++------- .../views/behavior/NonScrollingTextView.kt | 36 +++++++++----- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt index d524b7cf..76652236 100644 --- a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt +++ b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt @@ -8,11 +8,14 @@ import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.view.MotionEvent import android.widget.TextView +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.timestampRegex -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() { @@ -60,31 +63,39 @@ class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMe val dx = event.x - downX val dy = event.y - downY if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(widget, event)) { - runBlocking { - for (link in pressedLinks!!) { - Logger.i(TAG) { "Link clicked '${link.url}'." } + for (link in pressedLinks!!) { + Logger.i(TAG) { "Link clicked '${link.url}'." } - if (_context is MainActivity) { - if (_context.handleUrl(link.url)) continue - if (timestampRegex.matches(link.url)) { - val tokens = link.url.split(':') - var time_s = -1L - when (tokens.size) { - 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() - 3 -> time_s = tokens[0].toLong() * 3600 + - tokens[1].toLong() * 60 + - tokens[2].toLong() - } + val c = _context + if (c is MainActivity) { + c.lifecycleScope.launch(Dispatchers.IO) { + if (c.handleUrl(link.url)) { + return@launch + } + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':') + var time_s = -1L + when (tokens.size) { + 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() + 3 -> time_s = tokens[0].toLong() * 3600 + + tokens[1].toLong() * 60 + + tokens[2].toLong() + } - if (time_s != -1L) { + if (time_s != -1L) { + withContext(Dispatchers.Main) { MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) - continue } + return@launch } } - _context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + + withContext(Dispatchers.Main) { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } } } + } pressedLinks = null linkPressed = false return true diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt index cf599176..6e3a8860 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt @@ -8,12 +8,16 @@ import android.text.Spannable import android.text.style.URLSpan import android.util.AttributeSet import android.view.MotionEvent +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.others.PlatformLinkMovementMethod import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.timestampRegex -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { private var _lastTouchedLinks: Array? = null @@ -77,12 +81,14 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { val dx = event.x - downX val dy = event.y - downY if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(event)) { - runBlocking { - for (link in _lastTouchedLinks!!) { - Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." } - val c = context - if (c is MainActivity) { - if (c.handleUrl(link.url)) continue + for (link in _lastTouchedLinks!!) { + Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." } + val c = context + if (c is MainActivity) { + c.lifecycleScope.launch(Dispatchers.IO) { + if (c.handleUrl(link.url)) { + return@launch + } if (timestampRegex.matches(link.url)) { val tokens = link.url.split(':') var time_s = -1L @@ -92,13 +98,21 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { tokens[1].toLong() * 60 + tokens[2].toLong() } + if (time_s != -1L) { - MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) - continue + withContext(Dispatchers.Main) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) + } + return@launch } } - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) - } else { + + withContext(Dispatchers.Main) { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } + } + } else { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) } } From 5b50ac926ecb6a4e25155f30170e70d6cb499650 Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 10:15:15 +0200 Subject: [PATCH 2/7] Freeze fix when clicking link in description. --- .../others/PlatformLinkMovementMethod.kt | 47 ++++++++++++------- .../views/behavior/NonScrollingTextView.kt | 36 +++++++++----- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt index d524b7cf..76652236 100644 --- a/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt +++ b/app/src/main/java/com/futo/platformplayer/others/PlatformLinkMovementMethod.kt @@ -8,11 +8,14 @@ import android.text.method.LinkMovementMethod import android.text.style.URLSpan import android.view.MotionEvent import android.widget.TextView +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.receivers.MediaControlReceiver import com.futo.platformplayer.timestampRegex -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMethod() { @@ -60,31 +63,39 @@ class PlatformLinkMovementMethod(private val _context: Context) : LinkMovementMe val dx = event.x - downX val dy = event.y - downY if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(widget, event)) { - runBlocking { - for (link in pressedLinks!!) { - Logger.i(TAG) { "Link clicked '${link.url}'." } + for (link in pressedLinks!!) { + Logger.i(TAG) { "Link clicked '${link.url}'." } - if (_context is MainActivity) { - if (_context.handleUrl(link.url)) continue - if (timestampRegex.matches(link.url)) { - val tokens = link.url.split(':') - var time_s = -1L - when (tokens.size) { - 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() - 3 -> time_s = tokens[0].toLong() * 3600 + - tokens[1].toLong() * 60 + - tokens[2].toLong() - } + val c = _context + if (c is MainActivity) { + c.lifecycleScope.launch(Dispatchers.IO) { + if (c.handleUrl(link.url)) { + return@launch + } + if (timestampRegex.matches(link.url)) { + val tokens = link.url.split(':') + var time_s = -1L + when (tokens.size) { + 2 -> time_s = tokens[0].toLong() * 60 + tokens[1].toLong() + 3 -> time_s = tokens[0].toLong() * 3600 + + tokens[1].toLong() * 60 + + tokens[2].toLong() + } - if (time_s != -1L) { + if (time_s != -1L) { + withContext(Dispatchers.Main) { MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) - continue } + return@launch } } - _context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + + withContext(Dispatchers.Main) { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } } } + } pressedLinks = null linkPressed = false return true diff --git a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt index cf599176..6e3a8860 100644 --- a/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/behavior/NonScrollingTextView.kt @@ -8,12 +8,16 @@ import android.text.Spannable import android.text.style.URLSpan import android.util.AttributeSet import android.view.MotionEvent +import androidx.lifecycle.lifecycleScope import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.others.PlatformLinkMovementMethod import com.futo.platformplayer.receivers.MediaControlReceiver +import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.timestampRegex -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { private var _lastTouchedLinks: Array? = null @@ -77,12 +81,14 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { val dx = event.x - downX val dy = event.y - downY if (Math.abs(dx) <= touchSlop && Math.abs(dy) <= touchSlop && isTouchInside(event)) { - runBlocking { - for (link in _lastTouchedLinks!!) { - Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." } - val c = context - if (c is MainActivity) { - if (c.handleUrl(link.url)) continue + for (link in _lastTouchedLinks!!) { + Logger.i(PlatformLinkMovementMethod.TAG) { "Link clicked '${link.url}'." } + val c = context + if (c is MainActivity) { + c.lifecycleScope.launch(Dispatchers.IO) { + if (c.handleUrl(link.url)) { + return@launch + } if (timestampRegex.matches(link.url)) { val tokens = link.url.split(':') var time_s = -1L @@ -92,13 +98,21 @@ class NonScrollingTextView : androidx.appcompat.widget.AppCompatTextView { tokens[1].toLong() * 60 + tokens[2].toLong() } + if (time_s != -1L) { - MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) - continue + withContext(Dispatchers.Main) { + MediaControlReceiver.onSeekToReceived.emit(time_s * 1000) + } + return@launch } } - c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) - } else { + + withContext(Dispatchers.Main) { + c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) + } + } + } else { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { c.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(link.url))) } } From d08dffd9e2001d3445b05342f69a380250c159ff Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 11:12:31 +0200 Subject: [PATCH 3/7] Added potential fix for having to restart app to get casting devices to show. Added persistent ordering for creators. --- .../java/com/futo/platformplayer/casting/StateCasting.kt | 5 +++-- .../futo/platformplayer/dialogs/ConnectCastingDialog.kt | 2 -- .../fragment/mainactivity/main/CreatorsFragment.kt | 7 ++++++- .../platformplayer/views/adapters/SubscriptionAdapter.kt | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index 58bd772c..b2c2156c 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -166,10 +166,11 @@ class StateCasting { Logger.i(TAG, "CastingService started."); _nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager + startDiscovering() } @Synchronized - fun startDiscovering() { + private fun startDiscovering() { _nsdManager?.apply { _discoveryListeners.forEach { discoverServices(it.key, NsdManager.PROTOCOL_DNS_SD, it.value) @@ -178,7 +179,7 @@ class StateCasting { } @Synchronized - fun stopDiscovering() { + private fun stopDiscovering() { _nsdManager?.apply { _discoveryListeners.forEach { try { diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt index f00bd191..87375779 100644 --- a/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt +++ b/app/src/main/java/com/futo/platformplayer/dialogs/ConnectCastingDialog.kt @@ -103,7 +103,6 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { super.show(); Logger.i(TAG, "Dialog shown."); - StateCasting.instance.startDiscovering() (_imageLoader.drawable as Animatable?)?.start(); synchronized(StateCasting.instance.devices) { @@ -148,7 +147,6 @@ class ConnectCastingDialog(context: Context?) : AlertDialog(context) { override fun dismiss() { super.dismiss() (_imageLoader.drawable as Animatable?)?.stop() - StateCasting.instance.stopDiscovering() StateCasting.instance.onDeviceAdded.remove(this) StateCasting.instance.onDeviceChanged.remove(this) StateCasting.instance.onDeviceRemoved.remove(this) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt index 54649ebf..345d577e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorsFragment.kt @@ -16,6 +16,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.R import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage import com.futo.platformplayer.views.adapters.SubscriptionAdapter class CreatorsFragment : MainFragment() { @@ -29,6 +31,8 @@ class CreatorsFragment : MainFragment() { private var _editSearch: EditText? = null; private var _textMeta: TextView? = null; private var _buttonClearSearch: ImageButton? = null + private var _ordering = FragmentedStorage.get("creators_ordering") + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.fragment_creators, container, false); @@ -44,7 +48,7 @@ class CreatorsFragment : MainFragment() { _buttonClearSearch?.visibility = View.INVISIBLE; } - val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription)) { subs -> + val adapter = SubscriptionAdapter(inflater, getString(R.string.confirm_delete_subscription), _ordering?.value?.toIntOrNull() ?: 5) { subs -> _textMeta?.let { it.text = "${subs.size} creator${if(subs.size > 1) "s" else ""}"; } @@ -61,6 +65,7 @@ class CreatorsFragment : MainFragment() { spinnerSortBy.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id: Long) { adapter.sortBy = pos; + _ordering.setAndSave(pos.toString()) } override fun onNothingSelected(parent: AdapterView<*>?) = Unit }; diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt index 33783e67..fb28c2e4 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/SubscriptionAdapter.kt @@ -31,10 +31,11 @@ class SubscriptionAdapter : RecyclerView.Adapter { updateDataset(); } - constructor(inflater: LayoutInflater, confirmationMessage: String, onDatasetChanged: ((List)->Unit)? = null) : super() { + constructor(inflater: LayoutInflater, confirmationMessage: String, sortByDefault: Int, onDatasetChanged: ((List)->Unit)? = null) : super() { _inflater = inflater; _confirmationMessage = confirmationMessage; _onDatasetChanged = onDatasetChanged; + sortBy = sortByDefault StateSubscriptions.instance.onSubscriptionsChanged.subscribe { _, _ -> if(Looper.myLooper() != Looper.getMainLooper()) StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { updateDataset() } From 2c454a0ec55ec8b80bd469fefdb23f3ce5761f69 Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 11:20:04 +0200 Subject: [PATCH 4/7] Added boolean setting to allow link local casting over ipv4. --- .../main/java/com/futo/platformplayer/Settings.kt | 5 +++++ .../com/futo/platformplayer/casting/StateCasting.kt | 13 ++++++++++--- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index b8e23283..e8f4f70a 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -681,6 +681,11 @@ class Settings : FragmentedStorageFileJson() { @Serializable(with = FlexibleBooleanSerializer::class) var allowIpv6: Boolean = true; + @AdvancedField + @FormField(R.string.allow_ipv4, FieldForm.TOGGLE, R.string.allow_ipv4_description, 5) + @Serializable(with = FlexibleBooleanSerializer::class) + var allowLinkLocalIpv4: Boolean = false; + /*TODO: Should we have a different casting quality? @FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3) @DropdownFieldOptionsId(R.array.preferred_quality_array) diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index b2c2156c..1e8e1830 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -1221,9 +1221,16 @@ class StateCasting { private fun getLocalUrl(ad: CastingDevice): String { var address = ad.localAddress!! - if (address.isLinkLocalAddress) { - address = findPreferredAddress() ?: address - Logger.i(TAG, "Selected casting address: $address") + if (Settings.instance.casting.allowLinkLocalIpv4) { + if (address.isLinkLocalAddress && address is Inet6Address) { + address = findPreferredAddress() ?: address + Logger.i(TAG, "Selected casting address: $address") + } + } else { + if (address.isLinkLocalAddress) { + address = findPreferredAddress() ?: address + Logger.i(TAG, "Selected casting address: $address") + } } return "http://${address.toUrlAddress().trim('/')}:${_castServer.port}"; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e2b7d730..b2513e21 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ Always proxy requests when casting data through the device. Allow IPV6 If casting over IPV6 is allowed, can cause issues on some networks + Allow Link Local IPV4 + If casting over IPV4 link local is allowed, can cause issues on some networks Discover Find new video sources to add These sources have been disabled From a810f82ce2a03c93962e39e92c44efa0c17eac5e Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 11:20:04 +0200 Subject: [PATCH 5/7] Added boolean setting to allow link local casting over ipv4. --- .../main/java/com/futo/platformplayer/Settings.kt | 5 +++++ .../com/futo/platformplayer/casting/StateCasting.kt | 13 ++++++++++--- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index 560ceb0a..281523c6 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -628,6 +628,11 @@ class Settings : FragmentedStorageFileJson() { @Serializable(with = FlexibleBooleanSerializer::class) var allowIpv6: Boolean = true; + @AdvancedField + @FormField(R.string.allow_ipv4, FieldForm.TOGGLE, R.string.allow_ipv4_description, 5) + @Serializable(with = FlexibleBooleanSerializer::class) + var allowLinkLocalIpv4: Boolean = false; + /*TODO: Should we have a different casting quality? @FormField("Preferred Casting Quality", FieldForm.DROPDOWN, "", 3) @DropdownFieldOptionsId(R.array.preferred_quality_array) diff --git a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt index b2c2156c..1e8e1830 100644 --- a/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt +++ b/app/src/main/java/com/futo/platformplayer/casting/StateCasting.kt @@ -1221,9 +1221,16 @@ class StateCasting { private fun getLocalUrl(ad: CastingDevice): String { var address = ad.localAddress!! - if (address.isLinkLocalAddress) { - address = findPreferredAddress() ?: address - Logger.i(TAG, "Selected casting address: $address") + if (Settings.instance.casting.allowLinkLocalIpv4) { + if (address.isLinkLocalAddress && address is Inet6Address) { + address = findPreferredAddress() ?: address + Logger.i(TAG, "Selected casting address: $address") + } + } else { + if (address.isLinkLocalAddress) { + address = findPreferredAddress() ?: address + Logger.i(TAG, "Selected casting address: $address") + } } return "http://${address.toUrlAddress().trim('/')}:${_castServer.port}"; } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 98f542c0..cd96fd44 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,8 @@ Always proxy requests when casting data through the device. Allow IPV6 If casting over IPV6 is allowed, can cause issues on some networks + Allow Link Local IPV4 + If casting over IPV4 link local is allowed, can cause issues on some networks Discover Find new video sources to add These sources have been disabled From 10753eb87937adb2d9caca6d75ac504c8883dc2a Mon Sep 17 00:00:00 2001 From: Koen J Date: Fri, 6 Jun 2025 12:25:25 +0200 Subject: [PATCH 6/7] Sort to prefer ipv4 over ipv6. --- .../java/com/futo/platformplayer/Extensions_Network.kt | 7 +++++-- app/src/main/java/com/futo/platformplayer/Utility.kt | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) 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 f0137f2d..fca7deda 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_Network.kt @@ -241,8 +241,11 @@ fun getConnectedSocket(attemptAddresses: List, port: Int): Socket? return null; } + val sortedAddresses: List = addresses + .sortedBy { addr -> addressScore(addr) } + val sockets: ArrayList = arrayListOf(); - for (i in addresses.indices) { + for (i in sortedAddresses.indices) { sockets.add(Socket()); } @@ -250,7 +253,7 @@ fun getConnectedSocket(attemptAddresses: List, port: Int): Socket? var connectedSocket: Socket? = null; val threads: ArrayList = arrayListOf(); for (i in 0 until sockets.size) { - val address = addresses[i]; + val address = sortedAddresses[i]; val socket = sockets[i]; val thread = Thread { try { diff --git a/app/src/main/java/com/futo/platformplayer/Utility.kt b/app/src/main/java/com/futo/platformplayer/Utility.kt index e2868ed6..0875aadb 100644 --- a/app/src/main/java/com/futo/platformplayer/Utility.kt +++ b/app/src/main/java/com/futo/platformplayer/Utility.kt @@ -434,7 +434,7 @@ private fun interfaceScore(nif: NetworkInterface): Int { } } -private fun addressScore(addr: InetAddress): Int { +fun addressScore(addr: InetAddress): Int { return when (addr) { is Inet4Address -> { val octets = addr.address.map { it.toInt() and 0xFF } From 0d9e1cd3c5e680082a107e23038aa5c08118f8b7 Mon Sep 17 00:00:00 2001 From: zvonimir Date: Fri, 6 Jun 2025 15:40:43 +0200 Subject: [PATCH 7/7] fix: Scope getting removed when switching between settings 'Kelvin approved' --- .../platformplayer/activities/MainActivity.kt | 17 ++++++++++------- .../com/futo/platformplayer/states/StateApp.kt | 14 +++++++++++--- .../platformplayer/views/fields/ToggleField.kt | 2 +- app/src/unstable/AndroidManifest.xml | 2 +- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index b46b6502..829b7857 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -115,6 +115,7 @@ import java.io.StringWriter import java.lang.reflect.InvocationTargetException import java.util.LinkedList import java.util.Queue +import java.util.UUID import java.util.concurrent.ConcurrentLinkedQueue @@ -218,6 +219,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { } } + val mainId = UUID.randomUUID().toString().substring(0, 5) + constructor() : super() { if (BuildConfig.DEBUG) { StrictMode.setVmPolicy( @@ -269,8 +272,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { @UnstableApi override fun onCreate(savedInstanceState: Bundle?) { - Logger.i(TAG, "MainActivity Starting"); - StateApp.instance.setGlobalContext(this, lifecycleScope); + Logger.w(TAG, "MainActivity Starting [$mainId]"); + StateApp.instance.setGlobalContext(this, lifecycleScope, mainId); StateApp.instance.mainAppStarting(this); super.onCreate(savedInstanceState); @@ -671,13 +674,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { override fun onResume() { super.onResume(); - Logger.v(TAG, "onResume") + Logger.w(TAG, "onResume [$mainId]") _isVisible = true; } override fun onPause() { super.onPause(); - Logger.v(TAG, "onPause") + Logger.w(TAG, "onPause [$mainId]") _isVisible = false; _qrCodeLoadingDialog?.dismiss() @@ -686,7 +689,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { override fun onStop() { super.onStop() - Logger.v(TAG, "_wasStopped = true"); + Logger.w(TAG, "onStop [$mainId]"); _wasStopped = true; } @@ -1103,8 +1106,8 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { override fun onDestroy() { super.onDestroy(); - Logger.v(TAG, "onDestroy") - StateApp.instance.mainAppDestroyed(this); + Logger.w(TAG, "onDestroy [$mainId]") + StateApp.instance.mainAppDestroyed(this, mainId); } inline fun isFragmentActive(): Boolean { diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index e2155e8b..9757f005 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -156,6 +156,8 @@ class StateApp { return thisContext; } + private var _mainId: String? = null; + //Files private var _tempDirectory: File? = null; private var _cacheDirectory: File? = null; @@ -295,9 +297,12 @@ class StateApp { } //Lifecycle - fun setGlobalContext(context: Context, coroutineScope: CoroutineScope? = null) { + fun setGlobalContext(context: Context, coroutineScope: CoroutineScope? = null, mainId: String? = null) { + _mainId = mainId; _context = context; _scope = coroutineScope + Logger.w(TAG, "Scope initialized ${(coroutineScope != null)}\n ${Log.getStackTraceString(Throwable())}") + } fun initializeFiles(force: Boolean = false) { @@ -719,7 +724,9 @@ class StateApp { migrateStores(context, managedStores, index + 1); } - fun mainAppDestroyed(context: Context) { + fun mainAppDestroyed(context: Context, mainId: String? = null) { + if (mainId != null && (_mainId != mainId || _mainId == null)) + return Logger.i(TAG, "App ended"); _receiverBecomingNoisy?.let { _receiverBecomingNoisy = null; @@ -743,7 +750,8 @@ class StateApp { fun dispose(){ _context = null; - _scope = null; + // _scope = null; + Logger.w(TAG, "StateApp disposed: ${Log.getStackTraceString(Throwable())}") } private val _connectivityEvents = object : ConnectivityManager.NetworkCallback() { diff --git a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt index c419092c..7675a791 100644 --- a/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt +++ b/app/src/main/java/com/futo/platformplayer/views/fields/ToggleField.kt @@ -90,7 +90,7 @@ class ToggleField : TableRow, IField { val advancedFieldAttr = field.getAnnotation(AdvancedField::class.java) if(advancedFieldAttr != null || advanced) { - Logger.w("ToggleField", "Found advanced field: " + field.name); + // Logger.w("ToggleField", "Found advanced field: " + field.name); isAdvanced = true; } diff --git a/app/src/unstable/AndroidManifest.xml b/app/src/unstable/AndroidManifest.xml index 7c47d7bd..de471f2d 100644 --- a/app/src/unstable/AndroidManifest.xml +++ b/app/src/unstable/AndroidManifest.xml @@ -7,7 +7,7 @@ - +