davidliu
Committed by GitHub

Fix ConcurrentModificationException in RemoteVideoTrack (#413)

... ... @@ -23,7 +23,11 @@ import io.livekit.android.events.TrackEvent
import io.livekit.android.room.track.video.VideoSinkVisibility
import io.livekit.android.room.track.video.ViewVisibility
import io.livekit.android.util.LKLog
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import livekit.org.webrtc.RtpReceiver
import livekit.org.webrtc.VideoSink
import javax.inject.Named
... ... @@ -40,7 +44,8 @@ class RemoteVideoTrack(
private var coroutineScope = CoroutineScope(dispatcher + SupervisorJob())
private val sinkVisibilityMap = mutableMapOf<VideoSink, VideoSinkVisibility>()
private val visibilities = sinkVisibilityMap.values
private val visibilities
get() = sinkVisibilityMap.values
/**
* @suppress
... ... @@ -80,7 +85,9 @@ class RemoteVideoTrack(
fun addRenderer(renderer: VideoSink, visibility: VideoSinkVisibility) {
super.addRenderer(renderer)
if (autoManageVideo) {
sinkVisibilityMap[renderer] = visibility
synchronized(sinkVisibilityMap) {
sinkVisibilityMap[renderer] = visibility
}
visibility.addObserver { _, _ -> recalculateVisibility() }
recalculateVisibility()
} else {
... ... @@ -90,7 +97,11 @@ class RemoteVideoTrack(
override fun removeRenderer(renderer: VideoSink) {
super.removeRenderer(renderer)
val visibility = sinkVisibilityMap.remove(renderer)
val visibility = synchronized(sinkVisibilityMap) {
sinkVisibilityMap.remove(renderer)
}
visibility?.close()
if (autoManageVideo && visibility != null) {
recalculateVisibility()
... ... @@ -99,29 +110,40 @@ class RemoteVideoTrack(
override fun stop() {
super.stop()
sinkVisibilityMap.values.forEach { it.close() }
sinkVisibilityMap.clear()
synchronized(sinkVisibilityMap) {
sinkVisibilityMap.values.forEach { it.close() }
sinkVisibilityMap.clear()
}
}
private fun hasVisibleSinks(): Boolean {
return visibilities.any { it.isVisible() }
synchronized(sinkVisibilityMap) {
return visibilities.any { it.isVisible() }
}
}
private fun largestVideoViewSize(): Dimensions {
var maxWidth = 0
var maxHeight = 0
visibilities.forEach { visibility ->
val size = visibility.size()
maxWidth = max(maxWidth, size.width)
maxHeight = max(maxHeight, size.height)
}
synchronized(sinkVisibilityMap) {
visibilities.forEach { visibility ->
val size = visibility.size()
maxWidth = max(maxWidth, size.width)
maxHeight = max(maxHeight, size.height)
}
}
return Dimensions(maxWidth, maxHeight)
}
private fun recalculateVisibility() {
val isVisible = hasVisibleSinks()
val newDimensions = largestVideoViewSize()
var isVisible: Boolean
var newDimensions: Dimensions
synchronized(sinkVisibilityMap) {
isVisible = hasVisibleSinks()
newDimensions = largestVideoViewSize()
}
val eventsToPost = mutableListOf<TrackEvent>()
if (isVisible != lastVisibility) {
... ...