From fb5559bdd9d1cbbae331c46ccf494e0a49ea9b03 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 03:33:46 +0300 Subject: [PATCH 01/14] refactor(ytdl): format and renames --- ytdl/main.py | 75 ++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 34 deletions(-) diff --git a/ytdl/main.py b/ytdl/main.py index 37416e95..4bce9b66 100644 --- a/ytdl/main.py +++ b/ytdl/main.py @@ -1,14 +1,13 @@ +import pytube from flask import Flask import mariadb import os import os.path from pytube import YouTube, exceptions as pytube_exceptions import signal -import sys import threading import time from yt_dlp import YoutubeDL -from yt_dlp.utils import DownloadError ############################################################################################################################################################################################################################## @@ -20,14 +19,15 @@ def get_env(key) -> str: val = os.environ.get(key) if val is None or val == "": - print(f"Missing {val} suka") + print(f"Missing {key} suka") exit(1) return val -DB_NAME = get_env("DB_NAME") -DB_HOST = get_env("DB_HOST") -DB_USERNAME = get_env("DB_USERNAME") -DB_PASSWORD = get_env("DB_PASSWORD") + +DB_NAME = get_env("DB_NAME") +DB_HOST = get_env("DB_HOST") +DB_USERNAME = get_env("DB_USERNAME") +DB_PASSWORD = get_env("DB_PASSWORD") DOWNLOAD_PATH = get_env("YOUTUBE_MUSIC_DOWNLOAD_PATH") ############################################################################################################################################################################################################################## @@ -38,13 +38,14 @@ def get_env(key) -> str: conn = None + def open_db_conn(): try: db_host = DB_HOST db_port = 3306 if ":" in db_host: db_host = DB_HOST[:DB_HOST.index(":")] - db_port = int(DB_HOST[DB_HOST.index(":")+1:]) + db_port = int(DB_HOST[DB_HOST.index(":") + 1:]) global conn conn = mariadb.connect( user=DB_USERNAME, @@ -67,10 +68,10 @@ def update_song_status(id: str): cur.close() -def song_exists(id: str) -> bool: +def song_exists(song_yt_id: str) -> bool: try: cur = conn.cursor() - cur.execute("SELECT id FROM songs WHERE yt_id=? AND fully_downloaded=1", (id,)) + cur.execute("SELECT id FROM songs WHERE yt_id=? AND fully_downloaded=1", (song_yt_id,)) result = cur.fetchone() return result[0] if result else False except: @@ -96,11 +97,14 @@ def song_exists(id: str) -> bool: 3: "other youtube error", } -def download_yt_song(id) -> int: + + + +def download_yt_song(id: str) -> int: try: - YouTube("https://www.youtube.com/watch?v="+id) \ + YouTube("https://www.youtube.com/watch?v=" + id) \ .streams.filter(only_audio=True).first() \ - .download(output_path=DOWNLOAD_PATH, filename=id+".mp3") + .download(output_path=DOWNLOAD_PATH, filename=id + ".mp3") except pytube_exceptions.AgeRestrictedError: try: print(f"song with id {id} is age resticted, trying yt_dlp...") @@ -113,7 +117,7 @@ def download_yt_song(id) -> int: }], "outtmpl": f"{DOWNLOAD_PATH}/%(id)s.%(ext)s" }) - ytdl.download("https://www.youtube.com/watch?v="+id) + ytdl.download("https://www.youtube.com/watch?v=" + id) except: return 1 except pytube_exceptions.VideoUnavailable: @@ -125,6 +129,7 @@ def download_yt_song(id) -> int: return 0 + ############################################################################################################################################################################################################################## to_download_lock = threading.Lock() @@ -140,56 +145,57 @@ def background_task(): while not to_download_stop_event.is_set(): with to_download_lock: if to_download_queue: - id = to_download_queue.pop() - print(f"Downloading {id} from the queue.") - res = download_song(id) + song_yt_id = to_download_queue.pop() + print(f"Downloading {song_yt_id} from the queue.") + res = download_song(song_yt_id) if res != 0: - print(f"Error downloading {id}, error: {YT_ERROR[res]}") + print(f"Error downloading {song_yt_id}, error: {YT_ERROR[res]}") time.sleep(0.5) -def add_song_to_queue(id: str) -> int: +def add_song_to_queue(song_yt_id: str) -> int: """ add_song_to_queue adds a song's id to the download queue. """ with to_download_lock: - if song_exists(id): - print(f"The song with id {id} was already downloaded 😬") + if song_exists(song_yt_id): + print(f"The song with id {song_yt_id} was already downloaded 😬") return 0 - to_download_queue.add(id) - print(f"Added song {id} to the download queue.") + to_download_queue.add(song_yt_id) + print(f"Added song {song_yt_id} to the download queue.") return 0 -def download_song(id: str) -> int: +def download_song(song_yt_id: str) -> int: """ download_song downloads the given song's ids using yt_dlp, and returns the operation's status code. """ - if song_exists(id): - print(f"The song with id {id} was already downloaded 😬") + if song_exists(song_yt_id): + print(f"The song with id {song_yt_id} was already downloaded 😬") return 0 if not currently_downloading_stop_event.is_set(): with currently_downloading_lock: - print(f"Downloading song with id {id} ...") - while id in currently_downloading_queue: + print(f"Downloading song with id {song_yt_id} ...") + while song_yt_id in currently_downloading_queue: print("waiting suka") time.sleep(0.5) pass - currently_downloading_queue.add(id) - res = download_yt_song(id) - currently_downloading_queue.remove(id) - update_song_status(id) + currently_downloading_queue.add(song_yt_id) + res = download_yt_song(song_yt_id) + currently_downloading_queue.remove(song_yt_id) + update_song_status(song_yt_id) if res != 0: - print(f"error: {YT_ERROR[res]} when downloading {id}") + print(f"error: {YT_ERROR[res]} when downloading {song_yt_id}") return res - print("Successfully downloaded " + id) + print("Successfully downloaded " + song_yt_id) return 0 return 3 + thread = threading.Thread(target=background_task) thread.start() @@ -231,6 +237,7 @@ def close_server(arg1, arg2): conn.close() exit(0) + signal.signal(signal.SIGINT, close_server) signal.signal(signal.SIGTERM, close_server) From 9dff6a1f2f69b4a9b216522aa37f1fcb28a9073b Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 10:58:28 +0300 Subject: [PATCH 02/14] chore(ytdl): reduce docker container's size --- ytdl/Dockerfile | 32 ++++++++++---------------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/ytdl/Dockerfile b/ytdl/Dockerfile index a8ff9312..715240af 100644 --- a/ytdl/Dockerfile +++ b/ytdl/Dockerfile @@ -1,29 +1,17 @@ -FROM python:3.11-slim-bookworm AS build - -WORKDIR /app - -ADD requirements.txt /tmp -RUN apt-get update -y && \ - apt-get install -y gcc libmariadb-dev mariadb-client && \ - pip install --upgrade pip && \ - pip install --no-cache-dir -r /tmp/requirements.txt - -COPY . /app - - FROM python:3.11-alpine -FROM python:3.11-slim-bookworm +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 -RUN apt-get update -y && \ - apt-get install -y mariadb-client ffmpeg +RUN apk update && \ + apk add --no-cache mariadb-dev build-base gcc musl-dev linux-headers -WORKDIR /app +COPY requirements.txt /app/requirements.txt +RUN pip install --upgrade pip && \ + pip install -r /app/requirements.txt -COPY --from=build /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages -COPY --from=build /usr/local/bin/ /usr/local/bin/ -COPY --from=build /app . +COPY . /app +WORKDIR /app EXPOSE 8000 -CMD [ "gunicorn", "-w", "2", "-b", "0.0.0.0:8000", "main:app" ] - +CMD [ "gunicorn", "-w", "4", "-b", "0.0.0.0:8000", "main:app" ] From 076a34d6f565496d3424824d4d90e11470d4e93c Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 11:13:38 +0300 Subject: [PATCH 03/14] chore(search): reduce results' length even more --- services/youtube/search/search_scraper.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/services/youtube/search/search_scraper.go b/services/youtube/search/search_scraper.go index 12baeeb7..bbf8dfe1 100644 --- a/services/youtube/search/search_scraper.go +++ b/services/youtube/search/search_scraper.go @@ -52,11 +52,15 @@ func (y *ScraperSearch) Search(query string) (results []entities.Song, err error duration[0] = "0" + duration[0] } if len(duration) == 3 { - durationNum, err := strconv.Atoi(duration[0]) + hoursNum, err := strconv.Atoi(duration[0]) if err != nil { continue } - if durationNum > 2 { + minsNum, err := strconv.Atoi(duration[1]) + if err != nil { + continue + } + if hoursNum >= 1 && minsNum > 30 { continue } } From 7ce078b84667a8aa876d6b2b1a1a7468b1155fac Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 11:23:52 +0300 Subject: [PATCH 04/14] fix(playlist): next and previous when unfocused --- static/js/playlist_player.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/static/js/playlist_player.js b/static/js/playlist_player.js index e629cdf7..3deb8556 100644 --- a/static/js/playlist_player.js +++ b/static/js/playlist_player.js @@ -108,14 +108,20 @@ class PlaylistPlayer { const songEl = document.getElementById( "song-" + this.#playlist.songs[this.#currentSongIndex].yt_id, ); + if (!songEl) { + return; + } songEl.style.backgroundColor = "var(--accent-color-30)"; songEl.scrollIntoView(); } setSongNotPlayingStyle() { for (const song of this.#playlist.songs) { - document.getElementById("song-" + song.yt_id).style.backgroundColor = - "var(--secondary-color-20)"; + const songEl = document.getElementById("song-" + song.yt_id); + if (!songEl) { + return; + } + songEl.style.backgroundColor = "var(--secondary-color-20)"; } } From c439ddede40440f930ad5b0a7bcfcf28205f72d7 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 11:24:51 +0300 Subject: [PATCH 05/14] chore(player): remove console log --- static/js/player.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/js/player.js b/static/js/player.js index 551350e3..c5158ede 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -203,7 +203,6 @@ class PlayerUI { songSeekBarExpandedEl.max = Math.ceil(duration); songSeekBarExpandedEl.value = 0; } - console.log("duration", songDurationEl); if (songDurationEl) { songDurationEl.innerHTML = Utils.formatTime(duration); } From d9c74761076cf04b475a7b427b04a7f2a8a648aa Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 14:45:10 +0300 Subject: [PATCH 06/14] refactor(player): fix some calls --- static/js/player.js | 771 ++++++++++++++++----------- views/components/player/player.templ | 4 +- views/pages/playlist.templ | 6 +- views/pages/search_results.templ | 2 +- 4 files changed, 459 insertions(+), 324 deletions(-) diff --git a/static/js/player.js b/static/js/player.js index c5158ede..30272a31 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -1,46 +1,36 @@ "use strict"; -const loopModes = [ - { icon: "loop-off-icon.svg", mode: "OFF" }, - { icon: "loop-once-icon.svg", mode: "ONCE" }, - { icon: "loop-icon.svg", mode: "ALL" }, -]; - +// collapsed player's elements const playPauseToggleEl = document.getElementById("play"), - playPauseToggleExapndedEl = document.getElementById("play-expand"), shuffleEl = document.getElementById("shuffle"), nextEl = document.getElementById("next"), prevEl = document.getElementById("prev"), loopEl = document.getElementById("loop"), songNameEl = document.getElementById("song-name"), - songNameExpandedEl = document.getElementById("song-name-expanded"), artistNameEl = document.getElementById("artist-name"), - artistNameExpandedEl = document.getElementById("artist-name-expanded"), songSeekBarEl = document.getElementById("song-seek-bar"), - songSeekBarExpandedEl = document.getElementById("song-seek-bar-expanded"), songDurationEl = document.getElementById("song-duration"), - songDurationExpandedEl = document.getElementById("song-duration-expanded"), songCurrentTimeEl = document.getElementById("song-current-time"), - songCurrentTimeExpandedEl = document.getElementById( - "song-current-time-expanded", - ), songImageEl = document.getElementById("song-image"), - songImageExpandedEl = document.getElementById("song-image-expanded"), audioPlayerEl = document.getElementById("audio-player"), muzikkContainerEl = document.getElementById("muzikk"), zePlayerEl = document.getElementById("ze-player"), zeCollapsedMobilePlayer = document.getElementById( "ze-collapsed-mobile-player", + ); + +// expanded player's elements +const playPauseToggleExapndedEl = document.getElementById("play-expand"), + songNameExpandedEl = document.getElementById("song-name-expanded"), + artistNameExpandedEl = document.getElementById("artist-name-expanded"), + songSeekBarExpandedEl = document.getElementById("song-seek-bar-expanded"), + songDurationExpandedEl = document.getElementById("song-duration-expanded"), + songCurrentTimeExpandedEl = document.getElementById( + "song-current-time-expanded", ), + songImageExpandedEl = document.getElementById("song-image-expanded"), zeExpandedMobilePlayer = document.getElementById("ze-expanded-mobile-player"); -let shuffleSongs = false; -let currentLoopIdx = 0; -/** - * @type{PlayerUI} - */ -let ui; - /** * @typedef {object} Song * @property {string} title @@ -52,86 +42,367 @@ let ui; * @property {string} added_at */ -class PlayerUI { - show() { - muzikkContainerEl.style.display = "block"; - } +/** + * @typedef {object} Playlist + * @property {string} public_id + * @property {string} title + * @property {string} songs_count + * @property {Song[]} songs + */ - hide() { - muzikkContainerEl.style.display = "none"; - } +/** + * @typedef {object} PlayerState + * @property {LoopMode} loopMode + * @property {boolean} shuffled + * @property {Playlist} playlist + * @property {number} currentSongIdx + */ - expand() { - if (!zePlayerEl.classList.contains("exapnded")) { - zePlayerEl.classList.add("exapnded"); - zeCollapsedMobilePlayer.classList.add("hidden"); - zeExpandedMobilePlayer.classList.remove("hidden"); - } - } +/** + * @enum {LoopMode} + */ +const LOOP_MODES = Object.freeze({ + ALL: "ALL", + OFF: "OFF", + ONCE: "ONCE", +}); - collapse() { - if (zePlayerEl.classList.contains("exapnded")) { - zePlayerEl.classList.remove("exapnded"); - zeExpandedMobilePlayer.classList.add("hidden"); - zeCollapsedMobilePlayer.classList.remove("hidden"); - } - } +/** + * @type{PlayerState} + */ +const playerState = { + loopMode: LOOP_MODES.OFF, + shuffled: false, + currentSongIdx: 0, + playlist: { + title: "Queue", + songs_count: 0, + public_id: "", + songs: [], + }, +}; + +function isSingleSong() { + return playerState.playlist.songs.length <= 1; +} - /** - * @param {boolean} isPlay - */ - setPlay(isPlay = false) { - if (isPlay) { - playPauseToggleEl.innerHTML = Player.icons.pause; - if (!!playPauseToggleExapndedEl) { - playPauseToggleExapndedEl.innerHTML = Player.icons.pause; - } +/** + * @returns {[Function, Function]} + */ +function looper() { + const loopModes = [LOOP_MODES.OFF, LOOP_MODES.ONCE, LOOP_MODES.ALL]; + let currentLoopIdx = 0; + + const __toggle = () => { + if (isSingleSong()) { + currentLoopIdx = currentLoopIdx === 0 ? 1 : 0; } else { - playPauseToggleEl.innerHTML = Player.icons.play; - if (!!playPauseToggleExapndedEl) { - playPauseToggleExapndedEl.innerHTML = Player.icons.play; - } + currentLoopIdx = (currentLoopIdx + 1) % loopModes.length; } - } + loopEl.innerHTML = + Player.icons[ + loopModes[currentLoopIdx] === LOOP_MODES.ALL + ? "loop" + : loopModes[currentLoopIdx] === LOOP_MODES.ONCE + ? "loopOnce" + : loopModes[currentLoopIdx] === LOOP_MODES.OFF + ? "loopOff" + : "loopOff" + ]; + }; + + const __handle = () => { + switch (loopModes[currentLoopIdx]) { + case LOOP_MODES.OFF: + stopMuzikk(); + if (!isSingleSong()) { + nextMuzikk(); + } + break; + case LOOP_MODES.ONCE: + stopMuzikk(); + playMuzikk(); + break; + case LOOP_MODES.ALL: + if (!isSingleSong()) { + nextMuzikk(); + } + break; + } + }; /** - * @param {boolean} isShuffle + * @param {LoopMode} loopMode + * @returns {boolean} */ - setShuffle(isShuffle = false) { - if (isShuffle) { - shuffleEl.innerHTML = Player.icons.shuffleOff; + const __check = (loopMode) => loopMode === loopModes[currentLoopIdx]; + + return [__toggle, __handle, __check]; +} +/** + * @param {HTMLElement} el + * @param {string} icon + */ +const setPlayerButtonIcon = (el, icon) => { + if (!!el && !!icon) { + el.innerHTML = icon; + } +}; + +/** + * @param {boolean} loading + * @param {string} fallback is used when loading is false, that is to reset + * the loading thingy + */ +function setLoading(loading, fallback) { + if (loading) { + setPlayerButtonIcon(playPauseToggleEl, Player.icons.loading); + setPlayerButtonIcon(playPauseToggleExapndedEl, Player.icons.loading); + document.body.style.cursor = "progress"; + return; + } + if (fallback) { + setPlayerButtonIcon(playPauseToggleEl, fallback); + setPlayerButtonIcon(playPauseToggleExapndedEl, fallback); + } + document.body.style.cursor = "auto"; +} + +/** + * @param {HTMLAudioElement} audioEl + * + * @returns {[Function, Function, Function]} + */ +function playPauser(audioEl) { + const __play = () => { + audioEl.play(); + const songEl = document.getElementById( + "song-" + playerState.playlist.songs[playerState.currentSongIdx].yt_id, + ); + if (!!songEl) { + songEl.style.backgroundColor = "var(--accent-color-30)"; + } + setPlayerButtonIcon(playPauseToggleEl, Player.icons.pause); + setPlayerButtonIcon(playPauseToggleExapndedEl, Player.icons.pause); + }; + const __pause = () => { + audioEl.pause(); + setPlayerButtonIcon(playPauseToggleEl, Player.icons.play); + setPlayerButtonIcon(playPauseToggleExapndedEl, Player.icons.play); + }; + const __toggle = () => { + if (audioEl.paused) { + __play(); } else { - shuffleEl.innerHTML = Player.icons.shuffle; + __pause(); } - } + }; + return [__play, __pause, __toggle]; +} + +/** + * @param {HTMLAudioElement} audioEl + * + * @returns {Function} + */ +function stopper(audioEl) { + return () => { + audioEl.pause(); + audioEl.currentTime = 0; + const songEl = document.getElementById( + "song-" + playerState.playlist.songs[playerState.currentSongIdx].yt_id, + ); + if (!!songEl) { + songEl.style.backgroundColor = "var(--secondary-color-20)"; + } + setPlayerButtonIcon(playPauseToggleEl, Player.icons.play); + setPlayerButtonIcon(playPauseToggleExapndedEl, Player.icons.play); + }; +} + +/** + * @param {PlayerState} state + * + * @returns {Function} + */ +function shuffler(state) { + return () => { + state.shuffled = !state.shuffled; + setPlayerButtonIcon( + shuffleEl, + state.shuffled ? Player.icons.shuffle : Player.icons.shuffleOff, + ); + }; +} +/** + * @param {PlayerState} state + * + * @returns {[Function, Function, Function, Function]} + */ +function playlister(state) { /** - * @param {boolean} loading - * @param {string} fallback is used when loading is false, that is to reset - * the loading thingy + * @param {string} songYtId + * @param {Playlist} playlist */ - setLoading(loading, fallback) { - if (loading) { - playPauseToggleEl.innerHTML = Player.icons.loading; - if (!!playPauseToggleExapndedEl) { - playPauseToggleExapndedEl.innerHTML = Player.icons.loading; + const __setSongInPlaylistStyle = (songYtId, playlist) => { + for (const _song of playlist.songs) { + const songEl = document.getElementById("song-" + _song.yt_id); + if (!songEl) { + continue; } - document.body.style.cursor = "progress"; + if (songYtId === _song.yt_id) { + songEl.style.backgroundColor = "var(--accent-color-30)"; + songEl.scrollIntoView(); + } else { + songEl.style.backgroundColor = "var(--secondary-color-20)"; + } + } + }; + + const __updateSongPlays = async () => { + if (!state.playlist.public_id) { return; } - if (fallback) { - playPauseToggleEl.innerHTML = fallback; - if (!!playPauseToggleExapndedEl) { - playPauseToggleExapndedEl.innerHTML = fallback; - } + await fetch( + "/api/increment-song-plays?" + + new URLSearchParams({ + "song-id": state.playlist.songs[state.currentSongIdx].yt_id, + "playlist-id": state.playlist.public_id, + }).toString(), + { + method: "PUT", + }, + ).catch((err) => console.error(err)); + }; + + const __next = () => { + if ( + !checkLoop(LOOP_MODES.ALL) && + !state.shuffled && + state.currentSongIdx + 1 >= state.playlist.songs.length + ) { + stopMuzikk(); + return; } - document.body.style.cursor = "auto"; + state.currentSongIdx = state.shuffled + ? Math.floor(Math.random() * state.playlist.songs.length) + : checkLoop(LOOP_MODES.ALL) && + state.currentSongIdx + 1 >= state.playlist.songs.length + ? 0 + : state.currentSongIdx + 1; + const songToPlay = state.playlist.songs[state.currentSongIdx]; + playSongFromPlaylist(songToPlay.yt_id, state.playlist); + __updateSongPlays(); + __setSongInPlaylistStyle(songToPlay.yt_id, state.playlist); + }; + + const __prev = () => { + if ( + !checkLoop(LOOP_MODES.ALL) && + !state.shuffled && + state.currentSongIdx - 1 < 0 + ) { + stopMuzikk(); + return; + } + state.currentSongIdx = state.shuffled + ? Math.floor(Math.random() * state.playlist.songs.length) + : checkLoop(LOOP_MODES.ALL) && state.currentSongIdx - 1 < 0 + ? state.playlist.songs.length - 1 + : state.currentSongIdx - 1; + const songToPlay = state.playlist.songs[state.currentSongIdx]; + playSongFromPlaylist(songToPlay.yt_id, state.playlist); + __updateSongPlays(); + __setSongInPlaylistStyle(songToPlay.yt_id, state.playlist); + }; + + const __remove = (songYtId) => { + const songIndex = state.playlist.songs.findIndex( + (song) => song.yt_id === songYtId, + ); + if (songIndex < 0) { + return; + } + state.playlist.songs.splice(songIndex, 1); + }; + + return [__next, __prev, __remove, __setSongInPlaylistStyle]; +} + +/** + * @param {string} songYtId + */ +async function downloadSong(songYtId) { + return await fetch("/api/song?id=" + songYtId).catch((err) => + console.error(err), + ); +} + +/** + * @param {string} songYtId + * @param {string} songTitle + */ +async function downloadSongToDevice(songYtId, songTitle) { + Utils.showLoading(); + await downloadSong(songYtId) + .then(() => { + const a = document.createElement("a"); + a.href = `/muzikkx/${songYtId}.mp3`; + a.download = `${songTitle}.mp3`; + a.click(); + }) + .finally(() => { + Utils.hideLoading(); + }); +} + +/** + * @param {string} songYtId + */ +async function downloadToApp() { + throw new Error("not implemented!"); +} + +function show() { + muzikkContainerEl.style.display = "block"; +} + +function hide() { + muzikkContainerEl.style.display = "none"; +} + +function expand() { + if (!zePlayerEl.classList.contains("exapnded")) { + zePlayerEl.classList.add("exapnded"); + zeCollapsedMobilePlayer.classList.add("hidden"); + zeExpandedMobilePlayer.classList.remove("hidden"); } +} - /** - * @param {Song} song - */ - setDetails(song) { +function collapse() { + if (zePlayerEl.classList.contains("exapnded")) { + zePlayerEl.classList.remove("exapnded"); + zeExpandedMobilePlayer.classList.add("hidden"); + zeCollapsedMobilePlayer.classList.remove("hidden"); + } +} + +/** + * @param {Song} song + */ +async function playSong(song) { + setLoading(true); + show(); + + await downloadSong(song.yt_id).then(() => { + stopMuzikk(); + audioPlayerEl.src = `/muzikkx/${song.yt_id}.mp3`; + audioPlayerEl.load(); + }); + + // song's details setting, yada yada + { if (song.title) { songNameEl.innerHTML = song.title; songNameEl.title = song.title; @@ -170,57 +441,39 @@ class PlayerUI { songImageExpandedEl.innerHTML = ""; } } + setMediaSession(song); + playMuzikk(); +} - /** - * @param {number} time - */ - setCurrentTime(time) { - const currentTime = Math.floor(time); - if (songCurrentTimeEl) { - songCurrentTimeEl.innerHTML = Utils.formatTime(currentTime); - } - if (songCurrentTimeExpandedEl) { - songCurrentTimeExpandedEl.innerHTML = Utils.formatTime(currentTime); - } - if (songSeekBarEl) { - songSeekBarEl.value = Math.ceil(currentTime); - } - if (songSeekBarExpandedEl) { - songSeekBarExpandedEl.value = Math.ceil(currentTime); - } - } - - /** - * @param {number} duration - */ - setDuration(duration) { - if (isNaN(duration)) { - duration = 0; - } - songSeekBarEl.max = Math.ceil(duration); - songSeekBarEl.value = 0; - if (songSeekBarExpandedEl) { - songSeekBarExpandedEl.max = Math.ceil(duration); - songSeekBarExpandedEl.value = 0; - } - if (songDurationEl) { - songDurationEl.innerHTML = Utils.formatTime(duration); - } - if (songDurationExpandedEl) { - songDurationExpandedEl.innerHTML = Utils.formatTime(duration); - } - } +/** + * @param {Song} song + */ +function playSingleSong(song) { + playerState.playlist = { + title: "Queue", + songs_count: 1, + public_id: "", + songs: [song], + }; + playerState.currentSongIdx = 0; + playSong(song); +} - loopIcon() { - switch (loopModes[currentLoopIdx].mode) { - case "OFF": - return Player.icons.loopOff; - case "ONCE": - return Player.icons.loopOnce; - case "ALL": - return Player.icons.loop; - } +/** + * @param {string} songYtId + * @param {Playlist} playlist + */ +function playSongFromPlaylist(songYtId, playlist) { + const songIdx = playlist.songs.findIndex((s) => s.yt_id === songYtId); + if (songIdx < 0) { + alert("Invalid song!"); + return; } + playerState.playlist = playlist; + playerState.currentSongIdx = songIdx; + const songToPlay = playlist.songs[songIdx]; + highlightSongInPlaylist(songToPlay.yt_id, playlist); + playSong(songToPlay); } /** @@ -252,11 +505,11 @@ function setMediaSession(song) { }); navigator.mediaSession.setActionHandler("play", () => { - playPauseToggle(); + playMuzikk(); }); navigator.mediaSession.setActionHandler("pause", () => { - playPauseToggle(); + pauseMuzikk(); }); navigator.mediaSession.setActionHandler("stop", () => { @@ -289,7 +542,7 @@ function setMediaSession(song) { }); navigator.mediaSession.setActionHandler("nexttrack", () => { - nexMuzikk(); + nextMuzikk(); }); navigator.mediaSession.setActionHandler("stop", () => { @@ -297,177 +550,55 @@ function setMediaSession(song) { }); } -function nexMuzikk() { - if (!Player.playlistPlayer) { - return; - } - Player.playlistPlayer.next( - shuffleSongs, - loopModes[currentLoopIdx].mode === "ALL", - ); -} - -function previousMuzikk() { - if (!Player.playlistPlayer) { - return; - } - Player.playlistPlayer.previous( - shuffleSongs, - loopModes[currentLoopIdx].mode === "ALL", - ); -} - -function playMuzikk() { - audioPlayerEl.play(); - ui.setPlay(true); -} - -function pauseMuzikk() { - audioPlayerEl.pause(); - ui.setPlay(false); -} - -function stopMuzikk() { - pauseMuzikk(); - audioPlayerEl.currentTime = 0; -} - -function playPauseToggle() { - if (audioPlayerEl.paused) { - playMuzikk(); - } else { - pauseMuzikk(); - } -} - -function toggleShuffle() { - if (!Player.playlistPlayer) { - alert("Shuffling can't be enabled for a single song!"); - return; - } - ui.setShuffle(shuffleSongs); - shuffleSongs = !shuffleSongs; -} - -/** - * @param {string} songYtId - */ -async function downloadSong(songYtId) { - return await fetch("/api/song?id=" + songYtId).catch((err) => - console.error(err), - ); -} - -/** - * @param {string} songYtId - * @param {string} songTitle - */ -async function downloadSongToDevice(songYtId, songTitle) { - Utils.showLoading(); - await downloadSong(songYtId) - .then(() => { - const a = document.createElement("a"); - a.href = `/muzikkx/${songYtId}.mp3`; - a.download = `${songTitle}.mp3`; - a.click(); - }) - .finally(() => { - Utils.hideLoading(); - }); -} - -/** - * @param {string} songYtId - */ -async function downloadToApp() { - throw new Error("not implemented!"); -} - -/** - * @param {Song} song - * @param {boolean} inPlaylist - */ -async function playSong(song, inPlaylist) { - if (!inPlaylist) { - Player.playlistPlayer = null; - nextEl.style.display = "none"; - prevEl.style.display = "none"; - shuffleEl.style.display = "none"; - if (currentLoopIdx > 1) { - currentLoopIdx = 0; - loopEl.innerHTML = ui.loopIcon(); - } - loopEl.children[0].style.height = "60px"; - loopEl.children[0].style.width = "55px"; - } else { - loopEl.children[0].style.height = "30px"; - loopEl.children[0].style.width = "30px"; - } - ui.setLoading(true); - ui.show(); - - await downloadSong(song.yt_id).then(() => { - stopMuzikk(); - audioPlayerEl.src = `/muzikkx/${song.yt_id}.mp3`; - audioPlayerEl.load(); - }); - - ui.setDetails(song); - setMediaSession(song); - playMuzikk(); -} - -function showPlayer() { - muzikkContainerEl.style.display = "block"; -} - -function hidePlayer() { - muzikkContainerEl.style.display = "none"; - audioPlayerEl.stopMuzikk(); -} +const [toggleLoop, handleLoop, checkLoop] = looper(); +const [playMuzikk, pauseMuzikk, togglePP] = playPauser(audioPlayerEl); +const stopMuzikk = stopper(audioPlayerEl); +const toggleShuffle = shuffler(playerState); +const [ + nextMuzikk, + previousMuzikk, + removeSongFromPlaylist, + highlightSongInPlaylist, +] = playlister(playerState); playPauseToggleEl.addEventListener("click", (event) => { event.stopImmediatePropagation(); event.preventDefault(); - playPauseToggle(); + togglePP(); }); playPauseToggleExapndedEl?.addEventListener("click", (event) => { event.stopImmediatePropagation(); event.preventDefault(); - playPauseToggle(); + togglePP(); }); -nextEl?.addEventListener("click", nexMuzikk); +nextEl?.addEventListener("click", nextMuzikk); prevEl?.addEventListener("click", previousMuzikk); shuffleEl?.addEventListener("click", toggleShuffle); loopEl?.addEventListener("click", (event) => { event.stopImmediatePropagation(); event.preventDefault(); - if (!Player.playlistPlayer) { - currentLoopIdx = currentLoopIdx === 0 ? 1 : 0; - } else { - currentLoopIdx = (currentLoopIdx + 1) % loopModes.length; - } - loopEl.innerHTML = ui.loopIcon(); -}); - -songSeekBarEl.addEventListener("change", (event) => { - event.stopImmediatePropagation(); - event.preventDefault(); - const seekTime = Number(event.target.value); - audioPlayerEl.currentTime = seekTime; + toggleLoop(); }); -songSeekBarEl.addEventListener("click", (event) => { - event.stopImmediatePropagation(); - event.preventDefault(); -}); +(() => { + const __handler = (e) => { + e.stopImmediatePropagation(); + e.preventDefault(); + const seekTime = Number(e.target.value); + audioPlayerEl.currentTime = seekTime; + }; + for (const event of ["change", "click"]) { + songSeekBarEl?.addEventListener(event, __handler); + songSeekBarExpandedEl?.addEventListener(event, __handler); + } +})(); audioPlayerEl.addEventListener("loadeddata", (event) => { playPauseToggleEl.disabled = null; - if (playPauseToggleExapndedEl) { + if (!!playPauseToggleExapndedEl) { playPauseToggleExapndedEl.disabled = null; } shuffleEl.disabled = null; @@ -475,33 +606,47 @@ audioPlayerEl.addEventListener("loadeddata", (event) => { prevEl.disabled = null; loopEl.disabled = null; - ui.setDuration(event.target.duration); - ui.setLoading(false, Player.icons.pause); + // set duration AAA + { + let duration = event.target.duration; + if (isNaN(duration)) { + duration = 0; + } + songSeekBarEl.max = Math.ceil(duration); + songSeekBarEl.value = 0; + if (!!songSeekBarExpandedEl) { + songSeekBarExpandedEl.max = Math.ceil(duration); + songSeekBarExpandedEl.value = 0; + } + if (!!songDurationEl) { + songDurationEl.innerHTML = Utils.formatTime(duration); + } + if (!!songDurationExpandedEl) { + songDurationExpandedEl.innerHTML = Utils.formatTime(duration); + } + } + + setLoading(false, Player.icons.pause); }); audioPlayerEl.addEventListener("timeupdate", (event) => { - ui.setCurrentTime(event.target.currentTime); + const currentTime = Math.floor(event.target.currentTime); + if (songCurrentTimeEl) { + songCurrentTimeEl.innerHTML = Utils.formatTime(currentTime); + } + if (songCurrentTimeExpandedEl) { + songCurrentTimeExpandedEl.innerHTML = Utils.formatTime(currentTime); + } + if (songSeekBarEl) { + songSeekBarEl.value = Math.ceil(currentTime); + } + if (songSeekBarExpandedEl) { + songSeekBarExpandedEl.value = Math.ceil(currentTime); + } }); audioPlayerEl.addEventListener("ended", () => { - switch (loopModes[currentLoopIdx].mode) { - case "OFF": - stopMuzikk(); - if (Player.playlistPlayer) { - Player.playlistPlayer.next(shuffleSongs, false); - } - break; - case "ONCE": - stopMuzikk(); - playMuzikk(); - break; - case "ALL": - if (Player.playlistPlayer) { - Player.playlistPlayer.next(shuffleSongs, true); - return; - } - break; - } + handleLoop(); }); audioPlayerEl.addEventListener("progress", () => { @@ -513,20 +658,16 @@ document ?.addEventListener("click", (event) => { event.stopImmediatePropagation(); event.preventDefault(); - ui.collapse(); + collapse(); }); -function init() { - ui = new PlayerUI(); -} - -init(); - window.Player = {}; window.Player.downloadSongToDevice = downloadSongToDevice; -window.Player.showPlayer = showPlayer; -window.Player.hidePlayer = hidePlayer; -window.Player.playSong = playSong; +window.Player.showPlayer = show; +window.Player.hidePlayer = hide; +window.Player.playSingleSong = playSingleSong; +window.Player.playSongFromPlaylist = playSongFromPlaylist; +window.Player.removeSongFromPlaylist = removeSongFromPlaylist; window.Player.stopMuzikk = stopMuzikk; -window.Player.expand = () => ui.expand(); -window.Player.collapse = () => ui.collapse(); +window.Player.expand = () => expand(); +window.Player.collapse = () => collapse(); diff --git a/views/components/player/player.templ b/views/components/player/player.templ index 029292e7..f954ba8b 100644 --- a/views/components/player/player.templ +++ b/views/components/player/player.templ @@ -16,8 +16,6 @@ templ PlayerSticky() { > /// - - + } diff --git a/views/pages/playlist.templ b/views/pages/playlist.templ index 7a952a90..e03e8480 100644 --- a/views/pages/playlist.templ +++ b/views/pages/playlist.templ @@ -50,7 +50,7 @@ templ Playlist(pl entities.Playlist) { } @@ -242,10 +242,6 @@ css songThumb(url string) { background-image: { url }; } -script playPlaylist(playlist entities.Playlist) { - window.Player.playPlaylist(playlist) -} - script playSongFromPlaylist(songId string, playlist entities.Playlist) { window.Player.playSongFromPlaylist(songId, playlist) } diff --git a/views/pages/search_results.templ b/views/pages/search_results.templ index bbcc2e2f..6db1204e 100644 --- a/views/pages/search_results.templ +++ b/views/pages/search_results.templ @@ -81,7 +81,7 @@ templ SearchResults(results []entities.Song, playlists []entities.Playlist, song } script playSong(song entities.Song) { - window.Player.playSong(song, false); + window.Player.playSingleSong(song); } script downloadSong(songYtId, songTitle string) { From b30017d68663050ef07525c7e514cc25f73cd7f3 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 14:48:58 +0300 Subject: [PATCH 07/14] chore(player): remove playlist_player.js --- static/js/playlist_player.js | 176 ----------------------------------- 1 file changed, 176 deletions(-) delete mode 100644 static/js/playlist_player.js diff --git a/static/js/playlist_player.js b/static/js/playlist_player.js deleted file mode 100644 index 3deb8556..00000000 --- a/static/js/playlist_player.js +++ /dev/null @@ -1,176 +0,0 @@ -"use strict"; - -const shuffleEl1 = document.getElementById("shuffle"), - nextEl1 = document.getElementById("next"), - prevEl1 = document.getElementById("prev"); - -/** - * @typedef {object} Song - * @property {string} title - * @property {string} artist - * @property {string} duration - * @property {string} thumbnail_url - * @property {string} yt_id - * @property {number} play_times - * @property {string} added_at - */ - -/** - * @typedef {object} Playlist - * @property {string} public_id - * @property {string} title - * @property {string} songs_count - * @property {Song[]} songs - */ - -class PlaylistPlayer { - #playlist; - #currentSongIndex; - - /** - * @param {Playlist} playlist - */ - constructor(playlist) { - this.#playlist = playlist; - this.#currentSongIndex = 0; - } - - /** - * @param {string} songYtId - */ - play(songYtId = "") { - this.setSongNotPlayingStyle(); - this.#currentSongIndex = this.#playlist.songs.findIndex( - (song) => song.yt_id === songYtId, - ); - if (this.#currentSongIndex < 0) { - this.#currentSongIndex = 0; - } - const songToPlay = this.#playlist.songs[this.#currentSongIndex]; - Player.playSong(songToPlay, true); - nextEl1.style.display = "block"; - prevEl1.style.display = "block"; - shuffleEl1.style.display = "block"; - this.#updateSongPlays(); - this.setSongPlayingStyle(); - } - - next(shuffle = false, loop = false) { - this.setSongNotPlayingStyle(); - if ( - !loop && - !shuffle && - this.#currentSongIndex + 1 >= this.#playlist.songs.length - ) { - Player.stopMuzikk(); - return; - } - this.#currentSongIndex = shuffle - ? Math.floor(Math.random() * this.#playlist.songs.length) - : loop && this.#currentSongIndex + 1 >= this.#playlist.songs.length - ? 0 - : this.#currentSongIndex + 1; - const songToPlay = this.#playlist.songs[this.#currentSongIndex]; - Player.playSong(songToPlay, true); - this.#updateSongPlays(); - this.setSongPlayingStyle(); - } - - previous(shuffle = false, loop = false) { - this.setSongNotPlayingStyle(); - if (!loop && !shuffle && this.#currentSongIndex - 1 < 0) { - Player.stopMuzikk(); - return; - } - this.#currentSongIndex = shuffle - ? Math.floor(Math.random() * this.#playlist.songs.length) - : loop && this.#currentSongIndex - 1 < 0 - ? this.#playlist.songs.length - 1 - : this.#currentSongIndex - 1; - this.setSongNotPlayingStyle(); - const songToPlay = this.#playlist.songs[this.#currentSongIndex]; - Player.playSong(songToPlay, true); - this.#updateSongPlays(); - this.setSongPlayingStyle(); - } - - removeSong(songYtId) { - const songIndex = this.#playlist.songs.findIndex( - (song) => song.yt_id === songYtId, - ); - if (songIndex < 0) { - return; - } - this.#playlist.songs.splice(songIndex, 1); - } - - setSongPlayingStyle() { - const songEl = document.getElementById( - "song-" + this.#playlist.songs[this.#currentSongIndex].yt_id, - ); - if (!songEl) { - return; - } - songEl.style.backgroundColor = "var(--accent-color-30)"; - songEl.scrollIntoView(); - } - - setSongNotPlayingStyle() { - for (const song of this.#playlist.songs) { - const songEl = document.getElementById("song-" + song.yt_id); - if (!songEl) { - return; - } - songEl.style.backgroundColor = "var(--secondary-color-20)"; - } - } - - async #updateSongPlays() { - await fetch( - "/api/increment-song-plays?" + - new URLSearchParams({ - "song-id": this.#playlist.songs[this.#currentSongIndex].yt_id, - "playlist-id": this.#playlist.public_id, - }).toString(), - { - method: "PUT", - }, - ).catch((err) => console.error(err)); - } -} - -/** - * @param {string} songYtId - */ -function removeSongFromPlaylist(songYtId) { - if (!Player.playlistPlayer) { - return; - } - Player.playlistPlayer.removeSong(songYtId); -} - -/** - * @param {Playlist} playlist - */ -function playPlaylist(playlist) { - Player.playlistPlayer = new PlaylistPlayer(playlist); - Player.playlistPlayer.play(); -} - -/** - * @param {string} songId - * @param {Playlist} playlist - */ -function playSongFromPlaylist(songId, playlist) { - Player.playlistPlayer = new PlaylistPlayer(playlist); - Player.playlistPlayer.play(songId); -} - -if (!window.Player) { - window.Player = {}; -} - -Player.playlistPlayer = null; -window.Player.playPlaylist = playPlaylist; -window.Player.playSongFromPlaylist = playSongFromPlaylist; -window.Player.removeSongFromPlaylist = removeSongFromPlaylist; From 96fbf67e6810fbf2efc0ed16fb49be9d06fe2e90 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 14:58:07 +0300 Subject: [PATCH 08/14] refactor(player): renamings --- static/js/player.js | 126 +++++++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 60 deletions(-) diff --git a/static/js/player.js b/static/js/player.js index 30272a31..ef678a00 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -14,10 +14,8 @@ const playPauseToggleEl = document.getElementById("play"), songImageEl = document.getElementById("song-image"), audioPlayerEl = document.getElementById("audio-player"), muzikkContainerEl = document.getElementById("muzikk"), - zePlayerEl = document.getElementById("ze-player"), - zeCollapsedMobilePlayer = document.getElementById( - "ze-collapsed-mobile-player", - ); + playerEl = document.getElementById("ze-player"), + collapsedMobilePlayer = document.getElementById("ze-collapsed-mobile-player"); // expanded player's elements const playPauseToggleExapndedEl = document.getElementById("play-expand"), @@ -29,7 +27,7 @@ const playPauseToggleExapndedEl = document.getElementById("play-expand"), "song-current-time-expanded", ), songImageExpandedEl = document.getElementById("song-image-expanded"), - zeExpandedMobilePlayer = document.getElementById("ze-expanded-mobile-player"); + expandedMobilePlayer = document.getElementById("ze-expanded-mobile-player"); /** * @typedef {object} Song @@ -373,18 +371,18 @@ function hide() { } function expand() { - if (!zePlayerEl.classList.contains("exapnded")) { - zePlayerEl.classList.add("exapnded"); - zeCollapsedMobilePlayer.classList.add("hidden"); - zeExpandedMobilePlayer.classList.remove("hidden"); + if (!playerEl.classList.contains("exapnded")) { + playerEl.classList.add("exapnded"); + collapsedMobilePlayer.classList.add("hidden"); + expandedMobilePlayer.classList.remove("hidden"); } } function collapse() { - if (zePlayerEl.classList.contains("exapnded")) { - zePlayerEl.classList.remove("exapnded"); - zeExpandedMobilePlayer.classList.add("hidden"); - zeCollapsedMobilePlayer.classList.remove("hidden"); + if (playerEl.classList.contains("exapnded")) { + playerEl.classList.remove("exapnded"); + expandedMobilePlayer.classList.add("hidden"); + collapsedMobilePlayer.classList.remove("hidden"); } } @@ -441,7 +439,7 @@ async function playSong(song) { songImageExpandedEl.innerHTML = ""; } } - setMediaSession(song); + setMediaSessionMetadata(song); playMuzikk(); } @@ -479,11 +477,12 @@ function playSongFromPlaylist(songYtId, playlist) { /** * @param {Song} song */ -function setMediaSession(song) { +function setMediaSessionMetadata(song) { if (!("mediaSession" in navigator)) { console.error("Browser doesn't support mediaSession"); return; } + navigator.mediaSession.metadata = new MediaMetadata({ title: song.title, artist: song.artist, @@ -503,51 +502,6 @@ function setMediaSession(song) { }; }), }); - - navigator.mediaSession.setActionHandler("play", () => { - playMuzikk(); - }); - - navigator.mediaSession.setActionHandler("pause", () => { - pauseMuzikk(); - }); - - navigator.mediaSession.setActionHandler("stop", () => { - stopMuzikk(); - }); - - navigator.mediaSession.setActionHandler("seekbackward", () => { - let seekTo = -10; - if (audioPlayerEl.currentTime + seekTo < 0) { - seekTo = 0; - } - audioPlayerEl.currentTime += seekTo; - }); - - navigator.mediaSession.setActionHandler("seekforward", () => { - let seekTo = +10; - if (audioPlayerEl.currentTime + seekTo > audioPlayerEl.duration) { - seekTo = 0; - } - audioPlayerEl.currentTime += seekTo; - }); - - navigator.mediaSession.setActionHandler("seekto", (a) => { - const seekTime = Number(a.seekTime); - audioPlayerEl.currentTime = seekTime; - }); - - navigator.mediaSession.setActionHandler("previoustrack", () => { - previousMuzikk(); - }); - - navigator.mediaSession.setActionHandler("nexttrack", () => { - nextMuzikk(); - }); - - navigator.mediaSession.setActionHandler("stop", () => { - stopMuzikk(); - }); } const [toggleLoop, handleLoop, checkLoop] = looper(); @@ -661,6 +615,58 @@ document collapse(); }); +(() => { + if (!("mediaSession" in navigator)) { + console.error("Browser doesn't support mediaSession"); + return; + } + + navigator.mediaSession.setActionHandler("play", () => { + playMuzikk(); + }); + + navigator.mediaSession.setActionHandler("pause", () => { + pauseMuzikk(); + }); + + navigator.mediaSession.setActionHandler("stop", () => { + stopMuzikk(); + }); + + navigator.mediaSession.setActionHandler("seekbackward", () => { + let seekTo = -10; + if (audioPlayerEl.currentTime + seekTo < 0) { + seekTo = 0; + } + audioPlayerEl.currentTime += seekTo; + }); + + navigator.mediaSession.setActionHandler("seekforward", () => { + let seekTo = +10; + if (audioPlayerEl.currentTime + seekTo > audioPlayerEl.duration) { + seekTo = 0; + } + audioPlayerEl.currentTime += seekTo; + }); + + navigator.mediaSession.setActionHandler("seekto", (a) => { + const seekTime = Number(a.seekTime); + audioPlayerEl.currentTime = seekTime; + }); + + navigator.mediaSession.setActionHandler("previoustrack", () => { + previousMuzikk(); + }); + + navigator.mediaSession.setActionHandler("nexttrack", () => { + nextMuzikk(); + }); + + navigator.mediaSession.setActionHandler("stop", () => { + stopMuzikk(); + }); +})(); + window.Player = {}; window.Player.downloadSongToDevice = downloadSongToDevice; window.Player.showPlayer = show; From c5473c28d2b2e43477b2aac59f5148640c35fc39 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 15:02:30 +0300 Subject: [PATCH 09/14] chore(player): replay song on next/prev if loopOnce --- static/js/player.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/static/js/player.js b/static/js/player.js index ef678a00..97c068e8 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -275,6 +275,11 @@ function playlister(state) { }; const __next = () => { + if (checkLoop(LOOP_MODES.ONCE)) { + stopMuzikk(); + playMuzikk(); + return; + } if ( !checkLoop(LOOP_MODES.ALL) && !state.shuffled && @@ -296,6 +301,11 @@ function playlister(state) { }; const __prev = () => { + if (checkLoop(LOOP_MODES.ONCE)) { + stopMuzikk(); + playMuzikk(); + return; + } if ( !checkLoop(LOOP_MODES.ALL) && !state.shuffled && From 38429e3b46c44bcca456b97783f656f4ec7966e1 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 15:10:08 +0300 Subject: [PATCH 10/14] fix(home-page): remove song id from div's id --- views/pages/index.templ | 1 - 1 file changed, 1 deletion(-) diff --git a/views/pages/index.templ b/views/pages/index.templ index 69130a88..5e016efa 100644 --- a/views/pages/index.templ +++ b/views/pages/index.templ @@ -30,7 +30,6 @@ templ Index(recentPlays []entities.Song) { "bg-secondary-trans-20", "rounded-[10px]", "p-2", "lg:p-5", "flex", "flex-row", "items-center", "gap-5", "justify-between", } - id={ "song-" + song.YtId } >
Date: Sat, 25 May 2024 15:13:05 +0300 Subject: [PATCH 11/14] chore(player): move remove song to player.js --- static/js/player.js | 35 +++++++++++++++++++++++++++++++---- views/pages/playlist.templ | 19 +------------------ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/static/js/player.js b/static/js/player.js index 97c068e8..009a9223 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -325,14 +325,41 @@ function playlister(state) { __setSongInPlaylistStyle(songToPlay.yt_id, state.playlist); }; - const __remove = (songYtId) => { + const __remove = (songYtId, playlistId) => { const songIndex = state.playlist.songs.findIndex( (song) => song.yt_id === songYtId, ); - if (songIndex < 0) { - return; + if (songIndex >= 0) { + state.playlist.songs.splice(songIndex, 1); } - state.playlist.songs.splice(songIndex, 1); + + Utils.showLoading(); + fetch( + "/api/toggle-song-in-playlist?song-id=" + + songYtId + + "&playlist-id=" + + playlistId + + "&remove=true", + { + method: "PUT", + }, + ) + .then((res) => { + if (res.ok) { + const songEl = document.getElementById("song-" + songYtId); + if (!!songEl) { + songEl.remove(); + } + } else { + window.alert("Oopsie something went wrong!"); + } + }) + .catch((err) => { + window.alert("Oopsie something went wrong!\n", err); + }) + .finally(() => { + Utils.hideLoading(); + }); }; return [__next, __prev, __remove, __setSongInPlaylistStyle]; diff --git a/views/pages/playlist.templ b/views/pages/playlist.templ index e03e8480..935b3838 100644 --- a/views/pages/playlist.templ +++ b/views/pages/playlist.templ @@ -247,24 +247,7 @@ script playSongFromPlaylist(songId string, playlist entities.Playlist) { } script removeSongFromPlaylist(songId, playlistId string) { - Utils.showLoading(); - fetch("/api/toggle-song-in-playlist?song-id=" + songId + "&playlist-id=" + playlistId + "&remove=true", { - method: "PUT", - }) - .then((res) => { - if (res.ok) { - document.getElementById("song-" + songId).remove(); - Player.removeSongFromPlaylist(songId); - } else { - window.alert("Oopsie something went wrong!"); - } - }) - .catch((err) => { - window.alert("Oopsie something went wrong!\n", err); - }) - .finally(() => { - Utils.hideLoading(); - }); + Player.removeSongFromPlaylist(songId, playlistId); } script copyLink(pubId string, isPublic bool) { From 9bd693ef6a1b6be97336b350b7a244f2acd81127 Mon Sep 17 00:00:00 2001 From: Baraa Al-Masri Date: Sat, 25 May 2024 15:49:18 +0300 Subject: [PATCH 12/14] feat(queue): add append song to queue --- static/icons/add-to-queue.svg | 3 +++ static/js/player.js | 19 +++++++++++++++++++ views/pages/search_results.templ | 15 +++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 static/icons/add-to-queue.svg diff --git a/static/icons/add-to-queue.svg b/static/icons/add-to-queue.svg new file mode 100644 index 00000000..e26e8c73 --- /dev/null +++ b/static/icons/add-to-queue.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/js/player.js b/static/js/player.js index 009a9223..c264b807 100644 --- a/static/js/player.js +++ b/static/js/player.js @@ -511,6 +511,24 @@ function playSongFromPlaylist(songYtId, playlist) { playSong(songToPlay); } +/** + * @param {Song} song + */ +function appendSongToCurrentQueue(song) { + if ( + playerState.playlist.songs.findIndex((s) => s.yt_id === song.yt_id) !== -1 + ) { + alert(`${song.title} exists in the queue!`); + return; + } + playerState.playlist.songs.push(song); + alert(`Added ${song.title} to the queue!`); +} + +function addSongToPlaylist() { + throw new Error("not implemented!"); +} + /** * @param {Song} song */ @@ -711,6 +729,7 @@ window.Player.hidePlayer = hide; window.Player.playSingleSong = playSingleSong; window.Player.playSongFromPlaylist = playSongFromPlaylist; window.Player.removeSongFromPlaylist = removeSongFromPlaylist; +window.Player.addSongToQueue = appendSongToCurrentQueue; window.Player.stopMuzikk = stopMuzikk; window.Player.expand = () => expand(); window.Player.collapse = () => collapse(); diff --git a/views/pages/search_results.templ b/views/pages/search_results.templ index 6db1204e..1ae68cd7 100644 --- a/views/pages/search_results.templ +++ b/views/pages/search_results.templ @@ -61,6 +61,17 @@ templ SearchResults(results []entities.Song, playlists []entities.Playlist, song
@playlist.PlaylistsPopover(idx, res.YtId, playlists, songsInPlaylists) +