Merge branch 'main' of github.com:monochrome-music/monochrome

This commit is contained in:
Samidy 2026-04-13 18:49:37 +03:00
commit 5f8cb6e947
10 changed files with 66 additions and 53 deletions

6
.gitignore vendored
View file

@ -16,4 +16,8 @@ bin/
auth_storage/
www
neutralino.js
package-lock.json
package-lock.json
# Direnv
.envrc
# vim
.lazy.lua

4
android/.gitignore vendored
View file

@ -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

View file

@ -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')
}

View file

@ -32,7 +32,7 @@ public class BackgroundAudioPlugin extends Plugin {
}
}
`@PluginMethod`
@PluginMethod
public void stop(PluginCall call) {
try {
Intent intent = new Intent(getContext(), AudioPlaybackService.class);

View file

@ -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')

View file

@ -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",
@ -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=="],

View file

@ -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")
]
)
]

View file

@ -24,6 +24,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;
@ -423,10 +424,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
@ -445,11 +444,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);
@ -459,7 +458,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);
@ -470,24 +469,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();
@ -2091,37 +2090,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);
@ -2158,9 +2158,6 @@ export class Player {
}
updateMediaSessionPositionState() {
if (!('mediaSession' in navigator)) return;
if (!('setPositionState' in navigator.mediaSession)) return;
const el = this.activeElement;
const duration = el.duration;
@ -2168,15 +2165,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) {

View file

@ -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"
},
@ -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",
@ -76,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",

View file

@ -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) {