Improve URL handler, return error for encrypted Tidal streams

This commit is contained in:
Jonas Kvinge 2021-11-08 20:25:22 +01:00
parent fd85763fb4
commit 01f8129ed0
13 changed files with 218 additions and 108 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 (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));
}
if (!requests_.contains(id)) return;
Request req = requests_.take(id);
CancelTask(req.task_id);
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);
}

View File

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

View File

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

View File

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

View File

@ -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);
QObject::disconnect(reply_, nullptr, this, nullptr);
reply_->deleteLater();
reply_ = nullptr;
if (data.isEmpty()) {
reply_ = nullptr;
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);
}

View File

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

View File

@ -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 (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));
}
if (!requests_.contains(id)) return;
Request req = requests_.take(id);
CancelTask(req.task_id);
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);
}

View File

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