Files
jellytau/src-tauri/Cargo.toml
dtourolle 5fcf58fa78 feat(player): the controller talks to one contract
DR-245. PlayerController now holds a MediaPlayer instead of a PlayerBackend,
and every engine reaches it through that contract.

Deliberately a seam swap, not four rewrites: the existing backends are carried
across by LegacyPlayer, so MPV keeps its EQ and normalisation, ExoPlayer keeps
its media session, and nothing loses a feature to the migration. MpvPlayer
stays available for conformance until it grows the audio-settings half.

The substantive change is at the load site. Where the controller used to call
load() and then play(), it now issues one open() carrying the item and where
to begin — so the window a start position could be lost in is gone from the
controller as well as from the engines.

`state()` maps the engine's Phase back onto PlayerState using the queue, which
is what knows the item. External behaviour is unchanged.

Supporting pieces:

  - The contract gains set_audio_settings/audio_settings as *provided*
    methods. Engines that cannot honour them say so through Capabilities and
    inherit a no-op, rather than every implementation carrying an Ok(()) it
    does not mean.
  - PlayerBackend is implemented for Box<dyn PlayerBackend>, without which the
    boxed engine built at the composition root cannot be handed to anything
    generic over the trait.
  - StreamSelection::for_queued_item rebuilds a selection for an item already
    in the queue, without re-negotiating. The transport falls back rather than
    being sniffed out of the URL — that substring check is what DR-230 removed
    — and needs_transcoding is an exact stand-in because every transcode this
    app requests is HLS (DR-140).
  - default-run = "jellytau". The conformance binary made a bare `cargo run`
    ambiguous, which broke `tauri dev` outright. Caught by running the app
    rather than by any suite, which is the argument for doing both.

789 tests, clippy -D warnings clean with and without the feature.
2026-08-22 22:12:51 +02:00

163 lines
6.7 KiB
TOML

[package]
name = "jellytau"
# The app. Named explicitly because the crate also builds
# `player-conformance`, and a second binary makes a bare `cargo run` —
# which `tauri dev` issues — ambiguous.
default-run = "jellytau"
version = "0.10.1"
description = "A cross-platform Jellyfin client"
authors = ["Duncan Tourolle <duncan@tourolle.paris>"]
license = "MIT"
repository = "https://gitea.tourolle.paris/dtourolle/jellytau"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "jellytau_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
# Keep debug info minimal to reduce target/ size in CI (line numbers in
# backtraces are preserved; the bulky full debuginfo is dropped).
[profile.dev]
debug = "line-tables-only"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
# protocol-asset serves cached thumbnails to the webview (asset://localhost on
# Linux/macOS, http://asset.localhost on Windows/Android); without it
# convertFileSrc yields a URL nothing answers. Paired with
# app.security.assetProtocol in tauri.conf.json, which scopes it to
# $APPDATA/thumbnails/** — the one directory still read through this protocol.
# Downloaded media went the same way until DR-137 moved it to the loopback media
# server, so the database, the encrypted-token fallback file and downloads/ are
# all outside the grant now.
# TRACES: UR-012, UR-071 | DR-134, DR-137, DR-198
tauri = { version = "2", features = ["protocol-asset"] }
tauri-plugin-opener = "2"
tauri-plugin-os = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
uuid = { version = "1", features = ["v4"] }
rand = "0.8"
tokio = { version = "1", features = ["sync", "rt-multi-thread", "time", "fs", "io-util", "macros"] }
tokio-util = "0.7"
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "stream", "json"] }
urlencoding = "2"
futures-util = "0.3"
async-trait = "0.1"
# SQLite for offline storage
tokio-rusqlite = "0.6"
rusqlite = { version = "0.32", features = ["bundled"] }
chrono = { version = "0.4", features = ["serde"] }
directories = "5"
# Secure credential storage (system keyring with encrypted file fallback)
keyring = "3"
aes-gcm = "0.10"
base64 = "0.22"
sha2 = "0.10"
getrandom = "0.2"
log = "0.4"
env_logger = "0.11"
# Persistent, rotating, redacted logging on every platform -- and on Android the
# only thing that puts Rust output into logcat at all (env_logger writes to
# stdout, which Android discards, which is why the backend was invisible on the
# platform where the hardest bugs live).
#
# TRACES: UR-078 | DR-218
tauri-plugin-log = "2"
# Zip for the diagnostics export bundle.
zip = { version = "2", default-features = false, features = ["deflate"] }
tauri-specta = { version = "=2.0.0-rc.21", features = ["derive", "typescript"] }
specta-typescript = "=0.0.9"
specta = { version = "=2.0.0-rc.22", features = ["chrono", "derive"] }
tiny_http = { version = "0.12.0", default-features = false }
# In-app update, desktop only.
#
# `cfg(desktop)` is not decoration: tauri-plugin-updater does not support
# Android at all -- an APK cannot replace itself, that is the package manager's
# job -- and building it for the Android target fails. Android is offered the
# releases page through tauri-plugin-opener instead (see the frontend's
# updateCheck module). tauri-plugin-process supplies the relaunch that has to
# follow a desktop install.
#
# The cfg is spelled out as "not android, not iOS" rather than `cfg(desktop)`:
# Cargo evaluates a [target.'cfg(...)'] table against *target-triple* cfgs only
# (target_os, target_arch, target_family, unix/windows). `desktop` is a cfg
# Tauri's build script emits for use in Rust source, so `cfg(desktop)` here
# matches nothing, silently drops the dependency, and the build then fails much
# later with "Permission updater:default not found".
#
# TRACES: UR-077 | DR-217
[target.'cfg(not(any(target_os = "android", target_os = "ios")))'.dependencies]
tauri-plugin-updater = "2"
tauri-plugin-process = "2"
# Linux-specific dependencies
[target.'cfg(target_os = "linux")'.dependencies]
hostname = "0.4"
libc = "0.2"
# The crates.io release of libmpv predates the MPV versions we support, so this
# tracks the upstream git repo.
#
# Pinned by `rev`, not `branch = "master"`. With a branch, the revision is
# whatever Cargo.lock happens to hold and any `cargo update` silently swaps in
# new upstream code -- for the one dependency here that is not from crates.io,
# is not signed, and links a C library into the player. The rev below is the
# commit the lockfile already resolved to, so this pins current behaviour rather
# than changing it. To take upstream fixes, bump this deliberately.
libmpv = { git = "https://github.com/ParadoxSpiral/libmpv-rs.git", rev = "3e6c389b716f52a595cc5e8e3fa1f96cb76b3de7" }
# The raw FFI bindings behind `libmpv`, pinned to the *same* revision so the two
# can never describe different ABIs.
#
# Needed because the safe crate's `render` module is an empty stub at this
# revision — the render API (`mpv_render_context_create` and friends) exists only
# in the sys bindings, which do carry all of it. `Mpv::ctx` is public, so the
# render context can be built over the same handle the safe wrapper drives. This
# is what makes native video reachable *without* first completing the libmpv2
# migration, which the spike's use of `libmpv2-sys` had implied was a
# prerequisite.
#
# TRACES: UR-080 | DR-230, IR-033
libmpv-sys = { git = "https://github.com/ParadoxSpiral/libmpv-rs.git", rev = "3e6c389b716f52a595cc5e8e3fa1f96cb76b3de7" }
# Same major as the one Tauri/wry already resolve, so `gtk_window()` and
# `default_vbox()` hand back types this crate can name rather than a second,
# incompatible GTK.
gtk = "0.18"
# JNI for Android ExoPlayer integration
[target.'cfg(target_os = "android")'.dependencies]
jni = "0.21"
ndk-context = "0.1"
[dev-dependencies]
tempfile = "3.24.0"
[features]
# Exposes the MediaPlayer conformance suite and the `player-conformance` binary
# to non-test builds, so an engine that cannot run in-process — ExoPlayer on a
# device — is driven by the same cases as the ones that can, rather than by a
# second checklist that drifts.
conformance = []
# A standalone runner for the conformance suite. Deliberately a separate binary:
# it links libmpv and nothing else, so a wrapper can be verified without building
# or launching the app.
[[bin]]
name = "player-conformance"
path = "src/bin/player_conformance.rs"
required-features = ["conformance"]