Commit graph

93 commits

Author SHA1 Message Date
49a75f8118
Merge pull request #6 from goetzc/fix/high-cpu-usage
Fix high CPU usage: optimize artwork fetches and idle wake-ups
2026-03-01 12:57:28 +11:00
Götz
092f731b57 Revert heartbeat removal 2026-02-28 20:53:55 -05:00
5a2c5bb372
Merge pull request #4 from goetzc/fix/cli-help-version-early-exit
cli: exit early for --help/--version without starting app
2026-03-01 12:43:52 +11:00
da656ede74
Merge pull request #5 from goetzc/fix/memory-leak
Fix CoreFoundation high memory usage: clean up timers and file descriptors
2026-03-01 12:39:00 +11:00
Götz
5120b938ef Optimize idle wake-ups 2026-02-28 19:39:49 -05:00
Götz
fa82f45ef9 Avoid repeated artwork miss fetches and refresh churn
Lowers CPU usage:
- Only call refresh() when real artwork is found
- Treating NoArtwork as a valid cached result (art is not None check).
- Adding in-flight dedupe (pending_tracks) so the same track doesn't spawn parallel fetches.
2026-02-28 19:39:39 -05:00
Götz
9b910cd991 Remove concurrent heartbeat pings on the same MPD client
- idle() and periodic ping() on one connection can cause churn/wakeups

- Normalized mixrampdb == "nan" to 0
2026-02-28 19:39:39 -05:00
Götz
897cb383eb Avoids wakeups from noisy unrelated subsystems 2026-02-28 19:39:39 -05:00
Götz
14717b4866 Fix CF high memory usage: clean up timers and file descriptors
Resolved resident memory growth during long-running idle sessions.
Fixes: https://github.com/00dani/mpd-now-playable/issues/3

The CoreFoundation asyncio bridge was leaking native resources over time.
This change explicitly invalidates one-shot CFRunLoop timers after execution,
invalidates timers on cancellation, and invalidates CFFileDescriptor objects
when unregistering the final callback source.
2026-02-28 17:01:19 -05:00
Götz
3411a5a34d cli: exit early for --help/--version without starting app
The CLI previously always started full app initialization (config load, listener
setup, receiver construction, and debug output) even when invoked with `-h`,
`--help`, `-v`, or `--version`. That made basic introspection noisy and buried
the requested output in startup logs.

Add early argument checks in `main()` so help/version requests are handled
immediately and the process exits without starting the app. Introduce a small
`print_help()` helper for consistent usage output.

This improves terminal UX and makes debugging/invocation checks much clearer by
keeping `-h` and `-v` output focused and predictable.
2026-02-28 15:44:49 -05:00
28748df3c1
feat: hide running Cocoa receiver from the Dock 2025-04-30 12:46:47 +10:00
c2f67c4781
style: apply formatting fixes to AppKit code 2025-04-30 12:46:17 +10:00
413df0979d
Convert MPD_LOGO to the right type for ObjC 2025-03-05 13:17:36 +11:00
7dfd3f85e4
Make Queue.current nullable, since MPD may be stopped 2025-03-05 13:16:48 +11:00
b41339a8c5
Update schemata to accommodate changes to WebSockets 2025-01-23 18:57:11 +11:00
b9039b2ad4
Fix surprise incompatibility with websockets 14 :/ 2025-01-23 18:56:43 +11:00
41f5369b2f
Safe-update dependencies, following semver 2025-01-23 18:22:12 +11:00
e1156b47de
Remove unnecessary dependency on attrs 2025-01-23 17:53:10 +11:00
452867699e
Update Mypy so I can use PEP 695 type param syntax 2024-07-30 10:09:33 +10:00
3ef3112014
Load crossfade settings into Playback.settings too 2024-07-29 11:14:48 +10:00
c29f4b9b27
Add 'heartbeat' to MPD client, so we notice if we disconnect 2024-07-29 10:55:27 +10:00
68609f3d07
Wrap Song in a broader Playback state object with stuff like volume and repeat mode 2024-07-26 09:53:17 +10:00
085bca7974
Declare nextsong index as part of MPD status response 2024-07-26 09:49:45 +10:00
dbd507bccb
Support current songs with no duration, such as streams 2024-07-23 13:38:41 +10:00
012bc0b025
Generate serialisation schema for songs, not validation schema 2024-07-23 13:34:50 +10:00
d9c8e0fe28
Find the current song's URL and pass it on when possible 2024-07-23 13:30:06 +10:00
b8bcdc5a83
Wrap MPD's state into a transfer struct before finalising the Song 2024-07-23 13:12:06 +10:00
1bb2032b9f
Ditch convert_if_exists, just use option_fmap which I prefer 2024-07-23 11:04:18 +10:00
fda799e32e
Fix inheritance of MusicBrainzTags into MPD response types 2024-07-23 10:52:43 +10:00
30e0829ff3
Update MusicBrainz tag shape in song schema 2024-07-23 10:52:10 +10:00
e2268c0c34
Allow websocket server to reuse its port (handle crashes better) 2024-07-23 10:45:06 +10:00
1e6dffcdcc
Support multivalued tags for MusicBrainz IDs too 2024-07-23 10:43:22 +10:00
86761bc420
Don't worry about ormsgpack import error, it's always required now 2024-07-23 10:37:38 +10:00
21b7c28692
Add descriptions to websockets config 2024-07-13 19:28:18 +10:00
ca5086f93a
Fix path to MPD logo in Cocoa receiver (oops) 2024-07-13 18:38:16 +10:00
582a4628b7
Introduce new WebSockets receiver impl
When enabled, this new receiver will spin up a local WebSockets server
and will send the currently playing song information to any clients that
connect. It's designed with Übersicht in mind, since WebSockets is the
easiest way to efficiently push events into an Übersicht widget, but
I'm sure it'd work for a variety of other purposes too.

Currently the socket is only used in one direction, pushing the current
song info from server to client, but I'll probably extend it to support
sending MPD commands from WebSockets clients as well.
2024-07-13 18:34:53 +10:00
75206a97f1
Add extra for websockets support 2024-07-11 12:17:09 +10:00
04859b8c8b
Adjust receiver protocol to accommodate config 2024-07-11 12:15:34 +10:00
09fe3b3e6c
Expand MusicBrainz support to be much more comprehensive 2024-07-11 12:12:56 +10:00
60116fd616
Make PyObjC a Darwin-only dependency 2024-07-10 23:57:34 +10:00
00ba34bd0b
Refactor Cocoa stuff into a 'receiver'
The idea here is that there are other places that might want to know
what's playing, besides MPNowPlayingInfoCenter. For example, to expose
the now playing info to Übersicht efficiently, it needs to be available
from a web browser, ideally using WebSockets. So there could be a
receiver that runs a small WebSockets server and sends out now playing
info to anyone who connects.

Additionally, I hope to write receivers for MPRIS and for the System
Media Transport Controls on Windows, making mpd-now-playable equally
useful across all platforms.

None of this is implemented yet, of course, but I hope to get the
WebSockets receiver done pretty soon!

I'm going to keep the default behaviour unchanged. Unless you
explicitly configure different receivers in config.toml,
mpd-now-playable will just behave as an MPNowPlayingInfoCenter
integration as it's always done.
2024-07-09 12:52:49 +10:00
27d8c37139
Significantly overhaul configuration management
Everything now uses bog-standard Python dataclasses, with Pydantic
providing validation and type conversion through separate classes using
its type adapter feature. It's also possible to define your classes
using Pydantic's own model type directly, making the type adapter
unnecessary, but I didn't want to do things that way because no actual
validation is needed when constructing a Song instance for example.
Having Pydantic do its thing only on-demand was preferable.

I tried a number of validation libraries before settling on Pydantic for
this. It's not the fastest option out there (msgspec is I think), but it
makes adding support for third-party types like yarl.URL really easy, it
generates a nice clean JSON Schema which is easy enough to adjust to my
requirements through its GenerateJsonSchema hooks, and raw speed isn't
all that important anyway since this is a single-user desktop program
that reads its configuration file once on startup.

Also, MessagePack is now mandatory if you're caching to an external
service. It just didn't make a whole lot sense to explicitly install
mpd-now-playable's Redis or Memcached support and then use pickling with
them.

With all this fussing around done, I'm probably finally ready to
actually use that configuration file to configure new features! Yay!
2024-07-01 00:10:17 +10:00
3b7ddfa718
fix: handle missing environment variables more robustly 2024-06-28 14:14:24 +10:00
fcf7254e64
Remove deprecated Ruff option from pyproject.toml 2024-06-23 17:32:29 +10:00
1eca56b40e
Update Ruff and PyObjC versions 2024-06-23 17:32:10 +10:00
bc56686fc4
Support multivalued song tags (fixes #1)
python-mpd2 unreliably returns either a single value or a list of
values for commands like currentsong, which is super fun if you're
trying to write type stubs for it that statically describe its
behaviour. Whee.

Anyway, I ended up changing my internal song model to always use lists
for tags like artist and genre which are likely to have multiple values.
There's some finagling involved in massaging python-mpd2's output into
lists every time. However it's much nicer to work with an object that
always has a list of artists, even if it's a list of one or zero
artists, rather than an object that can have a single artist, a list of
multiple artists, or a null. So it's worth it.

The MPNowPlayingInfoCenter in MacOS only works with single string values
for these tags, not lists, so we have to join the artists and such into
a single string for its consumption. I'm using commas for the separator
at the moment, but I may make this a config option later on if there's
interest.
2024-06-23 17:24:37 +10:00
2f70c6f7fa
Prettier-print the generated config schema 2024-06-22 20:12:50 +10:00
3cb5db7528
Add field descriptions to the config schema :) 2024-06-22 20:12:23 +10:00
2def2aece5
Organise 'tools' modules into a subpackage 2024-06-22 18:48:32 +10:00
35703de261
Gitignore __version__.py to avoid spurious diffs 2024-06-22 18:41:49 +10:00