Improve URL handler, return error for encrypted Tidal streams
This commit is contained in:
parent
fd85763fb4
commit
01f8129ed0
@ -130,7 +130,8 @@ class InternetService : public QObject {
|
||||
void RemoveSongs(SongList songs);
|
||||
void RemoveSongs(SongMap songs);
|
||||
|
||||
void StreamURLFinished(QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString());
|
||||
void StreamURLFailure(uint id, QUrl original_url, QString error);
|
||||
void StreamURLSuccess(uint id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration);
|
||||
|
||||
protected:
|
||||
Application *app_;
|
||||
|
@ -178,10 +178,16 @@ QobuzService::QobuzService(Application *app, QObject *parent)
|
||||
|
||||
QobuzService::~QobuzService() {
|
||||
|
||||
while (!replies_.isEmpty()) {
|
||||
QNetworkReply *reply = replies_.takeFirst();
|
||||
QObject::disconnect(reply, nullptr, this, nullptr);
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
while (!stream_url_requests_.isEmpty()) {
|
||||
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(stream_url_requests_.firstKey());
|
||||
QObject::disconnect(stream_url_req.get(), nullptr, this, nullptr);
|
||||
stream_url_req->deleteLater();
|
||||
}
|
||||
|
||||
artists_collection_backend_->deleteLater();
|
||||
@ -714,31 +720,44 @@ void QobuzService::SearchResultsReceived(const int id, const SongMap &songs, con
|
||||
emit SearchResults(id, songs, error);
|
||||
}
|
||||
|
||||
void QobuzService::GetStreamURL(const QUrl &url) {
|
||||
uint QobuzService::GetStreamURL(const QUrl &url, QString &error) {
|
||||
|
||||
if (app_id().isEmpty() || app_secret().isEmpty()) { // Don't check for login here, because we allow automatic login.
|
||||
emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret."));
|
||||
return;
|
||||
error = tr("Missing Qobuz app ID or secret.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const int id = ++next_stream_url_request_id_;
|
||||
uint id = 0;
|
||||
while (id == 0) id = ++next_stream_url_request_id_;
|
||||
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = std::make_shared<QobuzStreamURLRequest>(this, network_, url, id);
|
||||
stream_url_requests_.insert(id, stream_url_req);
|
||||
|
||||
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::TryLogin, this, &QobuzService::TryLogin);
|
||||
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLFinished, this, &QobuzService::HandleStreamURLFinished);
|
||||
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLFailure, this, &QobuzService::HandleStreamURLFailure);
|
||||
QObject::connect(stream_url_req.get(), &QobuzStreamURLRequest::StreamURLSuccess, this, &QobuzService::HandleStreamURLSuccess);
|
||||
QObject::connect(this, &QobuzService::LoginComplete, stream_url_req.get(), &QobuzStreamURLRequest::LoginComplete);
|
||||
|
||||
stream_url_req->Process();
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
|
||||
void QobuzService::HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
|
||||
|
||||
if (!stream_url_requests_.contains(id)) return;
|
||||
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
|
||||
|
||||
emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error);
|
||||
emit StreamURLFailure(id, original_url, error);
|
||||
|
||||
}
|
||||
|
||||
void QobuzService::HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
|
||||
|
||||
if (!stream_url_requests_.contains(id)) return;
|
||||
std::shared_ptr<QobuzStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
|
||||
|
||||
emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration);
|
||||
|
||||
}
|
||||
|
||||
|
@ -93,7 +93,7 @@ class QobuzService : public InternetService {
|
||||
bool login_sent() { return login_sent_; }
|
||||
bool login_attempts() { return login_attempts_; }
|
||||
|
||||
void GetStreamURL(const QUrl &url);
|
||||
uint GetStreamURL(const QUrl &url, QString &error);
|
||||
|
||||
CollectionBackend *artists_collection_backend() override { return artists_collection_backend_; }
|
||||
CollectionBackend *albums_collection_backend() override { return albums_collection_backend_; }
|
||||
@ -138,7 +138,8 @@ class QobuzService : public InternetService {
|
||||
void ArtistsUpdateProgressReceived(const int id, const int progress);
|
||||
void AlbumsUpdateProgressReceived(const int id, const int progress);
|
||||
void SongsUpdateProgressReceived(const int id, const int progress);
|
||||
void HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error);
|
||||
void HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
|
||||
void HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
@ -211,8 +212,8 @@ class QobuzService : public InternetService {
|
||||
bool login_sent_;
|
||||
int login_attempts_;
|
||||
|
||||
int next_stream_url_request_id_;
|
||||
QMap<int, std::shared_ptr<QobuzStreamURLRequest>> stream_url_requests_;
|
||||
uint next_stream_url_request_id_;
|
||||
QMap<uint, std::shared_ptr<QobuzStreamURLRequest>> stream_url_requests_;
|
||||
|
||||
QStringList login_errors_;
|
||||
|
||||
|
@ -45,7 +45,7 @@
|
||||
#include "qobuzbaserequest.h"
|
||||
#include "qobuzstreamurlrequest.h"
|
||||
|
||||
QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent)
|
||||
QobuzStreamURLRequest::QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent)
|
||||
: QobuzBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
reply_(nullptr),
|
||||
@ -71,7 +71,7 @@ void QobuzStreamURLRequest::LoginComplete(const bool success, const QString &err
|
||||
need_login_ = false;
|
||||
|
||||
if (!success) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFailure(id_, original_url_, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ void QobuzStreamURLRequest::LoginComplete(const bool success, const QString &err
|
||||
void QobuzStreamURLRequest::Process() {
|
||||
|
||||
if (app_id().isEmpty() || app_secret().isEmpty()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Missing Qobuz app ID or secret."));
|
||||
emit StreamURLFailure(id_, original_url_, tr("Missing Qobuz app ID or secret."));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ void QobuzStreamURLRequest::Cancel() {
|
||||
reply_->abort();
|
||||
}
|
||||
else {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Cancelled."));
|
||||
emit StreamURLFailure(id_, original_url_, tr("Cancelled."));
|
||||
}
|
||||
|
||||
}
|
||||
@ -172,32 +172,32 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("track_id")) {
|
||||
Error("Invalid Json reply, stream url is missing track_id.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
int track_id = json_obj["track_id"].toInt();
|
||||
if (track_id != song_id_) {
|
||||
Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("mime_type") || !json_obj.contains("url")) {
|
||||
Error("Invalid Json reply, stream url is missing url or mime_type.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -218,7 +218,7 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
||||
|
||||
if (!url.isValid()) {
|
||||
Error("Returned stream url is invalid.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, filetype, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -235,7 +235,7 @@ void QobuzStreamURLRequest::StreamURLReceived() {
|
||||
bit_depth = static_cast<int>(json_obj["bit_depth"].toDouble());
|
||||
}
|
||||
|
||||
emit StreamURLFinished(id_, original_url_, url, filetype, samplerate, bit_depth, duration);
|
||||
emit StreamURLSuccess(id_, original_url_, url, filetype, samplerate, bit_depth, duration);
|
||||
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent = nullptr);
|
||||
explicit QobuzStreamURLRequest(QobuzService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent = nullptr);
|
||||
~QobuzStreamURLRequest();
|
||||
|
||||
void GetStreamURL();
|
||||
@ -54,7 +54,8 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
|
||||
|
||||
signals:
|
||||
void TryLogin();
|
||||
void StreamURLFinished(int id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString());
|
||||
void StreamURLFailure(uint id, QUrl original_url, QString error);
|
||||
void StreamURLSuccess(uint id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration);
|
||||
|
||||
private slots:
|
||||
void StreamURLReceived();
|
||||
@ -68,7 +69,7 @@ class QobuzStreamURLRequest : public QobuzBaseRequest {
|
||||
QobuzService *service_;
|
||||
QNetworkReply *reply_;
|
||||
QUrl original_url_;
|
||||
int id_;
|
||||
uint id_;
|
||||
int song_id_;
|
||||
int tries_;
|
||||
bool need_login_;
|
||||
|
@ -17,6 +17,8 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
@ -30,38 +32,53 @@
|
||||
QobuzUrlHandler::QobuzUrlHandler(Application *app, QobuzService *service)
|
||||
: UrlHandler(service),
|
||||
app_(app),
|
||||
service_(service),
|
||||
task_id_(-1) {
|
||||
service_(service) {
|
||||
|
||||
QObject::connect(service, &QobuzService::StreamURLFinished, this, &QobuzUrlHandler::GetStreamURLFinished);
|
||||
QObject::connect(service, &QobuzService::StreamURLFailure, this, &QobuzUrlHandler::GetStreamURLFailure);
|
||||
QObject::connect(service, &QobuzService::StreamURLSuccess, this, &QobuzUrlHandler::GetStreamURLSuccess);
|
||||
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult QobuzUrlHandler::StartLoading(const QUrl &url) {
|
||||
|
||||
Request req;
|
||||
req.task_id = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
QString error;
|
||||
req.id = service_->GetStreamURL(url, error);
|
||||
if (req.id == 0) {
|
||||
CancelTask(req.task_id);
|
||||
return LoadResult(url, LoadResult::Error, error);
|
||||
}
|
||||
|
||||
requests_.insert(req.id, req);
|
||||
|
||||
LoadResult ret(url);
|
||||
if (task_id_ != -1) return ret;
|
||||
task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
service_->GetStreamURL(url);
|
||||
ret.type_ = LoadResult::WillLoadAsynchronously;
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void QobuzUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
|
||||
void QobuzUrlHandler::GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
|
||||
|
||||
if (!requests_.contains(id)) return;
|
||||
Request req = requests_.take(id);
|
||||
CancelTask(req.task_id);
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
if (error.isEmpty()) {
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
}
|
||||
else {
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, error));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void QobuzUrlHandler::CancelTask() {
|
||||
app_->task_manager()->SetTaskFinished(task_id_);
|
||||
task_id_ = -1;
|
||||
void QobuzUrlHandler::GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
|
||||
|
||||
if (!requests_.contains(id)) return;
|
||||
Request req = requests_.take(id);
|
||||
CancelTask(req.task_id);
|
||||
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
|
||||
}
|
||||
|
||||
void QobuzUrlHandler::CancelTask(const int task_id) {
|
||||
app_->task_manager()->SetTaskFinished(task_id);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
@ -40,15 +41,22 @@ class QobuzUrlHandler : public UrlHandler {
|
||||
QString scheme() const { return service_->url_scheme(); }
|
||||
LoadResult StartLoading(const QUrl &url);
|
||||
|
||||
void CancelTask();
|
||||
private:
|
||||
void CancelTask(const int task_id);
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error = QString());
|
||||
void GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
|
||||
void GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
Request() : id(0), task_id(-1) {}
|
||||
uint id;
|
||||
int task_id;
|
||||
};
|
||||
Application *app_;
|
||||
QobuzService *service_;
|
||||
int task_id_;
|
||||
QMap<uint, Request> requests_;
|
||||
|
||||
};
|
||||
|
||||
|
@ -211,7 +211,6 @@ TidalService::~TidalService() {
|
||||
while (!stream_url_requests_.isEmpty()) {
|
||||
std::shared_ptr<TidalStreamURLRequest> stream_url_req = stream_url_requests_.take(stream_url_requests_.firstKey());
|
||||
QObject::disconnect(stream_url_req.get(), nullptr, this, nullptr);
|
||||
stream_url_req->deleteLater();
|
||||
}
|
||||
|
||||
artists_collection_backend_->deleteLater();
|
||||
@ -987,37 +986,50 @@ void TidalService::SearchResultsReceived(const int id, const SongMap &songs, con
|
||||
emit SearchResults(id, songs, error);
|
||||
}
|
||||
|
||||
void TidalService::GetStreamURL(const QUrl &url) {
|
||||
uint TidalService::GetStreamURL(const QUrl &url, QString &error) {
|
||||
|
||||
if (!authenticated()) {
|
||||
if (oauth_) {
|
||||
emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Not authenticated with Tidal."));
|
||||
return;
|
||||
error = tr("Not authenticated with Tidal.");
|
||||
return 0;
|
||||
}
|
||||
else if (api_token_.isEmpty() || username_.isEmpty() || password_.isEmpty()) {
|
||||
emit StreamURLFinished(url, url, Song::FileType_Stream, -1, -1, -1, tr("Missing Tidal API token, username or password."));
|
||||
return;
|
||||
error = tr("Missing Tidal API token, username or password.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const int id = ++next_stream_url_request_id_;
|
||||
uint id = 0;
|
||||
while (id == 0) id = ++next_stream_url_request_id_;
|
||||
std::shared_ptr<TidalStreamURLRequest> stream_url_req = std::make_shared<TidalStreamURLRequest>(this, network_, url, id);
|
||||
stream_url_requests_.insert(id, stream_url_req);
|
||||
|
||||
QObject::connect(stream_url_req.get(), &TidalStreamURLRequest::TryLogin, this, &TidalService::TryLogin);
|
||||
QObject::connect(stream_url_req.get(), &TidalStreamURLRequest::StreamURLFinished, this, &TidalService::HandleStreamURLFinished);
|
||||
QObject::connect(stream_url_req.get(), &TidalStreamURLRequest::StreamURLFailure, this, &TidalService::HandleStreamURLFailure);
|
||||
QObject::connect(stream_url_req.get(), &TidalStreamURLRequest::StreamURLSuccess, this, &TidalService::HandleStreamURLSuccess);
|
||||
QObject::connect(this, &TidalService::LoginComplete, stream_url_req.get(), &TidalStreamURLRequest::LoginComplete);
|
||||
|
||||
stream_url_req->Process();
|
||||
|
||||
return id;
|
||||
|
||||
}
|
||||
|
||||
void TidalService::HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
|
||||
void TidalService::HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
|
||||
|
||||
if (!stream_url_requests_.contains(id)) return;
|
||||
std::shared_ptr<TidalStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
|
||||
|
||||
emit StreamURLFinished(original_url, stream_url, filetype, samplerate, bit_depth, duration, error);
|
||||
emit StreamURLFailure(id, original_url, error);
|
||||
|
||||
}
|
||||
|
||||
void TidalService::HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
|
||||
|
||||
if (!stream_url_requests_.contains(id)) return;
|
||||
std::shared_ptr<TidalStreamURLRequest> stream_url_req = stream_url_requests_.take(id);
|
||||
|
||||
emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration);
|
||||
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ class TidalService : public InternetService {
|
||||
bool login_sent() { return login_sent_; }
|
||||
bool login_attempts() { return login_attempts_; }
|
||||
|
||||
void GetStreamURL(const QUrl &url);
|
||||
uint GetStreamURL(const QUrl &url, QString &error);
|
||||
|
||||
CollectionBackend *artists_collection_backend() override { return artists_collection_backend_; }
|
||||
CollectionBackend *albums_collection_backend() override { return albums_collection_backend_; }
|
||||
@ -151,7 +151,8 @@ class TidalService : public InternetService {
|
||||
void ArtistsUpdateProgressReceived(const int id, const int progress);
|
||||
void AlbumsUpdateProgressReceived(const int id, const int progress);
|
||||
void SongsUpdateProgressReceived(const int id, const int progress);
|
||||
void HandleStreamURLFinished(const int id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error = QString());
|
||||
void HandleStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
|
||||
void HandleStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
|
||||
|
||||
private:
|
||||
typedef QPair<QString, QString> Param;
|
||||
@ -241,8 +242,8 @@ class TidalService : public InternetService {
|
||||
QString code_verifier_;
|
||||
QString code_challenge_;
|
||||
|
||||
int next_stream_url_request_id_;
|
||||
QMap<int, std::shared_ptr<TidalStreamURLRequest>> stream_url_requests_;
|
||||
uint next_stream_url_request_id_;
|
||||
QMap<uint, std::shared_ptr<TidalStreamURLRequest>> stream_url_requests_;
|
||||
|
||||
QStringList login_errors_;
|
||||
|
||||
|
@ -46,7 +46,7 @@
|
||||
#include "tidalbaserequest.h"
|
||||
#include "tidalstreamurlrequest.h"
|
||||
|
||||
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent)
|
||||
TidalStreamURLRequest::TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent)
|
||||
: TidalBaseRequest(service, network, parent),
|
||||
service_(service),
|
||||
reply_(nullptr),
|
||||
@ -72,7 +72,7 @@ void TidalStreamURLRequest::LoginComplete(const bool success, const QString &err
|
||||
need_login_ = false;
|
||||
|
||||
if (!success) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, error);
|
||||
emit StreamURLFailure(id_, original_url_, error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -84,11 +84,11 @@ void TidalStreamURLRequest::Process() {
|
||||
|
||||
if (!authenticated()) {
|
||||
if (oauth()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Not authenticated with Tidal."));
|
||||
emit StreamURLFailure(id_, original_url_, tr("Not authenticated with Tidal."));
|
||||
return;
|
||||
}
|
||||
else if (api_token().isEmpty() || username().isEmpty() || password().isEmpty()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Missing Tidal API token, username or password."));
|
||||
emit StreamURLFailure(id_, original_url_, tr("Missing Tidal API token, username or password."));
|
||||
return;
|
||||
}
|
||||
need_login_ = true;
|
||||
@ -106,7 +106,7 @@ void TidalStreamURLRequest::Cancel() {
|
||||
reply_->abort();
|
||||
}
|
||||
else {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, tr("Cancelled."));
|
||||
emit StreamURLFailure(id_, original_url_, tr("Cancelled."));
|
||||
}
|
||||
|
||||
}
|
||||
@ -151,38 +151,37 @@ void TidalStreamURLRequest::GetStreamURL() {
|
||||
void TidalStreamURLRequest::StreamURLReceived() {
|
||||
|
||||
if (!reply_) return;
|
||||
QObject::disconnect(reply_, nullptr, this, nullptr);
|
||||
reply_->deleteLater();
|
||||
|
||||
QByteArray data = GetReplyData(reply_, true);
|
||||
if (data.isEmpty()) {
|
||||
|
||||
QObject::disconnect(reply_, nullptr, this, nullptr);
|
||||
reply_->deleteLater();
|
||||
reply_ = nullptr;
|
||||
|
||||
if (data.isEmpty()) {
|
||||
if (!authenticated() && login_sent() && tries_ <= 1) {
|
||||
need_login_ = true;
|
||||
return;
|
||||
}
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
reply_ = nullptr;
|
||||
|
||||
//qLog(Debug) << "Tidal:" << data;
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!json_obj.contains("trackId")) {
|
||||
Error("Invalid Json reply, stream missing trackId.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
int track_id(json_obj["trackId"].toInt());
|
||||
if (track_id != song_id_) {
|
||||
Error("Incorrect track ID returned.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -206,8 +205,6 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
||||
QString manifest(json_obj["manifest"].toString());
|
||||
QByteArray data_manifest = QByteArray::fromBase64(manifest.toUtf8());
|
||||
|
||||
//qLog(Debug) << "Tidal:" << data_manifest;
|
||||
|
||||
QXmlStreamReader xml_reader(data_manifest);
|
||||
if (xml_reader.readNextStartElement()) {
|
||||
QUrl url;
|
||||
@ -220,13 +217,23 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
||||
|
||||
json_obj = ExtractJsonObj(data_manifest);
|
||||
if (json_obj.isEmpty()) {
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
if (json_obj.contains("encryptionType") && json_obj.contains("keyId")) {
|
||||
QString encryption_type = json_obj["encryptionType"].toString();
|
||||
QString key_id = json_obj["encryptionType"].toString();
|
||||
if (!encryption_type.isEmpty() && !key_id.isEmpty()) {
|
||||
Error(tr("Received URL with %1 encrypted stream from Tidal. Strawberry does not currently support encrypted streams.").arg(encryption_type));
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!json_obj.contains("mimeType")) {
|
||||
Error("Invalid Json reply, stream url reply manifest is missing mimeType.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
@ -249,7 +256,7 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
||||
QJsonValue json_urls = json_obj["urls"];
|
||||
if (!json_urls.isArray()) {
|
||||
Error("Invalid Json reply, urls is not an array.", json_urls);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, Song::FileType_Stream, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
QJsonArray json_array_urls = json_urls.toArray();
|
||||
@ -268,13 +275,32 @@ void TidalStreamURLRequest::StreamURLReceived() {
|
||||
}
|
||||
}
|
||||
|
||||
if (json_obj.contains("encryptionKey")) {
|
||||
QString encryption_key = json_obj["encryptionKey"].toString();
|
||||
if (!encryption_key.isEmpty()) {
|
||||
Error(tr("Received URL with encrypted stream from Tidal. Strawberry does not currently support encrypted streams."));
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (json_obj.contains("securityType") && json_obj.contains("securityToken")) {
|
||||
QString security_type = json_obj["securityType"].toString();
|
||||
QString security_token = json_obj["securityToken"].toString();
|
||||
if (!security_type.isEmpty() && !security_token.isEmpty()) {
|
||||
Error(tr("Received URL with encrypted stream from Tidal. Strawberry does not currently support encrypted streams."));
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (urls.isEmpty()) {
|
||||
Error("Missing stream urls.", json_obj);
|
||||
emit StreamURLFinished(id_, original_url_, original_url_, filetype, -1, -1, -1, errors_.first());
|
||||
emit StreamURLFailure(id_, original_url_, errors_.first());
|
||||
return;
|
||||
}
|
||||
|
||||
emit StreamURLFinished(id_, original_url_, urls.first(), filetype, -1, -1, -1);
|
||||
emit StreamURLSuccess(id_, original_url_, urls.first(), filetype);
|
||||
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, const int id, QObject *parent = nullptr);
|
||||
explicit TidalStreamURLRequest(TidalService *service, NetworkAccessManager *network, const QUrl &original_url, const uint id, QObject *parent = nullptr);
|
||||
~TidalStreamURLRequest() override;
|
||||
|
||||
void GetStreamURL();
|
||||
@ -58,7 +58,8 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
||||
|
||||
signals:
|
||||
void TryLogin();
|
||||
void StreamURLFinished(int id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate, int bit_depth, qint64 duration, QString error = QString());
|
||||
void StreamURLFailure(uint id, QUrl original_url, QString error);
|
||||
void StreamURLSuccess(uint id, QUrl original_url, QUrl stream_url, Song::FileType filetype, int samplerate = -1, int bit_depth = -1, qint64 duration = -1);
|
||||
|
||||
private slots:
|
||||
void StreamURLReceived();
|
||||
@ -72,7 +73,7 @@ class TidalStreamURLRequest : public TidalBaseRequest {
|
||||
TidalService *service_;
|
||||
QNetworkReply *reply_;
|
||||
QUrl original_url_;
|
||||
int id_;
|
||||
uint id_;
|
||||
int song_id_;
|
||||
int tries_;
|
||||
bool need_login_;
|
||||
|
@ -32,38 +32,53 @@
|
||||
TidalUrlHandler::TidalUrlHandler(Application *app, TidalService *service)
|
||||
: UrlHandler(service),
|
||||
app_(app),
|
||||
service_(service),
|
||||
task_id_(-1) {
|
||||
service_(service) {
|
||||
|
||||
QObject::connect(service, &TidalService::StreamURLFinished, this, &TidalUrlHandler::GetStreamURLFinished);
|
||||
QObject::connect(service, &TidalService::StreamURLFailure, this, &TidalUrlHandler::GetStreamURLFailure);
|
||||
QObject::connect(service, &TidalService::StreamURLSuccess, this, &TidalUrlHandler::GetStreamURLSuccess);
|
||||
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult TidalUrlHandler::StartLoading(const QUrl &url) {
|
||||
|
||||
Request req;
|
||||
req.task_id = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
QString error;
|
||||
req.id = service_->GetStreamURL(url, error);
|
||||
if (req.id == 0) {
|
||||
CancelTask(req.task_id);
|
||||
return LoadResult(url, LoadResult::Error, error);
|
||||
}
|
||||
|
||||
requests_.insert(req.id, req);
|
||||
|
||||
LoadResult ret(url);
|
||||
if (task_id_ != -1) return ret;
|
||||
task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
service_->GetStreamURL(url);
|
||||
ret.type_ = LoadResult::WillLoadAsynchronously;
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void TidalUrlHandler::GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error) {
|
||||
void TidalUrlHandler::GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error) {
|
||||
|
||||
if (!requests_.contains(id)) return;
|
||||
Request req = requests_.take(id);
|
||||
CancelTask(req.task_id);
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
if (error.isEmpty()) {
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
}
|
||||
else {
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::Error, error));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void TidalUrlHandler::CancelTask() {
|
||||
app_->task_manager()->SetTaskFinished(task_id_);
|
||||
task_id_ = -1;
|
||||
void TidalUrlHandler::GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration) {
|
||||
|
||||
if (!requests_.contains(id)) return;
|
||||
Request req = requests_.take(id);
|
||||
CancelTask(req.task_id);
|
||||
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, stream_url, filetype, samplerate, bit_depth, duration));
|
||||
|
||||
}
|
||||
|
||||
void TidalUrlHandler::CancelTask(const int task_id) {
|
||||
app_->task_manager()->SetTaskFinished(task_id);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
@ -42,15 +43,22 @@ class TidalUrlHandler : public UrlHandler {
|
||||
QString scheme() const override { return service_->url_scheme(); }
|
||||
LoadResult StartLoading(const QUrl &url) override;
|
||||
|
||||
void CancelTask();
|
||||
private:
|
||||
void CancelTask(const int task_id);
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration, const QString &error = QString());
|
||||
void GetStreamURLFailure(const uint id, const QUrl &original_url, const QString &error);
|
||||
void GetStreamURLSuccess(const uint id, const QUrl &original_url, const QUrl &stream_url, const Song::FileType filetype, const int samplerate, const int bit_depth, const qint64 duration);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
Request() : id(0), task_id(-1) {}
|
||||
uint id;
|
||||
int task_id;
|
||||
};
|
||||
Application *app_;
|
||||
TidalService *service_;
|
||||
int task_id_;
|
||||
QMap<uint, Request> requests_;
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user