正在显示
8 个修改的文件
包含
76 行增加
和
217 行删除
| 1 | package io.livekit.android.room.track | 1 | package io.livekit.android.room.track |
| 2 | 2 | ||
| 3 | import io.livekit.android.room.participant.Participant | 3 | import io.livekit.android.room.participant.Participant |
| 4 | +import io.livekit.android.util.flowDelegate | ||
| 4 | import livekit.LivekitModels | 5 | import livekit.LivekitModels |
| 5 | import java.lang.ref.WeakReference | 6 | import java.lang.ref.WeakReference |
| 6 | 7 | ||
| @@ -9,7 +10,7 @@ open class TrackPublication( | @@ -9,7 +10,7 @@ open class TrackPublication( | ||
| 9 | track: Track?, | 10 | track: Track?, |
| 10 | participant: Participant | 11 | participant: Participant |
| 11 | ) { | 12 | ) { |
| 12 | - open var track: Track? = track | 13 | + open var track: Track? by flowDelegate(track) |
| 13 | internal set | 14 | internal set |
| 14 | var name: String | 15 | var name: String |
| 15 | internal set | 16 | internal set |
| @@ -32,9 +32,15 @@ android { | @@ -32,9 +32,15 @@ android { | ||
| 32 | 32 | ||
| 33 | dependencies { | 33 | dependencies { |
| 34 | 34 | ||
| 35 | - implementation 'androidx.core:core-ktx:1.7.0' | ||
| 36 | - implementation 'androidx.appcompat:appcompat:1.3.1' | ||
| 37 | - implementation 'com.google.android.material:material:1.4.0' | 35 | + api "androidx.core:core-ktx:${versions.androidx_core}" |
| 36 | + api 'androidx.appcompat:appcompat:1.4.0' | ||
| 37 | + api 'com.google.android.material:material:1.4.0' | ||
| 38 | + api deps.kotlinx_coroutines | ||
| 39 | + api deps.timber | ||
| 40 | + api "androidx.lifecycle:lifecycle-runtime-ktx:${versions.androidx_lifecycle}" | ||
| 41 | + api "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidx_lifecycle}" | ||
| 42 | + api "androidx.lifecycle:lifecycle-common-java8:${versions.androidx_lifecycle}" | ||
| 43 | + api project(":livekit-android-sdk") | ||
| 38 | testImplementation 'junit:junit:4.+' | 44 | testImplementation 'junit:junit:4.+' |
| 39 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' | 45 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' |
| 40 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' | 46 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' |
| 1 | -package io.livekit.android.composesample | 1 | +package io.livekit.android.sample |
| 2 | 2 | ||
| 3 | import android.app.Application | 3 | import android.app.Application |
| 4 | import android.content.Intent | 4 | import android.content.Intent |
| @@ -62,7 +62,7 @@ class CallViewModel( | @@ -62,7 +62,7 @@ class CallViewModel( | ||
| 62 | val flipButtonVideoEnabled = mutableFlipVideoButtonEnabled.hide() | 62 | val flipButtonVideoEnabled = mutableFlipVideoButtonEnabled.hide() |
| 63 | 63 | ||
| 64 | private val mutableScreencastEnabled = MutableLiveData(false) | 64 | private val mutableScreencastEnabled = MutableLiveData(false) |
| 65 | - val screencastEnabled = mutableScreencastEnabled.hide() | 65 | + val screenshareEnabled = mutableScreencastEnabled.hide() |
| 66 | 66 | ||
| 67 | init { | 67 | init { |
| 68 | viewModelScope.launch { | 68 | viewModelScope.launch { |
| @@ -152,7 +152,7 @@ class CallViewModel( | @@ -152,7 +152,7 @@ class CallViewModel( | ||
| 152 | } | 152 | } |
| 153 | } | 153 | } |
| 154 | 154 | ||
| 155 | - fun flipVideo() { | 155 | + fun flipCamera() { |
| 156 | room.value?.localParticipant?.let { participant -> | 156 | room.value?.localParticipant?.let { participant -> |
| 157 | val videoTrack = participant.getTrackPublication(Track.Source.CAMERA) | 157 | val videoTrack = participant.getTrackPublication(Track.Source.CAMERA) |
| 158 | ?.track as? LocalVideoTrack | 158 | ?.track as? LocalVideoTrack |
| @@ -27,6 +27,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi | @@ -27,6 +27,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi | ||
| 27 | import io.livekit.android.composesample.ui.theme.AppTheme | 27 | import io.livekit.android.composesample.ui.theme.AppTheme |
| 28 | import io.livekit.android.room.Room | 28 | import io.livekit.android.room.Room |
| 29 | import io.livekit.android.room.participant.Participant | 29 | import io.livekit.android.room.participant.Participant |
| 30 | +import io.livekit.android.sample.CallViewModel | ||
| 30 | import kotlinx.coroutines.Dispatchers | 31 | import kotlinx.coroutines.Dispatchers |
| 31 | import kotlinx.parcelize.Parcelize | 32 | import kotlinx.parcelize.Parcelize |
| 32 | 33 | ||
| @@ -87,7 +88,7 @@ class CallActivity : AppCompatActivity() { | @@ -87,7 +88,7 @@ class CallActivity : AppCompatActivity() { | ||
| 87 | val micEnabled by viewModel.micEnabled.observeAsState(true) | 88 | val micEnabled by viewModel.micEnabled.observeAsState(true) |
| 88 | val videoEnabled by viewModel.cameraEnabled.observeAsState(true) | 89 | val videoEnabled by viewModel.cameraEnabled.observeAsState(true) |
| 89 | val flipButtonEnabled by viewModel.flipButtonVideoEnabled.observeAsState(true) | 90 | val flipButtonEnabled by viewModel.flipButtonVideoEnabled.observeAsState(true) |
| 90 | - val screencastEnabled by viewModel.screencastEnabled.observeAsState(false) | 91 | + val screencastEnabled by viewModel.screenshareEnabled.observeAsState(false) |
| 91 | Content( | 92 | Content( |
| 92 | room, | 93 | room, |
| 93 | participants, | 94 | participants, |
| @@ -209,7 +210,7 @@ class CallActivity : AppCompatActivity() { | @@ -209,7 +210,7 @@ class CallActivity : AppCompatActivity() { | ||
| 209 | ) | 210 | ) |
| 210 | } | 211 | } |
| 211 | Surface( | 212 | Surface( |
| 212 | - onClick = { viewModel.flipVideo() }, | 213 | + onClick = { viewModel.flipCamera() }, |
| 213 | ) { | 214 | ) { |
| 214 | Icon( | 215 | Icon( |
| 215 | painterResource(id = R.drawable.outline_flip_camera_android_24), | 216 | painterResource(id = R.drawable.outline_flip_camera_android_24), |
| @@ -37,7 +37,7 @@ dependencies { | @@ -37,7 +37,7 @@ dependencies { | ||
| 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" | 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" |
| 38 | implementation deps.kotlinx_coroutines | 38 | implementation deps.kotlinx_coroutines |
| 39 | implementation 'com.google.android.material:material:1.4.0' | 39 | implementation 'com.google.android.material:material:1.4.0' |
| 40 | - implementation 'androidx.appcompat:appcompat:1.3.1' | 40 | + implementation 'androidx.appcompat:appcompat:1.4.0' |
| 41 | implementation "androidx.core:core-ktx:${versions.androidx_core}" | 41 | implementation "androidx.core:core-ktx:${versions.androidx_core}" |
| 42 | implementation "androidx.activity:activity-ktx:1.4.0" | 42 | implementation "androidx.activity:activity-ktx:1.4.0" |
| 43 | implementation 'androidx.fragment:fragment-ktx:1.3.6' | 43 | implementation 'androidx.fragment:fragment-ktx:1.3.6' |
| @@ -52,7 +52,6 @@ dependencies { | @@ -52,7 +52,6 @@ dependencies { | ||
| 52 | implementation 'com.snakydesign.livedataextensions:lives:1.3.0' | 52 | implementation 'com.snakydesign.livedataextensions:lives:1.3.0' |
| 53 | implementation deps.timber | 53 | implementation deps.timber |
| 54 | implementation project(":sample-app-common") | 54 | implementation project(":sample-app-common") |
| 55 | - implementation project(":livekit-android-sdk") | ||
| 56 | testImplementation 'junit:junit:4.12' | 55 | testImplementation 'junit:junit:4.12' |
| 57 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' | 56 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' |
| 58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' | 57 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' |
| @@ -8,15 +8,15 @@ import android.os.Parcelable | @@ -8,15 +8,15 @@ import android.os.Parcelable | ||
| 8 | import android.view.View | 8 | import android.view.View |
| 9 | import androidx.activity.result.contract.ActivityResultContracts | 9 | import androidx.activity.result.contract.ActivityResultContracts |
| 10 | import androidx.appcompat.app.AppCompatActivity | 10 | import androidx.appcompat.app.AppCompatActivity |
| 11 | +import androidx.lifecycle.lifecycleScope | ||
| 11 | import androidx.recyclerview.widget.LinearLayoutManager | 12 | import androidx.recyclerview.widget.LinearLayoutManager |
| 12 | import com.github.ajalt.timberkt.Timber | 13 | import com.github.ajalt.timberkt.Timber |
| 13 | -import com.snakydesign.livedataextensions.combineLatest | ||
| 14 | -import com.snakydesign.livedataextensions.scan | ||
| 15 | -import com.snakydesign.livedataextensions.take | ||
| 16 | import com.xwray.groupie.GroupieAdapter | 14 | import com.xwray.groupie.GroupieAdapter |
| 17 | -import io.livekit.android.room.participant.Participant | 15 | +import io.livekit.android.room.track.Track |
| 18 | import io.livekit.android.room.track.VideoTrack | 16 | import io.livekit.android.room.track.VideoTrack |
| 19 | import io.livekit.android.sample.databinding.CallActivityBinding | 17 | import io.livekit.android.sample.databinding.CallActivityBinding |
| 18 | +import io.livekit.android.util.flow | ||
| 19 | +import kotlinx.coroutines.flow.* | ||
| 20 | import kotlinx.parcelize.Parcelize | 20 | import kotlinx.parcelize.Parcelize |
| 21 | 21 | ||
| 22 | class CallActivity : AppCompatActivity() { | 22 | class CallActivity : AppCompatActivity() { |
| @@ -41,7 +41,7 @@ class CallActivity : AppCompatActivity() { | @@ -41,7 +41,7 @@ class CallActivity : AppCompatActivity() { | ||
| 41 | if (resultCode != Activity.RESULT_OK || data == null) { | 41 | if (resultCode != Activity.RESULT_OK || data == null) { |
| 42 | return@registerForActivityResult | 42 | return@registerForActivityResult |
| 43 | } | 43 | } |
| 44 | - viewModel.setScreenshare(true, data) | 44 | + viewModel.startScreenCapture(data) |
| 45 | } | 45 | } |
| 46 | 46 | ||
| 47 | override fun onCreate(savedInstanceState: Bundle?) { | 47 | override fun onCreate(savedInstanceState: Bundle?) { |
| @@ -60,48 +60,67 @@ class CallActivity : AppCompatActivity() { | @@ -60,48 +60,67 @@ class CallActivity : AppCompatActivity() { | ||
| 60 | this.adapter = adapter | 60 | this.adapter = adapter |
| 61 | } | 61 | } |
| 62 | 62 | ||
| 63 | - combineLatest( | ||
| 64 | - viewModel.room, | ||
| 65 | - viewModel.participants | ||
| 66 | - ) { room, participants -> room to participants } | ||
| 67 | - .observe(this) { | ||
| 68 | - | ||
| 69 | - val (room, participants) = it | ||
| 70 | - val items = participants.map { participant -> ParticipantItem(room, participant) } | ||
| 71 | - adapter.update(items) | ||
| 72 | - } | 63 | + lifecycleScope.launchWhenCreated { |
| 64 | + viewModel.room | ||
| 65 | + .combine(viewModel.participants) { room, participants -> room to participants } | ||
| 66 | + .collect { (room, participants) -> | ||
| 67 | + if (room != null) { | ||
| 68 | + val items = participants.map { participant -> ParticipantItem(room, participant) } | ||
| 69 | + adapter.update(items) | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | 73 | ||
| 74 | // speaker view setup | 74 | // speaker view setup |
| 75 | - viewModel.room.take(1).observe(this) { room -> | ||
| 76 | - room.initVideoRenderer(binding.speakerVideoView) | ||
| 77 | - viewModel.activeSpeaker | ||
| 78 | - .scan(Pair<Participant?, Participant?>(null, null)) { pair, participant -> | ||
| 79 | - // old participant is first | ||
| 80 | - // latest active participant is second | ||
| 81 | - Pair(pair.second, participant) | ||
| 82 | - }.observe(this) { (oldSpeaker, newSpeaker) -> | ||
| 83 | - // Remove any renderering from the old speaker | ||
| 84 | - oldSpeaker?.videoTracks | ||
| 85 | - ?.values | ||
| 86 | - ?.forEach { trackPublication -> | ||
| 87 | - (trackPublication.track as? VideoTrack)?.removeRenderer(binding.speakerVideoView) | ||
| 88 | - } | ||
| 89 | - | ||
| 90 | - binding.identityText.text = newSpeaker?.identity | ||
| 91 | - val videoTrack = newSpeaker?.videoTracks?.values | ||
| 92 | - ?.firstOrNull() | ||
| 93 | - ?.track as? VideoTrack | ||
| 94 | - if (videoTrack != null) { | 75 | + lifecycleScope.launchWhenCreated { |
| 76 | + viewModel.room.filterNotNull().take(1) | ||
| 77 | + .transform { room -> | ||
| 78 | + // Initialize video renderer | ||
| 79 | + room.initVideoRenderer(binding.speakerVideoView) | ||
| 80 | + | ||
| 81 | + // Observe primary speaker changes | ||
| 82 | + emitAll(viewModel.primarySpeaker) | ||
| 83 | + }.flatMapLatest { primarySpeaker -> | ||
| 84 | + // Update new primary speaker identity | ||
| 85 | + binding.identityText.text = primarySpeaker?.identity | ||
| 86 | + | ||
| 87 | + // observe videoTracks changes. | ||
| 88 | + if (primarySpeaker != null) { | ||
| 89 | + primarySpeaker::videoTracks.flow | ||
| 90 | + .map { primarySpeaker to it } | ||
| 91 | + } else { | ||
| 92 | + emptyFlow() | ||
| 93 | + } | ||
| 94 | + }.flatMapLatest { (participant, videoTracks) -> | ||
| 95 | + | ||
| 96 | + for (videoTrack in videoTracks.values) { | ||
| 97 | + Timber.e { "videoTrack is ${videoTrack.track}" } | ||
| 98 | + } | ||
| 99 | + // Prioritize any screenshare streams. | ||
| 100 | + val trackPublication = participant.getTrackPublication(Track.Source.SCREEN_SHARE) | ||
| 101 | + ?: participant.getTrackPublication(Track.Source.CAMERA) | ||
| 102 | + ?: videoTracks.values.firstOrNull() | ||
| 103 | + ?: return@flatMapLatest emptyFlow() | ||
| 104 | + | ||
| 105 | + trackPublication::track.flow | ||
| 106 | + }.collect { videoTrack -> | ||
| 107 | + // Cleanup old video track | ||
| 108 | + val oldVideoTrack = binding.speakerVideoView.tag as? VideoTrack | ||
| 109 | + oldVideoTrack?.removeRenderer(binding.speakerVideoView) | ||
| 110 | + | ||
| 111 | + // Bind new video track to video view. | ||
| 112 | + if (videoTrack is VideoTrack) { | ||
| 95 | videoTrack.addRenderer(binding.speakerVideoView) | 113 | videoTrack.addRenderer(binding.speakerVideoView) |
| 96 | binding.speakerVideoView.visibility = View.VISIBLE | 114 | binding.speakerVideoView.visibility = View.VISIBLE |
| 97 | } else { | 115 | } else { |
| 98 | binding.speakerVideoView.visibility = View.INVISIBLE | 116 | binding.speakerVideoView.visibility = View.INVISIBLE |
| 99 | } | 117 | } |
| 118 | + binding.speakerVideoView.tag = videoTrack | ||
| 100 | } | 119 | } |
| 101 | } | 120 | } |
| 102 | 121 | ||
| 103 | // Controls setup | 122 | // Controls setup |
| 104 | - viewModel.videoEnabled.observe(this) { enabled -> | 123 | + viewModel.cameraEnabled.observe(this) { enabled -> |
| 105 | binding.camera.setOnClickListener { viewModel.setCameraEnabled(!enabled) } | 124 | binding.camera.setOnClickListener { viewModel.setCameraEnabled(!enabled) } |
| 106 | binding.camera.setImageResource( | 125 | binding.camera.setImageResource( |
| 107 | if (enabled) R.drawable.outline_videocam_24 | 126 | if (enabled) R.drawable.outline_videocam_24 |
| @@ -121,7 +140,7 @@ class CallActivity : AppCompatActivity() { | @@ -121,7 +140,7 @@ class CallActivity : AppCompatActivity() { | ||
| 121 | viewModel.screenshareEnabled.observe(this) { enabled -> | 140 | viewModel.screenshareEnabled.observe(this) { enabled -> |
| 122 | binding.screenShare.setOnClickListener { | 141 | binding.screenShare.setOnClickListener { |
| 123 | if (enabled) { | 142 | if (enabled) { |
| 124 | - viewModel.setScreenshare(!enabled) | 143 | + viewModel.stopScreenCapture() |
| 125 | } else { | 144 | } else { |
| 126 | requestMediaProjection() | 145 | requestMediaProjection() |
| 127 | } | 146 | } |
| 1 | -package io.livekit.android.sample | ||
| 2 | - | ||
| 3 | -import android.app.Application | ||
| 4 | -import android.content.Intent | ||
| 5 | -import androidx.lifecycle.AndroidViewModel | ||
| 6 | -import androidx.lifecycle.MutableLiveData | ||
| 7 | -import androidx.lifecycle.viewModelScope | ||
| 8 | -import com.snakydesign.livedataextensions.distinctUntilChanged | ||
| 9 | -import io.livekit.android.ConnectOptions | ||
| 10 | -import io.livekit.android.LiveKit | ||
| 11 | -import io.livekit.android.events.RoomEvent | ||
| 12 | -import io.livekit.android.events.collect | ||
| 13 | -import io.livekit.android.room.Room | ||
| 14 | -import io.livekit.android.room.participant.Participant | ||
| 15 | -import io.livekit.android.room.participant.RemoteParticipant | ||
| 16 | -import io.livekit.android.room.track.CameraPosition | ||
| 17 | -import io.livekit.android.room.track.LocalVideoTrack | ||
| 18 | -import io.livekit.android.room.track.Track | ||
| 19 | -import io.livekit.android.sample.util.hide | ||
| 20 | -import kotlinx.coroutines.launch | ||
| 21 | - | ||
| 22 | -class CallViewModel( | ||
| 23 | - val url: String, | ||
| 24 | - val token: String, | ||
| 25 | - application: Application | ||
| 26 | -) : AndroidViewModel(application) { | ||
| 27 | - private val mutableRoom = MutableLiveData<Room>() | ||
| 28 | - val room = mutableRoom.hide() | ||
| 29 | - private val mutableParticipants = MutableLiveData<List<Participant>>() | ||
| 30 | - val participants = mutableParticipants.hide() | ||
| 31 | - private val mutableActiveSpeaker = MutableLiveData<Participant>() | ||
| 32 | - val activeSpeaker = mutableActiveSpeaker.hide().distinctUntilChanged() | ||
| 33 | - | ||
| 34 | - private val mutableVideoEnabled = MutableLiveData<Boolean>() | ||
| 35 | - val videoEnabled = mutableVideoEnabled.hide().distinctUntilChanged() | ||
| 36 | - private val mutableMicEnabled = MutableLiveData<Boolean>() | ||
| 37 | - val micEnabled = mutableMicEnabled.hide().distinctUntilChanged() | ||
| 38 | - private val mutableScreenshareEnabled = MutableLiveData<Boolean>() | ||
| 39 | - val screenshareEnabled = mutableScreenshareEnabled.hide().distinctUntilChanged() | ||
| 40 | - | ||
| 41 | - init { | ||
| 42 | - viewModelScope.launch { | ||
| 43 | - val room = LiveKit.connect( | ||
| 44 | - application, | ||
| 45 | - url, | ||
| 46 | - token, | ||
| 47 | - ConnectOptions(), | ||
| 48 | - null | ||
| 49 | - ) | ||
| 50 | - | ||
| 51 | - launch { | ||
| 52 | - room.events.collect { | ||
| 53 | - handleRoomEvent(it) | ||
| 54 | - } | ||
| 55 | - } | ||
| 56 | - | ||
| 57 | - val localParticipant = room.localParticipant | ||
| 58 | - val audioTrack = localParticipant.createAudioTrack() | ||
| 59 | - localParticipant.publishAudioTrack(audioTrack) | ||
| 60 | - val videoTrack = localParticipant.createVideoTrack() | ||
| 61 | - localParticipant.publishVideoTrack(videoTrack) | ||
| 62 | - videoTrack.startCapture() | ||
| 63 | - | ||
| 64 | - updateParticipants(room) | ||
| 65 | - mutableActiveSpeaker.value = localParticipant | ||
| 66 | - mutableRoom.value = room | ||
| 67 | - | ||
| 68 | - mutableVideoEnabled.value = | ||
| 69 | - !(localParticipant.getTrackPublication(Track.Source.CAMERA)?.muted ?: false) | ||
| 70 | - mutableMicEnabled.value = | ||
| 71 | - !(localParticipant.getTrackPublication(Track.Source.MICROPHONE)?.muted ?: false) | ||
| 72 | - mutableScreenshareEnabled.value = false | ||
| 73 | - } | ||
| 74 | - } | ||
| 75 | - | ||
| 76 | - private fun handleRoomEvent(event: RoomEvent) { | ||
| 77 | - when (event) { | ||
| 78 | - is RoomEvent.ParticipantConnected -> updateParticipants(event.room) | ||
| 79 | - is RoomEvent.ParticipantDisconnected -> updateParticipants(event.room) | ||
| 80 | - is RoomEvent.ActiveSpeakersChanged -> handleActiveSpeakersChanged(event.speakers) | ||
| 81 | - } | ||
| 82 | - } | ||
| 83 | - | ||
| 84 | - private fun updateParticipants(room: Room) { | ||
| 85 | - | ||
| 86 | - val participantList = listOf(room.localParticipant) + | ||
| 87 | - room.remoteParticipants | ||
| 88 | - .keys | ||
| 89 | - .sortedBy { it } | ||
| 90 | - .mapNotNull { room.remoteParticipants[it] } | ||
| 91 | - mutableParticipants.postValue(participantList) | ||
| 92 | - | ||
| 93 | - if (!participantList.contains(mutableActiveSpeaker.value) || mutableActiveSpeaker.value == null) { | ||
| 94 | - // active speaker has left, choose someone else at random. | ||
| 95 | - mutableActiveSpeaker.postValue(participantList.last()) | ||
| 96 | - } | ||
| 97 | - } | ||
| 98 | - | ||
| 99 | - fun handleActiveSpeakersChanged(speakers: List<Participant>) { | ||
| 100 | - // If old active speaker is still active, don't change. | ||
| 101 | - if (speakers.isEmpty() || speakers.contains(mutableActiveSpeaker.value)) { | ||
| 102 | - return | ||
| 103 | - } | ||
| 104 | - val newSpeaker = speakers | ||
| 105 | - .filter { it is RemoteParticipant } // Try not to display local participant as speaker. | ||
| 106 | - .firstOrNull() ?: return | ||
| 107 | - mutableActiveSpeaker.postValue(newSpeaker) | ||
| 108 | - } | ||
| 109 | - | ||
| 110 | - override fun onCleared() { | ||
| 111 | - super.onCleared() | ||
| 112 | - mutableRoom.value?.disconnect() | ||
| 113 | - } | ||
| 114 | - | ||
| 115 | - fun setCameraEnabled(enabled: Boolean) { | ||
| 116 | - val localParticipant = room.value?.localParticipant ?: return | ||
| 117 | - | ||
| 118 | - viewModelScope.launch { | ||
| 119 | - localParticipant.setCameraEnabled(enabled) | ||
| 120 | - mutableVideoEnabled.postValue(enabled) | ||
| 121 | - } | ||
| 122 | - } | ||
| 123 | - | ||
| 124 | - fun setMicEnabled(enabled: Boolean) { | ||
| 125 | - val localParticipant = room.value?.localParticipant ?: return | ||
| 126 | - | ||
| 127 | - viewModelScope.launch { | ||
| 128 | - localParticipant.setMicrophoneEnabled(enabled) | ||
| 129 | - mutableMicEnabled.postValue(enabled) | ||
| 130 | - } | ||
| 131 | - } | ||
| 132 | - | ||
| 133 | - fun setScreenshare( | ||
| 134 | - enabled: Boolean, | ||
| 135 | - mediaProjectionPermissionResultData: Intent? = null | ||
| 136 | - ) { | ||
| 137 | - val localParticipant = room.value?.localParticipant ?: return | ||
| 138 | - | ||
| 139 | - viewModelScope.launch { | ||
| 140 | - localParticipant.setScreenShareEnabled(enabled, mediaProjectionPermissionResultData) | ||
| 141 | - mutableScreenshareEnabled.postValue(enabled) | ||
| 142 | - } | ||
| 143 | - } | ||
| 144 | - | ||
| 145 | - fun flipCamera() { | ||
| 146 | - val localParticipant = room.value?.localParticipant ?: return | ||
| 147 | - val localVideoTrack = localParticipant | ||
| 148 | - .getTrackPublication(Track.Source.CAMERA) | ||
| 149 | - ?.track as? LocalVideoTrack | ||
| 150 | - ?: return | ||
| 151 | - | ||
| 152 | - val currentOptions = localVideoTrack.options | ||
| 153 | - val newPosition = when (currentOptions.position) { | ||
| 154 | - CameraPosition.FRONT -> CameraPosition.BACK | ||
| 155 | - CameraPosition.BACK -> CameraPosition.FRONT | ||
| 156 | - null -> null | ||
| 157 | - } | ||
| 158 | - | ||
| 159 | - if (newPosition != null) { | ||
| 160 | - localVideoTrack.restartTrack(options = currentOptions.copy(position = newPosition)) | ||
| 161 | - } | ||
| 162 | - } | ||
| 163 | -} |
| @@ -23,12 +23,8 @@ | @@ -23,12 +23,8 @@ | ||
| 23 | 23 | ||
| 24 | <io.livekit.android.renderer.TextureViewRenderer | 24 | <io.livekit.android.renderer.TextureViewRenderer |
| 25 | android:id="@+id/speaker_video_view" | 25 | android:id="@+id/speaker_video_view" |
| 26 | - android:layout_width="0dp" | ||
| 27 | - android:layout_height="0dp" | ||
| 28 | - app:layout_constraintBottom_toTopOf="@id/audience_row" | ||
| 29 | - app:layout_constraintEnd_toEndOf="parent" | ||
| 30 | - app:layout_constraintStart_toStartOf="parent" | ||
| 31 | - app:layout_constraintTop_toTopOf="parent" /> | 26 | + android:layout_width="match_parent" |
| 27 | + android:layout_height="match_parent" /> | ||
| 32 | </FrameLayout> | 28 | </FrameLayout> |
| 33 | 29 | ||
| 34 | <FrameLayout | 30 | <FrameLayout |
-
请 注册 或 登录 后发表评论