Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,14 @@ fun UserRobot.assertUserMicrophone(isEnabled: Boolean, videoCall: Boolean = true
if (isEnabled) {
assertTrue(CallPage.microphoneEnabledToggle.waitToAppear().isDisplayed())
if (videoCall) {
assertTrue(CallPage.ParticipantView.microphoneEnabledIcon.isDisplayed())
// The own-tile mic icon reflects the published audio track state and lags the
// local toggle by an SFU round-trip — wait for it instead of asserting instantly.
assertTrue(CallPage.ParticipantView.microphoneEnabledIcon.waitToAppear().isDisplayed())
}
} else {
assertTrue(CallPage.microphoneDisabledToggle.waitToAppear().isDisplayed())
if (videoCall) {
assertTrue(CallPage.ParticipantView.microphoneDisabledIcon.isDisplayed())
assertTrue(CallPage.ParticipantView.microphoneDisabledIcon.waitToAppear().isDisplayed())
}
}
return this
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ streamWebRTC = "137.1.1"
streamNoiseCancellation = "2.0.0"
streamResult = "1.3.0"
streamChat = "6.10.0"
streamCore = "5.0.0"
streamCore = "5.0.1"
streamLog = "1.3.2"
streamPush = "1.3.4"
streamRenderscript = "0.0.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.socket.coordinator.v2

import io.getstream.android.core.api.model.connection.StreamConnectionState
import io.getstream.result.Error
import io.getstream.video.android.core.ConnectionState

/**
* Maps a core [StreamConnectionState] onto the SDK's existing [ConnectionState] shape.
*
* The mapping produced here is a placeholder that preserves compilation until a later plan
* reshapes the video [ConnectionState] sealed interface to mirror the richer core states 1:1
* (D-04, D-09). Once that reshape lands, both this function body and the collaborating tests
* flip together in a single commit.
*/
internal fun StreamConnectionState.toVideoConnectionState(): ConnectionState {
// TODO(02-03): replace placeholder mapping when ConnectionState sealed interface is

Check warning on line 32 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/v2/ConnectionStateMapper.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZ8cp_i91enMAFMrozyH&open=AZ8cp_i91enMAFMrozyH&pullRequest=1737
// rewritten per D-04/D-09.
return when (this) {
is StreamConnectionState.Idle -> ConnectionState.PreConnect
is StreamConnectionState.Connecting.Opening -> ConnectionState.Loading
is StreamConnectionState.Connecting.Authenticating -> ConnectionState.Loading
is StreamConnectionState.Connected -> ConnectionState.Connected
is StreamConnectionState.Disconnected -> {
val throwable = cause
if (throwable == null) {
ConnectionState.Disconnected
} else {
ConnectionState.Failed(
Error.ThrowableError(
message = throwable.message ?: "",
cause = throwable,
),
)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.socket.coordinator.v2

import io.getstream.android.core.api.authentication.StreamTokenProvider
import io.getstream.android.core.api.model.value.StreamToken
import io.getstream.android.core.api.model.value.StreamUserId
import io.getstream.android.video.generated.apis.ProductvideoApi
import io.getstream.android.video.generated.models.CreateGuestRequest
import io.getstream.android.video.generated.models.UserRequest
import io.getstream.video.android.core.utils.toUser
import io.getstream.video.android.model.User
import io.getstream.video.android.model.UserType

/**
* Writable sink for the SDK's currently active [User]. Introduced here as a minimal seam so the
* guest flow can adopt the server-issued identity (id, role, image, custom) returned by the
* `createGuest` REST call. Plan 02-02 will connect this to the concrete SDK-wide user store.
*/
internal interface WritableUserRepository {

Check warning on line 34 in stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/v2/GuestStreamTokenProvider.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make this interface functional or replace it with a function type.

See more on https://sonarcloud.io/project/issues?id=GetStream_stream-video-android&issues=AZ8cp_e81enMAFMrozyG&open=AZ8cp_e81enMAFMrozyG&pullRequest=1737
/** Persists [user] as the SDK's active user. */
fun setUser(user: User)
}

/**
* [StreamTokenProvider] implementation for guest users (COORD-09, D-06).
*
* On invocation, calls the coordinator's `POST /video/guest` endpoint via [ProductvideoApi],
* adopts the server-issued user identity through [userRepository], and returns the resulting
* JWT wrapped in a [StreamToken].
*
* `StreamTokenManager` wraps this provider in a `StreamSingleFlightProcessor`, so concurrent
* callers share the same in-flight request. No additional deduplication is needed here (unlike
* the legacy `guestUserJob: Deferred<Unit>` synchronization layer that this replaces).
*/
internal class GuestStreamTokenProvider(
private val api: ProductvideoApi,
private val initialUser: User,
private val userRepository: WritableUserRepository,
) : StreamTokenProvider {

override suspend fun loadToken(userId: StreamUserId): StreamToken {
val response = api.createGuest(
createGuestRequest = CreateGuestRequest(
user = UserRequest(
id = initialUser.id,
image = initialUser.image,
name = initialUser.name,
custom = initialUser.custom,
),
),
)
// Adopt the server-issued user identity so downstream SDK components observe the
// canonical id/role instead of the placeholder used at builder time (AND-1202).
userRepository.setUser(response.user.toUser().copy(type = UserType.Guest))
return StreamToken.fromString(response.accessToken)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.socket.coordinator.v2

import io.getstream.android.core.api.authentication.StreamTokenProvider
import io.getstream.android.core.api.model.value.StreamToken
import io.getstream.android.core.api.model.value.StreamUserId
import io.getstream.video.android.core.socket.common.token.TokenProvider

/**
* Adapts the integration-supplied video [TokenProvider] to core's [StreamTokenProvider].
*
* Selected at builder time for authenticated users; guest users are served by
* `GuestStreamTokenProvider` instead. See D-05 in the phase context.
*/
internal class IntegrationStreamTokenProvider(
private val delegate: TokenProvider,
) : StreamTokenProvider {

/**
* Loads a token from the wrapped integration [TokenProvider].
*
* The [userId] parameter is intentionally unused — the integration's
* `TokenProvider.loadToken()` already binds the user identity through the SDK's builder
* configuration (D-05). Core passes the identifier for parity with alternate providers
* that mint per-user tokens.
*/
override suspend fun loadToken(userId: StreamUserId): StreamToken =
StreamToken.fromString(delegate.loadToken())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core.socket.coordinator.v2

import io.getstream.android.core.api.serialization.StreamEventSerialization
import io.getstream.android.video.generated.models.VideoEvent
import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser

/**
* Adapts the existing Moshi-based [MoshiVideoParser] to core's [StreamEventSerialization]
* interface so that `stream-android-core`'s composite deserializer can route product events
* to the video SDK without re-implementing Moshi adapter registration.
*
* Wraps parser calls in [runCatching] because [MoshiVideoParser.fromJson] throws on null while
* the contract here is a [Result].
*/
internal class VideoEventSerialization(
private val parser: MoshiVideoParser = MoshiVideoParser(),
) : StreamEventSerialization<VideoEvent> {

override fun serialize(data: VideoEvent): Result<String> = runCatching {
parser.toJson(data)
}

override fun deserialize(raw: String): Result<VideoEvent> = runCatching {
parser.fromJson(raw, VideoEvent::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-video-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.video.android.core

import io.getstream.android.core.api.model.connection.StreamConnectedUser
import io.getstream.android.core.api.model.connection.StreamConnectionState
import io.getstream.video.android.core.socket.coordinator.v2.toVideoConnectionState
import org.junit.Test
import java.util.Date
import kotlin.test.assertEquals
import kotlin.test.assertTrue

/**
* Table-driven mapping test for [StreamConnectionState] -> [ConnectionState] extension.
*
* Asserts the temporary placeholder mapping shipped by Plan 02-01. A subsequent plan reshapes
* the video [ConnectionState] sealed interface to mirror core 1:1; both this test and the
* mapper body flip together in that plan.
*/
internal class ClientStateConnectionMappingTest {

@Test
fun `Idle maps to PreConnect`() {
val result = (StreamConnectionState.Idle as StreamConnectionState).toVideoConnectionState()
assertEquals(ConnectionState.PreConnect, result)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

@Test
fun `Connecting Opening maps to Loading`() {
val result: ConnectionState =
StreamConnectionState.Connecting.Opening("user-1").toVideoConnectionState()
assertEquals(ConnectionState.Loading, result)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

@Test
fun `Connecting Authenticating maps to Loading`() {
val result: ConnectionState =
StreamConnectionState.Connecting.Authenticating("user-1").toVideoConnectionState()
assertEquals(ConnectionState.Loading, result)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

@Test
fun `Connected maps to Connected`() {
val result: ConnectionState = StreamConnectionState
.Connected(connectedUserFixture(), "connection-id")
.toVideoConnectionState()
assertEquals(ConnectionState.Connected, result)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

@Test
fun `Disconnected with null cause maps to Disconnected`() {
val result: ConnectionState =
StreamConnectionState.Disconnected(cause = null).toVideoConnectionState()
assertEquals(ConnectionState.Disconnected, result)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

@Test
fun `Disconnected with cause maps to Failed`() {
val boom = IllegalStateException("boom")
val result: ConnectionState =
StreamConnectionState.Disconnected(cause = boom).toVideoConnectionState()

assertTrue(result is ConnectionState.Failed)
assertEquals("boom", result.error.message)
// TODO(02-03): update expected value when ConnectionState sealed interface is rewritten
}

private fun connectedUserFixture(): StreamConnectedUser = StreamConnectedUser(
createdAt = Date(0),
id = "user-1",
language = "en",
role = "user",
updatedAt = Date(0),
blockedUserIds = emptyList(),
teams = emptyList(),
)
}
Loading
Loading