diff --git a/.github/workflows/build_deps.yml b/.github/workflows/build_deps.yml index 9927f5bef6..4e01051073 100644 --- a/.github/workflows/build_deps.yml +++ b/.github/workflows/build_deps.yml @@ -149,7 +149,7 @@ jobs: working-directory: ${{ github.workspace }} run: | if [ -z "${{ vars.SELF_HOSTED }}" ]; then - brew install automake texinfo libtool + brew install automake texinfo libtool pkgconf yasm nasm fi ./build_release_macos.sh -dx ${{ !vars.SELF_HOSTED && '-1' || '' }} -a ${{ inputs.arch }} -t 10.15 (cd "${{ github.workspace }}/deps/build/${{ inputs.arch }}" && \ diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 112c0b279b..e8dcef0b05 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -385,6 +385,13 @@ jobs: dir "C:/Program Files (x86)/Windows Kits/10/Include" choco install nsis + - name: Install pkg-config + # FFmpeg is discovered via pkg-config (pkg_check_modules LIBAV in + # src/slic3r/CMakeLists.txt); the Windows runners don't ship it. + if: runner.os == 'Windows' && !vars.SELF_HOSTED + run: | + choco install pkgconfiglite -y + - name: Build slicer Win if: runner.os == 'Windows' working-directory: ${{ github.workspace }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f0db669b8..c912cdd08f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,6 +298,8 @@ if (APPLE) SET(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.orcaslicer.OrcaSlicer") message(STATUS "Orca: IS_CROSS_COMPILE: ${IS_CROSS_COMPILE}") +elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(CMAKE_INSTALL_RPATH "$ORIGIN") endif () # Proposal for C++ unit tests and sandboxes @@ -474,7 +476,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # WIN10SDK_PATH is used to point CMake to the WIN10 SDK installation directory. # We pick it from environment if it is not defined in another way # ORCA: Removed Netfabb STL fixing service support in favor of CGAL. -# if(WIN32) +if(WIN32) + find_package(PkgConfig REQUIRED) # if(NOT DEFINED WIN10SDK_PATH) # if(DEFINED ENV{WIN10SDK_PATH}) # set(WIN10SDK_PATH "$ENV{WIN10SDK_PATH}") @@ -510,7 +513,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # else() # message("Building without Win10 Netfabb STL fixing service support") # endif() -# endif() +endif() if (APPLE) message("OS X SDK Path: ${CMAKE_OSX_SYSROOT}") @@ -1065,6 +1068,10 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) ${CMAKE_PREFIX_PATH}/bin/occt/TKXDESTEP.dll ${CMAKE_PREFIX_PATH}/bin/occt/TKXSBase.dll ${CMAKE_PREFIX_PATH}/bin/freetype.dll + ${CMAKE_PREFIX_PATH}/bin/avcodec-61.dll + ${CMAKE_PREFIX_PATH}/bin/swresample-5.dll + ${CMAKE_PREFIX_PATH}/bin/swscale-8.dll + ${CMAKE_PREFIX_PATH}/bin/avutil-59.dll DESTINATION ${_out_dir}) set(${output_dlls} @@ -1100,12 +1107,58 @@ function(orcaslicer_copy_dlls target config postfix output_dlls) ${_out_dir}/TKXSBase.dll ${_out_dir}/freetype.dll - + ${_out_dir}/avcodec-61.dll + ${_out_dir}/swresample-5.dll + ${_out_dir}/swscale-8.dll + ${_out_dir}/avutil-59.dll PARENT_SCOPE ) endfunction() +function(orcaslicer_copy_sos target config postfix output_sos) + + get_property(_is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + get_target_property(_alt_out_dir ${target} RUNTIME_OUTPUT_DIRECTORY) + + if (_alt_out_dir) + set(_out_dir "${_alt_out_dir}") + elseif (_is_multi) + set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}/${config}") + else () + set(_out_dir "${CMAKE_CURRENT_BINARY_DIR}") + endif () + + file(COPY ${CMAKE_PREFIX_PATH}/lib/libavcodec.so + ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61 + ${CMAKE_PREFIX_PATH}/lib/libavcodec.so.61.3.100 + ${CMAKE_PREFIX_PATH}/lib/libavutil.so + ${CMAKE_PREFIX_PATH}/lib/libavutil.so.59 + ${CMAKE_PREFIX_PATH}/lib/libavutil.so.59.8.100 + ${CMAKE_PREFIX_PATH}/lib/libswscale.so + ${CMAKE_PREFIX_PATH}/lib/libswscale.so.8 + ${CMAKE_PREFIX_PATH}/lib/libswscale.so.8.1.100 + ${CMAKE_PREFIX_PATH}/lib/libswresample.so + ${CMAKE_PREFIX_PATH}/lib/libswresample.so.5 + ${CMAKE_PREFIX_PATH}/lib/libswresample.so.5.1.100 + DESTINATION ${_out_dir}) + + set(${output_sos} + ${_out_dir}/libavcodec.so + ${_out_dir}/libavcodec.so.61 + ${_out_dir}/libavcodec.so.61.3.100 + ${_out_dir}/libavutil.so + ${_out_dir}/libavutil.so.59 + ${_out_dir}/libavutil.so.59.8.100 + ${_out_dir}/libswscale.so + ${_out_dir}/libswscale.so.8 + ${_out_dir}/libswscale.so.8.1.100 + ${_out_dir}/libswresample.so + ${_out_dir}/libswresample.so.5 + ${_out_dir}/libswresample.so.5.1.100 + PARENT_SCOPE + ) +endfunction() # Bundled sources set their own warning flags, and a plain -Wall there means /Wall # (= -Weverything) under clang-cl. Target options are applied after the ones a target @@ -1217,6 +1270,20 @@ else () endif() endif () +if (CMAKE_SYSTEM_NAME STREQUAL "Linux") + set(LIBRARY_FILES + ${LIBDIR_BIN}/libavcodec.so.61 + ${LIBDIR_BIN}/libavcodec.so.61.3.100 + ${LIBDIR_BIN}/libavutil.so.59 + ${LIBDIR_BIN}/libavutil.so.59.8.100 + ${LIBDIR_BIN}/libswresample.so.5 + ${LIBDIR_BIN}/libswresample.so.5.1.100 + ${LIBDIR_BIN}/libswscale.so.8 + ${LIBDIR_BIN}/libswscale.so.8.1.100 + ) + install(FILES ${LIBRARY_FILES} DESTINATION "${CMAKE_INSTALL_PREFIX}/bin") +endif () + install(FILES ${CMAKE_SOURCE_DIR}/LICENSE.txt DESTINATION ".") configure_file(${LIBDIR}/dev-utils/platform/unix/fhs.hpp.in ${LIBDIR_BIN}/dev-utils/platform/unix/fhs.hpp) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index c95cdf8d73..ed3af70d03 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -375,7 +375,7 @@ include(libnoise/libnoise.cmake) include(Draco/Draco.cmake) -# Assimp: glTF/GLB/FBX import for the texture-to-color feature. +include(FFMPEG/FFMPEG.cmake) include(Assimp/Assimp.cmake) @@ -459,6 +459,7 @@ set(_dep_list dep_libnoise dep_python3 dep_wxInspector + dep_FFMPEG dep_Assimp ) diff --git a/deps/FFMPEG/FFMPEG.cmake b/deps/FFMPEG/FFMPEG.cmake new file mode 100644 index 0000000000..38e952bbdd --- /dev/null +++ b/deps/FFMPEG/FFMPEG.cmake @@ -0,0 +1,87 @@ +set(_conf_cmd ./configure) + +if (MSVC) + set(_source_dir "${CMAKE_BINARY_DIR}/dep_FFMPEG-prefix/src/dep_FFMPEG") + + set(PREBUILD_URL_arm64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-winarm64-orca-shared-7.0.zip") + set(PREBUILD_HASH_arm64 "12f4140279f2f8469885e1b5b2e8be9d788882914c21523cacd56989f3548054") + set(PREBUILD_URL_x64 "https://github.com/Noisyfox/FFmpeg-Builds-Orca/releases/download/autobuild-2026-07-17-14-28/ffmpeg-n7.0.3-31-g9b6ffd74b5-win64-orca-shared-7.0.zip") + set(PREBUILD_HASH_x64 "e65916020ddb9ef84b2666dfbcbfc9b1d67f69d15b4a66db53754637bf2d498c") + + ExternalProject_Add(dep_FFMPEG + URL ${PREBUILD_URL_${DEPS_ARCH}} + URL_HASH SHA256=${PREBUILD_HASH_${DEPS_ARCH}} + DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/bin" "${DESTDIR}/bin" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/lib" "${DESTDIR}/lib" + COMMAND ${CMAKE_COMMAND} -E copy_directory "${_source_dir}/include" "${DESTDIR}/include" + ) + +else () + if (APPLE) + set(_minos_cmd + "--extra-cflags=-mmacosx-version-min=${DEP_OSX_TARGET}" + "--extra-ldflags=-mmacosx-version-min=${DEP_OSX_TARGET}" + ) + # Static FFmpeg: nothing to bundle into the .app, no rpath handling. + # Disable the VideoToolbox/AudioToolbox HW-accel paths: the player decodes + # in software (swscale), and the auto-detected HW objects would drag in + # system frameworks that the static libs would then depend on. + set(_link_cmd --enable-static --disable-shared --disable-videotoolbox --disable-audiotoolbox) + if (IS_CROSS_COMPILE) + set(_cross_cmd --enable-cross-compile) + set(_pic_cmd --enable-pic) + if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "x86_64") + set(_arch_cmd --arch=arm64) + set(_cc_cmd "--cc=clang -arch arm64") + else() + set(_arch_cmd --arch=x86_64) + set(_cc_cmd "--cc=clang -arch x86_64") + endif() + endif() + else () + set(_link_cmd --enable-shared) + endif () + + set(_build_j -j) + if(DEFINED ENV{CMAKE_BUILD_PARALLEL_LEVEL}) + set(_build_j "-j$ENV{CMAKE_BUILD_PARALLEL_LEVEL}") + endif() + + ExternalProject_Add(dep_FFMPEG + URL https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz + URL_HASH SHA256=DEEDCABE339165214A3637DF4C86A507AEF0D793CF8774FF68735F4737E8DDBC + DOWNLOAD_DIR ${DEP_DOWNLOAD_DIR}/FFMPEG + CONFIGURE_COMMAND ${_conf_cmd} + ${_cross_cmd} + ${_pic_cmd} + ${_arch_cmd} + ${_cc_cmd} + "--prefix=${DESTDIR}" + ${_link_cmd} + ${_minos_cmd} + --disable-doc + --enable-small + --disable-outdevs + --disable-filters + --enable-filter=*null*,afade,*fifo,*format,*resample,aeval,allrgb,allyuv,atempo,pan,*bars,color,*key,crop,draw*,eq*,framerate,*_qsv,*_vaapi,*v4l2*,hw*,scale,volume,test* + --disable-protocols + --enable-protocol=file,fd,pipe,rtp,udp + --disable-muxers + --enable-muxer=rtp + --disable-encoders + --disable-decoders + --enable-decoder=*aac*,h264*,mp3*,mjpeg,rv* + --disable-demuxers + --enable-demuxer=h264,mp3,mov + --disable-zlib + --disable-avdevice + BUILD_IN_SOURCE ON + BUILD_COMMAND make ${_build_j} + INSTALL_COMMAND make install + ) + +endif() diff --git a/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md b/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md new file mode 100644 index 0000000000..811626fc8b --- /dev/null +++ b/docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md @@ -0,0 +1,376 @@ +# 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 **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. + +--- + +### 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: `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 16–23 of `src/slic3r/GUI/wxMediaCtrl3.h`): + +```cpp +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: + +```cpp +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: + +```cpp +//wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); +``` + +becomes: + +```cpp +wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); +``` + +In `src/slic3r/GUI/wxMediaCtrl2.cpp:101`, delete: + +```cpp +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): + +```cmake + GUI/wxMediaCtrl2.mm + GUI/wxMediaCtrl2.h + GUI/wxMediaCtrl3.h + ) +``` + +becomes: + +```cmake + 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)`): + +```cmake +if (NOT APPLE) + pkg_check_modules(LIBAV REQUIRED IMPORTED_TARGET + libavcodec + libswscale + libavutil + ) + target_link_libraries(libslic3r_gui PkgConfig::LIBAV) +endif() +``` + +becomes: + +```cmake +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): + +```bash +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** + +```bash +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** + +```bash +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** + +```bash +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: + +```cmake + 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: + +```cmake + # 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: + +```cmake + 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: + +```cmake + "--prefix=${DESTDIR}" + --enable-shared +``` + +becomes: + +```cmake + "--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: + +```bash +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): + +```bash +rm -rf deps/build/arm64/dep_FFMPEG-prefix +cmake --build deps/build/arm64 --target dep_FFMPEG +``` + +- [ ] **Step 4: Verify the artifacts** + +```bash +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** + +```bash +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** + +```bash +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 22–60) + +**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** + +```bash +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** + +```bash +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** + +```bash +cmake --build build_arm64 --config RelWithDebInfo --target OrcaSlicer +``` + +Expected: configure + compile + link succeed with the deleted files gone. + +- [ ] **Step 5: Commit** + +```bash +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** + +```bash +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 1–2 (launch + idle image) and note in the PR that live-stream verification needs hardware. + +- [ ] **Step 5: Final review pass** + +```bash +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. diff --git a/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md b/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md new file mode 100644 index 0000000000..812a7bf735 --- /dev/null +++ b/docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md @@ -0,0 +1,110 @@ +# FFmpeg Media Player for macOS — Design + +Date: 2026-08-14 +Branch: `dev/ffmpeg-player-macos` + +## Problem + +The branch's new FFmpeg-based media player (`wxMediaCtrl3` + `AVVideoDecoder`) is used on +Windows and Linux, but macOS still runs the old player: `wxMediaCtrl2.mm`, an ObjC +`BambuPlayer` class dlsym'd from the Bambu network plugin that renders via CALayer. +On macOS, `wxMediaCtrl3` is currently aliased to `wxMediaCtrl2` and FFmpeg is not linked +into the app at all. + +Goal: make macOS use the same FFmpeg player as Windows/Linux, linking the **static** +FFmpeg libraries from the deps build instead of dynamic ones. + +## Current state (verified) + +- New player (Win/Linux): `GUI/wxMediaCtrl3.cpp` + `GUI/AVVideoDecoder.cpp`. Decodes with + FFmpeg (libavcodec/libswscale/libavutil), renders frames into `wxImage` (non-Windows) / + `wxBitmap` (Windows) drawn in a `paintEvent`, feeds via the `Bambu_*` C API + (`BambuTunnel.h`, `BAMBU_DYNAMIC`) dlsym'd from the network plugin through + `StaticBambuLib::get()` (`GUI/Printer/PrinterFileSystem.cpp`, compiled on all platforms). +- Old player (macOS): `GUI/wxMediaCtrl2.mm` uses the ObjC `BambuPlayer` class found via + `dlsym(module, "OBJC_CLASS_$_BambuPlayer")` in `libBambuSource.dylib`. +- The macOS network plugin `libBambuSource.dylib` already exports the full Bambu C API + (verified with `nm`), so the new player needs zero plugin changes. +- FFmpeg linking in `src/slic3r/CMakeLists.txt` is guarded by `if (NOT APPLE)` — + macOS currently does not link FFmpeg. +- `deps/FFMPEG/FFMPEG.cmake`: non-MSVC branch builds FFmpeg from source with + `--enable-shared`. The existing arm64 deps build on the dev machine happened to be + configured with both static and shared enabled, so `libavcodec.a` / `libswscale.a` / + `libavutil.a` are already present at + `deps/build/arm64/OrcaSlicer_dep/usr/local/lib/`. +- `EVT_MEDIA_CTRL_STAT` is `wxDEFINE_EVENT`'d in `wxMediaCtrl2.cpp` (Win/Linux) and + `wxMediaCtrl2.mm` (macOS); the define in `wxMediaCtrl3.cpp` is commented out. +- `wxMediaCtrl2` is never instantiated anywhere on any platform — dead code. +- `StatusPanel` already creates `wxMediaCtrl3`; `MediaPlayCtrl` only uses the + `wxMediaCtrl3` interface (`Load/Play/Stop/GetState/GetVideoSize/GetLastError/SetIdleImage`), + so no UI-side changes are needed. + +## Approach (approved) + +**Reuse the shared player on macOS.** Compile the existing `wxMediaCtrl3.cpp` + +`AVVideoDecoder.cpp` on macOS so all three platforms run one implementation. +Rendering uses the existing `wxImage` → `DrawBitmap` paint path, identical to Linux. +Known trade-off: frames are scaled to the widget's logical (1x) size, so Retina is +slightly soft compared to the old CALayer player. Accepted for now; a Retina-aware +scaling follow-up is possible later. + +Rejected alternative: a native CGImage/CALayer renderer for macOS — faster and +Retina-crisp, but adds a second render implementation to maintain. + +## Changes + +### 1. Enable the FFmpeg player on macOS (source) + +- `GUI/wxMediaCtrl3.h`: remove the `#ifdef __WXMAC__` branch (lines 18–22) that aliases + `wxMediaCtrl3` → `wxMediaCtrl2`. macOS then compiles the real `wxMediaCtrl3` class, + including the `BAMBU_DYNAMIC` BambuTunnel path used on Linux. +- Event symbol fix: move `wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent)` into + `wxMediaCtrl3.cpp` (uncomment the existing line) and remove it from + `wxMediaCtrl2.cpp`. One definition total in the lib; all three platforms resolve it. + +### 2. Static FFmpeg linking (deps + app) + +- `deps/FFMPEG/FFMPEG.cmake`: in the non-MSVC branch, pass + `--disable-shared --enable-static` when `APPLE`. Linux keeps `--enable-shared`; + Windows keeps its prebuilt shared DLL zips. Fresh macOS deps builds install only + `libavcodec.a` / `libswscale.a` / `libavutil.a` — no dylibs to bundle, no + rpath/install_name handling. (The existing local arm64 deps build already contains + the `.a` files, so no deps rebuild is strictly needed to try the change locally, + but a fresh CI deps build must produce them.) +- `src/slic3r/CMakeLists.txt`: + - APPLE branch of `SLIC3R_GUI_SOURCES`: add `GUI/wxMediaCtrl3.cpp`, + `GUI/wxMediaCtrl3.h`, `GUI/AVVideoDecoder.cpp`, `GUI/AVVideoDecoder.hpp`; + remove `GUI/wxMediaCtrl2.mm` and `GUI/wxMediaCtrl2.h` (the `.h` stays on + disk for the Win/Linux build of `wxMediaCtrl2.cpp`, but nothing on macOS + includes it after this change). + - Add an APPLE mirror of the `NOT APPLE` FFmpeg block: `find_library` for + `libavcodec.a`, `libswscale.a`, `libavutil.a` under `${CMAKE_PREFIX_PATH}/lib` + with `NO_DEFAULT_PATH`, link them (order avcodec → swscale → avutil), and add + `${CMAKE_PREFIX_PATH}/include` as a SYSTEM include directory. Deps are built with + `--disable-zlib` and no external codecs, so the three static libs link cleanly. + +### 3. Remove the old player + +- Delete `GUI/wxMediaCtrl2.mm` and `GUI/BambuPlayer/BambuPlayer.h` (header used only + by the `.mm`; the real `BambuPlayer` lives inside the network plugin). +- Remove the now-dead `__WXMAC__` section of `GUI/wxMediaCtrl2.h`. +- `wxMediaCtrl2.cpp` (Win/Linux) stays in the build as-is (dead but harmless; out of + scope to remove on this branch). + +### 4. Verification + +- Build on macOS: `cmake --build build_arm64` (or `build/arm64`). +- Confirm no dynamic FFmpeg dependency: `otool -L` on the app binary shows no `libav*` + dylib references. +- Runtime: with the network plugin loaded, the Device tab camera preview streams via + the FFmpeg player (check the device page / `MediaPlayCtrl`). +- macOS `ctest` still passes — static linking means no test-executable `.so` copying + hacks (unlike the Linux shared-lib setup). + +## Out of scope + +- Linux (shared libs, AppImage/flatpak bundling) and Windows (prebuilt DLL zips) + keep their current FFmpeg setup. +- Retina-aware frame scaling / native CGImage rendering (follow-up if visual quality + is judged insufficient). +- Audio streaming (neither player plays audio in this UI path). diff --git a/localization/i18n/list.txt b/localization/i18n/list.txt index 1614bc453b..5174679abf 100644 --- a/localization/i18n/list.txt +++ b/localization/i18n/list.txt @@ -193,7 +193,6 @@ src/slic3r/GUI/ObjColorDialog.cpp src/slic3r/GUI/SyncAmsInfoDialog.cpp src/slic3r/GUI/WipeTowerDialog.cpp src/slic3r/GUI/wxExtensions.cpp -src/slic3r/GUI/wxMediaCtrl2.cpp src/slic3r/GUI/WebUserLoginDialog.cpp src/slic3r/GUI/WebGuideDialog.cpp src/slic3r/GUI/KBShortcutsDialog.hpp diff --git a/resources/images/live_stream_default.png b/resources/images/live_stream_default.png index 326ceaf994..c1aa90bf17 100644 Binary files a/resources/images/live_stream_default.png and b/resources/images/live_stream_default.png differ diff --git a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml index d081e2f997..a3efaced8c 100644 --- a/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml +++ b/scripts/flatpak/com.orcaslicer.OrcaSlicer.yml @@ -318,6 +318,12 @@ modules: sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7 dest: external-packages/wxInspector + # FFmpeg n7.0.3 + - type: file + url: https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz + sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc + dest: external-packages/FFMPEG + # --------------------------------------------------------------- # Fallback archives for deps normally provided by the GNOME SDK. # These are only used if find_package() fails to locate them. diff --git a/scripts/linux.d/arch b/scripts/linux.d/arch index ead963e9a6..dc554b10b5 100644 --- a/scripts/linux.d/arch +++ b/scripts/linux.d/arch @@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=( wayland-protocols webkit2gtk-4.1 wget + pkgconf + yasm + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/cachyos b/scripts/linux.d/cachyos index d491747ace..6137a3ef5c 100644 --- a/scripts/linux.d/cachyos +++ b/scripts/linux.d/cachyos @@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=( wayland-protocols webkit2gtk wget + pkgconf + yasm + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/clear-linux-os b/scripts/linux.d/clear-linux-os index 149e805549..41ca68e835 100644 --- a/scripts/linux.d/clear-linux-os +++ b/scripts/linux.d/clear-linux-os @@ -20,6 +20,7 @@ export REQUIRED_BUNDLES=( perl-basic texinfo wget + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/debian b/scripts/linux.d/debian index f4d500760e..c33b24d2b7 100644 --- a/scripts/linux.d/debian +++ b/scripts/linux.d/debian @@ -27,6 +27,9 @@ REQUIRED_DEV_PACKAGES=( ninja-build texinfo wget + pkgconf + yasm + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/fedora b/scripts/linux.d/fedora index ca4eceaee3..993ea0b10a 100644 --- a/scripts/linux.d/fedora +++ b/scripts/linux.d/fedora @@ -31,6 +31,9 @@ REQUIRED_DEV_PACKAGES=( webkit2gtk4.1-devel wget libcurl-devel + pkgconf + yasm + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/gentoo b/scripts/linux.d/gentoo index f172ac92c3..f8554542ff 100644 --- a/scripts/linux.d/gentoo +++ b/scripts/linux.d/gentoo @@ -32,6 +32,9 @@ REQUIRED_DEV_PACKAGES=( sys-devel/m4 virtual/libudev x11-libs/gtk+:3 + dev-util/pkgconf + dev-lang/yasm + dev-lang/nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/scripts/linux.d/suse b/scripts/linux.d/suse index 1f4db45f46..682960650b 100644 --- a/scripts/linux.d/suse +++ b/scripts/linux.d/suse @@ -30,6 +30,9 @@ REQUIRED_DEV_PACKAGES=( webkit2gtk4-devel wget libcurl-devel + pkgconf + yasm + nasm ) if [[ -n "$UPDATE_LIB" ]] diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0082b29831..0036af0a8c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -75,7 +75,7 @@ if (SLIC3R_GUI) list(FILTER wxWidgets_LIBRARIES EXCLUDE REGEX expat) list(APPEND wxWidgets_LIBRARIES ${EXPAT_LIBRARIES}) endif () - + # This is an issue in the new wxWidgets cmake build, doesn't deal with librt find_library(LIBRT rt) if(LIBRT) @@ -294,6 +294,16 @@ if (WIN32) endif() else () + if (NOT APPLE) + set(output_sos_Release "") + set(output_sos_Debug "") + add_custom_target(OrcaSlicerSosCopy ALL DEPENDS OrcaSlicer) + if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + orcaslicer_copy_sos(OrcaSlicerSosCopy "Debug" "d" output_sos_Debug) + else() + orcaslicer_copy_sos(OrcaSlicerSosCopy "Release" "" output_sos_Release) + endif() + endif() if (APPLE AND NOT CMAKE_MACOSX_BUNDLE) # On OSX, the name of the binary matches the name of the Application. add_custom_command(TARGET OrcaSlicer POST_BUILD @@ -372,5 +382,9 @@ if (WIN32) install(FILES ${output_dlls_${build_type}} DESTINATION ".") install(DIRECTORY "${CMAKE_PREFIX_PATH}/libpython/" DESTINATION "python") else () + if (APPLE) + else() + install(FILES ${output_sos_${build_type}} DESTINATION "${CMAKE_INSTALL_PREFIX}") + endif() install(TARGETS OrcaSlicer RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" BUNDLE DESTINATION ${CMAKE_INSTALL_BINDIR}) endif () diff --git a/src/dev-utils/platform/unix/build_linux_image.sh.in b/src/dev-utils/platform/unix/build_linux_image.sh.in index 85134b764d..873cf2e1b1 100755 --- a/src/dev-utils/platform/unix/build_linux_image.sh.in +++ b/src/dev-utils/platform/unix/build_linux_image.sh.in @@ -83,6 +83,10 @@ copy_shared_object_to_dir() { src_real="$(readlink -f "$src")" dst_name="$(basename "$src_real")" mkdir -p "$dst_dir" + if [ "$src_real" = "$dst_dir/$dst_name" ]; then + # Already bundled; the dependency resolved from the bundle directory. + return 0 + fi cp -fL "$src_real" "$dst_dir/$dst_name" if [ -L "$src" ]; then @@ -96,12 +100,23 @@ copy_shared_object_to_dir() { } bundle_dependency_closure() { - local dst_dir="$1" + local dst_dir + dst_dir="$(cd -- "$1" && pwd)" shift local -a queue=("$@") - local target dep dep_real copied_path + local target dep dep_real dep_key copied_path declare -A seen=() + # Dependencies are resolved with ldd, which only searches the default + # loader path. Deps-built shared libraries (e.g. the FFmpeg stack) are not + # installed there and carry no RUNPATH of their own, so once copied into + # the bundle ldd can no longer resolve one sibling from another + # (libavcodec -> libavutil) and reports it as missing. Extend the loader + # path with the bundle directory plus the source directories of files + # already bundled, so every library that was resolved once keeps resolving + # for its own dependencies. The audit script does the same + # (scripts/check_appimage_libs.sh). + local -a search_dirs=("$dst_dir") while [ ${#queue[@]} -gt 0 ]; do target="${queue[0]}" @@ -122,17 +137,24 @@ bundle_dependency_closure() { continue fi - if [ -n "${seen[$dep_real]}" ]; then + # Key dedup on the bundled file rather than the source path: once + # ldd resolves a library from the bundle directory (via the + # LD_LIBRARY_PATH above) its path is a dst_dir path, which differs + # from the source path the first resolution returned. Keying on + # the source path would re-copy the file onto itself. + dep_key="$dst_dir/$(basename "$dep_real")" + if [ -n "${seen[$dep_key]}" ]; then continue fi - seen[$dep_real]=1 + seen[$dep_key]=1 copy_shared_object_to_dir "$dep" "$dst_dir" + search_dirs+=("$(dirname "$dep_real")") copied_path="$dst_dir/$(basename "$dep_real")" if [ -e "$copied_path" ]; then queue+=("$copied_path") fi - done < <(appimage_list_direct_dependencies "$target") + done < <(LD_LIBRARY_PATH="$(IFS=:; printf '%s' "${search_dirs[*]}")${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" appimage_list_direct_dependencies "$target") done } diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index b014b44bb2..e11b5153ae 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -38,6 +38,8 @@ set(SLIC3R_GUI_SOURCES GUI/AuxiliaryDataViewModel.hpp GUI/AuxiliaryDialog.cpp GUI/AuxiliaryDialog.hpp + GUI/AVVideoDecoder.cpp + GUI/AVVideoDecoder.hpp GUI/Auxiliary.hpp GUI/BackgroundSlicingProcess.cpp GUI/BackgroundSlicingProcess.hpp @@ -611,6 +613,8 @@ set(SLIC3R_GUI_SOURCES GUI/WipeTowerDialog.cpp GUI/wxExtensions.cpp GUI/wxExtensions.hpp + GUI/wxMediaCtrl3.cpp + GUI/wxMediaCtrl3.h plugin/PythonInterpreter.cpp plugin/PythonInterpreter.hpp plugin/PythonPluginBridge.cpp @@ -799,21 +803,8 @@ if (APPLE) GUI/DeepLinkHandlerMac.mm GUI/DeepLinkHandlerMac.h GUI/GUI_UtilsMac.mm - GUI/wxMediaCtrl2.mm - GUI/wxMediaCtrl2.h ) FIND_LIBRARY(DISKARBITRATION_LIBRARY DiskArbitration) -else () - list(APPEND SLIC3R_GUI_SOURCES - GUI/wxMediaCtrl2.cpp - GUI/wxMediaCtrl2.h - ) -endif () - -if (UNIX AND NOT APPLE) - list(APPEND SLIC3R_GUI_SOURCES - GUI/Printer/gstbambusrc.c - ) endif () set(ORCA_UPDATER_SIG_KEY_B64 "${ORCA_UPDATER_SIG_KEY}") @@ -913,6 +904,26 @@ if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) endif () +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) + if (NOT LIBAVCODEC_LIBRARY OR NOT LIBSWSCALE_LIBRARY OR NOT LIBAVUTIL_LIBRARY) + message(FATAL_ERROR "Static FFmpeg (libavcodec.a/libswscale.a/libavutil.a) not found under ${CMAKE_PREFIX_PATH}/lib. Rebuild the deps — FFMPEG builds static-only on macOS.") + endif () + 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() + # We need to implement some hacks for wxWidgets and touch the underlying GTK # layer and sub-libraries. This forces us to use the include locations and # link these libraries. @@ -939,11 +950,6 @@ if (UNIX AND NOT APPLE) target_compile_definitions(libslic3r_gui PRIVATE wxHAVE_GDK_WAYLAND) endif () - # We add GStreamer for bambu:/// support. - pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0) - pkg_check_modules(GST_BASE REQUIRED gstreamer-base-1.0) - target_link_libraries(libslic3r_gui ${GSTREAMER_LIBRARIES} ${GST_BASE_LIBRARIES}) - target_include_directories(libslic3r_gui SYSTEM PRIVATE ${GSTREAMER_INCLUDE_DIRS} ${GST_BASE_INCLUDE_DIRS}) endif () # Add a definition so that we can tell we are compiling slic3r. diff --git a/src/slic3r/GUI/AVVideoDecoder.cpp b/src/slic3r/GUI/AVVideoDecoder.cpp new file mode 100644 index 0000000000..d7b8432bd3 --- /dev/null +++ b/src/slic3r/GUI/AVVideoDecoder.cpp @@ -0,0 +1,170 @@ +#include "AVVideoDecoder.hpp" + +#include + +extern "C" +{ + #include + #include +} + +AVVideoDecoder::AVVideoDecoder() +{ + codec_ctx_ = avcodec_alloc_context3(nullptr); +} + +AVVideoDecoder::~AVVideoDecoder() +{ + if (sws_ctx_) + sws_freeContext(sws_ctx_); + if (frame_) + av_frame_free(&frame_); + if (codec_ctx_) + avcodec_free_context(&codec_ctx_); +} + +int AVVideoDecoder::open(Bambu_StreamInfo const &info) +{ + auto codec_id = info.sub_type == AVC1 ? AV_CODEC_ID_H264 : AV_CODEC_ID_MJPEG; + auto codec = avcodec_find_decoder(codec_id); + if (codec == nullptr) { + fprintf(stderr, "AVVideoDecoder: unsupported codec!\n"); + return -1; // Codec not found + } + /* open the coderc */ + if (avcodec_open2(codec_ctx_, codec, nullptr) < 0) { + fprintf(stderr, "AVVideoDecoder: could not open codec\n"); + return -1; + } + + // Allocate an AVFrame structure + frame_ = av_frame_alloc(); + if (frame_ == nullptr) + return -1; + + return 0; +} + +int AVVideoDecoder::decode(const Bambu_Sample &sample) +{ + int ret = -1; + AVPacket *pkt = av_packet_alloc(); + if (!pkt) { + return ret; + } + + ret = av_new_packet(pkt, sample.size); + if (ret != 0) { + av_packet_free(&pkt); + return ret; + } + + memcpy(pkt->data, sample.buffer, size_t(sample.size)); + + ret = avcodec_send_packet(codec_ctx_, pkt); + if (ret == 0) { + got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0; + } + + av_packet_unref(pkt); + av_packet_free(&pkt); + return ret; +} + +int AVVideoDecoder::flush() +{ + int ret = avcodec_send_packet(codec_ctx_, nullptr); + got_frame_ = avcodec_receive_frame(codec_ctx_, frame_) == 0; + return ret; +} + +void AVVideoDecoder::close() +{ +} + +bool AVVideoDecoder::toWxImage(wxImage &image, wxSize const &size2) +{ + if (!got_frame_) + return false; + + auto size1 = size2; + if (!size1.IsFullySpecified()) + size1 = {frame_->width, frame_->height }; + auto size = size1; + if (size.GetWidth() & 0x0f) { + size.SetWidth((size.GetWidth() & ~0x0f) + 0x10); + if (size.GetWidth() != width_) { + std::fill(bits_.begin(), bits_.end(), 0); + width_ = size.GetWidth(); + } + } + AVPixelFormat wxFmt = AV_PIX_FMT_RGB24; + sws_ctx_ = sws_getCachedContext(sws_ctx_, + frame_->width, frame_->height, AVPixelFormat(frame_->format), + size1.GetWidth(), size1.GetHeight(), wxFmt, + SWS_GAUSS, + nullptr, nullptr, nullptr); + if (sws_ctx_ == nullptr) + return false; + int length = size.GetWidth() * size.GetHeight() * 3; + if (bits_.size() < length) + bits_.resize(length); + uint8_t * datas[] = { bits_.data() }; + int strides[] = { size.GetWidth() * 3 }; + int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides); + if (result_h != size.GetHeight()) { + return false; + } + // Copy: the frame outlives this decoder and is painted by the GUI thread while the + // next sws_scale is already overwriting bits_, so it must own its pixels. The Windows + // path below needs no equivalent, wxBitmap copies the bits into GDI. + image = wxImage(size.GetWidth(), size.GetHeight(), bits_.data(), true).Copy(); + if (!image.IsOk()) { + fprintf(stderr, "AVVideoDecoder: image not ok %dx%d\n", size.GetWidth(), size.GetHeight()); + return false; + } + return true; +} + +bool AVVideoDecoder::toWxBitmap(wxBitmap &bitmap, wxSize const &size2) +{ + if (!got_frame_) + return false; + + auto size1 = size2; + if (!size1.IsFullySpecified()) + size1 = {frame_->width, frame_->height }; + auto size = size1; + if (size.GetWidth() & 0x0f) { + size.SetWidth((size.GetWidth() & ~0x0f) + 0x10); + if (size.GetWidth() != width_) { + std::fill(bits_.begin(), bits_.end(), 0); + width_ = size.GetWidth(); + } + } + AVPixelFormat wxFmt = AV_PIX_FMT_RGB32; + sws_ctx_ = sws_getCachedContext(sws_ctx_, + frame_->width, frame_->height, AVPixelFormat(frame_->format), + size1.GetWidth(), size1.GetHeight(), wxFmt, + SWS_GAUSS, + nullptr, nullptr, nullptr); + if (sws_ctx_ == nullptr) + return false; + int length = size.GetWidth() * size.GetHeight() * 4; + if (bits_.size() < length) + bits_.resize(length); + uint8_t *datas[] = { bits_.data() }; + int strides[] = { size.GetWidth() * 4 }; + int result_h = sws_scale(sws_ctx_, frame_->data, frame_->linesize, 0, frame_->height, datas, strides); + if (result_h != size.GetHeight()) { + fprintf(stderr, "AVVideoDecoder: result_h %d %d\n", result_h, size.GetHeight()); + return false; + } + bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32); + assert(bitmap.IsOk()); + if (!bitmap.IsOk()) { + fprintf(stderr, "AVVideoDecoder: bitmap not ok %dx%d\n", size.GetWidth(), size.GetHeight()); + return false; + } + return true; +} diff --git a/src/slic3r/GUI/AVVideoDecoder.hpp b/src/slic3r/GUI/AVVideoDecoder.hpp new file mode 100644 index 0000000000..4111e860a2 --- /dev/null +++ b/src/slic3r/GUI/AVVideoDecoder.hpp @@ -0,0 +1,46 @@ +#ifndef AVVIDEODECODER_HPP +#define AVVIDEODECODER_HPP + +#include "Printer/BambuTunnel.h" + +extern "C" { + #include + #include +} +#include +#include +#include +#include + +class wxBitmap; + +class AVVideoDecoder +{ +public: + AVVideoDecoder(); + + ~AVVideoDecoder(); + +public: + int open(Bambu_StreamInfo const &info); + + int decode(Bambu_Sample const &sample); + + int flush(); + + void close(); + + bool toWxImage(wxImage &image, wxSize const &size); + + bool toWxBitmap(wxBitmap &bitmap, wxSize const & size); + +private: + AVCodecContext *codec_ctx_ = nullptr; + AVFrame * frame_ = nullptr; + SwsContext * sws_ctx_ = nullptr; + bool got_frame_ = false; + int width_ { 0 }; // scale result width + std::vector bits_; +}; + +#endif // AVVIDEODECODER_HPP diff --git a/src/slic3r/GUI/BambuPlayer/BambuPlayer.h b/src/slic3r/GUI/BambuPlayer/BambuPlayer.h deleted file mode 100644 index fe5f0d049a..0000000000 --- a/src/slic3r/GUI/BambuPlayer/BambuPlayer.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// BambuPlayer.h -// BambuPlayer -// -// Created by cmguo on 2021/12/6. -// - -#import -#import -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface BambuPlayer : NSObject - -+ (void) initialize; - -- (instancetype) initWithDisplayLayer: (AVSampleBufferDisplayLayer*) layer; -- (instancetype) initWithImageView: (NSView*) view; -- (int) open: (char const *) url; -- (NSSize) videoSize; -- (int) play; -- (void) stop; -- (void) close; - -- (void) setLogger: (void (*)(void const * context, int level, char const * msg)) logger withContext: (void const *) context; - -@end - -NS_ASSUME_NONNULL_END diff --git a/src/slic3r/GUI/MediaPlayCtrl.cpp b/src/slic3r/GUI/MediaPlayCtrl.cpp index 90e956be69..71fbcb054b 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.cpp +++ b/src/slic3r/GUI/MediaPlayCtrl.cpp @@ -39,13 +39,14 @@ static std::map error_messages = { namespace Slic3r { namespace GUI { -MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size) +MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos, const wxSize &size) : wxPanel(parent, wxID_ANY, pos, size) , m_media_ctrl(media_ctrl) { SetLabel("MediaPlayCtrl"); SetBackgroundColour(*wxWHITE); m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this); + m_media_ctrl->SetIdleImage(from_u8(resources_dir() + "/images/live_stream_default.png")); m_button_play = new Button(this, "", "media_play", wxBORDER_NONE); m_button_play->SetCanFocus(false); @@ -177,13 +178,6 @@ void MediaPlayCtrl::SetMachineObject(MachineObject* obj) if (machine == m_machine) { if (m_last_state == MEDIASTATE_IDLE && IsEnabled()) Play(); - else if (m_last_state == MEDIASTATE_LOADING && m_tutk_state == "disable" - && m_last_user_play + wxTimeSpan::Seconds(3) < wxDateTime::Now()) { - // resend ttcode to printer - if (auto agent = wxGetApp().getAgent()) - agent->get_camera_url(machine, [](auto) {}, wxGetApp().get_printer_cloud_provider()); - m_last_user_play = wxDateTime::Now(); - } return; } m_machine = machine; @@ -313,7 +307,7 @@ void MediaPlayCtrl::Play() // !m_lan_mode && !m_remote_proto && m_lan_proto == LVL_None (x) if (m_lan_proto <= MachineObject::LVL_Disable && (m_lan_mode || !m_remote_proto)) { - Stop(m_lan_proto == MachineObject::LVL_None + Stop(m_lan_proto == MachineObject::LVL_None ? _L("A problem occurred. Please update the printer firmware and try again.") : _L("LAN Only Liveview is off. Please turn on the liveview on printer screen.")); return; @@ -351,7 +345,7 @@ void MediaPlayCtrl::Play() url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) { @@ -426,7 +420,7 @@ void MediaPlayCtrl::Stop(wxString const &msg, wxString const &msg2) auto tunnel = m_url.empty() ? "" : into_u8(wxURI(m_url).GetPath()).substr(1); if (auto n = tunnel.find_first_of("/_"); n != std::string::npos) tunnel = tunnel.substr(0, n); - if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0 + if (last_state != wxMEDIASTATE_PLAYING && m_failed_code != 0 && m_last_failed_codes.find(m_failed_code) == m_last_failed_codes.end() && (m_user_triggered || m_failed_retry > 3)) { m_last_failed_codes.insert(m_failed_code); @@ -560,7 +554,7 @@ void MediaPlayCtrl::ToggleStream() url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid"); url += "&cli_ver=" + std::string(SLIC3R_VERSION); } - BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::ToggleStream: " << hide_passwd(url, {"?uid=", "authkey=", "passwd=", "license=", "token="}); CallAfter([this, m, url] { if (m != m_machine) return; @@ -580,8 +574,8 @@ void MediaPlayCtrl::ToggleStream() }, wxGetApp().get_printer_cloud_provider()); } -void MediaPlayCtrl::msw_rescale() { - m_button_play->Rescale(); +void MediaPlayCtrl::msw_rescale() { + m_button_play->Rescale(); } void MediaPlayCtrl::jump_to_play() @@ -715,7 +709,9 @@ void MediaPlayCtrl::media_proc() break; } else if (url == "") { + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start play"; m_media_ctrl->Play(); + BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end play"; } else { BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load"; @@ -771,15 +767,15 @@ bool MediaPlayCtrl::start_stream_service(bool *need_install) if (!boost::filesystem::exists(file_dll) || boost::filesystem::last_write_time(file_dll) != boost::filesystem::last_write_time(file_dll2)) boost::filesystem::copy_file(file_dll2, file_dll, boost::filesystem::copy_options::overwrite_existing); } - boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir), - boost::process::windows::create_no_window, + boost::process::child process_source(file_source, file_url2.ToStdWstring(), boost::process::start_dir(tools_dir), + boost::process::windows::create_no_window, boost::process::std_out > intermediate, boost::process::limit_handles); - boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window, + boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::windows::create_no_window, boost::process::std_in < intermediate, boost::process::limit_handles); #else boost::filesystem::permissions(file_source, boost::filesystem::owner_exe | boost::filesystem::add_perms); boost::filesystem::permissions(file_ffmpeg, boost::filesystem::owner_exe | boost::filesystem::add_perms); - boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir), + boost::process::child process_source(file_source, file_url2.data().AsInternal(), boost::process::start_dir(start_dir), boost::process::std_out > intermediate, boost::process::limit_handles); boost::process::child process_ffmpeg(file_ffmpeg, configss, boost::process::std_in < intermediate, boost::process::limit_handles); #endif @@ -830,27 +826,16 @@ bool MediaPlayCtrl::get_stream_url(std::string *url) }} -void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags) +void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height) { -#ifdef __WXMAC__ - wxWindow::DoSetSize(x, y, width, height, sizeFlags); -#else - wxMediaCtrl::DoSetSize(x, y, width, height, sizeFlags); -#endif -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_gtk_video_window) { - const wxSize client_size = GetClientSize(); - m_gtk_video_window->SetSize(0, 0, client_size.GetWidth(), client_size.GetHeight()); - } -#endif - if (sizeFlags & wxSIZE_USE_EXISTING) return; - wxSize size = m_video_size; + wxSize size = videoSize; + if (!size.IsFullySpecified()) size = {16, 9}; int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth(); - if (maxHeight != GetMaxHeight()) { - // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight; - SetMaxSize({-1, maxHeight}); - CallAfter([this] { - if (auto p = GetParent()) { + if (maxHeight != ctrl->GetMaxHeight()) { + // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl_OnSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight; + ctrl->SetMaxSize({-1, maxHeight}); + ctrl->CallAfter([ctrl] { + if (auto p = ctrl->GetParent()) { p->Layout(); p->Refresh(); } diff --git a/src/slic3r/GUI/MediaPlayCtrl.h b/src/slic3r/GUI/MediaPlayCtrl.h index f5e5dcddfc..0a01daefc9 100644 --- a/src/slic3r/GUI/MediaPlayCtrl.h +++ b/src/slic3r/GUI/MediaPlayCtrl.h @@ -8,7 +8,7 @@ #ifndef MediaPlayCtrl_h #define MediaPlayCtrl_h -#include "wxMediaCtrl2.h" +#include "wxMediaCtrl3.h" #include @@ -30,7 +30,7 @@ namespace GUI { class MediaPlayCtrl : public wxPanel { public: - MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); + MediaPlayCtrl(wxWindow *parent, wxMediaCtrl3 *media_ctrl, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize); ~MediaPlayCtrl(); @@ -75,7 +75,7 @@ private: // token std::shared_ptr m_token = std::make_shared(0); - wxMediaCtrl2 * m_media_ctrl; + wxMediaCtrl3 * m_media_ctrl; wxMediaState m_last_state = MEDIASTATE_IDLE; std::string m_machine; int m_lan_proto = 0; @@ -90,7 +90,7 @@ private: bool m_device_busy = false; bool m_disable_lan = false; wxString m_url; - + std::deque m_tasks; boost::mutex m_mutex; boost::condition_variable m_cond; diff --git a/src/slic3r/GUI/MonitorBasePanel.h b/src/slic3r/GUI/MonitorBasePanel.h index c5c76acec3..42424a7187 100644 --- a/src/slic3r/GUI/MonitorBasePanel.h +++ b/src/slic3r/GUI/MonitorBasePanel.h @@ -34,7 +34,6 @@ #include "Widgets/AxisCtrlButton.hpp" #include "Widgets/TextInput.hpp" #include "Widgets/StaticLine.hpp" -#include "wxMediaCtrl2.h" #include "MediaPlayCtrl.h" /////////////////////////////////////////////////////////////////////////// diff --git a/src/slic3r/GUI/Printer/gstbambusrc.c b/src/slic3r/GUI/Printer/gstbambusrc.c deleted file mode 100644 index d3f4ab112a..0000000000 --- a/src/slic3r/GUI/Printer/gstbambusrc.c +++ /dev/null @@ -1,657 +0,0 @@ -/* bambusrc for gstreamer - * integration with proprietary Bambu Lab blob for getting raw h.264 video - * - * Copyright (C) 2023 Joshua Wise - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - * Alternatively, the contents of this file may be used under the - * GNU Lesser General Public License Version 2.1 (the "LGPL"), in - * which case the following provisions apply instead of the ones - * mentioned above: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include - -#include "gstbambusrc.h" - -#include -#include -#ifndef EXTERNAL_GST_PLUGIN -#define BAMBU_DYNAMIC -#endif -#include "BambuTunnel.h" - -#ifdef BAMBU_DYNAMIC -// From PrinterFileSystem. -#ifdef __cplusplus -extern "C" -#else -extern -#endif -BambuLib *bambulib_get(); -BambuLib *_lib = NULL; -#define BAMBULIB(x) (_lib->x) - -#else -#define BAMBULIB(x) (x) -#endif - -GST_DEBUG_CATEGORY_STATIC (gst_bambusrc_debug); -#define GST_CAT_DEFAULT gst_bambusrc_debug - -static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", - GST_PAD_SRC, - GST_PAD_ALWAYS, - GST_STATIC_CAPS_ANY); - //GST_STATIC_CAPS("video/x-h264,framerate=0/1,parsed=(boolean)false,stream-format=(string)byte-stream")); - -enum -{ - PROP_0, - PROP_LOCATION, -}; - -static void gst_bambusrc_uri_handler_init (gpointer g_iface, - gpointer iface_data); -static void gst_bambusrc_finalize (GObject * gobject); -static void gst_bambusrc_dispose (GObject * gobject); - -static void gst_bambusrc_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec); -static void gst_bambusrc_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec); - -static GstStateChangeReturn gst_bambusrc_change_state (GstElement * - element, GstStateChange transition); -static GstFlowReturn gst_bambusrc_create (GstPushSrc * psrc, - GstBuffer ** outbuf); -static gboolean gst_bambusrc_start (GstBaseSrc * bsrc); -static gboolean gst_bambusrc_stop (GstBaseSrc * bsrc); -static gboolean gst_bambusrc_is_seekable (GstBaseSrc * bsrc); -static gboolean gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query); -static gboolean gst_bambusrc_unlock (GstBaseSrc * bsrc); -static gboolean gst_bambusrc_unlock_stop (GstBaseSrc * bsrc); -static gboolean gst_bambusrc_set_location (GstBambuSrc * src, - const gchar * uri, GError ** error); - -#define gst_bambusrc_parent_class parent_class -G_DEFINE_TYPE_WITH_CODE (GstBambuSrc, gst_bambusrc, GST_TYPE_PUSH_SRC, - G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER, - gst_bambusrc_uri_handler_init)); - -static void -gst_bambusrc_class_init (GstBambuSrcClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSrcClass *gstbasesrc_class; - GstPushSrcClass *gstpushsrc_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - gstbasesrc_class = (GstBaseSrcClass *) klass; - gstpushsrc_class = (GstPushSrcClass *) klass; - - gobject_class->set_property = gst_bambusrc_set_property; - gobject_class->get_property = gst_bambusrc_get_property; - gobject_class->finalize = gst_bambusrc_finalize; - gobject_class->dispose = gst_bambusrc_dispose; - - g_object_class_install_property (gobject_class, - PROP_LOCATION, - g_param_spec_string ("location", "Location", - "URI to pass to Bambu Lab blobs", "", - (GParamFlags)(G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); - - gst_element_class_add_static_pad_template (gstelement_class, &srctemplate); - - gst_element_class_set_static_metadata (gstelement_class, "Bambu Lab source", - "Source/Network", - "Receive data as a client over the network using the proprietary Bambu Lab blobs", - "Joshua Wise "); - gstelement_class->change_state = - GST_DEBUG_FUNCPTR (gst_bambusrc_change_state); - - gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_bambusrc_start); - gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_bambusrc_stop); - gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_bambusrc_unlock); - gstbasesrc_class->unlock_stop = - GST_DEBUG_FUNCPTR (gst_bambusrc_unlock_stop); - gstbasesrc_class->is_seekable = - GST_DEBUG_FUNCPTR (gst_bambusrc_is_seekable); - gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_bambusrc_query); - - gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_bambusrc_create); - - GST_DEBUG_CATEGORY_INIT (gst_bambusrc_debug, "bambusrc", 0, - "Bambu Lab src"); -} - -static void -gst_bambusrc_reset (GstBambuSrc * src) -{ - gst_caps_replace (&src->src_caps, NULL); - if (src->tnl) { - BAMBULIB(Bambu_Close)(src->tnl); - BAMBULIB(Bambu_Destroy)(src->tnl); - src->tnl = NULL; - } -} - -static void -gst_bambusrc_init (GstBambuSrc * src) -{ - src->location = NULL; - src->tnl = NULL; - - gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE); - gst_base_src_set_live(GST_BASE_SRC(src), TRUE); - - gst_bambusrc_reset (src); -} - -static void -gst_bambusrc_dispose (GObject * gobject) -{ - GstBambuSrc *src = GST_BAMBUSRC (gobject); - - GST_DEBUG_OBJECT (src, "dispose"); - - G_OBJECT_CLASS (parent_class)->dispose (gobject); -} - -static void -gst_bambusrc_finalize (GObject * gobject) -{ - GstBambuSrc *src = GST_BAMBUSRC (gobject); - - GST_DEBUG_OBJECT (src, "finalize"); - - g_free (src->location); - if (src->tnl) { - BAMBULIB(Bambu_Close)(src->tnl); - BAMBULIB(Bambu_Destroy)(src->tnl); - } - - G_OBJECT_CLASS (parent_class)->finalize (gobject); -} - -static void -gst_bambusrc_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - GstBambuSrc *src = GST_BAMBUSRC (object); - - switch (prop_id) { - case PROP_LOCATION: - { - const gchar *location; - - location = g_value_get_string (value); - - if (location == NULL) { - GST_WARNING ("location property cannot be NULL"); - goto done; - } - if (!gst_bambusrc_set_location (src, location, NULL)) { - GST_WARNING ("badly formatted location"); - goto done; - } - break; - } - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -done: - return; -} - -static void -gst_bambusrc_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec) -{ - GstBambuSrc *src = GST_BAMBUSRC (object); - - switch (prop_id) { - case PROP_LOCATION: - g_value_set_string (value, src->location); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -int gst_bambu_last_error = 0; - -static GstFlowReturn -gst_bambusrc_create (GstPushSrc * psrc, GstBuffer ** outbuf) -{ - GstBambuSrc *src; - - src = GST_BAMBUSRC (psrc); - - (void) src; - GST_DEBUG_OBJECT (src, "create()"); - - int rv; - Bambu_Sample sample; - - if (!src->tnl) { - return GST_FLOW_ERROR; - } - - while ((rv = BAMBULIB(Bambu_ReadSample)(src->tnl, &sample)) == Bambu_would_block) { - GST_DEBUG_OBJECT(src, "create would block"); - usleep(33333); /* 30Hz */ - } - - if (rv == Bambu_stream_end) { - return GST_FLOW_EOS; - } - - if (rv != Bambu_success) { - gst_bambu_last_error = rv; - return GST_FLOW_ERROR; - } - -#if GLIB_CHECK_VERSION(2,68,0) - gpointer sbuf = g_memdup2(sample.buffer, sample.size); -#else - gpointer sbuf = g_memdup(sample.buffer, sample.size); -#endif - *outbuf = gst_buffer_new_wrapped_full(0, sbuf, sample.size, 0, sample.size, sbuf, g_free); - - /* Synthesize monotonic timestamps at the announced frame rate, anchored - * to the first frame's arrival time. The X1C's RTSPS server emits - * unreliable decode timestamps (wildly non-monotonic jumps, or sometimes - * none at all); forwarding them directly froze the pipeline after a few - * seconds. Pacing on a synthesized clock — the same trick mpv uses when - * it reports "No video PTS! Making something up." — gives smooth - * playback regardless of network jitter, and only drops late frames if - * the printer can't keep up. A snap-back resets the anchor if real - * arrival drifts more than two frame periods from the synthesized - * timeline (e.g. announced framerate was wrong). - */ - GstClock *clock = GST_ELEMENT_CLOCK(psrc); - GstClockTime base_time = gst_element_get_base_time((GstElement *)psrc); - GstClockTime running_now = GST_CLOCK_TIME_NONE; - if (clock) { - GstClockTime now = gst_clock_get_time(clock); - if (now != GST_CLOCK_TIME_NONE && now >= base_time) - running_now = now - base_time; - } - - /* Adapt the period to actual inter-arrival time via EWMA. The announced - * frame_rate is unreliable on Bambu printers (X1C announces 30 but - * delivers ~28), so trusting it causes the synthesized timeline to drift - * relative to real time, which makes the sink consider frames late and - * skip pacing entirely. Measuring the real rate keeps PTS in step with - * arrival on average, so the sink can pace inside bursts while still - * tracking the printer's actual frame cadence. - */ - if (src->avg_period == 0) { - int fps = src->frame_rate > 0 ? src->frame_rate : 30; - src->avg_period = GST_SECOND / fps; - } - if (running_now != GST_CLOCK_TIME_NONE && src->last_arrival != 0) { - GstClockTimeDiff delta = GST_CLOCK_DIFF(src->last_arrival, running_now); - /* clamp to plausible video frame periods (5..200 ms) so a one-off - * burst-of-zero or long stall doesn't poison the average */ - if (delta > 5 * GST_MSECOND && delta < 200 * GST_MSECOND) { - src->avg_period = (src->avg_period * 15 + (GstClockTime)delta) / 16; - } - } - src->last_arrival = (running_now != GST_CLOCK_TIME_NONE) ? running_now : src->last_arrival; - GstClockTime period = src->avg_period; - - /* Lead time: schedule frames a few periods in the future of their - * arrival, so the sink has a small jitter buffer. Without this, frames - * arriving slightly later than expected land behind the running clock - * and the sink renders them immediately, producing visible stutter. - * 100ms is invisible for a live print-monitor view. - */ - const GstClockTime LEAD = 100 * GST_MSECOND; - - if (!src->sttime) { - src->sttime = (running_now != GST_CLOCK_TIME_NONE) ? running_now + LEAD : LEAD; - src->frame_count = 0; - } - - GstClockTime pts = src->sttime + src->frame_count * period; - - /* Safety net: with the lead applied, expected drift is roughly -LEAD - * (pts sits LEAD ns ahead of running_now). Re-anchor only if the - * synthesized timeline diverges from that expectation by several frame - * periods, which indicates a real disturbance (printer paused, stream - * resumed, large fps change) rather than ordinary jitter. - */ - if (running_now != GST_CLOCK_TIME_NONE) { - GstClockTimeDiff drift = GST_CLOCK_DIFF(pts, running_now); - GstClockTimeDiff expected = -(GstClockTimeDiff)LEAD; - GstClockTimeDiff slack = (GstClockTimeDiff)(4 * period); - if (drift > expected + slack || drift < expected - slack) { - GST_DEBUG_OBJECT(src, "ts drift %" G_GINT64_FORMAT " ns; re-anchoring", drift); - src->sttime = running_now + LEAD; - src->frame_count = 0; - pts = src->sttime; - } - } - - GST_BUFFER_PTS(*outbuf) = pts; - GST_BUFFER_DTS(*outbuf) = pts; - GST_BUFFER_DURATION(*outbuf) = period; - src->frame_count++; - GST_DEBUG_OBJECT(src, - "sttime:%lu, DTS:%lu, PTS: %lu~", - src->sttime, GST_BUFFER_DTS(*outbuf), GST_BUFFER_PTS(*outbuf)); - - return GST_FLOW_OK; -} - -static void _log(void *ctx, int lvl, const char *msg) { - GstBambuSrc *src = (GstBambuSrc *) ctx; - GST_DEBUG_OBJECT(src, "bambu: %s", msg); - BAMBULIB(Bambu_FreeLogMsg)(msg); -} - -static gboolean -gst_bambusrc_start (GstBaseSrc * bsrc) -{ - GstBambuSrc *src = GST_BAMBUSRC (bsrc); - - GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location); - - if (src->tnl) { - BAMBULIB(Bambu_Close)(src->tnl); - BAMBULIB(Bambu_Destroy)(src->tnl); - src->tnl = NULL; - } - -#ifdef BAMBU_DYNAMIC - if (!_lib) { - _lib = bambulib_get(); - if (!_lib->Bambu_Open) { - return FALSE; - } - } -#endif - if (BAMBULIB(Bambu_Create)(&src->tnl, src->location) != Bambu_success) { - return FALSE; - } - - int rv = 0; - BAMBULIB(Bambu_SetLogger)(src->tnl, _log, (void *)src); - if ((rv = BAMBULIB(Bambu_Open)(src->tnl)) != Bambu_success) { - BAMBULIB(Bambu_Destroy)(src->tnl); - src->tnl = NULL; - gst_bambu_last_error = rv; - return FALSE; - } - - int n = 0; - while ((rv = BAMBULIB(Bambu_StartStream)(src->tnl, 1 /* video */)) == Bambu_would_block) { - usleep(100000); - } - if (rv != Bambu_success) { - BAMBULIB(Bambu_Close)(src->tnl); - BAMBULIB(Bambu_Destroy)(src->tnl); - src->tnl = NULL; - gst_bambu_last_error = rv; - return FALSE; - } - - src->video_type = AVC1; - n = BAMBULIB(Bambu_GetStreamCount)(src->tnl); - GST_INFO_OBJECT (src, "Bambu_GetStreamCount returned stream count=%d",n); - for (int i = 0; i < n; ++i) { - Bambu_StreamInfo info; - BAMBULIB(Bambu_GetStreamInfo)(src->tnl, i, &info); - - GST_INFO_OBJECT (src, "stream %d type=%d, sub_type=%d", i, info.type, info.sub_type); - if (info.type == VIDE) { - src->video_type = info.sub_type; - src->frame_rate = info.format.video.frame_rate; - GST_INFO_OBJECT (src, " width %d height=%d, frame_rate=%d", - info.format.video.width, info.format.video.height, info.format.video.frame_rate); - } - } - - src->sttime = 0; - src->frame_count = 0; - src->last_arrival = 0; - src->avg_period = 0; - return TRUE; -} - -static gboolean -gst_bambusrc_stop (GstBaseSrc * bsrc) -{ - GstBambuSrc *src; - - src = GST_BAMBUSRC (bsrc); - GST_DEBUG_OBJECT (src, "stop()"); - if (src->tnl) { - BAMBULIB(Bambu_Close)(src->tnl); - BAMBULIB(Bambu_Destroy)(src->tnl); - src->tnl = NULL; - } - - return TRUE; -} - -static GstStateChangeReturn -gst_bambusrc_change_state (GstElement * element, GstStateChange transition) -{ - GstStateChangeReturn ret; - GstBambuSrc *src; - - src = GST_BAMBUSRC (element); - - (void) src; - - switch (transition) { - case GST_STATE_CHANGE_READY_TO_NULL: - //gst_bambusrc_session_close (src); - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - - return ret; -} - -/* Interrupt a blocking request. */ -static gboolean -gst_bambusrc_unlock (GstBaseSrc * bsrc) -{ - GstBambuSrc *src; - - src = GST_BAMBUSRC (bsrc); - GST_DEBUG_OBJECT (src, "unlock()"); - - return TRUE; -} - -/* Interrupt interrupt. */ -static gboolean -gst_bambusrc_unlock_stop (GstBaseSrc * bsrc) -{ - GstBambuSrc *src; - - src = GST_BAMBUSRC (bsrc); - GST_DEBUG_OBJECT (src, "unlock_stop()"); - - return TRUE; -} - -static gboolean -gst_bambusrc_is_seekable (GstBaseSrc * bsrc) -{ - GstBambuSrc *src = GST_BAMBUSRC (bsrc); - - (void) src; - - return FALSE; -} - -static gboolean -gst_bambusrc_query (GstBaseSrc * bsrc, GstQuery * query) -{ - GstBambuSrc *src = GST_BAMBUSRC (bsrc); - gboolean ret; - GstSchedulingFlags flags; - gint minsize, maxsize, align; - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_URI: - gst_query_set_uri (query, src->location); - ret = TRUE; - break; - default: - ret = FALSE; - break; - } - - if (!ret) - ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query); - - switch (GST_QUERY_TYPE (query)) { - case GST_QUERY_SCHEDULING: - gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align); - flags = (GstSchedulingFlags)((int)flags | (int)GST_SCHEDULING_FLAG_SEQUENTIAL); - gst_query_set_scheduling (query, flags, minsize, maxsize, align); - break; - default: - break; - } - - return ret; -} - -static gboolean -gst_bambusrc_set_location (GstBambuSrc * src, const gchar * uri, - GError ** error) -{ - if (src->location) { - g_free (src->location); - src->location = NULL; - } - - if (uri == NULL) - return FALSE; - - src->location = g_strdup (uri); - - return TRUE; -} - -static GstURIType -gst_bambusrc_uri_get_type (GType type) -{ - return GST_URI_SRC; -} - -static const gchar *const * -gst_bambusrc_uri_get_protocols (GType type) -{ - static const gchar *protocols[] = { "bambu", NULL }; - - return protocols; -} - -static gchar * -gst_bambusrc_uri_get_uri (GstURIHandler * handler) -{ - GstBambuSrc *src = GST_BAMBUSRC (handler); - - /* FIXME: make thread-safe */ - return g_strdup (src->location); -} - -static gboolean -gst_bambusrc_uri_set_uri (GstURIHandler * handler, const gchar * uri, - GError ** error) -{ - GstBambuSrc *src = GST_BAMBUSRC (handler); - - return gst_bambusrc_set_location (src, uri, error); -} - -static void -gst_bambusrc_uri_handler_init (gpointer g_iface, gpointer iface_data) -{ - GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface; - - iface->get_type = gst_bambusrc_uri_get_type; - iface->get_protocols = gst_bambusrc_uri_get_protocols; - iface->get_uri = gst_bambusrc_uri_get_uri; - iface->set_uri = gst_bambusrc_uri_set_uri; -} - -static gboolean gstbambusrc_init(GstPlugin *plugin) -{ - return gst_element_register(plugin, "bambusrc", GST_RANK_PRIMARY, GST_TYPE_BAMBUSRC); -} - -#ifndef EXTERNAL_GST_PLUGIN - -// for use inside of Bambu Slicer -void gstbambusrc_register() -{ - static int did_register = 0; - if (did_register) - return; - did_register = 1; - - gst_plugin_register_static(GST_VERSION_MAJOR, GST_VERSION_MINOR, "bambusrc", "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "BambuStudio", "https://github.com/bambulab/BambuStudio"); -} - -#else - -#ifndef PACKAGE -#define PACKAGE "bambusrc" -#endif - -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, GST_VERSION_MINOR, bambusrc, "Bambu Lab source", gstbambusrc_init, "0.0.1", "GPL", "BambuStudio", "https://github.com/bambulab/BambuStudio") - -#endif diff --git a/src/slic3r/GUI/Printer/gstbambusrc.h b/src/slic3r/GUI/Printer/gstbambusrc.h deleted file mode 100644 index d5c022a40e..0000000000 --- a/src/slic3r/GUI/Printer/gstbambusrc.h +++ /dev/null @@ -1,78 +0,0 @@ -/* bambusrc for gstreamer - * integration with proprietary Bambu Lab blob for getting raw h.264 video - * - * Copyright (C) 2023 Joshua Wise - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - * Alternatively, the contents of this file may be used under the - * GNU Lesser General Public License Version 2.1 (the "LGPL"), in - * which case the following provisions apply instead of the ones - * mentioned above: - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -#ifndef __GST_BAMBUSRC_H__ -#define __GST_BAMBUSRC_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define GST_TYPE_BAMBUSRC (gst_bambusrc_get_type()) -G_DECLARE_FINAL_TYPE (GstBambuSrc, gst_bambusrc, - GST, BAMBUSRC, GstPushSrc) - -typedef void *Bambu_Tunnel; - -struct _GstBambuSrc -{ - GstPushSrc element; - GstCaps *src_caps; - gchar *location; - Bambu_Tunnel tnl; - GstClockTime sttime; - int video_type; - int frame_rate; - guint64 frame_count; - GstClockTime last_arrival; - GstClockTime avg_period; -}; - -extern void gstbambusrc_register(); - -G_END_DECLS - -#endif /* __GST_BAMBUSRC_H__ */ diff --git a/src/slic3r/GUI/StatusPanel.cpp b/src/slic3r/GUI/StatusPanel.cpp index 43522f352b..f7bd41fe84 100644 --- a/src/slic3r/GUI/StatusPanel.cpp +++ b/src/slic3r/GUI/StatusPanel.cpp @@ -983,7 +983,7 @@ void PrintingTaskPanel::paint(wxPaintEvent&) dc.DrawBitmap(m_thumbnail_bmp_display, wxPoint(0, 0)); } dc.SetFont(Label::Body_12); - + if (m_plate_index >= 0) { wxString plate_id_str = wxString::Format("%d", m_plate_index); dc.DrawText(plate_id_str, wxPoint(4, 4)); @@ -1271,7 +1271,7 @@ void PrintingTaskPanel::set_plate_index(int plate_idx) } void PrintingTaskPanel::market_scoring_show() -{ +{ m_score_staticline->Show(); m_score_subtask_info->Show(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " show market scoring page"; @@ -1402,7 +1402,7 @@ void StatusBasePanel::init_bitmaps() m_bitmap_fan_off = ScalableBitmap(this, "monitor_fan_off", 22); m_bitmap_speed = ScalableBitmap(this, "monitor_speed", 24); m_bitmap_speed_active = ScalableBitmap(this, "monitor_speed_active", 24); - + m_thumbnail_brokenimg = ScalableBitmap(this, "monitor_brokenimg", 120); m_thumbnail_sdcard = ScalableBitmap(this, "monitor_sdcard_thumbnail", 120); //m_bitmap_camera = create_scaled_bitmap("monitor_camera", nullptr, 18); @@ -1530,7 +1530,7 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page() // media_ctrl_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); // media_ctrl_panel->SetBackgroundColour(*wxBLACK); // wxBoxSizer *bSizer_monitoring = new wxBoxSizer(wxVERTICAL); - m_media_ctrl = new wxMediaCtrl2(this); + m_media_ctrl = new wxMediaCtrl3(this); m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288))); m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString); @@ -2458,7 +2458,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this); m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this); m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this); - m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this); + m_project_task_panel->get_market_retry_buttom()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this); m_project_task_panel->get_clean_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this); m_setting_button->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this); @@ -2520,7 +2520,7 @@ StatusPanel::~StatusPanel() m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this); m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this); m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this); - m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this); + m_project_task_panel->get_market_retry_buttom()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_retry), NULL, this); m_project_task_panel->get_clean_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_print_error_clean), NULL, this); m_setting_button->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::on_camera_enter), NULL, this); @@ -2566,7 +2566,7 @@ StatusPanel::~StatusPanel() if (sdcard_hint_dlg != nullptr) delete sdcard_hint_dlg; - if (m_score_data != nullptr) { + if (m_score_data != nullptr) { delete m_score_data; } } @@ -2590,7 +2590,7 @@ void StatusPanel::init_scaled_buttons() m_bpButton_e_down_10->SetCornerRadius(FromDIP(12)); } -void StatusPanel::on_market_scoring(wxCommandEvent &event) { +void StatusPanel::on_market_scoring(wxCommandEvent &event) { if (obj && obj->is_makeworld_subtask() && obj->rating_info && obj->rating_info->request_successful) { // model is mall model and has rating_id BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": on_market_scoring" ; if (m_score_data && m_score_data->rating_id == obj->rating_info->rating_id) { // current score data for model is same as mall model @@ -2599,7 +2599,7 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) { int ret = m_score_dlg.ShowModal(); BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data"; - if (ret == wxID_OK) { + if (ret == wxID_OK) { BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": old data is upload"; m_score_data->rating_id = -1; m_project_task_panel->set_star_count_dirty(false); @@ -2621,11 +2621,11 @@ void StatusPanel::on_market_scoring(wxCommandEvent &event) { std::string comment = obj->rating_info->content; if (!comment.empty()) { m_score_dlg.set_comment(comment); } - + std::vector images_json_array; images_json_array = obj->rating_info->image_url_paths; if (!images_json_array.empty()) m_score_dlg.set_cloud_bitmap(images_json_array); - + int ret = m_score_dlg.ShowModal(); if (ret == wxID_OK) { @@ -3651,14 +3651,14 @@ void StatusPanel::update_basic_print_data(bool def) void StatusPanel::update_model_info() { auto get_subtask_fn = [this](BBLModelTask* subtask) { - CallAfter([this, subtask]() { + CallAfter([this, subtask]() { if (obj && obj->subtask_id_ == subtask->task_id) { obj->set_modeltask(subtask); } }); }; - + if (wxGetApp().getAgent() && obj) { BBLSubTask* curr_task = obj->get_subtask(); if (curr_task) { @@ -3982,7 +3982,7 @@ void StatusPanel::reset_printing_values() m_project_task_panel->update_left_time(NA_STR); m_project_task_panel->update_layers_num(true, wxString::Format(_L("Layer: %s"), NA_STR)); update_calib_bitmap(); - + task_thumbnail_state = ThumbnailState::PLACE_HOLDER; m_start_loading_thumbnail = false; m_load_sdcard_thumbnail = false; @@ -4037,7 +4037,7 @@ bool StatusPanel::check_axis_z_at_home(MachineObject* obj) } void StatusPanel::on_axis_ctrl_z_up_10(wxCommandEvent &event) -{ +{ if (obj) { obj->command_axis_control("Z", 1.0, -10.0f, 900); if (!check_axis_z_at_home(obj)) @@ -5396,7 +5396,7 @@ void StatusPanel::msw_rescale() m_calibration_btn->Rescale(); m_options_btn->SetMinSize(wxSize(-1, FromDIP(26))); - m_options_btn->Rescale(); + m_options_btn->Rescale(); m_safety_btn->SetMinSize(wxSize(-1, FromDIP(26))); m_safety_btn->Rescale(); @@ -5578,11 +5578,11 @@ ScoreDialog::ScoreDialog(wxWindow *parent, ScoreData *score_data) , m_upload_status_code(StatusCode::CODE_NUMBER) { m_tocken.reset(new int(0)); - + wxBoxSizer *m_main_sizer = get_main_sizer(score_data->local_to_url_image, score_data->comment_text); m_image_url_paths = score_data->image_url_paths; - + this->SetSizer(m_main_sizer); Fit(); @@ -5598,16 +5598,16 @@ void ScoreDialog::on_dpi_changed(const wxRect &suggested_rect) {} void ScoreDialog::OnBitmapClicked(wxMouseEvent &event) { wxStaticBitmap *clickedBitmap = dynamic_cast(event.GetEventObject()); - if (m_image.find(clickedBitmap) != m_image.end()) { + if (m_image.find(clickedBitmap) != m_image.end()) { if (!m_image[clickedBitmap].is_selected) { - for (auto panel : m_image[clickedBitmap].image_broad) { + for (auto panel : m_image[clickedBitmap].image_broad) { panel->Show(); } m_image[clickedBitmap].is_selected = true; m_selected_image_list.insert(clickedBitmap); } else { - for (auto panel : m_image[clickedBitmap].image_broad) { - panel->Hide(); + for (auto panel : m_image[clickedBitmap].image_broad) { + panel->Hide(); } m_image[clickedBitmap].is_selected = false; m_selected_image_list.erase(clickedBitmap); @@ -5624,9 +5624,9 @@ void ScoreDialog::OnBitmapClicked(wxMouseEvent &event) } std::set > ScoreDialog::add_need_upload_imgs() -{ +{ std::set> need_upload_images; - for (auto bitmap : m_image) { + for (auto bitmap : m_image) { if (!bitmap.second.is_uploaded) { wxString &local_image_path = bitmap.second.local_image_url; if (!local_image_path.empty()) { need_upload_images.insert(std::make_pair(bitmap.first, local_image_path)); } @@ -5646,7 +5646,7 @@ std::pair ScoreDialog::create_local_thu cur_image_msg.local_image_url = local_path; cur_image_msg.img_url_paths = ""; cur_image_msg.is_uploaded = false; - + wxStaticBitmap *imageCtrl = new wxStaticBitmap(this, wxID_ANY, wxBitmap(wxImage(local_path, wxBITMAP_TYPE_ANY).Rescale(FromDIP(80), FromDIP(60))), wxDefaultPosition, wxDefaultSize, 0); imageCtrl->Bind(wxEVT_LEFT_DOWN, &ScoreDialog::OnBitmapClicked, this); @@ -5711,7 +5711,7 @@ void ScoreDialog::update_static_bitmap(wxStaticBitmap* static_bitmap, wxImage im } wxBoxSizer *ScoreDialog::create_broad_sizer(wxStaticBitmap *bitmap, ImageMsg& cur_image_msg) -{ +{ // tb: top and bottom lr: left and right auto m_image_tb_broad = new wxBoxSizer(wxVERTICAL); auto line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL); @@ -5755,7 +5755,7 @@ void ScoreDialog::init() { fail_image = wxImage(Slic3r::resources_dir() + "/images/oss_picture_load_failed.png", wxBITMAP_TYPE_ANY); } -wxBoxSizer *ScoreDialog::get_score_sizer() { +wxBoxSizer *ScoreDialog::get_score_sizer() { wxBoxSizer *score_sizer = new wxBoxSizer(wxHORIZONTAL); wxStaticText *static_score_text = new wxStaticText(this, wxID_ANY, _L("Rate"), wxDefaultPosition, wxDefaultSize, 0); static_score_text->Wrap(-1); @@ -5878,18 +5878,18 @@ wxBoxSizer *ScoreDialog::get_photo_btn_sizer() { for (int i = 0; i < filePaths.GetCount(); i++) { //It's ugly, but useful bool is_repeat = false; for (auto image : m_image) { - if (filePaths[i] == image.second.local_image_url) { + if (filePaths[i] == image.second.local_image_url) { is_repeat = true; continue; } } if (!is_repeat) { local_path.push_back(std::make_pair(filePaths[i], "")); - if (local_path.size() + m_image.size() > m_photo_nums) { - break; + if (local_path.size() + m_image.size() > m_photo_nums) { + break; } } - + } load_photo(local_path); @@ -6007,7 +6007,7 @@ wxBoxSizer *ScoreDialog::get_button_sizer() } } progress_dialog->Hide(); - if (progress_dialog) { + if (progress_dialog) { delete progress_dialog; progress_dialog = nullptr; } @@ -6141,7 +6141,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vectorAdd(m_photo_sizer, 0, wxEXPAND | wxTOP, FromDIP(8)); m_image_sizer = new wxGridSizer(5, FromDIP(5), FromDIP(5)); - if (!images.empty()) { + if (!images.empty()) { load_photo(images); } m_main_sizer->Add(m_image_sizer, 0, wxEXPAND | wxLEFT, FromDIP(24)); @@ -6153,7 +6153,7 @@ wxBoxSizer *ScoreDialog::get_main_sizer(const std::vectorGetValue(); score_data.image_url_paths = m_image_url_paths; for (auto img : m_image) { score_data.local_to_url_image.push_back(std::make_pair(img.second.local_image_url, img.second.img_url_paths)); } - + return score_data; } void ScoreDialog::set_comment(std::string comment) { - if (m_comment_text) { + if (m_comment_text) { m_comment_text->SetValue(wxString::FromUTF8(comment)); } } void ScoreDialog::set_cloud_bitmap(std::vector cloud_bitmaps) -{ +{ m_image_url_paths = cloud_bitmaps; for (std::string &url : cloud_bitmaps) { if (std::string::npos == url.find(m_model_id)) continue; diff --git a/src/slic3r/GUI/StatusPanel.hpp b/src/slic3r/GUI/StatusPanel.hpp index d0c8c99f49..341bb9ab69 100644 --- a/src/slic3r/GUI/StatusPanel.hpp +++ b/src/slic3r/GUI/StatusPanel.hpp @@ -14,7 +14,6 @@ #include #include #include -#include "wxMediaCtrl2.h" #include "MediaPlayCtrl.h" #include "AMSSetting.hpp" #include "Calibration.hpp" @@ -195,11 +194,11 @@ public: void set_cloud_bitmap(std::vector cloud_bitmaps); protected: - enum StatusCode { - UPLOAD_PROGRESS = 0, - UPLOAD_EXIST_ISSUE, + enum StatusCode { + UPLOAD_PROGRESS = 0, + UPLOAD_EXIST_ISSUE, UPLOAD_IMG_FAILED, - CODE_NUMBER + CODE_NUMBER }; std::shared_ptr m_tocken; @@ -217,7 +216,7 @@ protected: { wxString local_image_url; //local image path std::string img_url_paths; // oss url path - vector image_broad; + vector image_broad; bool is_selected; bool is_uploaded; // load wxBoxSizer * image_tb_broad = nullptr; @@ -252,7 +251,7 @@ protected: std::set> add_need_upload_imgs(); std::pair create_local_thumbnail(wxString &local_path); std::pair create_oss_thumbnail(std::string &oss_path); - + }; class PrintingTaskPanel : public wxPanel @@ -261,7 +260,7 @@ public: PrintingTaskPanel(wxWindow* parent, PrintingTaskType type); ~PrintingTaskPanel(); void create_panel(wxWindow* parent); - + private: MachineObject* m_obj{nullptr}; @@ -353,7 +352,7 @@ public: void set_plate_index(int plate_idx = -1); void market_scoring_show(); void market_scoring_hide(); - + public: ScalableButton* get_abort_button() {return m_button_abort;}; ScalableButton* get_pause_resume_button() {return m_button_pause_resume;}; @@ -443,7 +442,7 @@ protected: wxStaticBitmap* m_camera_switch_button; - wxMediaCtrl2 * m_media_ctrl; + wxMediaCtrl3 * m_media_ctrl; MediaPlayCtrl * m_media_play_ctrl; Label * m_staticText_printing; @@ -567,7 +566,7 @@ protected: virtual void on_bed_temp_kill_focus(wxFocusEvent &event) { event.Skip(); } virtual void on_bed_temp_set_focus(wxFocusEvent &event) { event.Skip(); } virtual void on_nozzle_temp_kill_focus(wxFocusEvent &event) { event.Skip(); } - virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); } + virtual void on_nozzle_temp_set_focus(wxFocusEvent &event) { event.Skip(); } virtual void on_nozzle_fan_switch(wxCommandEvent &event) { event.Skip(); } virtual void on_printing_fan_switch(wxCommandEvent &event) { event.Skip(); } virtual void on_axis_ctrl_z_up_10(wxCommandEvent &event) { event.Skip(); } diff --git a/src/slic3r/GUI/Widgets/TabCtrl.cpp b/src/slic3r/GUI/Widgets/TabCtrl.cpp index 40e291d5ad..882d2f6897 100644 --- a/src/slic3r/GUI/Widgets/TabCtrl.cpp +++ b/src/slic3r/GUI/Widgets/TabCtrl.cpp @@ -111,7 +111,7 @@ int TabCtrl::AppendItem(const wxString &item, btns.push_back(btn); if (btns.size() > 1) sizer->GetItem(sizer->GetItemCount() - 1)->SetMinSize({0, 0}); - sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE * 2); + sizer->Add(btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, TAB_BUTTON_SPACE); sizer->AddStretchSpacer(1); relayout(); return btns.size() - 1; @@ -225,8 +225,9 @@ bool TabCtrl::IsVisible(unsigned int item) const void TabCtrl::DoSetSize(int x, int y, int width, int height, int sizeFlags) { + auto size = GetSize(); wxWindow::DoSetSize(x, y, width, height, sizeFlags); - if (sizeFlags & wxSIZE_USE_EXISTING) return; + if (size == GetSize()) return; relayout(); } diff --git a/src/slic3r/GUI/wxMediaCtrl2.cpp b/src/slic3r/GUI/wxMediaCtrl2.cpp deleted file mode 100644 index f2d1d2701e..0000000000 --- a/src/slic3r/GUI/wxMediaCtrl2.cpp +++ /dev/null @@ -1,633 +0,0 @@ -#include "wxMediaCtrl2.h" -#include "libslic3r/Time.hpp" -#include "I18N.hpp" -#include "GUI_App.hpp" -#include "LinuxDisplayBackend.hpp" -#include -#include -#ifdef __WIN32__ -#include -#include -#include -#include -#endif - -#ifdef __LINUX__ -#include "Printer/gstbambusrc.h" -#include // main gstreamer header -#endif - -#if defined(__LINUX__) && defined(__WXGTK__) -#include -#include - -namespace { -bool ensure_gstreamer_initialized_for_liveview() -{ - GError* error = nullptr; - if (!gst_init_check(nullptr, nullptr, &error)) { - BOOST_LOG_TRIVIAL(error) << "wxMediaCtrl2: gst_init_check failed before native Wayland liveview setup" - << (error ? std::string(": ") + error->message : std::string()); - if (error) - g_error_free(error); - return false; - } - - return true; -} - -bool is_gstreamer_feature_available(const char* feature) -{ - if (!ensure_gstreamer_initialized_for_liveview()) - return false; - - GstElementFactory* factory = gst_element_factory_find(feature); - if (!factory) - return false; - - gst_object_unref(factory); - return true; -} - -void set_gstreamer_feature_rank(const char* feature, guint rank) -{ - GstElementFactory* factory = gst_element_factory_find(feature); - if (!factory) - return; - - gst_plugin_feature_set_rank(GST_PLUGIN_FEATURE(factory), rank); - gst_object_unref(factory); -} - -void configure_wayland_gstreamer_liveview_path() -{ - static bool configured = false; - if (configured) - return; - configured = true; - - if (!ensure_gstreamer_initialized_for_liveview()) - return; - - // Prefer software decode for Bambu liveview on Wayland/NVIDIA, where - // zero-copy GL/DMABUF display paths can be fragile. Keep hardware - // decoders available as lower-ranked fallbacks for VAAPI/NVDEC/V4L2-only - // installations instead of passing preflight and then blocking autoplug. - set_gstreamer_feature_rank("avdec_h264", GST_RANK_PRIMARY + 300); - set_gstreamer_feature_rank("openh264dec", GST_RANK_PRIMARY + 100); - set_gstreamer_feature_rank("nvh264dec", GST_RANK_MARGINAL); - set_gstreamer_feature_rank("vaapih264dec", GST_RANK_MARGINAL); - set_gstreamer_feature_rank("vah264dec", GST_RANK_MARGINAL); - set_gstreamer_feature_rank("v4l2h264dec", GST_RANK_MARGINAL); -} -} - -#endif // defined(__LINUX__) && defined(__WXGTK__) - -#ifdef __LINUX__ -extern "C" int gst_bambu_last_error; - -class WXDLLIMPEXP_MEDIA - wxGStreamerMediaBackend : public wxMediaBackendCommonBase -{ -public: - GstElement *m_playbin; // GStreamer media element -}; -#endif - -wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); - -wxMediaCtrl2::wxMediaCtrl2(wxWindow *parent) -{ -#if defined(__LINUX__) && defined(__WXGTK__) - m_native_wayland = Slic3r::GUI::is_running_on_wayland(); - if (m_native_wayland && is_gstreamer_feature_available("gtksink")) - configure_wayland_gstreamer_liveview_path(); - else if (m_native_wayland) { - m_gtk_sink_error = _L("Native Wayland liveview requires the GStreamer GTK video sink. Please install the gtksink plugin for GStreamer, then restart OrcaSlicer."); - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: native Wayland liveview disabled because GStreamer gtksink is unavailable"; - } -#endif -#ifdef __WIN32__ - auto hModExe = GetModuleHandle(NULL); - // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: GetModuleHandle " << hModExe; - auto NvOptimusEnablement = (DWORD *) GetProcAddress(hModExe, "NvOptimusEnablement"); - auto AmdPowerXpressRequestHighPerformance = (int *) GetProcAddress(hModExe, "AmdPowerXpressRequestHighPerformance"); - if (NvOptimusEnablement) { - // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: NvOptimusEnablement " << *NvOptimusEnablement; - *NvOptimusEnablement = 0; - } - if (AmdPowerXpressRequestHighPerformance) { - // BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: AmdPowerXpressRequestHighPerformance " << *AmdPowerXpressRequestHighPerformance; - *AmdPowerXpressRequestHighPerformance = 0; - } -#endif -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_native_wayland) - wxControl::Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize); - else -#endif - wxMediaCtrl::Create(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxMEDIACTRLPLAYERCONTROLS_NONE); -#ifdef __LINUX__ - gstbambusrc_register(); -#ifdef __WXGTK__ - if (m_native_wayland && m_gtk_sink_error.empty()) - m_use_gtk_sink = CreateGtkSinkPlayer(); - if (m_native_wayland && !m_use_gtk_sink && m_gtk_sink_error.empty()) - m_gtk_sink_error = _L("Failed to initialize the native Wayland GStreamer video sink. Please check your GStreamer GTK plugin installation."); -#endif - if (!m_use_gtk_sink && m_imp) { - auto playbin = reinterpret_cast(m_imp)->m_playbin; - g_object_set(G_OBJECT(playbin), - "audio-sink", nullptr, - nullptr); - } else if (!m_use_gtk_sink) { - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: wxMediaCtrl backend is unavailable"; - } - Bind(wxEVT_MEDIA_LOADED, [this](auto & e) { - m_loaded = true; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(0); - event.SetEventObject(this); - wxPostEvent(this, event); - }); -#endif -} - -wxMediaCtrl2::~wxMediaCtrl2() -{ -#if defined(__LINUX__) && defined(__WXGTK__) - DestroyGtkSinkPlayer(); -#endif -} - -#if defined(__LINUX__) && defined(__WXGTK__) -bool wxMediaCtrl2::CreateGtkSinkPlayer() -{ - GstElement *playbin = gst_element_factory_make("playbin", "orca-wayland-gtk-playbin"); - if (!playbin) - return false; - - GError *error = nullptr; - GstElement *video_sink = gst_parse_bin_from_description( - "videoconvert ! videoscale ! video/x-raw,format=BGRx ! gtksink name=orca_wayland_gtksink sync=false", - TRUE, - &error); - if (!video_sink) { - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to create gtksink video bin" - << (error ? std::string(": ") + error->message : std::string()); - if (error) - g_error_free(error); - gst_object_unref(playbin); - return false; - } - - GstElement *gtk_sink = gst_bin_get_by_name(GST_BIN(video_sink), "orca_wayland_gtksink"); - if (!gtk_sink) { - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: failed to find gtksink in video bin"; - gst_object_unref(video_sink); - gst_object_unref(playbin); - return false; - } - - GtkWidget *gtk_widget = nullptr; - g_object_get(G_OBJECT(gtk_sink), "widget", >k_widget, nullptr); - if (!gtk_widget) { - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink did not expose a GtkWidget"; - gst_object_unref(gtk_sink); - gst_object_unref(video_sink); - gst_object_unref(playbin); - return false; - } - - gtk_widget_show(gtk_widget); - m_gtk_video_window = new wxNativeWindow(this, wxID_ANY, gtk_widget); - m_gtk_video_window->Show(); - g_object_unref(gtk_widget); - - g_object_set(G_OBJECT(playbin), - "video-sink", video_sink, - "audio-sink", nullptr, - nullptr); - gst_object_unref(video_sink); - - m_gtk_playbin = playbin; - m_gtk_sink = gtk_sink; - - GstBus *bus = gst_element_get_bus(playbin); - m_gtk_bus_watch_id = gst_bus_add_watch(bus, [](GstBus *, GstMessage *message, gpointer data) -> gboolean { - auto *self = static_cast(data); - if (!self || !self->m_gtk_playbin) - return G_SOURCE_REMOVE; - - switch (GST_MESSAGE_TYPE(message)) { - case GST_MESSAGE_ERROR: - { - GError *error = nullptr; - gchar *debug = nullptr; - gst_message_parse_error(message, &error, &debug); - BOOST_LOG_TRIVIAL(warning) << "wxMediaCtrl2: gtksink pipeline error" - << (error ? std::string(": ") + error->message : std::string()) - << (debug ? std::string(" debug: ") + debug : std::string()); - if (error) - g_error_free(error); - if (debug) - g_free(debug); - - self->m_error = gst_bambu_last_error ? gst_bambu_last_error : 2; - self->m_loaded = false; - self->m_gtk_state = wxMEDIASTATE_STOPPED; - self->PostGtkSinkStateEvent(self->GetId()); - break; - } - case GST_MESSAGE_EOS: - self->m_loaded = false; - self->m_gtk_state = wxMEDIASTATE_STOPPED; - self->PostGtkSinkStateEvent(self->GetId()); - break; - case GST_MESSAGE_STATE_CHANGED: - if (GST_MESSAGE_SRC(message) == GST_OBJECT(self->m_gtk_playbin)) { - GstState old_state; - GstState new_state; - GstState pending_state; - gst_message_parse_state_changed(message, &old_state, &new_state, &pending_state); - - if (new_state == GST_STATE_PLAYING) { - self->m_loaded = true; - self->m_gtk_state = wxMEDIASTATE_PLAYING; - self->PostGtkSinkStateEvent(); - } else if (new_state == GST_STATE_PAUSED && old_state < GST_STATE_PAUSED) { - // Treat only upward READY/NULL -> PAUSED as load completion. - // PLAYING -> PAUSED is a normal teardown step before NULL. - self->m_loaded = true; - self->m_gtk_state = wxMEDIASTATE_PAUSED; - self->PostGtkSinkStateEvent(); - } else if (new_state <= GST_STATE_READY && old_state >= GST_STATE_PAUSED) { - self->m_loaded = false; - self->m_gtk_state = wxMEDIASTATE_STOPPED; - self->PostGtkSinkStateEvent(); - } - } - break; - default: - break; - } - - return G_SOURCE_CONTINUE; - }, this); - gst_object_unref(bus); - - BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2: using GTK native Wayland video sink"; - return true; -} - -void wxMediaCtrl2::DestroyGtkSinkPlayer() -{ - if (m_gtk_bus_watch_id) { - g_source_remove(m_gtk_bus_watch_id); - m_gtk_bus_watch_id = 0; - } - - if (m_gtk_playbin) { - gst_element_set_state(m_gtk_playbin, GST_STATE_NULL); - } - - if (m_gtk_video_window) { - m_gtk_video_window->Destroy(); - m_gtk_video_window = nullptr; - } - - if (m_gtk_playbin) { - gst_object_unref(m_gtk_playbin); - m_gtk_playbin = nullptr; - } - - if (m_gtk_sink) { - gst_object_unref(m_gtk_sink); - m_gtk_sink = nullptr; - } - -} - -void wxMediaCtrl2::PostGtkSinkStateEvent(int id) -{ - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(id); - event.SetEventObject(this); - wxPostEvent(this, event); -} -#endif // defined(__LINUX__) && defined(__WXGTK__) - -#define CLSID_BAMBU_SOURCE L"{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}" - -void wxMediaCtrl2::Load(wxURI url) -{ -#ifdef __WIN32__ - InvalidateBestSize(); - if (m_imp == nullptr) { - static bool notified = false; - if (!notified) CallAfter([] { - auto res = wxMessageBox(_L("Windows Media Player is required for this task! Do you want to enable 'Windows Media Player' for your operation system?"), _L("Error"), wxOK | wxCANCEL); - if (res == wxOK) { - wxString url = IsWindows10OrGreater() - ? "ms-settings:optionalfeatures?activationSource=SMC-Article-14209" - : "https://support.microsoft.com/en-au/windows/get-windows-media-player-81718e0d-cfce-25b1-aee3-94596b658287"; - wxExecute("cmd /c start " + url, wxEXEC_HIDE_CONSOLE); - } - }); - m_error = 100; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - return; - } - { - wxRegKey key11(wxRegKey::HKCU, L"SOFTWARE\\Classes\\CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32"); - wxRegKey key12(wxRegKey::HKCR, L"CLSID\\" CLSID_BAMBU_SOURCE L"\\InProcServer32"); - wxString path = key11.Exists() ? key11.QueryDefaultValue() - : key12.Exists() ? key12.QueryDefaultValue() : wxString{}; - wxRegKey key2(wxRegKey::HKCR, "bambu"); - wxString clsid; - if (key2.Exists()) - key2.QueryRawValue("Source Filter", clsid); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": clsid %1% path %2%") % clsid % path; - - std::string data_dir_str = Slic3r::data_dir(); - boost::filesystem::path data_dir_path(data_dir_str); - auto dll_path = data_dir_path / "plugins" / "BambuSource.dll"; - if (path.empty() || !wxFile::Exists(path) || clsid != CLSID_BAMBU_SOURCE) { - if (boost::filesystem::exists(dll_path)) { - CallAfter( - [dll_path] { - int res = wxMessageBox(_L("BambuSource has not correctly been registered for media playing! Press Yes to re-register it. You will be promoted twice"), _L("Error"), wxYES_NO); - if (res == wxYES) { - std::string regContent = R"(Windows Registry Editor Version 5.00 - [HKEY_CLASSES_ROOT\bambu] - "Source Filter"="{233E64FB-2041-4A6C-AFAB-FF9BCF83E7AA}" - )"; - - auto reg_path = (fs::temp_directory_path() / fs::unique_path()).replace_extension(".reg"); - std::ofstream temp_reg_file(reg_path.c_str()); - if (!temp_reg_file) { - return false; - } - temp_reg_file << regContent; - temp_reg_file.close(); - auto sei_params = L"/q /s " + reg_path.wstring(); - SHELLEXECUTEINFO sei{sizeof(sei), SEE_MASK_NOCLOSEPROCESS, NULL, L"open", - L"regedit", sei_params.c_str(),SW_HIDE,SW_HIDE}; - ::ShellExecuteEx(&sei); - - wstring quoted_dll_path = L"\"" + dll_path.wstring() + L"\""; - SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"runas", L"regsvr32", quoted_dll_path.c_str(), SW_HIDE }; - ::ShellExecuteEx(&info); - fs::remove(reg_path); - } - return true; - }); - } else { - CallAfter([] { - wxMessageBox(_L("Missing BambuSource component registered for media playing! Please re-install OrcaSlicer or seek community help."), _L("Error"), wxOK); - }); - } - m_error = clsid != CLSID_BAMBU_SOURCE ? 101 : path.empty() ? 102 : 103; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - return; - } - if (path != dll_path) { - static bool notified = false; - if (!notified) CallAfter([dll_path] { - int res = wxMessageBox(_L("Using a BambuSource from a different install, video play may not work correctly! Press Yes to fix it."), _L("Warning"), wxYES_NO | wxICON_WARNING); - if (res == wxYES) { - auto path = dll_path.wstring(); - if (path.find(L' ') != std::wstring::npos) - path = L"\"" + path + L"\""; - SHELLEXECUTEINFO info{sizeof(info), 0, NULL, L"open", L"regsvr32", path.c_str(), SW_HIDE}; - ::ShellExecuteEx(&info); - } - }); - notified = true; - } - wxRegKey keyWmp(wxRegKey::HKCU, "SOFTWARE\\Microsoft\\MediaPlayer\\Player\\Extensions\\."); - keyWmp.Create(); - long permissions = 0; - if (keyWmp.HasValue("Permissions")) - keyWmp.QueryValue("Permissions", &permissions); - if ((permissions & 32) == 0) { - permissions |= 32; - keyWmp.SetValue("Permissions", permissions); - } - } - url = wxURI(url.BuildURI().append("&hwnd=").append(boost::lexical_cast(GetHandle())).append("&tid=").append( - boost::lexical_cast(GetCurrentThreadId()))); -#endif -#ifdef __WXGTK3__ - GstElementFactory *factory; - int hasplugins = 1; - - factory = gst_element_factory_find("h264parse"); - if (!factory) { - hasplugins = 0; - } else { - gst_object_unref(factory); - } - - factory = gst_element_factory_find("openh264dec"); - if (!factory) { - factory = gst_element_factory_find("avdec_h264"); - } - if (!factory) { - factory = gst_element_factory_find("vaapih264dec"); - } - if (!factory) { - factory = gst_element_factory_find("vah264dec"); - } - if (!factory) { - factory = gst_element_factory_find("nvh264dec"); - } - if (!factory) { - factory = gst_element_factory_find("v4l2h264dec"); - } - if (!factory) { - hasplugins = 0; - } else { - gst_object_unref(factory); - } - - if (!hasplugins) { - CallAfter([] { - wxMessageBox(_L("Your system is missing H.264 codecs for GStreamer, which are required to play video. (Try installing the gstreamer1.0-plugins-bad or gstreamer1.0-libav packages, then restart Orca Slicer?)"), _L("Error"), wxOK); - }); - m_error = 101; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - return; - } - wxLog::EnableLogging(false); -#endif - m_error = 0; - m_loaded = false; -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_use_gtk_sink && m_gtk_playbin) { - const std::string uri = std::string(url.BuildURI().ToUTF8().data()); - gst_element_set_state(m_gtk_playbin, GST_STATE_NULL); - g_object_set(G_OBJECT(m_gtk_playbin), "uri", uri.c_str(), nullptr); - m_gtk_state = wxMEDIASTATE_STOPPED; - GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PAUSED); - if (state == GST_STATE_CHANGE_FAILURE) { - m_error = gst_bambu_last_error ? gst_bambu_last_error : 2; - PostGtkSinkStateEvent(GetId()); - } - return; - } - if (!m_imp) { - m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100; - m_loaded = false; - if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) { - m_gtk_sink_error_notified = true; - const wxString message = m_gtk_sink_error; - CallAfter([message] { - wxMessageBox(message, _L("Error"), wxOK); - }); - } - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - return; - } -#endif - wxMediaCtrl::Load(url); -} - -void wxMediaCtrl2::Play() -{ -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_use_gtk_sink && m_gtk_playbin) { - GstStateChangeReturn state = gst_element_set_state(m_gtk_playbin, GST_STATE_PLAYING); - if (state == GST_STATE_CHANGE_FAILURE) { - m_error = gst_bambu_last_error ? gst_bambu_last_error : 2; - m_gtk_state = wxMEDIASTATE_STOPPED; - PostGtkSinkStateEvent(GetId()); - } - return; - } - if (!m_imp) { - m_error = m_native_wayland && !m_gtk_sink_error.empty() ? 104 : 100; - if (m_native_wayland && !m_gtk_sink_error.empty() && !m_gtk_sink_error_notified) { - m_gtk_sink_error_notified = true; - const wxString message = m_gtk_sink_error; - CallAfter([message] { - wxMessageBox(message, _L("Error"), wxOK); - }); - } - PostGtkSinkStateEvent(GetId()); - return; - } -#endif - wxMediaCtrl::Play(); -} - -void wxMediaCtrl2::Stop() -{ -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_use_gtk_sink && m_gtk_playbin) { - gst_element_set_state(m_gtk_playbin, GST_STATE_NULL); - m_gtk_state = wxMEDIASTATE_STOPPED; - m_loaded = false; - PostGtkSinkStateEvent(0); - return; - } - if (!m_imp) - return; -#endif - wxMediaCtrl::Stop(); -} - -wxMediaState wxMediaCtrl2::GetState() -{ -#if defined(__LINUX__) && defined(__WXGTK__) - if (m_use_gtk_sink && m_gtk_playbin) - return m_gtk_state; - if (!m_imp) - return wxMEDIASTATE_STOPPED; -#endif - return wxMediaCtrl::GetState(); -} - -int wxMediaCtrl2::GetLastError() const -{ -#ifdef __LINUX__ -#ifdef __WXGTK__ - if (m_use_gtk_sink && m_error) - return m_error; -#endif - if (m_error) - return m_error; - return gst_bambu_last_error; -#else - return m_error; -#endif -} - -wxSize wxMediaCtrl2::GetVideoSize() const -{ -#ifdef __LINUX__ - // Gstreamer doesn't give us a VideoSize until we're playing, which - // confuses the MediaPlayCtrl into claiming that it is stuck - // "Loading...". Fake it out for now. - return m_loaded ? wxSize(1280, 720) : wxSize{}; -#else - wxSize size = m_imp ? m_imp->GetVideoSize() : wxSize(0, 0); - if (size.GetWidth() > 0) - const_cast(m_video_size) = size; - return size; -#endif -} - -wxSize wxMediaCtrl2::DoGetBestSize() const -{ - return {-1, -1}; -} - -#ifdef __WIN32__ - -WXLRESULT wxMediaCtrl2::MSWWindowProc(WXUINT nMsg, - WXWPARAM wParam, - WXLPARAM lParam) -{ - // The stream source sends WM_USER+1000 with a synchronous SendMessage from its own threads, - // so this runs re-entrantly on the UI thread at whatever message-retrieval point the player - // happens to be in - often nested inside an Orca log statement. Never BOOST_LOG_TRIVIAL here: - // boost::log is not re-entrant on one thread, and doing so corrupted its per-thread record - // state, crashing later in unrelated places (the player, the log filter, a plug-in heap free). - // Post the string out (as the stat branch does) and log it on a clean stack instead. - if (nMsg == WM_USER + 1000) { - wxString msg((wchar_t const *) lParam); - if (wParam == 1) { - if (msg.EndsWith("]")) { - int n = msg.find_last_of('['); - if (n != wxString::npos) { - long val = 0; - if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) - m_error = (int) val; - } - } else if (msg.Contains("stat_log")) { - wxCommandEvent evt(EVT_MEDIA_CTRL_STAT); - evt.SetEventObject(this); - evt.SetString(msg.Mid(msg.Find(' ') + 1)); - wxPostEvent(this, evt); - } - } - return 0; - } - return wxMediaCtrl::MSWWindowProc(nMsg, wParam, lParam); -} - -#endif diff --git a/src/slic3r/GUI/wxMediaCtrl2.h b/src/slic3r/GUI/wxMediaCtrl2.h deleted file mode 100644 index 4f37b5cd1e..0000000000 --- a/src/slic3r/GUI/wxMediaCtrl2.h +++ /dev/null @@ -1,117 +0,0 @@ -// -// wxMediaCtrl2.h -// libslic3r_gui -// -// Created by cmguo on 2021/12/7. -// - -#ifndef wxMediaCtrl2_h -#define wxMediaCtrl2_h - -#include "wx/uri.h" -#include "wx/mediactrl.h" - -wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); - -#if defined(__LINUX__) && defined(__WXGTK__) -typedef struct _GstElement GstElement; -#endif - -#ifdef __WXMAC__ - -class wxMediaCtrl2 : public wxWindow -{ -public: - wxMediaCtrl2(wxWindow * parent); - - ~wxMediaCtrl2(); - - void Load(wxURI url); - - void Play(); - - void Stop(); - - void SetIdleImage(wxString const & image); - - wxMediaState GetState() const; - - wxSize GetVideoSize() const; - - int GetLastError() const { return m_error; } - - static inline const wxMediaState MEDIASTATE_BUFFERING = static_cast(6); - -protected: - void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; - - static void bambu_log(void const * ctx, int level, char const * msg); - - void NotifyStopped(); - -private: - void create_player(); - void * m_player = nullptr; - wxMediaState m_state = wxMEDIASTATE_STOPPED; - int m_error = 0; - wxSize m_video_size{16, 9}; -}; - -#else - -class wxMediaCtrl2 : public wxMediaCtrl -{ -public: - wxMediaCtrl2(wxWindow *parent); - ~wxMediaCtrl2(); - - void Load(wxURI url); - - void Play(); - - void Stop(); - - void SetIdleImage(wxString const & image); - - wxMediaState GetState(); - - int GetLastError() const; - - wxSize GetVideoSize() const; - -protected: - wxSize DoGetBestSize() const override; - - void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; - -#ifdef __WIN32__ - WXLRESULT MSWWindowProc(WXUINT nMsg, - WXWPARAM wParam, - WXLPARAM lParam) override; -#endif - -private: -#if defined(__LINUX__) && defined(__WXGTK__) - bool CreateGtkSinkPlayer(); - void DestroyGtkSinkPlayer(); - void PostGtkSinkStateEvent(int id = 0); - - bool m_native_wayland = false; - bool m_use_gtk_sink = false; - wxString m_gtk_sink_error; - bool m_gtk_sink_error_notified = false; - GstElement *m_gtk_playbin = nullptr; - GstElement *m_gtk_sink = nullptr; - unsigned int m_gtk_bus_watch_id = 0; - wxWindow *m_gtk_video_window = nullptr; - wxMediaState m_gtk_state = wxMEDIASTATE_STOPPED; -#endif - wxString m_idle_image; - int m_error = 0; - bool m_loaded = false; - wxSize m_video_size{16, 9}; -}; - -#endif - -#endif /* wxMediaCtrl2_h */ diff --git a/src/slic3r/GUI/wxMediaCtrl2.mm b/src/slic3r/GUI/wxMediaCtrl2.mm deleted file mode 100644 index 063472eccc..0000000000 --- a/src/slic3r/GUI/wxMediaCtrl2.mm +++ /dev/null @@ -1,170 +0,0 @@ -// -// wxMediaCtrl2.m -// OrcaSlicer -// -// Created by cmguo on 2021/12/7. -// - -#import "wxMediaCtrl2.h" -#import "wx/mediactrl.h" -#include - -#import -#import "BambuPlayer/BambuPlayer.h" -#import "../Utils/NetworkAgent.hpp" - -#include -#include - -wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); - -#define BAMBU_DYNAMIC - -void wxMediaCtrl2::bambu_log(void const * ctx, int level, char const * msg) -{ - if (level == 1) { - wxString msg2(msg); - if (msg2.EndsWith("]")) { - int n = msg2.find_last_of('['); - if (n != wxString::npos) { - long val = 0; - wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx; - if (msg2.SubString(n + 1, msg2.Length() - 2).ToLong(&val)) - ctrl->m_error = (int) val; - } - } else if (strstr(msg, "stat_log")) { - wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx; - wxCommandEvent evt(EVT_MEDIA_CTRL_STAT); - evt.SetEventObject(ctrl); - evt.SetString(strchr(msg, ' ') + 1); - wxPostEvent(ctrl, evt); - } - } else if (level < 0) { - wxMediaCtrl2 * ctrl = (wxMediaCtrl2 *) ctx; - ctrl->NotifyStopped(); - } - BOOST_LOG_TRIVIAL(info) << msg; -} - -wxMediaCtrl2::wxMediaCtrl2(wxWindow * parent) - : wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize) -{ - NSView * imageView = (NSView *) GetHandle(); - imageView.layer = [[CALayer alloc] init]; - CGColorRef color = CGColorCreateGenericRGB(0, 0, 0, 1.0f); - imageView.layer.backgroundColor = color; - CGColorRelease(color); - imageView.wantsLayer = YES; - create_player(); -} - -wxMediaCtrl2::~wxMediaCtrl2() -{ - BambuPlayer * player = (BambuPlayer *) m_player; - [player dealloc]; -} - -void wxMediaCtrl2::create_player() -{ - auto module = Slic3r::NetworkAgent::get_bambu_source_entry(); - if (!module) { - //not ready yet - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "Network plugin not ready currently!"; - return; - } - Class cls = (__bridge Class) dlsym(module, "OBJC_CLASS_$_BambuPlayer"); - if (cls == nullptr) { - m_error = -2; - return; - } - NSView * imageView = (NSView *) GetHandle(); - BambuPlayer * player = [cls alloc]; - [player initWithImageView: imageView]; - [player setLogger: bambu_log withContext: this]; - m_player = player; -} - -void wxMediaCtrl2::Load(wxURI url) -{ - if (!m_player) { - create_player(); - if (!m_player) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!"; - return; - } - } - - BambuPlayer * player = (BambuPlayer *) m_player; - if (player) { - [player close]; - m_error = 0; - m_error = [player open: url.BuildURI().ToUTF8()]; - } - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); -} - -void wxMediaCtrl2::Play() -{ - if (!m_player) { - create_player(); - if (!m_player) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!"; - return; - } - } - BambuPlayer * player2 = (BambuPlayer *) m_player; - [player2 play]; - if (m_state != wxMEDIASTATE_PLAYING) { - m_state = wxMEDIASTATE_PLAYING; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - } -} - -void wxMediaCtrl2::Stop() -{ - if (!m_player) { - create_player(); - if (!m_player) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": create_player failed currently!"; - return; - } - } - BambuPlayer * player2 = (BambuPlayer *) m_player; - [player2 close]; - NotifyStopped(); -} - -void wxMediaCtrl2::NotifyStopped() -{ - if (m_state != wxMEDIASTATE_STOPPED) { - m_state = wxMEDIASTATE_STOPPED; - wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); - event.SetId(GetId()); - event.SetEventObject(this); - wxPostEvent(this, event); - } -} - -wxMediaState wxMediaCtrl2::GetState() const -{ - return m_state; -} - -wxSize wxMediaCtrl2::GetVideoSize() const -{ - BambuPlayer * player2 = (BambuPlayer *) m_player; - if (player2) { - NSSize size = [player2 videoSize]; - if (size.width > 0) - const_cast(m_video_size) = {(int) size.width, (int) size.height}; - return {(int) size.width, (int) size.height}; - } else { - return {0, 0}; - } -} diff --git a/src/slic3r/GUI/wxMediaCtrl3.cpp b/src/slic3r/GUI/wxMediaCtrl3.cpp new file mode 100644 index 0000000000..098db68808 --- /dev/null +++ b/src/slic3r/GUI/wxMediaCtrl3.cpp @@ -0,0 +1,313 @@ +#include "wxMediaCtrl3.h" +#include "AVVideoDecoder.hpp" +#include "I18N.hpp" +#include "libslic3r/Utils.hpp" +#include +#include +#ifdef __WIN32__ +#include +#include +#include +#endif + +wxDEFINE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); + +BEGIN_EVENT_TABLE(wxMediaCtrl3, wxWindow) + +// catch paint events +EVT_PAINT(wxMediaCtrl3::paintEvent) + +END_EVENT_TABLE() + +struct StaticBambuLib : BambuLib +{ + static StaticBambuLib &get(BambuLib *); +}; + +wxMediaCtrl3::wxMediaCtrl3(wxWindow *parent) + : wxWindow(parent, wxID_ANY) + , BambuLib(StaticBambuLib::get(this)) + , m_thread([this] { PlayThread(); }) +{ + SetBackgroundColour("#000001ff"); +} + +wxMediaCtrl3::~wxMediaCtrl3() +{ + { + std::unique_lock lk(m_mutex); + m_url.reset(new wxURI); + m_frame = wxImage(m_idle_image); + m_cond.notify_all(); + } + m_thread.join(); +} + +void wxMediaCtrl3::Load(wxURI url) +{ + std::unique_lock lk(m_mutex); + m_video_size = wxDefaultSize; + m_error = 0; + m_url.reset(new wxURI(url)); + m_cond.notify_all(); +} + +void wxMediaCtrl3::Play() +{ + std::unique_lock lk(m_mutex); + if (m_state != wxMEDIASTATE_PLAYING) { + m_state = wxMEDIASTATE_PLAYING; + wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); + event.SetId(GetId()); + event.SetEventObject(this); + wxPostEvent(this, event); + } +} + +void wxMediaCtrl3::Stop() +{ + std::unique_lock lk(m_mutex); + m_url.reset(); + m_frame = wxImage(m_idle_image); + NotifyStopped(); + m_cond.notify_all(); + Refresh(); +} + +void wxMediaCtrl3::SetIdleImage(wxString const &image) +{ + if (m_idle_image == image) + return; + m_idle_image = image; + if (m_url == nullptr) { + std::unique_lock lk(m_mutex); + m_frame = wxImage(m_idle_image); + assert(m_frame.IsOk()); + Refresh(); + } +} + +wxMediaState wxMediaCtrl3::GetState() +{ + std::unique_lock lk(m_mutex); + return m_state; +} + +int wxMediaCtrl3::GetLastError() +{ + std::unique_lock lk(m_mutex); + return m_error; +} + +wxSize wxMediaCtrl3::GetVideoSize() +{ + std::unique_lock lk(m_mutex); + return m_video_size; +} + +wxSize wxMediaCtrl3::DoGetBestSize() const +{ + return {-1, -1}; +} + +static void adjust_frame_size(wxSize & frame, wxSize const & video, wxSize const & window) +{ + if (video.x * window.y < video.y * window.x) + frame = { video.x * window.y / video.y, window.y }; + else + frame = { window.x, video.y * window.x / video.x }; +} + +void wxMediaCtrl3::paintEvent(wxPaintEvent &evt) +{ + wxPaintDC dc(this); + auto size = GetSize(); + if (size.x <= 0 || size.y <= 0) + return; + std::unique_lock lk(m_mutex); + if (!m_frame.IsOk()) + return; + auto size2 = m_frame.GetSize(); + if (size2.x != m_frame_size.x && size2.y == m_frame_size.y) + size2.x = m_frame_size.x; + auto size3 = (size - size2) / 2; + if (size2.x != size.x && size2.y != size.y) { + double scale = 1.; + if (size.x * size2.y > size.y * size2.x) { + size3 = {size.x * size2.y / size.y, size2.y}; + scale = double(size.y) / size2.y; + } else { + size3 = {size2.x, size.y * size2.x / size.x}; + scale = double(size.x) / size2.x; + } + dc.SetUserScale(scale, scale); + size3 = (size3 - size2) / 2; + } + dc.DrawBitmap(m_frame, size3.x, size3.y); +} + +void wxMediaCtrl3::DoSetSize(int x, int y, int width, int height, int sizeFlags) +{ + wxWindow::DoSetSize(x, y, width, height, sizeFlags); + if (sizeFlags == wxSIZE_USE_EXISTING) return; + wxMediaCtrl_OnSize(this, m_video_size, width, height); + std::unique_lock lk(m_mutex); + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + Refresh(); +} + +void wxMediaCtrl3::bambu_log(void *ctx, int level, tchar const *msg2) +{ +#ifdef _WIN32 + wxString msg(msg2); +#else + wxString msg = wxString::FromUTF8(msg2); +#endif + if (level == 1) { + if (msg.EndsWith("]")) { + int n = msg.find_last_of('['); + if (n != wxString::npos) { + long val = 0; + wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx; + if (msg.SubString(n + 1, msg.Length() - 2).ToLong(&val)) { + std::unique_lock lk(ctrl->m_mutex); + ctrl->m_error = (int) val; + } + } + } else if (msg.Contains("stat_log")) { + wxCommandEvent evt(EVT_MEDIA_CTRL_STAT); + wxMediaCtrl3 *ctrl = (wxMediaCtrl3 *) ctx; + evt.SetEventObject(ctrl); + evt.SetString(msg.Mid(msg.Find(' ') + 1)); + wxPostEvent(ctrl, evt); + } + } + BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data(); +} + +void wxMediaCtrl3::PlayThread() +{ + using namespace std::chrono_literals; + std::shared_ptr url; + std::unique_lock lk(m_mutex); + while (true) { + m_cond.wait(lk, [this, &url] { return m_url != url; }); + url = m_url; + if (url == nullptr) + continue; + if (!url->HasScheme()) + break; + lk.unlock(); + Bambu_Tunnel tunnel = nullptr; + int error = Bambu_Create(&tunnel, m_url->BuildURI().ToUTF8()); + if (error == 0) { + Bambu_SetLogger(tunnel, &wxMediaCtrl3::bambu_log, this); + error = Bambu_Open(tunnel); + if (error == 0) + error = Bambu_would_block; + } + lk.lock(); + while (error == int(Bambu_would_block)) { + m_cond.wait_for(lk, 100ms); + if (m_url != url) { + error = 1; + break; + } + lk.unlock(); + error = Bambu_StartStream(tunnel, true); + lk.lock(); + } + Bambu_StreamInfo info; + if (error == 0) + error = Bambu_GetStreamInfo(tunnel, 0, &info); + AVVideoDecoder decoder; + int minFrameDuration = 0; + if (error == 0) { + decoder.open(info); + m_video_size = { info.format.video.width, info.format.video.height }; + adjust_frame_size(m_frame_size, m_video_size, GetSize()); + minFrameDuration = 800 / info.format.video.frame_rate; // 80% + NotifyStopped(); + } + Bambu_Sample sample; + while (error == 0) { + lk.unlock(); + error = Bambu_ReadSample(tunnel, &sample); + lk.lock(); + while (error == int(Bambu_would_block)) { + m_cond.wait_for(lk, 100ms); + if (m_url != url) { + error = 1; + break; + } + lk.unlock(); + error = Bambu_ReadSample(tunnel, &sample); + lk.lock(); + } + if (error == 0) { + auto frame_size = m_frame_size; + lk.unlock(); + decoder.decode(sample); +#ifdef _WIN32 + wxBitmap bm; + decoder.toWxBitmap(bm, frame_size); +#else + wxImage bm; + decoder.toWxImage(bm, frame_size); +#endif + lk.lock(); + if (m_url != url) { + error = 1; + break; + } + if (bm.IsOk()) { + auto now = std::chrono::system_clock::now(); + if (m_last_PTS && (sample.decode_time - m_last_PTS) < 30000000ULL) { // 3s + auto next_PTS_expected = m_last_PTS_expected + std::chrono::milliseconds((sample.decode_time - m_last_PTS) / 10000ULL); + // The frame is late, catch up a little + auto next_PTS_practical = m_last_PTS_practical + std::chrono::milliseconds(minFrameDuration); + auto next_PTS = std::max(next_PTS_expected, next_PTS_practical); + if(now < next_PTS) + std::this_thread::sleep_until(next_PTS); + else + next_PTS = now; + //auto text = wxString::Format(L"wxMediaCtrl3 pts diff %ld\n", std::chrono::duration_cast(next_PTS - next_PTS_expected).count()); + //OutputDebugString(text); + m_last_PTS = sample.decode_time; + m_last_PTS_expected = next_PTS_expected; + m_last_PTS_practical = next_PTS; + } else { + // Resync + m_last_PTS = sample.decode_time; + m_last_PTS_expected = now; + m_last_PTS_practical = now; + } + m_frame = bm; + } + CallAfter([this] { Refresh(); }); + } + } + if (tunnel) { + lk.unlock(); + Bambu_Close(tunnel); + Bambu_Destroy(tunnel); + tunnel = nullptr; + lk.lock(); + } + if (m_url == url) + m_error = error; + m_frame_size = wxDefaultSize; + m_video_size = wxDefaultSize; + NotifyStopped(); + } + +} + +void wxMediaCtrl3::NotifyStopped() +{ + m_state = wxMEDIASTATE_STOPPED; + wxMediaEvent event(wxEVT_MEDIA_STATECHANGED); + event.SetId(GetId()); + event.SetEventObject(this); + wxPostEvent(this, event); +} diff --git a/src/slic3r/GUI/wxMediaCtrl3.h b/src/slic3r/GUI/wxMediaCtrl3.h new file mode 100644 index 0000000000..1d64955ffd --- /dev/null +++ b/src/slic3r/GUI/wxMediaCtrl3.h @@ -0,0 +1,85 @@ +// +// wxMediaCtrl3.h +// libslic3r_gui +// +// Created by cmguo on 2024/6/22. +// + +#ifndef wxMediaCtrl3_h +#define wxMediaCtrl3_h + +#include "wx/uri.h" +#include "wx/mediactrl.h" + +wxDECLARE_EVENT(EVT_MEDIA_CTRL_STAT, wxCommandEvent); + +void wxMediaCtrl_OnSize(wxWindow * ctrl, wxSize const & videoSize, int width, int height); + +#define BAMBU_DYNAMIC +#include +#include +#ifndef _WIN32 +#include +#endif +#include "Printer/BambuTunnel.h" + +class AVVideoDecoder; + +class wxMediaCtrl3 : public wxWindow, BambuLib +{ +public: + wxMediaCtrl3(wxWindow *parent); + + ~wxMediaCtrl3(); + + void Load(wxURI url); + + void Play(); + + void Stop(); + + void SetIdleImage(wxString const & image); + + wxMediaState GetState(); + + int GetLastError(); + + wxSize GetVideoSize(); + +protected: + DECLARE_EVENT_TABLE() + + void paintEvent(wxPaintEvent &evt); + + wxSize DoGetBestSize() const override; + + void DoSetSize(int x, int y, int width, int height, int sizeFlags) override; + + static void bambu_log(void *ctx, int level, tchar const *msg); + + void PlayThread(); + + void NotifyStopped(); + +private: + wxString m_idle_image; + wxMediaState m_state = wxMEDIASTATE_STOPPED; + int m_error = 0; + wxSize m_video_size = wxDefaultSize; + wxSize m_frame_size = wxDefaultSize; +#ifdef _WIN32 + wxBitmap m_frame; +#else + wxImage m_frame; +#endif + + std::shared_ptr m_url; + std::uint64_t m_last_PTS{0}; + std::chrono::system_clock::time_point m_last_PTS_expected; + std::chrono::system_clock::time_point m_last_PTS_practical; + std::mutex m_mutex; + std::condition_variable m_cond; + std::thread m_thread; +}; + +#endif /* wxMediaCtrl3_h */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 3cbdc25f5f..c403152c4e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -30,25 +30,47 @@ if (APPLE) target_link_libraries(test_common INTERFACE "-liconv -framework IOKit" "-framework CoreFoundation" -lc++) endif() -# Copies runtime DLLs next to each test executable. Handles both single-config -# generators (CMAKE_BUILD_TYPE set) and multi-config generators (Ninja -# Multi-Config, Visual Studio) where CMAKE_BUILD_TYPE is empty and DLLs must -# land in every per-config output directory. +# Copies runtime shared libraries next to each test executable. Handles both +# single-config generators (CMAKE_BUILD_TYPE set) and multi-config generators +# (Ninja Multi-Config, Visual Studio) where CMAKE_BUILD_TYPE is empty and DLLs +# must land in every per-config output directory. On Windows the loader finds +# DLLs in the executable's directory; the Linux branch below does the same for +# the deps-built FFmpeg libraries and adds an $ORIGIN rpath, since the ELF +# loader does not search the executable's directory and the CI unit-test runner +# only receives the tests artifact (no deps install). function(orcaslicer_copy_test_dlls) - if (NOT WIN32) - return() - endif() - set(_configs ${CMAKE_CONFIGURATION_TYPES}) - if (NOT _configs) - set(_configs "${CMAKE_BUILD_TYPE}") - endif() - foreach(_cfg IN LISTS _configs) - if (_cfg STREQUAL "Debug") - orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" _unused_dlls) - else() - orcaslicer_copy_dlls(COPY_DLLS "${_cfg}" "" _unused_dlls) + if (WIN32) + set(_configs ${CMAKE_CONFIGURATION_TYPES}) + if (NOT _configs) + set(_configs "${CMAKE_BUILD_TYPE}") endif() - endforeach() + foreach(_cfg IN LISTS _configs) + if (_cfg STREQUAL "Debug") + orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" _unused_dlls) + else() + orcaslicer_copy_dlls(COPY_DLLS "${_cfg}" "" _unused_dlls) + endif() + endforeach() + elseif (UNIX AND NOT APPLE) + # Only test executables that link libslic3r_gui pull in the FFmpeg + # shared libraries (src/slic3r/CMakeLists.txt links PkgConfig::LIBAV + # into it). Copy them next to the executable and give it an $ORIGIN + # rpath so the loader finds them when the tests run on the CI unit-test + # runner, which only receives this build/tests tree. + get_target_property(_linked_libs ${_TEST_NAME}_tests LINK_LIBRARIES) + if (NOT "libslic3r_gui" IN_LIST _linked_libs) + return() + endif() + + set_property(TARGET ${_TEST_NAME}_tests PROPERTY BUILD_RPATH "$ORIGIN") + set(_configs ${CMAKE_CONFIGURATION_TYPES}) + if (NOT _configs) + set(_configs "${CMAKE_BUILD_TYPE}") + endif() + foreach(_cfg IN LISTS _configs) + orcaslicer_copy_sos(${_TEST_NAME}_tests "${_cfg}" "" _unused_sos) + endforeach() + endif() endfunction() # Register Catch2 tags as CTest labels so `ctest -L`/`-LE` can filter by tag.