feat: code cleanup

This commit is contained in:
Haoyu Xu
2025-01-09 16:33:34 +08:00
parent b6d3e08eaf
commit 3c10956bae
10 changed files with 820 additions and 793 deletions

View File

@@ -47,28 +47,11 @@ To generate the latest charword_table.json
```
### Webpage & JavaScript
Add query string `settings` to bring up the settings panel to adjust your settings. Then use appropriate JavaScript code to load your settings
Add query string `aklive2d` to bring up the settings panel to adjust your settings.
Settings can be adjusted under `window.aklive2d` or by dispatching custom events (under `window.aklive2d.events`) to `document`.
Examples can be found at `showcase/src/libs/wallpaper_engine.js`.
``` javascript
settings.setFPS(integer) // set FPS
settings.setLogoDisplay(boolean) // display logo or not
settings.setLogoRatio(float) // the ratio of the logo
settings.setLogoOpacity(float) // the opacity of the logo
settings.setLogo(url) // change the logo, url: image url, removeInvert: boolean
settings.resetLogoImage() // reset to the default logo
settings.setDefaultBackground(url) // change the default background, url: image filename from `background` folder
settings.setBackgoundImage(url) // change the background, url: image url
settings.resetBackground() // reset to the default background
settings.positionPadding("left", integer) // left padding
settings.positionPadding("right", integer) // right padding
settings.positionPadding("top", integer) // top padding
settings.positionPadding("bottom", integer) // bottom padding
settings.positionReset() // reset the position
settings.open() // open settings panel
settings.close() // close settings panel
settings.reset() // reset settings
```
Using JS events to change settings is recommended.
## Config
### General Config

View File

@@ -5,6 +5,7 @@ import Player from "@/components/player";
import Background from "@/components/background";
import Logo from "@/components/logo";
import Insight from "@/components/insight";
import * as Event from "@/components/event";
import {
isWebGLSupported,
insertHTMLChild,
@@ -17,11 +18,11 @@ export default class AKLive2D {
#el = document.createElement("div")
#appEl
#queries = new URLSearchParams(window.location.search)
#voice = new Voice()
#music = new Music()
#player = new Player()
#background = new Background()
#logo = new Logo()
#voice
#music
#player
#background
#logo
#insight = new Insight()
constructor(appEl) {
@@ -32,18 +33,16 @@ export default class AKLive2D {
document.addEventListener("gesturestart", e => e.preventDefault());
this.#appEl = appEl
}
init() {
this.#logo.init(this.#appEl);
this.#background.init(this.#appEl);
this.#voice.init(this.#appEl);
this.#music.init(this.#appEl);
this.#logo = new Logo(this.#appEl)
this.#background = new Background(this.#appEl)
this.#voice = new Voice(this.#appEl)
this.#music = new Music(this.#appEl)
if (isWebGLSupported()) {
this.#player.init(this.#appEl);
this.#player = new Player(this.#appEl)
} else {
(new Fallback()).init(this.#appEl)
new Fallback(this.#appEl)
}
this.#el.id = "settings-box"
this.#el.hidden = true
this.#el.innerHTML = `
@@ -63,15 +62,8 @@ export default class AKLive2D {
insertHTMLChild(this.#appEl, this.#el)
addEventListeners([
{
event: "player-ready", handler: () => this.success()
},
...this.#logo.listeners,
...this.#background.listeners,
...this.#player.listeners,
...this.#voice.listeners,
...this.#music.listeners,
...this.#insight.listeners,
{
event: "player-ready", handler: () => this.#success()
}, {
id: "settings-reset", event: "click", handler: () => this.reset()
}, {
id: "settings-close", event: "click", handler: () => this.close()
@@ -79,10 +71,54 @@ export default class AKLive2D {
id: "settings-to-directory", event: "click", handler: () => {
window.location.href = '/';
}
}
},
...this.#logo.listeners,
...this.#background.listeners,
...this.#player.listeners,
...this.#voice.listeners,
...this.#music.listeners,
...this.#insight.listeners,
])
}
get voice() {
return this.#voice
}
get music() {
return this.#music
}
get player() {
return this.#player
}
get background() {
return this.#background
}
get logo() {
return this.#logo
}
get events() {
return Event
}
get config() {
return {
player: this.#player.config,
background: this.#background.config,
logo: this.#logo.config,
music: this.#music.config,
voice: this.#voice.config
}
}
get configStr() {
return JSON.stringify(this.config, null)
}
open() {
this.#el.hidden = false;
}
@@ -96,59 +132,36 @@ export default class AKLive2D {
this.#background.reset()
this.#logo.reset()
this.#voice.reset()
this.#music.reset()
}
success() {
#success() {
this.#music.link(this.#background)
this.#background.link(this.#music)
this.#voice.link(this.#player)
this.#player.success()
this.#voice.success()
this.#music.success()
this.#insight.success()
if (this.#queries.has("settings") || this.#queries.has("aklive2d") || import.meta.env.MODE === 'development') {
if (this.#queries.has("aklive2d") || import.meta.env.MODE === 'development') {
this.open()
}
this.#backCompatibility()
this.#registerBackCompatibilityFns()
}
#backCompatibility() {
window.voice = this.#voice
window.music = this.#music
#registerBackCompatibilityFns() {
const _this = this
window.voice = _this.#voice
window.music = _this.#music
window.settings = {
spinePlayer: this.#player.spine,
setFPS: this.#player.setFPS,
setLogoDisplay: this.#logo.setLogoDisplay,
setLogo: this.#logo.setLogo,
setLogoImage: this.#logo.setLogoImage,
resetLogoImage: this.#logo.resetLogoImage,
setLogoRatio: this.#logo.setLogoRatio,
setLogoOpacity: this.#logo.setLogoOpacity,
setBackgoundImage: this.#background.setBackgroundImage,
currentBackground: this.#background.currentBackground,
setDefaultBackground: this.#background.setDefaultBackground,
setBackground: this.#background.setBackground,
resetBackground: this.#background.resetBackground,
loadViewport: this.#player.loadViewport,
setScale: this.#player.setScale,
scale: this.#player.scale,
positionPadding: this.#player.positionPadding,
positionReset: this.#player.positionReset,
scaleReset: this.#player.scaleReset,
elementPosition: updateElementPosition,
logoPadding: this.#logo.logoPadding,
logoReset: this.#logo.logoReset,
useStartAnimation: this.#player.useStartAnimation,
open: this.open,
close: this.close,
reset: this.reset,
setMusicFromWE: this.#music.setMusicFromWE,
setMusic: this.#music.setMusic,
resetMusic: this.#music.resetMusic,
setVideo: this.#background.setVideo,
setVideoVolume: this.#background.setVideoVolume,
getVideoVolume: this.#background.getVideoVolume,
setVideoFromWE: this.#background.setVideoFromWE,
resetVideo: this.#background.resetVideo
open: _this.open,
close: _this.close,
reset: _this.reset,
..._this.#player.backCompatibilityFns,
..._this.#logo.backCompatibilityFns,
..._this.#music.backCompatibilityFns,
..._this.#background.backCompatibilityFns
}
}
}

View File

@@ -16,11 +16,16 @@ export default class Background {
image: "operator_bg.png"
}
#config = {
image: null
video: {
name: null,
volume: 100,
},
useVideo: false,
name: null
}
#musicObj
init(el) {
constructor(el) {
this.#parentEl = el
this.#el.id = "background-box"
this.image = this.#default.location + this.#default.image
@@ -31,119 +36,20 @@ export default class Background {
this.#videoEl = document.getElementById("video-src")
}
set image(v) {
this.#el.style.backgroundImage = `url("${v}")`
}
set video(v) {
const update = (url) => {
this.#videoEl.src = url
this.#videoEl.load()
document.getElementById("custom-video-background-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL)
)
} else {
update(v)
}
}
get volume() {
return this.#videoEl.volume * 100
}
set volume(v) {
this.#videoEl.volume = parseInt(v) / 100
}
get current() {
return this.#config.image || this.#default.image
}
set default(v) {
this.#default.image = v
if (!this.#config.image) {
this.image = this.#default.location + this.#default.image
}
}
set custom(v) {
const update = (url) => {
this.#config.image = v
this.image = url
document.getElementById("custom-background-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL)
)
} else {
update(v)
}
}
setVideoFromWE(url) {
// Note: Back Compatibility
this.video = url
}
get currentBackground() {
// Note: Back Compatibility
return this.current
}
resetImage() {
document.getElementById("custom-background").value = ""
document.getElementById("custom-background-clear").disabled = true
this.#config.image = null
this.#config.name = null
this.image = this.#default.location + this.#default.image
}
resetVideo() {
this.#config.video.name = null
this.#videoEl.src = ""
document.getElementById("custom-video-background").value = ""
document.getElementById("custom-video-background-clear").disabled = true
}
setBackgroundImage(v) {
// Note: Back Compatibility
this.image = v
}
setDefaultBackground(v) {
// Note: Back Compatibility
this.default = v
}
setBackground(v) {
// Note: Back Compatibility
this.custom = v
}
resetBackground() {
// Note: Back Compatibility
this.resetImage()
}
setVideo(e) {
// Note: Back Compatibility
this.video = e.target.files[0]
}
getVideoVolume() {
// Note: Back Compatibility
return this.volume
}
setVideoVolume(v) {
// Note: Back Compatibility
this.volume = v
}
reset() {
this.resetImage()
this.resetVideo()
@@ -153,6 +59,100 @@ export default class Background {
this.#musicObj = musicObj
}
get useVideo() {
return this.#config.useVideo
}
set useVideo(v) {
this.#config.useVideo = v
}
set image(v) {
this.#el.style.backgroundImage = `url("${v}")`
}
set video(v) {
const update = (url, v = null) => {
this.#config.video.name = {
isLocalFile: v !== null,
value: v ? v.name : url
}
this.#videoEl.src = url
this.#videoEl.load()
document.getElementById("custom-video-background-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL, v)
)
} else {
update(v)
}
}
get volume() {
return this.#config.video.volume
}
set volume(v) {
v = parseInt(v)
this.#config.video.volume = v
this.#videoEl.volume = v / 100
}
get current() {
return this.#config.name || this.#default.image
}
set default(v) {
this.#default.image = v
this.#musicObj.music = v
this.image = this.#default.location + this.#default.image
}
set custom(v) {
const update = (url, v = null) => {
this.#config.name = {
isLocalFile: v !== null,
value: v ? v.name : url
}
this.image = url
document.getElementById("custom-background-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL, v)
)
} else {
update(v)
}
}
get config() {
return {
default: this.#default.image,
...this.#config
}
}
get backCompatibilityFns() {
const _this = this
return {
currentBackground: _this.current,
setBackgoundImage: (v) => _this.image = v,
setDefaultBackground: (v) => _this.default = v,
setBackground: (v) => _this.custom = v,
resetBackground: _this.resetImage,
setVideo: (e) => _this.video = e.target.files[0],
getVideoVolume: () => _this.volume,
setVideoVolume: (v) => _this.volume = v,
setVideoFromWE: (url) => _this.video = url,
resetVideo: _this.resetVideo
}
}
get HTML() {
return `
<div>
@@ -163,19 +163,29 @@ export default class Background {
</select>
</div>
<div>
<label for="custom-background"> Custom Background (Store Locally)</label>
<label for="custom-background">Custom Background (Store Locally)</label>
<input type="file" id="custom-background" accept="image/*"/>
<button type="button" disabled id="custom-background-clear" disabled>Clear</button>
<button type="button" id="custom-background-clear" ${this.#config.name ? this.#config.name.isLocalFile ? "" : "disabled" : "disabled"}>Clear</button>
</div>
<div>
<label for="custom-background-url">Custom Background URL:</label>
<input type="text" id="custom-background-url" name="custom-background-url" value="${this.#config.name ? this.#config.name.value : ""}">
<button type="button" id="custom-background-url-apply">Apply</button>
</div>
</div>
<div>
<label for="video">Video</label>
<input type="checkbox" id="video" name="video" />
<div id="video-realted" hidden>
<input type="checkbox" id="video" name="video" ${this.useVideo ? "checked" : ""}/>
<div id="video-realted" ${this.useVideo ? "" : "hidden"}>
<div>
<label for="custom-video-background"> Custom Video Background (Store Locally)</label>
<label for="custom-video-background">Custom Video Background (Store Locally)</label>
<input type="file" id="custom-video-background" accept="video/*"/>
<button type="button" disabled id="custom-video-background-clear" disabled>Clear</button>
<button type="button" id="custom-video-background-clear" ${this.#config.video.name ? this.#config.video.name.isLocalFile ? "" : "disabled" : "disabled"}>Clear</button>
</div>
<div>
<label for="custom-video-background-url">Custom Video Background URL:</label>
<input type="text" id="custom-video-background-url" name="custom-video-background-url" value="${this.#config.video.name ? this.#config.video.name.value : ""}">
<button type="button" id="custom-video-background-url-apply">Apply</button>
</div>
<div>
<label for="video-volume">Video Volume</label>
@@ -204,21 +214,25 @@ export default class Background {
}, {
id: "default-background-select", event: "change", handler: e => {
this.default = e.currentTarget.value
this.#musicObj.music = e.currentTarget.value
}
}, {
id: "custom-background", event: "change", handler: e => this.custom = e.target.files[0]
}, {
id: "custom-background-clear", event: "click", handler: () => this.resetImage()
}, {
id: "custom-background-url-apply", event: "click", handler: () => this.custom = document.getElementById("custom-background-url").value
}, {
id: "video", event: "click", handler: e => {
showRelatedHTML(e.currentTarget, "video-realted");
this.useVideo = e.currentTarget.checked
if (!e.currentTarget.checked) this.resetVideo()
}
}, {
id: "custom-video-background", event: "change", handler: e => this.video = e.target.files[0]
}, {
id: "custom-video-background-clear", event: "click", handler: () => this.resetVideo()
}, {
id: "custom-video-background-url-apply", event: "click", handler: () => this.video = document.getElementById("custom-video-background-url").value
}, {
id: "video-volume-slider", event: "input", handler: e => {
syncHTMLValue(e.currentTarget, "video-volume-input");

View File

@@ -4,7 +4,7 @@ import '@/components/fallback.css'
export default class Fallback {
#el = document.createElement("div")
init(parentEl) {
constructor(parentEl) {
alert('WebGL is unavailable. Fallback image will be used.');
const calculateScale = (width, height) => {
return { x: window.innerWidth / width, y: window.innerHeight / height };

View File

@@ -42,10 +42,10 @@ export const updateHTMLOptions = (array, id = null) => {
export const addEventListeners = (listeners) => {
listeners.forEach(listener => {
if (typeof listener.id === "undefined") {
document.addEventListener(listener.event, e => listener.handler(e))
} else {
if (listener.id) {
document.getElementById(listener.id).addEventListener(listener.event, e => listener.handler(e))
} else {
document.addEventListener(listener.event, e => listener.handler(e))
}
})
}

View File

@@ -27,10 +27,11 @@ export default class Logo {
hidden: this.#default.hidden,
ratio: this.#default.ratio,
opacity: this.#default.opacity,
position: {...this.#default.position}
position: { ...this.#default.position },
name: null
}
init(el) {
constructor(el) {
this.#parentEl = el
this.#el.id = "logo-box"
this.#el.innerHTML = `
@@ -43,34 +44,48 @@ export default class Logo {
this.#updateSizeOnWindowResize()
}
#updateSizeOnWindowResize() {
const _this = this
const resize = () => {
_this.#resize(_this)
}
window.addEventListener("resize", resize, true);
resize()
}
setImage(src, invertFilter = false) {
this.#imageEl.src = src
this.#resize()
this.#setInvertFilter(invertFilter)
}
set image(v) {
const update = (url) => {
this.setImage(url, false)
document.getElementById("logo-image-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL)
)
} else {
update(v)
}
resetPosition() {
this.position = {...this.#default.position}
document.getElementById("logo-position-x-slider").value = this.#default.position.x
document.getElementById("logo-position-x-input").value = this.#default.position.x
document.getElementById("logo-position-y-slider").value = this.#default.position.y
document.getElementById("logo-position-y-input").value = this.#default.position.y
}
resetImage() {
this.#config.name = null
this.setImage(this.#default.location + this.#default.image, this.#default.useInvertFilter)
document.getElementById("logo-image-clear").disabled = true
}
resetOpacity() {
this.opacity = this.#default.opacity
document.getElementById("logo-opacity-slider").value = this.#default.opacity
document.getElementById("logo-opacity-input").value = this.#default.opacity
}
resetHidden() {
this.hidden = this.#default.hidden
}
resetRatio() {
this.ratio = this.#default.ratio
document.getElementById("logo-ratio-slider").value = this.#default.ratio
document.getElementById("logo-ratio-input").value = this.#default.ratio
}
reset() {
this.resetPosition()
this.resetImage()
this.resetRatio()
this.resetOpacity()
this.resetHidden()
}
#resize(_this, value) {
@@ -87,6 +102,38 @@ export default class Logo {
}
}
#updateLogoPosition() {
updateElementPosition(this.#imageEl, this.#config.position)
}
#updateSizeOnWindowResize() {
const _this = this
const resize = () => {
_this.#resize(_this)
}
window.addEventListener("resize", resize, true);
resize()
}
set image(v) {
const update = (url, v = null) => {
this.#config.name = {
isLocalFile: v !== null,
value: v ? v.name : url
}
this.setImage(url, false)
document.getElementById("logo-image-clear").disabled = false
}
if (typeof v === "object") {
readFile(
v,
(blobURL) => update(blobURL, v)
)
} else {
update(v)
}
}
get hidden() {
return this.#config.hidden
}
@@ -140,115 +187,61 @@ export default class Logo {
set position(v) {
if (typeof v !== "object") return;
if (typeof v.x !== "undefined") this.#config.position.x = v.x;
if (typeof v.y !== "undefined") this.#config.position.y = v.y;
if (v.x) this.#config.position.x = v.x;
if (v.y) this.#config.position.y = v.y;
this.#updateLogoPosition()
}
#updateLogoPosition() {
updateElementPosition(this.#imageEl, this.#config.position)
}
logoPadding(key, value) {
// Note: Back Compatibility
switch (key) {
case "x":
this.position = {
x: value
get backCompatibilityFns() {
const _this = this
return {
setLogoDisplay: (v) => _this.hidden = v,
setLogo: _this.setImage,
setLogoImage: (e) => _this.image = e.target.files[0],
resetLogoImage: _this.resetImage,
setLogoRatio: (v) => _this.ratio = v,
setLogoOpacity: (v) => _this.opacity = v,
logoPadding: (key, value) => {
switch (key) {
case "x":
this.position = {
x: value
}
break;
case "y":
this.position = {
y: value
}
break;
default:
this.position = value
break;
}
break;
case "y":
this.position = {
y: value
}
break;
default:
this.position = value
break;
},
logoReset: _this.resetPosition
}
}
setLogoOpacity(v) {
// Note: Back Compatibility
this.opacity = v
}
setLogoRatio(value) {
// Note: Back Compatibility
this.ratio = value
}
setLogoDisplay(flag) {
// Note: Back Compatibility
this.hidden = flag;
}
setLogo(src, invertFilter) {
// Note: Back Compatibility
this.setImage(src, invertFilter)
}
setLogoImage(e) {
// Note: Back Compatibility
this.image = e.target.files[0]
}
resetPosition() {
this.position = {...this.#default.position}
document.getElementById("logo-position-x-slider").value = this.#default.position.x
document.getElementById("logo-position-x-input").value = this.#default.position.x
document.getElementById("logo-position-y-slider").value = this.#default.position.y
document.getElementById("logo-position-y-input").value = this.#default.position.y
}
logoReset() {
// Note: Back Compatibility
this.resetPosition()
}
resetImage() {
this.setImage(this.#default.location + this.#default.image, this.#default.useInvertFilter)
document.getElementById("logo-image-clear").disabled = true
}
resetLogoImage() {
// Note: Back Compatibility
this.resetImage()
}
resetOpacity() {
this.setLogoOpacity(this.#default.opacity)
document.getElementById("logo-opacity-slider").value = this.#default.opacity
document.getElementById("logo-opacity-input").value = this.#default.opacity
}
resetHidden() {
this.hidden = this.#default.hidden
}
resetRatio() {
this.ratio = this.#default.ratio
document.getElementById("logo-ratio-slider").value = this.#default.ratio
document.getElementById("logo-ratio-input").value = this.#default.ratio
}
reset() {
this.resetPosition()
this.resetImage()
this.resetRatio()
this.resetOpacity()
this.resetHidden()
get config() {
return {
...this.#config
}
}
get HTML() {
return `
<label for="operator-logo">Operator Logo</label>
<input type="checkbox" id="operator-logo" name="operator-logo" checked/>
<div id="operator-logo-realted">
<input type="checkbox" id="operator-logo" name="operator-logo" ${this.hidden ? "" : "checked"}/>
<div id="operator-logo-realted" ${this.hidden ? "hidden" : ""}>
<div>
<label for="logo-image">Logo Image (Store Locally)</label>
<input type="file" id="logo-image" accept="image/*"/>
<button type="button" id="logo-image-clear" disabled>Clear</button>
<button type="button" id="logo-image-clear" ${this.#config.name ? this.#config.name.isLocalFile ? "" : "disabled" : "disabled"}>Clear</button>
</div>
<div>
<label for="logo-image-url">Logo Image URL:</label>
<input type="text" id="logo-image-url" name="logo-image-url" value="${this.#config.name ? this.#config.name.value : ""}">
<button type="button" id="logo-image-url-apply">Apply</button>
</div>
<div>
<label for="logo-ratio">Logo Ratio</label>
@@ -297,6 +290,8 @@ export default class Logo {
id: "logo-image", event: "change", handler: e => this.image = e.target.files[0]
}, {
id: "logo-image-clear", event: "click", handler: () => this.resetImage()
}, {
id: "logo-image-url-apply", event: "click", handler: () => this.image = document.getElementById("logo-image-url").value
}, {
id: "logo-ratio-slider", event: "input", handler: e => {
syncHTMLValue(e.currentTarget, "logo-ratio-input");

View File

@@ -30,60 +30,12 @@ export default class Music {
#config = {
useMusic: false,
timeOffset: 0.3,
volume: 0.5
volume: 50,
name: null
}
#backgroundObj
get timeOffset() {
return this.#config.timeOffset
}
set timeOffset(value) {
this.#config.timeOffset = value
}
get volume() {
return this.#config.volume * 100
}
set volume(value) {
value = value / 100
this.#config.volume = value
this.#audio.intro.el.volume = value
this.#audio.loop.el.volume = value
}
get musics() {
return this.#music.list
}
get useMusic() {
return this.#config.useMusic
}
set useMusic(value) {
this.#config.useMusic = value
if (value) {
this.#playMusic()
} else {
this.#stopMusic()
}
}
get currentMusic() {
// Note: Back Compatibility
return this.music
}
get music() {
return this.#music.current
}
get isUsingCustom() {
return this.#music.isUsingCustom
}
init(el) {
constructor(el) {
this.#parentEl = el
this.#el.id = "music-box"
this.#el.innerHTML = `
@@ -98,13 +50,13 @@ export default class Music {
this.#music.list = Object.keys(this.#music.mapping)
this.#audio.intro.el = document.getElementById(this.#audio.intro.id)
this.#audio.loop.el = document.getElementById(this.#audio.loop.id)
this.#audio.intro.el.volume = this.#config.volume
this.#audio.loop.el.volume = this.#config.volume
this.#audio.intro.el.volume = this.#volume
this.#audio.loop.el.volume = this.#volume
this.#audio.intro.el.ontimeupdate = () => {
if (this.#audio.intro.el.currentTime >= this.#audio.intro.el.duration - this.#config.timeOffset) {
this.#audio.intro.el.pause()
this.#audio.loop.el.currentTime = 0
this.#audio.loop.el.volume = this.#config.volume
this.#audio.loop.el.volume = this.#volume
}
}
this.#audio.loop.el.ontimeupdate = () => {
@@ -123,62 +75,16 @@ export default class Music {
this.#backgroundObj = backgroundObj
}
changeMusic(name) {
// Note: Back Compatibility
this.music = name
}
set music(name) {
if (name !== null && name !== this.#music.current) {
this.#music.current = name
if (this.#config.useMusic && !this.#music.isUsingCustom) {
this.#audio.loop.el.pause()
this.#audio.intro.el.pause()
this.#playMusic()
}
getCurrentHTMLOptions("music-select", name)
}
}
set custom(url) {
const update = (url, type) => {
this.#setMusic(url, type)
document.getElementById("custom-music-clear").disabled = false
}
if (typeof url === "object") {
readFile(
url,
(blobURL, type) => update(blobURL, type)
)
} else {
update(url, url.split(".").pop())
}
}
setMusicFromWE(url) {
// Note: Back Compatibility
this.custom = url
}
setMusic(e) {
// Note: Back Compatibility
this.custom = e.target.files[0]
}
reset() {
document.getElementById("custom-music").value = ""
document.getElementById("custom-music-clear").disabled = true
this.#music.isUsingCustom = false
this.#config.name = null
if (this.#config.useMusic) {
this.#playMusic()
}
}
resetMusic() {
// Note: Back Compatibility
this.reset()
}
#setMusic(data, type) {
this.#audio.loop.el.src = data
this.#audio.loop.el.querySelector('source').type = type
@@ -200,12 +106,12 @@ export default class Music {
this.#audio.loop.el.volume = 0
this.#audio.loop.el.play()
} else {
this.#audio.loop.el.volume = this.#config.volume
this.#audio.loop.el.volume = this.#volume
this.#audio.loop.el.play()
}
} else {
this.#audio.intro.el.pause()
this.#audio.loop.el.volume = this.#config.volume
this.#audio.loop.el.volume = this.#volume
this.#audio.loop.el.play()
}
}
@@ -215,12 +121,116 @@ export default class Music {
this.#audio.loop.el.pause()
}
get timeOffset() {
return this.#config.timeOffset
}
set timeOffset(value) {
this.#config.timeOffset = value
}
get volume() {
return this.#config.volume
}
get #volume() {
return this.#config.volume / 100
}
set volume(value) {
this.#config.volume = value
this.#audio.intro.el.volume = this.#volume
if (this.#audio.intro.el.paused) this.#audio.loop.el.volume = this.#volume
}
get musics() {
return this.#music.list
}
get useMusic() {
return this.#config.useMusic
}
set useMusic(value) {
this.#config.useMusic = value
if (value) {
this.#playMusic()
} else {
this.#stopMusic()
}
}
get music() {
return this.#music.current
}
get isUsingCustom() {
return this.#music.isUsingCustom
}
set music(name) {
if (name !== null && name !== this.#music.current) {
this.#music.current = name
if (this.#config.useMusic && !this.#music.isUsingCustom) {
this.#audio.loop.el.pause()
this.#audio.intro.el.pause()
this.#playMusic()
}
getCurrentHTMLOptions("music-select", name)
}
}
set custom(url) {
const update = (url, type, v = null) => {
this.#config.name = {
isLocalFile: v !== null,
value: v ? v.name : url
}
this.#setMusic(url, type)
document.getElementById("custom-music-clear").disabled = false
}
if (typeof url === "object") {
readFile(
url,
(blobURL, type) => update(blobURL, type, url)
)
} else {
update(url, url.split(".").pop())
}
}
get currentMusic() {
// Note: Back Compatibility
return this.music
}
changeMusic(name) {
// Note: Back Compatibility
this.music = name
}
get backCompatibilityFns() {
const _this = this
return {
setMusicFromWE: (url) => _this.custom = url,
setMusic: (e) => _this.custom = e.target.files[0],
resetMusic: _this.reset
}
}
get config() {
return {
default: this.#music.current,
...this.#config
}
}
get HTML() {
return `
<div>
<label for="music">Music</label>
<input type="checkbox" id="music" name="music" />
<div id="music-realted" hidden>
<input type="checkbox" id="music" name="music" ${this.useMusic ? "checked" : ""}/>
<div id="music-realted" ${this.useMusic ? "" : "hidden"}>
<div>
<label for="music-select">Choose theme music:</label>
<select name="music-select" id="music-select">
@@ -228,9 +238,14 @@ export default class Music {
</select>
</div>
<div>
<label for="custom-music"> Custom Music (Store Locally)</label>
<label for="custom-music">Custom Music (Store Locally)</label>
<input type="file" id="custom-music" accept="audio/*"/>
<button type="button" disabled id="custom-music-clear" disabled>Clear</button>
<button type="button" id="custom-music-clear" ${this.#config.name ? this.#config.name.isLocalFile ? "" : "disabled" : "disabled"}>Clear</button>
</div>
<div>
<label for="custom-music-url">Custom Music URL:</label>
<input type="text" id="custom-music-url" name="custom-music-url" value="${this.#config.name ? this.#config.name.value : ""}">
<button type="button" id="custom-music-url-apply">Apply</button>
</div>
<div>
<label for="music-volume">Music Volume</label>
@@ -270,6 +285,8 @@ export default class Music {
id: "custom-music", event: "change", handler: e => this.custom = e.target.files[0]
}, {
id: "custom-music-clear", event: "click", handler: () => this.reset()
}, {
id: "custom-music-url-apply", event: "click", handler: () => this.custom = document.getElementById("custom-music-url").value
}, {
id: "music-volume-slider", event: "input", handler: e => {
syncHTMLValue(e.currentTarget, "music-volume-input");

View File

@@ -32,66 +32,14 @@ export default class Player {
#config = {
fps: this.#default.fps,
useStartAnimation: true,
padding: {...this.#default.padding},
usePadding: false,
padding: {
...this.#default.padding
},
scale: this.#default.scale
}
set useStartAnimation(v) {
this.#config.useStartAnimation = v
}
get useStartAnimation() {
return this.#config.useStartAnimation
}
get spine() {
return this.#spine
}
set fps(v) {
this.#config.fps = v
this.#spine.setFps(v)
}
get fps() {
return this.#config.fps
}
setFPS(fps) {
// Note: Back Compatibility
this.fps = fps
}
resetFPS() {
this.fps = this.#default.fps
document.getElementById("fps-slider").value = this.#default.fps
document.getElementById("fps-input").value = this.#default.fps
}
set scale(v) {
this.#config.scale = 1 / v
this.#spine.setOperatorScale(1 / v)
}
setScale(v) {
// Note: Back Compatibility
this.scale = v
}
resetScale() {
this.scale = this.#default.scale
}
scaleReset() {
// Note: Back Compatibility
this.resetScale()
}
get scale() {
return this.#config.scale
}
init(el) {
constructor(el) {
this.#parentEl = el
this.#el.id = "player-box"
insertHTMLChild(this.#parentEl, this.#el)
@@ -144,7 +92,6 @@ export default class Player {
entry.mixDuration = 0.3;
widget.animationState.addAnimation(0, "Idle", true, 0);
}
_this.success()
document.dispatchEvent(PlayerReadyEvent);
},
}
@@ -161,8 +108,33 @@ export default class Player {
updateHTMLOptions(this.#spine.skeleton.data.animations.map(e => e.name), "animation-selection")
}
get node() {
return this.#el
resetPadding() {
this.padding = {...this.#default.padding}
document.getElementById("position-padding-left-slider").value = this.#default.padding.left
document.getElementById("position-padding-left-input").value = this.#default.padding.left
document.getElementById("position-padding-right-slider").value = this.#default.padding.right
document.getElementById("position-padding-right-input").value = this.#default.padding.right
document.getElementById("position-padding-top-slider").value = this.#default.padding.top
document.getElementById("position-padding-top-input").value = this.#default.padding.top
document.getElementById("position-padding-bottom-slider").value = this.#default.padding.bottom
document.getElementById("position-padding-bottom-input").value = this.#default.padding.bottom
}
resetScale() {
this.scale = this.#default.scale
}
resetFPS() {
this.fps = this.#default.fps
document.getElementById("fps-slider").value = this.#default.fps
document.getElementById("fps-input").value = this.#default.fps
}
reset() {
this.resetFPS()
this.resetPadding()
this.resetScale()
this.#spine.play()
}
#loadViewport() {
@@ -174,9 +146,46 @@ export default class Player {
})
}
loadViewport() {
// Note: Back Compatibility
this.#loadViewport()
get usePadding() {
return this.#config.usePadding
}
set usePadding(v) {
this.#config.usePadding = v
}
set useStartAnimation(v) {
this.#config.useStartAnimation = v
}
get useStartAnimation() {
return this.#config.useStartAnimation
}
get spine() {
return this.#spine
}
set fps(v) {
this.#config.fps = v
this.#spine.setFps(v)
}
get fps() {
return this.#config.fps
}
set scale(v) {
this.#config.scale = 1 / v
this.#spine.setOperatorScale(1 / v)
}
get scale() {
return this.#config.scale
}
get node() {
return this.#el
}
get padLeft() {
@@ -225,64 +234,56 @@ export default class Player {
set padding(v) {
if (typeof v !== "object") return;
if (typeof v.left !== "undefined") this.#config.padding.left = v.left;
if (typeof v.right !== "undefined") this.#config.padding.right = v.right ;
if (typeof v.top !== "undefined") this.#config.padding.top = v.top;
if (typeof v.bottom !== "undefined") this.#config.padding.bottom = v.bottom;
if (v.left) this.#config.padding.left = v.left;
if (v.right) this.#config.padding.right = v.right ;
if (v.top) this.#config.padding.top = v.top;
if (v.bottom) this.#config.padding.bottom = v.bottom;
this.#loadViewport()
}
positionPadding(key, value) {
// Note: Back Compatibility
switch (key) {
case "left":
this.padding = {
left: value
get backCompatibilityFns() {
const _this = this
return {
spinePlayer: _this.#spine,
setFPS: (fps) => _this.fps = fps,
loadViewport: _this.#loadViewport,
setScale: (v) => this.scale = v,
scale: _this.scale,
positionPadding: (key, value) => {
switch (key) {
case "left":
this.padding = {
left: value
}
break;
case "right":
this.padding = {
right: value
}
break;
case "top":
this.padding = {
top: value
}
break;
case "bottom":
this.padding = {
bottom: value
}
break;
default:
this.#config.padding = value
break;
}
break;
case "right":
this.padding = {
right: value
}
break;
case "top":
this.padding = {
top: value
}
break;
case "bottom":
this.padding = {
bottom: value
}
break;
default:
this.#config.padding = value
break;
},
positionReset: _this.resetPadding,
scaleReset: _this.resetScale,
useStartAnimation: _this.useStartAnimation
}
}
resetPadding() {
this.padding = {...this.#default.padding}
document.getElementById("position-padding-left-slider").value = this.#default.padding.left
document.getElementById("position-padding-left-input").value = this.#default.padding.left
document.getElementById("position-padding-right-slider").value = this.#default.padding.right
document.getElementById("position-padding-right-input").value = this.#default.padding.right
document.getElementById("position-padding-top-slider").value = this.#default.padding.top
document.getElementById("position-padding-top-input").value = this.#default.padding.top
document.getElementById("position-padding-bottom-slider").value = this.#default.padding.bottom
document.getElementById("position-padding-bottom-input").value = this.#default.padding.bottom
}
positionReset() {
// Note: Back Compatibility
this.resetPadding()
}
reset() {
this.resetFPS()
this.resetPadding()
this.resetScale()
this.#spine.play()
get config() {
return {...this.#config}
}
get HTML() {
@@ -310,8 +311,8 @@ export default class Player {
</div>
<div>
<label for="position">Position</label>
<input type="checkbox" id="position" name="position" />
<div id="position-realted" hidden>
<input type="checkbox" id="position" name="position" ${this.usePadding ? "checked" : ""}/>
<div id="position-realted" ${this.usePadding ? "" : "hidden"}>
<div>
<label for="position-padding-left">Padding Left</label>
<input type="range" min="-100" max="100" id="position-padding-left-slider" value="${this.padding.left}" />
@@ -398,6 +399,7 @@ export default class Player {
}, {
id: "position", event: "click", handler: e => {
showRelatedHTML(e.currentTarget, "position-realted");
this.usePadding = e.currentTarget.checked;
if (!e.currentTarget.checked) this.resetPadding();
}
}, {

View File

@@ -50,7 +50,7 @@ export default class Voice {
useSubtitle: false,
useVoice: false,
useVoiceActor: false,
voiceLang: null,
language: null,
subtitle: {
language: this.#default.region,
...this.#default.subtitle
@@ -59,11 +59,188 @@ export default class Voice {
}
#playerObj
constructor() {
constructor(el) {
this.#default.language.voice = this.#voice.languages[0]
this.#config.voiceLang = this.#default.language.voice
this.#config.language = this.#default.language.voice
this.#voice.locations = this.#getVoiceLocations()
this.#voice.list = Object.keys(this.#getVoices())
this.#parentEl = el
this.#el.id = "voice-box"
this.#el.hidden = true
this.#el.innerHTML = `
<audio id="${this.#audio.id}" autoplay>
<source type="audio/ogg" />
</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 id="voice-actor-box" hidden>
<div class="voice-actor">
<span class="voice-actor-icon"></span>
<span id="voice-actor-name" class="voice-actor-name"></span>
</div>
</div>
`
insertHTMLChild(this.#parentEl, this.#el)
}
success() {
const audioEndedFunc = () => {
this.#audio.isPlaying = false
this.#setCurrentSubtitle(null)
this.#audio.lastClickToNext = false
}
this.#audio.el.addEventListener('ended', audioEndedFunc)
this.#playEntryVoice()
this.#initNextVoiceTimer()
this.#playerObj.node.addEventListener('click', () => {
this.#audio.lastClickToNext = true
this.#nextVoice()
})
document.addEventListener('mousemove', () => {
if (this.#voice.listener.idle === -1) {
this.#initIdleVoiceTimer()
}
})
}
link(playerObj) {
this.#playerObj = playerObj
}
resetPosition() {
this.position = {...this.#default.subtitle}
document.getElementById("subtitle-padding-x-slider").value = this.#default.subtitle.x
document.getElementById("subtitle-padding-x-input").value = this.#default.subtitle.x
document.getElementById("subtitle-padding-y-slider").value = this.#default.subtitle.y
document.getElementById("subtitle-padding-y-input").value = this.#default.subtitle.y
}
reset() {
this.resetPosition()
}
#getVoiceLocations() {
const folders = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
const customVoiceName = this.#voice.languages.filter(i => !folders.sub.map(e => e.lang).includes(i))[0]
folders.sub = folders.sub.map(e => {
return {
name: e.name,
lang: e.lang === "CUSTOM" ? customVoiceName : e.lang
}
})
return folders
}
#getVoices() {
return charword_table.subtitleLangs[this.#config.subtitle.language].default
}
#playEntryVoice() {
this.#playSpecialVoice("问候")
}
#playSpecialVoice(matcher) {
const voices = this.#getVoices()
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
this.#playVoice(voiceId)
}
#playVoice(id) {
if (!this.useVoice) return
this.#voice.id.last = this.#voice.id.current
this.#voice.id.current = id
this.#audio.el.src = `./assets/${this.#getVoiceLocation()
}/${id}.ogg`
let startPlayPromise = this.#audio.el.play()
if (startPlayPromise !== undefined) {
startPlayPromise
.then(() => {
this.#audio.isPlaying = true
this.#setCurrentSubtitle(id)
})
.catch(() => {
return
})
}
}
#getVoiceLocation() {
const locations = this.#voice.locations
return `${locations.main}/${locations.sub.find(e => e.lang === this.#config.language).name}`
}
#setCurrentSubtitle(id) {
if (id === null) {
setTimeout(() => {
if (this.#audio.isPlaying) return
this.#toggleSubtitle(0)
}, 5 * 1000);
return
}
const subtitle = this.#getSubtitleById(id)
const title = subtitle.title
const content = subtitle.text
const cvInfo = charword_table.voiceLangs[this.subtitleLanguage][this.#config.language]
document.getElementById('voice-title').innerText = title
document.getElementById('voice-subtitle').innerText = content
document.getElementById('voice-actor-name').innerText = cvInfo.join('')
if (this.#audio.isPlaying) {
this.#toggleSubtitle(1)
}
}
#toggleSubtitle(v) {
this.#el.style.opacity = v ? 1 : 0
}
#getSubtitleById(id) {
const obj = charword_table.subtitleLangs[this.#config.subtitle.language]
let key = 'default'
if (obj[this.#config.language]) {
key = this.#config.language
}
return obj[key][id]
}
#getSubtitleLanguages() {
return Object.keys(charword_table.subtitleLangs)
}
#updateSubtitlePosition() {
updateElementPosition(this.#el, {
x: this.position.x,
y: this.position.y - 100
})
}
#initNextVoiceTimer() {
this.#voice.listener.next = setInterval(() => {
if (!this.#voice.lastClickToNext) {
this.#nextVoice()
}
}, this.#config.duration.next)
}
#nextVoice() {
const getVoiceId = () => {
const id = this.#voice.list[Math.floor((Math.random() * this.#voice.list.length))]
return id === this.#voice.id.last ? getVoiceId() : id
}
this.#playVoice(getVoiceId())
}
#initIdleVoiceTimer() {
this.#voice.listener.idle = setInterval(() => {
this.#playSpecialVoice("闲置")
clearInterval(this.#voice.listener.idle)
this.#voice.listener.idle = -1
}, this.#config.duration.idle)
}
set useSubtitle(show) {
@@ -143,11 +320,76 @@ export default class Voice {
set position(v) {
if (typeof v !== "object") return;
if (typeof v.x !== "undefined") this.#config.subtitle.x = v.x;
if (typeof v.y !== "undefined") this.#config.subtitle.y = v.y;
if (v.x) this.#config.subtitle.x = v.x;
if (v.y) this.#config.subtitle.y = v.y;
this.#updateSubtitlePosition()
}
set language(lang) {
if (this.#voice.languages.includes(lang)) {
this.#config.language = lang
} else {
this.#config.language = this.#default.language.voice
}
const availableSubtitleLang = this.#getSubtitleLanguages()
if (!availableSubtitleLang.includes(this.#config.subtitle.language)) {
this.#config.subtitle.language = availableSubtitleLang[0]
}
}
get language() {
return this.#config.language
}
get languages() {
return this.#voice.languages
}
get duration() {
return {
idle: this.#config.duration.idle / 60 / 1000,
next: this.#config.duration.next / 60 / 1000
}
}
set duration(v) {
if (typeof v !== "object") return;
if (v.idle) {
clearInterval(this.#voice.listener.idle)
if (v.idle !== 0) {
this.#config.duration.idle = parseInt(v.idle) * 60 * 1000
this.#initIdleVoiceTimer()
}
}
if (v.next) {
clearInterval(this.#voice.listener.next)
if (v.next !== 0) {
this.#config.duration.next = parseInt(v.next) * 60 * 1000
this.#initNextVoiceTimer()
}
}
}
get durationIdle() {
return this.duration.idle
}
set durationIdle(duration) {
this.duration = {
idle: duration
}
}
set durationNext(duration) {
this.duration = {
next: duration
}
}
get durationNext() {
return this.duration.next
}
set subtitleX(x) {
// Note: Back Compatibility
this.position = {
@@ -172,61 +414,6 @@ export default class Voice {
return this.position.y
}
set language(lang) {
if (this.#voice.languages.includes(lang)) {
this.#config.voiceLang = lang
} else {
this.#config.voiceLang = this.#default.language.voice
}
const availableSubtitleLang = this.#getSubtitleLanguages()
if (!availableSubtitleLang.includes(this.#config.subtitle.language)) {
this.#config.subtitle.language = availableSubtitleLang[0]
}
}
get language() {
return this.#config.voiceLang
}
get languages() {
return this.#voice.languages
}
get duration() {
return {
idle: this.#config.duration.idle / 60 / 1000,
next: this.#config.duration.next / 60 / 1000
}
}
set duration(v) {
if (typeof v !== "object") return;
if (typeof v.idle !== "undefined") {
clearInterval(this.#voice.listener.idle)
if (v.idle !== 0) {
this.#config.duration.idle = parseInt(v.idle) * 60 * 1000
this.#initIdleVoiceTimer()
}
}
if (typeof v.next !== "undefined") {
clearInterval(this.#voice.listener.next)
if (v.next !== 0) {
this.#config.duration.next = parseInt(v.next) * 60 * 1000
this.#initNextVoiceTimer()
}
}
}
get durationIdle() {
return this.duration.idle
}
set durationIdle(duration) {
this.duration = {
idle: duration
}
}
set idleDuration(duration) {
// Note: Back Compatibility
this.duration = {
@@ -239,211 +426,26 @@ export default class Voice {
return this.duration.idle
}
set durationNext(duration) {
this.duration = {
next: duration
}
}
set nextDuration(duration) {
// Note: Back Compatibility
this.duration.next = duration
}
get durationNext() {
return this.duration.next
}
get nextDuration() {
// Note: Back Compatibility
return this.duration.next
}
init(el) {
this.#parentEl = el
this.#el.id = "voice-box"
this.#el.hidden = true
this.#el.innerHTML = `
<audio id="${this.#audio.id}" autoplay>
<source type="audio/ogg" />
</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 id="voice-actor-box" hidden>
<div class="voice-actor">
<span class="voice-actor-icon"></span>
<span id="voice-actor-name" class="voice-actor-name"></span>
</div>
</div>
`
insertHTMLChild(this.#parentEl, this.#el)
}
success() {
const audioEndedFunc = () => {
this.#audio.isPlaying = false
this.#setCurrentSubtitle(null)
this.#audio.lastClickToNext = false
}
this.#audio.el.addEventListener('ended', audioEndedFunc)
this.#playEntryVoice()
this.#initNextVoiceTimer()
this.#playerObj.node.addEventListener('click', () => {
this.#audio.lastClickToNext = true
this.#nextVoice()
})
document.addEventListener('mousemove', () => {
if (this.#voice.listener.idle === -1) {
this.#initIdleVoiceTimer()
}
})
}
link(playerObj) {
this.#playerObj = playerObj
}
#getVoiceLocations() {
const folders = JSON.parse(import.meta.env.VITE_VOICE_FOLDERS)
const customVoiceName = this.#voice.languages.filter(i => !folders.sub.map(e => e.lang).includes(i))[0]
folders.sub = folders.sub.map(e => {
return {
name: e.name,
lang: e.lang === "CUSTOM" ? customVoiceName : e.lang
}
})
return folders
}
#getVoices() {
return charword_table.subtitleLangs[this.#config.subtitle.language].default
}
#playEntryVoice() {
this.#playSpecialVoice("问候")
}
#playSpecialVoice(matcher) {
const voices = this.#getVoices()
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
this.#playVoice(voiceId)
}
#playVoice(id) {
if (!this.useVoice) return
this.#voice.id.last = this.#voice.id.current
this.#voice.id.current = id
this.#audio.el.src = `./assets/${this.#getVoiceLocation()
}/${id}.ogg`
let startPlayPromise = this.#audio.el.play()
if (startPlayPromise !== undefined) {
startPlayPromise
.then(() => {
this.#audio.isPlaying = true
this.#setCurrentSubtitle(id)
})
.catch(() => {
return
})
}
}
#getVoiceLocation() {
const locations = this.#voice.locations
return `${locations.main}/${locations.sub.find(e => e.lang === this.#config.voiceLang).name}`
}
#setCurrentSubtitle(id) {
if (id === null) {
setTimeout(() => {
if (this.#audio.isPlaying) return
this.#toggleSubtitle(0)
}, 5 * 1000);
return
}
const subtitle = this.#getSubtitleById(id)
const title = subtitle.title
const content = subtitle.text
const cvInfo = charword_table.voiceLangs[this.subtitleLanguage][this.#config.voiceLang]
document.getElementById('voice-title').innerText = title
document.getElementById('voice-subtitle').innerText = content
document.getElementById('voice-actor-name').innerText = cvInfo.join('')
if (this.#audio.isPlaying) {
this.#toggleSubtitle(1)
}
}
#toggleSubtitle(v) {
this.#el.style.opacity = v ? 1 : 0
}
#getSubtitleById(id) {
const obj = charword_table.subtitleLangs[this.#config.subtitle.language]
let key = 'default'
if (obj[this.#config.voiceLang]) {
key = this.#config.voiceLang
}
return obj[key][id]
}
#getSubtitleLanguages() {
return Object.keys(charword_table.subtitleLangs)
}
#updateSubtitlePosition() {
updateElementPosition(this.#el, {
x: this.position.x,
y: this.position.y - 100
})
}
#initNextVoiceTimer() {
this.#voice.listener.next = setInterval(() => {
if (!this.#voice.lastClickToNext) {
this.#nextVoice()
}
}, this.#config.duration.next)
}
#nextVoice() {
const getVoiceId = () => {
const id = this.#voice.list[Math.floor((Math.random() * this.#voice.list.length))]
return id === this.#voice.id.last ? getVoiceId() : id
}
this.#playVoice(getVoiceId())
}
#initIdleVoiceTimer() {
this.#voice.listener.idle = setInterval(() => {
this.#playSpecialVoice("闲置")
clearInterval(this.#voice.listener.idle)
this.#voice.listener.idle = -1
}, this.#config.duration.idle)
}
resetPosition() {
this.position = {...this.#default.subtitle}
document.getElementById("subtitle-padding-x-slider").value = this.#default.subtitle.x
document.getElementById("subtitle-padding-x-input").value = this.#default.subtitle.x
document.getElementById("subtitle-padding-y-slider").value = this.#default.subtitle.y
document.getElementById("subtitle-padding-y-input").value = this.#default.subtitle.y
}
reset() {
this.resetPosition()
get config() {
return {...this.#config}
}
get HTML() {
return `
<div>
<label for="voice">Voice</label>
<input type="checkbox" id="voice" name="voice"/>
<div id="voice-realted" hidden>
<input type="checkbox" id="voice" name="voice" ${this.useVoice ? "checked" : ""}/>
<div id="voice-realted" ${this.useVoice ? "" : "hidden"}>
<div>
<label for="voice-lang-select">Choose the language of voice:</label>
<select name="voice-lang" id="voice-lang-select">
@@ -460,8 +462,8 @@ export default class Voice {
</div>
<div>
<label for="subtitle">Subtitle</label>
<input type="checkbox" id="subtitle" name="subtitle"/>
<div id="subtitle-realted" hidden>
<input type="checkbox" id="subtitle" name="subtitle" ${this.useSubtitle ? "checked" : ""}/>
<div id="subtitle-realted" ${this.useSubtitle ? "" : "hidden"}>
<div>
<label for="subtitle-lang-select">Choose the language of subtitle:</label>
<select name="subtitle-lang" id="subtitle-lang-select">
@@ -480,7 +482,7 @@ export default class Voice {
</div>
<div>
<label for="voice-actor">Voice Actor</label>
<input type="checkbox" id="voice-actor" name="voice-actor"/>
<input type="checkbox" id="voice-actor" name="voice-actor" ${this.useVoiceActor ? "checked" : ""}/>
</div>
</div>
</div>

View File

@@ -2,5 +2,6 @@ import '@/index.css'
import '@/libs/wallpaper_engine'
import AKLive2D from '@/components/aklive2d'
window.aklive2d = new AKLive2D(document.getElementById('app'))
window.aklive2d.init()
(() => {
window.aklive2d = new AKLive2D(document.getElementById("app"))
})()