15 KiB
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 wxImage → DrawBitmap 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 nolibav*dylib references (otool -Lcheck). - Follow existing code style: PascalCase classes, snake_case functions, C++17.
- No changes to
StatusPanel.cpp,MediaPlayCtrl.*, or the BambuTunnel interface — the app already createswxMediaCtrl3and 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.txtreferences onlywxMediaCtrl2.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.
Task 1: Enable wxMediaCtrl3 on macOS and link static FFmpeg
Files:
- Modify:
src/slic3r/GUI/wxMediaCtrl3.h(lines 18–22: 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 779–792; FFmpeg link block ~lines 905–910)
Interfaces:
-
Consumes: nothing new (all classes already exist).
-
Produces:
wxMediaCtrl3class compiled on macOS with the same interface as Linux/Windows —Load(wxURI),Play(),Stop(),SetIdleImage(wxString),GetState(),GetLastError(),GetVideoSize(), eventEVT_MEDIA_CTRL_STATdefined once in the lib (fromwxMediaCtrl3.cpp). -
Step 1: Remove the macOS alias in wxMediaCtrl3.h
Current (lines 16–23 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) — nolibav*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 emptyBambuPlayer/dir) - Modify:
src/slic3r/GUI/wxMediaCtrl2.h(remove the#ifdef __WXMAC__section, lines 22–60)
Interfaces:
-
Consumes: Task 1 (macOS no longer references
wxMediaCtrl2— nothing includeswxMediaCtrl2.hon macOS anymore;wxMediaCtrl2is never instantiated on any platform). -
Produces: a clean tree where the old BambuPlayer-based player is gone from macOS. The
BambuPlayerObjC 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 (theEVT_MEDIA_CTRL_STATpath is live — proves the Bambu C API dlsym worked fromlibBambuSource.dylib); -
no repeated decode/error messages like
AVVideoDecoder: ...orcan not find function ...(provesStaticBambuLib::getresolved 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 1–2 (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.