diff --git a/src/internet/internetservice.h b/src/internet/internetservice.h index 304fe4857..cb4fe1cb7 100644 --- a/src/internet/internetservice.h +++ b/src/internet/internetservice.h @@ -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_; diff --git a/src/qobuz/qobuzservice.cpp b/src/qobuz/qobuzservice.cpp index 38abbe046..ecc8cbff8 100644 --- a/src/qobuz/qobuzservice.cpp +++ b/src/qobuz/qobuzservice.cpp @@ -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 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 stream_url_req = std::make_shared(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 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 stream_url_req = stream_url_requests_.take(id); + + emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration); } diff --git a/src/qobuz/qobuzservice.h b/src/qobuz/qobuzservice.h index e38987983..08b1e5f0b 100644 --- a/src/qobuz/qobuzservice.h +++ b/src/qobuz/qobuzservice.h @@ -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 Param; @@ -211,8 +212,8 @@ class QobuzService : public InternetService { bool login_sent_; int login_attempts_; - int next_stream_url_request_id_; - QMap> stream_url_requests_; + uint next_stream_url_request_id_; + QMap> stream_url_requests_; QStringList login_errors_; diff --git a/src/qobuz/qobuzstreamurlrequest.cpp b/src/qobuz/qobuzstreamurlrequest.cpp index c87ac85e3..5a52b637f 100644 --- a/src/qobuz/qobuzstreamurlrequest.cpp +++ b/src/qobuz/qobuzstreamurlrequest.cpp @@ -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(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); } diff --git a/src/qobuz/qobuzstreamurlrequest.h b/src/qobuz/qobuzstreamurlrequest.h index c56f74082..0d9dcd747 100644 --- a/src/qobuz/qobuzstreamurlrequest.h +++ b/src/qobuz/qobuzstreamurlrequest.h @@ -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_; diff --git a/src/qobuz/qobuzurlhandler.cpp b/src/qobuz/qobuzurlhandler.cpp index ced4e21f8..cce85931a 100644 --- a/src/qobuz/qobuzurlhandler.cpp +++ b/src/qobuz/qobuzurlhandler.cpp @@ -17,6 +17,8 @@ * */ +#include "config.h" + #include #include #include @@ -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); } diff --git a/src/qobuz/qobuzurlhandler.h b/src/qobuz/qobuzurlhandler.h index 3a0990cf8..d4c69388c 100644 --- a/src/qobuz/qobuzurlhandler.h +++ b/src/qobuz/qobuzurlhandler.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -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 requests_; }; diff --git a/src/tidal/tidalservice.cpp b/src/tidal/tidalservice.cpp index 6218f0ca5..e5d3b9d88 100644 --- a/src/tidal/tidalservice.cpp +++ b/src/tidal/tidalservice.cpp @@ -211,7 +211,6 @@ TidalService::~TidalService() { while (!stream_url_requests_.isEmpty()) { std::shared_ptr 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 stream_url_req = std::make_shared(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 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 stream_url_req = stream_url_requests_.take(id); + + emit StreamURLSuccess(id, original_url, stream_url, filetype, samplerate, bit_depth, duration); } diff --git a/src/tidal/tidalservice.h b/src/tidal/tidalservice.h index 0e6b4bc30..0b442afae 100644 --- a/src/tidal/tidalservice.h +++ b/src/tidal/tidalservice.h @@ -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 Param; @@ -241,8 +242,8 @@ class TidalService : public InternetService { QString code_verifier_; QString code_challenge_; - int next_stream_url_request_id_; - QMap> stream_url_requests_; + uint next_stream_url_request_id_; + QMap> stream_url_requests_; QStringList login_errors_; diff --git a/src/tidal/tidalstreamurlrequest.cpp b/src/tidal/tidalstreamurlrequest.cpp index 389282a62..6257a1d8a 100644 --- a/src/tidal/tidalstreamurlrequest.cpp +++ b/src/tidal/tidalstreamurlrequest.cpp @@ -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); } diff --git a/src/tidal/tidalstreamurlrequest.h b/src/tidal/tidalstreamurlrequest.h index 700bbeb29..ad6428841 100644 --- a/src/tidal/tidalstreamurlrequest.h +++ b/src/tidal/tidalstreamurlrequest.h @@ -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_; diff --git a/src/tidal/tidalurlhandler.cpp b/src/tidal/tidalurlhandler.cpp index b6590c6e2..9283dd33d 100644 --- a/src/tidal/tidalurlhandler.cpp +++ b/src/tidal/tidalurlhandler.cpp @@ -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); } diff --git a/src/tidal/tidalurlhandler.h b/src/tidal/tidalurlhandler.h index f1bc102a0..5d6b00bf7 100644 --- a/src/tidal/tidalurlhandler.h +++ b/src/tidal/tidalurlhandler.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -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 requests_; };