PageRenderTime 87ms CodeModel.GetById 13ms app.highlight 69ms RepoModel.GetById 1ms app.codeStats 0ms

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

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