#include <QString>
#include <QUrl>
+#include "mpvhelper.h"
#include "qthelper.hpp"
#include <mpv/render.h>
}
{
double maxVolume = 130.0;
- mpv_set_property(m_handle, "max-volume", MPV_FORMAT_DOUBLE, &maxVolume);
- mpv_get_property(m_handle, "max-volume", MPV_FORMAT_DOUBLE, &maxVolume);
maxVolume /= 100.0;
m_player->playbackMaxVolumeChanged(maxVolume);
}
void MpvInstance::play() {
qCDebug(mpvBackend) << "Play";
- int f = 0;
- mpv_set_property(m_handle, "pause", MPV_FORMAT_FLAG, &f);
+ mpv::SetProperty(m_handle, mpv::property::Pause, false);
}
void MpvInstance::pause() {
qCDebug(mpvBackend) << "Pause";
- int f = 1;
- mpv_set_property(m_handle, "pause", MPV_FORMAT_FLAG, &f);
+ mpv::SetProperty(m_handle, mpv::property::Pause, true);
}
void MpvInstance::stop() { qCDebug(mpvBackend) << "Stop"; }
void MpvInstance::seek(TimeStamp pos) {
- mpv_set_property(m_handle, "playback-time", MPV_FORMAT_DOUBLE, &pos);
+ mpv::SetProperty(m_handle, mpv::property::PlaybackTime, pos);
}
void MpvInstance::setVolume(Volume volume) {
double percantageVolume = volume * 100;
int error =
- mpv_set_property(m_handle, VOLUME, MPV_FORMAT_DOUBLE, &percantageVolume);
+ mpv::SetProperty(m_handle, mpv::property::Volume, percantageVolume);
if (error) {
qCDebug(mpvBackend)
<< "Audio output not yet ready, setting volume at a later time";
}
void MpvInstance::setCurrentVideoStream(TrackIndex track) {
- qint64 tmp = track;
- if (track < 0)
- mpv_set_property_string(m_handle, "vid", "no");
- else
- mpv_set_property(m_handle, "vid", MPV_FORMAT_INT64, &tmp);
+ if (track < 0) {
+ mpv::SetProperty(m_handle, mpv::property::Vid, "no");
+ } else {
+ mpv::SetProperty(m_handle, mpv::property::Vid, mpv::format::Int64, track);
+ }
}
void MpvInstance::setCurrentAudioStream(TrackIndex track) {
- qint64 tmp = track;
- if (track < 0)
- mpv_set_property_string(m_handle, "aid", "no");
- else
- mpv_set_property(m_handle, "aid", MPV_FORMAT_INT64, &tmp);
+ if (track < 0) {
+ mpv::SetProperty(m_handle, mpv::property::Aid, "no");
+ } else {
+ mpv::SetProperty(m_handle, mpv::property::Aid, mpv::format::Int64, track);
+ }
}
void MpvInstance::setCurrentSubtitleStream(TrackIndex track) {
- qint64 tmp = track;
- if (track < 0)
- mpv_set_property_string(m_handle, "sid", "no");
- else
- mpv_set_property(m_handle, "sid", MPV_FORMAT_INT64, &tmp);
+ if (track < 0) {
+ mpv::SetProperty(m_handle, mpv::property::Sid, "no");
+ } else {
+ mpv::SetProperty(m_handle, mpv::property::Sid, mpv::format::Int64, track);
+ }
}
template <int type> struct MpvProperty;
void MpvInstance::onPropertyChange(mpv_event_property *property) {
qCDebug(mpvVerboseBackend) << "Property" << property->name << "changed";
if (property->format == MPV_FORMAT_NONE) {
- qCDebug(mpvBackend) << "No data in event";
+ qCDebug(mpvBackend) << "No data in event for property: " << property->name;
return;
}
if (strcmp(property->name, "pause") == 0) {
}
void MpvInstance::onFileLoaded() {
- int paused = 0;
- mpv_get_property(m_handle, "paused", MPV_FORMAT_FLAG, &paused);
+ bool paused = *mpv::GetProperty(m_handle, mpv::property::Paused);
qCDebug(mpvBackend) << "file-loaded event!" << paused;
m_loadedFile = true;
auto state = paused ? PlayerPluginInterface::PlayState::Paused
--- /dev/null
+#ifndef MPVHELPER_H
+#define MPVHELPER_H
+
+#include <mpv/client.h>
+
+#include <QString>
+#include <QVariant>
+
+#include <memory>
+
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(mpvHelper, "mpv.helper")
+
+namespace mpv {
+
+using Handle = mpv_handle *;
+
+struct Error
+{
+ using ErrorType = int;
+ ErrorType error;
+};
+
+template<typename T>
+class Outcome {
+ Error::ErrorType m_error = MPV_ERROR_SUCCESS;
+ T m_value;
+
+public:
+ Outcome(T value) : m_value{std::move(value)} {}
+ Outcome(Error error) : m_error{error.error} {}
+
+ bool HasError() const { return m_error != MPV_ERROR_SUCCESS; }
+ bool ok() const { return !HasError(); }
+ explicit operator bool() const { return ok(); }
+ const T &value() const {
+ assert(!HasError());
+ return m_value;
+ }
+ const T &operator*() const { return value(); }
+ Error::ErrorType error() const {
+ assert(HasError());
+ return m_error;
+ }
+};
+
+template<typename DataType = void>
+struct Event
+{
+ using type = DataType;
+ const mpv_event_id id;
+ constexpr explicit Event(mpv_event_id id) : id{id} {}
+};
+
+namespace event {
+// TODO add all events?
+constexpr Event<mpv_event_property> PropertyChange{MPV_EVENT_PROPERTY_CHANGE};
+constexpr Event<mpv_event_log_message> LogMessage{MPV_EVENT_LOG_MESSAGE};
+constexpr Event<> FileLoaded{MPV_EVENT_FILE_LOADED};
+constexpr Event<mpv_event_end_file> EndFile{MPV_EVENT_END_FILE};
+constexpr Event<> AudioReconfig{MPV_EVENT_AUDIO_RECONFIG};
+} // namespace event
+
+template<mpv_format FormatValue, typename CppType,
+ typename MpvInternalTypeName = CppType, bool ReadOnly = false>
+struct Format
+{
+ static_assert(!std::is_same_v<MpvInternalTypeName, bool>,
+ "MpvInternalType can not be bool");
+ // The type returned/provided from C++
+ using type = CppType;
+ // The type used internally by mpc
+ using MpvInternalType = MpvInternalTypeName;
+ static constexpr mpv_format format = FormatValue;
+ static constexpr bool readOnly = ReadOnly;
+};
+
+namespace format {
+constexpr Format<MPV_FORMAT_NONE, void> None;
+constexpr Format<MPV_FORMAT_STRING, QString, const char *> String;
+constexpr Format<MPV_FORMAT_OSD_STRING, QString, const char *, true> OsdString;
+constexpr Format<MPV_FORMAT_FLAG, bool, int> Flag;
+constexpr Format<MPV_FORMAT_INT64, int64_t> Int64;
+constexpr Format<MPV_FORMAT_DOUBLE, double> Double;
+constexpr Format<MPV_FORMAT_NODE, QVariant, mpv_node *> Node;
+// TODO should these be defined
+constexpr Format<MPV_FORMAT_NODE_ARRAY, QVariant, mpv_node *> NodeArray;
+constexpr Format<MPV_FORMAT_NODE_MAP, QVariant, mpv_node *> NodeMap;
+// TODO figure out correct type
+constexpr Format<MPV_FORMAT_BYTE_ARRAY, uint8_t *> ByteArray;
+
+} // namespace format
+
+template<typename Format, bool ReadOnly>
+struct Property
+{
+ static_assert(!(Format::readOnly == true && ReadOnly == false),
+ "Property with ReadOnly == false can not have a Format with "
+ "ReadOnly == true");
+ const Format format;
+ static constexpr bool readOnly = ReadOnly;
+ const char *name;
+ constexpr explicit Property(Format format, const char *name)
+ : format{format}, name{name} {}
+};
+
+template<bool ReadOnly = false, typename Format>
+constexpr auto MakeProperty(Format, const char *name) {
+ return Property<Format, ReadOnly>(Format{}, name);
+}
+
+namespace property {
+constexpr Property Pause = MakeProperty(format::Flag, "pause");
+constexpr Property Paused = MakeProperty(format::Flag, "paused");
+constexpr Property Duration = MakeProperty(format::Double, "duration");
+constexpr Property PlaybackTime = MakeProperty(format::Double, "playback-time");
+constexpr Property Volume = MakeProperty(format::Double, "volume");
+constexpr Property TrackList = MakeProperty<true>(format::Node, "track-list");
+constexpr Property Vid = MakeProperty(format::String, "vid");
+constexpr Property Aid = MakeProperty(format::String, "aid");
+constexpr Property Sid = MakeProperty(format::String, "sid");
+constexpr Property ChapterList = MakeProperty<true>(format::Node, "sid");
+constexpr Property IdleActive = MakeProperty(format::Flag, "idle-active");
+constexpr Property MaxVolume = MakeProperty(format::Double, "max-volume");
+} // namespace property
+
+namespace detail {
+template<typename From, typename To>
+struct TypeConversion
+{
+ static_assert(std::is_convertible_v<From, To>,
+ "Default type conversion not possible, new TypeConversion "
+ "specialization required");
+ static To convert(const From &from) { return To{from}; }
+};
+
+template<typename T>
+struct TypeConversion<T, T>
+{
+ static const T &convert(const T &from) { return from; }
+};
+
+template<>
+struct TypeConversion<QString, const char *>
+{
+ static const char *convert(const QString &from) {
+ return qUtf8Printable(from);
+ }
+};
+
+template<>
+struct TypeConversion<const char *, QString>
+{
+ static QString convert(const char *from) { return QString::fromUtf8(from); }
+};
+
+// Avoids narrwoing conversion warnings
+template<>
+struct TypeConversion<int, bool>
+{
+ static bool convert(const int &from) { return from != 0; }
+};
+
+template<typename Format>
+auto ReadFormat(const Handle &handle, Format format, const char *name)
+ -> Outcome<typename Format::type> {
+ typename Format::MpvInternalType data;
+ int error = mpv_get_property(handle, name, format.format, &data);
+ qCDebug(mpvHelper()).nospace()
+ << "ReadFormat, property: " << name << ", error: " << error
+ << ", original value: " << data;
+ if (error != MPV_ERROR_SUCCESS) {
+ return Error{error};
+ }
+ return TypeConversion<typename Format::MpvInternalType,
+ typename Format::type>::convert(data);
+}
+
+template<typename Format>
+int WriteFormat(const Handle &handle, Format format, const char *name,
+ const typename Format::type &value) {
+ const auto &data =
+ TypeConversion<typename Format::type,
+ typename Format::MpvInternalType>::convert(value);
+ int error =
+ mpv_set_property(handle, name, format.format,
+ const_cast<typename Format::MpvInternalType *>(&data));
+ qCDebug(mpvHelper()).nospace()
+ << "WriteFormat, property: " << name << ", error: " << error
+ << ", original value: " << value << ", converted value: " << data;
+ return error;
+}
+
+} // namespace detail
+
+template<typename Property>
+auto GetProperty(const Handle &handle, const Property &property)
+ -> Outcome<typename decltype(Property::format)::type> {
+ static_assert(decltype(Property::format)::format != MPV_FORMAT_NONE,
+ "Can not read property with format None");
+ return detail::ReadFormat(handle, property.format, property.name);
+}
+
+// Read property, using specific format rather than the properties format
+template<typename Property, typename Format>
+auto GetProperty(const Handle &handle, const Property &property,
+ const Format &format) -> Outcome<typename Format::type> {
+ static_assert(Format::format != MPV_FORMAT_NONE,
+ "Can not read property with format None");
+ return detail::ReadFormat(handle, format, property.name);
+}
+
+template<typename Property>
+int SetProperty(const Handle &handle, const Property &property,
+ const typename decltype(Property::format)::type &value) {
+ static_assert(decltype(Property::format)::format != MPV_FORMAT_NONE,
+ "Can not write property with format None");
+ static_assert(!decltype(Property::format)::readOnly,
+ "Can not write a readOnly property");
+
+ return detail::WriteFormat(handle, property.format, property.name, value);
+}
+
+template<typename Property, typename Format>
+int SetProperty(const Handle &handle, const Property &property,
+ const Format &format, const typename Format::type &value) {
+ static_assert(Format::format != MPV_FORMAT_NONE,
+ "Can not write property with format None");
+ static_assert(!Format::readOnly, "Can not write a readOnly property");
+
+ return detail::WriteFormat(handle, format, property.name, value);
+}
+
+} // namespace mpv
+
+#endif // MPVHELPER_H