2022-07-15 15:37:19 +00:00
|
|
|
#include "MediaPlayCtrl.h"
|
|
|
|
#include "Widgets/Button.hpp"
|
|
|
|
#include "Widgets/CheckBox.hpp"
|
|
|
|
#include "Widgets/Label.hpp"
|
|
|
|
#include "GUI_App.hpp"
|
|
|
|
#include "libslic3r/AppConfig.hpp"
|
|
|
|
#include "I18N.hpp"
|
|
|
|
|
|
|
|
namespace Slic3r {
|
|
|
|
namespace GUI {
|
|
|
|
|
|
|
|
MediaPlayCtrl::MediaPlayCtrl(wxWindow *parent, wxMediaCtrl2 *media_ctrl, const wxPoint &pos, const wxSize &size)
|
|
|
|
: wxPanel(parent, wxID_ANY, pos, size)
|
|
|
|
, m_media_ctrl(media_ctrl)
|
|
|
|
{
|
|
|
|
SetBackgroundColour(*wxWHITE);
|
|
|
|
m_media_ctrl->Bind(wxEVT_MEDIA_STATECHANGED, &MediaPlayCtrl::onStateChanged, this);
|
|
|
|
|
|
|
|
m_button_play = new Button(this, "", "media_play", wxBORDER_NONE);
|
2022-08-26 01:39:45 +00:00
|
|
|
m_button_play->SetCanFocus(false);
|
2022-07-15 15:37:19 +00:00
|
|
|
|
2022-09-09 07:05:59 +00:00
|
|
|
m_label_status = new Label(this, "", LB_HYPERLINK);
|
2022-07-15 15:37:19 +00:00
|
|
|
|
|
|
|
m_button_play->Bind(wxEVT_COMMAND_BUTTON_CLICKED, [this](auto & e) { TogglePlay(); });
|
|
|
|
|
|
|
|
m_button_play->Bind(wxEVT_RIGHT_UP, [this](auto & e) { m_media_ctrl->Play(); });
|
2022-09-09 07:05:59 +00:00
|
|
|
m_label_status->Bind(wxEVT_LEFT_UP, [this](auto &e) {
|
|
|
|
auto url = wxString::Format(L"https://wiki.bambulab.com/%s/software/bambu-studio/faq/live-view", L"en");
|
|
|
|
wxLaunchDefaultBrowser(url);
|
|
|
|
});
|
2022-07-15 15:37:19 +00:00
|
|
|
|
|
|
|
Bind(wxEVT_RIGHT_UP, [this](auto & e) { wxClipboard & c = *wxTheClipboard; if (c.Open()) { c.SetData(new wxTextDataObject(m_url)); c.Close(); } });
|
|
|
|
|
|
|
|
wxBoxSizer * sizer = new wxBoxSizer(wxHORIZONTAL);
|
2022-08-26 01:39:45 +00:00
|
|
|
sizer->Add(m_button_play, 0, wxEXPAND | wxALL, 0);
|
2022-07-15 15:37:19 +00:00
|
|
|
sizer->AddStretchSpacer(1);
|
|
|
|
sizer->Add(m_label_status, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(25));
|
|
|
|
SetSizer(sizer);
|
|
|
|
|
|
|
|
m_thread = boost::thread([this] {
|
|
|
|
media_proc();
|
|
|
|
});
|
2022-08-26 01:39:45 +00:00
|
|
|
|
2022-09-13 01:42:16 +00:00
|
|
|
//#if BBL_RELEASE_TO_PUBLIC
|
|
|
|
// m_next_retry = wxDateTime::Now();
|
|
|
|
//#endif
|
2022-08-31 09:15:34 +00:00
|
|
|
|
|
|
|
auto onShowHide = [this](auto &e) {
|
|
|
|
e.Skip();
|
|
|
|
if (m_isBeingDeleted) return;
|
|
|
|
IsShownOnScreen() ? Play() : Stop();
|
|
|
|
};
|
|
|
|
parent->Bind(wxEVT_SHOW, onShowHide);
|
|
|
|
parent->GetParent()->GetParent()->Bind(wxEVT_SHOW, onShowHide);
|
2022-09-26 03:11:05 +00:00
|
|
|
|
|
|
|
m_lan_user = "bblp";
|
|
|
|
m_lan_passwd = "bblp";
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MediaPlayCtrl::~MediaPlayCtrl()
|
|
|
|
{
|
|
|
|
{
|
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
m_tasks.push_back("<exit>");
|
|
|
|
m_cond.notify_all();
|
|
|
|
}
|
|
|
|
m_thread.join();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::SetMachineObject(MachineObject* obj)
|
|
|
|
{
|
|
|
|
std::string machine = obj ? obj->dev_id : "";
|
2022-09-26 03:11:05 +00:00
|
|
|
if (obj && obj->is_function_supported(PrinterFunction::FUNC_CAMERA_VIDEO)) {
|
2022-09-30 09:54:12 +00:00
|
|
|
m_camera_exists = obj->has_ipcam;
|
|
|
|
m_lan_mode = obj->is_lan_mode_printer();
|
2022-09-26 03:11:05 +00:00
|
|
|
m_lan_ip = obj->is_function_supported(PrinterFunction::FUNC_LOCAL_TUNNEL) ? obj->dev_ip : "";
|
2022-09-30 09:54:12 +00:00
|
|
|
m_lan_passwd = obj->is_function_supported(PrinterFunction::FUNC_LOCAL_TUNNEL) ? obj->access_code : "";
|
2022-09-26 03:11:05 +00:00
|
|
|
m_tutk_support = obj->is_function_supported(PrinterFunction::FUNC_REMOTE_TUNNEL);
|
|
|
|
} else {
|
2022-09-30 09:54:12 +00:00
|
|
|
m_camera_exists = false;
|
2022-09-26 03:11:05 +00:00
|
|
|
m_lan_mode = false;
|
|
|
|
m_lan_ip.clear();
|
|
|
|
m_lan_passwd.clear();
|
|
|
|
m_tutk_support = true;
|
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
if (machine == m_machine) {
|
|
|
|
if (m_last_state == MEDIASTATE_IDLE && m_next_retry.IsValid() && wxDateTime::Now() >= m_next_retry)
|
|
|
|
Play();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
m_machine = machine;
|
|
|
|
m_failed_retry = 0;
|
|
|
|
if (m_last_state != MEDIASTATE_IDLE)
|
|
|
|
Stop();
|
2022-08-26 01:39:45 +00:00
|
|
|
if (m_next_retry.IsValid())
|
|
|
|
Play();
|
|
|
|
else
|
|
|
|
SetStatus("");
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::Play()
|
|
|
|
{
|
2022-08-31 09:15:34 +00:00
|
|
|
if (!m_next_retry.IsValid())
|
|
|
|
return;
|
|
|
|
if (!IsShownOnScreen())
|
|
|
|
return;
|
2022-09-30 09:54:12 +00:00
|
|
|
if (m_last_state != MEDIASTATE_IDLE) {
|
|
|
|
return;
|
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
if (m_machine.empty()) {
|
|
|
|
SetStatus(_L("Initialize failed (No Device)!"));
|
|
|
|
return;
|
|
|
|
}
|
2022-09-30 09:54:12 +00:00
|
|
|
if (!m_camera_exists) {
|
|
|
|
SetStatus(_L("Initialize failed (No Camera Device)!"));
|
2022-07-15 15:37:19 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-09-26 03:11:05 +00:00
|
|
|
|
2022-07-15 15:37:19 +00:00
|
|
|
m_last_state = MEDIASTATE_INITIALIZING;
|
|
|
|
m_button_play->SetIcon("media_stop");
|
|
|
|
SetStatus(_L("Initializing..."));
|
|
|
|
|
2022-09-26 03:11:05 +00:00
|
|
|
if (!m_lan_ip.empty()) {
|
|
|
|
m_url = "bambu:///local/" + m_lan_ip + ".?port=6000&user=" + m_lan_user + "&passwd=" + m_lan_passwd;
|
|
|
|
m_last_state = MEDIASTATE_LOADING;
|
|
|
|
SetStatus(_L("Loading..."));
|
|
|
|
if (wxGetApp().app_config->get("dump_video") == "true") {
|
|
|
|
std::string file_h264 = data_dir() + "/video.h264";
|
|
|
|
std::string file_info = data_dir() + "/video.info";
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << file_h264;
|
|
|
|
// closed by BambuSource
|
|
|
|
FILE *dump_h264_file = boost::nowide::fopen(file_h264.c_str(), "wb");
|
|
|
|
FILE *dump_info_file = boost::nowide::fopen(file_info.c_str(), "wb");
|
|
|
|
m_url = m_url + "&dump_h264=" + boost::lexical_cast<std::string>(dump_h264_file);
|
|
|
|
m_url = m_url + "&dump_info=" + boost::lexical_cast<std::string>(dump_info_file);
|
|
|
|
}
|
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
m_tasks.push_back(m_url);
|
|
|
|
m_cond.notify_all();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-09-30 09:54:12 +00:00
|
|
|
if (m_lan_mode) {
|
|
|
|
SetStatus(m_lan_passwd.empty()
|
|
|
|
? _L("Initialize failed (Not supported with LAN-only mode)!")
|
|
|
|
: _L("Initialize failed (Not accessible in LAN-only mode)!"));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!m_tutk_support) { // not support tutk
|
|
|
|
SetStatus(_L("Initialize failed (Not supported without remote video tunnel)!"));
|
2022-09-26 03:11:05 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
|
|
|
|
NetworkAgent* agent = wxGetApp().getAgent();
|
|
|
|
if (agent) {
|
2022-09-08 11:05:34 +00:00
|
|
|
agent->get_camera_url(m_machine, [this, m = m_machine](std::string url) {
|
2022-07-15 15:37:19 +00:00
|
|
|
BOOST_LOG_TRIVIAL(info) << "camera_url: " << url;
|
2022-09-30 09:54:12 +00:00
|
|
|
CallAfter([this, m, url] {
|
|
|
|
if (m != m_machine) return;
|
2022-07-15 15:37:19 +00:00
|
|
|
m_url = url;
|
|
|
|
if (m_last_state == MEDIASTATE_INITIALIZING) {
|
2022-09-30 09:54:12 +00:00
|
|
|
if (url.empty() || !boost::algorithm::starts_with(url, "bambu:///")) {
|
2022-07-15 15:37:19 +00:00
|
|
|
Stop();
|
2022-09-30 09:54:12 +00:00
|
|
|
SetStatus(wxString::Format(_L("Initialize failed (%s)!"), url.empty() ? _L("Network unreachable") : from_u8(url)));
|
2022-07-15 15:37:19 +00:00
|
|
|
} else {
|
|
|
|
m_last_state = MEDIASTATE_LOADING;
|
|
|
|
SetStatus(_L("Loading..."));
|
|
|
|
if (wxGetApp().app_config->get("dump_video") == "true") {
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl dump video to " << boost::filesystem::current_path();
|
|
|
|
m_url = m_url + "&dump=video.h264";
|
|
|
|
}
|
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
m_tasks.push_back(m_url);
|
|
|
|
m_cond.notify_all();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::Stop()
|
|
|
|
{
|
|
|
|
if (m_last_state != MEDIASTATE_IDLE) {
|
|
|
|
m_media_ctrl->InvalidateBestSize();
|
|
|
|
m_button_play->SetIcon("media_play");
|
|
|
|
boost::unique_lock lock(m_mutex);
|
2022-07-22 09:46:10 +00:00
|
|
|
m_tasks.push_back("<stop>");
|
2022-07-15 15:37:19 +00:00
|
|
|
m_cond.notify_all();
|
2022-08-26 01:39:45 +00:00
|
|
|
m_last_state = MEDIASTATE_IDLE;
|
|
|
|
SetStatus(_L("Stopped."));
|
2022-09-07 06:03:53 +00:00
|
|
|
if (m_failed_code >= 100) // not keep retry on local error
|
|
|
|
m_next_retry = wxDateTime();
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
++m_failed_retry;
|
2022-08-26 01:39:45 +00:00
|
|
|
if (m_next_retry.IsValid())
|
|
|
|
m_next_retry = wxDateTime::Now() + wxTimeSpan::Seconds(5 * m_failed_retry);
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::TogglePlay()
|
|
|
|
{
|
2022-08-26 01:39:45 +00:00
|
|
|
if (m_last_state != MEDIASTATE_IDLE) {
|
|
|
|
m_next_retry = wxDateTime();
|
2022-07-15 15:37:19 +00:00
|
|
|
Stop();
|
2022-08-26 01:39:45 +00:00
|
|
|
} else {
|
|
|
|
m_failed_retry = 0;
|
|
|
|
m_next_retry = wxDateTime::Now();
|
2022-07-15 15:37:19 +00:00
|
|
|
Play();
|
2022-08-26 01:39:45 +00:00
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::SetStatus(wxString const& msg2)
|
|
|
|
{
|
|
|
|
auto msg = wxString::Format(msg2, m_failed_code);
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::SetStatus: " << msg.ToUTF8().data();
|
2022-07-22 09:46:10 +00:00
|
|
|
#ifdef __WXMSW__
|
|
|
|
OutputDebugStringA("MediaPlayCtrl::SetStatus: ");
|
|
|
|
OutputDebugStringA(msg.ToUTF8().data());
|
|
|
|
OutputDebugStringA("\n");
|
|
|
|
#endif // __WXMSW__
|
2022-07-15 15:37:19 +00:00
|
|
|
m_label_status->SetLabel(msg);
|
2022-09-09 07:05:59 +00:00
|
|
|
long style = m_label_status->GetWindowStyle() & ~LB_HYPERLINK;
|
|
|
|
if (m_failed_code && msg != msg2) {
|
|
|
|
style |= LB_HYPERLINK;
|
|
|
|
}
|
|
|
|
m_label_status->SetWindowStyle(style);
|
2022-07-15 15:37:19 +00:00
|
|
|
Layout();
|
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::media_proc()
|
|
|
|
{
|
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
while (true) {
|
|
|
|
while (m_tasks.empty()) {
|
|
|
|
m_cond.wait(lock);
|
|
|
|
}
|
|
|
|
wxString url = m_tasks.front();
|
|
|
|
lock.unlock();
|
|
|
|
if (url.IsEmpty()) {
|
2022-07-22 09:46:10 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (url == "<stop>") {
|
2022-07-15 15:37:19 +00:00
|
|
|
m_media_ctrl->Stop();
|
|
|
|
}
|
|
|
|
else if (url == "<exit>") {
|
|
|
|
break;
|
|
|
|
}
|
2022-07-22 09:46:10 +00:00
|
|
|
else if (url == "<play>") {
|
|
|
|
m_media_ctrl->Play();
|
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
else {
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: start load";
|
|
|
|
m_media_ctrl->Load(wxURI(url));
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl: end load";
|
|
|
|
}
|
|
|
|
lock.lock();
|
|
|
|
m_tasks.pop_front();
|
|
|
|
wxMediaEvent theEvent(wxEVT_MEDIA_STATECHANGED, m_media_ctrl->GetId());
|
|
|
|
theEvent.SetId(0);
|
|
|
|
m_media_ctrl->GetEventHandler()->AddPendingEvent(theEvent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void MediaPlayCtrl::onStateChanged(wxMediaEvent& event)
|
|
|
|
{
|
|
|
|
auto last_state = m_last_state;
|
|
|
|
auto state = m_media_ctrl->GetState();
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: " << state << ", last_state: " << last_state;
|
|
|
|
if ((int) state < 0)
|
|
|
|
return;
|
|
|
|
{
|
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
if (!m_tasks.empty()) {
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: skip when task not finished";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (last_state == MEDIASTATE_IDLE && state == wxMEDIASTATE_STOPPED) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ((last_state == wxMEDIASTATE_PAUSED || last_state == wxMEDIASTATE_PLAYING) &&
|
|
|
|
state == wxMEDIASTATE_STOPPED) {
|
|
|
|
Stop();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (last_state == MEDIASTATE_LOADING && state == wxMEDIASTATE_STOPPED) {
|
|
|
|
wxSize size = m_media_ctrl->GetVideoSize();
|
|
|
|
BOOST_LOG_TRIVIAL(info) << "MediaPlayCtrl::onStateChanged: size: " << size.x << "x" << size.y;
|
|
|
|
m_failed_code = m_media_ctrl->GetLastError();
|
|
|
|
if (size.GetWidth() > 1000) {
|
2022-08-26 01:39:45 +00:00
|
|
|
m_last_state = state;
|
2022-07-15 15:37:19 +00:00
|
|
|
SetStatus(_L("Playing..."));
|
|
|
|
m_failed_retry = 0;
|
2022-07-22 09:46:10 +00:00
|
|
|
boost::unique_lock lock(m_mutex);
|
|
|
|
m_tasks.push_back("<play>");
|
|
|
|
m_cond.notify_all();
|
2022-07-15 15:37:19 +00:00
|
|
|
}
|
|
|
|
else if (event.GetId()) {
|
|
|
|
Stop();
|
2022-09-09 07:05:59 +00:00
|
|
|
if (m_failed_code == 0)
|
|
|
|
m_failed_code = 2;
|
2022-07-15 15:37:19 +00:00
|
|
|
SetStatus(_L("Load failed [%d]!"));
|
|
|
|
} else {
|
|
|
|
m_last_state = last_state;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
void wxMediaCtrl2::DoSetSize(int x, int y, int width, int height, int sizeFlags)
|
|
|
|
{
|
|
|
|
wxWindow::DoSetSize(x, y, width, height, sizeFlags);
|
|
|
|
if (sizeFlags & wxSIZE_USE_EXISTING) return;
|
|
|
|
wxSize size = GetVideoSize();
|
|
|
|
if (size.GetWidth() <= 0)
|
|
|
|
size = wxSize{16, 9};
|
|
|
|
int maxHeight = (width * size.GetHeight() + size.GetHeight() - 1) / size.GetWidth();
|
|
|
|
if (maxHeight != GetMaxHeight()) {
|
2022-07-27 08:55:55 +00:00
|
|
|
// BOOST_LOG_TRIVIAL(info) << "wxMediaCtrl2::DoSetSize: width: " << width << ", height: " << height << ", maxHeight: " << maxHeight;
|
2022-07-15 15:37:19 +00:00
|
|
|
SetMaxSize({-1, maxHeight});
|
|
|
|
Slic3r::GUI::wxGetApp().CallAfter([this] {
|
2022-07-26 07:19:28 +00:00
|
|
|
if (auto p = GetParent()) {
|
|
|
|
p->Layout();
|
|
|
|
p->Refresh();
|
|
|
|
}
|
2022-07-15 15:37:19 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|