tv-controller-android/app/src/main/java/com/example/tvcontroller/services/DeviceService.kt

246 lines
8.8 KiB
Kotlin

package com.example.tvcontroller.services
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.util.Log
import com.example.tvcontroller.data.Integration
import com.example.tvcontroller.webrtc.RtcPeerConnection
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.websocket.WebSockets
import io.ktor.client.plugins.websocket.webSocket
import io.ktor.client.request.cookie
import io.ktor.client.request.headers
import io.ktor.client.request.request
import io.ktor.client.request.setBody
import io.ktor.client.statement.HttpResponse
import io.ktor.http.HttpMethod
import io.ktor.websocket.DefaultWebSocketSession
import io.ktor.websocket.Frame
import io.ktor.websocket.readText
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.webrtc.IceCandidate
private const val SHARED_PREFERENCES_NAME = "devices";
private const val TAG = "DeviceService"
class DeviceService(private val context: Context) {
private var client = HttpClient(CIO) {
install(WebSockets)
}
private var websocket: DefaultWebSocketSession? = null
private var serverAddress: String = ""
private var token: String = ""
private var deviceId: String = ""
var rtcPeerConnection: RtcPeerConnection? = null
suspend fun initialize() {
loadPreferences()
if (token.isEmpty()) return
getIntegration()?.let {
connect()
}
}
suspend fun registerIntegration(name: String, code: String) {
Log.i(TAG, "Creating integration for $name with code $code at $serverAddress")
val requestJson = JSONObject()
requestJson.put("name", name)
requestJson.put("code", code)
token = ""
deviceId = ""
try {
val response: HttpResponse =
client.request("http://$serverAddress/api/integrations/register") {
method = HttpMethod.Post
setBody(requestJson.toString())
headers {
append("Content-Type", "application/json")
}
cookie(name = "token", value = token)
}
val body: String = response.body()
val responseJson = JSONObject(body)
if (response.status.value != 200) {
val error = responseJson.getString("error")
Log.e(TAG, "Error getting integration: ${response.status.value} $error")
return
}
token = responseJson.getString("token")
deviceId = responseJson.getString("id")
savePreferences()
Log.i(TAG, "Response: ${response.status.value} $body")
} catch (e: Exception) {
Log.e(TAG, "Error registering integration", e)
}
}
suspend fun getIntegration(): Integration? {
Log.i(TAG, "Getting integration $deviceId at $serverAddress")
try {
val response: HttpResponse =
client.request("http://$serverAddress/api/integrations/$deviceId") {
method = HttpMethod.Get
headers {
append("Authorization", "Bearer $token")
}
cookie(name = "token", value = token)
}
val body: String = response.body()
val responseJson = JSONObject(body)
if (response.status.value != 200) {
val error = responseJson.getString("error")
Log.e(TAG, "Error getting integration: ${response.status.value} $error")
return null
}
val integration = Integration(
responseJson.getString("id"),
responseJson.getString("name")
)
return integration
} catch (e: Exception) {
Log.e(TAG, "Error getting integration", e)
}
return null
}
private fun loadPreferences() {
val sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
serverAddress = sharedPreferences.getString("server_address", "")!!
token = sharedPreferences.getString("token", "")!!
deviceId = sharedPreferences.getString("device_id", "")!!
Log.i(TAG, "Loaded preferences: $serverAddress $token")
}
private fun savePreferences() {
val sharedPreferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, MODE_PRIVATE)
val editor = sharedPreferences.edit()
editor.apply {
putString("server_address", serverAddress)
putString("token", token)
putString("device_id", deviceId)
apply()
}
}
suspend fun connect() {
Log.i(TAG, "Connecting to websocket at $serverAddress")
val (host, port) = serverAddress.split(":")
val portInt = if (port.isEmpty()) 80 else port.toInt()
client.webSocket(
method = HttpMethod.Get,
host = host,
port = portInt,
path = "/ws",
request = {
cookie(name = "token", value = token)
}
) {
Log.i(TAG, "Listening for incoming websocket messages")
websocket = this
while (true) {
val frame = incoming.receive()
if (frame is Frame.Text) {
val dataString = frame.readText()
val dataJson = JSONObject(dataString)
val senderId = dataJson.getString("sender")
val message = dataJson.getJSONObject("message")
val type = message.getString("type")
when (type) {
RtcPeerConnection.TYPE_OFFER -> {
Log.i(TAG, "Received offer from $senderId")
val sdp = message.getString("sdp")
rtcPeerConnection?.connect(senderId, sdp)
}
RtcPeerConnection.TYPE_ICE_CANDIDATE -> {
Log.i(TAG, "Received ice candidate")
val candidateString = message.getString("candidate")
val candidateJson = JSONObject(candidateString)
val sdpMid = candidateJson.getString("sdpMid")
val sdpMLineIndex = candidateJson.getInt("sdpMLineIndex")
val sdp = candidateJson.getString("candidate")
val candidate = IceCandidate(sdpMid, sdpMLineIndex, sdp)
Log.i(TAG, "Candidate: $candidate")
rtcPeerConnection?.addIceCandidate(candidate)
}
}
}
}
}
}
fun sendWebRtcAnswer(targetId: String, sdp: String) {
val messageJson = JSONObject()
messageJson.put("type", TYPE_SIGNALING)
messageJson.put("target", targetId)
messageJson.put("message", JSONObject().apply {
put("sdp", sdp)
put("type", RtcPeerConnection.TYPE_ANSWER)
})
runBlocking {
Log.i(TAG, "Sending answer")
val frame = Frame.Text(messageJson.toString())
websocket?.send(frame)
}
}
fun sendWebRtcOffer(targetId: String, sdp: String) {
val messageJson = JSONObject()
messageJson.put("type", TYPE_SIGNALING)
messageJson.put("target", targetId)
messageJson.put("message", JSONObject().apply {
put("sdp", sdp)
put("type", RtcPeerConnection.TYPE_OFFER)
})
runBlocking {
Log.i(TAG, "Sending offer")
val frame = Frame.Text(messageJson.toString())
websocket?.send(frame)
}
}
fun sendWebRtcIceCandidate(targetId: String, candidate: IceCandidate) {
val messageJson = JSONObject()
messageJson.put("type", TYPE_SIGNALING)
messageJson.put("target", targetId)
messageJson.put("message", JSONObject().apply {
put("candidate", JSONObject().apply {
put("sdpMid", candidate.sdpMid)
put("sdpMLineIndex", candidate.sdpMLineIndex)
put("candidate", candidate.sdp)
})
put("type", RtcPeerConnection.TYPE_ICE_CANDIDATE)
})
runBlocking {
Log.i(TAG, "Sending ice candidate")
val frame = Frame.Text(messageJson.toString())
websocket?.send(frame)
}
}
fun setServerAddress(url: String) {
serverAddress = url
}
fun getServerAddress(): String {
return serverAddress
}
fun getToken(): String {
return token
}
companion object {
const val TYPE_SIGNALING = "signaling"
}
}