mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-30 05:17:19 +00:00
Port FFmpeg based camera view from BambuStudio (#15234)
* Add ffmepg dep
* NEW: reimpl wxMediaCtrl from ffmpeg
Jira: none
Change-Id: I46a47118a7649b2a50fcce8911e2888342ef25de
(cherry picked from commit d6c7f08769c8cfdbbf0e80ad280c9b3408a3c27d)
(cherry picked from commit 94d91be60bfe9bbbcdd21f85b46abc3faf126f17)
* FIX: reset bambu lib after restart network plugin
Change-Id: I4a3a4b7420745835ca3fa00c6edebe9d8d98cbf6
Jira: STUDIO-7571
(cherry picked from commit 28d9c6743fae80bfd40e4ee391e30d62cb16d4ab)
* FIX: ffmpeg decoder memory leak
Change-Id: I997572b5730618a969959f9b24c405d80fa9f83c
Jira: STUDIO-7597
(cherry picked from commit 342cea29bd9593fa89cbb33caff58055b46ebeec)
* FIX: install ffmpeg symbolic sos
Change-Id: Ia4a45182cefcf62a7a4b4a5c89c92251609c5a68
Jira: none
(cherry picked from commit b7f8fa1efdbe0ac2cc896ca24f063f5894fe9f90)
* FIX: ffmpeg swscale & frame_size
Change-Id: I9f4cb8c739b726f7e5cdbe0df7ed06b2eb2154d5
Jira: STUDIO-7624
(cherry picked from commit 5a2c75d835fb437667b590a803eef148baa30875)
* FIX: wxMediaCtrl3 idle image & center pos
Change-Id: Ib9652573e31bfd6229f174c0a1388942d9d98822
Jira: STUDIO-7633
(cherry picked from commit d51247c46e26460b151de79c598d81151280e79c)
* FIX: AVVideoDecoder sws_ctx_ == nullptr on zero size
Change-Id: I9698354bb1f341e276ec9780d4ef4fcd9f8a1028
Jira: STUDIO-7706
(cherry picked from commit ff622e25026a8471c39eb308cf5b115c4a9d84aa)
* fix:cannot open shared object file on linux
Change-Id: Ica66500506cfe8932eac3ae0a58fb7ff30d1da9b
jira:none
(cherry picked from commit febd1aeb4d453bc96571fa5e5727e9e10046cb80)
(cherry picked from commit 5ad579f929154779abd84b01438fd235c647dbf5)
* NEW:add ffmepg build Cmake
buildLinuxImage add ffmpeg so file
jira:nojira
Change-Id: I3e1be53aa58a179b8d9ae048ed7538de3ae8d111
(cherry picked from commit 2d70a1bcb6a5ba601525b08a38e7610f018fe106)
* FIX: ffmpeg cmake install error
jira:nojira
Change-Id: I74cc0f7c86b5364e55cad2af2bd9a82306ee6864
(cherry picked from commit 805df79e3bb044dac29ec1c06736751ccf3675f9)
* FIX: decode video to wxImage on Linux
Change-Id: I5e332a1b0622b3dfc70ac5c4c3bfa62b3411ebdc
Jira: none
(cherry picked from commit c787ba921a31f259e8eb23fd59f178e96279caf9)
* FIX: wxMediaCtrl3 enter Stopped state soon
Change-Id: I120e9d4b9f85599a184650d1d95fe2bec42af171
Jira: STUDIO-8280
(cherry picked from commit 7648d96305d510b9e97f22124961de5115cde830)
* FIX: reset decode buffer zero when scale width changed
Change-Id: Iaa2f99111dd5f7228b7b25e1be0a8cbdbfe982a6
Jira: STUDIO-8422
(cherry picked from commit 659ebc7d07a8f6045ba5443141b44277d7257cec)
* slic3r: Fix missing declarations in wxMediaCtrl3.h
src/slic3r/GUI/wxMediaCtrl3.h:80:10: error: ‘condition_variable’ in namespace ‘std’ does not name a type
80 | std::condition_variable m_cond;
| ^~~~~~~~~~~~~~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::condition_variable’ is defined in header ‘<condition_variable>’; did you forget to ‘#include <condition_variable>’?
26 | #include "Printer/BambuTunnel.h"
+++ |+#include <condition_variable>
27 |
src/slic3r/GUI/wxMediaCtrl3.h:81:10: error: ‘thread’ in namespace ‘std’ does not name a type
81 | std::thread m_thread;
| ^~~~~~
src/slic3r/GUI/wxMediaCtrl3.h:27:1: note: ‘std::thread’ is defined in header ‘<thread>’; did you forget to ‘#include <thread>’?
26 | #include "Printer/BambuTunnel.h"
+++ |+#include <thread>
27 |
In file included from src/slic3r/GUI/MediaPlayCtrl.h:17,
from src/slic3r/GUI/MediaPlayCtrl.cpp:1:
src/slic3r/GUI/wxMediaCtrl3.h:77:13: error: field ‘m_frame’ has incomplete type ‘wxImage’
77 | wxImage m_frame;
| ^~~~~~~
(cherry picked from commit 727a73333bd67acf5ff2b1c51ff284c2bacdb413)
* slic3r: Fix missing includes in AVVideoDecoder
In file included from src/slic3r/GUI/AVVideoDecoder.cpp:1:
src/slic3r/GUI/AVVideoDecoder.hpp:28:20: error: ‘wxImage’ has not been declared
28 | bool toWxImage(wxImage &image, wxSize const &size);
| ^~~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:28:36: error: ‘wxSize’ has not been declared
28 | bool toWxImage(wxImage &image, wxSize const &size);
| ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:38:10: error: ‘vector’ in namespace ‘std’ does not name a template type
38 | std::vector<uint8_t> bits_;
| ^~~~~~
src/slic3r/GUI/AVVideoDecoder.hpp:9:1: note: ‘std::vector’ is defined in header ‘<vector>’; did you forget to ‘#include <vector>’?
8 | #include <libswscale/swscale.h>
+++ |+#include <vector>
9 | }
src/slic3r/GUI/AVVideoDecoder.cpp:145:89: error: invalid use of incomplete type ‘class wxBitmap’
145 | bitmap = wxBitmap((char const *) bits_.data(), size.GetWidth(), size.GetHeight(), 32);
| ^
(cherry picked from commit 781ce14e061366da64fdc2d0d592fa35ee57e67e)
* slic3r: Fix missing includes in wxMediaCtrl2
src/slic3r/GUI/wxMediaCtrl2.cpp: In lambda function:
src/slic3r/GUI/wxMediaCtrl2.cpp:170:13: error: ‘wxMessageBox’ was not declared in this scope; did you mean ‘wxInfoMessageBox’?
170 | 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 Bambu Studio?)"), _L("Error"), wxOK);
| ^~~~~~~~~~~~
| wxInfoMessageBox
src/slic3r/GUI/wxMediaCtrl2.cpp: In member function ‘void wxMediaCtrl2::Load(wxURI)’:
src/slic3r/GUI/wxMediaCtrl2.cpp:179:5: error: ‘wxLog’ has not been declared
179 | wxLog::EnableLogging(false);
| ^~~~~
(cherry picked from commit 73908d38d8b1f7c8dcae92d55711bc08cbfff23c)
* slic3r: Fix missing wxPaintDC declaration
src/slic3r/GUI/wxMediaCtrl3.cpp: In member function ‘void wxMediaCtrl3::paintEvent(wxPaintEvent&)’:
src/slic3r/GUI/wxMediaCtrl3.cpp:121:5: error: ‘wxPaintDC’ was not declared in this scope; did you mean ‘wxPoint’?
121 | wxPaintDC dc(this);
| ^~~~~~~~~
| wxPoint
(cherry picked from commit 9ab5009235d212699f91e01d7f930f92849ed1e3)
* slic3r: Fix missing BOOST_LOG_TRIVIAL declaration
src/slic3r/GUI/wxMediaCtrl3.cpp:181:23: error: ‘info’ was not declared in this scope
181 | BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
| ^~~~
src/slic3r/GUI/wxMediaCtrl3.cpp:181:5: error: ‘BOOST_LOG_TRIVIAL’ was not declared in this scope
181 | BOOST_LOG_TRIVIAL(info) << msg.ToUTF8().data();
| ^~~~~~~~~~~~~~~~~
(cherry picked from commit c5c41e20ca2fc7f3b53a4c769961f73df6992008)
* FIX: wxMediaCtrl3 zero size crash
Change-Id: I16a3f7b3afe142bb957a1740b8e8c9820c92b349
Jira: STUDIO-8522
(cherry picked from commit 8cdaea1162ccbcc0bd03ecd99346f3b9cf52cf64)
* FIX: TabCtrl button margin
Change-Id: If8b05a4ef9efb8b57989ee1de6543631e5a3cf90
Jira: STUDIO-8265
(cherry picked from commit 1c5e65707109ad0582b6442cf8e515344f799c27)
* ENH: wxMediaCtrl3 display video frame at pts
Change-Id: I8847236d2307101e5f2befc6477cd20b3691841c
Jira: none
(cherry picked from commit 05328da4612c11d50f6fd90e872b97f5f8f46b1d)
* Fix: fix memory leak caused by ffmpeg decoding
Change-Id: I162ad4ea8d4601c1ffe17a65f292566c9dea6f0b
jira: no-jira
(cherry picked from commit eb20d03186c86b7398b97e3bae0a3c7a7b81c58c)
* ENH: update some missing codes
jira: no-jira
Change-Id: Icb2da53911430ac144b0fb601637a7ad31e7e8db
(cherry picked from commit 13b4213f8a24c76c16e49daf905fa29c0f646a5a)
* Fix build
* Update idle image
* Attempt to fix Windows CI build
* FIX: GTK video window resize ran in a free function without member access
wxMediaCtrl_OnSize referenced wxMediaCtrl2's private m_gtk_video_window,
which does not compile on Linux/GTK. Move the resizing into
wxMediaCtrl2::DoSetSize where the member is in scope.
* Install required tools for Linux
* Install required tools for macOS
* Add ffmpeg to flatpak
* Fix Linux build
* Try fix appimage build
* Fix Linux AppImage bundling of deps-built shared libraries
The AppImage dependency closure resolves each bundled ELF's DT_NEEDED
entries with plain ldd, which cannot resolve the deps-built FFmpeg stack
(libavcodec/libavutil/libswscale) once it is copied into the bundle:
those libs are not installed in any standard loader path and carry no
RUNPATH of their own, so ldd reports the siblings as missing and the
build aborts. Extend the loader path with the bundle directory plus the
source directories of already-bundled files (mirroring
scripts/check_appimage_libs.sh), and key the dedup set on the bundled
file path instead of the source path so dependencies resolved from the
bundle directory are not copied onto themselves.
Co-Authored-By: Claude <noreply@anthropic.com>
* Attempt to fix Linux unit test
* Fix Linux unit tests loading deps-built FFmpeg libraries
The test executables that link libslic3r_gui (which links PkgConfig::LIBAV)
have a load-time dependency on the deps-built FFmpeg shared libraries. The CI
unit-test runner only receives the tests artifact, so those libraries were
unresolvable there (Ubuntu 24.04 ships libavcodec.so.60, not .61). Copy the
libraries next to each affected test executable and give it an $ORIGIN rpath,
mirroring the Windows branch that copies DLLs next to every test executable.
orcaslicer_copy_sos now places the copies in the per-config output directory
for multi-config generators, like orcaslicer_copy_dlls does.
Co-Authored-By: Claude <noreply@anthropic.com>
* Add design doc for macOS FFmpeg player
Co-Authored-By: Claude <noreply@anthropic.com>
* Add implementation plan for macOS FFmpeg player
Co-Authored-By: Claude <noreply@anthropic.com>
* feat: use FFmpeg media player on macOS with static FFmpeg
* build: build static-only FFmpeg for macOS deps
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: remove old BambuPlayer-based media player from macOS
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: add static FFmpeg NOTFOUND guard; drop dead wxMediaCtrl2.h include
* build: drop redundant --enable-shared in FFmpeg deps configure
The literal --enable-shared was always overridden by ${_link_cmd}
(--enable-static --disable-shared on Apple, --enable-shared elsewhere)
and FFmpeg configure processes these flags in order, last one wins.
Remove it and the stale comment documenting the workaround.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: remove dead wxMediaCtrl2 player
wxMediaCtrl2 was never instantiated on any platform (USE_WX_MEDIA_CTRL_2
is 0 everywhere); wxMediaCtrl3 replaced it. Delete wxMediaCtrl2.cpp/h,
drop them from the Win/Linux source list and the gettext list.txt, and
collapse the preprocessor-dead #if USE_WX_MEDIA_CTRL_2 gate in
MediaPlayCtrl.h.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor: remove dead GStreamer bambusrc plugin and its build dep
gstbambusrc was the GStreamer source element for the old wxMediaCtrl2
Wayland player, its only consumer (deleted in the previous commit).
The new FFmpeg player handles bambu:/// URIs through the Bambu C API
instead. Drop the plugin and the gstreamer-1.0 / gstreamer-base-1.0
REQUIRED pkg-config dependencies that existed solely for it.
Co-Authored-By: Claude <noreply@anthropic.com>
* build: move FFmpeg media player sources to the common GUI list
wxMediaCtrl3 and AVVideoDecoder are platform-neutral C++ compiled on
all three platforms, so list them once in the common SLIC3R_GUI_SOURCES
instead of duplicating them in the APPLE and non-APPLE branches. The
else() branch is now empty and drops out entirely.
Co-Authored-By: Claude <noreply@anthropic.com>
* deps: disable FFmpeg VideoToolbox/AudioToolbox HW-accel on macOS
The static libavcodec.a/avutil.a compiled the auto-detected
videotoolbox/audiotoolbox objects, which reference VideoToolbox
framework symbols (_VTDecompressionSession*). The app link line
happened to satisfy them transitively, but the orca_stubgen module
link (CI-only) failed with undefined symbols. The player decodes in
software (swscale), so disable both HW-accel paths to keep the
static libs self-contained.
Co-Authored-By: Claude <noreply@anthropic.com>
* Copy the decoded frame instead of aliasing the decoder's buffer
wxImage with static_data set stores the pointer and never copies it, so the
frame handed to wxMediaCtrl3 aliased AVVideoDecoder::bits_. That buffer is
rewritten by the next sws_scale with the mutex released, reallocated by
bits_.resize() when the window grows, and freed outright when the decoder
leaves PlayThread's loop body at end of stream, all while the GUI thread may
be painting from it.
Windows is unaffected either way, since toWxBitmap already copies the bits
into GDI.
---------
Co-authored-by: chunmao.guo <chunmao.guo@bambulab.com>
Co-authored-by: BBL\chuan.he <chuan.he@bambulab.com>
Co-authored-by: MackBambu <yongfang.bian@bambulab.com>
Co-authored-by: Bastien Nocera <hadess@hadess.net>
Co-authored-by: chao.zhang <chao.zhang@bambulab.com>
Co-authored-by: lane.wei <lane.wei@bambulab.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: SoftFever <103989404+SoftFever@users.noreply.github.com>
Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
2
.github/workflows/build_deps.yml
vendored
2
.github/workflows/build_deps.yml
vendored
@@ -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 }}" && \
|
||||
|
||||
7
.github/workflows/build_orca.yml
vendored
7
.github/workflows/build_orca.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
3
deps/CMakeLists.txt
vendored
3
deps/CMakeLists.txt
vendored
@@ -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
|
||||
)
|
||||
|
||||
|
||||
87
deps/FFMPEG/FFMPEG.cmake
vendored
Normal file
87
deps/FFMPEG/FFMPEG.cmake
vendored
Normal file
@@ -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()
|
||||
376
docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md
Normal file
376
docs/superpowers/plans/2026-08-14-ffmpeg-player-macos.md
Normal file
@@ -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.
|
||||
110
docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md
Normal file
110
docs/superpowers/specs/2026-08-14-ffmpeg-player-macos-design.md
Normal file
@@ -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).
|
||||
@@ -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
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 579 KiB |
@@ -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.
|
||||
|
||||
@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
|
||||
wayland-protocols
|
||||
webkit2gtk-4.1
|
||||
wget
|
||||
pkgconf
|
||||
yasm
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -25,6 +25,9 @@ export REQUIRED_DEV_PACKAGES=(
|
||||
wayland-protocols
|
||||
webkit2gtk
|
||||
wget
|
||||
pkgconf
|
||||
yasm
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -20,6 +20,7 @@ export REQUIRED_BUNDLES=(
|
||||
perl-basic
|
||||
texinfo
|
||||
wget
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -27,6 +27,9 @@ REQUIRED_DEV_PACKAGES=(
|
||||
ninja-build
|
||||
texinfo
|
||||
wget
|
||||
pkgconf
|
||||
yasm
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -31,6 +31,9 @@ REQUIRED_DEV_PACKAGES=(
|
||||
webkit2gtk4.1-devel
|
||||
wget
|
||||
libcurl-devel
|
||||
pkgconf
|
||||
yasm
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -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" ]]
|
||||
|
||||
@@ -30,6 +30,9 @@ REQUIRED_DEV_PACKAGES=(
|
||||
webkit2gtk4-devel
|
||||
wget
|
||||
libcurl-devel
|
||||
pkgconf
|
||||
yasm
|
||||
nasm
|
||||
)
|
||||
|
||||
if [[ -n "$UPDATE_LIB" ]]
|
||||
|
||||
@@ -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 ()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
170
src/slic3r/GUI/AVVideoDecoder.cpp
Normal file
170
src/slic3r/GUI/AVVideoDecoder.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "AVVideoDecoder.hpp"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/imgutils.h>
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
46
src/slic3r/GUI/AVVideoDecoder.hpp
Normal file
46
src/slic3r/GUI/AVVideoDecoder.hpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#ifndef AVVIDEODECODER_HPP
|
||||
#define AVVIDEODECODER_HPP
|
||||
|
||||
#include "Printer/BambuTunnel.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
#include <vector>
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/image.h>
|
||||
|
||||
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<uint8_t> bits_;
|
||||
};
|
||||
|
||||
#endif // AVVIDEODECODER_HPP
|
||||
@@ -1,30 +0,0 @@
|
||||
//
|
||||
// BambuPlayer.h
|
||||
// BambuPlayer
|
||||
//
|
||||
// Created by cmguo on 2021/12/6.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <AVFoundation/AVSampleBufferDisplayLayer.h>
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
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
|
||||
@@ -39,13 +39,14 @@ static std::map<int, std::string> 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 == "<play>") {
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#ifndef MediaPlayCtrl_h
|
||||
#define MediaPlayCtrl_h
|
||||
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "wxMediaCtrl3.h"
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
@@ -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<int> m_token = std::make_shared<int>(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<wxString> m_tasks;
|
||||
boost::mutex m_mutex;
|
||||
boost::condition_variable m_cond;
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include "Widgets/AxisCtrlButton.hpp"
|
||||
#include "Widgets/TextInput.hpp"
|
||||
#include "Widgets/StaticLine.hpp"
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "MediaPlayCtrl.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,657 +0,0 @@
|
||||
/* bambusrc for gstreamer
|
||||
* integration with proprietary Bambu Lab blob for getting raw h.264 video
|
||||
*
|
||||
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
|
||||
*
|
||||
* 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 <config.h>
|
||||
#endif
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
#include "gstbambusrc.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#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 <joshua@accelerated.tech>");
|
||||
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
|
||||
@@ -1,78 +0,0 @@
|
||||
/* bambusrc for gstreamer
|
||||
* integration with proprietary Bambu Lab blob for getting raw h.264 video
|
||||
*
|
||||
* Copyright (C) 2023 Joshua Wise <joshua@accelerated.tech>
|
||||
*
|
||||
* 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 <gst/gst.h>
|
||||
#include <gst/base/gstpushsrc.h>
|
||||
#include <glib.h>
|
||||
|
||||
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__ */
|
||||
@@ -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<std::string> 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<wxStaticBitmap *>(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 <std::pair<wxStaticBitmap * ,wxString>> ScoreDialog::add_need_upload_imgs()
|
||||
{
|
||||
{
|
||||
std::set<std::pair<wxStaticBitmap *, wxString>> 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<wxStaticBitmap *, ScoreDialog::ImageMsg> 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::vector<std::pair<wxString, st
|
||||
m_main_sizer->Add(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::vector<std::pair<wxString, st
|
||||
return m_main_sizer;
|
||||
}
|
||||
|
||||
ScoreData ScoreDialog::get_score_data() {
|
||||
ScoreData ScoreDialog::get_score_data() {
|
||||
ScoreData score_data;
|
||||
score_data.rating_id = m_rating_id;
|
||||
score_data.design_id = m_design_id;
|
||||
@@ -6164,20 +6164,20 @@ ScoreData ScoreDialog::get_score_data() {
|
||||
score_data.comment_text = m_comment_text->GetValue();
|
||||
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<std::string> cloud_bitmaps)
|
||||
{
|
||||
{
|
||||
m_image_url_paths = cloud_bitmaps;
|
||||
for (std::string &url : cloud_bitmaps) {
|
||||
if (std::string::npos == url.find(m_model_id)) continue;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/gbsizer.h>
|
||||
#include <wx/webrequest.h>
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "MediaPlayCtrl.h"
|
||||
#include "AMSSetting.hpp"
|
||||
#include "Calibration.hpp"
|
||||
@@ -195,11 +194,11 @@ public:
|
||||
void set_cloud_bitmap(std::vector<std::string> 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<int> m_tocken;
|
||||
@@ -217,7 +216,7 @@ protected:
|
||||
{
|
||||
wxString local_image_url; //local image path
|
||||
std::string img_url_paths; // oss url path
|
||||
vector<wxPanel *> image_broad;
|
||||
vector<wxPanel *> image_broad;
|
||||
bool is_selected;
|
||||
bool is_uploaded; // load
|
||||
wxBoxSizer * image_tb_broad = nullptr;
|
||||
@@ -252,7 +251,7 @@ protected:
|
||||
std::set<std::pair<wxStaticBitmap *, wxString>> add_need_upload_imgs();
|
||||
std::pair<wxStaticBitmap *, ImageMsg> create_local_thumbnail(wxString &local_path);
|
||||
std::pair<wxStaticBitmap *, ImageMsg> 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(); }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,633 +0,0 @@
|
||||
#include "wxMediaCtrl2.h"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "LinuxDisplayBackend.hpp"
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <string>
|
||||
#ifdef __WIN32__
|
||||
#include <winuser.h>
|
||||
#include <versionhelpers.h>
|
||||
#include <wx/msw/registry.h>
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#ifdef __LINUX__
|
||||
#include "Printer/gstbambusrc.h"
|
||||
#include <gst/gst.h> // main gstreamer header
|
||||
#endif
|
||||
|
||||
#if defined(__LINUX__) && defined(__WXGTK__)
|
||||
#include <gtk/gtk.h>
|
||||
#include <wx/nativewin.h>
|
||||
|
||||
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<wxGStreamerMediaBackend *>(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<wxMediaCtrl2 *>(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<std::string>(GetHandle())).append("&tid=").append(
|
||||
boost::lexical_cast<std::string>(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<wxSize&>(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
|
||||
@@ -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<wxMediaState>(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 */
|
||||
@@ -1,170 +0,0 @@
|
||||
//
|
||||
// wxMediaCtrl2.m
|
||||
// OrcaSlicer
|
||||
//
|
||||
// Created by cmguo on 2021/12/7.
|
||||
//
|
||||
|
||||
#import "wxMediaCtrl2.h"
|
||||
#import "wx/mediactrl.h"
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "BambuPlayer/BambuPlayer.h"
|
||||
#import "../Utils/NetworkAgent.hpp"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
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<wxSize&>(m_video_size) = {(int) size.width, (int) size.height};
|
||||
return {(int) size.width, (int) size.height};
|
||||
} else {
|
||||
return {0, 0};
|
||||
}
|
||||
}
|
||||
313
src/slic3r/GUI/wxMediaCtrl3.cpp
Normal file
313
src/slic3r/GUI/wxMediaCtrl3.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
#include "wxMediaCtrl3.h"
|
||||
#include "AVVideoDecoder.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/dcclient.h>
|
||||
#ifdef __WIN32__
|
||||
#include <versionhelpers.h>
|
||||
#include <wx/msw/registry.h>
|
||||
#include <shellapi.h>
|
||||
#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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> lk(m_mutex);
|
||||
m_frame = wxImage(m_idle_image);
|
||||
assert(m_frame.IsOk());
|
||||
Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
wxMediaState wxMediaCtrl3::GetState()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_state;
|
||||
}
|
||||
|
||||
int wxMediaCtrl3::GetLastError()
|
||||
{
|
||||
std::unique_lock<std::mutex> lk(m_mutex);
|
||||
return m_error;
|
||||
}
|
||||
|
||||
wxSize wxMediaCtrl3::GetVideoSize()
|
||||
{
|
||||
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<wxURI> url;
|
||||
std::unique_lock<std::mutex> 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<std::chrono::milliseconds>(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);
|
||||
}
|
||||
85
src/slic3r/GUI/wxMediaCtrl3.h
Normal file
85
src/slic3r/GUI/wxMediaCtrl3.h
Normal file
@@ -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 <condition_variable>
|
||||
#include <thread>
|
||||
#ifndef _WIN32
|
||||
#include <wx/image.h>
|
||||
#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<wxURI> 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 */
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user