summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorpavelxdd <pavel.otchertsov@gmail.com>2017-12-17 23:20:53 +0300
committerwm4 <1387750+wm4@users.noreply.github.com>2019-09-13 13:52:28 +0200
commit102bb7a45c18503de7f604fd04aef3e9f8d8fbbc (patch)
tree1bfefab4c4e19c28e530b8ec8486233c6ca8ee15
parenteec94a8c5ef0374be378402d750b9dd433e5fab9 (diff)
downloadmpv-examples-102bb7a45c18503de7f604fd04aef3e9f8d8fbbc.tar.bz2
mpv-examples-102bb7a45c18503de7f604fd04aef3e9f8d8fbbc.tar.xz
Add wxWidgets examples
-rw-r--r--libmpv/README.md8
-rw-r--r--libmpv/wxwidgets/main.cpp199
-rw-r--r--libmpv/wxwidgets/main.h37
-rw-r--r--libmpv/wxwidgets_opengl/main.cpp342
-rw-r--r--libmpv/wxwidgets_opengl/main.h75
5 files changed, 661 insertions, 0 deletions
diff --git a/libmpv/README.md b/libmpv/README.md
index 06ff1a7..0ff90e1 100644
--- a/libmpv/README.md
+++ b/libmpv/README.md
@@ -143,3 +143,11 @@ Show how to embed the mpv OpenGL renderer in SDL. Uses the render API for video.
Demonstrates use of the custom stream API.
+### wxwidgets
+
+Shows how to embed the mpv video window in wxWidgets frame.
+
+### wxwidgets_opengl
+
+Similar to wxwidgets sample, but shows how to use mpv's OpenGL video renderer
+using libmpv's opengl-cb API in wxWidgets frame via wxGLCanvas.
diff --git a/libmpv/wxwidgets/main.cpp b/libmpv/wxwidgets/main.cpp
new file mode 100644
index 0000000..196ac78
--- /dev/null
+++ b/libmpv/wxwidgets/main.cpp
@@ -0,0 +1,199 @@
+// Build with: g++ -o main main.cpp `wx-config --libs --cxxflags` -lmpv
+
+#include "main.h"
+
+#include <clocale>
+#include <string>
+
+#include <wx/display.h>
+
+wxIMPLEMENT_APP(MpvApp);
+
+bool MpvApp::OnInit()
+{
+ std::setlocale(LC_NUMERIC, "C");
+ (new MpvFrame)->Show(true);
+ return true;
+}
+
+wxDECLARE_APP(MpvApp);
+
+wxDEFINE_EVENT(WX_MPV_WAKEUP, wxThreadEvent);
+
+wxBEGIN_EVENT_TABLE(MpvFrame, wxFrame)
+ EVT_CHAR_HOOK(MpvFrame::OnKeyDown)
+ EVT_DROP_FILES(MpvFrame::OnDropFiles)
+wxEND_EVENT_TABLE()
+
+MpvFrame::MpvFrame()
+ : wxFrame(nullptr, wxID_ANY, "mpv")
+{
+ SetBackgroundColour(wxColour(*wxBLACK));
+ Center();
+ DragAcceptFiles(true);
+
+ auto panel = new wxPanel(this, wxID_ANY,
+ wxDefaultPosition, wxDefaultSize, wxWANTS_CHARS);
+ MpvCreate(reinterpret_cast<int64_t>(panel->GetHandle()));
+
+ if (wxGetApp().argc == 2) {
+ const std::string filepath(wxGetApp().argv[1].utf8_str().data());
+ const char *cmd[] = { "loadfile", filepath.c_str(), nullptr };
+ mpv_command(mpv, cmd);
+ }
+}
+
+bool MpvFrame::Destroy()
+{
+ MpvDestroy();
+ return wxFrame::Destroy();
+}
+
+void MpvFrame::MpvCreate(int64_t wid)
+{
+ MpvDestroy();
+
+ mpv = mpv_create();
+ if (!mpv)
+ throw std::runtime_error("failed to create mpv instance");
+
+ Bind(WX_MPV_WAKEUP, &MpvFrame::OnMpvWakeupEvent, this);
+ mpv_set_wakeup_callback(mpv, [](void *data) {
+ auto window = reinterpret_cast<MpvFrame *>(data);
+ if (window) {
+ auto event = new wxThreadEvent(WX_MPV_WAKEUP);
+ window->GetEventHandler()->QueueEvent(event);
+ }
+ }, this);
+
+ if (mpv_set_property(mpv, "wid", MPV_FORMAT_INT64, &wid) < 0)
+ throw std::runtime_error("failed to set mpv wid");
+
+ if (mpv_initialize(mpv) < 0)
+ throw std::runtime_error("failed to initialize mpv");
+
+ mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_NONE);
+}
+
+void MpvFrame::MpvDestroy()
+{
+ Unbind(WX_MPV_WAKEUP, &MpvFrame::OnMpvWakeupEvent, this);
+
+ if (mpv) {
+ mpv_terminate_destroy(mpv);
+ mpv = nullptr;
+ }
+}
+
+bool MpvFrame::Autofit(int percent, bool larger, bool smaller)
+{
+ int64_t w, h;
+ if (!mpv || mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) < 0 ||
+ mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) < 0 ||
+ w <= 0 || h <= 0)
+ return false;
+
+ int screen_id = wxDisplay::GetFromWindow(this);
+ if (screen_id == wxNOT_FOUND)
+ return false;
+
+ wxRect screen = wxDisplay(screen_id).GetClientArea();
+ const int n_w = (int)(screen.width * percent * 0.01);
+ const int n_h = (int)(screen.height * percent * 0.01);
+
+ if ((larger && (w > n_w || h > n_h)) ||
+ (smaller && (w < n_w || h < n_h)))
+ {
+ const float asp = w / (float)h;
+ const float n_asp = n_w / (float)n_h;
+ if (asp > n_asp) {
+ w = n_w;
+ h = (int)(n_w / asp);
+ } else {
+ w = (int)(n_h * asp);
+ h = n_h;
+ }
+ }
+
+ const wxRect rc = GetScreenRect();
+ SetClientSize(w, h);
+ const wxRect n_rc = GetScreenRect();
+
+ Move(rc.x + rc.width / 2 - n_rc.width / 2,
+ rc.y + rc.height / 2 - n_rc.height / 2);
+ return true;
+}
+
+void MpvFrame::OnKeyDown(wxKeyEvent &event)
+{
+ if (mpv && event.GetKeyCode() == WXK_SPACE)
+ mpv_command_string(mpv, "cycle pause");
+ event.Skip();
+}
+
+void MpvFrame::OnDropFiles(wxDropFilesEvent &event)
+{
+ int size = event.GetNumberOfFiles();
+ if (!size || !mpv)
+ return;
+
+ auto files = event.GetFiles();
+ if (!files)
+ return;
+
+ for (int i = 0; i < size; ++i) {
+ const std::string filepath(files[i].utf8_str().data());
+ const char *cmd[] = {
+ "loadfile",
+ filepath.c_str(),
+ i == 0 ? "replace" : "append-play",
+ NULL
+ };
+ mpv_command_async(mpv, 0, cmd);
+ }
+}
+
+void MpvFrame::OnMpvEvent(mpv_event &event)
+{
+ if (!mpv)
+ return;
+
+ switch (event.event_id) {
+ case MPV_EVENT_VIDEO_RECONFIG:
+ // something like --autofit-larger=95%
+ Autofit(95, true, false);
+ break;
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ mpv_event_property *prop = (mpv_event_property *)event.data;
+ if (strcmp(prop->name, "media-title") == 0) {
+ char *data = nullptr;
+ if (mpv_get_property(mpv, prop->name, MPV_FORMAT_OSD_STRING, &data) < 0) {
+ SetTitle("mpv");
+ } else {
+ wxString title = wxString::FromUTF8(data);
+ if (!title.IsEmpty())
+ title += " - ";
+ title += "mpv";
+ SetTitle(title);
+ mpv_free(data);
+ }
+ }
+ break;
+ }
+ case MPV_EVENT_SHUTDOWN:
+ MpvDestroy();
+ break;
+ default:
+ break;
+ }
+}
+
+void MpvFrame::OnMpvWakeupEvent(wxThreadEvent &)
+{
+ while (mpv) {
+ mpv_event *e = mpv_wait_event(mpv, 0);
+ if (e->event_id == MPV_EVENT_NONE)
+ break;
+ OnMpvEvent(*e);
+ }
+}
diff --git a/libmpv/wxwidgets/main.h b/libmpv/wxwidgets/main.h
new file mode 100644
index 0000000..db5fa55
--- /dev/null
+++ b/libmpv/wxwidgets/main.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include <wx/wxprec.h>
+#ifndef WX_PRECOMP
+ #include <wx/wx.h>
+#endif
+
+#include <mpv/client.h>
+
+class MpvApp : public wxApp
+{
+public:
+ bool OnInit() override;
+};
+
+class MpvFrame : public wxFrame
+{
+public:
+ MpvFrame();
+
+ bool Destroy() override;
+ bool Autofit(int percent, bool larger = true, bool smaller = true);
+
+private:
+ void MpvCreate(int64_t wid);
+ void MpvDestroy();
+
+ void OnKeyDown(wxKeyEvent &event);
+ void OnDropFiles(wxDropFilesEvent &event);
+
+ void OnMpvEvent(mpv_event &event);
+ void OnMpvWakeupEvent(wxThreadEvent &event);
+
+ mpv_handle *mpv = nullptr;
+
+ wxDECLARE_EVENT_TABLE();
+};
diff --git a/libmpv/wxwidgets_opengl/main.cpp b/libmpv/wxwidgets_opengl/main.cpp
new file mode 100644
index 0000000..603cf52
--- /dev/null
+++ b/libmpv/wxwidgets_opengl/main.cpp
@@ -0,0 +1,342 @@
+// Build with: g++ -o main main.cpp `wx-config --libs --cxxflags --gl_libs` -lmpv
+
+#include "main.h"
+
+#include <clocale>
+#include <string>
+
+#include <wx/dcbuffer.h>
+#include <wx/display.h>
+
+wxIMPLEMENT_APP(MpvApp);
+
+bool MpvApp::OnInit()
+{
+ std::setlocale(LC_NUMERIC, "C");
+ (new MpvFrame)->Show(true);
+ return true;
+}
+
+wxBEGIN_EVENT_TABLE(MpvGLCanvas, wxGLCanvas)
+ EVT_SIZE(MpvGLCanvas::OnSize)
+ EVT_PAINT(MpvGLCanvas::OnPaint)
+ EVT_ERASE_BACKGROUND(MpvGLCanvas::OnErase)
+wxEND_EVENT_TABLE()
+
+MpvGLCanvas::MpvGLCanvas(wxWindow *parent)
+ : wxGLCanvas(parent)
+{
+ SetBackgroundStyle(wxBG_STYLE_CUSTOM);
+ glContext = new wxGLContext(this);
+}
+
+MpvGLCanvas::~MpvGLCanvas()
+{
+ OnRender = nullptr;
+ OnSwapBuffers = nullptr;
+
+ if (glContext)
+ delete glContext;
+}
+
+bool MpvGLCanvas::SetCurrent() const
+{
+ if (!glContext)
+ return false;
+ return wxGLCanvas::SetCurrent(*glContext);
+}
+
+bool MpvGLCanvas::SwapBuffers()
+{
+ bool result = wxGLCanvas::SwapBuffers();
+ if (OnSwapBuffers)
+ OnSwapBuffers(this);
+ return result;
+}
+
+void MpvGLCanvas::OnSize(wxSizeEvent &)
+{
+ Update();
+}
+
+void MpvGLCanvas::OnErase(wxEraseEvent &event)
+{
+ // do nothing to skip erase
+}
+
+void MpvGLCanvas::Render()
+{
+ wxClientDC(this);
+ DoRender();
+}
+
+void MpvGLCanvas::OnPaint(wxPaintEvent &)
+{
+ wxAutoBufferedPaintDC(this);
+ DoRender();
+}
+
+void MpvGLCanvas::DoRender()
+{
+ SetCurrent();
+ if (OnRender)
+ OnRender(this, GetSize().x, GetSize().y);
+ SwapBuffers();
+}
+
+void *MpvGLCanvas::GetProcAddress(const char *name)
+{
+ SetCurrent();
+
+#ifdef __WINDOWS__
+ void *result = (void *)::wglGetProcAddress(name);
+ if (!result) {
+ HMODULE dll = ::LoadLibrary(wxT("opengl32.dll"));
+ if (dll) {
+ result = (void *)::GetProcAddress(dll, name);
+ ::FreeLibrary(dll);
+ }
+ }
+ return result;
+#else
+ return (void *)::glxGetProcAddressARB(name);
+#endif
+}
+
+wxDECLARE_APP(MpvApp);
+
+wxDEFINE_EVENT(WX_MPV_WAKEUP, wxThreadEvent);
+wxDEFINE_EVENT(WX_MPV_REDRAW, wxThreadEvent);
+
+wxBEGIN_EVENT_TABLE(MpvFrame, wxFrame)
+ EVT_SIZE(MpvFrame::OnSize)
+ EVT_CHAR_HOOK(MpvFrame::OnKeyDown)
+ EVT_DROP_FILES(MpvFrame::OnDropFiles)
+wxEND_EVENT_TABLE()
+
+MpvFrame::MpvFrame()
+ : wxFrame(nullptr, wxID_ANY, "mpv")
+{
+ SetBackgroundColour(wxColour(*wxBLACK));
+ Center();
+ DragAcceptFiles(true);
+
+ glCanvas = new MpvGLCanvas(this);
+ glCanvas->SetClientSize(GetClientSize());
+ glCanvas->OnRender = std::bind(&MpvFrame::DoMpvDraw, this,
+ std::placeholders::_2, std::placeholders::_3);
+ glCanvas->OnSwapBuffers = std::bind(&MpvFrame::DoMpvFlip, this);
+
+ MpvCreate();
+
+ if (wxGetApp().argc == 2) {
+ const std::string filepath(wxGetApp().argv[1].utf8_str().data());
+ const char *cmd[] = { "loadfile", filepath.c_str(), nullptr };
+ mpv_command(mpv, cmd);
+ }
+}
+
+bool MpvFrame::Destroy()
+{
+ MpvDestroy();
+ return wxFrame::Destroy();
+}
+
+void MpvFrame::MpvCreate()
+{
+ MpvDestroy();
+
+ mpv = mpv_create();
+ if (!mpv)
+ throw std::runtime_error("failed to create mpv instance");
+
+ if (mpv_initialize(mpv) < 0)
+ throw std::runtime_error("failed to initialize mpv");
+
+ mpv_gl = (mpv_opengl_cb_context *)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB);
+ if (!mpv_gl)
+ throw std::runtime_error("failed to create mpv GL API handle");
+
+ if (mpv_opengl_cb_init_gl(mpv_gl, nullptr, [](void *canvas, const char *name) {
+ auto glCanvas = reinterpret_cast<MpvGLCanvas *>(canvas);
+ return glCanvas ? glCanvas->GetProcAddress(name) : nullptr;
+ }, glCanvas) < 0)
+ throw std::runtime_error("failed to initialize mpv GL context");
+
+ if (mpv_set_property_string(mpv, "vo", "opengl-cb") < 0)
+ throw std::runtime_error("failed to set mpv VO");
+
+ mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_NONE);
+
+ Bind(WX_MPV_WAKEUP, &MpvFrame::OnMpvWakeupEvent, this);
+ mpv_set_wakeup_callback(mpv, [](void *data) {
+ auto window = reinterpret_cast<MpvFrame *>(data);
+ if (window) {
+ auto event = new wxThreadEvent(WX_MPV_WAKEUP);
+ window->GetEventHandler()->QueueEvent(event);
+ }
+ }, this);
+
+ Bind(WX_MPV_REDRAW, &MpvFrame::OnMpvRedrawEvent, this);
+ mpv_opengl_cb_set_update_callback(mpv_gl, [](void *data) {
+ auto window = reinterpret_cast<MpvFrame *>(data);
+ if (window) {
+ auto event = new wxThreadEvent(WX_MPV_REDRAW);
+ window->GetEventHandler()->QueueEvent(event);
+ }
+ }, this);
+}
+
+void MpvFrame::MpvDestroy()
+{
+ Unbind(WX_MPV_WAKEUP, &MpvFrame::OnMpvWakeupEvent, this);
+ Unbind(WX_MPV_REDRAW, &MpvFrame::OnMpvRedrawEvent, this);
+
+ if (mpv_gl) {
+ mpv_opengl_cb_set_update_callback(mpv_gl, nullptr, nullptr);
+ mpv_opengl_cb_uninit_gl(mpv_gl);
+ mpv_gl = nullptr;
+ }
+
+ if (mpv) {
+ mpv_terminate_destroy(mpv);
+ mpv = nullptr;
+ }
+}
+
+bool MpvFrame::Autofit(int percent, bool larger, bool smaller)
+{
+ int64_t w, h;
+ if (!mpv || mpv_get_property(mpv, "dwidth", MPV_FORMAT_INT64, &w) < 0 ||
+ mpv_get_property(mpv, "dheight", MPV_FORMAT_INT64, &h) < 0 ||
+ w <= 0 || h <= 0)
+ return false;
+
+ int screen_id = wxDisplay::GetFromWindow(this);
+ if (screen_id == wxNOT_FOUND)
+ return false;
+
+ wxRect screen = wxDisplay(screen_id).GetClientArea();
+ const int n_w = (int)(screen.width * percent * 0.01);
+ const int n_h = (int)(screen.height * percent * 0.01);
+
+ if ((larger && (w > n_w || h > n_h)) ||
+ (smaller && (w < n_w || h < n_h)))
+ {
+ const float asp = w / (float)h;
+ const float n_asp = n_w / (float)n_h;
+ if (asp > n_asp) {
+ w = n_w;
+ h = (int)(n_w / asp);
+ } else {
+ w = (int)(n_h * asp);
+ h = n_h;
+ }
+ }
+
+ const wxRect rc = GetScreenRect();
+ SetClientSize(w, h);
+ const wxRect n_rc = GetScreenRect();
+
+ Move(rc.x + rc.width / 2 - n_rc.width / 2,
+ rc.y + rc.height / 2 - n_rc.height / 2);
+ return true;
+}
+
+void MpvFrame::OnSize(wxSizeEvent &event)
+{
+ if (glCanvas)
+ glCanvas->SetClientSize(GetClientSize());
+}
+
+void MpvFrame::OnKeyDown(wxKeyEvent &event)
+{
+ if (mpv && event.GetKeyCode() == WXK_SPACE)
+ mpv_command_string(mpv, "cycle pause");
+ event.Skip();
+}
+
+void MpvFrame::OnDropFiles(wxDropFilesEvent &event)
+{
+ int size = event.GetNumberOfFiles();
+ if (!size || !mpv)
+ return;
+
+ auto files = event.GetFiles();
+ if (!files)
+ return;
+
+ for (int i = 0; i < size; ++i) {
+ const std::string filepath(files[i].utf8_str().data());
+ const char *cmd[] = {
+ "loadfile",
+ filepath.c_str(),
+ i == 0 ? "replace" : "append-play",
+ NULL
+ };
+ mpv_command_async(mpv, 0, cmd);
+ }
+}
+
+void MpvFrame::OnMpvEvent(mpv_event &event)
+{
+ if (!mpv)
+ return;
+
+ switch (event.event_id) {
+ case MPV_EVENT_VIDEO_RECONFIG:
+ // something like --autofit-larger=95%
+ Autofit(95, true, false);
+ break;
+ case MPV_EVENT_PROPERTY_CHANGE: {
+ mpv_event_property *prop = (mpv_event_property *)event.data;
+ if (strcmp(prop->name, "media-title") == 0) {
+ char *data = nullptr;
+ if (mpv_get_property(mpv, prop->name, MPV_FORMAT_OSD_STRING, &data) < 0) {
+ SetTitle("mpv");
+ } else {
+ wxString title = wxString::FromUTF8(data);
+ if (!title.IsEmpty())
+ title += " - ";
+ title += "mpv";
+ SetTitle(title);
+ mpv_free(data);
+ }
+ }
+ break;
+ }
+ case MPV_EVENT_SHUTDOWN:
+ MpvDestroy();
+ break;
+ default:
+ break;
+ }
+}
+
+void MpvFrame::OnMpvWakeupEvent(wxThreadEvent &)
+{
+ while (mpv) {
+ mpv_event *e = mpv_wait_event(mpv, 0);
+ if (e->event_id == MPV_EVENT_NONE)
+ break;
+ OnMpvEvent(*e);
+ }
+}
+
+void MpvFrame::OnMpvRedrawEvent(wxThreadEvent &)
+{
+ if (glCanvas)
+ glCanvas->Render();
+}
+
+void MpvFrame::DoMpvDraw(int w, int h)
+{
+ if (mpv_gl)
+ mpv_opengl_cb_draw(mpv_gl, 0, w, -h);
+}
+
+void MpvFrame::DoMpvFlip()
+{
+ if (mpv_gl)
+ mpv_opengl_cb_report_flip(mpv_gl, 0);
+}
diff --git a/libmpv/wxwidgets_opengl/main.h b/libmpv/wxwidgets_opengl/main.h
new file mode 100644
index 0000000..14628ed
--- /dev/null
+++ b/libmpv/wxwidgets_opengl/main.h
@@ -0,0 +1,75 @@
+#pragma once
+
+#include <functional>
+
+#include <wx/wxprec.h>
+#ifndef WX_PRECOMP
+ #include <wx/wx.h>
+#endif
+
+#include <wx/glcanvas.h>
+
+#include <mpv/client.h>
+#include <mpv/opengl_cb.h>
+
+class MpvApp : public wxApp
+{
+public:
+ bool OnInit() override;
+};
+
+class MpvGLCanvas : public wxGLCanvas
+{
+public:
+ MpvGLCanvas(wxWindow *parent);
+ ~MpvGLCanvas();
+
+ void Render();
+ bool SetCurrent() const;
+ bool SwapBuffers() override;
+ void *GetProcAddress(const char *name);
+
+ std::function<void (wxGLCanvas *, int w, int h)> OnRender = nullptr;
+ std::function<void (wxGLCanvas *)> OnSwapBuffers = nullptr;
+
+private:
+ void OnSize(wxSizeEvent &event);
+ void OnPaint(wxPaintEvent &event);
+ void OnErase(wxEraseEvent &event);
+
+ void DoRender();
+
+ wxGLContext *glContext = nullptr;
+
+ wxDECLARE_EVENT_TABLE();
+};
+
+class MpvFrame : public wxFrame
+{
+public:
+ MpvFrame();
+
+ bool Destroy() override;
+ bool Autofit(int percent, bool larger = true, bool smaller = true);
+
+private:
+ void MpvCreate();
+ void MpvDestroy();
+
+ void OnSize(wxSizeEvent &event);
+ void OnKeyDown(wxKeyEvent &event);
+ void OnDropFiles(wxDropFilesEvent &event);
+
+ void OnMpvEvent(mpv_event &event);
+ void OnMpvWakeupEvent(wxThreadEvent &event);
+ void OnMpvRedrawEvent(wxThreadEvent &event);
+
+ void DoMpvDraw(int w, int h);
+ void DoMpvFlip();
+
+ mpv_handle *mpv = nullptr;
+ mpv_opengl_cb_context *mpv_gl = nullptr;
+ MpvGLCanvas *glCanvas = nullptr;
+
+ wxDECLARE_EVENT_TABLE();
+};