Minor. Sync now carries the whole library, not only watch positions: shares with their links and passwords, playback, appearance and metadata settings, the TMDB key, hand-picked matches and per-title tracks, so every signed-in device shows the same library. Devices share it once all of them run 1.4.0; an older build keeps syncing watch positions and ignores the rest. Android drops picture-in-picture. iOS fixes the stretched player after a rotation or resize, the share that failed to add on the first try, and settings that saved late or silently. Both phone clients now show what is running and why a form was refused. No catalog schema change since 1.3.0.
Packaging
Everything here is driven by two scripts. scripts/build.sh covers Linux and
macOS; scripts/build.ps1 covers Windows, because the .exe and the .msi
need MSVC and WiX and neither is reachable from a Unix host.
| Artifact | Command | Host it must run on |
|---|---|---|
dist/proton-stream-<ver>-<triple>.tar.gz |
scripts/build.sh tarball |
Linux or macOS |
dist/proton-stream_<ver>-1_amd64.deb |
scripts/build.sh deb |
any Linux |
dist/proton-stream-<ver>-1.x86_64.rpm |
scripts/build.sh rpm |
any Linux with rpmbuild |
packaging/*.pkg.tar.zst |
cd packaging && makepkg -fi |
Arch |
dist/Proton Stream.app + .dmg |
scripts/build.sh dmg |
macOS |
dist/windows/proton-stream.exe + .zip |
scripts\build.ps1 -Artifacts zip |
Windows |
dist/proton-stream-<ver>-x64.msi |
scripts\build.ps1 -Artifacts msi |
Windows |
scripts/build.sh with no arguments produces everything the host can.
How it fits together
One cargo build feeds every Linux and macOS artifact. The binaries are compiled
once, staged into a filesystem image at dist/stage — rooted so that
dist/stage/usr/bin/pstr is exactly where the file lands on the target system —
and each packager copies out of that image. A .deb and an .rpm from the same
run are therefore the same bytes, and neither dpkg-deb nor rpmbuild needs a
Rust toolchain or libmpv headers.
--skip-build reuses dist/stage, which is what you want when iterating on a
control file or a spec.
The Arch package is the exception: makepkg compiles in place from the
checkout, by Arch convention and to match ../proton-drive-linux. It ignores
dist/stage entirely. scripts/build.sh arch runs it for you and copies the
result into dist/, but it is not part of all — it recompiles, and most hosts
are not Arch.
Runtime dependencies, and why they are what they are
| Why | |
|---|---|
| libmpv ≥ 0.34 | The player. pstr-player links it directly and drives it through stream_cb; nothing here shells out to mpv(1), so packages depend on the library, not the player binary. |
| libsecret / Secret Service | The share URL fragment and the custom password are secrets and live in the OS credential store, never in shares.json. |
| WebKitGTK 4.1 | The window Proton's CAPTCHA is shown in when it gates a sign-in (pstr-app::verify). The page reports success by posting a message to its host, so the system browser cannot stand in for it. |
| libGL, libxkbcommon, libX11, libwayland-client | eframe on glow, with both display backends compiled in. |
Not dependencies, deliberately: SQLite (rusqlite is bundled) and
OpenSSL (TLS is rustls with webpki-roots). Both are inside the binary.
Per packager that is:
- Debian —
libmpv2 | libmpv1(trixie | bookworm),libsecret-1-0,libwebkit2gtk-4.1-0,libgl1,libxkbcommon0,libx11-6,libwayland-client0. - RPM —
mpv-libs,libsecret,webkit2gtk4.1,mesa-libGL,libxkbcommon,libX11,libwayland-client. - Arch —
mpv(which carries bothlibmpv.soand its headers),libsecret,webkit2gtk-4.1.
The .deb and .rpm are built from binaries compiled on the packaging host, so
they inherit its glibc. A package built on Arch installs on Fedora rawhide but
not on an older distribution — build on the oldest one you intend to support, or
in that distribution's container.
AutoReqProv: no in the spec is for the same reason: the ELF scan would generate
soname requirements against whatever the packaging host happens to have, so the
explicit Requires list stands instead.
Windows
libmpv is the whole difficulty. libmpv2-sys emits cargo:rustc-link-lib=mpv,
so the linker wants an import library named mpv.lib and the process wants
libmpv-2.dll at run time. Neither ships with mpv's Windows player build —
both come from the separate mpv-dev archive:
https://github.com/shinchiro/mpv-winbuild-cmake/releases/latest
(The SourceForge project these are better known by mirrors the same builds, but
its /download URLs hand anything that does not look like a browser an HTML
interstitial, so --fetch-mpv goes to the source.)
The cross build from Linux is the supported path, and the one CI uses —
scripts/build-windows.sh --fetch-mpv fetches the archive, links against its
libmpv.dll.a with mingw-w64 and builds the .msi with wixl. On a real
Windows box scripts\build.ps1 -MpvDev C:\mpv-dev does the MSVC equivalent, but
note that it needs an mpv.lib or an mpv.def to generate one from with
lib.exe, and current mpv-dev archives ship neither — only the mingw import
library. Producing mpv.lib from a recent archive means writing the .def
yourself from dumpbin /exports libmpv-2.dll.
There is no static libmpv worth shipping, so "standalone exe" means the exe
plus libmpv-2.dll beside it — that pair, plus pstr.exe and the README, is
what the portable .zip contains and what the .msi installs.
The cross build adds WebView2Loader.dll to that set. The CAPTCHA window runs
on WebView2, and under -gnu webview2-com-sys links its loader as a DLL —
without it beside the exe, the app does not start at all. build-windows.sh
takes it from that crate's build output. An -msvc build links the loader
statically and needs no DLL. The WebView2 runtime is part of Windows 10 and
11 and is not shipped.
The MSI needs the WiX toolset; the script installs it (dotnet tool install --global wix) and adds the UI extension if they are missing. It installs to
Program Files\Proton Stream, puts a Start Menu shortcut on the GUI, and adds
the install directory to the system PATH so pstr works from a shell — that
PATH entry is tied to pstr.exe's component, so uninstalling removes it.
UpgradeCode in proton-stream.wxs is fixed forever: it is what makes 0.2.0
replace 0.1.0 rather than install beside it. Never regenerate it.
Signing is opt-in: -SignCertThumbprint <thumbprint> signs both executables and
the MSI.
macOS
scripts/build.sh dmg produces Proton Stream.app inside a drag-to-Applications
disk image.
By default it copies libmpv and its non-system dependency closure into
Contents/Frameworks and rewrites every install name, because a .dmg that
requires the recipient to have run brew install mpv is not a .dmg anyone can
use. The bundle is then ad-hoc signed — every install_name_tool edit
invalidates the signature, and an unsigned-but-modified binary is killed outright
on arm64. Pass --no-bundle-libs to link against the host's libmpv instead.
The .icns is rasterized from io.narl.proton-stream.svg when rsvg-convert or
magick is available; without either, the bundle ships without an icon.
Files
PKGBUILD |
Arch, in-tree makepkg build |
proton-stream.spec |
RPM, installs from dist/stage |
io.narl.proton-stream.desktop / .svg |
Freedesktop entry and icon |
macos/Info.plist.in |
.app bundle plist; @VERSION@/@BIN@/@APP_ID@ substituted at build time |
windows/proton-stream.wxs |
WiX v4+ installer definition |
windows/proton-stream.ico |
Exe and Add/Remove Programs icon, rasterized from the SVG; crates/pstr-app/build.rs compiles it into proton-stream.exe |
windows/license.rtf |
Fallback for the installer's license dialog; the repo's own LICENSE is preferred when it exists |
The Debian control file has no template — scripts/build.sh writes it, because
almost every field in it is computed.
Releasing
A release is a v<version> tag on main; .github/workflows/release.yml does
the rest. Every job runs on Linux: scripts/build.sh tarball deb rpm for the
Linux artifacts and scripts/build-windows.sh --fetch-mpv for the Windows ones,
published together with a SHA256SUMS to a GitHub release. So the steps are:
# 1. bump Cargo.toml's workspace version *and* packaging/PKGBUILD's pkgver
# 2. commit on main, then
git tag -a v0.1.0 -m 'proton-stream 0.1.0'
git push origin main --follow-tags
The workflow refuses to publish a tag whose commit is not an ancestor of main,
or whose version disagrees with Cargo.toml or the PKGBUILD — the artifacts are
named from the manifest, so a drifted tag would ship a file called something
other than the tag. It also refuses a Cargo.toml carrying [patch.crates-io]:
a release builds against the published SDK crates, never a sibling checkout.
workflow_dispatch runs everything except the publish step, which is how to
rehearse without burning a tag.
Not covered by the workflow: the Arch package (makepkg compiles in place, so
it wants an Arch host) and the macOS .app/.dmg (no runner for it here yet).
Both are still scripts/build.sh arch and scripts/build.sh dmg by hand.