Skip to content

Commit

Permalink
feat(episode): sentence could be played
Browse files Browse the repository at this point in the history
  • Loading branch information
justorez committed Feb 19, 2024
1 parent f5b0056 commit 2767646
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 3 deletions.
24 changes: 23 additions & 1 deletion src/hooks/episode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export function usePage(ep: number) {
new Array(total).fill(false)
)

// TODO 开关:是否跳过已练习的句子
// TODO 开关:跳过已练习的句子
const skip = useLocalStorage('skip', false)

// const index = ref(skip.value ? completedList.value.findIndex((x) => !x) : 0)
Expand Down Expand Up @@ -74,3 +74,25 @@ export function useAudio() {
playNo: () => noAudio.play()
}
}

export function useSpeech() {
if (window.speechSynthesis) {
const synth = window.speechSynthesis

let voices = synth.getVoices().filter((v) => v.lang === 'en-US')
synth.onvoiceschanged = () => {
voices = synth.getVoices().filter((v) => v.lang === 'en-US')
}

const speak = (text: string) => {
const utterance = new SpeechSynthesisUtterance(text)
utterance.voice =
voices.find((v) => v.name.includes('Emma')) || null
utterance.lang = 'en-US'
synth.speak(utterance)
}
return { supported: true, speak }
} else {
return { supported: false, speak: (text: string) => text }
}
}
14 changes: 12 additions & 2 deletions src/pages/episode.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { checkSentence } from '@/utils/index'
import { useAudio, usePage, useInput } from '@/hooks/episode'
import { useAudio, usePage, useInput, useSpeech } from '@/hooks/episode'
const route = useRoute()
const router = useRouter()
Expand All @@ -10,6 +10,7 @@ const ep = Number(route.query.i)
const { episode, sentence, page, completed, completedList, nextPage } =
usePage(ep)
const { input, noInput } = useInput()
const { speak, supported: speechSupported } = useSpeech()
const showResult = ref(false)
const result = ref(false)
Expand Down Expand Up @@ -60,7 +61,16 @@ function next() {
第 {{ ep }} 集:{{ episode.titleCN }}
</h1>
<div class="mt-5 mb-3 text-lg flex justify-between items-end">
<span>“{{ sentence.CN }}”</span>
<div class="flex items-center gap-2">
<button
v-if="speechSupported"
class="btn btn-circle btn-sm"
@click="speak(sentence.EN)"
>
<i-mdi:volume />
</button>
<span>“{{ sentence.CN }}”</span>
</div>
<!-- <label class="label cursor-pointer p-0 items-end">
<span class="label-text mr-3">跳过已完成</span>
<input
Expand Down
36 changes: 36 additions & 0 deletions test/speech.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<select id="voiceSelect"></select>
<button onclick="play()">play</button>
<script>
let voices = []
const voiceSelect = document.querySelector('#voiceSelect')
speechSynthesis.onvoiceschanged = populateVoiceList

function populateVoiceList() {
if (typeof speechSynthesis === 'undefined') {
return
}
voiceSelect.innerHTML = ''
voices = speechSynthesis.getVoices().filter((v) => v.lang === 'en-US')
for (const voice of voices) {
var option = document.createElement('option')
option.textContent = voice.name + ' (' + voice.lang + ')'
if (voice.default) {
option.textContent += ' -- DEFAULT'
}
option.setAttribute('data-lang', voice.lang)
option.setAttribute('data-name', voice.name)
voiceSelect.appendChild(option)
}
}

async function play() {
const synth = window.speechSynthesis
const textToSpeak = new SpeechSynthesisUtterance(
'So, peppa and george cannot play outside.'
)
const option = voiceSelect.selectedOptions[0]
textToSpeak.voice = voices.find(v => v.name === option.dataset.name)
textToSpeak.lang = 'en-US'
synth.speak(textToSpeak)
}
</script>
1 change: 1 addition & 0 deletions types/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ declare module 'vue' {
export interface GlobalComponents {
'IMdi:check': typeof import('~icons/mdi/check')['default']
'IMdi:close': typeof import('~icons/mdi/close')['default']
'IMdi:volume': typeof import('~icons/mdi/volume')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
}
Expand Down

0 comments on commit 2767646

Please sign in to comment.