Skip to content

Commit 145e038

Browse files
committed
Merge branch 'develop'
2 parents f857e09 + afa1489 commit 145e038

File tree

12 files changed

+87
-62
lines changed

12 files changed

+87
-62
lines changed

README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,17 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro
6666

6767
### 0.2.0 milestone
6868

69-
- [ ] Local Video disconnects sometimes (ICE restarts issue for the publisher. we're waiting for the backend support)
70-
- [ ] Deeplink support for video call demo & dogfooding app (skip auth for the video demo, keep it for dogfooding)
71-
- [ ] Chat Integration
72-
- [ ] XML version of VideoRenderer
73-
- [ ] Call Analytics stateflow
74-
- [ ] Automatically handle pagination and sorting on > 6 participants
75-
- [ ] Make it easy to test ringing support
76-
- [ ] publish app on play store
77-
- [ ] report version number of SDK on all API calls
78-
- [ ] Bug: java.net.UnknownHostException: Unable to resolve host "hint.stream-io-video.com" isn't throw but instead logged as INFO
69+
- [ ] Deeplink support for video call demo & dogfooding app (skip auth for the video demo, keep it for dogfooding) (Daniel)
70+
- [ ] Chat Integration (Jaewoong)
71+
- [ ] XML version of VideoRenderer (Jaewoong)
72+
- [ ] Local Video disconnects sometimes (ICE restarts issue for the publisher. we're waiting for the backend support) (Thierry)
73+
- [ ] Call Analytics stateflow (Thierry)
74+
- [ ] Automatically handle pagination and sorting on > 6 participants
75+
- [ ] Ringing: Make it easy to test
76+
- [ ] Ringing: Make a list of what needs to be configurable
77+
- [ ] Publish app on play store
78+
- [ ] Report version number of SDK on all API calls (Daniel)
79+
- [ ] Bug: java.net.UnknownHostException: Unable to resolve host "hint.stream-io-video.com" isn't throw but instead logged as INFO (Daniel)
7980
- [ ] Bug: screensharing is broken. android doesn’t receive/render (not sure) the screenshare. video shows up as the gray avatar
8081
- [X] Reactions
8182
- [X] bug: screenshare is not removed after it stops when a participant leaves the call (Thierry) (probably just dont update the state when the participant leaves)
@@ -92,8 +93,9 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro
9293
- [ ] Test coverage
9394
- [ ] Testing on more devices
9495
- [ ] Speaking while muted stateflow
95-
- [X] Cleanup the retry behaviour in the RtcSession
9696
- [ ] Android SDK development.md cleanup (Daniel)
97+
- [ ] Logging is too verbose (rtc is very noisy), clean it up to focus on the essential for info and higher
98+
- [X] Cleanup the retry behaviour in the RtcSession
9799
- [X] SDK development guide for all teams
98100

99101
### 0.4.0 milestone

buildSrc/src/main/kotlin/io/getstream/video/android/Configuration.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ object Configuration {
66
const val minSdk = 24
77
const val majorVersion = 0
88
const val minorVersion = 0
9-
const val patchVersion = 18
9+
const val patchVersion = 20
1010
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
1111
const val versionCode = 1
1212
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion}-SNAPSHOT"

docusaurus/docs/Android/02-tutorials/01-video-calling.mdx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ This tutorial teaches you how to build Zoom/Whatsapp style video calling for you
1515
### Step 1 - Create a new project in Android Studio
1616

1717
1. Create a new project
18-
2. Select Phone & Template -> **Empty Activity**
18+
2. Select Phone & Tablet -> **Empty Activity**
1919
3. Name your project **VideoCall**.
2020

2121
Note that setup steps can vary slightly across Android Studio versions.
@@ -29,7 +29,7 @@ If you're new to android, note that there are 2 `build.gradle` files, you want t
2929
```groovy
3030
dependencies {
3131
// Stream Video Compose SDK
32-
implementation("io.getstream:stream-video-android-compose:0.0.18")
32+
implementation("io.getstream:stream-video-android-compose:0.0.19")
3333
3434
// Optionally add Jetpack Compose if Android studio didn't automatically include them
3535
implementation(platform("androidx.compose:compose-bom:2023.06.00"))
@@ -51,6 +51,10 @@ For this tutorial, we'll use the compose UI components.
5151

5252
### Step 3 - Create & Join a call
5353

54+
To keep this tutorial short and easy to understand we'll place all code in `MainActivity.kt`.
55+
For a production app you'd want to initialize the client in your Application class or DI module.
56+
You'd also want to use a viewmodel.
57+
5458
Open up `MainActivity.kt` and replace the **MainActivity** class with:
5559

5660
```kotlin
@@ -65,8 +69,7 @@ class MainActivity : ComponentActivity() {
6569
// step1 - create a user.
6670
val user = User(
6771
id = userId, // any string
68-
name = "Tutorial", // name and image are used in the UI
69-
role = "admin"
72+
name = "Tutorial" // name and image are used in the UI
7073
)
7174

7275
// step2 - initialize StreamVideo. For a production app we recommend adding the client to your Application class or di module.
@@ -126,13 +129,11 @@ Let's review what we did in the above code.
126129
**Create a user**. First we create a user object.
127130
You typically sync these users via a server side integration from your own backend.
128131
Alternatively, you can also use guest or anonymous users.
129-
The user's role allows you to configure permissions in the video call.
130132

131133
```kotlin
132134
val user = User(
133135
id = userId, // any string
134-
name = "Tutorial", // name and image are used in the UI
135-
role = "admin"
136+
name = "Tutorial" // name and image are used in the UI
136137
)
137138
```
138139

docusaurus/docs/Android/02-tutorials/02-audio-room.mdx

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The end result will look like the image below and support the following features
1212
* Calls run on Stream's global edge network for optimal latency and scalability.
1313
* There is no cap to how many listeners you can have in a room.
1414
* Listeners can raise their hand, and be invited to speak by the host.
15-
* Audio tracks are send multiple times for optimal reliability.
15+
* Audio tracks are sent multiple times for optimal reliability.
1616

1717
![Audio Room](../assets/audio-room.png)
1818

@@ -25,7 +25,7 @@ Setup steps can vary slightly across Android Studio versions.
2525
If you run into trouble, make sure to use the latest version of Android Studio.
2626

2727
1. Create a new project
28-
2. Select Phone & Template -> **Empty Activity**
28+
2. Select Phone & Tablet -> **Empty Activity**
2929
3. Name your project **AudioRoom**.
3030

3131
### Step 2 - Install the SDK & Setup the client
@@ -36,7 +36,7 @@ If you're new to android, note that there are 2 `build.gradle` files, you want t
3636
```groovy
3737
dependencies {
3838
// Stream Video Compose SDK
39-
implementation("io.getstream:stream-video-android-compose:0.0.18")
39+
implementation("io.getstream:stream-video-android-compose:0.0.19")
4040
4141
// Jetpack Compose (optional/ android studio typically adds them when you create a new project)
4242
implementation(platform("androidx.compose:compose-bom:2023.06.00"))
@@ -72,8 +72,7 @@ class MainActivity : ComponentActivity() {
7272
// step1 - create a user.
7373
val user = User(
7474
id = userId, // any string
75-
name = "Tutorial", // name and image are used in the UI
76-
role = "admin"
75+
name = "Tutorial" // name and image are used in the UI
7776
)
7877

7978
// step2 - initialize StreamVideo. For a production app we recommend adding the client to your Application class or di module.
@@ -90,7 +89,7 @@ class MainActivity : ComponentActivity() {
9089
lifecycleScope.launch {
9190
val result = call.join(create = true, createOptions = CreateCallOptions(
9291
members = listOf(
93-
MemberRequest(userId = "sophia", role="host", custom = emptyMap())
92+
MemberRequest(userId = userId, role="host", custom = emptyMap())
9493
), custom = mapOf(
9594
"title" to "Compose Trends",
9695
"description" to "Talk about how easy compose makes it to reuse and combine UI"
@@ -126,13 +125,11 @@ Let's review the example above and go over the details.
126125
**Create a user**. First we create a user object.
127126
You typically sync your users via a server side integration from your own backend.
128127
Alternatively, you can also use guest or anonymous users.
129-
The user's role allows you to configure permissions in the video call.
130128

131129
```kotlin
132130
val user = User(
133131
id = userId, // any string
134-
name = "Tutorial", // name and image are used in the UI
135-
role = "admin"
132+
name = "Tutorial" // name and image are used in the UI
136133
)
137134
```
138135

@@ -156,7 +153,7 @@ lifecycleScope.launch {
156153
val result = call.join(
157154
create = true, createOptions = CreateCallOptions(
158155
members = listOf(
159-
MemberRequest(userId = "sophia", role = "host", custom = emptyMap())
156+
MemberRequest(userId = userId, role = "host", custom = emptyMap())
160157
), custom = mapOf(
161158
"title" to "Compose Trends",
162159
"description" to "Talk about how easy compose makes it to reuse and combine UI"
@@ -170,7 +167,7 @@ lifecycleScope.launch {
170167
```
171168

172169
* This joins and creates a call with the type: "audio_room" and the specified callId.
173-
* The user with id sophia is granted the "host" role. You can create custom roles and grant them permissions to fit your app.
170+
* You add yourself as a member with the "host" role. You can create custom roles and grant them permissions to fit your app.
174171
* The `title` and `description` custom fields are set on the call object.
175172
* Shows an error toast if you fail to join an audio room.
176173

@@ -195,7 +192,6 @@ Replace the code in `setContent` with the following sample:
195192
```kotlin
196193
setContent {
197194
VideoTheme {
198-
val connection by call.state.connection.collectAsState()
199195
val connection by call.state.connection.collectAsState()
200196
val activeSpeakers by call.state.activeSpeakers.collectAsState()
201197
val audioLevel = activeSpeakers.firstOrNull()?.audioLevel?.collectAsState()
@@ -223,6 +219,9 @@ setContent {
223219
}
224220
```
225221

222+
All state for a call is available in `call.state`. In the example above we're observing the connection state and the active speakers.
223+
The [ParticipantState docs](../03-guides/03-call-and-participant-state.mdx) explain the available stateflow objects.
224+
226225
You'll see that the **AudioRoom** composable hasn't been implemented yet. In `MainActivity`, add the following `AudioRoom` composable:
227226

228227
```kotlin
@@ -269,7 +268,9 @@ public fun AudioRoom(
269268
}
270269
```
271270

272-
The audio room is pretty basic. It needs a **Controls**, **Participants**, and **Description** composable functions to work.
271+
The code above observes the participants, active speakers and backstage stateflow objects in `call.state`.
272+
273+
We still need to implement a **Controls**, **Participants**, and **Description** composable.
273274
Let's add those next.
274275

275276
```kotlin
@@ -311,12 +312,6 @@ That's it for the basics. Now when you run your app, you'll see the following UI
311312
The approach is the same for all components. We take the states of the call by observing `call.state` properties, such as `call.state.participants` and use it to power our UI.
312313
The [ParticipantState docs](../03-guides/03-call-and-participant-state.mdx) exposes all the state objects we need for the name, avatar, audio levels, speaking, etc.
313314

314-
To make this a little more interactive, let's join the audio room from your browser.
315-
316-
<TokenSnippet sampleApp='audio-rooms' displayStyle='join' />
317-
318-
On your Android device, you'll see the text update to 2 participants.
319-
320315
### Step 5 - Audio Room Controls & Permission
321316

322317
Any app that records the microphone needs to ask the user for permission. We'll do this now.
@@ -371,6 +366,20 @@ public fun Controls(
371366

372367
Now when you run the app, you'll see a button to disable/enable the microphone and to start or end the broadcast.
373368

369+
To make this a little more interactive, let's join the audio room from your browser.
370+
371+
<TokenSnippet sampleApp='audio-rooms' displayStyle='join' />
372+
373+
At first you won't be allowed to join the room since it's not live yet.
374+
By default the audio_room call type has backstage mode enabled. This makes it easy to try out your room and talk to your co-hosts before going live.
375+
You can enable/disable the usage of backstage mode in the dashboard.
376+
377+
Let's go live and join the call:
378+
379+
* Click go live on Android
380+
* On web join the room
381+
* You'll see the participant count increase to 2
382+
374383
### Step 6 - Participants UI
375384

376385
Time to build a pretty UI for the participants. Replace the `Participants` composable with the following:

dogfooding/src/main/kotlin/io/getstream/video/android/dogfooding/ui/join/CallJoinScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ fun CallJoinScreen(
7070
navigateToCallLobby: (callId: String) -> Unit,
7171
navigateUpToLogin: () -> Unit
7272
) {
73-
val uiState by callJoinViewModel.uiState.collectAsState()
73+
val uiState by callJoinViewModel.uiState.collectAsState(CallJoinUiState.Nothing)
7474

7575
HandleCallJoinUiState(
7676
callJoinUiState = uiState,

dogfooding/src/main/kotlin/io/getstream/video/android/dogfooding/ui/join/CallJoinViewModel.kt

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ import io.getstream.video.android.datastore.delegate.StreamUserDataStore
2626
import io.getstream.video.android.model.User
2727
import io.getstream.video.android.model.mapper.isValidCallId
2828
import io.getstream.video.android.model.mapper.toTypeAndId
29-
import kotlinx.coroutines.flow.MutableStateFlow
29+
import kotlinx.coroutines.flow.MutableSharedFlow
30+
import kotlinx.coroutines.flow.SharedFlow
3031
import kotlinx.coroutines.flow.SharingStarted
3132
import kotlinx.coroutines.flow.StateFlow
3233
import kotlinx.coroutines.flow.flatMapLatest
3334
import kotlinx.coroutines.flow.flowOf
34-
import kotlinx.coroutines.flow.stateIn
35+
import kotlinx.coroutines.flow.shareIn
3536
import kotlinx.coroutines.launch
3637
import java.util.UUID
3738
import javax.inject.Inject
@@ -42,13 +43,14 @@ class CallJoinViewModel @Inject constructor(
4243
) : ViewModel() {
4344
val user: StateFlow<User?> = dataStore.user
4445

45-
private val event: MutableStateFlow<CallJoinEvent> = MutableStateFlow(CallJoinEvent.Nothing)
46-
internal val uiState: StateFlow<CallJoinUiState> = event
46+
private val event: MutableSharedFlow<CallJoinEvent> = MutableSharedFlow()
47+
internal val uiState: SharedFlow<CallJoinUiState> = event
4748
.flatMapLatest { event ->
4849
when (event) {
4950
is CallJoinEvent.GoBackToLogin -> {
5051
flowOf(CallJoinUiState.GoBackToLogin)
5152
}
53+
5254
is CallJoinEvent.JoinCall -> {
5355
val call = joinCall(event.callId)
5456
flowOf(CallJoinUiState.JoinCompleted(callId = call.cid))
@@ -58,21 +60,21 @@ class CallJoinViewModel @Inject constructor(
5860
else -> flowOf(CallJoinUiState.Nothing)
5961
}
6062
}
61-
.stateIn(viewModelScope, SharingStarted.Lazily, CallJoinUiState.Nothing)
63+
.shareIn(viewModelScope, SharingStarted.Lazily, 0)
6264

6365
init {
6466
viewModelScope.launch {
6567
// We need to check whether the StreamVideo instance is initialised and go back to Login
6668
// if not. In the current implementation we only initialise after Login and if the
6769
// Android process is restored then the Login is skipped Stream Video is not initialised.
6870
if (!StreamVideo.isInstalled) {
69-
event.value = CallJoinEvent.GoBackToLogin
71+
event.emit(CallJoinEvent.GoBackToLogin)
7072
}
7173
}
7274
}
7375

7476
fun handleUiEvent(event: CallJoinEvent) {
75-
this.event.value = event
77+
viewModelScope.launch { this@CallJoinViewModel.event.emit(event) }
7678
}
7779

7880
private fun joinCall(callId: String? = null): Call {

dogfooding/src/main/kotlin/io/getstream/video/android/dogfooding/ui/lobby/CallLobbyScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private fun CallLobbyHeader(
111111
callLobbyViewModel: CallLobbyViewModel = hiltViewModel(),
112112
navigateUpToLogin: () -> Unit
113113
) {
114-
val uiState by callLobbyViewModel.uiState.collectAsState()
114+
val uiState by callLobbyViewModel.uiState.collectAsState(initial = CallLobbyUiState.Nothing)
115115

116116
HandleCallLobbyUiState(
117117
callLobbyUiState = uiState,

dogfooding/src/main/kotlin/io/getstream/video/android/dogfooding/ui/lobby/CallLobbyViewModel.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@ import io.getstream.video.android.core.StreamVideo
2626
import io.getstream.video.android.datastore.delegate.StreamUserDataStore
2727
import io.getstream.video.android.model.StreamCallId
2828
import io.getstream.video.android.model.User
29+
import kotlinx.coroutines.flow.MutableSharedFlow
2930
import kotlinx.coroutines.flow.MutableStateFlow
31+
import kotlinx.coroutines.flow.SharedFlow
3032
import kotlinx.coroutines.flow.SharingStarted
3133
import kotlinx.coroutines.flow.StateFlow
3234
import kotlinx.coroutines.flow.flatMapLatest
3335
import kotlinx.coroutines.flow.flowOf
3436
import kotlinx.coroutines.flow.onCompletion
35-
import kotlinx.coroutines.flow.stateIn
37+
import kotlinx.coroutines.flow.shareIn
38+
import kotlinx.coroutines.launch
3639
import javax.inject.Inject
3740

3841
@HiltViewModel
@@ -54,8 +57,8 @@ class CallLobbyViewModel @Inject constructor(
5457
private val _isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
5558
internal val isLoading: StateFlow<Boolean> = _isLoading
5659

57-
private val event: MutableStateFlow<CallLobbyEvent> = MutableStateFlow(CallLobbyEvent.Nothing)
58-
internal val uiState: StateFlow<CallLobbyUiState> = event
60+
private val event: MutableSharedFlow<CallLobbyEvent> = MutableSharedFlow()
61+
internal val uiState: SharedFlow<CallLobbyUiState> = event
5962
.flatMapLatest { event ->
6063
when (event) {
6164
is CallLobbyEvent.JoinCall -> flowOf(CallLobbyUiState.JoinCompleted)
@@ -64,10 +67,10 @@ class CallLobbyViewModel @Inject constructor(
6467
}
6568
}
6669
.onCompletion { _isLoading.value = false }
67-
.stateIn(viewModelScope, SharingStarted.Lazily, CallLobbyUiState.Nothing)
70+
.shareIn(viewModelScope, SharingStarted.Lazily, 0)
6871

6972
fun handleUiEvent(event: CallLobbyEvent) {
70-
this.event.value = event
73+
viewModelScope.launch { this@CallLobbyViewModel.event.emit(event) }
7174
}
7275

7376
fun enableCamera(enabled: Boolean) {

dogfooding/src/main/kotlin/io/getstream/video/android/dogfooding/ui/login/LoginScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fun LoginScreen(
7373
loginViewModel: LoginViewModel = hiltViewModel(),
7474
navigateToCallJoin: () -> Unit
7575
) {
76-
val uiState by loginViewModel.uiState.collectAsState()
76+
val uiState by loginViewModel.uiState.collectAsState(initial = LoginUiState.Nothing)
7777
val isLoading by remember(uiState) { mutableStateOf(uiState !is LoginUiState.Nothing) }
7878
var isShowingEmailLoginDialog by remember { mutableStateOf(false) }
7979

0 commit comments

Comments
 (0)