From 71f8cccee1bf4d8d0a36571f6a69f2adf77a58de Mon Sep 17 00:00:00 2001 From: NamedKitten Date: Sun, 18 Nov 2018 19:23:31 +0000 Subject: [PATCH] [Multiple] Added Direct backend and backend interface support. --- CMakeLists.txt | 3 +- KittehPlayer.pro | 4 +- src/DirectMpvPlayerBackend.cpp | 584 +++++++++++++++++++++++++++++++++ src/DirectMpvPlayerBackend.h | 100 ++++++ src/MpvPlayerBackend.h | 13 +- src/backendinterface.hpp | 46 +++ src/enums.hpp | 6 + src/main.cpp | 30 +- src/qml/main.qml | 150 +++++---- 9 files changed, 855 insertions(+), 81 deletions(-) create mode 100644 src/DirectMpvPlayerBackend.cpp create mode 100644 src/DirectMpvPlayerBackend.h create mode 100644 src/backendinterface.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 47982c6..a28108f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,6 @@ project(KittehPlayer) include_directories(${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR}) set(CMAKE_AUTOMOC ON) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror -fstrict-aliasing -Wno-deprecated-declarations -Wno-unused-variable") option(DEVELOP "Enable runtime QML reloading for developing." OFF) option(DISCORD "Enable Discord SDK." OFF) @@ -12,6 +11,7 @@ option(DISCORD "Enable Discord SDK." OFF) find_package(Qt5Core REQUIRED) find_package(Qt5Gui REQUIRED) +find_package(Qt5QuickWidgets REQUIRED) find_package(Qt5 CONFIG REQUIRED COMPONENTS Qml Quick Gui Widgets Core X11Extras) find_package(Qt5QuickCompiler) @@ -26,6 +26,7 @@ pkg_check_modules(Xext REQUIRED xext) set(SOURCES src/main.cpp src/MpvPlayerBackend.cpp + src/DirectMpvPlayerBackend.cpp src/utils.cpp src/enums.hpp ) diff --git a/KittehPlayer.pro b/KittehPlayer.pro index d96600a..8f17203 100644 --- a/KittehPlayer.pro +++ b/KittehPlayer.pro @@ -3,7 +3,7 @@ TARGET = KittehPlayer TEMPLATE = app QT += qml quickcontrols2 widgets x11extras -SOURCES += src/main.cpp src/MpvPlayerBackend.cpp src/utils.cpp +SOURCES += src/main.cpp src/MpvPlayerBackend.cpp src/DirectMpvPlayerBackend.cpp src/utils.cpp CONFIG += debug CONFIG-=qtquickcompiler @@ -31,7 +31,7 @@ unix { INSTALLS += target -HEADERS += src/MpvPlayerBackend.h src/utils.hpp +HEADERS += src/MpvPlayerBackend.h src/DirectMpvPlayerBackend.h src/utils.hpp DISTFILES += KittehPlayer.desktop KittehPlayer.png README.md LICENSE.txt diff --git a/src/DirectMpvPlayerBackend.cpp b/src/DirectMpvPlayerBackend.cpp new file mode 100644 index 0000000..8d90a7a --- /dev/null +++ b/src/DirectMpvPlayerBackend.cpp @@ -0,0 +1,584 @@ +#include +#include +#include + +#include "DirectMpvPlayerBackend.h" + +#include "utils.hpp" +#include +#include +#include +#include +#include + +#include +#include + +#ifdef DISCORD +#include "discord_rpc.h" +#endif + +void +wakeup(void* ctx) +{ + QMetaObject::invokeMethod( + (DirectMpvPlayerBackend*)ctx, "on_mpv_events", Qt::QueuedConnection); +} + +static void* +get_proc_address(void* ctx, const char* name) +{ + (void)ctx; + QOpenGLContext* glctx = QOpenGLContext::currentContext(); + if (!glctx) + return NULL; + return (void*)glctx->getProcAddress(QByteArray(name)); +} + +MpvRenderer::MpvRenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl) + : mpv(a_mpv) + , mpv_gl(a_mpv_gl) + , window(0) + , size() +{ + int r = mpv_opengl_cb_init_gl(mpv_gl, NULL, get_proc_address, NULL); + if (r < 0) + qDebug() << "could not initialize OpenGL"; +} + +MpvRenderer::~MpvRenderer() +{ + // Until this call is done, we need to make sure the player remains + // alive. This is done implicitly with the mpv::qt::Handle instance + // in this class. + exit(0); +} + +void +MpvRenderer::paint() +{ + window->resetOpenGLState(); + + // This uses 0 as framebuffer, which indicates that mpv will render directly + // to the frontbuffer. Note that mpv will always switch framebuffers + // explicitly. Some QWindow setups (such as using QQuickWidget) actually + // want you to render into a FBO in the beforeRendering() signal, and this + // code won't work there. + // The negation is used for rendering with OpenGL's flipped coordinates. + mpv_opengl_cb_draw(mpv_gl, 0, size.width(), -size.height()); + + window->resetOpenGLState(); +} + +DirectMpvPlayerBackend::DirectMpvPlayerBackend(QQuickItem* parent) + : QQuickItem(parent) + , mpv_gl(0) + , renderer(0) +{ + mpv = mpv_create(); + if (!mpv) + throw std::runtime_error("could not create mpv context"); + +#ifdef DISCORD + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + Discord_Initialize("511220330996432896", &handlers, 1, NULL); +#endif + + mpv_set_option_string(mpv, "terminal", "yes"); + mpv_set_option_string(mpv, "msg-level", "all=v"); + + // Fix? + mpv_set_option_string(mpv, "ytdl", "yes"); + + mpv_set_option_string(mpv, "slang", "en"); + + mpv_set_option_string(mpv, "config", "yes"); + // mpv_set_option_string(mpv, "sub-visibility", "no"); + mpv_observe_property(mpv, 0, "tracks-menu", MPV_FORMAT_NONE); + mpv_observe_property(mpv, 0, "playback-abort", MPV_FORMAT_NONE); + mpv_observe_property(mpv, 0, "chapter-list", MPV_FORMAT_NODE); + mpv_observe_property(mpv, 0, "track-list", MPV_FORMAT_NODE); + mpv_observe_property(mpv, 0, "audio-device-list", MPV_FORMAT_NODE); + mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "volume", MPV_FORMAT_NONE); + mpv_observe_property(mpv, 0, "mute", MPV_FORMAT_NONE); + mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "media-title", MPV_FORMAT_STRING); + mpv_observe_property(mpv, 0, "sub-text", MPV_FORMAT_STRING); + mpv_observe_property(mpv, 0, "time-pos", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "demuxer-cache-duration", MPV_FORMAT_DOUBLE); + mpv_observe_property(mpv, 0, "pause", MPV_FORMAT_NONE); + mpv_set_wakeup_callback(mpv, wakeup, this); + + if (mpv_initialize(mpv) < 0) + throw std::runtime_error("could not initialize mpv context"); + + // Make use of the MPV_SUB_API_OPENGL_CB API. + mpv::qt::set_option_variant(mpv, "vo", "opengl-cb"); + + // Setup the callback that will make QtQuick update and redraw if there + // is a new video frame. Use a queued connection: this makes sure the + // doUpdate() function is run on the GUI thread. + mpv_gl = (mpv_opengl_cb_context*)mpv_get_sub_api(mpv, MPV_SUB_API_OPENGL_CB); + if (!mpv_gl) + throw std::runtime_error("OpenGL not compiled in"); + mpv_opengl_cb_set_update_callback( + mpv_gl, DirectMpvPlayerBackend::on_update, (void*)this); + + connect(this, + &DirectMpvPlayerBackend::onUpdate, + this, + &DirectMpvPlayerBackend::doUpdate, + Qt::QueuedConnection); + connect(this, + &DirectMpvPlayerBackend::positionChanged, + &DirectMpvPlayerBackend::updateDurationString); + connect(this, + &DirectMpvPlayerBackend::durationChanged, + &DirectMpvPlayerBackend::updateDurationString); + + connect(this, + &QQuickItem::windowChanged, + this, + &DirectMpvPlayerBackend::handleWindowChanged); +} + +DirectMpvPlayerBackend::~DirectMpvPlayerBackend() +{ + printf("Shutting down...\n"); + + exit(0); + printf("MPV terminated.\n"); +} + +void +DirectMpvPlayerBackend::sync() +{ + + if (!renderer) { + renderer = new MpvRenderer(mpv, mpv_gl); + connect(window(), + &QQuickWindow::beforeRendering, + renderer, + &MpvRenderer::paint, + Qt::DirectConnection); + QMetaObject::invokeMethod(this, "startPlayer"); + } + renderer->window = window(); + renderer->size = window()->size() * window()->devicePixelRatio(); +} + +void +DirectMpvPlayerBackend::swapped() +{ + mpv_opengl_cb_report_flip(mpv_gl, 0); +} + +void +DirectMpvPlayerBackend::cleanup() +{ + if (renderer) { + delete renderer; + renderer = 0; + } +} + +void +DirectMpvPlayerBackend::on_update(void* ctx) +{ + DirectMpvPlayerBackend* self = (DirectMpvPlayerBackend*)ctx; + emit self->onUpdate(); +} + +void +DirectMpvPlayerBackend::doUpdate() +{ + window()->update(); + update(); +} + +QVariant +DirectMpvPlayerBackend::getProperty(const QString& name) const +{ + return mpv::qt::get_property_variant(mpv, name); +} + +void +DirectMpvPlayerBackend::command(const QVariant& params) +{ + mpv::qt::command_variant(mpv, params); +} + +void +DirectMpvPlayerBackend::setProperty(const QString& name, const QVariant& value) +{ + mpv::qt::set_property_variant(mpv, name, value); +} + +void +DirectMpvPlayerBackend::setOption(const QString& name, const QVariant& value) +{ + mpv::qt::set_option_variant(mpv, name, value); +} + +void +DirectMpvPlayerBackend::launchAboutQt() +{ + QApplication* qapp = + qobject_cast(QCoreApplication::instance()); + qapp->aboutQt(); +} + +#ifdef DISCORD +void +DirectMpvPlayerBackend::updateDiscord() +{ + char buffer[256]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = getProperty("pause").toBool() ? "Paused" : "Playing"; + sprintf(buffer, + "Currently Playing: Video %s", + getProperty("media-title").toString().toUtf8().constData()); + discordPresence.details = buffer; + discordPresence.startTimestamp = time(0); + discordPresence.endTimestamp = time(0) + (getProperty("duration").toFloat() - + getProperty("time-pos").toFloat()); + discordPresence.instance = 0; + Discord_UpdatePresence(&discordPresence); +} +#endif +QVariant +DirectMpvPlayerBackend::playerCommand(const Enums::Commands& cmd) +{ + return playerCommand(cmd, QVariant("NoArgProvided")); +} + +QVariant +DirectMpvPlayerBackend::playerCommand(const Enums::Commands& cmd, + const QVariant& args) +{ + switch (cmd) { + case Enums::Commands::TogglePlayPause: { + command(QVariantList() << "cycle" + << "pause"); + break; + } + case Enums::Commands::ToggleMute: { + command(QVariantList() << "cycle" + << "mute"); + break; + } + case Enums::Commands::SetAudioDevice: { + setProperty("audio-device", args.toString()); + break; + } + case Enums::Commands::GetAudioDevices: { + QVariant drivers = getProperty("audio-device-list"); + QVariant currentDevice = getProperty("audio-device"); + + QVariantMap newDrivers; + + QSequentialIterable iterable = drivers.value(); + foreach (const QVariant& v, iterable) { + QVariantMap item = v.toMap(); + item["selected"] = currentDevice == item["name"]; + newDrivers[item["description"].toString()] = item; + } + QMap pulseItem; + pulseItem["name"] = "pulse"; + pulseItem["description"] = "Default (pulseaudio)"; + pulseItem["selected"] = currentDevice == "pulse"; + newDrivers[pulseItem["description"].toString()] = pulseItem; + return QVariant(newDrivers); + } + case Enums::Commands::SetVolume: { + command(QVariantList() << "set" + << "volume" << args); + break; + } + + case Enums::Commands::AddVolume: { + + command(QVariantList() << "add" + << "volume" << args); + break; + } + + case Enums::Commands::AddSpeed: { + + QString speedString = + QString::number(getProperty("speed").toDouble() + args.toDouble()); + QVariant newSpeed = + QVariant(speedString.left(speedString.lastIndexOf('.') + 2)); + + playerCommand(Enums::Commands::SetSpeed, newSpeed); + break; + } + + case Enums::Commands::SubtractSpeed: { + + QString speedString = + QString::number(getProperty("speed").toDouble() - args.toDouble()); + QVariant newSpeed = + QVariant(speedString.left(speedString.lastIndexOf('.') + 2)); + playerCommand(Enums::Commands::SetSpeed, newSpeed); + break; + } + + case Enums::Commands::ChangeSpeed: { + + playerCommand( + Enums::Commands::SetSpeed, + QVariant(getProperty("speed").toDouble() * args.toDouble())); + break; + } + + case Enums::Commands::SetSpeed: { + + command(QVariantList() << "set" + << "speed" << args.toString()); + break; + } + case Enums::Commands::ToggleStats: { + + command(QVariantList() << "script-binding" + << "stats/display-stats-toggle"); + break; + } + case Enums::Commands::NextAudioTrack: { + + command(QVariantList() << "cycle" + << "audio"); + break; + } + case Enums::Commands::NextSubtitleTrack: { + + command(QVariantList() << "cycle" + << "sub"); + + break; + } + case Enums::Commands::NextVideoTrack: { + command(QVariantList() << "cycle" + << "video"); + break; + } + case Enums::Commands::PreviousPlaylistItem: { + + command(QVariantList() << "playlist-prev"); + + break; + } + case Enums::Commands::NextPlaylistItem: { + + command(QVariantList() << "playlist-next" + << "force"); + break; + } + case Enums::Commands::LoadFile: { + command(QVariantList() << "loadfile" << args); + + break; + } + case Enums::Commands::AppendFile: { + + command(QVariantList() << "loadfile" << args << "append-play"); + break; + } + case Enums::Commands::Seek: { + + command(QVariantList() << "seek" << args); + + break; + } + case Enums::Commands::SeekAbsolute: { + + command(QVariantList() << "seek" << args << "absolute"); + + break; + } + case Enums::Commands::GetTracks: { + + return mpv::qt::get_property_variant(mpv, "track-list"); + + break; + } + case Enums::Commands::ForwardFrame: { + + command(QVariantList() << "frame-step"); + + break; + } + case Enums::Commands::BackwardFrame: { + + command(QVariantList() << "frame-back-step"); + + break; + } + + case Enums::Commands::SetTrack: { + + command(QVariantList() << "set" << args.toList()[0] << args.toList()[1]); + + break; + } + + default: { + qDebug() << "Command not found: " << cmd; + break; + } + } + return QVariant("NoOutput"); +} + +QVariant +DirectMpvPlayerBackend::createTimestamp(const QVariant& seconds) const +{ + int d = seconds.toInt(); + double h = floor(d / 3600); + double m = floor(d % 3600 / 60); + double s = floor(d % 3600 % 60); + + QString hour = h > 0 ? QString::number(h) + ":" : ""; + QString minute = h < 1 ? QString::number(m) + ":" + : (m < 10 ? "0" + QString::number(m) + ":" + : QString::number(m) + ":"); + QString second = s < 10 ? "0" + QString::number(s) : QString::number(s); + return hour + minute + second; +} + +void +DirectMpvPlayerBackend::handleWindowChanged(QQuickWindow* win) +{ + if (!win) + return; + connect(win, + &QQuickWindow::beforeSynchronizing, + this, + &DirectMpvPlayerBackend::sync, + Qt::DirectConnection); + connect(win, + &QQuickWindow::sceneGraphInvalidated, + this, + &DirectMpvPlayerBackend::cleanup, + Qt::DirectConnection); + connect(win, + &QQuickWindow::frameSwapped, + this, + &DirectMpvPlayerBackend::swapped, + Qt::DirectConnection); + win->setClearBeforeRendering(false); +} + +void +DirectMpvPlayerBackend::toggleOnTop() +{ + onTop = !onTop; + Utils::AlwaysOnTop(window()->winId(), onTop); +} + +void +DirectMpvPlayerBackend::on_mpv_events() +{ + while (mpv) { + mpv_event* event = mpv_wait_event(mpv, 0); + if (event->event_id == MPV_EVENT_NONE) { + break; + } + handle_mpv_event(event); + } +} + +void +DirectMpvPlayerBackend::updateDurationString() +{ + emit durationStringChanged( + QString("%1 / %2 (%3x)") + .arg(createTimestamp(getProperty("time-pos")).toString(), + createTimestamp(getProperty("duration")).toString(), + getProperty("speed").toString())); +} + +void +DirectMpvPlayerBackend::updateAppImage() +{ + Utils::updateAppImage(); +} + +void +DirectMpvPlayerBackend::handle_mpv_event(mpv_event* event) +{ + switch (event->event_id) { + case MPV_EVENT_PROPERTY_CHANGE: { + mpv_event_property* prop = (mpv_event_property*)event->data; + if (strcmp(prop->name, "time-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double*)prop->data; + emit positionChanged(time); + } + } else if (strcmp(prop->name, "duration") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double time = *(double*)prop->data; + emit durationChanged(time); + } + } else if (strcmp(prop->name, "mute") == 0 || + strcmp(prop->name, "volume") == 0) { + double volume = getProperty("volume").toDouble(); + bool mute = getProperty("mute").toBool(); + if (mute || volume == 0) { + emit volumeStatusChanged(Enums::VolumeStatus::Muted); + } else { + if (volume < 25) { + emit volumeStatusChanged(Enums::VolumeStatus::Low); + } else { + emit volumeStatusChanged(Enums::VolumeStatus::Normal); + } + } + // emit volumeChanged(volume); + } else if (strcmp(prop->name, "media-title") == 0) { + if (prop->format == MPV_FORMAT_STRING) { + char* title = *(char**)prop->data; + emit titleChanged(QString(title)); + } + } else if (strcmp(prop->name, "sub-text") == 0) { + if (prop->format == MPV_FORMAT_STRING) { + char* subs = *(char**)prop->data; + emit subtitlesChanged(QString(subs)); + } + } else if (strcmp(prop->name, "demuxer-cache-duration") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double duration = *(double*)prop->data; + emit cachedDurationChanged(duration); + } + } else if (strcmp(prop->name, "playlist-pos") == 0) { + if (prop->format == MPV_FORMAT_DOUBLE) { + double pos = *(double*)prop->data; + emit playlistPositionChanged(pos); + } + } else if (strcmp(prop->name, "pause") == 0) { + if (getProperty("pause").toBool()) { + emit playStatusChanged(Enums::PlayStatus::Paused); + } else { + emit playStatusChanged(Enums::PlayStatus::Playing); + } + } else if (strcmp(prop->name, "tracks-menu") == 0) { + emit tracksChanged(); + } else if (strcmp(prop->name, "audio-device-list") == 0) { + emit audioDevicesChanged(); + } +#ifdef DISCORD + updateDiscord(); +#endif + break; + } + case MPV_EVENT_SHUTDOWN: { + qApp->exit(); + break; + } + default: { + break; + } + } +} diff --git a/src/DirectMpvPlayerBackend.h b/src/DirectMpvPlayerBackend.h new file mode 100644 index 0000000..b45581b --- /dev/null +++ b/src/DirectMpvPlayerBackend.h @@ -0,0 +1,100 @@ +#ifndef DirectMpvPlayerBackend_Hc +#define DirectMpvPlayerBackend_H + +#include +#include +#include + +#include +#include +#include +#include + +#include "backendinterface.hpp" +#include "enums.hpp" + +class MpvRenderer : public QObject +{ + Q_OBJECT + mpv_handle* mpv; + mpv_opengl_cb_context* mpv_gl; + +public: + QQuickWindow* window; + QSize size; + + friend class MpvObject; + MpvRenderer(mpv_handle* a_mpv, mpv_opengl_cb_context* a_mpv_gl); + virtual ~MpvRenderer(); +public slots: + void paint(); +}; + +class DirectMpvPlayerBackend + : public QQuickItem + , public BackendInterface +{ + Q_INTERFACES(BackendInterface) + + Q_OBJECT + + mpv_handle* mpv; + mpv_opengl_cb_context* mpv_gl; + MpvRenderer* renderer; + bool onTop = false; + +public: + static void on_update(void* ctx); + + DirectMpvPlayerBackend(QQuickItem* parent = 0); + virtual ~DirectMpvPlayerBackend(); + +public slots: + QVariant playerCommand(const Enums::Commands& command, const QVariant& args); + QVariant playerCommand(const Enums::Commands& command); + void launchAboutQt(); + void toggleOnTop(); + void updateAppImage(); + // Optional but handy for MPV or custom backend settings. + void command(const QVariant& params); + void setProperty(const QString& name, const QVariant& value); + void setOption(const QString& name, const QVariant& value); + QVariant getProperty(const QString& name) const; + // Misc + QVariant createTimestamp(const QVariant& seconds) const; + // + void sync(); + void swapped(); + void cleanup(); + +signals: + void onUpdate(); + void mpv_events(); + // All below required for Player API + void playStatusChanged(const Enums::PlayStatus& status); + void volumeStatusChanged(const Enums::VolumeStatus& status); + void volumeChanged(const int& volume); + void durationChanged(const double& duration); + void positionChanged(const double& position); + void cachedDurationChanged(const double& duration); + void playlistPositionChanged(const double& position); + void titleChanged(const QString& title); + void subtitlesChanged(const QString& subtitles); + void durationStringChanged(const QString& string); + void tracksChanged(); + void audioDevicesChanged(); + +private slots: + void doUpdate(); + void on_mpv_events(); + void updateDurationString(); + void handleWindowChanged(QQuickWindow* win); + +private: + void handle_mpv_event(mpv_event* event); +#ifdef DISCORD + void updateDiscord(); +#endif +}; + +#endif diff --git a/src/MpvPlayerBackend.h b/src/MpvPlayerBackend.h index a7960a6..527a991 100644 --- a/src/MpvPlayerBackend.h +++ b/src/MpvPlayerBackend.h @@ -1,4 +1,4 @@ -#ifndef MpvPlayerBackend_H +#ifndef MpvPlayerBackend_Hc #define MpvPlayerBackend_H #include @@ -9,13 +9,19 @@ #include #include +#include "backendinterface.hpp" #include "enums.hpp" class MpvRenderer; -class MpvPlayerBackend : public QQuickFramebufferObject +class MpvPlayerBackend + : public QQuickFramebufferObject + , public BackendInterface { + Q_INTERFACES(BackendInterface) + Q_OBJECT + mpv_handle* mpv; mpv_render_context* mpv_gl; bool onTop = false; @@ -30,7 +36,6 @@ public: virtual Renderer* createRenderer() const; public slots: - // All 5 required for Player API QVariant playerCommand(const Enums::Commands& command, const QVariant& args); QVariant playerCommand(const Enums::Commands& command); void launchAboutQt(); @@ -41,7 +46,7 @@ public slots: void setProperty(const QString& name, const QVariant& value); void setOption(const QString& name, const QVariant& value); QVariant getProperty(const QString& name) const; - // Misc function. + // Misc QVariant createTimestamp(const QVariant& seconds) const; signals: diff --git a/src/backendinterface.hpp b/src/backendinterface.hpp new file mode 100644 index 0000000..23eb53d --- /dev/null +++ b/src/backendinterface.hpp @@ -0,0 +1,46 @@ +#include +#include +#include + +#include "enums.hpp" + +#ifndef BackendInterface_H +#define BackendInterface_H + +class BackendInterface +{ +public: + virtual ~BackendInterface(){}; + +public slots: + // All 5 required for Player API + virtual QVariant playerCommand(const Enums::Commands& command, + const QVariant& args) = 0; + virtual QVariant playerCommand(const Enums::Commands& command) = 0; + virtual void launchAboutQt() = 0; + virtual void toggleOnTop() = 0; + virtual void updateAppImage() = 0; + // Optional but handy for MPV or custom backend settings. + virtual void command(const QVariant& params) = 0; + virtual void setProperty(const QString& name, const QVariant& value) = 0; + virtual void setOption(const QString& name, const QVariant& value) = 0; + virtual QVariant getProperty(const QString& name) const = 0; + +signals: + // All below required for Player API + virtual void playStatusChanged(const Enums::PlayStatus& status) = 0; + virtual void volumeStatusChanged(const Enums::VolumeStatus& status) = 0; + virtual void volumeChanged(const int& volume) = 0; + virtual void durationChanged(const double& duration) = 0; + virtual void positionChanged(const double& position) = 0; + virtual void cachedDurationChanged(const double& duration) = 0; + virtual void playlistPositionChanged(const double& position) = 0; + virtual void titleChanged(const QString& title) = 0; + virtual void subtitlesChanged(const QString& subtitles) = 0; + virtual void durationStringChanged(const QString& string) = 0; + virtual void tracksChanged() = 0; + virtual void audioDevicesChanged() = 0; +}; +Q_DECLARE_INTERFACE(BackendInterface, "NamedKitten.BackendInterface"); + +#endif diff --git a/src/enums.hpp b/src/enums.hpp index f003f91..c646472 100644 --- a/src/enums.hpp +++ b/src/enums.hpp @@ -47,6 +47,12 @@ enum class Commands : int SetTrack = 23 }; Q_ENUM_NS(Commands) +enum class Backends : int +{ + MpvBackend = 0, + DirectMpvBackend = 1 +}; +Q_ENUM_NS(Backends) } #endif diff --git a/src/main.cpp b/src/main.cpp index 4e4f2a7..20a088d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,7 +2,10 @@ #include "runtimeqml/runtimeqml.h" #endif +#include "DirectMpvPlayerBackend.h" #include "MpvPlayerBackend.h" + +#include "enums.hpp" #include "utils.hpp" #include @@ -51,6 +54,8 @@ catchUnixSignals(std::initializer_list quitSignals) int main(int argc, char* argv[]) { + + Enums::Backends backend = Enums::Backends::MpvBackend; setenv("QT_QUICK_CONTROLS_STYLE", "Desktop", 1); QApplication app(argc, argv); #ifdef __linux__ @@ -79,8 +84,9 @@ main(int argc, char* argv[]) QProcess notifier; notifier.setProcessChannelMode(QProcess::ForwardedChannels); notifier.start("notify-send", - QStringList() - << "KittehPlayer" << "New update avalable!" << "--icon=KittehPlayer"); + QStringList() << "KittehPlayer" + << "New update avalable!" + << "--icon=KittehPlayer"); notifier.waitForFinished(); } } else { @@ -94,6 +100,12 @@ main(int argc, char* argv[]) for (int i = 1; i < argc; ++i) { if (!qstrcmp(argv[i], "--update")) { Utils::updateAppImage(); + } else if (!qstrcmp(argv[i], "--backend=mpv")) { + qDebug() << "Using MPV backend."; + backend = Enums::Backends::MpvBackend; + } else if (!qstrcmp(argv[i], "--backend=direct-mpv")) { + qDebug() << "Using Direct MPV backend."; + backend = Enums::Backends::DirectMpvBackend; } } @@ -118,9 +130,21 @@ main(int argc, char* argv[]) ); qRegisterMetaType("Enums.PlayStatus"); qRegisterMetaType("Enums.VolumeStatus"); + qRegisterMetaType("Enums.Backends"); + qRegisterMetaType("Enums.Commands"); - qmlRegisterType("player", 1, 0, "PlayerBackend"); + switch (backend) { + case Enums::Backends::MpvBackend: { + qmlRegisterType("player", 1, 0, "PlayerBackend"); + break; + } + case Enums::Backends::DirectMpvBackend: { + qmlRegisterType("player", 1, 0, "PlayerBackend"); + break; + } + } + std::setlocale(LC_NUMERIC, "C"); QQmlApplicationEngine engine; diff --git a/src/qml/main.qml b/src/qml/main.qml index dc2c7bd..a4ce013 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -18,24 +18,7 @@ ApplicationWindow { Translator { id: translate } - - property int lastScreenVisibility - - function toggleFullscreen() { - if (mainWindow.visibility != Window.FullScreen) { - lastScreenVisibility = mainWindow.visibility - mainWindow.visibility = Window.FullScreen - } else { - mainWindow.visibility = lastScreenVisibility - } - } - - PlayerBackend { - id: player - anchors.fill: parent - width: parent.width - height: parent.height - + Settings { id: appearance category: "Appearance" @@ -56,7 +39,85 @@ ApplicationWindow { property bool nyanCat: false } + Settings { + id: keybinds + category: "Keybinds" + property string playPause: "K" + property string forward10: "L" + property string rewind10: "J" + property string forward5: "Right" + property string rewind5: "Left" + property string openFile: "Ctrl+O" + property string openURI: "Ctrl+Shift+O" + property string quit: "Ctrl+Q" + property string fullscreen: "F" + property string tracks: "Ctrl+T" + property string statsForNerds: "I" + property string forwardFrame: "." + property string backwardFrame: "," + property string cycleSub: "Alt+S" + property string cycleSubBackwards: "Alt+Shift+S" + property string cycleAudio: "A" + property string cycleVideo: "V" + property string cycleVideoAspect: "Shift+A" + property string screenshot: "S" + property string screenshotWithoutSubtitles: "Shift+S" + property string fullScreenshot: "Ctrl+S" + property string nyanCat: "Ctrl+N" + property string decreaseSpeedByPointOne: "[" + property string increaseSpeedByPointOne: "]" + property string halveSpeed: "{" + property string doubleSpeed: "}" + property string increaseVolume: "*" + property string decreaseVolume: "/" + property string mute: "m" + property string customKeybind0: "" + property string customKeybind0Command: "" + property string customKeybind1: "" + property string customKeybind1Command: "" + property string customKeybind2: "" + property string customKeybind2Command: "" + property string customKeybind3: "" + property string customKeybind3Command: "" + property string customKeybind4: "" + property string customKeybind4Command: "" + property string customKeybind5: "" + property string customKeybind5Command: "" + property string customKeybind6: "" + property string customKeybind6Command: "" + property string customKeybind7: "" + property string customKeybind7Command: "" + property string customKeybind8: "" + property string customKeybind8Command: "" + property string customKeybind9: "" + property string customKeybind9Command: "" + } + + + property int lastScreenVisibility + + function toggleFullscreen() { + if (mainWindow.visibility != Window.FullScreen) { + lastScreenVisibility = mainWindow.visibility + mainWindow.visibility = Window.FullScreen + } else { + mainWindow.visibility = lastScreenVisibility + } + } + + PlayerBackend { + id: player + anchors.fill: parent + width: parent.width + height: parent.height + z: 1 + + + + function startPlayer() { + console.log(player) + console.log(typeof(player)) var args = Qt.application.arguments var len = Qt.application.arguments.length var argNo = 0 @@ -228,59 +289,6 @@ ApplicationWindow { } } - Settings { - id: keybinds - category: "Keybinds" - property string playPause: "K" - property string forward10: "L" - property string rewind10: "J" - property string forward5: "Right" - property string rewind5: "Left" - property string openFile: "Ctrl+O" - property string openURI: "Ctrl+Shift+O" - property string quit: "Ctrl+Q" - property string fullscreen: "F" - property string tracks: "Ctrl+T" - property string statsForNerds: "I" - property string forwardFrame: "." - property string backwardFrame: "," - property string cycleSub: "Alt+S" - property string cycleSubBackwards: "Alt+Shift+S" - property string cycleAudio: "A" - property string cycleVideo: "V" - property string cycleVideoAspect: "Shift+A" - property string screenshot: "S" - property string screenshotWithoutSubtitles: "Shift+S" - property string fullScreenshot: "Ctrl+S" - property string nyanCat: "Ctrl+N" - property string decreaseSpeedByPointOne: "[" - property string increaseSpeedByPointOne: "]" - property string halveSpeed: "{" - property string doubleSpeed: "}" - property string increaseVolume: "*" - property string decreaseVolume: "/" - property string mute: "m" - property string customKeybind0: "" - property string customKeybind0Command: "" - property string customKeybind1: "" - property string customKeybind1Command: "" - property string customKeybind2: "" - property string customKeybind2Command: "" - property string customKeybind3: "" - property string customKeybind3Command: "" - property string customKeybind4: "" - property string customKeybind4Command: "" - property string customKeybind5: "" - property string customKeybind5Command: "" - property string customKeybind6: "" - property string customKeybind6Command: "" - property string customKeybind7: "" - property string customKeybind7Command: "" - property string customKeybind8: "" - property string customKeybind8Command: "" - property string customKeybind9: "" - property string customKeybind9Command: "" - } MainMenu { id: menuBar