davidliu
Committed by GitHub

Add fps to subscribe setting (#207)

... ... @@ -374,6 +374,7 @@ constructor(
disabled: Boolean,
videoDimensions: Track.Dimensions?,
videoQuality: LivekitModels.VideoQuality?,
fps: Int?,
) {
val trackSettings = LivekitRtc.UpdateTrackSettings.newBuilder()
.addTrackSids(sid)
... ... @@ -388,6 +389,10 @@ constructor(
// default to HIGH
quality = LivekitModels.VideoQuality.HIGH
}
if(fps != null){
setFps(fps)
}
}
val request = LivekitRtc.SignalRequest.newBuilder()
... ...
... ... @@ -53,6 +53,7 @@ class RemoteTrackPublication(
private var disabled: Boolean = false
private var videoQuality: LivekitModels.VideoQuality? = LivekitModels.VideoQuality.HIGH
private var videoDimensions: Track.Dimensions? = null
private var fps: Int? = null
var subscriptionAllowed: Boolean = true
internal set
... ... @@ -128,10 +129,12 @@ class RemoteTrackPublication(
}
/**
* for tracks that support simulcasting, directly adjust subscribed quality
* For tracks that support simulcasting, directly adjust subscribed quality
*
* this indicates the highest quality the client can accept. if network bandwidth does not
* allow, server will automatically reduce quality to optimize for uninterrupted video
* This indicates the highest quality the client can accept. If network bandwidth does not
* allow, server will automatically reduce quality to optimize for uninterrupted video.
*
* Will override previous calls to [setVideoDimensions].
*/
fun setVideoQuality(quality: LivekitModels.VideoQuality) {
if (isAutoManaged
... ... @@ -148,6 +151,8 @@ class RemoteTrackPublication(
/**
* Update the dimensions that the server will use for determining the video quality to send down.
*
* Will override previous calls to [setVideoQuality].
*/
fun setVideoDimensions(dimensions: Track.Dimensions) {
if (isAutoManaged
... ... @@ -163,6 +168,22 @@ class RemoteTrackPublication(
sendUpdateTrackSettings.invoke()
}
/**
* Update the fps that the server will use for determining the video quality to send down.
*/
fun setVideoFps(fps: Int?) {
if (isAutoManaged
|| !subscribed
|| this.fps == fps
|| track !is VideoTrack
) {
return
}
this.fps = fps
sendUpdateTrackSettings.invoke()
}
private fun handleVisibilityChanged(isVisible: Boolean) {
disabled = !isVisible
sendUpdateTrackSettings.invoke()
... ... @@ -194,7 +215,8 @@ class RemoteTrackPublication(
sid,
disabled,
videoDimensions,
videoQuality
videoQuality,
fps
)
}
... ...
... ... @@ -5,7 +5,7 @@ import org.webrtc.MediaStream
import org.webrtc.VideoTrack
fun createMediaStreamId(participantSid: String, trackSid: String) =
"${TestData.REMOTE_PARTICIPANT.sid}|${TestData.REMOTE_AUDIO_TRACK.sid}"
"${participantSid}|${trackSid}"
class MockMediaStream(private val id: String = "id") : MediaStream(1L) {
... ...
... ... @@ -5,11 +5,14 @@ import org.webrtc.VideoTrack
class MockVideoStreamTrack(
val id: String = "id",
val kind: String = AUDIO_TRACK_KIND,
val kind: String = VIDEO_TRACK_KIND,
var enabled: Boolean = true,
var state: State = State.LIVE,
) : VideoTrack(1L) {
val sinks = mutableSetOf<VideoSink>()
private var shouldReceive = true
override fun id(): String = id
override fun kind(): String = kind
... ... @@ -21,6 +24,12 @@ class MockVideoStreamTrack(
return true
}
override fun shouldReceive() = shouldReceive
override fun setShouldReceive(shouldReceive: Boolean) {
this.shouldReceive = shouldReceive
}
override fun state(): State {
return state
}
... ... @@ -35,4 +44,4 @@ class MockVideoStreamTrack(
override fun removeSink(sink: VideoSink) {
sinks.remove(sink)
}
}
\ No newline at end of file
}
... ...
... ... @@ -47,4 +47,8 @@ class MockWebSocket(
mutableSentRequests.add(bytes)
return !isClosed
}
fun clearRequests() {
mutableSentRequests.clear()
}
}
\ No newline at end of file
... ...
... ... @@ -16,6 +16,12 @@ object TestData {
build()
}
val REMOTE_VIDEO_TRACK = with(LivekitModels.TrackInfo.newBuilder()) {
sid = "remote_video_track_sid"
type = LivekitModels.TrackType.VIDEO
build()
}
val LOCAL_PARTICIPANT = with(LivekitModels.ParticipantInfo.newBuilder()) {
sid = "local_participant_sid"
identity = "local_participant_identity"
... ... @@ -44,6 +50,7 @@ object TestData {
build()
}
addTracks(REMOTE_AUDIO_TRACK)
addTracks(REMOTE_VIDEO_TRACK)
build()
}
... ...
... ... @@ -17,7 +17,6 @@ import io.livekit.android.room.track.Track
import io.livekit.android.util.flow
import io.livekit.android.util.toOkioByteString
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNull
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import org.junit.Assert
... ... @@ -137,9 +136,14 @@ class RoomMockE2ETest : MockE2ETest() {
simulateMessageFromServer(SignalClientTest.PARTICIPANT_JOIN)
val events = eventCollector.stopCollecting()
Assert.assertEquals(2, events.size)
Assert.assertEquals(true, events[0] is RoomEvent.ParticipantConnected)
Assert.assertEquals(true, events[1] is RoomEvent.TrackPublished)
assertIsClassList(
listOf(
RoomEvent.ParticipantConnected::class.java,
RoomEvent.TrackPublished::class.java,
RoomEvent.TrackPublished::class.java,
),
events
)
}
@Test
... ... @@ -157,9 +161,14 @@ class RoomMockE2ETest : MockE2ETest() {
)
val events = eventCollector.stopCollecting()
Assert.assertEquals(2, events.size)
Assert.assertEquals(true, events[0] is RoomEvent.TrackUnpublished)
Assert.assertEquals(true, events[1] is RoomEvent.ParticipantDisconnected)
assertIsClassList(
listOf(
RoomEvent.TrackUnpublished::class.java,
RoomEvent.TrackUnpublished::class.java,
RoomEvent.ParticipantDisconnected::class.java,
),
events
)
}
@Test
... ...
... ... @@ -16,7 +16,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.test.runTest
import org.junit.Assert
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
... ... @@ -211,11 +210,16 @@ class RoomTest {
room.onEngineDisconnected(DisconnectReason.CLIENT_INITIATED)
val events = eventCollector.stopCollecting()
assertEquals(4, events.size)
assertEquals(true, events[0] is RoomEvent.TrackUnsubscribed)
assertEquals(true, events[1] is RoomEvent.TrackUnpublished)
assertEquals(true, events[2] is RoomEvent.ParticipantDisconnected)
assertEquals(true, events[3] is RoomEvent.Disconnected)
Assert.assertTrue(room.remoteParticipants.isEmpty())
assertIsClassList(
listOf(
RoomEvent.TrackUnsubscribed::class.java,
RoomEvent.TrackUnpublished::class.java,
RoomEvent.TrackUnpublished::class.java,
RoomEvent.ParticipantDisconnected::class.java,
RoomEvent.Disconnected::class.java
),
events
)
assertTrue(room.remoteParticipants.isEmpty())
}
}
\ No newline at end of file
... ...
package io.livekit.android.room.track
import io.livekit.android.MockE2ETest
import io.livekit.android.mock.*
import io.livekit.android.room.SignalClientTest
import io.livekit.android.util.toOkioByteString
import io.livekit.android.util.toPBByteString
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.advanceUntilIdle
import livekit.LivekitModels
import livekit.LivekitModels.VideoQuality
import livekit.LivekitRtc
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(RobolectricTestRunner::class)
class RemoteTrackPublicationTest : MockE2ETest() {
@Test
fun trackSetting() = runTest {
room.adaptiveStream = false
connect()
wsFactory.listener.onMessage(
wsFactory.ws,
SignalClientTest.PARTICIPANT_JOIN.toOkioByteString()
)
room.onAddTrack(
MockVideoStreamTrack(),
arrayOf(
MockMediaStream(
id = createMediaStreamId(
TestData.REMOTE_PARTICIPANT.sid,
TestData.REMOTE_VIDEO_TRACK.sid
)
)
)
)
advanceUntilIdle()
wsFactory.ws.clearRequests()
val remoteVideoPub = room.remoteParticipants.values.first()
.videoTracks.first()
.first as RemoteTrackPublication
remoteVideoPub.setVideoQuality(VideoQuality.LOW)
remoteVideoPub.setVideoFps(100)
advanceUntilIdle()
val lastRequest = LivekitRtc.SignalRequest.newBuilder()
.mergeFrom(wsFactory.ws.sentRequests.last().toPBByteString())
.build()
assertTrue(lastRequest.hasTrackSetting())
assertEquals(100, lastRequest.trackSetting.fps)
assertEquals(VideoQuality.LOW, lastRequest.trackSetting.quality)
}
}
\ No newline at end of file
... ...