diff --git a/hitsounds/combobreak.wav b/hitsounds/combobreak.wav new file mode 100644 index 00000000..3ff09686 Binary files /dev/null and b/hitsounds/combobreak.wav differ diff --git a/hitsounds/convert.sh b/hitsounds/convert.sh index 8acdfdcf..95961bb6 100644 --- a/hitsounds/convert.sh +++ b/hitsounds/convert.sh @@ -21,3 +21,5 @@ ffmpeg -i soft-hitclap.wav soft-hitclap.ogg ffmpeg -i soft-sliderslide.wav soft-sliderslide.ogg ffmpeg -i soft-sliderwhistle.wav soft-sliderwhistle.ogg ffmpeg -i soft-slidertick.wav soft-slidertick.ogg + +ffmpeg -i combobreak.ogg combobreak.mp3 \ No newline at end of file diff --git a/scripts/downloader.js b/scripts/downloader.js index 964032a1..b7ace663 100644 --- a/scripts/downloader.js +++ b/scripts/downloader.js @@ -1,84 +1,182 @@ // beatmap downloader -function startpreview(box) { - let volume = 1; - if (window.gamesettings) { - volume = (window.gamesettings.mastervolume/100) * (window.gamesettings.musicvolume/100); - volume = Math.min(1, Math.max(0, volume)); - } - let audios = document.getElementsByTagName("audio"); - for (let i=0; i 9.3) // assume it's 10s long - a.volume = Math.max(0, a.volume - 0.05*volume); - if (a.volume == 0) - clearInterval(fadeOut); - }, 30); - a.softstop = function() { - let fadeOut = setInterval(function(){ - a.volume = Math.max(0, a.volume - 0.05*volume); - if (a.volume == 0) { - clearInterval(fadeOut); - a.remove(); +(() => { + + /** + * Preview Audio + */ + class PreviewAudio { + /** + * Build a PreviewAudio object + * @param {string | number} sid Beatmap's sid + * @param {number} volume Volume of preview audio + */ + constructor(sid, volume = 1) { + const audioContext = new Audio("https://cdn.sayobot.cn:25225/preview/" + sid + ".mp3"); + this._sid = sid; + this._volume = volume; + this._audioContext = audioContext; + this._playing = false; + this._ready = false; + this._fadeTimer = 0; + this._playTimer = 0; + audioContext.load(); + audioContext.volume = 0; + audioContext.addEventListener('canplay', () => { + this._ready = true + if(this._playing === true){ + this.play(); + } + }, {once: true}) + } + play() { + this._playing = true; + if(this._ready){ + this._playing = true + this._audioContext.volume = 0; + this._audioContext.currentTime = 0; + this._audioContext.play(); + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + this._fadeTimer = setInterval(() => { + if (this._audioContext.volume < this._volume) + this._audioContext.volume = Math.min(this._volume, this._audioContext.volume + 0.05 * this._volume); + else + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + }, 30); + this._playTimer = setTimeout(() => { + this._audioContext.volume = this._volume; + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + }, 600); } - }, 10); + } + stop() { + this._playing = false + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + this._fadeTimer = setInterval(() => { + this._audioContext.volume = Math.max(0, this._audioContext.volume - 0.05 * this._volume); + if (this._audioContext.volume === 0) { + this._audioContext.pause(); + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + } + }, 10); + this._playTimer = setTimeout(() => { + this._audioContext.pause(); + clearInterval(this._fadeTimer); + clearTimeout(this._playTimer); + }, 200); + } + set volume(value) { + this._volume = value; + } + get volume() { + return this._volume; + } } -} -function startdownload(box) { - startpreview(box); - if (box.downloading) { - return; - } - let url = "https://txy1.sayobot.cn/beatmaps/download/mini/" + box.sid; - box.downloading = true; - box.classList.add("downloading"); - let xhr = new XMLHttpRequest(); - xhr.responseType = 'arraybuffer'; - xhr.open("GET", url); - // create download progress bar - let container = document.createElement("div"); - let title = document.createElement("div"); - let bar = document.createElement("progress"); - container.className = "download-progress"; - title.className = "title"; - title.innerText = box.setdata.title; - container.appendChild(title); - container.appendChild(bar); - // insert so that download list from recent to old - let statuslines = document.getElementById("statuslines"); - statuslines.insertBefore(container, statuslines.children[3]); - bar.max = 1; - bar.value = 0; - // async part - xhr.onload = function() { - box.oszblob = new Blob([xhr.response]); - bar.className = "finished"; - box.classList.remove("downloading"); + class PreviewAudioManager { + /** + * Create a preview audio manager + * @param {object} [settings] Preview audio manager settings + * @param {number} [settings.volume] Audio's volume + */ + constructor(settings = {}) { + const {volume} = settings + /** + * @type {PreviewAudio} + */ + this._active = null; + this._volume = volume; + } + /** + * Play preview audio + * @param {string | number} sid Beatmap's sid + */ + play(sid) { + if(this._active){ + this._active.stop(); + } + this._active = new PreviewAudio(sid, this._volume); + this._active.play() + } + stop() { + if(this._active){ + this._active.stop(); + } + } + set volume(value) { + if(value > 1){ + value = 1; + } + this._volume = value; + if(this._active) { + this._active.volume = value; + } + } + get volume() { + return this._volume; + } } - xhr.onprogress = function(e) { - bar.value = e.loaded / e.total; + + const previewAudioMgr = new PreviewAudioManager() + + function startpreview(box) { + let volume = 1; + if (window.gamesettings) { + volume = (window.gamesettings.mastervolume/100) * (window.gamesettings.musicvolume/100); + volume = Math.min(1, Math.max(0, volume)); + } + previewAudioMgr.volume = volume + previewAudioMgr.play(box.sid) } - xhr.onerror = function() { - console.error("download failed"); - alert("Beatmap download failed. Please retry later.") - box.downloading = false; - box.classList.remove("downloading"); + + function startdownload(box) { + startpreview(box); + if (box.downloading) { + return; + } + let url = "https://txy1.sayobot.cn/beatmaps/download/mini/" + box.sid; + box.downloading = true; + box.classList.add("downloading"); + let xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; + xhr.open("GET", url); + // create download progress bar + let container = document.createElement("div"); + let title = document.createElement("div"); + let bar = document.createElement("progress"); + container.className = "download-progress"; + title.className = "title"; + title.innerText = box.setdata.title; + container.appendChild(title); + container.appendChild(bar); + // insert so that download list from recent to old + let statuslines = document.getElementById("statuslines"); + statuslines.insertBefore(container, statuslines.children[3]); + bar.max = 1; + bar.value = 0; + // async part + xhr.onload = function() { + box.oszblob = new Blob([xhr.response]); + bar.className = "finished"; + box.classList.remove("downloading"); + } + xhr.onprogress = function(e) { + bar.value = e.loaded / e.total; + } + xhr.onerror = function() { + console.error("download failed"); + alert("Beatmap download failed. Please retry later.") + box.downloading = false; + box.classList.remove("downloading"); + } + xhr.send(); } - xhr.send(); -} \ No newline at end of file + + window.startdownload = startdownload + window.previewAudioMgr = previewAudioMgr +})() \ No newline at end of file diff --git a/scripts/initgame.js b/scripts/initgame.js index 65788fee..0fef6730 100644 --- a/scripts/initgame.js +++ b/scripts/initgame.js @@ -108,23 +108,32 @@ function(Osu, _, sound, Playback) { 'hitsounds/drum-slidertick.ogg', 'hitsounds/combobreak.ogg', ]; + var issafariBrowser = /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent); + if(issafariBrowser){ + sample = sample.map(path => { + const pathSplited = path.split('.'); + pathSplited.pop(); + pathSplited.push('wav'); + return pathSplited.join('.'); + }) + } sounds.whenLoaded = function(){ - game.sample[1].hitnormal = sounds['hitsounds/normal-hitnormal.ogg']; - game.sample[1].hitwhistle = sounds['hitsounds/normal-hitwhistle.ogg']; - game.sample[1].hitfinish = sounds['hitsounds/normal-hitfinish.ogg']; - game.sample[1].hitclap = sounds['hitsounds/normal-hitclap.ogg']; - game.sample[1].slidertick = sounds['hitsounds/normal-slidertick.ogg']; - game.sample[2].hitnormal = sounds['hitsounds/soft-hitnormal.ogg']; - game.sample[2].hitwhistle = sounds['hitsounds/soft-hitwhistle.ogg']; - game.sample[2].hitfinish = sounds['hitsounds/soft-hitfinish.ogg']; - game.sample[2].hitclap = sounds['hitsounds/soft-hitclap.ogg']; - game.sample[2].slidertick = sounds['hitsounds/soft-slidertick.ogg']; - game.sample[3].hitnormal = sounds['hitsounds/drum-hitnormal.ogg']; - game.sample[3].hitwhistle = sounds['hitsounds/drum-hitwhistle.ogg']; - game.sample[3].hitfinish = sounds['hitsounds/drum-hitfinish.ogg']; - game.sample[3].hitclap = sounds['hitsounds/drum-hitclap.ogg']; - game.sample[3].slidertick = sounds['hitsounds/drum-slidertick.ogg']; - game.sampleComboBreak = sounds['hitsounds/combobreak.ogg']; + game.sample[1].hitnormal = sounds[sample[0]]; + game.sample[1].hitwhistle = sounds[sample[1]]; + game.sample[1].hitfinish = sounds[sample[2]]; + game.sample[1].hitclap = sounds[sample[3]]; + game.sample[1].slidertick = sounds[sample[4]]; + game.sample[2].hitnormal = sounds[sample[5]]; + game.sample[2].hitwhistle = sounds[sample[6]]; + game.sample[2].hitfinish = sounds[sample[7]]; + game.sample[2].hitclap = sounds[sample[8]]; + game.sample[2].slidertick = sounds[sample[9]]; + game.sample[3].hitnormal = sounds[sample[10]]; + game.sample[3].hitwhistle = sounds[sample[11]]; + game.sample[3].hitfinish = sounds[sample[12]]; + game.sample[3].hitclap = sounds[sample[13]]; + game.sample[3].slidertick = sounds[sample[14]]; + game.sampleComboBreak = sounds[sample[15]]; window.soundReady = true; document.getElementById("sound-progress").classList.add("finished"); document.body.classList.add("sound-ready"); diff --git a/scripts/launchgame.js b/scripts/launchgame.js index ecc8afe1..6ec7684d 100644 --- a/scripts/launchgame.js +++ b/scripts/launchgame.js @@ -108,9 +108,7 @@ function launchOSU(osu, beatmapid, version){ game.scene = playback; playback.onload = function() { // stop beatmap preview - let audios = document.getElementsByTagName("audio"); - for (let i=0; i { + audioContext.resume(); + }, { + once: true + }); + } + function OsuAudio(filename, buffer, callback) { var self = this; this.decoded = null; @@ -81,7 +90,7 @@ define([], function() { this.started = 0; this.position = 0; this.playing = false; - this.audio = new AudioContext(); + this.audio = audioContext; this.gain = this.audio.createGain(); this.gain.connect(this.audio.destination); this.playbackRate = 1.0; @@ -125,9 +134,11 @@ define([], function() { }; this.play = function play(wait = 0) { - if (self.audio.state == "suspended") { - window.alert("Audio can't play. Please use Chrome or Firefox.") + if (self.audio.state === "suspended") { + console.warn("Audio suspended. Waiting for touchstart."); + self.audio.resume(); } + self.playing = true; self.source = self.audio.createBufferSource(); self.source.playbackRate.value = self.playbackRate; self.source.buffer = self.decoded; @@ -140,7 +151,6 @@ define([], function() { else { self.source.start(0, self.position); } - self.playing = true; }; // return value true: success diff --git a/scripts/playerActions.js b/scripts/playerActions.js index 6da1002f..c736893f 100644 --- a/scripts/playerActions.js +++ b/scripts/playerActions.js @@ -186,6 +186,38 @@ define([], function() { playback.game.down = playback.game.K1down || playback.game.K2down || playback.game.M1down || playback.game.M2down; } + var touchmoveCallback = function(e) { + if(!e.touches || !e.touches[0]) return; + var touch = e.touches[0]; + playback.game.mouseX = (touch.clientX - gfx.xoffset) / gfx.width * 512; + playback.game.mouseY = (touch.clientY - gfx.yoffset) / gfx.height * 384; + movehistory.unshift({ + x: playback.game.mouseX, + y: playback.game.mouseY, + t: new Date().getTime() + }); + if (movehistory.length>10) movehistory.pop(); + } + var touchstartCallback = function(e) { + touchmoveCallback(e); + if (playback.game.M1down) return; + playback.game.M1down = true; + if(!playback.game.paused && !playback.ended){ + e.preventDefault(); + } + playback.game.down = playback.game.K1down || playback.game.K2down + || playback.game.M1down || playback.game.M2down; + checkClickdown(); + } + var touchendCallback = function(e) { + touchmoveCallback(e); + playback.game.M1down = false; + if(!playback.game.paused && !playback.ended){ + e.preventDefault(); + } + playback.game.down = playback.game.K1down || playback.game.K2down + || playback.game.M1down || playback.game.M2down; + } var keydownCallback = function(e) { if (e.keyCode == playback.game.K1keycode) { if (playback.game.K1down) return; @@ -221,10 +253,19 @@ define([], function() { if (playback.game.allowMouseButton) { playback.game.window.addEventListener("mousedown", mousedownCallback); playback.game.window.addEventListener("mouseup", mouseupCallback); + // touch handling for gameplay + if("ontouchstart" in window){ + playback.game.window.addEventListener("touchstart", touchstartCallback); + playback.game.window.addEventListener("touchend", touchendCallback); + } } // keyboard click handling for gameplay playback.game.window.addEventListener("keydown", keydownCallback); playback.game.window.addEventListener("keyup", keyupCallback); + + if("ontouchstart" in window){ + playback.game.window.addEventListener("touchmove", touchmoveCallback); + } } playback.game.cleanupPlayerActions = function() { @@ -233,6 +274,9 @@ define([], function() { playback.game.window.removeEventListener("mouseup", mouseupCallback); playback.game.window.removeEventListener("keydown", keydownCallback); playback.game.window.removeEventListener("keyup", keyupCallback); + playback.game.window.removeEventListener("touchmove", touchmoveCallback); + playback.game.window.removeEventListener("touchstart", touchstartCallback); + playback.game.window.removeEventListener("touchend", touchendCallback); } }