Add Deezer support

This commit is contained in:
Jonas Kvinge
2018-10-14 00:08:33 +02:00
parent 4aad44cb62
commit 0a81fa99fc
78 changed files with 5309 additions and 630 deletions

487
src/engine/deezerengine.cpp Normal file
View File

@@ -0,0 +1,487 @@
/*
* Strawberry Music Player
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <deezer/deezer-connect.h>
#include <deezer/deezer-player.h>
#include <QtGlobal>
#include <QCoreApplication>
#include <QStandardPaths>
#include <QByteArray>
#include <QString>
#include <QUrl>
#include <QDateTime>
#include "core/timeconstants.h"
#include "core/taskmanager.h"
#include "core/logging.h"
#include "engine_fwd.h"
#include "enginebase.h"
#include "enginetype.h"
#include "deezerengine.h"
#include "deezer/deezerservice.h"
#include "settings/deezersettingspage.h"
DeezerEngine::DeezerEngine(TaskManager *task_manager)
: EngineBase(),
state_(Engine::Empty),
position_(0) {
type_ = Engine::Deezer;
ReloadSettings();
}
DeezerEngine::~DeezerEngine() {
if (player_) {
dz_object_release((dz_object_handle) player_);
player_ = nullptr;
}
if (connect_) {
dz_object_release((dz_object_handle) connect_);
connect_ = nullptr;
}
}
bool DeezerEngine::Init() {
qLog(Debug) << "Deezer native SDK Version:" << dz_connect_get_build_id();
struct dz_connect_configuration config;
memset(&config, 0, sizeof(struct dz_connect_configuration));
config.app_id = QString::number(DeezerService::kAppID).toUtf8();
config.product_id = QCoreApplication::applicationName().toUtf8();
config.product_build_id = QCoreApplication::applicationVersion().toUtf8().constData();
config.user_profile_path = QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toUtf8().constData();
config.connect_event_cb = ConnectEventCallback;
connect_ = dz_connect_new(&config);
if (!connect_) {
qLog(Error) << "Deezer: Failed to create connect.";
return false;
}
qLog(Debug) << "Device ID:" << dz_connect_get_device_id(connect_);
dz_error_t dzerr(DZ_ERROR_NO_ERROR);
dzerr = dz_connect_debug_log_disable(connect_);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to disable debug log.";
return false;
}
dzerr = dz_connect_activate(connect_, this);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to activate connect.";
return false;
}
dz_connect_cache_path_set(connect_, nullptr, nullptr, QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toUtf8().constData());
player_ = dz_player_new(connect_);
if (!player_) {
qLog(Error) << "Deezer: Failed to create player.";
return false;
}
dzerr = dz_player_activate(player_, this);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to activate player.";
return false;
}
dzerr = dz_player_set_event_cb(player_, PlayerEventCallback);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set event callback.";
return false;
}
dzerr = dz_player_set_metadata_cb(player_, PlayerMetaDataCallback);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set metadata callback.";
return false;
}
dzerr = dz_player_set_render_progress_cb(player_, PlayerProgressCallback, 1000);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set progress callback.";
return false;
}
dzerr = dz_player_set_crossfading_duration(player_, nullptr, nullptr, 3000);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set crossfade duration.";
return false;
}
dzerr = dz_connect_offline_mode(connect_, nullptr, nullptr, false);
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set offline mode.";
return false;
}
LoadAccessToken();
return true;
}
bool DeezerEngine::Initialised() const {
if (connect_ && player_) return true;
return false;
}
void DeezerEngine::LoadAccessToken() {
QSettings s;
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
if (!s.contains("access_token") || !s.contains("expiry_time")) return;
access_token_ = s.value("access_token").toString();
expiry_time_ = s.value("expiry_time").toDateTime();
s.endGroup();
dz_error_t dzerr = dz_connect_set_access_token(connect_, nullptr, nullptr, access_token_.toUtf8().constData());
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set access token.";
}
}
bool DeezerEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Initialised()) return false;
Engine::Base::Load(media_url, original_url, change, force_stop_at_end, beginning_nanosec, end_nanosec);
dz_error_t dzerr = dz_player_load(player_, nullptr, nullptr, media_url.toString().toUtf8().constData());
if (dzerr != DZ_ERROR_NO_ERROR) return false;
return true;
}
bool DeezerEngine::Play(quint64 offset_nanosec) {
if (!Initialised()) return false;
dz_error_t dzerr(DZ_ERROR_NO_ERROR);
if (state() == Engine::Paused) dzerr = dz_player_resume(player_, nullptr, nullptr);
else dzerr = dz_player_play(player_, nullptr, nullptr, DZ_PLAYER_PLAY_CMD_START_TRACKLIST, DZ_INDEX_IN_QUEUELIST_CURRENT);
if (dzerr != DZ_ERROR_NO_ERROR) return false;
Seek(offset_nanosec);
return true;
}
void DeezerEngine::Stop(bool stop_after) {
if (!Initialised()) return;
dz_error_t dzerr = dz_player_stop(player_, nullptr, nullptr);
if (dzerr != DZ_ERROR_NO_ERROR) return;
state_ = Engine::Empty;
emit TrackEnded();
}
void DeezerEngine::Pause() {
if (!Initialised()) return;
dz_error_t dzerr = dz_player_pause(player_, nullptr, nullptr);
if (dzerr != DZ_ERROR_NO_ERROR) return;
}
void DeezerEngine::Unpause() {
if (!Initialised()) return;
dz_error_t dzerr = dz_player_resume(player_, nullptr, nullptr);
if (dzerr != DZ_ERROR_NO_ERROR) return;
}
void DeezerEngine::Seek(quint64 offset_nanosec) {
if (!Initialised()) return;
int offset = (offset_nanosec / kNsecPerMsec);
uint len = (length_nanosec() / kNsecPerMsec);
if (len == 0) return;
float pos = float(offset) / len;
dz_error_t dzerr = dz_player_seek(player_, nullptr, nullptr, pos);
if (dzerr != DZ_ERROR_NO_ERROR) return;
}
void DeezerEngine::SetVolumeSW(uint percent) {
if (!Initialised()) return;
dz_error_t dzerr = dz_player_set_output_volume(player_, nullptr, nullptr, percent);
if (dzerr != DZ_ERROR_NO_ERROR) qLog(Error) << "Deezer: Failed to set volume.";
}
qint64 DeezerEngine::position_nanosec() const {
if (state() == Engine::Empty) return 0;
const qint64 result = (position_ * kNsecPerUsec);
return qint64(qMax(0ll, result));
}
qint64 DeezerEngine::length_nanosec() const {
if (state() == Engine::Empty) return 0;
const qint64 result = (end_nanosec_ - beginning_nanosec_);
return result;
}
EngineBase::OutputDetailsList DeezerEngine::GetOutputsList() const {
OutputDetailsList ret;
OutputDetails output;
output.name = "default";
output.description = "Default";
output.iconname = "soundcard";
ret << output;
return ret;
}
bool DeezerEngine::ValidOutput(const QString &output) {
return(true);
}
bool DeezerEngine::CustomDeviceSupport(const QString &output) {
return false;
}
bool DeezerEngine::ALSADeviceSupport(const QString &output) {
return false;
}
bool DeezerEngine::CanDecode(const QUrl &url) {
if (url.scheme() == "dzmedia") return true;
else return false;
}
void DeezerEngine::ConnectEventCallback(dz_connect_handle handle, dz_connect_event_handle event, void *delegate) {
dz_connect_event_t type = dz_connect_event_get_type(event);
//DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(delegate);
switch (type) {
case DZ_CONNECT_EVENT_USER_OFFLINE_AVAILABLE:
qLog(Debug) << "CONNECT_EVENT USER_OFFLINE_AVAILABLE";
break;
case DZ_CONNECT_EVENT_USER_ACCESS_TOKEN_OK: {
const char* szAccessToken;
szAccessToken = dz_connect_event_get_access_token(event);
qLog(Debug) << "CONNECT_EVENT USER_ACCESS_TOKEN_OK Access_token :" << szAccessToken;
}
break;
case DZ_CONNECT_EVENT_USER_ACCESS_TOKEN_FAILED:
qLog(Debug) << "CONNECT_EVENT USER_ACCESS_TOKEN_FAILED";
break;
case DZ_CONNECT_EVENT_USER_LOGIN_OK:
qLog(Debug) << "Deezer CONNECT_EVENT USER_LOGIN_OK";
break;
case DZ_CONNECT_EVENT_USER_NEW_OPTIONS:
qLog(Debug) << "Deezer: CONNECT_EVENT USER_NEW_OPTIONS";
break;
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_NETWORK_ERROR:
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_NETWORK_ERROR";
break;
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_BAD_CREDENTIALS:
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_BAD_CREDENTIALS";
break;
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_USER_INFO:
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_USER_INFO";
break;
case DZ_CONNECT_EVENT_USER_LOGIN_FAIL_OFFLINE_MODE:
qLog(Debug) << "Deezer: CONNECT_EVENT USER_LOGIN_FAIL_OFFLINE_MODE";
break;
case DZ_CONNECT_EVENT_ADVERTISEMENT_START:
qLog(Debug) << "Deezer: CONNECT_EVENTADVERTISEMENT_START";
break;
case DZ_CONNECT_EVENT_ADVERTISEMENT_STOP:
qLog(Debug) << "Deezer: CONNECT_EVENTADVERTISEMENT_STOP";
break;
case DZ_CONNECT_EVENT_UNKNOWN:
default:
qLog(Debug) << "Deezer: CONNECT_EVENTUNKNOWN or default (type =" << type;
break;
}
}
void DeezerEngine::PlayerEventCallback(dz_player_handle handle, dz_player_event_handle event, void *supervisor) {
DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(supervisor);
dz_streaming_mode_t streaming_mode;
dz_index_in_queuelist idx;
dz_player_event_t type = dz_player_event_get_type(event);
if (!dz_player_event_get_queuelist_context(event, &streaming_mode, &idx)) {
streaming_mode = DZ_STREAMING_MODE_ONDEMAND;
idx = DZ_INDEX_IN_QUEUELIST_INVALID;
}
switch (type) {
case DZ_PLAYER_EVENT_LIMITATION_FORCED_PAUSE:
qLog(Debug) << "Deezer: PLAYER_EVENT_LIMITATION_FORCED_PAUSE";
break;
case DZ_PLAYER_EVENT_QUEUELIST_LOADED:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_LOADED";
break;
case DZ_PLAYER_EVENT_QUEUELIST_NO_RIGHT:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_NO_RIGHT";
break;
case DZ_PLAYER_EVENT_QUEUELIST_NEED_NATURAL_NEXT:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_NEED_NATURAL_NEXT";
break;
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_NOT_AVAILABLE_OFFLINE:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_TRACK_NOT_AVAILABLE_OFFLINE";
engine->state_ = Engine::Error;
emit engine->StateChanged(engine->state_);
emit engine->Error("Track not available offline.");
break;
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_RIGHTS_AFTER_AUDIOADS:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_TRACK_RIGHTS_AFTER_AUDIOADS";
break;
case DZ_PLAYER_EVENT_QUEUELIST_SKIP_NO_RIGHT:
qLog(Debug) << "Deezer: PLAYER_EVENT_QUEUELIST_SKIP_NO_RIGHT";
break;
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_SELECTED:
break;
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY:
qLog(Debug) << "Deezer: PLAYER_EVENT_MEDIASTREAM_DATA_READY";
break;
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY_AFTER_SEEK:
qLog(Debug) << "Deezer: PLAYER_EVENT_MEDIASTREAM_DATA_READY_AFTER_SEEK";
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_START_FAILURE:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_START_FAILURE";
engine->state_ = Engine::Error;
emit engine->StateChanged(engine->state_);
emit engine->Error("Track start failure.");
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_START:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_START";
engine->state_ = Engine::Playing;
engine->position_ = 0;
emit engine->StateChanged(engine->state_);
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_END:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_END";
engine->state_ = Engine::Idle;
engine->position_ = 0;
emit engine->TrackEnded();
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_PAUSED:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_PAUSED";
engine->state_ = Engine::Paused;
emit engine->StateChanged(engine->state_);
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_UNDERFLOW:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_UNDERFLOW";
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_RESUMED:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_RESUMED";
engine->state_ = Engine::Playing;
emit engine->StateChanged(engine->state_);
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_SEEKING:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_SEEKING";
break;
case DZ_PLAYER_EVENT_RENDER_TRACK_REMOVED:
qLog(Debug) << "Deezer: PLAYER_EVENT_RENDER_TRACK_REMOVED";
engine->state_ = Engine::Empty;
engine->position_ = 0;
emit engine->TrackEnded();
break;
case DZ_PLAYER_EVENT_UNKNOWN:
default:
qLog(Error) << "Deezer: Unknown player event" << type;
break;
}
//emit engine->StateChanged(engine->state_);
}
void DeezerEngine::PlayerProgressCallback(dz_player_handle handle, dz_useconds_t progress, void *userdata) {
DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(userdata);
engine->position_ = progress;
}
void DeezerEngine::PlayerMetaDataCallback(dz_player_handle handle, dz_track_metadata_handle metadata, void *userdata) {
//DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(userdata);
}

88
src/engine/deezerengine.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* Strawberry Music Player
* Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef DEEZERENGINE_H
#define DEEZERENGINE_H
#include "config.h"
#include <stdbool.h>
#include <deezer/deezer-connect.h>
#include <deezer/deezer-player.h>
#include <QtGlobal>
#include <QObject>
#include <QString>
#include <QUrl>
#include <QDateTime>
#include "engine_fwd.h"
#include "enginebase.h"
class TaskManager;
class DeezerEngine : public Engine::Base {
Q_OBJECT
public:
DeezerEngine(TaskManager *task_manager);
~DeezerEngine();
bool Init();
Engine::State state() const { return state_; }
bool Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec);
bool Play(quint64 offset_nanosec);
void Stop(bool stop_after = false);
void Pause();
void Unpause();
void Seek(quint64 offset_nanosec);
protected:
void SetVolumeSW(uint percent);
public:
virtual qint64 position_nanosec() const;
virtual qint64 length_nanosec() const;
OutputDetailsList GetOutputsList() const;
bool ValidOutput(const QString &output);
QString DefaultOutput() { return ""; }
bool CustomDeviceSupport(const QString &output);
bool ALSADeviceSupport(const QString &output);
private:
Engine::State state_;
dz_connect_handle connect_;
dz_player_handle player_;
QString access_token_;
QDateTime expiry_time_;
qint64 position_;
bool Initialised() const;
bool CanDecode(const QUrl &url);
static void ConnectEventCallback(dz_connect_handle handle, dz_connect_event_handle event, void *delegate);
static void PlayerEventCallback(dz_player_handle handle, dz_player_event_handle event, void *supervisor);
static void PlayerMetaDataCallback(dz_player_handle handle, dz_track_metadata_handle metadata, void *userdata);
static void PlayerProgressCallback(dz_player_handle handle, dz_useconds_t progress, void *userdata);
public slots:
void LoadAccessToken();
};
#endif

View File

@@ -147,8 +147,7 @@ signals:
void MetaData(const Engine::SimpleMetaBundle&);
// Signals that the engine's state has changed (a stream was stopped for example).
// Always use the state from event, because it's not guaranteed that immediate
// subsequent call to state() won't return a stale value.
// Always use the state from event, because it's not guaranteed that immediate subsequent call to state() won't return a stale value.
void StateChanged(Engine::State);
protected:

View File

@@ -1,7 +1,6 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
* Copyright 2014, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -1,7 +1,6 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2014, David Sansome <me@davidsansome.com>
* Copyright 2014, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by

View File

@@ -28,19 +28,21 @@ namespace Engine {
Engine::EngineType EngineTypeFromName(QString enginename) {
QString lower = enginename.toLower();
if (lower == "xine") return Engine::Xine;
else if (lower == "gstreamer") return Engine::GStreamer;
else if (lower == "phonon") return Engine::Phonon;
else if (lower == "vlc") return Engine::VLC;
else return Engine::None;
if (lower == "gstreamer") return Engine::GStreamer;
else if (lower == "xine") return Engine::Xine;
else if (lower == "vlc") return Engine::VLC;
else if (lower == "phonon") return Engine::Phonon;
else if (lower == "deezer") return Engine::Deezer;
else return Engine::None;
}
QString EngineName(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QString("xine");
case Engine::GStreamer: return QString("gstreamer");
case Engine::Phonon: return QString("phonon");
case Engine::Xine: return QString("xine");
case Engine::VLC: return QString("vlc");
case Engine::Phonon: return QString("phonon");
case Engine::Deezer: return QString("deezer");
case Engine::None:
default: return QString("None");
}
@@ -48,10 +50,11 @@ QString EngineName(Engine::EngineType enginetype) {
QString EngineDescription(Engine::EngineType enginetype) {
switch (enginetype) {
case Engine::Xine: return QString("Xine");
case Engine::GStreamer: return QString("GStreamer");
case Engine::Phonon: return QString("Phonon");
case Engine::Xine: return QString("Xine");
case Engine::VLC: return QString("VLC");
case Engine::Phonon: return QString("Phonon");
case Engine::Deezer: return QString("Deezer");
case Engine::None:
default: return QString("None");

View File

@@ -32,7 +32,8 @@ enum EngineType {
GStreamer,
VLC,
Xine,
Phonon
Phonon,
Deezer
};
Engine::EngineType EngineTypeFromName(QString enginename);