Committed by
GitHub
Adaptive stream fixes (#73)
* tests * update visibility immediately * handle visibility change from hidden * surface view version * clean up and readme update
正在显示
8 个修改的文件
包含
270 行增加
和
45 行删除
| @@ -82,7 +82,8 @@ screenCaptureIntentLauncher.launch(mediaProjectionManager.createScreenCaptureInt | @@ -82,7 +82,8 @@ screenCaptureIntentLauncher.launch(mediaProjectionManager.createScreenCaptureInt | ||
| 82 | 82 | ||
| 83 | ### Rendering subscribed tracks | 83 | ### Rendering subscribed tracks |
| 84 | 84 | ||
| 85 | -LiveKit uses WebRTC-provided `org.webrtc.SurfaceViewRenderer` to render video tracks. A `TextureView` implementation is also provided through `TextureViewRenderer`. Subscribed audio tracks are automatically played. | 85 | +LiveKit uses `SurfaceViewRenderer` to render video tracks. A `TextureView` implementation is also |
| 86 | +provided through `TextureViewRenderer`. Subscribed audio tracks are automatically played. | ||
| 86 | 87 | ||
| 87 | ```kt | 88 | ```kt |
| 88 | class MainActivity : AppCompatActivity(), RoomListener { | 89 | class MainActivity : AppCompatActivity(), RoomListener { |
| @@ -121,7 +122,7 @@ class MainActivity : AppCompatActivity(), RoomListener { | @@ -121,7 +122,7 @@ class MainActivity : AppCompatActivity(), RoomListener { | ||
| 121 | } | 122 | } |
| 122 | 123 | ||
| 123 | private fun attachVideo(videoTrack: VideoTrack) { | 124 | private fun attachVideo(videoTrack: VideoTrack) { |
| 124 | - // viewBinding.renderer is a `org.webrtc.SurfaceViewRenderer` in your | 125 | + // viewBinding.renderer is a `io.livekit.android.renderer.SurfaceViewRenderer` in your |
| 125 | // layout | 126 | // layout |
| 126 | videoTrack.addRenderer(viewBinding.renderer) | 127 | videoTrack.addRenderer(viewBinding.renderer) |
| 127 | } | 128 | } |
| 1 | +package io.livekit.android.renderer | ||
| 2 | + | ||
| 3 | +import android.content.Context | ||
| 4 | +import android.util.AttributeSet | ||
| 5 | +import android.view.View | ||
| 6 | +import io.livekit.android.room.track.video.ViewVisibility | ||
| 7 | +import org.webrtc.SurfaceViewRenderer | ||
| 8 | + | ||
| 9 | +class SurfaceViewRenderer : SurfaceViewRenderer, ViewVisibility.Notifier { | ||
| 10 | + constructor(context: Context) : super(context) | ||
| 11 | + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) | ||
| 12 | + | ||
| 13 | + override var viewVisibility: ViewVisibility? = null | ||
| 14 | + override fun onVisibilityChanged(changedView: View, visibility: Int) { | ||
| 15 | + super.onVisibilityChanged(changedView, visibility) | ||
| 16 | + viewVisibility?.recalculate() | ||
| 17 | + } | ||
| 18 | +} |
| @@ -10,30 +10,26 @@ | @@ -10,30 +10,26 @@ | ||
| 10 | package io.livekit.android.renderer | 10 | package io.livekit.android.renderer |
| 11 | 11 | ||
| 12 | import android.content.Context | 12 | import android.content.Context |
| 13 | -import android.view.SurfaceView | ||
| 14 | -import android.view.SurfaceHolder | ||
| 15 | -import org.webrtc.RendererCommon.RendererEvents | ||
| 16 | -import org.webrtc.RendererCommon.VideoLayoutMeasure | ||
| 17 | -import kotlin.jvm.JvmOverloads | ||
| 18 | -import org.webrtc.RendererCommon.GlDrawer | ||
| 19 | -import org.webrtc.RendererCommon.ScalingType | ||
| 20 | import android.content.res.Resources.NotFoundException | 13 | import android.content.res.Resources.NotFoundException |
| 21 | import android.graphics.Matrix | 14 | import android.graphics.Matrix |
| 15 | +import android.graphics.SurfaceTexture | ||
| 22 | import android.os.Looper | 16 | import android.os.Looper |
| 23 | import android.util.AttributeSet | 17 | import android.util.AttributeSet |
| 18 | +import android.view.Surface | ||
| 19 | +import android.view.SurfaceHolder | ||
| 24 | import android.view.TextureView | 20 | import android.view.TextureView |
| 21 | +import android.view.View | ||
| 22 | +import io.livekit.android.room.track.video.ViewVisibility | ||
| 25 | import org.webrtc.* | 23 | import org.webrtc.* |
| 26 | -import android.graphics.SurfaceTexture | ||
| 27 | -import android.view.Surface | ||
| 28 | - | ||
| 29 | -import org.webrtc.ThreadUtils | 24 | +import org.webrtc.RendererCommon.* |
| 30 | import java.util.concurrent.CountDownLatch | 25 | import java.util.concurrent.CountDownLatch |
| 31 | 26 | ||
| 32 | 27 | ||
| 33 | /** | 28 | /** |
| 34 | - * Display the video stream on a SurfaceView. | 29 | + * Display the video stream on a TextureView. |
| 35 | */ | 30 | */ |
| 36 | -class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, VideoSink, RendererEvents { | 31 | +class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureView.SurfaceTextureListener, VideoSink, |
| 32 | + RendererEvents, ViewVisibility.Notifier { | ||
| 37 | // Cached resource name. | 33 | // Cached resource name. |
| 38 | private val resourceName: String | 34 | private val resourceName: String |
| 39 | private val videoLayoutMeasure = VideoLayoutMeasure() | 35 | private val videoLayoutMeasure = VideoLayoutMeasure() |
| @@ -359,4 +355,10 @@ class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureView.Sur | @@ -359,4 +355,10 @@ class TextureViewRenderer : TextureView, SurfaceHolder.Callback, TextureView.Sur | ||
| 359 | companion object { | 355 | companion object { |
| 360 | private const val TAG = "SurfaceViewRenderer" | 356 | private const val TAG = "SurfaceViewRenderer" |
| 361 | } | 357 | } |
| 358 | + | ||
| 359 | + override var viewVisibility: ViewVisibility? = null | ||
| 360 | + override fun onVisibilityChanged(changedView: View, visibility: Int) { | ||
| 361 | + super.onVisibilityChanged(changedView, visibility) | ||
| 362 | + viewVisibility?.recalculate() | ||
| 363 | + } | ||
| 362 | } | 364 | } |
| @@ -33,12 +33,17 @@ class RemoteTrackPublication( | @@ -33,12 +33,17 @@ class RemoteTrackPublication( | ||
| 33 | trackJob = CoroutineScope(ioDispatcher).launch { | 33 | trackJob = CoroutineScope(ioDispatcher).launch { |
| 34 | value.events.collect { | 34 | value.events.collect { |
| 35 | when (it) { | 35 | when (it) { |
| 36 | - is TrackEvent.VisibilityChanged -> handleVisibilityChanged(it) | ||
| 37 | - is TrackEvent.VideoDimensionsChanged -> handleVideoDimensionsChanged(it) | 36 | + is TrackEvent.VisibilityChanged -> handleVisibilityChanged(it.isVisible) |
| 37 | + is TrackEvent.VideoDimensionsChanged -> handleVideoDimensionsChanged(it.newDimensions) | ||
| 38 | is TrackEvent.StreamStateChanged -> handleStreamStateChanged(it) | 38 | is TrackEvent.StreamStateChanged -> handleStreamStateChanged(it) |
| 39 | } | 39 | } |
| 40 | } | 40 | } |
| 41 | } | 41 | } |
| 42 | + | ||
| 43 | + if (value is RemoteVideoTrack) { | ||
| 44 | + handleVideoDimensionsChanged(value.lastDimensions) | ||
| 45 | + handleVisibilityChanged(value.lastVisibility) | ||
| 46 | + } | ||
| 42 | } | 47 | } |
| 43 | } | 48 | } |
| 44 | 49 | ||
| @@ -158,13 +163,13 @@ class RemoteTrackPublication( | @@ -158,13 +163,13 @@ class RemoteTrackPublication( | ||
| 158 | sendUpdateTrackSettings.invoke() | 163 | sendUpdateTrackSettings.invoke() |
| 159 | } | 164 | } |
| 160 | 165 | ||
| 161 | - private fun handleVisibilityChanged(trackEvent: TrackEvent.VisibilityChanged) { | ||
| 162 | - disabled = !trackEvent.isVisible | 166 | + private fun handleVisibilityChanged(isVisible: Boolean) { |
| 167 | + disabled = !isVisible | ||
| 163 | sendUpdateTrackSettings.invoke() | 168 | sendUpdateTrackSettings.invoke() |
| 164 | } | 169 | } |
| 165 | 170 | ||
| 166 | - private fun handleVideoDimensionsChanged(trackEvent: TrackEvent.VideoDimensionsChanged) { | ||
| 167 | - videoDimensions = trackEvent.newDimensions | 171 | + private fun handleVideoDimensionsChanged(newDimensions: Track.Dimensions) { |
| 172 | + videoDimensions = newDimensions | ||
| 168 | sendUpdateTrackSettings.invoke() | 173 | sendUpdateTrackSettings.invoke() |
| 169 | } | 174 | } |
| 170 | 175 |
| @@ -23,8 +23,10 @@ class RemoteVideoTrack( | @@ -23,8 +23,10 @@ class RemoteVideoTrack( | ||
| 23 | private val sinkVisibilityMap = mutableMapOf<VideoSink, VideoSinkVisibility>() | 23 | private val sinkVisibilityMap = mutableMapOf<VideoSink, VideoSinkVisibility>() |
| 24 | private val visibilities = sinkVisibilityMap.values | 24 | private val visibilities = sinkVisibilityMap.values |
| 25 | 25 | ||
| 26 | - private var lastVisibility = false | ||
| 27 | - private var lastDimensions: Dimensions = Dimensions(0, 0) | 26 | + internal var lastVisibility = false |
| 27 | + private set | ||
| 28 | + internal var lastDimensions: Dimensions = Dimensions(0, 0) | ||
| 29 | + private set | ||
| 28 | 30 | ||
| 29 | override fun addRenderer(renderer: VideoSink) { | 31 | override fun addRenderer(renderer: VideoSink) { |
| 30 | if (autoManageVideo && renderer is View) { | 32 | if (autoManageVideo && renderer is View) { |
| @@ -49,6 +51,9 @@ class RemoteVideoTrack( | @@ -49,6 +51,9 @@ class RemoteVideoTrack( | ||
| 49 | super.removeRenderer(renderer) | 51 | super.removeRenderer(renderer) |
| 50 | val visibility = sinkVisibilityMap.remove(renderer) | 52 | val visibility = sinkVisibilityMap.remove(renderer) |
| 51 | visibility?.close() | 53 | visibility?.close() |
| 54 | + if (autoManageVideo && visibility != null) { | ||
| 55 | + recalculateVisibility() | ||
| 56 | + } | ||
| 52 | } | 57 | } |
| 53 | 58 | ||
| 54 | override fun stop() { | 59 | override fun stop() { |
| @@ -8,6 +8,7 @@ import android.view.ViewTreeObserver | @@ -8,6 +8,7 @@ import android.view.ViewTreeObserver | ||
| 8 | import androidx.annotation.CallSuper | 8 | import androidx.annotation.CallSuper |
| 9 | import androidx.compose.ui.layout.LayoutCoordinates | 9 | import androidx.compose.ui.layout.LayoutCoordinates |
| 10 | import io.livekit.android.room.track.Track | 10 | import io.livekit.android.room.track.Track |
| 11 | +import io.livekit.android.room.track.video.ViewVisibility.Notifier | ||
| 11 | import java.util.* | 12 | import java.util.* |
| 12 | 13 | ||
| 13 | abstract class VideoSinkVisibility : Observable() { | 14 | abstract class VideoSinkVisibility : Observable() { |
| @@ -63,41 +64,55 @@ class ComposeVisibility : VideoSinkVisibility() { | @@ -63,41 +64,55 @@ class ComposeVisibility : VideoSinkVisibility() { | ||
| 63 | } | 64 | } |
| 64 | } | 65 | } |
| 65 | 66 | ||
| 67 | +/** | ||
| 68 | + * A [VideoSinkVisibility] for views. If using a custom view other than the sdk provided renderers, | ||
| 69 | + * you must implement [Notifier], override [View.onVisibilityChanged] and call through to [recalculate], or | ||
| 70 | + * the visibility may not be calculated correctly. | ||
| 71 | + */ | ||
| 66 | class ViewVisibility(private val view: View) : VideoSinkVisibility() { | 72 | class ViewVisibility(private val view: View) : VideoSinkVisibility() { |
| 67 | 73 | ||
| 74 | + private val lastVisibility = false | ||
| 75 | + private val lastSize = Track.Dimensions(0, 0) | ||
| 76 | + | ||
| 68 | private val handler = Handler(Looper.getMainLooper()) | 77 | private val handler = Handler(Looper.getMainLooper()) |
| 69 | - private val globalLayoutListener = object : ViewTreeObserver.OnGlobalLayoutListener { | ||
| 70 | - val lastVisibility = false | ||
| 71 | - val lastSize = Track.Dimensions(0, 0) | ||
| 72 | - | ||
| 73 | - override fun onGlobalLayout() { | ||
| 74 | - handler.removeCallbacksAndMessages(null) | ||
| 75 | - handler.postDelayed({ | ||
| 76 | - var shouldNotify = false | ||
| 77 | - val newVisibility = isVisible() | ||
| 78 | - val newSize = size() | ||
| 79 | - if (newVisibility != lastVisibility) { | ||
| 80 | - shouldNotify = true | ||
| 81 | - } | ||
| 82 | - if (newSize != lastSize) { | ||
| 83 | - shouldNotify = true | ||
| 84 | - } | ||
| 85 | - | ||
| 86 | - if (shouldNotify) { | ||
| 87 | - notifyChanged() | ||
| 88 | - } | ||
| 89 | - }, 2000) | ||
| 90 | - } | 78 | + private val globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener { |
| 79 | + handler.removeCallbacksAndMessages(null) | ||
| 80 | + handler.postDelayed({ | ||
| 81 | + recalculate() | ||
| 82 | + }, 2000) | ||
| 83 | + } | ||
| 84 | + | ||
| 85 | + interface Notifier { | ||
| 86 | + var viewVisibility: ViewVisibility? | ||
| 91 | } | 87 | } |
| 92 | 88 | ||
| 93 | init { | 89 | init { |
| 94 | view.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener) | 90 | view.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener) |
| 91 | + if (view is Notifier) { | ||
| 92 | + view.viewVisibility = this | ||
| 93 | + } | ||
| 95 | } | 94 | } |
| 96 | 95 | ||
| 97 | private val loc = IntArray(2) | 96 | private val loc = IntArray(2) |
| 98 | private val viewRect = Rect() | 97 | private val viewRect = Rect() |
| 99 | private val windowRect = Rect() | 98 | private val windowRect = Rect() |
| 100 | 99 | ||
| 100 | + fun recalculate() { | ||
| 101 | + var shouldNotify = false | ||
| 102 | + val newVisibility = isVisible() | ||
| 103 | + val newSize = size() | ||
| 104 | + if (newVisibility != lastVisibility) { | ||
| 105 | + shouldNotify = true | ||
| 106 | + } | ||
| 107 | + if (newSize != lastSize) { | ||
| 108 | + shouldNotify = true | ||
| 109 | + } | ||
| 110 | + | ||
| 111 | + if (shouldNotify) { | ||
| 112 | + notifyChanged() | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + | ||
| 101 | private fun isViewAncestorsVisible(view: View): Boolean { | 116 | private fun isViewAncestorsVisible(view: View): Boolean { |
| 102 | if (view.visibility != View.VISIBLE) { | 117 | if (view.visibility != View.VISIBLE) { |
| 103 | return false | 118 | return false |
| @@ -132,5 +147,8 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() { | @@ -132,5 +147,8 @@ class ViewVisibility(private val view: View) : VideoSinkVisibility() { | ||
| 132 | super.close() | 147 | super.close() |
| 133 | handler.removeCallbacksAndMessages(null) | 148 | handler.removeCallbacksAndMessages(null) |
| 134 | view.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener) | 149 | view.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener) |
| 150 | + if (view is Notifier && view.viewVisibility == this) { | ||
| 151 | + view.viewVisibility = null | ||
| 152 | + } | ||
| 135 | } | 153 | } |
| 136 | } | 154 | } |
| 1 | +package io.livekit.android.mock | ||
| 2 | + | ||
| 3 | +import org.webrtc.VideoSink | ||
| 4 | +import org.webrtc.VideoTrack | ||
| 5 | + | ||
| 6 | +class MockVideoStreamTrack( | ||
| 7 | + val id: String = "id", | ||
| 8 | + val kind: String = AUDIO_TRACK_KIND, | ||
| 9 | + var enabled: Boolean = true, | ||
| 10 | + var state: State = State.LIVE, | ||
| 11 | +) : VideoTrack(1L) { | ||
| 12 | + val sinks = mutableSetOf<VideoSink>() | ||
| 13 | + override fun id(): String = id | ||
| 14 | + | ||
| 15 | + override fun kind(): String = kind | ||
| 16 | + | ||
| 17 | + override fun enabled(): Boolean = enabled | ||
| 18 | + | ||
| 19 | + override fun setEnabled(enable: Boolean): Boolean { | ||
| 20 | + enabled = enable | ||
| 21 | + return true | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + override fun state(): State { | ||
| 25 | + return state | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | + override fun dispose() { | ||
| 29 | + } | ||
| 30 | + | ||
| 31 | + override fun addSink(sink: VideoSink) { | ||
| 32 | + sinks.add(sink) | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + override fun removeSink(sink: VideoSink) { | ||
| 36 | + sinks.remove(sink) | ||
| 37 | + } | ||
| 38 | +} |
| 1 | +package io.livekit.android.room.track | ||
| 2 | + | ||
| 3 | +import io.livekit.android.BaseTest | ||
| 4 | +import io.livekit.android.events.EventCollector | ||
| 5 | +import io.livekit.android.events.TrackEvent | ||
| 6 | +import io.livekit.android.mock.MockVideoStreamTrack | ||
| 7 | +import io.livekit.android.room.track.video.VideoSinkVisibility | ||
| 8 | +import kotlinx.coroutines.ExperimentalCoroutinesApi | ||
| 9 | +import org.junit.Assert | ||
| 10 | +import org.junit.Before | ||
| 11 | +import org.junit.Test | ||
| 12 | +import org.webrtc.VideoFrame | ||
| 13 | +import org.webrtc.VideoSink | ||
| 14 | + | ||
| 15 | +@OptIn(ExperimentalCoroutinesApi::class) | ||
| 16 | +class RemoteVideoTrackTest : BaseTest() { | ||
| 17 | + | ||
| 18 | + lateinit var track: RemoteVideoTrack | ||
| 19 | + | ||
| 20 | + @Before | ||
| 21 | + fun setup() { | ||
| 22 | + track = RemoteVideoTrack( | ||
| 23 | + name = "track", | ||
| 24 | + rtcTrack = MockVideoStreamTrack(), | ||
| 25 | + autoManageVideo = true, | ||
| 26 | + dispatcher = coroutineRule.dispatcher | ||
| 27 | + ) | ||
| 28 | + } | ||
| 29 | + | ||
| 30 | + @Test | ||
| 31 | + fun defaultVisibility() = runTest { | ||
| 32 | + Assert.assertFalse(track.lastVisibility) | ||
| 33 | + Assert.assertEquals(Track.Dimensions(0, 0), track.lastDimensions) | ||
| 34 | + } | ||
| 35 | + | ||
| 36 | + @Test | ||
| 37 | + fun addVisibility() = runTest { | ||
| 38 | + val visible = true | ||
| 39 | + val size = Track.Dimensions(100, 100) | ||
| 40 | + val visibility = CustomVisibility(visible = visible, size = size) | ||
| 41 | + val eventCollector = EventCollector(track.events, coroutineRule.scope) | ||
| 42 | + track.addRenderer(EmptyVideoSink(), visibility) | ||
| 43 | + val events = eventCollector.stopCollecting() | ||
| 44 | + | ||
| 45 | + Assert.assertEquals(2, events.size) | ||
| 46 | + val visibilityEvent = events.first { it is TrackEvent.VisibilityChanged } as TrackEvent.VisibilityChanged | ||
| 47 | + val sizeEvent = events.first { it is TrackEvent.VideoDimensionsChanged } as TrackEvent.VideoDimensionsChanged | ||
| 48 | + | ||
| 49 | + Assert.assertEquals(visible, visibilityEvent.isVisible) | ||
| 50 | + Assert.assertEquals(size, sizeEvent.newDimensions) | ||
| 51 | + } | ||
| 52 | + | ||
| 53 | + @Test | ||
| 54 | + fun removeVisibility() = runTest { | ||
| 55 | + val sink = EmptyVideoSink() | ||
| 56 | + val visibility = CustomVisibility(visible = true, size = Track.Dimensions(100, 100)) | ||
| 57 | + track.addRenderer(sink, visibility) | ||
| 58 | + val eventCollector = EventCollector(track.events, coroutineRule.scope) | ||
| 59 | + track.removeRenderer(sink) | ||
| 60 | + val events = eventCollector.stopCollecting() | ||
| 61 | + | ||
| 62 | + Assert.assertEquals(2, events.size) | ||
| 63 | + val visibilityEvent = events.first { it is TrackEvent.VisibilityChanged } as TrackEvent.VisibilityChanged | ||
| 64 | + val sizeEvent = events.first { it is TrackEvent.VideoDimensionsChanged } as TrackEvent.VideoDimensionsChanged | ||
| 65 | + | ||
| 66 | + Assert.assertEquals(false, visibilityEvent.isVisible) | ||
| 67 | + Assert.assertEquals(Track.Dimensions(0, 0), sizeEvent.newDimensions) | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + @Test | ||
| 71 | + fun changeVisibility() = runTest { | ||
| 72 | + val visibility = CustomVisibility(visible = true, size = Track.Dimensions(100, 100)) | ||
| 73 | + track.addRenderer(EmptyVideoSink(), visibility) | ||
| 74 | + | ||
| 75 | + val visible = false | ||
| 76 | + val size = Track.Dimensions(200, 200) | ||
| 77 | + | ||
| 78 | + val eventCollector = EventCollector(track.events, coroutineRule.scope) | ||
| 79 | + visibility.visible = visible | ||
| 80 | + visibility.size = size | ||
| 81 | + val events = eventCollector.stopCollecting() | ||
| 82 | + | ||
| 83 | + Assert.assertEquals(2, events.size) | ||
| 84 | + val visibilityEvent = events.first { it is TrackEvent.VisibilityChanged } as TrackEvent.VisibilityChanged | ||
| 85 | + val sizeEvent = events.first { it is TrackEvent.VideoDimensionsChanged } as TrackEvent.VideoDimensionsChanged | ||
| 86 | + Assert.assertEquals(visible, visibilityEvent.isVisible) | ||
| 87 | + Assert.assertEquals(size, sizeEvent.newDimensions) | ||
| 88 | + } | ||
| 89 | + | ||
| 90 | + @Test | ||
| 91 | + fun multipleVisibility() = runTest { | ||
| 92 | + val visible = true | ||
| 93 | + val size = Track.Dimensions(100, 100) | ||
| 94 | + val hiddenVisibility = CustomVisibility(visible = false, size = Track.Dimensions(0, 0)) | ||
| 95 | + val visibility = CustomVisibility(visible = visible, size = size) | ||
| 96 | + track.addRenderer(EmptyVideoSink(), hiddenVisibility) | ||
| 97 | + | ||
| 98 | + val eventCollector = EventCollector(track.events, coroutineRule.scope) | ||
| 99 | + track.addRenderer(EmptyVideoSink(), visibility) | ||
| 100 | + val events = eventCollector.stopCollecting() | ||
| 101 | + | ||
| 102 | + // Make sure we're grabbing the max of visibility and dimensions | ||
| 103 | + Assert.assertEquals(2, events.size) | ||
| 104 | + val visibilityEvent = events.first { it is TrackEvent.VisibilityChanged } as TrackEvent.VisibilityChanged | ||
| 105 | + val sizeEvent = events.first { it is TrackEvent.VideoDimensionsChanged } as TrackEvent.VideoDimensionsChanged | ||
| 106 | + | ||
| 107 | + Assert.assertEquals(visible, visibilityEvent.isVisible) | ||
| 108 | + Assert.assertEquals(size, sizeEvent.newDimensions) | ||
| 109 | + } | ||
| 110 | +} | ||
| 111 | + | ||
| 112 | +private class EmptyVideoSink : VideoSink { | ||
| 113 | + override fun onFrame(p0: VideoFrame?) { | ||
| 114 | + } | ||
| 115 | +} | ||
| 116 | + | ||
| 117 | +private class CustomVisibility( | ||
| 118 | + visible: Boolean = false, | ||
| 119 | + size: Track.Dimensions = Track.Dimensions(0, 0) | ||
| 120 | +) : VideoSinkVisibility() { | ||
| 121 | + | ||
| 122 | + var visible = visible | ||
| 123 | + set(value) { | ||
| 124 | + field = value | ||
| 125 | + notifyChanged() | ||
| 126 | + } | ||
| 127 | + | ||
| 128 | + var size = size | ||
| 129 | + set(value) { | ||
| 130 | + field = value | ||
| 131 | + notifyChanged() | ||
| 132 | + } | ||
| 133 | + | ||
| 134 | + override fun isVisible() = visible | ||
| 135 | + | ||
| 136 | + override fun size() = size | ||
| 137 | + | ||
| 138 | +} |
-
请 注册 或 登录 后发表评论