Update issue 2540

Add basic SoundCloud support.
However, only search in internet tab + listen is done for now.
We should at least add soundcloud in global search.
This commit is contained in:
Arnaud Bienner 2012-08-04 15:21:44 +02:00
parent c8b145c8b6
commit e69c932577
6 changed files with 344 additions and 1 deletions

View File

@ -289,6 +289,7 @@
<file>providers/skyfm.png</file>
<file>providers/somafm.png</file>
<file>providers/songkick.png</file>
<file>providers/soundcloud.png</file>
<file>providers/twitter.png</file>
<file>providers/wikipedia.png</file>
<file>sample.mood</file>

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@ -185,6 +185,7 @@ set(SOURCES
internet/searchboxwidget.cpp
internet/somafmservice.cpp
internet/somafmurlhandler.cpp
internet/soundcloudservice.cpp
library/groupbydialog.cpp
library/library.cpp
@ -452,6 +453,7 @@ set(HEADERS
internet/searchboxwidget.h
internet/somafmservice.h
internet/somafmurlhandler.h
internet/soundcloudservice.h
library/groupbydialog.h
library/library.h

View File

@ -16,6 +16,7 @@
*/
#include "digitallyimportedservicebase.h"
#include "groovesharkservice.h"
#include "icecastservice.h"
#include "jamendoservice.h"
#include "magnatuneservice.h"
@ -24,7 +25,7 @@
#include "internetservice.h"
#include "savedradio.h"
#include "somafmservice.h"
#include "groovesharkservice.h"
#include "soundcloudservice.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "podcasts/podcastservice.h"
@ -70,6 +71,7 @@ InternetModel::InternetModel(Application* app, QObject* parent)
AddService(new SavedRadio(app, this));
AddService(new SkyFmService(app, this));
AddService(new SomaFMService(app, this));
AddService(new SoundCloudService(app, this));
#ifdef HAVE_SPOTIFY
AddService(new SpotifyService(app, this));
#endif

View File

@ -0,0 +1,248 @@
/* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "soundcloudservice.h"
#include <QDesktopServices>
#include <QMenu>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <qjson/parser.h>
#include <qjson/serializer.h>
#include "internetmodel.h"
#include "searchboxwidget.h"
#include "core/application.h"
#include "core/closure.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "core/network.h"
#include "core/song.h"
#include "core/taskmanager.h"
#include "core/timeconstants.h"
#include "core/utilities.h"
#include "ui/iconloader.h"
const char* SoundCloudService::kApiClientId = "2add0f709fcfae1fd7a198ec7573d2d4";
const char* SoundCloudService::kServiceName = "SoundCloud";
const char* SoundCloudService::kSettingsGroup = "SoundCloud";
const char* SoundCloudService::kUrl = "https://api.soundcloud.com/";
const char* SoundCloudService::kHomepage = "http://soundcloud.com/";
const int SoundCloudService::kSearchDelayMsec = 400;
const int SoundCloudService::kSongSearchLimit = 100;
const int SoundCloudService::kSongSimpleSearchLimit = 10;
typedef QPair<QString, QString> Param;
SoundCloudService::SoundCloudService(Application* app, InternetModel *parent)
: InternetService(kServiceName, app, parent, parent),
root_(NULL),
search_(NULL),
network_(new NetworkAccessManager(this)),
context_menu_(NULL),
search_box_(new SearchBoxWidget(this)),
search_delay_(new QTimer(this)),
next_pending_search_id_(0) {
search_delay_->setInterval(kSearchDelayMsec);
search_delay_->setSingleShot(true);
connect(search_delay_, SIGNAL(timeout()), SLOT(DoSearch()));
connect(search_box_, SIGNAL(TextChanged(QString)), SLOT(Search(QString)));
}
SoundCloudService::~SoundCloudService() {
}
QStandardItem* SoundCloudService::CreateRootItem() {
root_ = new QStandardItem(QIcon(":providers/soundcloud.png"), kServiceName);
root_->setData(true, InternetModel::Role_CanLazyLoad);
root_->setData(InternetModel::PlayBehaviour_DoubleClickAction,
InternetModel::Role_PlayBehaviour);
return root_;
}
void SoundCloudService::LazyPopulate(QStandardItem* item) {
switch (item->data(InternetModel::Role_Type).toInt()) {
case InternetModel::Type_Service: {
EnsureItemsCreated();
break;
}
default:
break;
}
}
void SoundCloudService::EnsureItemsCreated() {
search_ = new QStandardItem(IconLoader::Load("edit-find"),
tr("Search results"));
search_->setToolTip(tr("Start typing something on the search box above to "
"fill this search results list"));
search_->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
root_->appendRow(search_);
}
QWidget* SoundCloudService::HeaderWidget() const {
return search_box_;
}
void SoundCloudService::Homepage() {
QDesktopServices::openUrl(QUrl(kHomepage));
}
void SoundCloudService::Search(const QString& text, bool now) {
pending_search_ = text;
// If there is no text (e.g. user cleared search box), we don't need to do a
// real query that will return nothing: we can clear the playlist now
if (text.isEmpty()) {
search_delay_->stop();
ClearSearchResults();
return;
}
if (now) {
search_delay_->stop();
DoSearch();
} else {
search_delay_->start();
}
}
void SoundCloudService::DoSearch() {
ClearSearchResults();
qLog(Debug) << "Search";
QList<Param> parameters;
parameters << Param("q", pending_search_);
QNetworkReply* reply = CreateRequest("tracks", parameters);
int id = next_pending_search_id_++;
NewClosure(reply, SIGNAL(finished()),
this, SLOT(SearchFinished(QNetworkReply*,int)),
reply, id);
}
void SoundCloudService::SearchFinished(QNetworkReply* reply, int task_id) {
qLog(Debug) << task_id;
SongList songs = ExtractSongs(ExtractResult(reply));
// Fill results list
foreach (const Song& song, songs) {
QStandardItem* child = CreateSongItem(song);
search_->appendRow(child);
}
QModelIndex index = model()->merged_model()->mapFromSource(search_->index());
ScrollToIndex(index);
}
void SoundCloudService::ClearSearchResults() {
if (search_)
search_->removeRows(0, search_->rowCount());
}
QNetworkReply* SoundCloudService::CreateRequest(
const QString& ressource_name,
const QList<Param>& params) {
QUrl url(kUrl);
url.setPath(ressource_name);
url.addQueryItem("client_id", kApiClientId);
foreach(const Param& param, params) {
url.addQueryItem(param.first, param.second);
}
qLog(Debug) << "Request Url: " << url.toEncoded();
QNetworkRequest req(url);
req.setRawHeader("Accept", "application/json");
QNetworkReply *reply = network_->get(req);
return reply;
}
QVariant SoundCloudService::ExtractResult(QNetworkReply* reply) {
QJson::Parser parser;
bool ok;
QVariant result = parser.parse(reply, &ok);
if (!ok) {
qLog(Error) << "Error while parsing SoundCloud result";
}
return result;
}
SongList SoundCloudService::ExtractSongs(const QVariant& result) {
SongList songs;
QVariantList q_variant_list = result.toList();
foreach(const QVariant& q, q_variant_list) {
Song song = ExtractSong(q.toMap());
if (song.is_valid()) {
songs << song;
}
}
return songs;
}
Song SoundCloudService::ExtractSong(const QVariantMap& result_song) {
Song song;
if (!result_song.isEmpty() && result_song["streamable"].toBool()) {
QUrl stream_url = result_song["stream_url"].toUrl();
stream_url.addQueryItem("client_id", kApiClientId);
song.set_url(stream_url);
QString title = result_song["title"].toString();
song.set_title(title);
QString genre = result_song["genre"].toString();
song.set_genre(genre);
float bpm = result_song["bpm"].toFloat();
song.set_bpm(bpm);
QVariant cover = result_song["artwork_url"];
if (cover.isValid()) {
// SoundCloud covers URL are https, but our cover loader doesn't seem to
// deal well with https URL. Anyway, we don't need a secure connection to
// get a cover image.
QUrl cover_url = cover.toUrl();
cover_url.setScheme("http");
song.set_art_automatic(cover_url.toEncoded());
}
int playcount = result_song["playback_count"].toInt();
song.set_playcount(playcount);
int year = result_song["release_year"].toInt();
song.set_year(year);
QVariant q_duration = result_song["duration"];
quint64 duration = q_duration.toULongLong() * kNsecPerMsec;
song.set_length_nanosec(duration);
song.set_valid(true);
}
return song;
}

View File

@ -0,0 +1,90 @@
/* This file is part of Clementine.
Copyright 2012, David Sansome <me@davidsansome.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOUNDCLOUDSERVICE_H
#define SOUNDCLOUDSERVICE_H
#include "internetmodel.h"
#include "internetservice.h"
class NetworkAccessManager;
class SearchBoxWidget;
class QMenu;
class QNetworkReply;
class SoundCloudService : public InternetService {
Q_OBJECT
public:
SoundCloudService(Application* app, InternetModel *parent);
~SoundCloudService();
// Internet Service methods
QStandardItem* CreateRootItem();
void LazyPopulate(QStandardItem *parent);
// TODO
//QList<QAction*> playlistitem_actions(const Song& song);
//void ShowContextMenu(const QPoint& global_pos);
QWidget* HeaderWidget() const;
static const char* kServiceName;
static const char* kSettingsGroup;
private slots:
void Search(const QString& text, bool now = false);
void DoSearch();
void SearchFinished(QNetworkReply* reply, int task);
void Homepage();
private:
void ClearSearchResults();
void EnsureItemsCreated();
QNetworkReply* CreateRequest(const QString& ressource_name,
const QList<QPair<QString, QString> >& params);
// Convenient function for extracting result from reply
QVariant ExtractResult(QNetworkReply* reply);
SongList ExtractSongs(const QVariant& result);
Song ExtractSong(const QVariantMap& result_song);
QStandardItem* root_;
QStandardItem* search_;
NetworkAccessManager* network_;
QMenu* context_menu_;
SearchBoxWidget* search_box_;
QTimer* search_delay_;
QString pending_search_;
int next_pending_search_id_;
QByteArray api_key_;
static const char* kUrl;
static const char* kUrlCover;
static const char* kHomepage;
static const int kSongSearchLimit;
static const int kSongSimpleSearchLimit;
static const int kSearchDelayMsec;
static const char* kApiClientId;
};
#endif // SOUNDCLOUDSERVICE_H