From 8d5302874ce8bfc1b90779e78ec5de2311df1579 Mon Sep 17 00:00:00 2001 From: theADAMJR Date: Mon, 21 Dec 2020 17:17:30 +0000 Subject: [PATCH] Player UI #9 - Track Control --- .../assets/js/music/html-music-wrapper.js | 37 ++++++++++++++++- dashboard/assets/js/music/music-wrapper.js | 40 +++++++++++++++++++ dashboard/assets/js/music/music.js | 10 +++++ dashboard/routes/music-routes.js | 33 +++++++++++++++ dashboard/views/dashboard/modules/music.pug | 4 +- 5 files changed, 120 insertions(+), 4 deletions(-) diff --git a/dashboard/assets/js/music/html-music-wrapper.js b/dashboard/assets/js/music/html-music-wrapper.js index 886e9ce..1eaa143 100644 --- a/dashboard/assets/js/music/html-music-wrapper.js +++ b/dashboard/assets/js/music/html-music-wrapper.js @@ -1,6 +1,14 @@ class HTMLMusicWrapper { #music; + get currentTimestamp() { + const position = this.#music.position; + + const minutes = Math.floor(position / 60).toString().padStart(2, '0'); + const seconds = Math.floor(position - (minutes * 60)).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; + } + set apiError(error) { if (!error) return $('#musicAPIError').addClass('d-none'); @@ -11,13 +19,33 @@ class HTMLMusicWrapper { constructor(musicClient) { this.#music = musicClient; + + setInterval(() => this.#updateSeeker(), 1000); + } + + #updateSeeker() { + if (!this.#music.isPlaying || this.#music.isPaused) return; + + this.#music.position++; + + $('#seekTrack input').val(this.#music.position); + $('.current').text(this.currentTimestamp); } updateList() { $('.now-playing').html(this.#nowPlaying()); + const track = (this.#music.isPlaying) ? this.#music.list[0] : null; + if (track) { + $('.current').text(this.currentTimestamp); + $('.duration').text(track.duration.timestamp); + $('#seekTrack input').attr('max', track.duration.seconds); + } else { + $('.current, .duration').text(`00:00`); + } + $('.track-list').html( - (this.#music.list.length <= 0) + (!this.#music.isPlaying) ? '

No tracks here.

' : this.#music.list .map(this.#htmlTrack) @@ -30,8 +58,13 @@ class HTMLMusicWrapper { }); } + toggle() { + $('#toggleTrack i').toggleClass('fa-pause'); + $('#toggleTrack i').toggleClass('fa-play'); + } + #nowPlaying() { - if (this.#music.list.length <= 0) return ``; + if (!this.#music.isPlaying) return ``; const track = this.#music.list[0]; return ` diff --git a/dashboard/assets/js/music/music-wrapper.js b/dashboard/assets/js/music/music-wrapper.js index dccdb8f..624d7ff 100644 --- a/dashboard/assets/js/music/music-wrapper.js +++ b/dashboard/assets/js/music/music-wrapper.js @@ -2,7 +2,13 @@ class MusicWrapper { #endpoint = `/api/guilds/${guildId}/music`; #html = new HTMLMusicWrapper(this); + isPaused = $('#toggleTrack i').hasClass('fa-play'); list = []; + position = +$('#seekTrack input').val(); + + get isPlaying() { + return this.list.length > 0; + } async #fetch(action) { try { @@ -32,10 +38,44 @@ class MusicWrapper { try { await this.#fetch(`stop`); this.#html.apiError = null; + this.position = 0; } catch {} await this.updateList(); } + async toggle() { + try { + await this.#fetch(`toggle`); + this.#html.apiError = null; + } catch {} + } + + async setVolume(value) { + try { + await this.#fetch(`volume?v=${value}`); + this.#html.apiError = null; + } catch {} + } + + async seek(to) { + try { + await this.#fetch(`seek?to=${to}`); + this.position = to; + + this.#html.apiError = null; + } catch {} + } + + async toggle() { + try { + await this.#fetch(`toggle`); + this.isPaused = !this.isPaused; + + this.#html.apiError = null; + this.#html.toggle(); + } catch {} + } + async remove(index) { try { const list = await this.#fetch(`remove?i=${index}`); diff --git a/dashboard/assets/js/music/music.js b/dashboard/assets/js/music/music.js index 4ed0249..d49ed16 100644 --- a/dashboard/assets/js/music/music.js +++ b/dashboard/assets/js/music/music.js @@ -3,10 +3,20 @@ $(async () => { await music.updateList(); $('#skipTrack').on('click', () => music.skip()); + $('#toggleTrack').on('click', () => music.toggle()); $('#shuffleList').on('click', () => music.shuffle()); $('#stopTrack').on('click', () => music.stop()); $('#trackSearch').on('click', async () => { const query = $('.q-control input').val(); await music.play(query); }); + + $('#seekTrack input').on('input', async function() { + const to = +$(this).val(); + await music.seek(to); + }); + $('#volume input').on('input', async function() { + const value = +$(this).val(); + await music.setVolume(value); + }); }); diff --git a/dashboard/routes/music-routes.js b/dashboard/routes/music-routes.js index cd2869e..cb529cf 100644 --- a/dashboard/routes/music-routes.js +++ b/dashboard/routes/music-routes.js @@ -22,6 +22,39 @@ router.get('/stop', async (req, res) => { } }); +router.get('/toggle', async (req, res) => { + try { + const player = res.locals.player; + (player.isPaused) + ? await player.resume() + : await player.pause(); + + res.json({ message: 'Success' }); + } catch (error) { + sendError(res, error); + } +}); + +router.get('/volume', async (req, res) => { + try { + await res.locals.player.setVolume(+req.query.v); + + res.json({ message: 'Success' }); + } catch (error) { + sendError(res, error); + } +}); + +router.get('/seek', async (req, res) => { + try { + await res.locals.player.seek(+req.query.to); + + res.json({ message: 'Success' }); + } catch (error) { + sendError(res, error); + } +}); + router.get('/list', async (req, res) => { try { res.json(res.locals.player.q.items); diff --git a/dashboard/views/dashboard/modules/music.pug b/dashboard/views/dashboard/modules/music.pug index fdc8351..ff67740 100644 --- a/dashboard/views/dashboard/modules/music.pug +++ b/dashboard/views/dashboard/modules/music.pug @@ -20,9 +20,9 @@ section#musicModule.module.container.px-5 button#shuffleList.btn.text-gradient #[i.fas.fa-random] #volume.col-3 i.fas.fa-volume-up.text-gradient - input.form-control(type='range', value='0') + input.form-control(type='range', value='0', max='1', step='0.1') #seekTrack - input.form-control(type='range', value=(player.position || 0) / 1000) + input.form-control(type='range', value=(Math.floor(player.position || 0) / 1000)) .position span.current 00:00 span.text-muted.px-1 /