Skip to content

Commit

Permalink
Merge branch 'develop' into feature/audio-filter
Browse files Browse the repository at this point in the history
  • Loading branch information
kanat authored Oct 2, 2024
2 parents f9c331a + 3f45333 commit f56eb73
Show file tree
Hide file tree
Showing 15 changed files with 449 additions and 128 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 = 14
const val patchVersion = 15
const val versionName = "$majorVersion.$minorVersion.$patchVersion"
const val versionCode = 38
const val versionCode = 39
const val snapshotVersionName = "$majorVersion.$minorVersion.${patchVersion + 1}-SNAPSHOT"
const val artifactGroup = "io.getstream"
const val streamVideoCallGooglePlayVersion = "1.1.7"
const val streamVideoCallGooglePlayVersion = "1.1.8"
const val streamWebRtcVersionName = "1.1.1"
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,5 @@ fun MyRingingCallScreen() {
So you'll be able to render your own composable or navigate to a different screen depending on the call state.
## Sounds
The SDK plays default sounds for incoming and outgoing calls. You can customize the sounds by passing your own instance of the `Sounds` class to `StreamVideoBuilder`.
`Sounds` has two properties, `incomingCallSound` and `outgoingCallSound`. You need to assign raw resource identifiers to these properties. These identifiers correspond to audio files in your project's `res/raw` directory.

To disable sounds, pass `null` to `incomingCallSound` or `outgoingCallSound`.
The SDK plays sounds for incoming and outgoing calls. Read [here](../06-advanced/01-ringing.mdx#sounds) for more details.
138 changes: 124 additions & 14 deletions docusaurus/docs/Android/06-advanced/01-ringing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,70 @@
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

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.
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).

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.get()
call.ring()
```
The `get()` - `ring()` combination is better used for when calls are created and managed externally via another system.

The `get()` - `ring()` combination is better used for when calls are created and managed externally
via another system.

### Monitor the outgoing call state

The state of the ringing call is available via the `StreamVideo` client.

```kotlin
val client = StreamVideo.instance()
val ringingCall = client.state.ringingCall
```

This will give you a `StateFlow` which can be monitored.

```kotlin
ringingCall.collectLatest { call ->
// There is a ringing 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
These two usually go together, unless there is a specific reason to keep the channel open for
further
events.

```kotlin
Expand All @@ -60,42 +81,56 @@ will receive a push notification about an incoming call.
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`.
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).
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.

### Accept an incoming call

The compose SDK provides built-in components to render and handle 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`.
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.
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.

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()
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" />
```

Expand All @@ -105,14 +140,89 @@ 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()
```

## Ringing sounds

The SDK plays sounds for incoming and outgoing calls. It bundles two sounds for this purpose.

### Customizing the ringing sounds

The ringing sounds can be customized in two ways:
1. Override the bundled resources inside your application.
2. Provide your own `RingingConfig` to the `StreamVideoBuilder`.

#### Override the bundled resources

The resources are: `/raw/call_incoming_sound.mp3` and `/raw/call_outgoing_sound.mp3`.
You can place your own `call_incoming_sound.mp3` and `call_outgoing_sound.mp3` files in the `res/raw` directory of your app.

#### Provide your own `RingingConfig`

You can customize the sounds by creating a `RingingConfig`.

:::note
Currently, the SDK accepts a `Sounds` object in the builder, so once you have a `RingingConfig`, you can
create a `Sounds` object via `ringingConfig.toSounds()` and pass it to the `StreamVideoBuilder`.
:::

The `RingingConfig` interface defines two properties:

- `incomingCallSoundUri`: The URI for the incoming call sound.
- `outgoingCallSoundUri`: The URI for the outgoing call sound.

You can implement this interface and provide your own values for the properties (e.g. a user chosen
URI). After that, create a `Sounds` object (e.g. `ringingConfig.toSounds()` or `Sounds(ringingConfig)`) and pass it to the SDK builder.
If one of the `Uri`s is null the SDK will simply not play that sound and log an error.

:::caution
The `Sounds` class is deprecated and will entirely be replaced by `RingingConfig` in the future.
The current `Sounds` constructor that accepts two integers will always return an `emptyRingingConfig()`
with muted sounds.
:::

`RingingConfig` can also be created via several factory methods:

- `defaultResourcesRingingConfig` - This method returns a `RingingConfig` that uses the SDK's
default sounds for both incoming and outgoing calls
- `resRingingConfig` - This method returns a `RingingConfig` that uses a resource identifier for both incoming and outgoing calls.
- `uriRingingConfig(Uri, Uri)` - Returns a `RingingConfig` that is configured with two `Uri` objects for the corresponding sounds.
- `emptyRingingConfig` - The SDK will not play any sounds for incoming and outgoing calls.

#### Customization examples

For example, to create a sound config that only includes an incoming sound and no outgoing sound, you can extend `RingingConfig` as shown below, setting `outgoingCallSoundUri` to `null`:

```kotlin
class IncomingOnlyRingingConfig : RingingConfig {
override val incomingCallSoundUri: Uri =
Uri.parse("android.resource://$packageName/${R.raw.custom_incoming_sound}")

override val outgoingCallSoundUri: Uri? = null // Outgoing sound will be muted
}
```

Another use case may include a user-chosen sound or a custom sound that is not bundled:

```kotlin
data class UserRingingConfig(
val incomingCallSoundUri: Uri,
val outgoingCallSoundUri: Uri
) : RingingConfig
```

:::note
If the sound resources cannot be found (Uri or resource ID) the SDK will simply not play any sound and
log the error.
:::
38 changes: 23 additions & 15 deletions stream-video-android-core/api/stream-video-android-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -478,15 +478,14 @@ public final class io/getstream/video/android/core/MemberState {
}

public final class io/getstream/video/android/core/MicrophoneManager {
public fun <init> (Lio/getstream/video/android/core/MediaManagerImpl;ZI)V
public fun <init> (Lio/getstream/video/android/core/MediaManagerImpl;I)V
public final fun canHandleDeviceSwitch ()Z
public final fun cleanup ()V
public final fun disable (Z)V
public static synthetic fun disable$default (Lio/getstream/video/android/core/MicrophoneManager;ZILjava/lang/Object;)V
public final fun getAudioUsage ()I
public final fun getDevices ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getMediaManager ()Lio/getstream/video/android/core/MediaManagerImpl;
public final fun getPreferSpeakerphone ()Z
public final fun getSelectedDevice ()Lkotlinx/coroutines/flow/StateFlow;
public final fun getStatus ()Lkotlinx/coroutines/flow/StateFlow;
public final fun isEnabled ()Lkotlinx/coroutines/flow/StateFlow;
Expand Down Expand Up @@ -868,11 +867,8 @@ public abstract interface class io/getstream/video/android/core/audio/AudioHandl

public final class io/getstream/video/android/core/audio/AudioSwitchHandler : io/getstream/video/android/core/audio/AudioHandler {
public static final field Companion Lio/getstream/video/android/core/audio/AudioSwitchHandler$Companion;
public fun <init> (Landroid/content/Context;ZLkotlin/jvm/functions/Function2;)V
public final fun getAudioDeviceChangeListener ()Lkotlin/jvm/functions/Function2;
public final fun getPreferSpeakerphone ()Z
public fun <init> (Landroid/content/Context;Lkotlin/jvm/functions/Function2;)V
public final fun selectDevice (Lcom/twilio/audioswitch/AudioDevice;)V
public final fun setAudioDeviceChangeListener (Lkotlin/jvm/functions/Function2;)V
public fun start ()V
public fun stop ()V
}
Expand Down Expand Up @@ -4476,17 +4472,29 @@ public final class io/getstream/video/android/core/socket/SocketState$NotConnect
public fun toString ()Ljava/lang/String;
}

public abstract interface class io/getstream/video/android/core/sounds/RingingConfig {
public abstract fun getIncomingCallSoundUri ()Landroid/net/Uri;
public abstract fun getOutgoingCallSoundUri ()Landroid/net/Uri;
}

public final class io/getstream/video/android/core/sounds/RingingConfigKt {
public static final fun defaultResourcesRingingConfig (Landroid/content/Context;)Lio/getstream/video/android/core/sounds/RingingConfig;
public static final fun deviceRingtoneRingingConfig (Landroid/content/Context;)Lio/getstream/video/android/core/sounds/RingingConfig;
public static final fun emptyRingingConfig ()Lio/getstream/video/android/core/sounds/RingingConfig;
public static final fun resRingingConfig (Landroid/content/Context;II)Lio/getstream/video/android/core/sounds/RingingConfig;
public static final fun toSounds (Lio/getstream/video/android/core/sounds/RingingConfig;)Lio/getstream/video/android/core/sounds/Sounds;
public static final fun uriRingingConfig (Landroid/net/Uri;Landroid/net/Uri;)Lio/getstream/video/android/core/sounds/RingingConfig;
}

public final class io/getstream/video/android/core/sounds/Sounds {
public fun <init> ()V
public fun <init> (Ljava/lang/Integer;Ljava/lang/Integer;)V
public synthetic fun <init> (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/Integer;
public final fun component2 ()Ljava/lang/Integer;
public final fun copy (Ljava/lang/Integer;Ljava/lang/Integer;)Lio/getstream/video/android/core/sounds/Sounds;
public static synthetic fun copy$default (Lio/getstream/video/android/core/sounds/Sounds;Ljava/lang/Integer;Ljava/lang/Integer;ILjava/lang/Object;)Lio/getstream/video/android/core/sounds/Sounds;
public fun <init> (II)V
public synthetic fun <init> (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Lio/getstream/video/android/core/sounds/RingingConfig;)V
public final fun component1 ()Lio/getstream/video/android/core/sounds/RingingConfig;
public final fun copy (Lio/getstream/video/android/core/sounds/RingingConfig;)Lio/getstream/video/android/core/sounds/Sounds;
public static synthetic fun copy$default (Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingConfig;ILjava/lang/Object;)Lio/getstream/video/android/core/sounds/Sounds;
public fun equals (Ljava/lang/Object;)Z
public final fun getIncomingCallSound ()Ljava/lang/Integer;
public final fun getOutgoingCallSound ()Ljava/lang/Integer;
public final fun getRingingConfig ()Lio/getstream/video/android/core/sounds/RingingConfig;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -900,17 +900,10 @@ public class Call(

private fun updateMediaManagerFromSettings(callSettings: CallSettingsResponse) {
// Speaker
if (speaker.status.value is DeviceStatus.NotSelected) {
val enableSpeaker =
if (callSettings.video.cameraDefaultOn || camera.status.value is DeviceStatus.Enabled) {
// if camera is enabled then enable speaker. Eventually this should
// be a new audio.defaultDevice setting returned from backend
true
} else {
callSettings.audio.defaultDevice == AudioSettingsResponse.DefaultDevice.Speaker
}
speaker.setEnabled(enableSpeaker)
}
speaker.setEnabled(
enabled = callSettings.audio.defaultDevice == AudioSettingsResponse.DefaultDevice.Speaker ||
callSettings.audio.speakerDefaultOn,
)

// Camera
if (camera.status.value is DeviceStatus.NotSelected) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,9 @@ class SpeakerManager(
fun setSpeakerPhone(enable: Boolean, defaultFallback: StreamAudioDevice? = null) {
microphoneManager.setup()
val devices = devices.value
val selectedBeforeSpeaker = selectedDevice.value
if (enable) {
val speaker = devices.filterIsInstance<StreamAudioDevice.Speakerphone>().firstOrNull()
selectedBeforeSpeaker = selectedDevice.value
_speakerPhoneEnabled.value = true
microphoneManager.select(speaker)
} else {
Expand Down Expand Up @@ -328,7 +328,6 @@ class ScreenShareManager(
*/
class MicrophoneManager(
val mediaManager: MediaManagerImpl,
val preferSpeakerphone: Boolean,
val audioUsage: Int,
) {
// Internal data
Expand Down Expand Up @@ -451,7 +450,7 @@ class MicrophoneManager(

if (canHandleDeviceSwitch()) {
audioHandler =
AudioSwitchHandler(mediaManager.context, preferSpeakerphone) { devices, selected ->
AudioSwitchHandler(mediaManager.context) { devices, selected ->
logger.i { "audio devices. selected $selected, available devices are $devices" }
_devices.value = devices.map { it.fromAudio() }
_selectedDevice.value = selected?.fromAudio()
Expand Down Expand Up @@ -858,7 +857,7 @@ class MediaManagerImpl(
)

internal val camera = CameraManager(this, eglBaseContext)
internal val microphone = MicrophoneManager(this, preferSpeakerphone = true, audioUsage)
internal val microphone = MicrophoneManager(this, audioUsage)
internal val speaker = SpeakerManager(this, microphone)
internal val screenShare = ScreenShareManager(this, eglBaseContext)

Expand Down
Loading

0 comments on commit f56eb73

Please sign in to comment.