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:
Tuomas Salokanto 2024-01-14 10:18:30 +02:00 committed by GitHub
parent d26513e635
commit b7b22eb78f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 346 additions and 0 deletions

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="39"
height="22"
viewBox="0 0 39 22"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="camera_switch.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5">
<clipPath
id="clip0_7112_29362">
<rect
id="svg_1"
fill="#ffffff"
height="30"
width="30"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="16"
inkscape:cx="6.84375"
inkscape:cy="11.21875"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="false" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
fill="#b3b3b5"
id="path1"
style="fill:#262e30;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
fill="#b3b3b5"
id="path4"
style="fill:#262e30;fill-opacity:1;stroke-width:1.05691" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
fill="#b3b3b5"
id="path1-5"
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#262e30;fill-opacity:1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
fill="#b3b3b5"
id="path4-1"
style="stroke-width:1.05691;fill:#262e30;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="39"
height="22"
viewBox="0 0 39 22"
fill="none"
version="1.1"
id="svg5"
sodipodi:docname="camera_switch_dark.svg"
inkscape:version="1.3.2 (091e20e, 2023-11-25, custom)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs5">
<clipPath
id="clip0_7112_29362">
<rect
id="svg_1"
fill="#ffffff"
height="30"
width="30"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="16"
inkscape:cx="6.84375"
inkscape:cy="16.21875"
inkscape:window-width="2560"
inkscape:window-height="1369"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg5"
showguides="false" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.066182,15.123836 h 1.463398 1.463399 1.463398 1.463399 m 0,0 h 1.463398 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 2.600884 c 0,-1.1674317 -0.946358,-2.11382107 -2.113821,-2.11382107 H 16.675987 c -1.167411,0 -2.1138,0.94637877 -2.1138,2.11381047 v 1.3011427 1.3011428 1.3011427 1.3011427 h 1.268271 V 6.5043016 5.2031589 3.9020161 2.6008734 c 0,-0.4669642 0.378586,-0.8455179 0.845529,-0.8455179 h 11.707187 c 0.467048,0 0.845528,0.3785537 0.845528,0.8455285 v 10.409131 c 0,0.467049 -0.37848,0.845529 -0.845528,0.845529 h -1.463398 m 0,0 H 25.456377 23.992979 22.52958 21.066182 m 0,0 v 1.268292"
fill="#b3b3b5"
id="path1"
style="fill:#b3b3b5;fill-opacity:1;stroke:none;stroke-width:0;stroke-dasharray:none"
sodipodi:nodetypes="ccccccssssssccccccccsssssscccccccc" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 31.074174,7.804414 4.093731,3.449544 V 4.3548482 Z M 30.065035,6.9961839 c -0.501081,0.4222886 -0.501081,1.1942138 0,1.6164496 l 4.633179,3.9041215 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 V 3.9003132 c 0,-0.8987967 -1.050675,-1.3873959 -1.737984,-0.80823 z"
fill="#b3b3b5"
id="path4"
style="fill:#b3b3b5;fill-opacity:1;stroke-width:1.05691" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M 19.153511,8.3696736 H 7.4463245 c -0.4669431,0 -0.8455285,0.3785536 -0.8455285,0.8455179 V 19.624333 c 0,0.467049 0.3785854,0.845528 0.8455285,0.845528 H 19.153511 c 0.467048,0 0.845528,-0.378479 0.845528,-0.845528 V 9.215202 c 0,-0.4669748 -0.37848,-0.8455284 -0.845528,-0.8455284 z M 7.4463245,7.1013809 c -1.1674106,0 -2.1138,0.9463789 -2.1138,2.1138106 V 19.624333 c 0,1.167463 0.9463894,2.113821 2.1138,2.113821 H 19.153511 c 1.167463,0 2.113821,-0.946358 2.113821,-2.113821 V 9.215202 c 0,-1.1674317 -0.946358,-2.1138211 -2.113821,-2.1138211 z"
fill="#b3b3b5"
id="path1-5"
style="stroke:none;stroke-width:0;stroke-dasharray:none;fill:#b3b3b5;fill-opacity:1" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 21.844511,14.418731 4.093731,3.449545 v -6.89911 z m -1.009139,-0.80823 c -0.501081,0.422289 -0.501081,1.194214 0,1.61645 l 4.633179,3.904122 c 0.687309,0.579187 1.737984,0.09058 1.737984,-0.80822 v -7.808222 c 0,-0.8987971 -1.050675,-1.3873956 -1.737984,-0.8082304 z"
fill="#b3b3b5"
id="path4-1"
style="stroke-width:1.05691;fill:#b3b3b5;fill-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -23,6 +23,7 @@ wxEND_EVENT_TABLE()
wxDEFINE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDEFINE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
#define CAMERAPOPUP_CLICK_INTERVAL 20
@ -78,6 +79,34 @@ CameraPopup::CameraPopup(wxWindow *parent)
top_sizer->Add(0, 0, wxALL, 0);
}
// custom IP camera
m_custom_camera_input_confirm = new Button(m_panel, _L("Enable"));
m_custom_camera_input_confirm->SetBackgroundColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetBorderColor(wxColour(38, 166, 154));
m_custom_camera_input_confirm->SetTextColor(wxColour(0xFFFFFE));
m_custom_camera_input_confirm->SetFont(Label::Body_14);
m_custom_camera_input_confirm->SetMinSize(wxSize(FromDIP(90), FromDIP(30)));
m_custom_camera_input_confirm->SetPosition(wxDefaultPosition);
m_custom_camera_input_confirm->SetCornerRadius(FromDIP(12));
m_custom_camera_input = new TextInput(m_panel, wxEmptyString, wxEmptyString, wxEmptyString, wxDefaultPosition, wxDefaultSize);
m_custom_camera_input->GetTextCtrl()->SetHint(_L("Hostname or IP"));
m_custom_camera_input->GetTextCtrl()->SetFont(Label::Body_14);
m_custom_camera_hint = new wxStaticText(m_panel, wxID_ANY, _L("Custom camera source"));
m_custom_camera_hint->Wrap(-1);
m_custom_camera_hint->SetFont(Label::Head_14);
m_custom_camera_hint->SetForegroundColour(TEXT_COL);
m_custom_camera_input_confirm->Bind(wxEVT_BUTTON, &CameraPopup::on_camera_source_changed, this);
if (!wxGetApp().app_config->get("camera", "custom_source").empty()) {
m_custom_camera_input->GetTextCtrl()->SetValue(wxGetApp().app_config->get("camera", "custom_source"));
set_custom_cam_button_state(wxGetApp().app_config->get("camera", "enable_custom_source") == "true");
}
top_sizer->Add(m_custom_camera_hint, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxALL, FromDIP(5));
top_sizer->Add(0, 0, wxALL, 0);
top_sizer->Add(m_custom_camera_input, 2, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxEXPAND | wxALL, FromDIP(5));
top_sizer->Add(m_custom_camera_input_confirm, 1, wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT | wxALL, FromDIP(5));
main_sizer->Add(top_sizer, 0, wxALL, FromDIP(10));
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/virtual-camera", L"en");
@ -132,6 +161,37 @@ void CameraPopup::sdcard_absent_hint()
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::on_camera_source_changed(wxCommandEvent &event)
{
if (m_obj && !m_custom_camera_input->GetTextCtrl()->IsEmpty()) {
handle_camera_source_change();
}
}
void CameraPopup::handle_camera_source_change()
{
m_custom_camera_enabled = !m_custom_camera_enabled;
set_custom_cam_button_state(m_custom_camera_enabled);
wxGetApp().app_config->set("camera", "custom_source", m_custom_camera_input->GetTextCtrl()->GetValue().ToStdString());
wxGetApp().app_config->set("camera", "enable_custom_source", m_custom_camera_enabled);
wxCommandEvent evt(EVT_CAM_SOURCE_CHANGE);
evt.SetEventObject(this);
GetEventHandler()->ProcessEvent(evt);
}
void CameraPopup::set_custom_cam_button_state(bool state)
{
m_custom_camera_enabled = state;
auto stateColour = state ? wxColour(170, 0, 0) : wxColour(38, 166, 154);
auto stateText = state ? "Disable" : "Enable";
m_custom_camera_input_confirm->SetBackgroundColor(stateColour);
m_custom_camera_input_confirm->SetBorderColor(stateColour);
m_custom_camera_input_confirm->SetLabel(_L(stateText));
}
void CameraPopup::on_switch_recording(wxCommandEvent& event)
{
if (!m_obj) return;

View file

@ -14,12 +14,14 @@
#include "Widgets/SwitchButton.hpp"
#include "Widgets/RadioBox.hpp"
#include "Widgets/PopupWindow.hpp"
#include "Widgets/TextInput.hpp"
namespace Slic3r {
namespace GUI {
wxDECLARE_EVENT(EVT_VCAMERA_SWITCH, wxMouseEvent);
wxDECLARE_EVENT(EVT_SDCARD_ABSENT_HINT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CAM_SOURCE_CHANGE, wxCommandEvent);
class CameraPopup : public PopupWindow
{
@ -50,6 +52,9 @@ protected:
void on_switch_recording(wxCommandEvent& event);
void on_set_resolution();
void sdcard_absent_hint();
void on_camera_source_changed(wxCommandEvent& event);
void handle_camera_source_change();
void set_custom_cam_button_state(bool state);
wxWindow * create_item_radiobox(wxString title, wxWindow *parent, wxString tooltip, int padding_left);
void select_curr_radiobox(int btn_idx);
@ -66,6 +71,10 @@ private:
SwitchButton* m_switch_recording;
wxStaticText* m_text_vcamera;
SwitchButton* m_switch_vcamera;
wxStaticText* m_custom_camera_hint;
TextInput* m_custom_camera_input;
Button* m_custom_camera_input_confirm;
bool m_custom_camera_enabled{ false };
wxStaticText* m_text_resolution;
wxWindow* m_resolution_options[RESOLUTION_OPTIONS_NUM];
wxScrolledWindow *m_panel;

View file

@ -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) {

View file

@ -287,6 +287,7 @@ protected:
ScalableBitmap m_bitmap_timelapse_off;
ScalableBitmap m_bitmap_vcamera_on;
ScalableBitmap m_bitmap_vcamera_off;
ScalableBitmap m_bitmap_switch_camera;
/* title panel */
wxPanel * media_ctrl_panel;
@ -307,6 +308,7 @@ protected:
wxStaticBitmap *m_bitmap_sdcard_img;
wxStaticBitmap *m_bitmap_static_use_time;
wxStaticBitmap *m_bitmap_static_use_weight;
wxStaticBitmap* m_camera_switch_button;
wxMediaCtrl2 * m_media_ctrl;
@ -326,6 +328,7 @@ protected:
ScalableButton *m_button_pause_resume;
ScalableButton *m_button_abort;
Button * m_button_clean;
wxWebView * m_custom_camera_view{nullptr};
wxStaticText * m_text_tasklist_caption;
@ -410,6 +413,13 @@ protected:
virtual void on_axis_ctrl_z_down_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_e_up_10(wxCommandEvent &event) { event.Skip(); }
virtual void on_axis_ctrl_e_down_10(wxCommandEvent &event) { event.Skip(); }
void on_camera_source_change(wxCommandEvent& event);
void handle_camera_source_change();
void remove_controls();
void on_webview_navigating(wxWebViewEvent& evt);
void on_camera_switch_toggled(wxMouseEvent& event);
void toggle_custom_camera();
void toggle_builtin_camera();
public:
StatusBasePanel(wxWindow * parent,