From caa3c390a2cc04a0d5934d6c129dc037ede9f0a8 Mon Sep 17 00:00:00 2001 From: Megghy Date: Wed, 20 Dec 2023 17:32:28 +0800 Subject: [PATCH] improve tts check --- src/views/open_live/ReadDanmaku.vue | 163 ++++++++++++++++++++++++---- 1 file changed, 141 insertions(+), 22 deletions(-) diff --git a/src/views/open_live/ReadDanmaku.vue b/src/views/open_live/ReadDanmaku.vue index 4e05f24..561e5b6 100644 --- a/src/views/open_live/ReadDanmaku.vue +++ b/src/views/open_live/ReadDanmaku.vue @@ -5,10 +5,34 @@ import { EventDataTypes, EventModel } from '@/api/api-models' import { QueryGetAPI, QueryPostAPI } from '@/api/query' import DanmakuClient, { RoomAuthInfo } from '@/data/DanmakuClient' import { VTSURU_API_URL } from '@/data/constants' -import { Mic24Filled } from '@vicons/fluent' +import { Info24Filled, Mic24Filled } from '@vicons/fluent' import { useStorage } from '@vueuse/core' import EasySpeech from 'easy-speech' -import { NAlert, NButton, NDivider, NIcon, NInput, NInputGroup, NInputGroupLabel, NPopconfirm, NSelect, NSlider, NSpace, NText, NTooltip, useMessage } from 'naive-ui' +import { List } from 'linqts' +import { + NAlert, + NButton, + NCheckbox, + NCollapse, + NCollapseItem, + NDivider, + NEmpty, + NIcon, + NInput, + NInputGroup, + NInputGroupLabel, + NInputNumber, + NList, + NListItem, + NPopconfirm, + NSelect, + NSlider, + NSpace, + NTag, + NText, + NTooltip, + useMessage, +} from 'naive-ui' import { computed, onMounted, onUnmounted, ref } from 'vue' import { useRoute } from 'vue-router' import { clearInterval, setInterval } from 'worker-timers' @@ -26,6 +50,8 @@ type SpeechSettings = { scTemplate: string guardTemplate: string giftTemplate: string + + combineGiftDelay?: number } type SpeechInfo = { volume: number @@ -48,6 +74,8 @@ const settings = useStorage('Setting.Speech.Settings', { scTemplate: '{name} 发送了醒目留言: {message}', guardTemplate: '感谢 {name} 的 {count} 个月 {guard_level}', giftTemplate: '感谢 {name} 赠送的 {count} 个 {gift_name}', + + combineGiftDelay: 2, }) const speechSynthesisInfo = ref<{ speechSynthesis: SpeechSynthesis | undefined @@ -66,15 +94,18 @@ const speechSynthesisInfo = ref<{ }>() const languageDisplayName = new Intl.DisplayNames(['zh'], { type: 'language' }) const voiceOptions = computed(() => { - return EasySpeech.voices().map((v) => { - return { - label: `[${languageDisplayName.of(v.lang)}] ${v.name}`, - value: v.name, - } - }) + return new List(EasySpeech.voices()) + .Select((v) => { + return { + label: `[${languageDisplayName.of(v.lang)}] ${v.name}`, + value: v.name, + } + }) + .DistinctBy((v) => v.value) + .ToArray() }) const isSpeaking = ref(false) -const speakQueue = ref([]) +const speakQueue = ref<{ updateAt: number; combineCount?: number; data: EventModel }[]>([]) const canSpeech = ref(false) const readedDanmaku = ref(0) @@ -123,13 +154,25 @@ const templateConstants = { } function forceSpeak(data: EventModel) { cancelSpeech() - speakQueue.value.unshift(data) + + speakQueue.value.splice( + speakQueue.value.findIndex((v) => v.data == data), + 1, + ) + speakQueue.value.unshift({ + updateAt: 0, + data, + }) } async function speak() { if (isSpeaking.value || speakQueue.value.length == 0) { return } - const text = getTextFromDanmaku(speakQueue.value.shift()) + const data = speakQueue.value[0] + if (data.data.type == EventDataTypes.Gift && data.updateAt > Date.now() - (settings.value.combineGiftDelay ?? 0) * 1000) { + return + } + const text = getTextFromDanmaku(speakQueue.value.shift()?.data) if (text) { isSpeaking.value = true readedDanmaku.value++ @@ -156,6 +199,7 @@ async function speak() { speakDirect(text) } } +let checkTimer: number function speakDirect(text: string) { try { const synth = window.speechSynthesis @@ -169,13 +213,20 @@ function speakDirect(text: string) { let voices = synth.getVoices() const voice = voices.find((v) => v.name === settings.value.speechInfo.voice) if (voice) { + if (checkTimer) { + clearInterval(checkTimer) + } u.voice = voice u.volume = settings.value.speechInfo.volume u.rate = settings.value.speechInfo.rate u.pitch = settings.value.speechInfo.pitch synth.speak(u) + checkTimer = setInterval(() => { + message.error('语音播放超时') + cancelSpeech() + }, 30000) u.onend = () => { - isSpeaking.value = false + cancelSpeech() } u.onerror = (err) => { if (err.error == 'interrupted') { @@ -183,7 +234,7 @@ function speakDirect(text: string) { } console.log(err) message.error('无法播放语音: ' + err.error) - isSpeaking.value = false + cancelSpeech() } } } catch (err) { @@ -199,15 +250,23 @@ function onGetEvent(data: EventModel) { return } if (data.type == EventDataTypes.Gift) { - const exist = speakQueue.value.find((v) => v.type == EventDataTypes.Gift && v.uid == data.uid && v.msg == data.msg) + const exist = speakQueue.value.find( + (v) => v.data.type == EventDataTypes.Gift && v.data.uid == data.uid && v.data.msg == data.msg && v.updateAt > Date.now() - (settings.value.combineGiftDelay ?? 0) * 1000, + ) if (exist) { - exist.num += data.num - exist.price += data.price - console.log(`[TTS] ${data.name} 增加已存在礼物数量: ${data.msg} [${exist.num - data.num} => ${exist.num}]`) + exist.updateAt = Date.now() + exist.data.num += data.num + exist.data.price += data.price + exist.combineCount ??= 0 + exist.combineCount += data.num + console.log(`[TTS] ${data.name} 增加已存在礼物数量: ${data.msg} [${exist.data.num - data.num} => ${exist.data.num}]`) return } } - speakQueue.value.push(data) + speakQueue.value.push({ + data, + updateAt: data.type == EventDataTypes.Gift ? Date.now() : 0, + }) } function getTextFromDanmaku(data: EventModel | undefined) { if (!data) { @@ -270,6 +329,9 @@ function stopSpeech() { function cancelSpeech() { EasySpeech.cancel() isSpeaking.value = false + if (checkTimer) { + clearInterval(checkTimer) + } } async function uploadConfig() { await QueryPostAPI(VTSURU_API_URL + 'set-config', { @@ -383,7 +445,7 @@ onMounted(() => { speechSynthesisInfo.value = EasySpeech.detect() speechQueueTimer = setInterval(() => { speak() - }, 500) + }, 250) props.client.onEvent('danmaku', onGetEvent) props.client.onEvent('sc', onGetEvent) @@ -417,10 +479,10 @@ onUnmounted(() => { {{ canSpeech ? '停止监听' : '开始监听' }} - 保存配置到服务器 + 保存配置到服务器 这将覆盖当前设置, 确定? @@ -439,6 +501,33 @@ onUnmounted(() => { {{ isSpeaking ? '取消朗读' : '未朗读' }} 队列: {{ speakQueue.length }} 已读: {{ readedDanmaku }} 条 + + + 暂无 + + + + + 取消 + 连续赠送中 + 等待连续赠送检查 + + 弹幕 + 礼物 + 舰长 + SC + + + {{ item.data.name }} + + + {{ getTextFromDanmaku(item.data) }} + + + + + + @@ -485,7 +574,37 @@ onUnmounted(() => { 设置 - 没想好需要什么, 有建议的话可以和我说 + + + 是否启用礼物合并 + + + 在指定时间内连续送相同礼物会等停止送礼物之后才会念 +
+ 这也会导致送的礼物会等待指定时间之后才会念, 即使没有连续赠送 +
+
+ + 送礼间隔 (秒) + + +