Use URL Handler for Tidal
This commit is contained in:
parent
25249be37f
commit
17fe201473
@ -192,8 +192,14 @@ set(SOURCES
|
||||
covermanager/coverfromurldialog.cpp
|
||||
covermanager/musicbrainzcoverprovider.cpp
|
||||
covermanager/discogscoverprovider.cpp
|
||||
#covermanager/amazoncoverprovider.cpp
|
||||
|
||||
|
||||
lyrics/lyricsproviders.cpp
|
||||
lyrics/lyricsprovider.cpp
|
||||
lyrics/lyricsfetcher.cpp
|
||||
lyrics/lyricsfetchersearch.cpp
|
||||
lyrics/auddlyricsprovider.cpp
|
||||
lyrics/apiseedslyricsprovider.cpp
|
||||
|
||||
settings/settingsdialog.cpp
|
||||
settings/settingspage.cpp
|
||||
settings/behavioursettingspage.cpp
|
||||
@ -258,13 +264,7 @@ set(SOURCES
|
||||
tidal/tidalsearchmodel.cpp
|
||||
tidal/tidalsearchsortmodel.cpp
|
||||
tidal/tidalsearchitemdelegate.cpp
|
||||
|
||||
lyrics/lyricsproviders.cpp
|
||||
lyrics/lyricsprovider.cpp
|
||||
lyrics/lyricsfetcher.cpp
|
||||
lyrics/lyricsfetchersearch.cpp
|
||||
lyrics/auddlyricsprovider.cpp
|
||||
lyrics/apiseedslyricsprovider.cpp
|
||||
tidal/tidalurlhandler.cpp
|
||||
|
||||
)
|
||||
|
||||
@ -359,7 +359,13 @@ set(HEADERS
|
||||
covermanager/coverfromurldialog.h
|
||||
covermanager/musicbrainzcoverprovider.h
|
||||
covermanager/discogscoverprovider.h
|
||||
#covermanager/amazoncoverprovider.h
|
||||
|
||||
lyrics/lyricsproviders.h
|
||||
lyrics/lyricsprovider.h
|
||||
lyrics/lyricsfetcher.h
|
||||
lyrics/lyricsfetchersearch.h
|
||||
lyrics/auddlyricsprovider.h
|
||||
lyrics/apiseedslyricsprovider.h
|
||||
|
||||
settings/settingsdialog.h
|
||||
settings/settingspage.h
|
||||
@ -421,13 +427,7 @@ set(HEADERS
|
||||
tidal/tidalsearch.h
|
||||
tidal/tidalsearchview.h
|
||||
tidal/tidalsearchmodel.h
|
||||
|
||||
lyrics/lyricsproviders.h
|
||||
lyrics/lyricsprovider.h
|
||||
lyrics/lyricsfetcher.h
|
||||
lyrics/lyricsfetchersearch.h
|
||||
lyrics/auddlyricsprovider.h
|
||||
lyrics/apiseedslyricsprovider.h
|
||||
tidal/tidalurlhandler.h
|
||||
|
||||
)
|
||||
|
||||
|
@ -552,6 +552,7 @@ void ContextView::ScaleCover() {
|
||||
void ContextView::AlbumArtLoaded(const Song &song, const QString&, const QImage &image) {
|
||||
|
||||
if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
|
||||
if (image == image_original_) return;
|
||||
|
||||
active_ = true;
|
||||
downloading_covers_ = false;
|
||||
|
@ -224,10 +224,26 @@ void Player::HandleLoadResult(const UrlHandler::LoadResult &result) {
|
||||
|
||||
qLog(Debug) << "URL handler for" << result.original_url_ << "returned" << result.media_url_;
|
||||
|
||||
Song song = item->Metadata();
|
||||
bool update(false);
|
||||
|
||||
// If there was no filetype in the song's metadata, use the one provided by URL handler, if there is one
|
||||
if (
|
||||
(item->Metadata().filetype() == Song::FileType_Unknown && result.filetype_ != Song::FileType_Unknown)
|
||||
||
|
||||
(item->Metadata().filetype() == Song::FileType_Stream && result.filetype_ != Song::FileType_Stream)
|
||||
)
|
||||
{
|
||||
song.set_filetype(result.filetype_);
|
||||
update = true;
|
||||
}
|
||||
// If there was no length info in song's metadata, use the one provided by URL handler, if there is one
|
||||
if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) {
|
||||
if (item->Metadata().length_nanosec() <= 0 && result.length_nanosec_ != -1) {
|
||||
Song song = item->Metadata();
|
||||
song.set_length_nanosec(result.length_nanosec_);
|
||||
update = true;
|
||||
}
|
||||
if (update) {
|
||||
item->SetTemporaryMetadata(song);
|
||||
app_->playlist_manager()->active()->InformOfCurrentSongChange();
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
|
||||
#include "urlhandler.h"
|
||||
|
||||
UrlHandler::LoadResult::LoadResult(const QUrl &original_url, Type type, const QUrl &media_url, qint64 length_nanosec) : original_url_(original_url), type_(type), media_url_(media_url), length_nanosec_(length_nanosec) {}
|
||||
UrlHandler::LoadResult::LoadResult(const QUrl &original_url, Type type, const QUrl &media_url, const Song::FileType &filetype, qint64 length_nanosec) : original_url_(original_url), type_(type), media_url_(media_url), filetype_(filetype), length_nanosec_(length_nanosec) {}
|
||||
|
||||
UrlHandler::UrlHandler(QObject *parent) : QObject(parent) {}
|
||||
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <QUrl>
|
||||
#include <QIcon>
|
||||
|
||||
#include "song.h"
|
||||
|
||||
class UrlHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -53,7 +55,7 @@ class UrlHandler : public QObject {
|
||||
TrackAvailable,
|
||||
};
|
||||
|
||||
LoadResult(const QUrl &original_url = QUrl(), Type type = NoMoreTracks, const QUrl &media_url = QUrl(), qint64 length_nanosec_ = -1);
|
||||
LoadResult(const QUrl &original_url = QUrl(), Type type = NoMoreTracks, const QUrl &media_url = QUrl(), const Song::FileType &filetype = Song::FileType_Stream, qint64 length_nanosec_ = -1);
|
||||
|
||||
// The url that the playlist item has in Url().
|
||||
// Might be something unplayable like lastfm://...
|
||||
@ -64,6 +66,9 @@ class UrlHandler : public QObject {
|
||||
// The actual url to something that gstreamer can play.
|
||||
QUrl media_url_;
|
||||
|
||||
// The type of the stream
|
||||
Song::FileType filetype_;
|
||||
|
||||
// Track length, if we are able to get it only now
|
||||
qint64 length_nanosec_;
|
||||
};
|
||||
@ -78,8 +83,9 @@ class UrlHandler : public QObject {
|
||||
virtual void TrackAboutToEnd() {};
|
||||
virtual void TrackSkipped() {};
|
||||
|
||||
signals:
|
||||
signals:
|
||||
void AsyncLoadComplete(const UrlHandler::LoadResult &result);
|
||||
|
||||
};
|
||||
|
||||
#endif // URLHANDLER_H
|
||||
|
@ -110,7 +110,6 @@ class InternetModel : public QStandardItemModel {
|
||||
|
||||
template <typename T>
|
||||
static T *Service() {
|
||||
//return static_cast<T*>(ServiceByName(T::kServiceName));
|
||||
return static_cast<T*>(ServiceBySource(T::kSource));
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,6 @@
|
||||
#include "internetmodel.h"
|
||||
#include "internetservice.h"
|
||||
|
||||
InternetService::InternetService(Song::Source source, const QString &name, Application *app, InternetModel *model, QObject *parent)
|
||||
: QObject(parent), app_(app), model_(model), source_(source), name_(name) {
|
||||
InternetService::InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, InternetModel *model, QObject *parent)
|
||||
: QObject(parent), app_(app), model_(model), source_(source), name_(name), url_scheme_(url_scheme) {
|
||||
}
|
||||
|
@ -42,10 +42,11 @@ class InternetService : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
InternetService(Song::Source source, const QString &name, Application *app, InternetModel *model, QObject *parent = nullptr);
|
||||
InternetService(Song::Source source, const QString &name, const QString &url_scheme, Application *app, InternetModel *model, QObject *parent = nullptr);
|
||||
virtual ~InternetService() {}
|
||||
Song::Source source() const { return source_; }
|
||||
QString name() const { return name_; }
|
||||
QString url_scheme() const { return url_scheme_; }
|
||||
InternetModel *model() const { return model_; }
|
||||
virtual bool has_initial_load_settings() const { return false; }
|
||||
virtual void InitialLoadSettings() {}
|
||||
@ -61,6 +62,7 @@ class InternetService : public QObject {
|
||||
InternetModel *model_;
|
||||
Song::Source source_;
|
||||
QString name_;
|
||||
QString url_scheme_;
|
||||
|
||||
};
|
||||
Q_DECLARE_METATYPE(InternetService*);
|
||||
|
@ -45,7 +45,7 @@ TidalSettingsPage::TidalSettingsPage(SettingsDialog *parent)
|
||||
connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked()));
|
||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
|
||||
|
||||
connect(this, SIGNAL(Login(QString, QString, int)), service_, SLOT(SendLogin(QString, QString, int)));
|
||||
connect(this, SIGNAL(Login(QString, QString)), service_, SLOT(SendLogin(QString, QString)));
|
||||
|
||||
connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString)));
|
||||
connect(service_, SIGNAL(LoginSuccess()), SLOT(LoginSuccess()));
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mergedproxymodel.h"
|
||||
@ -51,20 +52,22 @@
|
||||
#include "internet/internetmodel.h"
|
||||
#include "tidalservice.h"
|
||||
#include "tidalsearch.h"
|
||||
#include "tidalurlhandler.h"
|
||||
#include "settings/tidalsettingspage.h"
|
||||
|
||||
const Song::Source TidalService::kSource = Song::Source_Tidal;
|
||||
const char *TidalService::kServiceName = "Tidal";
|
||||
const char *TidalService::kApiUrl = "https://listen.tidal.com/v1";
|
||||
const char *TidalService::kAuthUrl = "https://listen.tidal.com/v1/login/username";
|
||||
const char *TidalService::kResourcesUrl = "http://resources.tidal.com";
|
||||
const char *TidalService::kApiToken = "P5Xbeo5LFvESeDy6";
|
||||
const int TidalService::kLoginAttempts = 2;
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
|
||||
TidalService::TidalService(Application *app, InternetModel *parent)
|
||||
: InternetService(kSource, kServiceName, app, parent, parent),
|
||||
: InternetService(Song::Source_Tidal, "Tidal", "tidal", app, parent, parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
url_handler_(new TidalUrlHandler(app, this)),
|
||||
timer_searchdelay_(new QTimer(this)),
|
||||
searchdelay_(1500),
|
||||
albumssearchlimit_(1),
|
||||
@ -79,8 +82,10 @@ TidalService::TidalService(Application *app, InternetModel *parent)
|
||||
timer_searchdelay_->setSingleShot(true);
|
||||
connect(timer_searchdelay_, SIGNAL(timeout()), SLOT(StartSearch()));
|
||||
|
||||
connect(this, SIGNAL(Login(int)), SLOT(SendLogin(int)));
|
||||
connect(this, SIGNAL(Login(QString, QString, int)), SLOT(SendLogin(QString, QString, int)));
|
||||
connect(this, SIGNAL(Login()), SLOT(SendLogin()));
|
||||
connect(this, SIGNAL(Login(QString, QString)), SLOT(SendLogin(QString, QString)));
|
||||
|
||||
app->player()->RegisterUrlHandler(url_handler_);
|
||||
|
||||
ReloadSettings();
|
||||
LoadSessionID();
|
||||
@ -121,13 +126,13 @@ void TidalService::LoadSessionID() {
|
||||
|
||||
}
|
||||
|
||||
void TidalService::SendLogin(const int search_id) {
|
||||
SendLogin(username_, password_, search_id);
|
||||
void TidalService::SendLogin() {
|
||||
SendLogin(username_, password_);
|
||||
}
|
||||
|
||||
void TidalService::SendLogin(const QString &username, const QString &password, const int search_id) {
|
||||
void TidalService::SendLogin(const QString &username, const QString &password) {
|
||||
|
||||
if (search_id != 0) emit UpdateStatus("Authenticating...");
|
||||
if (search_id_ != 0) emit UpdateStatus("Authenticating...");
|
||||
|
||||
login_sent_ = true;
|
||||
login_attempts_++;
|
||||
@ -153,11 +158,11 @@ void TidalService::SendLogin(const QString &username, const QString &password, c
|
||||
|
||||
req.setRawHeader("Origin", "http://listen.tidal.com");
|
||||
QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*, int)), reply, search_id);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
||||
void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
@ -167,7 +172,7 @@ void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
@ -189,7 +194,7 @@ void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
@ -201,21 +206,21 @@ void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
QString failure_reason("Authentication reply from server missing Json data.");
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
QString failure_reason("Authentication reply from server has empty Json document.");
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
QString failure_reason("Authentication reply from server has Json document that is not an object.");
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
@ -223,14 +228,14 @@ void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
QString failure_reason("Authentication reply from server has empty Json object.");
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !json_obj.contains("userId") || !json_obj.contains("sessionId") || !json_obj.contains("countryCode") ) {
|
||||
QString failure_reason("Authentication reply from server is missing userId, sessionId or countryCode");
|
||||
if (search_id != 0) Error(failure_reason);
|
||||
Error(failure_reason);
|
||||
emit LoginFailure(failure_reason);
|
||||
return;
|
||||
}
|
||||
@ -248,10 +253,16 @@ void TidalService::HandleAuthReply(QNetworkReply *reply, int search_id) {
|
||||
|
||||
qLog(Debug) << "Tidal: Login successful" << "user id" << user_id_ << "session id" << session_id_ << "country code" << country_code_;
|
||||
|
||||
if (search_id != 0) {
|
||||
qLog(Debug) << "Tidal: Resuming search";
|
||||
login_attempts_ = 0;
|
||||
|
||||
if (search_id_ != 0) {
|
||||
qLog(Debug) << "Tidal: Resuming search" << search_id_;
|
||||
SendSearch();
|
||||
}
|
||||
if (!stream_request_url_.isEmpty()) {
|
||||
qLog(Debug) << "Tidal: Resuming get stream url" << stream_request_url_;
|
||||
emit GetStreamURL(stream_request_url_);
|
||||
}
|
||||
|
||||
emit LoginSuccess();
|
||||
|
||||
@ -303,7 +314,7 @@ QNetworkReply *TidalService::CreateRequest(const QString &ressource_name, const
|
||||
|
||||
}
|
||||
|
||||
QJsonObject TidalService::ExtractJsonObj(QNetworkReply *reply, bool sendlogin) {
|
||||
QJsonObject TidalService::ExtractJsonObj(QNetworkReply *reply, const bool sendlogin) {
|
||||
|
||||
QByteArray data;
|
||||
|
||||
@ -334,14 +345,15 @@ QJsonObject TidalService::ExtractJsonObj(QNetworkReply *reply, bool sendlogin) {
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
qLog(Debug) << reply->error();
|
||||
if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::ContentOperationNotPermittedError || reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||
// Session is probably expired, attempt to login once
|
||||
Logout();
|
||||
if (sendlogin && login_attempts_ < 1 && !username_.isEmpty() && !password_.isEmpty()) {
|
||||
if (sendlogin && login_attempts_ < kLoginAttempts && !username_.isEmpty() && !password_.isEmpty()) {
|
||||
qLog(Error) << "Tidal:" << failure_reason;
|
||||
qLog(Error) << "Tidal:" << QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
qLog(Error) << "Tidal:" << "Attempting to login.";
|
||||
emit Login(search_id_);
|
||||
emit Login();
|
||||
}
|
||||
else {
|
||||
Error(failure_reason);
|
||||
@ -439,7 +451,7 @@ void TidalService::StartSearch() {
|
||||
search_text_ = pending_search_text_;
|
||||
|
||||
if (authenticated()) SendSearch();
|
||||
else emit Login(username_, password_, search_id_);
|
||||
else emit Login(username_, password_);
|
||||
|
||||
}
|
||||
|
||||
@ -525,9 +537,10 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
||||
// This was a tracks search
|
||||
if (!fetchalbums_) {
|
||||
Song song = ParseSong(0, value);
|
||||
requests_song_.insert(song.id(), song);
|
||||
songs_requested_++;
|
||||
GetStreamURL(0, song.id());
|
||||
//requests_song_.insert(song.id(), song);
|
||||
//songs_requested_++;
|
||||
//GetStreamURL(0, song.id());
|
||||
songs_ << song;
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_album = json_obj["album"];
|
||||
@ -649,9 +662,10 @@ void TidalService::GetAlbumFinished(QNetworkReply *reply, int search_id, int alb
|
||||
QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc()));
|
||||
song.set_album(album_full);
|
||||
}
|
||||
requests_song_.insert(song.id(), song);
|
||||
songs_requested_++;
|
||||
GetStreamURL(album_id, song.id());
|
||||
//requests_song_.insert(song.id(), song);
|
||||
//songs_requested_++;
|
||||
//GetStreamURL(album_id, song.id());
|
||||
songs_ << song;
|
||||
}
|
||||
|
||||
if (albums_requested_ <= albums_received_) {
|
||||
@ -702,10 +716,11 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||
QJsonArray json_artists = json_obj["artists"].toArray();
|
||||
|
||||
int song_id = json_obj["id"].toInt();
|
||||
|
||||
if (requests_song_.contains(song_id)) return requests_song_.value(song_id);
|
||||
|
||||
QString title = json_obj["title"].toString();
|
||||
QString url = json_obj["url"].toString();
|
||||
QString urlstr = json_obj["url"].toString();
|
||||
int track = json_obj["trackNumber"].toInt();
|
||||
int disc = json_obj["volumeNumber"].toInt();
|
||||
bool allow_streaming = json_obj["allowStreaming"].toBool();
|
||||
@ -760,9 +775,6 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||
song.set_title(title);
|
||||
song.set_track(track);
|
||||
song.set_disc(disc);
|
||||
song.set_bitrate(0);
|
||||
song.set_samplerate(0);
|
||||
song.set_bitdepth(0);
|
||||
|
||||
QVariant q_duration = json_duration.toVariant();
|
||||
if (q_duration.isValid()) {
|
||||
@ -773,13 +785,23 @@ Song TidalService::ParseSong(const int album_id_requested, const QJsonValue &val
|
||||
cover = cover.replace("-", "/");
|
||||
QUrl cover_url (QString("%1/images/%2/%3.jpg").arg(kResourcesUrl).arg(cover).arg(coversize_));
|
||||
song.set_art_automatic(cover_url.toEncoded());
|
||||
|
||||
QUrl url;
|
||||
url.setScheme(url_handler_->scheme());
|
||||
url.setPath(QString::number(song_id));
|
||||
song.set_url(url);
|
||||
|
||||
song.set_valid(true);
|
||||
|
||||
return song;
|
||||
|
||||
}
|
||||
|
||||
void TidalService::GetStreamURL(const int album_id, const int song_id) {
|
||||
void TidalService::GetStreamURL(const QUrl &url) {
|
||||
|
||||
stream_request_url_ = url;
|
||||
int song_id = url.path().toInt();
|
||||
requests_song_.insert(song_id, url);
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("token", session_id_)
|
||||
@ -787,60 +809,44 @@ void TidalService::GetStreamURL(const int album_id, const int song_id) {
|
||||
|
||||
QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters);
|
||||
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetStreamURLFinished(QNetworkReply*, int, int)), reply, search_id_, song_id);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetStreamURLFinished(QNetworkReply*, int, QUrl)), reply, song_id, url);
|
||||
|
||||
}
|
||||
|
||||
void TidalService::GetStreamURLFinished(QNetworkReply *reply, const int search_id, const int song_id) {
|
||||
void TidalService::GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url) {
|
||||
|
||||
reply->deleteLater();
|
||||
if (requests_song_.contains(song_id)) requests_song_.remove(song_id);
|
||||
if (original_url != stream_request_url_) return;
|
||||
|
||||
if (search_id != search_id_) return;
|
||||
|
||||
if (!requests_song_.contains(song_id)) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
Song song = requests_song_.value(song_id);
|
||||
songs_received_++;
|
||||
|
||||
if (albums_requested_ <= albums_received_) {
|
||||
emit UpdateProgress(songs_received_);
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(reply);
|
||||
QJsonObject json_obj = ExtractJsonObj(reply, true);
|
||||
if (json_obj.isEmpty()) {
|
||||
requests_song_.remove(song_id);
|
||||
CheckFinish();
|
||||
if (!stream_request_url_.isEmpty() && !login_sent_) {
|
||||
emit StreamURLFinished(QUrl(), Song::FileType_Stream);
|
||||
stream_request_url_ = QUrl();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("url") || !json_obj.contains("codec")) {
|
||||
qLog(Error) << "Tidal: Invalid Json reply, stream missing url or codec.";
|
||||
qLog(Debug) << json_obj;
|
||||
requests_song_.remove(song_id);
|
||||
CheckFinish();
|
||||
emit StreamURLFinished(QUrl(), Song::FileType_Stream);
|
||||
stream_request_url_ = QUrl();
|
||||
return;
|
||||
}
|
||||
|
||||
song.set_url(QUrl(json_obj["url"].toString()));
|
||||
stream_request_url_ = QUrl();
|
||||
|
||||
QString codec = json_obj["codec"].toString().toLower();
|
||||
song.set_filetype(Song::FiletypeByExtension(codec));
|
||||
if (song.filetype() == Song::FileType_Unknown) {
|
||||
QUrl new_url(json_obj["url"].toString());
|
||||
QString codec(json_obj["codec"].toString().toLower());
|
||||
Song::FileType filetype(Song::FiletypeByExtension(codec));
|
||||
if (filetype == Song::FileType_Unknown) {
|
||||
qLog(Debug) << "Tidal: Unknown codec" << codec;
|
||||
song.set_filetype(Song::FileType_Stream);
|
||||
filetype = Song::FileType_Stream;
|
||||
}
|
||||
|
||||
song.set_valid(true);
|
||||
|
||||
//qLog(Debug) << song.artist() << song.album() << song.title() << song.url() << song.filetype();
|
||||
|
||||
songs_ << song;
|
||||
|
||||
requests_song_.remove(song_id);
|
||||
|
||||
CheckFinish();
|
||||
emit StreamURLFinished(new_url, filetype);
|
||||
|
||||
}
|
||||
|
||||
@ -862,6 +868,15 @@ void TidalService::CheckFinish() {
|
||||
void TidalService::Error(QString error, QString debug) {
|
||||
qLog(Error) << "Tidal:" << error;
|
||||
if (!debug.isEmpty()) qLog(Debug) << debug;
|
||||
search_error_ = error;
|
||||
CheckFinish();
|
||||
if (search_id_ != 0) {
|
||||
if (!error.isEmpty()) {
|
||||
search_error_ += error;
|
||||
search_error_ += "<br />";
|
||||
}
|
||||
CheckFinish();
|
||||
}
|
||||
if (!stream_request_url_.isEmpty() && !login_sent_) {
|
||||
emit StreamURLFinished(QUrl(), Song::FileType_Stream);
|
||||
stream_request_url_ = QUrl();
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
@ -39,6 +40,7 @@
|
||||
#include "settings/tidalsettingspage.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class TidalUrlHandler;
|
||||
|
||||
class TidalService : public InternetService {
|
||||
Q_OBJECT
|
||||
@ -48,7 +50,7 @@ class TidalService : public InternetService {
|
||||
~TidalService();
|
||||
|
||||
static const Song::Source kSource;
|
||||
static const char *kServiceName;
|
||||
static const int kLoginAttempts;
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
@ -59,9 +61,11 @@ class TidalService : public InternetService {
|
||||
const bool login_sent() { return login_sent_; }
|
||||
const bool authenticated() { return (!session_id_.isEmpty() && !country_code_.isEmpty()); }
|
||||
|
||||
void GetStreamURL(const QUrl &url);
|
||||
|
||||
signals:
|
||||
void Login(const int search_id = 0);
|
||||
void Login(const QString &username, const QString &password, const int search_id = 0);
|
||||
void Login();
|
||||
void Login(const QString &username, const QString &password);
|
||||
void LoginSuccess();
|
||||
void LoginFailure(QString failure_reason);
|
||||
void SearchResults(int id, SongList songs);
|
||||
@ -69,18 +73,20 @@ class TidalService : public InternetService {
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int max);
|
||||
void UpdateProgress(int max);
|
||||
void GetStreamURLFinished(QNetworkReply *reply, const QUrl url);
|
||||
void StreamURLFinished(const QUrl url, const Song::FileType);
|
||||
|
||||
public slots:
|
||||
void ShowConfig();
|
||||
void SendLogin(const QString &username, const QString &password, const int search_id = 0);
|
||||
void SendLogin(const QString &username, const QString &password);
|
||||
|
||||
private slots:
|
||||
void SendLogin(const int search_id = 0);
|
||||
void HandleAuthReply(QNetworkReply *reply, int search_id);
|
||||
void SendLogin();
|
||||
void HandleAuthReply(QNetworkReply *reply);
|
||||
void StartSearch();
|
||||
void SearchFinished(QNetworkReply *reply, int search_id);
|
||||
void GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id);
|
||||
void GetStreamURLFinished(QNetworkReply *reply, const int search_id, const int song_id);
|
||||
void GetStreamURLFinished(QNetworkReply *reply, const int song_id, const QUrl original_url);
|
||||
|
||||
private:
|
||||
void ClearSearch();
|
||||
@ -91,7 +97,6 @@ class TidalService : public InternetService {
|
||||
void SendSearch();
|
||||
void GetAlbum(const int album_id);
|
||||
Song ParseSong(const int album_id_requested, const QJsonValue &value);
|
||||
void GetStreamURL(const int album_id, const int song_id);
|
||||
void CheckFinish();
|
||||
void Error(QString error, QString debug = QString());
|
||||
|
||||
@ -101,6 +106,7 @@ class TidalService : public InternetService {
|
||||
static const char *kApiToken;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
TidalUrlHandler *url_handler_;
|
||||
QTimer *timer_searchdelay_;
|
||||
|
||||
QString username_;
|
||||
@ -123,7 +129,7 @@ class TidalService : public InternetService {
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
QHash<int, int> requests_album_;
|
||||
QHash<int, Song> requests_song_;
|
||||
QHash<int, QUrl> requests_song_;
|
||||
int albums_requested_;
|
||||
int albums_received_;
|
||||
int songs_requested_;
|
||||
@ -132,6 +138,7 @@ class TidalService : public InternetService {
|
||||
QString search_error_;
|
||||
bool login_sent_;
|
||||
int login_attempts_;
|
||||
QUrl stream_request_url_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -274,8 +274,8 @@ void PlayingWidget::SongChanged(const Song &song) {
|
||||
void PlayingWidget::AlbumArtLoaded(const Song &song, const QString &, const QImage &image) {
|
||||
|
||||
if (!playing_) return;
|
||||
|
||||
if (song.effective_albumartist() != song_playing_.effective_albumartist() || song.effective_album() != song_playing_.effective_album() || song.title() != song_playing_.title()) return;
|
||||
if (timeline_fade_->state() == QTimeLine::Running && image == image_original_ && song.effective_albumartist() == song_.effective_albumartist() && song.effective_album() == song_.effective_album() && song.title() == song_.title()) return;
|
||||
|
||||
active_ = true;
|
||||
downloading_covers_ = false;
|
||||
|
Loading…
x
Reference in New Issue
Block a user