mirror of
https://github.com/KRTirtho/spotube.git
synced 2026-09-20 06:34:00 +00:00
fix(remote-control): update connection handling and improve logging for remote control events
This commit is contained in:
parent
df3a6dcbd2
commit
c5d35bfc93
@ -101,13 +101,16 @@ class RemoteControlClient {
|
|||||||
|
|
||||||
private suspend fun receiveLoop(host: String, port: Int) {
|
private suspend fun receiveLoop(host: String, port: Int) {
|
||||||
val currentSession = session ?: return
|
val currentSession = session ?: return
|
||||||
|
logger.d { "Starting receive loop for $host:$port" }
|
||||||
try {
|
try {
|
||||||
for (frame in currentSession.incoming) {
|
for (frame in currentSession.incoming) {
|
||||||
when (frame) {
|
when (frame) {
|
||||||
is Frame.Text -> {
|
is Frame.Text -> {
|
||||||
val text = frame.readText()
|
val text = frame.readText()
|
||||||
|
logger.d { "Received frame: $text" }
|
||||||
try {
|
try {
|
||||||
val event = json.decodeFromString(RemoteControlEvent.serializer(), text)
|
val event = json.decodeFromString(RemoteControlEvent.serializer(), text)
|
||||||
|
logger.d { "Parsed event: $event" }
|
||||||
when (event) {
|
when (event) {
|
||||||
is RemoteControlEvent.Connected -> {
|
is RemoteControlEvent.Connected -> {
|
||||||
logger.i { "Connection authorized by server" }
|
logger.i { "Connection authorized by server" }
|
||||||
@ -136,6 +139,7 @@ class RemoteControlClient {
|
|||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.d { "Receive loop exited normally" }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.e(e) { "Error in receive loop" }
|
logger.e(e) { "Error in receive loop" }
|
||||||
_connectionState.value = ConnectionState.Error(e.message ?: "Connection lost")
|
_connectionState.value = ConnectionState.Error(e.message ?: "Connection lost")
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class RemoteControlHandler(
|
|||||||
val waitingMessage = RemoteControlEvent.WaitingForPermission(
|
val waitingMessage = RemoteControlEvent.WaitingForPermission(
|
||||||
"Waiting for permission from $deviceName..."
|
"Waiting for permission from $deviceName..."
|
||||||
)
|
)
|
||||||
session.send(Frame.Text(json.encodeToString(RemoteControlEvent.WaitingForPermission.serializer(), waitingMessage)))
|
session.send(Frame.Text(json.encodeToString(RemoteControlEvent.serializer(), waitingMessage)))
|
||||||
|
|
||||||
val request = ConnectionRequest(
|
val request = ConnectionRequest(
|
||||||
deviceId = deviceId ?: "unknown",
|
deviceId = deviceId ?: "unknown",
|
||||||
@ -99,14 +99,18 @@ class RemoteControlHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send connected message
|
// Send connected message
|
||||||
session.send(Frame.Text(json.encodeToString(RemoteControlEvent.Connected.serializer(), RemoteControlEvent.Connected)))
|
session.send(Frame.Text(json.encodeToString(RemoteControlEvent.serializer(), RemoteControlEvent.Connected)))
|
||||||
logger.i { "Remote control connection established from $deviceName ($deviceId)" }
|
logger.i { "Remote control connection established from $deviceName ($deviceId)" }
|
||||||
|
|
||||||
|
// Broadcast initial player state so the controller shows current track info
|
||||||
|
broadcastState(session)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
handleControlLoop(session)
|
handleControlLoop(session)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.w(e) { "Error in remote control session" }
|
logger.w(e) { "Error in remote control session" }
|
||||||
} finally {
|
} finally {
|
||||||
|
logger.d { "Closing session in finally block" }
|
||||||
session.close()
|
session.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -129,9 +133,11 @@ class RemoteControlHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleControlLoop(session: WebSocketServerSession) {
|
private suspend fun handleControlLoop(session: WebSocketServerSession) {
|
||||||
|
logger.d { "Starting control loop for session" }
|
||||||
for (frame in session.incoming) {
|
for (frame in session.incoming) {
|
||||||
if (frame is Frame.Text) {
|
if (frame is Frame.Text) {
|
||||||
val text = frame.readText()
|
val text = frame.readText()
|
||||||
|
logger.d { "Received command: $text" }
|
||||||
try {
|
try {
|
||||||
val envelope = json.decodeFromString(CommandEnvelope.serializer(), text)
|
val envelope = json.decodeFromString(CommandEnvelope.serializer(), text)
|
||||||
handleCommand(session, envelope)
|
handleCommand(session, envelope)
|
||||||
@ -141,6 +147,7 @@ class RemoteControlHandler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.d { "Control loop exited normally" }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun handleCommand(session: WebSocketServerSession, envelope: CommandEnvelope) {
|
private suspend fun handleCommand(session: WebSocketServerSession, envelope: CommandEnvelope) {
|
||||||
@ -197,12 +204,12 @@ class RemoteControlHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendAck(session: WebSocketServerSession, commandId: String) {
|
private suspend fun sendAck(session: WebSocketServerSession, commandId: String) {
|
||||||
val text = json.encodeToString(RemoteControlEvent.Ack.serializer(), RemoteControlEvent.Ack(commandId))
|
val text = json.encodeToString(RemoteControlEvent.serializer(), RemoteControlEvent.Ack(commandId))
|
||||||
session.send(Frame.Text(text))
|
session.send(Frame.Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendError(session: WebSocketServerSession, message: String) {
|
private suspend fun sendError(session: WebSocketServerSession, message: String) {
|
||||||
val text = json.encodeToString(RemoteControlEvent.Error.serializer(), RemoteControlEvent.Error(message))
|
val text = json.encodeToString(RemoteControlEvent.serializer(), RemoteControlEvent.Error(message))
|
||||||
session.send(Frame.Text(text))
|
session.send(Frame.Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -221,7 +228,7 @@ class RemoteControlHandler(
|
|||||||
currentTrackAlbum = current?.albumOrNull(),
|
currentTrackAlbum = current?.albumOrNull(),
|
||||||
currentTrackCoverUrl = current?.coverUrlOrNull(),
|
currentTrackCoverUrl = current?.coverUrlOrNull(),
|
||||||
)
|
)
|
||||||
val text = json.encodeToString(RemoteControlEvent.PlayerState.serializer(), state)
|
val text = json.encodeToString(RemoteControlEvent.serializer(), state)
|
||||||
session.send(Frame.Text(text))
|
session.send(Frame.Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,7 +72,8 @@ fun DevicesScreen(
|
|||||||
viewModel.startDiscovery()
|
viewModel.startDiscovery()
|
||||||
onDispose {
|
onDispose {
|
||||||
viewModel.stopDiscovery()
|
viewModel.stopDiscovery()
|
||||||
viewModel.disconnect()
|
// Don't disconnect here - the connection should persist when navigating to RemoteControlScreen
|
||||||
|
// The RemoteControlViewModel will manage the connection lifecycle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,6 +57,7 @@ import dev.krtirtho.spotube.core.ui.base.GhostIconButton
|
|||||||
import dev.krtirtho.spotube.core.ui.base.IconButton
|
import dev.krtirtho.spotube.core.ui.base.IconButton
|
||||||
import dev.krtirtho.spotube.core.ui.base.Slider
|
import dev.krtirtho.spotube.core.ui.base.Slider
|
||||||
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
|
||||||
|
import dev.krtirtho.spotube.modules.shell.LocalAppShellBottomInset
|
||||||
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
import dev.krtirtho.spotube.resources.iconsax.Iconsax
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxCloseSquare
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxCloseSquare
|
||||||
import dev.krtirtho.spotube.resources.iconsax.IconsaxNext
|
import dev.krtirtho.spotube.resources.iconsax.IconsaxNext
|
||||||
@ -77,6 +78,7 @@ fun RemoteControlScreen(
|
|||||||
val viewModel = koinViewModel<RemoteControlViewModel>()
|
val viewModel = koinViewModel<RemoteControlViewModel>()
|
||||||
val playerState by viewModel.playerState.collectAsStateWithLifecycle()
|
val playerState by viewModel.playerState.collectAsStateWithLifecycle()
|
||||||
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
|
val connectionState by viewModel.connectionState.collectAsStateWithLifecycle()
|
||||||
|
val shellBottomInset = LocalAppShellBottomInset.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -110,14 +112,15 @@ fun RemoteControlScreen(
|
|||||||
onSetVolume = viewModel::setVolume,
|
onSetVolume = viewModel::setVolume,
|
||||||
onToggleShuffle = viewModel::toggleShuffle,
|
onToggleShuffle = viewModel::toggleShuffle,
|
||||||
onCycleLoopMode = viewModel::cycleLoopMode,
|
onCycleLoopMode = viewModel::cycleLoopMode,
|
||||||
modifier = Modifier.padding(padding)
|
modifier = Modifier.padding(padding).padding(bottom = shellBottomInset)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is ConnectionState.Connecting -> {
|
is ConnectionState.Connecting -> {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding),
|
.padding(padding)
|
||||||
|
.padding(bottom = shellBottomInset),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text("Connecting...")
|
Text("Connecting...")
|
||||||
@ -127,7 +130,8 @@ fun RemoteControlScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding),
|
.padding(padding)
|
||||||
|
.padding(bottom = shellBottomInset),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text("Disconnected")
|
Text("Disconnected")
|
||||||
@ -137,7 +141,8 @@ fun RemoteControlScreen(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(padding),
|
.padding(padding)
|
||||||
|
.padding(bottom = shellBottomInset),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text("Connection error: ${(connectionState as ConnectionState.Error).message}")
|
Text("Connection error: ${(connectionState as ConnectionState.Error).message}")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user