Simple last.fm streaming works

This commit is contained in:
David Sansome 2009-12-26 21:35:45 +00:00
parent 4cbad8929d
commit 7a3678e806
25 changed files with 513 additions and 18 deletions

View File

@ -11,14 +11,15 @@
const char* LastFMService::kSettingsGroup = "Last.fm";
LastFMService::LastFMService(QObject* parent)
: RadioService("Last.fm", parent)
: RadioService("Last.fm", parent),
tuner_(NULL)
{
lastfm::ws::ApiKey = "75d20fb472be99275392aefa2760ea09";
lastfm::ws::SharedSecret = "d3072b60ae626be12be69448f5c46e70";
QSettings settings;
settings.beginGroup(kSettingsGroup);
lastfm::ws::Username = settings.value("user").toString();
lastfm::ws::Username = settings.value("username").toString();
lastfm::ws::SessionKey = settings.value("session").toString();
config_ = new LastFMConfig(this);
@ -63,6 +64,7 @@ RadioItem* LastFMService::CreateStationItem(ItemType type, const QString& name,
RadioItem* ret = new RadioItem(this, type, name, parent);
ret->lazy_loaded = true;
ret->icon = QIcon(icon);
ret->playable = true;
return ret;
}
@ -104,3 +106,90 @@ void LastFMService::AuthenticateReplyFinished() {
emit AuthenticationComplete(true);
}
QList<QUrl> LastFMService::UrlsForItem(RadioItem* item) {
QList<QUrl> ret;
switch (item->type) {
case Type_MyRecommendations:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/recommended");
break;
case Type_MyLoved:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/loved");
break;
case Type_MyNeighbourhood:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/neighbours");
break;
case Type_MyRadio:
ret << QUrl("lastfm://user/" + lastfm::ws::Username + "/library");
break;
}
return ret;
}
void LastFMService::StartLoading(const QUrl& url) {
if (url.scheme() != "lastfm")
return;
if (lastfm::ws::SessionKey.isEmpty())
return;
emit LoadingStarted();
delete tuner_;
last_url_ = url;
tuner_ = new lastfm::RadioTuner(lastfm::RadioStation(url));
connect(tuner_, SIGNAL(trackAvailable()), SLOT(TunerTrackAvailable()));
connect(tuner_, SIGNAL(error(lastfm::ws::Error)), SLOT(TunerError(lastfm::ws::Error)));
}
void LastFMService::TunerError(lastfm::ws::Error error) {
emit LoadingFinished();
if (error == lastfm::ws::NotEnoughContent) {
emit StreamFinished();
return;
}
emit StreamError(ErrorString(error));
qDebug() << "Last.fm error" << error;
}
QString LastFMService::ErrorString(lastfm::ws::Error error) const {
switch (error) {
case lastfm::ws::InvalidService: return "Invalid service";
case lastfm::ws::InvalidMethod: return "Invalid method";
case lastfm::ws::AuthenticationFailed: return "Authentication failed";
case lastfm::ws::InvalidFormat: return "Invalid format";
case lastfm::ws::InvalidParameters: return "Invalid parameters";
case lastfm::ws::InvalidResourceSpecified: return "Invalid resource specified";
case lastfm::ws::OperationFailed: return "Operation failed";
case lastfm::ws::InvalidSessionKey: return "Invalid session key";
case lastfm::ws::InvalidApiKey: return "Invalid API key";
case lastfm::ws::ServiceOffline: return "Service offline";
case lastfm::ws::SubscribersOnly: return "This stream is for paid subscribers only";
case lastfm::ws::TryAgainLater: return "Last.fm is currently busy, please try again in a few minutes";
case lastfm::ws::NotEnoughContent: return "Not enough content";
case lastfm::ws::NotEnoughMembers: return "Not enough members";
case lastfm::ws::NotEnoughFans: return "Not enough fans";
case lastfm::ws::NotEnoughNeighbours: return "Not enough neighbours";
case lastfm::ws::MalformedResponse: return "Malformed response";
case lastfm::ws::UnknownError:
default:
return "Unknown error";
}
}
void LastFMService::TunerTrackAvailable() {
emit LoadingFinished();
lastfm::Track track = tuner_->takeNextTrack();
emit StreamReady(last_url_, track.url());
}

View File

@ -3,6 +3,8 @@
#include "radioservice.h"
#include <lastfm/RadioTuner>
class LastFMConfig;
class LastFMService : public RadioService {
@ -21,8 +23,11 @@ class LastFMService : public RadioService {
Type_MyNeighbourhood,
};
// RadioService
RadioItem* CreateRootItem(RadioItem* parent);
void LazyPopulate(RadioItem *item);
QList<QUrl> UrlsForItem(RadioItem* item);
void StartLoading(const QUrl& url);
void Authenticate(const QString& username, const QString& password);
@ -32,12 +37,18 @@ class LastFMService : public RadioService {
private slots:
void AuthenticateReplyFinished();
void TunerTrackAvailable();
void TunerError(lastfm::ws::Error error);
private:
RadioItem* CreateStationItem(ItemType type, const QString& name,
const QString& icon, RadioItem* parent);
QString ErrorString(lastfm::ws::Error error) const;
private:
LastFMConfig* config_;
lastfm::RadioTuner* tuner_;
QUrl last_url_;
};
#endif // LASTFMSERVICE_H

View File

@ -23,10 +23,10 @@ const char* MainWindow::kSettingsGroup = "MainWindow";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
radio_model_(new RadioModel(this)),
playlist_(new Playlist(this)),
player_(new Player(playlist_, this)),
library_(new Library(player_->GetEngine(), this)),
radio_model_(new RadioModel(this)),
library_sort_model_(new QSortFilterProxyModel(this)),
tray_icon_(new SystemTrayIcon(this))
{
@ -137,6 +137,13 @@ MainWindow::MainWindow(QWidget *parent)
library_menu->addAction("Configure library...", library_, SLOT(ShowConfig()));
ui_.library_options->setMenu(library_menu);
// Radio connections
connect(radio_model_, SIGNAL(LoadingStarted()), ui_.playlist, SLOT(StartRadioLoading()));
connect(radio_model_, SIGNAL(LoadingFinished()), ui_.playlist, SLOT(StopRadioLoading()));
connect(radio_model_, SIGNAL(StreamError(QString)), SLOT(ReportError(QString)));
connect(radio_model_, SIGNAL(StreamFinished()), player_, SLOT(Next()));
connect(radio_model_, SIGNAL(StreamReady(QUrl,QUrl)), player_, SLOT(StreamReady(QUrl,QUrl)));
// Tray icon
QMenu* tray_menu = new QMenu(this);
tray_menu->addAction(ui_.action_previous_track);

View File

@ -55,10 +55,10 @@ class MainWindow : public QMainWindow {
Ui::MainWindow ui_;
RadioModel* radio_model_;
Playlist* playlist_;
Player* player_;
Library* library_;
RadioModel* radio_model_;
QSortFilterProxyModel* library_sort_model_;

View File

@ -99,5 +99,21 @@ Engine::State Player::GetState() const {
void Player::PlayAt(int index) {
playlist_->set_current_item(index);
engine_->play(playlist_->item_at(index)->Url());
PlaylistItem* item = playlist_->item_at(index);
if (!item->StartLoading())
engine_->play(item->Url());
}
void Player::StreamReady(const QUrl& original_url, const QUrl& media_url) {
int current_index = playlist_->current_item();
if (current_index == -1)
return;
PlaylistItem* item = playlist_->item_at(current_index);
if (!item || item->Url() != original_url)
return;
engine_->play(media_url);
}

View File

@ -27,6 +27,8 @@ class Player : public QObject {
void Stop();
void SetVolume(int value);
void StreamReady(const QUrl& original_url, const QUrl& media_url);
signals:
void Playing();
void Paused();

View File

@ -1,6 +1,8 @@
#include "playlist.h"
#include "songmimedata.h"
#include "songplaylistitem.h"
#include "radiomimedata.h"
#include "radioplaylistitem.h"
#include <QtDebug>
#include <QMimeData>
@ -117,10 +119,12 @@ bool Playlist::dropMimeData(const QMimeData* data, Qt::DropAction action, int ro
if (action == Qt::IgnoreAction)
return false;
QList<PlaylistItem*> added_items;
if (const SongMimeData* song_data = qobject_cast<const SongMimeData*>(data)) {
// Dragged from the library
InsertSongs(song_data->songs, row);
} else if (const RadioMimeData* radio_data = qobject_cast<const RadioMimeData*>(data)) {
// Dragged from the Radio pane
InsertRadioStations(radio_data->services, radio_data->urls(), row);
} else if (data->hasFormat(kRowsMimetype)) {
// Dragged from the playlist
// Rearranging it is tricky...
@ -232,6 +236,17 @@ QModelIndex Playlist::InsertSongs(const SongList& songs, int after) {
return InsertItems(items, after);
}
QModelIndex Playlist::InsertRadioStations(const QList<RadioService*>& services,
const QList<QUrl>& urls, int after) {
Q_ASSERT(services.count() == urls.count());
QList<PlaylistItem*> items;
for (int i=0 ; i<services.count() ; ++i) {
items << new RadioPlaylistItem(services[i], urls[i]);
}
return InsertItems(items, after);
}
QMimeData* Playlist::mimeData(const QModelIndexList& indexes) const {
QMimeData* data = new QMimeData;

View File

@ -7,6 +7,8 @@
#include "playlistitem.h"
#include "song.h"
class RadioService;
class Playlist : public QAbstractListModel {
Q_OBJECT
@ -49,6 +51,8 @@ class Playlist : public QAbstractListModel {
// Changing the playlist
QModelIndex InsertItems(const QList<PlaylistItem*>& items, int after = -1);
QModelIndex InsertSongs(const SongList& items, int after = -1);
QModelIndex InsertRadioStations(const QList<RadioService*>& services,
const QList<QUrl>& urls, int after = -1);
QModelIndex InsertPaths(QList<QUrl> urls, int after = -1);
void StopAfter(int row);

View File

@ -1,11 +1,13 @@
#include "playlistitem.h"
#include "songplaylistitem.h"
#include "radioplaylistitem.h"
#include <QtDebug>
QString PlaylistItem::type_string() const {
switch (type()) {
case Type_Song: return "Song";
case Type_Radio: return "Radio";
default:
qWarning() << "Invalid PlaylistItem type:" << type();
return QString::null;
@ -15,6 +17,8 @@ QString PlaylistItem::type_string() const {
PlaylistItem* PlaylistItem::NewFromType(const QString& type) {
if (type == "Song")
return new SongPlaylistItem;
if (type == "Radio")
return new RadioPlaylistItem;
qWarning() << "Invalid PlaylistItem type:" << type;
return NULL;

View File

@ -15,6 +15,7 @@ class PlaylistItem {
enum Type {
Type_Song,
Type_Radio,
};
virtual Type type() const = 0;
@ -29,6 +30,11 @@ class PlaylistItem {
virtual int Length() const = 0;
virtual int Track() 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
// return true. If it returns false then the URL from Url() will be passed
// directly to xine instead.
virtual bool StartLoading() { return false; }
virtual QUrl Url() = 0;
};

View File

@ -1,6 +1,7 @@
#include "playlistview.h"
#include "playlist.h"
#include "playlistheader.h"
#include "radioloadingindicator.h"
#include <QPainter>
#include <QHeaderView>
@ -9,6 +10,7 @@
#include <QTimer>
#include <QKeyEvent>
#include <QMenu>
#include <QScrollBar>
#include <math.h>
@ -98,7 +100,8 @@ PlaylistView::PlaylistView(QWidget *parent)
row_height_(-1),
currenttrack_play_(":currenttrack_play.png"),
currenttrack_pause_(":currenttrack_pause.png"),
menu_(new QMenu(this))
menu_(new QMenu(this)),
radio_loading_(new RadioLoadingIndicator(this))
{
setItemDelegate(new PlaylistDelegateBase(this));
setItemDelegateForColumn(Playlist::Column_Length, new LengthItemDelegate(this));
@ -114,6 +117,8 @@ PlaylistView::PlaylistView(QWidget *parent)
menu_->addAction(QIcon(":media-playback-stop.png"), "Stop playing after track",
this, SLOT(StopAfter()));
radio_loading_->hide();
}
void PlaylistView::setModel(QAbstractItemModel *model) {
@ -281,3 +286,24 @@ void PlaylistView::StopAfter() {
playlist->StopAfter(menu_index_.row());
}
void PlaylistView::resizeEvent(QResizeEvent *event) {
const QPoint kPadding(5,5);
QPoint pos(viewport()->mapTo(this, viewport()->rect().bottomRight()) -
kPadding - QPoint(radio_loading_->sizeHint().width(),
radio_loading_->sizeHint().height()));
radio_loading_->move(pos);
radio_loading_->resize(radio_loading_->sizeHint());
QTreeView::resizeEvent(event);
}
void PlaylistView::StartRadioLoading() {
radio_loading_->show();
}
void PlaylistView::StopRadioLoading() {
radio_loading_->hide();
}

View File

@ -4,6 +4,8 @@
#include <QStyledItemDelegate>
#include <QTreeView>
class RadioLoadingIndicator;
class PlaylistDelegateBase : public QStyledItemDelegate {
public:
PlaylistDelegateBase(QTreeView* view);
@ -28,6 +30,9 @@ class PlaylistView : public QTreeView {
public:
PlaylistView(QWidget* parent = 0);
// QWidget
void resizeEvent(QResizeEvent *event);
// QTreeView
void setModel(QAbstractItemModel *model);
void drawRow(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const;
@ -40,6 +45,9 @@ class PlaylistView : public QTreeView {
void StopGlowing();
void StartGlowing();
void StartRadioLoading();
void StopRadioLoading();
protected:
void hideEvent(QHideEvent* event);
void showEvent(QShowEvent* event);
@ -74,6 +82,8 @@ class PlaylistView : public QTreeView {
QMenu* menu_;
QModelIndex menu_index_;
RadioLoadingIndicator* radio_loading_;
};
#endif // PLAYLISTVIEW_H

View File

@ -3,6 +3,7 @@
RadioItem::RadioItem(RadioService* _service, int type, const QString& key,
RadioItem* parent)
: SimpleTreeItem<RadioItem>(type, key, parent),
service(_service)
service(_service),
playable(false)
{
}

View File

@ -19,6 +19,7 @@ class RadioItem : public SimpleTreeItem<RadioItem> {
QIcon icon;
RadioService* service;
bool playable;
};
#endif // RADIOITEM_H

View File

@ -0,0 +1,18 @@
#include "radioloadingindicator.h"
#include <QPainter>
RadioLoadingIndicator::RadioLoadingIndicator(QWidget *parent)
: QWidget(parent)
{
ui_.setupUi(this);
}
void RadioLoadingIndicator::paintEvent(QPaintEvent*) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing, true);
p.setOpacity(0.15);
p.setBrush(palette().color(QPalette::Text));
p.drawRoundedRect(rect(), 10, 10);
}

View File

@ -0,0 +1,19 @@
#ifndef RADIOLOADINGINDICATOR_H
#define RADIOLOADINGINDICATOR_H
#include <QWidget>
#include "ui_radioloadingindicator.h"
class RadioLoadingIndicator : public QWidget {
Q_OBJECT
public:
RadioLoadingIndicator(QWidget *parent = 0);
void paintEvent(QPaintEvent *);
private:
Ui::RadioLoadingIndicator ui_;
};
#endif // RADIOLOADINGINDICATOR_H

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RadioLoadingIndicator</class>
<widget class="QWidget" name="RadioLoadingIndicator">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>160</width>
<height>24</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>4</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>4</number>
</property>
<item>
<widget class="QLabel" name="text">
<property name="text">
<string>&lt;span style=&quot; font-weight:600;&quot;&gt;Loading radio stream...&lt;/span&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="BusyIndicator" name="spinner">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BusyIndicator</class>
<extends>QLabel</extends>
<header>busyindicator.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

15
src/radiomimedata.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef RADIOMIMEDATA_H
#define RADIOMIMEDATA_H
#include <QMimeData>
class RadioService;
class RadioMimeData : public QMimeData {
Q_OBJECT
public:
QList<RadioService*> services;
};
#endif // RADIOMIMEDATA_H

View File

@ -1,15 +1,38 @@
#include "radiomodel.h"
#include "radioservice.h"
#include "lastfmservice.h"
#include "radiomimedata.h"
#include <QMimeData>
#include <QtDebug>
QMap<QString, RadioService*> RadioModel::sServices;
RadioModel::RadioModel(QObject* parent)
: SimpleTreeModel<RadioItem>(new RadioItem(NULL, RadioItem::Type_Root), parent)
{
Q_ASSERT(sServices.isEmpty());
root_->lazy_loaded = true;
LastFMService* lastfm = new LastFMService(this);
services_ << lastfm;
lastfm->CreateRootItem(root_);
AddService(new LastFMService(this));
}
void RadioModel::AddService(RadioService *service) {
sServices[service->name()] = service;
service->CreateRootItem(root_);
connect(service, SIGNAL(LoadingStarted()), SIGNAL(LoadingStarted()));
connect(service, SIGNAL(LoadingFinished()), SIGNAL(LoadingFinished()));
connect(service, SIGNAL(StreamReady(QUrl,QUrl)), SIGNAL(StreamReady(QUrl,QUrl)));
connect(service, SIGNAL(StreamFinished()), SIGNAL(StreamFinished()));
connect(service, SIGNAL(StreamError(QString)), SIGNAL(StreamError(QString)));
}
RadioService* RadioModel::ServiceByName(const QString& name) {
if (sServices.contains(name))
return sServices[name];
return NULL;
}
QVariant RadioModel::data(const QModelIndex& index, int role) const {
@ -43,3 +66,44 @@ void RadioModel::LazyPopulate(RadioItem* parent) {
if (parent->service)
parent->service->LazyPopulate(parent);
}
Qt::ItemFlags RadioModel::flags(const QModelIndex& index) const {
RadioItem* item = IndexToItem(index);
if (item->playable)
return Qt::ItemIsSelectable |
Qt::ItemIsEnabled |
Qt::ItemIsDragEnabled;
return Qt::ItemIsSelectable |
Qt::ItemIsEnabled;
}
QStringList RadioModel::mimeTypes() const {
return QStringList() << "text/uri-list";
}
QMimeData* RadioModel::mimeData(const QModelIndexList& indexes) const {
QList<QUrl> urls;
QList<RadioService*> services;
foreach (const QModelIndex& index, indexes) {
RadioItem* item = IndexToItem(index);
if (!item || !item->service || !item->playable)
continue;
QList<QUrl> service_urls(item->service->UrlsForItem(item));
foreach (const QUrl& url, service_urls) {
urls << url;
services << item->service;
}
}
if (urls.isEmpty())
return NULL;
RadioMimeData* data = new RadioMimeData;
data->setUrls(urls);
data->services = services;
return data;
}

View File

@ -7,6 +7,8 @@
class RadioService;
class RadioModel : public SimpleTreeModel<RadioItem> {
Q_OBJECT
public:
RadioModel(QObject* parent = 0);
@ -16,17 +18,31 @@ class RadioModel : public SimpleTreeModel<RadioItem> {
Role_Key,
};
// Needs to be static for RadioPlaylistItem::restore
static RadioService* ServiceByName(const QString& name);
// QAbstractItemModel
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
Qt::ItemFlags flags(const QModelIndex& index) const;
QStringList mimeTypes() const;
QMimeData* mimeData(const QModelIndexList& indexes) const;
signals:
void LoadingStarted();
void LoadingFinished();
void StreamReady(const QUrl& original_url, const QUrl& media_url);
void StreamFinished();
void StreamError(const QString& message);
protected:
void LazyPopulate(RadioItem* parent);
private:
QVariant data(const RadioItem* item, int role) const;
void AddService(RadioService* service);
private:
QList<RadioService*> services_;
static QMap<QString, RadioService*> sServices;
};
#endif // RADIOMODEL_H

59
src/radioplaylistitem.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "radioplaylistitem.h"
#include "radioservice.h"
#include "radiomodel.h"
#include <QSettings>
RadioPlaylistItem::RadioPlaylistItem()
: service_(NULL)
{
}
RadioPlaylistItem::RadioPlaylistItem(RadioService* service, const QUrl& url)
: service_(service),
url_(url)
{
}
void RadioPlaylistItem::Save(QSettings& settings) const {
settings.setValue("service", service_->name());
settings.setValue("url", url_.toString());
}
void RadioPlaylistItem::Restore(const QSettings& settings) {
service_ = RadioModel::ServiceByName(settings.value("service").toString());
url_ = settings.value("url").toString();
}
QString RadioPlaylistItem::Title() const {
if (service_)
return url_.toString();
return "Radio service couldn't be loaded :-(";
}
QString RadioPlaylistItem::Artist() const {
return QString::null;
}
QString RadioPlaylistItem::Album() const {
return QString::null;
}
int RadioPlaylistItem::Length() const {
return -1;
}
int RadioPlaylistItem::Track() const {
return -1;
}
bool RadioPlaylistItem::StartLoading() {
if (service_)
service_->StartLoading(url_);
return false;
}
QUrl RadioPlaylistItem::Url() {
return url_;
}

34
src/radioplaylistitem.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef RADIOPLAYLISTITEM_H
#define RADIOPLAYLISTITEM_H
#include "playlistitem.h"
#include <QUrl>
class RadioService;
class RadioPlaylistItem : public PlaylistItem {
public:
RadioPlaylistItem();
RadioPlaylistItem(RadioService* service, const QUrl& url);
Type type() const { return Type_Radio; }
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;
bool StartLoading();
QUrl Url();
private:
RadioService* service_;
QUrl url_;
};
#endif // RADIOPLAYLISTITEM_H

View File

@ -2,6 +2,8 @@
#define RADIOSERVICE_H
#include <QObject>
#include <QList>
#include <QUrl>
class RadioItem;
@ -17,6 +19,16 @@ class RadioService : public QObject {
virtual RadioItem* CreateRootItem(RadioItem* parent) = 0;
virtual void LazyPopulate(RadioItem* item) = 0;
virtual QList<QUrl> UrlsForItem(RadioItem* item) = 0;
virtual void StartLoading(const QUrl& url) = 0;
signals:
void LoadingStarted();
void LoadingFinished();
void StreamReady(const QUrl& original_url, const QUrl& media_url);
void StreamFinished();
void StreamError(const QString& message);
private:
QString name_;
};

View File

@ -1 +0,0 @@
#include "songmimedata.h"

View File

@ -2,7 +2,9 @@
# Project created by QtCreator 2009-12-15T18:38:35
# -------------------------------------------------
QT += sql \
network opengl xml
network \
opengl \
xml
TARGET = tangerine
TEMPLATE = app
SOURCES += main.cpp \
@ -24,7 +26,6 @@ SOURCES += main.cpp \
backgroundthread.cpp \
librarywatcher.cpp \
song.cpp \
songmimedata.cpp \
songplaylistitem.cpp \
libraryview.cpp \
libraryconfig.cpp \
@ -38,7 +39,9 @@ SOURCES += main.cpp \
lastfmservice.cpp \
radiomodel.cpp \
lastfmconfig.cpp \
busyindicator.cpp
busyindicator.cpp \
radioplaylistitem.cpp \
radioloadingindicator.cpp
HEADERS += mainwindow.h \
player.h \
library.h \
@ -76,11 +79,15 @@ HEADERS += mainwindow.h \
simpletreemodel.h \
radiomodel.h \
lastfmconfig.h \
busyindicator.h
busyindicator.h \
radiomimedata.h \
radioplaylistitem.h \
radioloadingindicator.h
FORMS += mainwindow.ui \
libraryconfig.ui \
fileview.ui \
lastfmconfig.ui
lastfmconfig.ui \
radioloadingindicator.ui
RESOURCES += ../data/data.qrc
OTHER_FILES += ../data/schema.sql \
../data/mainwindow.css