/src/libtomahawk/infosystem/infoplugins/unix/mprisplugin.cpp

http://github.com/tomahawk-player/tomahawk · C++ · 675 lines · 503 code · 114 blank · 58 comment · 51 complexity · bcd2423f530d04ccdb463cca202947ab MD5 · raw file

  1. /* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
  2. *
  3. * Copyright 2010-2011, Christian Muehlhaeuser <muesli@tomahawk-player.org>
  4. *
  5. * Tomahawk is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * Tomahawk is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. #include <QApplication>
  19. #include <QImage>
  20. #include <QtDBus/QtDBus>
  21. #include "audio/audioengine.h"
  22. #include "infosystem/infosystemworker.h"
  23. #include "album.h"
  24. #include "artist.h"
  25. #include "result.h"
  26. #include "tomahawksettings.h"
  27. #include "globalactionmanager.h"
  28. #include "utils/logger.h"
  29. #include "utils/tomahawkutils.h"
  30. #include "mprisplugin.h"
  31. #include "mprispluginrootadaptor.h"
  32. #include "mprispluginplayeradaptor.h"
  33. using namespace Tomahawk::InfoSystem;
  34. static QString s_mpInfoIdentifier = QString( "MPRISPLUGIN" );
  35. MprisPlugin::MprisPlugin()
  36. : InfoPlugin()
  37. , m_coverTempFile( 0 )
  38. {
  39. qDebug() << Q_FUNC_INFO;
  40. // init
  41. m_playbackStatus = "Stopped";
  42. // Types of pushInfo we care about
  43. m_supportedPushTypes << InfoNowPlaying << InfoNowPaused << InfoNowResumed << InfoNowStopped;
  44. // DBus connection
  45. new MprisPluginRootAdaptor( this );
  46. new MprisPluginPlayerAdaptor( this );
  47. QDBusConnection dbus = QDBusConnection::sessionBus();
  48. dbus.registerObject("/org/mpris/MediaPlayer2", this);
  49. dbus.registerService("org.mpris.MediaPlayer2.tomahawk");
  50. // Listen to volume changes
  51. connect( AudioEngine::instance(), SIGNAL( volumeChanged( int ) ),
  52. SLOT( onVolumeChanged( int ) ) );
  53. // When the playlist changes, signals for several properties are sent
  54. connect( AudioEngine::instance(), SIGNAL( playlistChanged( Tomahawk::playlistinterface_ptr ) ),
  55. SLOT( onPlaylistChanged( Tomahawk::playlistinterface_ptr ) ) );
  56. // When a track is added or removed, CanGoNext updated signal is sent
  57. Tomahawk::playlistinterface_ptr playlist = AudioEngine::instance()->playlist();
  58. if( !playlist.isNull() )
  59. connect( playlist.data(), SIGNAL( trackCountChanged( unsigned int ) ),
  60. SLOT( onTrackCountChanged( unsigned int ) ) );
  61. // Connect to AudioEngine's seeked signal
  62. connect( AudioEngine::instance(), SIGNAL( seeked( qint64 ) ),
  63. SLOT( onSeeked( qint64 ) ) );
  64. // Connect to the InfoSystem (we need to get album covers via getInfo)
  65. connect( Tomahawk::InfoSystem::InfoSystem::instance(),
  66. SIGNAL( info( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ),
  67. SLOT( infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData, QVariant ) ) );
  68. connect( Tomahawk::InfoSystem::InfoSystem::instance(), SIGNAL( finished( QString ) ), SLOT( infoSystemFinished( QString ) ) );
  69. }
  70. MprisPlugin::~MprisPlugin()
  71. {
  72. qDebug() << Q_FUNC_INFO;
  73. delete m_coverTempFile;
  74. }
  75. // org.mpris.MediaPlayer2
  76. bool
  77. MprisPlugin::canQuit() const
  78. {
  79. qDebug() << Q_FUNC_INFO;
  80. return true;
  81. }
  82. bool
  83. MprisPlugin::canRaise() const
  84. {
  85. qDebug() << Q_FUNC_INFO;
  86. return false;
  87. }
  88. bool
  89. MprisPlugin::hasTrackList() const
  90. {
  91. qDebug() << Q_FUNC_INFO;
  92. return false;
  93. }
  94. QString
  95. MprisPlugin::identity() const
  96. {
  97. return QString("Tomahawk");
  98. }
  99. QString
  100. MprisPlugin::desktopEntry() const
  101. {
  102. return QString("tomahawk");
  103. }
  104. QStringList
  105. MprisPlugin::supportedUriSchemes() const
  106. {
  107. QStringList uriSchemes;
  108. uriSchemes << "tomahawk" << "spotify";
  109. return uriSchemes;
  110. }
  111. QStringList
  112. MprisPlugin::supportedMimeTypes() const
  113. {
  114. return QStringList();
  115. }
  116. void
  117. MprisPlugin::Raise()
  118. {
  119. }
  120. void
  121. MprisPlugin::Quit()
  122. {
  123. QApplication::quit();
  124. }
  125. // org.mpris.MediaPlayer2.Player
  126. bool
  127. MprisPlugin::canControl() const
  128. {
  129. return true;
  130. }
  131. bool
  132. MprisPlugin::canGoNext() const
  133. {
  134. return AudioEngine::instance()->canGoNext();
  135. }
  136. bool
  137. MprisPlugin::canGoPrevious() const
  138. {
  139. return AudioEngine::instance()->canGoPrevious();
  140. }
  141. bool
  142. MprisPlugin::canPause() const
  143. {
  144. return AudioEngine::instance()->currentTrack();
  145. }
  146. bool
  147. MprisPlugin::canPlay() const
  148. {
  149. // If there is a currently playing track, or if there is a playlist with at least 1 track, you can hit play
  150. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  151. return AudioEngine::instance()->currentTrack() || ( !p.isNull() && p->trackCount() );
  152. }
  153. bool
  154. MprisPlugin::canSeek() const
  155. {
  156. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  157. if ( p.isNull() )
  158. return false;
  159. return p->seekRestrictions() != PlaylistInterface::NoSeek;
  160. }
  161. QString
  162. MprisPlugin::loopStatus() const
  163. {
  164. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  165. if ( p.isNull() )
  166. return "None";
  167. PlaylistInterface::RepeatMode mode = p->repeatMode();
  168. switch( mode )
  169. {
  170. case PlaylistInterface::RepeatOne:
  171. return "Track";
  172. break;
  173. case PlaylistInterface::RepeatAll:
  174. return "Playlist";
  175. break;
  176. case PlaylistInterface::NoRepeat:
  177. return "None";
  178. break;
  179. default:
  180. return QString("None");
  181. break;
  182. }
  183. return QString("None");
  184. }
  185. void
  186. MprisPlugin::setLoopStatus( const QString &value )
  187. {
  188. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  189. if ( p.isNull() )
  190. return;
  191. if( value == "Track")
  192. p->setRepeatMode( PlaylistInterface::RepeatOne );
  193. else if( value == "Playlist" )
  194. p->setRepeatMode( PlaylistInterface::RepeatAll );
  195. else if( value == "None" )
  196. p->setRepeatMode( PlaylistInterface::NoRepeat );
  197. }
  198. double
  199. MprisPlugin::maximumRate() const
  200. {
  201. return 1.0;
  202. }
  203. QVariantMap
  204. MprisPlugin::metadata() const
  205. {
  206. QVariantMap metadataMap;
  207. Tomahawk::result_ptr track = AudioEngine::instance()->currentTrack();
  208. if( track )
  209. {
  210. metadataMap.insert( "mpris:trackid", QString( "/track/" ) + track->id().replace( "-", "" ) );
  211. metadataMap.insert( "mpris:length", track->duration() );
  212. metadataMap.insert( "xesam:album", track->album()->name() );
  213. metadataMap.insert( "xesam:artist", track->artist()->name() );
  214. metadataMap.insert( "xesam:title", track->track() );
  215. // Only return art if tempfile exists, and if its name contains the same "artist_album_tomahawk_cover.png"
  216. if( m_coverTempFile && m_coverTempFile->exists() &&
  217. m_coverTempFile->fileName().contains( track->artist()->name() + "_" + track->album()->name() + "_tomahawk_cover.png" ) )
  218. metadataMap.insert( "mpris:artUrl", QString( QUrl::fromLocalFile( QFileInfo( *m_coverTempFile ).absoluteFilePath() ).toEncoded() ) );
  219. else
  220. {
  221. // Need to fetch the album cover
  222. Tomahawk::InfoSystem::InfoStringHash trackInfo;
  223. trackInfo["artist"] = track->artist()->name();
  224. trackInfo["album"] = track->album()->name();
  225. Tomahawk::InfoSystem::InfoRequestData requestData;
  226. requestData.caller = s_mpInfoIdentifier;
  227. requestData.type = Tomahawk::InfoSystem::InfoAlbumCoverArt;
  228. requestData.input = QVariant::fromValue< Tomahawk::InfoSystem::InfoStringHash >( trackInfo );
  229. requestData.customData = QVariantMap();
  230. Tomahawk::InfoSystem::InfoSystem::instance()->getInfo( requestData );
  231. }
  232. }
  233. return metadataMap;
  234. }
  235. double
  236. MprisPlugin::minimumRate() const
  237. {
  238. return 1.0;
  239. }
  240. QString
  241. MprisPlugin::playbackStatus() const
  242. {
  243. return m_playbackStatus;
  244. }
  245. qlonglong
  246. MprisPlugin::position() const
  247. {
  248. // Convert Tomahawk's milliseconds to microseconds
  249. return (qlonglong) ( AudioEngine::instance()->currentTime() * 1000 );
  250. }
  251. double
  252. MprisPlugin::rate() const
  253. {
  254. return 1.0;
  255. }
  256. void
  257. MprisPlugin::setRate( double value )
  258. {
  259. Q_UNUSED( value );
  260. }
  261. bool
  262. MprisPlugin::shuffle() const
  263. {
  264. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  265. if ( p.isNull() )
  266. return false;
  267. return p->shuffled();
  268. }
  269. void
  270. MprisPlugin::setShuffle( bool value )
  271. {
  272. Tomahawk::playlistinterface_ptr p = AudioEngine::instance()->playlist();
  273. if ( p.isNull() )
  274. return;
  275. return p->setShuffled( value );
  276. }
  277. double
  278. MprisPlugin::volume() const
  279. {
  280. return AudioEngine::instance()->volume();
  281. }
  282. void
  283. MprisPlugin::setVolume( double value )
  284. {
  285. AudioEngine::instance()->setVolume( value );
  286. }
  287. void
  288. MprisPlugin::Next()
  289. {
  290. AudioEngine::instance()->next();
  291. }
  292. void
  293. MprisPlugin::OpenUri( const QString &Uri )
  294. {
  295. if( Uri.contains( "tomahawk://" ) )
  296. GlobalActionManager::instance()->parseTomahawkLink( Uri );
  297. else if( Uri.contains( "spotify:" ) )
  298. GlobalActionManager::instance()->openSpotifyLink( Uri );
  299. }
  300. void
  301. MprisPlugin::Pause()
  302. {
  303. AudioEngine::instance()->pause();
  304. }
  305. void
  306. MprisPlugin::Play()
  307. {
  308. AudioEngine::instance()->play();
  309. }
  310. void
  311. MprisPlugin::PlayPause()
  312. {
  313. AudioEngine::instance()->playPause();
  314. }
  315. void
  316. MprisPlugin::Previous()
  317. {
  318. AudioEngine::instance()->previous();
  319. }
  320. void
  321. MprisPlugin::Seek( qlonglong Offset )
  322. {
  323. qDebug() << Q_FUNC_INFO;
  324. if( !canSeek() )
  325. return;
  326. qlonglong seekTime = position() + Offset;
  327. qDebug() << "seekTime: " << seekTime;
  328. if( seekTime < 0 )
  329. AudioEngine::instance()->seek( 0 );
  330. else if( seekTime > AudioEngine::instance()->currentTrackTotalTime()*1000 )
  331. Next();
  332. // seekTime is in microseconds, but we work internally in milliseconds
  333. else
  334. AudioEngine::instance()->seek( (qint64) ( seekTime / 1000 ) );
  335. }
  336. void
  337. MprisPlugin::SetPosition( const QDBusObjectPath &TrackId, qlonglong Position )
  338. {
  339. qDebug() << Q_FUNC_INFO;
  340. if( !canSeek() )
  341. return;
  342. qDebug() << "path: " << TrackId.path();
  343. qDebug() << "position: " << Position;
  344. if( TrackId.path() != QString("/track/") + AudioEngine::instance()->currentTrack()->id().replace( "-", "" ) )
  345. return;
  346. if( ( Position < 0) || ( Position > AudioEngine::instance()->currentTrackTotalTime()*1000 ) )
  347. return;
  348. qDebug() << "seeking to: " << Position/1000 << "ms";
  349. AudioEngine::instance()->seek( (qint64) (Position / 1000 ) );
  350. }
  351. void
  352. MprisPlugin::Stop()
  353. {
  354. AudioEngine::instance()->stop();
  355. }
  356. // InfoPlugin Methods
  357. void
  358. MprisPlugin::getInfo( Tomahawk::InfoSystem::InfoRequestData requestData )
  359. {
  360. Q_UNUSED( requestData );
  361. qDebug() << Q_FUNC_INFO;
  362. return;
  363. }
  364. void
  365. MprisPlugin::pushInfo( QString caller, Tomahawk::InfoSystem::InfoType type, QVariant input )
  366. {
  367. Q_UNUSED( caller );
  368. qDebug() << Q_FUNC_INFO;
  369. bool isPlayingInfo = false;
  370. switch ( type )
  371. {
  372. case InfoNowPlaying:
  373. isPlayingInfo = true;
  374. audioStarted( input );
  375. break;
  376. case InfoNowPaused:
  377. isPlayingInfo = true;
  378. audioPaused();
  379. break;
  380. case InfoNowResumed:
  381. isPlayingInfo = true;
  382. audioResumed( input );
  383. break;
  384. case InfoNowStopped:
  385. isPlayingInfo = true;
  386. audioStopped();
  387. break;
  388. default:
  389. break;
  390. }
  391. if( isPlayingInfo )
  392. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "PlaybackStatus");
  393. }
  394. void
  395. MprisPlugin::stateChanged( AudioState newState, AudioState oldState )
  396. {
  397. Q_UNUSED( newState );
  398. Q_UNUSED( oldState );
  399. }
  400. /** Audio state slots */
  401. void
  402. MprisPlugin::audioStarted( const QVariant &input )
  403. {
  404. qDebug() << Q_FUNC_INFO;
  405. if ( !input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  406. return;
  407. InfoStringHash hash = input.value< Tomahawk::InfoSystem::InfoStringHash >();
  408. if ( !hash.contains( "title" ) || !hash.contains( "artist" ) || !hash.contains( "album" ) )
  409. return;
  410. m_playbackStatus = "Playing";
  411. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata");
  412. //hash["artist"];
  413. //hash["title"];
  414. //QString nowPlaying = "";
  415. //qDebug() << "nowPlaying: " << nowPlaying;
  416. }
  417. void
  418. MprisPlugin::audioFinished( const QVariant &input )
  419. {
  420. Q_UNUSED( input );
  421. //qDebug() << Q_FUNC_INFO;
  422. }
  423. void
  424. MprisPlugin::audioStopped()
  425. {
  426. qDebug() << Q_FUNC_INFO;
  427. m_playbackStatus = "Stopped";
  428. }
  429. void
  430. MprisPlugin::audioPaused()
  431. {
  432. qDebug() << Q_FUNC_INFO;
  433. m_playbackStatus = "Paused";
  434. }
  435. void
  436. MprisPlugin::audioResumed( const QVariant &input )
  437. {
  438. qDebug() << Q_FUNC_INFO;
  439. audioStarted( input );
  440. }
  441. void
  442. MprisPlugin::onVolumeChanged( int volume )
  443. {
  444. Q_UNUSED( volume );
  445. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Volume");
  446. }
  447. void
  448. MprisPlugin::onPlaylistChanged( Tomahawk::playlistinterface_ptr playlist )
  449. {
  450. qDebug() << Q_FUNC_INFO;
  451. disconnect( this, SLOT( onTrackCountChanged( unsigned int ) ) );
  452. qDebug() << "disconnected";
  453. if( !playlist.isNull() )
  454. qDebug() << "playlist not null";
  455. if( !playlist.isNull() )
  456. connect( playlist.data(), SIGNAL( trackCountChanged( unsigned int ) ),
  457. SLOT( onTrackCountChanged( unsigned int ) ) );
  458. qDebug() << "connected new playlist";
  459. // Notify relevant changes
  460. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "LoopStatus" );
  461. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Shuffle" );
  462. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "CanSeek" );
  463. onTrackCountChanged( 0 );
  464. }
  465. void
  466. MprisPlugin::onTrackCountChanged( unsigned int tracks )
  467. {
  468. Q_UNUSED( tracks );
  469. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "CanGoNext" );
  470. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "CanGoPrevious" );
  471. }
  472. void
  473. MprisPlugin::onSeeked( qint64 ms )
  474. {
  475. qlonglong us = (qlonglong) ( ms*1000 );
  476. emit Seeked( us );
  477. }
  478. void
  479. MprisPlugin::infoSystemInfo( Tomahawk::InfoSystem::InfoRequestData requestData, QVariant output )
  480. {
  481. // If the caller for the request was not us, or not the type of info we are seeking, ignore it
  482. if ( requestData.caller != s_mpInfoIdentifier || requestData.type != Tomahawk::InfoSystem::InfoAlbumCoverArt )
  483. {
  484. //notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" );
  485. return;
  486. }
  487. if ( !output.canConvert< QVariantMap >() )
  488. {
  489. //notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" );
  490. tDebug( LOGINFO ) << "Cannot convert fetched art from a QByteArray";
  491. return;
  492. }
  493. // Pull image data into byte array
  494. QVariantMap returnedData = output.value< QVariantMap >();
  495. const QByteArray ba = returnedData["imgbytes"].toByteArray();
  496. if ( ba.length() )
  497. {
  498. // Load from byte array to image
  499. QImage image;
  500. image.loadFromData( ba );
  501. // Pull out request data for album+artist
  502. if( !requestData.input.canConvert< Tomahawk::InfoSystem::InfoStringHash >() )
  503. {
  504. qDebug() << "Cannot convert metadata input to album cover retrieval";
  505. return;
  506. }
  507. Tomahawk::InfoSystem::InfoStringHash hash = requestData.input.value< Tomahawk::InfoSystem::InfoStringHash>();
  508. // delete the old tempfile and make new one, to avoid caching of filename by mpris clients
  509. if( m_coverTempFile )
  510. delete m_coverTempFile;
  511. m_coverTempFile = new QTemporaryFile( QDir::toNativeSeparators(
  512. QDir::tempPath() + "/" + hash["artist"] + "_" + hash["album"] + "_tomahawk_cover.png" ) );
  513. if( !m_coverTempFile->open() )
  514. {
  515. qDebug() << "WARNING: could not write temporary file for cover art!";
  516. }
  517. // Finally, save the image to the new temp file
  518. //if( image.save( QFileInfo( *m_coverTempFile ).absoluteFilePath(), "PNG" ) )
  519. if( image.save( m_coverTempFile, "PNG") )
  520. {
  521. qDebug() << Q_FUNC_INFO << "Image saving successful, notifying";
  522. qDebug() << "Saving to: " << QFileInfo( *m_coverTempFile ).absoluteFilePath();
  523. m_coverTempFile->close();
  524. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" );
  525. }
  526. else
  527. {
  528. qDebug() << Q_FUNC_INFO << " failed to save image!";
  529. m_coverTempFile->close();
  530. }
  531. /*
  532. if( m_coverTempFile->open() )
  533. {
  534. QTextStream out( m_coverTempFile );
  535. out << ba;
  536. m_coverTempFile->close();
  537. notifyPropertyChanged( "org.mpris.MediaPlayer2.Player", "Metadata" );
  538. }
  539. */
  540. }
  541. }
  542. void
  543. MprisPlugin::infoSystemFinished( QString target )
  544. {
  545. Q_UNUSED( target );
  546. }
  547. void
  548. MprisPlugin::notifyPropertyChanged( const QString& interface,
  549. const QString& propertyName )
  550. {
  551. QDBusMessage signal = QDBusMessage::createSignal(
  552. "/org/mpris/MediaPlayer2",
  553. "org.freedesktop.DBus.Properties",
  554. "PropertiesChanged");
  555. signal << interface;
  556. QVariantMap changedProps;
  557. changedProps.insert(propertyName, property(propertyName.toAscii()));
  558. signal << changedProps;
  559. signal << QStringList();
  560. QDBusConnection::sessionBus().send(signal);
  561. }