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