fix(mobile): translate microphone errors and pre-request permission
实时陪练失败时,getUserMedia 抛出的 Chromium 英文原文会直接透给用户(真机实测见过 "Could not start audio source"),既不可读也不含任何可执行指引。新增 utils/microphone.ts 把权限、设备占用、无设备、非安全上下文四类底层错误翻译成中文 引导;业务层自己抛的中文文案原样透出,未识别的错误回落到原有通用提示。 同时在建会话之前主动申请 RECORD_AUDIO,并在 manifest 显式声明该权限。 需要说明:此权限预申请是防御性的。真机核查 RECORD_AUDIO 已授予、APK 本就声明该权限、 实时链路可正常跑通,先前那次 "Could not start audio source" 是麦克风被其他应用占用 的瞬时故障,不是权限缺陷。未在"权限未授予"的设备上实测过 WebView 是否会自行弹出 Android 运行时授权框,故此调用在已授权设备上为无害空转;若确实不弹,则它是唯一授权入口。 manifest 声明同样只为防未来模块调整导致权限丢失。 新增 6 项单测覆盖错误映射与授权分支(共 168 项通过)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -531,6 +531,7 @@ import type { SpeechCapture, SpeechCaptureSession } from '@/services/speech-capt
|
||||
import { createPageRequestScope, currentAccountKey, type RequestScopeTicket } from '@/services/request-scope';
|
||||
import { createRealtimePracticeController, createRealtimeSession, fetchRealtimePersonas, invokeRealtimeTool, type RealtimePersonaOption } from '@/services/realtime-practice';
|
||||
import { stripInternalCodes } from '@/services/text';
|
||||
import { describeMicrophoneError, microphoneDeniedMessage, requestMicrophonePermission } from '@/utils/microphone';
|
||||
import { safeTopPadding } from '@/utils/safe-area';
|
||||
|
||||
type PracticeStatus = 'idle' | 'starting' | 'active' | 'submitting' | 'finished';
|
||||
@@ -1052,7 +1053,8 @@ const handleRealtimeTerminated = () => {
|
||||
status.value = 'idle';
|
||||
};
|
||||
const handleRealtimeError = (text: string) => {
|
||||
if (realtimeActive.value) message.value = text;
|
||||
// 会话过程中的底层错误同样可能是 Chromium 英文原文,统一翻译后再展示。
|
||||
if (realtimeActive.value) message.value = describeMicrophoneError(text, '实时语音出现异常,请结束后重试');
|
||||
};
|
||||
const realtimePractice = createRealtimePracticeController({
|
||||
onStatus: handleRealtimeStatus,
|
||||
@@ -1115,6 +1117,14 @@ const startRealtime = async () => {
|
||||
status.value = 'starting';
|
||||
realtimeActive.value = true;
|
||||
try {
|
||||
// 先申请麦克风权限:App WebView 的 getUserMedia 不会自行弹出系统授权框,
|
||||
// 若跳过这一步,权限缺失时用户只会看到 Chromium 英文报错且没有授权入口。
|
||||
// 放在建会话之前,避免留下一个无法使用的孤儿会话。
|
||||
const permission = await requestMicrophonePermission();
|
||||
if (permission === 'denied' || permission === 'denied-always') {
|
||||
throw new Error(microphoneDeniedMessage(permission));
|
||||
}
|
||||
if (!realtimeActive.value) return;
|
||||
const session = await createRealtimeSession(selectedRealtimePersonaId.value || undefined, selectedScenarioId.value || undefined);
|
||||
if (!realtimeActive.value) return;
|
||||
realtimeSessionId.value = session.sessionId;
|
||||
@@ -1130,7 +1140,11 @@ const startRealtime = async () => {
|
||||
realtimeStatus.value = 'idle';
|
||||
realtimeSessionId.value = '';
|
||||
status.value = 'idle';
|
||||
handleError(error, '实时陪练连接失败,请使用录音对练');
|
||||
// 底层音频/WebRTC 错误为 Chromium 英文原文,必须翻译后再展示。
|
||||
handleError(
|
||||
new Error(describeMicrophoneError(error, '实时陪练连接失败,请使用录音对练')),
|
||||
'实时陪练连接失败,请使用录音对练'
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user