refactor: refactor music player logic

This commit is contained in:
Araozu 2024-10-18 23:53:14 -05:00
parent da39b305c8
commit 5d91aad2f2
3 changed files with 103 additions and 61 deletions

View File

@ -3,6 +3,7 @@ package album
import ( import (
"acide/src/modules/song" "acide/src/modules/song"
"acide/src/utils" "acide/src/utils"
"bytes"
"encoding/json" "encoding/json"
"net/http" "net/http"
"sync" "sync"
@ -89,9 +90,9 @@ func albumPage(c echo.Context) error {
} }
// convert the song list to json // convert the song list to json
clientSons := make([]ClientSong, len(songs)) clientSongs := make([]ClientSong, len(songs))
for i, song := range songs { for i, song := range songs {
clientSons[i] = ClientSong{ clientSongs[i] = ClientSong{
Title: song.Title, Title: song.Title,
Artist: song.Artist, Artist: song.Artist,
AlbumId: album.ID, AlbumId: album.ID,
@ -99,11 +100,15 @@ func albumPage(c echo.Context) error {
SongId: song.ID, 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) log.Printf("Error marshaling clientSongs: %s", err)
return err return err
} }
clientSongsJson := buff.String()
return utils.RenderTempl(c, http.StatusOK, albumTempl(albumId, album, songs, string(clientSongsJson))) return utils.RenderTempl(c, http.StatusOK, albumTempl(albumId, album, songs, string(clientSongsJson)))
} }

View File

@ -88,33 +88,73 @@ templ MusicPlayer() {
volume: 0.1, volume: 0.1,
playing: false, playing: false,
loading: false, loading: false,
progress: 0,
// sets the queue, and plays the song at idx
replaceQueueAndPlayAt(queue, idx) { replaceQueueAndPlayAt(queue, idx) {
this.queue = queue; this.queue = queue;
this.idx = idx; this.idx = idx;
this.play(); this.play(idx);
},
// Plays the song at the current position // setup the preload and progress listener
async play() { setInterval(() => this.checkDuration(), 1000);
const songId = this.queue[this.idx].songId; },
// 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); this.currentSound.fade(this.volume, 0.0, 250);
await wait(250); await wait(250);
this.currentSound.unload();
} }
const sound = new Howl({ this.currentSound?.unload?.();
src: `https://navidrome.araozu.dev/rest/stream.view?id=${songId}&v=1.13.0&c=music-to-go&u=fernando&s=49805d&t=4148cd1c83ae1bd01334facf4e70a947`, this.playing = false;
html5: true, this.currentSound = null;
volume: this.volume, this.idx = idx;
})
this.loading = true; // if a song is preloaded, assume it's the next song and play it
sound.play(); if (preloaded === true && this.nextSound !== null) {
let preloadInterval; this.currentSound = this.nextSound;
sound.once("load", () => { 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 length = sound.duration();
const targetLength = length - 5; const targetLength = length - 5;
let preloadInterval;
preloadInterval = setInterval(() => { preloadInterval = setInterval(() => {
const pos = sound.seek(); const pos = sound.seek();
if (pos > targetLength) { if (pos > targetLength) {
@ -122,22 +162,37 @@ templ MusicPlayer() {
clearInterval(preloadInterval); clearInterval(preloadInterval);
} }
}, 1000); }, 1000);
this.loading = false;
this.playing = true;
}); });
// set-up playing the next song when the current finishes
sound.once("end", () => { sound.once("end", () => {
this.playing = false; this.playing = false;
this.next(); this.next();
}); });
this.currentSound = sound; this.currentSound = sound;
}, },
async playNext() {
this.currentSound?.unload(); // checks the duration of the playing song and:
this.nextSound.play(); // - updates the song progress (0-100%)
this.playing = true; // - begins preloading
this.currentSound = this.nextSound; checkDuration() {
this.nextSound = null; 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() { togglePlayPause() {
if (this.playing === true) { if (this.playing === true) {
this.playing = false; this.playing = false;
@ -149,8 +204,7 @@ templ MusicPlayer() {
}, },
next() { next() {
if (this.idx + 1 < this.queue.length) { if (this.idx + 1 < this.queue.length) {
this.idx += 1; this.play(this.idx + 1);
this.playNext();
} }
}, },
preload() { preload() {
@ -166,30 +220,13 @@ templ MusicPlayer() {
volume: 0, volume: 0,
preload: true, 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(); nextSound.play();
let preloadInterval; let preloadInterval;
nextSound.once("load", () => { nextSound.once("load", () => {
nextSound.pause(); nextSound.pause();
nextSound.seek(0); nextSound.seek(0);
nextSound.volume(this.volume); 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; this.nextSound = nextSound;
} }

File diff suppressed because one or more lines are too long