Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandar-apostolov committed May 27, 2024
2 parents 448590e + d7a2c5a commit 914e968
Show file tree
Hide file tree
Showing 19 changed files with 353 additions and 229 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ object Configuration {
const val minSdk = 24
const val majorVersion = 1
const val minorVersion = 0
const val patchVersion = 1
const val patchVersion = 2
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
const val versionCode = 24
const val versionCode = 25
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT"
const val artifactGroup = "io.getstream"
const val streamVideoCallGooglePlayVersion = "1.1.1"
const val streamVideoCallGooglePlayVersion = "1.1.2"
const val streamWebRtcVersionName = "1.1.1"
}
2 changes: 1 addition & 1 deletion docusaurus/docs/Android/02-tutorials/01-video-calling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ If you're new to android, note that there are 2 `build.gradle` files, you want t
```kotlin
dependencies {
// Stream Video Compose SDK
implementation("io.getstream:stream-video-android-ui-compose:1.0.1")
implementation("io.getstream:stream-video-android-ui-compose:1.0.2")

// Optionally add Jetpack Compose if Android studio didn't automatically include them
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Android/02-tutorials/02-audio-room.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,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-ui-compose:1.0.1")
implementation("io.getstream:stream-video-android-ui-compose:1.0.2")
// Jetpack Compose (optional/ android studio typically adds them when you create a new project)
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
Expand Down
2 changes: 1 addition & 1 deletion docusaurus/docs/Android/02-tutorials/03-livestream.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ If you're new to android, note that there are 2 `build.gradle` files, you want t
```kotlin
dependencies {
// Stream Video Compose SDK
implementation("io.getstream:stream-video-android-ui-compose:1.0.1")
implementation("io.getstream:stream-video-android-ui-compose:1.0.2")

// Jetpack Compose (optional/ android studio typically adds them when you create a new project)
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
Expand Down
55 changes: 55 additions & 0 deletions docusaurus/docs/Android/05-ui-cookbook/16-watching-livestream.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Watching a Livestream

This guide describes how to watch a livestream using our SDK.

:::note
- Read our [tutorial](https://getstream.io/video/sdk/android/tutorial/livestreaming) for more info on how to implement livestreaming host and viewer apps.
- Go to the [HLS section](https://getstream.io/video/sdk/android/tutorial/livestreaming/#optional-viewing-a-livestream-with-hls) of our tutorial to find out how to view a HLS livestream.
:::

If you want to watch a WebRTC livestream, then you can either use our `LivestreamPlayer`, or build your own component.

## LivestreamPlayer UI component

The `LivestreamPlayer` component shows the following information:

- _live_ indicator
- number of participants
- duration
- _pause/resume_ on tap functionality
- _mute/unmute_ incoming sound

### Basic usage

For standard usage, you just need to pass a `call` object:

```kotlin
LivestreamPlayer(call = call)
```

![watching-livestream-1.png](../assets/cookbook/watching-livestream-1.png)

### Customization

This is the full signature of the component:

```kotlin
@Composable
public fun LivestreamPlayer(
modifier: Modifier = Modifier,
call: Call,
enablePausing: Boolean,
onPausedPlayer: ((isPaused: Boolean) -> Unit)?,
backstageContent: @Composable BoxScope.(Call) -> Unit,
rendererContent: @Composable BoxScope.(Call) -> Unit,
overlayContent: @Composable BoxScope.(Call) -> Unit
)
```

- `modifier`: Used to apply styling to the component, such as extra borders, background, elevation, size or shape and more.
- `call`: The livestream call to watch.
- `enablePausing`: Controls the _pause/resume_ on tap functionality.
- `onPausedPlayer`: Event handler for the _pause/resume_ on tap event.
- `backstageContent`: Content to show when the call is in backstage mode (i.e. the host has not started the livestream yet)
- `rendererContent`: Used to control how the video feed is rendered on screen. By default, it uses the `VideoRenderer` component under the hood.
- `overlayContent`: Used for customizing the overlay that contains participant count, duration and other info.
119 changes: 92 additions & 27 deletions docusaurus/docs/Android/06-advanced/01-ringing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,117 @@
title: Ringing
description: How to ring the call and notify all members
---
The `Call` object provides several options to ring and notify users about a call.
### Create and start a ringing call

## Ring & Notify

You can ring or notify a call. Ring triggers an incoming call style UI for all members of the call.
Notify is a smaller notification. Commonly used when someone starts a livestream, joins a zoom style call etc.
To create a ring call, we need to set the `ring` flag to `true` and provide the list of members we want to call. It is important to note that the caller should also be included in the list of members.
For this, you can use the `create` method from the `Call` object.
```kotlin
val call = client.call("default", "123")
call.create(ring = true, members = listOf("caller-id", "receiver-1", "receiver-2"))
```
When ring is `true`, a push notification will be sent to the members, provided you have the required setup for push notifications.
For more details around push notifications, please check [this page](./02-push-notifications/01-overview.mdx).
If ring is `false`, no push notification will be sent.

### Ring an existing call
If you are sure that a call exists, you can use the `get` method instead:
```kotlin
val call = client.call("default", "123")
call.create()
call.get()
call.ring()
call.notify()

// you can also ring and notify as a side effect of joining or creating a call
call.create(ring=true, notify=false)
call.join(ring=true, notify=false)
```
The `get()` - `ring()` combination is better used for when calls are created and managed externally via another system.

Users who receive the incoming call can either accept or reject it.

### Monitor the outgoing call state
The state of the ringing call is available via the `StreamVideo` client.
```kotlin
call.accept()
call.reject()
val client = StreamVideo.instance()
val ringingCall = client.state.ringingCall
```

The state for ringing calls is available here:
This will give you a `StateFlow` which can be monitored.
```kotlin
ringingCall.collectLatest { call ->
// There is a ringing call
}
```
or simply just get a current value.
```kotlin
val call = ringingCall.value
```
### Canceling an outgoing call
To cancel an outgoing call you can simply `reject` the call from the caller side.
The `reject()` method will notify the endpoint that the call is being rejected and corresponding
events will be sent. In order to cleanup on the caller side, a call to `leave()` is required.
These two usually go together, unless there is a specific reason to keep the channel open for further
events.

```kotlin
val incomingCall = client.state.incomingCall.value
val accepted = call.state.acceptedBy.value // list of user ids who accepted
val rejected = call.state.rejectedBy.value // list of user ids who rejected
call.reject()
call.leave()
```

### Push Setup
### Handle an incoming call

3 types of push notifications are triggered:
If you have setup [push notifications](./02-push-notifications/01-overview.mdx) properly a "member"
will receive a push notification about an incoming call.

* call.ring
* call.notification
* call.live_started
By default the SDK will show the push notification (with a call style) with an option to either
accept or decline the call.

When the user clicks on a push notification. There is an intent fired `ACTION_REJECT_CALL` or `ACTION_ACCEPT_CALL`.
The

You can learn more about how to setup [push notifications in the docs](./02-push-notifications/01-overview.mdx).
The docs also explain how to customize the notifications.

### Incoming Calls
### Accept an incoming call

The compose SDK provides built-in components to render and handle an incoming call.

The compose SDK provides built-in components to render an incoming call.
One of them is `StreamCallActivity`. This abstract activity handles everything that is needed for a call.
Stream also provides a default compose implementation of this activity called `ComposeStreamCallActivity`.

These components are already predefined and registered in the SDK. If you want to customize them you can easily extend them as any other activity in Android.

For more details check:
* [UI Component docs for incoming calls](../04-ui-components/04-call/04-ringing-call.mdx)
* UI Cookbook how to build [your own incoming call UI](../05-ui-cookbook/05-incoming-and-outgoing-call.mdx)
* UI Cookbook how to build [your own incoming call UI](../05-ui-cookbook/05-incoming-and-outgoing-call.mdx)

The Stream SDK provides a way to accept a call within the code so if you are building a new UI, you can do this via the SDK API.
```kotlin
call.accept()
call.join()
```
The above calls are all you need to accept and join a call.

Its important to note that if there is already an ongoing call you first have to leave that call.
```kotlin
val client = StreamVideo.instance()
val activeCall = client.start.activeCall.value
if (activeCall != null) {
activeCall.leave()
}
```
All this needs to be done with a component that handles the accept action.
```xml
<action android:name="io.getstream.video.android.action.ACCEPT_CALL" />
```

### Reject an incoming call

Clicking the notification will automatically reject the call.
There are certain instances that you might want to do this manually in your code.

Stream offers a simple API to do this.
```kotlin
call.reject()
```

Note that rejecting the call will notify the caller and other members that the participant rejected
the call. However it will not clean up the local `call` state.
For this you need to leave the call by using:
```kotlin
call.leave()
```

2 changes: 1 addition & 1 deletion docusaurus/docs/Android/06-advanced/07-chat-with-video.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Let the project sync. It should have all the dependencies required for you to fi
```groovy
dependencies {
// Stream Video Compose SDK
implementation("io.getstream:stream-video-android-ui-compose:1.0.1")
implementation("io.getstream:stream-video-android-ui-compose:1.0.2")
// Stream Chat
implementation(libs.stream.chat.compose)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions stream-video-android-core/api/stream-video-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ public final class io/getstream/video/android/core/CallState {
public final fun updateFromResponse (Lorg/openapitools/client/models/StopLiveResponse;)V
public final fun updateFromResponse (Lorg/openapitools/client/models/UpdateCallResponse;)V
public final fun updateParticipant (Lio/getstream/video/android/core/ParticipantState;)V
public final fun updateParticipantSortingOrder (Ljava/util/Comparator;)V
public final fun updateParticipantVisibility (Ljava/lang/String;Lio/getstream/video/android/core/model/VisibilityOnScreenState;)V
public final fun updateParticipantVisibilityFlow (Lkotlinx/coroutines/flow/Flow;)V
public final fun upsertParticipants (Ljava/util/List;)V
Expand Down Expand Up @@ -5907,6 +5908,7 @@ public final class io/getstream/video/android/model/User$$serializer : kotlinx/s
}

public final class io/getstream/video/android/model/User$Companion {
public final fun anonymous ()Lio/getstream/video/android/model/User;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@ class LivestreamTest : IntegrationTestBase() {
assertSuccess(response)

val rtmp = call.state.ingress.value?.rtmp
println("client token: ${clientImpl.token}")
println("rtmp address: ${rtmp?.address}")
println("rtmp streamKey: ${rtmp?.streamKey}")

// streamKey should be just the token, not apiKey/token
assertThat(rtmp?.streamKey?.equals(clientImpl.token)).isTrue()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -293,56 +293,12 @@ public class CallState(
val livestream: StateFlow<ParticipantState.Video?> = livestreamFlow.debounce(1000)
.stateIn(scope, SharingStarted.WhileSubscribed(10000L), null)

internal val sortedParticipantsFlow = channelFlow {
// uses a channel flow to handle concurrency and 3 things updating: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/channel-flow.html

fun emitSorted() {
val participants = participants.value
val pinned = _pinnedParticipants.value
var lastParticipants: List<ParticipantState>? = null
val sorted = participants.sortedWith(
compareBy(
{ pinned.containsKey(it.sessionId) },
{ it.screenSharingEnabled.value },
{ it.dominantSpeaker.value },
{ it.videoEnabled.value },
{ it.lastSpeakingAt.value },
{ it.joinedAt.value },
{ it.userId.value },
),
).reversed()
scope.launch {
if (lastParticipants != sorted) {
send(sorted)
lastParticipants = sorted
}
}
}

scope.launch {
_participants.collect {
emitSorted()
}
}
// Since participant state exposes it's own stateflows this is a little harder to do than usual
// we need to listen to the events and update the flow when it changes

// emit the sorted list
emitSorted()

// TODO: could optimize performance by subscribing only to relevant events
call.subscribe {
emitSorted()
}

scope.launch {
_pinnedParticipants.collect {
emitSorted()
}
}

awaitClose {}
}
private var _sortedParticipantsState = SortedParticipantsState(
scope,
call,
_participants,
_pinnedParticipants,
)

/**
* Sorted participants based on
Expand All @@ -355,12 +311,16 @@ public class CallState(
*
* Debounced 100ms to avoid rapid changes
*/
val sortedParticipants = SortedParticipantsState(
scope,
call,
_participants,
_pinnedParticipants,
).asFlow().debounce(100)
val sortedParticipants = _sortedParticipantsState.asFlow().debounce(100)

/**
* Update participant sorting order
*
* @param comparator a new comparator to be used in [sortedParticipants] flow.
*/
fun updateParticipantSortingOrder(
comparator: Comparator<ParticipantState>,
) = _sortedParticipantsState.updateComparator(comparator)

/** Members contains the list of users who are permanently associated with this call. This includes users who are currently not active in the call
* As an example if you invite "john", "bob" and "jane" to a call and only Jane joins.
Expand Down Expand Up @@ -527,7 +487,7 @@ public class CallState(
if (it != null) {
val token = call.clientImpl.token
val apiKey = call.clientImpl.apiKey
Ingress(rtmp = RTMP(address = it.rtmp.address, streamKey = "$apiKey/$token"))
Ingress(rtmp = RTMP(address = it.rtmp.address, streamKey = token))
} else {
null
}
Expand Down
Loading

0 comments on commit 914e968

Please sign in to comment.