백엔드 임시 SFU 서버 + socket 서버가 완성됐다.
node.js와 socket.io와 mediasoup 라이브러리를 사용해 구현하셨다.
이와 소통하는 front-end client application을 Kotlin Jetpack Compose 형식으로 구현할 예정이다.
이번 포스팅은 socket 서버와 connect하고 joinroom까지 진행하는 코드이다.
테스트용으로 만들고 있기에 구조는 아래와 같다.
socket-io-client = { group = "io.socket", name = "socket.io-client", version.ref = "socketIoClient" }
아직 완성되지 않은 함수, rtpCapabilities를 받는 부분 수정 필요
import androidx.lifecycle.ViewModel
import io.socket.client.Ack
import io.socket.client.IO
import io.socket.client.Socket
import java.net.URISyntaxException
class VoiceCallViewModel : ViewModel() {
private var socket: Socket? = null // 클라이언트 소켓 인스턴스 생성
// 소켓 서버 초기화
init {
try {
// "http://your_server_address:port"를 실제 서버 주소로 변경해야 합니다.
socket = IO.socket("http://your_server_address:port")
} catch (e: URISyntaxException) {
e.printStackTrace() // 주소 형식 오류 발생 시 예외 처리
}
}
// 서버에 연결한 뒤, 정해진 방에 join
fun connectToServerAndJoinRoom(roomName: String, userName: String, isHost: Boolean) {
// 소켓 서버와 연결
socket?.connect()
socket?.on(Socket.EVENT_CONNECT) {
println("Connected to server")
// 연결 성공 후 바로 방에 참여
joinRoom(roomName, userName, isHost)
}
}
private fun joinRoom(roomName: String, userName: String, isHost: Boolean) {
// 클라이언트가 서버로 'joinRoon'이벤트를 보내고, 콜백 함수를 통해 응답을 처리한다
socket?.emit("joinRoom", roomName, userName, isHost, Ack { args ->
// args이 비어 있지 않고 첫 번째 인자가 Map 형태일 때
if (args.isNotEmpty() && args[0] is Map<*, *>) {
val response = args[0] as Map<*, *>
val rtpCapabilities = response["rtpCapabilities"]
// 서버로부터 { rtpCapabilities } 객체를 받았다면 방에 접속 성공!
if (rtpCapabilities != null) {
println("Successfully joined room: $roomName with RTP Capabilities: $rtpCapabilities")
} else {
println("Failed to join room: $roomName")
}
} else {
println("Unexpected response format from server")
}
})
}
// 소켓 서버와의 연결 끊기
override fun onCleared() {
super.onCleared()
socket?.disconnect()
}
}
roomName, userName, isHost, callback 함수를 인자로 받는다.
callback 함수를 마지막 인자로 받을 때 callback 함수임을 Ack 형태로 명시해줘야 한다.
(관련된 오류 및 공부한 내용 내일 중으로 아래에 추가 정리)
현재 back에서 callback을 통해 클라이언트가 성공적으로 방에 참여하면 {rtpCapabiolities} 객체의 데이터를 전달한다.
이는 java 객체의 형태로 지금 함수에 적힌 Map 배열과 맞지 않아서 수정해야 한다.
(rtpCapabilities의 데이터 형태를 잘 몰라서 배열로 넣었다. 나중에 수정하여 다시 작성할 것)
만약 if 조건문이 true면 성공적으로 연결되었다는 Log가 뜬다. 현재 args가 Map 형태로 오지 않아서 애초에 오류가 났다. 수정이 급하다.
ui/ux 구현 화면
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun VoiceCallScreen(voiceCallViewModel: VoiceCallViewModel = viewModel()) {
var roomName by remember { mutableStateOf("") }
var userName by remember { mutableStateOf("") }
Column {
TextField(value = roomName,
onValueChange = { roomName = it },
label = { Text("Room Name") })
TextField(value = userName,
onValueChange = { userName = it },
label = { Text("User Name") })
Button(onClick = {
// 서버 연결 및 방 참여를 한 번에 처리
voiceCallViewModel.connectToServerAndJoinRoom(roomName, userName, isHost = true)
}) {
Text(text = "Connect and Join Room")
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Surface {
VoiceCallScreen()
}
}
}
}
}
~~아직 작성 중이며 추후 전체 코드와 함께 다시 정리하여 글을 올릴 계획이다. ~~
=> kotlin에서 mediasoup client library를 적용하려면 native application이기 때문에 C언어를 빌드해 처리하는 과정이 필요했다. 이 과정에서 C언어에 대한 학습이 따로 필요해 사용하기 어렵다 판단하였고 시간 제약 상 openvidu 커스텀으로 노선을 바꾸기로 결정했다.