mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-07 23:17:35 -06:00
Support custom IP camera (#3415)
* Support custom IP camera Allow adding custom IP camera source while keeping possible built-in bambulab camera also functional and add button to switch between them. This uses WebView widget to show the stream. Upon loading the page, javascript is used to remove native controls and scroll- bars for aesthetic reasons. * Add partial support for PiP video HTMLVideoElement supports picture-in-picture video but the dedicated control is hidden in this implementation to have more integrated look in OrcaSlicer. Add right-click listener to the camera switch icon that opens the video element in a PiP window. Only works when the video is in <video> HTML element, so for example MJPEG streams in <img> element won't work.
This commit is contained in:
parent
d26513e635
commit
b7b22eb78f
6 changed files with 346 additions and 0 deletions
|
@ -5,6 +5,7 @@
|
|||
#include "Widgets/Button.hpp"
|
||||
#include "Widgets/StepCtrl.hpp"
|
||||
#include "Widgets/SideTools.hpp"
|
||||
#include "Widgets/WebView.hpp"
|
||||
|
||||
#include "BitmapCache.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
@ -889,6 +890,11 @@ StatusBasePanel::StatusBasePanel(wxWindow *parent, wxWindowID id, const wxPoint
|
|||
StatusBasePanel::~StatusBasePanel()
|
||||
{
|
||||
delete m_media_play_ctrl;
|
||||
|
||||
if (m_custom_camera_view) {
|
||||
delete m_custom_camera_view;
|
||||
m_custom_camera_view = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::init_bitmaps()
|
||||
|
@ -922,6 +928,7 @@ void StatusBasePanel::init_bitmaps()
|
|||
m_bitmap_timelapse_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_timelapse_off_dark" : "monitor_timelapse_off", 20);
|
||||
m_bitmap_vcamera_on = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_on_dark" : "monitor_vcamera_on", 20);
|
||||
m_bitmap_vcamera_off = ScalableBitmap(this, wxGetApp().dark_mode() ? "monitor_vcamera_off_dark" : "monitor_vcamera_off", 20);
|
||||
m_bitmap_switch_camera = ScalableBitmap(this, wxGetApp().dark_mode() ? "camera_switch_dark" : "camera_switch", 20);
|
||||
|
||||
}
|
||||
|
||||
|
@ -989,12 +996,27 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
|||
m_setting_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||
m_setting_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||
|
||||
m_camera_switch_button = new wxStaticBitmap(m_panel_monitoring_title, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize(FromDIP(38), FromDIP(24)), 0);
|
||||
m_camera_switch_button->SetMinSize(wxSize(FromDIP(38), FromDIP(24)));
|
||||
m_camera_switch_button->SetBackgroundColour(STATUS_TITLE_BG);
|
||||
m_camera_switch_button->SetBitmap(m_bitmap_switch_camera.bmp());
|
||||
m_camera_switch_button->Bind(wxEVT_LEFT_DOWN, &StatusBasePanel::on_camera_switch_toggled, this);
|
||||
m_camera_switch_button->Bind(wxEVT_RIGHT_DOWN, [this](auto& e) {
|
||||
const std::string js_request_pip = R"(
|
||||
document.querySelector('video').requestPictureInPicture();
|
||||
)";
|
||||
m_custom_camera_view->RunScript(js_request_pip);
|
||||
});
|
||||
m_camera_switch_button->Hide();
|
||||
|
||||
m_bitmap_sdcard_img->SetToolTip(_L("SD Card"));
|
||||
m_bitmap_timelapse_img->SetToolTip(_L("Timelapse"));
|
||||
m_bitmap_recording_img->SetToolTip(_L("Video"));
|
||||
m_bitmap_vcamera_img->SetToolTip(_L("Go Live"));
|
||||
m_setting_button->SetToolTip(_L("Camera Setting"));
|
||||
m_camera_switch_button->SetToolTip(_L("Switch Camera View"));
|
||||
|
||||
bSizer_monitoring_title->Add(m_camera_switch_button, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_sdcard_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_timelapse_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
bSizer_monitoring_title->Add(m_bitmap_recording_img, 0, wxALIGN_CENTER_VERTICAL | wxALL, FromDIP(5));
|
||||
|
@ -1014,17 +1036,44 @@ wxBoxSizer *StatusBasePanel::create_monitoring_page()
|
|||
m_media_ctrl = new wxMediaCtrl2(this);
|
||||
m_media_ctrl->SetMinSize(wxSize(PAGE_MIN_WIDTH, FromDIP(288)));
|
||||
|
||||
m_custom_camera_view = WebView::CreateWebView(this, wxEmptyString);
|
||||
m_custom_camera_view->EnableContextMenu(false);
|
||||
Bind(wxEVT_WEBVIEW_NAVIGATING, &StatusBasePanel::on_webview_navigating, this, m_custom_camera_view->GetId());
|
||||
|
||||
m_media_play_ctrl = new MediaPlayCtrl(this, m_media_ctrl, wxDefaultPosition, wxSize(-1, FromDIP(40)));
|
||||
m_custom_camera_view->Hide();
|
||||
m_custom_camera_view->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, [this](wxWebViewEvent& evt) {
|
||||
if (evt.GetString() == "leavepictureinpicture") {
|
||||
// When leaving PiP, video gets paused in some cases and toggling play
|
||||
// programmatically does not work.
|
||||
m_custom_camera_view->Reload();
|
||||
}
|
||||
else if (evt.GetString() == "enterpictureinpicture") {
|
||||
toggle_builtin_camera();
|
||||
}
|
||||
});
|
||||
|
||||
sizer->Add(m_media_ctrl, 1, wxEXPAND | wxALL, 0);
|
||||
sizer->Add(m_custom_camera_view, 1, wxEXPAND | wxALL, 0);
|
||||
sizer->Add(m_media_play_ctrl, 0, wxEXPAND | wxALL, 0);
|
||||
// media_ctrl_panel->SetSizer(bSizer_monitoring);
|
||||
// media_ctrl_panel->Layout();
|
||||
//
|
||||
// sizer->Add(media_ctrl_panel, 1, wxEXPAND | wxALL, 1);
|
||||
|
||||
if (wxGetApp().app_config->get("camera", "enable_custom_source") == "true") {
|
||||
handle_camera_source_change();
|
||||
}
|
||||
|
||||
return sizer;
|
||||
}
|
||||
|
||||
void StatusBasePanel::on_webview_navigating(wxWebViewEvent& evt) {
|
||||
wxGetApp().CallAfter([this] {
|
||||
remove_controls();
|
||||
});
|
||||
}
|
||||
|
||||
wxBoxSizer *StatusBasePanel::create_machine_control_page(wxWindow *parent)
|
||||
{
|
||||
wxBoxSizer *bSizer_right = new wxBoxSizer(wxVERTICAL);
|
||||
|
@ -3863,6 +3912,7 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
|||
}
|
||||
sdcard_hint_dlg->on_show();
|
||||
});
|
||||
m_camera_popup->Bind(EVT_CAM_SOURCE_CHANGE, &StatusPanel::on_camera_source_change, this);
|
||||
wxWindow* ctrl = (wxWindow*)event.GetEventObject();
|
||||
wxPoint pos = ctrl->ClientToScreen(wxPoint(0, 0));
|
||||
wxSize sz = ctrl->GetSize();
|
||||
|
@ -3874,6 +3924,71 @@ void StatusPanel::on_camera_enter(wxMouseEvent& event)
|
|||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::on_camera_source_change(wxCommandEvent& event)
|
||||
{
|
||||
handle_camera_source_change();
|
||||
}
|
||||
|
||||
void StatusBasePanel::handle_camera_source_change()
|
||||
{
|
||||
const auto new_cam_url = wxGetApp().app_config->get("camera", "custom_source");
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
|
||||
if (enabled && !new_cam_url.empty()) {
|
||||
m_custom_camera_view->LoadURL(new_cam_url);
|
||||
toggle_custom_camera();
|
||||
m_camera_switch_button->Show();
|
||||
} else {
|
||||
toggle_builtin_camera();
|
||||
m_camera_switch_button->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::toggle_builtin_camera()
|
||||
{
|
||||
m_custom_camera_view->Hide();
|
||||
m_media_ctrl->Show();
|
||||
m_media_play_ctrl->Show();
|
||||
}
|
||||
|
||||
void StatusBasePanel::toggle_custom_camera()
|
||||
{
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
|
||||
if (enabled) {
|
||||
m_custom_camera_view->Show();
|
||||
m_media_ctrl->Hide();
|
||||
m_media_play_ctrl->Hide();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::on_camera_switch_toggled(wxMouseEvent& event)
|
||||
{
|
||||
const auto enabled = wxGetApp().app_config->get("camera", "enable_custom_source") == "true";
|
||||
if (enabled && m_media_ctrl->IsShown()) {
|
||||
toggle_custom_camera();
|
||||
} else {
|
||||
toggle_builtin_camera();
|
||||
}
|
||||
}
|
||||
|
||||
void StatusBasePanel::remove_controls()
|
||||
{
|
||||
const std::string js_cleanup_video_element = R"(
|
||||
document.body.style.overflow='hidden';
|
||||
const video = document.querySelector('video');
|
||||
video.setAttribute('style', 'width: 100% !important;');
|
||||
video.removeAttribute('controls');
|
||||
video.addEventListener('leavepictureinpicture', () => {
|
||||
window.wx.postMessage('leavepictureinpicture');
|
||||
});
|
||||
video.addEventListener('enterpictureinpicture', () => {
|
||||
window.wx.postMessage('enterpictureinpicture');
|
||||
});
|
||||
)";
|
||||
m_custom_camera_view->RunScript(js_cleanup_video_element);
|
||||
}
|
||||
|
||||
void StatusPanel::on_camera_leave(wxMouseEvent& event)
|
||||
{
|
||||
if (obj && m_camera_popup) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue