1 /*
2  * Strawberry Music Player
3  * This file was part of Clementine.
4  * Copyright 2010, David Sansome <me@davidsansome.com>
5  * Copyright 2018-2021, Jonas Kvinge <jonas@jkvinge.net>
6  *
7  * Strawberry is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * Strawberry is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with Strawberry.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  */
21 
22 #include "config.h"
23 
24 #include <cstdlib>
25 #include <memory>
26 #include <algorithm>
27 
28 #include <QApplication>
29 #include <QCoreApplication>
30 #include <QGuiApplication>
31 #include <QObject>
32 #include <QFile>
33 #include <QList>
34 #include <QJsonArray>
35 #include <QVariant>
36 #include <QVariantMap>
37 #include <QString>
38 #include <QStringList>
39 #include <QUrl>
40 #include <QDBusConnection>
41 #include <QDBusMessage>
42 #include <QDBusArgument>
43 #include <QDBusObjectPath>
44 #include <QtDebug>
45 
46 #include "core/logging.h"
47 
48 #include "mpris_common.h"
49 #include "mpris2.h"
50 
51 #include "timeconstants.h"
52 #include "song.h"
53 #include "application.h"
54 #include "player.h"
55 #include "engine/enginebase.h"
56 #include "playlist/playlist.h"
57 #include "playlist/playlistitem.h"
58 #include "playlist/playlistmanager.h"
59 #include "playlist/playlistsequence.h"
60 #include "covermanager/currentalbumcoverloader.h"
61 #include "covermanager/albumcoverloaderresult.h"
62 
63 #include <core/mpris2_player.h>
64 #include <core/mpris2_playlists.h>
65 #include <core/mpris2_root.h>
66 #include <core/mpris2_tracklist.h>
67 
operator <<(QDBusArgument & arg,const MprisPlaylist & playlist)68 QDBusArgument &operator<<(QDBusArgument &arg, const MprisPlaylist &playlist) {
69   arg.beginStructure();
70   arg << playlist.id << playlist.name << playlist.icon;
71   arg.endStructure();
72   return arg;
73 }
74 
operator >>(const QDBusArgument & arg,MprisPlaylist & playlist)75 const QDBusArgument &operator>>(const QDBusArgument &arg, MprisPlaylist &playlist) {
76   arg.beginStructure();
77   arg >> playlist.id >> playlist.name >> playlist.icon;
78   arg.endStructure();
79   return arg;
80 }
81 
operator <<(QDBusArgument & arg,const MaybePlaylist & playlist)82 QDBusArgument &operator<<(QDBusArgument &arg, const MaybePlaylist &playlist) {
83   arg.beginStructure();
84   arg << playlist.valid;
85   arg << playlist.playlist;
86   arg.endStructure();
87   return arg;
88 }
89 
operator >>(const QDBusArgument & arg,MaybePlaylist & playlist)90 const QDBusArgument &operator>>(const QDBusArgument &arg, MaybePlaylist &playlist) {
91   arg.beginStructure();
92   arg >> playlist.valid >> playlist.playlist;
93   arg.endStructure();
94   return arg;
95 }
96 
97 namespace mpris {
98 
99 const char *Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2";
100 const char *Mpris2::kServiceName = "org.mpris.MediaPlayer2.strawberry";
101 const char *Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties";
102 
Mpris2(Application * app,QObject * parent)103 Mpris2::Mpris2(Application *app, QObject *parent)
104     : QObject(parent),
105       app_(app),
106       app_name_(QCoreApplication::applicationName()) {
107 
108   new Mpris2Root(this);
109   new Mpris2TrackList(this);
110   new Mpris2Player(this);
111   new Mpris2Playlists(this);
112 
113   if (!QDBusConnection::sessionBus().registerService(kServiceName)) {
114     qLog(Warning) << "Failed to register" << QString(kServiceName) << "on the session bus";
115     return;
116   }
117 
118   if (!QDBusConnection::sessionBus().registerObject(kMprisObjectPath, this)) {
119     qLog(Warning) << "Failed to register" << QString(kMprisObjectPath) << "on the session bus";
120     return;
121   }
122 
123   QObject::connect(app_->current_albumcover_loader(), &CurrentAlbumCoverLoader::AlbumCoverLoaded, this, &Mpris2::AlbumCoverLoaded);
124 
125   QObject::connect(app_->player()->engine(), &EngineBase::StateChanged, this, &Mpris2::EngineStateChanged);
126   QObject::connect(app_->player(), &Player::VolumeChanged, this, &Mpris2::VolumeChanged);
127   QObject::connect(app_->player(), &Player::Seeked, this, &Mpris2::Seeked);
128 
129   QObject::connect(app_->playlist_manager(), &PlaylistManager::PlaylistManagerInitialized, this, &Mpris2::PlaylistManagerInitialized);
130   QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentSongChanged, this, &Mpris2::CurrentSongChanged);
131   QObject::connect(app_->playlist_manager(), &PlaylistManager::PlaylistChanged, this, &Mpris2::PlaylistChangedSlot);
132   QObject::connect(app_->playlist_manager(), &PlaylistManager::CurrentChanged, this, &Mpris2::PlaylistCollectionChanged);
133 
134   app_name_[0] = app_name_[0].toUpper();
135 
136   if (!QGuiApplication::desktopFileName().isEmpty()) {
137     desktop_files_ << QGuiApplication::desktopFileName();
138   }
139 
140   QStringList domain_split = QCoreApplication::organizationDomain().split(".");
141   std::reverse(domain_split.begin(), domain_split.end());
142   desktop_files_ << QStringList() << domain_split.join(".") + "." + QCoreApplication::applicationName().toLower();
143   desktop_files_ << QCoreApplication::applicationName().toLower();
144   desktop_file_ = desktop_files_.first();
145 
146   data_dirs_ = QString(qgetenv("XDG_DATA_DIRS")).split(":");
147   data_dirs_.append("/usr/local/share");
148   data_dirs_.append("/usr/share");
149 
150   for (const QString &directory : data_dirs_) {
151     for (const QString &desktop_file : desktop_files_) {
152       QString path = QString("%1/applications/%2.desktop").arg(directory, desktop_file);
153       if (QFile::exists(path)) {
154         desktop_file_ = desktop_file;
155       }
156     }
157   }
158 
159 }
160 
161 // when PlaylistManager gets it ready, we connect PlaylistSequence with this
PlaylistManagerInitialized()162 void Mpris2::PlaylistManagerInitialized() {
163   QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::ShuffleModeChanged, this, &Mpris2::ShuffleModeChanged);
164   QObject::connect(app_->playlist_manager()->sequence(), &PlaylistSequence::RepeatModeChanged, this, &Mpris2::RepeatModeChanged);
165 }
166 
EngineStateChanged(Engine::State newState)167 void Mpris2::EngineStateChanged(Engine::State newState) {
168 
169   if (newState != Engine::Playing && newState != Engine::Paused) {
170     last_metadata_ = QVariantMap();
171     EmitNotification("Metadata");
172   }
173 
174   EmitNotification("CanPlay");
175   EmitNotification("CanPause");
176   EmitNotification("PlaybackStatus", PlaybackStatus(newState));
177   if (newState == Engine::Playing) EmitNotification("CanSeek", CanSeek(newState));
178 
179 }
180 
VolumeChanged()181 void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
182 
ShuffleModeChanged()183 void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
184 
RepeatModeChanged()185 void Mpris2::RepeatModeChanged() {
186 
187   EmitNotification("LoopStatus");
188   EmitNotification("CanGoNext", CanGoNext());
189   EmitNotification("CanGoPrevious", CanGoPrevious());
190 
191 }
192 
EmitNotification(const QString & name,const QVariant & val)193 void Mpris2::EmitNotification(const QString &name, const QVariant &val) {
194   EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
195 }
196 
EmitNotification(const QString & name,const QVariant & val,const QString & mprisEntity)197 void Mpris2::EmitNotification(const QString &name, const QVariant &val, const QString &mprisEntity) {
198 
199   QDBusMessage msg = QDBusMessage::createSignal(kMprisObjectPath, kFreedesktopPath, "PropertiesChanged");
200   QVariantMap map;
201   map.insert(name, val);
202   QVariantList args = QVariantList() << mprisEntity << map << QStringList();
203   msg.setArguments(args);
204   QDBusConnection::sessionBus().send(msg);
205 
206 }
207 
EmitNotification(const QString & name)208 void Mpris2::EmitNotification(const QString &name) {
209 
210   QVariant value;
211   if (name == "PlaybackStatus") value = PlaybackStatus();
212   else if (name == "LoopStatus") value = LoopStatus();
213   else if (name == "Shuffle") value = Shuffle();
214   else if (name == "Metadata") value = Metadata();
215   else if (name == "Volume") value = Volume();
216   else if (name == "Position") value = Position();
217   else if (name == "CanPlay") value = CanPlay();
218   else if (name == "CanPause") value = CanPause();
219   else if (name == "CanSeek") value = CanSeek();
220   else if (name == "CanGoNext") value = CanGoNext();
221   else if (name == "CanGoPrevious") value = CanGoPrevious();
222 
223   if (value.isValid()) EmitNotification(name, value);
224 
225 }
226 
227 //------------------Root Interface--------------------------//
228 
CanQuit() const229 bool Mpris2::CanQuit() const { return true; }
230 
CanRaise() const231 bool Mpris2::CanRaise() const { return true; }
232 
HasTrackList() const233 bool Mpris2::HasTrackList() const { return true; }
234 
Identity() const235 QString Mpris2::Identity() const { return app_name_; }
236 
DesktopEntryAbsolutePath() const237 QString Mpris2::DesktopEntryAbsolutePath() const {
238 
239   for (const QString &directory : data_dirs_) {
240     for (const QString &desktop_file : desktop_files_) {
241       QString path = QString("%1/applications/%2.desktop").arg(directory, desktop_file);
242       if (QFile::exists(path)) {
243         return path;
244       }
245     }
246   }
247   return QString();
248 
249 }
250 
DesktopEntry() const251 QString Mpris2::DesktopEntry() const { return desktop_file_; }
252 
SupportedUriSchemes() const253 QStringList Mpris2::SupportedUriSchemes() const {
254 
255   static QStringList res = QStringList() << "file"
256                                          << "http"
257                                          << "cdda"
258                                          << "smb"
259                                          << "sftp";
260   return res;
261 
262 }
263 
SupportedMimeTypes() const264 QStringList Mpris2::SupportedMimeTypes() const {
265 
266   static QStringList res = QStringList() << "x-content/audio-player"
267                                          << "application/ogg"
268                                          << "application/x-ogg"
269                                          << "application/x-ogm-audio"
270                                          << "audio/flac"
271                                          << "audio/ogg"
272                                          << "audio/vorbis"
273                                          << "audio/aac"
274                                          << "audio/mp4"
275                                          << "audio/mpeg"
276                                          << "audio/mpegurl"
277                                          << "audio/vnd.rn-realaudio"
278                                          << "audio/x-flac"
279                                          << "audio/x-oggflac"
280                                          << "audio/x-vorbis"
281                                          << "audio/x-vorbis+ogg"
282                                          << "audio/x-speex"
283                                          << "audio/x-wav"
284                                          << "audio/x-wavpack"
285                                          << "audio/x-ape"
286                                          << "audio/x-mp3"
287                                          << "audio/x-mpeg"
288                                          << "audio/x-mpegurl"
289                                          << "audio/x-ms-wma"
290                                          << "audio/x-musepack"
291                                          << "audio/x-pn-realaudio"
292                                          << "audio/x-scpls"
293                                          << "video/x-ms-asf";
294 
295   return res;
296 
297 }
298 
Raise()299 void Mpris2::Raise() { emit RaiseMainWindow(); }
300 
Quit()301 void Mpris2::Quit() { QCoreApplication::quit(); }
302 
PlaybackStatus() const303 QString Mpris2::PlaybackStatus() const {
304   return PlaybackStatus(app_->player()->GetState());
305 }
306 
PlaybackStatus(Engine::State state) const307 QString Mpris2::PlaybackStatus(Engine::State state) const {
308 
309   switch (state) {
310     case Engine::Playing: return "Playing";
311     case Engine::Paused: return "Paused";
312     default: return "Stopped";
313   }
314 
315 }
316 
LoopStatus() const317 QString Mpris2::LoopStatus() const {
318 
319   if (!app_->playlist_manager()->sequence()) {
320     return "None";
321   }
322 
323   switch (app_->playlist_manager()->sequence()->repeat_mode()) {
324     case PlaylistSequence::Repeat_Album:
325     case PlaylistSequence::Repeat_Playlist: return "Playlist";
326     case PlaylistSequence::Repeat_Track: return "Track";
327     default: return "None";
328   }
329 
330 }
331 
SetLoopStatus(const QString & value)332 void Mpris2::SetLoopStatus(const QString &value) {
333 
334   PlaylistSequence::RepeatMode mode = PlaylistSequence::Repeat_Off;
335 
336   if (value == "None") {
337     mode = PlaylistSequence::Repeat_Off;
338   }
339   else if (value == "Track") {
340     mode = PlaylistSequence::Repeat_Track;
341   }
342   else if (value == "Playlist") {
343     mode = PlaylistSequence::Repeat_Playlist;
344   }
345 
346   app_->playlist_manager()->active()->sequence()->SetRepeatMode(mode);
347 
348 }
349 
Rate() const350 double Mpris2::Rate() const { return 1.0; }
351 
SetRate(double rate)352 void Mpris2::SetRate(double rate) {
353 
354   if (rate == 0) {
355     app_->player()->Pause();
356   }
357 
358 }
359 
Shuffle() const360 bool Mpris2::Shuffle() const {
361 
362   return app_->playlist_manager()->sequence()->shuffle_mode() != PlaylistSequence::Shuffle_Off;
363 
364 }
365 
SetShuffle(bool enable)366 void Mpris2::SetShuffle(bool enable) {
367   app_->playlist_manager()->active()->sequence()->SetShuffleMode(enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
368 }
369 
Metadata() const370 QVariantMap Mpris2::Metadata() const { return last_metadata_; }
371 
current_track_id() const372 QString Mpris2::current_track_id() const {
373   return QString("/org/strawberrymusicplayer/strawberry/Track/%1").arg(QString::number(app_->playlist_manager()->active()->current_row()));
374 }
375 
376 // We send Metadata change notification as soon as the process of changing song starts...
CurrentSongChanged(const Song & song)377 void Mpris2::CurrentSongChanged(const Song &song) {
378 
379   AlbumCoverLoaded(song);
380   EmitNotification("CanPlay");
381   EmitNotification("CanPause");
382   EmitNotification("CanGoNext", CanGoNext());
383   EmitNotification("CanGoPrevious", CanGoPrevious());
384   EmitNotification("CanSeek", CanSeek());
385 
386 }
387 
388 // ... and we add the cover information later, when it's available.
AlbumCoverLoaded(const Song & song,const AlbumCoverLoaderResult & result)389 void Mpris2::AlbumCoverLoaded(const Song &song, const AlbumCoverLoaderResult &result) {
390 
391   last_metadata_ = QVariantMap();
392   song.ToXesam(&last_metadata_);
393 
394   using mpris::AddMetadata;
395   AddMetadata("mpris:trackid", current_track_id(), &last_metadata_);
396 
397   QUrl cover_url;
398   if (result.album_cover.cover_url.isValid() && result.album_cover.cover_url.isLocalFile() && QFile(result.album_cover.cover_url.toLocalFile()).exists()) {
399     cover_url = result.album_cover.cover_url;
400   }
401   else if (result.temp_cover_url.isValid() && result.temp_cover_url.isLocalFile()) {
402     cover_url = result.temp_cover_url;
403   }
404   if (cover_url.isValid()) AddMetadata("mpris:artUrl", cover_url.toString(), &last_metadata_);
405 
406   AddMetadata("year", song.year(), &last_metadata_);
407   AddMetadata("bitrate", song.bitrate(), &last_metadata_);
408 
409   EmitNotification("Metadata", last_metadata_);
410 
411 }
412 
Volume() const413 double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; }
414 
SetVolume(double value)415 void Mpris2::SetVolume(double value) { app_->player()->SetVolume(static_cast<int>(value * 100)); }
416 
Position() const417 qint64 Mpris2::Position() const {
418   return app_->player()->engine()->position_nanosec() / kNsecPerUsec;
419 }
420 
MaximumRate() const421 double Mpris2::MaximumRate() const { return 1.0; }
422 
MinimumRate() const423 double Mpris2::MinimumRate() const { return 1.0; }
424 
CanGoNext() const425 bool Mpris2::CanGoNext() const {
426   return app_->playlist_manager()->active() && app_->playlist_manager()->active()->next_row() != -1;
427 }
428 
CanGoPrevious() const429 bool Mpris2::CanGoPrevious() const {
430   return app_->playlist_manager()->active() && (app_->playlist_manager()->active()->previous_row() != -1 || app_->player()->PreviousWouldRestartTrack());
431 }
432 
CanPlay() const433 bool Mpris2::CanPlay() const {
434   return app_->playlist_manager()->active() && app_->playlist_manager()->active()->rowCount() != 0;
435 }
436 
437 // This one's a bit different than MPRIS 1 - we want this to be true even when the song is already paused or stopped.
CanPause() const438 bool Mpris2::CanPause() const {
439   return (app_->player()->GetCurrentItem() && app_->player()->GetState() == Engine::Playing && !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled)) || PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
440 }
441 
CanSeek() const442 bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
443 
CanSeek(Engine::State state) const444 bool Mpris2::CanSeek(Engine::State state) const {
445   return app_->player()->GetCurrentItem() && state != Engine::Empty && !app_->player()->GetCurrentItem()->Metadata().is_stream();
446 }
447 
CanControl() const448 bool Mpris2::CanControl() const { return true; }
449 
Next()450 void Mpris2::Next() {
451   if (CanGoNext()) {
452     app_->player()->Next();
453   }
454 }
455 
Previous()456 void Mpris2::Previous() {
457   if (CanGoPrevious()) {
458     app_->player()->Previous();
459   }
460 }
461 
Pause()462 void Mpris2::Pause() {
463   if (CanPause() && app_->player()->GetState() != Engine::Paused) {
464     app_->player()->Pause();
465   }
466 }
467 
PlayPause()468 void Mpris2::PlayPause() {
469   if (CanPause()) {
470     app_->player()->PlayPause();
471   }
472 }
473 
Stop()474 void Mpris2::Stop() { app_->player()->Stop(); }
475 
Play()476 void Mpris2::Play() {
477   if (CanPlay()) {
478     app_->player()->Play();
479   }
480 }
481 
Seek(qint64 offset)482 void Mpris2::Seek(qint64 offset) {
483 
484   if (CanSeek()) {
485     app_->player()->SeekTo(app_->player()->engine()->position_nanosec() / kNsecPerSec + offset / kUsecPerSec);
486   }
487 
488 }
489 
SetPosition(const QDBusObjectPath & trackId,qint64 offset)490 void Mpris2::SetPosition(const QDBusObjectPath &trackId, qint64 offset) {
491 
492   if (CanSeek() && trackId.path() == current_track_id() && offset >= 0) {
493     offset *= kNsecPerUsec;
494 
495     if (offset < app_->player()->GetCurrentItem()->Metadata().length_nanosec()) {
496       app_->player()->SeekTo(offset / kNsecPerSec);
497     }
498   }
499 
500 }
501 
OpenUri(const QString & uri)502 void Mpris2::OpenUri(const QString &uri) {
503   app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(uri), -1, true);
504 }
505 
Tracks() const506 Track_Ids Mpris2::Tracks() const {
507   // TODO
508   return Track_Ids();
509 }
510 
CanEditTracks() const511 bool Mpris2::CanEditTracks() const { return false; }
512 
GetTracksMetadata(const Track_Ids & tracks) const513 TrackMetadata Mpris2::GetTracksMetadata(const Track_Ids &tracks) const {
514 
515   Q_UNUSED(tracks);
516 
517   // TODO
518   return TrackMetadata();
519 
520 }
521 
AddTrack(const QString & uri,const QDBusObjectPath & afterTrack,bool setAsCurrent)522 void Mpris2::AddTrack(const QString &uri, const QDBusObjectPath &afterTrack, bool setAsCurrent) {
523 
524   Q_UNUSED(uri);
525   Q_UNUSED(afterTrack);
526   Q_UNUSED(setAsCurrent);
527 
528   // TODO
529 
530 }
531 
RemoveTrack(const QDBusObjectPath & trackId)532 void Mpris2::RemoveTrack(const QDBusObjectPath &trackId) {
533   Q_UNUSED(trackId);
534   // TODO
535 }
536 
GoTo(const QDBusObjectPath & trackId)537 void Mpris2::GoTo(const QDBusObjectPath &trackId) {
538   Q_UNUSED(trackId);
539   // TODO
540 }
541 
PlaylistCount() const542 quint32 Mpris2::PlaylistCount() const {
543   return app_->playlist_manager()->GetAllPlaylists().size();
544 }
545 
Orderings() const546 QStringList Mpris2::Orderings() const { return QStringList() << "User"; }
547 
548 namespace {
549 
MakePlaylistPath(int id)550 QDBusObjectPath MakePlaylistPath(int id) {
551   return QDBusObjectPath(QString("/org/strawberrymusicplayer/strawberry/PlaylistId/%1").arg(id));
552 }
553 
554 }  // namespace
555 
ActivePlaylist() const556 MaybePlaylist Mpris2::ActivePlaylist() const {
557 
558   MaybePlaylist maybe_playlist;
559   Playlist *current_playlist = app_->playlist_manager()->current();
560   maybe_playlist.valid = current_playlist;
561   if (!current_playlist) {
562     return maybe_playlist;
563   }
564 
565   maybe_playlist.playlist.id = MakePlaylistPath(current_playlist->id());
566   maybe_playlist.playlist.name = app_->playlist_manager()->GetPlaylistName(current_playlist->id());
567   return maybe_playlist;
568 
569 }
570 
ActivatePlaylist(const QDBusObjectPath & playlist_id)571 void Mpris2::ActivatePlaylist(const QDBusObjectPath &playlist_id) {
572 
573   QStringList split_path = playlist_id.path().split('/');
574   qLog(Debug) << Q_FUNC_INFO << playlist_id.path() << split_path;
575   if (split_path.isEmpty()) {
576     return;
577   }
578   bool ok = false;
579   int p = split_path.last().toInt(&ok);
580   if (!ok) {
581     return;
582   }
583   if (!app_->playlist_manager()->IsPlaylistOpen(p)) {
584     qLog(Error) << "Playlist isn't opened!";
585     return;
586   }
587   app_->playlist_manager()->SetActivePlaylist(p);
588   app_->player()->Next();
589 
590 }
591 
592 // TODO: Support sort orders.
GetPlaylists(quint32 index,quint32 max_count,const QString & order,bool reverse_order)593 MprisPlaylistList Mpris2::GetPlaylists(quint32 index, quint32 max_count, const QString &order, bool reverse_order) {
594 
595   Q_UNUSED(order);
596 
597   QList<Playlist*> playlists = app_->playlist_manager()->GetAllPlaylists();
598   MprisPlaylistList ret;
599   ret.reserve(playlists.count());
600   for (Playlist *p : playlists) {
601     MprisPlaylist mpris_playlist;
602     mpris_playlist.id = MakePlaylistPath(p->id());
603     mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(p->id());
604     ret << mpris_playlist;
605   }
606 
607   if (reverse_order) {
608     std::reverse(ret.begin(), ret.end());
609   }
610 
611   return ret.mid(index, max_count);
612 
613 }
614 
PlaylistChangedSlot(Playlist * playlist)615 void Mpris2::PlaylistChangedSlot(Playlist *playlist) {
616 
617   MprisPlaylist mpris_playlist;
618   mpris_playlist.id = MakePlaylistPath(playlist->id());
619   mpris_playlist.name = app_->playlist_manager()->GetPlaylistName(playlist->id());
620 
621   emit PlaylistChanged(mpris_playlist);
622 
623 }
624 
PlaylistCollectionChanged(Playlist * playlist)625 void Mpris2::PlaylistCollectionChanged(Playlist *playlist) {
626   Q_UNUSED(playlist);
627   EmitNotification("PlaylistCount", "", "org.mpris.MediaPlayer2.Playlists");
628 }
629 
630 }  // namespace mpris
631