[package] name = "spotifast" default-run = "spotifast" version = "0.10.1" edition = "2024" rust-version = "1.95" license = "MIT" description = "A native Spotify client" repository = "https://github.com/crmne/spotifast" keywords = ["spotify", "music", "egui", "librespot", "player"] categories = ["multimedia::audio"] [lib] name = "spotifast" path = "src/lib.rs" [[bin]] name = "spotifast" path = "src/spotifast.rs" # Compatibility command for scripts and the updater in earlier releases. [[bin]] name = "fastpotify" path = "src/main.rs" [dependencies] anyhow = "1" thiserror = "2" clap = { version = "4.5", features = ["derive"] } serde = { version = "1", features = ["derive"] } serde_json = "1" log = "0.4" env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] } directories = "6" # A small translation trait for catalogs generated from PO files at build time. # No system libintl, global locale, or runtime catalog parsing is needed. tr = { version = "0.1.11", default-features = false } # The operating system's preferred languages, so the interface can follow # them: the preferred-language APIs on macOS and Windows and the POSIX # locale variables elsewhere, without platform code of our own. sys-locale = "0.3" # One protected-store API, with only each target's native provider below. # Durable Spotify grants must not be copied into ordinary state JSON. keyring-core = "1" # The whole UI. glow keeps the binary and the dependency tree small; wgpu is # not needed for a 2D music client. # AccessKit connects egui's control names, states, and actions to native screen readers. eframe = { version = "0.36", default-features = false, features = ["accesskit", "glow", "default_fonts", "links", "wayland", "x11", "persistence"] } egui = "0.36" egui_extras = { version = "0.36", features = ["image", "svg"] } # Reading what a system font covers, so the app can borrow a face for a script # Inter cannot draw. skrifa is the parser epaint already rasterizes with, and # the version has to track epaint's or the binary compiles two copies of it; # memmap2 keeps the probe to the few pages of each font that answer the # question. skrifa = "0.44" memmap2 = "0.9" # bmp is for Winamp skins, whose bitmaps are BMP files. image = { version = "0.25", default-features = false, features = ["jpeg", "png", "bmp"] } # Inflating the zip a Winamp skin comes as (src/skin/zip.rs). Already # compiled for reqwest's gzip support, so this adds no code to the build. flate2 = "1" # Filling glyph outlines with no anti-aliasing for the Winamp playlist's # text (src/ui/winamp/pixel_text.rs). Already compiled for the SVG icons # through egui_extras and resvg, so this adds no code to the build. tiny-skia = "0.11" # MilkDrop, as libprojectM reimplements it, for the MilkDrop window # (src/milkdrop.rs). Built into the binary, so it stays one file. projectm-sys = { version = "1.2.2", default-features = false, features = ["static"], optional = true } # The MilkDrop window runs as a child process with its own winit event loop # and OpenGL context (src/milkdrop/child.rs), because winit allows one event # loop per process and eframe owns the app's. These are the same versions # eframe already pulls in, so they add no build. Only compiled with the # milkdrop feature. winit = { version = "0.30", optional = true } glutin = { version = "0.32", optional = true } glutin-winit = { version = "0.5", optional = true } # Detect the live window backend, including when MilkDrop is disabled. # eframe already compiles this crate. raw-window-handle = "0.6" # Spotify playback and Connect. rustls everywhere so the client links one TLS # stack and builds without OpenSSL on every platform. # # librespot-core depends on hyper-proxy2 for HTTP proxy support. The # published 0.1.0 release pins rustls 0.22 / rustls-webpki 0.102; we patch # it (see [patch.crates-io]) onto rustls 0.23 so CONNECT-then-TLS uses the # same stack as the rest of the client. SOCKS5 still covers the Web API but # not the engine: librespot only speaks HTTP CONNECT. librespot-core = { version = "0.8", default-features = false, features = ["rustls-tls-native-roots"] } librespot-playback = { version = "0.8", default-features = false, features = ["rodio-backend", "rustls-tls-native-roots"] } librespot-connect = { version = "0.8", default-features = false } librespot-metadata = { version = "0.8", default-features = false } librespot-protocol = { version = "0.8", default-features = false } # Parses the rootlist's playlist4 protobuf, the same crate librespot generates with. protobuf = "3" # Local playback's audio sink is this crate's own (src/sink.rs): librespot's # rodio backend unwraps the device open on the player thread, and a release # build aborts on panic. Same rodio and cpal versions librespot-playback uses, # so nothing new is compiled. rodio = { version = "0.21", default-features = false, features = ["playback"] } cpal = "0.16" # `socks` is SOCKS5 proxy support for the Web API, artwork, lyrics, and # update checks. HTTP proxies work without it. `system-proxy` reads the OS # proxy on macOS and Windows when the mode is System; Linux uses environment # variables either way. reqwest = { version = "0.12", default-features = false, features = ["rustls-tls-native-roots", "json", "gzip", "http2", "blocking", "socks", "system-proxy"] } # Same System-proxy matcher reqwest uses, so local playback follows the same # HTTP proxy (environment, and the OS proxy on macOS and Windows). hyper-util = { version = "0.1", default-features = false, features = ["client-proxy", "client-proxy-system"] } http = "1" # `fs` is used by the playlist cache in `backend.rs`. On Linux the # mpris-server dependency happens to turn it on, which is why leaving it out # built there and nowhere else. `net` is the local HTTP-proxy tests. tokio = { version = "1", features = ["rt-multi-thread", "sync", "time", "macros", "fs", "net", "io-util"] } sha1 = "0.10" sha2 = "0.10" base64 = "0.22" # Native asynchronous file selection on macOS, Windows and Linux desktop portals. rfd = "0.17.2" rand = "0.9" open = "5" percent-encoding = "2" urlencoding = "2" jiff = "0.2" mdns-sd = "0.21" unicode-bidi = "0.3" hmac = "0.12" pbkdf2 = "0.12" aes = "0.8" ctr = "0.9" [target.'cfg(not(target_os = "linux"))'.dependencies] # The tray item and the desktop's media controls on Windows and macOS, so # closing the window keeps the music playing there too. Linux has ksni and # mpris-server below. tray-icon = { version = "0.24", default-features = false } souvlaki = { version = "0.8", default-features = false } [target.'cfg(target_os = "macos")'.dependencies] apple-native-keyring-store = { version = "1.0.2", features = ["keychain"] } # Serving AppKit while no window exists: the status item and Dock reopen # callback live on the main thread. "exception" lets # src/mac_touchbar_crash_guard.rs catch a specific AppKit Objective-C # exception instead of letting it abort the process. objc2 = { version = "0.6", features = ["exception"] } objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSEvent", "NSMenu", "NSMenuItem", "NSResponder", "NSRunningApplication", "NSWindow"] } # NSAppleEventManager and NSAppleEventDescriptor carry the Spotify links # macOS hands an app registered for the scheme (src/mac_links.rs), and # NSUserDefaults reads its title-bar double-click action. objc2-foundation = { version = "0.3", default-features = false, features = ["std", "NSAppleEventDescriptor", "NSAppleEventManager", "NSDate", "NSObjCRuntime", "NSRunLoop", "NSString", "NSUserDefaults"] } [target.'cfg(windows)'.dependencies] windows-native-keyring-store = { version = "1.1", default-features = false } # A hidden window for the media controls to belong to, and message loops. windows-sys = { version = "0.60", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Graphics_Gdi", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_LibraryLoader", "Win32_System_Registry", "Win32_System_Threading", "Win32_UI_Accessibility", "Win32_UI_HiDpi", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] } [target.'cfg(target_os = "linux")'.dependencies] # Reuses the zbus/Tokio stack already used for desktop integration. zbus-secret-service-keyring-store = { version = "1.0.1", features = ["rt-tokio-crypto-rust"] } ksni = { version = "0.3", features = ["blocking"] } librespot-playback = { version = "0.8", default-features = false, features = ["rodio-backend", "pulseaudio-backend", "rustls-tls-native-roots"] } mpris-server = { version = "0.10", features = ["tokio"] } # The bus mpris-server already speaks, named directly so its interface macro # resolves: the running instance answers `Open` on its own name for the # launch a Spotify link makes (src/single_instance.rs). Same version, so # nothing new is compiled. zbus = { version = "5", default-features = false, features = ["tokio"] } # Asks the Wayland compositor at startup whether it reports hidden windows as # suspended (xdg-shell v6), which decides whether vsync is safe. winit already # compiles this version. wayland-client = "0.31" # Keeps the X11 mini player out of the taskbar with _NET_WM_STATE_SKIP_TASKBAR # (src/window.rs), which winit only offers on Windows. winit already # compiles this version. x11rb = { version = "0.13", default-features = false } [build-dependencies] # Validate/filter unfinished PO entries, then compile catalog text and gettext # plural expressions to Rust. Ordinary builds do not need gettext tools. polib = "0.3" include-po = "0.2" [dev-dependencies] # Exercise the same PO compiler used by build.rs with partial catalogs. polib = "0.3" # Run connection-deadline regressions with a virtual clock. tokio = { version = "1", features = ["test-util"] } # The icon and version information Windows shows for the executable. [target.'cfg(windows)'.build-dependencies] winresource = "0.1" [features] default = ["milkdrop"] # Sample data and screenshot flags; never part of a release build. demo = [] # MilkDrop, through libprojectM: a C++ library that CMake builds into the # binary at compile time, so building needs cmake, a C++ compiler, and # libclang. `--no-default-features` leaves it out. milkdrop = ["dep:projectm-sys", "dep:winit", "dep:glutin", "dep:glutin-winit"] [profile.release] lto = "thin" codegen-units = 1 strip = true panic = "abort" # Audio decoding and immediate-mode layout are too slow at opt-level 0 to # audition the app in development. Keep every dependency optimized while the # application crate itself stays incremental. [profile.dev.package."*"] opt-level = 2 # projectm-sys names the library it links after Rust's profile (a `d` suffix # for dev) but CMake names what it builds after the optimisation level, so an # optimised dev build would look for a library that was never made. A debug # build of libprojectM is fine to develop against; releases are optimised. [profile.dev.package.projectm-sys] opt-level = 0 # projectm-sys 1.2.2 hardcodes the Visual Studio 17 2022 CMake generator, # which GitHub's windows-latest images no longer carry; the fork lets an # explicit CMAKE_GENERATOR win. Drop when upstream takes the change. [patch.crates-io] # winit 0.30.13 plus Wayland `Occluded` events for the xdg-shell suspended # state, without a redraw request while suspended. eframe then stops painting # a hidden window instead of blocking in eglSwapBuffers, so vsync stays safe on # Wayland. Proposed upstream for rust-windowing/winit#3442; drop the fork when # a release includes it. winit = { git = "https://github.com/crmne/winit", rev = "c8df61a52b8b563e1bb65e0f1d8630289d1c1bd6" } projectm-sys = { git = "https://github.com/crmne/projectm-rs", branch = "respect-cmake-generator" } # hyper-proxy2's unreleased rustls 0.23 update plus our upstream provider # compatibility fix. Drop this fork when a release includes both changes. hyper-proxy2 = { git = "https://github.com/crmne/hyper-proxy2", rev = "efca4811371ddae81c06be168de54194938d70b1" } # librespot 0.8.0 plus the small patches Spotifast needs: queue controls, # normalisation-factor reporting for the visualisers, and a distinct event # when Spotify refuses an audio key, bounded socket setup with DNS address # fallback, an in-memory credential cache for platform storage, and full # playback-state recovery after a session disconnect. # Recovery is proposed upstream in librespot-org/librespot#1765. # Every librespot crate comes from the # fork so one copy exists. Drop patches as upstream takes them. librespot-audio = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-connect = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-core = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-metadata = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-oauth = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-playback = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" } librespot-protocol = { git = "https://github.com/crmne/librespot", rev = "669e004ec7a79e6eea0f87779efc69680793d3b7" }