Add Grooveshark radios

This commit is contained in:
Arnaud Bienner 2011-11-29 13:57:35 +01:00
parent 8d5ef62256
commit 1f5ac97934
6 changed files with 154 additions and 42 deletions

View File

@ -136,6 +136,7 @@ set(SOURCES
internet/digitallyimportedservicebase.cpp
internet/digitallyimportedsettingspage.cpp
internet/digitallyimportedurlhandler.cpp
internet/groovesharkradio.cpp
internet/groovesharksearchplaylisttype.cpp
internet/groovesharkservice.cpp
internet/groovesharksettingspage.cpp
@ -379,6 +380,7 @@ set(HEADERS
internet/digitallyimportedclient.h
internet/digitallyimportedservicebase.h
internet/digitallyimportedsettingspage.h
internet/groovesharkradio.h
internet/groovesharkservice.h
internet/groovesharksettingspage.h
internet/groovesharkurlhandler.h

View File

@ -37,6 +37,7 @@
#include "qtiocompressor.h"
#include "internetmodel.h"
#include "groovesharkradio.h"
#include "groovesharksearchplaylisttype.h"
#include "groovesharkurlhandler.h"
@ -57,6 +58,9 @@
#include "playlist/playlistmanager.h"
#include "ui/iconloader.h"
using smart_playlists::Generator;
using smart_playlists::GeneratorPtr;
// The Grooveshark terms of service require that application keys are not
// accessible to third parties. Therefore this application key is obfuscated to
// prevent third parties from viewing it.
@ -84,6 +88,7 @@ GroovesharkService::GroovesharkService(InternetModel *parent)
search_(NULL),
popular_month_(NULL),
popular_today_(NULL),
stations_(NULL),
favorites_(NULL),
subscribed_playlists_divider_(NULL),
network_(new NetworkAccessManager(this)),
@ -306,21 +311,9 @@ void GroovesharkService::InitCountry() {
return;
// Get country info
QNetworkReply *reply_country = CreateRequest("getCountry", QList<Param>());
if (!WaitForReply(reply_country))
return;
// Wait for the reply
{
QEventLoop event_loop;
QTimer timeout_timer;
connect(&timeout_timer, SIGNAL(timeout()), &event_loop, SLOT(quit()));
connect(reply_country, SIGNAL(finished()), &event_loop, SLOT(quit()));
timeout_timer.start(3000);
event_loop.exec();
if (!timeout_timer.isActive()) {
qLog(Error) << "Grooveshark request timeout";
return;
}
timeout_timer.stop();
}
country_ = ExtractResult(reply_country);
}
@ -332,20 +325,8 @@ QUrl GroovesharkService::GetStreamingUrlFromSongId(const QString& song_id,
parameters << Param("songID", song_id)
<< Param("country", country_);
QNetworkReply* reply = CreateRequest("getSubscriberStreamKey", parameters);
// Wait for the reply
{
QEventLoop event_loop;
QTimer timeout_timer;
connect(&timeout_timer, SIGNAL(timeout()), &event_loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &event_loop, SLOT(quit()));
timeout_timer.start(3000);
event_loop.exec();
if (!timeout_timer.isActive()) {
qLog(Error) << "Grooveshark request timeout";
return QUrl();
}
timeout_timer.stop();
}
if (!WaitForReply(reply))
return QUrl();
QVariantMap result = ExtractResult(reply);
server_id->clear();
server_id->append(result["StreamServerID"].toString());
@ -544,6 +525,15 @@ void GroovesharkService::EnsureItemsCreated() {
InternetModel::Role_PlayBehaviour);
root_->appendRow(popular_today_);
QStandardItem* radios_divider = new QStandardItem(tr("Radios"));
radios_divider->setData(true, InternetModel::Role_IsDivider);
root_->appendRow(radios_divider);
stations_ = new QStandardItem(QIcon(":last.fm/icon_radio.png"), tr("Stations"));
stations_->setData(InternetModel::Type_UserPlaylist, InternetModel::Role_Type);
stations_->setData(true, InternetModel::Role_CanLazyLoad);
root_->appendRow(stations_);
QStandardItem* playlists_divider = new QStandardItem(tr("Playlists"));
playlists_divider->setData(true, InternetModel::Role_IsDivider);
root_->appendRow(playlists_divider);
@ -564,6 +554,7 @@ void GroovesharkService::EnsureItemsCreated() {
RetrieveUserFavorites();
RetrieveUserPlaylists();
RetrieveSubscribedPlaylists();
RetrieveAutoplayTags();
RetrievePopularSongs();
}
}
@ -802,6 +793,56 @@ void GroovesharkService::SubscribedPlaylistsRetrieved(QNetworkReply* reply) {
}
}
void GroovesharkService::RetrieveAutoplayTags() {
QNetworkReply* reply = CreateRequest("getAutoplayTags", QList<Param>());
NewClosure(reply, SIGNAL(finished()),
this, SLOT(AutoplayTagsRetrieved(QNetworkReply*)), reply);
}
void GroovesharkService::AutoplayTagsRetrieved(QNetworkReply* reply) {
reply->deleteLater();
QVariantMap result = ExtractResult(reply);
QVariantMap::const_iterator it;
for (it = result.constBegin(); it != result.constEnd(); ++it) {
int id = it.key().toInt();
QString name = it.value().toString().toLower();
// Names received aren't very nice: make them more user friendly to display
name.replace("_", " ");
name[0] = name[0].toUpper();
QStandardItem* item = new QStandardItem(QIcon(":last.fm/icon_radio.png"), name);
item->setData(InternetModel::Type_SmartPlaylist, InternetModel::Role_Type);
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
item->setData(id, Role_UserPlaylistId);
stations_->appendRow(item);
}
}
Song GroovesharkService::StartAutoplayTag(int tag_id, QVariantMap& autoplay_state) {
QList<Param> parameters;
parameters << Param("tagID", tag_id);
QNetworkReply* reply = CreateRequest("startAutoplayTag", parameters);
if (!WaitForReply(reply))
return Song();
reply->deleteLater();
QVariantMap result = ExtractResult(reply);
autoplay_state = result["autoplayState"].toMap();
return ExtractSong(result["nextSong"].toMap());
}
Song GroovesharkService::GetAutoplaySong(QVariantMap& autoplay_state) {
QList<Param> parameters;
parameters << Param("autoplayState", autoplay_state);
QNetworkReply* reply = CreateRequest("getAutoplaySong", parameters);
if (!WaitForReply(reply))
return Song();
reply->deleteLater();
QVariantMap result = ExtractResult(reply);
autoplay_state = result["autoplayState"].toMap();
return ExtractSong(result["nextSong"].toMap());
}
void GroovesharkService::MarkStreamKeyOver30Secs(const QString& stream_key,
const QString& server_id) {
QList<Param> parameters;
@ -862,6 +903,17 @@ void GroovesharkService::ItemDoubleClicked(QStandardItem* item) {
}
}
GeneratorPtr GroovesharkService::CreateGenerator(QStandardItem* item) {
GeneratorPtr ret;
if (!item ||
item->data(InternetModel::Role_Type).toInt() != InternetModel::Type_SmartPlaylist) {
return ret;
}
int tag_id = item->data(Role_UserPlaylistId).toInt();
ret = GeneratorPtr(new GroovesharkRadio(this ,tag_id));
return ret;
}
void GroovesharkService::DropMimeData(const QMimeData* data, const QModelIndex& index) {
if (!data) {
return;
@ -1279,6 +1331,21 @@ QNetworkReply* GroovesharkService::CreateRequest(const QString& method_name, QLi
return reply;
}
bool GroovesharkService::WaitForReply(QNetworkReply* reply) {
QEventLoop event_loop;
QTimer timeout_timer;
connect(&timeout_timer, SIGNAL(timeout()), &event_loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &event_loop, SLOT(quit()));
timeout_timer.start(10000);
event_loop.exec();
if (!timeout_timer.isActive()) {
qLog(Error) << "Grooveshark request timeout";
return false;
}
timeout_timer.stop();
return true;
}
QVariantMap GroovesharkService::ExtractResult(QNetworkReply* reply) {
QJson::Parser parser;
bool ok;
@ -1309,23 +1376,28 @@ SongList GroovesharkService::ExtractSongs(const QVariantMap& result) {
SongList songs;
for (int i=0; i<result_songs.size(); ++i) {
QVariantMap result_song = result_songs[i].toMap();
Song song;
int song_id = result_song["SongID"].toInt();
QString song_name = result_song["SongName"].toString();
QString artist_name = result_song["ArtistName"].toString();
QString album_name = result_song["AlbumName"].toString();
QString cover = result_song["CoverArtFilename"].toString();
song.Init(song_name, artist_name, album_name, 0);
song.set_art_automatic(QString(kUrlCover) + cover);
// Special kind of URL: because we need to request a stream key for each
// play, we generate a fake URL for now, and we will create a real streaming
// URL when user will actually play the song (through url handler)
song.set_url(QString("grooveshark://%1").arg(song_id));
songs << song;
songs << ExtractSong(result_song);
}
return songs;
}
Song GroovesharkService::ExtractSong(const QVariantMap& result_song) {
Song song;
int song_id = result_song["SongID"].toInt();
QString song_name = result_song["SongName"].toString();
QString artist_name = result_song["ArtistName"].toString();
QString album_name = result_song["AlbumName"].toString();
QString cover = result_song["CoverArtFilename"].toString();
song.Init(song_name, artist_name, album_name, 0);
song.set_art_automatic(QString(kUrlCover) + cover);
// Special kind of URL: because we need to request a stream key for each
// play, we generate a fake URL for now, and we will create a real streaming
// URL when user will actually play the song (through url handler)
song.set_url(QString("grooveshark://%1").arg(song_id));
return song;
}
QList<int> GroovesharkService::ExtractSongsIds(const QVariantMap& result) {
QVariantList result_songs = result["songs"].toList();
QList<int> songs_ids;

View File

@ -65,6 +65,7 @@ class GroovesharkService : public InternetService {
void LazyPopulate(QStandardItem *parent);
void ItemDoubleClicked(QStandardItem* item);
smart_playlists::GeneratorPtr CreateGenerator(QStandardItem* item);
void DropMimeData(const QMimeData* data, const QModelIndex& index);
QList<QAction*> playlistitem_actions(const Song& song);
void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos);
@ -83,6 +84,7 @@ class GroovesharkService : public InternetService {
void RetrievePopularSongsMonth();
void RetrievePopularSongsToday();
void RetrieveSubscribedPlaylists();
void RetrieveAutoplayTags();
void SetPlaylistSongs(int playlist_id, const QList<int>& songs_ids);
void RemoveFromPlaylist(int playlist_id, int song_id);
// Refresh playlist_id playlist , or create it if it doesn't exist
@ -92,6 +94,11 @@ class GroovesharkService : public InternetService {
void AddUserFavoriteSong(int song_id);
void RemoveFromFavorites(int song_id);
void GetSongUrlToShare(int song_id);
// Start autoplay for the given tag_id, fill the autoplay_state, returns a
// first song to play
Song StartAutoplayTag(int tag_id, QVariantMap& autoplay_state);
// Get another autoplay song. autoplay_state is the autoplay_state received from StartAutoplayTag
Song GetAutoplaySong(QVariantMap& autoplay_state);
void MarkStreamKeyOver30Secs(const QString& stream_key, const QString& server_id);
void MarkSongComplete(const QString& song_id, const QString& stream_key, const QString& server_id);
@ -146,6 +153,7 @@ class GroovesharkService : public InternetService {
void PopularSongsMonthRetrieved(QNetworkReply* reply);
void PopularSongsTodayRetrieved(QNetworkReply* reply);
void SubscribedPlaylistsRetrieved(QNetworkReply* reply);
void AutoplayTagsRetrieved(QNetworkReply* reply);
void PlaylistSongsRetrieved();
void PlaylistSongsSet(QNetworkReply* reply, int playlist_id, int task_id);
void CreateNewPlaylist();
@ -184,10 +192,18 @@ class GroovesharkService : public InternetService {
// Returns the reply object created
QNetworkReply* CreateRequest(const QString& method_name, const QList<QPair<QString, QVariant> > params,
bool use_https = false);
// Convenient function which block until 'reply' replies, or timeout after 10
// seconds. Returns false if reply has timeouted
bool WaitForReply(QNetworkReply* reply);
// Convenient function for extracting result from reply
QVariantMap ExtractResult(QNetworkReply* reply);
// Convenient function for extracting songs from grooveshark result
// Convenient function for extracting songs from grooveshark result. result
// should be the "result" field of most Grooveshark replies
SongList ExtractSongs(const QVariantMap& result);
// Convenient function for extracting song from grooveshark result.
// result_song should be the song field ('song', 'nextSong', ...) of the
// Grooveshark reply
Song ExtractSong(const QVariantMap& result_song);
// Convenient functions for extracting Grooveshark songs ids
QList<int> ExtractSongsIds(const QVariantMap& result);
QList<int> ExtractSongsIds(const QList<QUrl>& urls);
@ -213,6 +229,7 @@ class GroovesharkService : public InternetService {
QStandardItem* search_;
QStandardItem* popular_month_;
QStandardItem* popular_today_;
QStandardItem* stations_;
QStandardItem* favorites_;
QStandardItem* subscribed_playlists_divider_;

View File

@ -27,6 +27,7 @@
#include "groovesharkservice.h"
#include "core/logging.h"
#include "core/mergedproxymodel.h"
#include "smartplaylists/generatormimedata.h"
#ifdef HAVE_LIBLASTFM
#include "lastfmservice.h"
@ -38,6 +39,10 @@
#include <QMimeData>
#include <QtDebug>
using smart_playlists::Generator;
using smart_playlists::GeneratorMimeData;
using smart_playlists::GeneratorPtr;
QMap<QString, InternetService*>* InternetModel::sServices = NULL;
InternetModel::InternetModel(BackgroundThread<Database>* db_thread,
@ -197,6 +202,18 @@ QMimeData* InternetModel::mimeData(const QModelIndexList& indexes) const {
return NULL;
}
if (indexes.count() == 1 &&
indexes[0].data(Role_Type).toInt() == Type_SmartPlaylist) {
GeneratorPtr generator =
InternetModel::ServiceForIndex(indexes[0])->CreateGenerator(itemFromIndex(indexes[0]));
if (!generator)
return NULL;
GeneratorMimeData* data = new GeneratorMimeData(generator);
data->setData(LibraryModel::kSmartPlaylistsMimeType, QByteArray());
data->name_for_new_playlist_ = this->data(indexes.first()).toString();
return data;
}
QList<QUrl> urls;
QModelIndexList new_indexes;

View File

@ -88,6 +88,7 @@ public:
enum Type {
Type_Service = 1,
Type_UserPlaylist,
Type_SmartPlaylist,
TypeCount
};

View File

@ -24,6 +24,7 @@
#include "core/song.h"
#include "playlist/playlistitem.h"
#include "smartplaylists/generator.h"
#include "ui/settingsdialog.h"
#include "widgets/multiloadingindicator.h"
@ -47,6 +48,8 @@ public:
virtual void ShowContextMenu(const QModelIndex& index, const QPoint& global_pos) {}
virtual void ItemDoubleClicked(QStandardItem* item) {}
// Create a generator for smart playlists
virtual smart_playlists::GeneratorPtr CreateGenerator(QStandardItem* item) { return smart_playlists::GeneratorPtr(); }
// Give the service a chance to do a custom action when data is dropped on it
virtual void DropMimeData(const QMimeData* data, const QModelIndex& index) {}