Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package io.getstream.video.android.util

import android.annotation.SuppressLint
import android.content.Context
import android.media.AudioAttributes
import android.util.Log
import io.getstream.android.push.firebase.FirebasePushDeviceGenerator
import io.getstream.chat.android.client.ChatClient
Expand All @@ -29,8 +30,11 @@ import io.getstream.log.Priority
import io.getstream.video.android.BuildConfig
import io.getstream.video.android.core.StreamVideo
import io.getstream.video.android.core.StreamVideoBuilder
import io.getstream.video.android.core.call.CallType
import io.getstream.video.android.core.logging.LoggingLevel
import io.getstream.video.android.core.notifications.NotificationConfig
import io.getstream.video.android.core.notifications.internal.service.CallServiceConfigRegistry
import io.getstream.video.android.core.notifications.internal.service.DefaultCallConfigurations
import io.getstream.video.android.core.socket.common.token.TokenProvider
import io.getstream.video.android.data.services.stream.GetAuthDataResponse
import io.getstream.video.android.data.services.stream.StreamService
Expand Down Expand Up @@ -188,6 +192,21 @@ object StreamVideoInitHelper {
token: String,
loggingLevel: LoggingLevel,
): StreamVideo {
val callServiceConfigRegistry = CallServiceConfigRegistry()

callServiceConfigRegistry.createConfigRegistry {
register(DefaultCallConfigurations.getLivestreamGuestCallServiceConfig())

update(CallType.Default.name) {
setAudioUsage(AudioAttributes.USAGE_MEDIA)
setRunCallServiceInForeground(true)
}

update(CallType.Livestream.name) {
setRunCallServiceInForeground(true)
}
}

return StreamVideoBuilder(
context = context,
apiKey = apiKey,
Expand All @@ -212,6 +231,7 @@ object StreamVideoInitHelper {
},
appName = "Stream Video Demo App",
audioProcessing = NoiseCancellation(context),
callServiceConfigRegistry = callServiceConfigRegistry,
).build()
}
}
85 changes: 72 additions & 13 deletions stream-video-android-core/api/stream-video-android-core.api

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import io.getstream.result.Result.Failure
import io.getstream.result.Result.Success
import io.getstream.video.android.core.call.RtcSession
import io.getstream.video.android.core.call.audio.InputAudioFilter
import io.getstream.video.android.core.call.connection.StreamPeerConnectionFactory
import io.getstream.video.android.core.call.utils.SoundInputProcessor
import io.getstream.video.android.core.call.video.VideoFilter
import io.getstream.video.android.core.call.video.YuvFrame
Expand Down Expand Up @@ -214,6 +215,12 @@ public class Call(
internal var connectedAt: Long? = null
internal var reconnectAt: Pair<WebsocketReconnectStrategy, Long>? = null

internal var peerConnectionFactory: StreamPeerConnectionFactory = StreamPeerConnectionFactory(
context = clientImpl.context,
audioProcessing = clientImpl.audioProcessing,
audioUsage = clientImpl.callServiceConfigRegistry.get(type).audioUsage,
)

internal val mediaManager by lazy {
if (testInstanceProvider.mediaManagerCreator != null) {
testInstanceProvider.mediaManagerCreator!!.invoke()
Expand All @@ -222,8 +229,8 @@ public class Call(
clientImpl.context,
this,
scope,
clientImpl.peerConnectionFactory.eglBase.eglBaseContext,
clientImpl.callServiceConfig.audioUsage,
peerConnectionFactory.eglBase.eglBaseContext,
clientImpl.callServiceConfigRegistry.get(type).audioUsage,
)
}
}
Expand Down Expand Up @@ -377,6 +384,9 @@ public class Call(
"You can re-define your permissions and their expected state by overriding the [permissionCheck] in [StreamVideoBuilder]\n"
}
}

client.state.setActiveCall(this)

// if we are a guest user, make sure we wait for the token before running the join flow
clientImpl.guestUserJob?.await()
// the join flow should retry up to 3 times
Expand Down Expand Up @@ -493,7 +503,6 @@ public class Call(
} catch (e: Exception) {
return Failure(Error.GenericError(e.message ?: "RtcSession error occurred."))
}
client.state.setActiveCall(this)
monitorSession(result.value)
return Success(value = session!!)
}
Expand Down Expand Up @@ -775,11 +784,11 @@ public class Call(

sfuSocketReconnectionTime = null
stopScreenSharing()
client.state.removeActiveCall() // Will also stop CallService
client.state.removeRingingCall()
(client as StreamVideoClient).onCallCleanUp(this)
camera.disable()
microphone.disable()
client.state.removeActiveCall() // Will also stop CallService
client.state.removeRingingCall()
cleanup()
}

Expand Down Expand Up @@ -876,7 +885,7 @@ public class Call(

// Note this comes from peerConnectionFactory.eglBase
videoRenderer.init(
clientImpl.peerConnectionFactory.eglBase.eglBaseContext,
peerConnectionFactory.eglBase.eglBaseContext,
object : RendererCommon.RendererEvents {
override fun onFirstFrameRendered() {
val width = videoRenderer.measuredWidth
Expand Down Expand Up @@ -1203,7 +1212,7 @@ public class Call(
state.acceptedOnThisDevice = true

clientImpl.state.removeRingingCall()
clientImpl.state.maybeStopForegroundService()
clientImpl.state.maybeStopForegroundService(call = this)
return clientImpl.accept(type, id)
}

Expand Down Expand Up @@ -1277,15 +1286,15 @@ public class Call(
}

fun isAudioProcessingEnabled(): Boolean {
return clientImpl.isAudioProcessingEnabled()
return peerConnectionFactory.isAudioProcessingEnabled()
}

fun setAudioProcessingEnabled(enabled: Boolean) {
return clientImpl.setAudioProcessingEnabled(enabled)
return peerConnectionFactory.setAudioProcessingEnabled(enabled)
}

fun toggleAudioProcessing(): Boolean {
return clientImpl.toggleAudioProcessing()
return peerConnectionFactory.toggleAudioProcessing()
}

suspend fun startTranscription(): Result<StartTranscriptionResponse> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ class ClientState(private val client: StreamVideo) {
/** When there is an active call, this state will be set, otherwise its null. */
public val activeCall: StateFlow<Call?> = _activeCall

public val callConfigRegistry = (client as StreamVideoClient).callServiceConfigRegistry

/**
* Returns true if there is an active or ringing call
*/
Expand Down Expand Up @@ -145,14 +147,16 @@ class ClientState(private val client: StreamVideo) {
}

fun setActiveCall(call: Call) {
this._activeCall.value = call
removeRingingCall()
maybeStartForegroundService(call, CallService.TRIGGER_ONGOING_CALL)
this._activeCall.value = call
}

fun removeActiveCall() {
this._activeCall.value = null
maybeStopForegroundService()
if (this._activeCall.value != null) {
maybeStopForegroundService(this._activeCall.value!!)
this._activeCall.value = null
}
removeRingingCall()
}

Expand All @@ -174,13 +178,14 @@ class ClientState(private val client: StreamVideo) {
* This depends on the flag in [StreamVideoBuilder] called `runForegroundServiceForCalls`
*/
internal fun maybeStartForegroundService(call: Call, trigger: String) {
if (streamVideoClient.callServiceConfig.runCallServiceInForeground) {
val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type)
if (callConfig.runCallServiceInForeground) {
val context = streamVideoClient.context
val serviceIntent = CallService.buildStartIntent(
context,
StreamCallId.fromCallCid(call.cid),
trigger,
callServiceConfiguration = streamVideoClient.callServiceConfig,
callServiceConfiguration = callConfig,
)
ContextCompat.startForegroundService(context, serviceIntent)
}
Expand All @@ -189,12 +194,13 @@ class ClientState(private val client: StreamVideo) {
/**
* Stop the foreground service that manages the call even when the UI is gone.
*/
internal fun maybeStopForegroundService() {
if (streamVideoClient.callServiceConfig.runCallServiceInForeground) {
internal fun maybeStopForegroundService(call: Call) {
val callConfig = streamVideoClient.callServiceConfigRegistry.get(call.type)
if (callConfig.runCallServiceInForeground) {
val context = streamVideoClient.context
val serviceIntent = CallService.buildStopIntent(
context,
callServiceConfiguration = streamVideoClient.callServiceConfig,
callConfig,
)
context.stopService(serviceIntent)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,39 +654,43 @@ public class CameraManager(
* Capture is called whenever you call enable()
*/
internal fun startCapture() = synchronized(this) {
if (isCapturingVideo) {
stopCapture()
}
safeCall {
if (isCapturingVideo) {
stopCapture()
}

val selectedDevice = _selectedDevice.value ?: return
val selectedResolution = resolution.value ?: return
val selectedDevice = _selectedDevice.value ?: return
val selectedResolution = resolution.value ?: return

// setup the camera 2 capturer
videoCapturer = Camera2Capturer(mediaManager.context, selectedDevice.id, null)
// setup the camera 2 capturer
videoCapturer = Camera2Capturer(mediaManager.context, selectedDevice.id, null)

// initialize it
videoCapturer.initialize(
surfaceTextureHelper,
mediaManager.context,
mediaManager.videoSource.capturerObserver,
)
// initialize it
videoCapturer.initialize(
surfaceTextureHelper,
mediaManager.context,
mediaManager.videoSource.capturerObserver,
)

// and start capture
videoCapturer.startCapture(
selectedResolution.width,
selectedResolution.height,
selectedResolution.framerate.max,
)
isCapturingVideo = true
// and start capture
videoCapturer.startCapture(
selectedResolution.width,
selectedResolution.height,
selectedResolution.framerate.max,
)
isCapturingVideo = true
}
}

/**
* Stops capture if it's running
*/
internal fun stopCapture() = synchronized(this) {
if (isCapturingVideo) {
videoCapturer.stopCapture()
isCapturingVideo = false
safeCall {
if (isCapturingVideo) {
videoCapturer.stopCapture()
isCapturingVideo = false
}
}
}

Expand Down Expand Up @@ -838,29 +842,29 @@ class MediaManagerImpl(

// source & tracks
val videoSource =
call.clientImpl.peerConnectionFactory.makeVideoSource(false, filterVideoProcessor)
call.peerConnectionFactory.makeVideoSource(false, filterVideoProcessor)

val screenShareVideoSource by lazy {
call.clientImpl.peerConnectionFactory.makeVideoSource(true, screenShareFilterVideoProcessor)
call.peerConnectionFactory.makeVideoSource(true, screenShareFilterVideoProcessor)
}

// for track ids we emulate the browser behaviour of random UUIDs, doing something different would be confusing
val videoTrack = call.clientImpl.peerConnectionFactory.makeVideoTrack(
val videoTrack = call.peerConnectionFactory.makeVideoTrack(
source = videoSource,
trackId = UUID.randomUUID().toString(),
)

val screenShareTrack by lazy {
call.clientImpl.peerConnectionFactory.makeVideoTrack(
call.peerConnectionFactory.makeVideoTrack(
source = screenShareVideoSource,
trackId = UUID.randomUUID().toString(),
)
}

val audioSource = call.clientImpl.peerConnectionFactory.makeAudioSource(buildAudioConstraints())
val audioSource = call.peerConnectionFactory.makeAudioSource(buildAudioConstraints())

// for track ids we emulate the browser behaviour of random UUIDs, doing something different would be confusing
val audioTrack = call.clientImpl.peerConnectionFactory.makeAudioTrack(
val audioTrack = call.peerConnectionFactory.makeAudioTrack(
source = audioSource,
trackId = UUID.randomUUID().toString(),
)
Expand Down
Loading
Loading