Stupid callbacks xd.Reworked to proper await/async
This commit is contained in:
parent
dd4b45103b
commit
a5df675913
7 changed files with 243 additions and 293 deletions
|
@ -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)
|
||||
)
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -23,28 +23,26 @@ 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) => {
|
||||
async try_preload_song(id: number) {
|
||||
const next = this.db.songs.find((song) => song!.id == id)
|
||||
if (!next) reject(new Error(`Song with id ${id} doesn't exist`))
|
||||
if (!next) throw new Error(`Song with id ${id} doesn't exist`)
|
||||
else {
|
||||
this.try_new_song_async(next.url.pathname).then((s) => {
|
||||
await this.try_new_song(next.url.pathname)
|
||||
this.current_song = next
|
||||
resolve(s)
|
||||
}, (e) => reject(e))
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 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) => {
|
||||
async try_next_song() {
|
||||
let new_song: Song
|
||||
if (this.queue.length > 0) {
|
||||
new_song = this.queue.shift()!
|
||||
|
@ -53,30 +51,22 @@ class Euterpe extends MusicPlayer {
|
|||
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"))
|
||||
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()
|
||||
this.try_new_song_async(url).then(
|
||||
() => {
|
||||
this.try_play_async().then((s) => {
|
||||
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!
|
||||
resolve(s)
|
||||
}, (e) => reject(e))
|
||||
},
|
||||
(e) => reject(e)
|
||||
)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
async try_next_song_looping() {
|
||||
let new_song: Song
|
||||
if (this.queue.length > 0) {
|
||||
new_song = this.queue.shift()!
|
||||
|
@ -89,24 +79,18 @@ class Euterpe extends MusicPlayer {
|
|||
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) => {
|
||||
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!
|
||||
resolve(s)
|
||||
}, (e) => reject(e))
|
||||
},
|
||||
(e) => reject(e)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,58 +131,28 @@ 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 {
|
||||
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()
|
||||
|
@ -206,13 +160,13 @@ class Euterpe extends MusicPlayer {
|
|||
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) => {
|
||||
async try_previous_song() {
|
||||
let new_song: Song
|
||||
if (this.played_history.length > 0) {
|
||||
new_song = this.played_history.pop()!
|
||||
|
@ -221,30 +175,22 @@ class Euterpe extends MusicPlayer {
|
|||
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"))
|
||||
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()
|
||||
this.try_new_song_async(url).then(
|
||||
() => {
|
||||
this.try_play_async().then((s) => {
|
||||
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!
|
||||
resolve(s)
|
||||
}, (e) => reject(e))
|
||||
},
|
||||
(e) => reject(e)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()!
|
||||
|
@ -257,24 +203,18 @@ class Euterpe extends MusicPlayer {
|
|||
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) => {
|
||||
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!
|
||||
resolve(s)
|
||||
}, (e) => reject(e))
|
||||
},
|
||||
(e) => reject(e)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
|
|
|
@ -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))
|
|
@ -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",
|
||||
|
|
|
@ -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") {
|
||||
async try_seek(new_time: number) {
|
||||
if (this.audio_context.state !== "running") {
|
||||
this.is_playing = false
|
||||
reject(new Error("Can't seek - track not playing"))
|
||||
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
|
||||
resolve(null)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
async try_play_toggle() {
|
||||
if (this.audio_context.state !== "running") {
|
||||
await this.audio_context.resume()
|
||||
}
|
||||
if (this.audio_element.paused) {
|
||||
this.audio_element.play().then((s) => {
|
||||
try {
|
||||
await this.audio_element.play()
|
||||
this.is_playing = true
|
||||
resolve(s)
|
||||
}, (r) => {
|
||||
} catch (e) {
|
||||
this.is_playing = false
|
||||
reject(r)
|
||||
})
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
this.audio_element.pause()
|
||||
this.is_playing = false
|
||||
resolve(null)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
})
|
||||
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
|
||||
} catch (e) {
|
||||
this.is_playing = false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) => {
|
||||
this.is_playing = true
|
||||
resolve(s)
|
||||
}, (r) => {
|
||||
this.is_playing = false
|
||||
reject(r)
|
||||
})
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 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<void>((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
|
||||
})
|
||||
}
|
||||
|
|
47
test.js
Normal file
47
test.js
Normal file
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue