Skip to content

Commit

Permalink
[AND-18] Codec negotiation (#1261)
Browse files Browse the repository at this point in the history
* New publisher

* map svc codec correctly

* Set codec

* Send correct layers

* Spotless & Api dump

* Correctly update, publish options, send all three layers correctly, respect `shouldRetry` when set publisher fails, send preferred options on reconnect or migrate

* Spotless and apidump

* Remove unnecessary tests

* Make the layers functions internal

* Api dump

* Remove code

* Correctly compute encodings and layers based on spatial layers, add test

* Remove unused code in transceiver cache and add tests

* Remove old dynascale test

* Remove lscale test, add mini publisher test

* Spotless & ApiDump and import cleanup

* Additional cleanup

* Update on layer calculation and tests

* Add codec to track info.

* Spotless & ApiDump

* Remove transceiver when stream is unpublished

* Log encodings

* Spotless ApiDump

* Spotless ApiDump

* Do not calculate layers for audio tracks

* Spotless

* Update initial screenshare resolution

* Use PCF from call

* Send publish options Id with the track info

* Api dump

* Cache publisher events in case publisher is not created yet.

* setTrack instead of add new transceiver when there is already publish option available for that kind of track

* remove commented code

* Update package name of video layers

* Update package in test

* Update publisher tests

* Minimal SPC test

* Spotless & Apidump

* Add minimal SPC Factory tests

* Fix wildcard imports

* Spotless & ApiDump

* Add SPC test for the init parameters

* Add more tests for RTC session

* Remove wildcard imports

* Remove wildcard imports

* Reformatting

* Fix failing tests

* Formatting

* Add change quality test

* Spotless & ApiDump

* Correctly test empty encodings

* Formatting
  • Loading branch information
aleksandar-apostolov authored Jan 15, 2025
1 parent 1aa5792 commit d2fcdd1
Show file tree
Hide file tree
Showing 28 changed files with 5,149 additions and 1,331 deletions.
111 changes: 95 additions & 16 deletions stream-video-android-core/api/stream-video-android-core.api

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions stream-video-android-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ dependencies {
implementation(libs.tink)

// unit tests
testImplementation(libs.stream.result)
testImplementation(libs.stream.result.call)
testImplementation(libs.junit)
testImplementation(libs.truth)
testImplementation(libs.mockk)
Expand All @@ -206,6 +208,10 @@ dependencies {
testImplementation(libs.kotlin.test.junit)
testImplementation(libs.kotlinx.coroutines.debug)
testImplementation(libs.kotlinx.serialization.converter)
testImplementation(libs.retrofit)
testImplementation(libs.retrofit.moshi)
testImplementation(libs.retrofit.scalars)
testImplementation(libs.retrofit.wire.converter)

// instrument tests
androidTestImplementation(libs.stream.log.android)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import com.google.common.truth.Truth.assertThat
import io.getstream.log.taggedLogger
import io.getstream.video.android.core.api.SignalServerService
import io.getstream.video.android.core.call.video.FilterVideoProcessor
import io.getstream.video.android.core.events.ChangePublishQualityEvent
import io.getstream.video.android.core.events.ParticipantJoinedEvent
import io.getstream.video.android.core.utils.buildAudioConstraints
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest
Expand All @@ -41,12 +39,6 @@ import org.webrtc.RTCStats
import org.webrtc.VideoCodecInfo
import retrofit2.Retrofit
import retrofit2.converter.wire.WireConverterFactory
import stream.video.sfu.event.ChangePublishQuality
import stream.video.sfu.event.VideoLayerSetting
import stream.video.sfu.event.VideoMediaRequest
import stream.video.sfu.event.VideoSender
import stream.video.sfu.models.Participant
import stream.video.sfu.models.TrackType
import stream.video.sfu.signal.UpdateMuteStatesRequest
import java.io.IOException
import java.io.InterruptedIOException
Expand Down Expand Up @@ -525,79 +517,4 @@ class AndroidDeviceTest : IntegrationTestBase(connectCoordinatorWS = false) {
// clean up a call
call.cleanup()
}

@Test
fun dynascale() = runTest {
// join will automatically start the audio and video capture
// based on the call settings
val joinResult = call.join(create = true)
assertSuccess(joinResult)

// create a turbine connection state
val connectionState = call.state.connection.testIn(backgroundScope)
// asset that the connection state is connected
val connectionStateItem = connectionState.awaitItem()
assertThat(connectionStateItem).isAnyOf(
RealtimeConnection.Connected,
RealtimeConnection.Joined(joinResult.getOrThrow()),
)
if (connectionStateItem is RealtimeConnection.Joined) {
connectionState.awaitItem()
}

// fake a participant joining
val joinEvent = ParticipantJoinedEvent(
callCid = call.cid,
participant = Participant(session_id = "fake", user_id = "fake"),
)
clientImpl.fireEvent(joinEvent, call.cid)

val participantsState = call.state.participants.testIn(backgroundScope)
val participants = participantsState.awaitItem()
assertThat(participants.size).isEqualTo(2)

val remoteParticipants = call.state.remoteParticipants.testIn(backgroundScope)
assertThat(remoteParticipants.awaitItem().size).isEqualTo(1)

val sortedParticipants = call.state.sortedParticipants.testIn(backgroundScope)
assertThat(sortedParticipants.awaitItem().size).isEqualTo(2)

// set their video as visible
call.setVisibility(sessionId = "fake", TrackType.TRACK_TYPE_VIDEO, true)
call.setVisibility(sessionId = "fake", TrackType.TRACK_TYPE_SCREEN_SHARE, true)

// val tracks1 = call.session?.defaultTracks()
// val tracks2 = call.session?.visibleTracks()
//
// assertThat(tracks1?.size).isEqualTo(2)
// assertThat(tracks1?.map { it.session_id }).contains("fake")
// assertThat(tracks2?.size).isEqualTo(2)
// assertThat(tracks2?.map { it.session_id }).contains("fake")
//
// // if their video isn't visible it shouldn't be in the tracks
// call.setVisibility(sessionId = "fake", TrackType.TRACK_TYPE_VIDEO, false)
// val tracks3 = call.session?.visibleTracks()
// assertThat(tracks3?.size).isEqualTo(1)

// test handling publish quality change
val mediaRequest = VideoMediaRequest()
val layers = listOf(
VideoLayerSetting(name = "f", active = false),
VideoLayerSetting(name = "h", active = true),
VideoLayerSetting(name = "q", active = false),
)
val quality = ChangePublishQuality(
video_senders = listOf(
VideoSender(
media_request = mediaRequest,
layers = layers,
),
),
)
val event = ChangePublishQualityEvent(changePublishQuality = quality)
call.session?.updatePublishQuality(event)

// clean up a call
call.cleanup()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ public class Call(
private var callStatsReportingJob: Job? = null
private var powerManager: PowerManager? = null

private val scope = CoroutineScope(clientImpl.scope.coroutineContext + supervisorJob)
internal val scope = CoroutineScope(clientImpl.scope.coroutineContext + supervisorJob)

/** The call state contains all state such as the participant list, reactions etc */
val state = CallState(client, this, user, scope)
Expand Down Expand Up @@ -661,6 +661,7 @@ public class Call(
// switch to the new SFU
val cred = joinResponse.value.credentials
val session = this.session!!
val currentOptions = this.session?.publisher?.currentOptions()
logger.i { "Rejoin SFU ${session?.sfuUrl} to ${cred.server.url}" }

this.sessionId = UUID.randomUUID().toString()
Expand Down Expand Up @@ -688,7 +689,7 @@ public class Call(
ice.toIceServer()
},
)
this.session?.connect(reconnectDetails)
this.session?.connect(reconnectDetails, currentOptions)
session.cleanup()
monitorSession(joinResponse.value)
} else {
Expand All @@ -714,6 +715,7 @@ public class Call(
// switch to the new SFU
val cred = joinResponse.value.credentials
val session = this.session!!
val currentOptions = this.session?.publisher?.currentOptions()
val oldSfuUrl = session.sfuUrl
logger.i { "Rejoin SFU $oldSfuUrl to ${cred.server.url}" }

Expand Down Expand Up @@ -744,7 +746,7 @@ public class Call(
)
val oldSession = this.session
this.session = newSession
this.session?.connect(reconnectDetails)
this.session?.connect(reconnectDetails, currentOptions)
monitorSession(joinResponse.value)
oldSession?.leaveWithReason("migrating")
oldSession?.cleanup()
Expand Down Expand Up @@ -1384,7 +1386,7 @@ public class Call(

fun fastReconnect() {
call.scope.launch {
call.rejoin()
call.fastReconnect()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -844,27 +844,24 @@ class MediaManagerImpl(
val videoSource =
call.peerConnectionFactory.makeVideoSource(false, filterVideoProcessor)

val screenShareVideoSource by lazy {
val screenShareVideoSource =
call.peerConnectionFactory.makeVideoSource(true, screenShareFilterVideoProcessor)
}

// for track ids we emulate the browser behaviour of random UUIDs, doing something different would be confusing
val videoTrack = call.peerConnectionFactory.makeVideoTrack(
var videoTrack = call.peerConnectionFactory.makeVideoTrack(
source = videoSource,
trackId = UUID.randomUUID().toString(),
)

val screenShareTrack by lazy {
call.peerConnectionFactory.makeVideoTrack(
source = screenShareVideoSource,
trackId = UUID.randomUUID().toString(),
)
}
var screenShareTrack = call.peerConnectionFactory.makeVideoTrack(
source = screenShareVideoSource,
trackId = UUID.randomUUID().toString(),
)

val audioSource = call.peerConnectionFactory.makeAudioSource(buildAudioConstraints())

// for track ids we emulate the browser behaviour of random UUIDs, doing something different would be confusing
val audioTrack = call.peerConnectionFactory.makeAudioTrack(
var audioTrack = call.peerConnectionFactory.makeAudioTrack(
source = audioSource,
trackId = UUID.randomUUID().toString(),
)
Expand Down
Loading

0 comments on commit d2fcdd1

Please sign in to comment.