From 1ae667bd6dca3e959a8384f75733df9203117af4 Mon Sep 17 00:00:00 2001 From: Simone Ianniciello Date: Fri, 10 Apr 2026 17:22:04 +0200 Subject: [PATCH 1/5] chore(gitignore): ignore autogenerated / devenv paths --- .gitignore | 6 +++++- android/.gitignore | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 408564e..256a172 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ bin/ auth_storage/ www neutralino.js -package-lock.json \ No newline at end of file +package-lock.json +# Direnv +.envrc +# vim +.lazy.lua diff --git a/android/.gitignore b/android/.gitignore index 48354a3..409e290 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -99,3 +99,7 @@ app/src/main/assets/public app/src/main/assets/capacitor.config.json app/src/main/assets/capacitor.plugins.json app/src/main/res/xml/config.xml + +# Generated by LS +.settings/ +.project From 6b3f78e6088cebaee1227eab41a3ce545a4468e4 Mon Sep 17 00:00:00 2001 From: Simone Ianniciello Date: Fri, 10 Apr 2026 17:24:14 +0200 Subject: [PATCH 2/5] fix(android): remove typo --- .../main/java/tf/monochrome/music/BackgroundAudioPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/java/tf/monochrome/music/BackgroundAudioPlugin.java b/android/app/src/main/java/tf/monochrome/music/BackgroundAudioPlugin.java index 42a6b43..96bc39d 100644 --- a/android/app/src/main/java/tf/monochrome/music/BackgroundAudioPlugin.java +++ b/android/app/src/main/java/tf/monochrome/music/BackgroundAudioPlugin.java @@ -32,7 +32,7 @@ public class BackgroundAudioPlugin extends Plugin { } } - `@PluginMethod` + @PluginMethod public void stop(PluginCall call) { try { Intent intent = new Intent(getContext(), AudioPlaybackService.class); From 56f86205052a85fc8ba396b004ec15c26c7b6b57 Mon Sep 17 00:00:00 2001 From: Simone Ianniciello Date: Fri, 10 Apr 2026 17:25:10 +0200 Subject: [PATCH 3/5] feat(player): use @capgo/capacitor-media-session for android compatibility --- android/app/capacitor.build.gradle | 1 + android/capacitor.settings.gradle | 3 ++ bun.lock | 7 ++- ios/App/CapApp-SPM/Package.swift | 8 +-- js/player.js | 84 ++++++++++++++---------------- package.json | 1 + 6 files changed, 55 insertions(+), 49 deletions(-) diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle index 4e379a3..fe3fb30 100644 --- a/android/app/capacitor.build.gradle +++ b/android/app/capacitor.build.gradle @@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { implementation project(':capacitor-app') implementation project(':capacitor-haptics') + implementation project(':capgo-capacitor-media-session') } diff --git a/android/capacitor.settings.gradle b/android/capacitor.settings.gradle index df5cd63..0603a5b 100644 --- a/android/capacitor.settings.gradle +++ b/android/capacitor.settings.gradle @@ -7,3 +7,6 @@ project(':capacitor-app').projectDir = new File('../node_modules/@capacitor/app/ include ':capacitor-haptics' project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/haptics/android') + +include ':capgo-capacitor-media-session' +project(':capgo-capacitor-media-session').projectDir = new File('../node_modules/@capgo/capacitor-media-session/android') diff --git a/bun.lock b/bun.lock index 8494616..ea7a58f 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@capacitor/core": "^8.2.0", "@capacitor/haptics": "^8.0.1", "@capacitor/ios": "^8.2.0", + "@capgo/capacitor-media-session": "^8.0.19", "@dantheman827/taglib-ts": "^0.1.5", "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", @@ -18,7 +19,7 @@ "@svta/common-media-library": "^0.18.1", "@types/wicg-file-system-access": "^2023.10.7", "@typescript-eslint/eslint-plugin": "^8.57.2", - "@uimaxbai/am-lyrics": "^1.1.7", + "@uimaxbai/am-lyrics": "^1.1.8", "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", "butterchurn": "^2.6.7", @@ -277,6 +278,8 @@ "@capacitor/ios": ["@capacitor/ios@8.3.0", "", { "peerDependencies": { "@capacitor/core": "^8.3.0" } }, "sha512-5Rtwv8SITKlYTt8lAZG+khnVIdzPtqbocH3eP+JkEmX1vpSMwx4TOKtT8OBz8gpQ+pUJDRp7DBYOv3U6l/obCw=="], + "@capgo/capacitor-media-session": ["@capgo/capacitor-media-session@8.0.19", "", { "peerDependencies": { "@capacitor/core": ">=8.0.0" } }, "sha512-m0eCJvnuYpxz3wj3Snc1kIHCr45XQOBgPwOhkId/xXZ01DzXsOpw/lIT8Fl/Y1AscpNxs2fF5Qoj4E8QMIyJJg=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260401.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZSmceM70jH6k+/62VkEcmMNzrpr4kSctkX5Lsgqv38KktfhPY/hsh75y1lRoPWS3H3kgMa4p2pUSlidZR1u2hw=="], "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260401.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7UKWF+IUZ3NXMVPsDg8Cjg0r58b+uYlfvs5Yt8bvtU+geCtW4P2MxRHmRSEo8SryckXOJjb/b8tcncgCykFu8g=="], @@ -675,7 +678,7 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.58.0", "", { "dependencies": { "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ=="], - "@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.1.7", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-hEwPl4dFmJ08sJf4VBaR7k7yxA3BNaoINS89j0KrkSFJYpCkohHDy24AIfzEMonPloJ3H6HBA55nCFMnAzm50w=="], + "@uimaxbai/am-lyrics": ["@uimaxbai/am-lyrics@1.1.8", "", { "dependencies": { "@babel/runtime": "^7.27.6", "lit": "^3.1.4" }, "peerDependencies": { "@lit/react": "^1.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@lit/react", "react"] }, "sha512-VcbrlB2cOmkOjElmivf2SZujDmj8UAUaBkXyIfJ8dYq/Iv4H3PxmQY/s9VaRfF6UTnCgfix8ZPll1T1MA8eS4A=="], "@vitest/browser": ["@vitest/browser@4.1.2", "", { "dependencies": { "@blazediff/core": "1.9.1", "@vitest/mocker": "4.1.2", "@vitest/utils": "4.1.2", "magic-string": "^0.30.21", "pngjs": "^7.0.0", "sirv": "^3.0.2", "tinyrainbow": "^3.1.0", "ws": "^8.19.0" }, "peerDependencies": { "vitest": "4.1.2" } }, "sha512-CwdIf90LNf1Zitgqy63ciMAzmyb4oIGs8WZ40VGYrWkssQKeEKr32EzO8MKUrDPPcPVHFI9oQ5ni2Hp24NaNRQ=="], diff --git a/ios/App/CapApp-SPM/Package.swift b/ios/App/CapApp-SPM/Package.swift index 80f1c8f..b94a368 100644 --- a/ios/App/CapApp-SPM/Package.swift +++ b/ios/App/CapApp-SPM/Package.swift @@ -11,9 +11,10 @@ let package = Package( targets: ["CapApp-SPM"]) ], dependencies: [ - .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.2.0"), + .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", exact: "8.3.0"), .package(name: "CapacitorApp", path: "../../../node_modules/@capacitor/app"), - .package(name: "CapacitorHaptics", path: "../../../node_modules/@capacitor/haptics") + .package(name: "CapacitorHaptics", path: "../../../node_modules/@capacitor/haptics"), + .package(name: "CapgoCapacitorMediaSession", path: "../../../node_modules/@capgo/capacitor-media-session") ], targets: [ .target( @@ -22,7 +23,8 @@ let package = Package( .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm"), .product(name: "CapacitorApp", package: "CapacitorApp"), - .product(name: "CapacitorHaptics", package: "CapacitorHaptics") + .product(name: "CapacitorHaptics", package: "CapacitorHaptics"), + .product(name: "CapgoCapacitorMediaSession", package: "CapgoCapacitorMediaSession") ] ) ] diff --git a/js/player.js b/js/player.js index 508e9d5..96e3a92 100644 --- a/js/player.js +++ b/js/player.js @@ -23,6 +23,7 @@ import { db } from './db.js'; import { SVG_CLOCK, SVG_ATMOS } from './icons.js'; import { UIRenderer } from './ui.js'; +import { MediaSession } from '@capgo/capacitor-media-session'; export class Player { static #instance = null; @@ -422,10 +423,8 @@ export class Player { } async setupMediaSession() { - if (!('mediaSession' in navigator)) return; - const setHandlers = async () => { - navigator.mediaSession.setActionHandler('play', async () => { + await MediaSession.setActionHandler({ action: 'play' }, async () => { const el = this.activeElement; // Initialize and resume audio context first (required for iOS lock screen) // Must happen before audio.play() or audio won't route through Web Audio @@ -444,11 +443,11 @@ export class Player { } }); - navigator.mediaSession.setActionHandler('pause', () => { + await MediaSession.setActionHandler({ action: 'pause' }, () => { this.activeElement.pause(); }); - navigator.mediaSession.setActionHandler('previoustrack', async () => { + await MediaSession.setActionHandler({ action: 'previoustrack' }, async () => { // Ensure audio context is active for iOS lock screen controls if (!audioContextManager.isReady()) { audioContextManager.init(this.activeElement); @@ -458,7 +457,7 @@ export class Player { this.playPrev(); }); - navigator.mediaSession.setActionHandler('nexttrack', async () => { + await MediaSession.setActionHandler({ action: 'nexttrack' }, async () => { // Ensure audio context is active for iOS lock screen controls if (!audioContextManager.isReady()) { audioContextManager.init(this.activeElement); @@ -469,24 +468,24 @@ export class Player { }); if (!this.isIOS) { - navigator.mediaSession.setActionHandler('seekbackward', (details) => { + await MediaSession.setActionHandler({ action: 'seekbackward' }, (details) => { const skipTime = details.seekOffset || 10; this.seekBackward(skipTime); }); - navigator.mediaSession.setActionHandler('seekforward', (details) => { + await MediaSession.setActionHandler({ action: 'seekforward' }, (details) => { const skipTime = details.seekOffset || 10; this.seekForward(skipTime); }); } - navigator.mediaSession.setActionHandler('seekto', (details) => { + await MediaSession.setActionHandler({ action: 'seekto' }, (details) => { if (details.seekTime !== undefined) { this.activeElement.currentTime = Math.max(0, details.seekTime); this.updateMediaSessionPositionState(); } }); - navigator.mediaSession.setActionHandler('stop', () => { + await MediaSession.setActionHandler({ action: 'stop' }, () => { this.activeElement.pause(); this.activeElement.currentTime = 0; this.updateMediaSessionPlaybackState(); @@ -2070,37 +2069,38 @@ export class Player { } updateMediaSession(track) { - if (!('mediaSession' in navigator)) return; - - // Force a refresh for picky Bluetooth systems by clearing metadata first - navigator.mediaSession.metadata = null; const coverId = track.album?.cover; const trackTitle = getTrackTitle(track); - navigator.mediaSession.metadata = new MediaMetadata({ - title: trackTitle || 'Unknown Title', - artist: getTrackArtists(track) || 'Unknown Artist', - album: track.album?.title || 'Unknown Album', - artwork: coverId - ? [ - { - src: this.api.getCoverUrl(coverId, '1280'), - sizes: '1280x1280', - type: 'image/jpeg', - }, - ] - : undefined, + // Force a refresh for picky Bluetooth systems by clearing metadata first + MediaSession.setMetadata({}) + .finally(() => + MediaSession.setMetadata({ + title: trackTitle || 'Unknown Title', + artist: getTrackArtists(track) || 'Unknown Artist', + album: track.album?.title || 'Unknown Album', + artwork: coverId + ? [ + { + src: this.api.getCoverUrl(coverId, '1280'), + sizes: '1280x1280', + type: 'image/jpeg', + }, + ] + : undefined, + }) + ) + .catch(() => {}) + .finally(() => { + this.updateMediaSessionPlaybackState(); + this.updateMediaSessionPositionState(); }); - - this.updateMediaSessionPlaybackState(); - this.updateMediaSessionPositionState(); } updateMediaSessionPlaybackState() { - if (!('mediaSession' in navigator)) return; const isPlaying = !this.activeElement.paused; - navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused'; + void MediaSession.setPlaybackState({ playbackState: isPlaying ? 'playing' : 'paused' }); // Start/stop Android foreground service to prevent background audio throttling this._updateBackgroundAudioService(isPlaying); @@ -2137,9 +2137,6 @@ export class Player { } updateMediaSessionPositionState() { - if (!('mediaSession' in navigator)) return; - if (!('setPositionState' in navigator.mediaSession)) return; - const el = this.activeElement; const duration = el.duration; @@ -2147,15 +2144,14 @@ export class Player { return; } - try { - navigator.mediaSession.setPositionState({ - duration: duration, - playbackRate: el.playbackRate || 1, - position: Math.min(el.currentTime, duration), - }); - } catch (error) { - console.log('Failed to update Media Session position:', error); - } + MediaSession.setPositionState({ + duration: duration, + playbackRate: el.playbackRate || 1, + position: Math.min(el.currentTime, duration), + }) + .catch((error) => { + console.log('Failed to update Media Session position:', error); + }); } async safePlay(element = this.activeElement) { diff --git a/package.json b/package.json index 2e03d4e..6a22ef6 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "@capacitor/core": "^8.2.0", "@capacitor/haptics": "^8.0.1", "@capacitor/ios": "^8.2.0", + "@capgo/capacitor-media-session": "^8.0.19", "@dantheman827/taglib-ts": "^0.1.5", "@ffmpeg/core": "^0.12.10", "@ffmpeg/ffmpeg": "^0.12.15", From ac08f750ab0559c78be1efabbdcb514212fdaa6b Mon Sep 17 00:00:00 2001 From: uimaxbai <61615730+uimaxbai@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:31:52 +0100 Subject: [PATCH 4/5] bump am-lyrics --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 89b12f2..911d9a0 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "type": "git", "url": "git+https://github.com/monochrome-music/monochrome.git" }, - "keywords": [], + "keywords": ["music"], "author": "", - "license": "ISC", + "license": "Apache-2.0", "bugs": { "url": "https://github.com/monochrome-music/monochrome/issues" }, @@ -77,7 +77,7 @@ "@svta/common-media-library": "^0.18.1", "@types/wicg-file-system-access": "^2023.10.7", "@typescript-eslint/eslint-plugin": "^8.57.2", - "@uimaxbai/am-lyrics": "^1.2.7", + "@uimaxbai/am-lyrics": "^1.2.8", "@vitest/web-worker": "^4.1.2", "appwrite": "^23.0.0", "butterchurn": "^2.6.7", From 411681a3cdb238029442e63d149a656f8deb934d Mon Sep 17 00:00:00 2001 From: edideaur Date: Mon, 13 Apr 2026 18:42:26 +0300 Subject: [PATCH 5/5] one line fix btw --- styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/styles.css b/styles.css index 0d1df0b..7605627 100644 --- a/styles.css +++ b/styles.css @@ -2577,7 +2577,6 @@ body.multi-select-mode .track-item:hover { min-height: 550px; display: flex; align-items: flex-end; - background-color: var(--card); } @media (max-width: 1024px) {