diff --git a/data/data.qrc b/data/data.qrc
index 15217e8a9..b3389161d 100644
--- a/data/data.qrc
+++ b/data/data.qrc
@@ -39,5 +39,7 @@
last.fm/personal_radio.png
last.fm/recommended_radio.png
spinner.gif
+ last.fm/ban.png
+ last.fm/love.png
diff --git a/data/last.fm/ban.png b/data/last.fm/ban.png
new file mode 100644
index 000000000..5cbf154e4
Binary files /dev/null and b/data/last.fm/ban.png differ
diff --git a/data/last.fm/love.png b/data/last.fm/love.png
new file mode 100644
index 000000000..5a185a9d1
Binary files /dev/null and b/data/last.fm/love.png differ
diff --git a/src/lastfmservice.cpp b/src/lastfmservice.cpp
index 09a6d2c3d..60f930a6f 100644
--- a/src/lastfmservice.cpp
+++ b/src/lastfmservice.cpp
@@ -6,18 +6,24 @@
#include
#include
#include
+#include
#include
+const char* LastFMService::kServiceName = "Last.fm";
const char* LastFMService::kSettingsGroup = "Last.fm";
+const char* LastFMService::kAudioscrobblerClientId = "tng";
+const char* LastFMService::kApiKey = "75d20fb472be99275392aefa2760ea09";
+const char* LastFMService::kSecret = "d3072b60ae626be12be69448f5c46e70";
LastFMService::LastFMService(QObject* parent)
- : RadioService("Last.fm", parent),
+ : RadioService(kServiceName, parent),
tuner_(NULL),
+ scrobbler_(NULL),
initial_tune_(false)
{
- lastfm::ws::ApiKey = "75d20fb472be99275392aefa2760ea09";
- lastfm::ws::SharedSecret = "d3072b60ae626be12be69448f5c46e70";
+ lastfm::ws::ApiKey = kApiKey;
+ lastfm::ws::SharedSecret = kSecret;
QSettings settings;
settings.beginGroup(kSettingsGroup);
@@ -31,6 +37,10 @@ LastFMService::~LastFMService() {
delete config_;
}
+bool LastFMService::IsAuthenticated() const {
+ return !lastfm::ws::SessionKey.isEmpty();
+}
+
RadioItem* LastFMService::CreateRootItem(RadioItem* parent) {
RadioItem* item = new RadioItem(this, RadioItem::Type_Service, "Last.fm", parent);
item->icon = QIcon(":last.fm/as.png");
@@ -50,7 +60,7 @@ void LastFMService::LazyPopulate(RadioItem *item) {
CreateStationItem(Type_MyNeighbourhood, "My Neighbourhood",
":last.fm/neighbour_radio.png", item);
- if (lastfm::ws::SessionKey.isEmpty())
+ if (!IsAuthenticated())
config_->show();
break;
@@ -106,6 +116,10 @@ void LastFMService::AuthenticateReplyFinished() {
settings.setValue("username", lastfm::ws::Username);
settings.setValue("session", lastfm::ws::SessionKey);
+ // Invalidate the scrobbler - it will get recreated later
+ delete scrobbler_;
+ scrobbler_ = NULL;
+
emit AuthenticationComplete(true);
}
@@ -142,7 +156,7 @@ QList LastFMService::DataForItem(RadioItem* item) {
void LastFMService::StartLoading(const QUrl& url) {
if (url.scheme() != "lastfm")
return;
- if (lastfm::ws::SessionKey.isEmpty())
+ if (!IsAuthenticated())
return;
emit LoadingStarted();
@@ -158,18 +172,18 @@ void LastFMService::StartLoading(const QUrl& url) {
}
void LastFMService::LoadNext(const QUrl &) {
- lastfm::Track track = tuner_->takeNextTrack();
+ last_track_ = tuner_->takeNextTrack();
- if (track.isNull()) {
+ if (last_track_.isNull()) {
emit StreamFinished();
return;
}
- emit StreamReady(last_url_, track.url());
-
Song metadata;
- metadata.InitFromLastFM(track);
+ metadata.InitFromLastFM(last_track_);
+
emit StreamMetadataFound(last_url_, metadata);
+ emit StreamReady(last_url_, last_track_.url());
}
void LastFMService::TunerError(lastfm::ws::Error error) {
@@ -224,3 +238,60 @@ void LastFMService::TunerTrackAvailable() {
initial_tune_ = false;
}
}
+
+bool LastFMService::InitScrobbler() {
+ if (!IsAuthenticated())
+ return false;
+
+ if (!scrobbler_) {
+ scrobbler_ = new lastfm::Audioscrobbler(kAudioscrobblerClientId);
+ connect(scrobbler_, SIGNAL(status(int)), SLOT(ScrobblerStatus(int)));
+ }
+
+ return true;
+}
+
+lastfm::Track LastFMService::TrackFromSong(const Song &song) const {
+ qDebug() << song.title() << last_track_.title();
+ qDebug() << song.artist() << last_track_.artist();
+ qDebug() << song.album() << last_track_.album();
+ qDebug() << last_track_.fingerprintId() << last_track_.mbid();
+
+ if (song.title() == last_track_.title() &&
+ song.artist() == last_track_.artist() &&
+ song.album() == last_track_.album())
+ return last_track_;
+
+ lastfm::Track ret;
+ song.ToLastFM(&ret);
+ return ret;
+
+}
+
+void LastFMService::NowPlaying(const Song &song) {
+ if (!InitScrobbler())
+ return;
+
+ scrobbler_->nowPlaying(TrackFromSong(song));
+}
+
+void LastFMService::Scrobble(const Song& song) {
+ if (!InitScrobbler())
+ return;
+
+ scrobbler_->cache(TrackFromSong(song));
+}
+
+void LastFMService::Love(const Song& song) {
+ lastfm::MutableTrack mtrack(TrackFromSong(song));
+ mtrack.love();
+}
+
+void LastFMService::Ban(const Song& song) {
+ lastfm::MutableTrack mtrack(TrackFromSong(song));
+ mtrack.ban();
+}
+
+void LastFMService::ScrobblerStatus(int status) {
+ qDebug() << static_cast(status);
+}
diff --git a/src/lastfmservice.h b/src/lastfmservice.h
index 69f112c56..2392b0642 100644
--- a/src/lastfmservice.h
+++ b/src/lastfmservice.h
@@ -2,6 +2,7 @@
#define LASTFMSERVICE_H
#include "radioservice.h"
+#include "song.h"
#include
@@ -14,7 +15,11 @@ class LastFMService : public RadioService {
LastFMService(QObject* parent = 0);
~LastFMService();
+ static const char* kServiceName;
static const char* kSettingsGroup;
+ static const char* kAudioscrobblerClientId;
+ static const char* kApiKey;
+ static const char* kSecret;
enum ItemType {
Type_MyRecommendations = 1000,
@@ -27,15 +32,19 @@ class LastFMService : public RadioService {
RadioItem* CreateRootItem(RadioItem* parent);
void LazyPopulate(RadioItem *item);
QList DataForItem(RadioItem* item);
-
void StartLoading(const QUrl& url);
void LoadNext(const QUrl& url);
-
bool IsPauseAllowed() const { return false; }
bool ShowLastFmControls() const { return true; }
+ bool IsAuthenticated() const;
void Authenticate(const QString& username, const QString& password);
+ void NowPlaying(const Song& song);
+ void Scrobble(const Song& song);
+ void Love(const Song& song);
+ void Ban(const Song& song);
+
signals:
void AuthenticationComplete(bool success);
@@ -45,14 +54,21 @@ class LastFMService : public RadioService {
void TunerTrackAvailable();
void TunerError(lastfm::ws::Error error);
+ void ScrobblerStatus(int status);
+
private:
RadioItem* CreateStationItem(ItemType type, const QString& name,
const QString& icon, RadioItem* parent);
QString ErrorString(lastfm::ws::Error error) const;
+ bool InitScrobbler();
+ lastfm::Track TrackFromSong(const Song& song) const;
private:
- LastFMConfig* config_;
lastfm::RadioTuner* tuner_;
+ lastfm::Audioscrobbler* scrobbler_;
+ lastfm::Track last_track_;
+
+ LastFMConfig* config_;
QUrl last_url_;
bool initial_tune_;
};
diff --git a/src/main.cpp b/src/main.cpp
index 23f480eaa..418c582c1 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -6,6 +6,7 @@
int main(int argc, char *argv[]) {
QCoreApplication::setApplicationName("Tangerine");
+ QCoreApplication::setApplicationVersion("0.1");
QCoreApplication::setOrganizationName("Tangerine");
QCoreApplication::setOrganizationDomain("davidsansome.com");
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 7b49d2f05..086d94c70 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -25,7 +25,7 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
radio_model_(new RadioModel(this)),
playlist_(new Playlist(this)),
- player_(new Player(playlist_, this)),
+ player_(new Player(playlist_, radio_model_->GetLastFMService(), this)),
library_(new Library(player_->GetEngine(), this)),
library_sort_model_(new QSortFilterProxyModel(this)),
tray_icon_(new SystemTrayIcon(this))
@@ -36,6 +36,7 @@ MainWindow::MainWindow(QWidget *parent)
tray_icon_->show();
ui_.volume->setValue(player_->GetVolume());
+ ui_.last_fm_controls->hide();
// Models
library_sort_model_->setSourceModel(library_);
@@ -72,6 +73,8 @@ MainWindow::MainWindow(QWidget *parent)
ui_.back_button->setDefaultAction(ui_.action_previous_track);
ui_.pause_play_button->setDefaultAction(ui_.action_play_pause);
ui_.stop_button->setDefaultAction(ui_.action_stop);
+ ui_.love_button->setDefaultAction(ui_.action_love);
+ ui_.ban_button->setDefaultAction(ui_.action_ban);
// Stop actions
QMenu* stop_menu = new QMenu(this);
@@ -151,6 +154,8 @@ MainWindow::MainWindow(QWidget *parent)
tray_menu->addAction(ui_.action_play_pause);
tray_menu->addAction(ui_.action_stop);
tray_menu->addAction(ui_.action_next_track);
+ tray_menu->addAction(ui_.action_love);
+ tray_menu->addAction(ui_.action_ban);
tray_menu->addSeparator();
tray_menu->addAction(ui_.action_quit);
tray_icon_->setContextMenu(tray_menu);
@@ -209,6 +214,10 @@ void MainWindow::MediaStopped() {
ui_.action_play_pause->setText("Play");
ui_.action_play_pause->setEnabled(true);
+
+ ui_.action_ban->setVisible(false);
+ ui_.action_love->setVisible(false);
+ ui_.last_fm_controls->hide();
}
void MainWindow::MediaPaused() {
@@ -228,6 +237,11 @@ void MainWindow::MediaPlaying() {
ui_.action_play_pause->setEnabled(
! playlist_->current_item_options() & PlaylistItem::PauseDisabled);
+
+ bool lastfm = playlist_->current_item_options() & PlaylistItem::LastFMControls;
+ ui_.action_ban->setVisible(lastfm);
+ ui_.action_love->setVisible(lastfm);
+ ui_.last_fm_controls->setVisible(lastfm);
}
void MainWindow::resizeEvent(QResizeEvent*) {
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index ba8e2b28f..84031e9df 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -136,6 +136,51 @@
+ -
+
+
+
+ 1
+
+
+ 0
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ -
+
+
+
+ 22
+ 22
+
+
+
+ true
+
+
+
+ -
+
+
+
+ 22
+ 22
+
+
+
+ true
+
+
+
+
+
+
-
@@ -489,6 +534,30 @@
Added this month
+
+
+
+ :/last.fm/love.png:/last.fm/love.png
+
+
+ Love
+
+
+ false
+
+
+
+
+
+ :/last.fm/ban.png:/last.fm/ban.png
+
+
+ Ban
+
+
+ false
+
+
diff --git a/src/player.cpp b/src/player.cpp
index 354c2c4c3..831ce00cc 100644
--- a/src/player.cpp
+++ b/src/player.cpp
@@ -1,12 +1,14 @@
#include "player.h"
#include "playlist.h"
#include "xine-engine.h"
+#include "lastfmservice.h"
#include
-Player::Player(Playlist* playlist, QObject* parent)
+Player::Player(Playlist* playlist, LastFMService* lastfm, QObject* parent)
: QObject(parent),
playlist_(playlist),
+ lastfm_(lastfm),
engine_(new XineEngine)
{
if (!engine_->init()) {
@@ -119,8 +121,10 @@ void Player::PlayAt(int index) {
if (item->options() & PlaylistItem::SpecialPlayBehaviour)
item->StartLoading();
- else
+ else {
engine_->play(item->Url());
+ lastfm_->NowPlaying(item->Metadata());
+ }
}
void Player::StreamReady(const QUrl& original_url, const QUrl& media_url) {
@@ -133,4 +137,5 @@ void Player::StreamReady(const QUrl& original_url, const QUrl& media_url) {
return;
engine_->play(media_url);
+ lastfm_->NowPlaying(item->Metadata());
}
diff --git a/src/player.h b/src/player.h
index 1a89efbfc..9e78e60f0 100644
--- a/src/player.h
+++ b/src/player.h
@@ -8,12 +8,13 @@
class Playlist;
class Settings;
+class LastFMService;
class Player : public QObject {
Q_OBJECT
public:
- Player(Playlist* playlist, QObject* parent = 0);
+ Player(Playlist* playlist, LastFMService* lastfm, QObject* parent = 0);
EngineBase* GetEngine() { return engine_; }
Engine::State GetState() const;
@@ -41,6 +42,7 @@ class Player : public QObject {
private:
Playlist* playlist_;
+ LastFMService* lastfm_;
QSettings settings_;
EngineBase* engine_;
diff --git a/src/playlist.cpp b/src/playlist.cpp
index 63d635f5c..0577fccca 100644
--- a/src/playlist.cpp
+++ b/src/playlist.cpp
@@ -55,13 +55,14 @@ QVariant Playlist::data(const QModelIndex& index, int role) const {
case Qt::DisplayRole: {
PlaylistItem* item = items_[index.row()];
+ Song song = item->Metadata();
switch (index.column()) {
- case Column_Title: return item->Title();
- case Column_Artist: return item->Artist();
- case Column_Album: return item->Album();
- case Column_Length: return item->Length();
- case Column_Track: return item->Track();
+ case Column_Title: return song.title();
+ case Column_Artist: return song.artist();
+ case Column_Album: return song.album();
+ case Column_Length: return song.length();
+ case Column_Track: return song.track();
}
}
@@ -282,11 +283,11 @@ bool Playlist::CompareItems(int column, Qt::SortOrder order,
const PlaylistItem* b = order == Qt::AscendingOrder ? _b : _a;
switch (column) {
- case Column_Title: return a->Title() < b->Title();
- case Column_Artist: return a->Artist() < b->Artist();
- case Column_Album: return a->Album() < b->Album();
- case Column_Length: return a->Length() < b->Length();
- case Column_Track: return a->Track() < b->Track();
+ case Column_Title: return a->Metadata().title() < b->Metadata().title();
+ case Column_Artist: return a->Metadata().artist() < b->Metadata().artist();
+ case Column_Album: return a->Metadata().album() < b->Metadata().album();
+ case Column_Length: return a->Metadata().length() < b->Metadata().length();
+ case Column_Track: return a->Metadata().track() < b->Metadata().track();
}
return false;
}
diff --git a/src/playlistitem.h b/src/playlistitem.h
index bd045daf5..f294794ae 100644
--- a/src/playlistitem.h
+++ b/src/playlistitem.h
@@ -38,11 +38,7 @@ class PlaylistItem {
virtual void Save(QSettings& settings) const = 0;
virtual void Restore(const QSettings& settings) = 0;
- virtual QString Title() const = 0;
- virtual QString Artist() const = 0;
- virtual QString Album() const = 0;
- virtual int Length() const = 0;
- virtual int Track() const = 0;
+ virtual Song Metadata() const = 0;
// If the item needs to do anything special before it can play (eg. start
// streaming the radio stream), then it should implement StartLoading() and
diff --git a/src/radiomodel.cpp b/src/radiomodel.cpp
index e1f4ae09f..9be785cde 100644
--- a/src/radiomodel.cpp
+++ b/src/radiomodel.cpp
@@ -111,3 +111,9 @@ QMimeData* RadioModel::mimeData(const QModelIndexList& indexes) const {
return data;
}
+
+LastFMService* RadioModel::GetLastFMService() const {
+ if (sServices.contains(LastFMService::kServiceName))
+ return static_cast(sServices[LastFMService::kServiceName]);
+ return NULL;
+}
diff --git a/src/radiomodel.h b/src/radiomodel.h
index b6746db37..9e275e6f4 100644
--- a/src/radiomodel.h
+++ b/src/radiomodel.h
@@ -5,6 +5,7 @@
#include "simpletreemodel.h"
class RadioService;
+class LastFMService;
class Song;
class RadioModel : public SimpleTreeModel {
@@ -22,6 +23,9 @@ class RadioModel : public SimpleTreeModel {
// Needs to be static for RadioPlaylistItem::restore
static RadioService* ServiceByName(const QString& name);
+ // This is special because Player needs it for scrobbling
+ LastFMService* GetLastFMService() const;
+
// QAbstractItemModel
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
diff --git a/src/radioplaylistitem.cpp b/src/radioplaylistitem.cpp
index ac99a4e15..6fae07e25 100644
--- a/src/radioplaylistitem.cpp
+++ b/src/radioplaylistitem.cpp
@@ -15,6 +15,7 @@ RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url,
url_(url),
title_(title)
{
+ InitMetadata();
}
void RadioPlaylistItem::Save(QSettings& settings) const {
@@ -27,35 +28,23 @@ void RadioPlaylistItem::Restore(const QSettings& settings) {
service_ = RadioModel::ServiceByName(settings.value("service").toString());
url_ = settings.value("url").toString();
title_ = settings.value("title").toString();
+
+ InitMetadata();
}
-QString RadioPlaylistItem::Title() const {
+void RadioPlaylistItem::InitMetadata() {
if (!service_)
- return "Radio service couldn't be loaded :-(";
-
- if (metadata_.is_valid())
- return metadata_.title();
-
- if (!title_.isEmpty())
- return title_;
-
- return url_.toString();
+ metadata_.set_title("Radio service couldn't be loaded :-(");
+ else if (!title_.isEmpty())
+ metadata_.set_title(title_);
+ else
+ metadata_.set_title(url_.toString());
}
-QString RadioPlaylistItem::Artist() const {
- return metadata_.is_valid() ? metadata_.artist() : QString::null;
-}
-
-QString RadioPlaylistItem::Album() const {
- return metadata_.is_valid() ? metadata_.album() : QString::null;
-}
-
-int RadioPlaylistItem::Length() const {
- return metadata_.is_valid() ? metadata_.length() : -1;
-}
-
-int RadioPlaylistItem::Track() const {
- return metadata_.is_valid() ? metadata_.track() : -1;
+Song RadioPlaylistItem::Metadata() const {
+ if (temp_metadata_.is_valid())
+ return temp_metadata_;
+ return metadata_;
}
void RadioPlaylistItem::StartLoading() {
@@ -88,9 +77,9 @@ PlaylistItem::Options RadioPlaylistItem::options() const {
}
void RadioPlaylistItem::SetTemporaryMetadata(const Song& metadata) {
- metadata_ = metadata;
+ temp_metadata_ = metadata;
}
void RadioPlaylistItem::ClearTemporaryMetadata() {
- metadata_ = Song();
+ temp_metadata_ = Song();
}
diff --git a/src/radioplaylistitem.h b/src/radioplaylistitem.h
index ad40b3568..aea501300 100644
--- a/src/radioplaylistitem.h
+++ b/src/radioplaylistitem.h
@@ -19,11 +19,7 @@ class RadioPlaylistItem : public PlaylistItem {
void Save(QSettings& settings) const;
void Restore(const QSettings& settings);
- QString Title() const;
- QString Artist() const;
- QString Album() const;
- int Length() const;
- int Track() const;
+ Song Metadata() const;
void StartLoading();
QUrl Url();
@@ -33,12 +29,16 @@ class RadioPlaylistItem : public PlaylistItem {
void SetTemporaryMetadata(const Song& metadata);
void ClearTemporaryMetadata();
+ private:
+ void InitMetadata();
+
private:
RadioService* service_;
QUrl url_;
QString title_;
Song metadata_;
+ Song temp_metadata_;
};
#endif // RADIOPLAYLISTITEM_H
diff --git a/src/song.cpp b/src/song.cpp
index 86adf32a7..02f636748 100644
--- a/src/song.cpp
+++ b/src/song.cpp
@@ -234,6 +234,16 @@ void Song::BindToQuery(QSqlQuery *query) const {
#undef intval
}
+void Song::ToLastFM(lastfm::Track* track) const {
+ lastfm::MutableTrack mtrack(*track);
+
+ mtrack.setArtist(artist_);
+ mtrack.setAlbum(album_);
+ mtrack.setTitle(title_);
+ mtrack.setDuration(length_);
+ mtrack.setTrackNumber(track_);
+}
+
QString Song::PrettyTitleWithArtist() const {
QString title(title_);
diff --git a/src/song.h b/src/song.h
index 743955fa8..bda3d18cc 100644
--- a/src/song.h
+++ b/src/song.h
@@ -9,6 +9,7 @@ namespace lastfm {
class Track;
}
+// TODO: QSharedData
class Song {
public:
Song();
@@ -24,6 +25,7 @@ class Song {
// Save
void BindToQuery(QSqlQuery* query) const;
+ void ToLastFM(lastfm::Track* track) const;
// Simple accessors
bool is_valid() const { return valid_; }
@@ -59,6 +61,7 @@ class Song {
// Setters
void set_id(int id) { id_ = id; }
+ void set_title(const QString& title) { title_ = title; }
// Comparison functions
bool IsMetadataEqual(const Song& other) const;
diff --git a/src/songplaylistitem.cpp b/src/songplaylistitem.cpp
index f11ca8ceb..eec206a66 100644
--- a/src/songplaylistitem.cpp
+++ b/src/songplaylistitem.cpp
@@ -24,26 +24,6 @@ void SongPlaylistItem::Restore(const QSettings& settings) {
song_.InitFromFile(filename, directory_id);
}
-QString SongPlaylistItem::Title() const {
- return song_.PrettyTitle();
-}
-
-QString SongPlaylistItem::Artist() const {
- return song_.artist();
-}
-
-QString SongPlaylistItem::Album() const {
- return song_.album();
-}
-
-int SongPlaylistItem::Length() const {
- return song_.length();
-}
-
-int SongPlaylistItem::Track() const {
- return song_.track();
-}
-
QUrl SongPlaylistItem::Url() {
QUrl ret(QUrl::fromLocalFile(song_.filename()));
ret.setHost("localhost");
diff --git a/src/songplaylistitem.h b/src/songplaylistitem.h
index 4868d9b3e..0a32b34fc 100644
--- a/src/songplaylistitem.h
+++ b/src/songplaylistitem.h
@@ -14,11 +14,7 @@ class SongPlaylistItem : public PlaylistItem {
void Save(QSettings& settings) const;
void Restore(const QSettings& settings);
- QString Title() const;
- QString Artist() const;
- QString Album() const;
- int Length() const;
- int Track() const;
+ Song Metadata() const { return song_; }
QUrl Url();