1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-06 22:26:32 +02:00

support playing spotify track urls (http://open.spotify.com or spotify:track). No drag-n-drop yet.

This commit is contained in:
Leo Franchi
2011-08-03 18:06:18 -04:00
parent 92efd0da7f
commit 5fd8e2181d
3 changed files with 129 additions and 27 deletions

View File

@@ -37,6 +37,10 @@
#include "utils/xspfloader.h" #include "utils/xspfloader.h"
#include "utils/xspfgenerator.h" #include "utils/xspfgenerator.h"
#include "utils/logger.h" #include "utils/logger.h"
#include "utils/tomahawkutils.h"
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
GlobalActionManager* GlobalActionManager::s_instance = 0; GlobalActionManager* GlobalActionManager::s_instance = 0;
@@ -100,7 +104,7 @@ GlobalActionManager::copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& p
QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == Tomahawk::OnDemand ? "station" : "autoplaylist" ) ); QUrl link( QString( "%1/%2/create/" ).arg( hostname() ).arg( playlist->mode() == Tomahawk::OnDemand ? "station" : "autoplaylist" ) );
if( playlist->generator()->type() != "echonest" ) { if( playlist->generator()->type() != "echonest" ) {
qDebug() << "Only echonest generators are supported"; tLog() << "Only echonest generators are supported";
return QString(); return QString();
} }
@@ -177,7 +181,7 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
if( url.contains( "tomahawk://" ) ) { if( url.contains( "tomahawk://" ) ) {
QString cmd = url.mid( 11 ); QString cmd = url.mid( 11 );
cmd.replace( "%2B", "%20" ); cmd.replace( "%2B", "%20" );
qDebug() << "Parsing tomahawk link command" << cmd; tLog() << "Parsing tomahawk link command" << cmd;
QString cmdType = cmd.split( "/" ).first(); QString cmdType = cmd.split( "/" ).first();
QUrl u = QUrl::fromEncoded( cmd.toUtf8() ); QUrl u = QUrl::fromEncoded( cmd.toUtf8() );
@@ -187,7 +191,7 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
if( u.hasQueryItem( "xspf" ) ) { if( u.hasQueryItem( "xspf" ) ) {
QUrl xspf = QUrl::fromUserInput( u.queryItemValue( "xspf" ) ); QUrl xspf = QUrl::fromUserInput( u.queryItemValue( "xspf" ) );
XSPFLoader* l = new XSPFLoader( true, this ); XSPFLoader* l = new XSPFLoader( true, this );
qDebug() << "Loading spiff:" << xspf.toString(); tDebug() << "Loading spiff:" << xspf.toString();
l->load( xspf ); l->load( xspf );
connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) ); connect( l, SIGNAL( ok( Tomahawk::playlist_ptr ) ), ViewManager::instance(), SLOT( show( Tomahawk::playlist_ptr ) ) );
@@ -214,11 +218,11 @@ GlobalActionManager::parseTomahawkLink( const QString& url )
} else if( cmdType == "open" ) { } else if( cmdType == "open" ) {
return handleOpenCommand( u ); return handleOpenCommand( u );
} else { } else {
qDebug() << "Tomahawk link not supported, command not known!" << cmdType << u.path(); tLog() << "Tomahawk link not supported, command not known!" << cmdType << u.path();
return false; return false;
} }
} else { } else {
qDebug() << "Not a tomahawk:// link!"; tLog() << "Not a tomahawk:// link!";
return false; return false;
} }
} }
@@ -228,13 +232,13 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url )
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific playlist command:" << url.toString(); tLog() << "No specific playlist command:" << url.toString();
return false; return false;
} }
if( parts[ 0 ] == "import" ) { if( parts[ 0 ] == "import" ) {
if( !url.hasQueryItem( "xspf" ) ) { if( !url.hasQueryItem( "xspf" ) ) {
qDebug() << "No xspf to load..."; tDebug() << "No xspf to load...";
return false; return false;
} }
QUrl xspf = QUrl( url.queryItemValue( "xspf" ) ); QUrl xspf = QUrl( url.queryItemValue( "xspf" ) );
@@ -246,14 +250,14 @@ GlobalActionManager::handlePlaylistCommand( const QUrl& url )
} else if( parts [ 0 ] == "new" ) { } else if( parts [ 0 ] == "new" ) {
if( !url.hasQueryItem( "title" ) ) { if( !url.hasQueryItem( "title" ) ) {
qDebug() << "New playlist command needs a title..."; tLog() << "New playlist command needs a title...";
return false; return false;
} }
Tomahawk::playlist_ptr pl = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), url.queryItemValue( "title" ), QString(), QString(), false ); Tomahawk::playlist_ptr pl = Tomahawk::Playlist::create( SourceList::instance()->getLocal(), uuid(), url.queryItemValue( "title" ), QString(), QString(), false );
ViewManager::instance()->show( pl ); ViewManager::instance()->show( pl );
} else if( parts[ 0 ] == "add" ) { } else if( parts[ 0 ] == "add" ) {
if( !url.hasQueryItem( "playlistid" ) || !url.hasQueryItem( "title" ) || !url.hasQueryItem( "artist" ) ) { if( !url.hasQueryItem( "playlistid" ) || !url.hasQueryItem( "title" ) || !url.hasQueryItem( "artist" ) ) {
qDebug() << "Add to playlist command needs playlistid, track, and artist..." << url.toString(); tLog() << "Add to playlist command needs playlistid, track, and artist..." << url.toString();
return false; return false;
} }
// TODO implement. Let the user select what playlist to add to // TODO implement. Let the user select what playlist to add to
@@ -268,7 +272,7 @@ GlobalActionManager::handleCollectionCommand( const QUrl& url )
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific collection command:" << url.toString(); tLog() << "No specific collection command:" << url.toString();
return false; return false;
} }
@@ -284,27 +288,39 @@ GlobalActionManager::handleOpenCommand(const QUrl& url)
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); QStringList parts = url.path().split( "/" ).mid( 1 );
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific type to open:" << url.toString(); tLog() << "No specific type to open:" << url.toString();
return false; return false;
} }
// TODO user configurable in the UI // TODO user configurable in the UI
return doQueueAdd( parts, url.queryItems() ); return doQueueAdd( parts, url.queryItems() );
} }
void
GlobalActionManager::handleOpenTrack ( const Tomahawk::query_ptr& q )
{
ViewManager::instance()->queue()->model()->append( q );
ViewManager::instance()->showQueue();
if( !AudioEngine::instance()->isPlaying() ) {
connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
m_waitingToPlay = q;
}
}
bool bool
GlobalActionManager::handleQueueCommand( const QUrl& url ) GlobalActionManager::handleQueueCommand( const QUrl& url )
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific queue command:" << url.toString(); tLog() << "No specific queue command:" << url.toString();
return false; return false;
} }
if( parts[ 0 ] == "add" ) { if( parts[ 0 ] == "add" ) {
doQueueAdd( parts.mid( 1 ), url.queryItems() ); doQueueAdd( parts.mid( 1 ), url.queryItems() );
} else { } else {
qDebug() << "Only queue/add/track is support at the moment, got:" << parts; tLog() << "Only queue/add/track is support at the moment, got:" << parts;
return false; return false;
} }
@@ -330,18 +346,12 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
} }
if( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) { // an individual; query to add to queue if( !title.isEmpty() || !artist.isEmpty() || !album.isEmpty() ) { // an individual; query to add to queue
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album ); Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), false );
if( !urlStr.isEmpty() ) if( !urlStr.isEmpty() )
q->setResultHint( urlStr ); q->setResultHint( urlStr );
Tomahawk::Pipeline::instance()->resolve( q, true ); Tomahawk::Pipeline::instance()->resolve( q, true );
ViewManager::instance()->queue()->model()->append( q ); handleOpenTrack( q );
ViewManager::instance()->showQueue();
if( !AudioEngine::instance()->isPlaying() ) {
connect( q.data(), SIGNAL( resolvingFinished( bool ) ), this, SLOT( waitingForResolved( bool ) ) );
m_waitingToPlay = q;
}
return true; return true;
} else { // a list of urls to add to the queue } else { // a list of urls to add to the queue
@@ -354,8 +364,9 @@ GlobalActionManager::doQueueAdd( const QStringList& parts, const QList< QPair< Q
// TODO // TODO
} else { // give it a web result hint } else { // give it a web result hint
QFileInfo info( track.path() ); QFileInfo info( track.path() );
Tomahawk::query_ptr q = Tomahawk::Query::get( QString(), info.baseName(), QString() ); Tomahawk::query_ptr q = Tomahawk::Query::get( QString(), info.baseName(), QString(), uuid(), false );
q->setResultHint( track.toString() ); q->setResultHint( track.toString() );
Tomahawk::Pipeline::instance()->resolve( q, true ); Tomahawk::Pipeline::instance()->resolve( q, true );
ViewManager::instance()->queue()->model()->append( q ); ViewManager::instance()->queue()->model()->append( q );
@@ -400,13 +411,13 @@ GlobalActionManager::loadDynamicPlaylist( const QUrl& url, bool station )
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific station command:" << url.toString(); tLog() << "No specific station command:" << url.toString();
return Tomahawk::dynplaylist_ptr(); return Tomahawk::dynplaylist_ptr();
} }
if( parts[ 0 ] == "create" ) { if( parts[ 0 ] == "create" ) {
if( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) { if( !url.hasQueryItem( "title" ) || !url.hasQueryItem( "type" ) ) {
qDebug() << "Station create command needs title and type..." << url.toString(); tLog() << "Station create command needs title and type..." << url.toString();
return Tomahawk::dynplaylist_ptr(); return Tomahawk::dynplaylist_ptr();
} }
QString title = url.queryItemValue( "title" ); QString title = url.queryItemValue( "title" );
@@ -550,7 +561,7 @@ GlobalActionManager::handlePlayCommand( const QUrl& url )
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific play command:" << url.toString(); tLog() << "No specific play command:" << url.toString();
return false; return false;
} }
@@ -585,7 +596,7 @@ bool GlobalActionManager::handleBookmarkCommand(const QUrl& url)
{ {
QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command QStringList parts = url.path().split( "/" ).mid( 1 ); // get the rest of the command
if( parts.isEmpty() ) { if( parts.isEmpty() ) {
qDebug() << "No specific bookmark command:" << url.toString(); tLog() << "No specific bookmark command:" << url.toString();
return false; return false;
} }
@@ -676,7 +687,7 @@ GlobalActionManager::waitingForResolved( bool success )
return; return;
} }
if( success && !m_waitingToPlay.isNull() && !m_waitingToPlay->results().isEmpty() ) { // play it! if( !m_waitingToPlay.isNull() && m_waitingToPlay->playable() ) { // play it!
// AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() ); // AudioEngine::instance()->playItem( AudioEngine::instance()->playlist(), m_waitingToPlay->results().first() );
AudioEngine::instance()->play(); AudioEngine::instance()->play();
@@ -689,3 +700,83 @@ GlobalActionManager::hostname() const
{ {
return QString( "http://toma.hk" ); return QString( "http://toma.hk" );
} }
/// SPOTIFY URL HANDLING
bool
GlobalActionManager::parseSpotifyLink( const QString& link )
{
if( !link.contains( "track" ) ) // we only support track links atm
return false;
// we need Spotify URIs such as spotify:track:XXXXXX, so if we by chance get a http://open.spotify.com url, convert it
QString uri = link;
if( link.contains( "open.spotify.com" ) )
{
QString hash = link;
hash.replace( "http://open.spotify.com/track/", "" );
uri = QString( "spotify:track:%1" ).arg( hash );
}
tLog() << "Parsing Spotify Track URI:" << uri;
QUrl url = QUrl( QString( "http://ws.spotify.com/lookup/1/.json?uri=%1" ).arg( uri ) );
tDebug() << "Looking up..." << url.toString();
QNetworkReply* reply = TomahawkUtils::nam()->get( QNetworkRequest( url ) );
connect( reply, SIGNAL( finished() ), this, SLOT( spotifyTrackLookupFinished() ) );
return true; // all we know now
}
void
GlobalActionManager::spotifyTrackLookupFinished()
{
QNetworkReply* r = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( r );
if( r->error() == QNetworkReply::NoError )
{
QJson::Parser p;
bool ok;
QVariantMap res = p.parse( r, &ok ).toMap();
if( !ok )
{
tLog() << "Failed to parse json from Spotify track lookup:" << p.errorString() << "On line" << p.errorLine();
return;
} else if( !res.contains( "track" ) )
{
tLog() << "No 'track' item in the spotify track lookup result... not doing anything";
return;
}
// lets parse this baby
QVariantMap t = res.value( "track" ).toMap();
QString title, artist, album;
title = t.value( "name", QString() ).toString();
// TODO for now only take the first artist
if( t.contains( "artists" ) && t[ "artists" ].canConvert< QVariantList >() && t[ "artists" ].toList().size() > 0 )
artist = t[ "artists" ].toList().first().toMap().value( "name", QString() ).toString();
if( t.contains( "album" ) && t[ "album" ].canConvert< QVariantMap >() )
album = t[ "album" ].toMap().value( "name", QString() ).toString();
if( title.isEmpty() && artist.isEmpty() ) // don't have enough...
{
tLog() << "Didn't get an artist and track name from spotify, not enough to build a query on. Aborting" << title << artist << album;
return;
}
Tomahawk::query_ptr q = Tomahawk::Query::get( artist, title, album, uuid(), true );
handleOpenTrack( q );
} else
{
tLog() << "Error in network request to Spotify for track decoding:" << r->errorString();
}
}

View File

@@ -38,6 +38,9 @@ public:
QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const; QUrl openLinkFromQuery( const Tomahawk::query_ptr& query ) const;
QUrl openLink( const QString& title, const QString& artist, const QString& album ) const; QUrl openLink( const QString& title, const QString& artist, const QString& album ) const;
// spotify
bool parseSpotifyLink( const QString& link );
void copyToClipboard( const Tomahawk::query_ptr& query ) const; void copyToClipboard( const Tomahawk::query_ptr& query ) const;
QString copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist ); QString copyPlaylistToClipboard( const Tomahawk::dynplaylist_ptr& playlist );
void savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename ); void savePlaylistToFile( const Tomahawk::playlist_ptr& playlist, const QString& filename );
@@ -47,12 +50,16 @@ public slots:
void waitingForResolved( bool ); void waitingForResolved( bool );
Tomahawk::dynplaylist_ptr loadDynamicPlaylist( const QUrl& url, bool station ); Tomahawk::dynplaylist_ptr loadDynamicPlaylist( const QUrl& url, bool station );
private slots: private slots:
void bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl ); void bookmarkPlaylistCreated( const Tomahawk::playlist_ptr& pl );
void showPlaylist(); void showPlaylist();
void xspfCreated( const QByteArray& xspf ); void xspfCreated( const QByteArray& xspf );
// SPOTIFY
void spotifyTrackLookupFinished();
private: private:
explicit GlobalActionManager( QObject* parent = 0 ); explicit GlobalActionManager( QObject* parent = 0 );
void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q ); void doBookmark( const Tomahawk::playlist_ptr& pl, const Tomahawk::query_ptr& q );
@@ -67,6 +74,8 @@ private:
bool handleBookmarkCommand(const QUrl& url ); bool handleBookmarkCommand(const QUrl& url );
bool handleOpenCommand(const QUrl& url ); bool handleOpenCommand(const QUrl& url );
void handleOpenTrack( const Tomahawk::query_ptr& qry );
bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems ); bool doQueueAdd( const QStringList& parts, const QList< QPair< QString, QString > >& queryItems );
QString hostname() const; QString hostname() const;

View File

@@ -514,6 +514,8 @@ TomahawkApp::loadUrl( const QString& url )
activate(); activate();
if ( url.startsWith( "tomahawk://" ) ) if ( url.startsWith( "tomahawk://" ) )
return GlobalActionManager::instance()->parseTomahawkLink( url ); return GlobalActionManager::instance()->parseTomahawkLink( url );
else if ( url.contains( "open.spotify.com" ) || url.contains( "spotify:track" ) )
return GlobalActionManager::instance()->parseSpotifyLink( url );
else else
{ {
QFile f( url ); QFile f( url );