lyra ships a LyraKit library product alongside the lyra executable so
sibling apps can reuse lyra's building blocks over SwiftPM instead of
re-implementing them. It exists for the planned lyra-screensaver .saver
bundle (#325), which drives the same video-wallpaper pipeline the daemon uses
— reading the user's existing ~/.config/lyra/config.toml [wallpaper] set and
~/.cache/lyra/wallpapers/ cache — but any Swift target on macOS 14+ can depend
on it.
Stability:
LyraKitis an internal-reuse surface, not a SemVer-stable public API. It re-exports modules that evolve with the app; pin an exact version and expect to adjust on upgrades.
- macOS 14+
- Swift 6.0+
// Package.swift
dependencies: [
.package(url: "https://github.com/GeneralD/lyra.git", from: "2.22.0"),
],
targets: [
.target(
name: "MyApp",
dependencies: [
.product(name: "LyraKit", package: "lyra"),
]
),
]LyraKit is the product name. In SwiftPM a product name is not itself an
importable module — but LyraKit is also an umbrella target that
@_exported imports the reuse surface, so a single import LyraKit brings in
everything below.
import LyraKit| Re-exported module | What you get |
|---|---|
Entity |
Pure data types — ResolvedWallpaperItem, WallpaperState, WallpaperPlaybackMode, AppStyle, … |
Domain |
@Dependency keys + protocols — WallpaperInteractor, ConfigUseCase, … |
Presenters |
Ready-made presentation engines — WallpaperPresenter (the AVPlayer loop / trim / cycle controller, NSWindow-free) |
DependencyInjection |
liveValue wiring, so @Dependency resolves to the real implementations without you re-registering the graph |
WallpaperPresenter is the highest-level entry point: an @MainActor
ObservableObject that resolves the configured wallpaper set (local files,
HTTP(S), YouTube — downloaded and cached exactly as the daemon does), owns the
AVPlayer lifecycle (loop, per-item trim, cycle / shuffle advance), and reacts
to system sleep / wake. Construct it, call start(), and observe its published
player:
import LyraKit
import SwiftUI
import AVKit
struct WallpaperView: View {
@StateObject private var presenter = WallpaperPresenter()
var body: some View {
ZStack {
Color.black
if let player = presenter.player {
VideoPlayer(player: player)
.scaleEffect(presenter.wallpaperScale)
}
}
.onAppear { presenter.start() }
.onDisappear { presenter.stop() }
}
}The wallpaper set comes from lyra's own config file — nothing to pass in. Point
lyra's [wallpaper] at whatever you like and every LyraKit consumer picks it
up. (For a controls-free, aspect-fill surface, host an AVPlayerLayer instead
of VideoPlayer and set its videoGravity — presenter.player is the same
AVPlayer either way.)
If you drive your own player, consume the interactor's stream directly. This
path uses the @Dependency property wrapper, so add
swift-dependencies to
your target as well:
import LyraKit
import Dependencies
@Dependency(\.wallpaperInteractor) var wallpaper
for await item in wallpaper.resolvedWallpapers() {
// item.url — a local file path (remote / YouTube already downloaded & cached)
// item.start — optional trim-in (seconds)
// item.end — optional trim-out (seconds)
// item.scale — per-item zoom (>= 1.0), e.g. to hide letterboxing
play(item.url)
}.cycle mode emits items in configured order; .shuffle emits each as it
resolves. Read wallpaper.playbackMode first to decide how to advance.
The umbrella excludes only the top of the stack: the CLI (CLI), the AppKit
foreground lifecycle (App), and the overlay views (Views / AppWindow).
A .saver or embedding host supplies its own window, so it never links the
SwiftUI rendering layer or the executable entry point.
It does not trim below that. Presenters is re-exported whole, so the
track / lyrics / spectrum / ripple presenters come along even in a
wallpaper-only host — they are one module, and you simply don't instantiate the
ones you don't use. And because DependencyInjection registers the full live
graph, a consumer transitively links the entire implementation (MediaRemote /
Audio included) even when it only ever drives the wallpaper path. So LyraKit is
about single-import reuse, not a minimal binary. If you need a lighter,
wallpaper-only slice, see #325: splitting the DI registrations per feature is
a tracked follow-up.
