SUBDIRS += \
core \
- backendplugins
+ backendplugins \
+ featureplugins
player.h \
include/aniplayer/backendpluginbase.h \
include/aniplayer/playerplugininterface.h \
+ include/aniplayer/featurepluginbase.h \
pluginmanager.h \
videoelement.h \
timeformatter.h \
--- /dev/null
+#ifndef FEATUREPLUGINBASE_H
+#define FEATUREPLUGINBASE_H
+
+#include <QObject>
+
+class FeaturePluginBase {
+public:
+ virtual ~FeaturePluginBase() = default;
+
+ virtual void instanceCreated(QObject *instance) = 0;
+ virtual void instanceDestroyed(QObject *instance) = 0;
+};
+
+#define ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID \
+ "org.aptx.aniplayer.FeaturePluginInterface"
+
+Q_DECLARE_INTERFACE(FeaturePluginBase, ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID)
+
+#endif // FEATUREPLUGINBASE_H
#include <QQmlApplicationEngine>
#include <QQmlContext>
+#include "aniplayer/featurepluginbase.h"
#include "player.h"
#include "pluginmanager.h"
#include "timeformatter.h"
m_parser.addOption(positionOption);
m_parser.addOption(multiInstanceOption);
m_parser.addOption(cwdOption);
+
+#ifdef Q_OS_WIN
+ static QStringList pluginPaths{"featureplugins"};
+#else
+ static QStringList pluginPaths{"featureplugins",
+ "/usr/lib/aniplayer/featureplugins"};
+#endif
+
+ m_featurePluginManager = new PluginManager;
+ m_featurePluginManager->setPluginDirectories(pluginPaths);
+ m_featurePluginManager->setPluginPrefix("feature");
+ // TODO feature plugins should be looked up and not preferred.
+ m_featurePluginManager->setPreferredPlugins({"localmylist"});
+ m_featurePluginManager->loadAll();
}
void InstanceManager::startFirstInstance() {
if (!positionalArgs.empty()) {
auto url = parseUserInput(positionalArgs[0], m_parser.value(cwdOption));
qCDebug(imCategory) << "Parsed positional argument as" << url;
- qCDebug(imCategory) << "Parsed positional argument as" << url;
player->setNextSource(url);
}
auto timeFormatter = new TimeFormatter{this};
auto player = new Player{instance, this};
Q_CHECK_PTR(player);
+
+ m_featurePluginManager->forEach<FeaturePluginBase>(
+ [player](FeaturePluginBase *plugin) { plugin->instanceCreated(player); });
return player;
}
QCommandLineParser m_parser;
QSet<Player *> m_instances;
PluginManager *m_backendPluginManager;
+ PluginManager *m_featurePluginManager;
};
#endif // INSTANCEMANAGER_H
return m_availableSubtitleStreams;
}
-Player::TimeStamp Player::duration() const { return m_duration; }
+double Player::duration() const { return m_duration; }
-PlayerPluginInterface::TimeStamp Player::position() const { return m_position; }
+double Player::position() const { return m_position; }
void Player::load(const QUrl &resource) {
if (canLoadVideoNow())
Q_PROPERTY(QUrl nextSource READ nextSource WRITE setNextSource NOTIFY
nextSourceChanged)
- Q_PROPERTY(Player::TimeStamp duration READ duration NOTIFY durationChanged)
- Q_PROPERTY(Player::TimeStamp position READ position WRITE seek NOTIFY
- positionChanged)
+ Q_PROPERTY(double duration READ duration NOTIFY durationChanged)
+ Q_PROPERTY(double position READ position WRITE seek NOTIFY positionChanged)
Q_PROPERTY(Player::PlayState state READ state NOTIFY stateChanged)
Q_PROPERTY(double volume READ volume WRITE setVolume NOTIFY volumeChanged)
using VideoStreams = QList<QString>;
using SubtitleStreams = QList<QString>;
using Volume = double;
- using TimeStamp = PlayerPluginInterface::TimeStamp;
static const constexpr Volume MAX_VOLUME = Volume{1.0};
VideoStreams availableVideoStreams() const;
SubtitleStreams availableSubtitleStreams() const;
- Player::TimeStamp duration() const;
- Player::TimeStamp position() const;
+ double duration() const;
+ double position() const;
signals:
void stateChanged(PlayState state);
void currentSourceChanged(QUrl currentSource);
void nextSourceChanged(QUrl nextSource);
- void durationChanged(Player::TimeStamp duration);
- void positionChanged(Player::TimeStamp position);
+ void durationChanged(double duration);
+ void positionChanged(double position);
public slots:
// Basic Play state
emit preferredPluginsChanged(preferredPlugins);
}
+void PluginManager::loadAll()
+{
+ // TODO should load all plugins found, not all preferred plugins
+ for (const auto &pluginName : m_preferredPlugins)
+ load(pluginName);
+}
+
bool PluginManager::load(const QString &plugin) {
const auto it = m_plugins.find(plugin);
#ifndef PLUGINMANAGER_H
#define PLUGINMANAGER_H
+#include <QMap>
#include <QObject>
#include <QPluginLoader>
#include <QStringList>
-#include <QMap>
class PluginManager : public QObject {
Q_OBJECT
return qobject_cast<Interface *>(bestQObjectInstance());
}
+ // TODO remove this hack
+ template <typename Interface, typename Func> void forEach(Func &&func) {
+ for (const auto &plugin : m_plugins) {
+ const auto instance = qobject_cast<Interface *>(plugin->instance());
+ if (instance)
+ func(instance);
+ }
+ }
+
public slots:
void setPluginDirectories(QStringList pluginDirectories);
void setPluginPrefix(QString pluginPrefix);
void setPreferredPlugins(QStringList preferredPlugins);
+ void loadAll();
+
bool load(const QString &plugin);
QObject *qObjectInstance(const QString &plugin);
--- /dev/null
+TARGET = feature_localmylist
+QT -= gui
+QT += sql
+TEMPLATE = lib
+
+include(../../core/core.pri)
+include(../featurebuildconfig.pri)
+
+DEFINES += FEATURE_LOCALMYLIST_LIBRARY QT_DEPRECATED_WARNINGS
+
+SOURCES += featurelocalmylist.cpp
+
+HEADERS += featurelocalmylist.h\
+ feature_localmylist_global.h
+
+DISTFILES += feature_localmylist.json
+
+LIBS += -llocalmylist
+
+unix {
+ target.path = $${PREFIX}/lib/aniplayer/featureplugins
+ INSTALLS += target
+}
--- /dev/null
+#ifndef FEATURE_LOCALMYLIST_GLOBAL_H
+#define FEATURE_LOCALMYLIST_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(FEATURE_LOCALMYLIST_LIBRARY)
+# define FEATURE_LOCALMYLISTSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define FEATURE_LOCALMYLISTSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // FEATURE_LOCALMYLIST_GLOBAL_H
--- /dev/null
+#include "featurelocalmylist.h"
+
+#include <QLoggingCategory>
+#include <QMetaProperty>
+
+#include <LocalMyList/Database>
+#include <LocalMyList/MyList>
+
+Q_LOGGING_CATEGORY(lmlCategory, "LML")
+
+constexpr const double PERCENT = 80.0;
+
+FeatureLocalMyList::FeatureLocalMyList(QObject *parent) : QObject{parent} {
+ LocalMyList::instance()->loadLocalSettings();
+
+ if (LocalMyList::instance()->database()->connect())
+ qCInfo(lmlCategory) << "Init successful";
+ else
+ qCWarning(lmlCategory) << "Init failed";
+}
+
+void FeatureLocalMyList::instanceCreated(QObject *instance) {
+ qCDebug(lmlCategory) << "Registering with instance" << instance;
+ connect(instance, SIGNAL(currentSourceChanged(QUrl)), this,
+ SLOT(sourceChanged(QUrl)));
+ connect(instance, SIGNAL(positionChanged(double)), this,
+ SLOT(positionChanged(double)));
+ connect(instance, SIGNAL(durationChanged(double)), this,
+ SLOT(durationChanged(double)));
+}
+
+void FeatureLocalMyList::instanceDestroyed(QObject *instance) {
+ qCDebug(lmlCategory) << "Unregistering from instance" << instance;
+ m_instanceMap.remove(instance);
+}
+
+void FeatureLocalMyList::sourceChanged(const QUrl &source) {
+ if (!source.isLocalFile())
+ return;
+ const auto path = source.toLocalFile();
+
+ const auto file = LocalMyList::instance()->database()->getFileByPath(path);
+
+ if (!file.fid) {
+ qCInfo(lmlCategory) << "File" << path << "is not in LocalMyList";
+ return;
+ }
+
+ if (file.myWatched.isValid()) {
+ qCInfo(lmlCategory) << "File" << path << " already marked watched";
+ return;
+ }
+
+ qCInfo(lmlCategory) << "File" << path
+ << "found in LocalMyList, fid =" << file.fid;
+
+ auto &data = m_instanceMap[sender()];
+ data.duration = readDuration(sender());
+ data.fid = file.fid;
+ data.path = path;
+}
+
+void FeatureLocalMyList::durationChanged(double duration)
+{
+ qCDebug(lmlCategory) << "Duration changed for " << sender();
+ const auto it = m_instanceMap.find(sender());
+ if (it == m_instanceMap.cend())
+ return;
+
+ auto &data = it.value();
+
+ data.duration = duration;
+}
+
+void FeatureLocalMyList::positionChanged(double position) {
+ {
+ const auto it = m_instanceMap.find(sender());
+ if (it == m_instanceMap.cend())
+ return;
+
+ const auto &data = it.value();
+
+ if (data.duration < 1.0)
+ return;
+
+ if (!data.fid)
+ return;
+
+ if (position / data.duration * 100.0 < PERCENT)
+ return;
+
+ qCInfo(lmlCategory) << "Marking file" << data.path << "watched";
+ LocalMyList::instance()->markWatchedIfUnwatched(data.fid);
+ }
+ m_instanceMap.remove(sender());
+}
+
+double FeatureLocalMyList::readDuration(QObject *obj)
+{
+ const auto mo = obj->metaObject();
+ const auto durationIdx = mo->indexOfProperty("duration");
+ qCDebug(lmlCategory) << "duration propert index" << durationIdx;
+ const auto durationVariant = mo->property(durationIdx).read(obj);
+ if (!durationVariant.isValid())
+ qCWarning(lmlCategory) << "Failed to read duration";
+
+ const auto duration = durationVariant.toDouble();
+ qCDebug(lmlCategory) << "File duration read" << duration;
+ return duration;
+}
--- /dev/null
+#ifndef FEATURELOCALMYLIST_H
+#define FEATURELOCALMYLIST_H
+
+#include <QHash>
+#include <QObject>
+#include <QUrl>
+#include <QtPlugin>
+
+#include "aniplayer/featurepluginbase.h"
+#include "feature_localmylist_global.h"
+
+class FEATURE_LOCALMYLISTSHARED_EXPORT FeatureLocalMyList
+ : public QObject,
+ public FeaturePluginBase {
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID ANIPLAYER_FEATURE_PLUGIN_INTERFACE_IID FILE
+ "feature_localmylist.json")
+ Q_INTERFACES(FeaturePluginBase)
+public:
+ FeatureLocalMyList(QObject *parent = nullptr);
+
+ void instanceCreated(QObject *instance) override;
+ void instanceDestroyed(QObject *instance) override;
+
+private slots:
+ void sourceChanged(const QUrl &source);
+ void durationChanged(double duration);
+ void positionChanged(double position);
+
+private:
+ static double readDuration(QObject *obj);
+
+ struct FileData {
+ int fid;
+ QString path;
+ double duration;
+ };
+ QHash<QObject *, FileData> m_instanceMap;
+};
+
+#endif // FEATURELOCALMYLIST_H
--- /dev/null
+include(../buildconfig.pri)
+DESTDIR=../../build/featureplugins
\ No newline at end of file
--- /dev/null
+TEMPLATE = subdirs
+
+include(../config.pri)
+
+feature_plugin_localmylist {
+ SUBDIRS += feature_localmylist
+}