mirror of
https://github.com/Megghy/vtsuru.live.git
synced 2025-12-06 18:36:55 +08:00
fix tts
This commit is contained in:
@@ -132,7 +132,7 @@ async function speak() {
|
|||||||
speechCount.value--
|
speechCount.value--
|
||||||
readedDanmaku.value++
|
readedDanmaku.value++
|
||||||
console.log(`[TTS] 正在朗读: ${text}`)
|
console.log(`[TTS] 正在朗读: ${text}`)
|
||||||
await EasySpeech.speak({
|
/*await EasySpeech.speak({
|
||||||
text: text,
|
text: text,
|
||||||
volume: settings.value.speechInfo.volume,
|
volume: settings.value.speechInfo.volume,
|
||||||
pitch: settings.value.speechInfo.pitch,
|
pitch: settings.value.speechInfo.pitch,
|
||||||
@@ -150,7 +150,29 @@ async function speak() {
|
|||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
isSpeaking.value = false
|
isSpeaking.value = false
|
||||||
})
|
})*/
|
||||||
|
const synth = window.speechSynthesis
|
||||||
|
let u = new SpeechSynthesisUtterance()
|
||||||
|
u.text = text
|
||||||
|
let voices = synth.getVoices()
|
||||||
|
const voice = voices.find((v) => v.name === settings.value.speechInfo.voice)
|
||||||
|
if (voice) {
|
||||||
|
u.voice = voice
|
||||||
|
u.volume = settings.value.speechInfo.volume
|
||||||
|
u.rate = settings.value.speechInfo.rate
|
||||||
|
u.pitch = settings.value.speechInfo.pitch
|
||||||
|
synth.speak(u)
|
||||||
|
u.onend = () => {
|
||||||
|
isSpeaking.value = false
|
||||||
|
}
|
||||||
|
u.onerror = (err) => {
|
||||||
|
if (err.error == 'interrupted') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
console.log(err)
|
||||||
|
message.error('无法播放语音: ' + err.error)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function onGetEvent(data: EventModel) {
|
function onGetEvent(data: EventModel) {
|
||||||
@@ -219,6 +241,7 @@ function stopSpeech() {
|
|||||||
}
|
}
|
||||||
function cancelSpeech() {
|
function cancelSpeech() {
|
||||||
EasySpeech.cancel()
|
EasySpeech.cancel()
|
||||||
|
isSpeaking.value = false
|
||||||
}
|
}
|
||||||
async function uploadConfig() {
|
async function uploadConfig() {
|
||||||
await QueryPostAPI(VTSURU_API_URL + 'set-config', {
|
await QueryPostAPI(VTSURU_API_URL + 'set-config', {
|
||||||
@@ -351,77 +374,92 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<NAlert v-if="!speechSynthesisInfo || !speechSynthesisInfo.speechSynthesis" type="error"> 你的浏览器不支持语音功能 </NAlert>
|
<NAlert v-if="!speechSynthesisInfo || !speechSynthesisInfo.speechSynthesis" type="error"> 你的浏览器不支持语音功能 </NAlert>
|
||||||
<NSpace v-else>
|
<template v-else>
|
||||||
<NButton @click="canSpeech ? stopSpeech() : startSpeech()" :type="canSpeech ? 'error' : 'primary'"> {{ canSpeech ? '停止监听' : '开始监听' }} </NButton>
|
<NAlert type="info">
|
||||||
<NButton @click="uploadConfig" type="primary" secondary> 保存配置到服务器 </NButton>
|
建议在 Edge 浏览器使用
|
||||||
<NPopconfirm @positive-click="downloadConfig">
|
|
||||||
<template #trigger>
|
|
||||||
<NButton type="primary" secondary> 从服务器获取配置 </NButton>
|
|
||||||
</template>
|
|
||||||
这将覆盖当前设置, 确定?
|
|
||||||
</NPopconfirm>
|
|
||||||
</NSpace>
|
|
||||||
<template v-if="canSpeech">
|
|
||||||
<NDivider> 状态 </NDivider>
|
|
||||||
<NSpace vertical align="center">
|
|
||||||
<NTooltip>
|
<NTooltip>
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<NButton circle :disabled="!isSpeaking" @click="cancelSpeech" :style="`animation: ${isSpeaking ? 'animated-border 2.5s infinite;' : ''}`">
|
<NText strong italic type="primary">Microsoft 某某 Online (Nature)</NText>
|
||||||
<template #icon>
|
|
||||||
<NIcon :component="Mic24Filled" :color="isSpeaking ? 'green' : 'gray'" />
|
|
||||||
</template>
|
|
||||||
</NButton>
|
|
||||||
</template>
|
</template>
|
||||||
{{ isSpeaking ? '取消朗读' : '未朗读' }}
|
例如 Microsoft Xiaoxiao Online (Natural) - Chinese (Mainland), 各种营销号就用的这些配音
|
||||||
</NTooltip>
|
</NTooltip>
|
||||||
<NText depth="3"> 队列: {{ speechCount }} <NDivider vertical /> 已读: {{ readedDanmaku }} 条 </NText>
|
系列语音, 效果要好很多
|
||||||
</NSpace>
|
</NAlert>
|
||||||
</template>
|
<br />
|
||||||
<NDivider />
|
|
||||||
<NSpace vertical>
|
|
||||||
<NSelect v-model:value="settings.speechInfo.voice" :options="voiceOptions" :fallback-option="() => ({ label: '未选择, 将使用默认语音', value: '' })" />
|
|
||||||
<span style="width: 100%">
|
|
||||||
<NText> 音量 </NText>
|
|
||||||
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.volume" :min="0" :max="1" :step="0.01" />
|
|
||||||
</span>
|
|
||||||
<span style="width: 100%">
|
|
||||||
<NText> 音调 </NText>
|
|
||||||
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.pitch" :min="0" :max="2" :step="0.01" />
|
|
||||||
</span>
|
|
||||||
<span style="width: 100%">
|
|
||||||
<NText> 语速 </NText>
|
|
||||||
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.rate" :min="0" :max="2" :step="0.01" />
|
|
||||||
</span>
|
|
||||||
</NSpace>
|
|
||||||
<NDivider> 自定义内容 </NDivider>
|
|
||||||
<NSpace vertical>
|
|
||||||
<NSpace>
|
<NSpace>
|
||||||
支持的变量:
|
<NButton @click="canSpeech ? stopSpeech() : startSpeech()" :type="canSpeech ? 'error' : 'primary'" data-umami-event="Use TTS" :data-umami-event-uid="accountInfo?.id">
|
||||||
<NButton size="tiny" secondary v-for="item in Object.values(templateConstants)" :key="item.name" @click="copyToClipboard(item.words)"> {{ item.words }} | {{ item.name }} </NButton>
|
{{ canSpeech ? '停止监听' : '开始监听' }}
|
||||||
|
</NButton>
|
||||||
|
<NButton @click="uploadConfig" type="primary" secondary> 保存配置到服务器 </NButton>
|
||||||
|
<NPopconfirm @positive-click="downloadConfig">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton type="primary" secondary> 从服务器获取配置 </NButton>
|
||||||
|
</template>
|
||||||
|
这将覆盖当前设置, 确定?
|
||||||
|
</NPopconfirm>
|
||||||
</NSpace>
|
</NSpace>
|
||||||
<NInputGroup>
|
<template v-if="canSpeech">
|
||||||
<NInputGroupLabel> 弹幕模板 </NInputGroupLabel>
|
<NDivider> 状态 </NDivider>
|
||||||
<NInput v-model:value="settings.danmakuTemplate" placeholder="弹幕消息" />
|
<NSpace vertical align="center">
|
||||||
<NButton @click="test(EventDataTypes.Message)" type="info"> 测试 </NButton>
|
<NTooltip>
|
||||||
</NInputGroup>
|
<template #trigger>
|
||||||
<NInputGroup>
|
<NButton circle :disabled="!isSpeaking" @click="cancelSpeech" :style="`animation: ${isSpeaking ? 'animated-border 2.5s infinite;' : ''}`">
|
||||||
<NInputGroupLabel> 礼物模板 </NInputGroupLabel>
|
<template #icon>
|
||||||
<NInput v-model:value="settings.giftTemplate" placeholder="礼物消息" />
|
<NIcon :component="Mic24Filled" :color="isSpeaking ? 'green' : 'gray'" />
|
||||||
<NButton @click="test(EventDataTypes.Gift)" type="info"> 测试 </NButton>
|
</template>
|
||||||
</NInputGroup>
|
</NButton>
|
||||||
<NInputGroup>
|
</template>
|
||||||
<NInputGroupLabel> SC模板 </NInputGroupLabel>
|
{{ isSpeaking ? '取消朗读' : '未朗读' }}
|
||||||
<NInput v-model:value="settings.scTemplate" placeholder="SC消息" />
|
</NTooltip>
|
||||||
<NButton @click="test(EventDataTypes.SC)" type="info"> 测试 </NButton>
|
<NText depth="3"> 队列: {{ speechCount }} <NDivider vertical /> 已读: {{ readedDanmaku }} 条 </NText>
|
||||||
</NInputGroup>
|
</NSpace>
|
||||||
<NInputGroup>
|
</template>
|
||||||
<NInputGroupLabel> 上舰模板 </NInputGroupLabel>
|
<NDivider />
|
||||||
<NInput v-model:value="settings.guardTemplate" placeholder="上舰消息" />
|
<NSpace vertical>
|
||||||
<NButton @click="test(EventDataTypes.Guard)" type="info"> 测试 </NButton>
|
<NSelect v-model:value="settings.speechInfo.voice" :options="voiceOptions" :fallback-option="() => ({ label: '未选择, 将使用默认语音', value: '' })" />
|
||||||
</NInputGroup>
|
<span style="width: 100%">
|
||||||
</NSpace>
|
<NText> 音量 </NText>
|
||||||
<NDivider> 设置 </NDivider>
|
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.volume" :min="0" :max="1" :step="0.01" />
|
||||||
<NText depth="3"> 没想好需要什么, 有建议的话可以和我说 </NText>
|
</span>
|
||||||
|
<span style="width: 100%">
|
||||||
|
<NText> 音调 </NText>
|
||||||
|
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.pitch" :min="0" :max="2" :step="0.01" />
|
||||||
|
</span>
|
||||||
|
<span style="width: 100%">
|
||||||
|
<NText> 语速 </NText>
|
||||||
|
<NSlider style="min-width: 200px" v-model:value="settings.speechInfo.rate" :min="0" :max="2" :step="0.01" />
|
||||||
|
</span>
|
||||||
|
</NSpace>
|
||||||
|
<NDivider> 自定义内容 </NDivider>
|
||||||
|
<NSpace vertical>
|
||||||
|
<NSpace>
|
||||||
|
支持的变量:
|
||||||
|
<NButton size="tiny" secondary v-for="item in Object.values(templateConstants)" :key="item.name" @click="copyToClipboard(item.words)"> {{ item.words }} | {{ item.name }} </NButton>
|
||||||
|
</NSpace>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputGroupLabel> 弹幕模板 </NInputGroupLabel>
|
||||||
|
<NInput v-model:value="settings.danmakuTemplate" placeholder="弹幕消息" />
|
||||||
|
<NButton @click="test(EventDataTypes.Message)" type="info"> 测试 </NButton>
|
||||||
|
</NInputGroup>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputGroupLabel> 礼物模板 </NInputGroupLabel>
|
||||||
|
<NInput v-model:value="settings.giftTemplate" placeholder="礼物消息" />
|
||||||
|
<NButton @click="test(EventDataTypes.Gift)" type="info"> 测试 </NButton>
|
||||||
|
</NInputGroup>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputGroupLabel> SC模板 </NInputGroupLabel>
|
||||||
|
<NInput v-model:value="settings.scTemplate" placeholder="SC消息" />
|
||||||
|
<NButton @click="test(EventDataTypes.SC)" type="info"> 测试 </NButton>
|
||||||
|
</NInputGroup>
|
||||||
|
<NInputGroup>
|
||||||
|
<NInputGroupLabel> 上舰模板 </NInputGroupLabel>
|
||||||
|
<NInput v-model:value="settings.guardTemplate" placeholder="上舰消息" />
|
||||||
|
<NButton @click="test(EventDataTypes.Guard)" type="info"> 测试 </NButton>
|
||||||
|
</NInputGroup>
|
||||||
|
</NSpace>
|
||||||
|
<NDivider> 设置 </NDivider>
|
||||||
|
<NText depth="3"> 没想好需要什么, 有建议的话可以和我说 </NText>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
Reference in New Issue
Block a user