From fe95cb23cdc72698d8f2227bdcc3395e67eb66f8 Mon Sep 17 00:00:00 2001 From: Samidy Date: Mon, 9 Mar 2026 04:23:08 +0300 Subject: [PATCH 01/15] fix(DB): why did i make it overwrite --- js/accounts/pocketbase.js | 42 +++++++++++++++++++++++---------------- js/db.js | 4 ++++ 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/js/accounts/pocketbase.js b/js/accounts/pocketbase.js index 67a2f08..dcbf116 100644 --- a/js/accounts/pocketbase.js +++ b/js/accounts/pocketbase.js @@ -534,22 +534,15 @@ const syncManager = { database = await database; } - const getAll = async (store) => { - if (database && typeof database.getAll === 'function') return database.getAll(store); - if (database && database.db && typeof database.db.getAll === 'function') - return database.db.getAll(store); - return []; - }; - const localData = { - tracks: (await getAll('favorites_tracks')) || [], - albums: (await getAll('favorites_albums')) || [], - artists: (await getAll('favorites_artists')) || [], - playlists: (await getAll('favorites_playlists')) || [], - mixes: (await getAll('favorites_mixes')) || [], - history: (await getAll('history_tracks')) || [], - userPlaylists: (await getAll('user_playlists')) || [], - userFolders: (await getAll('user_folders')) || [], + tracks: (await database.getAll('favorites_tracks')) || [], + albums: (await database.getAll('favorites_albums')) || [], + artists: (await database.getAll('favorites_artists')) || [], + playlists: (await database.getAll('favorites_playlists')) || [], + mixes: (await database.getAll('favorites_mixes')) || [], + history: (await database.getAll('history_tracks')) || [], + userPlaylists: (await database.getAll('user_playlists')) || [], + userFolders: (await database.getAll('user_folders')) || [], }; let { library, history, userPlaylists, userFolders } = cloudData; @@ -612,8 +605,23 @@ const syncManager = { } }); - if (history.length === 0 && localData.history.length > 0) { - history = localData.history; + const combinedHistory = [...history, ...localData.history]; + combinedHistory.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); + + const uniqueHistory = []; + const seenTimestamps = new Set(); + + for (const item of combinedHistory) { + if (!item.timestamp) continue; + if (!seenTimestamps.has(item.timestamp)) { + seenTimestamps.add(item.timestamp); + uniqueHistory.push(item); + } + if (uniqueHistory.length >= 100) break; + } + + if (JSON.stringify(history) !== JSON.stringify(uniqueHistory)) { + history = uniqueHistory; needsUpdate = true; } diff --git a/js/db.js b/js/db.js index 643328f..76477aa 100644 --- a/js/db.js +++ b/js/db.js @@ -89,6 +89,10 @@ export class MusicDatabase { }); } + async getAll(storeName) { + return this.performTransaction(storeName, 'readonly', (store) => store.getAll()); + } + // History API async addToHistory(track) { const storeName = 'history_tracks'; From 34ba9206626f9a4f9b99fec7a016bde572aedf6e Mon Sep 17 00:00:00 2001 From: Samidy Date: Mon, 9 Mar 2026 04:47:03 +0300 Subject: [PATCH 02/15] fix(anim-covers): not loading 99% of the time (i havent even properly tested this im js hoping it works) --- js/ui.js | 87 +++++++++++++++++++++++++++++--------------------------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/js/ui.js b/js/ui.js index 80cdfee..8f4f53e 100644 --- a/js/ui.js +++ b/js/ui.js @@ -1025,11 +1025,11 @@ export class UIRenderer { if (currentImage.tagName === 'IMG') { const video = document.createElement('video'); video.src = videoCoverUrl; - video.autoplay = false; - video.loop = false; + video.autoplay = true; + video.loop = true; video.muted = true; video.playsInline = true; - video.preload = 'metadata'; + video.preload = 'auto'; video.className = currentImage.className; currentImage.replaceWith(video); } else if (currentImage.src !== videoCoverUrl) { @@ -2590,16 +2590,22 @@ export class UIRenderer { } setupHlsVideo(video, result, fallbackImg) { - const url = result.videoUrl || result.hlsUrl; + if (!result) return; + const url = typeof result === 'string' ? result : (result.videoUrl || result.hlsUrl); if (!url) return; if (url.endsWith('.m3u8')) { if (Hls.isSupported()) { const hls = new Hls(); + video._hls = hls; hls.loadSource(url); hls.attachMedia(video); hls.on(Hls.Events.MANIFEST_PARSED, () => { - video.play().catch(() => {}); + video.play().catch((e) => { + console.warn('Autoplay failed, muted play might be required:', e); + video.muted = true; + video.play().catch(() => {}); + }); }); hls.on(Hls.Events.ERROR, (event, data) => { if (data.fatal) { @@ -2609,7 +2615,7 @@ export class UIRenderer { } }); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - // i heard safari supports HLS natively + // safari supports HLS natively video.src = url; } else { video.replaceWith(fallbackImg); @@ -2617,15 +2623,20 @@ export class UIRenderer { } else { // MP4 video.src = url; - video.onerror = () => { - if (result.hlsUrl) { - // HLS fallback (for some reason alot of animated covers js dont work on MP4 lol) - this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg); - } else { - video.replaceWith(fallbackImg); - } - }; + video.play().catch((e) => { + console.warn('MP4 autoplay failed:', e); + video.muted = true; + video.play().catch(() => {}); + }); } + video.onerror = () => { + if (result.hlsUrl) { + // HLS fallback (for some reason alot of animated covers js dont work on MP4 lol) + this.setupHlsVideo(video, { videoUrl: null, hlsUrl: result.hlsUrl }, fallbackImg); + } else { + video.replaceWith(fallbackImg); + } + }; } replaceVideoArtwork(container, type, id, result) { @@ -2637,11 +2648,11 @@ export class UIRenderer { const img = card.querySelector('.card-image'); if (img && img.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.autoplay = false; - video.loop = false; + video.autoplay = true; + video.loop = true; video.muted = true; video.playsInline = true; - video.preload = 'metadata'; + video.preload = 'auto'; video.className = img.className; video.id = img.id; video.style.objectFit = 'cover'; @@ -2672,16 +2683,6 @@ export class UIRenderer { img.replaceWith(video); this.setupHlsVideo(video, result, img); - - // If HLS, dont play - const hls = video._hls; - if (hls) { - hls.on(Hls.Events.MANIFEST_PARSED, () => { - // Dont play - }); - } else { - video.src = url; - } } } @@ -2946,11 +2947,11 @@ export class UIRenderer { const currentImageEl = document.getElementById('album-detail-image'); if (currentImageEl && currentImageEl.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.autoplay = false; - video.loop = false; + video.autoplay = true; + video.loop = true; video.muted = true; video.playsInline = true; - video.preload = 'metadata'; + video.preload = 'auto'; video.className = currentImageEl.className; video.id = currentImageEl.id; video.style.opacity = '1'; @@ -2966,18 +2967,19 @@ export class UIRenderer { const coverUrl = videoCoverUrl || this.api.getCoverUrl(album.cover); if (videoCoverUrl) { - if (imageEl.tagName === 'IMG') { + if (imageEl.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.src = videoCoverUrl; video.autoplay = true; video.loop = true; video.muted = true; video.playsInline = true; + video.preload = 'auto'; video.className = imageEl.className; video.id = imageEl.id; + this.setupHlsVideo(video, videoCoverUrl, imageEl); imageEl.replaceWith(video); } else { - imageEl.src = videoCoverUrl; + this.setupHlsVideo(imageEl, videoCoverUrl, null); } } else { if (imageEl.tagName === 'VIDEO') { @@ -3737,11 +3739,11 @@ export class UIRenderer { const currentImageEl = document.getElementById('mix-detail-image'); if (currentImageEl && currentImageEl.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.autoplay = false; - video.loop = false; + video.autoplay = true; + video.loop = true; video.muted = true; video.playsInline = true; - video.preload = 'metadata'; + video.preload = 'auto'; video.className = currentImageEl.className; video.id = currentImageEl.id; video.style.opacity = '1'; @@ -4846,11 +4848,11 @@ export class UIRenderer { const currentImageEl = document.getElementById('track-detail-image'); if (currentImageEl && currentImageEl.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.autoplay = false; - video.loop = false; + video.autoplay = true; + video.loop = true; video.muted = true; video.playsInline = true; - video.preload = 'metadata'; + video.preload = 'auto'; video.className = currentImageEl.className; video.id = currentImageEl.id; video.style.opacity = '1'; @@ -4883,18 +4885,19 @@ export class UIRenderer { const coverUrl = videoCoverUrl || this.api.getCoverUrl(track.image || track.cover || track.album?.cover); if (videoCoverUrl) { - if (imageEl.tagName === 'IMG') { + if (imageEl.tagName !== 'VIDEO') { const video = document.createElement('video'); - video.src = videoCoverUrl; video.autoplay = true; video.loop = true; video.muted = true; video.playsInline = true; + video.preload = 'auto'; video.className = imageEl.className; video.id = imageEl.id; + this.setupHlsVideo(video, videoCoverUrl, imageEl); imageEl.replaceWith(video); } else { - imageEl.src = videoCoverUrl; + this.setupHlsVideo(imageEl, videoCoverUrl, null); } } else { if (imageEl.tagName === 'VIDEO') { From 11a828668c4dea2161e3551e967bca60528c1fdf Mon Sep 17 00:00:00 2001 From: SamidyFR <168582143+SamidyFR@users.noreply.github.com> Date: Mon, 9 Mar 2026 01:47:31 +0000 Subject: [PATCH 03/15] style: auto-fix linting issues --- js/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/ui.js b/js/ui.js index 8f4f53e..ee70705 100644 --- a/js/ui.js +++ b/js/ui.js @@ -2591,7 +2591,7 @@ export class UIRenderer { setupHlsVideo(video, result, fallbackImg) { if (!result) return; - const url = typeof result === 'string' ? result : (result.videoUrl || result.hlsUrl); + const url = typeof result === 'string' ? result : result.videoUrl || result.hlsUrl; if (!url) return; if (url.endsWith('.m3u8')) { From e8b15314d2c0d074af13aaf36a77afc7d285fcf0 Mon Sep 17 00:00:00 2001 From: edideaur Date: Mon, 9 Mar 2026 21:26:23 +0200 Subject: [PATCH 04/15] Update Dockerfile to use debian:unstable-slim --- .devcontainer/Dockerfile | 46 +++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index dd49f58..f7d5492 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,26 +1,52 @@ # ------------------------------------------------------------ # Base Image # ------------------------------------------------------------ -FROM mcr.microsoft.com/devcontainers/base:debian +FROM debian:unstable-slim + +ENV DEBIAN_FRONTEND=noninteractive +ENV LANG=C.UTF-8 +ENV LC_ALL=C.UTF-8 # ------------------------------------------------------------ # System Dependencies # ------------------------------------------------------------ -RUN apt update && apt upgrade -y && \ +RUN apt-get update && apt-get upgrade -y && \ apt-get install -y --no-install-recommends \ + curl \ + ca-certificates \ git \ - git-lfs \ + build-essential \ + sudo \ fish \ + unzip \ + xz-utils \ + libatomic1 \ + libc6 \ + wget \ nodejs \ - npm \ - curl + npm && \ + rm -rf /var/lib/apt/lists/* + +# ------------------------------------------------------------ +# Create Non-Root User +# ------------------------------------------------------------ +ARG USERNAME=devuser +ARG UID=1000 +ARG GID=1000 + +RUN groupadd --gid ${GID} ${USERNAME} && \ + useradd --uid ${UID} --gid ${GID} -m -s /usr/bin/fish ${USERNAME} && \ + echo "${USERNAME} ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +USER ${USERNAME} +WORKDIR /home/${USERNAME} # ------------------------------------------------------------ # Install Bun (Non-Root) # ------------------------------------------------------------ -ENV BUN_INSTALL="$HOME/.bun" -ENV PATH="$BUN_INSTALL/bin:$PATH" - +ENV BUN_INSTALL=/home/${USERNAME}/.bun +ENV PATH="${BUN_INSTALL}/bin:${PATH}" + RUN curl -fsSL https://bun.sh/install | bash # ------------------------------------------------------------ @@ -32,11 +58,11 @@ RUN curl -fsSL https://opencode.ai/install -o opencode-install && \ rm opencode-install # Add OpenCode to PATH permanently -ENV PATH="$HOME/.opencode/bin:$PATH" +ENV PATH="/home/${USERNAME}/.opencode/bin:${PATH}" # ------------------------------------------------------------ # Ensure fish is Default Shell # ------------------------------------------------------------ ENV SHELL=/usr/bin/fish -CMD ["fish"] \ No newline at end of file +CMD ["fish"] From 8b2dfc3b2d9d4adfc7353aba22bef879a4f06a62 Mon Sep 17 00:00:00 2001 From: edideaur Date: Mon, 9 Mar 2026 21:28:59 +0200 Subject: [PATCH 05/15] Update devcontainer configuration for new setup --- .devcontainer/devcontainer.json | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index bd87db0..3aee656 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -1,14 +1,22 @@ { - "name": "Monochrome Dev Container", + "name": "debian-npm-fish-devcontainer", "build": { - "context": "..", - "dockerfile": "./Dockerfile" + "dockerfile": "Dockerfile" }, - "postCreateCommand": "git config --local core.editor \"code --wait\" && git config --local commit.gpgsign false && npm install && bun install", + + "remoteUser": "devuser", + + "features": {}, + "customizations": { "vscode": { "extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] } }, - "mounts": ["source=${env:HOME}/.gitconfig,target=/home/vscode/.gitconfig,type=bind,consistency=cached"] + + "postCreateCommand": "npm install", + + "remoteEnv": { + "SHELL": "/usr/bin/fish" + } } From 473e5ba8b64f008e0bf50f95a1671e1ac5380b8a Mon Sep 17 00:00:00 2001 From: Samidy Date: Tue, 10 Mar 2026 04:58:17 +0300 Subject: [PATCH 06/15] feat(recommendations): Infinite Radio --- index.html | 73 ++++--- js/app.js | 32 +-- js/audio-context.js | 65 +++--- js/events.js | 410 +++++++++++++++++++----------------- js/player.js | 497 ++++++++++++++++++++++++++++++++------------ js/storage.js | 16 ++ js/ui.js | 119 ++++++----- styles.css | 52 +++++ 8 files changed, 828 insertions(+), 436 deletions(-) diff --git a/index.html b/index.html index d8e8e0c..08a88c0 100644 --- a/index.html +++ b/index.html @@ -36,12 +36,16 @@ - + +
  • Shuffle play
  • +
  • + Start Infinite Radio +
  • Start mix
  • Play next
  • Add to queue
  • @@ -2212,36 +2216,41 @@ @@ -5615,6 +5624,10 @@
+
+
+ Finding more songs for you... +
-
+
+ +