- Rust 51.5%
- Shell 29.7%
- HTML 18.8%
| assets | ||
| src | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
tui-remote
Shared SSH remote playback library for Rust TUI media apps.
Table of Contents
Prerequisites
sshbinary inPATH— for script deployment and emergency process killpython3on the remote host — for mpv/VLC IPC control (pause, seek, chapters)- SSH key-based auth configured in
~/.ssh/config
Installation
[dependencies]
tui-remote = { git = "https://git.kvndl.xyz/kvndl/tui-remote.git" }
use tui_remote::{DaemonConnection, DaemonStatus};
API
| Module | Exports |
|---|---|
ssh |
SshHostConfig, resolve_ssh_config, authenticate, ssh_worker, ssh_kill, shell_escape |
daemon |
DaemonConnection, DaemonStatus |
DaemonConnection
// Connect and start the daemon on the remote host
let mut conn = DaemonConnection::start("pihost", "mpv")?;
// false when another TUI session already holds the playback lock on this host
if !conn.is_primary {
eprintln!("read-only: another session controls playback");
}
| Method | Description |
|---|---|
start(host, player) |
Deploy daemon and open persistent SSH channel |
play(args) |
Start remote player with given arguments |
stop() |
Kill remote player |
status() |
Query playback state (DaemonStatus) |
getinfo() |
Get current player arguments if playing |
pause() |
Toggle pause (requires python3 on remote) |
seek(secs) |
Seek ±N seconds (requires python3 on remote) |
subtitle_toggle() |
Toggle subtitle visibility (mpv only) |
chapter_prev() |
Previous chapter (mpv only) |
chapter_next() |
Next chapter (mpv only) |
taillog(n) |
Fetch last N lines from remote player log |
quit() |
Shut down daemon and stop player |
send_keepalive() |
Send SSH keepalive packet — call every ~10s from idle event loop |
DaemonStatus
pub enum DaemonStatus {
Playing,
Stopped,
Error(String),
}
Daemon Protocol
The daemon script is deployed to /tmp/tui-daemon.sh on first connection and executed over a persistent SSH channel. It reads newline-delimited commands from stdin and writes responses to stdout. The player process survives SSH disconnects — reconnecting resumes the existing session.
| Command | Arguments | Response |
|---|---|---|
PLAY |
player args string | OK / ERR <msg> |
STOP |
— | OK |
STATUS |
— | PLAYING / STOPPED |
GETINFO |
— | PLAYING <args> / STOPPED |
PAUSE |
— | OK / ERR <msg> |
SEEK |
seconds (integer) | OK / ERR <msg> |
SUBTITLE |
— | OK / ERR <msg> |
CHAPTER_PREV |
— | OK / ERR <msg> |
CHAPTER_NEXT |
— | OK / ERR <msg> |
TAILLOG |
N (default 50) | LOG:<line> × N, then LOG_END |
QUIT |
— | OK |
On startup the daemon responds READY (primary controller) or READY_RO (read-only — another session holds the lock).
Multi-Session Behaviour
Multiple TUI instances connecting to the same host share a single player via a lock file on the remote. Only the first connection becomes primary and may issue PLAY, STOP, PAUSE, and other write commands. Subsequent connections are read-only (is_primary = false) and limited to STATUS, GETINFO, and TAILLOG. Write commands from a read-only connection return ERR locked: primary session controls playback.
The lock is released automatically when the primary session exits. If the primary crashes without cleaning up, the stale lock is detected on the next connection attempt and the incoming session takes over.
Example
use tui_remote::{DaemonConnection, DaemonStatus};
fn main() -> anyhow::Result<()> {
let mut conn = DaemonConnection::start("pihost", "mpv")?;
if !conn.is_primary {
// Another TUI session is already playing — observe only
match conn.status()? {
DaemonStatus::Playing => println!("remote is playing"),
_ => {}
}
return Ok(());
}
conn.play("https://example.com/stream.m3u8 --vo=drm")?;
match conn.status()? {
DaemonStatus::Playing => println!("playing"),
DaemonStatus::Stopped => println!("stopped"),
DaemonStatus::Error(e) => eprintln!("error: {}", e),
}
// In the TUI event loop — keep the connection alive between commands
conn.send_keepalive()?;
conn.stop()?;
Ok(())
}
License
MIT