喷气背包Compose - 语音识别

4

你知道如何在Jetpack Compose中应用语音识别 (SpeechRecognizer)吗?

类似这样的东西,但是在Compose中。

我按照这个视频中的步骤进行了操作:

  • 在清单文件中添加了以下权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
  • 我在MainActivity中编写了以下代码:
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            PageUi()
        }
    }
}

@Composable
fun PageUi() {
    val context = LocalContext.current
    val talk by remember { mutableStateOf("Speech text should come here") }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = talk,
            style = MaterialTheme.typography.h4,
            modifier = Modifier
                .fillMaxSize(0.85f)
                .padding(16.dp)
                .background(Color.LightGray)
        )
        Button(onClick = { askSpeechInput(context) }) {
            Text(
                text = "Talk", style = MaterialTheme.typography.h3
            )
        }
    }
}

fun askSpeechInput(context: Context) {
    if (!SpeechRecognizer.isRecognitionAvailable(context)) {
        Toast.makeText(context, "Speech not available", Toast.LENGTH_SHORT).show()
    } else {
        val i = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
        i.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM)
        i.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault())
        i.putExtra(RecognizerIntent.EXTRA_PROMPT, "Talk")

        //startActivityForResult(MainActivity(),i,102)
    }
}

@Preview(showBackground = true)
@Composable
fun PageShow() {
    PageUi()
}

但是我不知道在Compose中如何使用startActivityForResult并完成其余部分? 而且到目前为止,当我在手机(或模拟器)上测试时,它总是以toast消息结束!

2个回答

4
我将解释我的实现方式。首先让我给你一个概括性的想法,然后我会逐步解释每个步骤。所以首先你需要每次请求权限,如果权限被授予,那么你应该启动一个意图来听用户说什么。用户说的话保存在一个变量中并存储到一个视图模型中。视图模型上的变量正在被可组合项观察,因此你可以获取数据。 1)将以下内容添加到你的清单文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="your.package">

    // Add uses-permission
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

   [...]
   [...]
   [...]

    // Add above the last line  </manifest> like so:
    <queries>
        <intent>
            <action android:name="android.speech.RecognitionService" />
        </intent>
    </queries>

</manifest>

2) 创建一个ViewModel

class ScreenViewModel : ViewModel() {

    var textFromSpeech: String? by mutableStateOf(null)

}

为了从可组合中观察变量并实现干净架构的代码逻辑,您需要ViewModel。

3) 实现请求权限

build.gradle中添加以下内容:

implementation "com.google.accompanist:accompanist-permissions:$accompanist_version"

然后像这样创建一个可组合的组件:
@ExperimentalPermissionsApi
@Composable
fun  OpenVoiceWithPermission(
    onDismiss: () -> Unit,
    vm: ScreenViewModel,
    ctxFromScreen: Context,
    finished: () -> Unit
) {

    val voicePermissionState = rememberPermissionState(android.Manifest.permission.RECORD_AUDIO)
    val ctx = LocalContext.current

fun newIntent(ctx: Context) {
    val intent = Intent()
    intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
    val uri = Uri.fromParts(
        "package",
        BuildConfig.APPLICATION_ID, null
    )
    intent.data = uri
    intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
    ctx.startActivity(intent)
}

    PermissionRequired(
        permissionState = voicePermissionState,
        permissionNotGrantedContent = {
            DialogCustomBox(
                onDismiss = onDismiss,
                dialogBoxState = DialogLogInState.REQUEST_VOICE,
                onRequestPermission = { voicePermissionState.launchPermissionRequest() }
            )
        },
        permissionNotAvailableContent = {
            DialogCustomBox(
                onDismiss = onDismiss,
                dialogBoxState = DialogLogInState.VOICE_OPEN_SYSTEM_SETTINGS,
                onOpenSystemSettings = { newIntent(ctx) }
            )
        }
    ) {
        startSpeechToText(vm, ctxFromScreen, finished = finished)
    }
}

DialogBox中,你可以创建自己的定制版本,如我所做的,或使用标准版本,这取决于你,超出了这个答案的范围。

在上面的代码中,如果授权,则会自动转到此代码片段:startSpeechToText(vm,ctxFromScreen,finished = finished),接下来必须实现它。

4) 实施语音识别器

fun startSpeechToText(vm: ScreenViewModel, ctx: Context, finished: ()-> Unit) {
    val speechRecognizer = SpeechRecognizer.createSpeechRecognizer(ctx)
    val speechRecognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)
    speechRecognizerIntent.putExtra(
        RecognizerIntent.EXTRA_LANGUAGE_MODEL,
        RecognizerIntent.LANGUAGE_MODEL_FREE_FORM,
    )

    // Optionally I have added my mother language
    speechRecognizerIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "el_GR")

    speechRecognizer.setRecognitionListener(object : RecognitionListener {
        override fun onReadyForSpeech(bundle: Bundle?) {}
        override fun onBeginningOfSpeech() {}
        override fun onRmsChanged(v: Float) {}
        override fun onBufferReceived(bytes: ByteArray?) {}
        override fun onEndOfSpeech() {
            finished()
            // changing the color of your mic icon to
            // gray to indicate it is not listening or do something you want
        }

        override fun onError(i: Int) {}

        override fun onResults(bundle: Bundle) {
            val result = bundle.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
            if (result != null) {
                // attaching the output
                // to our viewmodel
                vm.textFromSpeech = result[0]
            }
        }

        override fun onPartialResults(bundle: Bundle) {}
        override fun onEvent(i: Int, bundle: Bundle?) {}

    })
    speechRecognizer.startListening(speechRecognizerIntent)
}

使用此实现方式,它非常可定制,并且您不会收到来自Google的弹出窗口。因此,您可以使用自己独特的方式通知用户他的设备正在侦听!
5) 从您的组合中调用函数以开始侦听:
@ExperimentalPermissionsApi
@Composable
fun YourScreen() {

    val ctx = LocalContext.current
    val vm: ScreenViewModel = viewModel()
    var clickToShowPermission by rememberSaveable { mutableStateOf(false) }

    if (clickToShowPermission) {
        OpenVoiceWithPermission(
            onDismiss = { clickToShowPermission = false },
            vm = vm,
            ctxFromScreen = ctx
        ) {
            // Do anything you want when the voice has finished and do
            // not forget to return clickToShowPermission to false!!
            clickToShowPermission = false
        }
    }
}

因此,每次调用clickToShowPermission = true时,您都可以开始听取用户的讲话......


1
哇!!!我真的不知道这个问题的答案如此之长,而且有不同的阶段!非常感谢您的时间、完整的描述和逐节代码。我会尝试实现它,并让您知道是否成功,但我敢打赌一定会成功。再次感谢。 - Ali
是的,请告诉我它是否有效,因为我可能漏掉了什么......我相信这应该是实现它的正确方式。 - F.Mysir

2

使用registerForActivityResult(ActivityResultContract, ActivityResultCallback)方法,传入一个androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult对象作为ActivityResultContract参数。

通过声明StartActivityForResult回调函数

  val startLauncher = rememberLauncherForActivityResult(
        ActivityResultContracts.StartActivityForResult()
    ) {it ->
        //you implement
    } 

启动 Intent

startLauncher.launch(intent)

测试简单示例

@Composable
fun TestStartForResult() {
    val content = LocalContext.current
    val startLauncher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult())
    {
        Toast.makeText(content, "Result", Toast.LENGTH_SHORT).show()
    }
    Button(onClick = {
        startLauncher.launch(Intent(content,TestActivity::class.java))
    }) {
        Text("start")
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接