From 5d91aad2f27c6b5c567421b946001f7b1bb4423d Mon Sep 17 00:00:00 2001 From: Araozu Date: Fri, 18 Oct 2024 23:53:14 -0500 Subject: [PATCH] refactor: refactor music player logic --- src/modules/album/album.go | 13 ++-- src/utils/utils.templ | 125 ++++++++++++++++++++++++------------- src/utils/utils_templ.go | 26 ++++---- 3 files changed, 103 insertions(+), 61 deletions(-) diff --git a/src/modules/album/album.go b/src/modules/album/album.go index fade5d1..0cd2daf 100644 --- a/src/modules/album/album.go +++ b/src/modules/album/album.go @@ -3,6 +3,7 @@ package album import ( "acide/src/modules/song" "acide/src/utils" + "bytes" "encoding/json" "net/http" "sync" @@ -89,9 +90,9 @@ func albumPage(c echo.Context) error { } // convert the song list to json - clientSons := make([]ClientSong, len(songs)) + clientSongs := make([]ClientSong, len(songs)) for i, song := range songs { - clientSons[i] = ClientSong{ + clientSongs[i] = ClientSong{ Title: song.Title, Artist: song.Artist, AlbumId: album.ID, @@ -99,11 +100,15 @@ func albumPage(c echo.Context) error { SongId: song.ID, } } - clientSongsJson, err := json.Marshal(clientSons) - if err != nil { + + var buff bytes.Buffer + enc := json.NewEncoder(&buff) + enc.SetEscapeHTML(false) + if err := enc.Encode(&clientSongs); err != nil { log.Printf("Error marshaling clientSongs: %s", err) return err } + clientSongsJson := buff.String() return utils.RenderTempl(c, http.StatusOK, albumTempl(albumId, album, songs, string(clientSongsJson))) } diff --git a/src/utils/utils.templ b/src/utils/utils.templ index eadfa55..6fd9cf6 100644 --- a/src/utils/utils.templ +++ b/src/utils/utils.templ @@ -88,33 +88,73 @@ templ MusicPlayer() { volume: 0.1, playing: false, loading: false, + progress: 0, + // sets the queue, and plays the song at idx replaceQueueAndPlayAt(queue, idx) { this.queue = queue; this.idx = idx; - this.play(); - }, - // Plays the song at the current position - async play() { - const songId = this.queue[this.idx].songId; + this.play(idx); + + // setup the preload and progress listener + setInterval(() => this.checkDuration(), 1000); + }, + + // Plays the song at the passed idx, sets this.idx to that, + // and plays a preloaded song + // + // If preloaded=true, this function will assume that it is the + // next song. It will trust that idx is correct. + async play(idx) { + const preloaded = this.nextSound !== null && idx === this.idx + 1; + + // if a song is currently playing + // then fade it out before playing the next sound + if (this.playing === true + && this.currentSound !== null + ) { + // this will not trigger when next() is called, + // because next() sets this.playing=false - if (this.currentSound !== null) { this.currentSound.fade(this.volume, 0.0, 250); await wait(250); - this.currentSound.unload(); } - const sound = new Howl({ - src: `https://navidrome.araozu.dev/rest/stream.view?id=${songId}&v=1.13.0&c=music-to-go&u=fernando&s=49805d&t=4148cd1c83ae1bd01334facf4e70a947`, - html5: true, - volume: this.volume, - }) - this.loading = true; - sound.play(); - let preloadInterval; - sound.once("load", () => { + this.currentSound?.unload?.(); + this.playing = false; + this.currentSound = null; + this.idx = idx; + + // if a song is preloaded, assume it's the next song and play it + if (preloaded === true && this.nextSound !== null) { + this.currentSound = this.nextSound; + this.nextSound = null; + this.currentSound.play(); + this.playing = true; + } else { + // otherwise, load the song at idx and play it + const songId = this.queue[idx].songId; + + const sound = new Howl({ + src: `https://navidrome.araozu.dev/rest/stream.view?id=${songId}&v=1.13.0&c=music-to-go&u=fernando&s=49805d&t=4148cd1c83ae1bd01334facf4e70a947`, + html5: true, + volume: this.volume, + }); + this.currentSound = sound; + this.loading = true; + sound.play(); + sound.once("load", () => { + this.loading = false; + this.playing = true; + }); + } + + // set-up preloading for the next song + const sound = this.currentSound; + sound.once("play", () => { const length = sound.duration(); const targetLength = length - 5; + let preloadInterval; preloadInterval = setInterval(() => { const pos = sound.seek(); if (pos > targetLength) { @@ -122,22 +162,37 @@ templ MusicPlayer() { clearInterval(preloadInterval); } }, 1000); - this.loading = false; - this.playing = true; }); + + // set-up playing the next song when the current finishes sound.once("end", () => { this.playing = false; this.next(); }); this.currentSound = sound; }, - async playNext() { - this.currentSound?.unload(); - this.nextSound.play(); - this.playing = true; - this.currentSound = this.nextSound; - this.nextSound = null; + + // checks the duration of the playing song and: + // - updates the song progress (0-100%) + // - begins preloading + checkDuration() { + const sound = this.currentSound; + if (this.playing) { + const length = sound.duration(); + if (length <= 0) return; + + const position = sound.seek(); + + // preload 5s before the song ends + if (position >= length - 5 && this.nextSound === null) { + this.preload(); + } + + // update the song progress percentage + this.progress = Math.floor((position * 100) / length); + } }, + togglePlayPause() { if (this.playing === true) { this.playing = false; @@ -149,8 +204,7 @@ templ MusicPlayer() { }, next() { if (this.idx + 1 < this.queue.length) { - this.idx += 1; - this.playNext(); + this.play(this.idx + 1); } }, preload() { @@ -166,30 +220,13 @@ templ MusicPlayer() { volume: 0, preload: true, }); - // Attempt to play immediately the song, immediately pause it, rewind it and set volume back up + // Attempt to immediately play the song, immediately pause it, rewind it and set volume back up nextSound.play(); - let preloadInterval; nextSound.once("load", () => { nextSound.pause(); nextSound.seek(0); nextSound.volume(this.volume); - - const length = nextSound.duration(); - const targetLength = length - 5; - preloadInterval = setInterval(() => { - const pos = nextSound.seek(); - if (pos > targetLength) { - this.preload(); - clearInterval(preloadInterval); - } - }, 1000); - this.loading = false; - this.playing = true; - }); - nextSound.once("end", () => { - this.playing = false; - this.next(); }); this.nextSound = nextSound; } diff --git a/src/utils/utils_templ.go b/src/utils/utils_templ.go index 688ecbb..a91ebdc 100644 --- a/src/utils/utils_templ.go +++ b/src/utils/utils_templ.go @@ -100,7 +100,7 @@ func MusicPlayer() templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -181,7 +181,7 @@ func playIcon(size int) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 250, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 287, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -194,7 +194,7 @@ func playIcon(size int) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 251, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 288, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -236,7 +236,7 @@ func pauseIcon(size int) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 271, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 308, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -249,7 +249,7 @@ func pauseIcon(size int) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 272, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 309, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -291,7 +291,7 @@ func skipForwardIcon(size int) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 280, Col: 67} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 317, Col: 67} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -304,7 +304,7 @@ func skipForwardIcon(size int) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 280, Col: 97} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 317, Col: 97} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -346,7 +346,7 @@ func circleNotchIcon(size int) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 294, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 331, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -359,7 +359,7 @@ func circleNotchIcon(size int) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 295, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 332, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -401,7 +401,7 @@ func playlistIcon(size int) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 310, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 347, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -414,7 +414,7 @@ func playlistIcon(size int) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 311, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 348, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -456,7 +456,7 @@ func caretDoubleDownIcon(size int) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 325, Col: 28} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 362, Col: 28} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -469,7 +469,7 @@ func caretDoubleDownIcon(size int) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(size)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 326, Col: 29} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `src/utils/utils.templ`, Line: 363, Col: 29} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil {