Remove Spotify playback support

libspotify is dead
This commit is contained in:
John Maguire 2022-09-01 21:48:13 +01:00
parent 99029ed643
commit a551c40c4e
35 changed files with 9 additions and 5555 deletions

View File

@ -60,7 +60,6 @@ find_library(PROTOBUF_STATIC_LIBRARY libprotobuf.a libprotobuf)
pkg_check_modules(CDIO libcdio)
pkg_check_modules(CHROMAPRINT REQUIRED libchromaprint)
pkg_search_module(CRYPTOPP cryptopp libcrypto++)
pkg_check_modules(GIO gio-2.0)
pkg_check_modules(GLIB REQUIRED glib-2.0)
pkg_check_modules(GOBJECT REQUIRED gobject-2.0)
@ -75,7 +74,6 @@ pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(LIBMYGPO_QT5 libmygpo-qt5>=1.0.9)
pkg_check_modules(LIBPULSE libpulse)
pkg_check_modules(LIBXML libxml-2.0)
pkg_check_modules(LIBSPOTIFY libspotify>=12.1.45)
pkg_check_modules(TAGLIB taglib)
if (WIN32)
@ -167,12 +165,6 @@ endif()
if (APPLE)
find_library(SPARKLE Sparkle)
find_library(LIBSPOTIFY libspotify)
if(LIBSPOTIFY_FOUND)
set(LIBSPOTIFY_INCLUDE_DIRS ${LIBSPOTIFY})
set(LIBSPOTIFY_LIBRARIES ${LIBSPOTIFY})
endif(LIBSPOTIFY_FOUND)
add_subdirectory(3rdparty/SPMediaKeyTap)
set(SPMEDIAKEYTAP_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/SPMediaKeyTap)
@ -296,19 +288,6 @@ optional_component(UDISKS2 ON "Devices: UDisks2 backend"
DEPENDS "D-Bus support" Qt5DBus_FOUND
)
optional_component(SPOTIFY_BLOB ON "Spotify support: non-GPL binary helper"
DEPENDS "protobuf" PROTOBUF_FOUND PROTOBUF_PROTOC_EXECUTABLE
DEPENDS "libspotify" LIBSPOTIFY_FOUND
)
if (CRYPTOPP_FOUND OR HAVE_SPOTIFY_BLOB)
set(CRYPTOPP_OR_HAVE_SPOTIFY_BLOB ON)
endif()
optional_component(SPOTIFY ON "Spotify support"
DEPENDS "cryptopp or spotify blob" CRYPTOPP_OR_HAVE_SPOTIFY_BLOB
)
optional_component(MOODBAR ON "Moodbar support"
DEPENDS "fftw3" FFTW3_FOUND
)
@ -340,13 +319,6 @@ if (APPLE AND USE_BUNDLE AND NOT USE_BUNDLE_DIR)
set(USE_BUNDLE_DIR "../PlugIns")
endif()
if(CRYPTOPP_FOUND)
set(HAVE_CRYPTOPP ON)
if(HAVE_SPOTIFY)
set(HAVE_SPOTIFY_DOWNLOADER ON)
endif(HAVE_SPOTIFY)
endif(CRYPTOPP_FOUND)
# Remove GLU and GL from the link line - they're not really required
# and don't exist on my mingw toolchain
list(REMOVE_ITEM QT_LIBRARIES "-lGLU -lGL")
@ -454,9 +426,6 @@ add_subdirectory(ext/libclementine-common)
add_subdirectory(ext/libclementine-tagreader)
add_subdirectory(ext/clementine-tagreader)
add_subdirectory(ext/libclementine-remote)
if(HAVE_SPOTIFY)
add_subdirectory(ext/libclementine-spotifyblob)
endif(HAVE_SPOTIFY)
option(WITH_DEBIAN OFF)
if(WITH_DEBIAN)
@ -467,10 +436,6 @@ if(HAVE_BREAKPAD)
add_subdirectory(3rdparty/google-breakpad)
endif(HAVE_BREAKPAD)
if(HAVE_SPOTIFY_BLOB)
add_subdirectory(ext/clementine-spotifyblob)
endif(HAVE_SPOTIFY_BLOB)
if(HAVE_MOODBAR)
add_subdirectory(gst/moodbar)
endif()

View File

@ -302,7 +302,6 @@ Section "Clementine" Clementine
File "clementine.exe"
File "clementine-tagreader.exe"
File "clementine-spotifyblob.exe"
File "clementine.ico"
File "glew32.dll"
File "libcdio-19.dll"
@ -355,7 +354,6 @@ Section "Clementine" Clementine
File "libpsl-5.dll"
File "libsoup-2.4-1.dll"
File "libspeex-1.dll"
File "libspotify.dll"
File "libssl-1_1.dll"
File "libsqlite3-0.dll"
File "libstdc++-6.dll"

View File

@ -1,79 +0,0 @@
include_directories(${LIBSPOTIFY_INCLUDE_DIRS})
include_directories(${PROTOBUF_INCLUDE_DIRS})
include_directories(${CMAKE_CURRENT_BINARY_DIR})
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-spotifyblob)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-spotifyblob)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-common)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual -Wall -Wno-sign-compare -Wno-deprecated-declarations -Wno-unused-local-typedefs -Wno-unused-private-field -Wno-unknown-warning-option")
link_directories(${LIBSPOTIFY_LIBRARY_DIRS})
set(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR})
set(SOURCES
main.cpp
mediapipeline.cpp
spotifyclient.cpp
spotify_utilities.cpp
)
set(HEADERS
spotifyclient.h
)
if(APPLE)
list(APPEND SOURCES spotify_utilities.mm)
endif(APPLE)
qt5_wrap_cpp(MOC ${HEADERS})
if(WIN32 AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE)
set(win32_build_flag WIN32)
endif(WIN32 AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT ENABLE_WIN32_CONSOLE)
add_executable(clementine-spotifyblob
${win32_build_flag}
${SOURCES}
${MOC}
)
target_link_libraries(clementine-spotifyblob
${LIBSPOTIFY_LIBRARIES} ${LIBSPOTIFY_LDFLAGS}
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
${GSTREAMER_BASE_LIBRARIES}
${GSTREAMER_APP_LIBRARIES}
${PROTOBUF_STATIC_LIBRARY}
clementine-spotifyblob-messages
libclementine-common
)
if(APPLE)
target_link_libraries(clementine-spotifyblob
"-framework Foundation"
)
endif(APPLE)
if(NOT APPLE)
# macdeploy.py takes care of this on mac
install(TARGETS clementine-spotifyblob
RUNTIME DESTINATION bin
)
endif(NOT APPLE)
if(LINUX)
# Versioned name of the blob
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(SPOTIFY_BLOB_ARCH 32)
else(CMAKE_SIZEOF_VOID_P EQUAL 4)
set(SPOTIFY_BLOB_ARCH 64)
endif(CMAKE_SIZEOF_VOID_P EQUAL 4)
install(
FILES ${CMAKE_BINARY_DIR}/clementine-spotifyblob
DESTINATION ${CMAKE_BINARY_DIR}/spotify/version${SPOTIFY_BLOB_VERSION}-${SPOTIFY_BLOB_ARCH}bit/
RENAME blob
)
endif(LINUX)

View File

@ -1,49 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#include <gst/gst.h>
#include <QCoreApplication>
#include <QStringList>
#include "core/logging.h"
#include "spotifyclient.h"
int main(int argc, char** argv) {
QCoreApplication a(argc, argv);
QCoreApplication::setApplicationName("Clementine");
QCoreApplication::setOrganizationName("Clementine");
QCoreApplication::setOrganizationDomain("clementine-player.org");
logging::Init();
gst_init(nullptr, nullptr);
const QStringList arguments(a.arguments());
if (arguments.length() != 2) {
qFatal("Usage: %s port", argv[0]);
}
SpotifyClient client;
client.Init(arguments[1].toInt());
return a.exec();
}

View File

@ -1,169 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#include "mediapipeline.h"
#include <cstring>
#include "core/logging.h"
#include "core/timeconstants.h"
MediaPipeline::MediaPipeline(int port, quint64 length_msec)
: port_(port),
length_msec_(length_msec),
accepting_data_(true),
pipeline_(nullptr),
appsrc_(nullptr),
byte_rate_(1),
offset_bytes_(0) {}
MediaPipeline::~MediaPipeline() {
if (pipeline_) {
gst_element_set_state(pipeline_, GST_STATE_NULL);
gst_object_unref(GST_OBJECT(pipeline_));
}
}
bool MediaPipeline::Init(int sample_rate, int channels) {
if (is_initialised()) return false;
pipeline_ = gst_pipeline_new("pipeline");
// Create elements
appsrc_ = GST_APP_SRC(gst_element_factory_make("appsrc", nullptr));
GstElement* gdppay = gst_element_factory_make("gdppay", nullptr);
tcpsink_ = gst_element_factory_make("tcpclientsink", nullptr);
if (!pipeline_ || !appsrc_ || !tcpsink_) {
if (pipeline_) {
gst_object_unref(GST_OBJECT(pipeline_));
pipeline_ = nullptr;
}
if (appsrc_) {
gst_object_unref(GST_OBJECT(appsrc_));
appsrc_ = nullptr;
}
if (gdppay) {
gst_object_unref(GST_OBJECT(gdppay));
}
if (tcpsink_) {
gst_object_unref(GST_OBJECT(tcpsink_));
tcpsink_ = nullptr;
}
return false;
}
// Add elements to the pipeline and link them
gst_bin_add(GST_BIN(pipeline_), GST_ELEMENT(appsrc_));
gst_bin_add(GST_BIN(pipeline_), gdppay);
gst_bin_add(GST_BIN(pipeline_), tcpsink_);
gst_element_link_many(GST_ELEMENT(appsrc_), gdppay, tcpsink_, nullptr);
// Set the sink's port
g_object_set(G_OBJECT(tcpsink_), "host", "127.0.0.1", nullptr);
g_object_set(G_OBJECT(tcpsink_), "port", port_, nullptr);
// Try to send 5 seconds of audio in advance to initially fill Clementine's
// buffer.
g_object_set(G_OBJECT(tcpsink_), "ts-offset", qint64(-5 * kNsecPerSec),
nullptr);
// We know the time of each buffer
g_object_set(G_OBJECT(appsrc_), "format", GST_FORMAT_TIME, nullptr);
// Spotify only pushes data to us every 100ms, so keep the appsrc half full
// to prevent tiny stalls.
g_object_set(G_OBJECT(appsrc_), "min-percent", 50, nullptr);
// Set callbacks for when to start/stop pushing data
GstAppSrcCallbacks callbacks;
callbacks.enough_data = EnoughDataCallback;
callbacks.need_data = NeedDataCallback;
callbacks.seek_data = SeekDataCallback;
gst_app_src_set_callbacks(appsrc_, &callbacks, this, nullptr);
#if Q_BYTE_ORDER == Q_BIG_ENDIAN
static const char* format = "S16BE";
#elif Q_BYTE_ORDER == Q_LITTLE_ENDIAN
static const char* format = "S16LE";
#endif
// Set caps
GstCaps* caps = gst_caps_new_simple(
"audio/x-raw", "format", G_TYPE_STRING, format, "rate", G_TYPE_INT,
sample_rate, "channels", G_TYPE_INT, channels, "layout", G_TYPE_STRING,
"interleaved", nullptr);
gst_app_src_set_caps(appsrc_, caps);
gst_caps_unref(caps);
// Set size
byte_rate_ = quint64(sample_rate) * channels * 2;
const quint64 bytes = byte_rate_ * length_msec_ / 1000;
gst_app_src_set_size(appsrc_, bytes);
// Ready to go
return gst_element_set_state(pipeline_, GST_STATE_PLAYING) !=
GST_STATE_CHANGE_FAILURE;
}
void MediaPipeline::WriteData(const char* data, qint64 length) {
if (!is_initialised()) return;
GstBuffer* buffer = gst_buffer_new_allocate(nullptr, length, nullptr);
GstMapInfo map_info;
gst_buffer_map(buffer, &map_info, GST_MAP_WRITE);
memcpy(map_info.data, data, length);
gst_buffer_unmap(buffer, &map_info);
GST_BUFFER_PTS(buffer) = offset_bytes_ * kNsecPerSec / byte_rate_;
GST_BUFFER_DURATION(buffer) = length * kNsecPerSec / byte_rate_;
offset_bytes_ += length;
gst_app_src_push_buffer(appsrc_, buffer);
}
void MediaPipeline::EndStream() {
if (!is_initialised()) return;
gst_app_src_end_of_stream(appsrc_);
}
void MediaPipeline::NeedDataCallback(GstAppSrc* src, guint length, void* data) {
MediaPipeline* me = reinterpret_cast<MediaPipeline*>(data);
me->accepting_data_ = true;
}
void MediaPipeline::EnoughDataCallback(GstAppSrc* src, void* data) {
MediaPipeline* me = reinterpret_cast<MediaPipeline*>(data);
me->accepting_data_ = false;
}
gboolean MediaPipeline::SeekDataCallback(GstAppSrc* src, guint64 offset,
void* data) {
// MediaPipeline* me = reinterpret_cast<MediaPipeline*>(data);
qLog(Debug) << "Gstreamer wants seek to" << offset;
return false;
}

View File

@ -1,62 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#ifndef MEDIAPIPELINE_H
#define MEDIAPIPELINE_H
#include <QtGlobal>
#include <gst/gst.h>
#include <gst/app/gstappsrc.h>
class MediaPipeline {
public:
MediaPipeline(int port, quint64 length_msec);
~MediaPipeline();
bool is_initialised() const { return pipeline_; }
bool is_accepting_data() const { return accepting_data_; }
bool Init(int sample_rate, int channels);
void WriteData(const char* data, qint64 length);
void EndStream();
private:
static void NeedDataCallback(GstAppSrc* src, guint length, void* data);
static void EnoughDataCallback(GstAppSrc* src, void* data);
static gboolean SeekDataCallback(GstAppSrc* src, guint64 offset, void* data);
private:
Q_DISABLE_COPY(MediaPipeline)
const int port_;
const quint64 length_msec_;
bool accepting_data_;
GstElement* pipeline_;
GstAppSrc* appsrc_;
GstElement* tcpsink_;
quint64 byte_rate_;
quint64 offset_bytes_;
};
#endif // MEDIAPIPELINE_H

View File

@ -1,66 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#include "spotify_utilities.h"
#include <stdlib.h>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QSettings>
namespace utilities {
QString GetCacheDirectory() {
QString user_cache = GetUserDataDirectory();
return user_cache + "/" + QCoreApplication::applicationName() +
"/spotify-cache";
}
#ifndef Q_OS_DARWIN // See spotify_utilities.mm for Mac implementation.
QString GetUserDataDirectory() {
const char* xdg_cache_dir = getenv("XDG_CACHE_HOME");
if (!xdg_cache_dir) {
return QDir::homePath() + "/.config";
}
return QString::fromLocal8Bit(xdg_cache_dir);
}
QString GetSettingsDirectory() {
QString ret;
#ifdef Q_OS_WIN32
ret = GetUserDataDirectory() + "/" + QCoreApplication::applicationName() +
"/spotify-settings";
#else
ret = QFileInfo(QSettings().fileName()).absolutePath() + "/spotify-settings";
#endif // Q_OS_WIN32
// Create the directory
QDir dir;
dir.mkpath(ret);
return ret;
}
#endif // Q_OS_DARWIN
} // namespace utilities

View File

@ -1,37 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#ifndef SPOTIFY_UTILITIES_H
#define SPOTIFY_UTILITIES_H
#include <QString>
namespace utilities {
// Get the path to the current user's data directory for all apps.
QString GetUserDataDirectory();
// Get the path for Clementine's cache.
QString GetCacheDirectory();
QString GetSettingsDirectory();
}
#endif

View File

@ -1,44 +0,0 @@
#include "spotify_utilities.h"
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSFileManager.h>
#import <Foundation/NSPathUtilities.h>
#import "core/scoped_nsautorelease_pool.h"
namespace utilities {
QString GetUserDataDirectory() {
ScopedNSAutoreleasePool pool;
NSArray* paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
QString ret;
if ([paths count] > 0) {
NSString* user_path = [paths objectAtIndex:0];
ret = QString::fromUtf8([user_path UTF8String]);
} else {
ret = "~/Library/Caches";
}
return ret;
}
QString GetSettingsDirectory() {
ScopedNSAutoreleasePool pool;
NSArray* paths =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString* ret;
if ([paths count] > 0) {
ret = [paths objectAtIndex:0];
} else {
ret = @"~/Library/Application Support";
}
ret = [ret stringByAppendingString:@"/Clementine/spotify-settings"];
NSFileManager* file_manager = [NSFileManager defaultManager];
[file_manager createDirectoryAtPath:ret withIntermediateDirectories:YES attributes:nil error:nil];
QString path = QString::fromUtf8([ret UTF8String]);
return path;
}
} // namespace utilities

File diff suppressed because it is too large Load Diff

View File

@ -1,204 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
#ifndef SPOTIFYCLIENT_H
#define SPOTIFYCLIENT_H
#include "spotifymessages.pb.h"
#include "core/messagehandler.h"
#include <QMap>
#include <QObject>
#include <libspotify/api.h>
class QTcpSocket;
class QTimer;
class MediaPipeline;
class ResponseMessage;
class SpotifyClient : public AbstractMessageHandler<cpb::spotify::Message> {
Q_OBJECT
public:
SpotifyClient(QObject* parent = nullptr);
~SpotifyClient();
static const int kSpotifyImageIDSize;
static const int kWaveHeaderSize;
void Init(quint16 port);
protected:
void MessageArrived(const cpb::spotify::Message& message);
void DeviceClosed();
private slots:
void ProcessEvents();
private:
void SendLoginCompleted(bool success, const QString& error,
cpb::spotify::LoginResponse_Error error_code);
void SendPlaybackError(const QString& error);
void SendSearchResponse(sp_search* result);
// Spotify session callbacks.
static void SP_CALLCONV LoggedInCallback(sp_session* session, sp_error error);
static void SP_CALLCONV NotifyMainThreadCallback(sp_session* session);
static void SP_CALLCONV
LogMessageCallback(sp_session* session, const char* data);
static void SP_CALLCONV
SearchCompleteCallback(sp_search* result, void* userdata);
static void SP_CALLCONV MetadataUpdatedCallback(sp_session* session);
static int SP_CALLCONV
MusicDeliveryCallback(sp_session* session, const sp_audioformat* format,
const void* frames, int num_frames);
static void SP_CALLCONV EndOfTrackCallback(sp_session* session);
static void SP_CALLCONV
StreamingErrorCallback(sp_session* session, sp_error error);
static void SP_CALLCONV OfflineStatusUpdatedCallback(sp_session* session);
static void SP_CALLCONV
ConnectionErrorCallback(sp_session* session, sp_error error);
static void SP_CALLCONV
UserMessageCallback(sp_session* session, const char* message);
static void SP_CALLCONV StartPlaybackCallback(sp_session* session);
static void SP_CALLCONV StopPlaybackCallback(sp_session* session);
// Spotify playlist container callbacks.
static void SP_CALLCONV PlaylistAddedCallback(sp_playlistcontainer* pc,
sp_playlist* playlist,
int position, void* userdata);
static void SP_CALLCONV PlaylistRemovedCallback(sp_playlistcontainer* pc,
sp_playlist* playlist,
int position, void* userdata);
static void SP_CALLCONV
PlaylistMovedCallback(sp_playlistcontainer* pc, sp_playlist* playlist,
int position, int new_position, void* userdata);
static void SP_CALLCONV
PlaylistContainerLoadedCallback(sp_playlistcontainer* pc, void* userdata);
// Spotify playlist callbacks - when loading the list of playlists
// initially
static void SP_CALLCONV
PlaylistStateChangedForGetPlaylists(sp_playlist* pl, void* userdata);
// Spotify playlist callbacks - when loading a playlist
static void SP_CALLCONV
PlaylistStateChangedForLoadPlaylist(sp_playlist* pl, void* userdata);
// Spotify image callbacks.
static void SP_CALLCONV ImageLoaded(sp_image* image, void* userdata);
// Spotify album browse callbacks.
static void SP_CALLCONV
SearchAlbumBrowseComplete(sp_albumbrowse* result, void* userdata);
static void SP_CALLCONV
AlbumBrowseComplete(sp_albumbrowse* result, void* userdata);
// Spotify toplist browse callbacks.
static void SP_CALLCONV
ToplistBrowseComplete(sp_toplistbrowse* result, void* userdata);
// Request handlers.
void Login(const cpb::spotify::LoginRequest& req);
void Search(const cpb::spotify::SearchRequest& req);
void LoadPlaylist(const cpb::spotify::LoadPlaylistRequest& req);
void SyncPlaylist(const cpb::spotify::SyncPlaylistRequest& req);
void AddTracksToPlaylist(const cpb::spotify::AddTracksToPlaylistRequest& req);
void RemoveTracksFromPlaylist(
const cpb::spotify::RemoveTracksFromPlaylistRequest& req);
void StartPlayback(const cpb::spotify::PlaybackRequest& req);
void Seek(qint64 offset_nsec);
void LoadImage(const QString& id_b64);
void BrowseAlbum(const QString& uri);
void BrowseToplist(const cpb::spotify::BrowseToplistRequest& req);
void SetPlaybackSettings(const cpb::spotify::PlaybackSettings& req);
void SetPaused(const cpb::spotify::PauseRequest& req);
void SendPlaylistList();
void ConvertTrack(sp_track* track, cpb::spotify::Track* pb);
void ConvertAlbum(sp_album* album, cpb::spotify::Track* pb);
void ConvertAlbumBrowse(sp_albumbrowse* browse, cpb::spotify::Track* pb);
// Gets the appropriate sp_playlist* but does not load it.
sp_playlist* GetPlaylist(cpb::spotify::PlaylistType type, int user_index);
private:
struct PendingLoadPlaylist {
cpb::spotify::LoadPlaylistRequest request_;
sp_playlist* playlist_;
QList<sp_track*> tracks_;
bool offline_sync;
};
struct PendingPlaybackRequest {
cpb::spotify::PlaybackRequest request_;
sp_link* link_;
sp_track* track_;
bool operator==(const PendingPlaybackRequest& other) const {
return request_.track_uri() == other.request_.track_uri() &&
request_.media_port() == other.request_.media_port();
}
};
struct PendingImageRequest {
QString id_b64_;
QByteArray id_;
sp_image* image_;
};
void TryPlaybackAgain(const PendingPlaybackRequest& req);
void TryImageAgain(sp_image* image);
int GetDownloadProgress(sp_playlist* playlist);
void SendDownloadProgress(cpb::spotify::PlaylistType type, int index,
int download_progress);
QByteArray api_key_;
QTcpSocket* protocol_socket_;
sp_session_config spotify_config_;
sp_session_callbacks spotify_callbacks_;
sp_playlistcontainer_callbacks playlistcontainer_callbacks_;
sp_playlist_callbacks get_playlists_callbacks_;
sp_playlist_callbacks load_playlist_callbacks_;
sp_session* session_;
QTimer* events_timer_;
QList<PendingLoadPlaylist> pending_load_playlists_;
QList<PendingPlaybackRequest> pending_playback_requests_;
QList<PendingImageRequest> pending_image_requests_;
QMap<sp_image*, int> image_callbacks_registered_;
QMap<sp_search*, cpb::spotify::SearchRequest> pending_searches_;
QMap<sp_albumbrowse*, QString> pending_album_browses_;
QMap<sp_toplistbrowse*, cpb::spotify::BrowseToplistRequest>
pending_toplist_browses_;
QMap<sp_search*, QList<sp_albumbrowse*>> pending_search_album_browses_;
QMap<sp_albumbrowse*, sp_search*> pending_search_album_browse_responses_;
QScopedPointer<MediaPipeline> media_pipeline_;
};
#endif // SPOTIFYCLIENT_H

View File

@ -1,37 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
// The Spotify terms of service require that application keys are not
// accessible to third parties. Therefore this application key is heavily
// encrypted here in the source to prevent third parties from viewing it.
// It is most definitely not base64 encoded.
static const char* kSpotifyApiKey =
"AVlOrvJkKx8T+LEsCk+Kyl24I0MSsjohZAtMFzm2O5Lms1bmAWFWgdZaHkpypzSJPmSd+"
"Wi50wwg"
"JwVCU0sq4Lep1zB4t6Z8h26NK6+z8gmkHVkV9DRPkRgebcUkWTDTflwVPKWF4+"
"gdRjUwprsqBw6O"
"iofRLJzeKaxbmaUGqkSkxVLOiXC9lxylNq6ju7Q7uY8u8XkDUsVM3YIxiWy2+EM7I/"
"lhatzT9xrq"
"rxHe2lg7CzOwF5kuFdwgmi8MQ72xTYXIKnNlOry/"
"hJDlN9lKxkbUBLh+pzbYvO92S2fYKK5PAHvX"
"5+SmSBGbh6dlpHeCGqb8MPdaeZ5I1YxMcDkxa2+tbLA/Muat7gKA9u57TFCtYjun/u/i/"
"ONwdBIQ"
"rePzXZjipO32kYmQAiCkN1p8sgQEcF43QxaVwXGo2X0rRnJf";

View File

@ -1,18 +0,0 @@
include_directories(${PROTOBUF_INCLUDE_DIRS})
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/blobversion.h.in
${CMAKE_CURRENT_BINARY_DIR}/blobversion.h)
set(MESSAGES
spotifymessages.proto
)
protobuf_generate_cpp(PROTO_SOURCES PROTO_HEADERS ${MESSAGES})
add_library(clementine-spotifyblob-messages STATIC
${PROTO_SOURCES}
)
target_link_libraries(clementine-spotifyblob-messages
libclementine-common
)

View File

@ -1,23 +0,0 @@
/* This file is part of Clementine.
Copyright 2010, David Sansome <me@davidsansome.com>
Clementine 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.
Clementine 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 Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SPOTIFY_BLOBVERSION_H_IN
#define SPOTIFY_BLOBVERSION_H_IN
#define SPOTIFY_BLOB_VERSION ${SPOTIFY_BLOB_VERSION}
#endif // SPOTIFY_BLOBVERSION_H_IN

View File

@ -1,236 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, David Sansome <me@davidsansome.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Note: this file is licensed under the Apache License instead of GPL because
// it is used by the Spotify blob which links against libspotify and is not GPL
// compatible.
syntax = "proto2";
package cpb.spotify;
message LoginRequest {
required string username = 1;
optional string password = 2;
optional PlaybackSettings playback_settings = 3;
}
message LoginResponse {
enum Error {
BadUsernameOrPassword = 1;
UserBanned = 2;
UserNeedsPremium = 3;
Other = 4;
ReloginFailed = 5;
}
required bool success = 1;
required string error = 2;
optional Error error_code = 3 [default = Other];
}
message Playlists {
message Playlist {
required int32 index = 1;
required string name = 2;
required int32 nb_tracks = 3;
required bool is_mine = 4;
required string owner= 5;
required bool is_offline = 6;
required string uri = 7;
// Offline sync progress between 0-100.
optional int32 download_progress = 8;
}
repeated Playlist playlist = 1;
}
message Track {
required bool starred = 1;
required string title = 2;
repeated string artist = 3;
required string album = 4;
required int32 duration_msec = 5;
required int32 popularity = 6;
required int32 disc = 7;
required int32 track = 8;
required int32 year = 9;
required string uri = 10;
required string album_art_id = 11;
}
message Album {
required Track metadata = 1;
repeated Track track = 2;
}
enum PlaylistType {
Starred = 1;
Inbox = 2;
UserPlaylist = 3;
}
message LoadPlaylistRequest {
required PlaylistType type = 1;
optional int32 user_playlist_index = 2;
}
message LoadPlaylistResponse {
required LoadPlaylistRequest request = 1;
repeated Track track = 2;
}
message SyncPlaylistRequest {
required LoadPlaylistRequest request = 1;
required bool offline_sync = 2;
}
message SyncPlaylistProgress {
required LoadPlaylistRequest request = 1;
required int32 sync_progress = 2;
}
message PlaybackRequest {
required string track_uri = 1;
required int32 media_port = 2;
}
message PlaybackError {
required string error = 1;
}
message SearchRequest {
required string query = 1;
optional int32 limit = 2 [default = 250];
optional int32 limit_album = 3 [default = 0];
}
message SearchResponse {
required SearchRequest request = 1;
repeated Track result = 2;
optional int32 total_tracks = 3;
optional string did_you_mean = 4;
optional string error = 5;
// field 6 is deprecated
repeated Album album = 7;
}
message ImageRequest {
required string id = 1;
}
message ImageResponse {
required string id = 1;
optional bytes data = 2;
}
message BrowseAlbumRequest {
required string uri = 1;
}
message BrowseAlbumResponse {
required string uri = 1;
repeated Track track = 2;
}
message BrowseToplistRequest {
enum ToplistType {
Artists = 1;
Albums = 2;
Tracks = 3;
};
enum Region {
Everywhere = 1;
User = 2;
};
required ToplistType type = 1;
optional Region region = 2 [default=Everywhere];
// Username to use if region is User.
optional string username = 3;
}
message BrowseToplistResponse {
required BrowseToplistRequest request = 1;
repeated Track track = 2;
repeated Album album = 3;
}
message SeekRequest {
optional int64 offset_nsec = 1;
}
enum Bitrate {
Bitrate96k = 1;
Bitrate160k = 2;
Bitrate320k = 3;
}
message PlaybackSettings {
optional Bitrate bitrate = 1 [default = Bitrate320k];
optional bool volume_normalisation = 2 [default = false];
}
message PauseRequest {
optional bool paused = 1 [default = false];
}
message AddTracksToPlaylistRequest {
required PlaylistType playlist_type = 1;
optional int64 playlist_index = 2; // Used if playlist_index == UserPlaylist
repeated string track_uri = 3;
}
message RemoveTracksFromPlaylistRequest {
required PlaylistType playlist_type = 1;
optional int64 playlist_index = 2; // Used if playlist_index == UserPlaylist
repeated int64 track_index = 3;
}
// NEXT_ID: 25
message Message {
// Not currently used
optional int32 id = 18;
optional LoginRequest login_request = 1;
optional LoginResponse login_response = 2;
optional Playlists playlists_updated = 3;
optional LoadPlaylistRequest load_playlist_request = 4;
optional LoadPlaylistResponse load_playlist_response = 5;
optional PlaybackRequest playback_request = 6;
optional PlaybackError playback_error = 7;
optional SearchRequest search_request = 8;
optional SearchResponse search_response = 9;
optional ImageRequest image_request = 10;
optional ImageResponse image_response = 11;
optional SyncPlaylistRequest sync_playlist_request = 12;
optional SyncPlaylistProgress sync_playlist_progress = 13;
optional BrowseAlbumRequest browse_album_request = 14;
optional BrowseAlbumResponse browse_album_response = 15;
optional SeekRequest seek_request = 16;
optional PlaybackSettings set_playback_settings_request = 17;
optional BrowseToplistRequest browse_toplist_request = 19;
optional BrowseToplistResponse browse_toplist_response = 20;
optional PauseRequest pause_request = 21;
// ID 22 unused.
optional AddTracksToPlaylistRequest add_tracks_to_playlist = 23;
optional RemoveTracksFromPlaylistRequest remove_tracks_from_playlist = 24;
}

View File

@ -47,10 +47,6 @@ include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-tagreader)
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-tagreader)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-remote)
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-remote)
if(HAVE_SPOTIFY)
include_directories(${CMAKE_SOURCE_DIR}/ext/libclementine-spotifyblob)
include_directories(${CMAKE_BINARY_DIR}/ext/libclementine-spotifyblob)
endif(HAVE_SPOTIFY)
include(../cmake/ParseArguments.cmake)
@ -885,37 +881,6 @@ optional_source(HAVE_LIBLASTFM
internet/lastfm/lastfmsettingspage.ui
)
# Spotify support
optional_source(HAVE_SPOTIFY
SOURCES
internet/spotify/spotifyserver.cpp
internet/spotify/spotifyservice.cpp
internet/spotify/spotifysettingspage.cpp
internet/spotifywebapi/spotifywebapiservice.cpp
globalsearch/spotifysearchprovider.cpp
globalsearch/spotifywebapisearchprovider.cpp
HEADERS
globalsearch/spotifysearchprovider.h
globalsearch/spotifywebapisearchprovider.h
internet/spotify/spotifyserver.h
internet/spotify/spotifyservice.h
internet/spotify/spotifysettingspage.h
internet/spotifywebapi/spotifywebapiservice.h
UI
internet/spotify/spotifysettingspage.ui
)
if(HAVE_SPOTIFY)
optional_source(HAVE_SPOTIFY_DOWNLOADER
SOURCES
internet/spotify/spotifyblobdownloader.cpp
HEADERS
internet/spotify/spotifyblobdownloader.h
INCLUDE_DIRECTORIES
${CRYPTOPP_INCLUDE_DIRS}
)
endif(HAVE_SPOTIFY)
# Platform specific - OS X
optional_source(APPLE
INCLUDE_DIRECTORIES
@ -1353,17 +1318,6 @@ if(HAVE_BREAKPAD)
endif (LINUX)
endif(HAVE_BREAKPAD)
if(HAVE_SPOTIFY)
target_link_libraries(clementine_lib clementine-spotifyblob-messages)
endif(HAVE_SPOTIFY)
if(HAVE_SPOTIFY_DOWNLOADER)
target_link_libraries(clementine_lib
${CRYPTOPP_LIBRARIES}
)
link_directories(${CRYPTOPP_LIBRARY_DIRS})
endif(HAVE_SPOTIFY_DOWNLOADER)
if(HAVE_LIBPULSE)
target_link_libraries(clementine_lib ${LIBPULSE_LIBRARIES})
endif()
@ -1451,9 +1405,6 @@ target_link_libraries(clementine
)
# macdeploy.py relies on the blob being built first.
if(HAVE_SPOTIFY_BLOB)
add_dependencies(clementine clementine-spotifyblob)
endif(HAVE_SPOTIFY_BLOB)
add_dependencies(clementine clementine-tagreader)
set_target_properties(clementine PROPERTIES

View File

@ -34,9 +34,6 @@
#include "core/tagreaderclient.h"
#include "core/utilities.h"
#include "internet/core/internetmodel.h"
#ifdef HAVE_SPOTIFY
#include "internet/spotify/spotifyservice.h"
#endif
AlbumCoverLoader::AlbumCoverLoader(QObject* parent)
: QObject(parent),
@ -180,31 +177,7 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(
remote_tasks_.insert(reply, task);
return TryLoadResult(true, false, QImage());
}
#ifdef HAVE_SPOTIFY
else if (filename.toLower().startsWith("spotify://image/")) {
// HACK: we should add generic image URL handlers
SpotifyService* spotify = InternetModel::Service<SpotifyService>();
if (!connected_spotify_) {
connect(spotify, SIGNAL(ImageLoaded(QString, QImage)),
SLOT(SpotifyImageLoaded(QString, QImage)));
connected_spotify_ = true;
}
QString id = QUrl(filename).path();
if (id.startsWith('/')) {
id.remove(0, 1);
}
remote_spotify_tasks_.insert(id, task);
// Need to schedule this in the spotify service's thread
QMetaObject::invokeMethod(spotify, "LoadImage", Qt::QueuedConnection,
Q_ARG(QString, id));
return TryLoadResult(true, false, QImage());
}
#endif
else if (filename.isEmpty()) {
} else if (filename.isEmpty()) {
// Avoid "QFSFileEngine::open: No file name specified" messages if we know
// that the filename is empty
return TryLoadResult(false, false, task.options.default_output_image_);
@ -216,18 +189,6 @@ AlbumCoverLoader::TryLoadResult AlbumCoverLoader::TryLoadImage(
image.isNull() ? task.options.default_output_image_ : image);
}
#ifdef HAVE_SPOTIFY
void AlbumCoverLoader::SpotifyImageLoaded(const QString& id,
const QImage& image) {
if (!remote_spotify_tasks_.contains(id)) return;
Task task = remote_spotify_tasks_.take(id);
QImage scaled = ScaleAndPad(task.options, image);
emit ImageLoaded(task.id, scaled);
emit ImageLoaded(task.id, scaled, image);
}
#endif
void AlbumCoverLoader::RemoteFetchFinished(QNetworkReply* reply) {
reply->deleteLater();

View File

@ -67,9 +67,6 @@ class AlbumCoverLoader : public QObject {
protected slots:
void ProcessTasks();
void RemoteFetchFinished(QNetworkReply* reply);
#ifdef HAVE_SPOTIFY
void SpotifyImageLoaded(const QString& url, const QImage& image);
#endif
protected:
enum State {

View File

@ -39,10 +39,6 @@
#include "devices/cddadevice.h"
#endif
#include "internet/core/internetmodel.h"
#ifdef HAVE_SPOTIFY
#include "internet/spotify/spotifyserver.h"
#include "internet/spotify/spotifyservice.h"
#endif
const int GstEnginePipeline::kGstStateTimeoutNanosecs = 10000000;
const int GstEnginePipeline::kFaderFudgeMsec = 2000;
@ -171,58 +167,14 @@ QByteArray GstEnginePipeline::GstUriFromUrl(const QUrl& url) {
GstElement* GstEnginePipeline::CreateDecodeBinFromUrl(const QUrl& url) {
GstElement* new_bin = nullptr;
#ifdef HAVE_SPOTIFY
if (url.scheme() == "spotify") {
new_bin = gst_bin_new("spotify_bin");
if (!new_bin) return nullptr;
// Create elements
GstElement* src = engine_->CreateElement("tcpserversrc", new_bin);
if (!src) {
gst_object_unref(GST_OBJECT(new_bin));
return nullptr;
}
GstElement* gdp = engine_->CreateElement("gdpdepay", new_bin);
if (!gdp) {
gst_object_unref(GST_OBJECT(new_bin));
return nullptr;
}
// Pick a port number
const int port = Utilities::PickUnusedPort();
g_object_set(G_OBJECT(src), "host", "127.0.0.1", nullptr);
g_object_set(G_OBJECT(src), "port", port, nullptr);
// Link the elements
gst_element_link(src, gdp);
// Add a ghost pad
GstPad* pad = gst_element_get_static_pad(gdp, "src");
gst_element_add_pad(GST_ELEMENT(new_bin), gst_ghost_pad_new("src", pad));
gst_object_unref(GST_OBJECT(pad));
// Tell spotify to start sending data to us.
SpotifyServer* spotify_server =
InternetModel::Service<SpotifyService>()->server();
// Need to schedule this in the spotify server's thread
QMetaObject::invokeMethod(
spotify_server, "StartPlayback", Qt::QueuedConnection,
Q_ARG(QString, url.toString()), Q_ARG(quint16, port));
} else {
#endif
QByteArray uri = GstUriFromUrl(url);
new_bin = engine_->CreateElement("uridecodebin");
if (!new_bin) return nullptr;
g_object_set(G_OBJECT(new_bin), "uri", uri.constData(), nullptr);
CHECKED_GCONNECT(G_OBJECT(new_bin), "drained", &SourceDrainedCallback,
this);
CHECKED_GCONNECT(G_OBJECT(new_bin), "pad-added", &NewPadCallback, this);
CHECKED_GCONNECT(G_OBJECT(new_bin), "notify::source", &SourceSetupCallback,
this);
#ifdef HAVE_SPOTIFY
}
#endif
QByteArray uri = GstUriFromUrl(url);
new_bin = engine_->CreateElement("uridecodebin");
if (!new_bin) return nullptr;
g_object_set(G_OBJECT(new_bin), "uri", uri.