#include <QQmlContext>
#include "player.h"
+#include "pluginmanager.h"
#include "timeformatter.h"
Q_LOGGING_CATEGORY(imCategory, "InstanceManager")
-InstanceManager::InstanceManager(QObject *parent) : QObject(parent) {
- parser.addHelpOption();
- parser.addVersionOption();
- parser.addPositionalArgument("files", "Files to play", "[files...]");
- parser.addOption(backendOption);
- parser.addOption(uiOption);
- parser.addOption(positionOption);
+InstanceManager::InstanceManager(QObject *parent)
+ : QObject(parent), m_backendPluginManager{new PluginManager} {
+ m_parser.addHelpOption();
+ m_parser.addVersionOption();
+ m_parser.addPositionalArgument("files", "Files to play", "[files...]");
+ m_parser.addOption(backendOption);
+ m_parser.addOption(uiOption);
+ m_parser.addOption(positionOption);
}
-int InstanceManager::runInstance(const QCoreApplication &app) {
- parser.process(app);
+void InstanceManager::startFirstInstance() {
+ m_parser.process(*qApp);
- const auto positionalArgs = parser.positionalArguments();
+ const auto positionalArgs = m_parser.positionalArguments();
- QQmlApplicationEngine engine;
+ QQmlApplicationEngine *engine = new QQmlApplicationEngine{this};
+ Q_CHECK_PTR(engine);
- player = new Player{this};
- Q_CHECK_PTR(player);
+ auto player = createInstance();
qCDebug(imCategory, "Player Created");
+ m_instances.insert(player);
+
if (!positionalArgs.empty())
player->setNextSource(QUrl::fromUserInput(positionalArgs[0]));
TimeFormatter timeFormatter;
- engine.rootContext()->setContextProperty("player", player);
- engine.rootContext()->setContextProperty("timeFormatter", &timeFormatter);
+ engine->rootContext()->setContextProperty("player", player);
+ engine->rootContext()->setContextProperty("timeFormatter", &timeFormatter);
qCDebug(imCategory, "Player Added");
- engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
+ engine->load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
qCDebug(imCategory, "QML engine loaded");
- return app.exec();
+}
+
+void InstanceManager::startInstance() {}
+
+QString InstanceManager::encodeCommandLine() const {
+ QByteArray arr;
+ {
+ QDataStream stream{&arr, QIODevice::WriteOnly};
+ stream << qApp->arguments();
+ }
+ const auto base64 = arr.toBase64();
+ return QString::fromLatin1(base64);
}
void InstanceManager::handleSingleInstanceMessage(const QString &message) {
<< "Failed to read serialized single instance message";
return;
}
- if (!parser.parse(args)) {
+ if (!m_parser.parse(args)) {
qCWarning(imCategory)
<< "Failed to parse arguments from single instance message";
return;
}
- const auto positionalArgs = parser.positionalArguments();
+ const auto positionalArgs = m_parser.positionalArguments();
if (positionalArgs.empty()) {
qCInfo(imCategory()) << "No new file to open";
return;
}
- Q_ASSERT(player);
+
+ const auto it = m_instances.cbegin();
+
+ Q_ASSERT(it != m_instances.cend());
+
+ auto player = *it;
+
player->loadAndPlay(QUrl::fromUserInput(positionalArgs[0]));
}
+
+Player *InstanceManager::createInstance() {
+#ifdef Q_OS_WIN
+ static QStringList pluginPaths{"backendplugins"};
+#else
+ static QStringList pluginPaths{"backendplugins",
+ "/usr/lib/aniplayer/backendplugins"};
+#endif
+ m_backendPluginManager->setPluginDirectories(pluginPaths);
+ m_backendPluginManager->setPluginPrefix("backend");
+ m_backendPluginManager->setPreferredPlugins({"mpv"});
+ m_backendPluginManager->loadBestPlugin();
+ auto instance = m_backendPluginManager->bestInstance<BackendPluginBase>();
+ if (!instance)
+ throw std::runtime_error{std::string("Failed to load backend: ") +
+ qPrintable(m_backendPluginManager->errorString())};
+
+ auto player = new Player{instance, this};
+ Q_CHECK_PTR(player);
+ return player;
+}
#include <QObject>
-#include <QCoreApplication>
#include <QCommandLineOption>
#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QSet>
class Player;
+class PluginManager;
class InstanceManager : public QObject {
Q_OBJECT
public:
explicit InstanceManager(QObject *parent = 0);
- int runInstance(const QCoreApplication &app);
+ void startFirstInstance();
+ void startInstance();
+
public slots:
+ QString encodeCommandLine() const;
void handleSingleInstanceMessage(const QString &message);
private:
+ Player *createInstance();
const QCommandLineOption backendOption{"backend", "Use backend",
"name of backend plugin", "default"};
const QCommandLineOption uiOption{"ui", "Use ui", "name of ui", "default"};
const QCommandLineOption positionOption{"position",
"Start playback specific position",
"position in seconds", "0"};
- QCommandLineParser parser;
- Player *player = nullptr;
+ const QCommandLineOption multiInstanceOption{
+ QStringList{"m", "multi-instance"}, "Open more than one player instance"};
+ QCommandLineParser m_parser;
+ QSet<Player *> m_instances;
+ PluginManager *m_backendPluginManager;
};
#endif // INSTANCEMANAGER_H
SLOT(handleSingleInstanceMessage(QString)));
if (app.isRunning()) {
- QByteArray arr;
- {
- QDataStream stream{&arr, QIODevice::WriteOnly};
- stream << app.arguments();
- }
- const auto base64 = arr.toBase64();
- if (app.sendMessage(QString::fromUtf8(base64)))
+
+ if (app.sendMessage(im.encodeCommandLine()))
return 0;
return 1;
}
Player::reqisterQmlTypes();
try {
- return im.runInstance(app);
+ im.startFirstInstance();
+ return app.exec();
} catch (const std::exception &ex) {
qDebug("Exception: %s", ex.what());
}
Q_LOGGING_CATEGORY(playerCategory, "Player")
-Player::Player(QObject *parent) : QObject(parent) {
+Player::Player(BackendPluginBase *backendPlugin, QObject *parent)
+ : QObject(parent) {
+ if (!backendPlugin) {
+ qCCritical(playerCategory)
+ << "Trying to create a player without a backendPlugin";
+ return;
+ }
qCDebug(playerCategory) << "Creating player" << this;
- loadBackend();
+ m_backend = backendPlugin->createInstance(this);
+ Q_CHECK_PTR(m_backend);
}
-Player::~Player()
-{
- qCDebug(playerCategory) << "Destroying player" << this;
-}
+Player::~Player() { qCDebug(playerCategory) << "Destroying player" << this; }
BackendInstance *Player::backend() const { return m_backend; }
qmlRegisterType<Player>("org.aptx.aniplayer", 1, 0, "Player");
}
-void Player::loadBackend() {
-#ifdef Q_OS_WIN
- QStringList pluginPaths{"backendplugins"};
-#else
- QStringList pluginPaths{"backendplugins",
- "/usr/lib/aniplayer/backendplugins"};
-#endif
- m_pluginManager.setPluginDirectories(pluginPaths);
- m_pluginManager.setPluginPrefix("backend");
- m_pluginManager.setPreferredPlugins({"mpv"});
- m_pluginManager.loadBestPlugin();
- m_plugin = m_pluginManager.bestInstance<BackendPluginBase>();
- if (!m_plugin)
- throw std::runtime_error{std::string("Failed to load backend: ") +
- qPrintable(m_pluginManager.errorString())};
- m_backend = m_plugin->createInstance(this);
- Q_CHECK_PTR(m_backend);
- qCDebug(playerCategory) << "Loaded backend" << m_backend;
-}
-
bool Player::canLoadVideoNow() const {
return m_backendInstanceReady && m_renderer && m_rendererReady;
}
static const constexpr Volume MAX_VOLUME = Volume{1.0};
- explicit Player(QObject *parent = 0);
+ // BackendPluginBase * allowed to be null because this is exposed to QML
+ // for the enums/types. Player should never be created in QML.
+ explicit Player(BackendPluginBase * = nullptr, QObject *parent = nullptr);
~Player() override;
enum class PlayState {
static void reqisterQmlTypes();
private:
- void loadBackend();
bool canLoadVideoNow() const;
void loadNextFile();
- PluginManager m_pluginManager;
- BackendPluginBase *m_plugin = nullptr;
BackendInstance *m_backend = nullptr;
QUrl m_currentSource;
+ QUrl m_nextSource;
PlayState m_state = PlayState::Stopped;
Volume m_volume = MAX_VOLUME;
AudioStreams m_availableAudioStreams;
SubtitleStreams m_availableSubtitleStreams;
Player::TimeStamp m_duration = 0;
Player::TimeStamp m_position = 0;
- QUrl m_nextSource;
VideoUpdateInterface *m_renderer = nullptr;
bool m_muted = false;
bool m_backendInstanceReady = false;
signals:
void pluginDirectoriesChanged(QStringList pluginDirectories);
void pluginPrefixChanged(QString pluginPrefix);
-
void preferredPluginsChanged(QStringList preferredPlugins);
private: