diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98759a8..83e00e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -255,11 +255,22 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - libmpv-dev libgtk-3-dev libwebkit2gtk-4.1-dev \ + libgtk-3-dev libwebkit2gtk-4.1-dev \ libjavascriptcoregtk-4.1-dev libsoup-3.0-dev \ libayatana-appindicator3-dev \ libegl-dev libssl-dev pkg-config librsvg2-dev \ - patchelf rpm + patchelf rpm \ + meson ninja-build \ + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ + libass-dev libpulse-dev libasound2-dev \ + libplacebo-dev \ + libx11-dev libxss-dev libxext-dev libxpresent-dev libxrandr-dev \ + libwayland-dev + + - name: Build libmpv from source + run: | + ./scripts/build-libmpv.sh linux + echo "LD_LIBRARY_PATH=$(pwd)/libs/linux:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV" - name: Install frontend deps run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0188dd6..d139003 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -228,11 +228,22 @@ jobs: run: | sudo apt-get update sudo apt-get install -y \ - libmpv-dev libgtk-3-dev libwebkit2gtk-4.1-dev \ + libgtk-3-dev libwebkit2gtk-4.1-dev \ libjavascriptcoregtk-4.1-dev libsoup-3.0-dev \ libayatana-appindicator3-dev \ libegl-dev libssl-dev pkg-config librsvg2-dev \ - patchelf rpm + patchelf rpm \ + meson ninja-build \ + libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ + libass-dev libpulse-dev libasound2-dev \ + libplacebo-dev \ + libx11-dev libxss-dev libxext-dev libxpresent-dev libxrandr-dev \ + libwayland-dev + + - name: Build libmpv from source + run: | + ./scripts/build-libmpv.sh linux + echo "LD_LIBRARY_PATH=$(pwd)/libs/linux:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV" - name: Prepare signing key run: | diff --git a/apps/desktop/src-tauri/src/commands.rs b/apps/desktop/src-tauri/src/commands.rs index 0d9950e..2959f1d 100644 --- a/apps/desktop/src-tauri/src/commands.rs +++ b/apps/desktop/src-tauri/src/commands.rs @@ -1270,14 +1270,32 @@ pub async fn package_update(app: AppHandle) -> Result<(), String> drop(file); // 3. Install via pkexec (async so we do not block the Tokio worker for the whole GUI prompt) + // For deb: prefer apt-get install which resolves dependencies automatically, + // fall back to dpkg -i for systems without apt-get. let output = match info.install_type.as_str() { - "deb" => tokio::process::Command::new("pkexec") - .arg("dpkg") - .arg("-i") - .arg(&tmp_path) - .output() - .await - .map_err(|e| format!("Failed to run installer: {e}"))?, + "deb" => { + let apt_result = tokio::process::Command::new("pkexec") + .arg("apt-get") + .arg("install") + .arg("-y") + .arg("--allow-downgrades") + .arg(&tmp_path) + .output() + .await; + match apt_result { + Ok(o) if o.status.success() => o, + _ => { + // Fallback to dpkg -i (no dependency resolution) + tokio::process::Command::new("pkexec") + .arg("dpkg") + .arg("-i") + .arg(&tmp_path) + .output() + .await + .map_err(|e| format!("Failed to run installer: {e}"))? + } + } + }, "rpm" => tokio::process::Command::new("pkexec") .arg("rpm") .arg("-U") diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 3e75709..cabc141 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -63,10 +63,24 @@ }, "linux": { "deb": { - "depends": ["libmpv2 | libmpv1", "libegl1", "libgtk-3-0", "libwebkit2gtk-4.1-0"] + "depends": [ + "libmpv2 | libmpv1", + "libegl1", + "libgtk-3-0", + "libwebkit2gtk-4.1-0", + "libpulse0 | libasound2", + "libass9" + ] }, "rpm": { - "depends": ["mpv-libs", "mesa-libEGL", "gtk3", "webkit2gtk4.1"] + "depends": [ + "mpv-libs", + "mesa-libEGL", + "gtk3", + "webkit2gtk4.1", + "pulseaudio-libs", + "libass" + ] } } } diff --git a/crates/tauri-plugin-mpv/build.rs b/crates/tauri-plugin-mpv/build.rs index 7d263c4..e8cb956 100644 --- a/crates/tauri-plugin-mpv/build.rs +++ b/crates/tauri-plugin-mpv/build.rs @@ -61,7 +61,22 @@ fn link_libmpv() { #[cfg(target_os = "linux")] { - // Use pkg-config if available; libmpv2-sys may handle this, but we ensure the link + // Prefer libs/linux/ (source build with audio support) over system pkg-config + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let libs_linux = std::path::Path::new(&manifest_dir) + .join("..") + .join("..") + .join("libs") + .join("linux"); + if libs_linux.join("libmpv.so").exists() { + if let Ok(abs) = libs_linux.canonicalize() { + println!("cargo:rustc-link-search=native={}", abs.display()); + } else { + println!("cargo:rustc-link-search=native={}", libs_linux.display()); + } + return; + } + // Fallback: system pkg-config (for development with libmpv-dev) if let Ok(output) = std::process::Command::new("pkg-config") .args(["--libs", "--cflags", "libmpv"]) .output() @@ -76,17 +91,6 @@ fn link_libmpv() { return; } } - // Fallback: libs/linux/ at workspace root - let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let libs_linux = std::path::Path::new(&manifest_dir) - .join("..") - .join("..") - .join("libs") - .join("linux"); - if libs_linux.join("libmpv.so").exists() { - println!("cargo:rustc-link-search=native={}", libs_linux.display()); - return; - } } #[cfg(target_os = "windows")] diff --git a/crates/tauri-plugin-mpv/src/linux.rs b/crates/tauri-plugin-mpv/src/linux.rs index fa79e14..5c76e55 100644 --- a/crates/tauri-plugin-mpv/src/linux.rs +++ b/crates/tauri-plugin-mpv/src/linux.rs @@ -1549,7 +1549,10 @@ pub fn embedded_options() -> Vec<(&'static str, &'static str)> { // color corruption (red/green hue) on drivers that can't handle the // NV12→RGB interop (VMware, some Mesa/Intel setups). ("hwdec", "auto-copy"), - ("ao", "pipewire,pulse,alsa"), + // Don't restrict audio outputs — let mpv try ALL compiled-in backends. + // Hardcoding "pipewire,pulse,alsa" prevented fallback to other outputs + // (sdl, oss, jack, sndio) on systems where libmpv was built differently. + ("ao", "auto"), // audio sync is correct for callback-driven rendering (not vsync-locked). // display-resample requires calling render() at every vsync, which our // idle_add_once approach doesn't guarantee — it deactivates after a few seconds. @@ -1569,7 +1572,7 @@ pub fn embedded_options() -> Vec<(&'static str, &'static str)> { pub fn fallback_options() -> Vec<(&'static str, &'static str)> { vec![ ("hwdec", "auto"), - ("ao", "pipewire,pulse,alsa"), + ("ao", "auto"), ("video-sync", "display-resample"), ("cache", "yes"), ("demuxer-max-bytes", "150MiB"), @@ -1587,7 +1590,7 @@ pub fn software_fallback_options() -> Vec<(&'static str, &'static str)> { vec![ ("vo", "x11"), ("hwdec", "no"), - ("ao", "pipewire,pulse,alsa"), + ("ao", "auto"), ("cache", "yes"), ("demuxer-max-bytes", "150MiB"), ("demuxer-max-back-bytes", "75MiB"), diff --git a/scripts/build-libmpv.sh b/scripts/build-libmpv.sh index 25b97e9..488eb1a 100755 --- a/scripts/build-libmpv.sh +++ b/scripts/build-libmpv.sh @@ -117,6 +117,19 @@ case "$PLATFORM" in exit 1 fi + # Check audio dev packages — at least one is required for playback with sound + AUDIO_FOUND=false + for lib in libpulse alsa libpipewire-0.3; do + if pkg-config --exists "$lib" 2>/dev/null; then + AUDIO_FOUND=true + fi + done + if [[ "$AUDIO_FOUND" != "true" ]]; then + echo "Warning: No audio dev packages found. Install at least one of:" + echo " sudo apt-get install libpulse-dev libasound2-dev libpipewire-0.3-dev" + echo "Without these, libmpv will have no audio output support." + fi + # Clone mpv source (same version as macOS for consistency) MPV_SRC="$LIBS_DIR/mpv-src" MPV_TAG="v0.40.0" @@ -127,24 +140,65 @@ case "$PLATFORM" in echo " mpv source already present, skipping clone." fi - # Build + # Build with audio + video output support. + # Audio backends are conditionally enabled based on available dev headers. + # If no audio dev headers are found, the build will warn but continue — + # the AUDIO_FOUND check above already warns the user. BUILD_DIR="$MPV_SRC/build-linux" echo " Running meson setup..." - meson setup "$BUILD_DIR" "$MPV_SRC" \ - --buildtype=release \ - --wipe \ - -Dlibmpv=true \ - -Dgl=enabled \ - -Dvulkan=disabled \ - -Dwayland=enabled \ - -Dx11=enabled \ + + MESON_ARGS=( + --buildtype=release + --wipe + -Dlibmpv=true + # Video output: EGL/GL on X11 and Wayland + -Dgl=enabled -Degl=enabled + -Dx11=enabled + -Dwayland=enabled + -Dvulkan=disabled + # Disable optional features we don't need — avoids auto-detection pulling + # in deps that may not be on the CI runner (lua, javascript, etc.) + # Note: libplacebo is a hard requirement of mpv 0.40.0 (not a meson option), + # so libplacebo-dev must be installed. + -Dlua=disabled + -Djavascript=disabled + -Dcaca=disabled + -Dsdl2=disabled + -Ddrm=disabled + -Djack=disabled + -Doss-audio=disabled + -Dsndio=disabled + -Dopenal=disabled + ) + + # Note: X11 support requires: libx11-dev libxss-dev libxext-dev libxpresent-dev libxrandr-dev + # Wayland EGL requires: libwayland-dev + + # Enable audio outputs that have dev headers available. + # Using -D=enabled makes meson fail if the dep can't be satisfied, + # catching misconfigured build environments early. + if pkg-config --exists alsa 2>/dev/null; then + MESON_ARGS+=(-Dalsa=enabled) + echo " Audio: ALSA enabled" + fi + if pkg-config --exists libpulse 2>/dev/null; then + MESON_ARGS+=(-Dpulse=enabled) + echo " Audio: PulseAudio enabled" + fi + if pkg-config --exists libpipewire-0.3 2>/dev/null; then + MESON_ARGS+=(-Dpipewire=enabled) + echo " Audio: PipeWire enabled" + fi + + meson setup "$BUILD_DIR" "$MPV_SRC" "${MESON_ARGS[@]}" echo " Building libmpv.so (this takes a few minutes)..." - ninja -C "$BUILD_DIR" libmpv.so.2 + # Build all targets — the .so name varies by mpv version/config + ninja -C "$BUILD_DIR" # Copy .so to libs/linux/ - SO=$(find "$BUILD_DIR" -name "libmpv.so*" -not -type l | head -1) + SO=$(find "$BUILD_DIR" -name "libmpv.so*" -type f | head -1) if [[ -z "$SO" ]]; then echo "Error: libmpv.so not found after build" exit 1 diff --git a/scripts/bundle-libmpv-linux.sh b/scripts/bundle-libmpv-linux.sh index 63cf5fc..da1b30d 100755 --- a/scripts/bundle-libmpv-linux.sh +++ b/scripts/bundle-libmpv-linux.sh @@ -25,9 +25,13 @@ if ! command -v patchelf &>/dev/null; then exit 1 fi -# Find libmpv — prefer system pkg-config, then libs/linux/ +# Find libmpv — prefer libs/linux/ (source build), then system pkg-config. +# Source build is preferred because it includes audio outputs we control. LIBMPV_PATH="" -if pkg-config --exists mpv 2>/dev/null; then +if [[ -f "$WORKSPACE_ROOT/libs/linux/libmpv.so" ]]; then + LIBMPV_PATH="$WORKSPACE_ROOT/libs/linux/libmpv.so" + echo " Using source-built libmpv from libs/linux/" +elif pkg-config --exists mpv 2>/dev/null; then LIBMPV_DIR=$(pkg-config --variable=libdir mpv 2>/dev/null || echo "") if [[ -n "$LIBMPV_DIR" ]]; then # Try versioned names first (libmpv.so.2, libmpv.so.1), then unversioned @@ -39,11 +43,8 @@ if pkg-config --exists mpv 2>/dev/null; then done fi fi -if [[ -z "$LIBMPV_PATH" && -f "$WORKSPACE_ROOT/libs/linux/libmpv.so" ]]; then - LIBMPV_PATH="$WORKSPACE_ROOT/libs/linux/libmpv.so" -fi if [[ -z "$LIBMPV_PATH" ]]; then - echo "Error: libmpv.so not found via pkg-config or libs/linux/" + echo "Error: libmpv.so not found in libs/linux/ or via pkg-config" exit 1 fi @@ -69,20 +70,66 @@ SYSTEM_LIBS_RE+="|libGL\.so|libEGL\.so|libGLX|libGLdispatch" SYSTEM_LIBS_RE+="|libgtk|libgdk|libglib|libgobject|libgio|libpango|libcairo|libatk" SYSTEM_LIBS_RE+="|libdbus|libsystemd|libfontconfig|libfreetype" -# Resolve and copy transitive multimedia dependencies -ldd "$LIBMPV_PATH" | grep "=> /" | awk '{print $3}' | while read -r dep; do - basename_dep=$(basename "$dep") - if echo "$basename_dep" | grep -qE "$SYSTEM_LIBS_RE"; then - continue - fi - if [[ ! -f "$BUNDLE_DIR/$basename_dep" ]]; then - cp "$dep" "$BUNDLE_DIR/" - echo " bundled: $basename_dep" +# bundle_all_deps: recursively resolve and copy dependencies to a fixpoint. +# Uses the bundle dir itself as the visited set — if a .so already exists +# there, it's been processed and won't be traversed again. +# +# Uses process substitution (< <(...)) instead of a pipeline so that: +# 1. The while loop body runs in the current shell — queue+= propagates. +# 2. "|| true" after the process substitution suppresses grep exit-1 when a +# lib has no "=> /" lines (e.g. only linux-vdso / ld-linux deps), which +# would otherwise trigger set -euo pipefail and kill the script silently. +bundle_all_deps() { + local queue=("$@") + while [[ ${#queue[@]} -gt 0 ]]; do + local lib="${queue[0]}" + queue=("${queue[@]:1}") + while IFS= read -r dep; do + local basename_dep + basename_dep=$(basename "$dep") + if echo "$basename_dep" | grep -qE "$SYSTEM_LIBS_RE"; then + continue + fi + if [[ ! -f "$BUNDLE_DIR/$basename_dep" ]]; then + cp "$dep" "$BUNDLE_DIR/" + echo " bundled: $basename_dep" + queue+=("$BUNDLE_DIR/$basename_dep") + fi + done < <(ldd "$lib" 2>/dev/null | grep "=> /" | awk '{print $3}' || true) + done +} + +# Bundle libmpv and all transitive dependencies +bundle_all_deps "$LIBMPV_PATH" + +# Set RPATH so the binary and all bundled libs find co-located libs +echo " Setting RPATH on binary..." +patchelf --set-rpath '$ORIGIN' "$BINARY" \ + || { echo "Error: patchelf --set-rpath failed on $BINARY"; exit 1; } +patchelf --set-rpath '$ORIGIN' "$BUNDLE_DIR/libmpv.so" \ + || { echo "Error: patchelf --set-rpath failed on $BUNDLE_DIR/libmpv.so"; exit 1; } + +# Also set RPATH on all bundled .so files so they can find each other +for so in "$BUNDLE_DIR"/*.so*; do + if [[ -f "$so" && ! -L "$so" ]]; then + patchelf --set-rpath '$ORIGIN' "$so" 2>/dev/null || true fi done -# Set RPATH so the binary and libmpv find co-located libs -patchelf --set-rpath '$ORIGIN' "$BINARY" -patchelf --set-rpath '$ORIGIN' "$BUNDLE_DIR/libmpv.so" +# Verify audio output support +echo "" +echo "==> Verifying bundled audio libraries..." +AUDIO_LIBS=0 +for pattern in libpulse libasound libpipewire; do + if ls "$BUNDLE_DIR"/${pattern}* 2>/dev/null | head -1 >/dev/null; then + echo " ✓ Found ${pattern}" + AUDIO_LIBS=$((AUDIO_LIBS + 1)) + fi +done +if [[ $AUDIO_LIBS -eq 0 ]]; then + echo " ⚠ WARNING: No audio libraries bundled. AppImage may have no audio output." + echo " Install audio dev packages and rebuild libmpv: sudo apt-get install libpulse-dev libasound2-dev" +fi +echo "" echo " Done. Bundled libs placed alongside binary in $BUNDLE_DIR"