246 lines
8.8 KiB
Kotlin
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"
|
|
}
|
|
}
|