feat(showcase): finish voice.js
This commit is contained in:
@@ -234,8 +234,9 @@ export default class Settings {
|
||||
}
|
||||
|
||||
elementPosition(el, x, y) {
|
||||
const elWidth = getComputedStyle(el).width
|
||||
const elHeight = getComputedStyle(el).height
|
||||
const computedStyle = getComputedStyle(el)
|
||||
const elWidth = computedStyle.width
|
||||
const elHeight = computedStyle.height
|
||||
const windowWidth = window.innerWidth
|
||||
const windowHeight = window.innerHeight
|
||||
const xRange = windowWidth - parseInt(elWidth)
|
||||
@@ -352,8 +353,8 @@ export default class Settings {
|
||||
</div>
|
||||
<div>
|
||||
<label for="voice">Voice</label>
|
||||
<input type="checkbox" id="voice" name="voice" checked/>
|
||||
<div id="voice_realted">
|
||||
<input type="checkbox" id="voice" name="voice"/>
|
||||
<div id="voice_realted" hidden>
|
||||
<div>
|
||||
<label for="voice_lang_select">Choose the language of voice:</label>
|
||||
<select name="voice_lang" id="voice_lang_select">
|
||||
@@ -368,9 +369,7 @@ export default class Settings {
|
||||
<label for="voice_next_duration">Next Duration (min)</label>
|
||||
<input type="number" id="voice_next_duration_input" name="voice_next_duration" value="${window.voice.nextDuration}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<label for="subtitle">Subtitle</label>
|
||||
<input type="checkbox" id="subtitle" name="subtitle" checked/>
|
||||
<div id="subtitle_realted">
|
||||
@@ -380,11 +379,23 @@ export default class Settings {
|
||||
${this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="subtitle_padding_x">Subtitle X Position</label>
|
||||
<input type="range" min="0" max="100" id="subtitle_padding_x_slider" value="${window.voice.subtitleX}" />
|
||||
<input type="number" id="subtitle_padding_x_input" name="subtitle_padding_x" value="${window.voice.subtitleX}" />
|
||||
</div>
|
||||
<div>
|
||||
<label for="subtitle_padding_y">Subtitle Y Position</label>
|
||||
<input type="range" min="0" max="100" id="subtitle_padding_y_slider" value="${window.voice.subtitleY}" />
|
||||
<input type="number" id="subtitle_padding_y_input" name="subtitle_padding_y" value="${window.voice.subtitleY}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="voice_actor">Voice Actor</label>
|
||||
<input type="checkbox" id="voice_actor" name="voice_actor" checked/>
|
||||
<input type="checkbox" id="voice_actor" name="voice_actor"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label for="position">Position</label>
|
||||
@@ -445,6 +456,13 @@ export default class Settings {
|
||||
return value
|
||||
}
|
||||
|
||||
#getCurrentOptions(id, value) {
|
||||
const e = document.getElementById(id);
|
||||
const options = [...e]
|
||||
const toSelecteIndex = options.findIndex(i => options.find(o => o.value === value) === i)
|
||||
e.selectedIndex = toSelecteIndex;
|
||||
}
|
||||
|
||||
#addEventListeners() {
|
||||
const _this = this;
|
||||
const listeners = [
|
||||
@@ -526,6 +544,7 @@ export default class Settings {
|
||||
id: "voice_lang_select", event: "change", handler: e => {
|
||||
window.voice.language = e.currentTarget.value
|
||||
_this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)
|
||||
_this.#getCurrentOptions("subtitle_lang_select", window.voice.subtitleLanguage)
|
||||
}
|
||||
}, {
|
||||
id: "voice_idle_duration_input", event: "change", handler: e => {
|
||||
@@ -542,6 +561,26 @@ export default class Settings {
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_lang_select", event: "change", handler: e => window.voice.subtitleLanguage = e.currentTarget.value
|
||||
}, {
|
||||
id: "subtitle_padding_x_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_x_input");
|
||||
window.voice.subtitleX = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_x_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_x_slider");
|
||||
window.voice.subtitleX = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_y_slider", event: "input", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_y_input");
|
||||
window.voice.subtitleY = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "subtitle_padding_y_input", event: "change", handler: e => {
|
||||
_this.#sync(e.currentTarget, "subtitle_padding_y_slider");
|
||||
window.voice.subtitleY = e.currentTarget.value
|
||||
}
|
||||
}, {
|
||||
id: "voice_actor", event: "click", handler: e => {
|
||||
window.voice.useVoiceActor = e.currentTarget.checked;
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#voice_box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
width: 480px;
|
||||
opacity: 0;
|
||||
margin: 16px;
|
||||
font-family: 'Roboto', sans-serif;
|
||||
transition: opacity 0.5s cubic-bezier(0.65, 0.05, 0.36, 1);
|
||||
}
|
||||
|
||||
.voice-title {
|
||||
background-color: #9e9e9e;
|
||||
color: black;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
left: -8px;
|
||||
padding: 2px 8px;
|
||||
font-size: 14px;
|
||||
min-width: 120px;
|
||||
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.5);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.voice-subtitle {
|
||||
background-color: rgba(0, 0, 0, 0.65);
|
||||
color: white;
|
||||
padding: 16px;
|
||||
font-size: 18px;
|
||||
box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.voice-triangle {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
right: 8px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 8px 8px 8px;
|
||||
border-color: white transparent transparent transparent;
|
||||
}
|
||||
|
||||
.voice-actor {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.voice-actor-icon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
background-color: rgba(0, 0, 0, 1);
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAXCAYAAAAGAx/kAAACGElEQVQ4jY2UO4sUQRSFv+6e3hkf6yqi62LiD1DRwGRFZDUTRTAVMTHzHxhosBgr+IhMzIXFRzKJYiQoCLqroYGCgiDK6K6z8+hj0Kd2yrZn8EJRza06X91761Ynkphgh4ETwBB4BrwZu1NS3WhJui1pTSNblXTLa/9oxoEWNd4W/xc0K6kzAfRd0lxV16jJdg6YBtaAB8AvoPA4D+wEZoEvsSgGJYAiXwf4EEFyYOC1qYqmFhQ2bQEO+MYKILMv1tWCikqKm4CPwKoF00DTa6pqkqiP9gIHgR3AWaDvdJIoip4jewh8BVaAz1XQOeAKcNcp9O0fAqlTLgzsApeBa8BSNbUUaAFvgVdMtnlHmsXiEHrHpw+iaOps4BRlDUASQDnw3uA9QHsCqA3sMmjFmhRJmaSmO/SmpCeSjktarunqd5IWJLUl3bCmKSlLJGWuVQbMAHcoi3oPWAAOOerXwFPgErAVuEjZGkNgkEhKDco9bwOuG/Ac+OS67ANOGngV+OYDewEUqt+gvLWmxxHgFHDapz52fV4C64Z3PW+AUsPyCJYCP4FHPuQMsNlRrHv0DRo2XP3Ccw/4Tdl8U2GTb6trYYimYNQ6Gw0pYDuwn1E7hJR3W3TMvp5hKbAM/IC/n8hR4L6hqSPJGT3QjPIfFX4nDeAC8CIGhTrBqNsz16plWN/R9KJShGzUiCBBnFS+FYmCP41gBaA/cImMsTl24NIAAAAASUVORK5CYII=');
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.voice-actor-name {
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
line-height: 16px;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
color: white;
|
||||
display: inline-block;
|
||||
padding: 8px;
|
||||
}
|
||||
@@ -2,288 +2,373 @@ import charword_table from '!/charword_table.json'
|
||||
import '@/components/voice.css'
|
||||
|
||||
export default class Voice {
|
||||
#el
|
||||
#widgetEl
|
||||
#audioEl = new Audio()
|
||||
#audioElId = 'voice-audio'
|
||||
#defaultVoiceLang = "JP"
|
||||
#defaultRegion = charword_table.config.default_region
|
||||
#defaultIdleDuration = 10 * 60 * 1000
|
||||
#defaultNextDuration = 3 * 60 * 1000
|
||||
#voiceLang = this.#defaultVoiceLang
|
||||
#voiceLanguages = Object.keys(this.#getCVInfo(this.#defaultRegion))
|
||||
#subtitleLang = this.#defaultRegion
|
||||
#useSubtitle = false
|
||||
#useVoice = false
|
||||
#useVoiceActor = false
|
||||
#currentVoiceId = null
|
||||
#lastVoiceId = null
|
||||
#idleListener = -1
|
||||
#idleDuration = this.#defaultIdleDuration
|
||||
#nextListener = -1
|
||||
#nextDuration = this.#defaultNextDuration
|
||||
#lastClickToNext = false
|
||||
#voiceFolderObject = this.#getVoiceFolderObject()
|
||||
|
||||
constructor(el, widgetEl) {
|
||||
this.#el = el
|
||||
this.#widgetEl = widgetEl
|
||||
}
|
||||
#el
|
||||
#widgetEl
|
||||
#audioEl = new Audio()
|
||||
#audioElId = 'voice-audio'
|
||||
#defaultVoiceLang = "CN_MANDARIN"
|
||||
#defaultRegion = charword_table.config.default_region
|
||||
#defaultIdleDuration = 10 * 60 * 1000
|
||||
#defaultNextDuration = 3 * 60 * 1000
|
||||
#voiceLang = this.#defaultVoiceLang
|
||||
#voiceLanguages = Object.keys(this.#getCVInfo(this.#defaultRegion))
|
||||
#subtitleLang = this.#defaultRegion
|
||||
#useSubtitle = true
|
||||
#useVoice = false
|
||||
#useVoiceActor = false
|
||||
#isPlaying = false
|
||||
#currentVoiceId = null
|
||||
#lastVoiceId = null
|
||||
#idleListener = -1
|
||||
#idleDuration = this.#defaultIdleDuration
|
||||
#nextListener = -1
|
||||
#nextDuration = this.#defaultNextDuration
|
||||
#lastClickToNext = false
|
||||
#voiceFolderObject = this.#getVoiceFolderObject()
|
||||
#voiceList = Object.keys(this.#getVoices())
|
||||
#defaultSubtitleX = 0
|
||||
#defaultSubtitleY = 0
|
||||
#subtitleX = this.#defaultSubtitleX
|
||||
#subtitleY = this.#defaultSubtitleY
|
||||
|
||||
init() {
|
||||
this.#insertHTML()
|
||||
this.#audioEl = document.getElementById(this.#audioElId)
|
||||
}
|
||||
constructor(el, widgetEl) {
|
||||
this.#el = el
|
||||
this.#widgetEl = widgetEl
|
||||
}
|
||||
|
||||
success() {
|
||||
this.#playEntryVoice()
|
||||
this.#initNextVoiceTimer()
|
||||
this.#widgetEl.addEventListener('click', e => {
|
||||
this.#lastClickToNext = true
|
||||
this.#nextVoice()
|
||||
init() {
|
||||
this.#insertHTML()
|
||||
this.#audioEl = document.getElementById(this.#audioElId)
|
||||
}
|
||||
|
||||
success() {
|
||||
this.#playEntryVoice()
|
||||
this.#initNextVoiceTimer()
|
||||
this.#widgetEl.addEventListener('click', e => {
|
||||
this.#lastClickToNext = true
|
||||
this.#nextVoice()
|
||||
})
|
||||
document.addEventListener('mousemove', e => {
|
||||
if (this.#idleListener === -1) {
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useSubtitle(show) {
|
||||
this.#useSubtitle = show
|
||||
this.#el.hidden = !show
|
||||
}
|
||||
|
||||
get useSubtitle() {
|
||||
return this.#useSubtitle
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoice(show) {
|
||||
this.#useVoice = show
|
||||
this.#el.hidden = !show
|
||||
this.#playEntryVoice()
|
||||
if (!show && this.#isPlaying) {
|
||||
this.#audioEl.pause()
|
||||
}
|
||||
}
|
||||
|
||||
get useVoice() {
|
||||
return this.#useVoice
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoiceActor(show) {
|
||||
this.#useVoiceActor = show
|
||||
document.getElementById('voice_actor_box').hidden = !show
|
||||
}
|
||||
|
||||
get useVoiceActor() {
|
||||
return this.#useVoiceActor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set subtitleLanguage(lang) {
|
||||
if (this.#getSubtitleLanguages().includes(lang)) {
|
||||
this.#subtitleLang = lang
|
||||
} else {
|
||||
this.#subtitleLang = this.#defaultRegion
|
||||
}
|
||||
}
|
||||
|
||||
get subtitleLanguage() {
|
||||
return this.#subtitleLang
|
||||
}
|
||||
|
||||
get subtitleLanguages() {
|
||||
return this.#getSubtitleLanguages()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} x
|
||||
*/
|
||||
set subtitleX(x) {
|
||||
this.#subtitleX = x
|
||||
this.#updateSubtitlePosition()
|
||||
}
|
||||
|
||||
get subtitleX() {
|
||||
return this.#subtitleX
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} y
|
||||
*/
|
||||
set subtitleY(y) {
|
||||
this.#subtitleY = y - 100
|
||||
this.#updateSubtitlePosition()
|
||||
}
|
||||
|
||||
get subtitleY() {
|
||||
return this.#subtitleY + 100
|
||||
}
|
||||
|
||||
#updateSubtitlePosition() {
|
||||
window.settings.elementPosition(this.#el, this.#subtitleX, this.#subtitleY)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set language(lang) {
|
||||
if (this.#voiceLanguages.includes(lang)) {
|
||||
this.#voiceLang = lang
|
||||
} else {
|
||||
this.#voiceLang = this.#defaultVoiceLang
|
||||
}
|
||||
const availableSubtitleLang = this.#getSubtitleLanguages()
|
||||
if (!availableSubtitleLang.includes(this.#subtitleLang)) {
|
||||
this.#subtitleLang = availableSubtitleLang[0]
|
||||
}
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this.#voiceLang
|
||||
}
|
||||
|
||||
get languages() {
|
||||
return this.#voiceLanguages
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set idleDuration(duration) {
|
||||
clearInterval(this.#idleListener)
|
||||
if (duration !== 0) {
|
||||
this.#idleDuration = duration * 60 * 1000
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
}
|
||||
|
||||
get idleDuration() {
|
||||
return this.#idleDuration / 60 / 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set nextDuration(duration) {
|
||||
clearInterval(this.#nextListener)
|
||||
if (duration !== 0) {
|
||||
this.#nextDuration = duration * 60 * 1000
|
||||
this.#initNextVoiceTimer()
|
||||
}
|
||||
}
|
||||
|
||||
get nextDuration() {
|
||||
return this.#nextDuration / 60 / 1000
|
||||
}
|
||||
|
||||
#initIdleVoiceTimer() {
|
||||
this.#idleListener = setInterval(() => {
|
||||
this.#playSpecialVoice("闲置")
|
||||
clearInterval(this.#idleListener)
|
||||
this.#idleListener = -1
|
||||
}, this.#idleDuration)
|
||||
}
|
||||
|
||||
#initNextVoiceTimer() {
|
||||
this.#nextListener = setInterval(() => {
|
||||
if (!this.#lastClickToNext) {
|
||||
this.#nextVoice()
|
||||
}
|
||||
}, this.#nextDuration)
|
||||
}
|
||||
|
||||
#nextVoice() {
|
||||
const voiceId = () => {
|
||||
const id = this.#voiceList[Math.floor((Math.random() * this.#voiceList.length))]
|
||||
return id === this.#lastVoiceId ? voiceId() : id
|
||||
}
|
||||
this.#playVoice(voiceId())
|
||||
}
|
||||
|
||||
#playEntryVoice() {
|
||||
this.#playSpecialVoice("问候")
|
||||
}
|
||||
|
||||
#setCurrentSubtitle(id) {
|
||||
if (id === null) {
|
||||
setTimeout(() => {
|
||||
if (this.#isPlaying) return
|
||||
this.#el.style.opacity = 1
|
||||
}, 5 * 1000);
|
||||
return
|
||||
}
|
||||
const subtitle = this.#getSubtitleById(id)
|
||||
const title = subtitle.title
|
||||
const content = subtitle.text
|
||||
const cvInfo = this.#getCVInfoByVoiceLang()[this.#voiceLang][this.subtitleLanguage]
|
||||
document.getElementById('voice_title').innerText = title
|
||||
document.getElementById('voice_subtitle').innerText = content
|
||||
this.#el.style.opacity = 1
|
||||
document.getElementById('voice_actor_name').innerText = cvInfo.join('')
|
||||
}
|
||||
|
||||
#playVoice(id) {
|
||||
if (!this.useVoice) return
|
||||
this.#lastVoiceId = this.#currentVoiceId
|
||||
this.#currentVoiceId = id
|
||||
this.#audioEl.src = `./assets/${this.#getVoiceFoler()
|
||||
}/${id}.wav`
|
||||
let startPlayPromise = this.#audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
this.#isPlaying = true
|
||||
const audioEndedFunc = () => {
|
||||
this.#isPlaying = false
|
||||
this.#audioEl.removeEventListener('ended', audioEndedFunc)
|
||||
if (this.#currentVoiceId !== id) return
|
||||
this.#setCurrentSubtitle(null)
|
||||
this.#lastClickToNext = false
|
||||
}
|
||||
|
||||
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
||||
this.#setCurrentSubtitle(id)
|
||||
})
|
||||
document.addEventListener('mousemove', e => {
|
||||
if (this.#idleListener === -1) {
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useSubtitle(show) {
|
||||
this.#useSubtitle = show
|
||||
#playSpecialVoice(matcher) {
|
||||
const voiceId = this.#getSpecialVoiceId(matcher)
|
||||
this.#playVoice(voiceId)
|
||||
}
|
||||
|
||||
#getVoiceFoler() {
|
||||
const folderObject = this.#voiceFolderObject
|
||||
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
||||
}
|
||||
|
||||
#getSpecialVoiceId(matcher) {
|
||||
const voices = this.#getVoices()
|
||||
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
||||
return voiceId
|
||||
}
|
||||
|
||||
#getVoices() {
|
||||
return charword_table.operator.voice[this.#defaultRegion][this.#getWordKeyByVoiceLang()[this.#defaultVoiceLang]]
|
||||
}
|
||||
|
||||
#getSubtitleById(id) {
|
||||
return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id]
|
||||
}
|
||||
|
||||
#getVoiceFolderObject() {
|
||||
const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
|
||||
const languagesCopy = this.#voiceLanguages.slice()
|
||||
const customVoiceName = languagesCopy.filter(i => !folderObject.sub.map(e => e.lang).includes(i))[0]
|
||||
folderObject.sub = folderObject.sub.map(e => {
|
||||
return {
|
||||
name: e.name,
|
||||
lang: e.lang === "CUSTOM" ? customVoiceName : e.lang
|
||||
}
|
||||
})
|
||||
return folderObject
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the cvInfo in the region's language
|
||||
*/
|
||||
#getCVInfo(region) {
|
||||
const infoArray = Object.values(charword_table.operator.info[region])
|
||||
// combine the infoArray
|
||||
let output = {}
|
||||
for (const info of infoArray) {
|
||||
output = {
|
||||
...output,
|
||||
...info
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoice(show) {
|
||||
this.#useVoice = show
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} show
|
||||
*/
|
||||
set useVoiceActor(show) {
|
||||
this.#useVoiceActor = show
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set subtitleLanguage(lang) {
|
||||
if (this.#getSubtitleLanguages().includes(lang)) {
|
||||
this.#subtitleLang = lang
|
||||
} else {
|
||||
this.#subtitleLang = this.#defaultRegion
|
||||
/**
|
||||
* @returns the cvInfo corresponsing to the voice language
|
||||
*/
|
||||
#getCVInfoByVoiceLang() {
|
||||
const languages = {}
|
||||
for (const lang of Object.keys(charword_table.operator.info)) {
|
||||
const cvInfo = this.#getCVInfo(lang)
|
||||
for (const [voiceLanguage, cvArray] of Object.entries(cvInfo)) {
|
||||
if (languages[voiceLanguage] === undefined) {
|
||||
languages[voiceLanguage] = {}
|
||||
}
|
||||
languages[voiceLanguage][lang] = cvArray
|
||||
}
|
||||
}
|
||||
return languages
|
||||
}
|
||||
|
||||
get subtitleLanguage() {
|
||||
return this.#subtitleLang
|
||||
#getWordKeyByVoiceLang() {
|
||||
const output = {}
|
||||
for (const [wordKey, wordKeyDict] of Object.entries(charword_table.operator.info[this.#defaultRegion])) {
|
||||
for (const lang of Object.keys(wordKeyDict)) {
|
||||
output[lang] = wordKey
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
get subtitleLanguages() {
|
||||
return this.#getSubtitleLanguages()
|
||||
}
|
||||
#getSubtitleLanguages() {
|
||||
return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} lang
|
||||
*/
|
||||
set language(lang) {
|
||||
if (this.#voiceLanguages.includes(lang)) {
|
||||
this.#voiceLang = lang
|
||||
} else {
|
||||
this.#voiceLang = this.#defaultVoiceLang
|
||||
}
|
||||
const availableSubtitleLang = this.#getSubtitleLanguages()
|
||||
if (!availableSubtitleLang.includes(this.#subtitleLang)) {
|
||||
this.#subtitleLang = availableSubtitleLang[0]
|
||||
}
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this.#voiceLang
|
||||
}
|
||||
|
||||
get languages() {
|
||||
return this.#voiceLanguages
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set idleDuration(duration) {
|
||||
clearInterval(this.#idleListener)
|
||||
if (duration !== 0) {
|
||||
this.#idleDuration = duration * 60 * 1000
|
||||
this.#initIdleVoiceTimer()
|
||||
}
|
||||
}
|
||||
|
||||
get idleDuration() {
|
||||
return this.#idleDuration / 60 / 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {int} duration
|
||||
*/
|
||||
set nextDuration(duration) {
|
||||
clearInterval(this.#nextListener)
|
||||
if (duration !== 0) {
|
||||
this.#nextDuration = duration * 1000
|
||||
this.#initNextVoiceTimer()
|
||||
}
|
||||
}
|
||||
|
||||
get nextDuration() {
|
||||
return this.#nextDuration / 60 / 1000
|
||||
}
|
||||
|
||||
#initIdleVoiceTimer() {
|
||||
this.#idleListener = setInterval(() => {
|
||||
this.#playSpecialVoice("闲置")
|
||||
clearInterval(this.#idleListener)
|
||||
this.#idleListener = -1
|
||||
}, this.#idleDuration)
|
||||
}
|
||||
|
||||
#initNextVoiceTimer() {
|
||||
this.#nextListener = setInterval(() => {
|
||||
if (!this.#lastClickToNext) {
|
||||
this.#nextVoice()
|
||||
}
|
||||
}, this.#nextDuration)
|
||||
}
|
||||
|
||||
#nextVoice() {
|
||||
this.#playVoice("CN_001")
|
||||
}
|
||||
|
||||
#playEntryVoice() {
|
||||
this.#playSpecialVoice("问候")
|
||||
}
|
||||
|
||||
#setCurrentSubtitle(id) {
|
||||
console.log(id, this.#getSubtitleById(id))
|
||||
}
|
||||
|
||||
#playVoice(id) {
|
||||
this.#currentVoiceId = id
|
||||
this.#audioEl.src = `./assets/${this.#getVoiceFoler()
|
||||
}/${id}.wav`
|
||||
let startPlayPromise = this.#audioEl.play()
|
||||
if (startPlayPromise !== undefined) {
|
||||
startPlayPromise
|
||||
.then(() => {
|
||||
const audioEndedFunc = () => {
|
||||
this.#lastVoiceId = this.#currentVoiceId
|
||||
this.#currentVoiceId = null
|
||||
this.#setCurrentSubtitle(null)
|
||||
this.#lastClickToNext = false
|
||||
this.#audioEl.removeEventListener('ended', audioEndedFunc)
|
||||
}
|
||||
this.#audioEl.addEventListener('ended', audioEndedFunc)
|
||||
this.#setCurrentSubtitle(id)
|
||||
})
|
||||
.catch(() => {
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#playSpecialVoice(matcher) {
|
||||
const voiceId = this.#getSpecialVoiceId(matcher)
|
||||
this.#playVoice(voiceId)
|
||||
}
|
||||
|
||||
#getVoiceFoler() {
|
||||
const folderObject = this.#voiceFolderObject
|
||||
return `${folderObject.main}/${folderObject.sub.find(e => e.lang === this.#voiceLang).name}`
|
||||
}
|
||||
|
||||
#getSpecialVoiceId(matcher) {
|
||||
const voices = this.#getVoices()
|
||||
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
|
||||
return voiceId
|
||||
}
|
||||
|
||||
#getVoices() {
|
||||
return charword_table.operator.voice[this.#defaultRegion][this.#getWordKeyByVoiceLang()[this.#defaultVoiceLang]]
|
||||
}
|
||||
|
||||
#getSubtitleById(id) {
|
||||
return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id]
|
||||
}
|
||||
|
||||
#getVoiceFolderObject() {
|
||||
const folderObject = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
|
||||
const languagesCopy = this.#voiceLanguages.slice()
|
||||
const customVoiceName = languagesCopy.filter(i => !folderObject.sub.map(e => e.lang).includes(i))[0]
|
||||
folderObject.sub = folderObject.sub.map(e => {
|
||||
return {
|
||||
name: e.name,
|
||||
lang: e.lang === "CUSTOM" ? customVoiceName : e.lang
|
||||
}
|
||||
})
|
||||
return folderObject
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the cvInfo in the region's language
|
||||
*/
|
||||
#getCVInfo(region) {
|
||||
const infoArray = Object.values(charword_table.operator.info[region])
|
||||
// combine the infoArray
|
||||
let output = {}
|
||||
for (const info of infoArray) {
|
||||
output = {
|
||||
...output,
|
||||
...info
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the cvInfo corresponsing to the voice language
|
||||
*/
|
||||
#getCVInfoByVoiceLang() {
|
||||
const languages = {}
|
||||
for (const lang of Object.keys(charword_table.operator.info)) {
|
||||
const cvInfo = this.#getCVInfo(lang)
|
||||
for (const [voiceLanguage, cvArray] of Object.entries(cvInfo)) {
|
||||
if (languages[voiceLanguage] === undefined) {
|
||||
languages[voiceLanguage] = {}
|
||||
}
|
||||
languages[voiceLanguage][lang] = cvArray
|
||||
}
|
||||
}
|
||||
return languages
|
||||
}
|
||||
|
||||
#getWordKeyByVoiceLang() {
|
||||
const output = {}
|
||||
for (const [wordKey, wordKeyDict] of Object.entries(charword_table.operator.info[this.#defaultRegion])) {
|
||||
for (const lang of Object.keys(wordKeyDict)) {
|
||||
output[lang] = wordKey
|
||||
}
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
#getSubtitleLanguages() {
|
||||
return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
|
||||
}
|
||||
|
||||
#insertHTML() {
|
||||
this.#el.innerHTML = `
|
||||
<audio id="${this.#audioElId}" autoplay>
|
||||
<source type="audio/wav">
|
||||
Your browser does not support the audio element.
|
||||
</audio>
|
||||
`
|
||||
}
|
||||
#insertHTML() {
|
||||
this.#el.innerHTML = `
|
||||
<audio id="${this.#audioElId}" autoplay>
|
||||
<source type="audio/wav">
|
||||
</audio>
|
||||
<div class="voice-wrapper" id="voice_wrapper">
|
||||
<div class="voice-title" id="voice_title"></div>
|
||||
<div class="voice-subtitle">
|
||||
<div id="voice_subtitle"></div>
|
||||
<div class="voice-triangle"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="voice-actor" id="voice_actor_box" hidden>
|
||||
<span class="voice-actor-icon"></span>
|
||||
<span id="voice_actor_name" class="voice-actor-name"></span>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import Voice from '@/components/voice'
|
||||
document.querySelector('#app').innerHTML = `
|
||||
<img src="./assets/${import.meta.env.VITE_LOGO_FILENAME}.png" class="logo invert-filter" id="logo" alt="operator logo" />
|
||||
<div id="settings"></div>
|
||||
<div id="voice" hidden></div>
|
||||
<div id="voice_box" hidden></div>
|
||||
<div id="widget-wrapper">
|
||||
<div id="fallback"
|
||||
style="background-image: url(./assets/${import.meta.env.VITE_FALLBACK_FILENAME}.png)"
|
||||
@@ -16,7 +16,7 @@ document.querySelector('#app').innerHTML = `
|
||||
<div id="player" hidden></div>
|
||||
</div>
|
||||
`
|
||||
window.voice = new Voice(document.querySelector('#voice'), document.querySelector('#widget-wrapper'))
|
||||
window.voice = new Voice(document.querySelector('#voice_box'), document.querySelector('#widget-wrapper'))
|
||||
window.voice.init()
|
||||
window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo'))
|
||||
document.title = import.meta.env.VITE_TITLE
|
||||
|
||||
Reference in New Issue
Block a user