[Kotlin] WebRTC를 이용한 안드로이드 어플리케이션 내 멀티 음성 통화 기능 구현 (2) - SFU 서버와 통신하는 client application 작성 : socket 서버와 연결 시도

괄괄이·2024년 4월 30일
0

WebRTC with Kotlin

목록 보기
4/4
post-thumbnail

백엔드 임시 SFU 서버 + socket 서버가 완성됐다.
node.js와 socket.io와 mediasoup 라이브러리를 사용해 구현하셨다.

이와 소통하는 front-end client application을 Kotlin Jetpack Compose 형식으로 구현할 예정이다.
이번 포스팅은 socket 서버와 connect하고 joinroom까지 진행하는 코드이다.

코드 구현 전, 준비 사항

테스트용으로 만들고 있기에 구조는 아래와 같다.

1. 구조 만들기

2. 의존성 추가

  • socket.io-client java library(2.1.0)를 사용한다.
  • project structure에서 검색하여 toml 및 필요한 build.gradle에 자동으로 등록할 수 있다.

    socket-io-client = { group = "io.socket", name = "socket.io-client", version.ref = "socketIoClient" }

3. AndroidManifest 설정

  • users-permission과 usersCleartextTraffic을 통해 어플리케이션에서 http url 인터넷 서버 주소 접속을 허용한다.

코드 구현

VoiceCallViewModel

아직 완성되지 않은 함수, 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()
    }
}
  • socket 인스턴스를 생성하고 socket 서버를 초기화한다.
  • 특정 ui button을 누르면 socket 서버와 연결을 시도한다.
  • socket 서버와 연결이 성공하면 joinRoom 함수를 통해 입력한 username과 roomname으로 생성된 방(backend mediasoup의 router)에 참여한다.

joinRoom (수정 및 공부 내용 더 정리 필요!!)

  • roomName, userName, isHost, callback 함수를 인자로 받는다.

  • callback 함수를 마지막 인자로 받을 때 callback 함수임을 Ack 형태로 명시해줘야 한다.
    (관련된 오류 및 공부한 내용 내일 중으로 아래에 추가 정리)

  • 현재 back에서 callback을 통해 클라이언트가 성공적으로 방에 참여하면 {rtpCapabiolities} 객체의 데이터를 전달한다.

  • 이는 java 객체의 형태로 지금 함수에 적힌 Map 배열과 맞지 않아서 수정해야 한다.
    (rtpCapabilities의 데이터 형태를 잘 몰라서 배열로 넣었다. 나중에 수정하여 다시 작성할 것)

  • 만약 if 조건문이 true면 성공적으로 연결되었다는 Log가 뜬다. 현재 args가 Map 형태로 오지 않아서 애초에 오류가 났다. 수정이 급하다.

rtpCapabilities? (더 공부해서 정리할 것!!)

VoiceCallScreen

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()
                }
            }
        }
    }
}
  • roomName 및 userName을 입력하고 button을 누르면 socket 서버와 연결한다.
  • 안 입력하면 오류가 난다...
  • 프로젝트 코드 구현이 다 완성되면 로그인한 userName과 선택된 roomName을 앱 내에서 자동으로 입력하여 사용자의 입력이 따로 필요없게 할 것이다. 지금은 기능 개발 중이라 임시 방편으로 만든 ui다.

~~아직 작성 중이며 추후 전체 코드와 함께 다시 정리하여 글을 올릴 계획이다. ~~

=> kotlin에서 mediasoup client library를 적용하려면 native application이기 때문에 C언어를 빌드해 처리하는 과정이 필요했다. 이 과정에서 C언어에 대한 학습이 따로 필요해 사용하기 어렵다 판단하였고 시간 제약 상 openvidu 커스텀으로 노선을 바꾸기로 결정했다.

0개의 댓글