- Python 100%
| src/mpvtv | ||
| tests | ||
| .gitignore | ||
| pyproject.toml | ||
| README.md | ||
mpvtv
mpvtv beams media from a Mac to an Android TV running mpv for Android.
It plays:
- local media files on your Mac
- direct playback URLs
- cached Premiumize magnet links
The primary interface is a full-screen terminal app. Scripted and unattended playback is available with quiet mode.
mpvtv is Mac-first software. It assumes macOS, Wi-Fi ADB, and an Android TV on the same network.
Contents
- Requirements
- Installation
- First Run
- Interactive Playback
- Quiet Mode
- Sources
- Subtitles
- Playback Controls
- TV Management
- Premiumize
- macOS Magnet Handler
- Configuration
- How Playback Works
- CLI Reference
- Troubleshooting
- Development
Requirements
mpvtv needs:
- macOS
- Python 3.11 or newer
- Android platform-tools, for
adb - an Android TV with network ADB enabled
- mpv for Android installed on the TV, or permission for
mpvtvto install it - a Premiumize API key for magnet playback
Install Android platform-tools with Homebrew:
brew install android-platform-tools
Confirm adb is available:
adb version
The TV must be reachable from the Mac by IP address. If your TV uses Android's wireless debugging workflow, enable it on the TV before using mpvtv.
Installation
The recommended install method is pipx, which gives mpvtv its own Python environment and puts the mpvtv command on your shell path.
From a checkout of this repository:
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install .
Open a new terminal, then run:
mpvtv
To reinstall after pulling changes from the repository:
pipx install --force .
For development:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
mpvtv
First Run
Start the app:
mpvtv
If no TV is configured, mpvtv opens setup.
Setup asks for:
- a friendly TV name
- the TV's ADB IP address, with an optional port
- ADB authorization on the TV
- mpv availability on the TV
- an optional Premiumize API key
TV IDs are assigned automatically. The first TV becomes the default TV unless you later choose another default.
ADB addresses may be written with or without a port:
10.81.10.128
10.81.10.128:5555
When the TV asks whether to allow ADB debugging, approve the prompt on the TV.
If mpv is not installed, mpvtv shows the detected Android CPU ABI and offers to install the matching mpv APK.
Premiumize setup is optional. Local files and direct URLs do not require Premiumize.
Interactive Playback
Run:
mpvtv
The interactive app is organized around three screens:
TVMediaPlayback
Choose a TV, paste a source, and start playback.
The TV screen shows:
- configured TV ID
- friendly name
- ADB address
- whether the TV is currently attached to ADB
- an asterisk next to the configured default TV
The media screen accepts one input. Paste any supported source:
/Users/me/Movies/movie.mkv
https://example.com/video.mkv
rtsp://example.com/live
magnet:?xt=urn:btih:...
On macOS, dragging a local file into the terminal inserts its path into the focused source field.
You can also start the TUI with a source already supplied:
mpvtv "/Users/me/Movies/movie.mkv"
mpvtv -t 1 "https://example.com/video.mkv"
mpvtv -t 1 "magnet:?xt=urn:btih:..."
If -t/--tv is supplied, that TV is selected. If no TV is supplied and a default TV is configured, the default is selected. If no default TV is configured, the TV picker is shown.
Interactive playback starts from the beginning. Use k during playback to skip to a position.
Quiet Mode
Quiet mode is for command-line playback without the full-screen TUI.
Use -q or --quiet:
mpvtv -q -t 1 "https://example.com/video.mkv"
Quiet mode requires:
- a source
- either
-t/--tvor a configured default TV
Examples:
mpvtv -q -t 1 "https://example.com/video.mkv"
mpvtv -q -t 1 "/Users/me/Movies/movie.mkv"
mpvtv -q -t 1 --seek 12:34 "/Users/me/Movies/movie.mkv"
mpvtv -q -t 1 "magnet:?xt=urn:btih:..."
If a default TV is configured, -t/--tv may be omitted:
mpvtv -q "https://example.com/video.mkv"
Quiet mode prints timestamped status lines. It exits with a non-zero status when playback cannot be started.
For URL and Premiumize playback, quiet mode exits after handing the source to mpv. For local files, quiet mode stays running because it is serving the media file to the TV. Press Ctrl-C to stop quiet local-file playback; quiet cleanup force-stops mpv on the TV.
The mpv install prompt may still appear in quiet mode if mpv is missing from the TV.
Sources
mpvtv detects the source type automatically.
Local Files
Anything that is not a supported URL or magnet link is treated as a local path.
Supported forms include:
/Users/me/Movies/movie.mkv
~/Movies/movie.mkv
/Users/me/Movies/movie\ with\ spaces.mkv
file:///Users/me/Movies/movie.mkv
The file must exist, be a regular file, and be readable.
URLs
Supported URL schemes:
http
https
rtmp
rtmps
rtp
rtsp
mms
mmst
mmsh
tcp
udp
http:// and https:// URLs must include a host. The other supported streaming schemes are accepted by scheme prefix.
Examples:
https://example.com/video.mkv
rtsp://camera.local/live
udp://239.0.0.1:1234
udp:239.0.0.1:1234
URL playback is handed directly to mpv on the TV.
Magnets
Magnet links must contain a valid btih info hash:
magnet:?xt=urn:btih:0123456789abcdef0123456789abcdef01234567
mpvtv supports 40-character hex btih hashes and 32-character base32 btih hashes.
Premiumize magnet playback requires:
- a configured Premiumize API key
- a cache hit from Premiumize
- at least one supported media file in the direct download result
If a magnet contains multiple playable files, mpvtv naturally sorts the file list before displaying choices or auto-selecting the first file in quiet mode.
Subtitles
Subtitle attachment is automatic for:
- local files
- Premiumize magnets
Direct URL playback is left unchanged.
For a local file, mpvtv looks in the same directory for a readable subtitle file with the same base name:
movie.mp4
movie.srt
For Premiumize, mpvtv filters returned direct-download content for subtitle files and attaches the same-base-name match for the selected media file.
Supported subtitle extensions:
aqt
gsub
jss
sub
ttxt
pjs
psb
rt
smi
slt
ssf
srt
ssa
ass
usf
idx
vtt
Disable subtitle attachment with:
mpvtv -ns "/Users/me/Movies/movie.mkv"
mpvtv --nosubs "magnet:?xt=urn:btih:..."
Playback Controls
Interactive playback keys:
m change media by reopening the media screen
k skip to a position
f change file, when a Premiumize magnet has multiple playable files
s stop mpv on the TV and exit
q quit mpvtv without force-stopping mpv
Ctrl-R replay the last mpv launch command
Ctrl-C open the stop prompt
If the stop prompt is open, pressing Ctrl-C again exits immediately and leaves playback running on the TV.
Changing media keeps the current TV selected and reopens the source input so you can beam another local file, URL, or magnet link.
Other interactive keys:
Ctrl-T add a TV from the TV screen
Ctrl-P open Premiumize settings from the TV or Media screen
d make the highlighted TV the default from the TV screen
q quit from non-playback screens
Seek positions accept:
90
12:34
1:02:03
These mean seconds, mm:ss, and hh:mm:ss. The Android intent receives the value in milliseconds as --ei position.
For local playback, quitting mpvtv closes the local server. Direct URL and Premiumize playback may continue independently on the TV.
TV Management
TVs are stored in the mpvtv config file.
List TVs:
mpvtv config tv list
Add a TV:
mpvtv config tv add --name "Living Room TV" --ip 10.81.10.128
Add a TV and make it the default:
mpvtv config tv add --name "Bedroom TV" --ip 10.81.10.129 --default
Save a TV without validating ADB and mpv:
mpvtv config tv add --name "Bedroom TV" --ip 10.81.10.129 --skip
Set the default TV:
mpvtv config tv set-default --id 2
Remove a TV:
mpvtv config tv remove --id 2
mpvtv config tv add always assigns the next numeric ID automatically.
When adding a TV without --skip, mpvtv saves the TV first, then validates ADB connectivity and mpv availability. This allows the config to remain available for later use even when validation needs attention.
If the default TV is removed, another configured TV is selected as the default when one is available.
Premiumize
Premiumize is used only for magnet playback.
Set or replace the API key:
mpvtv config premiumize set
Set the key directly:
mpvtv config premiumize set --api-key YOUR_API_KEY
Save without checking account info:
mpvtv config premiumize set --api-key YOUR_API_KEY --skip
Show whether a key is configured:
mpvtv config premiumize status
Remove the key:
mpvtv config premiumize clear
The status command does not call Premiumize. It only reports whether a key is saved.
The set command saves the key before checking account info. If the check fails, the key remains saved so it can be replaced or tested again later.
When account checking succeeds, mpvtv reports whether premium access is active and shows the subscription expiry time.
macOS Magnet Handler
Install a macOS magnet: URL handler:
mpvtv macos magnet install
This creates ~/Applications/mpvtv Magnet Handler.app, registers it with Launch Services, and makes it the default handler for magnet links. Opening a magnet link creates a temporary .command file and asks macOS to open it, so your configured app for shell command files can run regular mpvtv "magnet:...". The full TUI, TV picker, setup flow, and prompts remain available.
The temporary .command file deletes itself as soon as it starts. The handler shell-quotes both the configured mpvtv executable path and the incoming magnet URI before writing the command, and the URI is not interpolated raw into shell code. A stale file should only be left behind if macOS creates the file but never launches it.
If mpvtv is installed somewhere unusual, pass the executable path:
mpvtv macos magnet install --mpvtv /Users/me/.local/bin/mpvtv
Show the current macOS magnet handler:
mpvtv macos magnet status
If you reinstall mpvtv to a different executable path, rerun the install command.
Configuration
Default config path:
~/.config/mpvtv/mpvtv.ini
Override the config path with MPVTV_CONFIG:
MPVTV_CONFIG=/tmp/mpvtv.ini mpvtv
MPVTV_CONFIG=/tmp/mpvtv.ini mpvtv config tv list
Example config:
[tvs]
default = 1
[tv:1]
name = Living Room TV
ip = 10.81.10.128
[premiumize]
api_key = YOUR_API_KEY
TV sections are named tv:ID. The [tvs] default value points at the configured TV ID used by quiet mode when -t/--tv is not supplied.
How Playback Works
ADB and mpv
Playback uses Android intents sent through ADB.
The selected TV is addressed with adb -s TV_IP:PORT, so multiple attached ADB devices are supported.
mpv package:
is.xyz.mpv
mpv activity:
is.xyz.mpv/.MPVActivity
After connecting to a TV and before checking mpv, mpvtv wakes the TV and opens the home screen:
adb -s TV_IP:5555 shell input keyevent KEYCODE_WAKEUP
adb -s TV_IP:5555 shell input keyevent KEYCODE_HOME
Before launching playback, mpvtv force-stops mpv:
adb -s TV_IP:5555 shell am force-stop is.xyz.mpv
Playback launch shape:
adb -s TV_IP:5555 shell am start --user 0 \
-a android.intent.action.VIEW \
-n is.xyz.mpv/.MPVActivity \
-d "MEDIA_URL" \
-t "video/any"
Seek uses milliseconds:
--ei position 83590000
Subtitles use a string extra:
--es subs "SUBTITLE_URL"
mpv Installation
mpv availability is checked with:
adb -s TV_IP:5555 shell pm list packages is.xyz.mpv
If mpv is missing, the TV CPU ABI is read with:
adb -s TV_IP:5555 shell getprop ro.product.cpu.abi
Supported ABIs:
arm64-v8a
armeabi-v7a
Configured APK URLs:
https://github.com/mpv-android/mpv-android/releases/download/2026-04-25/app-default-arm64-v8a-release.apk
https://github.com/mpv-android/mpv-android/releases/download/2026-04-25/app-default-armeabi-v7a-release.apk
The APK is downloaded to a temporary directory, installed with adb install -r, and removed when the temporary directory is cleaned up.
Local File Streaming
Local files are served from the Mac by a temporary aiohttp server.
The local server:
- binds a random local port
- serves only the selected media file
- also serves the matching subtitle file, when one is found
- rejects clients whose IP is not the selected TV IP
- supports HTTP byte-range requests for seeking
- shuts down when
mpvtvexits
The local IP is resolved in this order:
ipconfig getifaddr en0- parsing
ifconfig en0 - socket fallback using the selected TV address
Premiumize Magnet Flow
For magnets, mpvtv:
- validates the magnet link
- extracts the
btihinfo hash - checks
/api/cache/check - stops if Premiumize reports a cache miss
- calls
/api/transfer/directdlafter a cache hit - filters returned files to supported video extensions, then falls back to supported audio extensions
- filters subtitle files separately
- attaches a matching subtitle when available
- naturally sorts playable files before display or automatic selection
Supported video extensions:
3g2
3gp
avi
flv
mkv
mk3d
mov
mp2
mp4
m4v
mpe
mpeg
mpg
mpv
webm
wmv
ogm
ts
mts
m2ts
Supported audio fallback extensions:
flac
mp3
ogg
CLI Reference
Playback
mpvtv [SOURCE] [-t ID] [--seek POSITION] [-q] [-ns]
mpvtv -v
Arguments and options:
SOURCE
Local file path, playback URL, or magnet link.
-t, --tv ID
Configured TV ID.
--seek, --position POSITION
Quiet-mode start position. Accepts seconds, mm:ss, or hh:mm:ss.
In the TUI, playback starts from the beginning and seek is available with k.
-q, --quiet
Use unattended CLI mode.
-ns, --nosubs
Disable automatic subtitle attachment.
-v, --version
Print the mpvtv version and exit.
Config
mpvtv config tv list
mpvtv config tv add --name NAME --ip IP [--default] [--skip]
mpvtv config tv set-default --id ID
mpvtv config tv remove --id ID
mpvtv config premiumize status
mpvtv config premiumize set [--api-key KEY] [--skip]
mpvtv config premiumize clear
mpvtv macos magnet install [--mpvtv PATH]
mpvtv macos magnet status
Detailed command help:
mpvtv --help
mpvtv config --help
mpvtv config tv --help
mpvtv config tv add --help
mpvtv config premiumize --help
mpvtv config premiumize set --help
mpvtv macos magnet --help
Troubleshooting
mpvtv: command not found
Reopen your terminal after pipx ensurepath, or run from the source tree:
PYTHONPATH=src python3 -m mpvtv
adb was not found on PATH
Install Android platform-tools:
brew install android-platform-tools
No TV appears attached
Check the TV IP address, confirm network ADB is enabled, and make sure the TV is awake and on the same network as the Mac.
TV stays unauthorized
Approve the ADB debugging prompt on the TV. If the prompt does not appear, disable and re-enable network debugging on the TV.
TV is offline
Reconnect ADB from the TV settings, wake the TV, and verify the IP address in mpvtv config tv list.
mpv is missing
Accept the install prompt, or install mpv for Android manually.
Magnet unavailable
The magnet is not cached by Premiumize. mpvtv does not start uncached Premiumize transfers.
Premiumize reports "Not logged in"
Replace the saved API key:
mpvtv config premiumize set
Local playback stops when mpvtv exits
Local files are served from the Mac. Keep mpvtv running while watching a local file.
Direct URL or Premiumize playback continues after quitting
Those sources are remote URLs handed to mpv. Use s in the TUI to stop mpv on the TV, or run playback again and stop it from the app.
Development
Run tests:
python3 -m pytest
Run from source:
PYTHONPATH=src python3 -m mpvtv
Run quiet mode from source with a temporary config:
MPVTV_CONFIG=/tmp/mpvtv.ini PYTHONPATH=src python3 -m mpvtv -q -t 1 "https://example.com/video.mkv"
Compile-check the package:
python3 -m compileall src/mpvtv
Primary modules:
src/mpvtv/cli.py argparse entrypoint and scriptable commands
src/mpvtv/tui/app.py Textual full-screen app
src/mpvtv/config.py INI config loading and TV management
src/mpvtv/source.py source classification
src/mpvtv/seek.py seek parsing
src/mpvtv/subtitles.py subtitle matching
src/mpvtv/preflight.py TV, mpv, and Premiumize checks
src/mpvtv/playback.py playback orchestration
src/mpvtv/adb.py ADB and mpv APK operations
src/mpvtv/local_server.py local HTTP file server
src/mpvtv/premiumize.py Premiumize API client and filtering