Files
OrcaSlicer/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md
T
2026-08-14 20:11:09 +08:00

15 KiB
Raw Blame History

macOS FFmpeg Media Player Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make macOS use the same FFmpeg-based media player (wxMediaCtrl3 + AVVideoDecoder) as Windows/Linux, linking the static FFmpeg libraries from the deps build, and remove the old wxMediaCtrl2.mm BambuPlayer-based player.

Architecture: The new player is platform-neutral C++ already used on Linux/Windows. Enabling it on macOS is pure build wiring: compile wxMediaCtrl3.cpp + AVVideoDecoder.cpp on macOS, drop the __WXMAC__ alias that redirects wxMediaCtrl3 to the old wxMediaCtrl2, and link static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) from the deps install. The Bambu stream API is dlsym'd at runtime from the network plugin (libBambuSource.dylib), which already exports it — no plugin changes needed. Rendering reuses the existing wxImageDrawBitmap paint path (same as Linux).

Tech Stack: C++17, wxWidgets, CMake, FFmpeg 7.0.3 (libavcodec/libswscale/libavutil), macOS (Xcode generator), deps/ ExternalProject build system.

Global Constraints

  • Branch: dev/ffmpeg-player-macos. Commit after every task.
  • Linux and Windows builds must not change — the FFmpeg deps flag change is guarded by APPLE; Linux keeps --enable-shared, Windows keeps its prebuilt DLL zips.
  • Static FFmpeg only on macOS: deps produce libavcodec.a/libswscale.a/libavutil.a; the app links those explicitly — the app binary must have no libav* dylib references (otool -L check).
  • Follow existing code style: PascalCase classes, snake_case functions, C++17.
  • No changes to StatusPanel.cpp, MediaPlayCtrl.*, or the BambuTunnel interface — the app already creates wxMediaCtrl3 and uses only its public interface.
  • The player cannot be unit-tested (hardware/plugin-dependent GUI code); verification is build-level, link-level, and manual runtime on a Mac.
  • localization/i18n/list.txt references only wxMediaCtrl2.cpp (Win/Linux, stays) — no translation-list changes needed.
  • Build dirs on the dev machine: main app = build_arm64/ (Xcode generator, multi-config), deps = deps/build/arm64/ (Unix Makefiles). App target name: OrcaSlicer. Substitute your own configured build dirs where noted.

Files:

  • Modify: src/slic3r/GUI/wxMediaCtrl3.h (lines 1822: the #ifdef __WXMAC__ alias branch)
  • Modify: src/slic3r/GUI/wxMediaCtrl3.cpp:13 (uncomment the event define)
  • Modify: src/slic3r/GUI/wxMediaCtrl2.cpp:101 (remove the event define)
  • Modify: src/slic3r/CMakeLists.txt (APPLE source list ~lines 779792; FFmpeg link block ~lines 905910)

Interfaces:

  • Consumes: nothing new (all classes already exist).

  • Produces: wxMediaCtrl3 class compiled on macOS with the same interface as Linux/Windows — Load(wxURI), Play(), Stop(), SetIdleImage(wxString), GetState(), GetLastError(), GetVideoSize(), event EVT_MEDIA_CTRL_STAT defined once in the lib (from wxMediaCtrl3.cpp).

  • Step 1: Remove the macOS alias in wxMediaCtrl3.h

Current (lines 1623 of src/slic3r/GUI/wxMediaCtrl3.h):

void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);

#ifdef __WXMAC__

#include "wxMediaCtrl2.h"
#define wxMediaCtrl3 wxMediaCtrl2

#else

#define BAMBU_DYNAMIC

New:

void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height);

#define BAMBU_DYNAMIC

Also remove the matching #endif that closed the #else branch (the one before the final #endif /* wxMediaCtrl3_h */), so the file's #ifndef/#endif guard pair stays balanced.

  • Step 2: Move the EVT_MEDIA_CTRL_STAT definition into wxMediaCtrl3.cpp

In src/slic3r/GUI/wxMediaCtrl3.cpp:13, uncomment:

//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);

becomes:

wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);

In src/slic3r/GUI/wxMediaCtrl2.cpp:101, delete:

wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent);

(One definition total in the lib — MediaPlayCtrl.cpp:59 binds this event on the media ctrl.)

  • Step 3: Update the APPLE source list in CMakeLists.txt

In src/slic3r/CMakeLists.txt, the APPLE branch (currently compiles wxMediaCtrl2.mm, which becomes dead on macOS):

            GUI/wxMediaCtrl2.mm
            GUI/wxMediaCtrl2.h
            GUI/wxMediaCtrl3.h
        )

becomes:

            GUI/AVVideoDecoder.cpp
            GUI/AVVideoDecoder.hpp
            GUI/wxMediaCtrl3.cpp
            GUI/wxMediaCtrl3.h
        )

(The else () branch — Win/Linux — stays exactly as it is.)

  • Step 4: Link static FFmpeg on macOS

In src/slic3r/CMakeLists.txt, the FFmpeg block (currently if (NOT APPLE)):

if (NOT APPLE)
    pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
        libavcodec
        libswscale
        libavutil
    )
    target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()

becomes:

if (APPLE)
    # Static FFmpeg from the deps install: nothing to bundle into the .app,
    # no rpath/install_name handling. Order matters: avcodec -> swscale -> avutil.
    find_library(LIBAVCODEC_LIBRARY NAMES libavcodec.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
    find_library(LIBSWSCALE_LIBRARY NAMES libswscale.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
    find_library(LIBAVUTIL_LIBRARY NAMES libavutil.a PATHS ${CMAKE_PREFIX_PATH}/lib NO_DEFAULT_PATH)
    target_link_libraries(libslic3r_gui ${LIBAVCODEC_LIBRARY} ${LIBSWSCALE_LIBRARY} ${LIBAVUTIL_LIBRARY})
    target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_PREFIX_PATH}/include)
else ()
    pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET
        libavcodec
        libswscale
        libavutil
    )
    target_link_libraries(libslic3r_gui PkgConfig::LIBAV)
endif()

The deps install (${CMAKE_PREFIX_PATH}/lib) already contains the three .a files from the existing arm64 deps build — no deps rebuild needed for this task.

  • Step 5: Reconfigure and build the app

Run (Xcode generator; cmake re-runs automatically on build):

cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer

Expected: configure succeeds (no pkg_check_modules errors on macOS, find_library finds all three .a files), compile succeeds (wxMediaCtrl3.cpp and AVVideoDecoder.cpp compile on macOS without changes), link succeeds.

If CMake complains that wxMediaCtrl3.h is included but not in the source list or similar IDE-only warnings — ignore; headers in the list are cosmetic.

  • Step 6: Verify no dynamic FFmpeg dependency
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"

Expected: prints OK: no dynamic FFmpeg (empty grep output). This is the whole point of static linking — nothing to bundle into the .app.

  • Step 7: Quick sanity — macOS unit tests still pass
ctest --test-dir build_arm64/tests/libslic3r --output-on-failure

Expected: passes (add -C RelWithDebInfo if the multi-config generator requires it). If no tests were built in this build dir, build target tests first (cmake --build build_arm64 --config RelWithDebInfo --target tests).

  • Step 8: Commit
git add src/slic3r/CMakeLists.txt src/slic3r/GUI/wxMediaCtrl3.h src/slic3r/GUI/wxMediaCtrl3.cpp src/slic3r/GUI/wxMediaCtrl2.cpp
git commit -m "feat: use FFmpeg media player on macOS with static FFmpeg"

Task 2: Static-only FFmpeg in the macOS deps build

Files:

  • Modify: deps/FFMPEG/FFMPEG.cmake (non-MSVC branch, APPLE section and CONFIGURE_COMMAND)

Interfaces:

  • Consumes: nothing.

  • Produces: a deps install on macOS containing only libavcodec.a, libswscale.a, libavutil.a (+ headers) — no libav* dylibs, so no bundling/rpath machinery is ever needed on macOS. Linux and Windows output are unchanged.

  • Step 1: Add the static flag variable

In deps/FFMPEG/FFMPEG.cmake, inside the non-MSVC else () branch, in the existing if (APPLE) block:

    if (APPLE)
        set(_minos_cmd 
            "CFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
            "LDFLAGS=-mmacosx-version-min=${DEP_OSX_TARGET}"
            )

add after the _minos_cmd set:

        # Static FFmpeg: nothing to bundle into the .app, no rpath handling.
        # Shared flags must come AFTER --enable-shared below so they win.
        set(_link_cmd --enable-static --disable-shared)

and add a matching else () after the if (IS_CROSS_COMPILE) ... endif() block inside that if (APPLE), so non-Apple Unix keeps shared:

    else ()
        set(_link_cmd --enable-shared)
    endif ()

(If the existing if (IS_CROSS_COMPILE) block is the last thing inside if (APPLE), the new else () closes the if (APPLE) itself.)

  • Step 2: Use the variable in CONFIGURE_COMMAND

In the ExternalProject_Add(dep_FFMPEG ...) configure command:

            "--prefix=${DESTDIR}"
            --enable-shared

becomes:

            "--prefix=${DESTDIR}"
            --enable-shared
            ${_link_cmd}

Order matters: --enable-shared comes first, then --enable-static --disable-shared (APPLE) or --enable-shared (Linux) — the last flag wins in FFmpeg configure.

  • Step 3: Rebuild the FFmpeg dep (slow — several minutes, run in background)

The changed CONFIGURE_COMMAND invalidates the ExternalProject stamp, so this re-configures and rebuilds FFmpeg:

cmake --build deps/build/arm64 --target dep_FFMPEG

For a fully clean static-only check (removes the previous shared build tree, which can leave stale .dylib files behind in the in-source build):

rm -rf deps/build/arm64/dep_FFMPEG-prefix
cmake --build deps/build/arm64 --target dep_FFMPEG
  • Step 4: Verify the artifacts
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.a
ls deps/build/arm64/dep_FFMPEG-prefix/src/dep_FFMPEG/libavcodec/*.dylib 2>/dev/null || echo "OK: no dylibs"

Expected: libavcodec.a present, second command prints OK: no dylibs. Check libavutil and libswscale the same way.

  • Step 5: Verify the app still links against the static libs
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer
otool -L build_arm64/src/RelWithDebInfo/OrcaSlicer.app/Contents/MacOS/OrcaSlicer | grep -i "libav" || echo "OK: no dynamic FFmpeg"

Expected: build succeeds, OK: no dynamic FFmpeg.

  • Step 6: Commit
git add deps/FFMPEG/FFMPEG.cmake
git commit -m "build: build static-only FFmpeg for macOS deps"

Task 3: Remove the old macOS player

Files:

  • Delete: src/slic3r/GUI/wxMediaCtrl2.mm
  • Delete: src/slic3r/GUI/BambuPlayer/BambuPlayer.h (and the empty BambuPlayer/ dir)
  • Modify: src/slic3r/GUI/wxMediaCtrl2.h (remove the #ifdef __WXMAC__ section, lines 2260)

Interfaces:

  • Consumes: Task 1 (macOS no longer references wxMediaCtrl2 — nothing includes wxMediaCtrl2.h on macOS anymore; wxMediaCtrl2 is never instantiated on any platform).

  • Produces: a clean tree where the old BambuPlayer-based player is gone from macOS. The BambuPlayer ObjC class itself remains inside the network plugin (external prebuilt binary) — only the GUI-side consumer is removed.

  • Step 1: Delete the old player files

git rm src/slic3r/GUI/wxMediaCtrl2.mm
git rm src/slic3r/GUI/BambuPlayer/BambuPlayer.h
rmdir src/slic3r/GUI/BambuPlayer 2>/dev/null || true
  • Step 2: Strip the WXMAC section from wxMediaCtrl2.h

In src/slic3r/GUI/wxMediaCtrl2.h, remove the entire macOS branch of the #ifdef __WXMAC__ guard — from #ifdef __WXMAC__ (line 22) through the closing }; of the mac class (line 60), and the #else marker — leaving only the non-mac class wxMediaCtrl2 : public wxMediaCtrl { ... }; definition followed by the final #endif /* wxMediaCtrl2_h */. The resulting file keeps its #ifndef/#endif include guard pair balanced.

The file stays on disk because Win/Linux compile wxMediaCtrl2.cpp, which includes it.

  • Step 3: Grep for leftover references
grep -rn "wxMediaCtrl2.mm\|BambuPlayer/BambuPlayer.h\|BambuPlayer" src/slic3r --include="*.cpp" --include="*.h" --include="*.mm" --include="*.txt"

Expected: no hits in src/slic3r/GUI (ignore localization/i18n/list.txt:196, which lists the Win/Linux wxMediaCtrl2.cpp and stays).

  • Step 4: Rebuild the app
cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer

Expected: configure + compile + link succeed with the deleted files gone.

  • Step 5: Commit
git add -A src/slic3r/GUI
git commit -m "refactor: remove old BambuPlayer-based media player from macOS"

Task 4: Runtime verification on hardware

Files: none — manual verification.

Interfaces: consumes all prior tasks. Final gate: the new player must actually stream on a Mac.

  • Step 1: Launch the freshly built app
open build_arm64/src/RelWithDebInfo/OrcaSlicer.app

Expected: app launches normally; no crash in the network/device subsystem.

  • Step 2: Load the network plugin and open the Device tab

Log in / ensure the network plugin (libBambuSource.dylib) loads, select a printer, open the Device tab (camera monitoring panel).

Expected: the camera preview area shows the idle image initially (no crash — this exercises wxMediaCtrl3::SetIdleImage and the wxImage load path on macOS for the first time).

  • Step 3: Start the stream and watch it render

Click play / wait for MediaPlayCtrl to start the stream.

Expected: live video renders in the panel. Check the console/log output (BOOST_LOG goes to the terminal if run from it, or check the log file):

  • stat_log ... lines appear (the EVT_MEDIA_CTRL_STAT path is live — proves the Bambu C API dlsym worked from libBambuSource.dylib);

  • no repeated decode/error messages like AVVideoDecoder: ... or can not find function ... (proves StaticBambuLib::get resolved all Bambu functions);

  • Stop/Play toggle works; idle image reappears on stop;

  • window resize keeps aspect ratio (exercises DoSetSize/adjust_frame_size/paintEvent).

  • Step 4: Confirm the old player is really gone

Expected: nothing in the logs references BambuPlayer (the ObjC class is no longer dlsym'd); the video path is entirely wxMediaCtrl3 + AVVideoDecoder.

If a printer is unavailable, at minimum verify Steps 12 (launch + idle image) and note in the PR that live-stream verification needs hardware.

  • Step 5: Final review pass
git log --oneline -6
git show --stat HEAD  # and each of the three task commits

Expected: the last 4 commits are the design doc + the 3 implementation tasks (each task commit touches only its listed files). Review the diff for scope: no Linux/Windows changes beyond the two EVT_MEDIA_CTRL_STAT lines in Task 1, no StatusPanel/MediaPlayCtrl changes.