diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index cddab9ea..2fbae5e0 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -553,6 +553,7 @@
Broker host is empty
Connection failed
Connection lost
+ Couldn't reach %1$s:%2$d. Check your network or pick another broker in Settings → Jam.
Broker
diff --git a/composeApp/src/commonMain/kotlin/org/spotube/spotube/core/jam/JamRoomClient.kt b/composeApp/src/commonMain/kotlin/org/spotube/spotube/core/jam/JamRoomClient.kt
index 85085f0f..07810202 100644
--- a/composeApp/src/commonMain/kotlin/org/spotube/spotube/core/jam/JamRoomClient.kt
+++ b/composeApp/src/commonMain/kotlin/org/spotube/spotube/core/jam/JamRoomClient.kt
@@ -33,12 +33,14 @@ import org.spotube.spotube.modules.settings.JamBroker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@@ -47,6 +49,7 @@ import kotlinx.serialization.json.Json
import org.jetbrains.compose.resources.getString
import spotube.composeapp.generated.resources.Res
import spotube.composeapp.generated.resources.jam_error_broker_host_empty
+import spotube.composeapp.generated.resources.jam_error_broker_unreachable
import spotube.composeapp.generated.resources.jam_error_connection_failed
import spotube.composeapp.generated.resources.jam_error_connection_lost
import spotube.composeapp.generated.resources.jam_error_no_broker
@@ -135,8 +138,15 @@ class JamRoomClient : KoinComponent {
val mqtt = startClient(broker, clientId)
client = mqtt
- withTimeout(broker.connectionTimeoutSeconds.seconds) {
- mqtt.awaitConnectivity()
+ try {
+ withTimeout(broker.connectionTimeoutSeconds.seconds) {
+ // connectionState replays its current value, unlike awaitConnectivity's
+ // broadcast flow, which loses a CONNACK that lands before it is collected.
+ mqtt.connectionState.first { it is ConnectionState.Connected }
+ }
+ } catch (e: TimeoutCancellationException) {
+ throw IllegalStateException(
+ getString(Res.string.jam_error_broker_unreachable, broker.host, broker.port), e)
}
_connectionError.value = null
log.i { "Connected to ${broker.host}:${broker.port} room=$code as $clientId" }
diff --git a/composeApp/src/commonMain/kotlin/org/spotube/spotube/modules/settings/SettingsModels.kt b/composeApp/src/commonMain/kotlin/org/spotube/spotube/modules/settings/SettingsModels.kt
index f22688a3..90f87e3e 100644
--- a/composeApp/src/commonMain/kotlin/org/spotube/spotube/modules/settings/SettingsModels.kt
+++ b/composeApp/src/commonMain/kotlin/org/spotube/spotube/modules/settings/SettingsModels.kt
@@ -98,9 +98,9 @@ data class UserSettings(
@Serializable
data class JamBroker(
val name: String = "",
- val host: String = "test.mosquitto.org",
- val port: Int = 1883,
- val useTls: Boolean = false,
+ val host: String = "broker.hivemq.com",
+ val port: Int = 8883,
+ val useTls: Boolean = true,
val username: String? = null,
val password: String? = null,
val clientIdPrefix: String = "spotube",