Add MPRIS support (thanks Amarok ;-)

Fixes issue #29

Example command lines:
dbus-send --print-reply --dest=org.mpris.clementine /Player org.freedesktop.MediaPlayer.Play
dbus-send --print-reply --dest=org.mpris.clementine / org.freedesktop.MediaPlayer.Identity
dbus-send --print-reply --dest=org.mpris.clementine /TrackList org.freedesktop.MediaPlayer.GetCurrentTrack
dbus-send --print-reply --dest=org.mpris.clementine /TrackList org.freedesktop.MediaPlayer.GetMetadata int32:0
This commit is contained in:
John Maguire 2010-03-24 20:58:17 +00:00
parent ceb225c236
commit a8a37264f7
17 changed files with 609 additions and 7 deletions

View File

@ -0,0 +1,87 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="Pause">
</method>
<method name="Stop">
</method>
<method name="Play">
</method>
<method name="Prev">
</method>
<method name="Next">
</method>
<method name="Repeat">
<arg type="b" direction="in"/>
</method>
<method name="GetStatus">
<arg type="(iiii)" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusStatus"/>
</method>
<method name="VolumeSet">
<arg type="i" direction="in"/>
</method>
<method name="VolumeGet">
<arg type="i" direction="out"/>
</method>
<method name="PositionSet">
<arg type="i" direction="in"/>
</method>
<method name="PositionGet">
<arg type="i" direction="out"/>
</method>
<method name="GetMetadata">
<arg type="a{sv}" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<method name="GetCaps">
<arg type="i" direction="out" />
</method>
<signal name="TrackChange">
<arg type="a{sv}"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QVariantMap"/>
</signal>
<signal name="StatusChange">
<arg type="(iiii)"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="DBusStatus"/>
</signal>
<signal name="CapsChange">
<arg type="i" />
</signal>
<!-- NB: Amarok extensions to the mpris spec -->
<method name="VolumeUp">
<arg type="i" drection="in"/>
</method>
<method name="VolumeDown">
<arg type="i" drection="in"/>
</method>
<method name="Mute">
</method>
<method name="ShowOSD">
</method>
</interface>
</node>

View File

@ -0,0 +1,20 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="Identity">
<arg type="s" direction="out"/>
</method>
<method name="Quit">
</method>
<method name="MprisVersion">
<arg type="(qq)" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Version"/>
</method>
</interface>
</node>

View File

@ -0,0 +1,48 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="GetMetadata">
<arg type="i" direction="in" />
<arg type="a{sv}" direction="out" />
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<method name="GetCurrentTrack">
<arg type="i" direction="out" />
</method>
<method name="GetLength">
<arg type="i" direction="out" />
</method>
<method name="AddTrack">
<arg type="s" direction="in" />
<arg type="b" direction="in" />
<arg type="i" direction="out" />
</method>
<method name="DelTrack">
<arg type="i" />
</method>
<method name="SetLoop">
<arg type="b" />
</method>
<method name="SetRandom">
<arg type="b" />
</method>
<method name="PlayTrack">
<arg type="i" />
</method>
<signal name="TrackListChange">
<arg type="i" />
</signal>
</interface>
</node>

View File

@ -64,6 +64,7 @@ set(CLEMENTINE-SOURCES
globalshortcuts/globalshortcuts.cpp
fixlastfm.cpp
backgroundthread.cpp
mpris.cpp
)
# Header files that have Q_OBJECT in
@ -120,6 +121,7 @@ set(CLEMENTINE-MOC-HEADERS
analyzers/sonogram.h
analyzers/turbine.h
globalshortcuts/globalshortcuts.h
mpris.h
)
# UI files
@ -184,6 +186,15 @@ else(APPLE)
set(CLEMENTINE-SOURCES ${CLEMENTINE-SOURCES} osd_win.cpp)
else(WIN32)
set(CLEMENTINE-SOURCES ${CLEMENTINE-SOURCES} osd_x11.cpp)
qt4_add_dbus_adaptor(MPRIS-PLAYER-SOURCES
../data/org.freedesktop.MediaPlayer.player.xml
player.h Player mpris_player MprisPlayer)
qt4_add_dbus_adaptor(MPRIS-ROOT-SOURCES
../data/org.freedesktop.MediaPlayer.root.xml
mpris.h MPRIS mpris_root MprisRoot)
qt4_add_dbus_adaptor(MPRIS-TRACKLIST-SOURCES
../data/org.freedesktop.MediaPlayer.tracklist.xml
player.h Player mpris_tracklist MprisTrackList)
endif(WIN32)
endif(APPLE)
@ -209,6 +220,9 @@ add_library(clementine_lib
${CLEMENTINE-SOURCES-UI}
${CLEMENTINE-SOURCES-RESOURCE}
${CLEMENTINE-QM-FILES}
${MPRIS-ROOT-SOURCES}
${MPRIS-PLAYER-SOURCES}
${MPRIS-TRACKLIST-SOURCES}
)
target_link_libraries(clementine_lib
qtsingleapplication

View File

@ -23,7 +23,9 @@
#include "directory.h"
#include "lastfmservice.h"
#include "mainwindow.h"
#include "player.h"
#include "song.h"
#include "mpris.h"
#include <QtSingleApplication>
#include <QtDebug>
@ -31,6 +33,8 @@
#include <QTranslator>
#include <QDir>
#include <QNetworkAccessManager>
#include <QDBusConnection>
#include <QDBusMetaType>
// Load sqlite plugin on windows
#ifdef WIN32
@ -61,6 +65,9 @@ int main(int argc, char *argv[]) {
qRegisterMetaType<DirectoryList>("DirectoryList");
qRegisterMetaType<SongList>("SongList");
qDBusRegisterMetaType<DBusStatus>();
qDBusRegisterMetaType<Version>();
lastfm::ws::ApiKey = LastFMService::kApiKey;
lastfm::ws::SharedSecret = LastFMService::kSecret;
@ -85,6 +92,9 @@ int main(int argc, char *argv[]) {
QNetworkAccessManager network;
QDBusConnection::sessionBus().registerService("org.mpris.clementine");
MPRIS mpris;
// Window
MainWindow w(&network);;
a.setActivationWindow(&w);

View File

@ -181,8 +181,10 @@ MainWindow::MainWindow(QNetworkAccessManager* network, QWidget *parent)
connect(player_, SIGNAL(Paused()), osd_, SLOT(Paused()));
connect(player_, SIGNAL(Stopped()), osd_, SLOT(Stopped()));
connect(player_, SIGNAL(VolumeChanged(int)), osd_, SLOT(VolumeChanged(int)));
connect(player_, SIGNAL(ForceShowOSD(Song)), osd_, SLOT(SongChanged(Song)));
connect(playlist_, SIGNAL(CurrentSongChanged(Song)), osd_, SLOT(SongChanged(Song)));
connect(playlist_, SIGNAL(CurrentSongChanged(Song)), player_, SLOT(CurrentMetadataChanged(Song)));
connect(playlist_, SIGNAL(PlaylistChanged()), player_, SLOT(PlaylistChanged()));
connect(ui_.playlist, SIGNAL(doubleClicked(QModelIndex)), SLOT(PlayIndex(QModelIndex)));
connect(ui_.playlist, SIGNAL(PlayPauseItem(QModelIndex)), SLOT(PlayIndex(QModelIndex)));

56
src/mpris.cpp Normal file
View File

@ -0,0 +1,56 @@
/* This file is part of Clementine.
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mpris.h"
#include <QCoreApplication>
#include "mpris_root.h"
QDBusArgument& operator<< (QDBusArgument& arg, const Version& version) {
arg.beginStructure();
arg << version.major << version.minor;
arg.endStructure();
return arg;
}
const QDBusArgument& operator>> (const QDBusArgument& arg, Version& version) {
arg.beginStructure();
arg >> version.major >> version.minor;
arg.endStructure();
return arg;
}
MPRIS::MPRIS(QObject* parent)
: QObject(parent) {
new MprisRoot(this);
QDBusConnection::sessionBus().registerObject("/", this);
}
QString MPRIS::Identity() {
return "Clementine 0.2";
}
Version MPRIS::MprisVersion() {
Version version;
version.major = 1;
version.minor = 0;
return version;
}
void MPRIS::Quit() {
qApp->quit();
}

41
src/mpris.h Normal file
View File

@ -0,0 +1,41 @@
/* This file is part of Clementine.
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MPRIS_H
#define MPRIS_H
#include <QDBusArgument>
#include <QObject>
struct Version {
quint16 minor;
quint16 major;
};
Q_DECLARE_METATYPE(Version)
QDBusArgument& operator<< (QDBusArgument& arg, const Version& version);
const QDBusArgument& operator>> (const QDBusArgument& arg, Version& version);
class MPRIS : public QObject {
Q_OBJECT
public:
MPRIS(QObject* parent = 0);
QString Identity();
void Quit();
Version MprisVersion();
};
#endif // MPRIS_H

View File

@ -17,6 +17,8 @@
#include "player.h"
#include "playlist.h"
#include "lastfmservice.h"
#include "mpris_player.h"
#include "mpris_tracklist.h"
#ifdef Q_OS_WIN32
# include "phononengine.h"
@ -26,9 +28,30 @@
#include <QtDebug>
#include <QtConcurrentRun>
#include <QDBusConnection>
#include <boost/bind.hpp>
QDBusArgument& operator<< (QDBusArgument& arg, const DBusStatus& status) {
arg.beginStructure();
arg << status.Play;
arg << status.Random;
arg << status.Repeat;
arg << status.RepeatPlaylist;
arg.endStructure();
return arg;
}
const QDBusArgument& operator>> (const QDBusArgument& arg, DBusStatus& status) {
arg.beginStructure();
arg >> status.Play;
arg >> status.Random;
arg >> status.Repeat;
arg >> status.RepeatPlaylist;
arg.endStructure();
return arg;
}
Player::Player(Playlist* playlist, LastFMService* lastfm, QObject* parent)
: QObject(parent),
playlist_(playlist),
@ -49,6 +72,13 @@ Player::Player(Playlist* playlist, LastFMService* lastfm, QObject* parent)
connect(init_engine_watcher_, SIGNAL(finished()), SLOT(EngineInitFinished()));
connect(engine_, SIGNAL(error(QString)), SIGNAL(Error(QString)));
MprisPlayer* mpris = new MprisPlayer(this);
// Hack so the next registerObject() doesn't override this one.
QDBusConnection::sessionBus().registerObject(
"/Player", mpris, QDBusConnection::ExportAllSlots | QDBusConnection::ExportAllSignals);
new MprisTrackList(this);
QDBusConnection::sessionBus().registerObject("/TrackList", this);
}
void Player::Init() {
@ -168,6 +198,8 @@ void Player::EngineStateChanged(Engine::State state) {
case Engine::Empty:
case Engine::Idle: emit Stopped(); break;
}
emit StatusChange(GetStatus());
emit CapsChange(GetCaps());
}
void Player::SetVolume(int value) {
@ -202,6 +234,8 @@ void Player::PlayAt(int index) {
if (lastfm_->IsScrobblingEnabled())
lastfm_->NowPlaying(item->Metadata());
}
emit CapsChange(GetCaps());
}
void Player::StreamReady(const QUrl& original_url, const QUrl& media_url) {
@ -225,6 +259,7 @@ void Player::StreamReady(const QUrl& original_url, const QUrl& media_url) {
void Player::CurrentMetadataChanged(const Song &metadata) {
lastfm_->NowPlaying(metadata);
current_item_ = metadata;
emit TrackChange(GetMetadata());
}
void Player::Seek(int seconds) {
@ -251,3 +286,217 @@ void Player::EngineMetadataReceived(const Engine::SimpleMetaBundle& bundle) {
playlist_->SetStreamMetadata(item->Url(), song);
}
int Player::GetCaps() const {
int caps = CAN_HAS_TRACKLIST;
if (current_item_.is_valid()) { caps |= CAN_PROVIDE_METADATA; }
if (GetState() == Engine::Playing && current_item_.filetype() != Song::Type_Stream) {
caps |= CAN_PAUSE;
}
if (GetState() == Engine::Paused) {
caps |= CAN_PLAY;
}
if (GetState() != Engine::Empty && current_item_.filetype() != Song::Type_Stream) {
caps |= CAN_SEEK;
}
if (playlist_->next_index() != -1 ||
playlist_->current_item_options() & PlaylistItem::ContainsMultipleTracks) {
caps |= CAN_GO_NEXT;
}
if (playlist_->previous_index() != -1) {
caps |= CAN_GO_PREV;
}
return caps;
}
DBusStatus Player::GetStatus() const {
DBusStatus status;
switch (GetState()) {
case Engine::Empty:
case Engine::Idle:
status.Play = 2;
break;
case Engine::Playing:
status.Play = 0;
break;
case Engine::Paused:
status.Play = 1;
break;
}
status.Random = playlist_->sequence()->shuffle_mode() == PlaylistSequence::Shuffle_Off ? 0 : 1;
PlaylistSequence::RepeatMode repeat_mode = playlist_->sequence()->repeat_mode();
status.Repeat = repeat_mode == PlaylistSequence::Repeat_Track ? 1 : 0;
status.RepeatPlaylist = (repeat_mode == PlaylistSequence::Repeat_Album ||
repeat_mode == PlaylistSequence::Repeat_Playlist) ? 1 : 0;
return status;
}
namespace {
inline void AddMetadata(const QString& key, const QString& metadata, QVariantMap* map) {
if (!metadata.isEmpty()) {
(*map)[key] = metadata;
}
}
inline void AddMetadata(const QString& key, int metadata, QVariantMap* map) {
if (metadata > 0) {
(*map)[key] = metadata;
}
}
} // namespace
QVariantMap Player::GetMetadata(const PlaylistItem& item) const {
QVariantMap ret;
if (item.type() == PlaylistItem::Type_Song) {
const Song& song = item.Metadata();
if (song.is_valid()) {
AddMetadata("location", item.Url().toString(), &ret);
AddMetadata("title", song.PrettyTitle(), &ret);
AddMetadata("artist", song.artist(), &ret);
AddMetadata("album", song.album(), &ret);
AddMetadata("time", song.length(), &ret);
AddMetadata("tracknumber", song.track(), &ret);
}
return ret;
} else {
AddMetadata("location", item.Url().toString(), &ret);
const Song& song = item.Metadata();
AddMetadata("title", song.PrettyTitle(), &ret);
AddMetadata("artist", song.artist(), &ret);
AddMetadata("album", song.album(), &ret);
AddMetadata("time", song.length(), &ret);
AddMetadata("tracknumber", song.track(), &ret);
return ret;
}
}
QVariantMap Player::GetMetadata() const {
return GetMetadata(*(playlist_->current_item()));
}
QVariantMap Player::GetMetadata(int track) const {
if (track >= playlist_->rowCount()) {
return QVariantMap();
}
const PlaylistItem& item = *(playlist_->item_at(track));
return GetMetadata(item);
}
void Player::Mute() {
SetVolume(0);
}
void Player::Pause() {
switch (GetState()) {
case Engine::Playing:
engine_->pause();
break;
case Engine::Paused:
engine_->pause();
break;
default:
return;
}
}
void Player::Play() {
switch (GetState()) {
case Engine::Playing:
Seek(0);
break;
case Engine::Paused:
engine_->unpause();
break;
default:
Next();
break;
}
}
void Player::Prev() {
Previous();
}
int Player::PositionGet() const {
return engine_->position();
}
void Player::PositionSet(int x) {
Seek(x / 1000);
}
void Player::Repeat(bool enable) {
playlist_->sequence()->SetRepeatMode(
enable ? PlaylistSequence::Repeat_Track : PlaylistSequence::Repeat_Off);
}
void Player::ShowOSD() {
emit ForceShowOSD(current_item_);
}
void Player::VolumeDown(int change) {
SetVolume(GetVolume() - change);
}
void Player::VolumeUp(int change) {
SetVolume(GetVolume() + change);
}
int Player::VolumeGet() const {
return GetVolume();
}
void Player::VolumeSet(int volume) {
SetVolume(volume);
}
int Player::AddTrack(const QString& track, bool play_now) {
QUrl url(track);
QList<QUrl> list;
list << url;
QModelIndex index;
if (url.scheme() == "file") {
index = playlist_->InsertPaths(list, play_now ? playlist_->current_index() + 1 : -1);
} else {
index = playlist_->InsertStreamUrls(list, play_now ? playlist_->current_index() + 1: -1);
}
if (index.isValid()) {
if (play_now) {
Next();
}
return 0;
}
return -1;
}
void Player::DelTrack(int index) {
playlist_->removeRows(index, 1);
}
int Player::GetCurrentTrack() const {
return playlist_->current_index();
}
int Player::GetLength() const {
return playlist_->rowCount();
}
void Player::SetLoop(bool enable) {
playlist_->sequence()->SetRepeatMode(
enable ? PlaylistSequence::Repeat_Playlist : PlaylistSequence::Repeat_Off);
}
void Player::SetRandom(bool enable) {
playlist_->sequence()->SetShuffleMode(
enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
}
void Player::PlayTrack(int index) {
PlayAt(index);
}
void Player::PlaylistChanged() {
emit TrackListChange(GetLength());
}

View File

@ -21,6 +21,7 @@
#include <QSettings>
#include <QFuture>
#include <QFutureWatcher>
#include <QDBusArgument>
#include "engine_fwd.h"
#include "playlistitem.h"
@ -30,6 +31,17 @@ class Playlist;
class Settings;
class LastFMService;
struct DBusStatus { // From Amarok.
int Play; // Playing = 0, Paused = 1, Stopped = 2
int Random; // Linearly = 0, Randomly = 1
int Repeat; // Go_To_Next = 0, Repeat_Current = 1
int RepeatPlaylist; // Stop_When_Finished = 0, Never_Give_Up_Playing = 1
};
Q_DECLARE_METATYPE(DBusStatus);
QDBusArgument& operator<< (QDBusArgument& arg, const DBusStatus& status);
const QDBusArgument& operator>> (const QDBusArgument& arg, DBusStatus& status);
class Player : public QObject {
Q_OBJECT
@ -45,15 +57,25 @@ class Player : public QObject {
PlaylistItem::Options GetCurrentItemOptions() const { return current_item_options_; }
Song GetCurrentItem() const { return current_item_; }
// MPRIS
enum DBusCaps {
NONE = 0,
CAN_GO_NEXT = 1 << 0,
CAN_GO_PREV = 1 << 1,
CAN_PAUSE = 1 << 2,
CAN_PLAY = 1 << 3,
CAN_SEEK = 1 << 4,
CAN_PROVIDE_METADATA = 1 << 5,
CAN_HAS_TRACKLIST = 1 << 6,
};
public slots:
void ReloadSettings();
void PlayAt(int index);
void PlayPause();
void Next();
void NextItem();
void Previous();
void Stop();
void SetVolume(int value);
void Seek(int seconds);
@ -61,6 +83,39 @@ class Player : public QObject {
void StreamReady(const QUrl& original_url, const QUrl& media_url);
void CurrentMetadataChanged(const Song& metadata);
void PlaylistChanged();
// MPRIS /Player
int GetCaps() const;
DBusStatus GetStatus() const;
QVariantMap GetMetadata() const;
void Mute();
void Pause();
void Stop();
void Play();
void Next();
void Prev();
int PositionGet() const;
void PositionSet(int);
void Repeat(bool);
void ShowOSD();
void VolumeDown(int);
void VolumeUp(int);
int VolumeGet() const;
void VolumeSet(int);
// MPRIS /Tracklist
int AddTrack(const QString&, bool);
void DelTrack(int index);
int GetCurrentTrack() const;
int GetLength() const;
QVariantMap GetMetadata(int) const;
void SetLoop(bool enable);
void SetRandom(bool enable);
// Amarok extension.
void PlayTrack(int index);
signals:
void InitFinished();
@ -70,12 +125,24 @@ class Player : public QObject {
void VolumeChanged(int volume);
void Error(const QString& message);
void ForceShowOSD(Song);
// MPRIS
// Player
void CapsChange(int);
void TrackChange(QVariantMap);
void StatusChange(DBusStatus);
// TrackList
void TrackListChange(int i);
private slots:
void EngineInitFinished();
void EngineStateChanged(Engine::State);
void EngineMetadataReceived(const Engine::SimpleMetaBundle& bundle);
private:
QVariantMap GetMetadata(const PlaylistItem& item) const;
Playlist* playlist_;
LastFMService* lastfm_;
QSettings settings_;

View File

@ -47,6 +47,8 @@ Playlist::Playlist(QObject *parent) :
playlist_sequence_(NULL),
ignore_sorting_(false)
{
connect(this, SIGNAL(rowsInserted(const QModelIndex&, int, int)), SIGNAL(PlaylistChanged()));
connect(this, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), SIGNAL(PlaylistChanged()));
}
Playlist::~Playlist() {
@ -569,6 +571,9 @@ void Playlist::Restore() {
}
bool Playlist::removeRows(int row, int count, const QModelIndex& parent) {
if (row < 0 || row >= items_.size() || row + count > items_.size()) {
return false;
}
beginRemoveRows(parent, row, row+count-1);
// Remove items

View File

@ -119,6 +119,7 @@ class Playlist : public QAbstractListModel {
void sort(int column, Qt::SortOrder order);
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex());
public slots:
void set_current_index(int index);
void Paused();
@ -137,6 +138,8 @@ class Playlist : public QAbstractListModel {
signals:
void CurrentSongChanged(const Song& metadata);
void PlaylistChanged();
private:
void SetCurrentIsPaused(bool paused);
void UpdateScrobblePoint();

View File

@ -62,7 +62,7 @@ class PlaylistItem {
// return true. If it returns false then the URL from Url() will be passed
// directly to xine instead.
virtual void StartLoading() {}
virtual QUrl Url() = 0;
virtual QUrl Url() const = 0;
// If the item is a radio station that can play another song after one has
// finished then it should do so and return true

View File

@ -80,7 +80,7 @@ void RadioPlaylistItem::LoadNext() {
service_->LoadNext(url_);
}
QUrl RadioPlaylistItem::Url() {
QUrl RadioPlaylistItem::Url() const {
return url_;
}

View File

@ -39,7 +39,7 @@ class RadioPlaylistItem : public PlaylistItem {
Song Metadata() const;
void StartLoading();
QUrl Url();
QUrl Url() const;
void LoadNext();

View File

@ -84,7 +84,7 @@ void SongPlaylistItem::RestoreStream(const QSettings& settings) {
settings.value("length", -1).toInt());
}
QUrl SongPlaylistItem::Url() {
QUrl SongPlaylistItem::Url() const {
if (QFile::exists(song_.filename())) {
return QUrl::fromLocalFile(song_.filename());
} else {

View File

@ -33,7 +33,7 @@ class SongPlaylistItem : public PlaylistItem {
Song Metadata() const { return song_; }
QUrl Url();
QUrl Url() const;
private:
void SaveFile(QSettings& settings) const;