正在显示
9 个修改的文件
包含
140 行增加
和
100 行删除
| @@ -3,6 +3,7 @@ package io.livekit.android | @@ -3,6 +3,7 @@ package io.livekit.android | ||
| 3 | import android.content.Context | 3 | import android.content.Context |
| 4 | import io.livekit.android.dagger.DaggerLiveKitComponent | 4 | import io.livekit.android.dagger.DaggerLiveKitComponent |
| 5 | import io.livekit.android.room.Room | 5 | import io.livekit.android.room.Room |
| 6 | +import io.livekit.android.room.RoomListener | ||
| 6 | import io.livekit.android.room.track.LocalAudioTrack | 7 | import io.livekit.android.room.track.LocalAudioTrack |
| 7 | import io.livekit.android.room.track.LocalVideoTrack | 8 | import io.livekit.android.room.track.LocalVideoTrack |
| 8 | import org.webrtc.EglBase | 9 | import org.webrtc.EglBase |
| @@ -21,7 +22,7 @@ class LiveKit { | @@ -21,7 +22,7 @@ class LiveKit { | ||
| 21 | url: String, | 22 | url: String, |
| 22 | token: String, | 23 | token: String, |
| 23 | options: ConnectOptions, | 24 | options: ConnectOptions, |
| 24 | - listener: Room.Listener? | 25 | + listener: RoomListener? |
| 25 | ): Room { | 26 | ): Room { |
| 26 | 27 | ||
| 27 | val component = DaggerLiveKitComponent | 28 | val component = DaggerLiveKitComponent |
| @@ -26,7 +26,6 @@ import kotlin.coroutines.suspendCoroutine | @@ -26,7 +26,6 @@ import kotlin.coroutines.suspendCoroutine | ||
| 26 | class RTCEngine | 26 | class RTCEngine |
| 27 | @Inject | 27 | @Inject |
| 28 | constructor( | 28 | constructor( |
| 29 | - private val appContext: Context, | ||
| 30 | val client: RTCClient, | 29 | val client: RTCClient, |
| 31 | pctFactory: PeerConnectionTransport.Factory, | 30 | pctFactory: PeerConnectionTransport.Factory, |
| 32 | @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher, | 31 | @Named(InjectionNames.DISPATCHER_IO) ioDispatcher: CoroutineDispatcher, |
| @@ -190,6 +189,7 @@ constructor( | @@ -190,6 +189,7 @@ constructor( | ||
| 190 | override fun onAnswer(sessionDescription: SessionDescription) { | 189 | override fun onAnswer(sessionDescription: SessionDescription) { |
| 191 | Timber.v { "received server answer: ${sessionDescription.type}, ${publisher.peerConnection.signalingState()}" } | 190 | Timber.v { "received server answer: ${sessionDescription.type}, ${publisher.peerConnection.signalingState()}" } |
| 192 | coroutineScope.launch { | 191 | coroutineScope.launch { |
| 192 | + Timber.i { sessionDescription.toString() } | ||
| 193 | when (val outcome = publisher.peerConnection.setRemoteDescription(sessionDescription)) { | 193 | when (val outcome = publisher.peerConnection.setRemoteDescription(sessionDescription)) { |
| 194 | is Either.Left -> { | 194 | is Either.Left -> { |
| 195 | if (!rtcConnected) { | 195 | if (!rtcConnected) { |
| 1 | package io.livekit.android.room | 1 | package io.livekit.android.room |
| 2 | 2 | ||
| 3 | import com.github.ajalt.timberkt.Timber | 3 | import com.github.ajalt.timberkt.Timber |
| 4 | -import com.vdurmont.semver4j.Semver | ||
| 5 | import dagger.assisted.Assisted | 4 | import dagger.assisted.Assisted |
| 6 | import dagger.assisted.AssistedFactory | 5 | import dagger.assisted.AssistedFactory |
| 7 | import dagger.assisted.AssistedInject | 6 | import dagger.assisted.AssistedInject |
| 8 | import io.livekit.android.ConnectOptions | 7 | import io.livekit.android.ConnectOptions |
| 9 | import io.livekit.android.room.participant.LocalParticipant | 8 | import io.livekit.android.room.participant.LocalParticipant |
| 10 | import io.livekit.android.room.participant.Participant | 9 | import io.livekit.android.room.participant.Participant |
| 10 | +import io.livekit.android.room.participant.ParticipantListener | ||
| 11 | import io.livekit.android.room.participant.RemoteParticipant | 11 | import io.livekit.android.room.participant.RemoteParticipant |
| 12 | import io.livekit.android.room.track.DataTrack | 12 | import io.livekit.android.room.track.DataTrack |
| 13 | import io.livekit.android.room.track.Track | 13 | import io.livekit.android.room.track.Track |
| @@ -27,7 +27,7 @@ constructor( | @@ -27,7 +27,7 @@ constructor( | ||
| 27 | @Assisted private val connectOptions: ConnectOptions, | 27 | @Assisted private val connectOptions: ConnectOptions, |
| 28 | private val engine: RTCEngine, | 28 | private val engine: RTCEngine, |
| 29 | private val eglBase: EglBase, | 29 | private val eglBase: EglBase, |
| 30 | -) : RTCEngine.Listener, RemoteParticipant.Listener { | 30 | +) : RTCEngine.Listener, ParticipantListener { |
| 31 | init { | 31 | init { |
| 32 | engine.listener = this | 32 | engine.listener = this |
| 33 | } | 33 | } |
| @@ -41,7 +41,7 @@ constructor( | @@ -41,7 +41,7 @@ constructor( | ||
| 41 | 41 | ||
| 42 | inline class Sid(val sid: String) | 42 | inline class Sid(val sid: String) |
| 43 | 43 | ||
| 44 | - var listener: Listener? = null | 44 | + var listener: RoomListener? = null |
| 45 | 45 | ||
| 46 | var sid: Sid? = null | 46 | var sid: Sid? = null |
| 47 | private set | 47 | private set |
| @@ -95,7 +95,7 @@ constructor( | @@ -95,7 +95,7 @@ constructor( | ||
| 95 | } else { | 95 | } else { |
| 96 | RemoteParticipant(sid, null) | 96 | RemoteParticipant(sid, null) |
| 97 | } | 97 | } |
| 98 | - participant.listener = this | 98 | + participant.internalListener = this |
| 99 | mutableRemoteParticipants[sid] = participant | 99 | mutableRemoteParticipants[sid] = participant |
| 100 | return participant | 100 | return participant |
| 101 | } | 101 | } |
| @@ -108,13 +108,15 @@ constructor( | @@ -108,13 +108,15 @@ constructor( | ||
| 108 | val speakerSid = speakerInfo.sid!! | 108 | val speakerSid = speakerInfo.sid!! |
| 109 | seenSids.add(speakerSid) | 109 | seenSids.add(speakerSid) |
| 110 | 110 | ||
| 111 | - if (speakerSid == localParticipant?.sid) { | 111 | + if (speakerSid == localParticipant.sid) { |
| 112 | localParticipant.audioLevel = speakerInfo.level | 112 | localParticipant.audioLevel = speakerInfo.level |
| 113 | + localParticipant.isSpeaking = true | ||
| 113 | speakers.add(localParticipant) | 114 | speakers.add(localParticipant) |
| 114 | } else { | 115 | } else { |
| 115 | val participant = remoteParticipants[speakerSid] | 116 | val participant = remoteParticipants[speakerSid] |
| 116 | if (participant != null) { | 117 | if (participant != null) { |
| 117 | participant.audioLevel = speakerInfo.level | 118 | participant.audioLevel = speakerInfo.level |
| 119 | + participant.isSpeaking = true | ||
| 118 | speakers.add(participant) | 120 | speakers.add(participant) |
| 119 | } | 121 | } |
| 120 | } | 122 | } |
| @@ -122,10 +124,14 @@ constructor( | @@ -122,10 +124,14 @@ constructor( | ||
| 122 | 124 | ||
| 123 | if (!seenSids.contains(localParticipant.sid)) { | 125 | if (!seenSids.contains(localParticipant.sid)) { |
| 124 | localParticipant.audioLevel = 0.0f | 126 | localParticipant.audioLevel = 0.0f |
| 127 | + localParticipant.isSpeaking = false | ||
| 125 | } | 128 | } |
| 126 | remoteParticipants.values | 129 | remoteParticipants.values |
| 127 | .filterNot { seenSids.contains(it.sid) } | 130 | .filterNot { seenSids.contains(it.sid) } |
| 128 | - .forEach { it.audioLevel = 0.0f } | 131 | + .forEach { |
| 132 | + it.audioLevel = 0.0f | ||
| 133 | + it.isSpeaking = false | ||
| 134 | + } | ||
| 129 | 135 | ||
| 130 | mutableActiveSpeakers.clear() | 136 | mutableActiveSpeakers.clear() |
| 131 | mutableActiveSpeakers.addAll(speakers) | 137 | mutableActiveSpeakers.addAll(speakers) |
| @@ -296,12 +302,13 @@ constructor( | @@ -296,12 +302,13 @@ constructor( | ||
| 296 | viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) | 302 | viewRenderer.setScalingType(RendererCommon.ScalingType.SCALE_ASPECT_FIT) |
| 297 | viewRenderer.setEnableHardwareScaler(false /* enabled */); | 303 | viewRenderer.setEnableHardwareScaler(false /* enabled */); |
| 298 | } | 304 | } |
| 305 | +} | ||
| 299 | 306 | ||
| 300 | - /** | 307 | +/** |
| 301 | * Room Listener, this class provides callbacks that clients should override. | 308 | * Room Listener, this class provides callbacks that clients should override. |
| 302 | * | 309 | * |
| 303 | */ | 310 | */ |
| 304 | - interface Listener { | 311 | +interface RoomListener { |
| 305 | /** | 312 | /** |
| 306 | * Disconnected from room | 313 | * Disconnected from room |
| 307 | */ | 314 | */ |
| @@ -337,7 +344,7 @@ constructor( | @@ -337,7 +344,7 @@ constructor( | ||
| 337 | * When RoomService.UpdateParticipantMetadata is called to change a participant's state, | 344 | * When RoomService.UpdateParticipantMetadata is called to change a participant's state, |
| 338 | * this event will be fired for all clients in the room. | 345 | * this event will be fired for all clients in the room. |
| 339 | */ | 346 | */ |
| 340 | - fun onMetadataChanged(Participant: Participant, prevMetadata: String?, room: Room) {} | 347 | + fun onMetadataChanged(participant: Participant, prevMetadata: String?, room: Room) {} |
| 341 | 348 | ||
| 342 | /** | 349 | /** |
| 343 | * When a new track is published to room after the local participant has joined. It will | 350 | * When a new track is published to room after the local participant has joined. It will |
| @@ -371,7 +378,6 @@ constructor( | @@ -371,7 +378,6 @@ constructor( | ||
| 371 | * Message received over a [DataTrack] | 378 | * Message received over a [DataTrack] |
| 372 | */ | 379 | */ |
| 373 | fun onDataReceived(data: ByteBuffer, dataTrack: DataTrack, participant: RemoteParticipant, room: Room) {} | 380 | fun onDataReceived(data: ByteBuffer, dataTrack: DataTrack, participant: RemoteParticipant, room: Room) {} |
| 374 | - } | ||
| 375 | } | 381 | } |
| 376 | 382 | ||
| 377 | sealed class RoomException(message: String? = null, cause: Throwable? = null) : | 383 | sealed class RoomException(message: String? = null, cause: Throwable? = null) : |
| @@ -15,15 +15,9 @@ class LocalParticipant(info: LivekitModels.ParticipantInfo, private val engine: | @@ -15,15 +15,9 @@ class LocalParticipant(info: LivekitModels.ParticipantInfo, private val engine: | ||
| 15 | updateFromInfo(info) | 15 | updateFromInfo(info) |
| 16 | } | 16 | } |
| 17 | 17 | ||
| 18 | - val localTrackPublications | 18 | + private val localTrackPublications |
| 19 | get() = tracks.values.toList() | 19 | get() = tracks.values.toList() |
| 20 | 20 | ||
| 21 | - var listener: Listener? = null | ||
| 22 | - set(v) { | ||
| 23 | - field = v | ||
| 24 | - participantListener = v | ||
| 25 | - } | ||
| 26 | - | ||
| 27 | suspend fun publishAudioTrack( | 21 | suspend fun publishAudioTrack( |
| 28 | track: LocalAudioTrack, | 22 | track: LocalAudioTrack, |
| 29 | publishListener: PublishListener? = null | 23 | publishListener: PublishListener? = null |
| @@ -2,6 +2,7 @@ package io.livekit.android.room.participant | @@ -2,6 +2,7 @@ package io.livekit.android.room.participant | ||
| 2 | 2 | ||
| 3 | import io.livekit.android.room.track.* | 3 | import io.livekit.android.room.track.* |
| 4 | import livekit.LivekitModels | 4 | import livekit.LivekitModels |
| 5 | +import java.nio.ByteBuffer | ||
| 5 | 6 | ||
| 6 | open class Participant(var sid: String, identity: String? = null) { | 7 | open class Participant(var sid: String, identity: String? = null) { |
| 7 | var participantInfo: LivekitModels.ParticipantInfo? = null | 8 | var participantInfo: LivekitModels.ParticipantInfo? = null |
| @@ -10,8 +11,27 @@ open class Participant(var sid: String, identity: String? = null) { | @@ -10,8 +11,27 @@ open class Participant(var sid: String, identity: String? = null) { | ||
| 10 | internal set | 11 | internal set |
| 11 | var audioLevel: Float = 0f | 12 | var audioLevel: Float = 0f |
| 12 | internal set | 13 | internal set |
| 14 | + var isSpeaking: Boolean = false | ||
| 15 | + internal set(v) { | ||
| 16 | + val changed = v == field | ||
| 17 | + field = v | ||
| 18 | + if (changed) { | ||
| 19 | + listener?.onSpeakingChanged(this) | ||
| 20 | + internalListener?.onSpeakingChanged(this) | ||
| 21 | + } | ||
| 22 | + } | ||
| 13 | var metadata: String? = null | 23 | var metadata: String? = null |
| 14 | - var participantListener: Listener? = null | 24 | + |
| 25 | + /** | ||
| 26 | + * Listener for when participant properties change | ||
| 27 | + */ | ||
| 28 | + var listener: ParticipantListener? = null | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * @suppress | ||
| 32 | + */ | ||
| 33 | + internal var internalListener: ParticipantListener? = null | ||
| 34 | + | ||
| 15 | val hasInfo | 35 | val hasInfo |
| 16 | get() = participantInfo != null | 36 | get() = participantInfo != null |
| 17 | 37 | ||
| @@ -49,7 +69,8 @@ open class Participant(var sid: String, identity: String? = null) { | @@ -49,7 +69,8 @@ open class Participant(var sid: String, identity: String? = null) { | ||
| 49 | metadata = info.metadata | 69 | metadata = info.metadata |
| 50 | 70 | ||
| 51 | if (prevMetadata != metadata) { | 71 | if (prevMetadata != metadata) { |
| 52 | - participantListener?.onMetadataChanged(this, prevMetadata) | 72 | + listener?.onMetadataChanged(this, prevMetadata) |
| 73 | + internalListener?.onMetadataChanged(this, prevMetadata) | ||
| 53 | } | 74 | } |
| 54 | } | 75 | } |
| 55 | 76 | ||
| @@ -67,8 +88,48 @@ open class Participant(var sid: String, identity: String? = null) { | @@ -67,8 +88,48 @@ open class Participant(var sid: String, identity: String? = null) { | ||
| 67 | override fun hashCode(): Int { | 88 | override fun hashCode(): Int { |
| 68 | return sid.hashCode() | 89 | return sid.hashCode() |
| 69 | } | 90 | } |
| 91 | +} | ||
| 92 | + | ||
| 70 | 93 | ||
| 71 | - interface Listener { | 94 | +interface ParticipantListener { |
| 95 | + /** | ||
| 96 | + * When a participant's metadata is updated, fired for all participants | ||
| 97 | + */ | ||
| 72 | fun onMetadataChanged(participant: Participant, prevMetadata: String?) {} | 98 | fun onMetadataChanged(participant: Participant, prevMetadata: String?) {} |
| 99 | + | ||
| 100 | + /** | ||
| 101 | + * Fired when the current participant's isSpeaking property changes. (including LocalParticipant) | ||
| 102 | + */ | ||
| 103 | + fun onSpeakingChanged(participant: Participant) {} | ||
| 104 | + | ||
| 105 | + fun onTrackPublished(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 106 | + fun onTrackUnpublished(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 107 | + | ||
| 108 | + fun onEnable(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 109 | + fun onDisable(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 110 | + | ||
| 111 | + fun onTrackSubscribed(track: Track, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 112 | + fun onTrackSubscriptionFailed( | ||
| 113 | + sid: String, | ||
| 114 | + exception: Exception, | ||
| 115 | + participant: RemoteParticipant | ||
| 116 | + ) { | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + fun onTrackUnsubscribed( | ||
| 120 | + track: Track, | ||
| 121 | + publication: TrackPublication, | ||
| 122 | + participant: RemoteParticipant | ||
| 123 | + ) { | ||
| 73 | } | 124 | } |
| 125 | + | ||
| 126 | + fun onDataReceived( | ||
| 127 | + data: ByteBuffer, | ||
| 128 | + dataTrack: DataTrack, | ||
| 129 | + participant: RemoteParticipant | ||
| 130 | + ) { | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + fun switchedOffVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 134 | + fun switchedOnVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 74 | } | 135 | } |
| @@ -23,12 +23,6 @@ class RemoteParticipant( | @@ -23,12 +23,6 @@ class RemoteParticipant( | ||
| 23 | updateFromInfo(info) | 23 | updateFromInfo(info) |
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | - var listener: Listener? = null | ||
| 27 | - set(v) { | ||
| 28 | - field = v | ||
| 29 | - participantListener = v | ||
| 30 | - } | ||
| 31 | - | ||
| 32 | private val coroutineScope = CloseableCoroutineScope(SupervisorJob()) | 26 | private val coroutineScope = CloseableCoroutineScope(SupervisorJob()) |
| 33 | 27 | ||
| 34 | fun getTrackPublication(sid: String): TrackPublication? = tracks[sid] | 28 | fun getTrackPublication(sid: String): TrackPublication? = tracks[sid] |
| @@ -61,6 +55,7 @@ class RemoteParticipant( | @@ -61,6 +55,7 @@ class RemoteParticipant( | ||
| 61 | 55 | ||
| 62 | if (hadInfo) { | 56 | if (hadInfo) { |
| 63 | for (publication in newTrackPublications.values) { | 57 | for (publication in newTrackPublications.values) { |
| 58 | + internalListener?.onTrackPublished(publication, this) | ||
| 64 | listener?.onTrackPublished(publication, this) | 59 | listener?.onTrackPublished(publication, this) |
| 65 | } | 60 | } |
| 66 | } | 61 | } |
| @@ -89,6 +84,7 @@ class RemoteParticipant( | @@ -89,6 +84,7 @@ class RemoteParticipant( | ||
| 89 | val exception = TrackException.InvalidTrackStateException(message) | 84 | val exception = TrackException.InvalidTrackStateException(message) |
| 90 | Timber.e { "remote participant ${this.sid} --- $message" } | 85 | Timber.e { "remote participant ${this.sid} --- $message" } |
| 91 | 86 | ||
| 87 | + internalListener?.onTrackSubscriptionFailed(sid, exception, this) | ||
| 92 | listener?.onTrackSubscriptionFailed(sid, exception, this) | 88 | listener?.onTrackSubscriptionFailed(sid, exception, this) |
| 93 | } else { | 89 | } else { |
| 94 | coroutineScope.launch { | 90 | coroutineScope.launch { |
| @@ -106,6 +102,7 @@ class RemoteParticipant( | @@ -106,6 +102,7 @@ class RemoteParticipant( | ||
| 106 | 102 | ||
| 107 | // TODO: how does mediatrack send ended event? | 103 | // TODO: how does mediatrack send ended event? |
| 108 | 104 | ||
| 105 | + internalListener?.onTrackSubscribed(track, publication, this) | ||
| 109 | listener?.onTrackSubscribed(track, publication, this) | 106 | listener?.onTrackSubscribed(track, publication, this) |
| 110 | } | 107 | } |
| 111 | 108 | ||
| @@ -125,6 +122,7 @@ class RemoteParticipant( | @@ -125,6 +122,7 @@ class RemoteParticipant( | ||
| 125 | publication = TrackPublication(info = trackInfo) | 122 | publication = TrackPublication(info = trackInfo) |
| 126 | addTrackPublication(publication) | 123 | addTrackPublication(publication) |
| 127 | if (hasInfo) { | 124 | if (hasInfo) { |
| 125 | + internalListener?.onTrackPublished(publication, this) | ||
| 128 | listener?.onTrackPublished(publication, this) | 126 | listener?.onTrackPublished(publication, this) |
| 129 | } | 127 | } |
| 130 | } | 128 | } |
| @@ -137,15 +135,18 @@ class RemoteParticipant( | @@ -137,15 +135,18 @@ class RemoteParticipant( | ||
| 137 | val newState = dataChannel.state() | 135 | val newState = dataChannel.state() |
| 138 | if (newState == DataChannel.State.CLOSED) { | 136 | if (newState == DataChannel.State.CLOSED) { |
| 139 | publication.track = null | 137 | publication.track = null |
| 138 | + internalListener?.onTrackUnsubscribed(track, publication, this@RemoteParticipant) | ||
| 140 | listener?.onTrackUnsubscribed(track, publication, this@RemoteParticipant) | 139 | listener?.onTrackUnsubscribed(track, publication, this@RemoteParticipant) |
| 141 | } | 140 | } |
| 142 | } | 141 | } |
| 143 | 142 | ||
| 144 | override fun onMessage(buffer: DataChannel.Buffer) { | 143 | override fun onMessage(buffer: DataChannel.Buffer) { |
| 144 | + internalListener?.onDataReceived(buffer.data, track, this@RemoteParticipant) | ||
| 145 | listener?.onDataReceived(buffer.data, track, this@RemoteParticipant) | 145 | listener?.onDataReceived(buffer.data, track, this@RemoteParticipant) |
| 146 | } | 146 | } |
| 147 | }) | 147 | }) |
| 148 | - listener?.onTrackSubscribed(track, publication, participant = this) | 148 | + internalListener?.onTrackSubscribed(track, publication, participant = this) |
| 149 | + listener?.onTrackSubscribed(track, publication, this) | ||
| 149 | } | 150 | } |
| 150 | 151 | ||
| 151 | fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) { | 152 | fun unpublishTrack(trackSid: String, sendUnpublish: Boolean = false) { |
| @@ -160,9 +161,11 @@ class RemoteParticipant( | @@ -160,9 +161,11 @@ class RemoteParticipant( | ||
| 160 | val track = publication.track | 161 | val track = publication.track |
| 161 | if (track != null) { | 162 | if (track != null) { |
| 162 | track.stop() | 163 | track.stop() |
| 164 | + internalListener?.onTrackUnsubscribed(track, publication, this) | ||
| 163 | listener?.onTrackUnsubscribed(track, publication, this) | 165 | listener?.onTrackUnsubscribed(track, publication, this) |
| 164 | } | 166 | } |
| 165 | if (sendUnpublish) { | 167 | if (sendUnpublish) { |
| 168 | + internalListener?.onTrackUnpublished(publication, this) | ||
| 166 | listener?.onTrackUnpublished(publication, this) | 169 | listener?.onTrackUnpublished(publication, this) |
| 167 | } | 170 | } |
| 168 | } | 171 | } |
| @@ -171,38 +174,4 @@ class RemoteParticipant( | @@ -171,38 +174,4 @@ class RemoteParticipant( | ||
| 171 | private const val KIND_AUDIO = "audio" | 174 | private const val KIND_AUDIO = "audio" |
| 172 | private const val KIND_VIDEO = "video" | 175 | private const val KIND_VIDEO = "video" |
| 173 | } | 176 | } |
| 174 | - | ||
| 175 | - interface Listener: Participant.Listener { | ||
| 176 | - fun onTrackPublished(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 177 | - fun onTrackUnpublished(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 178 | - | ||
| 179 | - fun onEnable(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 180 | - fun onDisable(publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 181 | - | ||
| 182 | - fun onTrackSubscribed(track: Track, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 183 | - fun onTrackSubscriptionFailed( | ||
| 184 | - sid: String, | ||
| 185 | - exception: Exception, | ||
| 186 | - participant: RemoteParticipant | ||
| 187 | - ) { | ||
| 188 | - } | ||
| 189 | - | ||
| 190 | - fun onTrackUnsubscribed( | ||
| 191 | - track: Track, | ||
| 192 | - publication: TrackPublication, | ||
| 193 | - participant: RemoteParticipant | ||
| 194 | - ) { | ||
| 195 | - } | ||
| 196 | - | ||
| 197 | - fun onDataReceived( | ||
| 198 | - data: ByteBuffer, | ||
| 199 | - dataTrack: DataTrack, | ||
| 200 | - participant: RemoteParticipant | ||
| 201 | - ) { | ||
| 202 | - } | ||
| 203 | - | ||
| 204 | - fun switchedOffVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 205 | - fun switchedOnVideo(track: VideoTrack, publication: TrackPublication, participant: RemoteParticipant) {} | ||
| 206 | - } | ||
| 207 | - | ||
| 208 | } | 177 | } |
| @@ -41,7 +41,6 @@ class CallActivity : AppCompatActivity() { | @@ -41,7 +41,6 @@ class CallActivity : AppCompatActivity() { | ||
| 41 | viewModel.remoteParticipants | 41 | viewModel.remoteParticipants |
| 42 | ) { room, participants -> room to participants } | 42 | ) { room, participants -> room to participants } |
| 43 | .observe(this) { | 43 | .observe(this) { |
| 44 | - | ||
| 45 | tabLayoutMediator?.detach() | 44 | tabLayoutMediator?.detach() |
| 46 | tabLayoutMediator = null | 45 | tabLayoutMediator = null |
| 47 | 46 | ||
| @@ -58,14 +57,11 @@ class CallActivity : AppCompatActivity() { | @@ -58,14 +57,11 @@ class CallActivity : AppCompatActivity() { | ||
| 58 | 57 | ||
| 59 | viewModel.room.observe(this) { room -> | 58 | viewModel.room.observe(this) { room -> |
| 60 | room.initVideoRenderer(binding.pipVideoView) | 59 | room.initVideoRenderer(binding.pipVideoView) |
| 61 | - val localParticipant = room.localParticipant | ||
| 62 | - if (localParticipant != null) { | ||
| 63 | - val videoTrack = localParticipant.videoTracks.values | 60 | + val videoTrack = room.localParticipant.videoTracks.values |
| 64 | .firstOrNull() | 61 | .firstOrNull() |
| 65 | ?.track as? LocalVideoTrack | 62 | ?.track as? LocalVideoTrack |
| 66 | videoTrack?.addRenderer(binding.pipVideoView) | 63 | videoTrack?.addRenderer(binding.pipVideoView) |
| 67 | } | 64 | } |
| 68 | - } | ||
| 69 | val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager | 65 | val audioManager = getSystemService(AUDIO_SERVICE) as AudioManager |
| 70 | with(audioManager) { | 66 | with(audioManager) { |
| 71 | isSpeakerphoneOn = true | 67 | isSpeakerphoneOn = true |
| @@ -5,9 +5,11 @@ import androidx.lifecycle.AndroidViewModel | @@ -5,9 +5,11 @@ import androidx.lifecycle.AndroidViewModel | ||
| 5 | import androidx.lifecycle.LiveData | 5 | import androidx.lifecycle.LiveData |
| 6 | import androidx.lifecycle.MutableLiveData | 6 | import androidx.lifecycle.MutableLiveData |
| 7 | import androidx.lifecycle.viewModelScope | 7 | import androidx.lifecycle.viewModelScope |
| 8 | +import com.github.ajalt.timberkt.Timber | ||
| 8 | import io.livekit.android.ConnectOptions | 9 | import io.livekit.android.ConnectOptions |
| 9 | import io.livekit.android.LiveKit | 10 | import io.livekit.android.LiveKit |
| 10 | import io.livekit.android.room.Room | 11 | import io.livekit.android.room.Room |
| 12 | +import io.livekit.android.room.RoomListener | ||
| 11 | import io.livekit.android.room.participant.Participant | 13 | import io.livekit.android.room.participant.Participant |
| 12 | import io.livekit.android.room.participant.RemoteParticipant | 14 | import io.livekit.android.room.participant.RemoteParticipant |
| 13 | import kotlinx.coroutines.launch | 15 | import kotlinx.coroutines.launch |
| @@ -16,24 +18,52 @@ class CallViewModel( | @@ -16,24 +18,52 @@ class CallViewModel( | ||
| 16 | val url: String, | 18 | val url: String, |
| 17 | val token: String, | 19 | val token: String, |
| 18 | application: Application | 20 | application: Application |
| 19 | -) : AndroidViewModel(application) { | ||
| 20 | - | ||
| 21 | - | 21 | +) : AndroidViewModel(application), RoomListener { |
| 22 | private val mutableRoom = MutableLiveData<Room>() | 22 | private val mutableRoom = MutableLiveData<Room>() |
| 23 | val room: LiveData<Room> = mutableRoom | 23 | val room: LiveData<Room> = mutableRoom |
| 24 | private val mutableRemoteParticipants = MutableLiveData<List<RemoteParticipant>>() | 24 | private val mutableRemoteParticipants = MutableLiveData<List<RemoteParticipant>>() |
| 25 | val remoteParticipants: LiveData<List<RemoteParticipant>> = mutableRemoteParticipants | 25 | val remoteParticipants: LiveData<List<RemoteParticipant>> = mutableRemoteParticipants |
| 26 | + private val participants = HashMap<String, LiveData<RemoteParticipant>>() | ||
| 26 | 27 | ||
| 27 | init { | 28 | init { |
| 28 | - | ||
| 29 | viewModelScope.launch { | 29 | viewModelScope.launch { |
| 30 | - | ||
| 31 | - mutableRoom.value = LiveKit.connect( | 30 | + val room = LiveKit.connect( |
| 32 | application, | 31 | application, |
| 33 | url, | 32 | url, |
| 34 | token, | 33 | token, |
| 35 | ConnectOptions(false), | 34 | ConnectOptions(false), |
| 36 | - object : Room.Listener { | 35 | + this@CallViewModel |
| 36 | + ) | ||
| 37 | + updateParticipants(room) | ||
| 38 | + for (p in room.remoteParticipants.values) { | ||
| 39 | + if (p.identity == null) { | ||
| 40 | + continue | ||
| 41 | + } | ||
| 42 | + participants[p.identity!!] = MutableLiveData(p) | ||
| 43 | + } | ||
| 44 | + mutableRoom.value = room | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + private fun updateParticipants(room: Room) { | ||
| 49 | + mutableRemoteParticipants.postValue( | ||
| 50 | + room.remoteParticipants | ||
| 51 | + .keys | ||
| 52 | + .sortedBy { it } | ||
| 53 | + .mapNotNull { room.remoteParticipants[it] } | ||
| 54 | + ) | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + fun getParticipantObservable(identity: String): LiveData<RemoteParticipant>? { | ||
| 58 | + return participants[identity] | ||
| 59 | + } | ||
| 60 | + | ||
| 61 | + override fun onCleared() { | ||
| 62 | + super.onCleared() | ||
| 63 | + mutableRoom.value?.disconnect() | ||
| 64 | + mutableRoom.value = null | ||
| 65 | + } | ||
| 66 | + | ||
| 37 | override fun onDisconnect(room: Room, error: Exception?) { | 67 | override fun onDisconnect(room: Room, error: Exception?) { |
| 38 | } | 68 | } |
| 39 | 69 | ||
| @@ -42,6 +72,8 @@ class CallViewModel( | @@ -42,6 +72,8 @@ class CallViewModel( | ||
| 42 | participant: RemoteParticipant | 72 | participant: RemoteParticipant |
| 43 | ) { | 73 | ) { |
| 44 | updateParticipants(room) | 74 | updateParticipants(room) |
| 75 | + participants[participant.identity!!] = MutableLiveData(participant) | ||
| 76 | + | ||
| 45 | } | 77 | } |
| 46 | 78 | ||
| 47 | override fun onParticipantDisconnected( | 79 | override fun onParticipantDisconnected( |
| @@ -49,6 +81,7 @@ class CallViewModel( | @@ -49,6 +81,7 @@ class CallViewModel( | ||
| 49 | participant: RemoteParticipant | 81 | participant: RemoteParticipant |
| 50 | ) { | 82 | ) { |
| 51 | updateParticipants(room) | 83 | updateParticipants(room) |
| 84 | + participants.remove(participant.identity!!) | ||
| 52 | } | 85 | } |
| 53 | 86 | ||
| 54 | override fun onFailedToConnect(room: Room, error: Exception) { | 87 | override fun onFailedToConnect(room: Room, error: Exception) { |
| @@ -57,27 +90,7 @@ class CallViewModel( | @@ -57,27 +90,7 @@ class CallViewModel( | ||
| 57 | override fun onActiveSpeakersChanged(speakers: List<Participant>, room: Room) { | 90 | override fun onActiveSpeakersChanged(speakers: List<Participant>, room: Room) { |
| 58 | } | 91 | } |
| 59 | 92 | ||
| 60 | - override fun onMetadataChanged(Participant: Participant, prevMetadata: String?, room: Room) { | ||
| 61 | - | ||
| 62 | - } | ||
| 63 | - } | ||
| 64 | - ) | ||
| 65 | - updateParticipants(mutableRoom.value!!) | ||
| 66 | - } | ||
| 67 | - } | ||
| 68 | - | ||
| 69 | - fun updateParticipants(room: Room) { | ||
| 70 | - mutableRemoteParticipants.postValue( | ||
| 71 | - room.remoteParticipants | ||
| 72 | - .keys | ||
| 73 | - .sortedBy { it } | ||
| 74 | - .mapNotNull { room.remoteParticipants[it] } | ||
| 75 | - ) | ||
| 76 | - } | ||
| 77 | - | ||
| 78 | - override fun onCleared() { | ||
| 79 | - super.onCleared() | ||
| 80 | - mutableRoom.value?.disconnect() | ||
| 81 | - mutableRoom.value = null | 93 | + override fun onMetadataChanged(participant: Participant, prevMetadata: String?, room: Room) { |
| 94 | + Timber.i { "Participant metadata changed: ${participant.identity}" } | ||
| 82 | } | 95 | } |
| 83 | } | 96 | } |
| 1 | package io.livekit.android.sample | 1 | package io.livekit.android.sample |
| 2 | 2 | ||
| 3 | -import android.provider.MediaStore | ||
| 4 | import android.view.View | 3 | import android.view.View |
| 5 | import com.github.ajalt.timberkt.Timber | 4 | import com.github.ajalt.timberkt.Timber |
| 6 | import com.xwray.groupie.viewbinding.BindableItem | 5 | import com.xwray.groupie.viewbinding.BindableItem |
| 7 | import com.xwray.groupie.viewbinding.GroupieViewHolder | 6 | import com.xwray.groupie.viewbinding.GroupieViewHolder |
| 8 | import io.livekit.android.room.Room | 7 | import io.livekit.android.room.Room |
| 8 | +import io.livekit.android.room.participant.ParticipantListener | ||
| 9 | import io.livekit.android.room.participant.RemoteParticipant | 9 | import io.livekit.android.room.participant.RemoteParticipant |
| 10 | import io.livekit.android.room.track.* | 10 | import io.livekit.android.room.track.* |
| 11 | import io.livekit.android.sample.databinding.ParticipantItemBinding | 11 | import io.livekit.android.sample.databinding.ParticipantItemBinding |
| @@ -27,7 +27,7 @@ class ParticipantItem( | @@ -27,7 +27,7 @@ class ParticipantItem( | ||
| 27 | override fun bind(viewBinding: ParticipantItemBinding, position: Int) { | 27 | override fun bind(viewBinding: ParticipantItemBinding, position: Int) { |
| 28 | viewBinding.run { | 28 | viewBinding.run { |
| 29 | 29 | ||
| 30 | - remoteParticipant.listener = object : RemoteParticipant.Listener { | 30 | + remoteParticipant.listener = object : ParticipantListener { |
| 31 | override fun onTrackSubscribed( | 31 | override fun onTrackSubscribed( |
| 32 | track: Track, | 32 | track: Track, |
| 33 | publication: TrackPublication, | 33 | publication: TrackPublication, |
| @@ -51,7 +51,7 @@ class ParticipantItem( | @@ -51,7 +51,7 @@ class ParticipantItem( | ||
| 51 | .firstOrNull()?.track as? VideoTrack | 51 | .firstOrNull()?.track as? VideoTrack |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | - private fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) { | 54 | + internal fun setupVideoIfNeeded(videoTrack: VideoTrack, viewBinding: ParticipantItemBinding) { |
| 55 | if (videoBound) { | 55 | if (videoBound) { |
| 56 | return | 56 | return |
| 57 | } | 57 | } |
-
请 注册 或 登录 后发表评论