Focus readback input after auto playback and restore radio EQ

This commit is contained in:
Remi
2025-10-05 12:18:46 +02:00
committed by itsrubberduck
parent 7d41df2452
commit 13cd0ab609

View File

@@ -1017,6 +1017,7 @@
:aria-label="fieldLabel(segment.key)"
:placeholder="fieldPlaceholder(segment.key)"
:inputmode="fieldInputmode(segment.key)"
:ref="el => assignReadbackFieldRef(segment.key, el as HTMLInputElement | null)"
/>
<v-icon v-if="fieldPass(segment.key)" size="16" class="blank-status ok">mdi-check</v-icon>
<v-icon v-else-if="fieldHasAnswer(segment.key)" size="16" class="blank-status warn">mdi-alert
@@ -2590,6 +2591,7 @@ const hasSpokenTarget = ref(false)
const pendingAutoSay = ref(false)
const activeFrequency = ref<Frequency | null>(null)
const userAnswers = reactive<Record<string, string>>({})
const readbackFieldRefs = new Map<string, HTMLInputElement>()
const result = ref<ScoreResult | null>(null)
const evaluating = ref(false)
const ttsLoading = ref(false)
@@ -3019,6 +3021,39 @@ const fieldMap = computed<Record<string, LessonField>>(() => {
return map
})
const firstReadbackFieldKey = computed<string | null>(() => {
const lesson = activeLesson.value
if (!lesson) return null
const firstField = lesson.readback.find(segment => segment.type === 'field')
if (!firstField || firstField.type !== 'field') return null
return firstField.key
})
function assignReadbackFieldRef(key: string, el: HTMLInputElement | null) {
if (!el) {
readbackFieldRefs.delete(key)
return
}
readbackFieldRefs.set(key, el)
}
function focusFirstReadbackField() {
const key = firstReadbackFieldKey.value
if (!key) return
const target = readbackFieldRefs.get(key)
if (target) {
target.focus()
target.select?.()
return
}
void nextTick(() => {
const fallback = readbackFieldRefs.get(key)
if (!fallback) return
fallback.focus()
fallback.select?.()
})
}
const fieldStates = computed<Record<string, FieldState>>(() => {
const map: Record<string, FieldState> = {}
if (!activeLesson.value || !scenario.value) return map
@@ -3308,6 +3343,9 @@ async function speakTarget(auto = false) {
pendingAutoSay.value = false
}
hasSpokenTarget.value = true
if (auto) {
focusFirstReadbackField()
}
await say(phrase)
}
@@ -3991,14 +4029,17 @@ async function playAudioSource(source: CachedAudio, targetRate: number) {
const nativeRate = supportsNativeSpeed
? clampRate(typeof source.speed === 'number' && Number.isFinite(source.speed) ? source.speed : desiredRate, 0.5, 2)
: 1
const playbackRate = supportsNativeSpeed
const basePlaybackRate = supportsNativeSpeed
? clampRate(desiredRate / (nativeRate || 1), 0.5, 2)
: desiredRate
const needsRateFallback = !supportsNativeSpeed && Math.abs(basePlaybackRate - 1) > 0.0001
const playbackRate = needsRateFallback ? 1 : basePlaybackRate
const htmlPlaybackRate = needsRateFallback ? basePlaybackRate : playbackRate
const playWithoutEffects = async () => {
const audio = new Audio(dataUrl)
audio.preload = 'auto'
audio.playbackRate = playbackRate
audio.playbackRate = htmlPlaybackRate
setMediaElementPreservesPitch(audio, true)
audioElement.value = audio
audio.onended = () => {
@@ -4025,11 +4066,6 @@ async function playAudioSource(source: CachedAudio, targetRate: number) {
throw new Error('Audio engine unavailable')
}
if (!supportsNativeSpeed && Math.abs(playbackRate - 1) > 0.0001) {
await playWithoutEffects()
return
}
const sound = await pizzicato.createSoundFromBase64(ctx, source.base64)
sound.setPlaybackRate(playbackRate)
const profile = getReadabilityProfile(readability)