You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin端LiveKit RoomDelegateProtocol代理方法未触发求助

Kotlin Multiplatform iOS端LiveKit RoomDelegateProtocol代理方法未触发问题

问题描述

在Kotlin Multiplatform项目的iOS实现中,我尝试实现LiveKit的RoomDelegateProtocol,但所有代理方法都没有被调用。相同逻辑在Swift端直接实现时完全正常,且已通过iOS日志确认代理实例成功添加到Room对象中,尝试多种方案均无效,寻求解决思路。

相关代码如下:

@OptIn(ExperimentalForeignApi::class)
class IosRoom() : RoomWrapper {

    private val scope = CoroutineScope(
        SupervisorJob() + Dispatchers.Main + CoroutineName("IosRoomCoroutine")
    )

    private val delegate = IosRoomDelegate()


    private val room: Room = Room().apply {
        println("will add the room delegate")
        addDelegate(delegate)
    }


    override val localParticipant: LocalParticipantWrapper
        get() = IosLocalParticipant(
            liveKitLocalParticipant = room.localParticipant(),
            scope = scope,
        )

    override val remoteParticipants = MutableSharedFlow<List<RemoteParticipantWrapper>>()


    override val activeSpeakers: Flow<List<ParticipantWrapper>> = flow {
        // 预留实现
    }

    override suspend fun connect(url: String, token: String) {
          room.connectWithUrl(
            url = url,
            token = token,
            connectOptions = null,
            roomOptions = null,
            completionHandler = {},
        )
    }

    override fun disconnect() {
        room.disconnectWithCompletionHandler(completionHandler = {})
    }

    fun updateRemoteParticipants() {
        // 预留实现
    }
}


@OptIn(ExperimentalForeignApi::class)
class IosRoomDelegate : NSObject(), RoomDelegateProtocol {

    override fun room(room: Room, participantDidConnect: RemoteParticipant) {
        println("remote participantDidConnect called")
    }

    override fun room(room: Room, participant: Participant, didUpdatePermissions: ParticipantPermissions) {
        println("didUpdatePermissions called")
    }


    override fun room(room: Room, localParticipant: LocalParticipant, didPublishTrack: LocalTrackPublication) {
        println("didPublishTrack called")
    }


    override fun room(room: Room, participant: Participant, didUpdateConnectionQuality: ConnectionQuality) {
        println("connection quality updated")
    }

    override fun roomDidConnect(room: Room) {
        println("delegate room did connect")
    }

}

interface RoomWrapper {
    val localParticipant: LocalParticipantWrapper
    val remoteParticipants: Flow<List<RemoteParticipantWrapper>>
    val activeSpeakers: Flow<List<ParticipantWrapper>>
    suspend fun connect(url: String, token: String)
    fun disconnect()
}

class SessionViewModel(
    private val roomId: String,
    isCameraEnabled: Boolean,
    isMicrophoneEnabled: Boolean,
    val room: RoomWrapper,
    private val scope: CoroutineScope,
    private val sessionRepository: SessionRepository,
    private val state: MutableStateFlow<SessionState>,
) {
    private val localParticipant get() = state.value.localParticipantState.localParticipant

    init {
        scope.launch {
            state.update {
                it.copy(
                    roomId = roomId,
                )
            }
            getRoomToken(roomId)

            launch {
                connect(state.value.roomToken)
                toggleCamera(isCameraEnabled)
                toggleMic(isMicrophoneEnabled)
            }

            state.update {
                it.copy(
                    localParticipantState = it.localParticipantState.copy(
                        localParticipant = room.localParticipant,
                    )
                )
            }

        }

    }


    fun onEvent(event: SessionEvent) {
        when (event) {

            SessionEvent.OnDisconnect -> room.disconnect()

            SessionEvent.OnMicToggle -> scope.launch {
                toggleMic(!state.value.localParticipantState.isMicOn)
            }

            SessionEvent.OnSwitchCamera -> scope.launch {
                switchCamera()
            }

            SessionEvent.OnCameraToggle -> scope.launch {
                toggleCamera(!state.value.localParticipantState.isCameraOn)
            }


            is SessionEvent.OnConnect -> scope.launch {
                connect(
                    token = event.token,
                )
            }

            is SessionEvent.OnGetRoomToken -> scope.launch {
                getRoomToken(event.roomId)
            }


            is SessionEvent.OnChangeRoomId -> changeRoomId(event.roomId)

        }
    }

    suspend fun connect(token: String) {
        room.connect(
            url = "wss://livekit.cloudUrl",
            token = token,
        )
    }

    suspend fun getRoomToken(roomId: String) {
        sessionRepository.getRoomToken(roomId).onSuccess { token ->
            state.update {
                it.copy(
                    roomToken = token,
                )
            }
        }
    }

    suspend fun toggleCamera(enabled: Boolean) {
        localParticipant.setCameraEnabled(enabled)
        state.update {
            it.copy(
                localParticipantState = it.localParticipantState.copy(
                    isCameraOn = enabled,
                )
            )
        }
    }

    suspend fun toggleMic(enabled: Boolean) {
        localParticipant.setMicrophoneEnabled(enabled)
        state.update {
            it.copy(
                localParticipantState = it.localParticipantState.copy(
                    isMicOn = enabled,
                )
            )
        }
    }

    suspend fun switchCamera() {
        localParticipant.videoTracksPublications.first().firstOrNull()?.second?.switchCamera()
    }

    fun changeRoomId(roomId: String) {
        state.update {
            it.copy(
                roomId = roomId,
            )
        }
    }
}

排查与解决方案

1. 确保Delegate实例持有强引用

iOS的代理通常是弱引用,Kotlin/Native中如果Delegate对象被GC回收,会直接导致代理方法无法触发。当前代码中IosRoom的delegate是强引用,但需确认:

  • Room对象存活期间,delegate没有被意外释放
  • 可在IosRoomDelegate中添加init和finalize日志,确认对象生命周期是否符合预期

2. 强制匹配Objective-C方法选择器

Kotlin到Objective-C的桥接可能存在自动生成的方法签名与预期不一致的情况,可手动用@ObjCName注解指定选择器,确保和Swift端完全匹配:

@OptIn(ExperimentalForeignApi::class)
class IosRoomDelegate : NSObject(), RoomDelegateProtocol {
    @ObjCName("roomDidConnect:")
    override fun roomDidConnect(room: Room) {
        println("delegate room did connect")
    }

    @ObjCName("room:participantDidConnect:")
    override fun room(room: Room, participantDidConnect: RemoteParticipant) {
        println("remote participantDidConnect called")
    }

    // 其他代理方法同理添加@ObjCName注解
}

3. 检查Room对象生命周期

确认IosRoom中的room实例在连接后未被提前释放,可在IosRoom中添加日志跟踪room对象的创建与销毁时间,确保其生命周期覆盖整个会话过程。

4. 确保代理方法在主线程执行

LiveKit的代理方法通常在主线程触发,可在Delegate方法中强制切换到主线程处理:

override fun roomDidConnect(room: Room) {
    DispatchQueue.main.async {
        println("delegate room did connect on main thread")
        // 后续业务逻辑
    }
}

5. 验证LiveKit Kotlin绑定正确性

如果使用第三方提供的LiveKit Kotlin绑定,可直接在Kotlin中调用Objective-C的Delegate方法进行测试,或对比Swift端的Delegate实现,检查Protocol的方法签名、参数类型是否完全一致。

内容的提问来源于stack exchange,提问作者Mohamed Mabrouki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 11:10:05