Load the list of sky.fm/di.fm streams using the undocumented API, getting artwork for each stream as well.
This commit is contained in:
parent
37eeb70e3a
commit
2b6beb7417
@ -370,7 +370,6 @@ set(HEADERS
|
|||||||
engines/gstenginepipeline.h
|
engines/gstenginepipeline.h
|
||||||
engines/gstelementdeleter.h
|
engines/gstelementdeleter.h
|
||||||
|
|
||||||
globalsearch/librarysearchprovider.h
|
|
||||||
globalsearch/globalsearch.h
|
globalsearch/globalsearch.h
|
||||||
globalsearch/globalsearchpopup.h
|
globalsearch/globalsearchpopup.h
|
||||||
globalsearch/globalsearchsettingspage.h
|
globalsearch/globalsearchsettingspage.h
|
||||||
|
@ -24,8 +24,8 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
|
|||||||
: SimpleSearchProvider(parent),
|
: SimpleSearchProvider(parent),
|
||||||
service_(service)
|
service_(service)
|
||||||
{
|
{
|
||||||
Init(service_->name(), service->url_scheme(), service_->icon());
|
Init(service_->name(), service->url_scheme(), service_->icon(),
|
||||||
icon_ = ScaleAndPad(QImage(service_->icon_path()));
|
ArtIsInSongMetadata);
|
||||||
|
|
||||||
set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm"
|
set_safe_words(QStringList() << "sky.fm" << "skyfm" << "di.fm" << "difm"
|
||||||
<< "digitallyimported");
|
<< "digitallyimported");
|
||||||
@ -33,21 +33,14 @@ DigitallyImportedSearchProvider::DigitallyImportedSearchProvider(
|
|||||||
connect(service_, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems()));
|
connect(service_, SIGNAL(StreamsChanged()), SLOT(MaybeRecreateItems()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedSearchProvider::LoadArtAsync(int id, const Result& result) {
|
|
||||||
emit ArtLoaded(id, icon_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DigitallyImportedSearchProvider::RecreateItems() {
|
void DigitallyImportedSearchProvider::RecreateItems() {
|
||||||
QList<Item> items;
|
QList<Item> items;
|
||||||
|
|
||||||
DigitallyImportedServiceBase::StreamList streams = service_->Streams();
|
DigitallyImportedClient::ChannelList channels = service_->Channels();
|
||||||
|
|
||||||
foreach (const DigitallyImportedServiceBase::Stream& stream, streams) {
|
foreach (const DigitallyImportedClient::Channel& channel, channels) {
|
||||||
Song song;
|
Song song;
|
||||||
song.set_title(stream.name_);
|
service_->SongFromChannel(channel, &song);
|
||||||
song.set_artist(service_->service_description());
|
|
||||||
song.set_url(QUrl(service_->url_scheme() + "://" + stream.key_));
|
|
||||||
|
|
||||||
items << Item(song);
|
items << Item(song);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,14 +27,11 @@ public:
|
|||||||
DigitallyImportedSearchProvider(DigitallyImportedServiceBase* service,
|
DigitallyImportedSearchProvider(DigitallyImportedServiceBase* service,
|
||||||
QObject* parent);
|
QObject* parent);
|
||||||
|
|
||||||
void LoadArtAsync(int id, const Result& result);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void RecreateItems();
|
void RecreateItems();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DigitallyImportedServiceBase* service_;
|
DigitallyImportedServiceBase* service_;
|
||||||
QImage icon_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // DIGITALLYIMPORTEDSEARCHPROVIDER_H
|
#endif // DIGITALLYIMPORTEDSEARCHPROVIDER_H
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "librarysearchprovider.h"
|
#include "librarysearchprovider.h"
|
||||||
#include "globalsearch.h"
|
#include "globalsearch.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
|
#include "covers/albumcoverloader.h"
|
||||||
|
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
@ -29,8 +30,17 @@ const char* GlobalSearch::kSettingsGroup = "GlobalSearch";
|
|||||||
|
|
||||||
GlobalSearch::GlobalSearch(QObject* parent)
|
GlobalSearch::GlobalSearch(QObject* parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
next_id_(1)
|
next_id_(1),
|
||||||
|
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
||||||
{
|
{
|
||||||
|
cover_loader_->Start(true);
|
||||||
|
cover_loader_->Worker()->SetDesiredHeight(SearchProvider::kArtHeight);
|
||||||
|
cover_loader_->Worker()->SetPadOutputImage(true);
|
||||||
|
cover_loader_->Worker()->SetScaleOutputImage(true);
|
||||||
|
|
||||||
|
connect(cover_loader_->Worker().get(),
|
||||||
|
SIGNAL(ImageLoaded(quint64,QImage)),
|
||||||
|
SLOT(AlbumArtLoaded(quint64,QImage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GlobalSearch::AddProvider(SearchProvider* provider, bool enable_by_default) {
|
void GlobalSearch::AddProvider(SearchProvider* provider, bool enable_by_default) {
|
||||||
@ -169,7 +179,10 @@ int GlobalSearch::LoadArtAsync(const SearchProvider::Result& result) {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result.provider_->wants_serialised_art()) {
|
if (result.provider_->art_is_in_song_metadata()) {
|
||||||
|
quint64 loader_id = cover_loader_->Worker()->LoadImageAsync(result.metadata_);
|
||||||
|
cover_loader_tasks_[loader_id] = id;
|
||||||
|
} else if (result.provider_->wants_serialised_art()) {
|
||||||
QueuedArt request;
|
QueuedArt request;
|
||||||
request.id_ = id;
|
request.id_ = id;
|
||||||
request.result_ = result;
|
request.result_ = result;
|
||||||
@ -199,6 +212,18 @@ void GlobalSearch::TakeNextQueuedArt(SearchProvider* provider) {
|
|||||||
|
|
||||||
void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
|
void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
|
||||||
SearchProvider* provider = static_cast<SearchProvider*>(sender());
|
SearchProvider* provider = static_cast<SearchProvider*>(sender());
|
||||||
|
HandleLoadedArt(id, image, provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalSearch::AlbumArtLoaded(quint64 id, const QImage& image) {
|
||||||
|
if (!cover_loader_tasks_.contains(id))
|
||||||
|
return;
|
||||||
|
int orig_id = cover_loader_tasks_.take(id);
|
||||||
|
|
||||||
|
HandleLoadedArt(orig_id, image, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalSearch::HandleLoadedArt(int id, const QImage& image, SearchProvider* provider) {
|
||||||
const QString key = pending_art_searches_.take(id);
|
const QString key = pending_art_searches_.take(id);
|
||||||
|
|
||||||
QPixmap pixmap = QPixmap::fromImage(image);
|
QPixmap pixmap = QPixmap::fromImage(image);
|
||||||
@ -206,7 +231,8 @@ void GlobalSearch::ArtLoadedSlot(int id, const QImage& image) {
|
|||||||
|
|
||||||
emit ArtLoaded(id, pixmap);
|
emit ArtLoaded(id, pixmap);
|
||||||
|
|
||||||
if (providers_.contains(provider) &&
|
if (provider &&
|
||||||
|
providers_.contains(provider) &&
|
||||||
!providers_[provider].queued_art_.isEmpty()) {
|
!providers_[provider].queued_art_.isEmpty()) {
|
||||||
providers_[provider].queued_art_.removeFirst();
|
providers_[provider].queued_art_.removeFirst();
|
||||||
TakeNextQueuedArt(provider);
|
TakeNextQueuedArt(provider);
|
||||||
|
@ -22,8 +22,11 @@
|
|||||||
#include <QPixmapCache>
|
#include <QPixmapCache>
|
||||||
|
|
||||||
#include "searchprovider.h"
|
#include "searchprovider.h"
|
||||||
|
#include "core/backgroundthread.h"
|
||||||
|
|
||||||
|
|
||||||
|
class AlbumCoverLoader;
|
||||||
|
|
||||||
class GlobalSearch : public QObject {
|
class GlobalSearch : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@ -74,10 +77,12 @@ private slots:
|
|||||||
void SearchFinishedSlot(int id);
|
void SearchFinishedSlot(int id);
|
||||||
|
|
||||||
void ArtLoadedSlot(int id, const QImage& image);
|
void ArtLoadedSlot(int id, const QImage& image);
|
||||||
|
void AlbumArtLoaded(quint64 id, const QImage& image);
|
||||||
|
|
||||||
void ProviderDestroyedSlot(QObject* object);
|
void ProviderDestroyedSlot(QObject* object);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void HandleLoadedArt(int id, const QImage& image, SearchProvider* provider);
|
||||||
void TakeNextQueuedArt(SearchProvider* provider);
|
void TakeNextQueuedArt(SearchProvider* provider);
|
||||||
QString PixmapCacheKey(const SearchProvider::Result& result) const;
|
QString PixmapCacheKey(const SearchProvider::Result& result) const;
|
||||||
|
|
||||||
@ -111,6 +116,10 @@ private:
|
|||||||
QMap<int, QString> pending_art_searches_;
|
QMap<int, QString> pending_art_searches_;
|
||||||
|
|
||||||
QMap<QString, bool> providers_state_preference_;
|
QMap<QString, bool> providers_state_preference_;
|
||||||
|
|
||||||
|
// Used for providers with ArtIsInSongMetadata set.
|
||||||
|
BackgroundThread<AlbumCoverLoader>* cover_loader_;
|
||||||
|
QMap<quint64, int> cover_loader_tasks_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GLOBALSEARCH_H
|
#endif // GLOBALSEARCH_H
|
||||||
|
@ -30,19 +30,9 @@ LibrarySearchProvider::LibrarySearchProvider(LibraryBackendInterface* backend,
|
|||||||
const QIcon& icon,
|
const QIcon& icon,
|
||||||
QObject* parent)
|
QObject* parent)
|
||||||
: BlockingSearchProvider(parent),
|
: BlockingSearchProvider(parent),
|
||||||
backend_(backend),
|
backend_(backend)
|
||||||
cover_loader_(new BackgroundThreadImplementation<AlbumCoverLoader, AlbumCoverLoader>(this))
|
|
||||||
{
|
{
|
||||||
Init(name, id, icon, WantsSerialisedArtQueries);
|
Init(name, id, icon, WantsSerialisedArtQueries | ArtIsInSongMetadata);
|
||||||
|
|
||||||
cover_loader_->Start(true);
|
|
||||||
cover_loader_->Worker()->SetDesiredHeight(kArtHeight);
|
|
||||||
cover_loader_->Worker()->SetPadOutputImage(true);
|
|
||||||
cover_loader_->Worker()->SetScaleOutputImage(true);
|
|
||||||
|
|
||||||
connect(cover_loader_->Worker().get(),
|
|
||||||
SIGNAL(ImageLoaded(quint64,QImage)),
|
|
||||||
SLOT(AlbumArtLoaded(quint64,QImage)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
|
SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString& query) {
|
||||||
@ -114,19 +104,6 @@ SearchProvider::ResultList LibrarySearchProvider::Search(int id, const QString&
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LibrarySearchProvider::LoadArtAsync(int id, const Result& result) {
|
|
||||||
quint64 loader_id = cover_loader_->Worker()->LoadImageAsync(result.metadata_);
|
|
||||||
cover_loader_tasks_[loader_id] = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LibrarySearchProvider::AlbumArtLoaded(quint64 id, const QImage& image) {
|
|
||||||
if (!cover_loader_tasks_.contains(id))
|
|
||||||
return;
|
|
||||||
int orig_id = cover_loader_tasks_.take(id);
|
|
||||||
|
|
||||||
emit ArtLoaded(orig_id, image);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
void LibrarySearchProvider::LoadTracksAsync(int id, const Result& result) {
|
||||||
SongList ret;
|
SongList ret;
|
||||||
|
|
||||||
|
@ -26,24 +26,15 @@ class LibraryBackendInterface;
|
|||||||
|
|
||||||
|
|
||||||
class LibrarySearchProvider : public BlockingSearchProvider {
|
class LibrarySearchProvider : public BlockingSearchProvider {
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
LibrarySearchProvider(LibraryBackendInterface* backend, const QString& name,
|
||||||
const QString& id, const QIcon& icon, QObject* parent = 0);
|
const QString& id, const QIcon& icon, QObject* parent = 0);
|
||||||
|
|
||||||
ResultList Search(int id, const QString& query);
|
ResultList Search(int id, const QString& query);
|
||||||
void LoadArtAsync(int id, const Result& result);
|
|
||||||
void LoadTracksAsync(int id, const Result& result);
|
void LoadTracksAsync(int id, const Result& result);
|
||||||
|
|
||||||
private slots:
|
|
||||||
void AlbumArtLoaded(quint64 id, const QImage& image);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LibraryBackendInterface* backend_;
|
LibraryBackendInterface* backend_;
|
||||||
|
|
||||||
BackgroundThread<AlbumCoverLoader>* cover_loader_;
|
|
||||||
QMap<quint64, int> cover_loader_tasks_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LIBRARYSEARCHPROVIDER_H
|
#endif // LIBRARYSEARCHPROVIDER_H
|
||||||
|
@ -137,3 +137,7 @@ namespace {
|
|||||||
void SearchProvider::SortSongs(SongList* list) {
|
void SearchProvider::SortSongs(SongList* list) {
|
||||||
qStableSort(list->begin(), list->end(), SortSongsCompare);
|
qStableSort(list->begin(), list->end(), SortSongsCompare);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchProvider::LoadArtAsync(int id, const Result& result) {
|
||||||
|
emit ArtLoaded(id, QImage());
|
||||||
|
}
|
||||||
|
@ -75,7 +75,13 @@ public:
|
|||||||
// If a third-party application is making art requests over dbus and has
|
// If a third-party application is making art requests over dbus and has
|
||||||
// to get all the art it can before showing results to the user, it might
|
// to get all the art it can before showing results to the user, it might
|
||||||
// not load art from this provider.
|
// not load art from this provider.
|
||||||
ArtIsProbablyRemote = 0x04
|
ArtIsProbablyRemote = 0x04,
|
||||||
|
|
||||||
|
// Indicates the art URL (or filename) for each result is stored in the
|
||||||
|
// normal place in the song metadata. LoadArtAsync will never be called and
|
||||||
|
// WantsSerialisedArtQueries and ArtIsProbablyRemote will be ignored if
|
||||||
|
// they are set as well. The GlobalSearch engine will load the art itself.
|
||||||
|
ArtIsInSongMetadata = 0x08
|
||||||
};
|
};
|
||||||
Q_DECLARE_FLAGS(Hints, Hint)
|
Q_DECLARE_FLAGS(Hints, Hint)
|
||||||
|
|
||||||
@ -87,6 +93,7 @@ public:
|
|||||||
bool wants_delayed_queries() const { return hints() & WantsDelayedQueries; }
|
bool wants_delayed_queries() const { return hints() & WantsDelayedQueries; }
|
||||||
bool wants_serialised_art() const { return hints() & WantsSerialisedArtQueries; }
|
bool wants_serialised_art() const { return hints() & WantsSerialisedArtQueries; }
|
||||||
bool art_is_probably_remote() const { return hints() & ArtIsProbablyRemote; }
|
bool art_is_probably_remote() const { return hints() & ArtIsProbablyRemote; }
|
||||||
|
bool art_is_in_song_metadata() const { return hints() & ArtIsInSongMetadata; }
|
||||||
|
|
||||||
// Starts a search. Must emit ResultsAvailable zero or more times and then
|
// Starts a search. Must emit ResultsAvailable zero or more times and then
|
||||||
// SearchFinished exactly once, using this ID.
|
// SearchFinished exactly once, using this ID.
|
||||||
@ -94,7 +101,7 @@ public:
|
|||||||
|
|
||||||
// Starts loading an icon for a result that was previously emitted by
|
// Starts loading an icon for a result that was previously emitted by
|
||||||
// ResultsAvailable. Must emit ArtLoaded exactly once with this ID.
|
// ResultsAvailable. Must emit ArtLoaded exactly once with this ID.
|
||||||
virtual void LoadArtAsync(int id, const Result& result) = 0;
|
virtual void LoadArtAsync(int id, const Result& result);
|
||||||
|
|
||||||
// Starts loading tracks for a result that was previously emitted by
|
// Starts loading tracks for a result that was previously emitted by
|
||||||
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
// ResultsAvailable. Must emit TracksLoaded exactly once with this ID.
|
||||||
|
@ -33,6 +33,9 @@ const char* DigitallyImportedClient::kApiPassword = "dayeiph0ne@pp";
|
|||||||
const char* DigitallyImportedClient::kAuthUrl =
|
const char* DigitallyImportedClient::kAuthUrl =
|
||||||
"http://api.audioaddict.com/%1/premium/auth";
|
"http://api.audioaddict.com/%1/premium/auth";
|
||||||
|
|
||||||
|
const char* DigitallyImportedClient::kChannelListUrl =
|
||||||
|
"http://api.v2.audioaddict.com/v1/%1/mobile/batch_update?asset_group_key=mobile_icons&stream_set_key=";
|
||||||
|
|
||||||
|
|
||||||
DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent)
|
DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QObject* parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
@ -41,12 +44,16 @@ DigitallyImportedClient::DigitallyImportedClient(const QString& service_name, QO
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DigitallyImportedClient::SetAuthorisationHeader(QNetworkRequest* req) const {
|
||||||
|
req->setRawHeader("Authorization",
|
||||||
|
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
|
||||||
|
.toAscii().toBase64());
|
||||||
|
}
|
||||||
|
|
||||||
QNetworkReply* DigitallyImportedClient::Auth(const QString& username,
|
QNetworkReply* DigitallyImportedClient::Auth(const QString& username,
|
||||||
const QString& password) {
|
const QString& password) {
|
||||||
QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_)));
|
QNetworkRequest req(QUrl(QString(kAuthUrl).arg(service_name_)));
|
||||||
req.setRawHeader("Authorization",
|
SetAuthorisationHeader(&req);
|
||||||
"Basic " + QString("%1:%2").arg(kApiUsername, kApiPassword)
|
|
||||||
.toAscii().toBase64());
|
|
||||||
|
|
||||||
QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) +
|
QByteArray postdata = "username=" + QUrl::toPercentEncoding(username) +
|
||||||
"&password=" + QUrl::toPercentEncoding(password);
|
"&password=" + QUrl::toPercentEncoding(password);
|
||||||
@ -83,3 +90,65 @@ DigitallyImportedClient::ParseAuthReply(QNetworkReply* reply) const {
|
|||||||
ret.listen_hash_ = user["listen_hash"].toString();
|
ret.listen_hash_ = user["listen_hash"].toString();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QNetworkReply* DigitallyImportedClient::GetChannelList() {
|
||||||
|
//QNetworkRequest req(QUrl(QString(kChannelListUrl)));
|
||||||
|
QNetworkRequest req(QUrl(QString(kChannelListUrl).arg(service_name_)));
|
||||||
|
SetAuthorisationHeader(&req);
|
||||||
|
|
||||||
|
return network_->get(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
DigitallyImportedClient::ChannelList
|
||||||
|
DigitallyImportedClient::ParseChannelList(QNetworkReply* reply) const {
|
||||||
|
ChannelList ret;
|
||||||
|
|
||||||
|
QJson::Parser parser;
|
||||||
|
QVariantMap data = parser.parse(reply).toMap();
|
||||||
|
|
||||||
|
if (!data.contains("channel_filters"))
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
QVariantList filters = data["channel_filters"].toList();
|
||||||
|
|
||||||
|
foreach (const QVariant& filter, filters) {
|
||||||
|
// Find the filter called "All"
|
||||||
|
QVariantMap filter_map = filter.toMap();
|
||||||
|
if (filter_map.value("name", QString()).toString() != "All")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Add all its stations to the result
|
||||||
|
QVariantList channels = filter_map.value("channels", QVariantList()).toList();
|
||||||
|
foreach (const QVariant& channel_var, channels) {
|
||||||
|
QVariantMap channel_map = channel_var.toMap();
|
||||||
|
|
||||||
|
Channel channel;
|
||||||
|
channel.art_url_ = QUrl(channel_map.value("asset_url").toString());
|
||||||
|
channel.description_ = channel_map.value("description").toString();
|
||||||
|
channel.director_ = channel_map.value("channel_director").toString();
|
||||||
|
channel.key_ = channel_map.value("key").toString();
|
||||||
|
channel.name_ = channel_map.value("name").toString();
|
||||||
|
ret << channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DigitallyImportedClient::Channel::Load(const QSettings& s) {
|
||||||
|
art_url_ = s.value("art_url").toUrl();
|
||||||
|
director_ = s.value("director").toString();
|
||||||
|
description_ = s.value("description").toString();
|
||||||
|
name_ = s.value("name").toString();
|
||||||
|
key_ = s.value("key").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DigitallyImportedClient::Channel::Save(QSettings* s) const {
|
||||||
|
s->setValue("art_url", art_url_);
|
||||||
|
s->setValue("director", director_);
|
||||||
|
s->setValue("description", description_);
|
||||||
|
s->setValue("name", name_);
|
||||||
|
s->setValue("key", key_);
|
||||||
|
}
|
||||||
|
@ -20,9 +20,12 @@
|
|||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
class QNetworkAccessManager;
|
class QNetworkAccessManager;
|
||||||
class QNetworkReply;
|
class QNetworkReply;
|
||||||
|
class QNetworkRequest;
|
||||||
|
|
||||||
class DigitallyImportedClient : public QObject {
|
class DigitallyImportedClient : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@ -33,6 +36,7 @@ public:
|
|||||||
static const char* kApiUsername;
|
static const char* kApiUsername;
|
||||||
static const char* kApiPassword;
|
static const char* kApiPassword;
|
||||||
static const char* kAuthUrl;
|
static const char* kAuthUrl;
|
||||||
|
static const char* kChannelListUrl;
|
||||||
|
|
||||||
struct AuthReply {
|
struct AuthReply {
|
||||||
bool success_;
|
bool success_;
|
||||||
@ -47,9 +51,30 @@ public:
|
|||||||
QString listen_hash_;
|
QString listen_hash_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Channel {
|
||||||
|
QUrl art_url_;
|
||||||
|
|
||||||
|
QString director_;
|
||||||
|
QString description_;
|
||||||
|
QString name_;
|
||||||
|
QString key_;
|
||||||
|
|
||||||
|
void Save(QSettings* s) const;
|
||||||
|
void Load(const QSettings& s);
|
||||||
|
|
||||||
|
bool operator <(const Channel& other) const { return name_ < other.name_; }
|
||||||
|
};
|
||||||
|
typedef QList<Channel> ChannelList;
|
||||||
|
|
||||||
QNetworkReply* Auth(const QString& username, const QString& password);
|
QNetworkReply* Auth(const QString& username, const QString& password);
|
||||||
AuthReply ParseAuthReply(QNetworkReply* reply) const;
|
AuthReply ParseAuthReply(QNetworkReply* reply) const;
|
||||||
|
|
||||||
|
QNetworkReply* GetChannelList();
|
||||||
|
ChannelList ParseChannelList(QNetworkReply* reply) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetAuthorisationHeader(QNetworkRequest* req) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager* network_;
|
QNetworkAccessManager* network_;
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "digitallyimportedservicebase.h"
|
#include "digitallyimportedservicebase.h"
|
||||||
#include "digitallyimportedurlhandler.h"
|
#include "digitallyimportedurlhandler.h"
|
||||||
#include "internetmodel.h"
|
#include "internetmodel.h"
|
||||||
|
#include "core/closure.h"
|
||||||
#include "core/logging.h"
|
#include "core/logging.h"
|
||||||
#include "core/network.h"
|
#include "core/network.h"
|
||||||
#include "core/player.h"
|
#include "core/player.h"
|
||||||
@ -44,7 +45,6 @@ DigitallyImportedServiceBase::DigitallyImportedServiceBase(
|
|||||||
url_handler_(new DigitallyImportedUrlHandler(this)),
|
url_handler_(new DigitallyImportedUrlHandler(this)),
|
||||||
basic_audio_type_(1),
|
basic_audio_type_(1),
|
||||||
premium_audio_type_(2),
|
premium_audio_type_(2),
|
||||||
task_id_(-1),
|
|
||||||
root_(NULL),
|
root_(NULL),
|
||||||
context_item_(NULL),
|
context_item_(NULL),
|
||||||
api_client_(NULL)
|
api_client_(NULL)
|
||||||
@ -98,7 +98,7 @@ void DigitallyImportedServiceBase::LazyPopulate(QStandardItem* parent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::RefreshStreams() {
|
void DigitallyImportedServiceBase::RefreshStreams() {
|
||||||
if (IsStreamListStale()) {
|
if (IsChannelListStale()) {
|
||||||
ForceRefreshStreams();
|
ForceRefreshStreams();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -107,61 +107,25 @@ void DigitallyImportedServiceBase::RefreshStreams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::ForceRefreshStreams() {
|
void DigitallyImportedServiceBase::ForceRefreshStreams() {
|
||||||
if (task_id_ != -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
qLog(Info) << "Getting stream list from" << stream_list_url_;
|
|
||||||
|
|
||||||
// Get the list of streams
|
|
||||||
QNetworkReply* reply = network_->get(QNetworkRequest(stream_list_url_));
|
|
||||||
connect(reply, SIGNAL(finished()), SLOT(RefreshStreamsFinished()));
|
|
||||||
|
|
||||||
// Start a task to tell the user we're busy
|
// Start a task to tell the user we're busy
|
||||||
task_id_ = model()->task_manager()->StartTask(tr("Getting streams"));
|
int task_id = model()->task_manager()->StartTask(tr("Getting streams"));
|
||||||
|
|
||||||
|
QNetworkReply* reply = api_client_->GetChannelList();
|
||||||
|
NewClosure(reply, SIGNAL(finished()),
|
||||||
|
this, SLOT(RefreshStreamsFinished(QNetworkReply*,int)),
|
||||||
|
reply, task_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::RefreshStreamsFinished() {
|
void DigitallyImportedServiceBase::RefreshStreamsFinished(QNetworkReply* reply, int task_id) {
|
||||||
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
|
model()->task_manager()->SetTaskFinished(task_id);
|
||||||
if (!reply) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
model()->task_manager()->SetTaskFinished(task_id_);
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
const QString data = QString::fromUtf8(reply->readAll());
|
saved_channels_ = api_client_->ParseChannelList(reply);
|
||||||
|
|
||||||
// Poor man's JSON parser that's good enough for the stream lists and means
|
|
||||||
// we don't have to pull in QJSON as a dependency.
|
|
||||||
const QRegExp re("\\{"
|
|
||||||
"\"id\":(\\d+),"
|
|
||||||
"\"key\":\"([^\"]+)\","
|
|
||||||
"\"name\":\"([^\"]+)\","
|
|
||||||
"\"description\":\"([^\"]+)\"");
|
|
||||||
|
|
||||||
saved_streams_.clear();
|
|
||||||
|
|
||||||
int pos = 0;
|
|
||||||
while (pos >= 0) {
|
|
||||||
pos = re.indexIn(data, pos);
|
|
||||||
if (pos == -1) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
pos += re.matchedLength();
|
|
||||||
|
|
||||||
Stream stream;
|
|
||||||
stream.id_ = re.cap(1).toInt();
|
|
||||||
stream.key_ = re.cap(2).replace("\\/", "/");
|
|
||||||
stream.name_ = re.cap(3).replace("\\/", "/");
|
|
||||||
stream.description_ = re.cap(4).replace("\\/", "/");
|
|
||||||
saved_streams_ << stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by name
|
// Sort by name
|
||||||
qSort(saved_streams_);
|
qSort(saved_channels_);
|
||||||
|
|
||||||
SaveStreams(saved_streams_);
|
SaveChannels(saved_channels_);
|
||||||
PopulateStreams();
|
PopulateStreams();
|
||||||
|
|
||||||
emit StreamsChanged();
|
emit StreamsChanged();
|
||||||
@ -172,21 +136,27 @@ void DigitallyImportedServiceBase::PopulateStreams() {
|
|||||||
root_->removeRows(0, root_->rowCount());
|
root_->removeRows(0, root_->rowCount());
|
||||||
|
|
||||||
// Add each stream to the model
|
// Add each stream to the model
|
||||||
foreach (const Stream& stream, saved_streams_) {
|
foreach (const DigitallyImportedClient::Channel& channel, saved_channels_) {
|
||||||
Song song;
|
Song song;
|
||||||
song.set_title(stream.name_);
|
SongFromChannel(channel, &song);
|
||||||
song.set_artist(service_description_);
|
|
||||||
song.set_url(QUrl(url_scheme_ + "://" + stream.key_));
|
|
||||||
|
|
||||||
QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"),
|
QStandardItem* item = new QStandardItem(QIcon(":/last.fm/icon_radio.png"),
|
||||||
stream.name_);
|
song.title());
|
||||||
item->setData(stream.description_, Qt::ToolTipRole);
|
item->setData(channel.description_, Qt::ToolTipRole);
|
||||||
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
|
item->setData(InternetModel::PlayBehaviour_SingleItem, InternetModel::Role_PlayBehaviour);
|
||||||
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
|
item->setData(QVariant::fromValue(song), InternetModel::Role_SongMetadata);
|
||||||
root_->appendRow(item);
|
root_->appendRow(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DigitallyImportedServiceBase::SongFromChannel(
|
||||||
|
const DigitallyImportedClient::Channel& channel, Song* song) const {
|
||||||
|
song->set_title(channel.name_);
|
||||||
|
song->set_artist(service_description_ + " - " + channel.director_);
|
||||||
|
song->set_url(QUrl(url_scheme_ + "://" + channel.key_));
|
||||||
|
song->set_art_automatic(channel.art_url_.toString());
|
||||||
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::Homepage() {
|
void DigitallyImportedServiceBase::Homepage() {
|
||||||
QDesktopServices::openUrl(homepage_url_);
|
QDesktopServices::openUrl(homepage_url_);
|
||||||
}
|
}
|
||||||
@ -199,8 +169,8 @@ void DigitallyImportedServiceBase::ReloadSettings() {
|
|||||||
premium_audio_type_ = s.value("premium_audio_type", 2).toInt();
|
premium_audio_type_ = s.value("premium_audio_type", 2).toInt();
|
||||||
username_ = s.value("username").toString();
|
username_ = s.value("username").toString();
|
||||||
listen_hash_ = s.value("listen_hash").toString();
|
listen_hash_ = s.value("listen_hash").toString();
|
||||||
last_refreshed_streams_ = s.value("last_refreshed_" + url_scheme_).toDateTime();
|
last_refreshed_channels_ = s.value("last_refreshed_v2_" + url_scheme_).toDateTime();
|
||||||
saved_streams_ = LoadStreams();
|
saved_channels_ = LoadChannels();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::ShowContextMenu(
|
void DigitallyImportedServiceBase::ShowContextMenu(
|
||||||
@ -248,8 +218,8 @@ void DigitallyImportedServiceBase::ShowSettingsDialog() {
|
|||||||
emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported);
|
emit OpenSettingsAtPage(SettingsDialog::Page_DigitallyImported);
|
||||||
}
|
}
|
||||||
|
|
||||||
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStreams() const {
|
DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::LoadChannels() const {
|
||||||
StreamList ret;
|
DigitallyImportedClient::ChannelList ret;
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
@ -258,49 +228,44 @@ DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::LoadStrea
|
|||||||
for (int i=0 ; i<count ; ++i) {
|
for (int i=0 ; i<count ; ++i) {
|
||||||
s.setArrayIndex(i);
|
s.setArrayIndex(i);
|
||||||
|
|
||||||
Stream stream;
|
DigitallyImportedClient::Channel channel;
|
||||||
stream.id_ = s.value("id").toInt();
|
channel.Load(s);
|
||||||
stream.key_ = s.value("key").toString();
|
ret << channel;
|
||||||
stream.name_ = s.value("name").toString();
|
|
||||||
stream.description_ = s.value("description").toString();
|
|
||||||
ret << stream;
|
|
||||||
}
|
}
|
||||||
s.endArray();
|
s.endArray();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::SaveStreams(const StreamList& streams) {
|
void DigitallyImportedServiceBase::SaveChannels(
|
||||||
|
const DigitallyImportedClient::ChannelList& channels) {
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
|
|
||||||
s.beginWriteArray(url_scheme_, streams.count());
|
s.beginWriteArray(url_scheme_, channels.count());
|
||||||
for (int i=0 ; i<streams.count() ; ++i) {
|
for (int i=0 ; i<channels.count() ; ++i) {
|
||||||
const Stream& stream = streams[i];
|
|
||||||
s.setArrayIndex(i);
|
s.setArrayIndex(i);
|
||||||
s.setValue("id", stream.id_);
|
channels[i].Save(&s);
|
||||||
s.setValue("key", stream.key_);
|
|
||||||
s.setValue("name", stream.name_);
|
|
||||||
s.setValue("description", stream.description_);
|
|
||||||
}
|
}
|
||||||
s.endArray();
|
s.endArray();
|
||||||
|
|
||||||
last_refreshed_streams_ = QDateTime::currentDateTime();
|
last_refreshed_channels_ = QDateTime::currentDateTime();
|
||||||
s.setValue("last_refreshed_" + url_scheme_, last_refreshed_streams_);
|
s.setValue("last_refreshed_v2_" + url_scheme_, last_refreshed_channels_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DigitallyImportedServiceBase::IsStreamListStale() const {
|
bool DigitallyImportedServiceBase::IsChannelListStale() const {
|
||||||
return last_refreshed_streams_.isNull() ||
|
return last_refreshed_channels_.isNull() ||
|
||||||
last_refreshed_streams_.secsTo(QDateTime::currentDateTime()) >
|
last_refreshed_channels_.secsTo(QDateTime::currentDateTime()) >
|
||||||
kStreamsCacheDurationSecs;
|
kStreamsCacheDurationSecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
DigitallyImportedServiceBase::StreamList DigitallyImportedServiceBase::Streams() {
|
DigitallyImportedClient::ChannelList DigitallyImportedServiceBase::Channels() {
|
||||||
if (IsStreamListStale()) {
|
if (IsChannelListStale()) {
|
||||||
metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection);
|
metaObject()->invokeMethod(this, "ForceRefreshStreams", Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
return saved_streams_;
|
return saved_channels_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedServiceBase::LoadStation(const QString& key) {
|
void DigitallyImportedServiceBase::LoadStation(const QString& key) {
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#ifndef DIGITALLYIMPORTEDSERVICEBASE_H
|
#ifndef DIGITALLYIMPORTEDSERVICEBASE_H
|
||||||
#define DIGITALLYIMPORTEDSERVICEBASE_H
|
#define DIGITALLYIMPORTEDSERVICEBASE_H
|
||||||
|
|
||||||
|
#include "digitallyimportedclient.h"
|
||||||
#include "internetservice.h"
|
#include "internetservice.h"
|
||||||
|
|
||||||
#include <boost/scoped_ptr.hpp>
|
#include <boost/scoped_ptr.hpp>
|
||||||
@ -57,19 +58,10 @@ public:
|
|||||||
const QString& url_scheme() const { return url_scheme_; }
|
const QString& url_scheme() const { return url_scheme_; }
|
||||||
const QString& api_service_name() const { return api_service_name_; }
|
const QString& api_service_name() const { return api_service_name_; }
|
||||||
|
|
||||||
// Public for the global search provider.
|
bool IsChannelListStale() const;
|
||||||
struct Stream {
|
DigitallyImportedClient::ChannelList Channels();
|
||||||
int id_;
|
void SongFromChannel(const DigitallyImportedClient::Channel& channel,
|
||||||
QString key_;
|
Song* song) const;
|
||||||
QString name_;
|
|
||||||
QString description_;
|
|
||||||
|
|
||||||
bool operator <(const Stream& other) const { return name_ < other.name_; }
|
|
||||||
};
|
|
||||||
typedef QList<Stream> StreamList;
|
|
||||||
|
|
||||||
bool IsStreamListStale() const;
|
|
||||||
StreamList Streams();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void StreamsChanged();
|
void StreamsChanged();
|
||||||
@ -90,13 +82,13 @@ private slots:
|
|||||||
void Homepage();
|
void Homepage();
|
||||||
void ForceRefreshStreams();
|
void ForceRefreshStreams();
|
||||||
void RefreshStreams();
|
void RefreshStreams();
|
||||||
void RefreshStreamsFinished();
|
void RefreshStreamsFinished(QNetworkReply* reply, int task_id);
|
||||||
void ShowSettingsDialog();
|
void ShowSettingsDialog();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void PopulateStreams();
|
void PopulateStreams();
|
||||||
StreamList LoadStreams() const;
|
DigitallyImportedClient::ChannelList LoadChannels() const;
|
||||||
void SaveStreams(const StreamList& streams);
|
void SaveChannels(const DigitallyImportedClient::ChannelList& streams);
|
||||||
|
|
||||||
void LoadStation(const QString& key);
|
void LoadStation(const QString& key);
|
||||||
|
|
||||||
@ -122,15 +114,13 @@ private:
|
|||||||
QString username_;
|
QString username_;
|
||||||
QString listen_hash_;
|
QString listen_hash_;
|
||||||
|
|
||||||
int task_id_;
|
|
||||||
|
|
||||||
QStandardItem* root_;
|
QStandardItem* root_;
|
||||||
|
|
||||||
boost::scoped_ptr<QMenu> context_menu_;
|
boost::scoped_ptr<QMenu> context_menu_;
|
||||||
QStandardItem* context_item_;
|
QStandardItem* context_item_;
|
||||||
|
|
||||||
QList<Stream> saved_streams_;
|
DigitallyImportedClient::ChannelList saved_channels_;
|
||||||
QDateTime last_refreshed_streams_;
|
QDateTime last_refreshed_channels_;
|
||||||
|
|
||||||
DigitallyImportedClient* api_client_;
|
DigitallyImportedClient* api_client_;
|
||||||
};
|
};
|
||||||
|
@ -61,6 +61,8 @@ void DigitallyImportedSettingsPage::Login() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) {
|
void DigitallyImportedSettingsPage::LoginFinished(QNetworkReply* reply) {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply);
|
DigitallyImportedClient::AuthReply result = client_->ParseAuthReply(reply);
|
||||||
|
|
||||||
QString name = QString("%1 %2").arg(result.first_name_, result.last_name_);
|
QString name = QString("%1 %2").arg(result.first_name_, result.last_name_);
|
||||||
|
@ -111,17 +111,17 @@ msgid "%L1 total plays"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: transcoder/transcodedialog.cpp:198
|
#: transcoder/transcodedialog.cpp:198
|
||||||
#, c-format
|
#, c-format, qt-plural-format
|
||||||
msgid "%n failed"
|
msgid "%n failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: transcoder/transcodedialog.cpp:193
|
#: transcoder/transcodedialog.cpp:193
|
||||||
#, c-format
|
#, c-format, qt-plural-format
|
||||||
msgid "%n finished"
|
msgid "%n finished"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: transcoder/transcodedialog.cpp:188
|
#: transcoder/transcodedialog.cpp:188
|
||||||
#, c-format
|
#, c-format, qt-plural-format
|
||||||
msgid "%n remaining"
|
msgid "%n remaining"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -662,7 +662,7 @@ msgstr ""
|
|||||||
msgid "Authenticating..."
|
msgid "Authenticating..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedsettingspage.cpp:80
|
#: internet/digitallyimportedsettingspage.cpp:82
|
||||||
#: internet/magnatunesettingspage.cpp:113 internet/lastfmservice.cpp:434
|
#: internet/magnatunesettingspage.cpp:113 internet/lastfmservice.cpp:434
|
||||||
#: internet/lastfmsettingspage.cpp:79 remote/remotesettingspage.cpp:113
|
#: internet/lastfmsettingspage.cpp:79 remote/remotesettingspage.cpp:113
|
||||||
msgid "Authentication failed"
|
msgid "Authentication failed"
|
||||||
@ -1003,7 +1003,7 @@ msgstr ""
|
|||||||
msgid "Configure library..."
|
msgid "Configure library..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedservicebase.cpp:219
|
#: internet/digitallyimportedservicebase.cpp:189
|
||||||
#: ../bin/src/ui_globalsearchsettingspage.h:167
|
#: ../bin/src/ui_globalsearchsettingspage.h:167
|
||||||
msgid "Configure..."
|
msgid "Configure..."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1593,7 +1593,7 @@ msgstr ""
|
|||||||
msgid "Error loading %1"
|
msgid "Error loading %1"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedservicebase.cpp:241
|
#: internet/digitallyimportedservicebase.cpp:211
|
||||||
#: internet/digitallyimportedurlhandler.cpp:73
|
#: internet/digitallyimportedurlhandler.cpp:73
|
||||||
msgid "Error loading di.fm playlist"
|
msgid "Error loading di.fm playlist"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1615,7 +1615,7 @@ msgstr ""
|
|||||||
msgid "Except between tracks on the same album or in the same CUE sheet"
|
msgid "Except between tracks on the same album or in the same CUE sheet"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedsettingspage.cpp:93
|
#: internet/digitallyimportedsettingspage.cpp:95
|
||||||
#, qt-format
|
#, qt-format
|
||||||
msgid "Expires on %1"
|
msgid "Expires on %1"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -1854,7 +1854,7 @@ msgstr ""
|
|||||||
msgid "Getting channels"
|
msgid "Getting channels"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedservicebase.cpp:121
|
#: internet/digitallyimportedservicebase.cpp:111
|
||||||
msgid "Getting streams"
|
msgid "Getting streams"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -2736,7 +2736,7 @@ msgstr ""
|
|||||||
msgid "Only show the first"
|
msgid "Only show the first"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedservicebase.cpp:212
|
#: internet/digitallyimportedservicebase.cpp:182
|
||||||
#, qt-format
|
#, qt-format
|
||||||
msgid "Open %1 in browser"
|
msgid "Open %1 in browser"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@ -3146,7 +3146,7 @@ msgstr ""
|
|||||||
msgid "Refresh station list"
|
msgid "Refresh station list"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedservicebase.cpp:215
|
#: internet/digitallyimportedservicebase.cpp:185
|
||||||
msgid "Refresh streams"
|
msgid "Refresh streams"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4078,7 +4078,7 @@ msgstr ""
|
|||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: internet/digitallyimportedclient.cpp:61 internet/lastfmservice.cpp:455
|
#: internet/digitallyimportedclient.cpp:68 internet/lastfmservice.cpp:455
|
||||||
msgid "Unknown error"
|
msgid "Unknown error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4471,7 +4471,7 @@ msgid "Zero"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: playlist/playlistundocommands.cpp:37
|
#: playlist/playlistundocommands.cpp:37
|
||||||
#, c-format
|
#, c-format, qt-plural-format
|
||||||
msgid "add %n songs"
|
msgid "add %n songs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -4591,7 +4591,7 @@ msgid "press enter"
|
|||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: playlist/playlistundocommands.cpp:65 playlist/playlistundocommands.cpp:88
|
#: playlist/playlistundocommands.cpp:65 playlist/playlistundocommands.cpp:88
|
||||||
#, c-format
|
#, c-format, qt-plural-format
|
||||||
msgid "remove %n songs"
|
msgid "remove %n songs"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user