feat: handle webrtc data channel to send commands
This commit is contained in:
parent
22570e0e6d
commit
c8a0c4160c
@ -34,7 +34,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
private val bluetoothService by lazy { BluetoothService(applicationContext) }
|
private val bluetoothService by lazy { BluetoothService(applicationContext) }
|
||||||
private val deviceService by lazy { DeviceService(applicationContext, webClient, websocketClient) }
|
private val deviceService by lazy { DeviceService(applicationContext, webClient, websocketClient) }
|
||||||
private val controllerService by lazy { ControllerService(bluetoothService) }
|
private val controllerService by lazy { ControllerService(bluetoothService, webRtcService) }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package com.example.tvcontroller.data
|
package com.example.tvcontroller.data
|
||||||
|
|
||||||
class RemoteCommand {
|
class RemoteCommand {
|
||||||
var functionName: String? = null
|
|
||||||
var protocol: String? = null
|
var protocol: String? = null
|
||||||
var device: String? = null
|
var device: String? = null
|
||||||
var subdevice: String? = null
|
var command: String? = null
|
||||||
var function: String? = null
|
|
||||||
}
|
}
|
||||||
@ -1,32 +1,33 @@
|
|||||||
package com.example.tvcontroller.services
|
package com.example.tvcontroller.services
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.example.tvcontroller.data.RemoteCommand
|
import com.example.tvcontroller.data.RemoteCommand
|
||||||
|
import com.example.tvcontroller.services.webrtc.WebRtcService
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
|
|
||||||
class ControllerService(
|
class ControllerService(
|
||||||
private val bluetoothService: BluetoothService
|
private val bluetoothService: BluetoothService,
|
||||||
|
private val webRtcService: WebRtcService
|
||||||
) {
|
) {
|
||||||
private val samsungCommands = mutableMapOf<String, RemoteCommand>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadCommands()
|
loadCommands()
|
||||||
|
webRtcService.onDataChannelData(this::handleWebRtcData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendCommand(command: String) {
|
fun sendCommand(command: RemoteCommand) {
|
||||||
if (samsungCommands[command] == null) return
|
val jsonString = remoteCommandToJsonString(command)
|
||||||
Log.i("ControllerService", "Sending command: $command")
|
Log.i(TAG, "Sending command: $jsonString")
|
||||||
val jsonString = remoteCommandToJsonString(samsungCommands[command]!!)
|
|
||||||
sendData(jsonString)
|
sendData(jsonString)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remoteCommandToJsonString(command: RemoteCommand): String {
|
fun remoteCommandToJsonString(command: RemoteCommand): String {
|
||||||
var commandObject = JSONObject()
|
var commandObject = JSONObject()
|
||||||
commandObject.put("protocol", command.protocol)
|
commandObject.put("protocol", command.protocol)
|
||||||
commandObject.put("address", command.device)
|
commandObject.put("device", command.device)
|
||||||
commandObject.put("command", command.function)
|
commandObject.put("command", command.command)
|
||||||
return commandObject.toString()
|
return commandObject.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +38,27 @@ class ControllerService(
|
|||||||
fun loadCommands() {
|
fun loadCommands() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun handleWebRtcData(data: ByteBuffer) {
|
||||||
|
val dataString = StandardCharsets.UTF_8.decode(data).toString()
|
||||||
|
val json = JSONObject(dataString)
|
||||||
|
if (!json.has("type") || json.getString("type") != MESSAGE_TYPE_COMMAND) return
|
||||||
|
val commandJson = json.getJSONObject("data")
|
||||||
|
val protocol = if (commandJson.has("protocol")) commandJson.getString("protocol") else null
|
||||||
|
val device = if (commandJson.has("device")) commandJson.getString("device") else null
|
||||||
|
val command = if (commandJson.has("commandNumber")) commandJson.getString("commandNumber") else null
|
||||||
|
val remoteCommand = RemoteCommand().apply {
|
||||||
|
this.protocol = protocol
|
||||||
|
this.device = device
|
||||||
|
this.command = command
|
||||||
|
}
|
||||||
|
sendCommand(remoteCommand)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
private const val TAG = "ControllerService"
|
||||||
|
|
||||||
|
const val MESSAGE_TYPE_COMMAND = "command"
|
||||||
|
|
||||||
const val POWER = "POWER"
|
const val POWER = "POWER"
|
||||||
const val CURSOR_UP = "CURSOR UP"
|
const val CURSOR_UP = "CURSOR UP"
|
||||||
const val CURSOR_DOWN = "CURSOR DOWN"
|
const val CURSOR_DOWN = "CURSOR DOWN"
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
package com.example.tvcontroller.services.webrtc
|
package com.example.tvcontroller.services.webrtc
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.hardware.camera2.CameraCharacteristics
|
|
||||||
import android.hardware.camera2.CameraManager
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.example.tvcontroller.services.CameraService
|
import com.example.tvcontroller.services.CameraService
|
||||||
import org.webrtc.AudioTrack
|
import org.webrtc.AudioTrack
|
||||||
import org.webrtc.Camera2Capturer
|
|
||||||
import org.webrtc.DataChannel
|
import org.webrtc.DataChannel
|
||||||
import org.webrtc.DefaultVideoDecoderFactory
|
|
||||||
import org.webrtc.EglBase
|
import org.webrtc.EglBase
|
||||||
import org.webrtc.HardwareVideoEncoderFactory
|
|
||||||
import org.webrtc.IceCandidate
|
import org.webrtc.IceCandidate
|
||||||
import org.webrtc.MediaConstraints
|
import org.webrtc.MediaConstraints
|
||||||
import org.webrtc.MediaStream
|
import org.webrtc.MediaStream
|
||||||
@ -20,11 +15,11 @@ import org.webrtc.PeerConnectionFactory
|
|||||||
import org.webrtc.PeerConnectionFactory.InitializationOptions
|
import org.webrtc.PeerConnectionFactory.InitializationOptions
|
||||||
import org.webrtc.SdpObserver
|
import org.webrtc.SdpObserver
|
||||||
import org.webrtc.SessionDescription
|
import org.webrtc.SessionDescription
|
||||||
import org.webrtc.SimulcastVideoEncoderFactory
|
|
||||||
import org.webrtc.SoftwareVideoEncoderFactory
|
|
||||||
import org.webrtc.SurfaceTextureHelper
|
import org.webrtc.SurfaceTextureHelper
|
||||||
import org.webrtc.VideoSource
|
import org.webrtc.VideoSource
|
||||||
import org.webrtc.VideoTrack
|
import org.webrtc.VideoTrack
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
private const val TAG = "RtcPeerConnection"
|
private const val TAG = "RtcPeerConnection"
|
||||||
|
|
||||||
@ -37,6 +32,7 @@ class RtcPeerConnection(private val context: Context, private val cameraService:
|
|||||||
private val videoSource by lazy { createVideoSource() }
|
private val videoSource by lazy { createVideoSource() }
|
||||||
private var peerConnection: PeerConnection? = null
|
private var peerConnection: PeerConnection? = null
|
||||||
private val iceCandidateHandlers = ArrayList<((IceCandidate) -> Unit)>()
|
private val iceCandidateHandlers = ArrayList<((IceCandidate) -> Unit)>()
|
||||||
|
private val dataChannelHandlers = ArrayList<((ByteBuffer) -> Unit)>()
|
||||||
|
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
@ -52,7 +48,16 @@ class RtcPeerConnection(private val context: Context, private val cameraService:
|
|||||||
override fun onIceCandidatesRemoved(p0: Array<out IceCandidate?>?) {}
|
override fun onIceCandidatesRemoved(p0: Array<out IceCandidate?>?) {}
|
||||||
override fun onAddStream(p0: MediaStream?) {}
|
override fun onAddStream(p0: MediaStream?) {}
|
||||||
override fun onRemoveStream(p0: MediaStream?) {}
|
override fun onRemoveStream(p0: MediaStream?) {}
|
||||||
override fun onDataChannel(p0: DataChannel?) {}
|
override fun onDataChannel(channel: DataChannel?) {
|
||||||
|
Log.i(TAG, "Data channel created: $channel")
|
||||||
|
channel?.registerObserver(object : DataChannel.Observer {
|
||||||
|
override fun onBufferedAmountChange(p0: Long) { }
|
||||||
|
override fun onStateChange() { }
|
||||||
|
override fun onMessage(p0: DataChannel.Buffer?) {
|
||||||
|
dataChannelHandlers.forEach { it(p0?.data!!) }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
override fun onRenegotiationNeeded() {}
|
override fun onRenegotiationNeeded() {}
|
||||||
}
|
}
|
||||||
var rtcConfig = PeerConnection.RTCConfiguration(iceServers)
|
var rtcConfig = PeerConnection.RTCConfiguration(iceServers)
|
||||||
@ -132,6 +137,10 @@ class RtcPeerConnection(private val context: Context, private val cameraService:
|
|||||||
iceCandidateHandlers.add(handler)
|
iceCandidateHandlers.add(handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onDataChannelData(handler: (ByteBuffer) -> Unit) {
|
||||||
|
dataChannelHandlers.add(handler)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initializeFactory(): PeerConnectionFactory {
|
private fun initializeFactory(): PeerConnectionFactory {
|
||||||
val initOptions = InitializationOptions.builder(context).createInitializationOptions()
|
val initOptions = InitializationOptions.builder(context).createInitializationOptions()
|
||||||
PeerConnectionFactory.initialize(initOptions)
|
PeerConnectionFactory.initialize(initOptions)
|
||||||
|
|||||||
@ -9,12 +9,14 @@ import org.json.JSONObject
|
|||||||
import org.webrtc.IceCandidate
|
import org.webrtc.IceCandidate
|
||||||
import org.webrtc.MediaConstraints
|
import org.webrtc.MediaConstraints
|
||||||
import org.webrtc.SessionDescription
|
import org.webrtc.SessionDescription
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
class WebRtcService(
|
class WebRtcService(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val websocketClient: WebsocketClient,
|
private val websocketClient: WebsocketClient,
|
||||||
private val cameraService: CameraService
|
private val cameraService: CameraService
|
||||||
) {
|
) {
|
||||||
|
private val dataChannelHandlers = ArrayList<((ByteBuffer) -> Unit)>()
|
||||||
private var rtcPeerConnection: RtcPeerConnection = createRtcPeerConnection()
|
private var rtcPeerConnection: RtcPeerConnection = createRtcPeerConnection()
|
||||||
val videoTrack by lazy { rtcPeerConnection.createVideoTrack() }
|
val videoTrack by lazy { rtcPeerConnection.createVideoTrack() }
|
||||||
val audioTrack by lazy { rtcPeerConnection.createAudioTrack() }
|
val audioTrack by lazy { rtcPeerConnection.createAudioTrack() }
|
||||||
@ -25,6 +27,10 @@ class WebRtcService(
|
|||||||
websocketClient.onData(this::handleData)
|
websocketClient.onData(this::handleData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun onDataChannelData(handler: (ByteBuffer) -> Unit) {
|
||||||
|
dataChannelHandlers.add(handler)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRtcPeerConnection(): RtcPeerConnection {
|
private fun createRtcPeerConnection(): RtcPeerConnection {
|
||||||
val iceServers = arrayOf("stun:stun.l.google.com:19302")
|
val iceServers = arrayOf("stun:stun.l.google.com:19302")
|
||||||
val webRtcService = this
|
val webRtcService = this
|
||||||
@ -33,6 +39,7 @@ class WebRtcService(
|
|||||||
onIceCandidate(webRtcService::sendIceCandidate)
|
onIceCandidate(webRtcService::sendIceCandidate)
|
||||||
initialize()
|
initialize()
|
||||||
}
|
}
|
||||||
|
dataChannelHandlers.forEach { rtcPeerConnection.onDataChannelData(it) }
|
||||||
return rtcPeerConnection
|
return rtcPeerConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package com.example.tvcontroller.ui.views
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.example.tvcontroller.data.RemoteCommand
|
||||||
import com.example.tvcontroller.services.BluetoothService
|
import com.example.tvcontroller.services.BluetoothService
|
||||||
import com.example.tvcontroller.services.ControllerService
|
import com.example.tvcontroller.services.ControllerService
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -12,8 +13,10 @@ import org.json.JSONObject
|
|||||||
class RemoteViewModel(
|
class RemoteViewModel(
|
||||||
private val controllerService: ControllerService
|
private val controllerService: ControllerService
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val commands = mutableMapOf<String, RemoteCommand>()
|
||||||
|
|
||||||
fun sendCommand(command: String) {
|
fun sendCommand(commandType: String) {
|
||||||
|
val command = commands[commandType]?: return
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
controllerService.sendCommand(command)
|
controllerService.sendCommand(command)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user