Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
skydoves committed Jul 4, 2023
2 parents f857e09 + afa1489 commit 145e038
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 62 deletions.
24 changes: 13 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,17 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro

### 0.2.0 milestone

- [ ] Local Video disconnects sometimes (ICE restarts issue for the publisher. we're waiting for the backend support)
- [ ] Deeplink support for video call demo & dogfooding app (skip auth for the video demo, keep it for dogfooding)
- [ ] Chat Integration
- [ ] XML version of VideoRenderer
- [ ] Call Analytics stateflow
- [ ] Automatically handle pagination and sorting on > 6 participants
- [ ] Make it easy to test ringing support
- [ ] publish app on play store
- [ ] report version number of SDK on all API calls
- [ ] Bug: java.net.UnknownHostException: Unable to resolve host "hint.stream-io-video.com" isn't throw but instead logged as INFO
- [ ] Deeplink support for video call demo & dogfooding app (skip auth for the video demo, keep it for dogfooding) (Daniel)
- [ ] Chat Integration (Jaewoong)
- [ ] XML version of VideoRenderer (Jaewoong)
- [ ] Local Video disconnects sometimes (ICE restarts issue for the publisher. we're waiting for the backend support) (Thierry)
- [ ] Call Analytics stateflow (Thierry)
- [ ] Automatically handle pagination and sorting on > 6 participants
- [ ] Ringing: Make it easy to test
- [ ] Ringing: Make a list of what needs to be configurable
- [ ] Publish app on play store
- [ ] Report version number of SDK on all API calls (Daniel)
- [ ] Bug: java.net.UnknownHostException: Unable to resolve host "hint.stream-io-video.com" isn't throw but instead logged as INFO (Daniel)
- [ ] Bug: screensharing is broken. android doesn’t receive/render (not sure) the screenshare. video shows up as the gray avatar
- [X] Reactions
- [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)
Expand All @@ -92,8 +93,9 @@ Video roadmap and changelog is available [here](https://github.com/GetStream/pro
- [ ] Test coverage
- [ ] Testing on more devices
- [ ] Speaking while muted stateflow
- [X] Cleanup the retry behaviour in the RtcSession
- [ ] Android SDK development.md cleanup (Daniel)
- [ ] Logging is too verbose (rtc is very noisy), clean it up to focus on the essential for info and higher
- [X] Cleanup the retry behaviour in the RtcSession
- [X] SDK development guide for all teams

### 0.4.0 milestone
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ object Configuration {
const val minSdk = 24
const val majorVersion = 0
const val minorVersion = 0
const val patchVersion = 18
const val patchVersion = 20
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
const val versionCode = 1
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion}-SNAPSHOT"
Expand Down
15 changes: 8 additions & 7 deletions docusaurus/docs/Android/02-tutorials/01-video-calling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This tutorial teaches you how to build Zoom/Whatsapp style video calling for you
### Step 1 - Create a new project in Android Studio

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

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

### Step 3 - Create & Join a call

To keep this tutorial short and easy to understand we'll place all code in `MainActivity.kt`.
For a production app you'd want to initialize the client in your Application class or DI module.
You'd also want to use a viewmodel.

Open up `MainActivity.kt` and replace the **MainActivity** class with:

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

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

```kotlin
val user = User(
id = userId, // any string
name = "Tutorial", // name and image are used in the UI
role = "admin"
name = "Tutorial" // name and image are used in the UI
)
```

Expand Down
47 changes: 28 additions & 19 deletions docusaurus/docs/Android/02-tutorials/02-audio-room.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The end result will look like the image below and support the following features
* Calls run on Stream's global edge network for optimal latency and scalability.
* There is no cap to how many listeners you can have in a room.
* Listeners can raise their hand, and be invited to speak by the host.
* Audio tracks are send multiple times for optimal reliability.
* Audio tracks are sent multiple times for optimal reliability.

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

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

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

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

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

```kotlin
val user = User(
id = userId, // any string
name = "Tutorial", // name and image are used in the UI
role = "admin"
name = "Tutorial" // name and image are used in the UI
)
```

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

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

Expand All @@ -195,7 +192,6 @@ Replace the code in `setContent` with the following sample:
```kotlin
setContent {
VideoTheme {
val connection by call.state.connection.collectAsState()
val connection by call.state.connection.collectAsState()
val activeSpeakers by call.state.activeSpeakers.collectAsState()
val audioLevel = activeSpeakers.firstOrNull()?.audioLevel?.collectAsState()
Expand Down Expand Up @@ -223,6 +219,9 @@ setContent {
}
```

All state for a call is available in `call.state`. In the example above we're observing the connection state and the active speakers.
The [ParticipantState docs](../03-guides/03-call-and-participant-state.mdx) explain the available stateflow objects.

You'll see that the **AudioRoom** composable hasn't been implemented yet. In `MainActivity`, add the following `AudioRoom` composable:

```kotlin
Expand Down Expand Up @@ -269,7 +268,9 @@ public fun AudioRoom(
}
```

The audio room is pretty basic. It needs a **Controls**, **Participants**, and **Description** composable functions to work.
The code above observes the participants, active speakers and backstage stateflow objects in `call.state`.

We still need to implement a **Controls**, **Participants**, and **Description** composable.
Let's add those next.

```kotlin
Expand Down Expand Up @@ -311,12 +312,6 @@ That's it for the basics. Now when you run your app, you'll see the following UI
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.
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.

To make this a little more interactive, let's join the audio room from your browser.

<TokenSnippet sampleApp='audio-rooms' displayStyle='join' />

On your Android device, you'll see the text update to 2 participants.

### Step 5 - Audio Room Controls & Permission

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

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

To make this a little more interactive, let's join the audio room from your browser.

<TokenSnippet sampleApp='audio-rooms' displayStyle='join' />

At first you won't be allowed to join the room since it's not live yet.
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.
You can enable/disable the usage of backstage mode in the dashboard.

Let's go live and join the call:

* Click go live on Android
* On web join the room
* You'll see the participant count increase to 2

### Step 6 - Participants UI

Time to build a pretty UI for the participants. Replace the `Participants` composable with the following:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fun CallJoinScreen(
navigateToCallLobby: (callId: String) -> Unit,
navigateUpToLogin: () -> Unit
) {
val uiState by callJoinViewModel.uiState.collectAsState()
val uiState by callJoinViewModel.uiState.collectAsState(CallJoinUiState.Nothing)

HandleCallJoinUiState(
callJoinUiState = uiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ import io.getstream.video.android.datastore.delegate.StreamUserDataStore
import io.getstream.video.android.model.User
import io.getstream.video.android.model.mapper.isValidCallId
import io.getstream.video.android.model.mapper.toTypeAndId
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
Expand All @@ -42,13 +43,14 @@ class CallJoinViewModel @Inject constructor(
) : ViewModel() {
val user: StateFlow<User?> = dataStore.user

private val event: MutableStateFlow<CallJoinEvent> = MutableStateFlow(CallJoinEvent.Nothing)
internal val uiState: StateFlow<CallJoinUiState> = event
private val event: MutableSharedFlow<CallJoinEvent> = MutableSharedFlow()
internal val uiState: SharedFlow<CallJoinUiState> = event
.flatMapLatest { event ->
when (event) {
is CallJoinEvent.GoBackToLogin -> {
flowOf(CallJoinUiState.GoBackToLogin)
}

is CallJoinEvent.JoinCall -> {
val call = joinCall(event.callId)
flowOf(CallJoinUiState.JoinCompleted(callId = call.cid))
Expand All @@ -58,21 +60,21 @@ class CallJoinViewModel @Inject constructor(
else -> flowOf(CallJoinUiState.Nothing)
}
}
.stateIn(viewModelScope, SharingStarted.Lazily, CallJoinUiState.Nothing)
.shareIn(viewModelScope, SharingStarted.Lazily, 0)

init {
viewModelScope.launch {
// We need to check whether the StreamVideo instance is initialised and go back to Login
// if not. In the current implementation we only initialise after Login and if the
// Android process is restored then the Login is skipped Stream Video is not initialised.
if (!StreamVideo.isInstalled) {
event.value = CallJoinEvent.GoBackToLogin
event.emit(CallJoinEvent.GoBackToLogin)
}
}
}

fun handleUiEvent(event: CallJoinEvent) {
this.event.value = event
viewModelScope.launch { this@CallJoinViewModel.event.emit(event) }
}

private fun joinCall(callId: String? = null): Call {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ private fun CallLobbyHeader(
callLobbyViewModel: CallLobbyViewModel = hiltViewModel(),
navigateUpToLogin: () -> Unit
) {
val uiState by callLobbyViewModel.uiState.collectAsState()
val uiState by callLobbyViewModel.uiState.collectAsState(initial = CallLobbyUiState.Nothing)

HandleCallLobbyUiState(
callLobbyUiState = uiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ import io.getstream.video.android.core.StreamVideo
import io.getstream.video.android.datastore.delegate.StreamUserDataStore
import io.getstream.video.android.model.StreamCallId
import io.getstream.video.android.model.User
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import javax.inject.Inject

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

private val event: MutableStateFlow<CallLobbyEvent> = MutableStateFlow(CallLobbyEvent.Nothing)
internal val uiState: StateFlow<CallLobbyUiState> = event
private val event: MutableSharedFlow<CallLobbyEvent> = MutableSharedFlow()
internal val uiState: SharedFlow<CallLobbyUiState> = event
.flatMapLatest { event ->
when (event) {
is CallLobbyEvent.JoinCall -> flowOf(CallLobbyUiState.JoinCompleted)
Expand All @@ -64,10 +67,10 @@ class CallLobbyViewModel @Inject constructor(
}
}
.onCompletion { _isLoading.value = false }
.stateIn(viewModelScope, SharingStarted.Lazily, CallLobbyUiState.Nothing)
.shareIn(viewModelScope, SharingStarted.Lazily, 0)

fun handleUiEvent(event: CallLobbyEvent) {
this.event.value = event
viewModelScope.launch { this@CallLobbyViewModel.event.emit(event) }
}

fun enableCamera(enabled: Boolean) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ fun LoginScreen(
loginViewModel: LoginViewModel = hiltViewModel(),
navigateToCallJoin: () -> Unit
) {
val uiState by loginViewModel.uiState.collectAsState()
val uiState by loginViewModel.uiState.collectAsState(initial = LoginUiState.Nothing)
val isLoading by remember(uiState) { mutableStateOf(uiState !is LoginUiState.Nothing) }
var isShowingEmailLoginDialog by remember { mutableStateOf(false) }

Expand Down
Loading

0 comments on commit 145e038

Please sign in to comment.