diff --git a/packages/euterpe-web-test/src/main.ts b/packages/euterpe-web-test/src/main.ts index 68607de..83c2996 100644 --- a/packages/euterpe-web-test/src/main.ts +++ b/packages/euterpe-web-test/src/main.ts @@ -7,14 +7,17 @@ const euterpe = new EuterpeBuilder(document.querySelector("#audio")!, db) .build() add_library_to_dom() -euterpe.preload_song_async(0).then(() => { +euterpe.try_preload_song(0).then(() => { document.querySelector("#text-playing")!.innerHTML = euterpe.format_current_song() }, (e) => console.log(e + " Failed to preload")) document.querySelector("#seek")?.addEventListener("mouseup", (e) => { - euterpe.try_seek_async(e.target?.valueAsNumber).then(() => { console.log("seeked to " + e.target?.valueAsNumber) }, () => { + try { + euterpe.try_seek(e.target?.valueAsNumber) + console.log("seeked to " + e.target?.valueAsNumber) + } catch { alert("Failed seeking! " + e) - }) + } is_seeking = false }) @@ -35,18 +38,18 @@ euterpe.on_time_tick((time) => { }) document.querySelector("#previous")?.addEventListener("click", () => { - euterpe.previous_song_async().then(() => { + euterpe.try_previous_song_looping().then(() => { document.querySelector("#text-playing")!.innerHTML = euterpe.format_current_song() }, (e) => alert(e + "Failed to change song")) }) document.querySelector("#next")?.addEventListener("click", () => { - euterpe.next_song_async().then(() => { + euterpe.try_next_song_looping().then(() => { document.querySelector("#text-playing")!.innerHTML = euterpe.format_current_song() }, (e) => alert(e + "Failed to change song")) }) document.querySelector("#play")?.addEventListener("click", () => { - euterpe.try_play_async().catch((e) => alert("Failed to play, " + e)) + euterpe.try_play().catch((e) => alert("Failed to play, " + e)) }) document.querySelector("#pause")?.addEventListener("click", () => { euterpe.pause() @@ -61,7 +64,7 @@ document.querySelector("#toggle-mute")?.addEventListener("click", () => { euterpe.mute_toggle() }) document.querySelector("#toggle-play")?.addEventListener("click", () => { - euterpe.play_toggle_async().catch((e) => alert("failed to toggle pause/play!" + e)) + euterpe.try_play_toggle().catch((e) => alert("failed to toggle pause/play!" + e)) }) document.querySelector("#volume")?.addEventListener("input", (e) => { euterpe.change_volume(e.target?.valueAsNumber) @@ -99,7 +102,7 @@ function add_library_to_dom() { } function library_play(e: MouseEvent) { const b = e.currentTarget as HTMLButtonElement - euterpe.try_specific_song_async(parseInt(b.dataset["id"]!)).then( + euterpe.try_specific_song(parseInt(b.dataset["id"]!)).then( () => document.querySelector("#text-playing")!.innerHTML = euterpe.format_current_song(), (e) => alert(e) ) diff --git a/packages/euterpe/package.json b/packages/euterpe/package.json index fafd313..d37d1a8 100644 --- a/packages/euterpe/package.json +++ b/packages/euterpe/package.json @@ -1,6 +1,6 @@ { "name": "@euterpe.js/euterpe", - "version": "1.0.15", + "version": "2.0.0", "type": "module", "description": "Fully featured solution for playing music on the web. Support for local library, audio visuals and more!", "main": "./src/index.js", diff --git a/packages/euterpe/src/index.ts b/packages/euterpe/src/index.ts index 911ea79..148d375 100644 --- a/packages/euterpe/src/index.ts +++ b/packages/euterpe/src/index.ts @@ -23,90 +23,74 @@ class Euterpe extends MusicPlayer { super(audio_context, audio_element, track, gain, volume, current_song_path) } + /** * Use to load song on page load. + * @throws if song with ID doesn't exist */ - preload_song_async(id: number) { - return new Promise((resolve, reject) => { - const next = this.db.songs.find((song) => song!.id == id) - if (!next) reject(new Error(`Song with id ${id} doesn't exist`)) - else { - this.try_new_song_async(next.url.pathname).then((s) => { - this.current_song = next - resolve(s) - }, (e) => reject(e)) - } - - }) + async try_preload_song(id: number) { + const next = this.db.songs.find((song) => song!.id == id) + if (!next) throw new Error(`Song with id ${id} doesn't exist`) + else { + await this.try_new_song(next.url.pathname) + this.current_song = next + } } + /** * Won't loop back to first song if already on the last. * If queue present, uses that, if not, relies on Song ID directly from DB + * @throws if on last song or song fails to start */ - try_next_song_async() { - return new Promise((resolve, reject) => { - let new_song: Song - if (this.queue.length > 0) { - new_song = this.queue.shift()! - } else { - let id_i = this.db.songs.length; - while (this.db.songs[--id_i].id! > this.current_song_id); - const next_id = ++id_i; + async try_next_song() { + let new_song: Song + if (this.queue.length > 0) { + new_song = this.queue.shift()! + } else { + let id_i = this.db.songs.length; + while (this.db.songs[--id_i].id! > this.current_song_id); + const next_id = ++id_i; - if (next_id == this.db.songs.length) reject(new Error("Won't go past the last song")) - new_song = this.db.songs.find((song) => song.id == next_id)! - } - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.try_new_song_async(url).then( - () => { - this.try_play_async().then((s) => { - if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - - }) + if (next_id == this.db.songs.length) throw new Error("Won't go past the last song") + new_song = this.db.songs.find((song) => song.id == next_id)! + } + const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() + await this.try_new_song(url) + await this.try_play() + if (this.current_song) this.played_history.push(this.current_song) + this.current_song = new_song + this.current_song_id = new_song.id! } + /** * Will loop back to first song if already on last song, * If queue present, uses that, if not, relies on Song ID directly from DB */ - next_song_async() { - return new Promise((resolve, reject) => { - let new_song: Song - if (this.queue.length > 0) { - new_song = this.queue.shift()! - } else { - let id_i = this.db.songs.length; - while (this.db.songs[--id_i].id! > this.current_song_id); - let next_id = ++id_i + async try_next_song_looping() { + let new_song: Song + if (this.queue.length > 0) { + new_song = this.queue.shift()! + } else { + let id_i = this.db.songs.length; + while (this.db.songs[--id_i].id! > this.current_song_id); + let next_id = ++id_i - if (next_id == this.db.songs.length) next_id = this.db.songs[0].id! - new_song = this.db.songs.find((song) => song.id == next_id)! - } - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.try_new_song_async(url).then( - () => { - this.try_play_async().then((s) => { - if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - }) + if (next_id == this.db.songs.length) next_id = this.db.songs[0].id! + new_song = this.db.songs.find((song) => song.id == next_id)! + } + const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() + await this.try_new_song(url) + await this.try_play() + if (this.current_song) this.played_history.push(this.current_song) + this.current_song = new_song + this.current_song_id = new_song.id! } + /** * Won't tell you if the playback was successsful & wil loop back if already on last song. Best use try_next_song_async() * If queue present, uses that, if not, relies on Song ID directly from DB */ - next_song() { + next_song_looping() { let new_song: Song if (this.queue.length > 0) { new_song = this.queue.shift()! @@ -125,11 +109,12 @@ class Euterpe extends MusicPlayer { this.current_song = new_song this.current_song_id = new_song.id! } + /** - * Won't tell you if the playback was successsful, won't loop back if already on last song and will throw error if attempted. Best use next_song_async() + * Won't tell you if the playback was successsful, won't loop back if already on last song and won't throw error if attempted. Best use next_song_async() * If queue present, uses that, if not, relies on Song ID directly from DB */ - try_next_song() { + next_song() { let new_song: Song if (this.queue.length > 0) { new_song = this.queue.shift()! @@ -137,7 +122,6 @@ class Euterpe extends MusicPlayer { let id_i = this.db.songs.length; while (this.db.songs[--id_i].id! > this.current_song_id); const next_id = ++id_i; - if (next_id == this.db.songs.length) throw new Error("Won't go past the last song") new_song = this.db.songs.find((song) => song.id == next_id)! } const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() @@ -147,134 +131,90 @@ class Euterpe extends MusicPlayer { this.current_song = new_song this.current_song_id = new_song.id! } + /** * Uses safer try_play_async. Normal play / play_async will try to start the player even if the track hasn't started yet, or was previously suspended/closed */ - try_specific_song_async(new_song_id: number) { - return new Promise((resolve, reject) => { - const new_song = this.db.songs.find((song) => song.id! == new_song_id) - if (!new_song) reject(new Error(`No song with id "${new_song_id}" found`)) - else { - this.try_new_song_async(new_song.url.pathname).then( - () => { - this.try_play_async().then((s) => { - if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - } - }) - } - /** - * uses play_async. Will try to play even if the audio context was suspended or closed. - */ - specific_song_async(new_song_id: number) { - return new Promise((resolve, reject) => { - const new_song = this.db.songs.find((song) => song.id! == new_song_id) - if (!new_song) reject(new Error(`No song with id "${new_song_id}" found`)) - else { - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.try_new_song_async(url).then( - () => { - this.play_async().then((s) => { - if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - } - }) - } - /** - * Will throw an error if new ID not found. Won't tell you if the play was successful, best use specific_song_async() or try_specific_song_async() - */ - specific_song(new_song_id: number) { + async try_specific_song(new_song_id: number) { const new_song = this.db.songs.find((song) => song.id! == new_song_id) if (!new_song) throw new Error(`No song with id "${new_song_id}" found`) else { - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.new_song(url) - this.play() + this.try_new_song(new_song.url.pathname) + await this.try_play() if (this.current_song) this.played_history.push(this.current_song) this.current_song = new_song this.current_song_id = new_song.id! } } + + /** + * Won't throw an error if new ID not found. Won't tell you if the play was successful, best use specific_song_async() or try_specific_song_async() + */ + specific_song(new_song_id: number) { + const new_song = this.db.songs.find((song) => song.id! == new_song_id) + if (!new_song) return + const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() + this.new_song(url) + this.play() + if (this.current_song) this.played_history.push(this.current_song) + this.current_song = new_song + this.current_song_id = new_song.id! + } + /** * Won't loop back to first song if already on the last. * If played_history is present, uses that, if not, relies on Song ID directly from DB + * @throws if playback was unsuccessful or at first song/ can't go more previous */ - try_previous_song_async() { - return new Promise((resolve, reject) => { - let new_song: Song - if (this.played_history.length > 0) { - new_song = this.played_history.pop()! - } else { - let id_i = 0; - while (this.db.songs[++id_i].id! < this.current_song_id); - const next_id = --id_i; + async try_previous_song() { + let new_song: Song + if (this.played_history.length > 0) { + new_song = this.played_history.pop()! + } else { + let id_i = 0; + while (this.db.songs[++id_i].id! < this.current_song_id); + const next_id = --id_i; - if (next_id == this.db.songs.length) reject(new Error("Won't roll backwards to last song")) - new_song = this.db.songs.find((song) => song.id == next_id)! - } - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.try_new_song_async(url).then( - () => { - this.try_play_async().then((s) => { - //if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - }) + if (next_id == this.db.songs.length) throw new Error("Won't roll backwards to last song") + new_song = this.db.songs.find((song) => song.id == next_id)! + } + const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() + await this.try_new_song(url) + await this.try_play() + //if (this.current_song) this.played_history.push(this.current_song) + this.current_song = new_song + this.current_song_id = new_song.id! } + /** * Will loop back to first song if already on the last. * If history present, uses that, if not, relies on Song ID directly from DB */ - previous_song_async() { - return new Promise((resolve, reject) => { + async try_previous_song_looping() { + let new_song: Song + if (this.played_history.length > 0) { + new_song = this.played_history.pop()! + } else { + let id_i = -1; + while (this.db.songs[++id_i].id! < this.current_song_id); + let next_id = --id_i; - let new_song: Song - if (this.played_history.length > 0) { - new_song = this.played_history.pop()! - } else { - let id_i = -1; - while (this.db.songs[++id_i].id! < this.current_song_id); - let next_id = --id_i; - - if (next_id == -1) next_id = this.db.songs[this.db.songs.length - 1].id! - new_song = this.db.songs.find((song) => song.id == next_id)! - } - const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() - this.try_new_song_async(url).then( - () => { - this.try_play_async().then((s) => { - //if (this.current_song) this.played_history.push(this.current_song) - this.current_song = new_song - this.current_song_id = new_song.id! - resolve(s) - }, (e) => reject(e)) - }, - (e) => reject(e) - ) - }) + if (next_id == -1) next_id = this.db.songs[this.db.songs.length - 1].id! + new_song = this.db.songs.find((song) => song.id == next_id)! + } + const url = this.options?.use_only_pathname_url ? new_song.url.pathname : new_song.url.toString() + await this.try_new_song(url) + await this.try_play() + //if (this.current_song) this.played_history.push(this.current_song) + this.current_song = new_song + this.current_song_id = new_song.id! } + /** * won't tell you if the play was successful, won't loop back to last song if already on the first and will throw error if attempted. * If history present, uses that, if not, relies on Song ID directly from DB */ - try_previous_song() { + previous_song() { let new_song: Song if (this.played_history.length > 0) { new_song = this.played_history.pop()! @@ -293,11 +233,12 @@ class Euterpe extends MusicPlayer { this.current_song_id = new_song.id! this.current_song = new_song } + /** * won't tell you if the play was successful & will loop back to last song if already on the first. * If queue present, uses that, if not, relies on Song ID directly from DB */ - previous_song() { + previous_song_looping() { let new_song: Song if (this.played_history.length > 0) { new_song = this.played_history.pop()! @@ -316,6 +257,7 @@ class Euterpe extends MusicPlayer { this.current_song_id = new_song.id! this.current_song = new_song } + /** * Takes the song data from current song if no song ID is specified. Will return "ID - ID" if ID and current song doesn't exist * @returns {ARTIST}, {ARTIST2}... - {SONG NAME} ({REMIX ARTIST}, {REMIX ARTIST2}... remix) diff --git a/packages/player-web-test/src/main.ts b/packages/player-web-test/src/main.ts index 115557f..7d00c1f 100644 --- a/packages/player-web-test/src/main.ts +++ b/packages/player-web-test/src/main.ts @@ -4,12 +4,12 @@ const music_player_builder = new MusicPlayerBuilder(audio_el) const music_player = music_player_builder.build() music_player.change_volume(1) -music_player.try_new_song_async(encodeURI("http://127.0.0.1:4200/nuphory - NVISION (EXTENDED MIX).ogg")) +music_player.try_new_song(encodeURI("http://" + window.location.host + "/nuphory - NVISION (EXTENDED MIX).ogg")) .then(() => { let is_seeking = false document.querySelector("#play")?.addEventListener("click", () => { //const analyser_node = music_player_builder.add_analyser() - music_player.play_async() + music_player.try_play() .then(() => { console.log("Playing!") }, (e) => alert("Failed to play, " + e)) }) document.querySelector("#pause")?.addEventListener("click", () => { @@ -25,7 +25,7 @@ music_player.try_new_song_async(encodeURI("http://127.0.0.1:4200/nuphory - NVISI music_player.mute_toggle() }) document.querySelector("#toggle-play")?.addEventListener("click", () => { - music_player.play_toggle_async().then((s) => console.log("toggled play/pause"), (e) => alert("failed to toggle pause/play!" + e)) + music_player.try_play_toggle().then((s) => console.log("toggled play/pause"), (e) => alert("failed to toggle pause/play!" + e)) }) document.querySelector("#volume")?.addEventListener("input", (e) => { music_player.change_volume(e.target?.valueAsNumber) @@ -34,22 +34,25 @@ music_player.try_new_song_async(encodeURI("http://127.0.0.1:4200/nuphory - NVISI is_seeking = true; }) document.querySelector("#seek")?.addEventListener("mouseup", (e) => { - music_player.try_seek_async(e.target?.valueAsNumber).then(() => { console.log("seeked to " + e.target?.valueAsNumber) }, () => { + try { + music_player.try_seek(e.target?.valueAsNumber) + console.log("seeked to " + e.target?.valueAsNumber) + } catch (e) { alert("Failed seeking! " + e) - }) + } is_seeking = false }) // Subscriptions to AudioContext changes, eg. time.. - music_player.subscribe_to_formatted_duration_time((time) => { - document.querySelector("#duration").innerHTML = time - document.querySelector("#seek").max = "" + music_player.get_current_duration() + music_player.on_duration_formatted((time) => { + document.querySelector("#duration")!.innerHTML = time + document.querySelector("#seek")!.max = "" + music_player.current_song_duration }) - music_player.subscribe_to_formatted_current_time_tick((time) => { - document.querySelector("#current").innerHTML = time + music_player.on_time_tick_formatted((time) => { + document.querySelector("#current")!.innerHTML = time }) - music_player.subscribe_to_time_tick((time) => { + music_player.on_time_tick((time) => { if (is_seeking) return - document.querySelector("#seek").value = "" + time + document.querySelector("#seek")!.value = "" + time }) }, (e) => console.log(e)) \ No newline at end of file diff --git a/packages/player/package.json b/packages/player/package.json index 8da3a17..2d5f86e 100644 --- a/packages/player/package.json +++ b/packages/player/package.json @@ -1,6 +1,6 @@ { "name": "@euterpe.js/player", - "version": "1.0.10", + "version": "2.0.0", "type": "module", "description": "A simple, safe AudioContext web music player", "main": "./src/index.js", diff --git a/packages/player/src/index.ts b/packages/player/src/index.ts index 2e2a44a..ee37702 100644 --- a/packages/player/src/index.ts +++ b/packages/player/src/index.ts @@ -118,73 +118,50 @@ export class MusicPlayer { this.volume = this.gain.gain.value = volume_i } /** - * Safer seek_async. Normal seek will try to start the player even if the track hasn't started yet, or was previously suspended/closed + * Safer seek_async. Normal seek will try to start the player even if the track hasn't started yet, or was previously suspended/closed. + * Will also resume playback if player is paused (by finishing the song etc) + * @throws if "Can't seek - Audiocontext is not running" */ - try_seek_async(new_time: number) { - return new Promise((resolve, reject) => { - if (this.track.context.state !== "running") { - this.is_playing = false - reject(new Error("Can't seek - track not playing")) - } - this.audio_element.currentTime = new_time - resolve(null) - }) + async try_seek(new_time: number) { + if (this.audio_context.state !== "running") { + this.is_playing = false + throw new Error("Can't seek - audioContext not running, audio_context.state : " + this.audio_context.state) + } + if (this.audio_element.paused) await this.try_play() + this.audio_element.currentTime = new_time } + /** * Unsafe, throws error if failed. Use try_seek_async or seek_async unless you don't care about the result. */ seek(new_time: number) { this.audio_element.currentTime = new_time } + /** * Safer play_toggle_async. Normal play_toggle will try to start the player even if the track hasn't started yet, or was previously suspended/closed + * @throws Error if playback failed */ - try_play_toggle_async() { - return new Promise((resolve, reject) => { - if (this.track.context.state !== "running") { - this.audio_context.resume().then(undefined, (e) => - reject(e)) - } - if (this.audio_element.paused) { - this.audio_element.play().then((s) => { - this.is_playing = true - resolve(s) - }, (r) => { - this.is_playing = false - reject(r) - }) - } else { - this.audio_element.pause() + async try_play_toggle() { + if (this.audio_context.state !== "running") { + await this.audio_context.resume() + } + if (this.audio_element.paused) { + try { + await this.audio_element.play() + this.is_playing = true + } catch (e) { this.is_playing = false - resolve(null) + throw e } - }) + } else { + this.audio_element.pause() + this.is_playing = false + } } + /** - * Can try to play even if the audio context was suspended or closed. Best to use try_play_toggle_async() - */ - play_toggle_async() { - return new Promise((resolve, reject) => { - if (this.track.context.state !== "running") { - this.audio_context.resume() - } - if (this.audio_element.paused) { - this.audio_element.play().then((s) => { - this.is_playing = true - resolve(s) - }, (r) => { - this.is_playing = false - reject(r) - }) - } else { - this.audio_element.pause() - this.is_playing = false - resolve(null) - } - }) - } - /** - * Unsafe, throws error if failed. Use play_toggle_async or try_play_toggle_async unless you don't care about the result. + * Unsafe, can just fail. Use try_play_toggle unless you don't care about the result. */ play_toggle() { if (this.audio_element.paused) { @@ -198,59 +175,37 @@ export class MusicPlayer { this.audio_element.pause() } } + /** - * Safer play_async. Normal play will try to start the player even if the track hasn't started yet, or was previously suspended/closed + * Safer play. Normal play will try to start the player even if the track hasn't started yet, or was previously suspended/closed + * @throws Error if playback failed */ - try_play_async() { - return new Promise((resolve, reject) => { - if (this.is_playing) resolve(Error("Already playing")) - if (this.track.context.state !== "running") { - this.audio_context.resume().then(() => { - this.audio_element.play().then((s) => { - this.is_playing = true - resolve(s) - }, (r) => { - this.is_playing = false - reject(r) - }) - }, (e) => - reject(new Error("Context closed or suspended" + JSON.stringify(e)))) - } else { - this.audio_element.play().then((s) => { - this.is_playing = true - resolve(s) - }, (r) => { - this.is_playing = false - reject(r) - }) - } - }) - } - /** - * Will try to play even if the audio context was suspended or closed. Best to use try_play_async() - */ - play_async() { - return new Promise((resolve, reject) => { - if (this.is_playing) resolve(Error("Already playing")) - this.audio_element.play().then((s) => { + async try_play() { + if (this.is_playing) return + if (this.audio_context.state !== "running") { + await this.audio_context.resume() + } + if (this.audio_element.paused) { + try { + await this.audio_element.play() this.is_playing = true - resolve(s) - }, (r) => { + } catch (e) { this.is_playing = false - reject(r) - }) - }) + throw e + } + } } + /** - * Unsafe, throws error if failed. Use play_async or try_play_async unless you don't care about the result. + * Unsafe, can just fail. Use play_async or try_play_async unless you don't care about the result. */ play() { if (this.is_playing) return - this.audio_element.play().catch((r) => { + this.audio_element.play().catch(() => { this.is_playing = false - throw r }) } + /** * Safe technically. Even if audioContext is suspended or closed it will pretend that it paused. */ @@ -258,35 +213,35 @@ export class MusicPlayer { this.audio_element.pause() this.is_playing = false } + /** * Will only load metadata of the upcoming song. Need to call try_play_async() afterwards to start the playback + * @throws Error if adding element throwed Error or Stalled */ - try_new_song_async(path: string) { - return new Promise((resolve, reject) => { + try_new_song(path: string) { + return new Promise((resolve, reject) => { this.audio_element.src = this.current_song_path = path //Found out today about this. Such a nice new way to mass remove event listeners! const controller = new AbortController(); this.audio_element.addEventListener("canplay", function canplay_listener(s) { controller.abort() - resolve(s) }, { signal: controller.signal }) this.audio_element.addEventListener("error", function error_listener(e) { - controller.abort() - reject(e) + controller.abort("new src error") }, { signal: controller.signal }) this.audio_element.addEventListener("stalled", function stalled_listener(e) { - controller.abort() - reject(e) + controller.abort("new src stalled") }, { signal: controller.signal }) //once aborted, try to set current_song_duration - controller.signal.addEventListener("abort", () => { + controller.signal.addEventListener("abort", (r) => { this.current_song_duration = this.audio_element.duration + if (typeof controller.signal.reason == "string") reject(new Error(controller.signal.reason)) + resolve() }) - this.is_playing = false }) } diff --git a/test.js b/test.js new file mode 100644 index 0000000..93cede9 --- /dev/null +++ b/test.js @@ -0,0 +1,47 @@ +class AudioContexthehe { + state = "suspended" + constructor() { } + resume() { + return new Promise((resolve, reject) => { + this.state = "running" + resolve() + }) + } +} +class AudioElementHehe { + constructor() { } + play() { + return new Promise((resolve, reject) => { + console.log("playing!") + resolve() + }) + } + pause() { + console.log("Pausing!") + } +} +const audio_context = new AudioContexthehe +const audio_element = new AudioElementHehe +let is_playing = false +try_play_toggle_async() + +function try_play_toggle_async() { + return new Promise((resolve, reject) => { + if (audio_context.state !== "running") { + audio_context.resume().catch((e) => reject(e)) + } + if (audio_element.paused) { + audio_element.play().then((s) => { + is_playing = true + resolve(s) + }, (r) => { + is_playing = false + reject(r) + }) + } else { + audio_element.pause() + is_playing = false + resolve(null) + } + }) +} \ No newline at end of file