Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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 @@ -346,6 +346,7 @@ object StreamVideoInitHelper {
audioProcessing = NoiseCancellation(context),
telecomConfig = TelecomConfig(context.packageName),
connectOnInit = false,
rejectCallWhenBusy = false,
).build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9465,7 +9465,8 @@ public final class io/getstream/video/android/core/StreamVideoBuilder {
public fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZ)V
public fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;)V
public fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;Z)V
public synthetic fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ZZ)V
public synthetic fun <init> (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlin/jvm/functions/Function2;Lio/getstream/video/android/core/socket/common/token/TokenProvider;Lio/getstream/video/android/core/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;ZLio/getstream/video/android/core/notifications/internal/service/CallServiceConfig;Lio/getstream/video/android/core/notifications/internal/service/CallServiceConfigRegistry;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Lio/getstream/video/android/core/sounds/RingingCallVibrationConfig;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JZZZLio/getstream/video/android/core/notifications/internal/telecom/TelecomConfig;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun build ()Lio/getstream/video/android/core/StreamVideo;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import io.getstream.android.video.generated.models.ConnectedEvent
import io.getstream.android.video.generated.models.VideoEvent
import io.getstream.log.taggedLogger
import io.getstream.result.Error
import io.getstream.video.android.core.internal.InternalStreamVideoApi
import io.getstream.video.android.core.notifications.internal.service.CallService
import io.getstream.video.android.core.notifications.internal.service.ServiceLauncher
import io.getstream.video.android.core.notifications.internal.telecom.TelecomIntegrationType
Expand Down Expand Up @@ -86,6 +87,9 @@ class ClientState(private val client: StreamVideo) {
public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry
private val serviceLauncher = ServiceLauncher(client.context)

@InternalStreamVideoApi
public val rejectCallWhenBusy: Boolean = (client as StreamVideoClient).rejectCallWhenBusy
Comment thread
aleksandar-apostolov marked this conversation as resolved.

/**
* Returns true if there is an active or ringing call
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ import java.net.ConnectException
* geo = GEO.GlobalEdgeNetwork,
* user = user,
* token = token,
* sounds = ringingConfig(
* defaultResourcesRingingConfig(context),
* defaultMutedRingingConfig(true, true)
* ),
* loggingLevel = LoggingLevel.BODY,
*
* // ...
* ).build()
*```
Expand Down Expand Up @@ -97,6 +102,9 @@ import java.net.ConnectException
* When `false` and the socket is not connected, incoming calls will not be delivered via WebSocket events;
* the SDK will rely on push notifications instead.
* To start receiving WebSocket events, explicitly invoke `client.connect()`.
* @property rejectCallWhenBusy Automatically rejects incoming calls when the user is already in another call.
* When enabled, the SDK suppresses incoming call notifications.
* CallRingEvent will not be propagated if there is an active or ongoing ringing call.
*
* @see build
* @see ClientState.connection
Expand All @@ -118,7 +126,7 @@ public class StreamVideoBuilder @JvmOverloads constructor(
private val loggingLevel: LoggingLevel = LoggingLevel(),
private val notificationConfig: NotificationConfig = NotificationConfig(),
private val ringNotification: ((call: Call) -> Notification?)? = null,
private val connectionTimeoutInMs: Long = 10000,
private val connectionTimeoutInMs: Long = 10_000,
private var ensureSingleInstance: Boolean = true,
private val videoDomain: String = "video.stream-io-api.com",
@Deprecated(
Expand Down Expand Up @@ -153,6 +161,7 @@ public class StreamVideoBuilder @JvmOverloads constructor(
private val enableStereoForSubscriber: Boolean = true,
private val telecomConfig: TelecomConfig? = null,
private val connectOnInit: Boolean = true,
private val rejectCallWhenBusy: Boolean = false,
) {
private val context: Context = context.applicationContext
private val scope = UserScope(ClientScope())
Expand Down Expand Up @@ -282,6 +291,7 @@ public class StreamVideoBuilder @JvmOverloads constructor(
enableStereoForSubscriber = enableStereoForSubscriber,
telecomConfig = telecomConfig,
tokenRepository = tokenRepository,
rejectCallWhenBusy = rejectCallWhenBusy,
)

if (user.type == UserType.Guest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import io.getstream.android.video.generated.models.BlockUserRequest
import io.getstream.android.video.generated.models.BlockUserResponse
import io.getstream.android.video.generated.models.CallAcceptedEvent
import io.getstream.android.video.generated.models.CallRequest
import io.getstream.android.video.generated.models.CallRingEvent
import io.getstream.android.video.generated.models.CallSettingsRequest
import io.getstream.android.video.generated.models.CollectUserFeedbackRequest
import io.getstream.android.video.generated.models.ConnectedEvent
import io.getstream.android.video.generated.models.CreateGuestRequest
import io.getstream.android.video.generated.models.CreateGuestResponse
import io.getstream.android.video.generated.models.GetCallResponse
Expand Down Expand Up @@ -83,6 +83,7 @@ import io.getstream.result.Error
import io.getstream.result.Result
import io.getstream.result.Result.Failure
import io.getstream.result.Result.Success
import io.getstream.video.android.core.call.CallBusyHandler
import io.getstream.video.android.core.errors.VideoErrorCode
import io.getstream.video.android.core.events.VideoEventListener
import io.getstream.video.android.core.filter.Filters
Expand Down Expand Up @@ -135,7 +136,10 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
Expand All @@ -147,7 +151,6 @@ import okhttp3.Response
import org.webrtc.ManagedAudioProcessingFactory
import retrofit2.HttpException
import java.util.*
import kotlin.coroutines.Continuation

internal const val WAIT_FOR_CONNECTION_ID_TIMEOUT = 5000L
internal const val defaultAudioUsage = AudioAttributes.USAGE_VOICE_COMMUNICATION
Expand Down Expand Up @@ -181,12 +184,15 @@ internal class StreamVideoClient internal constructor(
internal val enableStatsCollection: Boolean = true,
internal val enableStereoForSubscriber: Boolean = true,
internal val telecomConfig: TelecomConfig? = null,
internal val rejectCallWhenBusy: Boolean = false,
) : StreamVideo, NotificationHandler by streamNotificationManager {

private var locationJob: Deferred<Result<String>>? = null

/** the state for the client, includes the current user */
override val state = ClientState(this)
internal val callBusyHandler =
CallBusyHandler(rejectCallWhenBusy, state.activeCall, state.ringingCall)

/**
* Can be set from tests to be returned as a session id for the coordinator.
Expand All @@ -196,7 +202,6 @@ internal class StreamVideoClient internal constructor(
/** if true we fail fast on errors instead of logging them */

internal var guestUserJob: Deferred<Unit>? = null
private lateinit var connectContinuation: Continuation<Result<ConnectedEvent>>

public override val userId = user.id

Expand Down Expand Up @@ -409,6 +414,20 @@ internal class StreamVideoClient internal constructor(
}
}
}

scope.launch {
/**
* Invoke the reject API only when the busy check is triggered from the video client.
*
* Network calls initiated from background notification flows may be suspended
* by the OS, so API calls are intentionally avoided in those cases.
*/
callBusyHandler.callBusyHandlerState.filterNotNull()
.filter { it.source == CallBusyHandler.CallBusyHandlerCheckerSource.VIDEO_CLIENT }
.map { it.streamCallId }.collect { streamCallId ->
call(streamCallId.type, streamCallId.id).reject(RejectReason.Busy)
}
}
}

var location: String? = null
Expand Down Expand Up @@ -531,64 +550,89 @@ internal class StreamVideoClient internal constructor(
* Internal function that fires the event. It starts by updating client state and call state
* After that it loops over the subscriptions and calls their listener
*/

internal fun fireEvent(event: VideoEvent, cid: String = "") {
Comment thread
rahul-lohra marked this conversation as resolved.
logger.d { "Event received $event" }
// update state for the client

if (!shouldProcessEvent(event)) return

propagateEventToClientState(event)

val selectedCid = resolveSelectedCid(event, cid)

notifyClientSubscriptions(event)

if (selectedCid.isEmpty()) return

forwardToCallSubscriptions(selectedCid, event)

if (!shouldProcessCallAcceptedEvent(event)) return

propagateEventToCall(selectedCid, event)

deliverToDestroyedCalls(event)
}

internal fun shouldProcessEvent(event: VideoEvent): Boolean {
return when (event) {
is CallRingEvent -> callBusyHandler.shouldPropagateEvent(event)
else -> true
}
}

internal fun propagateEventToClientState(event: VideoEvent) {
state.handleEvent(event)
}

internal fun resolveSelectedCid(event: VideoEvent, cid: String): String {
if (cid.isNotEmpty()) return cid

// update state for the calls. calls handle updating participants and members
val selectedCid = cid.ifEmpty {
val callEvent = event as? WSCallEvent
callEvent?.getCallCID()
} ?: ""
val callEvent = event as? WSCallEvent
return callEvent?.getCallCID().orEmpty()
}

// client level subscriptions
internal fun notifyClientSubscriptions(event: VideoEvent) {
subscriptions.forEach { sub ->
if (!sub.isDisposed) {
// subs without filters should always fire
if (sub.filter == null) {
sub.listener.onEvent(event)
}
if (sub.isDisposed) return@forEach

// if there is a filter, check it and fire if it matches
sub.filter?.let {
if (it.invoke(event)) {
sub.listener.onEvent(event)
}
}
if (sub.filter == null || sub.filter.invoke(event)) {
sub.listener.onEvent(event)
}
}
// call level subscriptions
if (selectedCid.isNotEmpty()) {
calls[selectedCid]?.fireEvent(event)
notifyDestroyedCalls(event)
}
}

if (selectedCid.isNotEmpty()) {
// Special handling for accepted events
if (event is CallAcceptedEvent) {
// Skip accepted events not meant for the current outgoing call.
val currentRingingCall = state.ringingCall.value
val state = currentRingingCall?.state?.ringingState?.value
if (currentRingingCall != null &&
(state is RingingState.Outgoing || state == RingingState.Idle) &&
currentRingingCall.cid != event.callCid
) {
// Skip this event
return
}
}
internal fun forwardToCallSubscriptions(cid: String, event: VideoEvent) {
calls[cid]?.fireEvent(event)
notifyDestroyedCalls(event)
}

// Update calls as usual
calls[selectedCid]?.let {
it.state.handleEvent(event)
it.session?.handleEvent(event)
it.handleEvent(event)
}
deliverIntentToDestroyedCalls(event)
internal fun shouldProcessCallAcceptedEvent(event: VideoEvent): Boolean {
if (event !is CallAcceptedEvent) return true

val currentRingingCall = state.ringingCall.value ?: return true
val ringingState = currentRingingCall.state.ringingState.value

val isOutgoingOrIdle =
ringingState is RingingState.Outgoing ||
ringingState == RingingState.Idle

val isDifferentCall = currentRingingCall.cid != event.callCid

return !(isOutgoingOrIdle && isDifferentCall)
}

internal fun propagateEventToCall(cid: String, event: VideoEvent) {
calls[cid]?.let { call ->
call.state.handleEvent(event)
call.session?.handleEvent(event)
call.handleEvent(event)
}
}

internal fun deliverToDestroyedCalls(event: VideoEvent) {
deliverIntentToDestroyedCalls(event)
}

private fun shouldProcessDestroyedCall(event: VideoEvent, callCid: String): Boolean {
return when (event) {
is WSCallEvent -> event.getCallCID() == callCid
Expand Down
Loading
Loading