"Now listening" last.fm notifications

This commit is contained in:
David Sansome 2009-12-29 19:22:02 +00:00
parent 62dda6430c
commit 38feb17697
20 changed files with 253 additions and 88 deletions

View File

@ -39,5 +39,7 @@
<file>last.fm/personal_radio.png</file>
<file>last.fm/recommended_radio.png</file>
<file>spinner.gif</file>
<file>last.fm/ban.png</file>
<file>last.fm/love.png</file>
</qresource>
</RCC>

BIN
data/last.fm/ban.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
data/last.fm/love.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -6,18 +6,24 @@
#include <lastfm/ws.h>
#include <lastfm/misc.h>
#include <lastfm/XmlQuery>
#include <lastfm/Audioscrobbler>
#include <QSettings>
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<RadioItem::PlaylistData> 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<lastfm::Audioscrobbler::Status>(status);
}

View File

@ -2,6 +2,7 @@
#define LASTFMSERVICE_H
#include "radioservice.h"
#include "song.h"
#include <lastfm/RadioTuner>
@ -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<RadioItem::PlaylistData> 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_;
};

View File

@ -6,6 +6,7 @@
int main(int argc, char *argv[]) {
QCoreApplication::setApplicationName("Tangerine");
QCoreApplication::setApplicationVersion("0.1");
QCoreApplication::setOrganizationName("Tangerine");
QCoreApplication::setOrganizationDomain("davidsansome.com");

View File

@ -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*) {

View File

@ -136,6 +136,51 @@
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="last_fm_controls" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="spacing">
<number>1</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="love_button">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="ban_button">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line">
<property name="maximumSize">
@ -489,6 +534,30 @@
<string>Added this month</string>
</property>
</action>
<action name="action_love">
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/last.fm/love.png</normaloff>:/last.fm/love.png</iconset>
</property>
<property name="text">
<string>Love</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
<action name="action_ban">
<property name="icon">
<iconset resource="../data/data.qrc">
<normaloff>:/last.fm/ban.png</normaloff>:/last.fm/ban.png</iconset>
</property>
<property name="text">
<string>Ban</string>
</property>
<property name="visible">
<bool>false</bool>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -1,12 +1,14 @@
#include "player.h"
#include "playlist.h"
#include "xine-engine.h"
#include "lastfmservice.h"
#include <QtDebug>
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());
}

View File

@ -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_;

View File

@ -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;
}

View File

@ -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

View File

@ -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<LastFMService*>(sServices[LastFMService::kServiceName]);
return NULL;
}

View File

@ -5,6 +5,7 @@
#include "simpletreemodel.h"
class RadioService;
class LastFMService;
class Song;
class RadioModel : public SimpleTreeModel<RadioItem> {
@ -22,6 +23,9 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
// 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;

View File

@ -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();
}

View File

@ -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

View File

@ -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_);

View File

@ -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;

View File

@ -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");

View File

@ -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();