update installation instructions and add mpv handler setup script

This commit is contained in:
2026-04-18 18:12:17 -07:00
parent 0bd3bff92d
commit 3bd335cfb5
9 changed files with 982 additions and 413 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
VITE_HYDRUS_HOST=http://localhost
VITE_HYDRUS_PORT=45869
VITE_HYDRUS_API_KEY=95178b08e6ba3c57991e7b4e162c6efff1ce90c500005c6ebf8524122ed2486e
VITE_HYDRUS_API_KEY=
VITE_HYDRUS_SSL=false
+91 -128
View File
@@ -1,45 +1,107 @@
# API Media Player (PWA)
# API Media Player
This is a small web-first PWA prototype for browsing media from a Hydrus client via its Client API. The app ships with a demo library and routes playback to external players such as mpv or VLC instead of using an in-browser player.
API Media Player is a web-first PWA for browsing Hydrus media and handing playback off to native apps instead of an in-browser player.
## Quick start (PowerShell)
## What works today
1. Install dependencies:
- Browse the seeded demo library right after `npm install` and `npm run dev`
- Connect to a real Hydrus client from the Settings page or a local `.env` file
- Launch playback in native apps:
- Windows and Linux desktop: `mpv` through `mpv-handler://`
- Android: `mpv-android` through `intent://`
- iPhone and iPad: VLC through `vlc-x-callback://`
```powershell
pwsh -c "npm install"
## Fast start
1. Install Node.js 20 or newer.
2. Install dependencies:
```bash
npm install
```
2. Run the dev server:
3. Start the dev server:
```powershell
pwsh -c "npm run dev"
```bash
npm run dev
```
Open `http://localhost:5173` in your browser to test the PWA and external-player launch flow.
4. Open `http://localhost:5173`.
## Configuring Hydrus
The app includes sample data, so you can verify the UI immediately. Playback itself is always external, so browsing works before player setup, but actual media launch depends on the platform-specific player flow below.
Create a `.env` file in the project root (not committed) with these variables if you want to connect to a real Hydrus instance:
## Connect Hydrus (optional)
```
If you want real library data, duplicate `.env.example` as `.env` and fill in your Hydrus values:
```dotenv
VITE_HYDRUS_HOST=http://localhost
VITE_HYDRUS_PORT=45869
VITE_HYDRUS_API_KEY=
VITE_HYDRUS_SSL=false
```
> Note: browsers cannot attach custom Hydrus API headers to direct media URLs. If your Hydrus server requires header-based authentication for file access, use a reverse proxy or another trusted layer that can mint playable URLs for your external player.
You can also add or edit servers from Settings inside the app. The first run seeds a sample server entry so you only need to supply your `Hydrus-Client-API-Access-Key` and test the connection.
### Settings UI & quick test
Browsers cannot attach custom Hydrus API headers to direct media URLs. If your Hydrus setup requires header-based authentication for file access, put a trusted reverse proxy in front of it or provide playable URLs another way.
On first run the app will seed a sample server entry for `192.168.1.128:45869` so you can quickly add your API key and test connectivity via the app's Settings (top-right gear). Open Settings, choose the server, paste your `Hydrus-Client-API-Access-Key` (if needed), and click **Test connection**. The test reports whether the server is reachable, whether authentication is required (HTTP 401/403), and whether byte-range requests are supported (needed for seeking).
## Playback setup by device
If you get a CORS or network error in the browser, consider running a reverse proxy that adds proper CORS headers or packaging the app with Capacitor to avoid browser CORS limitations.
### Windows desktop
## Violentmonkey mpv userscript
1. Install `mpv` and optionally `yt-dlp`.
2. Open an elevated PowerShell or Windows Terminal.
3. From the repo root, run:
If you want desktop or Android browsers to hand media straight to mpv instead of the in-browser player, install the userscript at:
```bash
npm run setup:mpv-handler
```
The repo already includes the Windows `mpv-handler` binaries under `scripts/`, so the helper script can:
- reuse the bundled handler files
- detect `mpv` and `yt-dlp` from `PATH` when possible
- update `scripts/config.toml`
- register `mpv-handler://` and `mpv-handler-debug://`
To remove the registration later:
```bash
npm run uninstall:mpv-handler
```
### Linux desktop
1. Install `mpv` and optionally `yt-dlp`.
2. Download and extract the latest upstream Linux release:
```text
https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
```
3. Run the helper against the extracted folder:
```bash
npm run setup:mpv-handler -- --root /path/to/extracted/mpv-handler-linux-amd64
```
On Linux the helper copies the binary and desktop files into `~/.local`, writes `config.toml`, and runs `xdg-mime` for both protocol handlers.
### Android
Install `mpv-android` (`is.xyz.mpv`). No extra handler setup is needed.
### iPhone / iPad
Install VLC for iOS. The app sends playback to `vlc-x-callback://` automatically.
### macOS desktop
The app can still browse Hydrus and demo content on macOS, but this repo does not currently automate `mpv-handler://` registration for desktop macOS. If you already have a compatible custom protocol handler installed, the desktop playback flow will use it. Otherwise, use another supported playback platform for now.
## Optional userscript for direct media URLs
If you want direct file loads in the browser to jump into `mpv` before the page player starts, install the userscript served by this app:
```text
/userscripts/api-media-player-open-in-mpv.user.js
@@ -52,117 +114,18 @@ http://localhost:5173/userscripts/api-media-player-open-in-mpv.user.js
http://127.0.0.1:4173/userscripts/api-media-player-open-in-mpv.user.js
```
What it does:
It only activates on `localhost`, loopback, RFC1918 LAN IPs, and `.local` or `.lan` hosts. On desktop it redirects to `mpv-handler://...`; on Android it redirects to the `mpv` app through `intent://...`.
- On desktop, direct media playback is redirected to `mpv-handler://...`
- On Android, direct media playback is redirected to the mpv app via `intent://...`
- It only activates by default on `localhost`, loopback, RFC1918 LAN IPs, and `.local` / `.lan` hosts so it does not hijack unrelated public websites
## Useful commands
Notes:
- Desktop requires `mpv-handler` to be installed and registered.
- Android requires `mpv-android` (`is.xyz.mpv`) to be installed.
- The script intercepts direct file URLs and Hydrus `/get_files/file` playback requests before the browser player starts.
### Desktop setup for mpv-handler
Official project:
```text
https://github.com/akiirui/mpv-handler
```bash
npm run dev
npm run build
npm run preview
npm run typecheck
npm run setup:mpv-handler -- --help
```
Latest releases:
## More detail
```text
https://github.com/akiirui/mpv-handler/releases
```
Windows:
1. Install `mpv` itself first if you do not already have it.
2. Download the latest Windows archive:
```text
https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-windows-amd64.zip
```
3. Extract it somewhere permanent.
4. Edit `config.toml` in that folder and set the path to your `mpv` executable. If you use `yt-dlp`, set that path too.
5. Register the protocol with either the upstream batch file or the PowerShell installer in this repo.
Upstream batch option:
```powershell
Set-Location C:\path\to\mpv-handler
.\handler-install.bat
```
PowerShell alternative from this repo:
```powershell
Set-Location C:\Forgejo\API-MediaPlayer
powershell -ExecutionPolicy Bypass -File .\scripts\install-mpv-handler.ps1 -InstallRoot 'C:\path\to\mpv-handler'
```
Or from an already elevated PowerShell window:
```powershell
& 'C:\Forgejo\API-MediaPlayer\scripts\install-mpv-handler.ps1' -InstallRoot 'C:\path\to\mpv-handler'
```
If you copied `config.toml`, `mpv-handler.exe`, and `mpv-handler-debug.exe` into the same folder as [scripts/install-mpv-handler.ps1](scripts/install-mpv-handler.ps1), you can also run it without `-InstallRoot`:
```powershell
& 'C:\Forgejo\API-MediaPlayer\scripts\install-mpv-handler.ps1'
```
What the PowerShell installer does:
- validates that `config.toml`, `mpv-handler.exe`, and `mpv-handler-debug.exe` exist
- removes old `mpv://` and existing `mpv-handler://` protocol keys unless you tell it not to
- registers `mpv-handler://` and `mpv-handler-debug://` in the Windows registry
- uses the `mpv` path from `config.toml` as the icon when possible, otherwise falls back to `mpv-handler.exe`
Requirements for the PowerShell installer:
- run it from an elevated PowerShell window
- point `-InstallRoot` at the extracted `mpv-handler` folder
- do not dot-source it from the repo root without `-InstallRoot`, because this repo does not contain the extracted `mpv-handler` binaries
Linux:
1. Download the latest Linux archive:
```text
https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
```
2. Extract it.
3. Copy `mpv-handler` to `$HOME/.local/bin`.
4. Copy `mpv-handler.desktop` and `mpv-handler-debug.desktop` to `$HOME/.local/share/applications/`.
5. Make the binary executable:
```text
chmod +x $HOME/.local/bin/mpv-handler
```
6. Register the protocol handlers:
```text
xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
7. Add `$HOME/.local/bin` to `PATH` if needed.
8. Optionally copy and edit `config.toml` for your `mpv` and `yt-dlp` paths.
After setup, clicking an `mpv-handler://...` link in the browser should launch mpv instead of showing an unknown protocol error.
## Next steps
- Wire the UI to a real Hydrus instance (update `src/api/hydrusClient.ts`).
- Add Capacitor for native packaging and secure storage for API keys.
- Improve Hydrus browsing and filtering UX for large libraries.
If you want, I can run `npm install` and start the dev server now and confirm the app is reachable locally. Let me know and I'll proceed.
See `scripts/README.md` for the helper script behavior, flags, and the Windows/Linux manual fallbacks.
+5
View File
@@ -5,8 +5,13 @@
"description": "Web-first PWA media player that integrates with Hydrus",
"scripts": {
"dev": "vite",
"start": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"setup:mpv-handler": "node ./scripts/setup-mpv-handler.mjs",
"install:mpv-handler": "npm run setup:mpv-handler",
"uninstall:mpv-handler": "powershell -NoProfile -ExecutionPolicy Bypass -File .\\scripts\\uninstall-mpv-handler.ps1",
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\""
},
"dependencies": {
+462 -145
View File
@@ -1,155 +1,472 @@
[English][readme-en] | [简体中文][readme-zh-hans] | [繁体中文][readme-zh-hant]
# mpv-handler setup
[readme-en]: https://github.com/akiirui/mpv-handler/blob/main/README.md
[readme-zh-hans]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hans.md
[readme-zh-hant]: https://github.com/akiirui/mpv-handler/blob/main/README.zh-Hant.md
This folder contains the desktop playback helper assets used by API Media Player.
# mpv handler
## What is in this folder
A protocol handler for **mpv**, written by Rust.
- `setup-mpv-handler.mjs`: cross-platform helper used by `npm run setup:mpv-handler`
- `install-mpv-handler.ps1`: Windows protocol registration script
- `uninstall-mpv-handler.ps1`: Windows protocol cleanup script
- `handler-install.bat` and `handler-uninstall.bat`: thin Windows wrappers for the PowerShell scripts
- `config.toml`: template used when the helper needs to create or refresh an `mpv-handler` config
- `mpv-handler.exe` and `mpv-handler-debug.exe`: bundled Windows handler binaries
Use **mpv** and **yt-dlp** to play video and music from the websites.
Please use it with userscript:
[![play-with-mpv][badges-play-with-mpv]][greasyfork-play-with-mpv]
## Breaking changes
### [v0.4.0][v0.4.0]
To avoid conflicts with the `mpv://` protocol provided by mpv.
> mpv://...
>
> mpv protocol. This is used for starting mpv from URL handler. The protocol is stripped and the rest is passed to the player as a normal open argument. Only safe network protocols are allowed to be opened this way.
Scheme `mpv://` and `mpv-debug://` are deprecated, use `mpv-handler://` and `mpv-handler-debug://`.
**Require manual intervention**
#### Windows
Run `handler-uninstall.bat` to uninstall deprecated protocol, and run `handler-install.bat` to install new procotol.
#### Linux
If you installed manually, please repeat the manual installation process.
## Protocol
![](share/proto.png)
### Scheme
- `mpv-handler`: Run mpv-handler without console window
- `mpv-handler-debug`: Run mpv-handler with console window to view outputs and errors
### Plugins
- `play`: Use mpv player to play video
### Encoded Data
Use [URL-safe base64][rfc-base64-url] to encode the URL or TITLE.
Replace `/` to `_`, `+` to `-` and remove padding `=`.
Example (JavaScript):
```javascript
let data = btoa("https://www.youtube.com/watch?v=Ggkn2f5e-IU");
let safe = data.replace(/\//g, "_").replace(/\+/g, "-").replace(/\=/g, "");
```
### Parameters (Optional)
```
cookies = [ www.domain.com.txt ]
profile = [ default, low-latency, etc... ]
quality = [ 2160p, 1440p, 1080p, 720p, 480p, 360p ]
v_codec = [ av01, vp9, h265, h264 ]
v_title = [ Encoded Title ]
subfile = [ Encoded URL ]
startat = [ Seconds (float) ]
referrer = [ Encoded URL ]
```
## Installation
### Linux
#### Arch Linux
[![mpv-handler][badges-aur]][download-aur]
[![mpv-handler-git][badges-aur-git]][download-aur-git]
#### Manual installation
1. Download [latest Linux release][download-linux]
2. Unzip the archive
3. Copy `mpv-handler` to `$HOME/.local/bin`
4. Copy `mpv-handler.desktop` to `$HOME/.local/share/applications/`
5. Copy `mpv-handler-debug.desktop` to `$HOME/.local/share/applications/`
6. Set executable permission for binary
- ```
$ chmod +x $HOME/.local/bin/mpv-handler
```
7. Register xdg-mime (thanks for the [linuxuprising][linuxuprising] reminder)
- ```
$ xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
$ xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
8. Add `$HOME/.local/bin` to your environment variable `PATH`
9. **Optional**: _Copy `config.toml` to `$HOME/.config/mpv-handler/config.toml` and configure_
## Recommended commands
### Windows
Windows users need to install manually.
Run from an elevated PowerShell or Windows Terminal:
#### Manual installation
1. Download [latest Windows release][download-windows]
2. Unzip the archive to the directory you want
3. Run `handler-install.bat` to register protocol handler
4. Edit `config.toml` and set `mpv` and `ytdl` path
## Configuration
```toml
mpv = "/usr/bin/mpv"
# Optional, Type: String
# The path of mpv executable binary
# Default value:
# - Linux: mpv
# - Windows: mpv.com
ytdl = "/usr/bin/yt-dlp"
# Optional, Type: String
# The path of yt-dlp executable binary
proxy = "http://example.com:8080"
# Optional, Type: String
# HTTP(S) proxy server address
# For Windows users:
# - The path can be "C:\\folder\\some.exe" or "C:/folder/some.exe"
# - The path target is an executable binary file, not a directory
```bash
npm run setup:mpv-handler
```
[v0.4.0]: https://github.com/akiirui/mpv-handler/releases/tag/v0.4.0
[rfc-base64-url]: https://datatracker.ietf.org/doc/html/rfc4648#section-5
[badges-aur-git]: https://img.shields.io/aur/version/mpv-handler-git?style=for-the-badge&logo=archlinux&label=mpv-handler-git
[badges-aur]: https://img.shields.io/aur/version/mpv-handler?style=for-the-badge&logo=archlinux&label=mpv-handler
[badges-play-with-mpv]: https://img.shields.io/greasyfork/v/416271?style=for-the-badge&logo=greasyfork&label=play-with-mpv
[download-aur-git]: https://aur.archlinux.org/packages/mpv-handler-git/
[download-aur]: https://aur.archlinux.org/packages/mpv-handler/
[download-linux]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
[download-macos]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-macos-amd64.zip
[download-windows]: https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-windows-amd64.zip
[greasyfork-play-with-mpv]: https://greasyfork.org/scripts/416271-play-with-mpv
[linuxuprising]: https://www.linuxuprising.com/2021/07/open-youtube-and-more-videos-from-your.html
Remove the registration later with:
```bash
npm run uninstall:mpv-handler
```
### Linux
1. Download and extract the upstream Linux release:
```text
https://github.com/akiirui/mpv-handler/releases/latest/download/mpv-handler-linux-amd64.zip
```
2. Point the helper at that extracted folder:
```bash
npm run setup:mpv-handler -- --root /path/to/extracted/mpv-handler-linux-amd64
```
The helper copies files into `~/.local/bin` and `~/.local/share/applications`, updates `config.toml`, and runs `xdg-mime` for both protocol handlers.
## Useful flags
```bash
npm run setup:mpv-handler -- --help
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv
npm run setup:mpv-handler -- --ytdl /path/to/yt-dlp
npm run setup:mpv-handler -- --skip-config
npm run setup:mpv-handler -- --dry-run
```
`--root` points at the extracted upstream `mpv-handler` folder. On Windows it is optional because this repo already ships the handler files under `scripts/`. On Linux it is normally required.
## What the helper does
### Windows
- finds the bundled handler files in this folder unless you override `--root`
- creates or updates `config.toml`
- tries to detect `mpv` and `yt-dlp` from `PATH`
- registers `mpv-handler://` and `mpv-handler-debug://` through the PowerShell installer
### Linux
- validates the extracted upstream release layout
- creates or updates `config.toml`
- copies the handler binary to `~/.local/bin/mpv-handler`
- copies the desktop entries to `~/.local/share/applications`
- rewrites `Exec=` entries to the installed absolute binary path
- runs `xdg-mime default ...` for both schemes
## Manual fallback
If you would rather install without the helper:
### Windows
```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\install-mpv-handler.ps1 -InstallRoot .\scripts
```
### Linux
```bash
cp /path/to/mpv-handler/mpv-handler ~/.local/bin/mpv-handler
cp /path/to/mpv-handler/mpv-handler.desktop ~/.local/share/applications/
cp /path/to/mpv-handler/mpv-handler-debug.desktop ~/.local/share/applications/
chmod +x ~/.local/bin/mpv-handler
xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler
xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug
```
## Upstream project
Upstream `mpv-handler` releases and source:
```text
https://github.com/akiirui/mpv-handler
https://github.com/akiirui/mpv-handler/releases
```
This repo uses the upstream protocol scheme and binaries; the docs here only describe the setup flow for API Media Player.
*** Add File: c:\Forgejo\API-MediaPlayer\scripts\uninstall-mpv-handler.ps1
#Requires -Version 5.1
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
[string]$SchemeName
)
$classesRoot = [Microsoft.Win32.Registry]::ClassesRoot
try {
$classesRoot.DeleteSubKeyTree($SchemeName, $false)
} catch {
}
}
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
throw 'This uninstaller is only for Windows.'
}
Remove-ProtocolKey -SchemeName 'mpv'
Remove-ProtocolKey -SchemeName 'mpv-debug'
Remove-ProtocolKey -SchemeName 'mpv-handler'
Remove-ProtocolKey -SchemeName 'mpv-handler-debug'
Write-Host 'Successfully removed mpv-handler protocol registration.' -ForegroundColor Green
*** Add File: c:\Forgejo\API-MediaPlayer\scripts\setup-mpv-handler.mjs
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, chmodSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptPath = fileURLToPath(import.meta.url)
const scriptDir = path.dirname(scriptPath)
const templateConfigPath = path.join(scriptDir, 'config.toml')
const usage = `Usage:
npm run setup:mpv-handler
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv --ytdl /path/to/yt-dlp
Options:
--root <path> Path to the extracted mpv-handler folder.
--mpv <path> Override the mpv executable path written to config.toml.
--ytdl <path> Override the yt-dlp executable path written to config.toml.
--skip-config Do not create or update config.toml.
--keep-existing Windows only. Keep existing protocol keys instead of replacing them.
--dry-run Print the actions without changing files or running installers.
--help Show this help text.
`
function fail(message) {
console.error(`Error: ${message}`)
process.exit(1)
}
function parseArgs(argv) {
const options = {
root: '',
mpv: '',
ytdl: '',
skipConfig: false,
keepExisting: false,
dryRun: false,
help: false,
}
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index]
if (value === '--help' || value === '-h') {
options.help = true
continue
}
if (value === '--skip-config') {
options.skipConfig = true
continue
}
if (value === '--keep-existing') {
options.keepExisting = true
continue
}
if (value === '--dry-run') {
options.dryRun = true
continue
}
if (value === '--root' || value === '--mpv' || value === '--ytdl') {
const nextValue = argv[index + 1]
if (!nextValue || nextValue.startsWith('--')) {
fail(`Missing value for ${value}`)
}
if (value === '--root') options.root = nextValue
if (value === '--mpv') options.mpv = nextValue
if (value === '--ytdl') options.ytdl = nextValue
index += 1
continue
}
fail(`Unknown argument: ${value}`)
}
return options
}
function escapeTomlString(value) {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}
function upsertTomlValue(content, key, value) {
const line = `${key} = "${escapeTomlString(value)}"`
const pattern = new RegExp(`^\\s*#?\\s*${key}\\s*=.*$`, 'm')
if (pattern.test(content)) {
return content.replace(pattern, line)
}
const trimmed = content.trimEnd()
return trimmed ? `${trimmed}\n${line}\n` : `${line}\n`
}
function resolveOnPath(commandNames) {
const locator = process.platform === 'win32' ? 'where.exe' : 'which'
for (const commandName of commandNames) {
const result = spawnSync(locator, [commandName], { encoding: 'utf8' })
if (result.status !== 0) continue
const match = result.stdout
.split(/\r?\n/)
.map((entry) => entry.trim())
.find((entry) => entry)
if (match && existsSync(match)) {
return match
}
}
return ''
}
function isFile(candidatePath) {
try {
return statSync(candidatePath).isFile()
} catch {
return false
}
}
function looksLikeRoot(rootPath) {
const requiredFiles = process.platform === 'win32'
? ['mpv-handler.exe', 'mpv-handler-debug.exe']
: process.platform === 'linux'
? ['mpv-handler', 'mpv-handler.desktop', 'mpv-handler-debug.desktop']
: ['mpv-handler']
return requiredFiles.every((fileName) => isFile(path.join(rootPath, fileName)))
}
function resolveRoot(options) {
const candidates = []
if (options.root) candidates.push(path.resolve(options.root))
candidates.push(scriptDir)
candidates.push(process.cwd())
const seen = new Set()
for (const candidate of candidates) {
if (!candidate || seen.has(candidate)) continue
seen.add(candidate)
if (looksLikeRoot(candidate)) {
return candidate
}
}
if (process.platform === 'win32') {
fail('Could not find mpv-handler files. Re-run with --root pointing at the extracted mpv-handler folder.')
}
if (process.platform === 'linux') {
fail('Could not find a Linux mpv-handler release. Download and extract the upstream archive, then pass --root /path/to/extracted/mpv-handler-linux-amd64.')
}
fail('Automatic setup is not available for this platform yet.')
}
function ensureConfig(rootPath, options) {
const configPath = path.join(rootPath, 'config.toml')
if (options.skipConfig) {
return { configPath, changed: false, mpvPath: '', ytdlPath: '' }
}
let content = existsSync(configPath)
? readFileSync(configPath, 'utf8')
: readFileSync(templateConfigPath, 'utf8')
const detectedMpv = options.mpv || resolveOnPath(process.platform === 'win32' ? ['mpv.com', 'mpv.exe', 'mpv'] : ['mpv'])
const detectedYtdl = options.ytdl || resolveOnPath(process.platform === 'win32' ? ['yt-dlp.exe', 'yt-dlp'] : ['yt-dlp'])
let changed = !existsSync(configPath)
let nextContent = content
if (detectedMpv) {
const updated = upsertTomlValue(nextContent, 'mpv', detectedMpv)
changed ||= updated !== nextContent
nextContent = updated
}
if (detectedYtdl) {
const updated = upsertTomlValue(nextContent, 'ytdl', detectedYtdl)
changed ||= updated !== nextContent
nextContent = updated
}
if (changed && !options.dryRun) {
writeFileSync(configPath, nextContent, 'utf8')
}
return {
configPath,
changed,
mpvPath: detectedMpv,
ytdlPath: detectedYtdl,
}
}
function runWindowsSetup(rootPath, options) {
const powershell = resolveOnPath(['powershell.exe', 'pwsh.exe']) || 'powershell.exe'
const installScript = path.join(scriptDir, 'install-mpv-handler.ps1')
const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', installScript, '-InstallRoot', rootPath]
if (options.keepExisting) {
args.push('-KeepExistingProtocolKeys')
}
if (options.dryRun) {
console.log('Dry run: would execute Windows installer')
console.log(`${powershell} ${args.map((value) => JSON.stringify(value)).join(' ')}`)
return
}
const result = spawnSync(powershell, args, { stdio: 'inherit' })
if (result.status !== 0) {
fail('Windows protocol registration failed. Re-run the command from an elevated PowerShell or Windows Terminal.')
}
}
function rewriteDesktopExec(content, targetBinary) {
return content.replace(/^Exec=.*$/m, (line) => {
const prefix = 'Exec='
const rest = line.slice(prefix.length).trim()
const firstSpaceIndex = rest.indexOf(' ')
const suffix = firstSpaceIndex === -1 ? '' : rest.slice(firstSpaceIndex)
return `${prefix}${targetBinary}${suffix}`
})
}
function runLinuxSetup(rootPath, options) {
const localBin = path.join(os.homedir(), '.local', 'bin')
const applicationsDir = path.join(os.homedir(), '.local', 'share', 'applications')
const targetBinary = path.join(localBin, 'mpv-handler')
const copies = [
{
source: path.join(rootPath, 'mpv-handler'),
target: targetBinary,
executable: true,
},
{
source: path.join(rootPath, 'mpv-handler.desktop'),
target: path.join(applicationsDir, 'mpv-handler.desktop'),
patchExec: true,
},
{
source: path.join(rootPath, 'mpv-handler-debug.desktop'),
target: path.join(applicationsDir, 'mpv-handler-debug.desktop'),
patchExec: true,
},
]
if (options.dryRun) {
console.log('Dry run: would install Linux desktop files to ~/.local')
for (const item of copies) {
console.log(`copy ${item.source} -> ${item.target}`)
}
console.log('xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler')
console.log('xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug')
return
}
mkdirSync(localBin, { recursive: true })
mkdirSync(applicationsDir, { recursive: true })
for (const item of copies) {
if (item.patchExec) {
const content = readFileSync(item.source, 'utf8')
writeFileSync(item.target, rewriteDesktopExec(content, targetBinary), 'utf8')
continue
}
copyFileSync(item.source, item.target)
if (item.executable) {
chmodSync(item.target, 0o755)
}
}
for (const args of [
['default', 'mpv-handler.desktop', 'x-scheme-handler/mpv-handler'],
['default', 'mpv-handler-debug.desktop', 'x-scheme-handler/mpv-handler-debug'],
]) {
const result = spawnSync('xdg-mime', args, { stdio: 'inherit' })
if (result.status !== 0) {
fail(`xdg-mime failed for ${args[2]}. Run the command manually after fixing your desktop environment registration.`)
}
}
}
function main() {
const options = parseArgs(process.argv.slice(2))
if (options.help) {
console.log(usage)
return
}
if (!['win32', 'linux', 'darwin'].includes(process.platform)) {
fail(`Unsupported platform: ${process.platform}`)
}
if (process.platform === 'darwin') {
console.log('Automatic macOS protocol registration is not available in this repo yet.')
console.log('The app can still browse media on macOS, but desktop playback setup must be handled manually.')
return
}
const rootPath = resolveRoot(options)
const configResult = ensureConfig(rootPath, options)
console.log(`Platform: ${process.platform}`)
console.log(`mpv-handler root: ${rootPath}`)
console.log(`config.toml: ${configResult.configPath}${configResult.changed ? ' (updated)' : ' (unchanged)'}`)
console.log(`mpv: ${configResult.mpvPath || 'not detected'}`)
console.log(`yt-dlp: ${configResult.ytdlPath || 'not detected'}`)
if (process.platform === 'win32') {
runWindowsSetup(rootPath, options)
} else if (process.platform === 'linux') {
runLinuxSetup(rootPath, options)
}
console.log('mpv-handler setup complete.')
}
main()
+13 -76
View File
@@ -1,82 +1,19 @@
@echo OFF
setlocal
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
set interactive=yes
if /I "%1"=="/u" (
set interactive=no
shift
)
:: Make sure this is Windows Vista or later
call :ensure_vista
:: Make sure the script is running as admin
call :ensure_admin
:: Get mpv.exe location
call :check_binary
:: Add registry
call :add_verbs
:die
if not [%1] == [] echo %~1
if [%unattended%] == [yes] exit 1
pause
exit 1
:ensure_admin
:: 'openfiles' is just a commmand that is present on all supported Windows
:: versions, requires admin privileges and has no side effects, see:
:: https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
openfiles >nul 2>&1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0install-mpv-handler.ps1" -InstallRoot "%~dp0" %*
if errorlevel 1 (
echo This batch script requires administrator privileges.
echo Right-click on handler-install.bat and select "Run as administrator".
call :die
echo.
echo Install failed. Re-run from an elevated PowerShell or Command Prompt.
if /I "%interactive%"=="yes" pause
exit /b 1
)
goto :EOF
:ensure_vista
ver | find "XP" >nul
if not errorlevel 1 (
echo This batch script only works on Windows Vista and later. To create file
echo associations on Windows XP, right click on a video file and use "Open with...".
call :die
)
goto :EOF
:check_binary
cd /D %~dp0
set mpv_handler_conf=%cd%\config.toml
set mpv_handler_path=%cd%\mpv-handler.exe
set mpv_handler_debug_path=%cd%\mpv-handler-debug.exe
if not exist "%mpv_handler_conf%" call :die "Not found config.toml"
if not exist "%mpv_handler_path%" call :die "Not found mpv-handler.exe"
if not exist "%mpv_handler_debug_path%" call :die "Not found mpv-handler-debug.exe"
goto :EOF
:reg
:: Wrap the reg command to check for errors
>nul reg %*
if errorlevel 1 set error=yes
if [%error%] == [yes] echo Error in command: reg %*
if [%error%] == [yes] call :die
goto :EOF
:add_verbs
:: Add the mpv protocol to the registry
call :reg add "HKCR\mpv-handler" /d "URL:MPV Handler" /f
call :reg add "HKCR\mpv-handler" /v "Content Type" /d "application/x-mpv-handler" /f
call :reg add "HKCR\mpv-handler" /v "URL Protocol" /f
call :reg add "HKCR\mpv-handler\DefaultIcon" /d "\"%mpv_exe_path%\",1" /f
call :reg add "HKCR\mpv-handler\shell\open\command" /d "\"%mpv_handler_path%\" \"%%%%1\"" /f
:: Add the mpv protocol to the registry
call :reg add "HKCR\mpv-handler-debug" /d "URL:MPV Handler Debug" /f
call :reg add "HKCR\mpv-handler-debug" /v "Content Type" /d "application/x-mpv-handler-debug" /f
call :reg add "HKCR\mpv-handler-debug" /v "URL Protocol" /f
call :reg add "HKCR\mpv-handler-debug\DefaultIcon" /d "\"%mpv_exe_path%\",1" /f
call :reg add "HKCR\mpv-handler-debug\shell\open\command" /d "\"%mpv_handler_debug_path%\" \"%%%%1\"" /f
echo Successfully installed mpv-handler
echo Enjoy!
goto :EOF
if /I "%interactive%"=="yes" pause
exit /b 0
+13 -58
View File
@@ -1,64 +1,19 @@
@echo OFF
setlocal
:: Unattended install flag. When set, the script will not require user input.
set unattended=no
if "%1"=="/u" set unattended=yes
set interactive=yes
if /I "%1"=="/u" (
set interactive=no
shift
)
:: Make sure this is Windows Vista or later
call :ensure_vista
:: Make sure the script is running as admin
call :ensure_admin
:: Delete registry
call :del_verbs
:die
if not [%1] == [] echo %~1
if [%unattended%] == [yes] exit 1
pause
exit 1
:ensure_admin
:: 'openfiles' is just a commmand that is present on all supported Windows
:: versions, requires admin privileges and has no side effects, see:
:: https://stackoverflow.com/questions/4051883/batch-script-how-to-check-for-admin-rights
openfiles >nul 2>&1
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0uninstall-mpv-handler.ps1" %*
if errorlevel 1 (
echo This batch script requires administrator privileges.
echo Right-click on handler-uninstall.bat and select "Run as administrator".
call :die
echo.
echo Uninstall failed. Re-run from an elevated PowerShell or Command Prompt.
if /I "%interactive%"=="yes" pause
exit /b 1
)
goto :EOF
:ensure_vista
ver | find "XP" >nul
if not errorlevel 1 (
echo This batch script only works on Windows Vista and later. To create file
echo associations on Windows XP, right click on a video file and use "Open with...".
call :die
)
goto :EOF
:reg
:: Wrap the reg command to check for errors
>nul reg %*
if errorlevel 1 set error=yes
if [%error%] == [yes] echo Error in command: reg %*
if [%error%] == [yes] call :die
goto :EOF
:del_verbs
:: Delete deprecated mpv and mpv-debug protocol
call :reg delete "HKCR\mpv" /f
call :reg delete "HKCR\mpv-debug" /f
:: Delete protocol
call :reg delete "HKCR\mpv-handler" /f
call :reg delete "HKCR\mpv-handler-debug" /f
echo Successfully uninstalled mpv-handler
goto :EOF
if /I "%interactive%"=="yes" pause
exit /b 0
+29 -1
View File
@@ -129,6 +129,29 @@ function Get-MpvPathFromConfig {
}
}
function Resolve-CommandPath {
param(
[Parameter(Mandatory = $true)]
[string[]]$Names
)
foreach ($name in $Names) {
try {
$command = Get-Command -Name $name -ErrorAction Stop
if ($command.Path) {
return $command.Path
}
if ($command.Source) {
return $command.Source
}
} catch {
}
}
return $null
}
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
@@ -204,7 +227,12 @@ $effectiveIconPath = if ($IconPath) {
}
(Resolve-Path -LiteralPath $IconPath).Path
} else {
Get-MpvPathFromConfig -ConfigPath $configPath
$configuredPath = Get-MpvPathFromConfig -ConfigPath $configPath
if ($configuredPath) {
$configuredPath
} else {
Resolve-CommandPath -Names @('mpv.com', 'mpv.exe', 'mpv')
}
}
if (-not $effectiveIconPath) {
+332
View File
@@ -0,0 +1,332 @@
#!/usr/bin/env node
import { spawnSync } from 'node:child_process'
import { copyFileSync, existsSync, mkdirSync, readFileSync, statSync, chmodSync, writeFileSync } from 'node:fs'
import os from 'node:os'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptPath = fileURLToPath(import.meta.url)
const scriptDir = path.dirname(scriptPath)
const templateConfigPath = path.join(scriptDir, 'config.toml')
const usage = `Usage:
npm run setup:mpv-handler
npm run setup:mpv-handler -- --root /path/to/mpv-handler
npm run setup:mpv-handler -- --mpv /path/to/mpv --ytdl /path/to/yt-dlp
Options:
--root <path> Path to the extracted mpv-handler folder.
--mpv <path> Override the mpv executable path written to config.toml.
--ytdl <path> Override the yt-dlp executable path written to config.toml.
--skip-config Do not create or update config.toml.
--keep-existing Windows only. Keep existing protocol keys instead of replacing them.
--dry-run Print the actions without changing files or running installers.
--help Show this help text.
`
function fail(message) {
console.error(`Error: ${message}`)
process.exit(1)
}
function parseArgs(argv) {
const options = {
root: '',
mpv: '',
ytdl: '',
skipConfig: false,
keepExisting: false,
dryRun: false,
help: false,
}
for (let index = 0; index < argv.length; index += 1) {
const value = argv[index]
if (value === '--help' || value === '-h') {
options.help = true
continue
}
if (value === '--skip-config') {
options.skipConfig = true
continue
}
if (value === '--keep-existing') {
options.keepExisting = true
continue
}
if (value === '--dry-run') {
options.dryRun = true
continue
}
if (value === '--root' || value === '--mpv' || value === '--ytdl') {
const nextValue = argv[index + 1]
if (!nextValue || nextValue.startsWith('--')) {
fail(`Missing value for ${value}`)
}
if (value === '--root') options.root = nextValue
if (value === '--mpv') options.mpv = nextValue
if (value === '--ytdl') options.ytdl = nextValue
index += 1
continue
}
fail(`Unknown argument: ${value}`)
}
return options
}
function escapeTomlString(value) {
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}
function upsertTomlValue(content, key, value) {
const line = `${key} = "${escapeTomlString(value)}"`
const pattern = new RegExp(`^\\s*#?\\s*${key}\\s*=.*$`, 'm')
if (pattern.test(content)) {
return content.replace(pattern, line)
}
const trimmed = content.trimEnd()
return trimmed ? `${trimmed}\n${line}\n` : `${line}\n`
}
function resolveOnPath(commandNames) {
const locator = process.platform === 'win32' ? 'where.exe' : 'which'
for (const commandName of commandNames) {
const result = spawnSync(locator, [commandName], { encoding: 'utf8' })
if (result.status !== 0) continue
const match = result.stdout
.split(/\r?\n/)
.map((entry) => entry.trim())
.find((entry) => entry)
if (match && existsSync(match)) {
return match
}
}
return ''
}
function isFile(candidatePath) {
try {
return statSync(candidatePath).isFile()
} catch {
return false
}
}
function looksLikeRoot(rootPath) {
const requiredFiles = process.platform === 'win32'
? ['mpv-handler.exe', 'mpv-handler-debug.exe']
: process.platform === 'linux'
? ['mpv-handler', 'mpv-handler.desktop', 'mpv-handler-debug.desktop']
: ['mpv-handler']
return requiredFiles.every((fileName) => isFile(path.join(rootPath, fileName)))
}
function resolveRoot(options) {
const candidates = []
if (options.root) candidates.push(path.resolve(options.root))
candidates.push(scriptDir)
candidates.push(process.cwd())
const seen = new Set()
for (const candidate of candidates) {
if (!candidate || seen.has(candidate)) continue
seen.add(candidate)
if (looksLikeRoot(candidate)) {
return candidate
}
}
if (process.platform === 'win32') {
fail('Could not find mpv-handler files. Re-run with --root pointing at the extracted mpv-handler folder.')
}
if (process.platform === 'linux') {
fail('Could not find a Linux mpv-handler release. Download and extract the upstream archive, then pass --root /path/to/extracted/mpv-handler-linux-amd64.')
}
fail('Automatic setup is not available for this platform yet.')
}
function ensureConfig(rootPath, options) {
const configPath = path.join(rootPath, 'config.toml')
if (options.skipConfig) {
return { configPath, changed: false, mpvPath: '', ytdlPath: '' }
}
const configExists = existsSync(configPath)
const content = configExists
? readFileSync(configPath, 'utf8')
: readFileSync(templateConfigPath, 'utf8')
const detectedMpv = options.mpv || resolveOnPath(process.platform === 'win32' ? ['mpv.com', 'mpv.exe', 'mpv'] : ['mpv'])
const detectedYtdl = options.ytdl || resolveOnPath(process.platform === 'win32' ? ['yt-dlp.exe', 'yt-dlp'] : ['yt-dlp'])
let changed = !configExists
let nextContent = content
if (detectedMpv) {
const updated = upsertTomlValue(nextContent, 'mpv', detectedMpv)
changed ||= updated !== nextContent
nextContent = updated
}
if (detectedYtdl) {
const updated = upsertTomlValue(nextContent, 'ytdl', detectedYtdl)
changed ||= updated !== nextContent
nextContent = updated
}
if (changed && !options.dryRun) {
writeFileSync(configPath, nextContent, 'utf8')
}
return {
configPath,
changed,
mpvPath: detectedMpv,
ytdlPath: detectedYtdl,
}
}
function runWindowsSetup(rootPath, options) {
const powershell = resolveOnPath(['powershell.exe', 'pwsh.exe']) || 'powershell.exe'
const installScript = path.join(scriptDir, 'install-mpv-handler.ps1')
const args = ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', installScript, '-InstallRoot', rootPath]
if (options.keepExisting) {
args.push('-KeepExistingProtocolKeys')
}
if (options.dryRun) {
console.log('Dry run: would execute Windows installer')
console.log(`${powershell} ${args.map((value) => JSON.stringify(value)).join(' ')}`)
return
}
const result = spawnSync(powershell, args, { stdio: 'inherit' })
if (result.status !== 0) {
fail('Windows protocol registration failed. Re-run the command from an elevated PowerShell or Windows Terminal.')
}
}
function rewriteDesktopExec(content, targetBinary) {
return content.replace(/^Exec=.*$/m, (line) => {
const prefix = 'Exec='
const rest = line.slice(prefix.length).trim()
const firstSpaceIndex = rest.indexOf(' ')
const suffix = firstSpaceIndex === -1 ? '' : rest.slice(firstSpaceIndex)
return `${prefix}${targetBinary}${suffix}`
})
}
function runLinuxSetup(rootPath, options) {
const localBin = path.join(os.homedir(), '.local', 'bin')
const applicationsDir = path.join(os.homedir(), '.local', 'share', 'applications')
const targetBinary = path.join(localBin, 'mpv-handler')
const copies = [
{
source: path.join(rootPath, 'mpv-handler'),
target: targetBinary,
executable: true,
},
{
source: path.join(rootPath, 'mpv-handler.desktop'),
target: path.join(applicationsDir, 'mpv-handler.desktop'),
patchExec: true,
},
{
source: path.join(rootPath, 'mpv-handler-debug.desktop'),
target: path.join(applicationsDir, 'mpv-handler-debug.desktop'),
patchExec: true,
},
]
if (options.dryRun) {
console.log('Dry run: would install Linux desktop files to ~/.local')
for (const item of copies) {
console.log(`copy ${item.source} -> ${item.target}`)
}
console.log('xdg-mime default mpv-handler.desktop x-scheme-handler/mpv-handler')
console.log('xdg-mime default mpv-handler-debug.desktop x-scheme-handler/mpv-handler-debug')
return
}
mkdirSync(localBin, { recursive: true })
mkdirSync(applicationsDir, { recursive: true })
for (const item of copies) {
if (item.patchExec) {
const content = readFileSync(item.source, 'utf8')
writeFileSync(item.target, rewriteDesktopExec(content, targetBinary), 'utf8')
continue
}
copyFileSync(item.source, item.target)
if (item.executable) {
chmodSync(item.target, 0o755)
}
}
for (const args of [
['default', 'mpv-handler.desktop', 'x-scheme-handler/mpv-handler'],
['default', 'mpv-handler-debug.desktop', 'x-scheme-handler/mpv-handler-debug'],
]) {
const result = spawnSync('xdg-mime', args, { stdio: 'inherit' })
if (result.status !== 0) {
fail(`xdg-mime failed for ${args[2]}. Run the command manually after fixing your desktop environment registration.`)
}
}
}
function main() {
const options = parseArgs(process.argv.slice(2))
if (options.help) {
console.log(usage)
return
}
if (!['win32', 'linux', 'darwin'].includes(process.platform)) {
fail(`Unsupported platform: ${process.platform}`)
}
if (process.platform === 'darwin') {
console.log('Automatic macOS protocol registration is not available in this repo yet.')
console.log('The app can still browse media on macOS, but desktop playback setup must be handled manually.')
return
}
const rootPath = resolveRoot(options)
const configResult = ensureConfig(rootPath, options)
console.log(`Platform: ${process.platform}`)
console.log(`mpv-handler root: ${rootPath}`)
console.log(`config.toml: ${configResult.configPath}${configResult.changed ? ' (updated)' : ' (unchanged)'}`)
console.log(`mpv: ${configResult.mpvPath || 'not detected'}`)
console.log(`yt-dlp: ${configResult.ytdlPath || 'not detected'}`)
if (process.platform === 'win32') {
runWindowsSetup(rootPath, options)
} else if (process.platform === 'linux') {
runLinuxSetup(rootPath, options)
}
console.log('mpv-handler setup complete.')
}
main()
+32
View File
@@ -0,0 +1,32 @@
#Requires -Version 5.1
#Requires -RunAsAdministrator
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Remove-ProtocolKey {
param(
[Parameter(Mandatory = $true)]
[string]$SchemeName
)
$classesRoot = [Microsoft.Win32.Registry]::ClassesRoot
try {
$classesRoot.DeleteSubKeyTree($SchemeName, $false)
} catch {
}
}
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
throw 'This uninstaller is only for Windows.'
}
Remove-ProtocolKey -SchemeName 'mpv'
Remove-ProtocolKey -SchemeName 'mpv-debug'
Remove-ProtocolKey -SchemeName 'mpv-handler'
Remove-ProtocolKey -SchemeName 'mpv-handler-debug'
Write-Host 'Successfully removed mpv-handler protocol registration.' -ForegroundColor Green