Watch together, hear your own soundtrack.
tineplayer.app · Download · Documentation · Screenshots
Play any video and send two soundtracks to two different audio devices at the same time. A second language on headphones, the original on speakers, audio description for whoever needs it. Many people experience films differently; TinePlayer lets you experience them together.
This README is for people working on TinePlayer. If you are here to use it, everything is on tineplayer.app: what it does, downloads for Windows, macOS and Linux, and the documentation.
TinePlayer is written in Rust, draws its interface with GTK 4, and plays everything through GStreamer. A video file's audio tracks are already separate streams inside the container, so the file is demuxed once and each chosen track is sent to its own output device from a single pipeline. Staying in step is not something that has to be maintained - it is all one clock.
The same split serves an audio description track, or a separate audio file sitting beside the video, which can be aligned to the picture automatically.
Charge © Blender Studio, CC BY 4.0
.\setup-windows.ps1 # Windows; ./setup-mac.sh on macOS, ./setup-linux.sh on Linux
cargo build --release
./target/release/tineplayerEach setup script installs only what is needed to build, and is safe to re-run.
main is the development branch and a build from it reports a version ending
in -dev; to build a release, check out its tag:
git clone --branch v1.0.0 https://github.com/scottarius/TinePlayerSee docs/building.md for what each script installs, what
to do on distributions without apt, and the traps around taking GTK and
GStreamer from different places.
Packaging - the Windows installer, the macOS disk images and the Debian
packages - is under packaging/.
Note
On macOS, build the packaged application rather than a local one if a screen reader matters to you. A build against Homebrew's GTK is silent to VoiceOver, because that build has no AccessKit backend compiled in. See Accessibility.
The Rust source is located in src/. The parts worth knowing about:
app/ |
The interface, a module per screen or panel - the media page, settings, the choosers, the file browser, input handling |
pipeline.rs, player.rs |
The GStreamer pipeline, and playback state |
audio.rs, subtitles.rs |
What each output and the subtitle layer can play, and what gets chosen automatically. One list and one selector each, so a preference and a command-line flag cannot disagree about what en means |
beside.rs |
Audio and subtitle files sitting next to the video, found by one convention read from both sides |
jellyfin.rs, kodi.rs |
Casting from Jellyfin, and handing playback back and forth with Kodi |
i18n.rs |
How the po/ catalogs are compiled into the binary |
config.rs, logging.rs |
config.yaml, positions.json and tineplayer.log |
Everything goes through cargo clippy --all-targets -- -D warnings and
cargo test before it lands. CI runs both on all three platforms, along with
cargo fmt, cargo deny, and checks that the translation template and the
bundled fonts still cover every interface string.
TinePlayer's interface is translated in .po files under po/. You do not
need to build anything and you do not need to know Rust - see
docs/translating.md for adding a language, and for
pointing a release build at your file with TINEPLAYER_PO so you can see your
work without compiling.
TinePlayer targets a deliberately conservative baseline of GTK 4.6 and GStreamer 1.18, so it builds on the distributions people actually run rather than only current ones. GTK 4.x and GStreamer 1.x are both API- and ABI-stable within their major version, so building against that baseline still runs correctly on much newer releases.
| System | GTK 4 | Status |
|---|---|---|
| Windows 10 / 11 | 4.20 (bundled with GStreamer) | Tested |
| macOS 26 (Apple Silicon) | 4.22 (Homebrew) | Tested |
| Raspberry Pi OS / Debian 12 (Bookworm) | 4.8 | Tested |
| Ubuntu 22.04 LTS | 4.6 | Installs and runs |
| Ubuntu 24.04 LTS | 4.14 | Installs and runs |
| Debian 13 (Trixie) | 4.18 | Installs and runs |
Both Wayland and X11 sessions are supported; the backend is chosen at runtime. Tested means a full run on real hardware: playback, two audio outputs and the whole interface. Installs and runs means the released package was installed on that release and the interface verified there, but playback and dual output have not been exercised. Reports welcome.
One consequence of that baseline: the GTK 4.14+ dmabuf zero-copy rendering
path is not used, so newer systems do not gain that particular optimization.
The OpenGL path used instead handles 1080p comfortably, including on a Pi 5.
Open an issue for anything broken, or start a discussion for anything else. Reports from people who use a screen reader or rely on audio description are especially useful.
The log is the most useful thing to attach - see Log file for where it is and what is in it. Security problems go privately rather than into an issue; SECURITY.md says what is already known and deliberate.
Written collaboratively with an AI assistant (Claude). While Claude wrote the bulk of the code, every architectural and design decision, and all testing and verification, was done by hand.
TinePlayer's own code is MIT. See LICENSE. The logo and the
artwork under data/branding/ are CC BY 4.0, with their own license file
beside them.
It builds on the following, none of which are vendored into this repository:
- GStreamer (LGPL 2.1) and GTK 4 (LGPL 2.1) are runtime dependencies, loaded as separate shared libraries and never vendored. Where they come from depends on the platform: the Windows and macOS packages ship them beside the executable, unmodified, with their license texts; the Linux package ships none of them and declares them as dependencies for apt to install from your distribution; a build from source uses whatever is already on the machine. Being separate shared libraries in every case, they can be replaced with your own build of the same version, which is what the LGPL asks for.
gst-plugin-gtk4(MPL-2.0) provides thegtk4paintablesinkelement and is statically linked into the binary, because it is not packaged by Debian or shipped with the GStreamer Windows installer. Depending on it as an ordinary crate avoids making every user build and install a plugin by hand. MPL-2.0 is file-scoped copyleft, so it combines with MIT code without affecting this project's own licensing; the plugin's source remains under MPL-2.0 and is available upstream.
Every Rust library compiled into TinePlayer, direct and transitive, is listed with its license in THIRD-PARTY.md. Nearly all are MIT or Apache-2.0, and their notices travel with the application as those licenses ask: the same list is readable in the application itself, under Settings → About TinePlayer → Third-Party Notices.


