feat(mobile-app): prepare native app packaging
This commit is contained in:
@@ -306,7 +306,7 @@
|
||||
:maxlength="500"
|
||||
:panel-items="practicePanelItems"
|
||||
@send="handleComposerSend"
|
||||
@voice-blob="handleSessionVoiceBlob"
|
||||
@voice-capture="handleSessionVoiceCapture"
|
||||
@pick-audio="composerPickAudio"
|
||||
@panel="handlePanelAction"
|
||||
/>
|
||||
@@ -397,7 +397,9 @@ import { getSelectedPosition, isLoggedIn, rememberLoginRedirect } from '@/servic
|
||||
import { searchKnowledge } from '@/services/knowledge';
|
||||
import { resetPageScroll } from '@/services/navigation';
|
||||
import { ensureEmployeePosition } from '@/services/position';
|
||||
import { chooseSpeechAudio, createSpeechPlaybackController, customerVoiceProfile, measureAudioDuration, MENTOR_VOICE_PROFILE, normalizeOssId, transcribeSpeechBlob, transcribeSpeechFile } from '@/services/speech';
|
||||
import { chooseSpeechAudio, createSpeechPlaybackController, customerVoiceProfile, measureAudioDuration, MENTOR_VOICE_PROFILE, normalizeOssId, transcribeSpeechCapture, transcribeSpeechFile } from '@/services/speech';
|
||||
import { startSpeechCapture } from '@/services/speech-capture';
|
||||
import type { SpeechCapture, SpeechCaptureSession } from '@/services/speech-capture';
|
||||
import { createPageRequestScope, currentAccountKey, type RequestScopeTicket } from '@/services/request-scope';
|
||||
import { createRealtimePracticeController, DEFAULT_REALTIME_PRACTICE_VOICE, isRealtimePracticeVoice, REALTIME_PRACTICE_VOICE_OPTIONS, type RealtimePracticeVoice } from '@/services/realtime-practice';
|
||||
import { stripInternalCodes } from '@/services/text';
|
||||
@@ -473,8 +475,7 @@ const transcribing = ref(false);
|
||||
const draftAudioUrl = ref('');
|
||||
const draftAudioOssId = ref<string | undefined>();
|
||||
const speechState = ref<{ key: string; status: SpeechPlaybackStatus }>({ key: '', status: 'idle' });
|
||||
const mediaRecorder = ref<MediaRecorder | null>(null);
|
||||
const mediaStream = ref<MediaStream | null>(null);
|
||||
let recordingCapture: SpeechCaptureSession | null = null;
|
||||
let recordingCaptureGeneration = 0;
|
||||
let transcriptionGeneration = 0;
|
||||
let abortTranscription: (() => void) | null = null;
|
||||
@@ -665,12 +666,12 @@ const submitTranscribed = async (text: string, audioUrl: string, ossId?: string)
|
||||
await submit();
|
||||
};
|
||||
|
||||
const handleSessionVoiceBlob = async (blob: Blob) => {
|
||||
const handleSessionVoiceCapture = async (capture: SpeechCapture) => {
|
||||
if (transcribing.value || busy.value) return;
|
||||
const generation = ++transcriptionGeneration;
|
||||
transcribing.value = true;
|
||||
try {
|
||||
const asr = await transcribeSpeechBlob(blob, 'practice-turn.webm', (abort) => {
|
||||
const asr = await transcribeSpeechCapture(capture, 'practice-turn.webm', (abort) => {
|
||||
if (generation === transcriptionGeneration) abortTranscription = abort;
|
||||
});
|
||||
if (generation !== transcriptionGeneration) return;
|
||||
@@ -907,11 +908,6 @@ const clearDraftAudio = () => {
|
||||
draftAudioOssId.value = undefined;
|
||||
};
|
||||
|
||||
const stopMediaTracks = () => {
|
||||
mediaStream.value?.getTracks().forEach((track) => track.stop());
|
||||
mediaStream.value = null;
|
||||
};
|
||||
|
||||
const cancelTranscription = () => {
|
||||
transcriptionGeneration += 1;
|
||||
abortTranscription?.();
|
||||
@@ -923,14 +919,8 @@ const cancelRecording = () => {
|
||||
recordingCaptureGeneration += 1;
|
||||
cancelTranscription();
|
||||
recordingStarting.value = false;
|
||||
const recorder = mediaRecorder.value;
|
||||
if (recorder && recorder.state !== 'inactive') {
|
||||
recorder.ondataavailable = null;
|
||||
recorder.onstop = null;
|
||||
recorder.stop();
|
||||
}
|
||||
mediaRecorder.value = null;
|
||||
stopMediaTracks();
|
||||
recordingCapture?.cancel();
|
||||
recordingCapture = null;
|
||||
recording.value = false;
|
||||
recordingAssignmentId.value = null;
|
||||
};
|
||||
@@ -965,13 +955,13 @@ const applyDailyAsrResult = (assignmentId: number, result: { text?: string; audi
|
||||
message.value = '每日一练语音已转成文字,请确认后提交';
|
||||
};
|
||||
|
||||
const transcribeBlob = async (blob: Blob, assignmentId?: number) => {
|
||||
const transcribeCapture = async (capture: SpeechCapture, assignmentId?: number) => {
|
||||
if (transcribing.value) return;
|
||||
const generation = ++transcriptionGeneration;
|
||||
transcribing.value = true;
|
||||
message.value = '语音转写中';
|
||||
try {
|
||||
const result = await transcribeSpeechBlob(blob, 'practice-audio.webm', (abort) => {
|
||||
const result = await transcribeSpeechCapture(capture, 'practice-audio.webm', (abort) => {
|
||||
if (generation === transcriptionGeneration) abortTranscription = abort;
|
||||
});
|
||||
if (generation !== transcriptionGeneration) return;
|
||||
@@ -1023,45 +1013,40 @@ const pickAudio = async (assignmentId?: number) => {
|
||||
const startRecording = async (assignmentId?: number) => {
|
||||
if (busy.value || recordingStarting.value || recording.value || transcribing.value) return;
|
||||
if (!requireLogin()) return;
|
||||
if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia || typeof MediaRecorder === 'undefined') {
|
||||
await pickAudio(assignmentId);
|
||||
return;
|
||||
}
|
||||
const generation = ++recordingCaptureGeneration;
|
||||
recordingStarting.value = true;
|
||||
let fallbackRequested = false;
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||
if (generation !== recordingCaptureGeneration) {
|
||||
stream.getTracks().forEach((track) => track.stop());
|
||||
const session = await startSpeechCapture({
|
||||
onCapture: (capture) => {
|
||||
if (generation !== recordingCaptureGeneration) return;
|
||||
recordingCapture = null;
|
||||
recording.value = false;
|
||||
const targetAssignmentId = assignmentId;
|
||||
recordingAssignmentId.value = null;
|
||||
void transcribeCapture(capture, targetAssignmentId);
|
||||
},
|
||||
onError: () => {
|
||||
if (generation !== recordingCaptureGeneration) return;
|
||||
fallbackRequested = true;
|
||||
recordingCapture = null;
|
||||
recording.value = false;
|
||||
recordingAssignmentId.value = null;
|
||||
void pickAudio(assignmentId);
|
||||
}
|
||||
});
|
||||
if (!session || generation !== recordingCaptureGeneration) {
|
||||
session?.cancel();
|
||||
if (generation === recordingCaptureGeneration && !fallbackRequested) await pickAudio(assignmentId);
|
||||
return;
|
||||
}
|
||||
mediaStream.value = stream;
|
||||
const recorder = new MediaRecorder(stream);
|
||||
const chunks: Blob[] = [];
|
||||
mediaRecorder.value = recorder;
|
||||
recordingCapture = session;
|
||||
recordingAssignmentId.value = assignmentId ?? null;
|
||||
recorder.ondataavailable = (event) => {
|
||||
if (event.data?.size) chunks.push(event.data);
|
||||
};
|
||||
recorder.onstop = () => {
|
||||
recording.value = false;
|
||||
mediaRecorder.value = null;
|
||||
stopMediaTracks();
|
||||
const blob = new Blob(chunks, { type: recorder.mimeType || 'audio/webm' });
|
||||
if (!blob.size) {
|
||||
message.value = '未录到音频,请改用文字输入';
|
||||
return;
|
||||
}
|
||||
const targetAssignmentId = assignmentId;
|
||||
recordingAssignmentId.value = null;
|
||||
void transcribeBlob(blob, targetAssignmentId);
|
||||
};
|
||||
recorder.start();
|
||||
recording.value = true;
|
||||
message.value = '正在录音';
|
||||
} catch (error) {
|
||||
recordingAssignmentId.value = null;
|
||||
stopMediaTracks();
|
||||
recordingCapture = null;
|
||||
if (generation === recordingCaptureGeneration) await pickAudio(assignmentId);
|
||||
} finally {
|
||||
if (generation === recordingCaptureGeneration) recordingStarting.value = false;
|
||||
@@ -1069,10 +1054,9 @@ const startRecording = async (assignmentId?: number) => {
|
||||
};
|
||||
|
||||
const stopRecording = () => {
|
||||
const recorder = mediaRecorder.value;
|
||||
if (!recording.value || !recorder || recorder.state === 'inactive') return;
|
||||
if (!recording.value || !recordingCapture) return;
|
||||
message.value = '录音结束,正在转写';
|
||||
recorder.stop();
|
||||
recordingCapture.stop();
|
||||
};
|
||||
|
||||
const toggleRecording = (assignmentId?: number) => {
|
||||
|
||||
Reference in New Issue
Block a user