added ffmpeg launcher
This commit is contained in:
parent
72bdfa1597
commit
a03a2a0e5b
42
README.md
42
README.md
|
@ -1,2 +1,42 @@
|
|||
# dbus-soundrecorder
|
||||
Records sound from Pulse Audio using ffmpeg. Uses dbus to determine current song, album and artist.
|
||||
Records sound from Pulse Audio using ffmpeg.
|
||||
|
||||
Uses D-Bus to determine current song, album, artist and other meta data
|
||||
provided by the media player application via MPRIS D-Bus service.
|
||||
|
||||
When the next song start, the recorder automatically starts a new file
|
||||
and sets available meta data.
|
||||
|
||||
## Usage
|
||||
```
|
||||
dbus-soundrecorder record [options]
|
||||
```
|
||||
|
||||
Here's a simple example:
|
||||
|
||||
First, get a list of your Pulse Audio sinks:
|
||||
```
|
||||
pactl list short sinks
|
||||
```
|
||||
|
||||
You can also create a new virtual Pulse Audio sink:
|
||||
```
|
||||
pactl load-module module-null-sink sink_name=virtual1
|
||||
```
|
||||
In any case, you should ensure that the media player uses the sink (eg. using pavucontrol).
|
||||
|
||||
Then start the recorder. You need to specify the media player application and the sink:
|
||||
```
|
||||
dbus-soundrecorder -a yfitops -s virtual1.monitor -o "-c:a libfdk_aac -vbr 4 -ar 44100"
|
||||
```
|
||||
As you can see, it is also possible to specify options for ffmpeg. However input sink,
|
||||
output file and meta data are provided by the recorder and shouldn't be specified.
|
||||
|
||||
For all available options, use the --help command.
|
||||
|
||||
## Build instructions
|
||||
The application depends on the c++utilities library. It is built in the same way as c++utilities.
|
||||
|
||||
The following Qt 5 modules are requried: core dbus
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
projectname = dbus-soundrecorder
|
||||
appname = "D-Bus Sound Recorder"
|
||||
appauthor = Martchus
|
||||
appurl = "https://github.com/$${appauthor}/$${projectname}"
|
||||
VERSION = 1.0.0
|
||||
|
||||
# include ../../common.pri when building as part of a subdirs project; otherwise include general.pri
|
||||
|
@ -15,10 +18,12 @@ CONFIG += console
|
|||
QT += core dbus
|
||||
|
||||
SOURCES += main.cpp \
|
||||
playerwatcher.cpp
|
||||
playerwatcher.cpp \
|
||||
ffmpeglauncher.cpp
|
||||
|
||||
HEADERS += \
|
||||
playerwatcher.h
|
||||
playerwatcher.h \
|
||||
ffmpeglauncher.h
|
||||
|
||||
DBUS_INTERFACES += \
|
||||
org.freedesktop.DBus.Properties.xml \
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
#include "ffmpeglauncher.h"
|
||||
#include "playerwatcher.h"
|
||||
|
||||
#include <c++utilities/io/inifile.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
using namespace std;
|
||||
using namespace IoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
|
||||
namespace DBusSoundRecorder {
|
||||
|
||||
inline ostream &operator <<(ostream &stream, const QString &str)
|
||||
{
|
||||
stream << str.toLocal8Bit().data();
|
||||
return stream;
|
||||
}
|
||||
|
||||
FfmpegLauncher::FfmpegLauncher(PlayerWatcher &watcher, QObject *parent) :
|
||||
QObject(parent),
|
||||
m_watcher(watcher),
|
||||
m_sink(QStringLiteral("default")),
|
||||
m_options(),
|
||||
m_targetDir(QStringLiteral(".")),
|
||||
m_targetExtension(QStringLiteral(".m4a")),
|
||||
m_ffmpeg(new QProcess(this))
|
||||
{
|
||||
connect(&watcher, &PlayerWatcher::nextSong, this, &FfmpegLauncher::nextSong);
|
||||
connect(m_ffmpeg, &QProcess::started, this, &FfmpegLauncher::ffmpegStarted);
|
||||
connect(m_ffmpeg, static_cast<void(QProcess::*)(QProcess::ProcessError)>(&QProcess::error), this, &FfmpegLauncher::ffmpegError);
|
||||
connect(m_ffmpeg, static_cast<void(QProcess::*)(int)>(&QProcess::finished), this, &FfmpegLauncher::ffmpegFinished);
|
||||
m_ffmpeg->setProgram(QStringLiteral("ffmpeg"));
|
||||
m_ffmpeg->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
}
|
||||
|
||||
void addMetaData(QStringList &args, const QString &field, const QString &value)
|
||||
{
|
||||
if(!value.isEmpty()) {
|
||||
args << QStringLiteral("-metadata");
|
||||
args << QStringLiteral("%1=%2").arg(field, value);
|
||||
}
|
||||
}
|
||||
|
||||
void FfmpegLauncher::nextSong()
|
||||
{
|
||||
// terminate/kill the current process
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
m_ffmpeg->terminate();
|
||||
m_ffmpeg->waitForFinished(250);
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
m_ffmpeg->kill();
|
||||
m_ffmpeg->waitForFinished(250);
|
||||
if(m_ffmpeg->state() != QProcess::NotRunning) {
|
||||
throw runtime_error("Unable to terminate/kill ffmpeg process.");
|
||||
}
|
||||
}
|
||||
}
|
||||
if(m_watcher.isPlaying()) {
|
||||
// determine output file, create target directory
|
||||
static const QString miscCategory(QStringLiteral("misc"));
|
||||
static const QString unknownTitle(QStringLiteral("unknown track"));
|
||||
QDir targetDir(QStringLiteral("%1/%2").arg(m_watcher.artist().isEmpty() ? miscCategory : m_watcher.artist(), m_watcher.artist().isEmpty() ? miscCategory : m_watcher.album()));
|
||||
if(!m_targetDir.mkpath(targetDir.path())) {
|
||||
cerr << "Error: Can not create target directory: " << targetDir.absolutePath() << endl;
|
||||
return;
|
||||
}
|
||||
// determine track number
|
||||
QString number;
|
||||
QString length;
|
||||
if(m_watcher.trackNumber()) {
|
||||
if(m_watcher.diskNumber()) {
|
||||
number = QStringLiteral("%2-%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0')).arg(m_watcher.diskNumber());
|
||||
} else {
|
||||
number = QStringLiteral("%1").arg(m_watcher.trackNumber(), 2, 10, QLatin1Char('0'));
|
||||
}
|
||||
}
|
||||
if(!number.isEmpty()) {
|
||||
number.append(QStringLiteral(" - "));
|
||||
}
|
||||
// determine additional info
|
||||
// - from a file called info.ini in the album directory
|
||||
// - currently only track length is supported (used to get rid of advertisements at the end)
|
||||
if(targetDir.exists(QStringLiteral("info.ini"))) {
|
||||
fstream infoFile;
|
||||
infoFile.exceptions(ios_base::badbit | ios_base::failbit);
|
||||
try {
|
||||
infoFile.open((targetDir.path() + QStringLiteral("/info.ini")).toLocal8Bit().data(), ios_base::in);
|
||||
IniFile infoIni;
|
||||
infoIni.parse(infoFile);
|
||||
// read length scope, only possible if track number known because the track number is used for mapping
|
||||
if(m_watcher.trackNumber()) {
|
||||
try {
|
||||
const auto &lengthScope = infoIni.data().at("length");
|
||||
for(const auto &entry : lengthScope) {
|
||||
try {
|
||||
if(stringToNumber<unsigned int>(entry.first) == m_watcher.trackNumber()) {
|
||||
// length entry for this track
|
||||
length = QString::fromLocal8Bit(entry.second.data());
|
||||
break;
|
||||
}
|
||||
} catch( const ConversionException &) {
|
||||
cerr << "Warning: Ignoring non-numeric key \"" << entry.first << "\" in info.ini." << endl;
|
||||
}
|
||||
}
|
||||
} catch(const out_of_range &) {
|
||||
// no length for the current track specified
|
||||
}
|
||||
}
|
||||
} catch(const ios_base::failure &) {
|
||||
cerr << "Warning: Can't parse info.ini because an IO error occured." << endl;
|
||||
}
|
||||
}
|
||||
// determine target name/path
|
||||
QString targetName(QStringLiteral("%3%1%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number));
|
||||
unsigned int count = 1;
|
||||
while(targetDir.exists(targetName)) {
|
||||
++count;
|
||||
targetName = QStringLiteral("%3%1 (%4)%2").arg(m_watcher.title().isEmpty() ? unknownTitle : m_watcher.title(), m_targetExtension, number).arg(count);
|
||||
}
|
||||
QString targetPath = targetDir.absoluteFilePath(targetName);
|
||||
// set input device
|
||||
QStringList args;
|
||||
args << QStringLiteral("-f");
|
||||
args << QStringLiteral("pulse");
|
||||
args << QStringLiteral("-i");
|
||||
args << m_sink;
|
||||
// set length
|
||||
if(!length.isEmpty()) {
|
||||
args << "-t";
|
||||
args << length;
|
||||
}
|
||||
// set additional options
|
||||
args << m_options;
|
||||
// set meta data
|
||||
addMetaData(args, QStringLiteral("title"), m_watcher.title());
|
||||
addMetaData(args, QStringLiteral("album"), m_watcher.album());
|
||||
addMetaData(args, QStringLiteral("artist"), m_watcher.artist());
|
||||
addMetaData(args, QStringLiteral("genre"), m_watcher.genre());
|
||||
addMetaData(args, QStringLiteral("year"), m_watcher.year());
|
||||
if(m_watcher.trackNumber()) {
|
||||
addMetaData(args, QStringLiteral("track"), QString::number(m_watcher.trackNumber()));
|
||||
}
|
||||
if(m_watcher.diskNumber()) {
|
||||
addMetaData(args, QStringLiteral("disk"), QString::number(m_watcher.diskNumber()));
|
||||
}
|
||||
// set output file
|
||||
args << targetPath;
|
||||
m_ffmpeg->setArguments(args);
|
||||
// start process
|
||||
m_ffmpeg->start();
|
||||
}
|
||||
}
|
||||
|
||||
void FfmpegLauncher::ffmpegStarted()
|
||||
{
|
||||
cerr << "Started ffmpeg: ";
|
||||
cerr << m_ffmpeg->program();
|
||||
for(const auto &arg : m_ffmpeg->arguments()) {
|
||||
cerr << ' ' << arg;
|
||||
}
|
||||
cerr << endl;
|
||||
}
|
||||
|
||||
void FfmpegLauncher::ffmpegError()
|
||||
{
|
||||
cerr << "Failed to start ffmpeg: " << m_ffmpeg->errorString();
|
||||
}
|
||||
|
||||
void FfmpegLauncher::ffmpegFinished(int exitCode)
|
||||
{
|
||||
cerr << "FFmpeg finished with exit code " << exitCode << endl;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef FFMPEGLAUNCHER_H
|
||||
#define FFMPEGLAUNCHER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QDir>
|
||||
|
||||
namespace DBusSoundRecorder {
|
||||
|
||||
class PlayerWatcher;
|
||||
|
||||
class FfmpegLauncher : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FfmpegLauncher(PlayerWatcher &watcher, QObject *parent = nullptr);
|
||||
|
||||
void setSink(const QString &sinkName);
|
||||
void setFfmpegBinary(const QString &path);
|
||||
void setFfmpegOptions(const QString &options);
|
||||
void setTargetDir(const QString &path);
|
||||
void setTargetExtension(const QString &extension);
|
||||
|
||||
private slots:
|
||||
void nextSong();
|
||||
void ffmpegStarted();
|
||||
void ffmpegError();
|
||||
void ffmpegFinished(int exitCode);
|
||||
|
||||
private:
|
||||
PlayerWatcher &m_watcher;
|
||||
QString m_sink;
|
||||
QStringList m_options;
|
||||
QDir m_targetDir;
|
||||
QString m_targetExtension;
|
||||
QProcess *m_ffmpeg;
|
||||
};
|
||||
|
||||
inline void FfmpegLauncher::setSink(const QString &sinkName)
|
||||
{
|
||||
m_sink = sinkName;
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setFfmpegBinary(const QString &path)
|
||||
{
|
||||
m_ffmpeg->setProgram(path);
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setFfmpegOptions(const QString &options)
|
||||
{
|
||||
m_options = options.split(QChar(' '), QString::SkipEmptyParts);
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setTargetDir(const QString &path)
|
||||
{
|
||||
m_targetDir = QDir(path);
|
||||
}
|
||||
|
||||
inline void FfmpegLauncher::setTargetExtension(const QString &extension)
|
||||
{
|
||||
m_targetExtension = extension.startsWith(QChar('.')) ? extension : QStringLiteral(".") + extension;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // FFMPEGLAUNCHER_H
|
51
main.cpp
51
main.cpp
|
@ -1,4 +1,5 @@
|
|||
#include "playerwatcher.h"
|
||||
#include "ffmpeglauncher.h"
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
#include <c++utilities/application/failure.h>
|
||||
|
@ -14,8 +15,11 @@ using namespace DBusSoundRecorder;
|
|||
int main(int argc, char *argv[])
|
||||
{
|
||||
// setup the argument parser
|
||||
SET_APPLICATION_INFO;
|
||||
ArgumentParser parser;
|
||||
HelpArgument helpArg(parser);
|
||||
Argument recordArg("record", "r", "starts recording");
|
||||
recordArg.setDenotesOperation(true);
|
||||
Argument applicationArg("application", "a", "specifies the application providing meta information via D-Bus interface");
|
||||
applicationArg.setRequired(true);
|
||||
applicationArg.setValueNames({"name"});
|
||||
|
@ -28,20 +32,57 @@ int main(int argc, char *argv[])
|
|||
targetDirArg.setValueNames({"path"});
|
||||
targetDirArg.setRequiredValueCount(1);
|
||||
targetDirArg.setCombinable(true);
|
||||
Argument targetExtArg("target-extension", "e", "specifies the target extension (default is .m4a)");
|
||||
targetExtArg.setValueNames({"extension"});
|
||||
targetExtArg.setRequiredValueCount(1);
|
||||
targetExtArg.setCombinable(true);
|
||||
Argument ffmpegBinArg("ffmpeg-bin", "f", "specifies the path to the ffmpeg binary");
|
||||
ffmpegBinArg.setValueNames({"path"});
|
||||
ffmpegBinArg.setRequiredValueCount(1);
|
||||
ffmpegBinArg.setCombinable(true);
|
||||
Argument ffmpegOptions("ffmpeg-options", "o", "specifies options for ffmpeg");
|
||||
ffmpegOptions.setValueNames({"options"});
|
||||
ffmpegOptions.setRequiredValueCount(1);
|
||||
ffmpegOptions.setCombinable(true);
|
||||
parser.setMainArguments({&applicationArg, &sinkArg, &targetDirArg, &ffmpegOptions, &helpArg});
|
||||
recordArg.setSecondaryArguments({&applicationArg, &sinkArg, &targetDirArg, &targetExtArg, &ffmpegBinArg, &ffmpegOptions});
|
||||
parser.setMainArguments({&recordArg, &helpArg});
|
||||
parser.setIgnoreUnknownArguments(false);
|
||||
// parse command line arguments
|
||||
try {
|
||||
parser.parseArgs(argc, argv);
|
||||
} catch (Failure &e) {
|
||||
} catch (const Failure &e) {
|
||||
cerr << "Unable to parse arguments: " << e.what() << endl;
|
||||
return 2;
|
||||
}
|
||||
QCoreApplication app(argc, argv);
|
||||
PlayerWatcher watcher(QString::fromLocal8Bit(applicationArg.values().front().data()));
|
||||
return app.exec();
|
||||
try {
|
||||
if(recordArg.isPresent()) {
|
||||
// start watching/recording
|
||||
cerr << "Watching MPRIS service of the specified application \"" << applicationArg.values().front() << "\" ..." << endl;
|
||||
// create app loop, player watcher and ffmpeg launcher
|
||||
QCoreApplication app(argc, argv);
|
||||
PlayerWatcher watcher(QString::fromLocal8Bit(applicationArg.values().front().data()));
|
||||
FfmpegLauncher ffmpeg(watcher);
|
||||
// pass specified args to ffmpeg launcher
|
||||
if(sinkArg.isPresent()) {
|
||||
ffmpeg.setSink(QString::fromLocal8Bit(sinkArg.values().front().data()));
|
||||
}
|
||||
if(ffmpegBinArg.isPresent()) {
|
||||
ffmpeg.setFfmpegBinary(QString::fromLocal8Bit(ffmpegBinArg.values().front().data()));
|
||||
}
|
||||
if(ffmpegOptions.isPresent()) {
|
||||
ffmpeg.setFfmpegOptions(QString::fromLocal8Bit(ffmpegOptions.values().front().data()));
|
||||
}
|
||||
if(targetDirArg.isPresent()) {
|
||||
ffmpeg.setTargetDir(QString::fromLocal8Bit(targetDirArg.values().front().data()));
|
||||
}
|
||||
if(targetExtArg.isPresent()) {
|
||||
ffmpeg.setTargetExtension(QString::fromLocal8Bit(targetExtArg.values().front().data()));
|
||||
}
|
||||
// enter app loop
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
} catch(const runtime_error &e) {
|
||||
cerr << "Fatal error: " << e.what() << endl;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,33 @@ PlayerWatcher::PlayerWatcher(const QString &appName, QObject *parent) :
|
|||
m_serviceWatcher(new QDBusServiceWatcher(m_service, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange, this)),
|
||||
m_propertiesInterface(new OrgFreedesktopDBusPropertiesInterface(m_service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_playerInterface(new OrgMprisMediaPlayer2PlayerInterface(m_service, QStringLiteral("/org/mpris/MediaPlayer2"), QDBusConnection::sessionBus(), this)),
|
||||
m_trackNumber(0)
|
||||
m_isPlaying(false),
|
||||
m_trackNumber(0),
|
||||
m_diskNumber(0)
|
||||
{
|
||||
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged, this, &PlayerWatcher::serviceOwnerChanged);
|
||||
connect(m_propertiesInterface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged, this, &PlayerWatcher::propertiesChanged);
|
||||
propertiesChanged();
|
||||
}
|
||||
|
||||
void PlayerWatcher::play()
|
||||
{
|
||||
m_playerInterface->Play();
|
||||
}
|
||||
|
||||
void PlayerWatcher::stop()
|
||||
{
|
||||
m_playerInterface->Stop();
|
||||
}
|
||||
|
||||
void PlayerWatcher::pause()
|
||||
{
|
||||
m_playerInterface->Pause();
|
||||
}
|
||||
|
||||
void PlayerWatcher::playPause()
|
||||
{
|
||||
m_playerInterface->PlayPause();
|
||||
}
|
||||
|
||||
void PlayerWatcher::serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner)
|
||||
|
@ -40,27 +63,55 @@ void PlayerWatcher::serviceOwnerChanged(const QString &service, const QString &o
|
|||
}
|
||||
}
|
||||
|
||||
void PlayerWatcher::propertiesChanged(const QString &, const QVariantMap &, const QStringList &)
|
||||
void PlayerWatcher::propertiesChanged()
|
||||
{
|
||||
// get meta data
|
||||
QVariantMap metadata = m_playerInterface->metadata();
|
||||
QString title = metadata.value(QStringLiteral("xesam:title")).toString();
|
||||
QString album = metadata.value(QStringLiteral("xesam:album")).toString();
|
||||
QString artist = metadata.value(QStringLiteral("xesam:artist")).toString();
|
||||
// use title, album and artist to identify song
|
||||
if(m_title != title || m_album != album || m_artist != artist) {
|
||||
// next song playing
|
||||
m_title = title;
|
||||
m_album = album;
|
||||
m_artist = artist;
|
||||
// read additional meta data
|
||||
m_year = metadata.value(QStringLiteral("xesam:contentCreated")).toString();
|
||||
m_genre = metadata.value(QStringLiteral("xesam:genre")).toString();
|
||||
m_trackNumber = metadata.value(QStringLiteral("xesam:tracknumber")).toUInt();
|
||||
// notify
|
||||
cerr << "Next song: " << m_title << endl;
|
||||
emit nextSong();
|
||||
if(!m_playerInterface->playbackStatus().compare(QLatin1String("playing"), Qt::CaseInsensitive)) {
|
||||
if(!m_isPlaying) {
|
||||
m_isPlaying = true;
|
||||
cerr << "Playback started" << endl;
|
||||
}
|
||||
QVariantMap metadata = m_playerInterface->metadata();
|
||||
QString title = metadata.value(QStringLiteral("xesam:title")).toString();
|
||||
QString album = metadata.value(QStringLiteral("xesam:album")).toString();
|
||||
QString artist = metadata.value(QStringLiteral("xesam:artist")).toString();
|
||||
// use title, album and artist to identify song
|
||||
if(m_title != title || m_album != album || m_artist != artist) {
|
||||
// next song playing
|
||||
m_title = title;
|
||||
m_album = album;
|
||||
m_artist = artist;
|
||||
// read additional meta data
|
||||
m_year = metadata.value(QStringLiteral("xesam:contentCreated")).toString();
|
||||
m_genre = metadata.value(QStringLiteral("xesam:genre")).toString();
|
||||
m_trackNumber = metadata.value(QStringLiteral("xesam:tracknumber")).toUInt();
|
||||
if(!m_trackNumber) {
|
||||
m_trackNumber = metadata.value(QStringLiteral("xesam:trackNumber")).toUInt();
|
||||
}
|
||||
m_diskNumber = metadata.value(QStringLiteral("xesam:discnumber")).toUInt();
|
||||
if(!m_diskNumber) {
|
||||
m_diskNumber = metadata.value(QStringLiteral("xesam:discNumber")).toUInt();
|
||||
}
|
||||
m_length = metadata.value(QStringLiteral("xesam:length")).toULongLong();
|
||||
// notify
|
||||
cerr << "Next song: " << m_title << endl;
|
||||
if(!m_isPlaying) {
|
||||
emit playbackStarted();
|
||||
}
|
||||
emit nextSong();
|
||||
} else if(!m_isPlaying) {
|
||||
emit playbackStarted();
|
||||
}
|
||||
} else if(m_isPlaying) {
|
||||
m_isPlaying = false;
|
||||
cerr << "Playback stopped" << endl;
|
||||
emit playbackStopped();
|
||||
}
|
||||
}
|
||||
|
||||
void PlayerWatcher::seeked(qlonglong pos)
|
||||
{
|
||||
cerr << "Seeked: " << pos << endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,33 +16,52 @@ class PlayerWatcher : public QObject
|
|||
public:
|
||||
explicit PlayerWatcher(const QString &appName, QObject *parent = nullptr);
|
||||
|
||||
void play();
|
||||
void stop();
|
||||
void pause();
|
||||
void playPause();
|
||||
|
||||
bool isPlaying() const;
|
||||
const QString &title() const;
|
||||
const QString &album() const;
|
||||
const QString &artist() const;
|
||||
const QString &year() const;
|
||||
const QString &genre() const;
|
||||
unsigned int trackNumber() const;
|
||||
unsigned int diskNumber() const;
|
||||
unsigned long long length() const;
|
||||
|
||||
signals:
|
||||
void nextSong();
|
||||
void playbackStarted();
|
||||
void playbackStopped();
|
||||
|
||||
private slots:
|
||||
void serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner);
|
||||
void propertiesChanged(const QString &, const QVariantMap &, const QStringList &);
|
||||
void propertiesChanged();
|
||||
void seeked(qlonglong pos);
|
||||
|
||||
private:
|
||||
QString m_service;
|
||||
QDBusServiceWatcher *m_serviceWatcher;
|
||||
OrgFreedesktopDBusPropertiesInterface *m_propertiesInterface;
|
||||
OrgMprisMediaPlayer2PlayerInterface *m_playerInterface;
|
||||
bool m_isPlaying;
|
||||
QString m_title;
|
||||
QString m_album;
|
||||
QString m_artist;
|
||||
QString m_year;
|
||||
QString m_genre;
|
||||
unsigned int m_trackNumber;
|
||||
unsigned int m_diskNumber;
|
||||
unsigned long long m_length;
|
||||
};
|
||||
|
||||
inline bool PlayerWatcher::isPlaying() const
|
||||
{
|
||||
return m_isPlaying;
|
||||
}
|
||||
|
||||
inline const QString &PlayerWatcher::title() const
|
||||
{
|
||||
return m_title;
|
||||
|
@ -73,6 +92,16 @@ inline unsigned int PlayerWatcher::trackNumber() const
|
|||
return m_trackNumber;
|
||||
}
|
||||
|
||||
inline unsigned int PlayerWatcher::diskNumber() const
|
||||
{
|
||||
return m_diskNumber;
|
||||
}
|
||||
|
||||
inline unsigned long long PlayerWatcher::length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // PLAYERWATCHER_H
|
||||
|
|
Loading…
Reference in New Issue