diff --git a/noname/game/index.js b/noname/game/index.js index d69b15670..d81d5aeca 100644 --- a/noname/game/index.js +++ b/noname/game/index.js @@ -1356,61 +1356,148 @@ export class Game { } } /** + * @overload + * @param { object } options + * @param { string } options.path + * //param { boolean } [options.broadcast = false] + * @param { boolean } [options.addVideo = true] + * @param { boolean } [options.video = false] + * @param { (evt: Event) => void } [options.onCanPlay = (evt => void 0)] + * @param { (evt: Event) => void } [options.onPlay = (evt => void 0)] + * @param { (evt: Event) => void } [options.onEnded = (evt => void 0)] + * @param { (evt: Event) => void } [options.onError = (evt => void 0)] * @returns { HTMLAudioElement } */ - playAudio() { - let path = "", - emptyPath = true, - notCheckDBPath = true, - onError = null; - if (_status.video) { - // 为了能更美观的写代码,默认返回audio而不额外加一个void类型 - // @ts-ignore - if (arguments[1] != "video") return; - path = arguments[0]; - } else { - for (const argument of arguments) { - if (typeof argument === "string" || typeof argument == "number") { - if (emptyPath) emptyPath = false; - else if (notCheckDBPath) { - notCheckDBPath = false; - if (/^db:extension-[^:]*$/.test(path)) path += ":"; - else path += "/"; - } else path += "/"; - path += argument; - } else if (typeof argument == "function") onError = argument; - if (_status.video) break; - } - if (path.startsWith("ext:")) path = path.replace(/^ext:/, "extension/"); - else if (!["db:", "blob:", "data:"].some(prefix => path.startsWith(prefix))) path = `audio/${path}`; - if (!lib.config.repeat_audio && _status.skillaudio.includes(path)) return; - } - const audio = document.createElement("audio"); - audio.autoplay = true; + /** + * @overload + * @param { ...string | number | ((evt: Event) => void) } args + * @returns { HTMLAudioElement } + */ + playAudio(...args) { + const options = (args.length === 1 && get.objtype(args[0]) === "object") + ? args[0] + : { + path: args.filter(arg => typeof arg === 'string' || typeof arg === 'number').join("/"), + onError: args.find(arg => typeof arg === "function"), + }; + + const { + path = "", + // broadcast = false, + addVideo = true, + video = false, + onCanPlay = (evt => void 0), + onPlay = (evt => void 0), + onEnded = (evt => void 0), + onError = (evt => void 0), + } = options; + + // 为了能更美观的写代码,默认返回audio而不额外加一个void类型 + // @ts-ignore + if (_status.video && !video) return; + + let parsedPath = ""; + if (["blob:", "data:"].some(prefix => path.startsWith(prefix))) parsedPath = path; + else if (path.startsWith('ext:')) parsedPath = path.replace(/^ext:/, 'extension/'); + else if (path.startsWith('db:')) parsedPath = path.replace(/^(db:[^:]*)\//, (_, p) => p + ":"); + else parsedPath = `audio/${path}`; + + // @ts-ignore + if (!lib.config.repeat_audio && _status.skillaudio.includes(parsedPath)) return; + + const audio = document.createElement('audio'); audio.volume = lib.config.volumn_audio / 8; - //Some browsers do not support "autoplay", so "oncanplay" listening has been added - audio.oncanplay = () => Promise.resolve(audio.play()).catch(() => void 0); - audio.onplay = () => { - _status.skillaudio.add(path); - setTimeout(() => _status.skillaudio.remove(path), 1000); - game.addVideo("playAudio", null, path); + audio.autoplay = true; + + audio.oncanplay = ev => { + //Some browsers do not support "autoplay", so "oncanplay" listening has been added + Promise.resolve(audio.play()).catch(e => console.error(e)); + onCanPlay(ev); + } + audio.onplay = ev => { + _status.skillaudio.add(parsedPath); + setTimeout(() => _status.skillaudio.remove(parsedPath), 1000); + // if (broadcast) game.broadcast(game.playAudio, options); + if (addVideo) game.addVideo("playAudio", null, path); + if (_status.video || game.online) return; + onPlay(ev); }; - audio.onended = event => audio.remove(); - audio.onerror = event => { + audio.onended = ev => { audio.remove(); - if (onError) onError(event); + if (_status.video || game.online) return; + onEnded(ev); }; - new Promise((resolve, reject) => { - if (path.startsWith("db:")) game.getDB("image", path.slice(3)).then(octetStream => resolve(get.objectURL(octetStream)), reject); - else if (lib.path.extname(path)) resolve(`${lib.assetURL}${path}`); - else if (URL.canParse(path)) resolve(path); - else resolve(`${lib.assetURL}${path}.mp3`); - }).then(resolvedPath => { + audio.onerror = ev => { + audio.remove(); + if (_status.video || game.online) return; + onError(ev); + }; + + Promise.resolve().then(async () => { + let resolvedPath; + if (parsedPath.startsWith('db:')) resolvedPath = get.objectURL(await game.getDB('image', parsedPath.slice(3))); + else if (lib.path.extname(parsedPath)) resolvedPath = `${lib.assetURL}${parsedPath}`; + else if (URL.canParse(path)) resolvedPath = path; + else resolvedPath = `${lib.assetURL}${parsedPath}.mp3`; + audio.src = resolvedPath; ui.window.appendChild(audio); }); + return audio; } + /** + * @param { object } options + * @param { string[] } options.audioList + * @param { boolean } [options.autoplay = true] + * @param { boolean } [options.random = true] + * @param { boolean } [options.addVideo = true] + * @returns + */ + tryAudio({ audioList, autoplay = true, random = true, addVideo=true}) { + /** + * @type {string} + */ + let audio, + list = get.Audio.copy(audioList), + refresh = false; // 当前audioList是否有可播放的音频 + + const check = () => { + if (list.length) return true; + if (refresh) { + list = get.Audio.copy(audioList); + return true; + } + //@ts-ignore + if (!list.alternate) return false; + //@ts-ignore + audioList = list.alternate; + list = get.Audio.copy(audioList); + return check(); + }; + + /** + * @returns {HTMLAudioElement} + */ + const play = () => { + //@ts-ignore + if (!check()) return; + //@ts-ignore + audio = random ? list.randomRemove() : list.shift(); + return game.playAudio({ + path: audio, + addVideo, + onCanPlay: () => refresh = true, + onError: play, + }); + }; + + if (autoplay) return play(); + return () => { + if (random) list = get.Audio.copy(audioList); + return play(); + }; + } /** * @deprecated 请使用get.Audio.skill + get.Audio.toFile * @@ -1465,7 +1552,7 @@ export class Game { * @param { Player | string } player * @param { boolean } [directaudio] * @param { boolean } [nobroadcast] - * @param { ['lib']['skill'] } [skillInfo] + * @param { any } [skillInfo] * @returns */ trySkillAudio(skill, player, directaudio, nobroadcast, skillInfo) { @@ -1476,21 +1563,9 @@ export class Game { if (!info) return; if (info.direct && !directaudio) return; if (lib.skill.global.includes(skill) && !info.forceaudio) return; - - let audio, list = get.Audio.skill({ skill, player, info: skillInfo }).randomSort(); - const check = () => { - if (list.length) return true; - //@ts-ignore - if (!list.alternate) return false; - //@ts-ignore - list = list.alternate; - return check(); - }; - return (function play() { - if (!check()) return; - audio = list.shift(); - return game.playAudio(audio.file, play); - })(); + + const audioList = get.Audio.toFile(get.Audio.skill({ skill, player, info: skillInfo })); + return game.tryAudio({ audioList }); } /** * @param { Player | string } player @@ -1500,22 +1575,11 @@ export class Game { game.broadcast(game.tryDieAudio, player); if (!lib.config.background_speak) return; - let audio, list = get.Audio.die({player}).randomSort(); - const check = () => { - if (list.length) return true; - //@ts-ignore - if (!list.alternate) return false; - //@ts-ignore - list = list.alternate; - return check(); - }; - return (function play() { - if (!check()) return; - audio = list.shift(); - return game.playAudio(audio.file, play); - })(); + const audioList = get.Audio.toFile(get.Audio.die({ player })); + return game.tryAudio({ audioList }); } /** + * @deprecated * @param { string } name * @param { number } [index] * @returns @@ -2576,7 +2640,7 @@ export class Game { } }, playAudio: function (str) { - game.playAudio(str, "video"); + game.playAudio({ path: str, video: true }); }, playSkillAudio: function (name) { game.playSkillAudio(name, "video"); diff --git a/noname/get/audio.js b/noname/get/audio.js index 2d97c430e..394c18f68 100644 --- a/noname/get/audio.js +++ b/noname/get/audio.js @@ -155,7 +155,7 @@ export class Audio { const parseAudioWithCache = (name, audioInfo, data, isDefault = false) => { const key = this.#getCacheKey(options, name, audioInfo, data); const result = this.#Cache[key]; - if (result !== void 0) return this.#copy(result); + if (result !== void 0) return this.copy(result); else { const result = parseAudio(name, audioInfo, data); if (isDefault && name.includes("_")) { @@ -163,7 +163,7 @@ export class Audio { result.alternate = getInfoAudio(name, originData); } this.#Cache[key] = result; - return this.#copy(result); + return this.copy(result); } } @@ -258,14 +258,15 @@ export class Audio { } /** - * @param {textMap[]} list + * @template {textMap | string} T + * @param {T[]} list * @this {typeof get.Audio} - * @returns {textMap[]} + * @returns {T[]} */ - #copy = function (list) { + copy(list) { const result = JSON.parse(JSON.stringify(list)); //@ts-ignore - if (list.alternate) result.alternate = this.#copy(list.alternate); + if (list.alternate) result.alternate = this.copy(list.alternate); return result; } diff --git a/noname/status/index.js b/noname/status/index.js index 760dbe1af..f3972cb55 100644 --- a/noname/status/index.js +++ b/noname/status/index.js @@ -14,7 +14,10 @@ export class status { event = null; ai = {}; lastdragchange = []; - skillaudio = []; + /** + * @type { string[] } + */ + skillaudio = [] dieClose = []; dragline = []; dying = []; @@ -56,6 +59,10 @@ export class status { * @type { boolean | undefined } */ connectMode = undefined; + /** + * @type { boolean | undefined } + */ + video = undefined /** * @type { boolean | undefined } */ diff --git a/noname/ui/click/index.js b/noname/ui/click/index.js index 1ff13697a..8db1c95cc 100644 --- a/noname/ui/click/index.js +++ b/noname/ui/click/index.js @@ -3555,23 +3555,17 @@ export class Click { }); } - if (lib.config.background_speak && e !== "init") { - let audio, - skillnode = this; - const playedAudios = []; - (function play() { - if (!skillnode.audioList || !skillnode.audioList.length) { - skillnode.audioList = game.parseSkillAudio(skillnode.link, playername); - if ( - !skillnode.audioList.length || - skillnode.audioList.length == playedAudios.length - ) - return; - } - audio = skillnode.audioList.shift(); - playedAudios.push(audio); - game.playAudio(audio, play); - })(); + if (lib.config.background_speak && e !== 'init') { + if (!this.playAudio) { + const audioList = get.Audio.toFile(get.Audio.skill({ skill: this.link, player: playername })); + this.playAudio = game.tryAudio({ + audioList, + addVideo: false, + random: false, + autoplay: false + }); + } + this.playAudio(); } }; } else { @@ -3914,23 +3908,17 @@ export class Click { }); } - if (lib.config.background_speak && e !== "init") { - let audio, - skillnode = this; - const playedAudios = []; - (function play() { - if (!skillnode.audioList || !skillnode.audioList.length) { - skillnode.audioList = game.parseSkillAudio(skillnode.link, playername); - if ( - !skillnode.audioList.length || - skillnode.audioList.length == playedAudios.length - ) - return; - } - audio = skillnode.audioList.shift(); - playedAudios.push(audio); - game.playAudio(audio, play); - })(); + if (lib.config.background_speak && e !== 'init') { + if (!this.playAudio) { + const audioList = get.Audio.toFile(get.Audio.skill({ skill: this.link, player: playername })); + this.playAudio = game.tryAudio({ + audioList, + addVideo: false, + random: false, + autoplay: false + }); + } + this.playAudio(); } }; }