diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt index 106566e118..9a3a3853c0 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/call/CallScreen.kt @@ -107,6 +107,7 @@ import io.getstream.video.android.ui.menu.availableVideoFilters import io.getstream.video.android.util.config.AppConfig import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import org.openapitools.client.models.OwnCapability @@ -331,33 +332,36 @@ fun CallScreen( ) }, floatingVideoRenderer = { _, _ -> - FloatingParticipantVideo( - call = call, - participant = me!!, - parentBounds = IntSize( - this@BoxWithConstraints.constraints.maxWidth, - this@BoxWithConstraints.constraints.maxHeight, - ), - videoRenderer = { participant -> - ParticipantVideo( - modifier = Modifier - .fillMaxSize() - .clip(VideoTheme.shapes.dialog), - call = call, - participant = participant, - reactionContent = { - CustomReactionContent( - participant = participant, - style = RegularVideoRendererStyle().copy( - isShowingConnectionQualityIndicator = false, - reactionPosition = Alignment.TopCenter, - reactionDuration = 5000, - ), - ) - }, - ) - }, - ) + val myself = me ?: participantsSize.firstOrNull { it.sessionId == call.sessionId } + myself?.let { + FloatingParticipantVideo( + call = call, + participant = it, + parentBounds = IntSize( + this@BoxWithConstraints.constraints.maxWidth, + this@BoxWithConstraints.constraints.maxHeight, + ), + videoRenderer = { participant -> + ParticipantVideo( + modifier = Modifier + .fillMaxSize() + .clip(VideoTheme.shapes.dialog), + call = call, + participant = participant, + reactionContent = { + CustomReactionContent( + participant = participant, + style = RegularVideoRendererStyle().copy( + isShowingConnectionQualityIndicator = false, + reactionPosition = Alignment.TopCenter, + reactionDuration = 5000, + ), + ) + }, + ) + }, + ) + } }, videoOverlayContent = { Crossfade( @@ -463,7 +467,8 @@ fun CallScreen( mutableStateOf(call.isAudioProcessingEnabled()) } val settings by call.state.settings.collectAsStateWithLifecycle() - val noiseCancellationFeatureEnabled = settings?.audio?.noiseCancellation?.isEnabled == true + val noiseCancellationFeatureEnabled = + settings?.audio?.noiseCancellation?.isEnabled == true SettingsMenu( call = call, selectedVideoFilter = selectedVideoFilter, @@ -478,11 +483,14 @@ fun CallScreen( is VideoFilter.None -> { call.videoFilter = null } + is VideoFilter.BlurredBackground -> { call.videoFilter = BlurredBackgroundVideoFilter() } + is VideoFilter.VirtualBackground -> { - call.videoFilter = VirtualBackgroundVideoFilter(context, filter.drawable) + call.videoFilter = + VirtualBackgroundVideoFilter(context, filter.drawable) } } }, @@ -493,10 +501,11 @@ fun CallScreen( onNoiseCancellation = { isNoiseCancellationEnabled = call.toggleAudioProcessing() }, - ) { - isShowingStats = true - isShowingSettingMenu = false - } + onShowCallStats = { + isShowingStats = true + isShowingSettingMenu = false + }, + ) } if (isShowingFeedbackDialog) { diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt index 91b0d69e39..c88b1a3ada 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/join/CallJoinScreen.kt @@ -461,7 +461,7 @@ private fun JoinCallForm( mutableStateOf( TextFieldValue( if (BuildConfig.FLAVOR == StreamFlavors.development) { - "default:79cYh3J5JgGk" + "default:123lexgvg" } else { "" }, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt index 826e9673eb..6364a1ff9b 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/MenuDefinitions.kt @@ -27,8 +27,9 @@ import androidx.compose.material.icons.filled.BluetoothAudio import androidx.compose.material.icons.filled.Feedback import androidx.compose.material.icons.filled.Headphones import androidx.compose.material.icons.filled.HeadsetMic -import androidx.compose.material.icons.filled.PortableWifiOff +import androidx.compose.material.icons.filled.Replay import androidx.compose.material.icons.filled.RestartAlt +import androidx.compose.material.icons.filled.SettingsBackupRestore import androidx.compose.material.icons.filled.SettingsVoice import androidx.compose.material.icons.filled.SpatialAudioOff import androidx.compose.material.icons.filled.SpeakerPhone @@ -57,11 +58,12 @@ fun defaultStreamMenu( onToggleAudioFilterClick: () -> Unit, onRestartSubscriberIceClick: () -> Unit, onRestartPublisherIceClick: () -> Unit, - onKillSfuWsClick: () -> Unit, onSwitchSfuClick: () -> Unit, onShowFeedback: () -> Unit, onNoiseCancellation: () -> Unit, onDeviceSelected: (StreamAudioDevice) -> Unit, + onSfuRejoinClick: () -> Unit, + onSfuFastReconnectClick: () -> Unit, availableDevices: List, loadRecordings: suspend () -> List, ) = buildList { @@ -133,8 +135,9 @@ fun defaultStreamMenu( onToggleAudioFilterClick, onRestartSubscriberIceClick, onRestartPublisherIceClick, - onKillSfuWsClick, onSwitchSfuClick, + onSfuRejoinClick, + onSfuFastReconnectClick, ), ), ) @@ -159,6 +162,40 @@ fun codecMenu(codecList: List, onCodecSelected: (MediaCodecInfo) ) } +fun reconnectMenu( + onRestartPublisherIceClick: () -> Unit, + onRestartSubscriberIceClick: () -> Unit, + onSwitchSfuClick: () -> Unit, + onSfuRejoinClick: () -> Unit, + onSfuFastReconnectClick: () -> Unit, +) = listOf( + ActionMenuItem( + title = "Publisher - ICE restart", + icon = Icons.Default.SettingsBackupRestore, + action = onRestartPublisherIceClick, + ), + ActionMenuItem( + title = "Subscriber - ICE restart", + icon = Icons.Default.SettingsBackupRestore, + action = onRestartSubscriberIceClick, + ), + ActionMenuItem( + title = "Reconnect SFU - migrate", + icon = Icons.Default.SwitchLeft, + action = onSwitchSfuClick, + ), + ActionMenuItem( + title = "Reconnect SFU - rejoin", + icon = Icons.Default.Replay, + action = onSfuRejoinClick, + ), + ActionMenuItem( + title = "Reconnect SFU - fast", + icon = Icons.Default.RestartAlt, + action = onSfuFastReconnectClick, + ), +) + /** * Optionally defines the debug sub-menu of the demo app. */ @@ -166,10 +203,11 @@ fun debugSubmenu( codecList: List, onCodecSelected: (MediaCodecInfo) -> Unit, onToggleAudioFilterClick: () -> Unit, - onRestartSubscriberIceClick: () -> Unit, onRestartPublisherIceClick: () -> Unit, - onKillSfuWsClick: () -> Unit, + onRestartSubscriberIceClick: () -> Unit, onSwitchSfuClick: () -> Unit, + onSfuRejoinClick: () -> Unit, + onSfuFastReconnectClick: () -> Unit, ) = listOf( SubMenuItem( title = "Available video codecs", @@ -181,24 +219,15 @@ fun debugSubmenu( icon = Icons.Default.Audiotrack, action = onToggleAudioFilterClick, ), - ActionMenuItem( - title = "Restart subscriber Ice", - icon = Icons.Default.RestartAlt, - action = onRestartSubscriberIceClick, - ), - ActionMenuItem( - title = "Restart publisher Ice", - icon = Icons.Default.RestartAlt, - action = onRestartPublisherIceClick, - ), - ActionMenuItem( - title = "Shut down SFU web-socket", - icon = Icons.Default.PortableWifiOff, - action = onKillSfuWsClick, - ), - ActionMenuItem( - title = "Switch SFU", - icon = Icons.Default.SwitchLeft, - action = onSwitchSfuClick, + SubMenuItem( + title = "Reconnect V2", + icon = Icons.Default.Replay, + items = reconnectMenu( + onRestartPublisherIceClick, + onRestartSubscriberIceClick, + onSwitchSfuClick, + onSfuRejoinClick, + onSfuFastReconnectClick, + ), ), ) diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt index 9c4d544de9..552bbde424 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/SettingsMenu.kt @@ -135,19 +135,25 @@ internal fun SettingsMenu( Toast.makeText(context, "Restart Publisher Ice", Toast.LENGTH_SHORT).show() } - val onKillSfuWsClick: () -> Unit = { - call.debug.doFullReconnection() + val onSfuRejoinClick: () -> Unit = { + call.debug.rejoin() onDismissed.invoke() Toast.makeText(context, "Killing SFU WS. Should trigger reconnect...", Toast.LENGTH_SHORT) .show() } val onSwitchSfuClick: () -> Unit = { - call.debug.switchSfu() + call.debug.migrate() onDismissed.invoke() Toast.makeText(context, "Switch sfu", Toast.LENGTH_SHORT).show() } + val onSfuFastReconnectClick: () -> Unit = { + call.debug.fastReconnect() + onDismissed.invoke() + Toast.makeText(context, "Fast Reconnect SFU", Toast.LENGTH_SHORT).show() + } + val codecInfos = remember { MediaCodecList(MediaCodecList.ALL_CODECS).codecInfos.filter { it.name.contains("encoder") && it.supportedTypes.firstOrNull { @@ -222,13 +228,14 @@ internal fun SettingsMenu( }, onShowFeedback = onShowFeedback, onToggleScreenShare = onScreenShareClick, - onKillSfuWsClick = onKillSfuWsClick, onRestartPublisherIceClick = onRestartPublisherIceClick, onRestartSubscriberIceClick = onRestartSubscriberIceClick, onToggleAudioFilterClick = onToggleAudioFilterClick, onSwitchSfuClick = onSwitchSfuClick, onShowCallStats = onShowCallStats, onNoiseCancellation = onNoiseCancellation, + onSfuRejoinClick = onSfuRejoinClick, + onSfuFastReconnectClick = onSfuFastReconnectClick, isScreenShareEnabled = isScreenSharing, loadRecordings = onLoadRecordings, ), @@ -285,7 +292,8 @@ private fun SettingsMenuPreview() { onToggleAudioFilterClick = { }, onRestartSubscriberIceClick = { }, onRestartPublisherIceClick = { }, - onKillSfuWsClick = { }, + onSfuRejoinClick = { }, + onSfuFastReconnectClick = {}, onSwitchSfuClick = { }, availableDevices = emptyList(), onDeviceSelected = {}, diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt index 216cc627b7..404562a816 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/ui/menu/base/DynamicMenu.kt @@ -48,6 +48,7 @@ import io.getstream.video.android.compose.ui.components.base.StreamToggleButton import io.getstream.video.android.compose.ui.components.base.styling.StyleSize import io.getstream.video.android.ui.menu.debugSubmenu import io.getstream.video.android.ui.menu.defaultStreamMenu +import io.getstream.video.android.ui.menu.reconnectMenu /** * A composable capable of loading a menu based on a list structure of menu items and sub menus. @@ -217,7 +218,8 @@ private fun DynamicMenuPreview() { onToggleAudioFilterClick = { }, onRestartSubscriberIceClick = { }, onRestartPublisherIceClick = { }, - onKillSfuWsClick = { }, + onSfuRejoinClick = { }, + onSfuFastReconnectClick = {}, onSwitchSfuClick = { }, availableDevices = emptyList(), onDeviceSelected = {}, @@ -244,7 +246,8 @@ private fun DynamicMenuDebugOptionPreview() { onToggleAudioFilterClick = { }, onRestartSubscriberIceClick = { }, onRestartPublisherIceClick = { }, - onKillSfuWsClick = { }, + onSfuRejoinClick = { }, + onSfuFastReconnectClick = {}, onSwitchSfuClick = { }, availableDevices = emptyList(), onDeviceSelected = {}, @@ -264,7 +267,8 @@ private fun DynamicMenuDebugPreview() { items = debugSubmenu( codecList = emptyList(), onCodecSelected = {}, - onKillSfuWsClick = { }, + onSfuRejoinClick = { }, + onSfuFastReconnectClick = {}, onRestartPublisherIceClick = { }, onRestartSubscriberIceClick = { }, onToggleAudioFilterClick = { }, @@ -273,3 +277,19 @@ private fun DynamicMenuDebugPreview() { ) } } + +@Preview +@Composable +private fun DynamicMenuReconnectPreview() { + VideoTheme { + DynamicMenu( + items = reconnectMenu( + onSfuRejoinClick = { }, + onSfuFastReconnectClick = {}, + onRestartPublisherIceClick = { }, + onRestartSubscriberIceClick = { }, + onSwitchSfuClick = { }, + ), + ) + } +} diff --git a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt index b60f8d8378..c3f92c5603 100644 --- a/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt +++ b/demo-app/src/main/kotlin/io/getstream/video/android/util/StreamVideoInitHelper.kt @@ -31,6 +31,7 @@ import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoBuilder import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.notifications.NotificationConfig +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 import io.getstream.video.android.datastore.delegate.StreamUserDataStore @@ -199,13 +200,15 @@ object StreamVideoInitHelper { FirebasePushDeviceGenerator(providerName = "firebase"), ), ), - tokenProvider = { - val email = user.custom?.get("email") - val authData = StreamService.instance.getAuthData( - environment = AppConfig.currentEnvironment.value!!.env, - userId = email, - ) - authData.token + tokenProvider = object : TokenProvider { + override suspend fun loadToken(): String { + val email = user.custom?.get("email") + val authData = StreamService.instance.getAuthData( + environment = AppConfig.currentEnvironment.value!!.env, + userId = email, + ) + return authData.token + } }, appName = "Stream Video Demo App", audioProcessing = NoiseCancellation(context), diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b84d63734e..a33164042c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,31 +1,31 @@ [versions] androidGradlePlugin = "8.4.2" -cameraCamera2 = "1.3.0" +cameraCamera2 = "1.3.4" spotless = "6.21.0" nexusPlugin = "1.3.0" kotlin = "2.0.20" ksp = "2.0.20-1.0.25" kotlinSerialization = "1.7.1" kotlinSerializationConverter = "1.0.0" -kotlinxCoroutines = "1.8.1" +kotlinxCoroutines = "1.9.0" kotlinDokka = "1.9.20" jvmTarget = "11" -androidxMaterial = "1.11.0" -androidxAppCompat = "1.6.1" -androidxCore = "1.12.0" -androidxAnnotation = "1.7.1" -androidxLifecycle = "2.8.5" -androidxStartup = "1.1.1" +androidxMaterial = "1.12.0" +androidxAppCompat = "1.7.0" +androidxCore = "1.13.1" +androidxAnnotation = "1.8.2" +androidxLifecycle = "2.8.6" +androidxStartup = "1.2.0" androidxActivity = "1.9.2" -androidxDataStore = "1.0.0" -googleService = "4.3.14" +androidxDataStore = "1.1.1" +googleService = "4.4.2" -androidxComposeBom = "2024.09.00" +androidxComposeBom = "2024.09.02" androidxComposeTracing = "1.0.0-beta01" androidxHiltNavigation = "1.2.0" -androidxComposeNavigation = "2.8.0" +androidxComposeNavigation = "2.8.1" composeStableMarker = "1.0.5" coil = "2.6.0" @@ -42,13 +42,14 @@ moshi = "1.15.1" threetenAbp = "1.4.7" tink = "1.9.0" turbine = "0.13.0" +itu = "1.7.3" streamWebRTC = "1.2.2" streamNoiseCancellation = "1.0.1" -streamResult = "1.2.0" -streamChat = "6.0.13" +streamResult = "1.3.0" +streamChat = "6.5.1" streamLog = "1.1.4" -streamPush = "1.1.7" +streamPush = "1.1.8" androidxTest = "1.5.2" androidxTestCore = "1.5.0" @@ -141,10 +142,13 @@ moshi-adapters = { group = "com.squareup.moshi", name = "moshi-adapters", versio threentenabp2 = { group = "com.jakewharton.threetenabp", name = "threetenabp", version.ref = "threetenAbp" } tink = { group = "com.google.crypto.tink", name = "tink-android", version.ref = "tink" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } +ituDate = { group = "com.ethlo.time", name = "itu", version.ref = "itu" } stream-webrtc = { group = "io.getstream", name = "stream-webrtc-android", version.ref = "streamWebRTC" } stream-webrtc-ui = { group = "io.getstream", name = "stream-webrtc-android-ui", version.ref = "streamWebRTC" } stream-result = { group = "io.getstream", name = "stream-result", version.ref = "streamResult" } +stream-result-call = { group = "io.getstream", name = "stream-result-call", version.ref = "streamResult" } + stream-log = { group = "io.getstream", name = "stream-log", version.ref = "streamLog" } stream-log-android = { group = "io.getstream", name = "stream-log-android", version.ref = "streamLog" } stream-push = { group = "io.getstream", name = "stream-android-push", version.ref = "streamPush" } diff --git a/stream-video-android-core/api/stream-video-android-core.api b/stream-video-android-core/api/stream-video-android-core.api index 3659f4d257..74a23886c1 100644 --- a/stream-video-android-core/api/stream-video-android-core.api +++ b/stream-video-android-core/api/stream-video-android-core.api @@ -7,6 +7,7 @@ public final class io/getstream/video/android/core/Call { public final fun create (Ljava/util/List;Ljava/util/List;Ljava/util/Map;Lorg/openapitools/client/models/CallSettingsRequest;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun create$default (Lio/getstream/video/android/core/Call;Ljava/util/List;Ljava/util/List;Ljava/util/Map;Lorg/openapitools/client/models/CallSettingsRequest;Lorg/threeten/bp/OffsetDateTime;Ljava/lang/String;ZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun end (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun fastReconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun fireEvent (Lorg/openapitools/client/models/VideoEvent;)V public final fun get (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getAudioFilter ()Lio/getstream/video/android/core/call/audio/InputAudioFilter; @@ -15,7 +16,6 @@ public final class io/getstream/video/android/core/Call { public final fun getId ()Ljava/lang/String; public final fun getLocalMicrophoneAudioLevel ()Lkotlinx/coroutines/flow/StateFlow; public final fun getMicrophone ()Lio/getstream/video/android/core/MicrophoneManager; - public final fun getMonitor ()Lio/getstream/video/android/core/CallHealthMonitor; public final fun getScreenShare ()Lio/getstream/video/android/core/ScreenShareManager; public final fun getSessionId ()Ljava/lang/String; public final fun getSpeaker ()Lio/getstream/video/android/core/SpeakerManager; @@ -41,6 +41,7 @@ public final class io/getstream/video/android/core/Call { public final fun leave ()V public final fun listRecordings (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listRecordings$default (Lio/getstream/video/android/core/Call;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public final fun migrate (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun muteAllUsers (ZZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun muteAllUsers$default (Lio/getstream/video/android/core/Call;ZZZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun muteUser (Ljava/lang/String;ZZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -52,7 +53,6 @@ public final class io/getstream/video/android/core/Call { public final fun processAudioSample (Lorg/webrtc/audio/JavaAudioDeviceModule$AudioSamples;)V public final fun queryMembers (Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryMembers$default (Lio/getstream/video/android/core/Call;Ljava/util/Map;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun reconnect (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/model/RejectReason;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun removeMembers (Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -64,6 +64,7 @@ public final class io/getstream/video/android/core/Call { public static synthetic fun sendReaction$default (Lio/getstream/video/android/core/Call;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun setAudioFilter (Lio/getstream/video/android/core/call/audio/InputAudioFilter;)V public final fun setAudioProcessingEnabled (Z)V + public final fun setSessionId (Ljava/lang/String;)V public final fun setVideoFilter (Lio/getstream/video/android/core/call/video/VideoFilter;)V public final fun setVisibility (Ljava/lang/String;Lstream/video/sfu/models/TrackType;Z)V public final fun startHLS (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -75,7 +76,6 @@ public final class io/getstream/video/android/core/Call { public final fun stopScreenSharing ()V public final fun subscribe (Lio/getstream/video/android/core/events/VideoEventListener;)Lio/getstream/video/android/core/EventSubscription; public final fun subscribeFor ([Ljava/lang/Class;Lio/getstream/video/android/core/events/VideoEventListener;)Lio/getstream/video/android/core/EventSubscription; - public final fun switchSfu (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun takeScreenshot (Lio/getstream/video/android/core/model/VideoTrack;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun toggleAudioProcessing ()Z public final fun unpinForEveryone (Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -168,6 +168,7 @@ public final class io/getstream/video/android/core/CallState { public final fun isReconnecting ()Lkotlinx/coroutines/flow/StateFlow; public final fun markSpeakingAsMuted ()V public final fun pin (Ljava/lang/String;Ljava/lang/String;)V + public final fun replaceParticipants (Ljava/util/List;)V public final fun unpin (Ljava/lang/String;)V public final fun updateFromResponse (Lorg/openapitools/client/models/CallResponse;)V public final fun updateFromResponse (Lorg/openapitools/client/models/CallStateResponseFields;)V @@ -271,9 +272,9 @@ public final class io/getstream/video/android/core/ClientState { public final fun addRingingCall (Lio/getstream/video/android/core/Call;Lio/getstream/video/android/core/RingingState;)V public final fun getActiveCall ()Lkotlinx/coroutines/flow/StateFlow; public final fun getConnection ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getLogger ()Lio/getstream/log/TaggedLogger; public final fun getRingingCall ()Lkotlinx/coroutines/flow/StateFlow; public final fun getUser ()Lkotlinx/coroutines/flow/StateFlow; + public final fun handleError (Lio/getstream/result/Error;)V public final fun handleEvent (Lorg/openapitools/client/models/VideoEvent;)V public final fun hasActiveOrRingingCall ()Z public final fun removeActiveCall ()V @@ -299,8 +300,8 @@ public final class io/getstream/video/android/core/ConnectionState$Disconnected } public final class io/getstream/video/android/core/ConnectionState$Failed : io/getstream/video/android/core/ConnectionState { - public fun (Ljava/lang/Error;)V - public final fun getError ()Ljava/lang/Error; + public fun (Lio/getstream/result/Error;)V + public final fun getError ()Lio/getstream/result/Error; } public final class io/getstream/video/android/core/ConnectionState$Loading : io/getstream/video/android/core/ConnectionState { @@ -818,22 +819,24 @@ public final class io/getstream/video/android/core/StreamVideoBuilder { public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;)V public fun (Landroid/content/Context;Ljava/lang/String;Lio/getstream/video/android/core/GEO;Lio/getstream/video/android/model/User;Ljava/lang/String;)V public fun (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;)V - public fun (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/logging/LoggingLevel;)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;J)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZ)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;)V - public fun (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/logging/LoggingLevel;Lio/getstream/video/android/core/notifications/NotificationConfig;Lkotlin/jvm/functions/Function1;JZLjava/lang/String;Z)V - public fun (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/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;)V - public fun (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/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;Ljava/lang/String;)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Z)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;I)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;)V - public fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;)V - public synthetic fun (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/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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (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;)V + public fun (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;)V + public fun (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;)V + public fun (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;)V + public fun (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;J)V + public fun (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;JZ)V + public fun (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;)V + public fun (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;Z)V + public fun (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;)V + public fun (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;Ljava/lang/String;)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;Z)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;I)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;)V + public fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;J)V + public synthetic fun (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;Ljava/lang/String;Lio/getstream/video/android/core/sounds/Sounds;ZLio/getstream/video/android/core/permission/android/StreamPermissionCheck;ILjava/lang/String;Lorg/webrtc/ManagedAudioProcessingFactory;JILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lio/getstream/video/android/core/StreamVideo; } @@ -957,7 +960,8 @@ public final class io/getstream/video/android/core/audio/StreamAudioDevice$Wired public final class io/getstream/video/android/core/call/RtcSession { public final fun cleanup ()V - public final fun connect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun connect (Lstream/video/sfu/event/ReconnectDetails;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun connect$default (Lio/getstream/video/android/core/call/RtcSession;Lstream/video/sfu/event/ReconnectDetails;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun createPublisher ()Lio/getstream/video/android/core/call/connection/StreamPeerConnection; public final fun createSubscriber ()Lio/getstream/video/android/core/call/connection/StreamPeerConnection; public final fun getPublisherStats (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -972,6 +976,7 @@ public final class io/getstream/video/android/core/call/RtcSession { public final fun handleSubscriberOffer (Lio/getstream/video/android/core/events/SubscriberOfferEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun onNegotiationNeeded (Lio/getstream/video/android/core/call/connection/StreamPeerConnection;Lio/getstream/video/android/core/model/StreamPeerType;)V public final fun reconnect (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun requestPublisherIceRestart (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun requestSubscriberIceRestart (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun setScreenShareTrack ()V public final fun setSubscriber (Lio/getstream/video/android/core/call/connection/StreamPeerConnection;)V @@ -2909,6 +2914,11 @@ public final class io/getstream/video/android/core/errors/DisconnectCause$Unreco public final fun getError ()Lio/getstream/result/Error$NetworkError; } +public final class io/getstream/video/android/core/errors/DisconnectCause$WebSocketNotAvailable : io/getstream/video/android/core/errors/DisconnectCause { + public static final field INSTANCE Lio/getstream/video/android/core/errors/DisconnectCause$WebSocketNotAvailable; + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/video/android/core/errors/MicrophoneException : java/lang/Exception { public fun ()V public fun (Ljava/lang/String;Ljava/lang/Throwable;)V @@ -2969,6 +2979,17 @@ public final class io/getstream/video/android/core/events/AudioLevelChangedEvent public fun toString ()Ljava/lang/String; } +public final class io/getstream/video/android/core/events/CallEndedSfuEvent : io/getstream/video/android/core/events/SfuDataEvent { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lio/getstream/video/android/core/events/CallEndedSfuEvent; + public static synthetic fun copy$default (Lio/getstream/video/android/core/events/CallEndedSfuEvent;IILjava/lang/Object;)Lio/getstream/video/android/core/events/CallEndedSfuEvent; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/video/android/core/events/CallGrantsUpdatedEvent : io/getstream/video/android/core/events/SfuDataEvent { public fun (Lstream/video/sfu/models/CallGrants;Ljava/lang/String;)V public synthetic fun (Lstream/video/sfu/models/CallGrants;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -3019,12 +3040,14 @@ public final class io/getstream/video/android/core/events/DominantSpeakerChanged } public final class io/getstream/video/android/core/events/ErrorEvent : io/getstream/video/android/core/events/SfuDataEvent { - public fun (Lstream/video/sfu/models/Error;)V + public fun (Lstream/video/sfu/models/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V public final fun component1 ()Lstream/video/sfu/models/Error; - public final fun copy (Lstream/video/sfu/models/Error;)Lio/getstream/video/android/core/events/ErrorEvent; - public static synthetic fun copy$default (Lio/getstream/video/android/core/events/ErrorEvent;Lstream/video/sfu/models/Error;ILjava/lang/Object;)Lio/getstream/video/android/core/events/ErrorEvent; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lstream/video/sfu/models/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/events/ErrorEvent; + public static synthetic fun copy$default (Lio/getstream/video/android/core/events/ErrorEvent;Lstream/video/sfu/models/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/events/ErrorEvent; public fun equals (Ljava/lang/Object;)Z public final fun getError ()Lstream/video/sfu/models/Error; + public final fun getReconnectStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; public fun hashCode ()I public fun toString ()Ljava/lang/String; } @@ -3065,14 +3088,16 @@ public final class io/getstream/video/android/core/events/ICETrickleEvent : io/g } public final class io/getstream/video/android/core/events/JoinCallResponseEvent : io/getstream/video/android/core/events/SfuDataEvent { - public fun (Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;Z)V + public fun (Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;IZ)V public final fun component1 ()Lstream/video/sfu/models/CallState; public final fun component2 ()Lio/getstream/video/android/core/events/ParticipantCount; - public final fun component3 ()Z - public final fun copy (Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;Z)Lio/getstream/video/android/core/events/JoinCallResponseEvent; - public static synthetic fun copy$default (Lio/getstream/video/android/core/events/JoinCallResponseEvent;Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;ZILjava/lang/Object;)Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public final fun component3 ()I + public final fun component4 ()Z + public final fun copy (Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;IZ)Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public static synthetic fun copy$default (Lio/getstream/video/android/core/events/JoinCallResponseEvent;Lstream/video/sfu/models/CallState;Lio/getstream/video/android/core/events/ParticipantCount;IZILjava/lang/Object;)Lio/getstream/video/android/core/events/JoinCallResponseEvent; public fun equals (Ljava/lang/Object;)Z public final fun getCallState ()Lstream/video/sfu/models/CallState; + public final fun getFastReconnectDeadlineSeconds ()I public final fun getParticipantCount ()Lio/getstream/video/android/core/events/ParticipantCount; public fun hashCode ()I public final fun isReconnected ()Z @@ -3118,6 +3143,13 @@ public final class io/getstream/video/android/core/events/ParticipantLeftEvent : public fun toString ()Ljava/lang/String; } +public final class io/getstream/video/android/core/events/ParticipantMigrationCompleteEvent : io/getstream/video/android/core/events/SfuDataEvent { + public static final field INSTANCE Lio/getstream/video/android/core/events/ParticipantMigrationCompleteEvent; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/video/android/core/events/PinUpdate { public fun (Ljava/lang/String;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; @@ -3179,6 +3211,18 @@ public abstract class io/getstream/video/android/core/events/SfuDataEvent : org/ public fun getEventType ()Ljava/lang/String; } +public final class io/getstream/video/android/core/events/SfuDataRequest : io/getstream/video/android/core/events/SfuDataEvent { + public fun (Lstream/video/sfu/event/SfuRequest;)V + public final fun component1 ()Lstream/video/sfu/event/SfuRequest; + public final fun copy (Lstream/video/sfu/event/SfuRequest;)Lio/getstream/video/android/core/events/SfuDataRequest; + public static synthetic fun copy$default (Lio/getstream/video/android/core/events/SfuDataRequest;Lstream/video/sfu/event/SfuRequest;ILjava/lang/Object;)Lio/getstream/video/android/core/events/SfuDataRequest; + public fun equals (Ljava/lang/Object;)Z + public fun getEventType ()Ljava/lang/String; + public final fun getSfuRequest ()Lstream/video/sfu/event/SfuRequest; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class io/getstream/video/android/core/events/SfuSocketError : java/lang/Throwable { public fun (Lstream/video/sfu/models/Error;)V public final fun getError ()Lstream/video/sfu/models/Error; @@ -3466,15 +3510,15 @@ public abstract interface annotation class io/getstream/video/android/core/inter } public final class io/getstream/video/android/core/internal/network/NetworkStateProvider { - public fun (Landroid/net/ConnectivityManager;)V + public fun (Lkotlinx/coroutines/CoroutineScope;Landroid/net/ConnectivityManager;)V public final fun isConnected ()Z public final fun subscribe (Lio/getstream/video/android/core/internal/network/NetworkStateProvider$NetworkStateListener;)V public final fun unsubscribe (Lio/getstream/video/android/core/internal/network/NetworkStateProvider$NetworkStateListener;)V } public abstract interface class io/getstream/video/android/core/internal/network/NetworkStateProvider$NetworkStateListener { - public abstract fun onConnected ()V - public abstract fun onDisconnected ()V + public abstract fun onConnected (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun onDisconnected (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class io/getstream/video/android/core/logging/HttpLoggingLevel : java/lang/Enum { @@ -4304,13 +4348,6 @@ public abstract interface class io/getstream/video/android/core/permission/andro public abstract fun checkAndroidPermissions (Landroid/content/Context;Lio/getstream/video/android/core/Call;)Z } -public final class io/getstream/video/android/core/socket/CoordinatorSocket : io/getstream/video/android/core/socket/PersistentSocket { - public fun (Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlinx/coroutines/CoroutineScope;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;)V - public synthetic fun (Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lkotlinx/coroutines/CoroutineScope;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun authenticate ()V - public fun onMessage (Lokhttp3/WebSocket;Ljava/lang/String;)V -} - public final class io/getstream/video/android/core/socket/ErrorResponse : java/lang/Throwable { public static final field Companion Lio/getstream/video/android/core/socket/ErrorResponse$Companion; public fun ()V @@ -4356,67 +4393,6 @@ public final class io/getstream/video/android/core/socket/ErrorResponse$Companio public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public class io/getstream/video/android/core/socket/PersistentSocket : okhttp3/WebSocketListener { - public field connectContinuation Lkotlinx/coroutines/CancellableContinuation; - public fun (Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - protected final fun ackHealthMonitor ()V - public fun authenticate ()V - public final fun cleanup ()V - public fun connect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun connect$default (Lio/getstream/video/android/core/socket/PersistentSocket;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun disconnect (Lio/getstream/video/android/core/socket/PersistentSocket$DisconnectReason;)V - public final fun getConnectContinuation ()Lkotlinx/coroutines/CancellableContinuation; - public final fun getConnectionId ()Lkotlinx/coroutines/flow/StateFlow; - public final fun getConnectionState ()Lkotlinx/coroutines/flow/StateFlow; - protected final fun getDestroyed ()Z - public final fun getErrors ()Lkotlinx/coroutines/flow/MutableSharedFlow; - public final fun getEvents ()Lkotlinx/coroutines/flow/MutableSharedFlow; - public final fun getReconnectTimeout ()J - public fun onClosed (Lokhttp3/WebSocket;ILjava/lang/String;)V - public fun onClosing (Lokhttp3/WebSocket;ILjava/lang/String;)V - public fun onFailure (Lokhttp3/WebSocket;Ljava/lang/Throwable;Lokhttp3/Response;)V - public final fun onInternetConnected (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun onInternetDisconnected (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onOpen (Lokhttp3/WebSocket;Lokhttp3/Response;)V - public final fun reconnect (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static synthetic fun reconnect$default (Lio/getstream/video/android/core/socket/PersistentSocket;JLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; - public final fun setConnectContinuation (Lkotlinx/coroutines/CancellableContinuation;)V - protected final fun setConnectedStateAndContinue (Lorg/openapitools/client/models/VideoEvent;)V - protected final fun setDestroyed (Z)V - public final fun setReconnectTimeout (J)V -} - -public abstract class io/getstream/video/android/core/socket/PersistentSocket$DisconnectReason { -} - -public final class io/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$ByRequest : io/getstream/video/android/core/socket/PersistentSocket$DisconnectReason { - public static final field INSTANCE Lio/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$ByRequest; - public fun equals (Ljava/lang/Object;)Z - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$PermanentError : io/getstream/video/android/core/socket/PersistentSocket$DisconnectReason { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lio/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$PermanentError; - public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$PermanentError;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/PersistentSocket$DisconnectReason$PermanentError; - public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/lang/Throwable; - public fun hashCode ()I - public fun toString ()Ljava/lang/String; -} - -public final class io/getstream/video/android/core/socket/SfuSocket : io/getstream/video/android/core/socket/PersistentSocket { - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlin/jvm/functions/Function1;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlinx/coroutines/CoroutineScope;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun authenticate ()V - public fun connect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public final fun connectMigrating (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun onMessage (Lokhttp3/WebSocket;Lokio/ByteString;)V -} - public final class io/getstream/video/android/core/socket/SocketError { public static final field Companion Lio/getstream/video/android/core/socket/SocketError$Companion; public fun (Lio/getstream/video/android/core/socket/ErrorResponse;)V @@ -4444,59 +4420,645 @@ public final class io/getstream/video/android/core/socket/SocketError$Companion public final fun serializer ()Lkotlinx/serialization/KSerializer; } -public abstract class io/getstream/video/android/core/socket/SocketState { +public abstract class io/getstream/video/android/core/socket/common/ConnectionConf { + public abstract fun getApiKey ()Ljava/lang/String; + public abstract fun getEndpoint ()Ljava/lang/String; + public abstract fun getUser ()Lio/getstream/video/android/model/User; + public final fun isReconnection ()Z +} + +public final class io/getstream/video/android/core/socket/common/ConnectionConf$AnonymousConnectionConf : io/getstream/video/android/core/socket/common/ConnectionConf { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/getstream/video/android/model/User; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;)Lio/getstream/video/android/core/socket/common/ConnectionConf$AnonymousConnectionConf; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/ConnectionConf$AnonymousConnectionConf;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/ConnectionConf$AnonymousConnectionConf; + public fun equals (Ljava/lang/Object;)Z + public fun getApiKey ()Ljava/lang/String; + public fun getEndpoint ()Ljava/lang/String; + public fun getUser ()Lio/getstream/video/android/model/User; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf : io/getstream/video/android/core/socket/common/ConnectionConf { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/getstream/video/android/model/User; + public final fun component4 ()Lstream/video/sfu/event/JoinRequest; + public final fun component5 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;)Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public fun equals (Ljava/lang/Object;)Z + public fun getApiKey ()Ljava/lang/String; + public fun getEndpoint ()Ljava/lang/String; + public final fun getJoinRequest ()Lstream/video/sfu/event/JoinRequest; + public final fun getToken ()Ljava/lang/String; + public fun getUser ()Lio/getstream/video/android/model/User; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/ConnectionConf$UserConnectionConf : io/getstream/video/android/core/socket/common/ConnectionConf { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Lio/getstream/video/android/model/User; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;)Lio/getstream/video/android/core/socket/common/ConnectionConf$UserConnectionConf; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/ConnectionConf$UserConnectionConf;Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/ConnectionConf$UserConnectionConf; + public fun equals (Ljava/lang/Object;)Z + public fun getApiKey ()Ljava/lang/String; + public fun getEndpoint ()Ljava/lang/String; + public fun getUser ()Lio/getstream/video/android/model/User; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/ErrorDetail { + public fun (ILjava/util/List;)V + public final fun component1 ()I + public final fun component2 ()Ljava/util/List; + public final fun copy (ILjava/util/List;)Lio/getstream/video/android/core/socket/common/ErrorDetail; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/ErrorDetail;ILjava/util/List;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/ErrorDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getCode ()I + public final fun getMessages ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class io/getstream/video/android/core/socket/common/SocketActions { + public static final field Companion Lio/getstream/video/android/core/socket/common/SocketActions$Companion; + public abstract fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun disconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun errors ()Lkotlinx/coroutines/flow/Flow; + public abstract fun events ()Lkotlinx/coroutines/flow/Flow; + public abstract fun reconnect (Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun reconnect$default (Lio/getstream/video/android/core/socket/common/SocketActions;Ljava/lang/Object;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; + public abstract fun sendData (Ljava/lang/String;)V + public abstract fun sendEvent (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun state ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun updateToken (Ljava/lang/Object;)V + public abstract fun whenConnected (JLkotlin/jvm/functions/Function2;)V + public static synthetic fun whenConnected$default (Lio/getstream/video/android/core/socket/common/SocketActions;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + +public final class io/getstream/video/android/core/socket/common/SocketActions$Companion { +} + +public class io/getstream/video/android/core/socket/common/SocketListener { + public fun ()V + public fun getDeliverOnMainThread ()Z + public fun onConnected (Ljava/lang/Object;)V + public fun onConnecting ()V + public fun onCreated ()V + public fun onDisconnected (Lio/getstream/video/android/core/errors/DisconnectCause;)V + public fun onError (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error;)V + public fun onEvent (Ljava/lang/Object;)V +} + +public abstract class io/getstream/video/android/core/socket/common/StreamWebSocketEvent { } -public final class io/getstream/video/android/core/socket/SocketState$Connected : io/getstream/video/android/core/socket/SocketState { +public final class io/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error : io/getstream/video/android/core/socket/common/StreamWebSocketEvent { + public fun (Lio/getstream/result/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public synthetic fun (Lio/getstream/result/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/result/Error; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/result/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error;Lio/getstream/result/Error;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error; + public fun equals (Ljava/lang/Object;)Z + public final fun getReconnectStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun getStreamError ()Lio/getstream/result/Error; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/StreamWebSocketEvent$SfuMessage : io/getstream/video/android/core/socket/common/StreamWebSocketEvent { + public fun (Lio/getstream/video/android/core/events/SfuDataEvent;)V + public final fun component1 ()Lio/getstream/video/android/core/events/SfuDataEvent; + public final fun copy (Lio/getstream/video/android/core/events/SfuDataEvent;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$SfuMessage; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$SfuMessage;Lio/getstream/video/android/core/events/SfuDataEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$SfuMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getSfuEvent ()Lio/getstream/video/android/core/events/SfuDataEvent; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/StreamWebSocketEvent$VideoMessage : io/getstream/video/android/core/socket/common/StreamWebSocketEvent { public fun (Lorg/openapitools/client/models/VideoEvent;)V public final fun component1 ()Lorg/openapitools/client/models/VideoEvent; - public final fun copy (Lorg/openapitools/client/models/VideoEvent;)Lio/getstream/video/android/core/socket/SocketState$Connected; - public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/SocketState$Connected;Lorg/openapitools/client/models/VideoEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/SocketState$Connected; + public final fun copy (Lorg/openapitools/client/models/VideoEvent;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$VideoMessage; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$VideoMessage;Lorg/openapitools/client/models/VideoEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$VideoMessage; + public fun equals (Ljava/lang/Object;)Z + public final fun getVideoEvent ()Lorg/openapitools/client/models/VideoEvent; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/TimeProvider { + public static final field INSTANCE Lio/getstream/video/android/core/socket/common/TimeProvider; + public final fun provideCurrentTimeInMilliseconds ()J + public final fun provideCurrentTimeInSeconds ()J +} + +public final class io/getstream/video/android/core/socket/common/VideoErrorDetail { + public fun (ILjava/util/List;)V + public final fun component1 ()I + public final fun component2 ()Ljava/util/List; + public final fun copy (ILjava/util/List;)Lio/getstream/video/android/core/socket/common/VideoErrorDetail; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/common/VideoErrorDetail;ILjava/util/List;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/common/VideoErrorDetail; + public fun equals (Ljava/lang/Object;)Z + public final fun getCode ()I + public final fun getMessages ()Ljava/util/List; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/common/VideoParserKt { + public static final fun fromVideoErrorCode (Lio/getstream/result/Error$NetworkError$Companion;Lio/getstream/video/android/core/errors/VideoErrorCode;ILjava/lang/Throwable;)Lio/getstream/result/Error$NetworkError; + public static synthetic fun fromVideoErrorCode$default (Lio/getstream/result/Error$NetworkError$Companion;Lio/getstream/video/android/core/errors/VideoErrorCode;ILjava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/result/Error$NetworkError; +} + +public final class io/getstream/video/android/core/socket/common/fsm/FiniteStateMachine { + public static final field Companion Lio/getstream/video/android/core/socket/common/fsm/FiniteStateMachine$Companion; + public fun (Ljava/lang/Object;Ljava/util/Map;Lkotlin/jvm/functions/Function2;)V + public final fun getState ()Ljava/lang/Object; + public final fun getStateFlow ()Lkotlinx/coroutines/flow/StateFlow; + public final fun sendEvent (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun stay ()Ljava/lang/Object; +} + +public final class io/getstream/video/android/core/socket/common/fsm/FiniteStateMachine$Companion { + public final fun invoke (Lkotlin/jvm/functions/Function1;)Lio/getstream/video/android/core/socket/common/fsm/FiniteStateMachine; +} + +public final class io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilder { + public fun ()V + public final fun defaultHandler (Lkotlin/jvm/functions/Function2;)V + public final fun getStateFunctions ()Ljava/util/Map; + public final fun initialState (Ljava/lang/Object;)V +} + +public abstract interface annotation class io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilderMarker : java/lang/annotation/Annotation { +} + +public final class io/getstream/video/android/core/socket/common/fsm/builder/StateHandlerBuilder { + public fun ()V + public final fun get ()Ljava/util/Map; + public final fun getEnterListeners ()Ljava/util/List; + public final fun getEventHandlers ()Ljava/util/Map; + public final fun getOnEnterListeners ()Ljava/util/List; + public final fun onEnter (Lkotlin/jvm/functions/Function2;)V +} + +public final class io/getstream/video/android/core/socket/common/parser2/adapters/DateAdapter : com/squareup/moshi/JsonAdapter { + public fun ()V + public synthetic fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/lang/Object; + public fun fromJson (Lcom/squareup/moshi/JsonReader;)Ljava/util/Date; + public synthetic fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/lang/Object;)V + public fun toJson (Lcom/squareup/moshi/JsonWriter;Ljava/util/Date;)V +} + +public final class io/getstream/video/android/core/socket/common/parser2/adapters/internal/StreamDateFormatter { + public static final field DATE_FORMAT Ljava/lang/String; + public static final field DATE_FORMAT_WITHOUT_NANOSECONDS Ljava/lang/String; + public static final field STATS Z + public fun ()V + public fun (Ljava/lang/String;Z)V + public synthetic fun (Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun format (Ljava/util/Date;)Ljava/lang/String; +} + +public abstract interface class io/getstream/video/android/core/socket/common/token/TokenProvider { + public abstract fun loadToken (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public class io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { + public fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lio/getstream/video/android/model/User;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun connect (Lio/getstream/video/android/model/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; + public fun disconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun errors ()Lkotlinx/coroutines/flow/Flow; + public fun events ()Lkotlinx/coroutines/flow/Flow; + public synthetic fun onConnected (Ljava/lang/Object;)V + public fun onConnected (Lorg/openapitools/client/models/ConnectedEvent;)V + public fun onConnecting ()V + public fun onCreated ()V + public fun onDisconnected (Lio/getstream/video/android/core/errors/DisconnectCause;)V + public fun onError (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error;)V + public synthetic fun onEvent (Ljava/lang/Object;)V + public fun onEvent (Lorg/openapitools/client/models/VideoEvent;)V + public fun reconnect (Lio/getstream/video/android/model/User;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun reconnect (Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun sendData (Ljava/lang/String;)V + public synthetic fun sendEvent (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun sendEvent (Lorg/openapitools/client/models/VideoEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun state ()Lkotlinx/coroutines/flow/StateFlow; + public synthetic fun updateToken (Ljava/lang/Object;)V + public fun updateToken (Ljava/lang/String;)V + public fun whenConnected (JLkotlin/jvm/functions/Function2;)V +} + +public final class io/getstream/video/android/core/socket/coordinator/state/RestartReason : java/lang/Enum { + public static final field LIFECYCLE_RESUME Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; + public static final field NETWORK_AVAILABLE Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; + public static fun values ()[Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType : java/lang/Enum { + public static final field AUTOMATIC_RECONNECTION Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public static final field FORCE_RECONNECTION Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public static final field INITIAL_CONNECTION Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public static fun values ()[Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; +} + +public abstract class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState { +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connected : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState { + public fun (Lorg/openapitools/client/models/ConnectedEvent;)V + public final fun component1 ()Lorg/openapitools/client/models/ConnectedEvent; + public final fun copy (Lorg/openapitools/client/models/ConnectedEvent;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connected; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connected;Lorg/openapitools/client/models/ConnectedEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connected; + public fun equals (Ljava/lang/Object;)Z + public final fun getEvent ()Lorg/openapitools/client/models/ConnectedEvent; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connecting : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState { + public fun (Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/common/ConnectionConf; + public final fun component2 ()Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public final fun copy (Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connecting; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connecting;Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Connecting; + public fun equals (Ljava/lang/Object;)Z + public final fun getConnectionConf ()Lio/getstream/video/android/core/socket/common/ConnectionConf; + public final fun getConnectionType ()Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState { +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedByRequest : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedByRequest; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedPermanently : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedPermanently; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedPermanently;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedPermanently; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedTemporarily : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedTemporarily; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedTemporarily;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$DisconnectedTemporarily; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$NetworkDisconnected : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$NetworkDisconnected; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$Stopped : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$Stopped; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$WebSocketEventLost : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$Disconnected$WebSocketEventLost; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketState$RestartConnection : io/getstream/video/android/core/socket/coordinator/state/VideoSocketState { + public fun (Lio/getstream/video/android/core/socket/coordinator/state/RestartReason;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; + public final fun copy (Lio/getstream/video/android/core/socket/coordinator/state/RestartReason;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$RestartConnection; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$RestartConnection;Lio/getstream/video/android/core/socket/coordinator/state/RestartReason;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketState$RestartConnection; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()Lio/getstream/video/android/core/socket/coordinator/state/RestartReason; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Connect : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public fun (Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/common/ConnectionConf; + public final fun component2 ()Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; + public final fun copy (Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Connect; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Connect;Lio/getstream/video/android/core/socket/common/ConnectionConf;Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Connect; public fun equals (Ljava/lang/Object;)Z - public final fun getEvent ()Lorg/openapitools/client/models/VideoEvent; + public final fun getConnectionConf ()Lio/getstream/video/android/core/socket/common/ConnectionConf; + public final fun getConnectionType ()Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$Connecting : io/getstream/video/android/core/socket/SocketState { - public static final field INSTANCE Lio/getstream/video/android/core/socket/SocketState$Connecting; +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$ConnectionEstablished : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public fun (Lorg/openapitools/client/models/ConnectedEvent;)V + public final fun component1 ()Lorg/openapitools/client/models/ConnectedEvent; + public final fun copy (Lorg/openapitools/client/models/ConnectedEvent;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$ConnectionEstablished; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$ConnectionEstablished;Lorg/openapitools/client/models/ConnectedEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$ConnectionEstablished; + public fun equals (Ljava/lang/Object;)Z + public final fun getConnectedEvent ()Lorg/openapitools/client/models/ConnectedEvent; + public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$DisconnectedByRequest : io/getstream/video/android/core/socket/SocketState { - public static final field INSTANCE Lio/getstream/video/android/core/socket/SocketState$DisconnectedByRequest; +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkAvailable : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkAvailable; public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$DisconnectedPermanently : io/getstream/video/android/core/socket/SocketState { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lio/getstream/video/android/core/socket/SocketState$DisconnectedPermanently; - public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/SocketState$DisconnectedPermanently;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/SocketState$DisconnectedPermanently; +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkError : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkError; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkError;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkError; public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/lang/Throwable; + public final fun getError ()Lio/getstream/result/Error$NetworkError; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$DisconnectedTemporarily : io/getstream/video/android/core/socket/SocketState { - public fun (Ljava/lang/Throwable;)V - public final fun component1 ()Ljava/lang/Throwable; - public final fun copy (Ljava/lang/Throwable;)Lio/getstream/video/android/core/socket/SocketState$DisconnectedTemporarily; - public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/SocketState$DisconnectedTemporarily;Ljava/lang/Throwable;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/SocketState$DisconnectedTemporarily; +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkNotAvailable : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$NetworkNotAvailable; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$RequiredDisconnection : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$RequiredDisconnection; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Resume : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Resume; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Stop : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$Stop; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$UnrecoverableError : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$UnrecoverableError; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$UnrecoverableError;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$UnrecoverableError; public fun equals (Ljava/lang/Object;)Z - public final fun getError ()Ljava/lang/Throwable; + public final fun getError ()Lio/getstream/result/Error$NetworkError; public fun hashCode ()I public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$NetworkDisconnected : io/getstream/video/android/core/socket/SocketState { - public static final field INSTANCE Lio/getstream/video/android/core/socket/SocketState$NetworkDisconnected; +public final class io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$WebSocketEventLost : io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent$WebSocketEventLost; public fun toString ()Ljava/lang/String; } -public final class io/getstream/video/android/core/socket/SocketState$NotConnected : io/getstream/video/android/core/socket/SocketState { - public static final field INSTANCE Lio/getstream/video/android/core/socket/SocketState$NotConnected; +public final class io/getstream/video/android/core/socket/sfu/SfuSocketConnection : io/getstream/video/android/core/socket/common/SocketListener, io/getstream/video/android/core/socket/common/SocketActions { + public static final field Companion Lio/getstream/video/android/core/socket/sfu/SfuSocketConnection$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokhttp3/OkHttpClient;Lio/getstream/video/android/core/internal/network/NetworkStateProvider;Lkotlinx/coroutines/CoroutineScope;Landroidx/lifecycle/Lifecycle;Lio/getstream/video/android/core/socket/common/token/TokenProvider;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun connect (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun connect (Lstream/video/sfu/event/JoinRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun connectionId ()Lkotlinx/coroutines/flow/StateFlow; + public fun disconnect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun errors ()Lkotlinx/coroutines/flow/Flow; + public fun errors ()Lkotlinx/coroutines/flow/MutableSharedFlow; + public synthetic fun events ()Lkotlinx/coroutines/flow/Flow; + public fun events ()Lkotlinx/coroutines/flow/MutableSharedFlow; + public fun onConnected (Lio/getstream/video/android/core/events/JoinCallResponseEvent;)V + public synthetic fun onConnected (Ljava/lang/Object;)V + public fun onConnecting ()V + public fun onCreated ()V + public fun onDisconnected (Lio/getstream/video/android/core/errors/DisconnectCause;)V + public fun onError (Lio/getstream/video/android/core/socket/common/StreamWebSocketEvent$Error;)V + public fun onEvent (Lio/getstream/video/android/core/events/SfuDataEvent;)V + public synthetic fun onEvent (Ljava/lang/Object;)V + public synthetic fun reconnect (Ljava/lang/Object;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun reconnect (Lstream/video/sfu/event/JoinRequest;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun sendData (Ljava/lang/String;)V + public fun sendEvent (Lio/getstream/video/android/core/events/SfuDataRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public synthetic fun sendEvent (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun state ()Lkotlinx/coroutines/flow/StateFlow; + public synthetic fun updateToken (Ljava/lang/Object;)V + public fun updateToken (Ljava/lang/String;)V + public fun whenConnected (JLkotlin/jvm/functions/Function2;)V +} + +public final class io/getstream/video/android/core/socket/sfu/SfuSocketConnection$Companion { +} + +public final class io/getstream/video/android/core/socket/sfu/state/RestartReason : java/lang/Enum { + public static final field NETWORK_AVAILABLE Lio/getstream/video/android/core/socket/sfu/state/RestartReason; + public static final field RECONNECT_STRATEGY Lio/getstream/video/android/core/socket/sfu/state/RestartReason; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lio/getstream/video/android/core/socket/sfu/state/RestartReason; + public static fun values ()[Lio/getstream/video/android/core/socket/sfu/state/RestartReason; +} + +public abstract class io/getstream/video/android/core/socket/sfu/state/SfuSocketState { +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connected : io/getstream/video/android/core/socket/sfu/state/SfuSocketState { + public fun (Lio/getstream/video/android/core/events/JoinCallResponseEvent;)V + public final fun component1 ()Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public final fun copy (Lio/getstream/video/android/core/events/JoinCallResponseEvent;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connected; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connected;Lio/getstream/video/android/core/events/JoinCallResponseEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connected; + public fun equals (Ljava/lang/Object;)Z + public final fun getEvent ()Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connecting : io/getstream/video/android/core/socket/sfu/state/SfuSocketState { + public fun (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public synthetic fun (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connecting; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connecting;Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Connecting; + public fun equals (Ljava/lang/Object;)Z + public final fun getConnectionConf ()Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public final fun getConnectionType ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected : io/getstream/video/android/core/socket/sfu/state/SfuSocketState { +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedByRequest : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedByRequest; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedPermanently : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedPermanently; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedPermanently;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedPermanently; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedTemporarily : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public fun (Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedTemporarily; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedTemporarily;Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$DisconnectedTemporarily; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public final fun getReconnectStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$NetworkDisconnected : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$NetworkDisconnected; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$Rejoin : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$Rejoin; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$Stopped : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$Stopped; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$WebSocketEventLost : io/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$Disconnected$WebSocketEventLost; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketState$RestartConnection : io/getstream/video/android/core/socket/sfu/state/SfuSocketState { + public fun (Lio/getstream/video/android/core/socket/sfu/state/RestartReason;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/sfu/state/RestartReason; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/video/android/core/socket/sfu/state/RestartReason;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$RestartConnection; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$RestartConnection;Lio/getstream/video/android/core/socket/sfu/state/RestartReason;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketState$RestartConnection; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()Lio/getstream/video/android/core/socket/sfu/state/RestartReason; + public final fun getReconnectStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Connect : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public fun (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public synthetic fun (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Connect; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Connect;Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Connect; + public fun equals (Ljava/lang/Object;)Z + public final fun getConnectionConf ()Lio/getstream/video/android/core/socket/common/ConnectionConf$SfuConnectionConf; + public final fun getConnectionType ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$ConnectionEstablished : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public fun (Lio/getstream/video/android/core/events/JoinCallResponseEvent;)V + public final fun component1 ()Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public final fun copy (Lio/getstream/video/android/core/events/JoinCallResponseEvent;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$ConnectionEstablished; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$ConnectionEstablished;Lio/getstream/video/android/core/events/JoinCallResponseEvent;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$ConnectionEstablished; + public fun equals (Ljava/lang/Object;)Z + public final fun getConnectedEvent ()Lio/getstream/video/android/core/events/JoinCallResponseEvent; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkAvailable : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkAvailable; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkError : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public fun (Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun component2 ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun copy (Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkError; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkError;Lio/getstream/result/Error$NetworkError;Lstream/video/sfu/models/WebsocketReconnectStrategy;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkError; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public final fun getReconnectStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkNotAvailable : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$NetworkNotAvailable; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$RequiredDisconnection : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$RequiredDisconnection; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Resume : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Resume; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Stop : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$Stop; + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$UnrecoverableError : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public fun (Lio/getstream/result/Error$NetworkError;)V + public final fun component1 ()Lio/getstream/result/Error$NetworkError; + public final fun copy (Lio/getstream/result/Error$NetworkError;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$UnrecoverableError; + public static synthetic fun copy$default (Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$UnrecoverableError;Lio/getstream/result/Error$NetworkError;ILjava/lang/Object;)Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$UnrecoverableError; + public fun equals (Ljava/lang/Object;)Z + public final fun getError ()Lio/getstream/result/Error$NetworkError; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$WebSocketEventLost : io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent { + public static final field INSTANCE Lio/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent$WebSocketEventLost; public fun toString ()Ljava/lang/String; } @@ -4528,7 +5090,6 @@ public final class io/getstream/video/android/core/sounds/Sounds { } public final class io/getstream/video/android/core/utils/AndroidUtilsKt { - public static final fun safeCall (Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object; public static final fun shouldShowRequestPermissionsRationale (Landroid/app/Activity;[Ljava/lang/String;)Z } @@ -6042,6 +6603,7 @@ public synthetic class io/getstream/video/android/model/User$$serializer : kotli public final class io/getstream/video/android/model/User$Companion { public final fun anonymous ()Lio/getstream/video/android/model/User; + public final fun isAnonymous (Lio/getstream/video/android/model/User;)Z public final fun serializer ()Lkotlinx/serialization/KSerializer; } @@ -10180,6 +10742,12 @@ public final class org/openapitools/client/models/UnpinResponse { public fun toString ()Ljava/lang/String; } +public final class org/openapitools/client/models/UnsupportedVideoEvent : org/openapitools/client/models/VideoEvent { + public fun (Ljava/lang/String;)V + public fun getEventType ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; +} + public final class org/openapitools/client/models/UnsupportedVideoEventException : java/lang/Exception { public fun (Ljava/lang/String;)V public final fun getType ()Ljava/lang/String; @@ -10929,13 +11497,14 @@ public final class org/openapitools/client/models/WSAuthMessage { public fun toString ()Ljava/lang/String; } -public final class org/openapitools/client/models/WSAuthMessageRequest { +public final class org/openapitools/client/models/WSAuthMessageRequest : org/openapitools/client/models/VideoEvent { public fun (Ljava/lang/String;Lorg/openapitools/client/models/ConnectUserDetailsRequest;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Lorg/openapitools/client/models/ConnectUserDetailsRequest; public final fun copy (Ljava/lang/String;Lorg/openapitools/client/models/ConnectUserDetailsRequest;)Lorg/openapitools/client/models/WSAuthMessageRequest; public static synthetic fun copy$default (Lorg/openapitools/client/models/WSAuthMessageRequest;Ljava/lang/String;Lorg/openapitools/client/models/ConnectUserDetailsRequest;ILjava/lang/Object;)Lorg/openapitools/client/models/WSAuthMessageRequest; public fun equals (Ljava/lang/Object;)Z + public fun getEventType ()Ljava/lang/String; public final fun getToken ()Ljava/lang/String; public final fun getUserDetails ()Lorg/openapitools/client/models/ConnectUserDetailsRequest; public fun hashCode ()I @@ -11267,14 +11836,15 @@ public final class stream/video/sfu/event/JoinRequest : com/squareup/wire/Messag public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/JoinRequest$Companion; public fun ()V - public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLokio/ByteString;)V - public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLokio/ByteString;)Lstream/video/sfu/event/JoinRequest; - public static synthetic fun copy$default (Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/JoinRequest; + public fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLstream/video/sfu/event/ReconnectDetails;Lokio/ByteString;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLstream/video/sfu/event/ReconnectDetails;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLstream/video/sfu/event/ReconnectDetails;Lokio/ByteString;)Lstream/video/sfu/event/JoinRequest; + public static synthetic fun copy$default (Lstream/video/sfu/event/JoinRequest;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lstream/video/sfu/models/ClientDetails;Lstream/video/sfu/event/Migration;ZLstream/video/sfu/event/ReconnectDetails;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/JoinRequest; public fun equals (Ljava/lang/Object;)Z public final fun getClient_details ()Lstream/video/sfu/models/ClientDetails; public final fun getFast_reconnect ()Z public final fun getMigration ()Lstream/video/sfu/event/Migration; + public final fun getReconnect_details ()Lstream/video/sfu/event/ReconnectDetails; public final fun getSession_id ()Ljava/lang/String; public final fun getSubscriber_sdp ()Ljava/lang/String; public final fun getToken ()Ljava/lang/String; @@ -11291,12 +11861,13 @@ public final class stream/video/sfu/event/JoinResponse : com/squareup/wire/Messa public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/JoinResponse$Companion; public fun ()V - public fun (Lstream/video/sfu/models/CallState;ZLokio/ByteString;)V - public synthetic fun (Lstream/video/sfu/models/CallState;ZLokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Lstream/video/sfu/models/CallState;ZLokio/ByteString;)Lstream/video/sfu/event/JoinResponse; - public static synthetic fun copy$default (Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/models/CallState;ZLokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/JoinResponse; + public fun (Lstream/video/sfu/models/CallState;ZILokio/ByteString;)V + public synthetic fun (Lstream/video/sfu/models/CallState;ZILokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lstream/video/sfu/models/CallState;ZILokio/ByteString;)Lstream/video/sfu/event/JoinResponse; + public static synthetic fun copy$default (Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/models/CallState;ZILokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/JoinResponse; public fun equals (Ljava/lang/Object;)Z public final fun getCall_state ()Lstream/video/sfu/models/CallState; + public final fun getFast_reconnect_deadline_seconds ()I public final fun getReconnected ()Z public fun hashCode ()I public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; @@ -11307,6 +11878,26 @@ public final class stream/video/sfu/event/JoinResponse : com/squareup/wire/Messa public final class stream/video/sfu/event/JoinResponse$Companion { } +public final class stream/video/sfu/event/LeaveCallRequest : com/squareup/wire/Message { + public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; + public static final field Companion Lstream/video/sfu/event/LeaveCallRequest$Companion; + public fun ()V + public fun (Ljava/lang/String;Ljava/lang/String;Lokio/ByteString;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Ljava/lang/String;Ljava/lang/String;Lokio/ByteString;)Lstream/video/sfu/event/LeaveCallRequest; + public static synthetic fun copy$default (Lstream/video/sfu/event/LeaveCallRequest;Ljava/lang/String;Ljava/lang/String;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/LeaveCallRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getReason ()Ljava/lang/String; + public final fun getSession_id ()Ljava/lang/String; + public fun hashCode ()I + public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; + public synthetic fun newBuilder ()Ljava/lang/Void; + public fun toString ()Ljava/lang/String; +} + +public final class stream/video/sfu/event/LeaveCallRequest$Companion { +} + public final class stream/video/sfu/event/Migration : com/squareup/wire/Message { public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/Migration$Companion; @@ -11368,6 +11959,24 @@ public final class stream/video/sfu/event/ParticipantLeft : com/squareup/wire/Me public final class stream/video/sfu/event/ParticipantLeft$Companion { } +public final class stream/video/sfu/event/ParticipantMigrationComplete : com/squareup/wire/Message { + public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; + public static final field Companion Lstream/video/sfu/event/ParticipantMigrationComplete$Companion; + public fun ()V + public fun (Lokio/ByteString;)V + public synthetic fun (Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lokio/ByteString;)Lstream/video/sfu/event/ParticipantMigrationComplete; + public static synthetic fun copy$default (Lstream/video/sfu/event/ParticipantMigrationComplete;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/ParticipantMigrationComplete; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; + public synthetic fun newBuilder ()Ljava/lang/Void; + public fun toString ()Ljava/lang/String; +} + +public final class stream/video/sfu/event/ParticipantMigrationComplete$Companion { +} + public final class stream/video/sfu/event/ParticipantUpdated : com/squareup/wire/Message { public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/ParticipantUpdated$Companion; @@ -11426,14 +12035,38 @@ public final class stream/video/sfu/event/PublisherAnswer : com/squareup/wire/Me public final class stream/video/sfu/event/PublisherAnswer$Companion { } +public final class stream/video/sfu/event/ReconnectDetails : com/squareup/wire/Message { + public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; + public static final field Companion Lstream/video/sfu/event/ReconnectDetails$Companion; + public fun ()V + public fun (Lstream/video/sfu/models/WebsocketReconnectStrategy;Ljava/util/List;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lokio/ByteString;)V + public synthetic fun (Lstream/video/sfu/models/WebsocketReconnectStrategy;Ljava/util/List;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lstream/video/sfu/models/WebsocketReconnectStrategy;Ljava/util/List;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lokio/ByteString;)Lstream/video/sfu/event/ReconnectDetails; + public static synthetic fun copy$default (Lstream/video/sfu/event/ReconnectDetails;Lstream/video/sfu/models/WebsocketReconnectStrategy;Ljava/util/List;Ljava/util/List;ILjava/lang/String;Ljava/lang/String;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/ReconnectDetails; + public fun equals (Ljava/lang/Object;)Z + public final fun getAnnounced_tracks ()Ljava/util/List; + public final fun getFrom_sfu_id ()Ljava/lang/String; + public final fun getPrevious_session_id ()Ljava/lang/String; + public final fun getReconnect_attempt ()I + public final fun getStrategy ()Lstream/video/sfu/models/WebsocketReconnectStrategy; + public final fun getSubscriptions ()Ljava/util/List; + public fun hashCode ()I + public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; + public synthetic fun newBuilder ()Ljava/lang/Void; + public fun toString ()Ljava/lang/String; +} + +public final class stream/video/sfu/event/ReconnectDetails$Companion { +} + public final class stream/video/sfu/event/SfuEvent : com/squareup/wire/Message { public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/SfuEvent$Companion; public fun ()V - public fun (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lokio/ByteString;)V - public synthetic fun (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lokio/ByteString;)Lstream/video/sfu/event/SfuEvent; - public static synthetic fun copy$default (Lstream/video/sfu/event/SfuEvent;Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/SfuEvent; + public fun (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lstream/video/sfu/event/ParticipantMigrationComplete;Lokio/ByteString;)V + public synthetic fun (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lstream/video/sfu/event/ParticipantMigrationComplete;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lstream/video/sfu/event/ParticipantMigrationComplete;Lokio/ByteString;)Lstream/video/sfu/event/SfuEvent; + public static synthetic fun copy$default (Lstream/video/sfu/event/SfuEvent;Lstream/video/sfu/event/SubscriberOffer;Lstream/video/sfu/event/PublisherAnswer;Lstream/video/sfu/event/ConnectionQualityChanged;Lstream/video/sfu/event/AudioLevelChanged;Lstream/video/sfu/models/ICETrickle;Lstream/video/sfu/event/ChangePublishQuality;Lstream/video/sfu/event/ParticipantJoined;Lstream/video/sfu/event/ParticipantLeft;Lstream/video/sfu/event/DominantSpeakerChanged;Lstream/video/sfu/event/JoinResponse;Lstream/video/sfu/event/HealthCheckResponse;Lstream/video/sfu/event/TrackPublished;Lstream/video/sfu/event/TrackUnpublished;Lstream/video/sfu/event/Error;Lstream/video/sfu/event/CallGrantsUpdated;Lstream/video/sfu/event/GoAway;Lstream/video/sfu/event/ICERestart;Lstream/video/sfu/event/PinsChanged;Lstream/video/sfu/event/CallEnded;Lstream/video/sfu/event/ParticipantUpdated;Lstream/video/sfu/event/ParticipantMigrationComplete;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/SfuEvent; public fun equals (Ljava/lang/Object;)Z public final fun getAudio_level_changed ()Lstream/video/sfu/event/AudioLevelChanged; public final fun getCall_ended ()Lstream/video/sfu/event/CallEnded; @@ -11449,6 +12082,7 @@ public final class stream/video/sfu/event/SfuEvent : com/squareup/wire/Message { public final fun getJoin_response ()Lstream/video/sfu/event/JoinResponse; public final fun getParticipant_joined ()Lstream/video/sfu/event/ParticipantJoined; public final fun getParticipant_left ()Lstream/video/sfu/event/ParticipantLeft; + public final fun getParticipant_migration_complete ()Lstream/video/sfu/event/ParticipantMigrationComplete; public final fun getParticipant_updated ()Lstream/video/sfu/event/ParticipantUpdated; public final fun getPins_updated ()Lstream/video/sfu/event/PinsChanged; public final fun getPublisher_answer ()Lstream/video/sfu/event/PublisherAnswer; @@ -11468,13 +12102,14 @@ public final class stream/video/sfu/event/SfuRequest : com/squareup/wire/Message public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/event/SfuRequest$Companion; public fun ()V - public fun (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lokio/ByteString;)V - public synthetic fun (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public final fun copy (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lokio/ByteString;)Lstream/video/sfu/event/SfuRequest; - public static synthetic fun copy$default (Lstream/video/sfu/event/SfuRequest;Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/SfuRequest; + public fun (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lstream/video/sfu/event/LeaveCallRequest;Lokio/ByteString;)V + public synthetic fun (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lstream/video/sfu/event/LeaveCallRequest;Lokio/ByteString;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun copy (Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lstream/video/sfu/event/LeaveCallRequest;Lokio/ByteString;)Lstream/video/sfu/event/SfuRequest; + public static synthetic fun copy$default (Lstream/video/sfu/event/SfuRequest;Lstream/video/sfu/event/JoinRequest;Lstream/video/sfu/event/HealthCheckRequest;Lstream/video/sfu/event/LeaveCallRequest;Lokio/ByteString;ILjava/lang/Object;)Lstream/video/sfu/event/SfuRequest; public fun equals (Ljava/lang/Object;)Z public final fun getHealth_check_request ()Lstream/video/sfu/event/HealthCheckRequest; public final fun getJoin_request ()Lstream/video/sfu/event/JoinRequest; + public final fun getLeave_call_request ()Lstream/video/sfu/event/LeaveCallRequest; public fun hashCode ()I public synthetic fun newBuilder ()Lcom/squareup/wire/Message$Builder; public synthetic fun newBuilder ()Ljava/lang/Void; @@ -12054,7 +12689,9 @@ public final class stream/video/sfu/models/SdkType : java/lang/Enum, com/squareu public static final field SDK_TYPE_ANDROID Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_ANGULAR Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_FLUTTER Lstream/video/sfu/models/SdkType; + public static final field SDK_TYPE_GO Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_IOS Lstream/video/sfu/models/SdkType; + public static final field SDK_TYPE_PLAIN_JAVASCRIPT Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_REACT Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_REACT_NATIVE Lstream/video/sfu/models/SdkType; public static final field SDK_TYPE_UNITY Lstream/video/sfu/models/SdkType; @@ -12216,11 +12853,10 @@ public final class stream/video/sfu/models/VideoQuality$Companion { public final class stream/video/sfu/models/WebsocketReconnectStrategy : java/lang/Enum, com/squareup/wire/WireEnum { public static final field ADAPTER Lcom/squareup/wire/ProtoAdapter; public static final field Companion Lstream/video/sfu/models/WebsocketReconnectStrategy$Companion; - public static final field WEBSOCKET_RECONNECT_STRATEGY_CLEAN Lstream/video/sfu/models/WebsocketReconnectStrategy; public static final field WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT Lstream/video/sfu/models/WebsocketReconnectStrategy; public static final field WEBSOCKET_RECONNECT_STRATEGY_FAST Lstream/video/sfu/models/WebsocketReconnectStrategy; - public static final field WEBSOCKET_RECONNECT_STRATEGY_FULL Lstream/video/sfu/models/WebsocketReconnectStrategy; public static final field WEBSOCKET_RECONNECT_STRATEGY_MIGRATE Lstream/video/sfu/models/WebsocketReconnectStrategy; + public static final field WEBSOCKET_RECONNECT_STRATEGY_REJOIN Lstream/video/sfu/models/WebsocketReconnectStrategy; public static final field WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED Lstream/video/sfu/models/WebsocketReconnectStrategy; public static final fun fromValue (I)Lstream/video/sfu/models/WebsocketReconnectStrategy; public static fun getEntries ()Lkotlin/enums/EnumEntries; diff --git a/stream-video-android-core/build.gradle.kts b/stream-video-android-core/build.gradle.kts index 383a0bf243..2d5f508cee 100644 --- a/stream-video-android-core/build.gradle.kts +++ b/stream-video-android-core/build.gradle.kts @@ -166,6 +166,8 @@ dependencies { implementation(libs.okhttp) implementation(libs.okhttp.logging) + implementation(libs.ituDate) + implementation(libs.moshi) implementation(libs.moshi.kotlin) implementation(libs.moshi.adapters) @@ -177,6 +179,7 @@ dependencies { // Stream api(libs.stream.result) + api(libs.stream.result.call) api(libs.stream.log.android) implementation(libs.stream.push) implementation(libs.stream.push.delegate) diff --git a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/IntegrationTestBase.kt b/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/IntegrationTestBase.kt index 2cfa352e85..866daff04c 100644 --- a/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/IntegrationTestBase.kt +++ b/stream-video-android-core/src/androidTest/kotlin/io/getstream/video/android/core/IntegrationTestBase.kt @@ -19,9 +19,9 @@ package io.getstream.video.android.core import android.content.Context import androidx.test.core.app.ApplicationProvider import com.google.common.truth.Truth +import io.getstream.log.AndroidStreamLogger import io.getstream.log.Priority import io.getstream.log.StreamLog -import io.getstream.log.android.AndroidStreamLogger import io.getstream.log.streamLog import io.getstream.result.Result import io.getstream.video.android.core.logging.LoggingLevel @@ -132,7 +132,7 @@ open class IntegrationTestBase(connectCoordinatorWS: Boolean = true) : TestBase( val client: StreamVideo /** Implementation of the client for more access to interals */ - internal val clientImpl: StreamVideoImpl + internal val clientImpl: StreamVideoClient /** Tracks all events received by the client during a test */ var events: MutableList @@ -158,7 +158,7 @@ open class IntegrationTestBase(connectCoordinatorWS: Boolean = true) : TestBase( if (IntegrationTestState.client == null) { client = builder.build() - clientImpl = client as StreamVideoImpl + clientImpl = client as StreamVideoClient // Connect to the WS if needed if (connectCoordinatorWS) { // wait for the connection/ avoids race conditions in tests @@ -172,7 +172,7 @@ open class IntegrationTestBase(connectCoordinatorWS: Boolean = true) : TestBase( IntegrationTestState.client = client } else { client = IntegrationTestState.client!! - clientImpl = client as StreamVideoImpl + clientImpl = client as StreamVideoClient } // monitor for events diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt index 243e18b8a6..bfc6f1b633 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/Call.kt @@ -31,8 +31,10 @@ 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 import io.getstream.video.android.core.events.GoAwayEvent +import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.VideoEventListener import io.getstream.video.android.core.internal.InternalStreamVideoApi +import io.getstream.video.android.core.internal.network.NetworkStateProvider import io.getstream.video.android.core.model.MuteUsersData import io.getstream.video.android.core.model.QueriedMembers import io.getstream.video.android.core.model.RejectReason @@ -40,7 +42,6 @@ import io.getstream.video.android.core.model.SortField import io.getstream.video.android.core.model.UpdateUserPermissionsData import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.core.model.toIceServer -import io.getstream.video.android.core.socket.SocketState import io.getstream.video.android.core.utils.RampValueUpAndDownHelper import io.getstream.video.android.core.utils.safeCall import io.getstream.video.android.core.utils.toQueriedMembers @@ -49,14 +50,15 @@ import io.getstream.webrtc.android.ui.VideoTextureViewRenderer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.onFailure import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.first import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull import org.openapitools.client.models.AcceptCallResponse import org.openapitools.client.models.AudioSettingsResponse import org.openapitools.client.models.BlockUserResponse @@ -84,12 +86,16 @@ import org.openapitools.client.models.UpdateUserPermissionsResponse import org.openapitools.client.models.VideoEvent import org.openapitools.client.models.VideoSettingsResponse import org.threeten.bp.OffsetDateTime +import org.webrtc.PeerConnection import org.webrtc.RendererCommon import org.webrtc.VideoSink import org.webrtc.audio.JavaAudioDeviceModule.AudioSamples +import stream.video.sfu.event.ReconnectDetails import stream.video.sfu.models.TrackType import stream.video.sfu.models.VideoDimension +import stream.video.sfu.models.WebsocketReconnectStrategy import java.util.Collections +import java.util.UUID import kotlin.coroutines.resume /** @@ -115,10 +121,11 @@ public class Call( val id: String, val user: User, ) { - private var location: String? = null + internal var location: String? = null private var subscriptions = Collections.synchronizedSet(mutableSetOf()) - internal val clientImpl = client as StreamVideoImpl + internal var reconnectAttepmts = 0 + internal val clientImpl = client as StreamVideoClient private val logger by taggedLogger("Call:$type:$id") private val supervisorJob = SupervisorJob() @@ -129,8 +136,7 @@ public class Call( /** The call state contains all state such as the participant list, reactions etc */ val state = CallState(client, this, user, scope) - val sessionId by lazy { clientImpl.sessionId } - private val network by lazy { clientImpl.connectionModule.networkStateProvider } + private val network by lazy { clientImpl.coordinatorConnectionModule.networkStateProvider } /** Camera gives you access to the local camera */ val camera by lazy(LazyThreadSafetyMode.PUBLICATION) { mediaManager.camera } @@ -151,18 +157,7 @@ public class Call( */ var audioFilter: InputAudioFilter? = null - /** - * Called by the [CallHealthMonitor] when the ICE restarts failed after - * several retries. At this point we can do a full reconnect. - */ - private val onIceRecoveryFailed = { - scope.launch { - handleSignalChannelDisconnect(false) - } - Unit - } - - val monitor = CallHealthMonitor(this, scope, onIceRecoveryFailed) + // val monitor = CallHealthMonitor(this, scope, onIceRecoveryFailed) private val soundInputProcessor = SoundInputProcessor(thresholdCrossedCallback = { if (!microphone.isEnabled.value) { @@ -206,6 +201,8 @@ public class Call( /** Session handles all real time communication for video and audio */ internal var session: RtcSession? = null + var sessionId = UUID.randomUUID().toString() + internal val mediaManager by lazy { if (testInstanceProvider.mediaManagerCreator != null) { testInstanceProvider.mediaManagerCreator!!.invoke() @@ -220,12 +217,55 @@ public class Call( } } + private val listener = object : NetworkStateProvider.NetworkStateListener { + override suspend fun onConnected() { + leaveTimeoutAfterDisconnect?.cancel() + logger.d { "[onConnected] no args" } + val elapsedTimeMils = System.currentTimeMillis() - lastDisconnect + if (lastDisconnect > 0 && elapsedTimeMils < reconnectDeadlineMils) { + logger.d { + "[onConnected] Reconnecting (fast) time since last disconnect is ${elapsedTimeMils / 1000} seconds. Deadline is ${reconnectDeadlineMils / 1000} seconds" + } + fastReconnect() + } else { + logger.d { + "[onConnected] Reconnecting (full) time since last disconnect is ${elapsedTimeMils / 1000} seconds. Deadline is ${reconnectDeadlineMils / 1000} seconds" + } + rejoin() + } + } + + override suspend fun onDisconnected() { + state._connection.value = RealtimeConnection.Reconnecting + lastDisconnect = System.currentTimeMillis() + leaveTimeoutAfterDisconnect = scope.launch { + delay(clientImpl.leaveAfterDisconnectSeconds * 1000) + logger.d { + "[onDisconnected] Leaving after being disconnected for ${clientImpl.leaveAfterDisconnectSeconds}" + } + leave() + } + logger.d { "[onDisconnected] at $lastDisconnect" } + } + } + + private var leaveTimeoutAfterDisconnect: Job? = null + private var lastDisconnect = 0L + private var reconnectDeadlineMils: Int = 10_000 + + private var monitorPublisherPCStateJob: Job? = null + private var monitorSubscriberPCStateJob: Job? = null + private var sfuListener: Job? = null + private var sfuEvents: Job? = null + init { scope.launch { soundInputProcessor.currentAudioLevel.collect { audioLevelOutputHelper.rampToValue(it) } } + + waitForReconnectTasks() } /** Basic crud operations */ @@ -375,6 +415,10 @@ public class Call( ring: Boolean = false, notify: Boolean = false, ): Result { + reconnectAttepmts = 0 + sfuEvents?.cancel() + sfuListener?.cancel() + if (session != null) { throw IllegalStateException( "Call $cid has already been joined. Please use call.leave before joining it again", @@ -404,23 +448,23 @@ public class Call( return result as Failure } val sfuToken = result.value.credentials.token - val sfuUrl = clientImpl.testSfuAddress ?: result.value.credentials.server.url + val sfuUrl = result.value.credentials.server.url + val sfuWsUrl = result.value.credentials.server.wsEndpoint val iceServers = result.value.credentials.iceServers.map { it.toIceServer() } session = if (testInstanceProvider.rtcSessionCreator != null) { testInstanceProvider.rtcSessionCreator!!.invoke() } else { RtcSession( + sessionId = this.sessionId, + apiKey = clientImpl.apiKey, + lifecycle = clientImpl.coordinatorConnectionModule.lifecycle, client = client, call = this, sfuUrl = sfuUrl, + sfuWsUrl = sfuWsUrl, sfuToken = sfuToken, - connectionModule = (client as StreamVideoImpl).connectionModule, remoteIceServers = iceServers, - onMigrationCompleted = { - state._connection.value = RealtimeConnection.Connected - monitor.check() - }, ) } @@ -433,31 +477,56 @@ public class Call( } catch (e: Exception) { return Failure(Error.GenericError(e.message ?: "RtcSession error occurred.")) } - - scope.launch { - // wait for the first stream to be added - session?.let { rtcSession -> - val mainRtcSession = rtcSession.lastVideoStreamAdded.filter { it != null }.first() - logger.d { "stream added, rtc completed, ready to display video $mainRtcSession" } - } - } - - monitor.start() client.state.setActiveCall(this) - startCallStatsReporting(result.value.statsOptions.reportingIntervalMs.toLong()) + monitorSession(result.value) + return Success(value = session!!) + } + private suspend fun Call.monitorSession(result: JoinCallResponse) { + sfuEvents?.cancel() + sfuListener?.cancel() + startCallStatsReporting(result.statsOptions.reportingIntervalMs.toLong()) // listen to Signal WS - scope.launch { + sfuEvents = scope.launch { session?.let { - it.sfuSocketState.collect { sfuSocketState -> - if (sfuSocketState is SocketState.DisconnectedPermanently) { - handleSignalChannelDisconnect(isRetry = false) + it.socket.events().collect { event -> + if (event is JoinCallResponseEvent) { + reconnectDeadlineMils = event.fastReconnectDeadlineSeconds * 1000 + logger.d { "[join] #deadline for reconnect is ${reconnectDeadlineMils / 1000} seconds" } } } } } + monitorPublisherPCStateJob?.cancel() + monitorPublisherPCStateJob = scope.launch { + session?.publisher?.iceState?.collect { + when (it) { + PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED -> { + session?.publisher?.connection?.restartIce() + } - return Success(value = session!!) + else -> { + logger.d { "[monitorConnectionState] Ice connection state is $it" } + } + } + } + } + + monitorSubscriberPCStateJob?.cancel() + monitorSubscriberPCStateJob = scope.launch { + session?.subscriber?.iceState?.collect { + when (it) { + PeerConnection.IceConnectionState.FAILED, PeerConnection.IceConnectionState.DISCONNECTED -> { + session?.requestSubscriberIceRestart() + } + + else -> { + logger.d { "[monitorConnectionState] Ice connection state is $it" } + } + } + } + } + network.subscribe(listener) } private suspend fun startCallStatsReporting(reportingIntervalMs: Long = 10_000) { @@ -493,84 +562,94 @@ public class Call( } } - private suspend fun handleSignalChannelDisconnect(isRetry: Boolean) { - // Prevent multiple starts of the reconnect flow. For the start call - // first check if sfuSocketReconnectionTime isn't already set - if yes - // then we are already doing a full reconnect - if (state._connection.value == RealtimeConnection.Migrating) { - logger.d { - "[handleSignalChannelDisconnect] #track; Skipping disconnected channel event - we are migrating" - } - return - } - - if (!isRetry && sfuSocketReconnectionTime != null) { - logger.d { - "[handleSignalChannelDisconnect] #track; Already doing a full reconnect cycle - ignoring call" - } - return - } - logger.d { "[handleSignalChannelDisconnect] #track; isRetry: $isRetry" } - - if (!isRetry) { - state._connection.value = RealtimeConnection.Reconnecting - - if (sfuSocketReconnectionTime == null) { - sfuSocketReconnectionTime = System.currentTimeMillis() - } - - // We were not able to restore the SFU peer connection in time - if (System.currentTimeMillis() - ( - sfuSocketReconnectionTime - ?: System.currentTimeMillis() - ) > sfuReconnectTimeoutMillis - ) { - leave(Error("Failed to do a full reconnect - connection issue?")) - return - } - - // Clean up the existing RtcSession - session?.cleanup() - session = null - - // Wait a little for clean-up - delay(250) - - // Re-join the call - val result = _join() - if (result.isFailure) { - // keep trying until timeout - handleSignalChannelDisconnect(isRetry = true) + internal suspend fun rejoin() = schedule { + logger.d { "[rejoin] Rejoining" } + reconnectAttepmts++ + state._connection.value = RealtimeConnection.Reconnecting + location?.let { + val joinResponse = joinRequest(location = it) + if (joinResponse is Success) { + // switch to the new SFU + val cred = joinResponse.value.credentials + val session = this.session!! + logger.i { "Rejoin SFU ${session?.sfuUrl} to ${cred.server.url}" } + + this.sessionId = UUID.randomUUID().toString() + val (prevSessionId, subscriptionsInfo, publishingInfo) = session.currentSfuInfo() + val reconnectDetails = ReconnectDetails( + previous_session_id = prevSessionId, + strategy = WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN, + announced_tracks = publishingInfo, + subscriptions = subscriptionsInfo, + reconnect_attempt = reconnectAttepmts, + ) + session.prepareRejoin() + this.session = RtcSession( + clientImpl, + this, + sessionId, + clientImpl.apiKey, + clientImpl.coordinatorConnectionModule.lifecycle, + cred.server.url, + cred.server.wsEndpoint, + cred.token, + cred.iceServers.map { ice -> + ice.toIceServer() + }, + ) + this.session?.connect(reconnectDetails) + monitorSession(joinResponse.value) } else { - sfuSocketReconnectionTime = null + logger.e { + "[rejoin] Failed to get a join response ${joinResponse.errorOrNull()}" + } + state._connection.value = RealtimeConnection.Reconnecting } } } - suspend fun switchSfu() { + suspend fun migrate() = schedule { + logger.d { "[migrate] Migrating" } state._connection.value = RealtimeConnection.Migrating - location?.let { - val joinResponse = joinRequest(location = it, migratingFrom = session?.sfuUrl) - + val joinResponse = joinRequest(location = it) if (joinResponse is Success) { // switch to the new SFU val cred = joinResponse.value.credentials - logger.i { "Switching SFU from ${session?.sfuUrl} to ${cred.server.url}" } - val iceServers = cred.iceServers.map { it.toIceServer() } - - session?.switchSfu( - cred.server.edgeName, + val session = this.session!! + val oldSfuUrl = session.sfuUrl + logger.i { "Rejoin SFU $oldSfuUrl to ${cred.server.url}" } + + this.sessionId = UUID.randomUUID().toString() + val (prevSessionId, subscriptionsInfo, publishingInfo) = session.currentSfuInfo() + val reconnectDetails = ReconnectDetails( + previous_session_id = prevSessionId, + strategy = WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE, + announced_tracks = publishingInfo, + subscriptions = subscriptionsInfo, + from_sfu_id = oldSfuUrl, + reconnect_attempt = reconnectAttepmts, + ) + session.prepareRejoin() + val newSession = RtcSession( + clientImpl, + this, + sessionId, + clientImpl.apiKey, + clientImpl.coordinatorConnectionModule.lifecycle, cred.server.url, + cred.server.wsEndpoint, cred.token, - iceServers, - failedToSwitch = { - logger.e { - "[switchSfu] Failed to connect to new SFU during migration. Reverting to full reconnect" - } - state._connection.value = RealtimeConnection.Reconnecting + cred.iceServers.map { ice -> + ice.toIceServer() }, ) + val oldSession = this.session + this.session = newSession + this.session?.connect(reconnectDetails) + monitorSession(joinResponse.value) + oldSession?.leaveWithReason("migrating") + oldSession?.cleanup() } else { logger.e { "[switchSfu] Failed to get a join response during " + @@ -581,20 +660,51 @@ public class Call( } } - suspend fun reconnect(forceRestart: Boolean) { - // mark us as reconnecting - val connectionState = state._connection.value + private var reconnectChannel = Channel(Channel.UNLIMITED) + private var monitorReconnectChannelJob: Job? = null - if (connectionState is RealtimeConnection.Joined || connectionState == RealtimeConnection.Connected) { - state._connection.value = RealtimeConnection.Reconnecting + private fun waitForReconnectTasks() { + monitorReconnectChannelJob?.cancel() + monitorReconnectChannelJob = scope.launch { + for (task in reconnectChannel) { + task.join() + } } + } - // see if we are online before attempting to reconnect - val online = network.isConnected() + private suspend fun schedule(block: suspend () -> Unit) { + logger.d { "[schedule] #reconnect; no args" } + val job = scope.launch { + withTimeoutOrNull(30_000) { + // Skip the job if its not finished in 15 seconds. + block() + } + } + reconnectChannel.trySend(job).onFailure { e -> + logger.e( + e ?: IllegalStateException(), + ) { "[schedule] Failed to send job to reconnect channel" } + } + } - if (online) { - // start by restarting ice connections - session?.reconnect(forceRestart = forceRestart) + suspend fun fastReconnect() = schedule { + logger.d { "[fastReconnect] Reconnecting" } + session?.prepareReconnect() + this@Call.state._connection.value = RealtimeConnection.Reconnecting + if (session != null) { + val session = session!! + val (prevSessionId, subscriptionsInfo, publishingInfo) = session.currentSfuInfo() + val reconnectDetails = ReconnectDetails( + previous_session_id = prevSessionId, + strategy = WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST, + announced_tracks = publishingInfo, + subscriptions = subscriptionsInfo, + reconnect_attempt = reconnectAttepmts, + ) + session.fastReconnect(reconnectDetails) + } else { + logger.e { "[reconnect] Disconnecting" } + this@Call.state._connection.value = RealtimeConnection.Disconnected } } @@ -605,6 +715,13 @@ public class Call( } private fun leave(disconnectionReason: Throwable?) = safeCall { + session?.leaveWithReason(disconnectionReason?.message ?: "user") + session?.cleanup() + leaveTimeoutAfterDisconnect?.cancel() + network.unsubscribe(listener) + sfuListener?.cancel() + sfuEvents?.cancel() + state._connection.value = RealtimeConnection.Disconnected logger.v { "[leave] #ringing; disconnectionReason: $disconnectionReason" } if (isDestroyed) { logger.w { "[leave] #ringing; Call already destroyed, ignoring" } @@ -613,15 +730,10 @@ public class Call( isDestroyed = true sfuSocketReconnectionTime = null - state._connection.value = if (disconnectionReason != null) { - RealtimeConnection.Failed(disconnectionReason) - } else { - RealtimeConnection.Disconnected - } stopScreenSharing() client.state.removeActiveCall() // Will also stop CallService client.state.removeRingingCall() - (client as StreamVideoImpl).onCallCleanUp(this) + (client as StreamVideoClient).onCallCleanUp(this) camera.disable() microphone.disable() cleanup() @@ -697,16 +809,11 @@ public class Call( when (event) { is GoAwayEvent -> scope.launch { - handleSessionMigrationEvent() + migrate() } } } - private suspend fun handleSessionMigrationEvent() { - logger.d { "[handleSessionMigrationEvent] Received goAway event - starting migration" } - switchSfu() - } - // TODO: review this /** * Perhaps it would be nicer to have an interface. Any UI elements that renders video should implement it @@ -926,14 +1033,15 @@ 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 || - callSettings.audio.speakerDefaultOn - } + 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 || + callSettings.audio.speakerDefaultOn + } speaker.setEnabled( enabled = enableSpeaker, @@ -1028,7 +1136,8 @@ public class Call( } fun cleanup() { - monitor.stop() + // monitor.stop() + monitorReconnectChannelJob?.cancel() session?.cleanup() supervisorJob.cancel() callStatsReportingJob?.cancel() @@ -1125,8 +1234,10 @@ public class Call( @InternalStreamVideoApi public class Debug(val call: Call) { - public fun doFullReconnection() { - call.session?.sfuConnectionModule?.sfuSocket?.cancel() + public fun rejoin() { + call.scope.launch { + call.rejoin() + } } public fun restartSubscriberIce() { @@ -1137,9 +1248,15 @@ public class Call( call.session?.publisher?.connection?.restartIce() } - public fun switchSfu() { + fun migrate() { + call.scope.launch { + call.migrate() + } + } + + fun fastReconnect() { call.scope.launch { - call.switchSfu() + call.fastReconnect() } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallHealthMonitor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallHealthMonitor.kt index cfc0de15ab..463aafa6eb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallHealthMonitor.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallHealthMonitor.kt @@ -48,7 +48,7 @@ public class CallHealthMonitor( ) { private val logger by taggedLogger("Call:HealthMonitor") - private val network by lazy { call.clientImpl.connectionModule.networkStateProvider } + private val network by lazy { call.clientImpl.coordinatorConnectionModule.networkStateProvider } private val supervisorJob = SupervisorJob() private val scope = CoroutineScope(callScope.coroutineContext + supervisorJob) @@ -156,8 +156,6 @@ public class CallHealthMonitor( return@launch } } - - scope.launch { reconnect(false) } } } @@ -197,7 +195,7 @@ public class CallHealthMonitor( logger.d { "[reconnect] skipping reconnect - too often" } } else { lastReconnectAt = now - call.reconnect(forceRestart = forceRestart) + call.fastReconnect() } reconnectInProgress = false @@ -205,14 +203,14 @@ public class CallHealthMonitor( // monitor the network state since it's faster to detect recovered network sometimes internal val networkStateListener = object : NetworkStateProvider.NetworkStateListener { - override fun onConnected() { + override suspend fun onConnected() { logger.i { "network connected, running check to see if we should reconnect" } scope.launch { check() } } - override fun onDisconnected() { + override suspend fun onDisconnected() { val connectionState = call.state._connection.value logger.i { "network disconnected. connection is $connectionState marking the connection as reconnecting" diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt index cc1f49bbe7..501f182338 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/CallState.kt @@ -221,7 +221,7 @@ public class CallState( /** Your own participant state */ public val me: StateFlow = _participants.mapState { - it[call.clientImpl.sessionId] + it[call.sessionId] } /** Your own participant state */ @@ -234,7 +234,7 @@ public class CallState( /** participants other than yourself */ public val remoteParticipants: StateFlow> = - _participants.mapState { it.filterKeys { key -> key != call.clientImpl.sessionId }.values.toList() } + _participants.mapState { it.filterKeys { key -> key != call.sessionId }.values.toList() } /** the dominant speaker */ private val _dominantSpeaker: MutableStateFlow = MutableStateFlow(null) @@ -465,7 +465,8 @@ public class CallState( val liveEndedAt = _session.value?.liveEndedAt ?: OffsetDateTime.now() liveStartedAt?.let { - val duration = liveEndedAt.toInstant().toEpochMilli() - liveStartedAt.toInstant().toEpochMilli() + val duration = liveEndedAt.toInstant().toEpochMilli() - liveStartedAt.toInstant() + .toEpochMilli() emit(duration) } } @@ -1371,6 +1372,23 @@ public class CallState( } } } + + fun replaceParticipants(participants: List) { + this._participants.value = participants.associate { it.sessionId to it }.toSortedMap() + val screensharing = mutableListOf() + participants.forEach { + if (it.screenSharingEnabled.value) { + screensharing.add(it) + } + } + _screenSharingSession.value = if (screensharing.isNotEmpty()) { + ScreenSharingSession( + screensharing[0], + ) + } else { + null + } + } } private fun MemberResponse.toMemberState(): MemberState { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt index 928c5d4574..7a5db1bf48 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ClientState.kt @@ -19,21 +19,20 @@ package io.getstream.video.android.core import androidx.compose.runtime.Stable import androidx.core.content.ContextCompat import io.getstream.log.taggedLogger +import io.getstream.result.Error import io.getstream.video.android.core.notifications.internal.service.CallService -import io.getstream.video.android.core.utils.safeCall +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.User -import io.getstream.video.android.model.UserType -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch import org.openapitools.client.models.CallCreatedEvent import org.openapitools.client.models.CallRingEvent import org.openapitools.client.models.ConnectedEvent import org.openapitools.client.models.VideoEvent -import java.net.ConnectException +// These are UI states, need to move out. @Stable public sealed interface ConnectionState { public data object PreConnect : ConnectionState @@ -44,6 +43,7 @@ public sealed interface ConnectionState { public class Failed(val error: Error) : ConnectionState } +// These are UI states, need to move out. @Stable public sealed interface RingingState { public data object Idle : RingingState @@ -55,42 +55,36 @@ public sealed interface RingingState { } @Stable -class ClientState(client: StreamVideo) { +class ClientState(private val client: StreamVideo) { + private val logger by taggedLogger("ClientState") - val logger by taggedLogger("ClientState") - - /** - * Current user object - */ + // Internal data private val _user: MutableStateFlow = MutableStateFlow(client.user) - public val user: StateFlow = _user - private val _connection: MutableStateFlow = MutableStateFlow(ConnectionState.PreConnect) + internal val _ringingCall: MutableStateFlow = MutableStateFlow(null) + private val _activeCall: MutableStateFlow = MutableStateFlow(null) - /** - * Shows the Coordinator connection state - */ + // Stream video client is used until full decoupling is archived between `CallState` and `StreamVideoClient (former StreamVideoImpl) + private val streamVideoClient: StreamVideoClient = client as StreamVideoClient + + // API + /** Current user for the client. */ + public val user: StateFlow = _user + + /** Coordinator connection state */ public val connection: StateFlow = _connection - /** - * Incoming call. True when we receive an event or notification with an incoming call - */ - internal val _ringingCall: MutableStateFlow = MutableStateFlow(null) + /** When there is an incoming call, this state will be set. */ public val ringingCall: StateFlow = _ringingCall - /** - * Active call. The call that you've currently joined - */ - private val _activeCall: MutableStateFlow = MutableStateFlow(null) + /** When there is an active call, this state will be set, otherwise its null. */ public val activeCall: StateFlow = _activeCall - internal val clientImpl = client as StreamVideoImpl - /** * Returns true if there is an active or ringing call */ - fun hasActiveOrRingingCall(): Boolean = safeCall(false) { + public fun hasActiveOrRingingCall(): Boolean = safeCallWithDefault(false) { val hasActiveCall = _activeCall.value != null val hasRingingCall = _ringingCall.value != null val activeOrRingingCall = hasActiveCall || hasRingingCall @@ -104,37 +98,50 @@ class ClientState(client: StreamVideo) { */ fun handleEvent(event: VideoEvent) { // mark connected - if (event is ConnectedEvent) { - _connection.value = ConnectionState.Connected - - registerPushDevice() - } else if (event is CallCreatedEvent) { - // what's the right thing to do here? - // if it's ringing we add it - - // get or create the call, update is handled by CallState - val (type, id) = event.callCid.split(":") - val call = clientImpl.call(type, id) - } else if (event is CallRingEvent) { - // get or create the call, update is handled by CallState - val (type, id) = event.callCid.split(":") - val call = clientImpl.call(type, id) - _ringingCall.value = call - } - } + when (event) { + is ConnectedEvent -> { + _connection.value = ConnectionState.Connected + } + + is CallCreatedEvent -> { + // what's the right thing to do here? + // if it's ringing we add it + + // get or create the call, update is handled by CallState + val (type, id) = event.callCid.split(":") + val call = client.call(type, id) + } - private fun registerPushDevice() { - with(clientImpl) { - scope.launch(CoroutineName("ClientState#registerPushDevice")) { - if (user.type == UserType.Authenticated) registerPushDevice() + is CallRingEvent -> { + val (type, id) = event.callCid.split(":") + val call = client.call(type, id) + _ringingCall.value = call } } } - internal fun handleError(error: Throwable) { - if (error is ConnectException) { - _connection.value = ConnectionState.Failed(error = Error(error)) + internal fun handleState(socketState: VideoSocketState) { + val state = when (socketState) { + // Before connection is established + is VideoSocketState.Disconnected.Stopped -> ConnectionState.PreConnect + // Loading + is VideoSocketState.Connecting -> ConnectionState.Loading + // Connected + is VideoSocketState.Connected -> ConnectionState.Connected + // Reconnecting + is VideoSocketState.Disconnected.DisconnectedTemporarily -> ConnectionState.Reconnecting + is VideoSocketState.RestartConnection -> ConnectionState.Reconnecting + // Disconnected + is VideoSocketState.Disconnected.WebSocketEventLost -> ConnectionState.Disconnected + is VideoSocketState.Disconnected.NetworkDisconnected -> ConnectionState.Disconnected + is VideoSocketState.Disconnected.DisconnectedByRequest -> ConnectionState.Disconnected + is VideoSocketState.Disconnected.DisconnectedPermanently -> ConnectionState.Disconnected } + _connection.value = state + } + + fun handleError(error: Error) { + _connection.value = ConnectionState.Failed(error) } fun setActiveCall(call: Call) { @@ -167,13 +174,13 @@ class ClientState(client: StreamVideo) { * This depends on the flag in [StreamVideoBuilder] called `runForegroundServiceForCalls` */ internal fun maybeStartForegroundService(call: Call, trigger: String) { - if (clientImpl.callServiceConfig.runCallServiceInForeground) { - val context = clientImpl.context + if (streamVideoClient.callServiceConfig.runCallServiceInForeground) { + val context = streamVideoClient.context val serviceIntent = CallService.buildStartIntent( context, StreamCallId.fromCallCid(call.cid), trigger, - callServiceConfiguration = clientImpl.callServiceConfig, + callServiceConfiguration = streamVideoClient.callServiceConfig, ) ContextCompat.startForegroundService(context, serviceIntent) } @@ -183,11 +190,11 @@ class ClientState(client: StreamVideo) { * Stop the foreground service that manages the call even when the UI is gone. */ internal fun maybeStopForegroundService() { - if (clientImpl.callServiceConfig.runCallServiceInForeground) { - val context = clientImpl.context + if (streamVideoClient.callServiceConfig.runCallServiceInForeground) { + val context = streamVideoClient.context val serviceIntent = CallService.buildStopIntent( context, - callServiceConfiguration = clientImpl.callServiceConfig, + callServiceConfiguration = streamVideoClient.callServiceConfig, ) context.stopService(serviceIntent) } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt index 6627c4cd5a..e4515d9416 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/MediaManager.kt @@ -44,7 +44,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking import org.openapitools.client.models.VideoSettingsResponse import org.webrtc.Camera2Capturer import org.webrtc.Camera2Enumerator @@ -142,7 +141,8 @@ class SpeakerManager( microphoneManager.enforceSetup { val devices = devices.value if (enable) { - val speaker = devices.filterIsInstance().firstOrNull() + val speaker = + devices.filterIsInstance().firstOrNull() selectedBeforeSpeaker = selectedDevice.value _speakerPhoneEnabled.value = true microphoneManager.select(speaker) @@ -152,9 +152,10 @@ class SpeakerManager( val defaultFallbackFromType = defaultFallback?.let { devices.filterIsInstance(defaultFallback::class.java) }?.firstOrNull() - val fallback = defaultFallbackFromType ?: selectedBeforeSpeaker ?: devices.firstOrNull { - it !is StreamAudioDevice.Speakerphone - } + val fallback = + defaultFallbackFromType ?: selectedBeforeSpeaker ?: devices.firstOrNull { + it !is StreamAudioDevice.Speakerphone + } microphoneManager.select(fallback) } } @@ -284,7 +285,8 @@ class ScreenShareManager( private fun startScreenShare(mediaProjectionPermissionResultData: Intent) { mediaManager.scope.launch { - this@ScreenShareManager.mediaProjectionPermissionResultData = mediaProjectionPermissionResultData + this@ScreenShareManager.mediaProjectionPermissionResultData = + mediaProjectionPermissionResultData // Screen sharing requires a foreground service with foregroundServiceType "mediaProjection" to be started first. // We can wait for the service to be ready by binding to it and then starting the @@ -652,7 +654,11 @@ public class CameraManager( /** * Capture is called whenever you call enable() */ - internal fun startCapture() { + internal fun startCapture() = synchronized(this) { + if (isCapturingVideo) { + stopCapture() + } + val selectedDevice = _selectedDevice.value ?: return val selectedResolution = resolution.value ?: return @@ -660,29 +666,25 @@ public class CameraManager( videoCapturer = Camera2Capturer(mediaManager.context, selectedDevice.id, null) // initialize it - runBlocking(mediaManager.scope.coroutineContext) { - videoCapturer.initialize( - surfaceTextureHelper, - mediaManager.context, - mediaManager.videoSource.capturerObserver, - ) - } + videoCapturer.initialize( + surfaceTextureHelper, + mediaManager.context, + mediaManager.videoSource.capturerObserver, + ) // and start capture - runBlocking(mediaManager.scope.coroutineContext) { - videoCapturer.startCapture( - selectedResolution.width, - selectedResolution.height, - selectedResolution.framerate.max, - ) - } + videoCapturer.startCapture( + selectedResolution.width, + selectedResolution.height, + selectedResolution.framerate.max, + ) isCapturingVideo = true } /** * Stops capture if it's running */ - internal fun stopCapture() { + internal fun stopCapture() = synchronized(this) { if (isCapturingVideo) { videoCapturer.stopCapture() isCapturingVideo = false diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ParticipantState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ParticipantState.kt index a499e40750..5babb2d88a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ParticipantState.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/ParticipantState.kt @@ -59,7 +59,7 @@ public data class ParticipantState( private val logger by taggedLogger("ParticipantState") val isLocal by lazy { - sessionId == call.session?.sessionId + sessionId == call.sessionId } /** video track */ diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt index c244b9610e..10fcd31c4a 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideo.kt @@ -37,7 +37,7 @@ import kotlinx.coroutines.flow.StateFlow import org.openapitools.client.models.VideoEvent /** - * The main interface to control the Video calls. [StreamVideoImpl] implements this interface. + * The main interface to control the Video calls. [StreamVideoClient] implements this interface. */ @Stable public interface StreamVideo : NotificationHandler { @@ -198,6 +198,7 @@ public interface StreamVideo : NotificationHandler { "install a new exception handler: $streamVideo" } } + isInstalled = true internalStreamVideo = streamVideo } } @@ -219,7 +220,7 @@ public interface StreamVideo : NotificationHandler { } private fun buildAppName(): String = - (internalStreamVideo as? StreamVideoImpl)?.let { streamVideoImpl -> + (internalStreamVideo as? StreamVideoClient)?.let { streamVideoImpl -> "|app_name=" + (streamVideoImpl.appName ?: streamVideoImpl.context.packageName) } ?: "" @@ -227,6 +228,7 @@ public interface StreamVideo : NotificationHandler { * Uninstall a previous [StreamVideo] instance. */ public fun removeClient() { + isInstalled = false internalStreamVideo?.cleanup() internalStreamVideo = null } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt index 01648d393a..2d15b4f695 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoBuilder.kt @@ -23,8 +23,7 @@ import com.jakewharton.threetenabp.AndroidThreeTen import io.getstream.log.StreamLog import io.getstream.log.android.AndroidStreamLogger import io.getstream.log.streamLog -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.internal.module.ConnectionModule +import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.notifications.NotificationConfig import io.getstream.video.android.core.notifications.internal.StreamNotificationManager @@ -33,6 +32,10 @@ import io.getstream.video.android.core.notifications.internal.service.callServic import io.getstream.video.android.core.notifications.internal.storage.DeviceTokenStorage import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenProvider import io.getstream.video.android.core.sounds.Sounds import io.getstream.video.android.core.sounds.defaultResourcesRingingConfig import io.getstream.video.android.core.sounds.toSounds @@ -40,7 +43,6 @@ import io.getstream.video.android.model.ApiKey import io.getstream.video.android.model.User import io.getstream.video.android.model.UserToken import io.getstream.video.android.model.UserType -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.webrtc.ManagedAudioProcessingFactory import java.net.ConnectException @@ -93,7 +95,12 @@ public class StreamVideoBuilder @JvmOverloads constructor( private val geo: GEO = GEO.GlobalEdgeNetwork, private var user: User = User.anonymous(), private val token: UserToken = "", - private val tokenProvider: (suspend (error: Throwable?) -> String)? = null, + private val legacyTokenProvider: (suspend (error: Throwable?) -> String)? = null, + private val tokenProvider: TokenProvider = legacyTokenProvider?.let { legacy -> + object : TokenProvider { + override suspend fun loadToken(): String = legacy.invoke(null) + } + } ?: ConstantTokenProvider(token), private val loggingLevel: LoggingLevel = LoggingLevel(), private val notificationConfig: NotificationConfig = NotificationConfig(), private val ringNotification: ((call: Call) -> Notification?)? = null, @@ -109,9 +116,10 @@ public class StreamVideoBuilder @JvmOverloads constructor( private val audioUsage: Int = defaultAudioUsage, private val appName: String? = null, private val audioProcessing: ManagedAudioProcessingFactory? = null, + private val leaveAfterDisconnectSeconds: Long = 30, ) { private val context: Context = context.applicationContext - private val scope = CoroutineScope(DispatcherProvider.IO) + private val scope = UserScope(ClientScope()) /** * Builds the [StreamVideo] client. @@ -138,10 +146,8 @@ public class StreamVideoBuilder @JvmOverloads constructor( throw IllegalArgumentException("The API key cannot be blank") } - if (token.isBlank() && tokenProvider == null && user.type == UserType.Authenticated) { - throw IllegalArgumentException( - "Either a user token or a token provider must be provided", - ) + if (token.isBlank()) { + throw IllegalStateException("The token cannot be blank") } if (user.type == UserType.Authenticated && user.id.isBlank()) { @@ -162,15 +168,18 @@ public class StreamVideoBuilder @JvmOverloads constructor( AndroidThreeTen.init(context) // This connection module class exposes the connections to the various retrofit APIs. - val connectionModule = ConnectionModule( + val coordinatorConnectionModule = CoordinatorConnectionModule( context = context, scope = scope, - videoDomain = videoDomain, + apiUrl = "https:///$videoDomain", + wssUrl = "wss://$videoDomain/video/connect", connectionTimeoutInMs = connectionTimeoutInMs, loggingLevel = loggingLevel, user = user, apiKey = apiKey, userToken = token, + tokenProvider = tokenProvider, + lifecycle = lifecycle, ) val deviceTokenStorage = DeviceTokenStorage(context) @@ -180,21 +189,21 @@ public class StreamVideoBuilder @JvmOverloads constructor( context = context, scope = scope, notificationConfig = notificationConfig, - api = connectionModule.api, + api = coordinatorConnectionModule.api, deviceTokenStorage = deviceTokenStorage, ) // Create the client - val client = StreamVideoImpl( + val client = StreamVideoClient( context = context, - _scope = scope, + scope = scope, user = user, apiKey = apiKey, token = token, tokenProvider = tokenProvider, loggingLevel = loggingLevel, lifecycle = lifecycle, - connectionModule = connectionModule, + coordinatorConnectionModule = coordinatorConnectionModule, streamNotificationManager = streamNotificationManager, callServiceConfig = callServiceConfig ?: callServiceConfig().copy( @@ -208,13 +217,14 @@ public class StreamVideoBuilder @JvmOverloads constructor( audioUsage = audioUsage, appName = appName, audioProcessing = audioProcessing, + leaveAfterDisconnectSeconds = leaveAfterDisconnectSeconds, ) if (user.type == UserType.Guest) { - connectionModule.updateAuthType("anonymous") + coordinatorConnectionModule.updateAuthType("anonymous") client.setupGuestUser(user) } else if (user.type == UserType.Anonymous) { - connectionModule.updateAuthType("anonymous") + coordinatorConnectionModule.updateAuthType("anonymous") } // Establish a WS connection with the coordinator (we don't support this for anonymous users) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt similarity index 81% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt index ddbadd449a..176f2b8abb 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoImpl.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/StreamVideoClient.kt @@ -31,9 +31,7 @@ import io.getstream.video.android.core.events.VideoEventListener import io.getstream.video.android.core.filter.Filters import io.getstream.video.android.core.filter.toMap import io.getstream.video.android.core.internal.InternalStreamVideoApi -import io.getstream.video.android.core.internal.module.ConnectionModule -import io.getstream.video.android.core.lifecycle.LifecycleHandler -import io.getstream.video.android.core.lifecycle.internal.StreamLifecycleObserver +import io.getstream.video.android.core.internal.module.CoordinatorConnectionModule import io.getstream.video.android.core.logging.LoggingLevel import io.getstream.video.android.core.model.EdgeData import io.getstream.video.android.core.model.MuteUsersData @@ -51,26 +49,26 @@ import io.getstream.video.android.core.notifications.internal.service.callServic import io.getstream.video.android.core.permission.android.DefaultStreamPermissionCheck import io.getstream.video.android.core.permission.android.StreamPermissionCheck import io.getstream.video.android.core.socket.ErrorResponse -import io.getstream.video.android.core.socket.PersistentSocket -import io.getstream.video.android.core.socket.SocketState +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState import io.getstream.video.android.core.sounds.Sounds import io.getstream.video.android.core.utils.LatencyResult import io.getstream.video.android.core.utils.getLatencyMeasurementsOKHttp import io.getstream.video.android.core.utils.safeCall import io.getstream.video.android.core.utils.safeSuspendingCall +import io.getstream.video.android.core.utils.safeSuspendingCallWithResult import io.getstream.video.android.core.utils.toEdge import io.getstream.video.android.core.utils.toQueriedCalls import io.getstream.video.android.core.utils.toQueriedMembers import io.getstream.video.android.model.ApiKey import io.getstream.video.android.model.Device import io.getstream.video.android.model.User -import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel @@ -129,7 +127,6 @@ import org.openapitools.client.models.VideoEvent import org.openapitools.client.models.WSCallEvent import org.webrtc.ManagedAudioProcessingFactory import retrofit2.HttpException -import java.net.ConnectException import java.util.* import kotlin.coroutines.Continuation @@ -139,16 +136,16 @@ internal const val defaultAudioUsage = AudioAttributes.USAGE_VOICE_COMMUNICATION /** * @param lifecycle The lifecycle used to observe changes in the process */ -internal class StreamVideoImpl internal constructor( +internal class StreamVideoClient internal constructor( override val context: Context, - internal val _scope: CoroutineScope, + internal val scope: CoroutineScope = ClientScope(), override val user: User, internal val apiKey: ApiKey, internal var token: String, private val lifecycle: Lifecycle, private val loggingLevel: LoggingLevel, - internal val connectionModule: ConnectionModule, - internal val tokenProvider: (suspend (error: Throwable?) -> String)?, + internal val coordinatorConnectionModule: CoordinatorConnectionModule, + internal val tokenProvider: TokenProvider = ConstantTokenProvider(token), internal val streamNotificationManager: StreamNotificationManager, internal val callServiceConfig: CallServiceConfig = callServiceConfig(), internal val testSfuAddress: String? = null, @@ -158,6 +155,7 @@ internal class StreamVideoImpl internal constructor( internal val audioUsage: Int = defaultAudioUsage, internal val appName: String? = null, internal val audioProcessing: ManagedAudioProcessingFactory? = null, + internal val leaveAfterDisconnectSeconds: Long = 30, ) : StreamVideo, NotificationHandler by streamNotificationManager { private var locationJob: Deferred>? = null @@ -165,21 +163,13 @@ internal class StreamVideoImpl internal constructor( /** the state for the client, includes the current user */ override val state = ClientState(this) - private val coroutineExceptionHandler = - CoroutineExceptionHandler { coroutineContext, exception -> - val coroutineName = coroutineContext[CoroutineName]?.name ?: "unknown" - logger.e(exception) { - "[StreamVideo#Scope] Uncaught exception in coroutine $coroutineName: $exception" - } - } - internal val scope = - CoroutineScope(_scope.coroutineContext + SupervisorJob() + coroutineExceptionHandler) + /** + * Can be set from tests to be returned as a session id for the coordinator. + */ + internal var testSessionId: String? = null /** if true we fail fast on errors instead of logging them */ - /** session id is generated client side */ - public val sessionId = UUID.randomUUID().toString() - internal var guestUserJob: Deferred? = null private lateinit var connectContinuation: Continuation> @@ -193,7 +183,7 @@ internal class StreamVideoImpl internal constructor( private var subscriptions = mutableSetOf() private var calls = mutableMapOf() - val socketImpl = connectionModule.coordinatorSocket + val socketImpl = coordinatorConnectionModule.socketConnection fun onCallCleanUp(call: Call) { calls.remove(call.cid) @@ -204,8 +194,6 @@ internal class StreamVideoImpl internal constructor( calls.clear() // stop all running coroutines scope.cancel() - // stop the socket - socketImpl.cleanup() // call cleanup on the active call val activeCall = state.activeCall.value activeCall?.leave() @@ -228,42 +216,38 @@ internal class StreamVideoImpl internal constructor( /** * Ensure that every API call runs on the IO dispatcher and has correct error handling */ - internal suspend fun wrapAPICall(apiCall: suspend () -> T): Result { - return withContext(scope.coroutineContext) { - try { - Success(apiCall()) - } catch (e: HttpException) { - val failure = parseError(e) - val parsedError = failure.value as Error.NetworkError - if (parsedError.serverErrorCode == VideoErrorCode.TOKEN_EXPIRED.code) { - if (tokenProvider != null) { - // TODO - handle this better, error structure is not great right now - val newToken = tokenProvider.invoke(null) - token = newToken - connectionModule.updateToken(newToken) - } - // retry the API call once - try { - Success(apiCall()) - } catch (e: HttpException) { - parseError(e) - } catch (e: Throwable) { - // rethrow exception (will be handled by outer-catch) - throw e - } - - // set the token, repeat API call - // keep track of retry count - } else { - failure - } - } catch (e: Throwable) { - // Other issues. For example UnknownHostException. - Failure(Error.ThrowableError(e.message ?: "", e)) + internal suspend fun wrapAPICall( + apiCall: suspend () -> T, + ): Result = safeSuspendingCallWithResult { + try { + apiCall() + } catch (e: HttpException) { + // Retry once with a new token if the token is expired + if (e.isAuthError()) { + val newToken = tokenProvider.loadToken() + token = newToken + coordinatorConnectionModule.updateToken(newToken) + apiCall() + } else { + throw e } } } + private fun HttpException.isAuthError(): Boolean { + val failure = parseError(this) + val parsedError = failure.value as Error.NetworkError + return when (parsedError.serverErrorCode) { + VideoErrorCode.AUTHENTICATION_ERROR.code, + VideoErrorCode.TOKEN_EXPIRED.code, + VideoErrorCode.TOKEN_NOT_VALID.code, + VideoErrorCode.TOKEN_DATE_INCORRECT.code, + VideoErrorCode.TOKEN_SIGNATURE_INCORRECT.code, + -> true + else -> false + } + } + /** * @see StreamVideo.updateCall */ @@ -274,7 +258,7 @@ internal class StreamVideoImpl internal constructor( ): Result { logger.d { "[updateCall] type: $type, id: $id, request: $request" } return wrapAPICall { - connectionModule.api.updateCall( + coordinatorConnectionModule.api.updateCall( type = type, id = id, updateCallRequest = request, @@ -334,74 +318,35 @@ internal class StreamVideoImpl internal constructor( } override suspend fun connectIfNotAlreadyConnected() = safeSuspendingCall { - if (connectionModule.coordinatorSocket.canConnect()) { - connectionModule.coordinatorSocket.connect() - } + coordinatorConnectionModule.socketConnection.connect(user) } - /** - * Observes the app lifecycle and attempts to reconnect/release the socket connection. - */ - private val lifecycleObserver = StreamLifecycleObserver( - lifecycle, - object : LifecycleHandler { - override fun started() { - scope.launch(CoroutineName("lifecycleObserver.started")) { - connectIfNotAlreadyConnected() - } - } - - override fun stopped() { - safeCall { - // We should only disconnect if we were previously connected - // Also don't disconnect the socket if we are in an active call - val socketDecision = connectionModule.coordinatorSocket.canDisconnect() - val activeCallDecision = state.hasActiveOrRingingCall() - val decision = socketDecision && !activeCallDecision - logger.d { - "[lifecycle#stopped] Decision to disconnect: $decision, caused by socket state: $socketDecision, active or ringing call: $activeCallDecision" - } - if (decision) { - connectionModule.coordinatorSocket.disconnect( - PersistentSocket.DisconnectReason.ByRequest, - ) - } - } - } - }, - ) - init { - - scope.launch(Dispatchers.Main.immediate + CoroutineName("init#lifecycleObserver.observe")) { - lifecycleObserver.observe() - } - // listen to socket events and errors scope.launch(CoroutineName("init#coordinatorSocket.events.collect")) { - connectionModule.coordinatorSocket.events.collect { + coordinatorConnectionModule.socketConnection.events().collect { fireEvent(it) } } + scope.launch { + coordinatorConnectionModule.socketConnection.state().collect { + state.handleState(it) + } + } + scope.launch(CoroutineName("init#coordinatorSocket.errors.collect")) { - connectionModule.coordinatorSocket.errors.collect { throwable -> - if (throwable is ConnectException) { - state.handleError(throwable) - } else { - (throwable as? ErrorResponse)?.let { - if (it.code == VideoErrorCode.TOKEN_EXPIRED.code) refreshToken(it) - } - } + coordinatorConnectionModule.socketConnection.errors().collect { error -> + state.handleError(error.streamError) } } scope.launch(CoroutineName("init#coordinatorSocket.connectionState.collect")) { - connectionModule.coordinatorSocket.connectionState.collect { it -> + coordinatorConnectionModule.socketConnection.state().collect { it -> // If the socket is reconnected then we have a new connection ID. // We need to re-watch every watched call with the new connection ID // (otherwise the WS events will stop) val watchedCalls = calls - if (it is SocketState.Connected && watchedCalls.isNotEmpty()) { + if (it is VideoSocketState.Connected && watchedCalls.isNotEmpty()) { val filter = Filters.`in`("cid", watchedCalls.values.map { it.cid }).toMap() queryCalls(filters = filter, watch = true).also { if (it is Failure) { @@ -455,7 +400,7 @@ internal class StreamVideoImpl internal constructor( guestUserJob?.await() try { val startTime = System.currentTimeMillis() - socketImpl.connect() + socketImpl.connect(user) val duration = System.currentTimeMillis() - startTime Success(duration) } catch (e: ErrorResponse) { @@ -471,12 +416,12 @@ internal class StreamVideoImpl internal constructor( private suspend fun refreshToken(error: Throwable) { tokenProvider?.let { - val newToken = tokenProvider.invoke(error) - connectionModule.updateToken(newToken) + val newToken = tokenProvider.loadToken() + coordinatorConnectionModule.updateToken(newToken) logger.d { "[refreshToken] Token has been refreshed with: $newToken" } - socketImpl.reconnect(0) + socketImpl.reconnect(user, true) } } @@ -501,15 +446,15 @@ internal class StreamVideoImpl internal constructor( throw IllegalStateException("Failed to create guest user") } response.onSuccess { - connectionModule.updateAuthType("jwt") - connectionModule.updateToken(it.accessToken) + coordinatorConnectionModule.updateAuthType("jwt") + coordinatorConnectionModule.updateToken(it.accessToken) } } } suspend fun createGuestUser(userRequest: UserRequest): Result { return wrapAPICall { - connectionModule.api.createGuest( + coordinatorConnectionModule.api.createGuest( createGuestRequest = CreateGuestRequest(userRequest), ) } @@ -585,7 +530,7 @@ internal class StreamVideoImpl internal constructor( internal suspend fun getCall(type: String, id: String): Result { return wrapAPICall { - connectionModule.api.getCall( + coordinatorConnectionModule.api.getCall( type, id, connectionId = waitForConnectionId(), @@ -638,7 +583,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[getOrCreateCall] type: $type, id: $id, members: $members" } return wrapAPICall { - connectionModule.api.getOrCreateCall( + coordinatorConnectionModule.api.getOrCreateCall( type = type, id = id, getOrCreateCallRequest = GetOrCreateCallRequest( @@ -657,19 +602,19 @@ internal class StreamVideoImpl internal constructor( } } - private suspend fun waitForConnectionId(): String? = + private suspend fun waitForConnectionId(): String? { // The Coordinator WS connection can take a moment to set up - this can be an issue // if we jump right into the call from a deep link and we connect the call quickly. // We return null on timeout. The Coordinator WS will update the connectionId later // after it reconnects (it will call queryCalls) - withTimeoutOrNull(timeMillis = WAIT_FOR_CONNECTION_ID_TIMEOUT) { - val value = connectionModule.coordinatorSocket.connectionId.first { it != null } + val connectionId = withTimeoutOrNull(timeMillis = WAIT_FOR_CONNECTION_ID_TIMEOUT) { + val value = coordinatorConnectionModule.socketConnection.connectionId().first { it != null } value }.also { - if (it == null) { - logger.w { "[waitForConnectionId] connectionId timeout - returning null" } - } + logger.d { "[waitForConnectionId]: $it" } } + return connectionId ?: testSessionId + } internal suspend fun inviteUsers( type: String, @@ -730,7 +675,7 @@ internal class StreamVideoImpl internal constructor( ) val result = wrapAPICall { - connectionModule.api.joinCall( + coordinatorConnectionModule.api.joinCall( type, id, joinCallRequest, @@ -746,7 +691,7 @@ internal class StreamVideoImpl internal constructor( request: UpdateCallMembersRequest, ): Result { return wrapAPICall { - connectionModule.api.updateCallMembers(type, id, request) + coordinatorConnectionModule.api.updateCallMembers(type, id, request) } } @@ -758,7 +703,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[sendCustomEvent] callCid: $type:$id, dataJson: $dataJson" } return wrapAPICall { - connectionModule.api.sendCallEvent( + coordinatorConnectionModule.api.sendCallEvent( type, id, SendCallEventRequest(custom = dataJson), @@ -776,7 +721,7 @@ internal class StreamVideoImpl internal constructor( limit: Int, ): Result { return wrapAPICall { - connectionModule.api.queryCallMembers( + coordinatorConnectionModule.api.queryCallMembers( QueryCallMembersRequest( type = type, id = id, @@ -814,7 +759,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[blockUser] callCid: $type:$id, userId: $userId" } return wrapAPICall { - connectionModule.api.blockUser( + coordinatorConnectionModule.api.blockUser( type, id, BlockUserRequest(userId), @@ -826,7 +771,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[unblockUser] callCid: $type:$id, userId: $userId" } return wrapAPICall { - connectionModule.api.unblockUser( + coordinatorConnectionModule.api.unblockUser( type, id, UnblockUserRequest(userId), @@ -836,7 +781,7 @@ internal class StreamVideoImpl internal constructor( suspend fun pinForEveryone(type: String, callId: String, sessionId: String, userId: String) = wrapAPICall { - connectionModule.api.videoPin( + coordinatorConnectionModule.api.videoPin( type, callId, PinRequest( @@ -848,7 +793,7 @@ internal class StreamVideoImpl internal constructor( suspend fun unpinForEveryone(type: String, callId: String, sessionId: String, userId: String) = wrapAPICall { - connectionModule.api.videoUnpin( + coordinatorConnectionModule.api.videoUnpin( type, callId, UnpinRequest( @@ -859,7 +804,7 @@ internal class StreamVideoImpl internal constructor( } suspend fun endCall(type: String, id: String): Result { - return wrapAPICall { connectionModule.api.endCall(type, id) } + return wrapAPICall { coordinatorConnectionModule.api.endCall(type, id) } } suspend fun goLive( @@ -872,7 +817,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[goLive] callCid: $type:$id" } return wrapAPICall { - connectionModule.api.goLive( + coordinatorConnectionModule.api.goLive( type = type, id = id, goLiveRequest = GoLiveRequest( @@ -885,7 +830,7 @@ internal class StreamVideoImpl internal constructor( } suspend fun stopLive(type: String, id: String): Result { - return wrapAPICall { connectionModule.api.stopLive(type, id) } + return wrapAPICall { coordinatorConnectionModule.api.stopLive(type, id) } } suspend fun muteUsers( @@ -895,7 +840,7 @@ internal class StreamVideoImpl internal constructor( ): Result { val request = muteUsersData.toRequest() return wrapAPICall { - connectionModule.api.muteUsers(type, id, request) + coordinatorConnectionModule.api.muteUsers(type, id, request) } } @@ -920,7 +865,7 @@ internal class StreamVideoImpl internal constructor( watch = watch, ) val result = wrapAPICall { - connectionModule.api.queryCalls(request, waitForConnectionId()) + coordinatorConnectionModule.api.queryCalls(request, waitForConnectionId()) } if (result.isSuccess) { // update state for these calls @@ -943,7 +888,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[requestPermissions] callCid: $type:$id, permissions: $permissions" } return wrapAPICall { - connectionModule.api.requestPermission( + coordinatorConnectionModule.api.requestPermission( type, id, RequestPermissionRequest(permissions), @@ -954,11 +899,11 @@ internal class StreamVideoImpl internal constructor( suspend fun startBroadcasting(type: String, id: String): Result { logger.d { "[startBroadcasting] callCid: $type $id" } - return wrapAPICall { connectionModule.api.startHLSBroadcasting(type, id) } + return wrapAPICall { coordinatorConnectionModule.api.startHLSBroadcasting(type, id) } } suspend fun stopBroadcasting(type: String, id: String): Result { - return wrapAPICall { connectionModule.api.stopHLSBroadcasting(type, id) } + return wrapAPICall { coordinatorConnectionModule.api.stopHLSBroadcasting(type, id) } } suspend fun startRecording( @@ -968,13 +913,13 @@ internal class StreamVideoImpl internal constructor( ): Result { return wrapAPICall { val req = StartRecordingRequest(externalStorage) - connectionModule.api.startRecording(type, id, req) + coordinatorConnectionModule.api.startRecording(type, id, req) } } suspend fun stopRecording(type: String, id: String): Result { return wrapAPICall { - connectionModule.api.stopRecording(type, id) + coordinatorConnectionModule.api.stopRecording(type, id) } } @@ -984,7 +929,7 @@ internal class StreamVideoImpl internal constructor( updateUserPermissionsData: UpdateUserPermissionsData, ): Result { return wrapAPICall { - connectionModule.api.updateUserPermissions( + coordinatorConnectionModule.api.updateUserPermissions( type, id, updateUserPermissionsData.toRequest(), @@ -998,7 +943,7 @@ internal class StreamVideoImpl internal constructor( sessionId: String?, ): Result { return wrapAPICall { - connectionModule.api.listRecordings(type, id) + coordinatorConnectionModule.api.listRecordings(type, id) } } @@ -1014,7 +959,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[sendVideoReaction] callCid: $type:$id, sendReactionData: $request" } return wrapAPICall { - connectionModule.api.sendVideoReaction(callType, id, request) + coordinatorConnectionModule.api.sendVideoReaction(callType, id, request) } } @@ -1025,7 +970,7 @@ internal class StreamVideoImpl internal constructor( logger.d { "[getEdges] no params" } return wrapAPICall { - val result = connectionModule.api.getEdges() + val result = coordinatorConnectionModule.api.getEdges() result.edges.map { it.toEdge() } } @@ -1058,7 +1003,7 @@ internal class StreamVideoImpl internal constructor( return wrapAPICall { val url = "https://hint.stream-io-video.com/" val request: Request = Request.Builder().url(url).method("HEAD", null).build() - val call = connectionModule.okHttpClient.newCall(request) + val call = coordinatorConnectionModule.http.newCall(request) val response = suspendCancellableCoroutine { continuation -> call.enqueue(object : Callback { override fun onFailure(call: okhttp3.Call, e: java.io.IOException) { @@ -1085,7 +1030,7 @@ internal class StreamVideoImpl internal constructor( internal suspend fun accept(type: String, id: String): Result { return wrapAPICall { - connectionModule.api.acceptCall(type, id) + coordinatorConnectionModule.api.acceptCall(type, id) } } @@ -1095,19 +1040,19 @@ internal class StreamVideoImpl internal constructor( reason: RejectReason? = null, ): Result { return wrapAPICall { - connectionModule.api.rejectCall(type, id, RejectCallRequest(reason?.alias)) + coordinatorConnectionModule.api.rejectCall(type, id, RejectCallRequest(reason?.alias)) } } internal suspend fun notify(type: String, id: String): Result { return wrapAPICall { - connectionModule.api.getCall(type, id, notify = true) + coordinatorConnectionModule.api.getCall(type, id, notify = true) } } internal suspend fun ring(type: String, id: String): Result { return wrapAPICall { - connectionModule.api.getCall(type, id, ring = true) + coordinatorConnectionModule.api.getCall(type, id, ring = true) } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt index 1cca79a2cc..c094a73214 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/RtcSession.kt @@ -16,25 +16,30 @@ package io.getstream.video.android.core.call +import android.os.Build import androidx.annotation.VisibleForTesting +import androidx.lifecycle.Lifecycle import io.getstream.log.taggedLogger import io.getstream.result.Result import io.getstream.result.Result.Failure import io.getstream.result.Result.Success +import io.getstream.result.extractCause import io.getstream.result.onSuccessSuspend import io.getstream.video.android.core.BuildConfig import io.getstream.video.android.core.Call import io.getstream.video.android.core.CallStatsReport import io.getstream.video.android.core.DeviceStatus import io.getstream.video.android.core.MediaManagerImpl +import io.getstream.video.android.core.RealtimeConnection import io.getstream.video.android.core.ScreenShareManager import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.StreamVideoImpl +import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.call.connection.StreamPeerConnection import io.getstream.video.android.core.call.stats.model.RtcStatsReport import io.getstream.video.android.core.call.utils.stringify import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.errors.RtcException +import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.ChangePublishQualityEvent import io.getstream.video.android.core.events.ICERestartEvent import io.getstream.video.android.core.events.ICETrickleEvent @@ -42,10 +47,10 @@ import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.ParticipantJoinedEvent import io.getstream.video.android.core.events.ParticipantLeftEvent import io.getstream.video.android.core.events.SfuDataEvent +import io.getstream.video.android.core.events.SfuDataRequest import io.getstream.video.android.core.events.SubscriberOfferEvent import io.getstream.video.android.core.events.TrackPublishedEvent import io.getstream.video.android.core.events.TrackUnpublishedEvent -import io.getstream.video.android.core.internal.module.ConnectionModule import io.getstream.video.android.core.internal.module.SfuConnectionModule import io.getstream.video.android.core.model.AudioTrack import io.getstream.video.android.core.model.IceCandidate @@ -54,7 +59,7 @@ import io.getstream.video.android.core.model.MediaTrack import io.getstream.video.android.core.model.StreamPeerType import io.getstream.video.android.core.model.VideoTrack import io.getstream.video.android.core.model.toPeerType -import io.getstream.video.android.core.socket.SocketState +import io.getstream.video.android.core.socket.sfu.state.SfuSocketState import io.getstream.video.android.core.toJson import io.getstream.video.android.core.utils.SdpSession import io.getstream.video.android.core.utils.buildAudioConstraints @@ -67,7 +72,6 @@ import io.getstream.video.android.core.utils.stringify import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel @@ -84,10 +88,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.retry import kotlinx.coroutines.flow.retryWhen import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeout import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import okio.IOException @@ -104,15 +105,25 @@ import org.webrtc.RtpParameters.Encoding import org.webrtc.RtpTransceiver import org.webrtc.SessionDescription import retrofit2.HttpException +import stream.video.sfu.event.JoinRequest +import stream.video.sfu.event.LeaveCallRequest import stream.video.sfu.event.Migration +import stream.video.sfu.event.ReconnectDetails +import stream.video.sfu.event.SfuRequest +import stream.video.sfu.models.ClientDetails +import stream.video.sfu.models.Device import stream.video.sfu.models.ICETrickle +import stream.video.sfu.models.OS import stream.video.sfu.models.Participant import stream.video.sfu.models.PeerType +import stream.video.sfu.models.Sdk +import stream.video.sfu.models.SdkType import stream.video.sfu.models.TrackInfo import stream.video.sfu.models.TrackType import stream.video.sfu.models.VideoDimension import stream.video.sfu.models.VideoLayer import stream.video.sfu.models.VideoQuality +import stream.video.sfu.models.WebsocketReconnectStrategy import stream.video.sfu.signal.ICERestartRequest import stream.video.sfu.signal.ICERestartResponse import stream.video.sfu.signal.ICETrickleResponse @@ -173,12 +184,14 @@ data class TrackDimensions( */ public class RtcSession internal constructor( client: StreamVideo, - private val connectionModule: ConnectionModule, private val call: Call, + private val sessionId: String, + private val apiKey: String, + private val lifecycle: Lifecycle, internal var sfuUrl: String, + internal var sfuWsUrl: String, internal var sfuToken: String, internal var remoteIceServers: List, - internal var onMigrationCompleted: () -> Unit, ) { internal val trackIdToParticipant: MutableStateFlow> = @@ -188,20 +201,20 @@ public class RtcSession internal constructor( private var syncPublisherJob: Job? = null private var subscriptionSyncJob: Job? = null private var muteStateSyncJob: Job? = null - private var sfuSocketStateJob: Job? = null private var subscriberListenJob: Job? = null private var videoTransceiverInitialized: Boolean = false private var audioTransceiverInitialized: Boolean = false private var screenshareTransceiverInitialized: Boolean = false + private var stateJob: Job? = null private var errorJob: Job? = null private var eventJob: Job? = null internal val socket - get() = sfuConnectionModule.sfuSocket + get() = sfuConnectionModule.socketConnection - private val logger by taggedLogger("Call:RtcSession") - private val dynascaleLogger by taggedLogger("Call:RtcSession:Dynascale") - private val clientImpl = client as StreamVideoImpl + private val logger by taggedLogger("Video:RtcSession") + private val dynascaleLogger by taggedLogger("Video:RtcSession:Dynascale") + private val clientImpl = client as StreamVideoClient internal val lastVideoStreamAdded = MutableStateFlow(null) @@ -210,8 +223,6 @@ public class RtcSession internal constructor( null, ) - internal val sessionId = clientImpl.sessionId - val trackDimensions = MutableStateFlow>>( emptyMap(), @@ -222,11 +233,6 @@ public class RtcSession internal constructor( private val supervisorJob = SupervisorJob() private val coroutineScope = CoroutineScope(clientImpl.scope.coroutineContext + supervisorJob) - /** - * We can't publish tracks till we've received the join event response - */ - private val joinEventReceivedMutex = Mutex(locked = true) - // participants by session id -> participant state private val trackPrefixToSessionIdMap = call.state.participants.mapState { it.associate { it.trackLookupPrefix to it.sessionId } } @@ -302,21 +308,31 @@ public class RtcSession internal constructor( internal lateinit var sfuConnectionModule: SfuConnectionModule + private val clientDetails = ClientDetails( + os = OS( + name = "Android", + version = Build.VERSION.SDK_INT.toString(), + ), + device = Device( + name = "${Build.MANUFACTURER} : ${Build.MODEL}", + ), + sdk = Sdk( + type = SdkType.SDK_TYPE_ANDROID, + major = BuildConfig.STREAM_VIDEO_VERSION_MAJOR.toString(), + minor = BuildConfig.STREAM_VIDEO_VERSION_MINOR.toString(), + patch = BuildConfig.STREAM_VIDEO_VERSION_PATCH.toString(), + ), + ) + /** * Used during a SFU migration as a temporary new SFU connection. Is null before and after * the migration is finished. */ private var sfuConnectionMigrationModule: SfuConnectionModule? = null - private val _sfuSocketState = MutableStateFlow(SocketState.NotConnected) - val sfuSocketState = _sfuSocketState.asStateFlow() - - private val sfuFastReconnectListener: suspend () -> Unit = { - // SFU socket has done a fast-reconnect. We need to an ICE restart immediately and not wait - // until the health check runs - call.monitor.stopTimer() - call.monitor.reconnect(forceRestart = true) - } + private val _sfuSfuSocketState = + MutableStateFlow(SfuSocketState.Disconnected.Stopped) + val sfuSocketState = _sfuSfuSocketState.asStateFlow() init { if (!StreamVideo.isInstalled) { @@ -328,24 +344,20 @@ public class RtcSession internal constructor( // step 1 setup the peer connections subscriber = createSubscriber() + publisher = createPublisher() listenToSubscriberConnection() - - val session = this - val getSdp = suspend { - session.getSubscriberSdp().description - } - val sfuConnectionModule = - connectionModule.createSFUConnectionModule( - sfuUrl, - sessionId, - sfuToken, - getSdp, - sfuFastReconnectListener, - ) + val sfuConnectionModule = SfuConnectionModule( + context = clientImpl.context, + apiKey = apiKey, + apiUrl = sfuUrl, + wssUrl = sfuWsUrl, + connectionTimeoutInMs = 2000L, + userToken = sfuToken, + lifecycle = lifecycle, + ) setSfuConnectionModule(sfuConnectionModule) - listenToSocketEventsAndErrors() - + listenToSfuSocket() coroutineScope.launch { // call update participant subscriptions debounced trackDimensionsDebounced.collect { @@ -368,20 +380,75 @@ public class RtcSession internal constructor( } } - private fun listenToSocketEventsAndErrors() { + private fun listenToSfuSocket() { // cancel any old socket monitoring if needed eventJob?.cancel() errorJob?.cancel() + stateJob?.cancel() + + // State + // Start listening to connection state on new SFU connection + stateJob = coroutineScope.launch { + sfuConnectionModule.socketConnection.state().collect { sfuSocketState -> + _sfuSfuSocketState.value = sfuSocketState + when (sfuSocketState) { + is SfuSocketState.Connected -> + call.state._connection.value = + RealtimeConnection.Connected + + is SfuSocketState.Connecting -> + call.state._connection.value = + RealtimeConnection.InProgress + else -> { + // Ignore it + } + } + } + } // listen to socket events and errors eventJob = coroutineScope.launch { - sfuConnectionModule.sfuSocket.events.collect { + sfuConnectionModule.socketConnection.events().collect { + if (it is CallEndedSfuEvent) { + logger.d { "#rtcsession #events #sfu, event: $it" } + call.leave() + } clientImpl.fireEvent(it, call.cid) } } + + // Errors errorJob = coroutineScope.launch { - sfuConnectionModule.sfuSocket.errors.collect { - logger.e(it) { "permanent failure on socket connection" } + sfuConnectionModule.socketConnection.errors().collect { + val reconnectStrategy = it.reconnectStrategy + logger.d { "[RtcSession#error] reconnectStrategy: $reconnectStrategy" } + when (reconnectStrategy) { + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST -> { + call.fastReconnect() + } + + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN -> { + call.rejoin() + } + + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_MIGRATE -> { + call.migrate() + } + + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT -> { + // We are told to disconnect. + sfuConnectionModule.socketConnection.disconnect() + call.state._connection.value = RealtimeConnection.Disconnected + } + + else -> { + // Either null or UNSPECIFIED, do not reconnect + } + } + logger.e( + it.streamError.extractCause() + ?: IllegalStateException("Error emitted without a cause on SFU connection."), + ) { "permanent failure on socket connection" } } } } @@ -420,19 +487,20 @@ public class RtcSession internal constructor( awaitAll(subscriberAsync, publisherAsync) } - suspend fun connect() { + suspend fun connect(reconnectDetails: ReconnectDetails? = null) { logger.i { "[connect] #sfu; #track; no args" } - sfuConnectionModule.sfuSocket.connect() - // ensure that the join event has been handled before starting RTC - try { - withTimeout(2000L) { - joinEventReceivedMutex.withLock { connectRtc() } - } - } catch (e: TimeoutCancellationException) { - throw RtcException( - message = "RtcSession.connect() timed out. JoinCallResponseEvent not received in time.", - cause = e, - ) + val request = JoinRequest( + session_id = sessionId, + token = sfuToken, + fast_reconnect = false, + client_details = clientDetails, + reconnect_details = reconnectDetails, + ) + logger.d { "Connecting RTC, $request" } + listenToSfuSocket() + sfuConnectionModule.socketConnection.connect(request) + sfuConnectionModule.socketConnection.whenConnected { + connectRtc() } } @@ -452,28 +520,7 @@ public class RtcSession internal constructor( private fun setSfuConnectionModule(sfuConnectionModule: SfuConnectionModule) { // This is used to switch from a current SFU connection to a new migrated SFU connection this@RtcSession.sfuConnectionModule = sfuConnectionModule - - // Stop listening to connection state on the existing SFU connection - sfuSocketStateJob?.cancel() - sfuSocketStateJob = null - - // Start listening to connection state on new SFU connection - sfuSocketStateJob = coroutineScope.launch { - sfuConnectionModule.sfuSocket.connectionState.collect { sfuSocketState -> - _sfuSocketState.value = sfuSocketState - - // make sure we stop handling ICE candidates when a new SFU socket - // connection is being established. We need to wait until a SubscriberOffer - // is received again and then we start listening to the ICE candidate queue - if (sfuSocketState == SocketState.Connecting || - sfuSocketState is SocketState.DisconnectedTemporarily || - sfuSocketState is SocketState.DisconnectedByRequest - ) { - syncSubscriberCandidates?.cancel() - syncSubscriberCandidates?.cancel() - } - } - } + listenToSfuSocket() } private fun initializeScreenshareTransceiver() { @@ -596,7 +643,7 @@ public class RtcSession internal constructor( setTrack(sessionId, trackType, videoTrack) } - if (sessionId != this.sessionId && mediaStream.videoTracks.isNotEmpty()) { + if (mediaStream.videoTracks.isNotEmpty()) { lastVideoStreamAdded.value = mediaStream } } @@ -725,12 +772,11 @@ public class RtcSession internal constructor( fun cleanup() { logger.i { "[cleanup] #sfu; #track; no args" } - supervisorJob.cancel() - // disconnect the socket and clean it up - sfuConnectionModule.sfuSocket.cleanup() - - sfuConnectionMigrationModule?.sfuSocket?.cleanup() + coroutineScope.launch { + sfuConnectionModule.socketConnection.disconnect() + sfuConnectionMigrationModule?.socketConnection?.disconnect() + } sfuConnectionMigrationModule = null // cleanup the publisher and subcriber peer connections @@ -751,8 +797,8 @@ public class RtcSession internal constructor( } } tracks.clear() - trackDimensions.value = emptyMap() + supervisorJob.cancel() } internal val muteState = MutableStateFlow( @@ -863,6 +909,9 @@ public class RtcSession internal constructor( @VisibleForTesting fun createPublisher(): StreamPeerConnection { + audioTransceiverInitialized = false + videoTransceiverInitialized = false + screenshareTransceiverInitialized = false logger.i { "[createPublisher] #sfu; no args" } val publisher = clientImpl.peerConnectionFactory.makePeerConnection( coroutineScope = coroutineScope, @@ -1066,16 +1115,17 @@ public class RtcSession internal constructor( fun handleEvent(event: VideoEvent) { logger.i { "[rtc handleEvent] #sfu; event: $event" } - if (event is JoinCallResponseEvent) { - if (joinEventReceivedMutex.isLocked) { - logger.i { "[rtc handleEvent] unlocking joinEventReceivedMutex" } - joinEventReceivedMutex.unlock() - } - } if (event is SfuDataEvent) { coroutineScope.launch { logger.v { "[onRtcEvent] event: $event" } when (event) { + is JoinCallResponseEvent -> { + val participantStates = event.callState.participants.map { + call.state.getOrCreateParticipant(it) + } + call.state.replaceParticipants(participantStates) + } + is SubscriberOfferEvent -> handleSubscriberOffer(event) // this dynascale event tells the SDK to change the quality of the video it's uploading is ChangePublishQualityEvent -> updatePublishQuality(event) @@ -1119,6 +1169,7 @@ public class RtcSession internal constructor( PeerType.PEER_TYPE_PUBLISHER_UNSPECIFIED -> { publisher?.connection?.restartIce() } + PeerType.PEER_TYPE_SUBSCRIBER -> { subscriber?.connection?.restartIce() } @@ -1266,8 +1317,10 @@ public class RtcSession internal constructor( // setRemoteDescription has been called and everything is ready - we can // now start handling the ICE subscriber candidates queue syncSubscriberCandidates = coroutineScope.launch { - sfuConnectionModule.sfuSocket.pendingSubscriberIceCandidates.collect { iceCandidates -> - subscriber.addIceCandidate(iceCandidates) + sfuConnectionModule.socketConnection.events().collect { event -> + if (event is ICETrickleEvent) { + handleIceTrickle(event) + } } } }.flowOn(DispatcherProvider.IO).retryWhen { cause, attempt -> @@ -1283,7 +1336,7 @@ public class RtcSession internal constructor( willRetry }.catch { logger.e { "setPublisher failed after 3 retries, asking the call monitor to do an ice restart" } - coroutineScope.launch { call.monitor.reconnect(forceRestart = true) } + coroutineScope.launch { call.rejoin() } }.collect() } } @@ -1335,69 +1388,68 @@ public class RtcSession internal constructor( return@launch } - // the Sfu WS needs to be connected before calling SetPublisherRequest - if (joinEventReceivedMutex.isLocked) { - logger.e { "[negotiate] #$id; #sfu; #${peerType.stringify()}; SFU WS isn't connected" } - return@launch - } - // step 3 - create the list of tracks val tracks = getPublisherTracks(mangledSdp.description) val currentSfu = sfuUrl publisherSdpOffer.value = mangledSdp + synchronized(this) { + syncPublisherJob?.cancel() + syncPublisherJob = coroutineScope.launch { + var attempt = 0 + val maxAttempts = 3 + val delayInMs = listOf(10L, 30L, 100L) + + while (attempt <= maxAttempts) { + try { + // step 4 - send the tracks and SDP + val request = SetPublisherRequest( + sdp = mangledSdp.description, + session_id = sessionId, + tracks = tracks, + ) + + val result = setPublisher(request) + // step 5 - set the remote description + + peerConnection.setRemoteDescription( + SessionDescription( + SessionDescription.Type.ANSWER, result.getOrThrow().sdp, + ), + ) + + // If successful, emit the result and break the loop + result.getOrThrow() + break + } catch (cause: Throwable) { + val sameValue = mangledSdp == publisherSdpOffer.value + val sameSfu = currentSfu == sfuUrl + val isPermanent = isPermanentError(cause) + val willRetry = + !isPermanent && sameValue && sameSfu && attempt < maxAttempts + + logger.w { + "onNegotationNeeded setPublisher failed $cause, retry attempt: $attempt. will retry $willRetry in ${delayInMs[attempt]} ms" + } - // prevent running multiple of these at the same time - // if there's already a job active. cancel it - syncPublisherJob?.cancel() - // start a new job - // this code is a bit more complicated due to the retry behaviour - syncPublisherJob = coroutineScope.launch { - flow { - // step 4 - send the tracks and SDP - val request = SetPublisherRequest( - sdp = mangledSdp.description, - session_id = sessionId, - tracks = tracks, - ) - - val result = setPublisher(request) - // step 5 - set the remote description - - peerConnection.setRemoteDescription( - SessionDescription( - SessionDescription.Type.ANSWER, result.getOrThrow().sdp, - ), - ) - - // start listening to ICE candidates - launch { - sfuConnectionModule.sfuSocket.pendingPublisherIceCandidates.collect { iceCandidates -> - publisher?.addIceCandidate(iceCandidates) + if (willRetry) { + delay(delayInMs[attempt]) + attempt++ + } else { + logger.e { + "setPublisher failed after $attempt retries, asking the call monitor to do an ice restart" + } + coroutineScope.launch { call.rejoin() } + break + } } } - - emit(result.getOrThrow()) - }.flowOn(DispatcherProvider.IO).retryWhen { cause, attempt -> - val sameValue = mangledSdp == publisherSdpOffer.value - val sameSfu = currentSfu == sfuUrl - val isPermanent = isPermanentError(cause) - val willRetry = !isPermanent && sameValue && sameSfu && attempt <= 3 - val delayInMs = if (attempt <= 1) 10L else if (attempt <= 2) 30L else 100L - logger.w { - "onNegotationNeeded setPublisher failed $cause, retry attempt: $attempt. will retry $willRetry in $delayInMs ms" - } - delay(delayInMs) - willRetry - }.catch { - logger.e { "setPublisher failed after 3 retries, asking the call monitor to do an ice restart" } - coroutineScope.launch { call.monitor.reconnect(forceRestart = true) } - }.collect() + } } } } - private fun getPublisherTracks(sdp: String): List { + internal fun getPublisherTracks(sdp: String): List { val captureResolution = call.camera.resolution.value val screenShareTrack = getLocalTrack(TrackType.TRACK_TYPE_SCREEN_SHARE) @@ -1575,7 +1627,7 @@ public class RtcSession internal constructor( internal suspend fun sendCallStats(report: CallStatsReport) { val result = wrapAPICall { - sfuConnectionModule.signalService.sendStats( + sfuConnectionModule.api.sendStats( sendStatsRequest = SendStatsRequest( session_id = sessionId, sdk = "stream-android", @@ -1639,7 +1691,7 @@ public class RtcSession internal constructor( // reply to when we get an offer from the SFU private suspend fun sendAnswer(request: SendAnswerRequest): Result = wrapAPICall { - val result = sfuConnectionModule.signalService.sendAnswer(request) + val result = sfuConnectionModule.api.sendAnswer(request) result.error?.let { throw RtcException(error = it, message = it.message) } @@ -1649,7 +1701,7 @@ public class RtcSession internal constructor( // send whenever we have a new ice candidate private suspend fun sendIceCandidate(request: ICETrickle): Result = wrapAPICall { - val result = sfuConnectionModule.signalService.iceTrickle(request) + val result = sfuConnectionModule.api.iceTrickle(request) result.error?.let { throw RtcException(error = it, message = it.message) } @@ -1658,9 +1710,9 @@ public class RtcSession internal constructor( // call after onNegotiation Needed private suspend fun setPublisher(request: SetPublisherRequest): Result { - logger.e { "[setPublisher] #sfu; request $request" } + logger.d { "[setPublisher] #sfu; request $request" } return wrapAPICall { - val result = sfuConnectionModule.signalService.setPublisher(request) + val result = sfuConnectionModule.api.setPublisher(request) result.error?.let { throw RtcException(error = it, message = it.message) } @@ -1674,7 +1726,7 @@ public class RtcSession internal constructor( ): Result = wrapAPICall { logger.v { "[updateSubscriptions] #sfu; #track; request $request" } - val result = sfuConnectionModule.signalService.updateSubscriptions(request) + val result = sfuConnectionModule.api.updateSubscriptions(request) result.error?.let { throw RtcException(error = it, message = it.message) } @@ -1688,12 +1740,21 @@ public class RtcSession internal constructor( session_id = sessionId, peer_type = PeerType.PEER_TYPE_SUBSCRIBER, ) - sfuConnectionModule.signalService.iceRestart(request) + sfuConnectionModule.api.iceRestart(request) + } + + suspend fun requestPublisherIceRestart(): Result = + wrapAPICall { + val request = ICERestartRequest( + session_id = sessionId, + peer_type = PeerType.PEER_TYPE_PUBLISHER_UNSPECIFIED, + ) + sfuConnectionModule.api.iceRestart(request) } private suspend fun updateMuteState(request: UpdateMuteStatesRequest): Result = wrapAPICall { - val result = sfuConnectionModule.signalService.updateMuteStates(request) + val result = sfuConnectionModule.api.updateMuteStates(request) result.error?.let { throw RtcException(error = it, message = it.message) } @@ -1747,6 +1808,71 @@ public class RtcSession internal constructor( } } + internal fun currentSfuInfo(): Triple, List> { + val previousSessionId = sessionId + val currentSubscriptions = subscriptions.value + val publisherTracks = publisher?.let { pub -> + if (pub.connection.remoteDescription != null) { + getPublisherTracks(pub.connection.localDescription.description) + } else { + null + } + } ?: emptyList() + return Triple(previousSessionId, currentSubscriptions, publisherTracks) + } + + internal fun fastReconnect(reconnectDetails: ReconnectDetails?) { + // Fast reconnect, send a JOIN request on the same SFU + // and restart ICE on publisher + logger.d { "[fastReconnect] Starting fast reconnect." } + val (previousSessionId, currentSubscriptions, publisherTracks) = currentSfuInfo() + logger.d { "[fastReconnect] Published tracks: $publisherTracks" } + val request = JoinRequest( + session_id = sessionId, + token = sfuToken, + client_details = clientDetails, + reconnect_details = reconnectDetails, + ) + logger.d { "Connecting RTC, $request" } + listenToSfuSocket() + coroutineScope.launch { + sfuConnectionModule.socketConnection.reconnect(request) + sfuConnectionModule.socketConnection.whenConnected { + // ice restart + val subscriberAsync = coroutineScope.async { + subscriber?.let { + requestSubscriberIceRestart() + } + } + + val publisherAsync = coroutineScope.async { + publisher?.connection?.restartIce() + } + + awaitAll(subscriberAsync, publisherAsync) + } + } + } + + internal fun prepareRejoin() { + // We are rejoining from the start, we don't want to know. + stateJob?.cancel() + eventJob?.cancel() + errorJob?.cancel() + coroutineScope.launch { + sfuConnectionModule.socketConnection.disconnect() + } + publisher?.connection?.close() + subscriber?.connection?.close() + } + + internal fun prepareReconnect() { + // We are rejoining from the start, we don't want to know. + stateJob?.cancel() + eventJob?.cancel() + errorJob?.cancel() + } + suspend fun switchSfu( sfuName: String, sfuUrl: String, @@ -1770,27 +1896,45 @@ public class RtcSession internal constructor( ) } // Create a parallel SFU socket - sfuConnectionMigrationModule = - connectionModule.createSFUConnectionModule( - sfuUrl, - sessionId, - sfuToken, - getSdp, - sfuFastReconnectListener, - ) + sfuConnectionMigrationModule = SfuConnectionModule( + context = clientImpl.context, + apiKey = apiKey, + apiUrl = sfuUrl, + wssUrl = sfuWsUrl, + connectionTimeoutInMs = 10000L, + userToken = sfuToken, + lifecycle = lifecycle, + ) + // Connect to SFU socket + val migrationData = migration.invoke() + val request = JoinRequest( + session_id = sessionId, + token = sfuToken, + subscriber_sdp = getSdp.invoke(), + fast_reconnect = false, + migration = migrationData, + client_details = clientDetails, + ) + + sfuConnectionModule.socketConnection.whenConnected { + sfuConnectionMigrationModule!!.socketConnection.sendEvent( + SfuDataRequest(SfuRequest(join_request = request)), + ) + } // Wait until the socket connects - if it fails to connect then return to "Reconnecting" // state (to make sure that the full reconnect logic will kick in) coroutineScope.launch { - sfuConnectionMigrationModule!!.sfuSocket.connectionState.collect { it -> + sfuConnectionMigrationModule!!.socketConnection.state().collect { it -> when (it) { - is SocketState.Connected -> { + is SfuSocketState.Connected -> { logger.d { "[switchSfu] Migration SFU socket state changed to Connected" } // Disconnect the old SFU and stop listening to SFU stateflows eventJob?.cancel() errorJob?.cancel() - sfuConnectionModule.sfuSocket.cleanup() + // Cleanup called after the migration is successful + // sfuConnectionModule.sfuSocket.cleanup() // Make the new SFU the currently used one setSfuConnectionModule(sfuConnectionMigrationModule!!) @@ -1804,7 +1948,7 @@ public class RtcSession internal constructor( this@RtcSession.iceServers = buildRemoteIceServers(remoteIceServers) // reconnect socket listeners - listenToSocketEventsAndErrors() + listenToSfuSocket() var tempSubscriber = subscriber @@ -1827,11 +1971,7 @@ public class RtcSession internal constructor( logger.d { "[switchSfu] Migration subscriber state changed to Connected" } tempSubscriber?.let { tempSubscriberValue -> tempSubscriberValue.connection.close() - tempSubscriber = null } - - onMigrationCompleted.invoke() - cancel() } else if (it == PeerConnectionState.CLOSED || it == PeerConnectionState.DISCONNECTED || @@ -1852,13 +1992,13 @@ public class RtcSession internal constructor( cancel() } - is SocketState.DisconnectedPermanently -> { + is SfuSocketState.Disconnected.DisconnectedPermanently -> { logger.d { "[switchSfu] Failed to migrate - SFU socket disconnected permanently ${it.error}" } failedToSwitch() cancel() } - is SocketState.DisconnectedTemporarily -> { + is SfuSocketState.Disconnected.DisconnectedTemporarily -> { logger.d { "[switchSfu] Failed to migrate - SFU socket disconnected temporarily ${it.error}" } // We don't wait for the socket to retry during migration // In this case we will fall back to full-reconnect @@ -1872,8 +2012,16 @@ public class RtcSession internal constructor( } } } + } - // Connect to SFU socket - sfuConnectionMigrationModule!!.sfuSocket.connectMigrating(migration) {} + internal fun leaveWithReason(reason: String) { + val leaveCallRequest = LeaveCallRequest( + session_id = sessionId, + reason = reason, + ) + val request = SfuRequest(leave_call_request = leaveCallRequest) + coroutineScope.launch { + sfuConnectionModule.socketConnection.sendEvent(SfuDataRequest(request)) + } } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt index 90c948f752..2a8b9f52ad 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/connection/StreamPeerConnection.kt @@ -73,12 +73,6 @@ public class StreamPeerConnection( private val setDescriptionMutex = Mutex() - private val goodStates = listOf( - PeerConnection.PeerConnectionState.NEW, // New is good, means we're not using it yet - PeerConnection.PeerConnectionState.CONNECTED, - PeerConnection.PeerConnectionState.CONNECTING, - ) - internal var localSdp: SessionDescription? = null internal var remoteSdp: SessionDescription? = null private val typeTag = type.stringify() @@ -108,7 +102,13 @@ public class StreamPeerConnection( private set fun isHealthy(): Boolean { - return state.value in goodStates + return when (state.value) { + PeerConnection.PeerConnectionState.NEW, + PeerConnection.PeerConnectionState.CONNECTED, + PeerConnection.PeerConnectionState.CONNECTING, + -> true + else -> false + } } init { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt index 67e739b228..6d2b154d42 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/call/signal/socket/RTCEventMapper.kt @@ -18,6 +18,7 @@ package io.getstream.video.android.core.call.signal.socket import io.getstream.log.taggedLogger import io.getstream.video.android.core.events.AudioLevelChangedEvent +import io.getstream.video.android.core.events.CallEndedSfuEvent import io.getstream.video.android.core.events.CallGrantsUpdatedEvent import io.getstream.video.android.core.events.ChangePublishQualityEvent import io.getstream.video.android.core.events.ConnectionQualityChangeEvent @@ -30,6 +31,7 @@ import io.getstream.video.android.core.events.JoinCallResponseEvent import io.getstream.video.android.core.events.ParticipantCount import io.getstream.video.android.core.events.ParticipantJoinedEvent import io.getstream.video.android.core.events.ParticipantLeftEvent +import io.getstream.video.android.core.events.ParticipantMigrationCompleteEvent import io.getstream.video.android.core.events.PinUpdate import io.getstream.video.android.core.events.PinsUpdatedEvent import io.getstream.video.android.core.events.PublisherAnswerEvent @@ -111,6 +113,7 @@ public object RTCEventMapper { JoinCallResponseEvent( event.join_response.call_state!!, counts, + event.join_response.fast_reconnect_deadline_seconds, event.join_response.reconnected, ) } @@ -122,7 +125,7 @@ public object RTCEventMapper { event.ice_restart != null -> ICERestartEvent(event.ice_restart.peer_type) event.publisher_answer != null -> PublisherAnswerEvent(sdp = event.publisher_answer.sdp) - event.error != null -> ErrorEvent(event.error.error) + event.error != null -> ErrorEvent(event.error.error, event.error.reconnect_strategy) event.call_grants_updated != null -> CallGrantsUpdatedEvent( event.call_grants_updated.current_grants, @@ -131,12 +134,18 @@ public object RTCEventMapper { event.go_away != null -> GoAwayEvent(reason = event.go_away.reason) + event.participant_migration_complete != null -> ParticipantMigrationCompleteEvent + event.pins_updated != null -> PinsUpdatedEvent( event.pins_updated.pins.map { PinUpdate(it.user_id, it.session_id) }, ) + event.call_ended != null -> { + CallEndedSfuEvent(event.call_ended.reason.value) + } + else -> { logger.w { "Unknown event: $event" } UnknownEvent(event) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/errors/DisconnectCause.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/errors/DisconnectCause.kt index 088f9ce426..f1f9301094 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/errors/DisconnectCause.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/errors/DisconnectCause.kt @@ -42,6 +42,13 @@ public sealed class DisconnectCause { public val error: io.getstream.result.Error.NetworkError?, ) : DisconnectCause() + /** + * Happens when Web Socket connection is not available. + */ + public object WebSocketNotAvailable : DisconnectCause() { + override fun toString(): String = "WebSocketNotAvailable" + } + /** * Happens when disconnection has been done intentionally. E.g. we release connection when app went to background. */ diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt index 94bce8caf5..297d0de6cc 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/events/SfuDataEvent.kt @@ -20,6 +20,7 @@ import io.getstream.video.android.model.UserAudioLevel import org.openapitools.client.models.VideoEvent import stream.video.sfu.event.ChangePublishQuality import stream.video.sfu.event.ConnectionQualityInfo +import stream.video.sfu.event.SfuRequest import stream.video.sfu.models.CallGrants import stream.video.sfu.models.CallState import stream.video.sfu.models.Error @@ -27,6 +28,7 @@ import stream.video.sfu.models.GoAwayReason import stream.video.sfu.models.Participant import stream.video.sfu.models.PeerType import stream.video.sfu.models.TrackType +import stream.video.sfu.models.WebsocketReconnectStrategy public sealed class SfuDataEvent : VideoEvent() { override fun getEventType(): String { @@ -34,6 +36,12 @@ public sealed class SfuDataEvent : VideoEvent() { } } +public data class SfuDataRequest(val sfuRequest: SfuRequest) : SfuDataEvent() { + override fun getEventType(): String { + return "SfuDataRequest" + } +} + public data class PublisherAnswerEvent( val sdp: String, ) : SfuDataEvent() @@ -116,9 +124,16 @@ public data class SFUHealthCheckEvent( public data class JoinCallResponseEvent( val callState: CallState, val participantCount: ParticipantCount, + val fastReconnectDeadlineSeconds: Int, val isReconnected: Boolean, ) : SfuDataEvent() +public data class CallEndedSfuEvent( + val reason: Int, +) : SfuDataEvent() + +public data object ParticipantMigrationCompleteEvent : SfuDataEvent() + public data class PinsUpdatedEvent( val pins: List, ) : SfuDataEvent() @@ -127,7 +142,8 @@ public data class PinUpdate(val userId: String, val sessionId: String) public data class UnknownEvent(val event: Any?) : SfuDataEvent() -public data class ErrorEvent(val error: Error?) : SfuDataEvent() +public data class ErrorEvent(val error: Error?, val reconnectStrategy: WebsocketReconnectStrategy) : + SfuDataEvent() public class SfuSocketError(val error: Error?) : Throwable() { override val message: String? diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModule.kt deleted file mode 100644 index d7ee523639..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModule.kt +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.internal.module - -import android.content.Context -import android.net.ConnectivityManager -import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.api.SignalServerService -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.logging.LoggingLevel -import io.getstream.video.android.core.socket.CoordinatorSocket -import io.getstream.video.android.core.socket.SfuSocket -import io.getstream.video.android.model.ApiKey -import io.getstream.video.android.model.User -import io.getstream.video.android.model.UserToken -import kotlinx.coroutines.CoroutineScope -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Response -import okhttp3.logging.HttpLoggingInterceptor -import org.openapitools.client.apis.ProductvideoApi -import org.openapitools.client.infrastructure.Serializer -import retrofit2.Retrofit -import retrofit2.converter.moshi.MoshiConverterFactory -import retrofit2.converter.scalars.ScalarsConverterFactory -import retrofit2.converter.wire.WireConverterFactory -import java.io.IOException -import java.util.concurrent.TimeUnit - -/** - * ConnectionModule provides several helpful attributes - * - * preferences: token & user settings - * oldService - * eventsApi - * defaultApi - * - * coordinatorSocket - * - * createSFUConnectionModule - */ -internal class ConnectionModule( - context: Context, - private val scope: CoroutineScope, - internal val videoDomain: String, - internal val connectionTimeoutInMs: Long, - internal val loggingLevel: LoggingLevel = LoggingLevel(), - private val user: User, - internal val apiKey: ApiKey, - internal val userToken: UserToken, -) { - private val authInterceptor: CoordinatorAuthInterceptor by lazy { - CoordinatorAuthInterceptor(apiKey, userToken) - } - private val headersInterceptor: HeadersInterceptor by lazy { HeadersInterceptor() } - val okHttpClient: OkHttpClient by lazy { buildOkHttpClient() } - val networkStateProvider: NetworkStateProvider by lazy { - NetworkStateProvider( - connectivityManager = context - .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager, - ) - } - private val retrofit: Retrofit by lazy { - Retrofit.Builder() - .baseUrl("https://$videoDomain") - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(MoshiConverterFactory.create(Serializer.moshi)) - .client(okHttpClient) - .build() - } - val api: ProductvideoApi by lazy { retrofit.create(ProductvideoApi::class.java) } - val coordinatorSocket: CoordinatorSocket by lazy { createCoordinatorSocket() } - - val localApi: ProductvideoApi by lazy { - Retrofit.Builder() - .baseUrl("https://c187-2a02-a46d-1c8b-1-b5c3-c938-b354-c7b0.ngrok-free.app") - .addConverterFactory(ScalarsConverterFactory.create()) - .addConverterFactory(MoshiConverterFactory.create(Serializer.moshi)) - .client(okHttpClient) - .build().create(ProductvideoApi::class.java) - } - - /** - * Key used to prove authorization to the API. - */ - - private fun buildOkHttpClient(): OkHttpClient { - // create a new OkHTTP client and set timeouts - return OkHttpClient.Builder() - .addInterceptor(headersInterceptor) - .addInterceptor(authInterceptor) - .addInterceptor( - HttpLoggingInterceptor().apply { - level = loggingLevel.httpLoggingLevel.level - }, - ) - .retryOnConnectionFailure(true) - .connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .writeTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .readTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .build() - } - - /** - * Provider that handles connectivity and listens to state changes, exposing them to listeners. - */ - - /** - * @return The WebSocket handler that is used to connect to different calls. - */ - private fun createCoordinatorSocket(): CoordinatorSocket { - val coordinatorUrl = "wss://$videoDomain/video/connect" - - return CoordinatorSocket( - coordinatorUrl, - user, - userToken, - scope, - okHttpClient, - networkStateProvider = networkStateProvider, - ) - } - - internal fun createSFUConnectionModule( - sfuUrl: String, - sessionId: String, - sfuToken: String, - getSubscriberSdp: suspend () -> String, - onFastReconnect: suspend () -> Unit, - ): SfuConnectionModule { - return SfuConnectionModule( - sfuUrl = sfuUrl, - sessionId = sessionId, - sfuToken = sfuToken, - apiKey = apiKey, - getSubscriberSdp = getSubscriberSdp, - scope = scope, - networkStateProvider = networkStateProvider, - loggingLevel = loggingLevel, - onFastReconnect = onFastReconnect, - ) - } - - fun updateToken(newToken: String) { - // the coordinator socket also needs to update the token - coordinatorSocket.token = newToken - // update the auth token as well - authInterceptor.token = newToken - } - - fun updateAuthType(authType: String) { - authInterceptor.authType = authType - } -} - -/** - * Sets upt he signalService and socket for the SFU connection. - */ -internal class SfuConnectionModule( - /** The url of the SFU */ - sfuUrl: String, - /** the session id, generated when we join/ client side */ - sessionId: String, - /** A token which gives you access to the sfu */ - private val sfuToken: String, - /** A token which gives you access to the sfu */ - private val apiKey: String, - /** Function that gives a fresh SDP */ - getSubscriberSdp: suspend () -> String, - private val loggingLevel: LoggingLevel = LoggingLevel(), - /** The scope to use for the socket */ - scope: CoroutineScope = CoroutineScope(DispatcherProvider.IO), - /** Network monitoring */ - networkStateProvider: NetworkStateProvider, - onFastReconnect: suspend () -> Unit, -) { - internal var sfuSocket: SfuSocket - private val updatedSignalUrl = if (sfuUrl.contains(Regex("https?://"))) { - sfuUrl - } else { - "http://$sfuUrl" - }.removeSuffix("/twirp") - - private fun buildSfuOkHttpClient(): OkHttpClient { - val connectionTimeoutInMs = 10000L - // create a new OkHTTP client and set timeouts - val authInterceptor = CoordinatorAuthInterceptor(apiKey, sfuToken) - return OkHttpClient.Builder() - .addInterceptor(authInterceptor) - .addInterceptor( - HttpLoggingInterceptor().apply { - level = loggingLevel.httpLoggingLevel.level - }, - ) - .retryOnConnectionFailure(true) - .connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .writeTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .readTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .build() - } - - val okHttpClient = buildSfuOkHttpClient() - - private val signalRetrofitClient: Retrofit by lazy { - Retrofit.Builder() - .client(okHttpClient) - .addConverterFactory(WireConverterFactory.create()) - .baseUrl(updatedSignalUrl) - .build() - } - - internal val signalService: SignalServerService by lazy { - signalRetrofitClient.create(SignalServerService::class.java) - } - - init { - val socketUrl = "$updatedSignalUrl/ws" - .replace("https", "wss") - .replace("http", "ws") - - sfuSocket = SfuSocket( - socketUrl, - sessionId, - sfuToken, - getSubscriberSdp, - scope, - okHttpClient, - networkStateProvider, - onFastReconnected = onFastReconnect, - ) - } -} - -/** - * CoordinatorAuthInterceptor adds the token authentication to the API calls - */ -internal class CoordinatorAuthInterceptor( - var apiKey: String, - var token: String, - var authType: String = "jwt", -) : Interceptor { - - @Throws(IOException::class) - override fun intercept(chain: Interceptor.Chain): Response { - val original = chain.request() - - val updatedUrl = if (original.url.toString().contains("video")) { - original.url.newBuilder() - .addQueryParameter(API_KEY, apiKey) - .build() - } else { - original.url - } - - val updated = original.newBuilder() - .url(updatedUrl) - .addHeader(HEADER_AUTHORIZATION, token) - .header(STREAM_AUTH_TYPE, authType) - .build() - - return chain.proceed(updated) - } - - private companion object { - /** - * Query key used to authenticate to the API. - */ - private const val API_KEY = "api_key" - private const val STREAM_AUTH_TYPE = "stream-auth-type" - private const val HEADER_AUTHORIZATION = "Authorization" - } -} - -internal class HeadersInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - .newBuilder() - .addHeader("X-Stream-Client", StreamVideo.buildSdkTrackingHeaders()) - .build() - return chain.proceed(request) - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt new file mode 100644 index 0000000000..8d251cc7b8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/ConnectionModuleDeclaration.kt @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.internal.module + +import androidx.lifecycle.Lifecycle +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.logging.LoggingLevel +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.model.ApiKey +import kotlinx.coroutines.CoroutineScope +import okhttp3.OkHttpClient + +/** + * Api definition for any connection module. + */ +internal interface ConnectionModuleDeclaration { + /** + * The application key. + */ + val apiKey: ApiKey + + /** + * The URL of the API. + */ + val apiUrl: String + + /** + * The URL of the web-socket + */ + val wssUrl: String + + /** + * The API that is accessed. + */ + val api: Api + + /** + * The HTTP client. + */ + val http: Http + + /** + * The coroutine scope. + */ + val scope: CoroutineScope get() = UserScope(ClientScope()) + + /** + * Connection timeout. + */ + val connectionTimeoutInMs: Long + + /** + * Logging levels. + */ + val loggingLevel: LoggingLevel get() = LoggingLevel() + + /** + * The user token. + */ + val userToken: Token + + /** + * The lifecycle of the application. + */ + val lifecycle: Lifecycle + + /** + * The network state provider. + */ + val networkStateProvider: NetworkStateProvider + + /** + * The actual socket connection. + */ + val socketConnection: SocketConnection + + /** + * API to update the token. + */ + fun updateToken(token: Token) + + /** + * API to update the authentication type. + */ + fun updateAuthType(authType: String) +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt new file mode 100644 index 0000000000..414638653e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorAuthInterceptor.kt @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.internal.module + +import okhttp3.Interceptor +import okhttp3.Response +import java.io.IOException + +/** + * CoordinatorAuthInterceptor adds the token authentication to the API calls + */ +internal class CoordinatorAuthInterceptor( + var apiKey: String, + var token: String, + var authType: String = "jwt", +) : Interceptor { + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val original = chain.request() + + val updatedUrl = if (original.url.toString().contains("video")) { + original.url.newBuilder() + .addQueryParameter(API_KEY, apiKey) + .build() + } else { + original.url + } + + val updated = original.newBuilder() + .url(updatedUrl) + .addHeader(HEADER_AUTHORIZATION, token) + .header(STREAM_AUTH_TYPE, authType) + .build() + + return chain.proceed(updated) + } + + private companion object { + /** + * Query key used to authenticate to the API. + */ + private const val API_KEY = "api_key" + private const val STREAM_AUTH_TYPE = "stream-auth-type" + private const val HEADER_AUTHORIZATION = "Authorization" + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt new file mode 100644 index 0000000000..fdf5ba1fad --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/CoordinatorConnectionModule.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.internal.module + +import android.content.Context +import android.net.ConnectivityManager +import androidx.lifecycle.Lifecycle +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.logging.LoggingLevel +import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketConnection +import io.getstream.video.android.model.ApiKey +import io.getstream.video.android.model.User +import io.getstream.video.android.model.UserToken +import kotlinx.coroutines.CoroutineScope +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import org.openapitools.client.apis.ProductvideoApi +import org.openapitools.client.infrastructure.Serializer +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import java.util.concurrent.TimeUnit + +/** + * ConnectionModule for the coordinator socket. + */ +internal class CoordinatorConnectionModule( + // Coordinator API + context: Context, + tokenProvider: TokenProvider, + user: User, + override val scope: CoroutineScope, + // Common API + override val apiUrl: String, + override val wssUrl: String, + override val connectionTimeoutInMs: Long, + override val loggingLevel: LoggingLevel = LoggingLevel(), + override val apiKey: ApiKey, + override val userToken: UserToken, + override val lifecycle: Lifecycle, +) : ConnectionModuleDeclaration { + // Internals + private val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + private val retrofit: Retrofit by lazy { + Retrofit.Builder().baseUrl(apiUrl) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(MoshiConverterFactory.create(Serializer.moshi)) + .client(http).build() + } + + // API + override val http: OkHttpClient = OkHttpClient.Builder().addInterceptor(HeadersInterceptor()) + .addInterceptor(authInterceptor).addInterceptor( + HttpLoggingInterceptor().apply { + level = loggingLevel.httpLoggingLevel.level + }, + ).retryOnConnectionFailure(true) + .connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .writeTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .readTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS).build() + override val networkStateProvider: NetworkStateProvider by lazy { + NetworkStateProvider( + scope, + connectivityManager = context.getSystemService( + Context.CONNECTIVITY_SERVICE, + ) as ConnectivityManager, + ) + } + override val api: ProductvideoApi by lazy { retrofit.create(ProductvideoApi::class.java) } + override val socketConnection: CoordinatorSocketConnection = CoordinatorSocketConnection( + apiKey = apiKey, + url = wssUrl, + user = user, + token = userToken, + httpClient = http, + networkStateProvider = networkStateProvider, + scope = scope, + lifecycle = lifecycle, + tokenProvider = tokenProvider, + ) + + override fun updateToken(token: UserToken) { + socketConnection.updateToken(token) + authInterceptor.token = token + } + + override fun updateAuthType(authType: String) { + authInterceptor.authType = authType + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/HeadersInterceptor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/HeadersInterceptor.kt new file mode 100644 index 0000000000..63c248e36d --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/HeadersInterceptor.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.internal.module + +import io.getstream.video.android.core.StreamVideo +import okhttp3.Interceptor +import okhttp3.Response + +internal class HeadersInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + .newBuilder() + .addHeader("X-Stream-Client", StreamVideo.buildSdkTrackingHeaders()) + .build() + return chain.proceed(request) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt new file mode 100644 index 0000000000..660cc455bd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/module/SfuConnectionModule.kt @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.internal.module + +import android.content.Context +import android.net.ConnectivityManager +import androidx.lifecycle.Lifecycle +import io.getstream.video.android.core.api.SignalServerService +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.socket.common.token.ConstantTokenProvider +import io.getstream.video.android.core.socket.sfu.SfuSocketConnection +import io.getstream.video.android.model.ApiKey +import io.getstream.video.android.model.SfuToken +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.wire.WireConverterFactory +import java.util.concurrent.TimeUnit + +internal class SfuConnectionModule( + context: Context, + override val apiKey: ApiKey, + override val apiUrl: String, + override val wssUrl: String, + override val connectionTimeoutInMs: Long, + override val userToken: SfuToken, + override val lifecycle: Lifecycle, +) : ConnectionModuleDeclaration { + + // Internal logic + override val http: OkHttpClient = buildSfuOkHttpClient() + + private val signalRetrofitClient: Retrofit by lazy { + Retrofit.Builder().client(http).addConverterFactory(WireConverterFactory.create()) + .baseUrl("$apiUrl/").build() + } + private fun buildSfuOkHttpClient(): OkHttpClient { + val connectionTimeoutInMs = 10000L + // create a new OkHTTP client and set timeouts + val authInterceptor = CoordinatorAuthInterceptor(apiKey, userToken) + return OkHttpClient.Builder().addInterceptor(authInterceptor).addInterceptor( + HttpLoggingInterceptor().apply { + level = loggingLevel.httpLoggingLevel.level + }, + ).retryOnConnectionFailure(true) + .connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .writeTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .readTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS).build() + } + + // API + override val api: SignalServerService = signalRetrofitClient.create( + SignalServerService::class.java, + ) + override val networkStateProvider: NetworkStateProvider by lazy { + NetworkStateProvider( + scope, + connectivityManager = context.getSystemService( + Context.CONNECTIVITY_SERVICE, + ) as ConnectivityManager, + ) + } + private var _internalSocketConnection: SfuSocketConnection = SfuSocketConnection( + url = wssUrl, + apiKey = apiKey, + scope = scope, + httpClient = http, + tokenProvider = ConstantTokenProvider(userToken), + lifecycle = lifecycle, + networkStateProvider = networkStateProvider, + ) + override val socketConnection: SfuSocketConnection = _internalSocketConnection + + override fun updateToken(token: SfuToken) { + _internalSocketConnection.updateToken(token) + } + + override fun updateAuthType(authType: String) { + throw UnsupportedOperationException("Not supported for SFU, do not call.") + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/network/NetworkStateProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/network/NetworkStateProvider.kt index a216f7b3c2..c535ba91e8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/network/NetworkStateProvider.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/internal/network/NetworkStateProvider.kt @@ -20,7 +20,11 @@ import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest +import android.os.Build import io.getstream.log.StreamLog +import io.getstream.log.taggedLogger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import java.util.concurrent.atomic.AtomicBoolean /** @@ -29,22 +33,19 @@ import java.util.concurrent.atomic.AtomicBoolean * @property connectivityManager Android manager which provides information about the current * connection state. */ -public class NetworkStateProvider(private val connectivityManager: ConnectivityManager) { +public class NetworkStateProvider( + private val scope: CoroutineScope, + private val connectivityManager: ConnectivityManager, +) { + private val logger by taggedLogger("Video:NetworkStateProvider") private val lock: Any = Any() - - /** - * Handler which is triggered whenever the network state changes. - */ private val callback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { notifyListenersIfNetworkStateChanged() } - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities, - ) { + override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { notifyListenersIfNetworkStateChanged() } @@ -64,28 +65,41 @@ public class NetworkStateProvider(private val connectivityManager: ConnectivityM private fun notifyListenersIfNetworkStateChanged() { val isNowConnected = isConnected() if (!isConnected && isNowConnected) { + logger.i { "Network connected." } isConnected = true - listeners.forEach { it.onConnected() } + listeners.onConnected() } else if (isConnected && !isNowConnected) { + logger.i { "Network disconnected." } isConnected = false - listeners.forEach { it.onDisconnected() } + listeners.onDisconnected() + } + } + + private fun Set.onConnected() { + scope.launch { + forEach { it.onConnected() } + } + } + + private fun Set.onDisconnected() { + scope.launch { + forEach { it.onDisconnected() } } } - /** - * Checks if the current device is connected to the Internet, based on the API level. - * - * @return If the device is connected or not. - */ public fun isConnected(): Boolean { - return runCatching { - connectivityManager.run { - getNetworkCapabilities(activeNetwork)?.run { - hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && - hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + runCatching { + connectivityManager.run { + getNetworkCapabilities(activeNetwork)?.run { + hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && + hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) + } } - } - }.getOrNull() ?: false + }.getOrNull() ?: false + } else { + connectivityManager.activeNetworkInfo?.isConnected ?: false + } } /** @@ -146,9 +160,9 @@ public class NetworkStateProvider(private val connectivityManager: ConnectivityM * Listener which is used to listen and react to network state changes. */ public interface NetworkStateListener { - public fun onConnected() + public suspend fun onConnected() - public fun onDisconnected() + public suspend fun onDisconnected() } } diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/StreamLifecycleObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/StreamLifecycleObserver.kt new file mode 100644 index 0000000000..43107e9b51 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/StreamLifecycleObserver.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.lifecycle + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.dispatchers.DispatcherProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.concurrent.atomic.AtomicBoolean + +internal class StreamLifecycleObserver( + private val scope: CoroutineScope, + private val lifecycle: Lifecycle, +) : DefaultLifecycleObserver { + + private val logger by taggedLogger("Video:LifecycleObserver") + private var recurringResumeEvent = false + private var handlers = setOf() + + private val isObserving = AtomicBoolean(false) + + suspend fun observe(handler: LifecycleHandler) { + if (isObserving.compareAndSet(false, true)) { + recurringResumeEvent = false + withContext(DispatcherProvider.Main) { + lifecycle.addObserver(this@StreamLifecycleObserver) + logger.v { "[observe] subscribed" } + } + } + handlers = handlers + handler + } + + suspend fun dispose(handler: LifecycleHandler) { + handlers = handlers - handler + if (handlers.isEmpty() && isObserving.compareAndSet(true, false)) { + withContext(DispatcherProvider.Main) { + lifecycle.removeObserver(this@StreamLifecycleObserver) + logger.v { "[dispose] unsubscribed" } + } + } + } + + override fun onResume(owner: LifecycleOwner) { + logger.d { "[onResume] owner: $owner, recurringResumeEvent: $recurringResumeEvent" } + // ignore event when we just started observing the lifecycle + if (recurringResumeEvent) { + scope.launch { + handlers.forEach { it.resume() } + } + } + recurringResumeEvent = true + } + + override fun onStop(owner: LifecycleOwner) { + scope.launch { + logger.d { "[onStop] owner: $owner" } + handlers.forEach { it.stopped() } + } + } +} + +internal interface LifecycleHandler { + suspend fun resume() + suspend fun stopped() +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/internal/StreamLifecycleObserver.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/internal/StreamLifecycleObserver.kt deleted file mode 100644 index 499c2803fe..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/internal/StreamLifecycleObserver.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.lifecycle.internal - -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.lifecycle.LifecycleHandler -import kotlinx.coroutines.withContext - -internal class StreamLifecycleObserver( - private val lifecycle: Lifecycle, - private val handler: LifecycleHandler, -) : DefaultLifecycleObserver { - private var recurringStartedEvent = false - - @Volatile - private var isObserving = false - - suspend fun observe() { - if (isObserving.not()) { - isObserving = true - withContext(DispatcherProvider.Main) { - lifecycle.addObserver(this@StreamLifecycleObserver) - } - } - } - - suspend fun dispose() { - if (isObserving) { - withContext(DispatcherProvider.Main) { - lifecycle.removeObserver(this@StreamLifecycleObserver) - } - } - isObserving = false - recurringStartedEvent = false - } - - override fun onStart(owner: LifecycleOwner) { - // ignore event when we just started observing the lifecycle - if (recurringStartedEvent) { - handler.started() - } - recurringStartedEvent = true - } - - override fun onStop(owner: LifecycleOwner) { - handler.stopped() - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt index 44f4fd6230..7b52ad73a8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/notifications/internal/service/CallService.kt @@ -41,12 +41,12 @@ import io.getstream.log.taggedLogger import io.getstream.video.android.core.R import io.getstream.video.android.core.RingingState import io.getstream.video.android.core.StreamVideo -import io.getstream.video.android.core.StreamVideoImpl +import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INCOMING_CALL_NOTIFICATION_ID import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_CID import io.getstream.video.android.core.notifications.NotificationHandler.Companion.INTENT_EXTRA_CALL_DISPLAY_NAME import io.getstream.video.android.core.notifications.internal.receivers.ToggleCameraBroadcastReceiver -import io.getstream.video.android.core.utils.safeCall +import io.getstream.video.android.core.utils.safeCallWithDefault import io.getstream.video.android.core.utils.startForegroundWithServiceType import io.getstream.video.android.model.StreamCallId import io.getstream.video.android.model.streamCallDisplayName @@ -152,7 +152,7 @@ internal open class CallService : Service() { fun buildStopIntent( context: Context, callServiceConfiguration: CallServiceConfig = callServiceConfig(), - ) = safeCall(Intent(context, CallService::class.java)) { + ) = safeCallWithDefault(Intent(context, CallService::class.java)) { val intent = callServiceConfiguration.callServicePerType.firstNotNullOfOrNull { val serviceClass = it.value if (isServiceRunning(context, serviceClass)) { @@ -202,7 +202,7 @@ internal open class CallService : Service() { ) } - private fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean = safeCall( + private fun isServiceRunning(context: Context, serviceClass: Class<*>): Boolean = safeCallWithDefault( true, ) { val activityManager = context.getSystemService( @@ -222,7 +222,7 @@ internal open class CallService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val trigger = intent?.getStringExtra(TRIGGER_KEY) - val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoImpl + val streamVideo = StreamVideo.instanceOrNull() as? StreamVideoClient val intentCallId = intent?.streamCallId(INTENT_EXTRA_CALL_CID) val intentCallDisplayName = intent?.streamCallDisplayName(INTENT_EXTRA_CALL_DISPLAY_NAME) @@ -347,7 +347,7 @@ internal open class CallService : Service() { } } - private fun maybePromoteToForegroundService(videoClient: StreamVideoImpl, notificationId: Int, trigger: String) { + private fun maybePromoteToForegroundService(videoClient: StreamVideoClient, notificationId: Int, trigger: String) { val hasActiveCall = videoClient.state.activeCall.value != null val not = if (hasActiveCall) " not" else "" @@ -430,13 +430,13 @@ internal open class CallService : Service() { } } - private fun observeCall(callId: StreamCallId, streamVideo: StreamVideoImpl) { + private fun observeCall(callId: StreamCallId, streamVideo: StreamVideoClient) { observeRingingState(callId, streamVideo) observeCallEvents(callId, streamVideo) observeNotificationUpdates(callId, streamVideo) } - private fun observeRingingState(callId: StreamCallId, streamVideo: StreamVideoImpl) { + private fun observeRingingState(callId: StreamCallId, streamVideo: StreamVideoClient) { serviceScope.launch { val call = streamVideo.call(callId.type, callId.id) call.state.ringingState.collect { @@ -599,7 +599,7 @@ internal open class CallService : Service() { } } - private fun observeCallEvents(callId: StreamCallId, streamVideo: StreamVideoImpl) { + private fun observeCallEvents(callId: StreamCallId, streamVideo: StreamVideoClient) { serviceScope.launch { val call = streamVideo.call(callId.type, callId.id) call.subscribe { event -> @@ -650,7 +650,7 @@ internal open class CallService : Service() { } } - private fun observeNotificationUpdates(callId: StreamCallId, streamVideo: StreamVideoImpl) { + private fun observeNotificationUpdates(callId: StreamCallId, streamVideo: StreamVideoClient) { streamVideo.getNotificationUpdates( serviceScope, streamVideo.call(callId.type, callId.id), diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/CoordinatorSocket.kt deleted file mode 100644 index 30e15b86c6..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/CoordinatorSocket.kt +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.socket - -import com.squareup.moshi.JsonAdapter -import com.squareup.moshi.JsonDataException -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.utils.isWhitespaceOnly -import io.getstream.video.android.core.utils.safeCall -import io.getstream.video.android.model.User -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch -import okhttp3.OkHttpClient -import okhttp3.WebSocket -import org.openapitools.client.infrastructure.Serializer -import org.openapitools.client.models.ConnectUserDetailsRequest -import org.openapitools.client.models.ConnectedEvent -import org.openapitools.client.models.ConnectionErrorEvent -import org.openapitools.client.models.UnsupportedVideoEventException -import org.openapitools.client.models.VideoEvent -import org.openapitools.client.models.WSAuthMessageRequest - -/** - * The Coordinator sockets send a user authentication request - * @see WSAuthMessageRequest - * - */ -public class CoordinatorSocket( - private val url: String, - private val user: User, - internal var token: String, - private val scope: CoroutineScope = CoroutineScope(DispatcherProvider.IO), - private val httpClient: OkHttpClient, - private val networkStateProvider: NetworkStateProvider, -) : PersistentSocket( - url = url, - httpClient = httpClient, - scope = scope, - networkStateProvider = networkStateProvider, - onFastReconnected = { }, -) { - override val logger by taggedLogger("PersistentCoordinatorSocket") - - override fun authenticate() { - logger.d { "[authenticateUser] user: $user" } - - if (token.isEmpty()) { - throw IllegalStateException("User token is empty") - } - - val adapter: JsonAdapter = - Serializer.moshi.adapter(WSAuthMessageRequest::class.java) - - val authRequest = WSAuthMessageRequest( - token = token, - userDetails = ConnectUserDetailsRequest( - id = user.id, - name = user.name.takeUnless { it.isWhitespaceOnly() }, - image = user.image.takeUnless { it.isWhitespaceOnly() }, - custom = user.custom, - ), - ) - val message = adapter.toJson(authRequest) - - super.socket?.send(message) - } - - /** Invoked when a text (type `0x1`) message has been received. */ - override fun onMessage(webSocket: WebSocket, text: String) { - logger.d { "[onMessage] text: $text " } - - if (text.isEmpty() || text == "null") { - logger.w { "[onMessage] Received empty socket message" } - return - } - - scope.launch(singleThreadDispatcher) { - try { - Serializer.moshi.adapter(VideoEvent::class.java).let { eventAdapter -> - eventAdapter.fromJson(text)?.let { parsedEvent -> processEvent(parsedEvent) } - } - } catch (e: Throwable) { - if (e.cause is UnsupportedVideoEventException) { - val ex = e.cause as UnsupportedVideoEventException - logger.w { "[onMessage] Received unsupported VideoEvent type: ${ex.type}. Ignoring." } - } else { - val eventType = extractEventType(text) - val errorMessage = "Error when parsing VideoEvent with type: $eventType. Cause: ${e.message}." - - logger.e { "[onMessage] $errorMessage" } - handleError(JsonDataException(errorMessage)) - } - } - } - } - - private suspend fun processEvent(parsedEvent: VideoEvent) { - if (parsedEvent is ConnectionErrorEvent) { - handleError( - ErrorResponse( - code = parsedEvent.error?.code ?: -1, - message = parsedEvent.error?.message ?: "", - statusCode = parsedEvent.error?.statusCode ?: -1, - exceptionFields = parsedEvent.error?.exceptionFields ?: emptyMap(), - moreInfo = parsedEvent.error?.moreInfo ?: "", - ), - ) - return - } - - if (parsedEvent is ConnectedEvent) { - _connectionId.value = parsedEvent.connectionId - setConnectedStateAndContinue(parsedEvent) - } - - ackHealthMonitor() - events.emit(parsedEvent) - } - - private fun extractEventType(json: String): String = safeCall("Unknown") { - val regex = """"type":"(.*?)"""".toRegex() - val matchResult = regex.find(json) - return matchResult?.groups?.get(1)?.value ?: "Unknown" - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt deleted file mode 100644 index 21a3cfeab4..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/PersistentSocket.kt +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.socket - -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.errors.VideoErrorCode -import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.socket.internal.HealthMonitor -import io.getstream.video.android.core.utils.safeCall -import io.getstream.video.android.core.utils.safeSuspendingCall -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.InternalCoroutinesApi -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import okhttp3.WebSocket -import okhttp3.WebSocketListener -import org.openapitools.client.models.VideoEvent -import stream.video.sfu.event.HealthCheckRequest -import java.io.IOException -import java.io.InterruptedIOException -import java.net.ConnectException -import java.net.SocketTimeoutException -import java.net.UnknownHostException -import java.util.concurrent.Executors - -/** - * PersistentSocket architecture - * - * - Healthmonitor that sends a ping every 30 seconds - * - Automatically reconnects if it encounters a temp failure - * - Raises the error if there is a permanent failure - * - Flow to avoid concurrency related bugs - * - Ability to wait till the socket is connected (important to prevent race conditions) - */ -public open class PersistentSocket( - /** The URL to connect to */ - private val url: String, - /** Inject your http client */ - private val httpClient: OkHttpClient, - /** Inject your network state provider */ - private val networkStateProvider: NetworkStateProvider, - /** Set the scope everything should run in */ - private val scope: CoroutineScope = CoroutineScope(DispatcherProvider.IO), - private val onFastReconnected: suspend () -> Unit, -) : WebSocketListener() { - internal open val logger by taggedLogger("PersistentSocket") - - internal val singleThreadDispatcher = - Executors.newSingleThreadExecutor().asCoroutineDispatcher() - - /** Mock the socket for testing */ - internal var mockSocket: WebSocket? = null - - var reconnectTimeout: Long = 500 - - /** flow with all the events, listen to this */ - val events = MutableSharedFlow(replay = 3) - - /** flow with temporary and permanent errors */ - val errors = MutableSharedFlow() - - private val _connectionState = MutableStateFlow(SocketState.NotConnected) - - /** the current connection state of the socket */ - val connectionState: StateFlow = _connectionState - - /** the connection id */ - internal val _connectionId: MutableStateFlow = MutableStateFlow(null) - val connectionId: StateFlow = _connectionId - - /** Continuation if the socket successfully connected and we've authenticated */ - lateinit var connectContinuation: CancellableContinuation - - // Controls when we can resume the continuation - private var connectContinuationCompleted: Boolean = false - - internal var socket: WebSocket? = null - - // True if cleanup was called and socket is completely destroyed (intentionally). - // You need to create a new instance (this is mainly used for the SfuSocket which is tied - // to a Subscriber SDP and needs to be recreated from scratch on WebRTC session clean up). - protected var destroyed: Boolean = false - - internal var reconnectionAttempts = 0 - - /** - * Connect the socket, authenticate, start the health monitor and see if the network is online - * @param invocation Provides a way to extend the [connect] method with additional behavior. - * This can be useful in cases where additional setup or checks need to be performed - * once the socket is connected but before the [connect] method returns. - * To return from [connect] and to resume the enclosing coroutine, use the provided [CancellableContinuation] parameter. - */ - open suspend fun connect( - invocation: (CancellableContinuation) -> Unit = {}, - ): T? = safeSuspendingCall(null) { - if (destroyed) { - logger.d { "[connect] Can't connect socket - it was already destroyed" } - null - } - - suspendCancellableCoroutine { continuation -> - logger.i { "[connect]" } - connectContinuation = continuation - - _connectionState.value = SocketState.Connecting - - // step 1 create the socket - socket = mockSocket ?: createSocket() - - scope.launch { - // step 2 authenticate the user/call etc - authenticate() - - // step 3 monitor for health every 30 seconds - - if (!DispatcherProvider.inTest) { - healthMonitor.start() - } - - // also monitor if we are offline/online - networkStateProvider.subscribe(networkStateListener) - - // run the invocation - invocation.invoke(continuation) - } - } - } - - /** - * Used for testing only - to bring down the socket connection immediately. - */ - internal fun cancel() { - socket?.cancel() - } - - fun cleanup() { - destroyed = true - disconnect(DisconnectReason.ByRequest) - } - - sealed class DisconnectReason { - data object ByRequest : DisconnectReason() - data class PermanentError(val error: Throwable) : DisconnectReason() - } - - /** - * Disconnect the socket - */ - fun disconnect(disconnectReason: DisconnectReason) = safeCall { - logger.i { "[disconnect]" } - _connectionState.value = SocketState.NotConnected - - _connectionState.value = when (disconnectReason) { - DisconnectReason.ByRequest -> { - SocketState.DisconnectedByRequest - } - is DisconnectReason.PermanentError -> { - SocketState.DisconnectedPermanently( - disconnectReason.error, - ) - } - } - - connectContinuationCompleted = false - - disconnectSocket() - healthMonitor.stop() - networkStateProvider.unsubscribe(networkStateListener) - } - - private fun disconnectSocket() { - socket?.close(CODE_CLOSE_SOCKET_FROM_CLIENT, "Connection close by client") - socket = null - _connectionId.value = null - } - - /** - * Increment the reconnection attempts, disconnect and reconnect - */ - suspend fun reconnect(timeout: Long = reconnectTimeout) { - logger.i { "[reconnect] reconnectionAttempts: $reconnectionAttempts" } - if (destroyed) { - logger.d { "[reconnect] Can't reconnect socket - it was already destroyed" } - return - } - - if (connectionState.value == SocketState.Connecting) { - logger.i { "[reconnect] already connecting" } - return - } - - // Don't disconnect if we are already disconnected - disconnectSocket() - - // Stop sending pings - healthMonitor.stop() - - if (!networkStateProvider.isConnected()) { - logger.d { "[reconnect] skipping reconnect - disconnected from internet" } - return - } - - // reconnect after the timeout - delay(timeout) - - reconnectionAttempts++ - - tryConnect() - } - - private suspend fun tryConnect() { - try { - connectContinuationCompleted = false - connect() - } catch (e: Throwable) { - logger.e { "[reconnect] failed to reconnect: $e" } - } - } - - open fun authenticate() {} - - suspend fun onInternetConnected() { - val state = connectionState.value - logger.i { "[onNetworkConnected] state: $state" } - if (state is SocketState.DisconnectedTemporarily || state == SocketState.NetworkDisconnected) { - // reconnect instantly when the internet is back - reconnect(0) - } - } - - suspend fun onInternetDisconnected() { - logger.i { "[onNetworkDisconnected] state: $connectionState.value" } - if (connectionState.value is SocketState.Connected || connectionState.value is SocketState.Connecting) { - _connectionState.value = SocketState.NetworkDisconnected - } - } - - private val networkStateListener = object : NetworkStateProvider.NetworkStateListener { - override fun onConnected() { - scope.launch { onInternetConnected() } - } - - override fun onDisconnected() { - scope.launch { onInternetDisconnected() } - } - } - - private fun createSocket(): WebSocket { - logger.d { "[createSocket] url: $url" } - - val request = Request.Builder() - .url(url) - .addHeader("Connection", "Upgrade") - .addHeader("Upgrade", "websocket") - .build() - - return httpClient.newWebSocket(request, this) - } - - override fun onOpen(webSocket: WebSocket, response: Response) { - logger.d { "[onOpen] response: $response" } - } - - protected fun ackHealthMonitor() { - healthMonitor.ack() - } - - @OptIn(InternalCoroutinesApi::class) - protected fun setConnectedStateAndContinue(message: VideoEvent) { - _connectionState.value = SocketState.Connected(message) - - if (!connectContinuationCompleted) { - connectContinuationCompleted = true - connectContinuation.tryResume(message as T)?.let { - connectContinuation.completeResume(it) - } - } - } - - internal fun handleError(receivedError: Throwable) { - // onFailure, onClosed and the 2 onMessage can all generate errors - // temporary errors should be logged and retried - // permanent errors should be emitted so the app can decide how to handle it - if (destroyed) { - logger.d { "[handleError] Ignoring socket error - already closed $receivedError" } - return - } - val error = receivedError - val permanentError = isPermanentError(error) - val isTokenAuthError = error is ErrorResponse && error.code == VideoErrorCode.TOKEN_EXPIRED.code - if (permanentError) { - logger.e { "[handleError] Permanent error: $error" } - _connectionState.value = (error as? ErrorResponse)?.let { - logger.e { "[handleError] ErrorResponse: $it" } - if (it.code == VideoErrorCode.TOKEN_EXPIRED.code) { - // token expired, we should reconnect - logger.e { "[handleError] Token expired, reconnecting" } - SocketState.DisconnectedTemporarily(it) - } else { - null - } - } ?: SocketState.DisconnectedPermanently(error) - - // If the connect continuation is not completed, it means the error happened during the connection phase. - connectContinuationCompleted.not().let { isConnectionPhaseError -> - if (isConnectionPhaseError && !isTokenAuthError) { - logger.e { "[handleError] Connection phase error: $error" } - emitError(error, isConnectionPhaseError = true) - resumeConnectionPhaseWithException(error) - } else { - logger.e { "[handleError] Emitting error: $error" } - emitError(error, isConnectionPhaseError = false) - } - } - } else { - logger.w { "[handleError] Temporary error: $error" } - - _connectionState.value = SocketState.DisconnectedTemporarily(error) - scope.launch { - reconnect(reconnectTimeout) - } - } - } - - internal open fun isPermanentError(error: Throwable): Boolean { - // errors returned by the server can be permanent. IE an invalid API call - // or an expired token (required a refresh) - // or temporary - var isPermanent = true - if (error is ErrorResponse) { - val serverError = error as ErrorResponse - } else { - // there are several timeout & network errors that are all temporary - // code errors are permanent - isPermanent = when (error) { - is UnknownHostException -> false - is SocketTimeoutException -> false - is InterruptedIOException -> false - is IOException -> false - else -> true - } - } - - return isPermanent - } - - private fun emitError(error: Throwable, isConnectionPhaseError: Boolean) { - scope.launch { - if (isConnectionPhaseError) { - logger.e { "[emitError] Emitting connection phase error: $error" } - errors.emit( - ConnectException( - "Failed to establish WebSocket connection. Will try to reconnect. Cause: ${error.message}", - ), - ) - } else { - logger.e { "[emitError] Emitting error: $error" } - errors.emit(error) - } - } - } - - @OptIn(InternalCoroutinesApi::class) - private fun resumeConnectionPhaseWithException(error: Throwable) { - connectContinuationCompleted = true - connectContinuation.tryResumeWithException(error)?.let { - connectContinuation.completeResume(it) - } - } - - /** - * Invoked when the remote peer has indicated that no more incoming messages will be transmitted. - */ - override fun onClosing(webSocket: WebSocket, code: Int, reason: String) { - logger.d { "[onClosing] code: $code, reason: $reason" } - } - - /** - * Invoked when both peers have indicated that no more messages will be transmitted and the - * connection has been successfully released. No further calls to this listener will be made. - */ - override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { - logger.d { "[onClosed] code: $code, reason: $reason" } - - if (destroyed) { - logger.d { "[onClosed] Ignoring onClosed - socket already closed" } - return - } - - if (code != CODE_CLOSE_SOCKET_FROM_CLIENT) { - handleError(IllegalStateException("socket closed by server, this shouldn't happen")) - } - } - - /** - * Invoked when a web socket has been closed due to an error reading from or writing to the - * network. Both outgoing and incoming messages may have been lost. No further calls to this - * listener will be made. - */ - override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { - logger.d { "[onFailure] t: $t, response: $response" } - handleError(t) - } - - internal fun sendHealthCheck() { - logger.d { "[sendHealthCheck] Sending..." } - val healthCheckRequest = HealthCheckRequest() - socket?.send(healthCheckRequest.encodeByteString()) - } - - /** - * Check if the socket is in a state that can connect. - */ - internal fun canConnect(): Boolean = safeCall(false) { - val connectionState = connectionState.value - logger.d { "[canConnect] Current state: $connectionState" } - val result = when (connectionState) { - // Can't connect if we are already connected. - is SocketState.Connected -> false - is SocketState.Connecting -> false - // We can connect if we are disconnected. - is SocketState.DisconnectedPermanently -> true - is SocketState.DisconnectedTemporarily -> true - is SocketState.NetworkDisconnected -> true - is SocketState.DisconnectedByRequest -> true - is SocketState.NotConnected -> true - } - logger.d { "[canConnect] Decision: $result" } - result - } - - /** - * Check if the socket is in a state that can reconnect. - */ - internal fun canReconnect(): Boolean = safeCall(false) { - val connectionState = connectionState.value - logger.d { "[canReconnect] Current state: $connectionState" } - val result = when (connectionState) { - // Can't reconnect if we are already connected. - is SocketState.Connected -> false - is SocketState.Connecting -> false - is SocketState.DisconnectedPermanently -> false - is SocketState.DisconnectedTemporarily -> true - is SocketState.NetworkDisconnected -> false - is SocketState.DisconnectedByRequest -> false - is SocketState.NotConnected -> false - } - logger.d { "[canReconnect] Decision: $result" } - result - } - - /** - * Check if the socket is in a state that can disconnect. - */ - internal fun canDisconnect(): Boolean = safeCall(true) { - val connectionState = connectionState.value - logger.d { "[canDisconnect] Current state: $connectionState" } - val result = when (connectionState) { - is SocketState.Connected -> true - is SocketState.Connecting -> true - is SocketState.DisconnectedPermanently -> false - is SocketState.DisconnectedByRequest -> false - is SocketState.DisconnectedTemporarily -> true - is SocketState.NetworkDisconnected -> false - else -> true - } - logger.d { "[canDisconnect] Decision: $result" } - result - } - - private val healthMonitor = HealthMonitor( - object : HealthMonitor.HealthCallback { - - override suspend fun reconnect() { - logger.i { "[HealthMonitor#reconnect] Health monitor triggered a reconnect" } - val state = connectionState.value - logger.i { "[reconnect] Socket state: $state" } - if (canReconnect()) { - this@PersistentSocket.reconnect() - } else { - logger.w { "[HealthMonitor#reconnect] Health monitor triggered a reconnect but state is $state" } - } - } - - override fun check() { - logger.d { "[HealthMonitor#check]. Started a health check." } - val state = connectionState.value - logger.d { "[HealthMonitor#check]. Socket state: $state" } - - if (state is SocketState.Connected) { - sendHealthCheck() - } else { - logger.d { "[HealthMonitor#check]. Skipping health check as the socket is not connected." } - } - } - }, - scope, - ) - - internal companion object { - internal const val CODE_CLOSE_SOCKET_FROM_CLIENT = 1000 - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SfuSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SfuSocket.kt deleted file mode 100644 index 21b448c81d..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SfuSocket.kt +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.socket - -import android.os.Build -import io.getstream.log.taggedLogger -import io.getstream.video.android.core.BuildConfig -import io.getstream.video.android.core.call.signal.socket.RTCEventMapper -import io.getstream.video.android.core.call.utils.stringify -import io.getstream.video.android.core.dispatchers.DispatcherProvider -import io.getstream.video.android.core.events.ErrorEvent -import io.getstream.video.android.core.events.ICETrickleEvent -import io.getstream.video.android.core.events.JoinCallResponseEvent -import io.getstream.video.android.core.events.SfuSocketError -import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.model.IceCandidate -import kotlinx.coroutines.CancellableContinuation -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.ensureActive -import kotlinx.coroutines.flow.receiveAsFlow -import kotlinx.coroutines.launch -import kotlinx.serialization.json.Json -import okhttp3.OkHttpClient -import okhttp3.WebSocket -import okio.ByteString -import stream.video.sfu.event.JoinRequest -import stream.video.sfu.event.Migration -import stream.video.sfu.event.SfuEvent -import stream.video.sfu.event.SfuRequest -import stream.video.sfu.models.ClientDetails -import stream.video.sfu.models.Device -import stream.video.sfu.models.ErrorCode -import stream.video.sfu.models.OS -import stream.video.sfu.models.PeerType -import stream.video.sfu.models.Sdk -import stream.video.sfu.models.SdkType - -/** - * The SFU socket is slightly different from the coordinator socket - * It sends a JoinRequest to authenticate - * SFU socket uses binary instead of text - */ -public class SfuSocket( - private val url: String, - private val sessionId: String, - private val token: String, - private val getSubscriberSdp: suspend () -> String, - private val scope: CoroutineScope = CoroutineScope(DispatcherProvider.IO), - private val httpClient: OkHttpClient, - private val networkStateProvider: NetworkStateProvider, - private val onFastReconnected: suspend () -> Unit, -) : PersistentSocket ( - url = url, - httpClient = httpClient, - scope = scope, - networkStateProvider = networkStateProvider, - onFastReconnected = onFastReconnected, -) { - - override val logger by taggedLogger("PersistentSFUSocket") - - // How many milliseconds can pass since the last disconnect when a fast-reconnect - // will still be attempted. After this time we do a full-reconnect directly. - private val maxReconnectWindowTime = if (BuildConfig.DEBUG) { 10000L } else { 3000L } - private var lastDisconnectTime: Long? = null - - // Only set during SFU migration - private var migrationData: (suspend () -> Migration)? = null - - private val _pendingPublisherIceCandidates = Channel(capacity = 99) - internal val pendingPublisherIceCandidates = _pendingPublisherIceCandidates.receiveAsFlow() - - private val _pendingSubscriberIceCandidates = Channel(capacity = 99) - internal val pendingSubscriberIceCandidates = _pendingSubscriberIceCandidates.receiveAsFlow() - - private val clientDetails - get() = ClientDetails( - os = OS( - name = "Android", - version = Build.VERSION.SDK_INT.toString(), - ), - device = Device( - name = "${Build.MANUFACTURER} : ${Build.MODEL}", - ), - sdk = Sdk( - type = SdkType.SDK_TYPE_ANDROID, - major = BuildConfig.STREAM_VIDEO_VERSION_MAJOR.toString(), - minor = BuildConfig.STREAM_VIDEO_VERSION_MINOR.toString(), - patch = BuildConfig.STREAM_VIDEO_VERSION_PATCH.toString(), - ), - ) - - init { - scope.launch { - connectionState.collect { - if (it is SocketState.Connected) { - lastDisconnectTime = null - } else if (it is SocketState.DisconnectedTemporarily || - it is SocketState.DisconnectedByRequest || - it is SocketState.DisconnectedPermanently - ) { - lastDisconnectTime = System.currentTimeMillis() - } - } - } - } - - suspend fun connectMigrating(migration: (suspend () -> Migration), invocation: (CancellableContinuation) -> Unit): JoinCallResponseEvent? { - migrationData = migration - return connect(invocation) - } - - override suspend fun connect(invocation: (CancellableContinuation) -> Unit): JoinCallResponseEvent? { - val lastDisconnectTime = lastDisconnectTime - // Check if too much time hasn't passed since last connect - if yes then a fast reconnect - // is not possible and we need to do a full reconnect. - if (lastDisconnectTime != null && (System.currentTimeMillis() - lastDisconnectTime > maxReconnectWindowTime)) { - logger.d { "[connect] SFU socket - need full-reconnect, too much time passed since disconnect" } - handleFastReconnectNotPossible() - return null - } else { - return super.connect(invocation) - } - } - - override fun authenticate() { - logger.d { "[authenticate] sessionId: $sessionId" } - scope.launch { - // check if we haven't disposed the socket - if (socket != null) { - val sdp = getSubscriberSdp() - - val migration = migrationData?.invoke() - // clear migration data - we only try to migrate once - migrationData = null - - val request = if (migration == null) { - JoinRequest( - session_id = sessionId, - token = token, - subscriber_sdp = sdp, - fast_reconnect = reconnectionAttempts > 0, - client_details = clientDetails, - ) - } else { - JoinRequest( - session_id = sessionId, - token = token, - subscriber_sdp = sdp, - fast_reconnect = false, - migration = migration, - client_details = clientDetails, - ) - } - - socket?.send(SfuRequest(join_request = request).encodeByteString()) - } - } - } - - /** Invoked when a binary (type `0x2`) message has been received. */ - override fun onMessage(webSocket: WebSocket, bytes: ByteString) { - if (destroyed) { - logger.d { "Received a message after being destroyed" } - return - } - - val byteBuffer = bytes.asByteBuffer() - val byteArray = ByteArray(byteBuffer.capacity()) - byteBuffer.get(byteArray) - scope.launch(singleThreadDispatcher) { - try { - val rawEvent = SfuEvent.ADAPTER.decode(byteArray) - val message = RTCEventMapper.mapEvent(rawEvent) - if (message is ErrorEvent) { - val errorEvent = message as ErrorEvent - handleError(SfuSocketError(errorEvent.error)) - } - ackHealthMonitor() - events.emit(message) - if (message is JoinCallResponseEvent) { - if (message.isReconnected) { - logger.d { "[onMessage] Fast-reconnect possible - requesting ICE restarts" } - onFastReconnected() - setConnectedStateAndContinue(message) - } else if (reconnectionAttempts > 0) { - logger.d { "[onMessage] Fast-reconnect request but not possible - doing full-reconnect" } - // We are reconnecting and we got reconnected=false, this means that - // a fast reconnect is not possible and we need to do a full reconnect. - handleFastReconnectNotPossible() - } else { - // standard connection (it's not a reconnect response) - logger.d { "[onMessage] SFU socket connected" } - setConnectedStateAndContinue(message) - } - } else if (message is ICETrickleEvent) { - handleIceTrickle(message) - } - } catch (error: Throwable) { - coroutineContext.ensureActive() - logger.e { "[onMessage] failed: $error" } - handleError(error) - } - } - } - - override fun isPermanentError(error: Throwable): Boolean { - if (error is SfuSocketError && - error.error?.code == ErrorCode.ERROR_CODE_PARTICIPANT_MEDIA_TRANSPORT_FAILURE - ) { - logger.w { "Received negotiation failure - disconnecting: $error" } - return false - } - return super.isPermanentError(error) - } - - private suspend fun handleIceTrickle(event: ICETrickleEvent) { - logger.d { - "[handleIceTrickle] #sfu; #${event.peerType.stringify()}; candidate: ${event.candidate}" - } - val iceCandidate: IceCandidate = Json.decodeFromString(event.candidate) - val result = if (event.peerType == PeerType.PEER_TYPE_PUBLISHER_UNSPECIFIED) { - _pendingPublisherIceCandidates.send(iceCandidate) - } else { - _pendingSubscriberIceCandidates.send(iceCandidate) - } - logger.v { "[handleTrickle] #sfu; #${event.peerType.stringify()}; result: $result" } - } - - private fun handleFastReconnectNotPossible() { - val fastReconnectError = Exception("SFU fast-reconnect failed, full reconnect required") - disconnect(DisconnectReason.PermanentError(fastReconnectError)) - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SocketState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SocketState.kt deleted file mode 100644 index b9bf1c2a88..0000000000 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/SocketState.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core.socket - -import org.openapitools.client.models.VideoEvent - -public sealed class SocketState { - /** We haven't started to connect yet */ - object NotConnected : SocketState() { - override fun toString(): String = "Not Connected" - } - - /** Connection is in progress */ - object Connecting : SocketState() { - override fun toString(): String = "Connecting" - } - - /** We are connected, the most common state */ - data class Connected(val event: VideoEvent) : SocketState() - - /** There is no internet available */ - object NetworkDisconnected : SocketState() { - override fun toString(): String = "NetworkDisconnected" - } - - /** A temporary error broken the connection, socket will retry */ - data class DisconnectedTemporarily(val error: Throwable) : SocketState() - - /** A permanent error broken the connection, socket will not retry */ - data class DisconnectedPermanently(val error: Throwable) : SocketState() - - /** You called socket.disconnect(), socket is disconnected */ - object DisconnectedByRequest : SocketState() { - override fun toString(): String = "DisconnectedByRequest" - } -} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ConnectionConf.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ConnectionConf.kt new file mode 100644 index 0000000000..ede37b26ca --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ConnectionConf.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.video.android.model.SfuToken +import io.getstream.video.android.model.User +import stream.video.sfu.event.JoinRequest + +public sealed class ConnectionConf { + var isReconnection: Boolean = false + private set + abstract val endpoint: String + abstract val apiKey: String + abstract val user: User + + data class AnonymousConnectionConf( + override val endpoint: String, + override val apiKey: String, + override val user: User, + ) : ConnectionConf() + + data class UserConnectionConf( + override val endpoint: String, + override val apiKey: String, + override val user: User, + ) : ConnectionConf() + + data class SfuConnectionConf( + override val endpoint: String, + override val apiKey: String, + override val user: User = User.anonymous(), + val joinRequest: JoinRequest, + val token: SfuToken, + ) : ConnectionConf() + + internal fun asReconnectionConf(): ConnectionConf = this.also { isReconnection = true } + + internal val id: String + get() = when (this) { + is AnonymousConnectionConf -> "!anon" + else -> user.id + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorDetail.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorDetail.kt new file mode 100644 index 0000000000..6e11abaa19 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorDetail.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +public data class ErrorDetail( + public val code: Int, + public val messages: List, +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorResponse.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorResponse.kt new file mode 100644 index 0000000000..142862ce6f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/ErrorResponse.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +internal data class ErrorResponse( + val code: Int = -1, + var message: String = "", + var statusCode: Int = -1, + val exceptionFields: Map = mapOf(), + var moreInfo: String = "", + val details: List = emptyList(), +) { + var duration: String = "" +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/HealthMonitor.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/HealthMonitor.kt new file mode 100644 index 0000000000..08044b2378 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/HealthMonitor.kt @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.socket.common.scope.UserScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min + +private const val HEALTH_CHECK_INTERVAL = 1_000L +private const val MONITOR_INTERVAL = 10_000L +private const val NO_EVENT_INTERVAL_THRESHOLD = 30_000L + +public object TimeProvider { + private const val MILLIS_TO_SECONDS_FACTOR = 1_000L + public fun provideCurrentTimeInSeconds(): Long = System.currentTimeMillis() / MILLIS_TO_SECONDS_FACTOR + public fun provideCurrentTimeInMilliseconds(): Long = System.currentTimeMillis() +} + +internal class HealthMonitor( + private val timeProvider: TimeProvider = TimeProvider, + private val retryInterval: RetryInterval = ExponentialRetryInterval, + private val userScope: UserScope, + private val checkCallback: suspend () -> Unit, + private val reconnectCallback: suspend () -> Unit, +) { + + private var consecutiveFailures = 0 + private var lastAck: Long = 0 + private var healthMonitorJob: Job? = null + private var healthCheckJob: Job? = null + private var reconnectJob: Job? = null + + private val logger by taggedLogger("Video:SocketMonitor") + + /** + * Stop monitoring connection. + */ + fun stop() { + stopAllJobs() + } + + /** + * Notify that connection keeps alive. + */ + fun ack() { + resetHealthMonitor() + } + + /** + * Notify connection is disconnected. + */ + fun onDisconnected() { + stopAllJobs() + lastAck = 0 + postponeReconnect() + } + + /** + * Reset health monitor process. + */ + private fun resetHealthMonitor() { + stopAllJobs() + lastAck = TimeProvider.provideCurrentTimeInMilliseconds() + consecutiveFailures = 0 + postponeHealthMonitor() + } + + /** + * Postpone the action to check if connection keeps alive. + * If the connection is not alive anymore, an action to reconnect is postponed. + * In another case the healthCheck is postponed. + */ + private fun postponeHealthMonitor() { + healthMonitorJob?.cancel() + healthMonitorJob = userScope.launchDelayed(MONITOR_INTERVAL) { + if (needToReconnect()) { + postponeReconnect() + } else { + postponeHealthCheck() + } + } + } + + /** + * Postpone the action to send an "echo event" that will keep the connection alive. + * Just after the event is sent, an action is postponed to verify the connection is alive. + */ + private fun postponeHealthCheck() { + healthCheckJob?.cancel() + healthCheckJob = userScope.launchDelayed(HEALTH_CHECK_INTERVAL) { + checkCallback() + postponeHealthMonitor() + } + } + + /** + * Postpone the action to reconnect the socket. + * Just after the reconnection of the socket is started, an action to monitor the connection is started. + */ + private fun postponeReconnect() { + reconnectJob?.cancel() + val retryIntervalTime = retryInterval.nextInterval(consecutiveFailures++) + logger.i { "Next connection attempt in $retryIntervalTime ms" } + reconnectJob = userScope.launchDelayed(retryIntervalTime) { + reconnectCallback() + postponeHealthMonitor() + } + } + + /** + * Stop all launched job on this health monitor. + */ + private fun stopAllJobs() { + reconnectJob?.cancel() + healthCheckJob?.cancel() + healthMonitorJob?.cancel() + } + + /** + * Check if time elapsed since the last received event is greater than [NO_EVENT_INTERVAL_THRESHOLD]. + * + * @return True if time elapsed is bigger and we need to start reconnection process. + */ + private fun needToReconnect(): Boolean = + (TimeProvider.provideCurrentTimeInMilliseconds() - lastAck) >= NO_EVENT_INTERVAL_THRESHOLD + + private fun CoroutineScope.launchDelayed( + delayMilliseconds: Long, + block: suspend CoroutineScope.() -> Unit, + ): Job = launch { + delay(delayMilliseconds) + block() + } + + internal fun interface RetryInterval { + fun nextInterval(consecutiveFailures: Int): Long + } + + object ExponentialRetryInterval : RetryInterval { + + @Suppress("MagicNumber") + override fun nextInterval(consecutiveFailures: Int): Long { + val max = min(500 + consecutiveFailures * 2000, 25000) + val min = min( + max(250, (consecutiveFailures - 1) * 2000), + 25000, + ) + return floor(Math.random() * (max - min) + min).toLong() + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt new file mode 100644 index 0000000000..76b476cd88 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketActions.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow + +interface SocketActions { + companion object { + internal const val DEFAULT_SOCKET_TIMEOUT: Long = 10000L + } + + /** + * State of the socket as [StateFlow] + */ + public fun state(): StateFlow + + /** + * Socket events as [Flow] + */ + public fun events(): Flow + + /** + * Socket errors as [Flow] + */ + public fun errors(): Flow + + /** + * Send raw data to the socket. If you already have a parsed event that can be sent. + */ + fun sendData(data: String) + + /** + * Send event to the socket. + */ + public suspend fun sendEvent(event: EventIn): Boolean + + /** + * Connect the user. + */ + public suspend fun connect(connectData: ConnectData) + + /** + * Reconnect the user to the socket. + */ + public suspend fun reconnect(data: ConnectData, force: Boolean = false) + + /** + * Disconnect the socket. + */ + public suspend fun disconnect() + + /** + * Update the token from the outside. + */ + fun updateToken(token: Token) + + /** + * Get the connection id. + */ + fun connectionId(): StateFlow + + /** + * When connected to the socket. + */ + fun whenConnected( + connectionTimeout: Long = DEFAULT_SOCKET_TIMEOUT, + connected: suspend (connectionId: String) -> Unit, + ) +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketErrorMessage.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketErrorMessage.kt new file mode 100644 index 0000000000..6d486dcf38 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketErrorMessage.kt @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +internal data class SocketErrorMessage(val error: ErrorResponse? = null) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketFactory.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketFactory.kt new file mode 100644 index 0000000000..5f3d5b5ebc --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketFactory.kt @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.StreamVideo.Companion.buildSdkTrackingHeaders +import okhttp3.OkHttpClient +import okhttp3.Request +import org.openapitools.client.models.VideoEvent +import java.io.UnsupportedEncodingException + +internal class SocketFactory, C : ConnectionConf>( + private val parser: P, + private val httpClient: OkHttpClient = OkHttpClient(), +) { + private val logger by taggedLogger("Video:SocketFactory") + + @Throws(UnsupportedEncodingException::class) + fun createSocket(connectionConf: C, tag: String): StreamWebSocket { + val request = buildRequest(connectionConf) + logger.i { "[createSocket] new web socket: ${request.url}" } + return StreamWebSocket(tag, parser) { httpClient.newWebSocket(request, it) } + } + + @Throws(UnsupportedEncodingException::class) + private fun buildRequest(connectionConf: C): Request = + Request.Builder() + .url(connectionConf.endpoint) + .addHeader("Connection", "Upgrade") + .addHeader("Upgrade", "websocket") + .addHeader("X-Stream-Client", buildSdkTrackingHeaders()) + .build() +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketListener.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketListener.kt new file mode 100644 index 0000000000..e64a4c1a6c --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/SocketListener.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.result.Error +import io.getstream.video.android.core.errors.DisconnectCause +import org.openapitools.client.models.VideoEvent + +/** + * Listener which is invoked for WebSocket events. + */ +public open class SocketListener { + + /** + * The callbacks are by default delivered on the main thread. Changing this property to false will deliver + * the callbacks on their originating threads. + * + * Set to false for faster callback delivery on the original thread (no unnecessary context switching). + */ + public open val deliverOnMainThread: Boolean = true + + /** + * Called when the socket is created. + */ + public open fun onCreated() { + } + + /** + * Invoked when the connection begins to establish and socket state changes to Connecting. + */ + public open fun onConnecting() { + } + + /** + * Invoked when we receive the first [ConnectedEventType] in this connection. + * + * Note: This is not invoked when the ws connection is opened but when the [ConnectedEventType] is received. + * + * @param event [ConnectedEventType] sent by server as first event once the connection is established. + */ + public open fun onConnected(event: ConnectedEventType) { + } + + /** + * Invoked when the web socket connection is disconnected. + * + * @param cause [DisconnectCause] reason of disconnection. + */ + public open fun onDisconnected(cause: DisconnectCause) { + } + + /** + * Invoked when there is any error in this web socket connection. + * + * @param error [Error] object with the error details. + */ + public open fun onError(error: StreamWebSocketEvent.Error) { + } + + /** + * Invoked when we receive any successful event. + * + * @param event parsed [VideoEvent] received in this web socket connection. + */ + public open fun onEvent(event: EventType) { + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocket.kt new file mode 100644 index 0000000000..1eefc79af9 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocket.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.log.taggedLogger +import io.getstream.result.Error +import io.getstream.video.android.core.errors.VideoErrorCode +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import okhttp3.Response +import okhttp3.WebSocket +import okhttp3.WebSocketListener +import okio.ByteString + +private const val EVENTS_BUFFER_SIZE = 100 +private const val CLOSE_SOCKET_CODE = 1000 +private const val CLOSE_SOCKET_REASON = "Connection close by client" + +internal class StreamWebSocket>( + private val tag: String = "", + private val parser: T, + socketCreator: (WebSocketListener) -> WebSocket, +) { + private val logger by taggedLogger("Video:Events$tag") + private val eventFlow = + MutableSharedFlow(extraBufferCapacity = EVENTS_BUFFER_SIZE) + + private val webSocket = socketCreator(object : WebSocketListener() { + + override fun onOpen(webSocket: WebSocket, response: Response) { + super.onOpen(webSocket, response) + } + + override fun onMessage(webSocket: WebSocket, bytes: ByteString) { + val event = parser.decodeOrError(bytes.toByteArray()).onSuccess { + eventFlow.tryEmit(it) + }.onError { + eventFlow.tryEmit(StreamWebSocketEvent.Error(it)) + } + logger.v { "[onMessage#ByteString] event: `$event`" } + } + + override fun onMessage(webSocket: WebSocket, text: String) { + val event = parser.decodeOrError(text.toByteArray()).onSuccess { + eventFlow.tryEmit(it) + }.onError { + eventFlow.tryEmit(StreamWebSocketEvent.Error(it)) + } + logger.v { "[onMessage#string] event: `$event`" } + } + + override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { + eventFlow.tryEmit( + StreamWebSocketEvent.Error( + Error.NetworkError.fromVideoErrorCode( + videoErrorCode = VideoErrorCode.SOCKET_FAILURE, + cause = t, + ), + ), + ) + } + + override fun onClosed(webSocket: WebSocket, code: Int, reason: String) { + if (code != CLOSE_SOCKET_CODE) { + // Treat as failure and reconnect, socket shouldn't be closed by server + eventFlow.tryEmit( + StreamWebSocketEvent.Error( + Error.NetworkError.fromVideoErrorCode( + videoErrorCode = VideoErrorCode.SOCKET_CLOSED, + ), + ), + ) + } + } + }) + + fun send(event: V): Boolean { + logger.d { "[send] event: `$event`" } + val parsedEvent = parser.encode(event) + return webSocket.send(parsedEvent) + } + fun close(): Boolean = webSocket.close(CLOSE_SOCKET_CODE, CLOSE_SOCKET_REASON) + fun listen(): Flow = eventFlow.asSharedFlow() + + fun sendRaw(data: String) { + logger.d { "[send#raw] event: `$data`" } + webSocket.send(data) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocketEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocketEvent.kt new file mode 100644 index 0000000000..994fef0e6e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/StreamWebSocketEvent.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.result.Error +import io.getstream.video.android.core.events.SfuDataEvent +import org.openapitools.client.models.VideoEvent +import stream.video.sfu.models.WebsocketReconnectStrategy + +public sealed class StreamWebSocketEvent { + data class Error( + val streamError: io.getstream.result.Error, + val reconnectStrategy: WebsocketReconnectStrategy? = null, + ) : StreamWebSocketEvent() + data class VideoMessage(val videoEvent: VideoEvent) : StreamWebSocketEvent() + data class SfuMessage(val sfuEvent: SfuDataEvent) : StreamWebSocketEvent() +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoErrorDetail.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoErrorDetail.kt new file mode 100644 index 0000000000..30a799d51f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoErrorDetail.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +/** + * The error detail. + * + * @property code The error code. + * @property messages The error messages. + */ +public data class VideoErrorDetail( + public val code: Int, + public val messages: List, +) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoParser.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoParser.kt new file mode 100644 index 0000000000..659389d548 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/VideoParser.kt @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common + +import io.getstream.log.StreamLog +import io.getstream.result.Error +import io.getstream.result.Result +import io.getstream.result.extractCause +import io.getstream.result.recover +import io.getstream.video.android.core.call.signal.socket.RTCEventMapper +import io.getstream.video.android.core.errors.VideoErrorCode +import io.getstream.video.android.core.events.ErrorEvent +import io.getstream.video.android.core.events.SfuDataRequest +import okhttp3.Response +import okio.ByteString +import okio.ByteString.Companion.toByteString +import org.openapitools.client.models.VideoEvent +import retrofit2.Retrofit +import stream.video.sfu.event.SfuEvent +import stream.video.sfu.models.WebsocketReconnectStrategy + +internal interface GenericParser { + /** Encodes the given object into a ByteString. */ + fun encode(event: E): ByteString + + /** Decodes the given [raw] [ByteArray] into an object of type [T]. */ + fun decodeOrError(raw: ByteArray): Result +} + +internal interface SfuParser : GenericParser { + + private val tag: String get() = "Video:SfuParser" + override fun encode(event: SfuDataRequest): ByteString { + return event.sfuRequest.encodeByteString() + } + + override fun decodeOrError(raw: ByteArray): Result { + try { + val event = SfuEvent.ADAPTER.decode(raw) + if (event.error != null) { + val errorEvent = RTCEventMapper.mapEvent(event) + return toError(errorEvent as ErrorEvent) + } + return Result.Success(StreamWebSocketEvent.SfuMessage(RTCEventMapper.mapEvent(event))) + } catch (e: Exception) { + val error = Error.NetworkError.fromVideoErrorCode( + VideoErrorCode.UNABLE_TO_PARSE_SOCKET_EVENT, + cause = e, + ) + return Result.Failure(error) + } + } + + fun toError(errorEvent: ErrorEvent?): Result { + if (errorEvent != null) { + val serverError = errorEvent.error + val streamError = serverError?.let { + Error.NetworkError( + message = it.message, + statusCode = it.code.value, + serverErrorCode = it.code.value, + ) + } ?: let { + Error.NetworkError.fromVideoErrorCode(VideoErrorCode.UNABLE_TO_PARSE_SOCKET_EVENT) + } + val reconnectStrategy = errorEvent.reconnectStrategy + return Result.Success(StreamWebSocketEvent.Error(streamError, reconnectStrategy)) + } else { + StreamLog.e(tag) { "[toError] failed" } + val error = Error.NetworkError.fromVideoErrorCode(VideoErrorCode.CANT_PARSE_EVENT) + return Result.Success( + StreamWebSocketEvent.Error( + error, + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, + ), + ) + } + } +} + +internal interface VideoParser : GenericParser { + + private val tag: String get() = "Video:VideoParser" + override fun encode(event: VideoEvent): ByteString { + val json = toJson(event) + return json.encodeToByteArray().toByteString() + } + + override fun decodeOrError(raw: ByteArray): Result { + val text = String(raw) + StreamLog.d(tag) { "[decode] raw: ${String(raw)}" } + val event = fromJsonOrError(text, VideoEvent::class.java) + .map { StreamWebSocketEvent.VideoMessage(it) } + .recover { parseChatError -> + StreamLog.d(tag) { "[decode#recover] raw: $parseChatError" } + val errorResponse = + when ( + val chatErrorResult = fromJsonOrError( + text, + SocketErrorMessage::class.java, + ) + ) { + is Result.Success -> { + chatErrorResult.value.error + } + is Result.Failure -> null + } + StreamWebSocketEvent.Error( + errorResponse?.let { + Error.NetworkError( + message = it.message, + statusCode = it.statusCode, + serverErrorCode = it.code, + ) + } ?: Error.NetworkError.fromVideoErrorCode( + videoErrorCode = VideoErrorCode.CANT_PARSE_EVENT, + cause = parseChatError.extractCause(), + ), + ) + }.value + + return Result.Success(event) + } + + fun toJson(any: Any): String + fun fromJson(raw: String, clazz: Class): T + fun configRetrofit(builder: Retrofit.Builder): Retrofit.Builder + + @Suppress("TooGenericExceptionCaught") + fun fromJsonOrError(raw: String, clazz: Class): Result { + return try { + Result.Success(fromJson(raw, clazz)) + } catch (expected: Throwable) { + Result.Failure( + Error.ThrowableError("fromJsonOrError error parsing of $clazz into $raw", expected), + ) + } + } + + @Suppress("TooGenericExceptionCaught", "NestedBlockDepth") + fun toError(okHttpResponse: Response): Error.NetworkError { + val statusCode: Int = okHttpResponse.code + + return try { + // Try to parse default Stream error body + val body = okHttpResponse.peekBody(Long.MAX_VALUE).string() + + if (body.isEmpty()) { + Error.NetworkError.fromVideoErrorCode( + videoErrorCode = VideoErrorCode.NO_ERROR_BODY, + statusCode = statusCode, + ) + } else { + val error = try { + fromJson(body, ErrorResponse::class.java) + } catch (_: Throwable) { + ErrorResponse().apply { message = body } + } + Error.NetworkError( + serverErrorCode = error.code, + message = error.message + + moreInfoTemplate(error.moreInfo) + + buildDetailsTemplate(error.details), + statusCode = statusCode, + ) + } + } catch (expected: Throwable) { + StreamLog.e(tag, expected) { "[toError] failed" } + Error.NetworkError.fromVideoErrorCode( + videoErrorCode = VideoErrorCode.NETWORK_FAILED, + cause = expected, + statusCode = statusCode, + ) + } + } + + private fun moreInfoTemplate(moreInfo: String): String { + return if (moreInfo.isNotBlank()) { + "\nMore information available at $moreInfo" + } else { + "" + } + } + + private fun buildDetailsTemplate(details: List): String { + return if (details.isNotEmpty()) { + "\nError details: $details" + } else { + "" + } + } +} + +public fun Error.NetworkError.Companion.fromVideoErrorCode( + videoErrorCode: VideoErrorCode, + statusCode: Int = UNKNOWN_STATUS_CODE, + cause: Throwable? = null, +): Error.NetworkError { + return Error.NetworkError( + message = videoErrorCode.description, + serverErrorCode = videoErrorCode.code, + statusCode = statusCode, + cause = cause, + ) +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/FiniteStateMachine.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/FiniteStateMachine.kt new file mode 100644 index 0000000000..6b60ee3863 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/FiniteStateMachine.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.fsm + +import io.getstream.video.android.core.socket.common.fsm.builder.FSMBuilder +import io.getstream.video.android.core.socket.common.fsm.builder.FSMBuilderMarker +import io.getstream.video.android.core.socket.common.fsm.builder.StateFunction +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlin.reflect.KClass + +/** + * This class represents a Finite State Machine. It can be only in one possible state at a time + * out of the set of possible states [S]. It can handle events from the set [E]. + * + * @param initialState The initial state. + * @property stateFunctions A map of states and possible event handlers for them. + * @property defaultEventHandler Called when [stateFunctions] has no handler for + * a given state/event combination. + */ +public class FiniteStateMachine( + initialState: S, + private val stateFunctions: Map, Map, StateFunction>>, + private val defaultEventHandler: (S, E) -> S, +) { + private val mutex = Mutex() + private val _state: MutableStateFlow = MutableStateFlow(initialState) + + /** + * The current state as [StateFlow]. + */ + public val stateFlow: StateFlow = _state + + /** + * The current state. + */ + public val state: S + get() = _state.value + + /** + * Sends an event to the state machine. The entry point to change state. + */ + public suspend fun sendEvent(event: E) { + mutex.withLock { + val currentState = _state.value + val handler = stateFunctions[currentState::class]?.get(event::class) ?: defaultEventHandler + _state.value = handler(currentState, event) + } + } + + /** + * Keeps the FSM in its current state. + * Usually used when handling events that don't need to make a transition. + * + * ```kotlin + * onEvent { state, event -> stay() } + * ``` + * + * @return the current state value + */ + public fun stay(): S = state + + public companion object { + @FSMBuilderMarker + public operator fun invoke( + builder: FSMBuilder.() -> Unit, + ): FiniteStateMachine { + return FSMBuilder().apply(builder).build() + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilder.kt new file mode 100644 index 0000000000..f2aae767b8 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilder.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.fsm.builder + +import io.getstream.video.android.core.socket.common.fsm.FiniteStateMachine +import kotlin.reflect.KClass + +@FSMBuilderMarker +public class FSMBuilder { + private lateinit var _initialState: STATE + public val stateFunctions: + MutableMap, Map, StateFunction>> = + mutableMapOf() + + private var _defaultHandler: (STATE, EVENT) -> STATE = { s, _ -> s } + + @FSMBuilderMarker + public fun initialState(state: STATE) { + _initialState = state + } + + @FSMBuilderMarker + public fun defaultHandler(defaultHandler: (STATE, EVENT) -> STATE) { + _defaultHandler = defaultHandler + } + + @FSMBuilderMarker + public inline fun state( + stateHandlerBuilder: StateHandlerBuilder.() -> Unit, + ) { + stateFunctions[S::class] = StateHandlerBuilder().apply(stateHandlerBuilder).get() + } + + internal fun build(): FiniteStateMachine { + check(this::_initialState.isInitialized) { "Initial state must be set!" } + return FiniteStateMachine(_initialState, stateFunctions, _defaultHandler) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/LifecycleHandler.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilderMarker.kt similarity index 83% rename from stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/LifecycleHandler.kt rename to stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilderMarker.kt index d84aea4697..0eae5c0c0d 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/lifecycle/LifecycleHandler.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/FSMBuilderMarker.kt @@ -14,9 +14,7 @@ * limitations under the License. */ -package io.getstream.video.android.core.lifecycle +package io.getstream.video.android.core.socket.common.fsm.builder -internal interface LifecycleHandler { - fun started() - fun stopped() -} +@DslMarker +public annotation class FSMBuilderMarker diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/StateHandlerBuilder.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/StateHandlerBuilder.kt new file mode 100644 index 0000000000..b46e8fcc32 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/fsm/builder/StateHandlerBuilder.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.fsm.builder + +import kotlin.reflect.KClass + +internal typealias StateFunction = (S, E) -> S + +@FSMBuilderMarker +public class StateHandlerBuilder { + + @PublishedApi + internal val eventHandlers: MutableMap, StateFunction> = + mutableMapOf() + + @PublishedApi + internal val onEnterListeners: MutableList<(STATE, EVENT) -> Unit> = mutableListOf() + + @FSMBuilderMarker + public inline fun onEvent(noinline func: S.(E) -> STATE) { + @Suppress("UNCHECKED_CAST") + eventHandlers[E::class] = func as (STATE, EVENT) -> STATE + } + + @FSMBuilderMarker + public inline fun onEnter(crossinline listener: S.(EVENT) -> Unit) { + onEnterListeners.add { state, cause -> + @Suppress("UNCHECKED_CAST") + listener(state as S, cause) + } + } + + @PublishedApi + internal fun get(): Map, StateFunction> = eventHandlers + + @PublishedApi + internal fun getEnterListeners(): MutableList<(STATE, EVENT) -> Unit> = onEnterListeners +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiErrorLogging.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiErrorLogging.kt new file mode 100644 index 0000000000..67ea158b44 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiErrorLogging.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2 + +import okhttp3.RequestBody +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory +import java.lang.reflect.Type + +internal fun MoshiConverterFactory.withErrorLogging(): Converter.Factory { + val originalFactory = this + + return object : Converter.Factory() { + override fun responseBodyConverter( + type: Type, + annotations: Array, + retrofit: Retrofit, + ): Converter { + val originalConverter: Converter = + originalFactory.responseBodyConverter(type, annotations, retrofit)!! + return Converter { value -> originalConverter.convert(value) } + } + + override fun requestBodyConverter( + type: Type, + parameterAnnotations: Array, + methodAnnotations: Array, + retrofit: Retrofit, + ): Converter<*, RequestBody>? { + return originalFactory.requestBodyConverter( + type, + parameterAnnotations, + methodAnnotations, + retrofit, + ) + } + + override fun stringConverter( + type: Type, + annotations: Array, + retrofit: Retrofit, + ): Converter<*, String>? { + return originalFactory.stringConverter(type, annotations, retrofit) + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiVideoParser.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiVideoParser.kt new file mode 100644 index 0000000000..344fbaf0bd --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/MoshiVideoParser.kt @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2 + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.Moshi +import com.squareup.moshi.Moshi.Builder +import com.squareup.moshi.ToJson +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import io.getstream.video.android.core.socket.common.VideoParser +import io.getstream.video.android.core.socket.common.parser2.adapters.DateAdapter +import org.openapitools.client.infrastructure.BigDecimalAdapter +import org.openapitools.client.infrastructure.BigIntegerAdapter +import org.openapitools.client.infrastructure.ByteArrayAdapter +import org.openapitools.client.infrastructure.LocalDateAdapter +import org.openapitools.client.infrastructure.LocalDateTimeAdapter +import org.openapitools.client.infrastructure.OffsetDateTimeAdapter +import org.openapitools.client.infrastructure.URIAdapter +import org.openapitools.client.infrastructure.UUIDAdapter +import org.openapitools.client.models.VideoEventAdapter +import retrofit2.Retrofit +import retrofit2.converter.moshi.MoshiConverterFactory + +internal class MoshiVideoParser : VideoParser { + + private val moshi: Moshi by lazy { + Builder() + // Infrastructure @ToJson @FromJson + .add(OffsetDateTimeAdapter()).add(LocalDateTimeAdapter()).add(LocalDateAdapter()) + .add(UUIDAdapter()).add(ByteArrayAdapter()).add(URIAdapter()).add(BigDecimalAdapter()) + .add(BigIntegerAdapter()) + // JsonAdapter + .addAdapter(DateAdapter()) + .add(lenientAdapter(VideoEventAdapter())).add( + lenientAdapter( + org.openapitools.client.models.AudioSettingsRequest.DefaultDevice.DefaultDeviceAdapter(), + ), + ).add( + lenientAdapter( + org.openapitools.client.models.AudioSettingsResponse.DefaultDevice.DefaultDeviceAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.BlockListOptions.Behavior.BehaviorAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.ChannelConfigWithInfo.Automod.AutomodAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.ChannelConfigWithInfo.AutomodBehavior.AutomodBehaviorAdapter(), + ), + ).add( + lenientAdapter( + org.openapitools.client.models.ChannelConfigWithInfo.BlocklistBehavior.BlocklistBehaviorAdapter(), + ), + ).add( + lenientAdapter( + org.openapitools.client.models.CreateDeviceRequest.PushProvider.PushProviderAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.NoiseCancellationSettings.Mode.ModeAdapter(), + ), + ) + .add( + lenientAdapter(org.openapitools.client.models.OwnCapability.OwnCapabilityAdapter()), + ) + .add( + lenientAdapter( + org.openapitools.client.models.RecordSettingsRequest.Mode.ModeAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.RecordSettingsRequest.Quality.QualityAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.TranscriptionSettingsRequest.Mode.ModeAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.TranscriptionSettingsResponse.Mode.ModeAdapter(), + ), + ) + .add( + lenientAdapter( + org.openapitools.client.models.VideoSettingsRequest.CameraFacing.CameraFacingAdapter(), + ), + ).add( + lenientAdapter( + org.openapitools.client.models.VideoSettingsResponse.CameraFacing.CameraFacingAdapter(), + ), + ) + // Factories + .addLast(KotlinJsonAdapterFactory()).build() + } + + private inline fun Moshi.Builder.addAdapter(adapter: JsonAdapter) = apply { + this.add(T::class.java, lenientAdapter(adapter)) + } + + override fun configRetrofit(builder: Retrofit.Builder): Retrofit.Builder { + return builder.addConverterFactory(MoshiConverterFactory.create(moshi).withErrorLogging()) + } + + override fun toJson(any: Any): String = when { + Map::class.java.isAssignableFrom(any.javaClass) -> serializeMap(any) + else -> moshi.adapter(any.javaClass).toJson(any) + } + + private val mapAdapter = moshi.adapter(Map::class.java) + + private fun serializeMap(any: Any): String { + return mapAdapter.toJson(any as Map<*, *>) + } + + override fun fromJson(raw: String, clazz: Class): T { + return moshi.adapter(clazz).fromJson(raw)!! + } +} + +// Make any adapter lenient +inline fun lenientAdapter(adapter: JsonAdapter): JsonAdapter { + return object : JsonAdapter() { + + @ToJson + override fun toJson(writer: JsonWriter, value: T?) { + writer.isLenient = true + adapter.toJson(writer, value) + } + + @FromJson + override fun fromJson(reader: JsonReader): T? { + reader.isLenient = true + return adapter.fromJson(reader) + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/ThreadLocalDelegate.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/ThreadLocalDelegate.kt new file mode 100644 index 0000000000..d813b03ee7 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/ThreadLocalDelegate.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2 + +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty + +internal class ThreadLocalDelegate( + private val value: () -> T, +) : ReadOnlyProperty { + + private val threadLocal = object : ThreadLocal() { + override fun initialValue(): T? = value() + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): T = threadLocal.get()!! +} + +internal fun threadLocal(value: () -> T): ReadOnlyProperty { + return ThreadLocalDelegate(value) +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/CustomObjectDtoAdapter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/CustomObjectDtoAdapter.kt new file mode 100644 index 0000000000..c521b7e487 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/CustomObjectDtoAdapter.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2.adapters + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import kotlin.reflect.KClass + +/** + * Base class for implementing Moshi adapters that support our API's dynamic + * JSON models. + */ +internal open class CustomObjectDtoAdapter(private val kClass: KClass) { + + private companion object { + private const val EXTRA_DATA = "custom" + } + + /** + * Names of the declared properties that are inside the [Value] type being + * handled. These will not be copied into extraData when parsing with + * [parseWithExtraData]. + */ + private val memberNames: List by lazy { + kClass.members.map { member -> member.name }.minus(EXTRA_DATA) + } + + /** + * Moves all values in the input JSON that are not declared properties of + * [Value] into an extraValue field, and then parses a [Value] instance + * from this transformed data. + */ + protected fun parseWithExtraData( + jsonReader: JsonReader, + mapAdapter: JsonAdapter>, + valueAdapter: JsonAdapter, + ): Value? { + if (jsonReader.peek() == JsonReader.Token.NULL) { + jsonReader.nextNull() + return null + } + + // Parse full JSON content as a MutableMap + val map = mapAdapter.fromJson(jsonReader)!! + + val extraData = mutableMapOf() + + // Save the value of the literal "extraData" field at the root of the object, if present + map[EXTRA_DATA]?.let { explicitExtraData -> + extraData[EXTRA_DATA] = explicitExtraData + } + + // Save the values of non-member fields as extra data + map.forEach { entry -> + if (entry.key !in memberNames) { + extraData[entry.key] = entry.value + } + } + + // Replace original "extraData" with the newly collected values + map[EXTRA_DATA] = extraData + + // Parse output value object from the transformed Map + return valueAdapter.fromJsonValue(map)!! + } + + /** + * Converts the input [value] into a Map, moves whatever it contained in its + * extraData property to top level values inside the Map, and writes this + * transformed Map into [jsonWriter]. + */ + @Suppress("UNCHECKED_CAST") + protected fun serializeWithExtraData( + jsonWriter: JsonWriter, + value: Value?, + mapAdapter: JsonAdapter>, + valueAdapter: JsonAdapter, + ) { + if (value == null) { + jsonWriter.nullValue() + return + } + + // Convert input value into a Map + val map: MutableMap = valueAdapter.toJsonValue( + value, + ) as MutableMap + + // Grab real "extraData" property's value + val extraData = map[EXTRA_DATA] as Map + + // Remove literal "extraData" field from Map + map.remove(EXTRA_DATA) + + // Merge all values from "extraData" property back into the Map as top level fields + map.putAll(extraData) + + // Write Map to output + mapAdapter.toJson(jsonWriter, map) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/DateAdapter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/DateAdapter.kt new file mode 100644 index 0000000000..9efb141e90 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/DateAdapter.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2.adapters + +import com.squareup.moshi.FromJson +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonReader +import com.squareup.moshi.JsonWriter +import com.squareup.moshi.ToJson +import io.getstream.video.android.core.socket.common.parser2.adapters.internal.StreamDateFormatter +import java.util.Date + +public class DateAdapter : JsonAdapter() { + + private val streamDateFormatter = StreamDateFormatter("DateAdapter", cacheEnabled = true) + + @ToJson + override fun toJson(writer: JsonWriter, value: Date?) { + if (value == null) { + writer.nullValue() + } else { + val rawValue = streamDateFormatter.format(value) + writer.value(rawValue) + } + } + + @Suppress("TooGenericExceptionCaught") + @FromJson + override fun fromJson(reader: JsonReader): Date? { + val nextValue = reader.peek() + if (nextValue == JsonReader.Token.NULL) { + reader.skipValue() + return null + } + + val rawValue = reader.nextString() + return streamDateFormatter.parse(rawValue) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/internal/StreamDateFormatter.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/internal/StreamDateFormatter.kt new file mode 100644 index 0000000000..4d4a340af0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/parser2/adapters/internal/StreamDateFormatter.kt @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.parser2.adapters.internal + +import android.os.Build +import androidx.collection.LruCache +import com.ethlo.time.ITU +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.socket.common.parser2.threadLocal +import java.text.SimpleDateFormat +import java.time.ZoneOffset +import java.util.Date +import java.util.Locale +import java.util.TimeZone +import java.util.concurrent.atomic.AtomicInteger + +/** + * This class handles parse and format date in the standard way of Stream. + */ +public class StreamDateFormatter( + private val src: String? = null, + private val cacheEnabled: Boolean = false, +) { + + private val logger by taggedLogger("StreamDateFormatter") + + private companion object { + const val STATS = false + const val DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" + const val DATE_FORMAT_WITHOUT_NANOSECONDS = "yyyy-MM-dd'T'HH:mm:ss'Z'" + } + + private val dateFormat: SimpleDateFormat by threadLocal { + SimpleDateFormat(DATE_FORMAT, Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + } + + private val dateFormatWithoutNanoseconds: SimpleDateFormat by threadLocal { + SimpleDateFormat(DATE_FORMAT_WITHOUT_NANOSECONDS, Locale.US).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + } + + internal val datePattern = DATE_FORMAT + + private val cache by lazy { LruCache(300) } + private val cacheHit = AtomicInteger() + private val allRequests = AtomicInteger() + + /** + * Parses the [String] to [Date] in the standard way to Stream's API + */ + internal fun parse(rawValue: String): Date? { + logCacheHitStats() + return parseInternal(rawValue) + } + + private fun logCacheHitStats() { + val allRequests = allRequests.get() + val cacheHit = cacheHit.get() + if (STATS && cacheEnabled && allRequests % 100 == 0) { + val hitRate = cacheHit.toDouble() / allRequests + val hitRatePercent = (hitRate * 10000).toInt() / 100f + logger.v { "[parse] cache hit rate($src): $hitRatePercent% ($cacheHit / $allRequests)" } + } + } + + private fun parseInternal(rawValue: String): Date? { + allRequests.incrementAndGet() + val cachedValue = fetchFromCache(rawValue) + if (cachedValue != null) { + cacheHit.incrementAndGet() + return cachedValue + } + return if (rawValue.isEmpty()) { + null + } else { + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // Java Instant is up to 4x faster for parsing + // and can parse the date both with and without nano-seconds + Date.from(ITU.parseDateTime(rawValue).toInstant()) + } else { + dateFormat.parse(rawValue) + } + } catch (_: Throwable) { + try { + dateFormatWithoutNanoseconds.parse(rawValue) + } catch (_: Throwable) { + null + } + } + }.also { + saveToCache(rawValue, it) + } + } + + private fun fetchFromCache(rawValue: String): Date? { + if (!cacheEnabled) return null + return cache[rawValue] + } + + private fun saveToCache(rawValue: String, date: Date?) { + if (!cacheEnabled) return + if (date == null) return + cache.put(rawValue, date) + } + + /** + * Formats the [Date] in the standard way to Stream's API + */ + public fun format(date: Date): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ITU.formatUtcMilli(date.toInstant().atOffset(ZoneOffset.UTC)) + } else { + dateFormat.format(date) + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/ClientScope.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/ClientScope.kt new file mode 100644 index 0000000000..3b87a221e0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/ClientScope.kt @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.scope + +import io.getstream.log.StreamLog +import io.getstream.result.call.SharedCalls +import io.getstream.video.android.core.dispatchers.DispatcherProvider +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob + +/** + * A client aware implementation of [CoroutineScope]. + */ +internal interface ClientScope : CoroutineScope + +/** + * Creates a client aware [CoroutineScope]. + */ +internal fun ClientScope(exceptionHandler: CoroutineExceptionHandler? = null): ClientScope = + ClientScopeImpl(exceptionHandler ?: defaultClientExceptionHandler) + +internal val defaultClientExceptionHandler = + CoroutineExceptionHandler { coroutineContext, exception -> + val coroutineName = coroutineContext[CoroutineName]?.name ?: "unknown" + StreamLog.e("ClientScope", exception) { + "[ClientScope] Uncaught exception in coroutine $coroutineName: $exception" + } + } + +/** + * Represents SDK root [CoroutineScope]. + */ +private class ClientScopeImpl(exceptionHandler: CoroutineExceptionHandler) : + ClientScope, + CoroutineScope by CoroutineScope( + SupervisorJob() + DispatcherProvider.IO + SharedCalls() + exceptionHandler, + ) diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt new file mode 100644 index 0000000000..2b27e8529e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/UserScope.kt @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.scope + +import io.getstream.video.android.core.socket.common.scope.user.UserId +import io.getstream.video.android.core.socket.common.scope.user.UserIdentifier +import io.getstream.video.android.core.socket.common.scope.user.UserJob +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.job +import kotlinx.coroutines.plus + +/** + * A user aware implementation of [CoroutineScope]. + */ +internal interface UserScope : CoroutineScope { + + /** + * Returns [UserIdentifier] context element. + */ + val userId: UserIdentifier + + /** + * Cancels all children of the [UserJob] in this scope's context connected to the specified [userId]. + */ + fun cancelChildren(userId: UserId? = null) +} + +/** + * Creates a user aware [CoroutineScope]. + */ +internal fun UserScope(clientScope: ClientScope): UserScope = UserScopeImpl(clientScope) + +/** + * Inherits [ClientScope] and adds elements such as [UserIdentifier] and [UserJob]. + */ +private class UserScopeImpl( + clientScope: ClientScope, + userIdentifier: UserIdentifier = UserIdentifier(), +) : UserScope, + CoroutineScope by ( + clientScope + userIdentifier + UserJob(clientScope.coroutineContext.job) { userIdentifier.value } + ) { + + /** + * Returns [UserIdentifier] context element. + */ + override val userId: UserIdentifier + get() = coroutineContext[UserIdentifier] ?: error("no UserIdentifier found") + + /** + * Cancels all children of the [UserJob] in this scope's context connected to the specified [userId]. + */ + override fun cancelChildren(userId: UserId?) { + (coroutineContext[Job] as UserJob).cancelChildren(userId) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserId.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserId.kt new file mode 100644 index 0000000000..f18c6effa3 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserId.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.scope.user + +/** + * Simply an alias for the user ID. + */ +public typealias UserId = String diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserIdentifier.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserIdentifier.kt new file mode 100644 index 0000000000..cc3f749e04 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserIdentifier.kt @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.scope.user + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withTimeout +import kotlin.coroutines.AbstractCoroutineContextElement +import kotlin.coroutines.CoroutineContext + +/** + * The [CoroutineContext.Element] which holds the current [UserId]. + */ +internal class UserIdentifier : AbstractCoroutineContextElement(UserIdentifier) { + + private val _value = MutableStateFlow(null) + + /** + * Key for [UserIdentifier] instance in the coroutine context. + */ + companion object Key : CoroutineContext.Key { + private const val DEFAULT_TIMEOUT_IN_MS = 10_000L + } + + /** + * Represents [User.id] String + */ + var value: UserId? + get() = _value.value + set(value) { + _value.value = value + } + + /** + * Awaits for the specified [userId] being set. + * + * @param userId Required user_id to wait for. + * @param timeoutInMs A timeout in milliseconds when the process will be cancelled. + */ + suspend fun awaitFor(userId: UserId, timeoutInMs: Long = DEFAULT_TIMEOUT_IN_MS) = runCatching { + withTimeout(timeoutInMs) { + _value.first { it == userId } + } + } + + /** + * Returns a string representation of the object. + */ + override fun toString(): String = "UserIdentifier($value)" +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserJob.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserJob.kt new file mode 100644 index 0000000000..fb29021d5b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/scope/user/UserJob.kt @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(InternalCoroutinesApi::class) +@file:Suppress("DEPRECATION_ERROR") + +package io.getstream.video.android.core.socket.common.scope.user + +import io.getstream.log.taggedLogger +import kotlinx.coroutines.ChildHandle +import kotlinx.coroutines.ChildJob +import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.InternalCoroutinesApi +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlin.coroutines.CoroutineContext + +/** + * A user aware implementation of [Job]. + */ +internal interface UserJob : CompletableJob { + /** + * Cancels all children of the [UserJob] in this context connected to the specified [userId]. + */ + fun cancelChildren(userId: UserId? = null) +} + +/** + * Creates a user aware job object in an active state. + */ +internal fun UserJob(parent: Job? = null, getUserId: () -> UserId?): UserJob = UserJobImpl( + SupervisorJob(parent), + getUserId, +) + +/** + * A user aware implementation of [Job]. It is optionally a child to a parent job. + */ +private class UserJobImpl( + private val delegate: CompletableJob, + private val getUserId: () -> UserId?, +) : CompletableJob by delegate, UserJob { + + private val logger by taggedLogger("Video:UserJob") + + /** + * Wraps child job with [UserChildJob] and attaches it so that [UserJob] job becomes its parent. + * + * WARNING! This function is marked as [InternalCoroutinesApi]. + */ + override fun attachChild(child: ChildJob): ChildHandle { + val userId = getUserId() + return delegate.attachChild(UserChildJob(userId, child)) + } + + /** + * Cancels all children of the [UserJob] in this context connected to the specified [userId]. + */ + override fun cancelChildren(userId: UserId?) { + logger.d { "[cancelChildren] userId: '$userId'" } + for (child in children) { + if (child is UserChildJob && child.userId != userId && userId != null) { + logger.v { "[cancelChildren] skip child: $child)" } + continue + } + logger.v { "[cancelChildren] cancel child: $child)" } + child.cancel() + } + } + + /** + * Returns the element with the given [key] from this context or `null`. + * + * Intentionally overridden to use [super] and not a [delegate] one. + */ + override fun fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R { + return super.fold(initial, operation) + } + + /** + * Returns the element with the given [key] from this context or `null`. + * + * Intentionally overridden to use [super] and not a [delegate] one. + */ + @Suppress("UNCHECKED_CAST") + override fun get(key: CoroutineContext.Key): E? { + return super.get(key) + } + + /** + * Returns a context containing elements from this context, but without an element with + * the specified [key]. + * + * Intentionally overridden to use [super] and not a [delegate] one. + */ + override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext { + return super.minusKey(key) + } + + /** + * Returns a string representation of the object. + */ + override fun toString(): String = "UserJob(userId=${getUserId()})" +} + +/** + * Combines [ChildJob] with specified [userId]. + */ +private class UserChildJob( + val userId: UserId?, + private val delegate: ChildJob, +) : ChildJob by delegate { + override fun toString(): String = "UserChildJob(userId='$userId')" +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt new file mode 100644 index 0000000000..39d127234a --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/CacheableTokenProvider.kt @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +/** + * An implementation of [TokenProvider] that keeps previous values of the loaded token. + * This implementation delegate the process to obtain a new token to another tokenProvider. + * + * @property tokenProvider The [TokenProvider] used to obtain new tokens. + */ +internal class CacheableTokenProvider(private val tokenProvider: TokenProvider) : TokenProvider { + private var cachedToken = "" + override suspend fun loadToken(): String = tokenProvider.loadToken().also { cachedToken = it } + + /** + * Obtain the cached token. + * + * @return The cached token. + */ + fun getCachedToken(): String = cachedToken +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt new file mode 100644 index 0000000000..03f32439ea --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/ConstantTokenProvider.kt @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +internal class ConstantTokenProvider(private val token: String) : TokenProvider { + override suspend fun loadToken(): String = token +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt new file mode 100644 index 0000000000..abd71237c9 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManager.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +internal interface TokenManager { + /** + * Update the current token. + */ + fun updateToken(token: String) + + /** + * Ensure a token has been loaded. + */ + suspend fun ensureTokenLoaded() + + /** + * Load a new token. + */ + suspend fun loadSync(): String + + /** + * Expire the current token. + */ + fun expireToken() + + /** + * Check if a [TokenProvider] has been provided. + * + * @return true if a token provider has been provided, false on another case. + */ + fun hasTokenProvider(): Boolean + + /** + * Inject a new [CacheableTokenProvider] + * + * @param provider A [CacheableTokenProvider] + */ + fun setTokenProvider(provider: CacheableTokenProvider) + + /** + * Obtain last token loaded. + * + * @return the last token loaded. If the token was expired an empty [String] will be returned. + */ + fun getToken(): String + + /** + * Check if a token was loaded. + * + * @return true if a token was loaded and it is not expired, false on another case. + */ + fun hasToken(): Boolean +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt new file mode 100644 index 0000000000..e4c1bca4e6 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenManagerImpl.kt @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +internal class TokenManagerImpl : TokenManager { + @Volatile + private var token: String = EMPTY_TOKEN + private lateinit var provider: TokenProvider + + override fun updateToken(token: String) { + this.token = token + } + + override suspend fun ensureTokenLoaded() { + if (!hasToken()) { + loadSync() + } + } + + override suspend fun loadSync(): String { + return provider.loadToken().also { + this.token = it + } + } + + override fun setTokenProvider(provider: CacheableTokenProvider) { + this.provider = provider + this.token = provider.getCachedToken() + } + + override fun hasTokenProvider(): Boolean { + return this::provider.isInitialized + } + + override fun getToken(): String = token + + override fun hasToken(): Boolean { + return token != EMPTY_TOKEN + } + + override fun expireToken() { + token = EMPTY_TOKEN + } + + companion object { + const val EMPTY_TOKEN = "" + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenProvider.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenProvider.kt new file mode 100644 index 0000000000..b9d04adc38 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/common/token/TokenProvider.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.common.token + +import androidx.annotation.WorkerThread + +/** + * Provides a token used to authenticate the user with Stream Chat API. + * The SDK doesn't refresh the token internally and will call + * [loadToken] function once the previous one has expired. + * + * Check out [docs](https://getstream.io/chat/docs/android/init_and_users/) for more info about tokens. + */ +public interface TokenProvider { + + /** + * Loads the token for the current user. + * The token will be loaded only if the token was not loaded yet or existing one has expired. + * If the token cannot be loaded, returns an empty string and never throws an exception. + * + * @return The valid JWT token. + */ + @WorkerThread + public suspend fun loadToken(): String +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt new file mode 100644 index 0000000000..acdc51c505 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocket.kt @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator + +import io.getstream.log.taggedLogger +import io.getstream.result.Error +import io.getstream.video.android.core.dispatchers.DispatcherProvider +import io.getstream.video.android.core.errors.DisconnectCause +import io.getstream.video.android.core.errors.VideoErrorCode +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.lifecycle.LifecycleHandler +import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver +import io.getstream.video.android.core.socket.common.ConnectionConf +import io.getstream.video.android.core.socket.common.HealthMonitor +import io.getstream.video.android.core.socket.common.SocketFactory +import io.getstream.video.android.core.socket.common.SocketListener +import io.getstream.video.android.core.socket.common.StreamWebSocket +import io.getstream.video.android.core.socket.common.StreamWebSocketEvent +import io.getstream.video.android.core.socket.common.VideoErrorDetail +import io.getstream.video.android.core.socket.common.VideoParser +import io.getstream.video.android.core.socket.common.fromVideoErrorCode +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.core.socket.common.token.TokenManager +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketConnectionType +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import io.getstream.video.android.model.User +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import org.openapitools.client.models.ConnectedEvent +import org.openapitools.client.models.ConnectionErrorEvent +import org.openapitools.client.models.HealthCheckEvent +import org.openapitools.client.models.VideoEvent +import stream.video.sfu.models.WebsocketReconnectStrategy +import kotlin.coroutines.EmptyCoroutineContext + +@Suppress("TooManyFunctions", "LongParameterList") +internal open class CoordinatorSocket( + private val apiKey: String, + private val wssUrl: String, + private val tokenManager: TokenManager, + private val socketFactory: SocketFactory, + private val userScope: UserScope, + private val lifecycleObserver: StreamLifecycleObserver, + private val networkStateProvider: NetworkStateProvider, +) { + private var streamWebSocket: StreamWebSocket? = null + open val logger by taggedLogger(TAG) + private var connectionConf: ConnectionConf? = null + private val listeners = mutableSetOf>() + private val coordinatorSocketStateService = CoordinatorSocketStateService() + private var socketStateObserverJob: Job? = null + private val healthMonitor = HealthMonitor( + userScope = userScope, + checkCallback = { + val connected = coordinatorSocketStateService.currentState as? VideoSocketState.Connected + connected?.event?.let(::sendEvent) + }, + reconnectCallback = { + coordinatorSocketStateService.onWebSocketEventLost() + }, + ) + private val lifecycleHandler = object : LifecycleHandler { + override suspend fun resume() { + coordinatorSocketStateService.onResume() + } + + override suspend fun stopped() { + coordinatorSocketStateService.onStop() + } + } + + private val networkStateListener = object : NetworkStateProvider.NetworkStateListener { + override suspend fun onConnected() { + coordinatorSocketStateService.onNetworkAvailable() + } + + override suspend fun onDisconnected() { + coordinatorSocketStateService.onNetworkNotAvailable() + } + } + + @Suppress("ComplexMethod") + private fun observeSocketStateService(): Job { + var socketListenerJob: Job? = null + + suspend fun connectUser(connectionConf: ConnectionConf) { + logger.d { "[connectUser] connectionConf: $connectionConf" } + userScope.launch { startObservers() } + this.connectionConf = connectionConf + socketListenerJob?.cancel() + when (networkStateProvider.isConnected()) { + true -> { + streamWebSocket = socketFactory.createSocket(connectionConf, "#coordinator").apply { + listeners.forEach { it.onCreated() } + + socketListenerJob = listen().onEach { + when (it) { + is StreamWebSocketEvent.Error -> handleError(it) + is StreamWebSocketEvent.VideoMessage -> when ( + val event = + it.videoEvent + ) { + is ConnectionErrorEvent -> handleError(StreamWebSocketEvent.Error(event.toNetworkError())) + else -> handleEvent(event) + } + is StreamWebSocketEvent.SfuMessage -> { + logger.v { "Received [SFUMessage] in coordinator socket. Ignoring" } + } + } + }.launchIn(userScope) + } + } + + false -> coordinatorSocketStateService.onNetworkNotAvailable() + } + } + + suspend fun reconnect(connectionConf: ConnectionConf) { + logger.d { "[reconnect] connectionConf: $connectionConf" } + connectUser(connectionConf.asReconnectionConf()) + } + + return userScope.launch { + coordinatorSocketStateService.observer { state -> + logger.i { "[onSocketStateChanged] state: $state" } + when (state) { + is VideoSocketState.RestartConnection -> { + connectionConf?.let { coordinatorSocketStateService.onReconnect(it, false) } + ?: run { + logger.e { "[onSocketStateChanged] #reconnect; connectionConf is null" } + } + } + + is VideoSocketState.Connected -> { + healthMonitor.ack() + callListeners { listener -> listener.onConnected(state.event) } + } + + is VideoSocketState.Connecting -> { + callListeners { listener -> listener.onConnecting() } + when (state.connectionType) { + VideoSocketConnectionType.INITIAL_CONNECTION -> + connectUser(state.connectionConf) + + VideoSocketConnectionType.AUTOMATIC_RECONNECTION -> + reconnect(state.connectionConf.asReconnectionConf()) + + VideoSocketConnectionType.FORCE_RECONNECTION -> + reconnect(state.connectionConf.asReconnectionConf()) + } + } + + is VideoSocketState.Disconnected -> { + when (state) { + is VideoSocketState.Disconnected.DisconnectedByRequest -> { + streamWebSocket?.close() + healthMonitor.stop() + userScope.launch { disposeObservers() } + } + + is VideoSocketState.Disconnected.NetworkDisconnected -> { + streamWebSocket?.close() + healthMonitor.stop() + } + + is VideoSocketState.Disconnected.Stopped -> { + streamWebSocket?.close() + healthMonitor.stop() + disposeNetworkStateObserver() + } + + is VideoSocketState.Disconnected.DisconnectedPermanently -> { + streamWebSocket?.close() + healthMonitor.stop() + userScope.launch { disposeObservers() } + } + + is VideoSocketState.Disconnected.DisconnectedTemporarily -> { + healthMonitor.onDisconnected() + } + + is VideoSocketState.Disconnected.WebSocketEventLost -> { + streamWebSocket?.close() + connectionConf?.let { + coordinatorSocketStateService.onReconnect( + it, + false, + ) + } + } + } + callListeners { listener -> listener.onDisconnected(cause = state.cause) } + } + } + } + } + } + + suspend fun connectUser(user: User, isAnonymous: Boolean) { + logger.d { "[connectUser] user.id: ${user.id}, isAnonymous: $isAnonymous" } + socketStateObserverJob?.cancel() + socketStateObserverJob = observeSocketStateService() + coordinatorSocketStateService.onConnect( + when (isAnonymous) { + true -> ConnectionConf.AnonymousConnectionConf(wssUrl, apiKey, user) + false -> ConnectionConf.UserConnectionConf(wssUrl, apiKey, user) + }, + ) + } + + suspend fun disconnect() { + logger.d { "[disconnect] no args" } + connectionConf = null + coordinatorSocketStateService.onRequiredDisconnect() + } + + private suspend fun handleEvent(chatEvent: VideoEvent) { + when (chatEvent) { + is ConnectedEvent -> coordinatorSocketStateService.onConnectionEstablished(chatEvent) + is HealthCheckEvent -> healthMonitor.ack() + else -> callListeners { listener -> listener.onEvent(chatEvent) } + } + } + + private suspend fun startObservers() { + lifecycleObserver.observe(lifecycleHandler) + networkStateProvider.subscribe(networkStateListener) + } + + private suspend fun disposeObservers() { + lifecycleObserver.dispose(lifecycleHandler) + disposeNetworkStateObserver() + } + + private fun disposeNetworkStateObserver() { + networkStateProvider.unsubscribe(networkStateListener) + } + + private suspend fun handleError(error: StreamWebSocketEvent.Error) { + logger.e { "[handleError] error: $error" } + when (error.streamError) { + is Error.NetworkError -> onVideoNetworkError(error.streamError, error.reconnectStrategy) + else -> callListeners { it.onError(error) } + } + } + + private suspend fun onVideoNetworkError(error: Error.NetworkError, reconnectStrategy: WebsocketReconnectStrategy?) { + if (VideoErrorCode.isAuthenticationError(error.serverErrorCode)) { + tokenManager.expireToken() + } + + when (error.serverErrorCode) { + VideoErrorCode.UNDEFINED_TOKEN.code, + VideoErrorCode.INVALID_TOKEN.code, + VideoErrorCode.API_KEY_NOT_FOUND.code, + VideoErrorCode.VALIDATION_ERROR.code, + -> { + logger.d { + "One unrecoverable error happened. Error: $error. Error code: ${error.serverErrorCode}" + } + coordinatorSocketStateService.onUnrecoverableError(error) + } + + else -> coordinatorSocketStateService.onNetworkError(error) + } + } + + fun removeListener(listener: SocketListener) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + fun addListener(listener: SocketListener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun state() = coordinatorSocketStateService.currentStateFlow + + /** + * Attempt to send [event] to the web socket connection. + * Returns true only if socket is connected and [okhttp3.WebSocket.send] returns true, otherwise false + * + * @see [okhttp3.WebSocket.send] + */ + internal fun sendEvent(event: VideoEvent): Boolean = streamWebSocket?.send(event) ?: false + + /** + * Send raw data to the web socket connection. + */ + internal fun senRawData(data: String) { + streamWebSocket?.sendRaw(data) + } + + internal fun isConnected(): Boolean = coordinatorSocketStateService.currentState is VideoSocketState.Connected + + /** + * Awaits until [VideoSocketState.Connected] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + internal suspend fun awaitConnection(timeoutInMillis: Long = DEFAULT_CONNECTION_TIMEOUT) { + awaitState(timeoutInMillis) + } + + /** + * Awaits until specified [VideoSocketState] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + internal suspend inline fun awaitState(timeoutInMillis: Long) { + withTimeout(timeoutInMillis) { + coordinatorSocketStateService.currentStateFlow.first { it is T } + } + } + + /** + * Get connection id of this connection. + */ + internal fun connectionIdOrError(): String = + when (val state = coordinatorSocketStateService.currentState) { + is VideoSocketState.Connected -> state.event.connectionId + else -> error("This state doesn't contain connectionId") + } + + suspend fun reconnectUser(user: User, isAnonymous: Boolean, forceReconnection: Boolean) { + logger.d { + "[reconnectUser] user.id: ${user.id}, isAnonymous: $isAnonymous, forceReconnection: $forceReconnection" + } + coordinatorSocketStateService.onReconnect( + when (isAnonymous) { + true -> ConnectionConf.AnonymousConnectionConf(wssUrl, apiKey, user) + false -> ConnectionConf.UserConnectionConf(wssUrl, apiKey, user) + }, + forceReconnection, + ) + } + + private fun callListeners(call: (SocketListener) -> Unit) { + synchronized(listeners) { + listeners.forEach { listener -> + val context = if (listener.deliverOnMainThread) { + DispatcherProvider.Main + } else { + EmptyCoroutineContext + } + userScope.launch(context) { call(listener) } + } + } + } + + private val VideoSocketState.Disconnected.cause + get() = when (this) { + is VideoSocketState.Disconnected.DisconnectedByRequest, + is VideoSocketState.Disconnected.Stopped, + -> DisconnectCause.ConnectionReleased + + is VideoSocketState.Disconnected.NetworkDisconnected -> DisconnectCause.NetworkNotAvailable + is VideoSocketState.Disconnected.DisconnectedPermanently -> DisconnectCause.UnrecoverableError( + error, + ) + + is VideoSocketState.Disconnected.DisconnectedTemporarily -> DisconnectCause.Error(error) + is VideoSocketState.Disconnected.WebSocketEventLost -> DisconnectCause.WebSocketNotAvailable + } + + private fun ConnectionErrorEvent.toNetworkError(): Error.NetworkError { + return error?.let { + return Error.NetworkError( + message = it.message + moreInfoTemplate(it.moreInfo) + buildDetailsTemplate( + it.details.map { code -> + VideoErrorDetail(code, listOf("")) + }, + ), + serverErrorCode = it.code, + statusCode = it.statusCode, + ) + } ?: Error.NetworkError.fromVideoErrorCode(VideoErrorCode.NO_ERROR_BODY, cause = null) + } + + private fun moreInfoTemplate(moreInfo: String): String { + return if (moreInfo.isNotBlank()) { + "\nMore information available at $moreInfo" + } else { + "" + } + } + + private fun buildDetailsTemplate(details: List): String { + return if (details.isNotEmpty()) { + "\nError details: $details" + } else { + "" + } + } + + companion object { + private const val TAG = "Video:Socket" + private const val DEFAULT_CONNECTION_TIMEOUT = 60_000L + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt new file mode 100644 index 0000000000..80133dee91 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketConnection.kt @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator + +import androidx.lifecycle.Lifecycle +import com.squareup.moshi.JsonAdapter +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.errors.DisconnectCause +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver +import io.getstream.video.android.core.socket.common.SocketActions +import io.getstream.video.android.core.socket.common.SocketFactory +import io.getstream.video.android.core.socket.common.SocketListener +import io.getstream.video.android.core.socket.common.StreamWebSocketEvent +import io.getstream.video.android.core.socket.common.VideoParser +import io.getstream.video.android.core.socket.common.parser2.MoshiVideoParser +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenManagerImpl +import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import io.getstream.video.android.core.utils.isWhitespaceOnly +import io.getstream.video.android.core.utils.mapState +import io.getstream.video.android.model.ApiKey +import io.getstream.video.android.model.User +import io.getstream.video.android.model.User.Companion.isAnonymous +import io.getstream.video.android.model.UserToken +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import org.openapitools.client.infrastructure.Serializer +import org.openapitools.client.models.ConnectUserDetailsRequest +import org.openapitools.client.models.ConnectedEvent +import org.openapitools.client.models.VideoEvent +import org.openapitools.client.models.WSAuthMessageRequest + +/** + * PersistentSocket architecture + * + * - Health monitor that sends a ping every 30 seconds + * - Automatically reconnects if it encounters a temp failure + * - Raises the error if there is a permanent failure + * - Flow to avoid concurrency related bugs + * - Ability to wait till the socket is connected (important to prevent race conditions) + */ +public open class CoordinatorSocketConnection( + private val apiKey: ApiKey, + /** The URL to connect to */ + private val url: String, + /** The user to connect. */ + private val user: User, + /** The initial token. */ + private val token: String, + /** Inject your http client */ + private val httpClient: OkHttpClient, + /** Inject your network state provider */ + private val networkStateProvider: NetworkStateProvider, + /** Set the scope everything should run in */ + private val scope: CoroutineScope = UserScope(ClientScope()), + /** Lifecycle */ + private val lifecycle: Lifecycle, + /** Token provider */ + private val tokenProvider: TokenProvider, +) : SocketListener(), + SocketActions { + + // Private state + private val parser: VideoParser = MoshiVideoParser() + private val tokenManager = TokenManagerImpl() + + // Internal state + private val logger by taggedLogger("Video:Socket") + private val errors: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.DROP_OLDEST, + replay = 1, + extraBufferCapacity = 100, + ) + private val events: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.DROP_OLDEST, + replay = 1, + extraBufferCapacity = 100, + ) + private val connectionId: MutableStateFlow = MutableStateFlow(null) + private val internalSocket = CoordinatorSocket( + apiKey, + url, + tokenManager, + SocketFactory( + parser, + httpClient, + ), + scope as? UserScope ?: UserScope(ClientScope()), + StreamLifecycleObserver(scope, lifecycle), + networkStateProvider, + ).also { + it.addListener(this) + } + + private val state: StateFlow = internalSocket.state() + + // Init + init { + tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + } + + // Extension opportunity for subclasses + override fun onCreated() { + super.onCreated() + logger.d { "[onCreated] Socket is created" } + scope.launch { + logger.d { "[onConnected] Video socket created, user: $user" } + if (token.isEmpty()) { + logger.e { "[onConnected] Token is empty. Disconnecting." } + disconnect() + } else { + val authRequest = WSAuthMessageRequest( + token = token, + userDetails = ConnectUserDetailsRequest( + id = user.id, + name = user.name.takeUnless { it.isWhitespaceOnly() }, + image = user.image.takeUnless { it.isWhitespaceOnly() }, + custom = user.custom, + ), + ) + + val adapter: JsonAdapter = + Serializer.moshi.adapter(WSAuthMessageRequest::class.java) + val data = adapter.toJson(authRequest) + logger.d { "[onConnected] Sending auth request: $authRequest" } + logger.d { "[onConnected#data] Data: $data" } + sendData(data) + } + } + } + + override fun onConnecting() { + super.onConnecting() + logger.d { "[onConnecting] Socket is connecting" } + } + + override fun onConnected(event: ConnectedEvent) { + super.onConnected(event) + whenConnected { + connectionId.value = it + } + logger.d { "[onConnected] Socket connected with event: $event" } + } + + override fun onEvent(event: VideoEvent) { + super.onEvent(event) + logger.d { "[onEvent] Received event: $event" } + val emit = events.tryEmit(event) + if (!emit) { + logger.e { "[onEvent] Failed to emit event: $event" } + } + } + + override fun onError(error: StreamWebSocketEvent.Error) { + super.onError(error) + logger.e { "[onError] Socket error: $error" } + val emit = errors.tryEmit(error) + if (!emit) { + logger.e { "[onError] Failed to emit error: $error" } + } + } + + override fun onDisconnected(cause: DisconnectCause) { + super.onDisconnected(cause) + logger.d { "[onDisconnected] Socket disconnected. Cause: $cause" } + } + + // API + override fun connectionId(): StateFlow = connectionId.mapState { + it + } + override fun whenConnected( + connectionTimeout: Long, + connected: suspend (connectionId: String) -> Unit, + ) { + scope.launch { + internalSocket.awaitConnection(connectionTimeout) + internalSocket.connectionIdOrError().also { + connected(it) + } + } + } + override fun state(): StateFlow = state + + override fun events(): Flow = events + + override fun errors(): Flow = errors + + override fun sendData(data: String) = internalSocket.senRawData(data) + + override suspend fun sendEvent(event: VideoEvent): Boolean = internalSocket.sendEvent(event) + + override suspend fun connect(connectData: User) { + internalSocket.connectUser(connectData, connectData.isAnonymous()) + } + + override suspend fun reconnect(data: User, force: Boolean) { + internalSocket.reconnectUser(data, data.isAnonymous(), force) + } + + override suspend fun disconnect() = internalSocket.disconnect() + + override fun updateToken(token: UserToken) { + tokenManager.updateToken(token) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt new file mode 100644 index 0000000000..451deb931a --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/CoordinatorSocketStateService.kt @@ -0,0 +1,384 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator + +import io.getstream.log.taggedLogger +import io.getstream.result.Error +import io.getstream.video.android.core.socket.common.ConnectionConf +import io.getstream.video.android.core.socket.common.fsm.FiniteStateMachine +import io.getstream.video.android.core.socket.coordinator.state.RestartReason +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketConnectionType +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketStateEvent +import kotlinx.coroutines.flow.StateFlow +import org.openapitools.client.models.ConnectedEvent + +internal class CoordinatorSocketStateService(initialState: VideoSocketState = VideoSocketState.Disconnected.Stopped) { + private val logger by taggedLogger("Video:SocketState") + + suspend fun observer(onNewState: suspend (VideoSocketState) -> Unit) { + stateMachine.stateFlow.collect(onNewState) + } + + /** + * Require a reconnection. + * + * @param connectionConf The [VideoSocketFactory.ConnectionConf] to be used to reconnect. + */ + suspend fun onReconnect( + connectionConf: ConnectionConf, + forceReconnection: Boolean, + ) { + logger.v { + "[onReconnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" + } + stateMachine.sendEvent( + VideoSocketStateEvent.Connect( + connectionConf, + when (forceReconnection) { + true -> VideoSocketConnectionType.FORCE_RECONNECTION + false -> VideoSocketConnectionType.AUTOMATIC_RECONNECTION + }, + ), + ) + } + + /** + * Require connection. + * + * @param connectionConf The [VideoSocketFactory.ConnectionConf] to be used on the new connection. + */ + suspend fun onConnect(connectionConf: ConnectionConf) { + logger.v { + "[onConnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" + } + stateMachine.sendEvent( + VideoSocketStateEvent.Connect( + connectionConf, + VideoSocketConnectionType.INITIAL_CONNECTION, + ), + ) + } + + /** + * Notify that the network is not available at the moment. + */ + suspend fun onNetworkNotAvailable() { + logger.w { "[onNetworkNotAvailable] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.NetworkNotAvailable) + } + + /** + * Notify the WebSocket connection has been established. + * + * @param connectedEvent The [ConnectedEvent] received within the WebSocket connection. + */ + suspend fun onConnectionEstablished(connectedEvent: ConnectedEvent) { + logger.i { + "[onConnected] user.id: '${connectedEvent.me.id}', connectionId: ${connectedEvent.connectionId}" + } + stateMachine.sendEvent(VideoSocketStateEvent.ConnectionEstablished(connectedEvent)) + } + + /** + * Notify that an unrecoverable error happened. + * + * @param error The [Error.NetworkError] + */ + suspend fun onUnrecoverableError(error: Error.NetworkError) { + logger.e { "[onUnrecoverableError] error: $error" } + stateMachine.sendEvent(VideoSocketStateEvent.UnrecoverableError(error)) + } + + /** + * Notify that a network error happened. + * + * @param error The [Error.NetworkError] + */ + suspend fun onNetworkError(error: Error.NetworkError) { + logger.e { "[onNetworkError] error: $error" } + stateMachine.sendEvent(VideoSocketStateEvent.NetworkError(error)) + } + + /** + * Notify that the user want to disconnect the WebSocket connection. + */ + suspend fun onRequiredDisconnect() { + logger.i { "[onRequiredDisconnect] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.RequiredDisconnection) + } + + /** + * Notify that the connection should be stopped. + */ + suspend fun onStop() { + logger.i { "[onStop] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.Stop) + } + + /** + * Notify that some WebSocket Event has been lost. + */ + suspend fun onWebSocketEventLost() { + logger.w { "[onWebSocketEventLost] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.WebSocketEventLost) + } + + /** + * Notify that the network is available at the moment. + */ + suspend fun onNetworkAvailable() { + logger.i { "[onNetworkAvailable] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.NetworkAvailable) + } + + /** + * Notify that the connection should be resumed. + */ + suspend fun onResume() { + logger.v { "[onResume] no args" } + stateMachine.sendEvent(VideoSocketStateEvent.Resume) + } + + /** + * Current state of the WebSocket connection. + */ + val currentState: VideoSocketState + get() = stateMachine.state + + /** + * Current state of the WebSocket connection as [StateFlow]. + */ + val currentStateFlow: StateFlow + get() = stateMachine.stateFlow + + private val stateMachine: FiniteStateMachine by lazy { + FiniteStateMachine { + initialState(initialState) + + defaultHandler { state, event -> + logger.e { "Cannot handle event $event while being in inappropriate state $state" } + state + } + + state { + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.WebSocketEventLost + } + onEvent { + VideoSocketState.Disconnected.NetworkDisconnected + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + } + + state { + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.WebSocketEventLost + } + onEvent { + VideoSocketState.Disconnected.NetworkDisconnected + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + } + + state { + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.WebSocketEventLost + } + onEvent { + VideoSocketState.Disconnected.NetworkDisconnected + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + } + + state { + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.RestartConnection( + RestartReason.LIFECYCLE_RESUME, + ) + } + } + + state { + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + onEvent { + VideoSocketState.RestartConnection( + RestartReason.NETWORK_AVAILABLE, + ) + } + } + + state { + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.NetworkDisconnected + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + } + + state { + onEvent { currentState } + onEvent { + when (it.connectionType) { + VideoSocketConnectionType.INITIAL_CONNECTION -> VideoSocketState.Connecting( + it.connectionConf, + it.connectionType, + ) + VideoSocketConnectionType.AUTOMATIC_RECONNECTION -> this + VideoSocketConnectionType.FORCE_RECONNECTION -> VideoSocketState.Connecting( + it.connectionConf, + it.connectionType, + ) + } + } + } + + state { + onEvent { + VideoSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + VideoSocketState.Connected(it.connectedEvent) + } + onEvent { + VideoSocketState.Disconnected.NetworkDisconnected + } + onEvent { + VideoSocketState.Disconnected.WebSocketEventLost + } + onEvent { + VideoSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedTemporarily(it.error) + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + onEvent { VideoSocketState.Disconnected.Stopped } + } + + state { + onEvent { + when (it.connectionType) { + VideoSocketConnectionType.INITIAL_CONNECTION -> VideoSocketState.Connecting( + it.connectionConf, + it.connectionType, + ) + VideoSocketConnectionType.AUTOMATIC_RECONNECTION -> this + VideoSocketConnectionType.FORCE_RECONNECTION -> VideoSocketState.Connecting( + it.connectionConf, + it.connectionType, + ) + } + } + onEvent { + VideoSocketState.Disconnected.DisconnectedByRequest + } + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/RestartReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/RestartReason.kt new file mode 100644 index 0000000000..3e38d6d0b0 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/RestartReason.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator.state + +enum class RestartReason { + LIFECYCLE_RESUME, + NETWORK_AVAILABLE, +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType.kt new file mode 100644 index 0000000000..ce2c2ae653 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketConnectionType.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator.state + +enum class VideoSocketConnectionType { + INITIAL_CONNECTION, + AUTOMATIC_RECONNECTION, + FORCE_RECONNECTION, +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketState.kt new file mode 100644 index 0000000000..8baf122e0e --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketState.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator.state + +import io.getstream.result.Error +import io.getstream.video.android.core.socket.common.ConnectionConf +import org.openapitools.client.models.ConnectedEvent + +public sealed class VideoSocketState { + + /** + * State of socket when connection need to be reestablished. + */ + data class RestartConnection(val reason: RestartReason) : VideoSocketState() + + /** + * State of socket when connection is being establishing. + */ + data class Connecting( + val connectionConf: ConnectionConf, + val connectionType: VideoSocketConnectionType, + ) : VideoSocketState() + + /** + * State of socket when the connection is established. + */ + data class Connected(val event: ConnectedEvent) : VideoSocketState() + + /** + * State of socket when connection is being disconnected. + */ + sealed class Disconnected : VideoSocketState() { + + /** + * State of socket when is stopped. + */ + object Stopped : Disconnected() { override fun toString() = "Disconnected.Stopped" } + + /** + * State of socket when network is disconnected. + */ + object NetworkDisconnected : Disconnected() { override fun toString() = "Disconnected.Network" } + + /** + * State of socket when HealthEvent is lost. + */ + object WebSocketEventLost : Disconnected() { override fun toString() = "Disconnected.InactiveWS" } + + /** + * State of socket when is disconnected by customer request. + */ + object DisconnectedByRequest : Disconnected() { override fun toString() = "Disconnected.ByRequest" } + + /** + * State of socket when a [Error] happens. + */ + data class DisconnectedTemporarily(val error: Error.NetworkError) : Disconnected() + + /** + * State of socket when a connection is permanently disconnected. + */ + data class DisconnectedPermanently(val error: Error.NetworkError) : Disconnected() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent.kt new file mode 100644 index 0000000000..100706756b --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/coordinator/state/VideoSocketStateEvent.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.coordinator.state + +import io.getstream.result.Error +import io.getstream.video.android.core.socket.common.ConnectionConf +import org.openapitools.client.models.ConnectedEvent + +public sealed class VideoSocketStateEvent { + + /** + * Event to start a new connection. + */ + data class Connect( + val connectionConf: ConnectionConf, + val connectionType: VideoSocketConnectionType, + ) : VideoSocketStateEvent() + + /** + * Event to notify the connection was established. + */ + data class ConnectionEstablished(val connectedEvent: ConnectedEvent) : VideoSocketStateEvent() + + /** + * Event to notify some WebSocket event has been lost. + */ + object WebSocketEventLost : VideoSocketStateEvent() { override fun toString() = "WebSocketEventLost" } + + /** + * Event to notify Network is not available. + */ + object NetworkNotAvailable : VideoSocketStateEvent() { override fun toString() = "NetworkNotAvailable" } + + /** + * Event to notify Network is available. + */ + object NetworkAvailable : VideoSocketStateEvent() { override fun toString() = "NetworkAvailable" } + + /** + * Event to notify an Unrecoverable Error happened on the WebSocket connection. + */ + data class UnrecoverableError(val error: Error.NetworkError) : VideoSocketStateEvent() + + /** + * Event to notify a network Error happened on the WebSocket connection. + */ + data class NetworkError(val error: Error.NetworkError) : VideoSocketStateEvent() + + /** + * Event to stop WebSocket connection required by user. + */ + object RequiredDisconnection : VideoSocketStateEvent() { override fun toString() = "RequiredDisconnection" } + + /** + * Event to stop WebSocket connection. + */ + object Stop : VideoSocketStateEvent() { override fun toString() = "Stop" } + + /** + * Event to resume WebSocket connection. + */ + object Resume : VideoSocketStateEvent() { override fun toString() = "Resume" } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt new file mode 100644 index 0000000000..965f8ab554 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocket.kt @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu + +import io.getstream.log.taggedLogger +import io.getstream.result.Error +import io.getstream.video.android.core.dispatchers.DispatcherProvider +import io.getstream.video.android.core.errors.DisconnectCause +import io.getstream.video.android.core.errors.VideoErrorCode +import io.getstream.video.android.core.events.ErrorEvent +import io.getstream.video.android.core.events.JoinCallResponseEvent +import io.getstream.video.android.core.events.SFUHealthCheckEvent +import io.getstream.video.android.core.events.SfuDataEvent +import io.getstream.video.android.core.events.SfuDataRequest +import io.getstream.video.android.core.events.UnknownEvent +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.lifecycle.LifecycleHandler +import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver +import io.getstream.video.android.core.socket.common.ConnectionConf +import io.getstream.video.android.core.socket.common.HealthMonitor +import io.getstream.video.android.core.socket.common.SfuParser +import io.getstream.video.android.core.socket.common.SocketFactory +import io.getstream.video.android.core.socket.common.SocketListener +import io.getstream.video.android.core.socket.common.StreamWebSocket +import io.getstream.video.android.core.socket.common.StreamWebSocketEvent +import io.getstream.video.android.core.socket.common.fromVideoErrorCode +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.core.socket.common.token.TokenManager +import io.getstream.video.android.core.socket.coordinator.state.VideoSocketState +import io.getstream.video.android.core.socket.sfu.state.SfuSocketState +import io.getstream.video.android.core.utils.safeCallWithResult +import io.getstream.video.android.model.ApiKey +import io.getstream.video.android.model.User +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch +import kotlinx.coroutines.withTimeout +import stream.video.sfu.event.HealthCheckRequest +import stream.video.sfu.event.JoinRequest +import stream.video.sfu.event.SfuRequest +import stream.video.sfu.models.WebsocketReconnectStrategy +import java.util.UUID +import kotlin.coroutines.EmptyCoroutineContext + +internal open class SfuSocket( + private val wssUrl: String, + private val apiKey: ApiKey, + private val tokenManager: TokenManager, + private val socketFactory: + SocketFactory, + private val userScope: UserScope, + private val lifecycleObserver: StreamLifecycleObserver, + private val networkStateProvider: NetworkStateProvider, +) { + private var streamWebSocket: StreamWebSocket? = null + open val logger by taggedLogger(TAG) + private val socketId = safeCallWithResult { UUID.randomUUID().toString() } + private var connectionConf: ConnectionConf.SfuConnectionConf? = null + private val listeners = mutableSetOf>() + private val sfuSocketStateService = SfuSocketStateService() + private var socketStateObserverJob: Job? = null + private val healthMonitor = HealthMonitor( + userScope = userScope, + checkCallback = { + sendEvent( + SfuDataRequest( + SfuRequest(health_check_request = HealthCheckRequest()), + ), + ) + }, + reconnectCallback = { sfuSocketStateService.onWebSocketEventLost() }, + ) + private val lifecycleHandler = object : LifecycleHandler { + override suspend fun resume() { + sfuSocketStateService.onResume() + } + + override suspend fun stopped() { + healthMonitor.stop() + } + } + + private val networkStateListener = object : NetworkStateProvider.NetworkStateListener { + override suspend fun onConnected() { + sfuSocketStateService.onNetworkAvailable() + } + + override suspend fun onDisconnected() { + sfuSocketStateService.onNetworkNotAvailable() + } + } + + @Suppress("ComplexMethod") + private fun observeSocketStateService(): Job { + var socketListenerJob: Job? = null + + suspend fun connectUser(connectionConf: ConnectionConf.SfuConnectionConf) { + logger.d { "[connectUser] connectionConf: $connectionConf" } + userScope.launch { startObservers() } + this.connectionConf = connectionConf + socketListenerJob?.cancel() + when (networkStateProvider.isConnected()) { + true -> { + streamWebSocket = + socketFactory.createSocket(connectionConf, "#sfu").apply { + listeners.forEach { it.onCreated() } + + logger.d { "[connectUser] send join request = ${connectionConf.joinRequest}" } + send( + SfuDataRequest( + SfuRequest( + join_request = connectionConf.joinRequest, + ), + ), + ) + + socketListenerJob = listen().onEach { + when (it) { + is StreamWebSocketEvent.Error -> handleError(it) + is StreamWebSocketEvent.SfuMessage -> when ( + val event = + it.sfuEvent + ) { + is ErrorEvent -> handleError(event.toNetworkError()) + else -> handleEvent(event) + } + + else -> { + handleEvent(UnknownEvent(it)) + } + } + }.launchIn(userScope) + } + } + + false -> sfuSocketStateService.onNetworkNotAvailable() + } + } + + return userScope.launch { + sfuSocketStateService.observer { state -> + logger.i { "[onSocketStateChanged] state: $state" } + when (state) { + is SfuSocketState.RestartConnection -> { + connectionConf?.let { sfuSocketStateService.onReconnect(it) } + ?: run { + logger.e { "[onSocketStateChanged] #reconnect; connectionConf is null" } + } + } + + is SfuSocketState.Connected -> { + healthMonitor.ack() + callListeners { listener -> listener.onConnected(state.event) } + } + + is SfuSocketState.Connecting -> { + connectUser(state.connectionConf) + callListeners { listener -> listener.onConnecting() } + } + + is SfuSocketState.Disconnected -> { + when (state) { + is SfuSocketState.Disconnected.DisconnectedByRequest -> { + streamWebSocket?.close() + healthMonitor.stop() + userScope.launch { disposeObservers() } + } + + is SfuSocketState.Disconnected.NetworkDisconnected -> { + streamWebSocket?.close() + healthMonitor.stop() + } + + is SfuSocketState.Disconnected.Rejoin -> { + streamWebSocket?.close() + healthMonitor.stop() + disposeNetworkStateObserver() + } + + is SfuSocketState.Disconnected.Stopped -> { + streamWebSocket?.close() + healthMonitor.stop() + disposeNetworkStateObserver() + } + + is SfuSocketState.Disconnected.DisconnectedPermanently -> { + streamWebSocket?.close() + healthMonitor.stop() + userScope.launch { disposeObservers() } + } + + is SfuSocketState.Disconnected.DisconnectedTemporarily -> { + healthMonitor.onDisconnected() + } + + is SfuSocketState.Disconnected.WebSocketEventLost -> { + streamWebSocket?.close() + connectionConf?.let { + sfuSocketStateService.onReconnect( + it, + ) + } + } + } + } + } + } + } + } + + suspend fun connect(joinRequest: JoinRequest) { + logger.d { "[connect] request: ${joinRequest.client_details}" } + socketStateObserverJob?.cancel() + socketStateObserverJob = observeSocketStateService() + sfuSocketStateService.onConnect( + ConnectionConf.SfuConnectionConf( + wssUrl, + apiKey, + User.anonymous(), + joinRequest, + tokenManager.getToken(), + ), + ) + } + + suspend fun disconnect() { + logger.d { "[disconnect] no args" } + connectionConf = null + sfuSocketStateService.onRequiredDisconnect() + } + + private suspend fun handleEvent(sfuEvent: SfuDataEvent) { + when (sfuEvent) { + is JoinCallResponseEvent -> sfuSocketStateService.onConnectionEstablished(sfuEvent) + is SFUHealthCheckEvent -> { + if (isConnected()) { + healthMonitor.ack() + } + } + + else -> { + // Ignore, maybe handled on upper levels. + } + } + listeners.forEach { listener -> listener.onEvent(sfuEvent) } + } + + private suspend fun startObservers() { + lifecycleObserver.observe(lifecycleHandler) + networkStateProvider.subscribe(networkStateListener) + } + + private suspend fun disposeObservers() { + lifecycleObserver.dispose(lifecycleHandler) + disposeNetworkStateObserver() + } + + private fun disposeNetworkStateObserver() { + networkStateProvider.unsubscribe(networkStateListener) + } + + private suspend fun handleError(error: StreamWebSocketEvent.Error) { + logger.e { "[handleError] error: $error" } + when (error.streamError) { + is Error.NetworkError -> onVideoNetworkError(error.streamError, error.reconnectStrategy) + else -> callListeners { it.onError(error) } + } + } + + private suspend fun onVideoNetworkError( + error: Error.NetworkError, + reconnectStrategy: WebsocketReconnectStrategy?, + ) { + if (VideoErrorCode.isAuthenticationError(error.serverErrorCode)) { + tokenManager.expireToken() + } + + when (error.serverErrorCode) { + VideoErrorCode.UNDEFINED_TOKEN.code, + VideoErrorCode.INVALID_TOKEN.code, + VideoErrorCode.API_KEY_NOT_FOUND.code, + VideoErrorCode.VALIDATION_ERROR.code, + VideoErrorCode.SOCKET_CLOSED.code, + -> { + logger.d { + "One unrecoverable error happened. Error: $error. Error code: ${error.serverErrorCode}" + } + sfuSocketStateService.onUnrecoverableError(error) + } + + else -> sfuSocketStateService.onNetworkError( + error, + reconnectStrategy + ?: WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, + ) + } + } + + fun removeListener(listener: SocketListener) { + synchronized(listeners) { + listeners.remove(listener) + } + } + + fun addListener(listener: SocketListener) { + synchronized(listeners) { + listeners.add(listener) + } + } + + fun state() = sfuSocketStateService.currentStateFlow + + /** + * Attempt to send [event] to the web socket connection. + * Returns true only if socket is connected and [okhttp3.WebSocket.send] returns true, otherwise false + * + * @see [okhttp3.WebSocket.send] + */ + /** + * Attempt to send [event] to the web socket connection. + * Returns true only if socket is connected and [okhttp3.WebSocket.send] returns true, otherwise false + * + * @see [okhttp3.WebSocket.send] + */ + internal fun sendEvent(event: SfuDataRequest): Boolean { + logger.d { "[sendEvent] event: $event" } + return streamWebSocket?.send(event) ?: false + } + + /** + * Send raw data to the web socket connection. + */ + internal fun sendRawData(data: String) { + logger.d { "[sendRawData] data: $data" } + streamWebSocket?.sendRaw(data) ?: Unit + } + + internal fun isConnected(): Boolean = + sfuSocketStateService.currentState is SfuSocketState.Connected + + /** + * Awaits until [VideoSocketState.Connected] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + /** + * Awaits until [VideoSocketState.Connected] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + internal suspend fun awaitConnection(timeoutInMillis: Long = DEFAULT_CONNECTION_TIMEOUT) { + awaitState(timeoutInMillis) + } + + /** + * Awaits until specified [VideoSocketState] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + /** + * Awaits until specified [VideoSocketState] is set. + * + * @param timeoutInMillis Timeout time in milliseconds. + */ + internal suspend inline fun awaitState(timeoutInMillis: Long) { + withTimeout(timeoutInMillis) { + sfuSocketStateService.currentStateFlow.first { it is T } + } + } + + /** + * Get connection id of this connection. + */ + /** + * Get connection id of this connection. + */ + internal fun connectionIdOrError(): String = + when (sfuSocketStateService.currentState) { + is SfuSocketState.Connected -> socketId.getOrThrow() + else -> error("This state doesn't contain connectionId") + } + + private fun callListeners(call: (SocketListener) -> Unit) { + synchronized(listeners) { + listeners.forEach { listener -> + val context = if (listener.deliverOnMainThread) { + DispatcherProvider.Main + } else { + EmptyCoroutineContext + } + userScope.launch(context) { call(listener) } + } + } + } + + private val SfuSocketState.Disconnected.cause + get() = when (this) { + is SfuSocketState.Disconnected.DisconnectedByRequest, + is SfuSocketState.Disconnected.Stopped, + -> DisconnectCause.ConnectionReleased + + is SfuSocketState.Disconnected.NetworkDisconnected -> DisconnectCause.NetworkNotAvailable + is SfuSocketState.Disconnected.DisconnectedPermanently -> DisconnectCause.UnrecoverableError( + error, + ) + + is SfuSocketState.Disconnected.DisconnectedTemporarily -> DisconnectCause.Error(error) + is SfuSocketState.Disconnected.WebSocketEventLost -> DisconnectCause.WebSocketNotAvailable + SfuSocketState.Disconnected.Rejoin -> DisconnectCause.ConnectionReleased + } + + private fun ErrorEvent.toNetworkError(): StreamWebSocketEvent.Error { + val error = error?.let { + Error.NetworkError( + message = it.message, + serverErrorCode = it.code.value, + statusCode = it.code.value, + ) + } ?: Error.NetworkError.fromVideoErrorCode(VideoErrorCode.NO_ERROR_BODY, cause = null) + return StreamWebSocketEvent.Error(error, reconnectStrategy) + } + + companion object { + private const val TAG = "Video:SfuSocket" + private const val DEFAULT_CONNECTION_TIMEOUT = 2000L + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt new file mode 100644 index 0000000000..404a95d816 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketConnection.kt @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu + +import androidx.lifecycle.Lifecycle +import io.getstream.log.taggedLogger +import io.getstream.video.android.core.errors.DisconnectCause +import io.getstream.video.android.core.events.JoinCallResponseEvent +import io.getstream.video.android.core.events.SfuDataEvent +import io.getstream.video.android.core.events.SfuDataRequest +import io.getstream.video.android.core.internal.network.NetworkStateProvider +import io.getstream.video.android.core.lifecycle.StreamLifecycleObserver +import io.getstream.video.android.core.socket.common.SfuParser +import io.getstream.video.android.core.socket.common.SocketActions +import io.getstream.video.android.core.socket.common.SocketFactory +import io.getstream.video.android.core.socket.common.SocketListener +import io.getstream.video.android.core.socket.common.StreamWebSocketEvent +import io.getstream.video.android.core.socket.common.scope.ClientScope +import io.getstream.video.android.core.socket.common.scope.UserScope +import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenManagerImpl +import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.socket.sfu.state.SfuSocketState +import io.getstream.video.android.core.utils.mapState +import io.getstream.video.android.model.ApiKey +import io.getstream.video.android.model.SfuToken +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import okhttp3.OkHttpClient +import stream.video.sfu.event.JoinRequest + +class SfuSocketConnection( + private val apiKey: ApiKey, + /** The URL to connect to */ + private val url: String, + /** Inject your http client */ + private val httpClient: OkHttpClient, + /** Inject your network state provider */ + private val networkStateProvider: NetworkStateProvider, + /** Set the scope everything should run in */ + private val scope: CoroutineScope = UserScope(ClientScope()), + /** Lifecycle */ + private val lifecycle: Lifecycle, + /** Token provider */ + private val tokenProvider: TokenProvider, +) : SocketListener(), + SocketActions { + + companion object { + internal const val DEFAULT_SFU_SOCKET_TIMEOUT: Long = 10000L + } + + private val logger by taggedLogger("Video:SfuSocket") + private val tokenManager = TokenManagerImpl() + private val internalSocket: SfuSocket = SfuSocket( + wssUrl = url, + apiKey = apiKey, + tokenManager = tokenManager, + socketFactory = SocketFactory( + parser = object : SfuParser {}, + httpClient = httpClient, + ), + lifecycleObserver = StreamLifecycleObserver(scope, lifecycle), + networkStateProvider = networkStateProvider, + userScope = scope as? UserScope ?: UserScope(ClientScope()), + ).also { + it.addListener(this) + } + private val state: StateFlow = internalSocket.state() + private val events: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.SUSPEND, + replay = 1, + extraBufferCapacity = 100, + ) + private val errors: MutableSharedFlow = MutableSharedFlow( + onBufferOverflow = BufferOverflow.SUSPEND, + replay = 1, + extraBufferCapacity = 100, + ) + private val connectionId: MutableStateFlow = MutableStateFlow(null) + + // Initialization + init { + tokenManager.setTokenProvider(CacheableTokenProvider(tokenProvider)) + } + + override fun onCreated() { + super.onCreated() + logger.d { "[onCreated] Socket is created" } + } + + override fun onConnecting() { + super.onConnecting() + logger.d { "[onConnecting] Socket is connecting" } + } + + override fun onConnected(event: JoinCallResponseEvent) { + super.onConnected(event) + logger.d { "[onConnected] Socket connected with event: $event" } + } + + override fun onEvent(event: SfuDataEvent) { + super.onEvent(event) + logger.d { "[onEvent] Received event: $event" } + val emit = events.tryEmit(event) + if (!emit) { + logger.e { "[onEvent] Failed to emit event: $event" } + } + } + + override fun onError(error: StreamWebSocketEvent.Error) { + super.onError(error) + logger.e { "[onError] Socket error: $error" } + val emit = errors.tryEmit(error) + if (!emit) { + logger.e { "[onError] Failed to emit error: $error" } + } + } + + override fun onDisconnected(cause: DisconnectCause) { + super.onDisconnected(cause) + logger.d { + "[onDisconnected] Socket disconnected. Cause: ${(cause as? DisconnectCause.Error)?.error}" + } + } + + override fun whenConnected( + connectionTimeout: Long, + connected: suspend (connectionId: String) -> Unit, + ) { + scope.launch { + internalSocket.awaitConnection(connectionTimeout) + internalSocket.connectionIdOrError().also { + connected(it) + } + } + } + + override suspend fun connect(connectData: JoinRequest) { + logger.d { "[connect] request: $connectData" } + internalSocket.connect(connectData) + } + + override suspend fun disconnect() { + logger.d { "[disconnect] Disconnecting socket" } + internalSocket.disconnect() + } + + override fun updateToken(token: SfuToken) { + throw UnsupportedOperationException( + "Update token is not supported for SFU. Create a new socket instead.", + ) + } + + override suspend fun reconnect(data: JoinRequest, force: Boolean) { + if (data.reconnect_details == null) { + logger.w { "[reconnect] Reconnect details are missing!" } + } + internalSocket.connect(data) + } + + override fun state(): StateFlow = state + + override fun events(): MutableSharedFlow = events + + override fun errors(): MutableSharedFlow = errors + + override fun sendData(data: String) = internalSocket.sendRawData(data) + + override suspend fun sendEvent(event: SfuDataRequest): Boolean = internalSocket.sendEvent(event) + + override fun connectionId(): StateFlow = connectionId.mapState { + it + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketStateService.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketStateService.kt new file mode 100644 index 0000000000..e481e6a297 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/SfuSocketStateService.kt @@ -0,0 +1,376 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu + +import io.getstream.log.taggedLogger +import io.getstream.result.Error +import io.getstream.video.android.core.events.JoinCallResponseEvent +import io.getstream.video.android.core.socket.common.ConnectionConf +import io.getstream.video.android.core.socket.common.fsm.FiniteStateMachine +import io.getstream.video.android.core.socket.sfu.state.RestartReason +import io.getstream.video.android.core.socket.sfu.state.SfuSocketState +import io.getstream.video.android.core.socket.sfu.state.SfuSocketStateEvent +import kotlinx.coroutines.flow.StateFlow +import org.openapitools.client.models.ConnectedEvent +import stream.video.sfu.models.WebsocketReconnectStrategy + +internal class SfuSocketStateService(initialState: SfuSocketState = SfuSocketState.Disconnected.Stopped) { + + private val logger by taggedLogger("Video:SfuSocketState") + + suspend fun observer(onNewState: suspend (SfuSocketState) -> Unit) { + stateMachine.stateFlow.collect(onNewState) + } + + /** + * Require a reconnection. + * + * @param connectionConf The [SocketFactory.ConnectionConf] to be used to reconnect. + */ + suspend fun onReconnect( + connectionConf: ConnectionConf.SfuConnectionConf, + ) { + logger.v { + "[onReconnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" + } + stateMachine.sendEvent( + SfuSocketStateEvent.Connect( + connectionConf, + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_FAST, + ), + ) + } + + /** + * Require connection. + * + * @param connectionConf The [VideoSocketFactory.ConnectionConf] to be used on the new connection. + */ + suspend fun onConnect(connectionConf: ConnectionConf.SfuConnectionConf) { + logger.v { + "[onConnect] user.id: '${connectionConf.user.id}', isReconnection: ${connectionConf.isReconnection}" + } + stateMachine.sendEvent( + SfuSocketStateEvent.Connect( + connectionConf, + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, + ), + ) + } + + /** + * Notify that the network is not available at the moment. + */ + suspend fun onNetworkNotAvailable() { + logger.w { "[onNetworkNotAvailable] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.NetworkNotAvailable) + } + + /** + * Notify the WebSocket connection has been established. + * + * @param connectedEvent The [ConnectedEvent] received within the WebSocket connection. + */ + suspend fun onConnectionEstablished(connectedEvent: JoinCallResponseEvent) { + logger.i { + "[onConnected] client.id: '$connectedEvent'" + } + stateMachine.sendEvent(SfuSocketStateEvent.ConnectionEstablished(connectedEvent)) + } + + /** + * Notify that an unrecoverable error happened. + * + * @param error The [Error.NetworkError] + */ + suspend fun onUnrecoverableError(error: Error.NetworkError) { + logger.e { "[onUnrecoverableError] error: $error" } + stateMachine.sendEvent(SfuSocketStateEvent.UnrecoverableError(error)) + } + + /** + * Notify that a network error happened. + * + * @param error The [Error.NetworkError] + */ + suspend fun onNetworkError( + error: Error.NetworkError, + reconnectStrategy: WebsocketReconnectStrategy, + ) { + logger.e { "[onNetworkError] error: $error" } + stateMachine.sendEvent(SfuSocketStateEvent.NetworkError(error, reconnectStrategy)) + } + + /** + * Notify that the user want to disconnect the WebSocket connection. + */ + suspend fun onRequiredDisconnect() { + logger.i { "[onRequiredDisconnect] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.RequiredDisconnection) + } + + /** + * Notify that the connection should be stopped. + */ + suspend fun onStop() { + logger.i { "[onStop] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.Stop) + } + + /** + * Notify that some WebSocket Event has been lost. + */ + suspend fun onWebSocketEventLost() { + logger.w { "[onWebSocketEventLost] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.WebSocketEventLost) + } + + /** + * Notify that the network is available at the moment. + */ + suspend fun onNetworkAvailable() { + logger.i { "[onNetworkAvailable] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.NetworkAvailable) + } + + /** + * Notify that the connection should be resumed. + */ + suspend fun onResume() { + logger.v { "[onResume] no args" } + stateMachine.sendEvent(SfuSocketStateEvent.Resume) + } + + /** + * Current state of the WebSocket connection. + */ + val currentState: SfuSocketState + get() = stateMachine.state + + /** + * Current state of the WebSocket connection as [StateFlow]. + */ + val currentStateFlow: StateFlow + get() = stateMachine.stateFlow + + private val stateMachine: FiniteStateMachine by lazy { + FiniteStateMachine { + initialState(initialState) + + defaultHandler { state, event -> + logger.e { "Cannot handle event $event while being in inappropriate state $state" } + state + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { SfuSocketState.Disconnected.WebSocketEventLost } + onEvent { SfuSocketState.Disconnected.NetworkDisconnected } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { SfuSocketState.Disconnected.WebSocketEventLost } + onEvent { SfuSocketState.Disconnected.NetworkDisconnected } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + } + + state { + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { SfuSocketState.Disconnected.WebSocketEventLost } + onEvent { SfuSocketState.Disconnected.NetworkDisconnected } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + } + + state { + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + onEvent { + SfuSocketState.RestartConnection( + RestartReason.NETWORK_AVAILABLE, + WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_REJOIN, + ) + } + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { SfuSocketState.Disconnected.NetworkDisconnected } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + } + + state { + onEvent { currentState } + onEvent { + SfuSocketState.Connecting( + it.connectionConf, + it.connectionType, + ) + } + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Connected( + it.connectedEvent, + ) + } + onEvent { SfuSocketState.Disconnected.NetworkDisconnected } + onEvent { SfuSocketState.Disconnected.WebSocketEventLost } + onEvent { + SfuSocketState.Disconnected.DisconnectedPermanently( + it.error, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedTemporarily( + it.error, + it.reconnectStrategy, + ) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + onEvent { SfuSocketState.Disconnected.Stopped } + } + + state { + onEvent { + SfuSocketState.Connecting(it.connectionConf, it.connectionType) + } + onEvent { + SfuSocketState.Disconnected.DisconnectedByRequest + } + } + } + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/RestartReason.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/RestartReason.kt new file mode 100644 index 0000000000..8a2cac9d1f --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/RestartReason.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu.state + +enum class RestartReason { + RECONNECT_STRATEGY, + NETWORK_AVAILABLE, +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketState.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketState.kt new file mode 100644 index 0000000000..2d04eacc0d --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketState.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu.state + +import io.getstream.result.Error +import io.getstream.video.android.core.events.JoinCallResponseEvent +import io.getstream.video.android.core.socket.common.ConnectionConf +import stream.video.sfu.models.WebsocketReconnectStrategy + +public sealed class SfuSocketState { + + /** + * State of socket when connection need to be reestablished. + */ + data class RestartConnection( + val reason: RestartReason, + val reconnectStrategy: WebsocketReconnectStrategy, + ) : SfuSocketState() + + /** + * State of socket when connection is being establishing. + */ + data class Connecting( + val connectionConf: ConnectionConf.SfuConnectionConf, + val connectionType: WebsocketReconnectStrategy = WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, + ) : SfuSocketState() + + /** + * State of socket when the connection is established. + */ + data class Connected(val event: JoinCallResponseEvent) : SfuSocketState() + + /** + * State of socket when connection is being disconnected. + */ + sealed class Disconnected : SfuSocketState() { + + object Rejoin : Disconnected() { override fun toString() = "Disconnected.Rejoin" } + + /** + * State of socket when is stopped. + */ + object Stopped : Disconnected() { override fun toString() = "Disconnected.Stopped" } + + /** + * State of socket when network is disconnected. + */ + object NetworkDisconnected : Disconnected() { override fun toString() = "Disconnected.Network" } + + /** + * State of socket when HealthEvent is lost. + */ + object WebSocketEventLost : Disconnected() { override fun toString() = "Disconnected.InactiveWS" } + + /** + * State of socket when is disconnected by customer request. + */ + object DisconnectedByRequest : Disconnected() { override fun toString() = "Disconnected.ByRequest" } + + /** + * State of socket when a [Error] happens. + */ + data class DisconnectedTemporarily( + val error: Error.NetworkError, + val reconnectStrategy: WebsocketReconnectStrategy, + ) : Disconnected() + + /** + * State of socket when a connection is permanently disconnected. + */ + data class DisconnectedPermanently(val error: Error.NetworkError) : Disconnected() + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent.kt new file mode 100644 index 0000000000..72f3702ab2 --- /dev/null +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/socket/sfu/state/SfuSocketStateEvent.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.socket.sfu.state + +import io.getstream.result.Error +import io.getstream.video.android.core.events.JoinCallResponseEvent +import io.getstream.video.android.core.socket.common.ConnectionConf +import stream.video.sfu.models.WebsocketReconnectStrategy + +public sealed class SfuSocketStateEvent { + + /** + * Event to start a new connection. + */ + data class Connect( + val connectionConf: ConnectionConf.SfuConnectionConf, + val connectionType: WebsocketReconnectStrategy = WebsocketReconnectStrategy.WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED, + ) : SfuSocketStateEvent() + + /** + * Event to notify the connection was established. + */ + data class ConnectionEstablished( + val connectedEvent: JoinCallResponseEvent, + ) : SfuSocketStateEvent() + + /** + * Event to notify some WebSocket event has been lost. + */ + object WebSocketEventLost : SfuSocketStateEvent() { override fun toString() = "WebSocketEventLost" } + + /** + * Event to notify Network is not available. + */ + object NetworkNotAvailable : SfuSocketStateEvent() { override fun toString() = "NetworkNotAvailable" } + + /** + * Event to notify Network is available. + */ + object NetworkAvailable : SfuSocketStateEvent() { override fun toString() = "NetworkAvailable" } + + /** + * Event to notify an Unrecoverable Error happened on the WebSocket connection. + */ + data class UnrecoverableError(val error: Error.NetworkError) : SfuSocketStateEvent() + + /** + * Event to notify a network Error happened on the WebSocket connection. + */ + data class NetworkError( + val error: Error.NetworkError, + val reconnectStrategy: WebsocketReconnectStrategy, + ) : SfuSocketStateEvent() + + /** + * Event to stop WebSocket connection required by user. + */ + object RequiredDisconnection : SfuSocketStateEvent() { override fun toString() = "RequiredDisconnection" } + + /** + * Event to stop WebSocket connection. + */ + object Stop : SfuSocketStateEvent() { override fun toString() = "Stop" } + + /** + * Event to resume WebSocket connection. + */ + object Resume : SfuSocketStateEvent() { override fun toString() = "Resume" } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/RingingConfig.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/RingingConfig.kt index 9042748f31..bd74e6bbe8 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/RingingConfig.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/sounds/RingingConfig.kt @@ -22,7 +22,7 @@ import android.net.Uri import androidx.annotation.RawRes import io.getstream.log.StreamLog import io.getstream.video.android.core.R -import io.getstream.video.android.core.utils.safeCall +import io.getstream.video.android.core.utils.safeCallWithDefault import org.jetbrains.annotations.ApiStatus // Interface & API @@ -80,7 +80,7 @@ public fun defaultResourcesRingingConfig(context: Context): RingingConfig = obje public fun deviceRingtoneRingingConfig(context: Context): RingingConfig = object : RingingConfig { private val streamResSoundConfig = defaultResourcesRingingConfig(context) override val incomingCallSoundUri: Uri? - get() = safeCall(default = null) { + get() = safeCallWithDefault(default = null) { RingtoneManager.getActualDefaultRingtoneUri( context, RingtoneManager.TYPE_RINGTONE, @@ -134,7 +134,7 @@ public fun RingingConfig.toSounds() = Sounds(this) // Internal utilities private fun Int?.toUriOrNUll(context: Context): Uri? = - safeCall(default = null) { + safeCallWithDefault(default = null) { if (this != null) { Uri.parse("android.resource://${context.packageName}/$this") } else { diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/AndroidUtils.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/AndroidUtils.kt index 4073ec7e95..7b91b1e030 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/AndroidUtils.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/core/utils/AndroidUtils.kt @@ -34,6 +34,8 @@ import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat import androidx.core.app.ServiceCompat import io.getstream.log.StreamLog +import io.getstream.result.Error +import io.getstream.result.Result import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_INCOMING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_ONGOING_CALL import io.getstream.video.android.core.notifications.internal.service.CallService.Companion.TRIGGER_OUTGOING_CALL @@ -139,16 +141,20 @@ internal inline fun safeCall(block: () -> Unit) { * @param default the default value to return in case of an exception. * @param block the suspending function to call. */ -internal suspend fun safeSuspendingCall(default: T, block: suspend () -> T): T { +internal suspend fun safeSuspendingCallWithDefault( + default: T, + defaultProvider: ((exception: Throwable) -> T)? = null, + block: suspend () -> T, +): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return try { block() - } catch (e: Exception) { + } catch (e: Throwable) { // Handle or log the exception here StreamLog.e("SafeSuspendingCall", e) { "Exception occurred: ${e.message}" } - default + safeCallWithDefault(default) { defaultProvider?.invoke(e) ?: default } } } @@ -158,7 +164,7 @@ internal suspend fun safeSuspendingCall(default: T, block: suspend () -> T): * @param default the default value to return in case of an exception. * @param block the function to call. */ -inline fun safeCall(default: T, block: () -> T): T { +internal inline fun safeCallWithDefault(default: T, block: () -> T): T { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } @@ -206,3 +212,35 @@ internal fun Service.startForegroundWithServiceType( ) } } + +/** + * Safely call a function and handle exceptions while returning a [Result]. + */ +internal inline fun safeCallWithResult(block: () -> T): Result { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return try { + Result.Success(block()) + } catch (e: Exception) { + // Handle or log the exception here + StreamLog.e("SafeCall", e) { "Exception occurred: ${e.message}" } + Result.Failure(Error.ThrowableError("Safe call failed", e)) + } +} + +/** + * Safely call a function and handle exceptions while returning a [Result]. + */ +internal suspend fun safeSuspendingCallWithResult(block: suspend () -> T): Result { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return try { + Result.Success(block()) + } catch (e: Exception) { + // Handle or log the exception here + StreamLog.e("SafeCall", e) { "Exception occurred: ${e.message}" } + Result.Failure(Error.ThrowableError("Safe call failed", e)) + } +} diff --git a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/User.kt b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/User.kt index 456f70eaa6..a916dcadd2 100644 --- a/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/User.kt +++ b/stream-video-android-core/src/main/kotlin/io/getstream/video/android/model/User.kt @@ -91,6 +91,8 @@ public data class User( inline get() = name.takeUnless { it.isNullOrBlank() } ?: id companion object { + /** Check if user is anonymous. */ + fun User.isAnonymous(): Boolean = id == "!anon" /** Get the anonymous user. */ fun anonymous() = User(id = "!anon", type = UserType.Anonymous, role = "user") diff --git a/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/VideoEvent.kt b/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/VideoEvent.kt index 480557f5d4..a52e6bb28a 100644 --- a/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/VideoEvent.kt +++ b/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/VideoEvent.kt @@ -164,9 +164,15 @@ class VideoEventAdapter : JsonAdapter() { "user.reactivated" -> UserReactivatedEvent::class.java "user.unbanned" -> UserUnbannedEvent::class.java "user.updated" -> UserUpdatedEvent::class.java - else -> throw UnsupportedVideoEventException(type) + else -> UnsupportedVideoEvent::class.java } } } +class UnsupportedVideoEvent(val type: String) : VideoEvent() { + override fun getEventType(): String { + return type + } +} + class UnsupportedVideoEventException(val type: String) : Exception() diff --git a/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/WSAuthMessageRequest.kt b/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/WSAuthMessageRequest.kt index 37051ff3b5..14d56acb06 100644 --- a/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/WSAuthMessageRequest.kt +++ b/stream-video-android-core/src/main/kotlin/org/openapitools/client/models/WSAuthMessageRequest.kt @@ -52,4 +52,6 @@ data class WSAuthMessageRequest ( @Json(name = "user_details") val userDetails: ConnectUserDetailsRequest -) +) : VideoEvent() { + override fun getEventType(): String = "authenticate" +} diff --git a/stream-video-android-core/src/main/proto/video/sfu/event/events.pb.go b/stream-video-android-core/src/main/proto/video/sfu/event/events.pb.go index 1f5182959e..b9c14620f3 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/event/events.pb.go +++ b/stream-video-android-core/src/main/proto/video/sfu/event/events.pb.go @@ -71,7 +71,7 @@ func (x VideoLayerSetting_Priority) Number() protoreflect.EnumNumber { // Deprecated: Use VideoLayerSetting_Priority.Descriptor instead. func (VideoLayerSetting_Priority) EnumDescriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{26, 0} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{29, 0} } // SFUEvent is a message that is sent from the SFU to the client. @@ -102,6 +102,7 @@ type SfuEvent struct { // *SfuEvent_PinsUpdated // *SfuEvent_CallEnded // *SfuEvent_ParticipantUpdated + // *SfuEvent_ParticipantMigrationComplete EventPayload isSfuEvent_EventPayload `protobuf_oneof:"event_payload"` } @@ -284,6 +285,13 @@ func (x *SfuEvent) GetParticipantUpdated() *ParticipantUpdated { return nil } +func (x *SfuEvent) GetParticipantMigrationComplete() *ParticipantMigrationComplete { + if x, ok := x.GetEventPayload().(*SfuEvent_ParticipantMigrationComplete); ok { + return x.ParticipantMigrationComplete + } + return nil +} + type isSfuEvent_EventPayload interface { isSfuEvent_EventPayload() } @@ -410,6 +418,11 @@ type SfuEvent_ParticipantUpdated struct { ParticipantUpdated *ParticipantUpdated `protobuf:"bytes,24,opt,name=participant_updated,json=participantUpdated,proto3,oneof"` } +type SfuEvent_ParticipantMigrationComplete struct { + // ParticipantMigrationComplete is sent when the participant migration is complete + ParticipantMigrationComplete *ParticipantMigrationComplete `protobuf:"bytes,25,opt,name=participant_migration_complete,json=participantMigrationComplete,proto3,oneof"` +} + func (*SfuEvent_SubscriberOffer) isSfuEvent_EventPayload() {} func (*SfuEvent_PublisherAnswer) isSfuEvent_EventPayload() {} @@ -450,6 +463,46 @@ func (*SfuEvent_CallEnded) isSfuEvent_EventPayload() {} func (*SfuEvent_ParticipantUpdated) isSfuEvent_EventPayload() {} +func (*SfuEvent_ParticipantMigrationComplete) isSfuEvent_EventPayload() {} + +type ParticipantMigrationComplete struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ParticipantMigrationComplete) Reset() { + *x = ParticipantMigrationComplete{} + if protoimpl.UnsafeEnabled { + mi := &file_video_sfu_event_events_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ParticipantMigrationComplete) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ParticipantMigrationComplete) ProtoMessage() {} + +func (x *ParticipantMigrationComplete) ProtoReflect() protoreflect.Message { + mi := &file_video_sfu_event_events_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ParticipantMigrationComplete.ProtoReflect.Descriptor instead. +func (*ParticipantMigrationComplete) Descriptor() ([]byte, []int) { + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{1} +} + type PinsChanged struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -463,7 +516,7 @@ type PinsChanged struct { func (x *PinsChanged) Reset() { *x = PinsChanged{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[1] + mi := &file_video_sfu_event_events_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -476,7 +529,7 @@ func (x *PinsChanged) String() string { func (*PinsChanged) ProtoMessage() {} func (x *PinsChanged) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[1] + mi := &file_video_sfu_event_events_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -489,7 +542,7 @@ func (x *PinsChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use PinsChanged.ProtoReflect.Descriptor instead. func (*PinsChanged) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{1} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{2} } func (x *PinsChanged) GetPins() []*models.Pin { @@ -512,7 +565,7 @@ type Error struct { func (x *Error) Reset() { *x = Error{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[2] + mi := &file_video_sfu_event_events_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -525,7 +578,7 @@ func (x *Error) String() string { func (*Error) ProtoMessage() {} func (x *Error) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[2] + mi := &file_video_sfu_event_events_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -538,7 +591,7 @@ func (x *Error) ProtoReflect() protoreflect.Message { // Deprecated: Use Error.ProtoReflect.Descriptor instead. func (*Error) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{2} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{3} } func (x *Error) GetError() *models.Error { @@ -567,7 +620,7 @@ type ICETrickle struct { func (x *ICETrickle) Reset() { *x = ICETrickle{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[3] + mi := &file_video_sfu_event_events_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -580,7 +633,7 @@ func (x *ICETrickle) String() string { func (*ICETrickle) ProtoMessage() {} func (x *ICETrickle) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[3] + mi := &file_video_sfu_event_events_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -593,7 +646,7 @@ func (x *ICETrickle) ProtoReflect() protoreflect.Message { // Deprecated: Use ICETrickle.ProtoReflect.Descriptor instead. func (*ICETrickle) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{3} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{4} } func (x *ICETrickle) GetPeerType() models.PeerType { @@ -621,7 +674,7 @@ type ICERestart struct { func (x *ICERestart) Reset() { *x = ICERestart{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[4] + mi := &file_video_sfu_event_events_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -634,7 +687,7 @@ func (x *ICERestart) String() string { func (*ICERestart) ProtoMessage() {} func (x *ICERestart) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[4] + mi := &file_video_sfu_event_events_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -647,7 +700,7 @@ func (x *ICERestart) ProtoReflect() protoreflect.Message { // Deprecated: Use ICERestart.ProtoReflect.Descriptor instead. func (*ICERestart) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{4} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{5} } func (x *ICERestart) GetPeerType() models.PeerType { @@ -667,13 +720,14 @@ type SfuRequest struct { // // *SfuRequest_JoinRequest // *SfuRequest_HealthCheckRequest + // *SfuRequest_LeaveCallRequest RequestPayload isSfuRequest_RequestPayload `protobuf_oneof:"request_payload"` } func (x *SfuRequest) Reset() { *x = SfuRequest{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[5] + mi := &file_video_sfu_event_events_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -686,7 +740,7 @@ func (x *SfuRequest) String() string { func (*SfuRequest) ProtoMessage() {} func (x *SfuRequest) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[5] + mi := &file_video_sfu_event_events_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -699,7 +753,7 @@ func (x *SfuRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SfuRequest.ProtoReflect.Descriptor instead. func (*SfuRequest) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{5} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{6} } func (m *SfuRequest) GetRequestPayload() isSfuRequest_RequestPayload { @@ -723,6 +777,13 @@ func (x *SfuRequest) GetHealthCheckRequest() *HealthCheckRequest { return nil } +func (x *SfuRequest) GetLeaveCallRequest() *LeaveCallRequest { + if x, ok := x.GetRequestPayload().(*SfuRequest_LeaveCallRequest); ok { + return x.LeaveCallRequest + } + return nil +} + type isSfuRequest_RequestPayload interface { isSfuRequest_RequestPayload() } @@ -735,10 +796,71 @@ type SfuRequest_HealthCheckRequest struct { HealthCheckRequest *HealthCheckRequest `protobuf:"bytes,2,opt,name=health_check_request,json=healthCheckRequest,proto3,oneof"` } +type SfuRequest_LeaveCallRequest struct { + LeaveCallRequest *LeaveCallRequest `protobuf:"bytes,3,opt,name=leave_call_request,json=leaveCallRequest,proto3,oneof"` +} + func (*SfuRequest_JoinRequest) isSfuRequest_RequestPayload() {} func (*SfuRequest_HealthCheckRequest) isSfuRequest_RequestPayload() {} +func (*SfuRequest_LeaveCallRequest) isSfuRequest_RequestPayload() {} + +type LeaveCallRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SessionId string `protobuf:"bytes,1,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + Reason string `protobuf:"bytes,2,opt,name=reason,proto3" json:"reason,omitempty"` +} + +func (x *LeaveCallRequest) Reset() { + *x = LeaveCallRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_video_sfu_event_events_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *LeaveCallRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LeaveCallRequest) ProtoMessage() {} + +func (x *LeaveCallRequest) ProtoReflect() protoreflect.Message { + mi := &file_video_sfu_event_events_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LeaveCallRequest.ProtoReflect.Descriptor instead. +func (*LeaveCallRequest) Descriptor() ([]byte, []int) { + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{7} +} + +func (x *LeaveCallRequest) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *LeaveCallRequest) GetReason() string { + if x != nil { + return x.Reason + } + return "" +} + type HealthCheckRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -748,7 +870,7 @@ type HealthCheckRequest struct { func (x *HealthCheckRequest) Reset() { *x = HealthCheckRequest{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[6] + mi := &file_video_sfu_event_events_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -761,7 +883,7 @@ func (x *HealthCheckRequest) String() string { func (*HealthCheckRequest) ProtoMessage() {} func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[6] + mi := &file_video_sfu_event_events_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -774,7 +896,7 @@ func (x *HealthCheckRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthCheckRequest.ProtoReflect.Descriptor instead. func (*HealthCheckRequest) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{6} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{8} } type HealthCheckResponse struct { @@ -788,7 +910,7 @@ type HealthCheckResponse struct { func (x *HealthCheckResponse) Reset() { *x = HealthCheckResponse{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[7] + mi := &file_video_sfu_event_events_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -801,7 +923,7 @@ func (x *HealthCheckResponse) String() string { func (*HealthCheckResponse) ProtoMessage() {} func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[7] + mi := &file_video_sfu_event_events_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -814,7 +936,7 @@ func (x *HealthCheckResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use HealthCheckResponse.ProtoReflect.Descriptor instead. func (*HealthCheckResponse) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{7} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{9} } func (x *HealthCheckResponse) GetParticipantCount() *models.ParticipantCount { @@ -843,7 +965,7 @@ type TrackPublished struct { func (x *TrackPublished) Reset() { *x = TrackPublished{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[8] + mi := &file_video_sfu_event_events_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -856,7 +978,7 @@ func (x *TrackPublished) String() string { func (*TrackPublished) ProtoMessage() {} func (x *TrackPublished) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[8] + mi := &file_video_sfu_event_events_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -869,7 +991,7 @@ func (x *TrackPublished) ProtoReflect() protoreflect.Message { // Deprecated: Use TrackPublished.ProtoReflect.Descriptor instead. func (*TrackPublished) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{8} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{10} } func (x *TrackPublished) GetUserId() string { @@ -920,7 +1042,7 @@ type TrackUnpublished struct { func (x *TrackUnpublished) Reset() { *x = TrackUnpublished{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[9] + mi := &file_video_sfu_event_events_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -933,7 +1055,7 @@ func (x *TrackUnpublished) String() string { func (*TrackUnpublished) ProtoMessage() {} func (x *TrackUnpublished) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[9] + mi := &file_video_sfu_event_events_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -946,7 +1068,7 @@ func (x *TrackUnpublished) ProtoReflect() protoreflect.Message { // Deprecated: Use TrackUnpublished.ProtoReflect.Descriptor instead. func (*TrackUnpublished) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{9} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{11} } func (x *TrackUnpublished) GetUserId() string { @@ -994,8 +1116,9 @@ type JoinRequest struct { // dumb SDP that allow us to extract subscriber's decode codecs SubscriberSdp string `protobuf:"bytes,3,opt,name=subscriber_sdp,json=subscriberSdp,proto3" json:"subscriber_sdp,omitempty"` ClientDetails *models.ClientDetails `protobuf:"bytes,4,opt,name=client_details,json=clientDetails,proto3" json:"client_details,omitempty"` - // TODO: we should know if this is going to be - // - publishing and subscribing, or just subscribing for future routing + // Deprecated: use ReconnectDetails instead + // + // Deprecated: Do not use. Migration *Migration `protobuf:"bytes,5,opt,name=migration,proto3" json:"migration,omitempty"` // Fast reconnect flag explicitly indicates that if the participant session // and the associated state is still present in the SFU, the client is ready @@ -1006,13 +1129,16 @@ type JoinRequest struct { // For the SFU, fast_reconnect:false indicates that even if it has the state // cached, the client state is not in sync and hence it must be cleaned up before // proceeding further. - FastReconnect bool `protobuf:"varint,6,opt,name=fast_reconnect,json=fastReconnect,proto3" json:"fast_reconnect,omitempty"` + // + // Deprecated: Do not use. + FastReconnect bool `protobuf:"varint,6,opt,name=fast_reconnect,json=fastReconnect,proto3" json:"fast_reconnect,omitempty"` + ReconnectDetails *ReconnectDetails `protobuf:"bytes,7,opt,name=reconnect_details,json=reconnectDetails,proto3" json:"reconnect_details,omitempty"` } func (x *JoinRequest) Reset() { *x = JoinRequest{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[10] + mi := &file_video_sfu_event_events_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1025,7 +1151,7 @@ func (x *JoinRequest) String() string { func (*JoinRequest) ProtoMessage() {} func (x *JoinRequest) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[10] + mi := &file_video_sfu_event_events_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1038,7 +1164,7 @@ func (x *JoinRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinRequest.ProtoReflect.Descriptor instead. func (*JoinRequest) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{10} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{12} } func (x *JoinRequest) GetToken() string { @@ -1069,6 +1195,7 @@ func (x *JoinRequest) GetClientDetails() *models.ClientDetails { return nil } +// Deprecated: Do not use. func (x *JoinRequest) GetMigration() *Migration { if x != nil { return x.Migration @@ -1076,6 +1203,7 @@ func (x *JoinRequest) GetMigration() *Migration { return nil } +// Deprecated: Do not use. func (x *JoinRequest) GetFastReconnect() bool { if x != nil { return x.FastReconnect @@ -1083,6 +1211,101 @@ func (x *JoinRequest) GetFastReconnect() bool { return false } +func (x *JoinRequest) GetReconnectDetails() *ReconnectDetails { + if x != nil { + return x.ReconnectDetails + } + return nil +} + +type ReconnectDetails struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Strategy models.WebsocketReconnectStrategy `protobuf:"varint,1,opt,name=strategy,proto3,enum=stream.video.sfu.models.WebsocketReconnectStrategy" json:"strategy,omitempty"` + AnnouncedTracks []*models.TrackInfo `protobuf:"bytes,3,rep,name=announced_tracks,json=announcedTracks,proto3" json:"announced_tracks,omitempty"` + Subscriptions []*signal_rpc.TrackSubscriptionDetails `protobuf:"bytes,4,rep,name=subscriptions,proto3" json:"subscriptions,omitempty"` + ReconnectAttempt uint32 `protobuf:"varint,5,opt,name=reconnect_attempt,json=reconnectAttempt,proto3" json:"reconnect_attempt,omitempty"` + FromSfuId string `protobuf:"bytes,6,opt,name=from_sfu_id,json=fromSfuId,proto3" json:"from_sfu_id,omitempty"` + // only set in case of rejoin + PreviousSessionId string `protobuf:"bytes,7,opt,name=previous_session_id,json=previousSessionId,proto3" json:"previous_session_id,omitempty"` +} + +func (x *ReconnectDetails) Reset() { + *x = ReconnectDetails{} + if protoimpl.UnsafeEnabled { + mi := &file_video_sfu_event_events_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReconnectDetails) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReconnectDetails) ProtoMessage() {} + +func (x *ReconnectDetails) ProtoReflect() protoreflect.Message { + mi := &file_video_sfu_event_events_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReconnectDetails.ProtoReflect.Descriptor instead. +func (*ReconnectDetails) Descriptor() ([]byte, []int) { + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{13} +} + +func (x *ReconnectDetails) GetStrategy() models.WebsocketReconnectStrategy { + if x != nil { + return x.Strategy + } + return models.WebsocketReconnectStrategy(0) +} + +func (x *ReconnectDetails) GetAnnouncedTracks() []*models.TrackInfo { + if x != nil { + return x.AnnouncedTracks + } + return nil +} + +func (x *ReconnectDetails) GetSubscriptions() []*signal_rpc.TrackSubscriptionDetails { + if x != nil { + return x.Subscriptions + } + return nil +} + +func (x *ReconnectDetails) GetReconnectAttempt() uint32 { + if x != nil { + return x.ReconnectAttempt + } + return 0 +} + +func (x *ReconnectDetails) GetFromSfuId() string { + if x != nil { + return x.FromSfuId + } + return "" +} + +func (x *ReconnectDetails) GetPreviousSessionId() string { + if x != nil { + return x.PreviousSessionId + } + return "" +} + type Migration struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1096,7 +1319,7 @@ type Migration struct { func (x *Migration) Reset() { *x = Migration{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[11] + mi := &file_video_sfu_event_events_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1109,7 +1332,7 @@ func (x *Migration) String() string { func (*Migration) ProtoMessage() {} func (x *Migration) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[11] + mi := &file_video_sfu_event_events_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1122,7 +1345,7 @@ func (x *Migration) ProtoReflect() protoreflect.Message { // Deprecated: Use Migration.ProtoReflect.Descriptor instead. func (*Migration) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{11} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{14} } func (x *Migration) GetFromSfuId() string { @@ -1151,14 +1374,15 @@ type JoinResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - CallState *models.CallState `protobuf:"bytes,1,opt,name=call_state,json=callState,proto3" json:"call_state,omitempty"` - Reconnected bool `protobuf:"varint,2,opt,name=reconnected,proto3" json:"reconnected,omitempty"` + CallState *models.CallState `protobuf:"bytes,1,opt,name=call_state,json=callState,proto3" json:"call_state,omitempty"` + Reconnected bool `protobuf:"varint,2,opt,name=reconnected,proto3" json:"reconnected,omitempty"` + FastReconnectDeadlineSeconds int32 `protobuf:"varint,3,opt,name=fast_reconnect_deadline_seconds,json=fastReconnectDeadlineSeconds,proto3" json:"fast_reconnect_deadline_seconds,omitempty"` } func (x *JoinResponse) Reset() { *x = JoinResponse{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[12] + mi := &file_video_sfu_event_events_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1171,7 +1395,7 @@ func (x *JoinResponse) String() string { func (*JoinResponse) ProtoMessage() {} func (x *JoinResponse) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[12] + mi := &file_video_sfu_event_events_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1184,7 +1408,7 @@ func (x *JoinResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use JoinResponse.ProtoReflect.Descriptor instead. func (*JoinResponse) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{12} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{15} } func (x *JoinResponse) GetCallState() *models.CallState { @@ -1201,6 +1425,13 @@ func (x *JoinResponse) GetReconnected() bool { return false } +func (x *JoinResponse) GetFastReconnectDeadlineSeconds() int32 { + if x != nil { + return x.FastReconnectDeadlineSeconds + } + return 0 +} + // ParticipantJoined is fired when a user joins a call type ParticipantJoined struct { state protoimpl.MessageState @@ -1214,7 +1445,7 @@ type ParticipantJoined struct { func (x *ParticipantJoined) Reset() { *x = ParticipantJoined{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[13] + mi := &file_video_sfu_event_events_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1227,7 +1458,7 @@ func (x *ParticipantJoined) String() string { func (*ParticipantJoined) ProtoMessage() {} func (x *ParticipantJoined) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[13] + mi := &file_video_sfu_event_events_proto_msgTypes[16] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1240,7 +1471,7 @@ func (x *ParticipantJoined) ProtoReflect() protoreflect.Message { // Deprecated: Use ParticipantJoined.ProtoReflect.Descriptor instead. func (*ParticipantJoined) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{13} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{16} } func (x *ParticipantJoined) GetCallCid() string { @@ -1270,7 +1501,7 @@ type ParticipantLeft struct { func (x *ParticipantLeft) Reset() { *x = ParticipantLeft{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[14] + mi := &file_video_sfu_event_events_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1283,7 +1514,7 @@ func (x *ParticipantLeft) String() string { func (*ParticipantLeft) ProtoMessage() {} func (x *ParticipantLeft) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[14] + mi := &file_video_sfu_event_events_proto_msgTypes[17] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1296,7 +1527,7 @@ func (x *ParticipantLeft) ProtoReflect() protoreflect.Message { // Deprecated: Use ParticipantLeft.ProtoReflect.Descriptor instead. func (*ParticipantLeft) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{14} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{17} } func (x *ParticipantLeft) GetCallCid() string { @@ -1326,7 +1557,7 @@ type ParticipantUpdated struct { func (x *ParticipantUpdated) Reset() { *x = ParticipantUpdated{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[15] + mi := &file_video_sfu_event_events_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1339,7 +1570,7 @@ func (x *ParticipantUpdated) String() string { func (*ParticipantUpdated) ProtoMessage() {} func (x *ParticipantUpdated) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[15] + mi := &file_video_sfu_event_events_proto_msgTypes[18] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1352,7 +1583,7 @@ func (x *ParticipantUpdated) ProtoReflect() protoreflect.Message { // Deprecated: Use ParticipantUpdated.ProtoReflect.Descriptor instead. func (*ParticipantUpdated) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{15} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{18} } func (x *ParticipantUpdated) GetCallCid() string { @@ -1382,7 +1613,7 @@ type SubscriberOffer struct { func (x *SubscriberOffer) Reset() { *x = SubscriberOffer{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[16] + mi := &file_video_sfu_event_events_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1395,7 +1626,7 @@ func (x *SubscriberOffer) String() string { func (*SubscriberOffer) ProtoMessage() {} func (x *SubscriberOffer) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[16] + mi := &file_video_sfu_event_events_proto_msgTypes[19] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1408,7 +1639,7 @@ func (x *SubscriberOffer) ProtoReflect() protoreflect.Message { // Deprecated: Use SubscriberOffer.ProtoReflect.Descriptor instead. func (*SubscriberOffer) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{16} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{19} } func (x *SubscriberOffer) GetIceRestart() bool { @@ -1436,7 +1667,7 @@ type PublisherAnswer struct { func (x *PublisherAnswer) Reset() { *x = PublisherAnswer{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[17] + mi := &file_video_sfu_event_events_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1449,7 +1680,7 @@ func (x *PublisherAnswer) String() string { func (*PublisherAnswer) ProtoMessage() {} func (x *PublisherAnswer) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[17] + mi := &file_video_sfu_event_events_proto_msgTypes[20] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1462,7 +1693,7 @@ func (x *PublisherAnswer) ProtoReflect() protoreflect.Message { // Deprecated: Use PublisherAnswer.ProtoReflect.Descriptor instead. func (*PublisherAnswer) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{17} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{20} } func (x *PublisherAnswer) GetSdp() string { @@ -1485,7 +1716,7 @@ type ConnectionQualityChanged struct { func (x *ConnectionQualityChanged) Reset() { *x = ConnectionQualityChanged{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[18] + mi := &file_video_sfu_event_events_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1498,7 +1729,7 @@ func (x *ConnectionQualityChanged) String() string { func (*ConnectionQualityChanged) ProtoMessage() {} func (x *ConnectionQualityChanged) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[18] + mi := &file_video_sfu_event_events_proto_msgTypes[21] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1511,7 +1742,7 @@ func (x *ConnectionQualityChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionQualityChanged.ProtoReflect.Descriptor instead. func (*ConnectionQualityChanged) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{18} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{21} } func (x *ConnectionQualityChanged) GetConnectionQualityUpdates() []*ConnectionQualityInfo { @@ -1534,7 +1765,7 @@ type ConnectionQualityInfo struct { func (x *ConnectionQualityInfo) Reset() { *x = ConnectionQualityInfo{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[19] + mi := &file_video_sfu_event_events_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1547,7 +1778,7 @@ func (x *ConnectionQualityInfo) String() string { func (*ConnectionQualityInfo) ProtoMessage() {} func (x *ConnectionQualityInfo) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[19] + mi := &file_video_sfu_event_events_proto_msgTypes[22] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1560,7 +1791,7 @@ func (x *ConnectionQualityInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use ConnectionQualityInfo.ProtoReflect.Descriptor instead. func (*ConnectionQualityInfo) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{19} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{22} } func (x *ConnectionQualityInfo) GetUserId() string { @@ -1597,7 +1828,7 @@ type DominantSpeakerChanged struct { func (x *DominantSpeakerChanged) Reset() { *x = DominantSpeakerChanged{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[20] + mi := &file_video_sfu_event_events_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1610,7 +1841,7 @@ func (x *DominantSpeakerChanged) String() string { func (*DominantSpeakerChanged) ProtoMessage() {} func (x *DominantSpeakerChanged) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[20] + mi := &file_video_sfu_event_events_proto_msgTypes[23] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1623,7 +1854,7 @@ func (x *DominantSpeakerChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use DominantSpeakerChanged.ProtoReflect.Descriptor instead. func (*DominantSpeakerChanged) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{20} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{23} } func (x *DominantSpeakerChanged) GetUserId() string { @@ -1655,7 +1886,7 @@ type AudioLevel struct { func (x *AudioLevel) Reset() { *x = AudioLevel{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[21] + mi := &file_video_sfu_event_events_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1668,7 +1899,7 @@ func (x *AudioLevel) String() string { func (*AudioLevel) ProtoMessage() {} func (x *AudioLevel) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[21] + mi := &file_video_sfu_event_events_proto_msgTypes[24] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1681,7 +1912,7 @@ func (x *AudioLevel) ProtoReflect() protoreflect.Message { // Deprecated: Use AudioLevel.ProtoReflect.Descriptor instead. func (*AudioLevel) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{21} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{24} } func (x *AudioLevel) GetUserId() string { @@ -1724,7 +1955,7 @@ type AudioLevelChanged struct { func (x *AudioLevelChanged) Reset() { *x = AudioLevelChanged{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[22] + mi := &file_video_sfu_event_events_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1737,7 +1968,7 @@ func (x *AudioLevelChanged) String() string { func (*AudioLevelChanged) ProtoMessage() {} func (x *AudioLevelChanged) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[22] + mi := &file_video_sfu_event_events_proto_msgTypes[25] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1750,7 +1981,7 @@ func (x *AudioLevelChanged) ProtoReflect() protoreflect.Message { // Deprecated: Use AudioLevelChanged.ProtoReflect.Descriptor instead. func (*AudioLevelChanged) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{22} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{25} } func (x *AudioLevelChanged) GetAudioLevels() []*AudioLevel { @@ -1771,7 +2002,7 @@ type AudioMediaRequest struct { func (x *AudioMediaRequest) Reset() { *x = AudioMediaRequest{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[23] + mi := &file_video_sfu_event_events_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1784,7 +2015,7 @@ func (x *AudioMediaRequest) String() string { func (*AudioMediaRequest) ProtoMessage() {} func (x *AudioMediaRequest) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[23] + mi := &file_video_sfu_event_events_proto_msgTypes[26] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1797,7 +2028,7 @@ func (x *AudioMediaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AudioMediaRequest.ProtoReflect.Descriptor instead. func (*AudioMediaRequest) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{23} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{26} } func (x *AudioMediaRequest) GetChannelCount() int32 { @@ -1819,7 +2050,7 @@ type AudioSender struct { func (x *AudioSender) Reset() { *x = AudioSender{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[24] + mi := &file_video_sfu_event_events_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1832,7 +2063,7 @@ func (x *AudioSender) String() string { func (*AudioSender) ProtoMessage() {} func (x *AudioSender) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[24] + mi := &file_video_sfu_event_events_proto_msgTypes[27] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1845,7 +2076,7 @@ func (x *AudioSender) ProtoReflect() protoreflect.Message { // Deprecated: Use AudioSender.ProtoReflect.Descriptor instead. func (*AudioSender) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{24} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{27} } func (x *AudioSender) GetMediaRequest() *AudioMediaRequest { @@ -1875,7 +2106,7 @@ type VideoMediaRequest struct { func (x *VideoMediaRequest) Reset() { *x = VideoMediaRequest{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[25] + mi := &file_video_sfu_event_events_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1888,7 +2119,7 @@ func (x *VideoMediaRequest) String() string { func (*VideoMediaRequest) ProtoMessage() {} func (x *VideoMediaRequest) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[25] + mi := &file_video_sfu_event_events_proto_msgTypes[28] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1901,7 +2132,7 @@ func (x *VideoMediaRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoMediaRequest.ProtoReflect.Descriptor instead. func (*VideoMediaRequest) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{25} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{28} } func (x *VideoMediaRequest) GetIdealHeight() int32 { @@ -1945,7 +2176,7 @@ type VideoLayerSetting struct { func (x *VideoLayerSetting) Reset() { *x = VideoLayerSetting{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[26] + mi := &file_video_sfu_event_events_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1958,7 +2189,7 @@ func (x *VideoLayerSetting) String() string { func (*VideoLayerSetting) ProtoMessage() {} func (x *VideoLayerSetting) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[26] + mi := &file_video_sfu_event_events_proto_msgTypes[29] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1971,7 +2202,7 @@ func (x *VideoLayerSetting) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoLayerSetting.ProtoReflect.Descriptor instead. func (*VideoLayerSetting) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{26} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{29} } func (x *VideoLayerSetting) GetName() string { @@ -2036,7 +2267,7 @@ type VideoSender struct { func (x *VideoSender) Reset() { *x = VideoSender{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[27] + mi := &file_video_sfu_event_events_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2049,7 +2280,7 @@ func (x *VideoSender) String() string { func (*VideoSender) ProtoMessage() {} func (x *VideoSender) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[27] + mi := &file_video_sfu_event_events_proto_msgTypes[30] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2062,7 +2293,7 @@ func (x *VideoSender) ProtoReflect() protoreflect.Message { // Deprecated: Use VideoSender.ProtoReflect.Descriptor instead. func (*VideoSender) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{27} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{30} } func (x *VideoSender) GetMediaRequest() *VideoMediaRequest { @@ -2099,7 +2330,7 @@ type ChangePublishQuality struct { func (x *ChangePublishQuality) Reset() { *x = ChangePublishQuality{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[28] + mi := &file_video_sfu_event_events_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2112,7 +2343,7 @@ func (x *ChangePublishQuality) String() string { func (*ChangePublishQuality) ProtoMessage() {} func (x *ChangePublishQuality) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[28] + mi := &file_video_sfu_event_events_proto_msgTypes[31] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2125,7 +2356,7 @@ func (x *ChangePublishQuality) ProtoReflect() protoreflect.Message { // Deprecated: Use ChangePublishQuality.ProtoReflect.Descriptor instead. func (*ChangePublishQuality) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{28} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{31} } func (x *ChangePublishQuality) GetAudioSenders() []*AudioSender { @@ -2170,7 +2401,7 @@ type CallGrantsUpdated struct { func (x *CallGrantsUpdated) Reset() { *x = CallGrantsUpdated{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[29] + mi := &file_video_sfu_event_events_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2183,7 +2414,7 @@ func (x *CallGrantsUpdated) String() string { func (*CallGrantsUpdated) ProtoMessage() {} func (x *CallGrantsUpdated) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[29] + mi := &file_video_sfu_event_events_proto_msgTypes[32] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2196,7 +2427,7 @@ func (x *CallGrantsUpdated) ProtoReflect() protoreflect.Message { // Deprecated: Use CallGrantsUpdated.ProtoReflect.Descriptor instead. func (*CallGrantsUpdated) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{29} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{32} } func (x *CallGrantsUpdated) GetCurrentGrants() *models.CallGrants { @@ -2226,7 +2457,7 @@ type GoAway struct { func (x *GoAway) Reset() { *x = GoAway{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[30] + mi := &file_video_sfu_event_events_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2239,7 +2470,7 @@ func (x *GoAway) String() string { func (*GoAway) ProtoMessage() {} func (x *GoAway) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[30] + mi := &file_video_sfu_event_events_proto_msgTypes[33] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2252,7 +2483,7 @@ func (x *GoAway) ProtoReflect() protoreflect.Message { // Deprecated: Use GoAway.ProtoReflect.Descriptor instead. func (*GoAway) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{30} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{33} } func (x *GoAway) GetReason() models.GoAwayReason { @@ -2275,7 +2506,7 @@ type CallEnded struct { func (x *CallEnded) Reset() { *x = CallEnded{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_event_events_proto_msgTypes[31] + mi := &file_video_sfu_event_events_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2288,7 +2519,7 @@ func (x *CallEnded) String() string { func (*CallEnded) ProtoMessage() {} func (x *CallEnded) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_event_events_proto_msgTypes[31] + mi := &file_video_sfu_event_events_proto_msgTypes[34] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2301,7 +2532,7 @@ func (x *CallEnded) ProtoReflect() protoreflect.Message { // Deprecated: Use CallEnded.ProtoReflect.Descriptor instead. func (*CallEnded) Descriptor() ([]byte, []int) { - return file_video_sfu_event_events_proto_rawDescGZIP(), []int{31} + return file_video_sfu_event_events_proto_rawDescGZIP(), []int{34} } func (x *CallEnded) GetReason() models.CallEndedReason { @@ -2321,7 +2552,7 @@ var file_video_sfu_event_events_proto_rawDesc = []byte{ 0x75, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x21, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2f, 0x73, 0x66, 0x75, 0x2f, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x5f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x69, 0x67, 0x6e, - 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc1, 0x0d, 0x0a, 0x08, 0x53, 0x66, 0x75, + 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xbf, 0x0e, 0x0a, 0x08, 0x53, 0x66, 0x75, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x54, 0x0a, 0x10, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x6f, 0x66, 0x66, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, @@ -2428,56 +2659,89 @@ var file_video_sfu_event_events_proto_rawDesc = []byte{ 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x48, 0x00, 0x52, 0x12, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, - 0x70, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, 0x0f, 0x0a, 0x0d, 0x65, - 0x76, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x3f, 0x0a, 0x0b, - 0x50, 0x69, 0x6e, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x04, 0x70, - 0x69, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x69, 0x6e, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x73, 0x22, 0xa1, 0x01, - 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, - 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, - 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x62, 0x0a, - 0x12, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x65, 0x67, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x11, - 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, - 0x79, 0x22, 0x71, 0x0a, 0x0a, 0x49, 0x43, 0x45, 0x54, 0x72, 0x69, 0x63, 0x6b, 0x6c, 0x65, 0x12, - 0x3e, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, - 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x65, 0x65, - 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x22, 0x4c, 0x0a, 0x0a, 0x49, 0x43, 0x45, 0x52, 0x65, 0x73, 0x74, 0x61, - 0x72, 0x74, 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, - 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, - 0x50, 0x65, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x79, - 0x70, 0x65, 0x22, 0xc9, 0x01, 0x0a, 0x0a, 0x53, 0x66, 0x75, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x48, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x70, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x7c, 0x0a, 0x1e, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x5f, 0x6d, 0x69, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x19, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x34, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, + 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x50, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x1c, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x42, 0x0f, 0x0a, 0x0d, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x1e, 0x0a, 0x1c, 0x50, 0x61, + 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x22, 0x3f, 0x0a, 0x0b, 0x50, 0x69, + 0x6e, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x04, 0x70, 0x69, 0x6e, + 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x73, 0x2e, 0x50, 0x69, 0x6e, 0x52, 0x04, 0x70, 0x69, 0x6e, 0x73, 0x22, 0xa1, 0x01, 0x0a, 0x05, + 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x34, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, + 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x62, 0x0a, 0x12, 0x72, + 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x73, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x52, 0x11, 0x72, 0x65, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x22, + 0x71, 0x0a, 0x0a, 0x49, 0x43, 0x45, 0x54, 0x72, 0x69, 0x63, 0x6b, 0x6c, 0x65, 0x12, 0x3e, 0x0a, + 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x21, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, + 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x65, 0x65, 0x72, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x69, 0x63, 0x65, 0x5f, 0x63, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x69, 0x63, 0x65, 0x43, 0x61, 0x6e, 0x64, 0x69, 0x64, 0x61, + 0x74, 0x65, 0x22, 0x4c, 0x0a, 0x0a, 0x49, 0x43, 0x45, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x12, 0x3e, 0x0a, 0x09, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, + 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x65, + 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, 0x52, 0x08, 0x70, 0x65, 0x65, 0x72, 0x54, 0x79, 0x70, 0x65, + 0x22, 0xa3, 0x02, 0x0a, 0x0a, 0x53, 0x66, 0x75, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x48, 0x0a, 0x0c, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, + 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4a, + 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, 0x6a, 0x6f, + 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5e, 0x0a, 0x14, 0x68, 0x65, 0x61, + 0x6c, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, - 0x2e, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0b, - 0x6a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x5e, 0x0a, 0x14, 0x68, - 0x65, 0x61, 0x6c, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x12, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x14, - 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x22, 0x6d, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x11, 0x70, - 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, - 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, - 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x52, 0x10, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x22, 0xc8, 0x01, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x50, 0x75, 0x62, + 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x48, 0x00, 0x52, 0x12, 0x68, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x58, 0x0a, 0x12, 0x6c, 0x65, 0x61, + 0x76, 0x65, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, + 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4c, + 0x65, 0x61, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x48, + 0x00, 0x52, 0x10, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x43, 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x42, 0x11, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x70, + 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x49, 0x0a, 0x10, 0x4c, 0x65, 0x61, 0x76, 0x65, 0x43, + 0x61, 0x6c, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, + 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, + 0x6e, 0x22, 0x14, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x6d, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, + 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, + 0x0a, 0x11, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, + 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x43, + 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x10, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, + 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xc8, 0x01, 0x0a, 0x0e, 0x54, 0x72, 0x61, 0x63, 0x6b, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x36, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, + 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, + 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, + 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, + 0x74, 0x22, 0x8f, 0x02, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, @@ -2485,231 +2749,254 @@ var file_video_sfu_event_events_proto_rawDesc = []byte{ 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, - 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, - 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, - 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x8f, - 0x02, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x36, 0x0a, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, - 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, - 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, - 0x63, 0x6b, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x52, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, - 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, - 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, - 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, - 0x22, 0xa0, 0x02, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x62, 0x65, 0x72, 0x5f, 0x73, 0x64, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, - 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x53, 0x64, 0x70, 0x12, 0x4d, 0x0a, 0x0e, - 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, - 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0d, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x3f, 0x0a, 0x09, 0x6d, - 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, - 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, - 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x52, 0x09, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, - 0x66, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x66, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, - 0x65, 0x63, 0x74, 0x22, 0xd3, 0x01, 0x0a, 0x09, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x66, 0x75, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x66, 0x75, 0x49, - 0x64, 0x12, 0x4d, 0x0a, 0x10, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, - 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, - 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x0f, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x73, - 0x12, 0x57, 0x0a, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, - 0x6c, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, - 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x73, 0x0a, 0x0c, 0x4a, 0x6f, 0x69, - 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x61, 0x6c, - 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, - 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, - 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x22, 0x76, - 0x0a, 0x11, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4a, 0x6f, 0x69, - 0x6e, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x6c, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x46, - 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, - 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, - 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, - 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x74, 0x0a, 0x0f, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, - 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x61, 0x6c, - 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x6c, - 0x6c, 0x43, 0x69, 0x64, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, - 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, - 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x77, 0x0a, 0x12, - 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x6c, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x46, 0x0a, - 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, - 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, - 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, - 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x44, 0x0a, 0x0f, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, - 0x62, 0x65, 0x72, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x63, 0x65, 0x5f, - 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x64, 0x70, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x64, 0x70, 0x22, 0x23, 0x0a, 0x0f, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x41, 0x6e, 0x73, 0x77, 0x65, 0x72, 0x12, 0x10, - 0x0a, 0x03, 0x73, 0x64, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x64, 0x70, - 0x22, 0x87, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, - 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x6b, 0x0a, - 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x71, 0x75, 0x61, 0x6c, - 0x69, 0x74, 0x79, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, - 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, - 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, - 0x69, 0x74, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x15, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, - 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x59, 0x0a, 0x12, - 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x71, 0x75, 0x61, 0x6c, 0x69, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x43, 0x0a, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2d, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, + 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, + 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x05, 0x63, 0x61, 0x75, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x0b, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, + 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, + 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, + 0x61, 0x6e, 0x74, 0x22, 0xff, 0x02, 0x0a, 0x0b, 0x4a, 0x6f, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, + 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x73, + 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x5f, 0x73, 0x64, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x53, 0x64, 0x70, 0x12, + 0x4d, 0x0a, 0x0e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x73, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, + 0x0d, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x43, + 0x0a, 0x09, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, + 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x4d, 0x69, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x0e, 0x66, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, + 0x0d, 0x66, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x12, 0x55, + 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x64, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, + 0x6e, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x73, 0x52, 0x10, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x22, 0x88, 0x03, 0x0a, 0x10, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, + 0x65, 0x63, 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x4f, 0x0a, 0x08, 0x73, 0x74, + 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, + 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, + 0x79, 0x52, 0x08, 0x73, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x4d, 0x0a, 0x10, 0x61, + 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x18, + 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, + 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, + 0x54, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x61, 0x6e, 0x6e, 0x6f, 0x75, + 0x6e, 0x63, 0x65, 0x64, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x57, 0x0a, 0x0d, 0x73, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x31, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, + 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x2e, 0x54, 0x72, 0x61, 0x63, + 0x6b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x5f, 0x61, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x10, + 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x41, 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, + 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x66, 0x75, 0x5f, 0x69, 0x64, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x66, 0x75, 0x49, 0x64, + 0x12, 0x2e, 0x0a, 0x13, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x73, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x70, + 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, + 0x22, 0xd3, 0x01, 0x0a, 0x09, 0x4d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1e, + 0x0a, 0x0b, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x73, 0x66, 0x75, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x66, 0x72, 0x6f, 0x6d, 0x53, 0x66, 0x75, 0x49, 0x64, 0x12, 0x4d, + 0x0a, 0x10, 0x61, 0x6e, 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x5f, 0x74, 0x72, 0x61, 0x63, + 0x6b, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, - 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, - 0x6c, 0x69, 0x74, 0x79, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x50, 0x0a, 0x16, 0x44, 0x6f, 0x6d, 0x69, 0x6e, - 0x61, 0x6e, 0x74, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x65, 0x72, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, - 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, - 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x7b, 0x0a, 0x0a, 0x41, 0x75, 0x64, - 0x69, 0x6f, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, - 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x02, 0x52, 0x05, - 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, 0x5f, 0x73, 0x70, 0x65, 0x61, - 0x6b, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x53, 0x70, - 0x65, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x22, 0x5a, 0x0a, 0x11, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x0c, 0x61, - 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, - 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x6f, - 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0b, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, - 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0x93, 0x01, 0x0a, - 0x0b, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0d, - 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, - 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x64, - 0x69, 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0c, - 0x6d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, - 0x63, 0x6f, 0x64, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, + 0x6c, 0x73, 0x2e, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x61, 0x6e, + 0x6e, 0x6f, 0x75, 0x6e, 0x63, 0x65, 0x64, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x12, 0x57, 0x0a, + 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, + 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x2e, 0x54, + 0x72, 0x61, 0x63, 0x6b, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x52, 0x0d, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xba, 0x01, 0x0a, 0x0c, 0x4a, 0x6f, 0x69, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x41, 0x0a, 0x0a, 0x63, 0x61, 0x6c, 0x6c, 0x5f, + 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x52, 0x05, 0x63, 0x6f, 0x64, - 0x65, 0x63, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x64, 0x65, 0x61, - 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, - 0x69, 0x64, 0x65, 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x69, - 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, - 0x52, 0x0a, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x57, 0x69, 0x64, 0x74, 0x68, 0x12, 0x28, 0x0a, 0x10, - 0x69, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x46, 0x72, 0x61, - 0x6d, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xad, 0x03, 0x0a, 0x11, 0x56, 0x69, 0x64, 0x65, 0x6f, - 0x4c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x12, 0x12, 0x0a, 0x04, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, - 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x61, 0x78, 0x5f, - 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6d, - 0x61, 0x78, 0x42, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x37, 0x0a, 0x18, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x64, 0x6f, - 0x77, 0x6e, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, 0x52, 0x15, 0x73, 0x63, 0x61, - 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x44, 0x6f, 0x77, 0x6e, - 0x42, 0x79, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, - 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, - 0x64, 0x65, 0x6f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x2e, - 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x69, - 0x74, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, - 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x64, 0x65, - 0x63, 0x52, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, - 0x66, 0x72, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x0c, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x22, 0x67, 0x0a, - 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1d, 0x0a, 0x19, 0x50, 0x52, 0x49, - 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, - 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x50, 0x52, 0x49, 0x4f, - 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, 0x13, 0x0a, 0x0f, 0x50, 0x52, - 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4d, 0x45, 0x44, 0x49, 0x55, 0x4d, 0x10, 0x02, 0x12, - 0x15, 0x0a, 0x11, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x56, 0x45, 0x52, 0x59, - 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x22, 0xd6, 0x01, 0x0a, 0x0b, 0x56, 0x69, 0x64, 0x65, 0x6f, - 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x52, + 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x72, 0x65, + 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x1f, + 0x66, 0x61, 0x73, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x1c, 0x66, 0x61, 0x73, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x44, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x22, 0x76, 0x0a, 0x11, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x6e, 0x74, 0x4a, 0x6f, 0x69, 0x6e, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, + 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x6c, 0x6c, + 0x43, 0x69, 0x64, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, + 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, + 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x74, 0x0a, 0x0f, 0x50, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x4c, 0x65, 0x66, 0x74, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x63, 0x61, 0x6c, 0x6c, 0x43, 0x69, 0x64, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, + 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, + 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, + 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, + 0x74, 0x22, 0x77, 0x0a, 0x12, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x5f, + 0x63, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x61, 0x6c, 0x6c, 0x43, + 0x69, 0x64, 0x12, 0x46, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, + 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, + 0x73, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x52, 0x0b, 0x70, + 0x61, 0x72, 0x74, 0x69, 0x63, 0x69, 0x70, 0x61, 0x6e, 0x74, 0x22, 0x44, 0x0a, 0x0f, 0x53, 0x75, + 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x72, 0x4f, 0x66, 0x66, 0x65, 0x72, 0x12, 0x1f, 0x0a, + 0x0b, 0x69, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x69, 0x63, 0x65, 0x52, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x73, 0x64, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x64, 0x70, + 0x22, 0x23, 0x0a, 0x0f, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x72, 0x41, 0x6e, 0x73, + 0x77, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x73, 0x64, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x73, 0x64, 0x70, 0x22, 0x87, 0x01, 0x0a, 0x18, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x64, 0x12, 0x6b, 0x0a, 0x1a, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x5f, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, + 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, + 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x73, 0x22, + 0xaa, 0x01, 0x0a, 0x15, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, + 0x61, 0x6c, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, + 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, + 0x64, 0x12, 0x59, 0x0a, 0x12, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, + 0x71, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, - 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4d, 0x65, 0x64, 0x69, - 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0c, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, - 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, - 0x43, 0x6f, 0x64, 0x65, 0x63, 0x52, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x12, 0x41, 0x0a, 0x06, - 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, - 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, - 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4c, 0x61, 0x79, 0x65, 0x72, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x22, - 0xaa, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, - 0x68, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x48, 0x0a, 0x0d, 0x61, 0x75, 0x64, 0x69, - 0x6f, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, - 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x65, - 0x6e, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, - 0x72, 0x73, 0x12, 0x48, 0x0a, 0x0d, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x5f, 0x73, 0x65, 0x6e, 0x64, - 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, + 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x11, 0x63, 0x6f, 0x6e, 0x6e, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x22, 0x50, 0x0a, 0x16, + 0x44, 0x6f, 0x6d, 0x69, 0x6e, 0x61, 0x6e, 0x74, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x65, 0x72, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x22, 0x7b, + 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x17, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x02, 0x52, 0x05, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x73, + 0x5f, 0x73, 0x70, 0x65, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x69, 0x73, 0x53, 0x70, 0x65, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x22, 0x5a, 0x0a, 0x11, 0x41, + 0x75, 0x64, 0x69, 0x6f, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, + 0x12, 0x45, 0x0a, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, + 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, + 0x41, 0x75, 0x64, 0x69, 0x6f, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0b, 0x61, 0x75, 0x64, 0x69, + 0x6f, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x73, 0x22, 0x38, 0x0a, 0x11, 0x41, 0x75, 0x64, 0x69, 0x6f, + 0x4d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x23, 0x0a, 0x0d, + 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0c, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x75, 0x6e, + 0x74, 0x22, 0x93, 0x01, 0x0a, 0x0b, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, + 0x72, 0x12, 0x4e, 0x0a, 0x0d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x52, 0x0c, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x34, 0x0a, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, + 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x63, + 0x52, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x22, 0x81, 0x01, 0x0a, 0x11, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, + 0x0c, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x0b, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, + 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x64, 0x74, 0x68, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x57, 0x69, 0x64, 0x74, + 0x68, 0x12, 0x28, 0x0a, 0x10, 0x69, 0x64, 0x65, 0x61, 0x6c, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, + 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x69, 0x64, 0x65, + 0x61, 0x6c, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x52, 0x61, 0x74, 0x65, 0x22, 0xad, 0x03, 0x0a, 0x11, + 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, + 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x6d, 0x61, 0x78, 0x5f, 0x62, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x05, 0x52, 0x0a, 0x6d, 0x61, 0x78, 0x42, 0x69, 0x74, 0x72, 0x61, 0x74, 0x65, 0x12, 0x37, + 0x0a, 0x18, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x5f, 0x64, 0x6f, 0x77, 0x6e, 0x5f, 0x62, 0x79, 0x18, 0x04, 0x20, 0x01, 0x28, 0x02, + 0x52, 0x15, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x44, 0x6f, 0x77, 0x6e, 0x42, 0x79, 0x12, 0x4e, 0x0a, 0x08, 0x70, 0x72, 0x69, 0x6f, 0x72, + 0x69, 0x74, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, - 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x0c, - 0x76, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x22, 0x79, 0x0a, 0x11, - 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, - 0x64, 0x12, 0x4a, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x5f, 0x67, 0x72, 0x61, - 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, - 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x52, 0x0d, - 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x47, 0x0a, 0x06, 0x47, 0x6f, 0x41, 0x77, 0x61, - 0x79, 0x12, 0x3d, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x25, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, - 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x47, 0x6f, 0x41, 0x77, - 0x61, 0x79, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, - 0x22, 0x4d, 0x0a, 0x09, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x12, 0x40, 0x0a, - 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, - 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, - 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x6e, 0x64, 0x65, - 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x42, - 0x64, 0x42, 0x0b, 0x53, 0x66, 0x75, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x56, 0x31, 0x50, 0x01, - 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x74, - 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2f, 0x73, - 0x66, 0x75, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xaa, 0x02, 0x1a, 0x53, 0x74, 0x72, 0x65, 0x61, - 0x6d, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x66, 0x75, 0x2e, 0x45, - 0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x4c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x65, 0x74, + 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x52, 0x08, 0x70, + 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x34, 0x0a, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, + 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, + 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x52, 0x05, 0x63, 0x6f, 0x64, 0x65, 0x63, 0x12, 0x23, 0x0a, + 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x65, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x22, 0x67, 0x0a, 0x08, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x1d, + 0x0a, 0x19, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x48, 0x49, 0x47, 0x48, 0x5f, + 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x10, 0x0a, + 0x0c, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x01, 0x12, + 0x13, 0x0a, 0x0f, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, 0x5f, 0x4d, 0x45, 0x44, 0x49, + 0x55, 0x4d, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x50, 0x52, 0x49, 0x4f, 0x52, 0x49, 0x54, 0x59, + 0x5f, 0x56, 0x45, 0x52, 0x59, 0x5f, 0x4c, 0x4f, 0x57, 0x10, 0x03, 0x22, 0xd6, 0x01, 0x0a, 0x0b, + 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x4e, 0x0a, 0x0d, 0x6d, + 0x65, 0x64, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, + 0x6f, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x52, 0x0c, 0x6d, + 0x65, 0x64, 0x69, 0x61, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x05, 0x63, + 0x6f, 0x64, 0x65, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, + 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x63, 0x52, 0x05, 0x63, 0x6f, 0x64, 0x65, + 0x63, 0x12, 0x41, 0x0a, 0x06, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x29, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, + 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, + 0x4c, 0x61, 0x79, 0x65, 0x72, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x52, 0x06, 0x6c, 0x61, + 0x79, 0x65, 0x72, 0x73, 0x22, 0xaa, 0x01, 0x0a, 0x14, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x51, 0x75, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x12, 0x48, 0x0a, + 0x0d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, + 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x41, 0x75, + 0x64, 0x69, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x0c, 0x61, 0x75, 0x64, 0x69, 0x6f, + 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x12, 0x48, 0x0a, 0x0d, 0x76, 0x69, 0x64, 0x65, 0x6f, + 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, + 0x75, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x6e, + 0x64, 0x65, 0x72, 0x52, 0x0c, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x53, 0x65, 0x6e, 0x64, 0x65, 0x72, + 0x73, 0x22, 0x79, 0x0a, 0x11, 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x12, 0x4a, 0x0a, 0x0e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x67, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, + 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, + 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, + 0x6e, 0x74, 0x73, 0x52, 0x0d, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x47, 0x72, 0x61, 0x6e, + 0x74, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x47, 0x0a, 0x06, + 0x47, 0x6f, 0x41, 0x77, 0x61, 0x79, 0x12, 0x3d, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, + 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, + 0x2e, 0x47, 0x6f, 0x41, 0x77, 0x61, 0x79, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x22, 0x4d, 0x0a, 0x09, 0x43, 0x61, 0x6c, 0x6c, 0x45, 0x6e, 0x64, + 0x65, 0x64, 0x12, 0x40, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x43, 0x61, 0x6c, + 0x6c, 0x45, 0x6e, 0x64, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x42, 0x64, 0x42, 0x0b, 0x53, 0x66, 0x75, 0x45, 0x76, 0x65, 0x6e, 0x74, + 0x73, 0x56, 0x31, 0x50, 0x01, 0x5a, 0x36, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x69, + 0x64, 0x65, 0x6f, 0x2f, 0x73, 0x66, 0x75, 0x2f, 0x65, 0x76, 0x65, 0x6e, 0x74, 0xaa, 0x02, 0x1a, + 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x76, 0x31, 0x2e, + 0x53, 0x66, 0x75, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, } var ( @@ -2725,122 +3012,131 @@ func file_video_sfu_event_events_proto_rawDescGZIP() []byte { } var file_video_sfu_event_events_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_video_sfu_event_events_proto_msgTypes = make([]protoimpl.MessageInfo, 32) +var file_video_sfu_event_events_proto_msgTypes = make([]protoimpl.MessageInfo, 35) var file_video_sfu_event_events_proto_goTypes = []interface{}{ (VideoLayerSetting_Priority)(0), // 0: stream.video.sfu.event.VideoLayerSetting.Priority (*SfuEvent)(nil), // 1: stream.video.sfu.event.SfuEvent - (*PinsChanged)(nil), // 2: stream.video.sfu.event.PinsChanged - (*Error)(nil), // 3: stream.video.sfu.event.Error - (*ICETrickle)(nil), // 4: stream.video.sfu.event.ICETrickle - (*ICERestart)(nil), // 5: stream.video.sfu.event.ICERestart - (*SfuRequest)(nil), // 6: stream.video.sfu.event.SfuRequest - (*HealthCheckRequest)(nil), // 7: stream.video.sfu.event.HealthCheckRequest - (*HealthCheckResponse)(nil), // 8: stream.video.sfu.event.HealthCheckResponse - (*TrackPublished)(nil), // 9: stream.video.sfu.event.TrackPublished - (*TrackUnpublished)(nil), // 10: stream.video.sfu.event.TrackUnpublished - (*JoinRequest)(nil), // 11: stream.video.sfu.event.JoinRequest - (*Migration)(nil), // 12: stream.video.sfu.event.Migration - (*JoinResponse)(nil), // 13: stream.video.sfu.event.JoinResponse - (*ParticipantJoined)(nil), // 14: stream.video.sfu.event.ParticipantJoined - (*ParticipantLeft)(nil), // 15: stream.video.sfu.event.ParticipantLeft - (*ParticipantUpdated)(nil), // 16: stream.video.sfu.event.ParticipantUpdated - (*SubscriberOffer)(nil), // 17: stream.video.sfu.event.SubscriberOffer - (*PublisherAnswer)(nil), // 18: stream.video.sfu.event.PublisherAnswer - (*ConnectionQualityChanged)(nil), // 19: stream.video.sfu.event.ConnectionQualityChanged - (*ConnectionQualityInfo)(nil), // 20: stream.video.sfu.event.ConnectionQualityInfo - (*DominantSpeakerChanged)(nil), // 21: stream.video.sfu.event.DominantSpeakerChanged - (*AudioLevel)(nil), // 22: stream.video.sfu.event.AudioLevel - (*AudioLevelChanged)(nil), // 23: stream.video.sfu.event.AudioLevelChanged - (*AudioMediaRequest)(nil), // 24: stream.video.sfu.event.AudioMediaRequest - (*AudioSender)(nil), // 25: stream.video.sfu.event.AudioSender - (*VideoMediaRequest)(nil), // 26: stream.video.sfu.event.VideoMediaRequest - (*VideoLayerSetting)(nil), // 27: stream.video.sfu.event.VideoLayerSetting - (*VideoSender)(nil), // 28: stream.video.sfu.event.VideoSender - (*ChangePublishQuality)(nil), // 29: stream.video.sfu.event.ChangePublishQuality - (*CallGrantsUpdated)(nil), // 30: stream.video.sfu.event.CallGrantsUpdated - (*GoAway)(nil), // 31: stream.video.sfu.event.GoAway - (*CallEnded)(nil), // 32: stream.video.sfu.event.CallEnded - (*models.ICETrickle)(nil), // 33: stream.video.sfu.models.ICETrickle - (*models.Pin)(nil), // 34: stream.video.sfu.models.Pin - (*models.Error)(nil), // 35: stream.video.sfu.models.Error - (models.WebsocketReconnectStrategy)(0), // 36: stream.video.sfu.models.WebsocketReconnectStrategy - (models.PeerType)(0), // 37: stream.video.sfu.models.PeerType - (*models.ParticipantCount)(nil), // 38: stream.video.sfu.models.ParticipantCount - (models.TrackType)(0), // 39: stream.video.sfu.models.TrackType - (*models.Participant)(nil), // 40: stream.video.sfu.models.Participant - (models.TrackUnpublishReason)(0), // 41: stream.video.sfu.models.TrackUnpublishReason - (*models.ClientDetails)(nil), // 42: stream.video.sfu.models.ClientDetails - (*models.TrackInfo)(nil), // 43: stream.video.sfu.models.TrackInfo - (*signal_rpc.TrackSubscriptionDetails)(nil), // 44: stream.video.sfu.signal.TrackSubscriptionDetails - (*models.CallState)(nil), // 45: stream.video.sfu.models.CallState - (models.ConnectionQuality)(0), // 46: stream.video.sfu.models.ConnectionQuality - (*models.Codec)(nil), // 47: stream.video.sfu.models.Codec - (*models.CallGrants)(nil), // 48: stream.video.sfu.models.CallGrants - (models.GoAwayReason)(0), // 49: stream.video.sfu.models.GoAwayReason - (models.CallEndedReason)(0), // 50: stream.video.sfu.models.CallEndedReason + (*ParticipantMigrationComplete)(nil), // 2: stream.video.sfu.event.ParticipantMigrationComplete + (*PinsChanged)(nil), // 3: stream.video.sfu.event.PinsChanged + (*Error)(nil), // 4: stream.video.sfu.event.Error + (*ICETrickle)(nil), // 5: stream.video.sfu.event.ICETrickle + (*ICERestart)(nil), // 6: stream.video.sfu.event.ICERestart + (*SfuRequest)(nil), // 7: stream.video.sfu.event.SfuRequest + (*LeaveCallRequest)(nil), // 8: stream.video.sfu.event.LeaveCallRequest + (*HealthCheckRequest)(nil), // 9: stream.video.sfu.event.HealthCheckRequest + (*HealthCheckResponse)(nil), // 10: stream.video.sfu.event.HealthCheckResponse + (*TrackPublished)(nil), // 11: stream.video.sfu.event.TrackPublished + (*TrackUnpublished)(nil), // 12: stream.video.sfu.event.TrackUnpublished + (*JoinRequest)(nil), // 13: stream.video.sfu.event.JoinRequest + (*ReconnectDetails)(nil), // 14: stream.video.sfu.event.ReconnectDetails + (*Migration)(nil), // 15: stream.video.sfu.event.Migration + (*JoinResponse)(nil), // 16: stream.video.sfu.event.JoinResponse + (*ParticipantJoined)(nil), // 17: stream.video.sfu.event.ParticipantJoined + (*ParticipantLeft)(nil), // 18: stream.video.sfu.event.ParticipantLeft + (*ParticipantUpdated)(nil), // 19: stream.video.sfu.event.ParticipantUpdated + (*SubscriberOffer)(nil), // 20: stream.video.sfu.event.SubscriberOffer + (*PublisherAnswer)(nil), // 21: stream.video.sfu.event.PublisherAnswer + (*ConnectionQualityChanged)(nil), // 22: stream.video.sfu.event.ConnectionQualityChanged + (*ConnectionQualityInfo)(nil), // 23: stream.video.sfu.event.ConnectionQualityInfo + (*DominantSpeakerChanged)(nil), // 24: stream.video.sfu.event.DominantSpeakerChanged + (*AudioLevel)(nil), // 25: stream.video.sfu.event.AudioLevel + (*AudioLevelChanged)(nil), // 26: stream.video.sfu.event.AudioLevelChanged + (*AudioMediaRequest)(nil), // 27: stream.video.sfu.event.AudioMediaRequest + (*AudioSender)(nil), // 28: stream.video.sfu.event.AudioSender + (*VideoMediaRequest)(nil), // 29: stream.video.sfu.event.VideoMediaRequest + (*VideoLayerSetting)(nil), // 30: stream.video.sfu.event.VideoLayerSetting + (*VideoSender)(nil), // 31: stream.video.sfu.event.VideoSender + (*ChangePublishQuality)(nil), // 32: stream.video.sfu.event.ChangePublishQuality + (*CallGrantsUpdated)(nil), // 33: stream.video.sfu.event.CallGrantsUpdated + (*GoAway)(nil), // 34: stream.video.sfu.event.GoAway + (*CallEnded)(nil), // 35: stream.video.sfu.event.CallEnded + (*models.ICETrickle)(nil), // 36: stream.video.sfu.models.ICETrickle + (*models.Pin)(nil), // 37: stream.video.sfu.models.Pin + (*models.Error)(nil), // 38: stream.video.sfu.models.Error + (models.WebsocketReconnectStrategy)(0), // 39: stream.video.sfu.models.WebsocketReconnectStrategy + (models.PeerType)(0), // 40: stream.video.sfu.models.PeerType + (*models.ParticipantCount)(nil), // 41: stream.video.sfu.models.ParticipantCount + (models.TrackType)(0), // 42: stream.video.sfu.models.TrackType + (*models.Participant)(nil), // 43: stream.video.sfu.models.Participant + (models.TrackUnpublishReason)(0), // 44: stream.video.sfu.models.TrackUnpublishReason + (*models.ClientDetails)(nil), // 45: stream.video.sfu.models.ClientDetails + (*models.TrackInfo)(nil), // 46: stream.video.sfu.models.TrackInfo + (*signal_rpc.TrackSubscriptionDetails)(nil), // 47: stream.video.sfu.signal.TrackSubscriptionDetails + (*models.CallState)(nil), // 48: stream.video.sfu.models.CallState + (models.ConnectionQuality)(0), // 49: stream.video.sfu.models.ConnectionQuality + (*models.Codec)(nil), // 50: stream.video.sfu.models.Codec + (*models.CallGrants)(nil), // 51: stream.video.sfu.models.CallGrants + (models.GoAwayReason)(0), // 52: stream.video.sfu.models.GoAwayReason + (models.CallEndedReason)(0), // 53: stream.video.sfu.models.CallEndedReason } var file_video_sfu_event_events_proto_depIdxs = []int32{ - 17, // 0: stream.video.sfu.event.SfuEvent.subscriber_offer:type_name -> stream.video.sfu.event.SubscriberOffer - 18, // 1: stream.video.sfu.event.SfuEvent.publisher_answer:type_name -> stream.video.sfu.event.PublisherAnswer - 19, // 2: stream.video.sfu.event.SfuEvent.connection_quality_changed:type_name -> stream.video.sfu.event.ConnectionQualityChanged - 23, // 3: stream.video.sfu.event.SfuEvent.audio_level_changed:type_name -> stream.video.sfu.event.AudioLevelChanged - 33, // 4: stream.video.sfu.event.SfuEvent.ice_trickle:type_name -> stream.video.sfu.models.ICETrickle - 29, // 5: stream.video.sfu.event.SfuEvent.change_publish_quality:type_name -> stream.video.sfu.event.ChangePublishQuality - 14, // 6: stream.video.sfu.event.SfuEvent.participant_joined:type_name -> stream.video.sfu.event.ParticipantJoined - 15, // 7: stream.video.sfu.event.SfuEvent.participant_left:type_name -> stream.video.sfu.event.ParticipantLeft - 21, // 8: stream.video.sfu.event.SfuEvent.dominant_speaker_changed:type_name -> stream.video.sfu.event.DominantSpeakerChanged - 13, // 9: stream.video.sfu.event.SfuEvent.join_response:type_name -> stream.video.sfu.event.JoinResponse - 8, // 10: stream.video.sfu.event.SfuEvent.health_check_response:type_name -> stream.video.sfu.event.HealthCheckResponse - 9, // 11: stream.video.sfu.event.SfuEvent.track_published:type_name -> stream.video.sfu.event.TrackPublished - 10, // 12: stream.video.sfu.event.SfuEvent.track_unpublished:type_name -> stream.video.sfu.event.TrackUnpublished - 3, // 13: stream.video.sfu.event.SfuEvent.error:type_name -> stream.video.sfu.event.Error - 30, // 14: stream.video.sfu.event.SfuEvent.call_grants_updated:type_name -> stream.video.sfu.event.CallGrantsUpdated - 31, // 15: stream.video.sfu.event.SfuEvent.go_away:type_name -> stream.video.sfu.event.GoAway - 5, // 16: stream.video.sfu.event.SfuEvent.ice_restart:type_name -> stream.video.sfu.event.ICERestart - 2, // 17: stream.video.sfu.event.SfuEvent.pins_updated:type_name -> stream.video.sfu.event.PinsChanged - 32, // 18: stream.video.sfu.event.SfuEvent.call_ended:type_name -> stream.video.sfu.event.CallEnded - 16, // 19: stream.video.sfu.event.SfuEvent.participant_updated:type_name -> stream.video.sfu.event.ParticipantUpdated - 34, // 20: stream.video.sfu.event.PinsChanged.pins:type_name -> stream.video.sfu.models.Pin - 35, // 21: stream.video.sfu.event.Error.error:type_name -> stream.video.sfu.models.Error - 36, // 22: stream.video.sfu.event.Error.reconnect_strategy:type_name -> stream.video.sfu.models.WebsocketReconnectStrategy - 37, // 23: stream.video.sfu.event.ICETrickle.peer_type:type_name -> stream.video.sfu.models.PeerType - 37, // 24: stream.video.sfu.event.ICERestart.peer_type:type_name -> stream.video.sfu.models.PeerType - 11, // 25: stream.video.sfu.event.SfuRequest.join_request:type_name -> stream.video.sfu.event.JoinRequest - 7, // 26: stream.video.sfu.event.SfuRequest.health_check_request:type_name -> stream.video.sfu.event.HealthCheckRequest - 38, // 27: stream.video.sfu.event.HealthCheckResponse.participant_count:type_name -> stream.video.sfu.models.ParticipantCount - 39, // 28: stream.video.sfu.event.TrackPublished.type:type_name -> stream.video.sfu.models.TrackType - 40, // 29: stream.video.sfu.event.TrackPublished.participant:type_name -> stream.video.sfu.models.Participant - 39, // 30: stream.video.sfu.event.TrackUnpublished.type:type_name -> stream.video.sfu.models.TrackType - 41, // 31: stream.video.sfu.event.TrackUnpublished.cause:type_name -> stream.video.sfu.models.TrackUnpublishReason - 40, // 32: stream.video.sfu.event.TrackUnpublished.participant:type_name -> stream.video.sfu.models.Participant - 42, // 33: stream.video.sfu.event.JoinRequest.client_details:type_name -> stream.video.sfu.models.ClientDetails - 12, // 34: stream.video.sfu.event.JoinRequest.migration:type_name -> stream.video.sfu.event.Migration - 43, // 35: stream.video.sfu.event.Migration.announced_tracks:type_name -> stream.video.sfu.models.TrackInfo - 44, // 36: stream.video.sfu.event.Migration.subscriptions:type_name -> stream.video.sfu.signal.TrackSubscriptionDetails - 45, // 37: stream.video.sfu.event.JoinResponse.call_state:type_name -> stream.video.sfu.models.CallState - 40, // 38: stream.video.sfu.event.ParticipantJoined.participant:type_name -> stream.video.sfu.models.Participant - 40, // 39: stream.video.sfu.event.ParticipantLeft.participant:type_name -> stream.video.sfu.models.Participant - 40, // 40: stream.video.sfu.event.ParticipantUpdated.participant:type_name -> stream.video.sfu.models.Participant - 20, // 41: stream.video.sfu.event.ConnectionQualityChanged.connection_quality_updates:type_name -> stream.video.sfu.event.ConnectionQualityInfo - 46, // 42: stream.video.sfu.event.ConnectionQualityInfo.connection_quality:type_name -> stream.video.sfu.models.ConnectionQuality - 22, // 43: stream.video.sfu.event.AudioLevelChanged.audio_levels:type_name -> stream.video.sfu.event.AudioLevel - 24, // 44: stream.video.sfu.event.AudioSender.media_request:type_name -> stream.video.sfu.event.AudioMediaRequest - 47, // 45: stream.video.sfu.event.AudioSender.codec:type_name -> stream.video.sfu.models.Codec - 0, // 46: stream.video.sfu.event.VideoLayerSetting.priority:type_name -> stream.video.sfu.event.VideoLayerSetting.Priority - 47, // 47: stream.video.sfu.event.VideoLayerSetting.codec:type_name -> stream.video.sfu.models.Codec - 26, // 48: stream.video.sfu.event.VideoSender.media_request:type_name -> stream.video.sfu.event.VideoMediaRequest - 47, // 49: stream.video.sfu.event.VideoSender.codec:type_name -> stream.video.sfu.models.Codec - 27, // 50: stream.video.sfu.event.VideoSender.layers:type_name -> stream.video.sfu.event.VideoLayerSetting - 25, // 51: stream.video.sfu.event.ChangePublishQuality.audio_senders:type_name -> stream.video.sfu.event.AudioSender - 28, // 52: stream.video.sfu.event.ChangePublishQuality.video_senders:type_name -> stream.video.sfu.event.VideoSender - 48, // 53: stream.video.sfu.event.CallGrantsUpdated.current_grants:type_name -> stream.video.sfu.models.CallGrants - 49, // 54: stream.video.sfu.event.GoAway.reason:type_name -> stream.video.sfu.models.GoAwayReason - 50, // 55: stream.video.sfu.event.CallEnded.reason:type_name -> stream.video.sfu.models.CallEndedReason - 56, // [56:56] is the sub-list for method output_type - 56, // [56:56] is the sub-list for method input_type - 56, // [56:56] is the sub-list for extension type_name - 56, // [56:56] is the sub-list for extension extendee - 0, // [0:56] is the sub-list for field type_name + 20, // 0: stream.video.sfu.event.SfuEvent.subscriber_offer:type_name -> stream.video.sfu.event.SubscriberOffer + 21, // 1: stream.video.sfu.event.SfuEvent.publisher_answer:type_name -> stream.video.sfu.event.PublisherAnswer + 22, // 2: stream.video.sfu.event.SfuEvent.connection_quality_changed:type_name -> stream.video.sfu.event.ConnectionQualityChanged + 26, // 3: stream.video.sfu.event.SfuEvent.audio_level_changed:type_name -> stream.video.sfu.event.AudioLevelChanged + 36, // 4: stream.video.sfu.event.SfuEvent.ice_trickle:type_name -> stream.video.sfu.models.ICETrickle + 32, // 5: stream.video.sfu.event.SfuEvent.change_publish_quality:type_name -> stream.video.sfu.event.ChangePublishQuality + 17, // 6: stream.video.sfu.event.SfuEvent.participant_joined:type_name -> stream.video.sfu.event.ParticipantJoined + 18, // 7: stream.video.sfu.event.SfuEvent.participant_left:type_name -> stream.video.sfu.event.ParticipantLeft + 24, // 8: stream.video.sfu.event.SfuEvent.dominant_speaker_changed:type_name -> stream.video.sfu.event.DominantSpeakerChanged + 16, // 9: stream.video.sfu.event.SfuEvent.join_response:type_name -> stream.video.sfu.event.JoinResponse + 10, // 10: stream.video.sfu.event.SfuEvent.health_check_response:type_name -> stream.video.sfu.event.HealthCheckResponse + 11, // 11: stream.video.sfu.event.SfuEvent.track_published:type_name -> stream.video.sfu.event.TrackPublished + 12, // 12: stream.video.sfu.event.SfuEvent.track_unpublished:type_name -> stream.video.sfu.event.TrackUnpublished + 4, // 13: stream.video.sfu.event.SfuEvent.error:type_name -> stream.video.sfu.event.Error + 33, // 14: stream.video.sfu.event.SfuEvent.call_grants_updated:type_name -> stream.video.sfu.event.CallGrantsUpdated + 34, // 15: stream.video.sfu.event.SfuEvent.go_away:type_name -> stream.video.sfu.event.GoAway + 6, // 16: stream.video.sfu.event.SfuEvent.ice_restart:type_name -> stream.video.sfu.event.ICERestart + 3, // 17: stream.video.sfu.event.SfuEvent.pins_updated:type_name -> stream.video.sfu.event.PinsChanged + 35, // 18: stream.video.sfu.event.SfuEvent.call_ended:type_name -> stream.video.sfu.event.CallEnded + 19, // 19: stream.video.sfu.event.SfuEvent.participant_updated:type_name -> stream.video.sfu.event.ParticipantUpdated + 2, // 20: stream.video.sfu.event.SfuEvent.participant_migration_complete:type_name -> stream.video.sfu.event.ParticipantMigrationComplete + 37, // 21: stream.video.sfu.event.PinsChanged.pins:type_name -> stream.video.sfu.models.Pin + 38, // 22: stream.video.sfu.event.Error.error:type_name -> stream.video.sfu.models.Error + 39, // 23: stream.video.sfu.event.Error.reconnect_strategy:type_name -> stream.video.sfu.models.WebsocketReconnectStrategy + 40, // 24: stream.video.sfu.event.ICETrickle.peer_type:type_name -> stream.video.sfu.models.PeerType + 40, // 25: stream.video.sfu.event.ICERestart.peer_type:type_name -> stream.video.sfu.models.PeerType + 13, // 26: stream.video.sfu.event.SfuRequest.join_request:type_name -> stream.video.sfu.event.JoinRequest + 9, // 27: stream.video.sfu.event.SfuRequest.health_check_request:type_name -> stream.video.sfu.event.HealthCheckRequest + 8, // 28: stream.video.sfu.event.SfuRequest.leave_call_request:type_name -> stream.video.sfu.event.LeaveCallRequest + 41, // 29: stream.video.sfu.event.HealthCheckResponse.participant_count:type_name -> stream.video.sfu.models.ParticipantCount + 42, // 30: stream.video.sfu.event.TrackPublished.type:type_name -> stream.video.sfu.models.TrackType + 43, // 31: stream.video.sfu.event.TrackPublished.participant:type_name -> stream.video.sfu.models.Participant + 42, // 32: stream.video.sfu.event.TrackUnpublished.type:type_name -> stream.video.sfu.models.TrackType + 44, // 33: stream.video.sfu.event.TrackUnpublished.cause:type_name -> stream.video.sfu.models.TrackUnpublishReason + 43, // 34: stream.video.sfu.event.TrackUnpublished.participant:type_name -> stream.video.sfu.models.Participant + 45, // 35: stream.video.sfu.event.JoinRequest.client_details:type_name -> stream.video.sfu.models.ClientDetails + 15, // 36: stream.video.sfu.event.JoinRequest.migration:type_name -> stream.video.sfu.event.Migration + 14, // 37: stream.video.sfu.event.JoinRequest.reconnect_details:type_name -> stream.video.sfu.event.ReconnectDetails + 39, // 38: stream.video.sfu.event.ReconnectDetails.strategy:type_name -> stream.video.sfu.models.WebsocketReconnectStrategy + 46, // 39: stream.video.sfu.event.ReconnectDetails.announced_tracks:type_name -> stream.video.sfu.models.TrackInfo + 47, // 40: stream.video.sfu.event.ReconnectDetails.subscriptions:type_name -> stream.video.sfu.signal.TrackSubscriptionDetails + 46, // 41: stream.video.sfu.event.Migration.announced_tracks:type_name -> stream.video.sfu.models.TrackInfo + 47, // 42: stream.video.sfu.event.Migration.subscriptions:type_name -> stream.video.sfu.signal.TrackSubscriptionDetails + 48, // 43: stream.video.sfu.event.JoinResponse.call_state:type_name -> stream.video.sfu.models.CallState + 43, // 44: stream.video.sfu.event.ParticipantJoined.participant:type_name -> stream.video.sfu.models.Participant + 43, // 45: stream.video.sfu.event.ParticipantLeft.participant:type_name -> stream.video.sfu.models.Participant + 43, // 46: stream.video.sfu.event.ParticipantUpdated.participant:type_name -> stream.video.sfu.models.Participant + 23, // 47: stream.video.sfu.event.ConnectionQualityChanged.connection_quality_updates:type_name -> stream.video.sfu.event.ConnectionQualityInfo + 49, // 48: stream.video.sfu.event.ConnectionQualityInfo.connection_quality:type_name -> stream.video.sfu.models.ConnectionQuality + 25, // 49: stream.video.sfu.event.AudioLevelChanged.audio_levels:type_name -> stream.video.sfu.event.AudioLevel + 27, // 50: stream.video.sfu.event.AudioSender.media_request:type_name -> stream.video.sfu.event.AudioMediaRequest + 50, // 51: stream.video.sfu.event.AudioSender.codec:type_name -> stream.video.sfu.models.Codec + 0, // 52: stream.video.sfu.event.VideoLayerSetting.priority:type_name -> stream.video.sfu.event.VideoLayerSetting.Priority + 50, // 53: stream.video.sfu.event.VideoLayerSetting.codec:type_name -> stream.video.sfu.models.Codec + 29, // 54: stream.video.sfu.event.VideoSender.media_request:type_name -> stream.video.sfu.event.VideoMediaRequest + 50, // 55: stream.video.sfu.event.VideoSender.codec:type_name -> stream.video.sfu.models.Codec + 30, // 56: stream.video.sfu.event.VideoSender.layers:type_name -> stream.video.sfu.event.VideoLayerSetting + 28, // 57: stream.video.sfu.event.ChangePublishQuality.audio_senders:type_name -> stream.video.sfu.event.AudioSender + 31, // 58: stream.video.sfu.event.ChangePublishQuality.video_senders:type_name -> stream.video.sfu.event.VideoSender + 51, // 59: stream.video.sfu.event.CallGrantsUpdated.current_grants:type_name -> stream.video.sfu.models.CallGrants + 52, // 60: stream.video.sfu.event.GoAway.reason:type_name -> stream.video.sfu.models.GoAwayReason + 53, // 61: stream.video.sfu.event.CallEnded.reason:type_name -> stream.video.sfu.models.CallEndedReason + 62, // [62:62] is the sub-list for method output_type + 62, // [62:62] is the sub-list for method input_type + 62, // [62:62] is the sub-list for extension type_name + 62, // [62:62] is the sub-list for extension extendee + 0, // [0:62] is the sub-list for field type_name } func init() { file_video_sfu_event_events_proto_init() } @@ -2862,7 +3158,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PinsChanged); i { + switch v := v.(*ParticipantMigrationComplete); i { case 0: return &v.state case 1: @@ -2874,7 +3170,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Error); i { + switch v := v.(*PinsChanged); i { case 0: return &v.state case 1: @@ -2886,7 +3182,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ICETrickle); i { + switch v := v.(*Error); i { case 0: return &v.state case 1: @@ -2898,7 +3194,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ICERestart); i { + switch v := v.(*ICETrickle); i { case 0: return &v.state case 1: @@ -2910,7 +3206,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SfuRequest); i { + switch v := v.(*ICERestart); i { case 0: return &v.state case 1: @@ -2922,7 +3218,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HealthCheckRequest); i { + switch v := v.(*SfuRequest); i { case 0: return &v.state case 1: @@ -2934,7 +3230,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*HealthCheckResponse); i { + switch v := v.(*LeaveCallRequest); i { case 0: return &v.state case 1: @@ -2946,7 +3242,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrackPublished); i { + switch v := v.(*HealthCheckRequest); i { case 0: return &v.state case 1: @@ -2958,7 +3254,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*TrackUnpublished); i { + switch v := v.(*HealthCheckResponse); i { case 0: return &v.state case 1: @@ -2970,7 +3266,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JoinRequest); i { + switch v := v.(*TrackPublished); i { case 0: return &v.state case 1: @@ -2982,7 +3278,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Migration); i { + switch v := v.(*TrackUnpublished); i { case 0: return &v.state case 1: @@ -2994,7 +3290,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*JoinResponse); i { + switch v := v.(*JoinRequest); i { case 0: return &v.state case 1: @@ -3006,7 +3302,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParticipantJoined); i { + switch v := v.(*ReconnectDetails); i { case 0: return &v.state case 1: @@ -3018,7 +3314,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParticipantLeft); i { + switch v := v.(*Migration); i { case 0: return &v.state case 1: @@ -3030,7 +3326,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ParticipantUpdated); i { + switch v := v.(*JoinResponse); i { case 0: return &v.state case 1: @@ -3042,7 +3338,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*SubscriberOffer); i { + switch v := v.(*ParticipantJoined); i { case 0: return &v.state case 1: @@ -3054,7 +3350,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*PublisherAnswer); i { + switch v := v.(*ParticipantLeft); i { case 0: return &v.state case 1: @@ -3066,7 +3362,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectionQualityChanged); i { + switch v := v.(*ParticipantUpdated); i { case 0: return &v.state case 1: @@ -3078,7 +3374,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ConnectionQualityInfo); i { + switch v := v.(*SubscriberOffer); i { case 0: return &v.state case 1: @@ -3090,7 +3386,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DominantSpeakerChanged); i { + switch v := v.(*PublisherAnswer); i { case 0: return &v.state case 1: @@ -3102,7 +3398,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AudioLevel); i { + switch v := v.(*ConnectionQualityChanged); i { case 0: return &v.state case 1: @@ -3114,7 +3410,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AudioLevelChanged); i { + switch v := v.(*ConnectionQualityInfo); i { case 0: return &v.state case 1: @@ -3126,7 +3422,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AudioMediaRequest); i { + switch v := v.(*DominantSpeakerChanged); i { case 0: return &v.state case 1: @@ -3138,7 +3434,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AudioSender); i { + switch v := v.(*AudioLevel); i { case 0: return &v.state case 1: @@ -3150,7 +3446,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[25].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VideoMediaRequest); i { + switch v := v.(*AudioLevelChanged); i { case 0: return &v.state case 1: @@ -3162,7 +3458,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[26].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VideoLayerSetting); i { + switch v := v.(*AudioMediaRequest); i { case 0: return &v.state case 1: @@ -3174,7 +3470,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[27].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VideoSender); i { + switch v := v.(*AudioSender); i { case 0: return &v.state case 1: @@ -3186,7 +3482,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[28].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ChangePublishQuality); i { + switch v := v.(*VideoMediaRequest); i { case 0: return &v.state case 1: @@ -3198,7 +3494,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[29].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CallGrantsUpdated); i { + switch v := v.(*VideoLayerSetting); i { case 0: return &v.state case 1: @@ -3210,7 +3506,7 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[30].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*GoAway); i { + switch v := v.(*VideoSender); i { case 0: return &v.state case 1: @@ -3222,6 +3518,42 @@ func file_video_sfu_event_events_proto_init() { } } file_video_sfu_event_events_proto_msgTypes[31].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ChangePublishQuality); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_video_sfu_event_events_proto_msgTypes[32].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CallGrantsUpdated); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_video_sfu_event_events_proto_msgTypes[33].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GoAway); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_video_sfu_event_events_proto_msgTypes[34].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*CallEnded); i { case 0: return &v.state @@ -3255,10 +3587,12 @@ func file_video_sfu_event_events_proto_init() { (*SfuEvent_PinsUpdated)(nil), (*SfuEvent_CallEnded)(nil), (*SfuEvent_ParticipantUpdated)(nil), + (*SfuEvent_ParticipantMigrationComplete)(nil), } - file_video_sfu_event_events_proto_msgTypes[5].OneofWrappers = []interface{}{ + file_video_sfu_event_events_proto_msgTypes[6].OneofWrappers = []interface{}{ (*SfuRequest_JoinRequest)(nil), (*SfuRequest_HealthCheckRequest)(nil), + (*SfuRequest_LeaveCallRequest)(nil), } type x struct{} out := protoimpl.TypeBuilder{ @@ -3266,7 +3600,7 @@ func file_video_sfu_event_events_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_video_sfu_event_events_proto_rawDesc, NumEnums: 1, - NumMessages: 32, + NumMessages: 35, NumExtensions: 0, NumServices: 0, }, diff --git a/stream-video-android-core/src/main/proto/video/sfu/event/events.proto b/stream-video-android-core/src/main/proto/video/sfu/event/events.proto index 8542b562f9..3200c912cf 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/event/events.proto +++ b/stream-video-android-core/src/main/proto/video/sfu/event/events.proto @@ -75,9 +75,15 @@ message SfuEvent { CallEnded call_ended = 23; // ParticipantUpdated is sent when user data is updated ParticipantUpdated participant_updated = 24; + // ParticipantMigrationComplete is sent when the participant migration is complete + ParticipantMigrationComplete participant_migration_complete = 25; } } +message ParticipantMigrationComplete{ + +} + message PinsChanged { // the list of pins in the call. // Pins are ordered in descending order (most important first). @@ -104,9 +110,15 @@ message SfuRequest { oneof request_payload { JoinRequest join_request = 1; HealthCheckRequest health_check_request = 2; + LeaveCallRequest leave_call_request = 3; } } +message LeaveCallRequest { + string session_id = 1; + string reason = 2; +} + message HealthCheckRequest { } @@ -147,9 +159,8 @@ message JoinRequest { // dumb SDP that allow us to extract subscriber's decode codecs string subscriber_sdp = 3; models.ClientDetails client_details = 4; - // TODO: we should know if this is going to be - // - publishing and subscribing, or just subscribing for future routing - Migration migration = 5; + // Deprecated: use ReconnectDetails instead + Migration migration = 5 [deprecated = true]; // Fast reconnect flag explicitly indicates that if the participant session // and the associated state is still present in the SFU, the client is ready // to restore the PeerConnection with an ICE restart. If the SFU replies with @@ -159,7 +170,19 @@ message JoinRequest { // For the SFU, fast_reconnect:false indicates that even if it has the state // cached, the client state is not in sync and hence it must be cleaned up before // proceeding further. - bool fast_reconnect = 6; + bool fast_reconnect = 6 [deprecated = true]; + + ReconnectDetails reconnect_details = 7; +} + +message ReconnectDetails { + models.WebsocketReconnectStrategy strategy = 1; + repeated models.TrackInfo announced_tracks = 3; + repeated signal.TrackSubscriptionDetails subscriptions = 4; + uint32 reconnect_attempt = 5; + string from_sfu_id = 6; + // only set in case of rejoin + string previous_session_id = 7; } message Migration { @@ -171,6 +194,7 @@ message Migration { message JoinResponse { models.CallState call_state = 1; bool reconnected = 2; + int32 fast_reconnect_deadline_seconds = 3; } // ParticipantJoined is fired when a user joins a call diff --git a/stream-video-android-core/src/main/proto/video/sfu/event/events_vtproto.pb.go b/stream-video-android-core/src/main/proto/video/sfu/event/events_vtproto.pb.go index 387f0fd358..be9a808604 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/event/events_vtproto.pb.go +++ b/stream-video-android-core/src/main/proto/video/sfu/event/events_vtproto.pb.go @@ -478,6 +478,60 @@ func (m *SfuEvent_ParticipantUpdated) MarshalToSizedBufferVT(dAtA []byte) (int, } return len(dAtA) - i, nil } +func (m *SfuEvent_ParticipantMigrationComplete) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *SfuEvent_ParticipantMigrationComplete) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.ParticipantMigrationComplete != nil { + size, err := m.ParticipantMigrationComplete.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xca + } + return len(dAtA) - i, nil +} +func (m *ParticipantMigrationComplete) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ParticipantMigrationComplete) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ParticipantMigrationComplete) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + return len(dAtA) - i, nil +} + func (m *PinsChanged) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -761,6 +815,72 @@ func (m *SfuRequest_HealthCheckRequest) MarshalToSizedBufferVT(dAtA []byte) (int } return len(dAtA) - i, nil } +func (m *SfuRequest_LeaveCallRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *SfuRequest_LeaveCallRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + i := len(dAtA) + if m.LeaveCallRequest != nil { + size, err := m.LeaveCallRequest.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x1a + } + return len(dAtA) - i, nil +} +func (m *LeaveCallRequest) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *LeaveCallRequest) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *LeaveCallRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.Reason) > 0 { + i -= len(m.Reason) + copy(dAtA[i:], m.Reason) + i = encodeVarint(dAtA, i, uint64(len(m.Reason))) + i-- + dAtA[i] = 0x12 + } + if len(m.SessionId) > 0 { + i -= len(m.SessionId) + copy(dAtA[i:], m.SessionId) + i = encodeVarint(dAtA, i, uint64(len(m.SessionId))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *HealthCheckRequest) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1032,6 +1152,16 @@ func (m *JoinRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.ReconnectDetails != nil { + size, err := m.ReconnectDetails.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + i-- + dAtA[i] = 0x3a + } if m.FastReconnect { i-- if m.FastReconnect { @@ -1098,6 +1228,111 @@ func (m *JoinRequest) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ReconnectDetails) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ReconnectDetails) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *ReconnectDetails) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if len(m.PreviousSessionId) > 0 { + i -= len(m.PreviousSessionId) + copy(dAtA[i:], m.PreviousSessionId) + i = encodeVarint(dAtA, i, uint64(len(m.PreviousSessionId))) + i-- + dAtA[i] = 0x3a + } + if len(m.FromSfuId) > 0 { + i -= len(m.FromSfuId) + copy(dAtA[i:], m.FromSfuId) + i = encodeVarint(dAtA, i, uint64(len(m.FromSfuId))) + i-- + dAtA[i] = 0x32 + } + if m.ReconnectAttempt != 0 { + i = encodeVarint(dAtA, i, uint64(m.ReconnectAttempt)) + i-- + dAtA[i] = 0x28 + } + if len(m.Subscriptions) > 0 { + for iNdEx := len(m.Subscriptions) - 1; iNdEx >= 0; iNdEx-- { + if marshalto, ok := interface{}(m.Subscriptions[iNdEx]).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.Subscriptions[iNdEx]) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = encodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.AnnouncedTracks) > 0 { + for iNdEx := len(m.AnnouncedTracks) - 1; iNdEx >= 0; iNdEx-- { + if marshalto, ok := interface{}(m.AnnouncedTracks[iNdEx]).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.AnnouncedTracks[iNdEx]) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = encodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0x1a + } + } + if m.Strategy != 0 { + i = encodeVarint(dAtA, i, uint64(m.Strategy)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *Migration) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1216,6 +1451,11 @@ func (m *JoinResponse) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if m.FastReconnectDeadlineSeconds != 0 { + i = encodeVarint(dAtA, i, uint64(m.FastReconnectDeadlineSeconds)) + i-- + dAtA[i] = 0x18 + } if m.Reconnected { i-- if m.Reconnected { @@ -2567,6 +2807,30 @@ func (m *SfuEvent_ParticipantUpdated) SizeVT() (n int) { } return n } +func (m *SfuEvent_ParticipantMigrationComplete) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.ParticipantMigrationComplete != nil { + l = m.ParticipantMigrationComplete.SizeVT() + n += 2 + l + sov(uint64(l)) + } + return n +} +func (m *ParticipantMigrationComplete) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.unknownFields != nil { + n += len(m.unknownFields) + } + return n +} + func (m *PinsChanged) SizeVT() (n int) { if m == nil { return 0 @@ -2689,6 +2953,38 @@ func (m *SfuRequest_HealthCheckRequest) SizeVT() (n int) { } return n } +func (m *SfuRequest_LeaveCallRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.LeaveCallRequest != nil { + l = m.LeaveCallRequest.SizeVT() + n += 1 + l + sov(uint64(l)) + } + return n +} +func (m *LeaveCallRequest) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.SessionId) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.Reason) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + if m.unknownFields != nil { + n += len(m.unknownFields) + } + return n +} + func (m *HealthCheckRequest) SizeVT() (n int) { if m == nil { return 0 @@ -2827,21 +3123,75 @@ func (m *JoinRequest) SizeVT() (n int) { if m.FastReconnect { n += 2 } + if m.ReconnectDetails != nil { + l = m.ReconnectDetails.SizeVT() + n += 1 + l + sov(uint64(l)) + } if m.unknownFields != nil { n += len(m.unknownFields) } return n } -func (m *Migration) SizeVT() (n int) { +func (m *ReconnectDetails) SizeVT() (n int) { if m == nil { return 0 } var l int _ = l - l = len(m.FromSfuId) - if l > 0 { - n += 1 + l + sov(uint64(l)) + if m.Strategy != 0 { + n += 1 + sov(uint64(m.Strategy)) + } + if len(m.AnnouncedTracks) > 0 { + for _, e := range m.AnnouncedTracks { + if size, ok := interface{}(e).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(e) + } + n += 1 + l + sov(uint64(l)) + } + } + if len(m.Subscriptions) > 0 { + for _, e := range m.Subscriptions { + if size, ok := interface{}(e).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(e) + } + n += 1 + l + sov(uint64(l)) + } + } + if m.ReconnectAttempt != 0 { + n += 1 + sov(uint64(m.ReconnectAttempt)) + } + l = len(m.FromSfuId) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.PreviousSessionId) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + if m.unknownFields != nil { + n += len(m.unknownFields) + } + return n +} + +func (m *Migration) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.FromSfuId) + if l > 0 { + n += 1 + l + sov(uint64(l)) } if len(m.AnnouncedTracks) > 0 { for _, e := range m.AnnouncedTracks { @@ -2892,6 +3242,9 @@ func (m *JoinResponse) SizeVT() (n int) { if m.Reconnected { n += 2 } + if m.FastReconnectDeadlineSeconds != 0 { + n += 1 + sov(uint64(m.FastReconnectDeadlineSeconds)) + } if m.unknownFields != nil { n += len(m.unknownFields) } @@ -4202,6 +4555,98 @@ func (m *SfuEvent) UnmarshalVT(dAtA []byte) error { m.EventPayload = &SfuEvent_ParticipantUpdated{v} } iNdEx = postIndex + case 25: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ParticipantMigrationComplete", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if oneof, ok := m.EventPayload.(*SfuEvent_ParticipantMigrationComplete); ok { + if err := oneof.ParticipantMigrationComplete.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + v := &ParticipantMigrationComplete{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.EventPayload = &SfuEvent_ParticipantMigrationComplete{v} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ParticipantMigrationComplete) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ParticipantMigrationComplete: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ParticipantMigrationComplete: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -4714,111 +5159,9 @@ func (m *SfuRequest) UnmarshalVT(dAtA []byte) error { m.RequestPayload = &SfuRequest_HealthCheckRequest{v} } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HealthCheckRequest) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HealthCheckRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HealthCheckRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HealthCheckResponse) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HealthCheckResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HealthCheckResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ParticipantCount", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field LeaveCallRequest", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4845,19 +5188,16 @@ func (m *HealthCheckResponse) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ParticipantCount == nil { - m.ParticipantCount = &models.ParticipantCount{} - } - if unmarshal, ok := interface{}(m.ParticipantCount).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + if oneof, ok := m.RequestPayload.(*SfuRequest_LeaveCallRequest); ok { + if err := oneof.LeaveCallRequest.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ParticipantCount); err != nil { + v := &LeaveCallRequest{} + if err := v.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } + m.RequestPayload = &SfuRequest_LeaveCallRequest{v} } iNdEx = postIndex default: @@ -4882,7 +5222,7 @@ func (m *HealthCheckResponse) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { +func (m *LeaveCallRequest) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4905,15 +5245,15 @@ func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: TrackPublished: wiretype end group for non-group") + return fmt.Errorf("proto: LeaveCallRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: TrackPublished: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: LeaveCallRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UserId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4941,11 +5281,11 @@ func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.UserId = string(dAtA[iNdEx:postIndex]) + m.SessionId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Reason", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4973,7 +5313,446 @@ func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.SessionId = string(dAtA[iNdEx:postIndex]) + m.Reason = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HealthCheckRequest) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HealthCheckRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HealthCheckRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *HealthCheckResponse) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: HealthCheckResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: HealthCheckResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ParticipantCount", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ParticipantCount == nil { + m.ParticipantCount = &models.ParticipantCount{} + } + if unmarshal, ok := interface{}(m.ParticipantCount).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ParticipantCount); err != nil { + return err + } + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TrackPublished: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TrackPublished: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UserId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SessionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + } + m.Type = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Type |= models.TrackType(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Participant", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Participant == nil { + m.Participant = &models.Participant{} + } + if unmarshal, ok := interface{}(m.Participant).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Participant); err != nil { + return err + } + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TrackUnpublished: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TrackUnpublished: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UserId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.UserId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SessionId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { @@ -4995,6 +5774,25 @@ func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { } } case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Cause", wireType) + } + m.Cause = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Cause |= models.TrackUnpublishReason(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Participant", wireType) } @@ -5060,7 +5858,7 @@ func (m *TrackPublished) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { +func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5079,19 +5877,83 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { if b < 0x80 { break } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TrackUnpublished: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TrackUnpublished: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SessionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UserId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field SubscriberSdp", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -5119,13 +5981,13 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.UserId = string(dAtA[iNdEx:postIndex]) + m.SubscriberSdp = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ClientDetails", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5135,29 +5997,41 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.SessionId = string(dAtA[iNdEx:postIndex]) + if m.ClientDetails == nil { + m.ClientDetails = &models.ClientDetails{} + } + if unmarshal, ok := interface{}(m.ClientDetails).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ClientDetails); err != nil { + return err + } + } iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Migration", wireType) } - m.Type = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5167,16 +6041,33 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Type |= models.TrackType(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - case 4: + if msglen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Migration == nil { + m.Migration = &Migration{} + } + if err := m.Migration.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Cause", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FastReconnect", wireType) } - m.Cause = 0 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5186,14 +6077,15 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Cause |= models.TrackUnpublishReason(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - case 5: + m.FastReconnect = bool(v != 0) + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Participant", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ReconnectDetails", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5220,19 +6112,11 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Participant == nil { - m.Participant = &models.Participant{} + if m.ReconnectDetails == nil { + m.ReconnectDetails = &ReconnectDetails{} } - if unmarshal, ok := interface{}(m.Participant).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Participant); err != nil { - return err - } + if err := m.ReconnectDetails.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -5257,7 +6141,7 @@ func (m *TrackUnpublished) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { +func (m *ReconnectDetails) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -5280,17 +6164,17 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: JoinRequest: wiretype end group for non-group") + return fmt.Errorf("proto: ReconnectDetails: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: JoinRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ReconnectDetails: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Strategy", wireType) } - var stringLen uint64 + m.Strategy = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5300,29 +6184,16 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Strategy |= models.WebsocketReconnectStrategy(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SessionId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AnnouncedTracks", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5332,59 +6203,37 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.SessionId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SubscriberSdp", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + m.AnnouncedTracks = append(m.AnnouncedTracks, &models.TrackInfo{}) + if unmarshal, ok := interface{}(m.AnnouncedTracks[len(m.AnnouncedTracks)-1]).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.AnnouncedTracks[len(m.AnnouncedTracks)-1]); err != nil { + return err } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SubscriberSdp = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ClientDetails", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Subscriptions", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -5411,26 +6260,43 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ClientDetails == nil { - m.ClientDetails = &models.ClientDetails{} - } - if unmarshal, ok := interface{}(m.ClientDetails).(interface { + m.Subscriptions = append(m.Subscriptions, &signal_rpc.TrackSubscriptionDetails{}) + if unmarshal, ok := interface{}(m.Subscriptions[len(m.Subscriptions)-1]).(interface { UnmarshalVT([]byte) error }); ok { if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { return err } } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.ClientDetails); err != nil { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Subscriptions[len(m.Subscriptions)-1]); err != nil { return err } } iNdEx = postIndex case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ReconnectAttempt", wireType) + } + m.ReconnectAttempt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ReconnectAttempt |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Migration", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FromSfuId", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5440,33 +6306,29 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Migration == nil { - m.Migration = &Migration{} - } - if err := m.Migration.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.FromSfuId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FastReconnect", wireType) + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PreviousSessionId", wireType) } - var v int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -5476,12 +6338,24 @@ func (m *JoinRequest) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - m.FastReconnect = bool(v != 0) + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PreviousSessionId = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -5764,6 +6638,25 @@ func (m *JoinResponse) UnmarshalVT(dAtA []byte) error { } } m.Reconnected = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FastReconnectDeadlineSeconds", wireType) + } + m.FastReconnectDeadlineSeconds = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.FastReconnectDeadlineSeconds |= int32(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) diff --git a/stream-video-android-core/src/main/proto/video/sfu/models/models.pb.go b/stream-video-android-core/src/main/proto/video/sfu/models/models.pb.go index 9c35234d67..f8d0d90d10 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/models/models.pb.go +++ b/stream-video-android-core/src/main/proto/video/sfu/models/models.pb.go @@ -331,14 +331,16 @@ func (ErrorCode) EnumDescriptor() ([]byte, []int) { type SdkType int32 const ( - SdkType_SDK_TYPE_UNSPECIFIED SdkType = 0 - SdkType_SDK_TYPE_REACT SdkType = 1 - SdkType_SDK_TYPE_ANGULAR SdkType = 2 - SdkType_SDK_TYPE_ANDROID SdkType = 3 - SdkType_SDK_TYPE_IOS SdkType = 4 - SdkType_SDK_TYPE_FLUTTER SdkType = 5 - SdkType_SDK_TYPE_REACT_NATIVE SdkType = 6 - SdkType_SDK_TYPE_UNITY SdkType = 7 + SdkType_SDK_TYPE_UNSPECIFIED SdkType = 0 + SdkType_SDK_TYPE_REACT SdkType = 1 + SdkType_SDK_TYPE_ANGULAR SdkType = 2 + SdkType_SDK_TYPE_ANDROID SdkType = 3 + SdkType_SDK_TYPE_IOS SdkType = 4 + SdkType_SDK_TYPE_FLUTTER SdkType = 5 + SdkType_SDK_TYPE_REACT_NATIVE SdkType = 6 + SdkType_SDK_TYPE_UNITY SdkType = 7 + SdkType_SDK_TYPE_GO SdkType = 8 + SdkType_SDK_TYPE_PLAIN_JAVASCRIPT SdkType = 9 ) // Enum value maps for SdkType. @@ -352,16 +354,20 @@ var ( 5: "SDK_TYPE_FLUTTER", 6: "SDK_TYPE_REACT_NATIVE", 7: "SDK_TYPE_UNITY", + 8: "SDK_TYPE_GO", + 9: "SDK_TYPE_PLAIN_JAVASCRIPT", } SdkType_value = map[string]int32{ - "SDK_TYPE_UNSPECIFIED": 0, - "SDK_TYPE_REACT": 1, - "SDK_TYPE_ANGULAR": 2, - "SDK_TYPE_ANDROID": 3, - "SDK_TYPE_IOS": 4, - "SDK_TYPE_FLUTTER": 5, - "SDK_TYPE_REACT_NATIVE": 6, - "SDK_TYPE_UNITY": 7, + "SDK_TYPE_UNSPECIFIED": 0, + "SDK_TYPE_REACT": 1, + "SDK_TYPE_ANGULAR": 2, + "SDK_TYPE_ANDROID": 3, + "SDK_TYPE_IOS": 4, + "SDK_TYPE_FLUTTER": 5, + "SDK_TYPE_REACT_NATIVE": 6, + "SDK_TYPE_UNITY": 7, + "SDK_TYPE_GO": 8, + "SDK_TYPE_PLAIN_JAVASCRIPT": 9, } ) @@ -566,19 +572,16 @@ type WebsocketReconnectStrategy int32 const ( WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED WebsocketReconnectStrategy = 0 - // Sent after reaching the maximum reconnection attempts, leading to permanent disconnect. + // Sent after reaching the maximum reconnection attempts, or any other unrecoverable error leading to permanent disconnect. WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT WebsocketReconnectStrategy = 1 // SDK should maintaining existing publisher/subscriber pc instances // and establish a new WebSocket connection. WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_FAST WebsocketReconnectStrategy = 2 - // SDK should drop existing pc instances and creates a fresh WebSocket connection, - // ensuring a clean state for the reconnection. - WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_CLEAN WebsocketReconnectStrategy = 3 - // SDK should obtain new credentials from the coordinator, drops existing pc instances, and initializes + // SDK should obtain new credentials from the coordinator, drops existing pc instances, set a new session_id and initializes // a completely new WebSocket connection, ensuring a comprehensive reset. - WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_FULL WebsocketReconnectStrategy = 4 + WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_REJOIN WebsocketReconnectStrategy = 3 // SDK should migrate to a new SFU instance - WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_MIGRATE WebsocketReconnectStrategy = 5 + WebsocketReconnectStrategy_WEBSOCKET_RECONNECT_STRATEGY_MIGRATE WebsocketReconnectStrategy = 4 ) // Enum value maps for WebsocketReconnectStrategy. @@ -587,17 +590,15 @@ var ( 0: "WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED", 1: "WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT", 2: "WEBSOCKET_RECONNECT_STRATEGY_FAST", - 3: "WEBSOCKET_RECONNECT_STRATEGY_CLEAN", - 4: "WEBSOCKET_RECONNECT_STRATEGY_FULL", - 5: "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE", + 3: "WEBSOCKET_RECONNECT_STRATEGY_REJOIN", + 4: "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE", } WebsocketReconnectStrategy_value = map[string]int32{ "WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED": 0, "WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT": 1, "WEBSOCKET_RECONNECT_STRATEGY_FAST": 2, - "WEBSOCKET_RECONNECT_STRATEGY_CLEAN": 3, - "WEBSOCKET_RECONNECT_STRATEGY_FULL": 4, - "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE": 5, + "WEBSOCKET_RECONNECT_STRATEGY_REJOIN": 3, + "WEBSOCKET_RECONNECT_STRATEGY_MIGRATE": 4, } ) @@ -1407,106 +1408,6 @@ func (x *TrackInfo) GetRed() bool { return false } -// todo remove this -type Call struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - // the call type - Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` - // the call id - Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` - // the id of the user that created this call - CreatedByUserId string `protobuf:"bytes,3,opt,name=created_by_user_id,json=createdByUserId,proto3" json:"created_by_user_id,omitempty"` - // the id of the current host for this call - HostUserId string `protobuf:"bytes,4,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` - Custom *structpb.Struct `protobuf:"bytes,5,opt,name=custom,proto3" json:"custom,omitempty"` - CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` - UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` -} - -func (x *Call) Reset() { - *x = Call{} - if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Call) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Call) ProtoMessage() {} - -func (x *Call) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Call.ProtoReflect.Descriptor instead. -func (*Call) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{10} -} - -func (x *Call) GetType() string { - if x != nil { - return x.Type - } - return "" -} - -func (x *Call) GetId() string { - if x != nil { - return x.Id - } - return "" -} - -func (x *Call) GetCreatedByUserId() string { - if x != nil { - return x.CreatedByUserId - } - return "" -} - -func (x *Call) GetHostUserId() string { - if x != nil { - return x.HostUserId - } - return "" -} - -func (x *Call) GetCustom() *structpb.Struct { - if x != nil { - return x.Custom - } - return nil -} - -func (x *Call) GetCreatedAt() *timestamppb.Timestamp { - if x != nil { - return x.CreatedAt - } - return nil -} - -func (x *Call) GetUpdatedAt() *timestamppb.Timestamp { - if x != nil { - return x.UpdatedAt - } - return nil -} - type Error struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1520,7 +1421,7 @@ type Error struct { func (x *Error) Reset() { *x = Error{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[11] + mi := &file_video_sfu_models_models_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1533,7 +1434,7 @@ func (x *Error) String() string { func (*Error) ProtoMessage() {} func (x *Error) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[11] + mi := &file_video_sfu_models_models_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1546,7 +1447,7 @@ func (x *Error) ProtoReflect() protoreflect.Message { // Deprecated: Use Error.ProtoReflect.Descriptor instead. func (*Error) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{11} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{10} } func (x *Error) GetCode() ErrorCode { @@ -1584,7 +1485,7 @@ type ClientDetails struct { func (x *ClientDetails) Reset() { *x = ClientDetails{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[12] + mi := &file_video_sfu_models_models_proto_msgTypes[11] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1597,7 +1498,7 @@ func (x *ClientDetails) String() string { func (*ClientDetails) ProtoMessage() {} func (x *ClientDetails) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[12] + mi := &file_video_sfu_models_models_proto_msgTypes[11] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1610,7 +1511,7 @@ func (x *ClientDetails) ProtoReflect() protoreflect.Message { // Deprecated: Use ClientDetails.ProtoReflect.Descriptor instead. func (*ClientDetails) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{12} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{11} } func (x *ClientDetails) GetSdk() *Sdk { @@ -1655,7 +1556,7 @@ type Sdk struct { func (x *Sdk) Reset() { *x = Sdk{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[13] + mi := &file_video_sfu_models_models_proto_msgTypes[12] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1668,7 +1569,7 @@ func (x *Sdk) String() string { func (*Sdk) ProtoMessage() {} func (x *Sdk) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[13] + mi := &file_video_sfu_models_models_proto_msgTypes[12] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1681,7 +1582,7 @@ func (x *Sdk) ProtoReflect() protoreflect.Message { // Deprecated: Use Sdk.ProtoReflect.Descriptor instead. func (*Sdk) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{13} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{12} } func (x *Sdk) GetType() SdkType { @@ -1725,7 +1626,7 @@ type OS struct { func (x *OS) Reset() { *x = OS{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[14] + mi := &file_video_sfu_models_models_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1738,7 +1639,7 @@ func (x *OS) String() string { func (*OS) ProtoMessage() {} func (x *OS) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[14] + mi := &file_video_sfu_models_models_proto_msgTypes[13] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1751,7 +1652,7 @@ func (x *OS) ProtoReflect() protoreflect.Message { // Deprecated: Use OS.ProtoReflect.Descriptor instead. func (*OS) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{14} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{13} } func (x *OS) GetName() string { @@ -1787,7 +1688,7 @@ type Browser struct { func (x *Browser) Reset() { *x = Browser{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[15] + mi := &file_video_sfu_models_models_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1800,7 +1701,7 @@ func (x *Browser) String() string { func (*Browser) ProtoMessage() {} func (x *Browser) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[15] + mi := &file_video_sfu_models_models_proto_msgTypes[14] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1813,7 +1714,7 @@ func (x *Browser) ProtoReflect() protoreflect.Message { // Deprecated: Use Browser.ProtoReflect.Descriptor instead. func (*Browser) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{15} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{14} } func (x *Browser) GetName() string { @@ -1842,7 +1743,7 @@ type Device struct { func (x *Device) Reset() { *x = Device{} if protoimpl.UnsafeEnabled { - mi := &file_video_sfu_models_models_proto_msgTypes[16] + mi := &file_video_sfu_models_models_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -1855,7 +1756,7 @@ func (x *Device) String() string { func (*Device) ProtoMessage() {} func (x *Device) ProtoReflect() protoreflect.Message { - mi := &file_video_sfu_models_models_proto_msgTypes[16] + mi := &file_video_sfu_models_models_proto_msgTypes[15] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -1868,7 +1769,7 @@ func (x *Device) ProtoReflect() protoreflect.Message { // Deprecated: Use Device.ProtoReflect.Descriptor instead. func (*Device) Descriptor() ([]byte, []int) { - return file_video_sfu_models_models_proto_rawDescGZIP(), []int{16} + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{15} } func (x *Device) GetName() string { @@ -1885,6 +1786,105 @@ func (x *Device) GetVersion() string { return "" } +type Call struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // the call type + Type string `protobuf:"bytes,1,opt,name=type,proto3" json:"type,omitempty"` + // the call id + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // the id of the user that created this call + CreatedByUserId string `protobuf:"bytes,3,opt,name=created_by_user_id,json=createdByUserId,proto3" json:"created_by_user_id,omitempty"` + // the id of the current host for this call + HostUserId string `protobuf:"bytes,4,opt,name=host_user_id,json=hostUserId,proto3" json:"host_user_id,omitempty"` + Custom *structpb.Struct `protobuf:"bytes,5,opt,name=custom,proto3" json:"custom,omitempty"` + CreatedAt *timestamppb.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamppb.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` +} + +func (x *Call) Reset() { + *x = Call{} + if protoimpl.UnsafeEnabled { + mi := &file_video_sfu_models_models_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Call) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Call) ProtoMessage() {} + +func (x *Call) ProtoReflect() protoreflect.Message { + mi := &file_video_sfu_models_models_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Call.ProtoReflect.Descriptor instead. +func (*Call) Descriptor() ([]byte, []int) { + return file_video_sfu_models_models_proto_rawDescGZIP(), []int{16} +} + +func (x *Call) GetType() string { + if x != nil { + return x.Type + } + return "" +} + +func (x *Call) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Call) GetCreatedByUserId() string { + if x != nil { + return x.CreatedByUserId + } + return "" +} + +func (x *Call) GetHostUserId() string { + if x != nil { + return x.HostUserId + } + return "" +} + +func (x *Call) GetCustom() *structpb.Struct { + if x != nil { + return x.Custom + } + return nil +} + +func (x *Call) GetCreatedAt() *timestamppb.Timestamp { + if x != nil { + return x.CreatedAt + } + return nil +} + +func (x *Call) GetUpdatedAt() *timestamppb.Timestamp { + if x != nil { + return x.UpdatedAt + } + return nil +} + // CallGrants represents the set of permissions given // to the user for the current call. type CallGrants struct { @@ -2085,68 +2085,68 @@ var file_video_sfu_models_models_proto_rawDesc = []byte{ 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x03, 0x64, 0x74, 0x78, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x73, 0x74, 0x65, 0x72, 0x65, 0x6f, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x03, 0x72, 0x65, 0x64, 0x22, 0xa0, 0x02, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x12, - 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, - 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, - 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x12, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, - 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, - 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, - 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, - 0x64, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74, - 0x6f, 0x6d, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, - 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, - 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, - 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0x7c, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x36, 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x22, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, - 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, - 0x6f, 0x64, 0x65, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, - 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x72, 0x65, - 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x6c, - 0x64, 0x52, 0x65, 0x74, 0x72, 0x79, 0x22, 0xe1, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2e, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, - 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, - 0x53, 0x64, 0x6b, 0x52, 0x03, 0x73, 0x64, 0x6b, 0x12, 0x2b, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, - 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x4f, - 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, 0x3a, 0x0a, 0x07, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, - 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, - 0x2e, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x52, 0x07, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, - 0x72, 0x12, 0x37, 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, - 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, - 0x63, 0x65, 0x52, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x7d, 0x0a, 0x03, 0x53, 0x64, - 0x6b, 0x12, 0x34, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, - 0x20, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, - 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x53, 0x64, 0x6b, 0x54, 0x79, 0x70, - 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, - 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, - 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x56, 0x0a, 0x02, 0x4f, 0x53, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, - 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x22, 0x37, 0x0a, 0x07, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x52, 0x03, 0x72, 0x65, 0x64, 0x22, 0x7c, 0x0a, 0x05, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x36, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x22, 0x2e, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, + 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x52, 0x65, + 0x74, 0x72, 0x79, 0x22, 0xe1, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x44, 0x65, + 0x74, 0x61, 0x69, 0x6c, 0x73, 0x12, 0x2e, 0x0a, 0x03, 0x73, 0x64, 0x6b, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, + 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x53, 0x64, 0x6b, + 0x52, 0x03, 0x73, 0x64, 0x6b, 0x12, 0x2b, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, + 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x4f, 0x53, 0x52, 0x02, + 0x6f, 0x73, 0x12, 0x3a, 0x0a, 0x07, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, + 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x42, 0x72, + 0x6f, 0x77, 0x73, 0x65, 0x72, 0x52, 0x07, 0x62, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x12, 0x37, + 0x0a, 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, + 0x75, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x52, + 0x06, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x22, 0x7d, 0x0a, 0x03, 0x53, 0x64, 0x6b, 0x12, 0x34, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x20, 0x2e, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x73, 0x66, 0x75, 0x2e, + 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x2e, 0x53, 0x64, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, + 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, + 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x56, 0x0a, 0x02, 0x4f, 0x53, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x36, 0x0a, 0x06, 0x44, 0x65, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x22, 0x8d, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, 0x6e, 0x74, + 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, + 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x22, 0x37, + 0x0a, 0x07, 0x42, 0x72, 0x6f, 0x77, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, + 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x36, 0x0a, 0x06, 0x44, 0x65, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, + 0xa0, 0x02, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x0e, 0x0a, 0x02, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x2b, 0x0a, 0x12, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, + 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x42, 0x79, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0c, 0x68, 0x6f, 0x73, + 0x74, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x68, 0x6f, 0x73, 0x74, 0x55, 0x73, 0x65, 0x72, 0x49, 0x64, 0x12, 0x2f, 0x0a, 0x06, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, + 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x12, 0x39, 0x0a, 0x0a, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x75, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x22, 0x8d, 0x01, 0x0a, 0x0a, 0x43, 0x61, 0x6c, 0x6c, 0x47, 0x72, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x61, 0x6e, 0x5f, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x5f, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x61, 0x6e, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x41, 0x75, 0x64, 0x69, 0x6f, 0x12, 0x2a, 0x0a, @@ -2235,7 +2235,7 @@ var file_video_sfu_models_models_proto_rawDesc = []byte{ 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x46, 0x55, 0x5f, 0x53, 0x48, 0x55, 0x54, 0x54, 0x49, 0x4e, 0x47, 0x5f, 0x44, 0x4f, 0x57, 0x4e, 0x10, 0xd8, 0x04, 0x12, 0x18, 0x0a, 0x13, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x46, 0x55, 0x5f, 0x46, - 0x55, 0x4c, 0x4c, 0x10, 0xbc, 0x05, 0x2a, 0xba, 0x01, 0x0a, 0x07, 0x53, 0x64, 0x6b, 0x54, 0x79, + 0x55, 0x4c, 0x4c, 0x10, 0xbc, 0x05, 0x2a, 0xea, 0x01, 0x0a, 0x07, 0x53, 0x64, 0x6b, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x14, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x43, 0x54, 0x10, 0x01, @@ -2247,7 +2247,10 @@ var file_video_sfu_models_models_proto_rawDesc = []byte{ 0x45, 0x52, 0x10, 0x05, 0x12, 0x19, 0x0a, 0x15, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x52, 0x45, 0x41, 0x43, 0x54, 0x5f, 0x4e, 0x41, 0x54, 0x49, 0x56, 0x45, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x49, 0x54, - 0x59, 0x10, 0x07, 0x2a, 0xbb, 0x01, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x70, + 0x59, 0x10, 0x07, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x47, 0x4f, 0x10, 0x08, 0x12, 0x1d, 0x0a, 0x19, 0x53, 0x44, 0x4b, 0x5f, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x50, 0x4c, 0x41, 0x49, 0x4e, 0x5f, 0x4a, 0x41, 0x56, 0x41, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x10, 0x09, 0x2a, 0xbb, 0x01, 0x0a, 0x14, 0x54, 0x72, 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x52, 0x41, 0x43, 0x4b, 0x5f, 0x55, 0x4e, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x53, 0x48, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, @@ -2277,7 +2280,7 @@ var file_video_sfu_models_models_proto_rawDesc = []byte{ 0x45, 0x4e, 0x44, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x4b, 0x49, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x03, 0x12, 0x23, 0x0a, 0x1f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x45, 0x4e, 0x44, 0x45, 0x44, 0x5f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x45, 0x53, 0x53, 0x49, - 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x04, 0x2a, 0x97, 0x02, 0x0a, 0x1a, 0x57, + 0x4f, 0x4e, 0x5f, 0x45, 0x4e, 0x44, 0x45, 0x44, 0x10, 0x04, 0x2a, 0xf1, 0x01, 0x0a, 0x1a, 0x57, 0x65, 0x62, 0x73, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x2c, 0x0a, 0x28, 0x57, 0x45, 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, @@ -2287,22 +2290,19 @@ var file_video_sfu_models_models_proto_rawDesc = []byte{ 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x44, 0x49, 0x53, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x10, 0x01, 0x12, 0x25, 0x0a, 0x21, 0x57, 0x45, 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, - 0x54, 0x45, 0x47, 0x59, 0x5f, 0x46, 0x41, 0x53, 0x54, 0x10, 0x02, 0x12, 0x26, 0x0a, 0x22, 0x57, + 0x54, 0x45, 0x47, 0x59, 0x5f, 0x46, 0x41, 0x53, 0x54, 0x10, 0x02, 0x12, 0x27, 0x0a, 0x23, 0x57, 0x45, 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, - 0x43, 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x43, 0x4c, 0x45, 0x41, - 0x4e, 0x10, 0x03, 0x12, 0x25, 0x0a, 0x21, 0x57, 0x45, 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, - 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x54, - 0x45, 0x47, 0x59, 0x5f, 0x46, 0x55, 0x4c, 0x4c, 0x10, 0x04, 0x12, 0x28, 0x0a, 0x24, 0x57, 0x45, - 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, 0x54, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, - 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, - 0x54, 0x45, 0x10, 0x05, 0x42, 0x65, 0x42, 0x0b, 0x53, 0x66, 0x75, 0x4d, 0x6f, 0x64, 0x65, 0x6c, - 0x73, 0x56, 0x31, 0x50, 0x01, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x47, 0x65, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x69, - 0x64, 0x65, 0x6f, 0x2f, 0x73, 0x66, 0x75, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0xaa, 0x02, - 0x1a, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x66, 0x75, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x43, 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, 0x54, 0x45, 0x47, 0x59, 0x5f, 0x52, 0x45, 0x4a, 0x4f, + 0x49, 0x4e, 0x10, 0x03, 0x12, 0x28, 0x0a, 0x24, 0x57, 0x45, 0x42, 0x53, 0x4f, 0x43, 0x4b, 0x45, + 0x54, 0x5f, 0x52, 0x45, 0x43, 0x4f, 0x4e, 0x4e, 0x45, 0x43, 0x54, 0x5f, 0x53, 0x54, 0x52, 0x41, + 0x54, 0x45, 0x47, 0x59, 0x5f, 0x4d, 0x49, 0x47, 0x52, 0x41, 0x54, 0x45, 0x10, 0x04, 0x42, 0x65, + 0x42, 0x0b, 0x53, 0x66, 0x75, 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x56, 0x31, 0x50, 0x01, 0x5a, + 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x47, 0x65, 0x74, 0x53, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x76, 0x69, 0x64, 0x65, 0x6f, 0x2f, 0x73, 0x66, + 0x75, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x73, 0xaa, 0x02, 0x1a, 0x53, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x2e, 0x56, 0x69, 0x64, 0x65, 0x6f, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x66, 0x75, 0x2e, 0x4d, + 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2340,13 +2340,13 @@ var file_video_sfu_models_models_proto_goTypes = []interface{}{ (*Codec)(nil), // 17: stream.video.sfu.models.Codec (*ICETrickle)(nil), // 18: stream.video.sfu.models.ICETrickle (*TrackInfo)(nil), // 19: stream.video.sfu.models.TrackInfo - (*Call)(nil), // 20: stream.video.sfu.models.Call - (*Error)(nil), // 21: stream.video.sfu.models.Error - (*ClientDetails)(nil), // 22: stream.video.sfu.models.ClientDetails - (*Sdk)(nil), // 23: stream.video.sfu.models.Sdk - (*OS)(nil), // 24: stream.video.sfu.models.OS - (*Browser)(nil), // 25: stream.video.sfu.models.Browser - (*Device)(nil), // 26: stream.video.sfu.models.Device + (*Error)(nil), // 20: stream.video.sfu.models.Error + (*ClientDetails)(nil), // 21: stream.video.sfu.models.ClientDetails + (*Sdk)(nil), // 22: stream.video.sfu.models.Sdk + (*OS)(nil), // 23: stream.video.sfu.models.OS + (*Browser)(nil), // 24: stream.video.sfu.models.Browser + (*Device)(nil), // 25: stream.video.sfu.models.Device + (*Call)(nil), // 26: stream.video.sfu.models.Call (*CallGrants)(nil), // 27: stream.video.sfu.models.CallGrants (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp (*structpb.Struct)(nil), // 29: google.protobuf.Struct @@ -2366,15 +2366,15 @@ var file_video_sfu_models_models_proto_depIdxs = []int32{ 0, // 11: stream.video.sfu.models.ICETrickle.peer_type:type_name -> stream.video.sfu.models.PeerType 3, // 12: stream.video.sfu.models.TrackInfo.track_type:type_name -> stream.video.sfu.models.TrackType 16, // 13: stream.video.sfu.models.TrackInfo.layers:type_name -> stream.video.sfu.models.VideoLayer - 29, // 14: stream.video.sfu.models.Call.custom:type_name -> google.protobuf.Struct - 28, // 15: stream.video.sfu.models.Call.created_at:type_name -> google.protobuf.Timestamp - 28, // 16: stream.video.sfu.models.Call.updated_at:type_name -> google.protobuf.Timestamp - 4, // 17: stream.video.sfu.models.Error.code:type_name -> stream.video.sfu.models.ErrorCode - 23, // 18: stream.video.sfu.models.ClientDetails.sdk:type_name -> stream.video.sfu.models.Sdk - 24, // 19: stream.video.sfu.models.ClientDetails.os:type_name -> stream.video.sfu.models.OS - 25, // 20: stream.video.sfu.models.ClientDetails.browser:type_name -> stream.video.sfu.models.Browser - 26, // 21: stream.video.sfu.models.ClientDetails.device:type_name -> stream.video.sfu.models.Device - 5, // 22: stream.video.sfu.models.Sdk.type:type_name -> stream.video.sfu.models.SdkType + 4, // 14: stream.video.sfu.models.Error.code:type_name -> stream.video.sfu.models.ErrorCode + 22, // 15: stream.video.sfu.models.ClientDetails.sdk:type_name -> stream.video.sfu.models.Sdk + 23, // 16: stream.video.sfu.models.ClientDetails.os:type_name -> stream.video.sfu.models.OS + 24, // 17: stream.video.sfu.models.ClientDetails.browser:type_name -> stream.video.sfu.models.Browser + 25, // 18: stream.video.sfu.models.ClientDetails.device:type_name -> stream.video.sfu.models.Device + 5, // 19: stream.video.sfu.models.Sdk.type:type_name -> stream.video.sfu.models.SdkType + 29, // 20: stream.video.sfu.models.Call.custom:type_name -> google.protobuf.Struct + 28, // 21: stream.video.sfu.models.Call.created_at:type_name -> google.protobuf.Timestamp + 28, // 22: stream.video.sfu.models.Call.updated_at:type_name -> google.protobuf.Timestamp 23, // [23:23] is the sub-list for method output_type 23, // [23:23] is the sub-list for method input_type 23, // [23:23] is the sub-list for extension type_name @@ -2509,7 +2509,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Call); i { + switch v := v.(*Error); i { case 0: return &v.state case 1: @@ -2521,7 +2521,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Error); i { + switch v := v.(*ClientDetails); i { case 0: return &v.state case 1: @@ -2533,7 +2533,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClientDetails); i { + switch v := v.(*Sdk); i { case 0: return &v.state case 1: @@ -2545,7 +2545,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Sdk); i { + switch v := v.(*OS); i { case 0: return &v.state case 1: @@ -2557,7 +2557,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*OS); i { + switch v := v.(*Browser); i { case 0: return &v.state case 1: @@ -2569,7 +2569,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Browser); i { + switch v := v.(*Device); i { case 0: return &v.state case 1: @@ -2581,7 +2581,7 @@ func file_video_sfu_models_models_proto_init() { } } file_video_sfu_models_models_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Device); i { + switch v := v.(*Call); i { case 0: return &v.state case 1: diff --git a/stream-video-android-core/src/main/proto/video/sfu/models/models.proto b/stream-video-android-core/src/main/proto/video/sfu/models/models.proto index a772402cde..2960015213 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/models/models.proto +++ b/stream-video-android-core/src/main/proto/video/sfu/models/models.proto @@ -135,22 +135,6 @@ message TrackInfo { bool red = 9; } -// todo remove this -message Call { - // the call type - string type = 1; - // the call id - string id = 2; - // the id of the user that created this call - string created_by_user_id = 3; - // the id of the current host for this call - string host_user_id = 4; - google.protobuf.Struct custom = 5; - google.protobuf.Timestamp created_at = 6; - google.protobuf.Timestamp updated_at = 7; -} - - enum ErrorCode { ERROR_CODE_UNSPECIFIED = 0; @@ -203,6 +187,8 @@ enum SdkType { SDK_TYPE_FLUTTER = 5; SDK_TYPE_REACT_NATIVE = 6; SDK_TYPE_UNITY = 7; + SDK_TYPE_GO = 8; + SDK_TYPE_PLAIN_JAVASCRIPT = 9; } message Sdk { @@ -228,6 +214,20 @@ message Device { string version = 2; } +message Call { + // the call type + string type = 1; + // the call id + string id = 2; + // the id of the user that created this call + string created_by_user_id = 3; + // the id of the current host for this call + string host_user_id = 4; + google.protobuf.Struct custom = 5; + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp updated_at = 7; +} + enum TrackUnpublishReason { // Default value which is used when the specific reason // for muting the track is not known. @@ -273,17 +273,14 @@ enum CallEndedReason { // WebsocketReconnectStrategy defines the ws strategies available for handling reconnections. enum WebsocketReconnectStrategy { WEBSOCKET_RECONNECT_STRATEGY_UNSPECIFIED = 0; - // Sent after reaching the maximum reconnection attempts, leading to permanent disconnect. + // Sent after reaching the maximum reconnection attempts, or any other unrecoverable error leading to permanent disconnect. WEBSOCKET_RECONNECT_STRATEGY_DISCONNECT = 1; // SDK should maintaining existing publisher/subscriber pc instances // and establish a new WebSocket connection. WEBSOCKET_RECONNECT_STRATEGY_FAST = 2; - // SDK should drop existing pc instances and creates a fresh WebSocket connection, - // ensuring a clean state for the reconnection. - WEBSOCKET_RECONNECT_STRATEGY_CLEAN = 3; - // SDK should obtain new credentials from the coordinator, drops existing pc instances, and initializes + // SDK should obtain new credentials from the coordinator, drops existing pc instances, set a new session_id and initializes // a completely new WebSocket connection, ensuring a comprehensive reset. - WEBSOCKET_RECONNECT_STRATEGY_FULL = 4; + WEBSOCKET_RECONNECT_STRATEGY_REJOIN = 3; // SDK should migrate to a new SFU instance - WEBSOCKET_RECONNECT_STRATEGY_MIGRATE = 5; -}; \ No newline at end of file + WEBSOCKET_RECONNECT_STRATEGY_MIGRATE = 4; +}; diff --git a/stream-video-android-core/src/main/proto/video/sfu/models/models_vtproto.pb.go b/stream-video-android-core/src/main/proto/video/sfu/models/models_vtproto.pb.go index c32790047b..82f8e2dfb4 100644 --- a/stream-video-android-core/src/main/proto/video/sfu/models/models_vtproto.pb.go +++ b/stream-video-android-core/src/main/proto/video/sfu/models/models_vtproto.pb.go @@ -747,133 +747,6 @@ func (m *TrackInfo) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *Call) MarshalVT() (dAtA []byte, err error) { - if m == nil { - return nil, nil - } - size := m.SizeVT() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBufferVT(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Call) MarshalToVT(dAtA []byte) (int, error) { - size := m.SizeVT() - return m.MarshalToSizedBufferVT(dAtA[:size]) -} - -func (m *Call) MarshalToSizedBufferVT(dAtA []byte) (int, error) { - if m == nil { - return 0, nil - } - i := len(dAtA) - _ = i - var l int - _ = l - if m.unknownFields != nil { - i -= len(m.unknownFields) - copy(dAtA[i:], m.unknownFields) - } - if m.UpdatedAt != nil { - if marshalto, ok := interface{}(m.UpdatedAt).(interface { - MarshalToSizedBufferVT([]byte) (int, error) - }); ok { - size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.UpdatedAt) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = encodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x3a - } - if m.CreatedAt != nil { - if marshalto, ok := interface{}(m.CreatedAt).(interface { - MarshalToSizedBufferVT([]byte) (int, error) - }); ok { - size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.CreatedAt) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = encodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x32 - } - if m.Custom != nil { - if marshalto, ok := interface{}(m.Custom).(interface { - MarshalToSizedBufferVT([]byte) (int, error) - }); ok { - size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) - if err != nil { - return 0, err - } - i -= size - i = encodeVarint(dAtA, i, uint64(size)) - } else { - encoded, err := proto.Marshal(m.Custom) - if err != nil { - return 0, err - } - i -= len(encoded) - copy(dAtA[i:], encoded) - i = encodeVarint(dAtA, i, uint64(len(encoded))) - } - i-- - dAtA[i] = 0x2a - } - if len(m.HostUserId) > 0 { - i -= len(m.HostUserId) - copy(dAtA[i:], m.HostUserId) - i = encodeVarint(dAtA, i, uint64(len(m.HostUserId))) - i-- - dAtA[i] = 0x22 - } - if len(m.CreatedByUserId) > 0 { - i -= len(m.CreatedByUserId) - copy(dAtA[i:], m.CreatedByUserId) - i = encodeVarint(dAtA, i, uint64(len(m.CreatedByUserId))) - i-- - dAtA[i] = 0x1a - } - if len(m.Id) > 0 { - i -= len(m.Id) - copy(dAtA[i:], m.Id) - i = encodeVarint(dAtA, i, uint64(len(m.Id))) - i-- - dAtA[i] = 0x12 - } - if len(m.Type) > 0 { - i -= len(m.Type) - copy(dAtA[i:], m.Type) - i = encodeVarint(dAtA, i, uint64(len(m.Type))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *Error) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1209,6 +1082,133 @@ func (m *Device) MarshalToSizedBufferVT(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *Call) MarshalVT() (dAtA []byte, err error) { + if m == nil { + return nil, nil + } + size := m.SizeVT() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBufferVT(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Call) MarshalToVT(dAtA []byte) (int, error) { + size := m.SizeVT() + return m.MarshalToSizedBufferVT(dAtA[:size]) +} + +func (m *Call) MarshalToSizedBufferVT(dAtA []byte) (int, error) { + if m == nil { + return 0, nil + } + i := len(dAtA) + _ = i + var l int + _ = l + if m.unknownFields != nil { + i -= len(m.unknownFields) + copy(dAtA[i:], m.unknownFields) + } + if m.UpdatedAt != nil { + if marshalto, ok := interface{}(m.UpdatedAt).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.UpdatedAt) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = encodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0x3a + } + if m.CreatedAt != nil { + if marshalto, ok := interface{}(m.CreatedAt).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.CreatedAt) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = encodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0x32 + } + if m.Custom != nil { + if marshalto, ok := interface{}(m.Custom).(interface { + MarshalToSizedBufferVT([]byte) (int, error) + }); ok { + size, err := marshalto.MarshalToSizedBufferVT(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarint(dAtA, i, uint64(size)) + } else { + encoded, err := proto.Marshal(m.Custom) + if err != nil { + return 0, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = encodeVarint(dAtA, i, uint64(len(encoded))) + } + i-- + dAtA[i] = 0x2a + } + if len(m.HostUserId) > 0 { + i -= len(m.HostUserId) + copy(dAtA[i:], m.HostUserId) + i = encodeVarint(dAtA, i, uint64(len(m.HostUserId))) + i-- + dAtA[i] = 0x22 + } + if len(m.CreatedByUserId) > 0 { + i -= len(m.CreatedByUserId) + copy(dAtA[i:], m.CreatedByUserId) + i = encodeVarint(dAtA, i, uint64(len(m.CreatedByUserId))) + i-- + dAtA[i] = 0x1a + } + if len(m.Id) > 0 { + i -= len(m.Id) + copy(dAtA[i:], m.Id) + i = encodeVarint(dAtA, i, uint64(len(m.Id))) + i-- + dAtA[i] = 0x12 + } + if len(m.Type) > 0 { + i -= len(m.Type) + copy(dAtA[i:], m.Type) + i = encodeVarint(dAtA, i, uint64(len(m.Type))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *CallGrants) MarshalVT() (dAtA []byte, err error) { if m == nil { return nil, nil @@ -1599,65 +1599,7 @@ func (m *TrackInfo) SizeVT() (n int) { return n } -func (m *Call) SizeVT() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Type) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - l = len(m.Id) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - l = len(m.CreatedByUserId) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - l = len(m.HostUserId) - if l > 0 { - n += 1 + l + sov(uint64(l)) - } - if m.Custom != nil { - if size, ok := interface{}(m.Custom).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.Custom) - } - n += 1 + l + sov(uint64(l)) - } - if m.CreatedAt != nil { - if size, ok := interface{}(m.CreatedAt).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.CreatedAt) - } - n += 1 + l + sov(uint64(l)) - } - if m.UpdatedAt != nil { - if size, ok := interface{}(m.UpdatedAt).(interface { - SizeVT() int - }); ok { - l = size.SizeVT() - } else { - l = proto.Size(m.UpdatedAt) - } - n += 1 + l + sov(uint64(l)) - } - if m.unknownFields != nil { - n += len(m.unknownFields) - } - return n -} - -func (m *Error) SizeVT() (n int) { +func (m *Error) SizeVT() (n int) { if m == nil { return 0 } @@ -1798,6 +1740,64 @@ func (m *Device) SizeVT() (n int) { return n } +func (m *Call) SizeVT() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Type) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.Id) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.CreatedByUserId) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + l = len(m.HostUserId) + if l > 0 { + n += 1 + l + sov(uint64(l)) + } + if m.Custom != nil { + if size, ok := interface{}(m.Custom).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(m.Custom) + } + n += 1 + l + sov(uint64(l)) + } + if m.CreatedAt != nil { + if size, ok := interface{}(m.CreatedAt).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(m.CreatedAt) + } + n += 1 + l + sov(uint64(l)) + } + if m.UpdatedAt != nil { + if size, ok := interface{}(m.UpdatedAt).(interface { + SizeVT() int + }); ok { + l = size.SizeVT() + } else { + l = proto.Size(m.UpdatedAt) + } + n += 1 + l + sov(uint64(l)) + } + if m.unknownFields != nil { + n += len(m.unknownFields) + } + return n +} + func (m *CallGrants) SizeVT() (n int) { if m == nil { return 0 @@ -3644,7 +3644,7 @@ func (m *TrackInfo) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *Call) UnmarshalVT(dAtA []byte) error { +func (m *Error) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3667,17 +3667,17 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Call: wiretype end group for non-group") + return fmt.Errorf("proto: Error: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Call: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Error: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) } - var stringLen uint64 + m.Code = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -3687,27 +3687,14 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + m.Code |= ErrorCode(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Type = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -3735,13 +3722,13 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(dAtA[iNdEx:postIndex]) + m.Message = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CreatedByUserId", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ShouldRetry", wireType) } - var stringLen uint64 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -3751,29 +3738,68 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + v |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLength + m.ShouldRetry = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err } - postIndex := iNdEx + intStringLen - if postIndex < 0 { + if (skippy < 0) || (iNdEx+skippy) < 0 { return ErrInvalidLength } - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - m.CreatedByUserId = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 4: + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ClientDetails: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ClientDetails: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field HostUserId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Sdk", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -3783,27 +3809,31 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.HostUserId = string(dAtA[iNdEx:postIndex]) + if m.Sdk == nil { + m.Sdk = &Sdk{} + } + if err := m.Sdk.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 5: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Custom", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Os", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3830,24 +3860,16 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Custom == nil { - m.Custom = &structpb.Struct{} + if m.Os == nil { + m.Os = &OS{} } - if unmarshal, ok := interface{}(m.Custom).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Custom); err != nil { - return err - } + if err := m.Os.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex - case 6: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CreatedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Browser", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3874,24 +3896,16 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.CreatedAt == nil { - m.CreatedAt = ×tamppb.Timestamp{} + if m.Browser == nil { + m.Browser = &Browser{} } - if unmarshal, ok := interface{}(m.CreatedAt).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.CreatedAt); err != nil { - return err - } + if err := m.Browser.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex - case 7: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -3918,19 +3932,11 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.UpdatedAt == nil { - m.UpdatedAt = ×tamppb.Timestamp{} + if m.Device == nil { + m.Device = &Device{} } - if unmarshal, ok := interface{}(m.UpdatedAt).(interface { - UnmarshalVT([]byte) error - }); ok { - if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - } else { - if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.UpdatedAt); err != nil { - return err - } + if err := m.Device.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -3955,7 +3961,7 @@ func (m *Call) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *Error) UnmarshalVT(dAtA []byte) error { +func (m *Sdk) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -3978,17 +3984,17 @@ func (m *Error) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Error: wiretype end group for non-group") + return fmt.Errorf("proto: Sdk: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Error: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Sdk: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Code", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) } - m.Code = 0 + m.Type = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -3998,14 +4004,14 @@ func (m *Error) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Code |= ErrorCode(b&0x7F) << shift + m.Type |= SdkType(b&0x7F) << shift if b < 0x80 { break } } case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4033,13 +4039,13 @@ func (m *Error) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Message = string(dAtA[iNdEx:postIndex]) + m.Major = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ShouldRetry", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Minor", wireType) } - var v int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4049,12 +4055,56 @@ func (m *Error) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - v |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - m.ShouldRetry = bool(v != 0) + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Minor = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Patch", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Patch = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skip(dAtA[iNdEx:]) @@ -4077,7 +4127,7 @@ func (m *Error) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { +func (m *OS) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4100,17 +4150,17 @@ func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ClientDetails: wiretype end group for non-group") + return fmt.Errorf("proto: OS: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ClientDetails: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: OS: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sdk", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4120,33 +4170,29 @@ func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Sdk == nil { - m.Sdk = &Sdk{} - } - if err := m.Sdk.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Os", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4156,69 +4202,29 @@ func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Os == nil { - m.Os = &OS{} - } - if err := m.Os.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Browser", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - msglen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLength - } - postIndex := iNdEx + msglen - if postIndex < 0 { - return ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Browser == nil { - m.Browser = &Browser{} - } - if err := m.Browser.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Architecture", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4228,27 +4234,23 @@ func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - msglen |= int(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLength } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - if m.Device == nil { - m.Device = &Device{} - } - if err := m.Device.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { - return err - } + m.Architecture = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -4272,7 +4274,7 @@ func (m *ClientDetails) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *Sdk) UnmarshalVT(dAtA []byte) error { +func (m *Browser) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4295,17 +4297,17 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Sdk: wiretype end group for non-group") + return fmt.Errorf("proto: Browser: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Sdk: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Browser: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } - m.Type = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4315,14 +4317,27 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Type |= SdkType(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Name = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4350,11 +4365,62 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Major = string(dAtA[iNdEx:postIndex]) + m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 3: + default: + iNdEx = preIndex + skippy, err := skip(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLength + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) UnmarshalVT(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Minor", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4382,11 +4448,11 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Minor = string(dAtA[iNdEx:postIndex]) + m.Name = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 4: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Patch", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4414,7 +4480,7 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Patch = string(dAtA[iNdEx:postIndex]) + m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -4438,7 +4504,7 @@ func (m *Sdk) UnmarshalVT(dAtA []byte) error { } return nil } -func (m *OS) UnmarshalVT(dAtA []byte) error { +func (m *Call) UnmarshalVT(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -4461,15 +4527,15 @@ func (m *OS) UnmarshalVT(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: OS: wiretype end group for non-group") + return fmt.Errorf("proto: Call: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: OS: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Call: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4497,11 +4563,11 @@ func (m *OS) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.Type = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4529,11 +4595,11 @@ func (m *OS) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Version = string(dAtA[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Architecture", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CreatedByUserId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4561,62 +4627,11 @@ func (m *OS) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Architecture = string(dAtA[iNdEx:postIndex]) + m.CreatedByUserId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Browser) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Browser: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Browser: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field HostUserId", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -4644,13 +4659,13 @@ func (m *Browser) UnmarshalVT(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + m.HostUserId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 2: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Custom", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4660,80 +4675,41 @@ func (m *Browser) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Version = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skip(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLength - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - m.unknownFields = append(m.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Device) UnmarshalVT(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + if m.Custom == nil { + m.Custom = &structpb.Struct{} } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break + if unmarshal, ok := interface{}(m.Custom).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.Custom); err != nil { + return err + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Device: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + iNdEx = postIndex + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field CreatedAt", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4743,29 +4719,41 @@ func (m *Device) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Name = string(dAtA[iNdEx:postIndex]) + if m.CreatedAt == nil { + m.CreatedAt = ×tamppb.Timestamp{} + } + if unmarshal, ok := interface{}(m.CreatedAt).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.CreatedAt); err != nil { + return err + } + } iNdEx = postIndex - case 2: + case 7: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UpdatedAt", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflow @@ -4775,23 +4763,35 @@ func (m *Device) UnmarshalVT(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - stringLen |= uint64(b&0x7F) << shift + msglen |= int(b&0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLength } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex < 0 { return ErrInvalidLength } if postIndex > l { return io.ErrUnexpectedEOF } - m.Version = string(dAtA[iNdEx:postIndex]) + if m.UpdatedAt == nil { + m.UpdatedAt = ×tamppb.Timestamp{} + } + if unmarshal, ok := interface{}(m.UpdatedAt).(interface { + UnmarshalVT([]byte) error + }); ok { + if err := unmarshal.UnmarshalVT(dAtA[iNdEx:postIndex]); err != nil { + return err + } + } else { + if err := proto.Unmarshal(dAtA[iNdEx:postIndex], m.UpdatedAt); err != nil { + return err + } + } iNdEx = postIndex default: iNdEx = preIndex diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallCrudTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallCrudTest.kt index 179756b813..eac706499c 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallCrudTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/CallCrudTest.kt @@ -28,6 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.openapitools.client.models.MemberRequest import org.robolectric.RobolectricTestRunner +import kotlin.test.assertTrue @RunWith(RobolectricTestRunner::class) public class CallCrudTest : IntegrationTestBase() { @@ -130,7 +131,7 @@ public class CallCrudTest : IntegrationTestBase() { val result = client.call("default", randomUUID()).update() assert(result.isFailure) result.onError { - assertThat((it as Error.NetworkError).serverErrorCode).isEqualTo(16) + assertTrue { it is Error.ThrowableError } } } @@ -139,7 +140,7 @@ public class CallCrudTest : IntegrationTestBase() { val result = client.call("missing", "123").create() assert(result.isFailure) result.onError { - assertThat((it as Error.NetworkError).serverErrorCode).isEqualTo(16) + assertTrue { it is Error.ThrowableError } } } diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ClientAndAuthTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ClientAndAuthTest.kt index c9c043a715..4bdf57eeac 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ClientAndAuthTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/ClientAndAuthTest.kt @@ -56,6 +56,7 @@ class ClientAndAuthTest : TestBase() { val builder = StreamVideoBuilder( context = context, apiKey = authData!!.apiKey, + token = authData!!.token, geo = GEO.GlobalEdgeNetwork, user = User( type = UserType.Anonymous, @@ -75,6 +76,7 @@ class ClientAndAuthTest : TestBase() { val client = StreamVideoBuilder( context = context, apiKey = authData!!.apiKey, + token = authData!!.token, geo = GEO.GlobalEdgeNetwork, user = User( id = "guest", @@ -133,7 +135,7 @@ class ClientAndAuthTest : TestBase() { token = authData!!.token, ).build() assertThat(client.state.connection.value).isEqualTo(ConnectionState.PreConnect) - val clientImpl = client as StreamVideoImpl + val clientImpl = client as StreamVideoClient val connectResultDeferred = clientImpl.connectAsync() @@ -184,7 +186,7 @@ class ClientAndAuthTest : TestBase() { geo = GEO.GlobalEdgeNetwork, user = testData.users["thierry"]!!, token = testData.expiredToken, - tokenProvider = { error -> + legacyTokenProvider = { error -> testData.tokens["thierry"]!! }, ).build() @@ -232,7 +234,7 @@ class ClientAndAuthTest : TestBase() { user = testData.users["thierry"]!!, token = authData!!.token, ).build() - val clientImpl = client as StreamVideoImpl + val clientImpl = client as StreamVideoClient client.subscribe { } val deferred = clientImpl.connectAsync() diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/EventTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SfuSocketStateEventTest.kt similarity index 99% rename from stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/EventTest.kt rename to stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SfuSocketStateEventTest.kt index 2b4b71c794..277fbca76f 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/EventTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SfuSocketStateEventTest.kt @@ -49,7 +49,7 @@ import stream.video.sfu.models.Participant import stream.video.sfu.models.TrackType @RunWith(RobolectricTestRunner::class) -class EventTest : IntegrationTestBase(connectCoordinatorWS = false) { +class SfuSocketStateEventTest : IntegrationTestBase(connectCoordinatorWS = false) { @Test fun `test start and stop recording`() = runTest { // start by sending the start recording event diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SocketTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SocketTest.kt deleted file mode 100644 index 1440dbec9b..0000000000 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/SocketTest.kt +++ /dev/null @@ -1,326 +0,0 @@ -/* - * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. - * - * Licensed under the Stream License; - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://github.com/GetStream/stream-video-android/blob/main/LICENSE - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.getstream.video.android.core - -import app.cash.turbine.testIn -import com.google.common.truth.Truth.assertThat -import io.getstream.video.android.core.base.SocketTestBase -import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.socket.CoordinatorSocket -import io.getstream.video.android.core.socket.PersistentSocket -import io.getstream.video.android.core.socket.SfuSocket -import io.getstream.video.android.core.socket.SocketState -import io.getstream.video.android.core.socket.SocketState.Connected -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.Ignore -import org.junit.Test -import org.junit.runner.RunWith -import org.openapitools.client.models.VideoEvent -import org.robolectric.RobolectricTestRunner - -/** - * Test coverage for the sockets - * - * @see PersistentSocket - * @see CoordinatorSocket - * @see SfuSocket - * - * The socket does a few things - * - health check/ping every 30 seconds - * - monitors network state - * - in case of temporary errors retry and for permanent ones fail - * - * The Sfu and coordinator have slightly different implements - * Auth receives different events - * - * @see JoinCallEvent (for the sfu) - * @see ConnectedEvent (for the coordinator) - * - * The sfu uses binary messages and the coordinator text - * Other than it's mostly the same logic - * - */ - -@RunWith(RobolectricTestRunner::class) -class CoordinatorSocketTest : SocketTestBase() { - - @Test - fun `coordinator - connect the socket`() = runTest { - val mockNetworkStateProvider = mockk() - every { mockNetworkStateProvider.isConnected() } returns true - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - authData?.token!!, - scope, - buildOkHttp(), - networkStateProvider, - ) - socket.connect() - - val connectionState = socket.connectionState.testIn(backgroundScope) - val connectionStateItem = connectionState.awaitItem() - - assertThat(connectionStateItem).isInstanceOf(Connected::class.java) - assertThat(socket.connectionId.value).isNotEmpty() - } - - @Test - @Ignore - fun `coordinator - an expired socket should be refreshed using the token provider`() = runTest { - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - testData.expiredToken, - scope, - buildOkHttp(), - networkStateProvider, - ) - // token refresh should be handled at the StreamVideoImpl level - try { - socket.connect() - } catch (e: Throwable) { - // ignore - } - } - - @Test - fun `coordinator - a permanent error shouldn't be retried`() = runTest { - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - "invalid token", - scope, - buildOkHttp(), - networkStateProvider, - ) - try { - socket.connect { it.cancel() } - } catch (e: Throwable) { - // ignore - } - - val connectionState = socket.connectionState.testIn(backgroundScope) - val connectionStateItem = connectionState.awaitItem() - - assertThat(socket.reconnectionAttempts).isEqualTo(0) - assertThat(connectionStateItem).isInstanceOf(SocketState.Connecting::class.java) - } - - @Test - @Ignore - fun `coordinator - a temporary error should be retried`() = runTest { - // mock the actual socket connection - val mockedNetworkStateProvider = mockk(relaxed = true) - every { mockedNetworkStateProvider.isConnected() } returns true - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - authData?.token!!, - scope, - buildOkHttp(), - mockedNetworkStateProvider, - ) - socket.mockSocket = mockedWebSocket - socket.reconnectTimeout = 0 - val job1 = scope.launch { socket.connect() } - val job2 = scope.launch { - // trigger an unknown host exception from onFailure - val error = java.net.UnknownHostException("internet is down") - socket.onFailure(mockedWebSocket, error, null) - } - // trigger the error - job2.join() - // complete the connection (which should fail) - delay(10) // timeout is 500ms by default - socket.disconnect(PersistentSocket.DisconnectReason.ByRequest) - - assertThat(socket.reconnectionAttempts).isEqualTo(1) - } - - @Test - fun `going offline should temporarily disconnect`() = runTest { - // mock the actual socket connection - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - testData.tokens["thierry"]!!, - scope, - buildOkHttp(), - networkStateProvider, - ) - socket.mockSocket = mockedWebSocket - socket.reconnectTimeout = 0 - val job = scope.launch { socket.connect() } - delay(100) - socket.onInternetDisconnected() - // assertThat(socket.connectionState.value).isInstanceOf(SocketState.NetworkDisconnected::class.java) - // go back online - val job2 = scope.launch { socket.onInternetConnected() } - delay(100) - job.cancel() - job2.cancel() - // TODO: this could be easier to test - // assertThat(socket.connectionState.value).isInstanceOf(SocketState.Connecting::class.java) - } - - @Test - fun `wrong formatted VideoEventType is ignored`() = runTest { - // mock the actual socket connection - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - authData?.token!!, - // make sure to use the TestScope because the exceptions will be swallowed by regular CoroutineScope - scope = this, - buildOkHttp(), - networkStateProvider, - ) - - socket.connect() - socket.connectContinuation.cancel() - - // create a VideoEvent type that resembles a real one, but doesn't contain the necessary fields - val testJson = "{\"type\":\"health.check\"}" - socket.onMessage(mockedWebSocket, testJson) - // no exception is thrown - } - - @Test - fun `wrong formatted error in VideoEventType is ignored`() = runTest { - // mock the actual socket connection - val socket = CoordinatorSocket( - coordinatorUrl, - testData.users["thierry"]!!, - authData?.token!!, - // make sure to use the TestScope because the exceptions will be swallowed by regular CoroutineScope - scope = this, - buildOkHttp(), - networkStateProvider, - ) - socket.connect() - - // create a socket message that doesn't even have the error message (so it's neither - // a valid VideoEventType nor it is a valid Error) - val testJson = "{\"someRandomField\":\"randomValue\"}" - socket.onMessage(mockedWebSocket, testJson) - // no exception is thrown - } -} - -@RunWith(RobolectricTestRunner::class) -class SfuSocketTest : SocketTestBase() { - fun collectEvents(socket: SfuSocket): Pair, List> { - val events = mutableListOf() - val errors = mutableListOf() - - runBlocking { - val job = launch { - socket.events.collect() { - events.add(it) - } - } - - val job2 = launch { - socket.errors.collect() { - errors.add(it) - } - } - - delay(1000) - socket.disconnect(PersistentSocket.DisconnectReason.ByRequest) - job.cancel() - job2.cancel() - } - - return Pair(events, errors) - } - - @Test - @Ignore - fun `sfu socket should connect and stay connected`() = runTest { - val sessionId = randomUUID().toString() - val updateSdp: () -> String = { - "hello" - } - val socket = SfuSocket( - sfuUrl, - sessionId, - sfuToken, - updateSdp, - scope, - buildOkHttp(), - networkStateProvider, - {}, - ) - socket.connect() - socket.sendHealthCheck() - - scope.launch { - socket.events.collect { - println("event: $it") - } - } - scope.launch { - socket.errors.collect { - println("errors: $it") - } - } - - while (true) { - Thread.sleep(5_000) - println("socket state: ${socket.connectionState.value}") - } - } - - @Test - @Ignore - fun `sfu - a permanent error shouldn't be retried a`() = runTest { - val sessionId = randomUUID().toString() - val updateSdp: () -> String = { - "hello" - } - val socket = SfuSocket( - sfuUrl, - sessionId, - "invalid", - updateSdp, - scope, - buildOkHttp(), - networkStateProvider, - {}, - ) - try { - socket.connect() - } catch (e: Throwable) { - // ignore - } - - val connectionState = socket.connectionState.testIn(backgroundScope) - val connectionStateItem = connectionState.awaitItem() - - assertThat(socket.reconnectionAttempts).isEqualTo(0) - assertThat( - connectionStateItem, - ).isInstanceOf(SocketState.DisconnectedPermanently::class.java) - } -} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt index 3707bbf9b2..4a52051030 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/IntegrationTestBase.kt @@ -23,7 +23,7 @@ import io.getstream.video.android.core.Call import io.getstream.video.android.core.GEO import io.getstream.video.android.core.StreamVideo import io.getstream.video.android.core.StreamVideoBuilder -import io.getstream.video.android.core.StreamVideoImpl +import io.getstream.video.android.core.StreamVideoClient import io.getstream.video.android.core.logging.HttpLoggingLevel import io.getstream.video.android.core.logging.LoggingLevel import io.mockk.MockKAnnotations @@ -57,6 +57,7 @@ import org.openapitools.client.models.VideoEvent import org.openapitools.client.models.VideoSettingsResponse import org.threeten.bp.Clock import org.threeten.bp.OffsetDateTime +import java.util.UUID import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -71,7 +72,7 @@ open class IntegrationTestBase(val connectCoordinatorWS: Boolean = true) : TestB lateinit var client: StreamVideo /** Implementation of the client for more access to interals */ - internal lateinit var clientImpl: StreamVideoImpl + internal lateinit var clientImpl: StreamVideoClient /** Tracks all events received by the client during a test */ lateinit var events: MutableList @@ -102,12 +103,12 @@ open class IntegrationTestBase(val connectCoordinatorWS: Boolean = true) : TestB if (IntegrationTestState.client == null) { client = builder.build() - clientImpl = client as StreamVideoImpl + clientImpl = client as StreamVideoClient + clientImpl.testSessionId = UUID.randomUUID().toString() // always mock the peer connection factory, it can't work in unit tests clientImpl.peerConnectionFactory = mockedPCFactory Call.testInstanceProvider.mediaManagerCreator = { mockk(relaxed = true) } Call.testInstanceProvider.rtcSessionCreator = { mockk(relaxed = true) } - // Connect to the WS if needed if (connectCoordinatorWS) { // wait for the connection/ avoids race conditions in tests @@ -121,7 +122,7 @@ open class IntegrationTestBase(val connectCoordinatorWS: Boolean = true) : TestB IntegrationTestState.client = client } else { client = IntegrationTestState.client!! - clientImpl = client as StreamVideoImpl + clientImpl = client as StreamVideoClient } // monitor for events diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/SocketTestBase.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/SocketTestBase.kt index 3adeb94827..ceac745acb 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/SocketTestBase.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/base/SocketTestBase.kt @@ -18,11 +18,13 @@ package io.getstream.video.android.core.base import android.content.Context import android.net.ConnectivityManager +import androidx.lifecycle.Lifecycle import io.getstream.video.android.core.dispatchers.DispatcherProvider import io.getstream.video.android.core.internal.network.NetworkStateProvider -import io.getstream.video.android.core.socket.CoordinatorSocket -import io.getstream.video.android.core.socket.PersistentSocket +import io.getstream.video.android.core.socket.common.StreamWebSocketEvent +import io.getstream.video.android.core.socket.coordinator.CoordinatorSocketConnection import io.mockk.impl.annotations.RelaxedMockK +import io.mockk.mockk import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -43,52 +45,53 @@ open class SocketTestBase : TestBase() { @RelaxedMockK lateinit var mockedWebSocket: WebSocket + val scope = CoroutineScope(DispatcherProvider.IO) + /** * Mocks * - network state, so we can fake going offline/online * - socket, so we can pretend the network is unavailable or we get an error */ - val networkStateProvider = NetworkStateProvider( - connectivityManager = context - .getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager, + scope = scope, + connectivityManager = context.getSystemService( + Context.CONNECTIVITY_SERVICE, + ) as ConnectivityManager, ) - val scope = CoroutineScope(DispatcherProvider.IO) + + val lifecycle = mockk(relaxed = true) fun buildOkHttp(): OkHttpClient { val connectionTimeoutInMs = 10000L - return OkHttpClient.Builder() - .addInterceptor( - HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.BASIC - }, - ) - .connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) + return OkHttpClient.Builder().addInterceptor( + HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BASIC + }, + ).connectTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) .writeTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) .readTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS) - .build() + .callTimeout(connectionTimeoutInMs, TimeUnit.MILLISECONDS).build() } - fun collectEvents(socket: CoordinatorSocket): Pair, List> { + fun collectEvents(socket: CoordinatorSocketConnection): Pair, List> { val events = mutableListOf() - val errors = mutableListOf() + val errors = mutableListOf() runBlocking { val job = launch { - socket.events.collect { + socket.events().collect { events.add(it) } } val job2 = launch { - socket.errors.collect() { - errors.add(it) + socket.errors().collect { err -> + errors.add(err) } } delay(1000) - socket.disconnect(PersistentSocket.DisconnectReason.ByRequest) + socket.disconnect() job.cancel() job2.cancel() } diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectAttemptsCountTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectAttemptsCountTest.kt new file mode 100644 index 0000000000..bbfb8bb923 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectAttemptsCountTest.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.reconnect + +import io.getstream.video.android.core.base.IntegrationTestBase +import io.getstream.video.android.core.call.RtcSession +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import kotlin.test.assertEquals + +@RunWith(RobolectricTestRunner::class) +class ReconnectAttemptsCountTest : IntegrationTestBase() { + + @Test + fun `Rejoin attempts are correctly updated`() = runTest { + // create the call + val sessionMock = mockk(relaxed = true) + val call = client.call("default", randomUUID()) + call.session = sessionMock + + // Rejoin + call.rejoin() + assertEquals(1, call.reconnectAttepmts) + } + + @Test + fun `Fast reconnect does not update the reconnect attempts`() = runTest { + // create the call + val sessionMock = mockk(relaxed = true) + val call = client.call("default", randomUUID()) + call.session = sessionMock + + // Rejoin + call.fastReconnect() + assertEquals(0, call.reconnectAttepmts) + } + + @Test + fun `Multiple rejoin calls will increase the reconnect attempts correctly`() = runTest { + // create the call + val sessionMock = mockk(relaxed = true) + val call = client.call("default", randomUUID()) + call.session = sessionMock + + // Rejoin + call.rejoin() + call.rejoin() + assertEquals(2, call.reconnectAttepmts) + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectSessionIdTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectSessionIdTest.kt new file mode 100644 index 0000000000..e54140c76c --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/reconnect/ReconnectSessionIdTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.reconnect + +import io.getstream.video.android.core.base.IntegrationTestBase +import io.getstream.video.android.core.call.RtcSession +import io.mockk.mockk +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertNotEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import kotlin.test.assertEquals + +@RunWith(RobolectricTestRunner::class) +class ReconnectSessionIdTest : IntegrationTestBase() { + + @Test + fun `Rejoin creates a new session`() = runTest(UnconfinedTestDispatcher()) { + // create the call + val sessionMock = mockk(relaxed = true) + val call = client.call("default", randomUUID()) + call.join().getOrNull() + + // Rejoin + call.rejoin() + assertNotEquals(sessionMock, call.session) + } + + @Test + fun `Fast reconnect does not recreate session`() = runTest { + // create the call + val sessionMock = mockk(relaxed = true) + val call = client.call("default", randomUUID()) + call.session = sessionMock + + // Rejoin + call.fastReconnect() + assertEquals(sessionMock, call.session) + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/CacheableTokenProviderTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/CacheableTokenProviderTest.kt new file mode 100644 index 0000000000..714bdf98e3 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/CacheableTokenProviderTest.kt @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.token + +import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenProvider +import io.getstream.video.android.core.utils.positiveRandomInt +import io.getstream.video.android.core.utils.randomString +import io.mockk.coEvery +import io.mockk.coVerify +import io.mockk.mockk +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class CacheableTokenProviderTest { + + private val delegatedTokenProvider: TokenProvider = mockk(relaxed = true) + private val cacheableTokenProvider = CacheableTokenProvider(delegatedTokenProvider) + + @Test + fun `Initial cached token should be empty`() { + assertEquals("", cacheableTokenProvider.getCachedToken()) + } + + @Test + fun `CacheableTokenProvider should store last value`() = runTest { + val tokens = List(positiveRandomInt(20)) { randomString() } + coEvery { delegatedTokenProvider.loadToken() } returns tokens.random() + + val result = mutableListOf() + repeat(3) { + val newToken = cacheableTokenProvider.loadToken() + result.add(newToken) + } + + assertEquals(result.size, 3) + coVerify(exactly = 3) { delegatedTokenProvider.loadToken() } + assertEquals(result.last(), cacheableTokenProvider.getCachedToken()) + } + + @Test + fun `CacheableTokenProvider should delegate the process to obtain a token to his delegated token provider`() = runTest { + val tokens = List(positiveRandomInt(20)) { randomString() } + coEvery { delegatedTokenProvider.loadToken() } returns tokens.random() + + val result = mutableListOf() + repeat(3) { + val newToken = cacheableTokenProvider.loadToken() + result.add(newToken) + } + + assertEquals(result.size, 3) + coVerify(exactly = 3) { delegatedTokenProvider.loadToken() } + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt new file mode 100644 index 0000000000..3b8b62cdf0 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenManager.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.token + +import io.getstream.video.android.core.socket.common.token.CacheableTokenProvider +import io.getstream.video.android.core.socket.common.token.TokenManager + +internal class FakeTokenManager( + private val token: String, + private val loadSyncToken: String = token, +) : TokenManager { + override suspend fun loadSync(): String = loadSyncToken + override fun getToken(): String = token + + override suspend fun ensureTokenLoaded() { + // empty + } + + override fun setTokenProvider(provider: CacheableTokenProvider) { + // empty + } + + override fun hasTokenProvider(): Boolean { + return true + } + + override fun hasToken(): Boolean { + return true + } + + override fun updateToken(token: String) { + TODO("Not yet implemented") + } + + override fun expireToken() { + // empty + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenProvider.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenProvider.kt new file mode 100644 index 0000000000..984878c634 --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/token/FakeTokenProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.token + +import io.getstream.video.android.core.socket.common.token.TokenProvider + +internal class FakeTokenProvider(vararg val tokens: String) : TokenProvider { + + var tokenId = 0 + + override suspend fun loadToken(): String { + return tokens[tokenId++] + } +} diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TestUtils.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TestUtils.kt new file mode 100644 index 0000000000..36a4ce854f --- /dev/null +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TestUtils.kt @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014-2024 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-video-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.video.android.core.utils + +import io.getstream.video.android.model.User +import org.threeten.bp.Instant +import org.threeten.bp.OffsetDateTime +import org.threeten.bp.ZoneId +import java.io.File +import java.util.Date +import kotlin.random.Random + +private val charPool: CharArray = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toCharArray() + +public fun positiveRandomInt(maxInt: Int = Int.MAX_VALUE - 1): Int = + Random.nextInt(1, maxInt + 1) + +public fun positiveRandomLong(maxLong: Long = Long.MAX_VALUE - 1): Long = + Random.nextLong(1, maxLong + 1) + +public fun randomInt(): Int = Random.nextInt() +public fun randomIntBetween(min: Int, max: Int): Int = Random.nextInt(min, max + 1) +public fun randomLong(): Long = Random.nextLong() +public fun randomLongBetween( + min: Long, + max: Long = Long.MAX_VALUE - 1, +): Long = Random.nextLong(min, max + 1) +public fun randomBoolean(): Boolean = Random.nextBoolean() +public fun randomString(size: Int = 20): String = buildString(capacity = size) { + repeat(size) { + append(charPool.random()) + } +} + +public fun randomDateOrNull(): OffsetDateTime? = randomDate().takeIf { randomBoolean() } +public fun randomDate(): OffsetDateTime = OffsetDateTime.ofInstant( + Instant.ofEpochMilli(positiveRandomLong()), + ZoneId.systemDefault(), +) +public fun randomDateBefore(date: Long): Date = Date(date - positiveRandomInt()) +public fun randomDateAfter(date: Long): Date = Date(randomLongBetween(date)) +public fun randomCID(): String = "${randomString()}:${randomString()}" +public fun randomFile(extension: String = randomString(3)): File { + return File("${randomString()}.$extension") +} + +public fun randomUser( + id: String = randomString(), + name: String = randomString(), + image: String = randomString(), + role: String = randomString(), + createdAt: OffsetDateTime? = randomDateOrNull(), + updatedAt: OffsetDateTime? = randomDateOrNull(), + teams: List = listOf(), +): User = User( + id = id, + role = role, + name = name, + image = image, + createdAt = createdAt, + updatedAt = updatedAt, + teams = teams, +) diff --git a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TokenUtilsTest.kt b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TokenUtilsTest.kt index f0fc1c039f..771a10d9f0 100644 --- a/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TokenUtilsTest.kt +++ b/stream-video-android-core/src/test/kotlin/io/getstream/video/android/core/utils/TokenUtilsTest.kt @@ -145,9 +145,3 @@ internal class TokenUtilsTest( } private val charPool: CharArray = (('a'..'z') + ('A'..'Z') + ('0'..'9')).toCharArray() - -private fun randomString(size: Int = 20): String = buildString(capacity = size) { - repeat(size) { - append(charPool.random()) - } -} diff --git a/stream-video-android-previewdata/src/main/kotlin/io/getstream/video/android/mock/StreamPreviewDataUtils.kt b/stream-video-android-previewdata/src/main/kotlin/io/getstream/video/android/mock/StreamPreviewDataUtils.kt index 2e0d44471c..1d816757dd 100644 --- a/stream-video-android-previewdata/src/main/kotlin/io/getstream/video/android/mock/StreamPreviewDataUtils.kt +++ b/stream-video-android-previewdata/src/main/kotlin/io/getstream/video/android/mock/StreamPreviewDataUtils.kt @@ -55,11 +55,7 @@ public val previewCall: Call = Call( user = previewUsers[0], ).apply { val participants = previewUsers.take(2).map { user -> - val sessionId = if (user == previewUsers.first()) { - sessionId - } else { - UUID.randomUUID().toString() - } + val sessionId = UUID.randomUUID().toString() ParticipantState( initialUserId = user.id, sessionId = sessionId, diff --git a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt index 409fd11684..59f3274605 100644 --- a/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt +++ b/stream-video-android-ui-core/src/main/kotlin/io/getstream/video/android/ui/common/StreamCallActivity.kt @@ -91,7 +91,7 @@ public abstract class StreamCallActivity : ComponentActivity() { context: Context, cid: StreamCallId, members: List = defaultExtraMembers, - leaveWhenLastInCall: Boolean = true, + leaveWhenLastInCall: Boolean = DEFAULT_LEAVE_WHEN_LAST, action: String? = null, clazz: Class, configuration: StreamCallActivityConfiguration = StreamCallActivityConfiguration(), @@ -179,6 +179,7 @@ public abstract class StreamCallActivity : ComponentActivity() { // Platform restriction public final override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + onPreCreate(savedInstanceState, null) logger.d { "Entered [onCreate(Bundle?)" } initializeCallOrFail( savedInstanceState, @@ -698,10 +699,13 @@ public abstract class StreamCallActivity : ComponentActivity() { } is ParticipantLeftEvent, is CallSessionParticipantLeftEvent -> { - val total = call.state.participantCounts.value?.total - logger.d { "Participant left, remaining: $total" } - if (total != null && total <= 2) { - onLastParticipant(call) + val connectionState = call.state.connection.value + if (connectionState == RealtimeConnection.Disconnected) { + val total = call.state.participantCounts.value?.total + logger.d { "Participant left, remaining: $total" } + if (total != null && total <= 2) { + onLastParticipant(call) + } } } } diff --git a/tutorials/tutorial-livestream/build.gradle.kts b/tutorials/tutorial-livestream/build.gradle.kts index 631a73afc7..e87d7d4d4e 100644 --- a/tutorials/tutorial-livestream/build.gradle.kts +++ b/tutorials/tutorial-livestream/build.gradle.kts @@ -48,5 +48,6 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.compose.navigation) } \ No newline at end of file diff --git a/tutorials/tutorial-ringing/build.gradle.kts b/tutorials/tutorial-ringing/build.gradle.kts index 498ccf095f..37dac8adc1 100644 --- a/tutorials/tutorial-ringing/build.gradle.kts +++ b/tutorials/tutorial-ringing/build.gradle.kts @@ -58,5 +58,6 @@ dependencies { implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.foundation) implementation(libs.androidx.compose.material) + implementation(libs.androidx.compose.material.iconsExtended) implementation(libs.androidx.lifecycle.runtime.compose) } \ No newline at end of file