Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,11 @@ 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.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import org.openapitools.client.models.OwnCapability
import org.openapitools.client.models.TranscriptionSettingsResponse

@OptIn(ExperimentalMaterialApi::class)
@Composable
Expand Down Expand Up @@ -218,6 +221,34 @@ fun CallScreen(
}
}

/**
* AUTO START/STOP TRANSCRIPTION LOGIC
*
* This code handles the automatic transcription logic, ensuring transcription starts or stops
* based on the current settings and state. While it usually behaves as expected, consider the following scenario:
*
* - Transcription is set to "Auto-On" in the settings.
* - The current transcription state (`isCurrentlyTranscribing`) is `false` because it was toggled by a participant.
* - A new participant joins the call.
*
* In this scenario, the transcription will automatically start, overriding the previous `false` state.
* This behavior is intentional for this demo-app ONLY and designed to prioritize the "Auto-On" setting over the current state.
*
* Please keep this behavior in mind, as it might appear unexpected at first glance.
*
* Note: Occasionally, when `call.startTranscription()` might throw a 400 error in the demo app when Transcription is set to "Auto-On", indicating that
* transcription is already in progress. This is expected and can safely be ignored as it does not impact
* the ongoing transcription functionality.
*/
val isCurrentlyTranscribing by call.state.transcribing.collectAsStateWithLifecycle()

LaunchedEffect(Unit) {
call.state.settings.map { it?.transcription }
.collectLatest { transcription ->
executeTranscriptionApis(call, isCurrentlyTranscribing, transcription)
}
}

VideoTheme {
ChatDialog(
state = chatState,
Expand Down Expand Up @@ -682,6 +713,27 @@ fun CallScreen(
}
}

/**
* Executes the transcription APIs based on the current transcription state and settings.
*
* - Stops transcription if the mode is "Disabled" and transcription is currently active.
* - Starts transcription if the mode is "Auto-On" and transcription is not currently active.
* - Takes no action for other scenarios.
*/
private suspend fun executeTranscriptionApis(
call: Call,
transcribing: Boolean,
transcriptionSettingsResponse:
TranscriptionSettingsResponse?,
) {
val mode = transcriptionSettingsResponse?.mode
if (mode == TranscriptionSettingsResponse.Mode.Disabled && transcribing) {
call.stopTranscription()
} else if (mode == TranscriptionSettingsResponse.Mode.AutoOn && !transcribing) {
call.startTranscription()
} else { }
}

@Composable
private fun SpeakingWhileMuted() {
Snackbar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import android.os.Build
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.MobileScreenShare
import androidx.compose.material.icons.automirrored.filled.ReadMore
import androidx.compose.material.icons.automirrored.filled.ReceiptLong
import androidx.compose.material.icons.filled.AspectRatio
import androidx.compose.material.icons.filled.Audiotrack
import androidx.compose.material.icons.filled.AutoGraph
Expand Down Expand Up @@ -54,6 +55,8 @@ import io.getstream.video.android.ui.menu.base.ActionMenuItem
import io.getstream.video.android.ui.menu.base.DynamicSubMenuItem
import io.getstream.video.android.ui.menu.base.MenuItem
import io.getstream.video.android.ui.menu.base.SubMenuItem
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch

/**
* Defines the default Stream menu for the demo app.
Expand Down Expand Up @@ -83,6 +86,9 @@ fun defaultStreamMenu(
onSelectScaleType: (VideoScalingType) -> Unit,
availableDevices: List<StreamAudioDevice>,
loadRecordings: suspend () -> List<MenuItem>,
transcriptionUiState: TranscriptionUiState,
onToggleTranscription: suspend () -> Unit,
loadTranscriptions: suspend () -> List<MenuItem>,
onToggleClosedCaptions: () -> Unit = {},
closedCaptionUiState: ClosedCaptionUiState,
) = buildList<MenuItem> {
Expand Down Expand Up @@ -202,6 +208,34 @@ fun defaultStreamMenu(
),
),
)

when (transcriptionUiState) {
is TranscriptionAvailableUiState, TranscriptionStoppedUiState -> {
add(
ActionMenuItem(
title = transcriptionUiState.text,
icon = transcriptionUiState.icon,
highlight = transcriptionUiState.highlight,
action = {
GlobalScope.launch {
onToggleTranscription.invoke()
}
},
),
)

add(
DynamicSubMenuItem(
title = "List Transcriptions",
icon = Icons.AutoMirrored.Filled.ReceiptLong,
itemsLoader = loadTranscriptions,
),
)
}

else -> {}
}

add(getCCActionMenu(closedCaptionUiState, onToggleClosedCaptions))
if (showDebugOptions) {
add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import io.getstream.video.android.ui.closedcaptions.ClosedCaptionUiState
import io.getstream.video.android.ui.menu.base.ActionMenuItem
import io.getstream.video.android.ui.menu.base.DynamicMenu
import io.getstream.video.android.ui.menu.base.MenuItem
import io.getstream.video.android.ui.menu.transcriptions.TranscriptionUiStateManager
import io.getstream.video.android.util.filters.SampleAudioFilter
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
Expand Down Expand Up @@ -194,6 +195,48 @@ internal fun SettingsMenu(
}
}

val isCurrentlyTranscribing by call.state.transcribing.collectAsStateWithLifecycle()
val settings by call.state.settings.collectAsStateWithLifecycle()

// Use the manager to determine the UI state
val transcriptionUiStateManager =
TranscriptionUiStateManager(isCurrentlyTranscribing, settings)
val transcriptionUiState = transcriptionUiStateManager.getUiState()

val onToggleTranscription: suspend () -> Unit = {
when (transcriptionUiState) {
TranscriptionAvailableUiState -> call.startTranscription()
TranscriptionStoppedUiState -> call.stopTranscription()
else -> {
throw IllegalStateException(
"Toggling of transcription should not work in state: $transcriptionUiState",
)
}
}
}

val onLoadTranscriptions: suspend () -> List<MenuItem> = storagePermissionAndroidBellow10 {
when (it) {
is PermissionStatus.Granted -> {
{
call.listTranscription().getOrNull()?.transcriptions?.map {
ActionMenuItem(
title = it.filename,
icon = Icons.Default.VideoFile, // TODO Rahul check this later
action = {
context.downloadFile(it.url, it.filename)
onDismissed()
},
)
} ?: emptyList()
}
}
is PermissionStatus.Denied -> {
{ emptyList() }
}
}
}

Popup(
offset = IntOffset(
0,
Expand Down Expand Up @@ -255,6 +298,9 @@ internal fun SettingsMenu(
loadRecordings = onLoadRecordings,
onToggleClosedCaptions = onClosedCaptionsToggle,
closedCaptionUiState = closedCaptionUiState,
transcriptionUiState = transcriptionUiState,
onToggleTranscription = onToggleTranscription,
loadTranscriptions = onLoadTranscriptions,
),
)
}
Expand Down Expand Up @@ -324,6 +370,9 @@ private fun SettingsMenuPreview() {
loadRecordings = { emptyList() },
onToggleClosedCaptions = { },
closedCaptionUiState = ClosedCaptionUiState.Available,
transcriptionUiState = TranscriptionAvailableUiState,
onToggleTranscription = {},
loadTranscriptions = { emptyList() },
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.ui.menu

import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Description
import androidx.compose.ui.graphics.vector.ImageVector

sealed class TranscriptionUiState(
val text: String,
val icon: ImageVector, // Assuming it's a drawable resource ID
val highlight: Boolean,
)

/**
* Stop Transcription
* Start Transcription
* Transcription is disabled
* Transcription failed
*/

data object TranscriptionAvailableUiState : TranscriptionUiState(
text = "Transcribe the call",
icon = Icons.Default.Description,
highlight = false,
)

data object TranscriptionStoppedUiState : TranscriptionUiState(
text = "Stop Transcription",
icon = Icons.Default.Description,
highlight = true,
)

data object TranscriptionDisabledUiState : TranscriptionUiState(
text = "Transcription not available",
icon = Icons.Default.Description,
highlight = false,
)
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import io.getstream.video.android.compose.theme.VideoTheme
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.closedcaptions.ClosedCaptionUiState
import io.getstream.video.android.ui.menu.TranscriptionAvailableUiState
import io.getstream.video.android.ui.menu.debugSubmenu
import io.getstream.video.android.ui.menu.defaultStreamMenu
import io.getstream.video.android.ui.menu.reconnectMenu
Expand Down Expand Up @@ -232,6 +233,9 @@ private fun DynamicMenuPreview() {
onToggleIncomingVideoEnabled = {},
onSelectScaleType = {},
loadRecordings = { emptyList() },
transcriptionUiState = TranscriptionAvailableUiState,
onToggleTranscription = {},
loadTranscriptions = { emptyList() },
onToggleClosedCaptions = {},
closedCaptionUiState = ClosedCaptionUiState.Available,
),
Expand Down Expand Up @@ -269,6 +273,9 @@ private fun DynamicMenuDebugOptionPreview() {
loadRecordings = { emptyList() },
onToggleClosedCaptions = {},
closedCaptionUiState = ClosedCaptionUiState.Available,
transcriptionUiState = TranscriptionAvailableUiState,
onToggleTranscription = {},
loadTranscriptions = { emptyList() },
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.ui.menu.transcriptions

import io.getstream.video.android.ui.menu.TranscriptionAvailableUiState
import io.getstream.video.android.ui.menu.TranscriptionDisabledUiState
import io.getstream.video.android.ui.menu.TranscriptionStoppedUiState
import io.getstream.video.android.ui.menu.TranscriptionUiState
import org.openapitools.client.models.CallSettingsResponse
import org.openapitools.client.models.TranscriptionSettingsResponse

class TranscriptionUiStateManager(
private val isTranscribing: Boolean,
private val settings: CallSettingsResponse?,
) {

fun getUiState(): TranscriptionUiState {
return if (settings != null) {
val mode = settings.transcription.mode
when (mode) {
TranscriptionSettingsResponse.Mode.Available, TranscriptionSettingsResponse.Mode.AutoOn -> {
if (isTranscribing) {
TranscriptionStoppedUiState
} else {
TranscriptionAvailableUiState
}
}
else -> {
TranscriptionDisabledUiState
}
}
} else {
TranscriptionDisabledUiState
}
}
}
Loading