summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Merry <alex.merry@cs.ox.ac.uk>2011-11-01 19:43:04 +0000
committerAlex Merry <alex.merry@cs.ox.ac.uk>2011-11-01 19:43:04 +0000
commitbe5b3bf09f01b488bbc0429a7cbd8e7980a9bb7e (patch)
treebd0e7eba3638dfa27c88775a09d6f111960e2f2e
parenta3031aea445a095c14c772e383e088b35c2be707 (diff)
More work on the player interface test
-rw-r--r--CMakeLists.txt1
-rw-r--r--mpris2/interfacetest.cpp9
-rw-r--r--mpris2/interfacetest.h3
-rw-r--r--mpris2/playerinterfacetest.cpp349
-rw-r--r--mpris2/playerinterfacetest.h12
-rw-r--r--mpris2/rootinterfacetest.cpp4
-rw-r--r--mpris2/rootinterfacetest.h1
-rw-r--r--ui/playertest.ui448
8 files changed, 818 insertions, 9 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bbdd637..559ead2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -27,6 +27,7 @@ qt4_wrap_cpp (mpristester_SRCS
)
qt4_wrap_ui (mpristester_SRCS
ui/roottest.ui
+ ui/playertest.ui
ui/window.ui
)
add_executable (mpristester ${mpristester_SRCS})
diff --git a/mpris2/interfacetest.cpp b/mpris2/interfacetest.cpp
index 12dfca9..50638dd 100644
--- a/mpris2/interfacetest.cpp
+++ b/mpris2/interfacetest.cpp
@@ -122,18 +122,20 @@ bool InterfaceTest::checkPropValid(const QString& propName, QVariant::Type expTy
return false;
} else if (props[propName].type() != expType) {
// FIXME: generate D-Bus type description
- const char * gotTypeCh = props[propName].typeName();
+ const char * gotTypeCh = QDBusMetaType::typeToSignature(props[propName].userType());
QString gotType = gotTypeCh ? QString::fromAscii(gotTypeCh) : "<unknown>";
- const char * expTypeCh = QVariant::typeToName(expType);
+ const char * expTypeCh = QDBusMetaType::typeToSignature(expType);
QString expType = expTypeCh ? QString::fromAscii(expTypeCh) : "<unknown>";
- emit interfaceError(Property, propName, "Property " + propName + " has type " + gotType + ", but should be type " + expType);
+ emit interfaceError(Property, propName, "Property " + propName + " has type '" + gotType + "', but should be type '" + expType + "'");
return false;
} else if (oldProps.contains(propName)) {
// FIXME: QVariant equality only works for builtin types
if (props[propName] != oldProps[propName]) {
outOfDateProperties.insert(propName, props[propName]);
props[propName] = oldProps[propName];
+ // don't check right now
+ return false;
}
}
return true;
@@ -201,6 +203,7 @@ void InterfaceTest::_m_propertiesChanged(const QString& interface,
outOfDateProperties.remove(*j);
++j;
}
+ checkConsistency();
emit propertiesChanged(changedPropsList);
}
diff --git a/mpris2/interfacetest.h b/mpris2/interfacetest.h
index c400fec..2de85e3 100644
--- a/mpris2/interfacetest.h
+++ b/mpris2/interfacetest.h
@@ -124,13 +124,14 @@ namespace Mpris2
virtual void checkProps(const QVariantMap& oldProps = QVariantMap()) = 0;
virtual void checkUpdatedProperty(const QString& propName) = 0;
+ virtual void checkConsistency(const QVariantMap& oldProps = QVariantMap()) = 0;
QDBusInterface* iface;
QVariantMap props;
+ QVariantMap outOfDateProperties; // prop name -> new value
private:
QDBusInterface* propsIface;
- QVariantMap outOfDateProperties; // prop name -> new value
QTimer* delayedCheckTimer;
QMap<QString,uint> propertyUpdateWarningCount;
};
diff --git a/mpris2/playerinterfacetest.cpp b/mpris2/playerinterfacetest.cpp
index 8d9949d..0731eac 100644
--- a/mpris2/playerinterfacetest.cpp
+++ b/mpris2/playerinterfacetest.cpp
@@ -18,9 +18,7 @@
#include "playerinterfacetest.h"
-#include <QDBusInterface>
-#include <QDBusMessage>
-#include <QDBusReply>
+#include <QtDBus>
#define MPRIS2_PLAYER_IFACE "org.mpris.MediaPlayer2.Player"
@@ -37,7 +35,35 @@ PlayerInterfaceTest::~PlayerInterfaceTest()
void PlayerInterfaceTest::checkUpdatedProperty(const QString& propName)
{
-
+ if (propName == "CanControl") {
+ checkPropValid("CanControl", QVariant::Bool);
+ } else if (propName == "CanGoNext") {
+ checkControlProp("CanGoNext");
+ } else if (propName == "CanGoPrevious") {
+ checkControlProp("CanGoPrevious");
+ } else if (propName == "CanPlay") {
+ checkControlProp("CanPlay");
+ } else if (propName == "CanPause") {
+ checkControlProp("CanPause");
+ } else if (propName == "CanSeek") {
+ checkControlProp("CanSeek");
+ } else if (propName == "Shuffle") {
+ checkPropValid("Shuffle", QVariant::Bool);
+ } else if (propName == "Volume") {
+ checkVolume();
+ } else if (propName == "PlaybackStatus") {
+ checkPlaybackStatus();
+ } else if (propName == "LoopStatus") {
+ checkLoopStatus();
+ } else if (propName == "MinimumRate") {
+ checkMinimumRate();
+ } else if (propName == "MaximumRate") {
+ checkMaximumRate();
+ } else if (propName == "Rate") {
+ checkPropValid("Rate", QVariant::Double);
+ } else if (propName == "Metadata") {
+ checkMetadata();
+ }
}
void PlayerInterfaceTest::checkProps(const QVariantMap& oldProps)
@@ -62,6 +88,13 @@ void PlayerInterfaceTest::checkProps(const QVariantMap& oldProps)
emit interfaceInfo(Property, "LoopStatus",
"Optional property not implemented");
}
+ checkMinimumRate(oldProps);
+ checkMaximumRate(oldProps);
+ checkPropValid("Rate", QVariant::Double, oldProps);
+ checkMetadata(oldProps);
+ checkPosition(oldProps);
+
+ checkConsistency();
}
void PlayerInterfaceTest::checkControlProp(const QString& propName, const QVariantMap& oldProps)
@@ -118,3 +151,311 @@ void PlayerInterfaceTest::checkPlaybackStatus(const QVariantMap& oldProps)
"Invalid value: '" + playbackStatus + "'");
}
}
+
+void PlayerInterfaceTest::checkMaximumRate(const QVariantMap& oldProps)
+{
+ if (!checkPropValid("MaximumRate", QVariant::Double, oldProps))
+ return;
+ double maxRate = properties().value("MaximumRate").toDouble();
+ if (maxRate < 1.0) {
+ emit interfaceWarning(Property, "MaximumRate",
+ "Maximum rate should not be less than 1.0");
+ }
+}
+
+void PlayerInterfaceTest::checkMinimumRate(const QVariantMap& oldProps)
+{
+ if (!checkPropValid("MinimumRate", QVariant::Double, oldProps))
+ return;
+ double minRate = properties().value("MinimumRate").toDouble();
+ if (minRate > 1.0) {
+ emit interfaceWarning(Property, "MinimumRate",
+ "Minimum rate should not be greater than 1.0");
+ }
+ if (minRate < 0.0) {
+ emit interfaceInfo(Property, "MinimumRate",
+ "Minimum rate is negative");
+ }
+}
+
+void PlayerInterfaceTest::checkRate(const QVariantMap& oldProps)
+{
+ if (!checkPropValid("Rate", QVariant::Double, oldProps))
+ return;
+ double rate = properties().value("Rate").toDouble();
+ if (qFuzzyCompare(rate, 0.0)) {
+ emit interfaceError(Property, "Rate",
+ "Rate must not be 0.0");
+ }
+}
+
+void PlayerInterfaceTest::checkMetadata(const QVariantMap& oldProps)
+{
+ if (!properties().contains("Metadata")) {
+ emit interfaceError(Property, "Metadata", "Property Metadata is missing");
+ return;
+ }
+ if (!properties().value("Metadata").canConvert<QDBusArgument>()) {
+ const char * gotTypeCh = QDBusMetaType::typeToSignature(props["Metadata"].userType());
+ QString gotType = gotTypeCh ? QString::fromAscii(gotTypeCh) : "<unknown>";
+ emit interfaceError(Property, "Metadata", "Property Metadata has type " + gotType + ", but should be type a{sv}");
+ return;
+ }
+ QVariantMap metadata;
+ QDBusArgument arg = properties().value("Metadata").value<QDBusArgument>();
+ arg >> metadata;
+
+ if (oldProps.contains("Metadata") &&
+ oldProps.value("Metadata").canConvert<QDBusArgument>())
+ {
+ QVariantMap oldMetadata;
+ oldProps.value("Metadata").value<QDBusArgument>() >> oldMetadata;
+ if (metadata != oldMetadata) {
+ outOfDateProperties.insert("Metadata", props["Metadata"]);
+ props["Metadata"] = oldProps["Metadata"];
+ return;
+ }
+ }
+ if (metadata.isEmpty()) {
+ emit interfaceInfo(Property, "Metadata",
+ "No metadata provided");
+ return;
+ }
+
+ if (!metadata.contains("mpris:trackid")) {
+ emit interfaceError(Property, "Metadata",
+ "No mpris:trackid entry for the current track");
+ } else if (metadata.value("mpris:trackid").type() != QVariant::String) {
+ emit interfaceError(Property, "Metadata",
+ "mpris:trackid entry is not a string");
+ } else if (metadata.value("mpris:trackid").toString().isEmpty()) {
+ emit interfaceError(Property, "Metadata",
+ "mpris:trackid entry is an empty string");
+ }
+
+ checkMetadataEntry(metadata, "mpris:length", QVariant::LongLong);
+
+ if (checkMetadataEntry(metadata, "mpris:artUrl", QVariant::Url)) {
+ QString artUrl = metadata.value("mpris:artUrl").toString();
+ QUrl asUrl(artUrl, QUrl::StrictMode);
+ if (asUrl.scheme() != "file" && asUrl.scheme() != "http" && asUrl.scheme() != "https") {
+ emit interfaceInfo(Property, "Metadata",
+ "mpris:artUrl has a scheme (" + asUrl.scheme() + ") which not all clients may recognise");
+ } else {
+ if (asUrl.scheme() == "file") {
+ if (!QFile::exists(asUrl.toLocalFile())) {
+ emit interfaceInfo(Property, "Metadata",
+ "mpris:artUrl references a file that does not exist");
+ }
+ }
+ // FIXME: check network files
+ }
+ }
+
+ Q_FOREACH( QString key, metadata.keys() ) {
+ if (!key.startsWith("xesam:")) {
+ if (key != "mpris:trackid" &&
+ key != "mpris:length" &&
+ key != "mpris:artUrl")
+ {
+ emit interfaceWarning(Property, "Metadata",
+ "Unrecognised entry " + key);
+ }
+ }
+ }
+
+ checkMetadataEntry(metadata, "xesam:album", QVariant::String);
+ checkMetadataEntry(metadata, "xesam:albumArtist", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:artist", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:asText", QVariant::String);
+ checkMetadataEntry(metadata, "xesam:audioBpm", QVariant::Int);
+ checkMetadataEntry(metadata, "xesam:autoRating", QVariant::Double);
+ checkMetadataEntry(metadata, "xesam:comment", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:composer", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:contentCreator", QVariant::DateTime);
+ checkMetadataEntry(metadata, "xesam:discNumber", QVariant::Int);
+ checkMetadataEntry(metadata, "xesam:firstUsed", QVariant::DateTime);
+ checkMetadataEntry(metadata, "xesam:genre", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:lastUsed", QVariant::DateTime);
+ checkMetadataEntry(metadata, "xesam:lyricist", QVariant::StringList);
+ checkMetadataEntry(metadata, "xesam:title", QVariant::String);
+ checkMetadataEntry(metadata, "xesam:trackNumber", QVariant::Int);
+ checkMetadataEntry(metadata, "xesam:url", QVariant::Url);
+ checkMetadataEntry(metadata, "xesam:useCount", QVariant::Int);
+ checkMetadataEntry(metadata, "xesam:userRating", QVariant::Double);
+}
+
+bool PlayerInterfaceTest::checkMetadataEntry(const QVariantMap& metadata, const QString& entry, QVariant::Type expType)
+{
+ if (metadata.contains(entry)) {
+ QVariant value = metadata.value(entry);
+
+ bool propertyTypeError = false;
+ bool propertyTypeWarning = false;
+ QVariant::Type realExpectedType = expType;
+ if (expType == QVariant::DateTime || expType == QVariant::Url) {
+ realExpectedType = QVariant::String;
+ }
+
+ // be lax about integers
+ if (realExpectedType == QVariant::Int) {
+ if (value.type() == QVariant::UInt ||
+ value.type() == QVariant::LongLong ||
+ value.type() == QVariant::ULongLong)
+ {
+ propertyTypeWarning = true;
+ } else if (value.type() != QVariant::Int) {
+ propertyTypeError = true;
+ }
+ } else if (realExpectedType == QVariant::UInt || realExpectedType == QVariant::LongLong) {
+ if (value.type() == QVariant::ULongLong) {
+ propertyTypeWarning = true;
+ } else if (value.type() != realExpectedType) {
+ propertyTypeError = true;
+ }
+ } else if (value.type() != realExpectedType) {
+ propertyTypeError = true;
+ }
+
+ if (propertyTypeError || propertyTypeWarning) {
+ const char * gotTypeCh = QDBusMetaType::typeToSignature(value.userType());
+ QString gotType = gotTypeCh ? QString::fromAscii(gotTypeCh) : "<unknown>";
+ const char * expTypeCh = QDBusMetaType::typeToSignature(realExpectedType);
+ QString expType = expTypeCh ? QString::fromAscii(expTypeCh) : "<unknown>";
+ if (propertyTypeError) {
+ emit interfaceError(Property, "Metadata",
+ entry + " entry is of type '" + gotType + "' but should have been of type '" + expType + "'");
+ return false;
+ } else {
+ emit interfaceWarning(Property, "Metadata",
+ entry + " entry is of type '" + gotType + "' but should have been of type '" + expType + "'");
+ return true;
+ }
+ }
+
+ // extra checks for special types
+ if (expType == QVariant::DateTime) {
+ QDateTime dtValue = QDateTime::fromString(value.toString(), Qt::ISODate);
+ if (!dtValue.isValid()) {
+ emit interfaceError(Property, "Metadata",
+ entry + " entry does not contain a valid date/time string (value was " + value.toString() + ")");
+ return false;
+ }
+ } else if (expType == QVariant::Url) {
+ if (value.toString().isEmpty()) {
+ return false;
+ } else {
+ QUrl asUrl(value.toString(), QUrl::StrictMode);
+ if (!asUrl.isValid()) {
+ emit interfaceError(Property, "Metadata",
+ entry + " entry is not a valid URL");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+void PlayerInterfaceTest::checkPosition(const QVariantMap& oldProps)
+{
+ if (!checkPropValid("Position", QVariant::LongLong, oldProps))
+ return;
+
+ qint64 position = properties().value("Position").toLongLong();
+ if (position < 0) {
+ emit interfaceError(Property, "Position",
+ "Negative position value");
+ }
+}
+
+void PlayerInterfaceTest::checkConsistency(const QVariantMap& oldProps)
+{
+ checkRateConsistency();
+ checkPositionConsistency();
+ checkPredictedPosition();
+}
+
+void PlayerInterfaceTest::checkPositionConsistency(const QVariantMap& oldProps)
+{
+ if (!properties().contains("Position") ||
+ !properties().contains("Metadata") ||
+ !properties().value("Metadata").canConvert<QDBusArgument>()
+ ) {
+ return;
+ }
+
+ qint64 position = properties().value("Position").toLongLong();
+
+ QVariantMap metadata;
+ properties().value("Metadata").value<QDBusArgument>() >> metadata;
+ if (!metadata.contains("mpris:length")) {
+ return;
+ }
+ qint64 length = metadata.value("mpris:length").toLongLong();
+
+ // drop out if the length and position have not changed
+ if (oldProps.contains("Position") &&
+ oldProps.value("Position") == position &&
+ oldProps.contains("Metadata") &&
+ oldProps.value("Metadata").canConvert<QDBusArgument>())
+ {
+ QVariantMap oldMetadata;
+ oldProps.value("Metadata").value<QDBusArgument>() >> oldMetadata;
+ if (oldMetadata.contains("mpris:length")) {
+ if (length == oldMetadata.value("mpris:length").toLongLong()) {
+ return;
+ }
+ }
+ }
+
+ if (position > length) {
+ emit interfaceError(Property, "Position",
+ "Position is greater than the track length");
+ }
+}
+
+void PlayerInterfaceTest::checkRateConsistency(const QVariantMap& oldProps)
+{
+ if (properties().value("MaximumRate") != oldProps.value("MaximumRate") ||
+ properties().value("MinimumRate") != oldProps.value("MinimumRate"))
+ {
+ if (properties().contains("MaximumRate") && properties().contains("MinimumRate")) {
+ double minRate = properties().value("MinimumRate").toDouble();
+ double maxRate = properties().value("MaximumRate").toDouble();
+ if (minRate > maxRate) {
+ emit interfaceError(Property, "MinimumRate",
+ "Minimum rate must not be greater than maximum rate");
+ }
+ }
+ }
+ if (properties().value("Rate") != oldProps.value("Rate") ||
+ properties().value("MaximumRate") != oldProps.value("MaximumRate") ||
+ properties().value("MinimumRate") != oldProps.value("MinimumRate"))
+ {
+ if (properties().contains("Rate")) {
+ double rate = properties().value("Rate").toDouble();
+ if (properties().contains("MaximumRate")) {
+ double maxRate = properties().value("MaximumRate").toDouble();
+ if (rate > maxRate) {
+ emit interfaceError(Property, "Rate",
+ "Rate must not be greater than maximum rate");
+ }
+ }
+ if (properties().contains("MinimumRate")) {
+ double minRate = properties().value("MinimumRate").toDouble();
+ if (minRate > rate) {
+ emit interfaceError(Property, "Rate",
+ "Rate must not be less than minimum rate");
+ }
+ }
+ }
+ }
+}
+
+void PlayerInterfaceTest::checkPredictedPosition()
+{
+ // FIXME: check predicted position
+}
+
diff --git a/mpris2/playerinterfacetest.h b/mpris2/playerinterfacetest.h
index 900e72d..33e682d 100644
--- a/mpris2/playerinterfacetest.h
+++ b/mpris2/playerinterfacetest.h
@@ -19,7 +19,7 @@
#ifndef MPRIS2_PLAYERINTERFACETEST_H
#define MPRIS2_PLAYERINTERFACETEST_H
-#include <../../home/alex/src/mpristester/mpris2/interfacetest.h>
+#include "interfacetest.h"
namespace Mpris2 {
@@ -34,12 +34,22 @@ namespace Mpris2 {
protected:
virtual void checkUpdatedProperty(const QString& propName);
virtual void checkProps(const QVariantMap& oldProps = QVariantMap());
+ virtual void checkConsistency(const QVariantMap& oldProps = QVariantMap());
private:
void checkControlProp(const QString& propName, const QVariantMap& oldProps = QVariantMap());
void checkVolume(const QVariantMap& oldProps = QVariantMap());
void checkLoopStatus(const QVariantMap& oldProps = QVariantMap());
void checkPlaybackStatus(const QVariantMap& oldProps = QVariantMap());
+ void checkMinimumRate(const QVariantMap& oldProps = QVariantMap());
+ void checkMaximumRate(const QVariantMap& oldProps = QVariantMap());
+ void checkRate(const QVariantMap& oldProps = QVariantMap());
+ void checkPosition(const QVariantMap& oldProps = QVariantMap());
+ void checkMetadata(const QVariantMap& oldProps = QVariantMap());
+ void checkRateConsistency(const QVariantMap& oldProps = QVariantMap());
+ void checkPositionConsistency(const QVariantMap& oldProps = QVariantMap());
+ void checkPredictedPosition();
+ bool checkMetadataEntry(const QVariantMap& metadata, const QString& entry, QVariant::Type type);
};
}
diff --git a/mpris2/rootinterfacetest.cpp b/mpris2/rootinterfacetest.cpp
index 7359fa0..f7e8475 100644
--- a/mpris2/rootinterfacetest.cpp
+++ b/mpris2/rootinterfacetest.cpp
@@ -132,4 +132,8 @@ void RootInterfaceTest::testRaise()
}
}
+void RootInterfaceTest::checkConsistency(const QVariantMap& oldProps)
+{
+}
+
// vim:et:sw=4:sts=4
diff --git a/mpris2/rootinterfacetest.h b/mpris2/rootinterfacetest.h
index 5e4481a..b29db35 100644
--- a/mpris2/rootinterfacetest.h
+++ b/mpris2/rootinterfacetest.h
@@ -57,6 +57,7 @@ namespace Mpris2
protected:
virtual void checkProps(const QVariantMap& oldProps = QVariantMap());
virtual void checkUpdatedProperty(const QString& propName);
+ virtual void checkConsistency(const QVariantMap& oldProps = QVariantMap());
private:
void checkPropertyIdentity(const QVariantMap& oldProps = QVariantMap());
diff --git a/ui/playertest.ui b/ui/playertest.ui
new file mode 100644
index 0000000..f9bbd7f
--- /dev/null
+++ b/ui/playertest.ui
@@ -0,0 +1,448 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PlayerTestForm</class>
+ <widget class="QWidget" name="PlayerTestForm">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>667</width>
+ <height>675</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Form</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QFormLayout" name="formLayout">
+ <property name="fieldGrowthPolicy">
+ <enum>QFormLayout::ExpandingFieldsGrow</enum>
+ </property>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Playback status:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="playbackStatusLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Loop status:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="loopStatusLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Shuffle:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="shuffleLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Volume:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QLabel" name="volumeLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="0">
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Maximum rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="4" column="1">
+ <widget class="QLabel" name="maxRateLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="0">
+ <widget class="QLabel" name="label_6">
+ <property name="text">
+ <string>Minimum rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="5" column="1">
+ <widget class="QLabel" name="minRateLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="0">
+ <widget class="QLabel" name="label_7">
+ <property name="text">
+ <string>Rate:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="6" column="1">
+ <widget class="QLabel" name="rateLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="label_8">
+ <property name="text">
+ <string>Last received position:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="7" column="1">
+ <widget class="QLabel" name="lastKnownPosLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
+ <widget class="QLabel" name="label_9">
+ <property name="text">
+ <string>Predicted position:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="1">
+ <widget class="QLabel" name="estPosLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="0">
+ <widget class="QLabel" name="label_10">
+ <property name="text">
+ <string>Can control:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="9" column="1">
+ <widget class="QLabel" name="canControlLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="10" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Can play:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="11" column="0">
+ <widget class="QLabel" name="label_12">
+ <property name="text">
+ <string>Can pause:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="0">
+ <widget class="QLabel" name="label_13">
+ <property name="text">
+ <string>Can go previous:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="0">
+ <widget class="QLabel" name="label_14">
+ <property name="text">
+ <string>Can go next:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="13" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="canGoNextLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="nextBtn">
+ <property name="text">
+ <string>Next</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="14" column="0">
+ <widget class="QLabel" name="label_15">
+ <property name="text">
+ <string>Can seek:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="14" column="1">
+ <widget class="QLabel" name="canSeekLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="12" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QLabel" name="canGoPrevLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="prevBtn">
+ <property name="text">
+ <string>Previous</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="10" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <item>
+ <widget class="QLabel" name="canPlayLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="playBtn">
+ <property name="text">
+ <string>Play</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stopBtn">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="11" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <item>
+ <widget class="QLabel" name="canPauseLbl">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>&lt;unknown&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pauseBtn">
+ <property name="text">
+ <string>Pause</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="playPauseBtn">
+ <property name="text">
+ <string>Play/Pause</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label_16">
+ <property name="text">
+ <string>Metadata</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTableView" name="metadataTableView">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_6">
+ <item>
+ <widget class="QLineEdit" name="openUriEdit"/>
+ </item>
+ <item>
+ <widget class="QPushButton" name="openUriBtn">
+ <property name="text">
+ <string>Open URI</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <widget class="QSpinBox" name="seekSpinBox">
+ <property name="suffix">
+ <string>µs</string>
+ </property>
+ <property name="minimum">
+ <number>-999999999</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="singleStep">
+ <number>1000000</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="seekBtn">
+ <property name="text">
+ <string>Seek by</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_8">
+ <item>
+ <widget class="QLabel" name="label_32">
+ <property name="text">
+ <string>Track id:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="setPosTrackIdEdit"/>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_33">
+ <property name="text">
+ <string>Position:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="setPosSpinBox">
+ <property name="suffix">
+ <string>µs</string>
+ </property>
+ <property name="minimum">
+ <number>-999999999</number>
+ </property>
+ <property name="maximum">
+ <number>999999999</number>
+ </property>
+ <property name="singleStep">
+ <number>1000000</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="setPosBtn">
+ <property name="text">
+ <string>Set position</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>