More Tidal fixes
This commit is contained in:
@@ -9,6 +9,10 @@ Unreleased:
|
|||||||
* Fixed bug in pipeline not setting url
|
* Fixed bug in pipeline not setting url
|
||||||
* Fixed bug setting wrong temporary metadata
|
* Fixed bug setting wrong temporary metadata
|
||||||
* Removed device module from windows, since it's not implemented for windows
|
* Removed device module from windows, since it's not implemented for windows
|
||||||
|
* Added support for both ALSA hw and plughw
|
||||||
|
* Added option to change url stream scheme for Tidal
|
||||||
|
* Added encoding of Tidal token in the source code
|
||||||
|
* Added encoding of Tidal password in the configuration
|
||||||
|
|
||||||
Version 0.3.1:
|
Version 0.3.1:
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -690,21 +691,6 @@ bool IsLaptop() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SystemLanguageName() {
|
|
||||||
|
|
||||||
#if QT_VERSION >= 0x040800
|
|
||||||
QString system_language = QLocale::system().uiLanguages().empty() ? QLocale::system().name() : QLocale::system().uiLanguages().first();
|
|
||||||
// uiLanguages returns strings with "-" as separators for language/region;
|
|
||||||
// however QTranslator needs "_" separators
|
|
||||||
system_language.replace("-", "_");
|
|
||||||
#else
|
|
||||||
QString system_language = QLocale::system().name();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return system_language;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UrlOnSameDriveAsStrawberry(const QUrl &url) {
|
bool UrlOnSameDriveAsStrawberry(const QUrl &url) {
|
||||||
|
|
||||||
if (url.scheme() != "file") return false;
|
if (url.scheme() != "file") return false;
|
||||||
@@ -723,18 +709,13 @@ bool UrlOnSameDriveAsStrawberry(const QUrl &url) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QUrl GetRelativePathToStrawberryBin(const QUrl &url) {
|
QUrl GetRelativePathToStrawberryBin(const QUrl &url) {
|
||||||
|
|
||||||
QDir appPath(QCoreApplication::applicationDirPath());
|
QDir appPath(QCoreApplication::applicationDirPath());
|
||||||
return QUrl::fromLocalFile(appPath.relativeFilePath(url.toLocalFile()));
|
return QUrl::fromLocalFile(appPath.relativeFilePath(url.toLocalFile()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PathWithoutFilenameExtension(const QString &filename) {
|
QString PathWithoutFilenameExtension(const QString &filename) {
|
||||||
|
if (filename.section('/', -1, -1).contains('.')) return filename.section('.', 0, -2);
|
||||||
if (filename.section('/', -1, -1).contains('.'))
|
|
||||||
return filename.section('.', 0, -2);
|
|
||||||
return filename;
|
return filename;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString FiddleFileExtension(const QString &filename, const QString &new_extension) {
|
QString FiddleFileExtension(const QString &filename, const QString &new_extension) {
|
||||||
@@ -786,6 +767,28 @@ void CheckPortable() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString GetRandomStringWithChars(const int len) {
|
||||||
|
const QString UseCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");
|
||||||
|
return GetRandomString(len, UseCharacters);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetRandomStringWithCharsAndNumbers(const int len) {
|
||||||
|
const QString UseCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||||
|
return GetRandomString(len, UseCharacters);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString GetRandomString(const int len, const QString &UseCharacters) {
|
||||||
|
|
||||||
|
QString randstr;
|
||||||
|
for(int i=0 ; i < len ; ++i) {
|
||||||
|
int index = qrand() % UseCharacters.length();
|
||||||
|
QChar nextchar = UseCharacters.at(index);
|
||||||
|
randstr.append(nextchar);
|
||||||
|
}
|
||||||
|
return randstr;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Utilities
|
} // namespace Utilities
|
||||||
|
|
||||||
ScopedWCharArray::ScopedWCharArray(const QString &str)
|
ScopedWCharArray::ScopedWCharArray(const QString &str)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* Strawberry Music Player
|
* Strawberry Music Player
|
||||||
* This file was part of Clementine.
|
* This file was part of Clementine.
|
||||||
* Copyright 2010, David Sansome <me@davidsansome.com>
|
* Copyright 2010, David Sansome <me@davidsansome.com>
|
||||||
|
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||||
*
|
*
|
||||||
* Strawberry is free software: you can redistribute it and/or modify
|
* Strawberry is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -149,7 +150,10 @@ int GetThreadId();
|
|||||||
// Returns true if this machine has a battery.
|
// Returns true if this machine has a battery.
|
||||||
bool IsLaptop();
|
bool IsLaptop();
|
||||||
|
|
||||||
QString SystemLanguageName();
|
QString GetRandomStringWithChars(const int len);
|
||||||
|
QString GetRandomStringWithCharsAndNumbers(const int len);
|
||||||
|
QString GetRandomString(const int len, const QString &UseCharacters);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScopedWCharArray {
|
class ScopedWCharArray {
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ void TidalSettingsPage::Load() {
|
|||||||
|
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
ui_->username->setText(s.value("username").toString());
|
ui_->username->setText(s.value("username").toString());
|
||||||
ui_->password->setText(s.value("password").toString());
|
QByteArray password = s.value("password").toByteArray();
|
||||||
|
if (password.isEmpty()) ui_->password->setText("");
|
||||||
|
else ui_->password->setText(QByteArray::fromBase64(password));
|
||||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH");
|
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "HIGH");
|
||||||
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||||
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
||||||
@@ -94,7 +96,7 @@ void TidalSettingsPage::Save() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(kSettingsGroup);
|
s.beginGroup(kSettingsGroup);
|
||||||
s.setValue("username", ui_->username->text());
|
s.setValue("username", ui_->username->text());
|
||||||
s.setValue("password", ui_->password->text());
|
s.setValue("password", ui_->password->text().toUtf8().toBase64());
|
||||||
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
||||||
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
||||||
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class TidalSettingsPage : public SettingsPage {
|
|||||||
bool eventFilter(QObject *object, QEvent *event);
|
bool eventFilter(QObject *object, QEvent *event);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void Login(const QString &username, const QString &password, const int search_id = 0);
|
void Login(const QString &username, const QString &password);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void LoginClicked();
|
void LoginClicked();
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ const Song::Source TidalService::kSource = Song::Source_Tidal;
|
|||||||
const char *TidalService::kApiUrl = "https://listen.tidal.com/v1";
|
const char *TidalService::kApiUrl = "https://listen.tidal.com/v1";
|
||||||
const char *TidalService::kAuthUrl = "https://listen.tidal.com/v1/login/username";
|
const char *TidalService::kAuthUrl = "https://listen.tidal.com/v1/login/username";
|
||||||
const char *TidalService::kResourcesUrl = "http://resources.tidal.com";
|
const char *TidalService::kResourcesUrl = "http://resources.tidal.com";
|
||||||
const char *TidalService::kApiToken = "P5Xbeo5LFvESeDy6";
|
const char *TidalService::kApiTokenB64 = "UDVYYmVvNUxGdkVTZUR5Ng==";
|
||||||
const int TidalService::kLoginAttempts = 2;
|
const int TidalService::kLoginAttempts = 2;
|
||||||
|
|
||||||
typedef QPair<QString, QString> Param;
|
typedef QPair<QString, QString> Param;
|
||||||
@@ -76,7 +76,11 @@ TidalService::TidalService(Application *app, InternetModel *parent)
|
|||||||
user_id_(0),
|
user_id_(0),
|
||||||
pending_search_id_(0),
|
pending_search_id_(0),
|
||||||
next_pending_search_id_(1),
|
next_pending_search_id_(1),
|
||||||
login_sent_(false)
|
search_id_(0),
|
||||||
|
albums_requested_(0),
|
||||||
|
albums_received_(0),
|
||||||
|
login_sent_(false),
|
||||||
|
login_attempts_(0)
|
||||||
{
|
{
|
||||||
|
|
||||||
timer_searchdelay_->setSingleShot(true);
|
timer_searchdelay_->setSingleShot(true);
|
||||||
@@ -103,7 +107,7 @@ void TidalService::ReloadSettings() {
|
|||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
username_ = s.value("username").toString();
|
username_ = s.value("username").toString();
|
||||||
password_ = s.value("password").toString();
|
password_ = QByteArray::fromBase64(s.value("password").toByteArray());
|
||||||
quality_ = s.value("quality").toString();
|
quality_ = s.value("quality").toString();
|
||||||
searchdelay_ = s.value("searchdelay", 1500).toInt();
|
searchdelay_ = s.value("searchdelay", 1500).toInt();
|
||||||
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
||||||
@@ -123,6 +127,7 @@ void TidalService::LoadSessionID() {
|
|||||||
session_id_ = s.value("session_id").toString();
|
session_id_ = s.value("session_id").toString();
|
||||||
user_id_ = s.value("user_id").toInt();
|
user_id_ = s.value("user_id").toInt();
|
||||||
country_code_ = s.value("country_code").toString();
|
country_code_ = s.value("country_code").toString();
|
||||||
|
clientuniquekey_ = Utilities::GetRandomStringWithChars(12);
|
||||||
s.endGroup();
|
s.endGroup();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -144,7 +149,10 @@ void TidalService::SendLogin(const QString &username, const QString &password) {
|
|||||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||||
typedef QList<EncodedArg> EncodedArgList;
|
typedef QList<EncodedArg> EncodedArgList;
|
||||||
|
|
||||||
ArgList args = ArgList() <<Arg("token", kApiToken) << Arg("username", username) << Arg("password", password) << Arg("clientVersion", "2.2.1--7");
|
ArgList args = ArgList() << Arg("token", QByteArray::fromBase64(kApiTokenB64))
|
||||||
|
<< Arg("username", username)
|
||||||
|
<< Arg("password", password)
|
||||||
|
<< Arg("clientUniqueKey", clientuniquekey_);
|
||||||
|
|
||||||
QStringList query_items;
|
QStringList query_items;
|
||||||
QUrlQuery url_query;
|
QUrlQuery url_query;
|
||||||
@@ -158,6 +166,7 @@ void TidalService::SendLogin(const QString &username, const QString &password) {
|
|||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
|
|
||||||
req.setRawHeader("Origin", "http://listen.tidal.com");
|
req.setRawHeader("Origin", "http://listen.tidal.com");
|
||||||
|
req.setRawHeader("X-Tidal-Token", QByteArray::fromBase64(kApiTokenB64));
|
||||||
QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
QNetworkReply *reply = network_->post(req, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(HandleAuthReply(QNetworkReply*)), reply);
|
||||||
|
|
||||||
@@ -244,6 +253,7 @@ void TidalService::HandleAuthReply(QNetworkReply *reply) {
|
|||||||
country_code_ = json_obj["countryCode"].toString();
|
country_code_ = json_obj["countryCode"].toString();
|
||||||
session_id_ = json_obj["sessionId"].toString();
|
session_id_ = json_obj["sessionId"].toString();
|
||||||
user_id_ = json_obj["userId"].toInt();
|
user_id_ = json_obj["userId"].toInt();
|
||||||
|
clientuniquekey_ = Utilities::GetRandomStringWithChars(12);
|
||||||
|
|
||||||
QSettings s;
|
QSettings s;
|
||||||
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
s.beginGroup(TidalSettingsPage::kSettingsGroup);
|
||||||
@@ -307,9 +317,11 @@ QNetworkReply *TidalService::CreateRequest(const QString &ressource_name, const
|
|||||||
QUrl url(kApiUrl + QString("/") + ressource_name);
|
QUrl url(kApiUrl + QString("/") + ressource_name);
|
||||||
url.setQuery(url_query);
|
url.setQuery(url_query);
|
||||||
QNetworkRequest req(url);
|
QNetworkRequest req(url);
|
||||||
|
req.setRawHeader("Origin", "http://listen.tidal.com");
|
||||||
|
req.setRawHeader("X-Tidal-SessionId", session_id_.toUtf8());
|
||||||
QNetworkReply *reply = network_->get(req);
|
QNetworkReply *reply = network_->get(req);
|
||||||
|
|
||||||
//qLog(Debug) << "Tidal: Sending request" << url;
|
qLog(Debug) << "Tidal: Sending request" << url;
|
||||||
|
|
||||||
return reply;
|
return reply;
|
||||||
|
|
||||||
@@ -611,11 +623,7 @@ void TidalService::SearchFinished(QNetworkReply *reply, int id) {
|
|||||||
void TidalService::GetAlbum(const int album_id) {
|
void TidalService::GetAlbum(const int album_id) {
|
||||||
|
|
||||||
QList<Param> parameters;
|
QList<Param> parameters;
|
||||||
parameters << Param("token", session_id_)
|
|
||||||
<< Param("soundQuality", quality_);
|
|
||||||
|
|
||||||
QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters);
|
QNetworkReply *reply = CreateRequest(QString("albums/%1/tracks").arg(album_id), parameters);
|
||||||
|
|
||||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_id);
|
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_id);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -784,8 +792,7 @@ void TidalService::GetStreamURL(const QUrl &url) {
|
|||||||
requests_song_.insert(song_id, url);
|
requests_song_.insert(song_id, url);
|
||||||
|
|
||||||
QList<Param> parameters;
|
QList<Param> parameters;
|
||||||
parameters << Param("token", session_id_)
|
parameters << Param("soundQuality", quality_);
|
||||||
<< Param("soundQuality", quality_);
|
|
||||||
|
|
||||||
QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters);
|
QNetworkReply *reply = CreateRequest(QString("tracks/%1/streamUrl").arg(song_id), parameters);
|
||||||
|
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ class TidalService : public InternetService {
|
|||||||
static const char *kApiUrl;
|
static const char *kApiUrl;
|
||||||
static const char *kAuthUrl;
|
static const char *kAuthUrl;
|
||||||
static const char *kResourcesUrl;
|
static const char *kResourcesUrl;
|
||||||
static const char *kApiToken;
|
static const char *kApiTokenB64;
|
||||||
|
|
||||||
NetworkAccessManager *network_;
|
NetworkAccessManager *network_;
|
||||||
TidalUrlHandler *url_handler_;
|
TidalUrlHandler *url_handler_;
|
||||||
@@ -121,6 +121,7 @@ class TidalService : public InternetService {
|
|||||||
QString session_id_;
|
QString session_id_;
|
||||||
quint64 user_id_;
|
quint64 user_id_;
|
||||||
QString country_code_;
|
QString country_code_;
|
||||||
|
QString clientuniquekey_;
|
||||||
|
|
||||||
int pending_search_id_;
|
int pending_search_id_;
|
||||||
int next_pending_search_id_;
|
int next_pending_search_id_;
|
||||||
|
|||||||
Reference in New Issue
Block a user