Remove deezer
@ -22,7 +22,7 @@ before_install:
|
||||
git pull;
|
||||
brew update;
|
||||
brew unlink python;
|
||||
brew install glib pkgconfig protobuf protobuf-c qt gettext;
|
||||
brew install glib pkgconfig libffi protobuf protobuf-c qt gettext;
|
||||
brew install sqlite --with-fts;
|
||||
brew install gstreamer gst-plugins-base;
|
||||
brew install gst-plugins-good --with-flac;
|
||||
@ -35,8 +35,8 @@ before_install:
|
||||
ls /usr/local/lib/gstreamer-1.0;
|
||||
fi
|
||||
before_script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild -DENABLE_STREAM_DEEZER=ON -DENABLE_TRANSLATIONS=ON ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON -DENABLE_STREAM_DEEZER=ON -DENABLE_TRANSLATIONS=ON ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build cmake -Hstrawberry -Bbuild -DENABLE_TRANSLATIONS=ON ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then mkdir build; cd build; cmake .. -DUSE_BUNDLE=ON -DENABLE_TRANSLATIONS=ON ; fi
|
||||
script:
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker exec build make -C build -j8 ; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
|
@ -112,8 +112,6 @@ pkg_check_modules(LIBMTP libmtp>=1.0)
|
||||
pkg_check_modules(LIBIMOBILEDEVICE libimobiledevice-1.0)
|
||||
pkg_check_modules(LIBUSBMUXD libusbmuxd)
|
||||
pkg_check_modules(LIBPLIST libplist)
|
||||
pkg_check_modules(LIBDEEZER libdeezer)
|
||||
pkg_check_modules(LIBDZMEDIA libdzmedia)
|
||||
find_package(Gettext)
|
||||
|
||||
if(WIN32)
|
||||
@ -291,18 +289,6 @@ optional_component(PHONON OFF "Engine: Phonon backend (UNSTABLE)"
|
||||
DEPENDS "phonon4qt5" PHONON_FOUND
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
optional_component(DEEZER ON "Engine: Deezer backend"
|
||||
DEPENDS "libdeezer" LIBDEEZER_FOUND
|
||||
)
|
||||
else ()
|
||||
optional_component(DEEZER ON "Engine: Deezer backend"
|
||||
DEPENDS "Linux" LINUX
|
||||
DEPENDS "libdeezer" LIBDEEZER_FOUND
|
||||
DEPENDS "libpulse" LIBPULSE_FOUND
|
||||
)
|
||||
endif()
|
||||
|
||||
optional_component(CHROMAPRINT ON "Chromaprint (Tag fetching from Musicbrainz)"
|
||||
DEPENDS "chromaprint" CHROMAPRINT_FOUND
|
||||
)
|
||||
@ -359,21 +345,6 @@ optional_component(TRANSLATIONS OFF "Translations (No languages included yet)"
|
||||
|
||||
optional_component(STREAM_TIDAL ON "Streaming: Tidal support")
|
||||
|
||||
if (LIBDZMEDIA_FOUND OR LIBDEEZER_FOUND)
|
||||
optional_component(STREAM_DEEZER ON "Streaming: Deezer support")
|
||||
else()
|
||||
optional_component(STREAM_DEEZER OFF "Streaming: Deezer support")
|
||||
endif()
|
||||
|
||||
optional_component(DZMEDIA ON "DZMedia"
|
||||
DEPENDS "libdzmedia" LIBDZMEDIA_FOUND
|
||||
DEPENDS "Deezer support" HAVE_STREAM_DEEZER
|
||||
)
|
||||
|
||||
if (HAVE_STREAM_DEEZER AND NOT HAVE_DZMEDIA AND NOT HAVE_DEEZER)
|
||||
message(STATUS "Deezer is enabled, but not DZMedia or Deezer engine, only preview streams will be available.")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
option(USE_BUNDLE "Bundle macOS dependencies" OFF)
|
||||
elseif(WIN32)
|
||||
@ -418,8 +389,8 @@ add_custom_target(uninstall
|
||||
|
||||
# Show a summary of what we have enabled
|
||||
summary_show()
|
||||
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC AND NOT HAVE_PHONON AND NOT HAVE_DEEZER)
|
||||
message(FATAL_ERROR "You need to have either GStreamer, Xine, VLC, Phonon or Deezer to compile!")
|
||||
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC AND NOT HAVE_PHONON)
|
||||
message(FATAL_ERROR "You need to have either GStreamer, Xine, VLC or Phonon to compile!")
|
||||
elseif(NOT HAVE_GSTREAMER)
|
||||
message(WARNING "GStreamer is the only engine that is fully implemented. Using other engines is possible but not recommended.")
|
||||
endif()
|
||||
|
@ -25,7 +25,7 @@ Strawberry is a audio player and music collection organizer. It is a fork of Cle
|
||||
* Audio analyzer
|
||||
* Audio equalizer
|
||||
* Transfer music to iPod, iPhone, MTP or mass-storage USB player
|
||||
* Streaming support for Tidal and Deezer [*]
|
||||
* Streaming support for Tidal
|
||||
* Scrobbler with support for Last.fm, Libre.fm and ListenBrainz
|
||||
|
||||
It has so far been tested to work on Linux, OpenBSD, macOS and Windows.
|
||||
@ -48,7 +48,7 @@ To build Strawberry from source you need the following installed on your system
|
||||
* [ALSA library (linux)](https://www.alsa-project.org/)
|
||||
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
|
||||
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
|
||||
* [GStreamer](https://gstreamer.freedesktop.org/), [Xine](https://www.xine-project.org), [VLC](https://www.videolan.org), [Deezer](https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip) or [Phonon](https://techbase.kde.org/Phonon)
|
||||
* [GStreamer](https://gstreamer.freedesktop.org/), [Xine](https://www.xine-project.org), [VLC](https://www.videolan.org) or [Phonon](https://techbase.kde.org/Phonon)
|
||||
|
||||
Optional dependencies:
|
||||
|
||||
@ -57,11 +57,9 @@ Optional dependencies:
|
||||
* iPod Classic devices: [libgpod](http://www.gtkpod.org/libgpod/)
|
||||
* iPhone, iPod Touch, iPad and Apple TV devices: [libimobiledevice, libplist and libusbmuxd](https://www.libimobiledevice.org/)
|
||||
|
||||
Either GStreamer, Xine, VLC, Deezer or Phonon engine is required, but only GStreamer is fully implemented so far.
|
||||
Either GStreamer, Xine, VLC or Phonon engine is required, but only GStreamer is fully implemented so far.
|
||||
You should also install the gstreamer plugins base and good, and optionally bad and ugly.
|
||||
|
||||
Deezer support require deezer's own engine, and usually only works on Windows. It is not available on Linux unless you specifically compile with the deezer library, which currently only works on Ubuntu Xenial. The Deezer SDK can be found here: https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip
|
||||
|
||||
### :wrench: Compiling from source
|
||||
|
||||
### Get the code:
|
||||
|
@ -29,7 +29,6 @@
|
||||
<file>pictures/osd_background.png</file>
|
||||
<file>pictures/osd_shadow_corner.png</file>
|
||||
<file>pictures/osd_shadow_edge.png</file>
|
||||
<file>pictures/deezer.png</file>
|
||||
<file>pictures/nyancat.png</file>
|
||||
<file>pictures/rainbowdash.png</file>
|
||||
<file>fonts/HumongousofEternitySt.ttf</file>
|
||||
|
@ -84,7 +84,6 @@
|
||||
<file>icons/128x128/zoom-in.png</file>
|
||||
<file>icons/128x128/zoom-out.png</file>
|
||||
<file>icons/128x128/tidal.png</file>
|
||||
<file>icons/128x128/deezer.png</file>
|
||||
<file>icons/128x128/scrobble.png</file>
|
||||
<file>icons/128x128/scrobble-disabled.png</file>
|
||||
<file>icons/64x64/albums.png</file>
|
||||
@ -171,7 +170,6 @@
|
||||
<file>icons/64x64/zoom-in.png</file>
|
||||
<file>icons/64x64/zoom-out.png</file>
|
||||
<file>icons/64x64/tidal.png</file>
|
||||
<file>icons/64x64/deezer.png</file>
|
||||
<file>icons/64x64/scrobble.png</file>
|
||||
<file>icons/64x64/scrobble-disabled.png</file>
|
||||
<file>icons/48x48/albums.png</file>
|
||||
@ -351,7 +349,6 @@
|
||||
<file>icons/32x32/zoom-in.png</file>
|
||||
<file>icons/32x32/zoom-out.png</file>
|
||||
<file>icons/32x32/tidal.png</file>
|
||||
<file>icons/32x32/deezer.png</file>
|
||||
<file>icons/32x32/scrobble.png</file>
|
||||
<file>icons/32x32/scrobble-disabled.png</file>
|
||||
<file>icons/22x22/albums.png</file>
|
||||
@ -442,7 +439,6 @@
|
||||
<file>icons/22x22/zoom-in.png</file>
|
||||
<file>icons/22x22/zoom-out.png</file>
|
||||
<file>icons/22x22/tidal.png</file>
|
||||
<file>icons/22x22/deezer.png</file>
|
||||
<file>icons/22x22/scrobble.png</file>
|
||||
<file>icons/22x22/scrobble-disabled.png</file>
|
||||
</qresource>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 28 KiB |
5
dist/debian/copyright
vendored
@ -25,8 +25,6 @@ Files: src/core/main.h
|
||||
src/engine/enginetype.h
|
||||
src/engine/alsadevicefinder.cpp
|
||||
src/engine/alsadevicefinder.h
|
||||
src/engine/deezerengine.cpp
|
||||
src/engine/deezerengine.h
|
||||
src/engine/devicefinder.cpp
|
||||
src/engine/devicefinder.h
|
||||
src/engine/enginedevice.cpp
|
||||
@ -39,8 +37,6 @@ Files: src/core/main.h
|
||||
src/settings/backendsettingspage.h
|
||||
src/settings/scrobblersettingspage.cpp
|
||||
src/settings/scrobblersettingspage.h
|
||||
src/settings/deezersettingspage.cpp
|
||||
src/settings/deezersettingspage.h
|
||||
src/settings/tidalsettingspage.cpp
|
||||
src/settings/tidalsettingspage.h
|
||||
src/covermanager/lastfmcoverprovider.cpp
|
||||
@ -58,7 +54,6 @@ Files: src/core/main.h
|
||||
src/lyrics/*
|
||||
src/scrobbler/*
|
||||
src/tidal/*
|
||||
src/deezer/*
|
||||
src/transcoder/transcoderoptionswavpack.cpp
|
||||
src/transcoder/transcoderoptionswavpack.h
|
||||
Copyright: 2012-2014, 2017-2018, Jonas Kvinge <jonas@jkvinge.net>
|
||||
|
3
dist/debian/rules
vendored
@ -12,8 +12,7 @@ configure-stamp:
|
||||
dh_testdir
|
||||
cmake .. \
|
||||
-DCMAKE_INSTALL_PREFIX=$(CURDIR)/debian/strawberry/usr \
|
||||
-DENABLE_TRANSLATIONS=ON \
|
||||
-DENABLE_STREAM_DEEZER=ON
|
||||
-DENABLE_TRANSLATIONS=ON
|
||||
touch configure-stamp
|
||||
|
||||
build: build-stamp
|
||||
|
1
dist/pacman/PKGBUILD.in
vendored
@ -52,7 +52,6 @@ build() {
|
||||
cmake ../${pkgname}-@STRAWBERRY_VERSION_PACKAGE@ \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
-DUSE_SYSTEM_TAGLIB=ON \
|
||||
-DENABLE_STREAM_DEEZER=ON \
|
||||
-DENABLE_PHONON=ON \
|
||||
-DENABLE_TRANSLATIONS=ON
|
||||
make -j$(nproc)
|
||||
|
2
dist/unix/org.strawbs.strawberry.appdata.xml
vendored
@ -34,7 +34,7 @@
|
||||
<li>Audio analyzer</li>
|
||||
<li>Audio equalizer</li>
|
||||
<li>Transfer music to iPod, iPhone, MTP or mass-storage USB player</li>
|
||||
<li>Streaming support for Tidal and Deezer</li>
|
||||
<li>Streaming support for Tidal</li>
|
||||
<li>Scrobbler with support for Last.fm, Libre.fm and ListenBrainz</li>
|
||||
</ul>
|
||||
</description>
|
||||
|
2
dist/windows/strawberry-debug-x64.nsi.in
vendored
@ -162,7 +162,6 @@ Section "Strawberry" Strawberry
|
||||
File "libxml2-2.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libdeezer.x64.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
@ -403,7 +402,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libdeezer.x64.dll"
|
||||
|
||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||
|
2
dist/windows/strawberry-debug-x86.nsi.in
vendored
@ -162,7 +162,6 @@ Section "Strawberry" Strawberry
|
||||
File "libxml2-2.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libdeezer.x86.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
@ -403,7 +402,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libdeezer.x86.dll"
|
||||
|
||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||
|
2
dist/windows/strawberry-x64.nsi.in
vendored
@ -161,7 +161,6 @@ Section "Strawberry" Strawberry
|
||||
File "libxml2-2.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libdeezer.x64.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
@ -370,7 +369,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libdeezer.x64.dll"
|
||||
|
||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||
|
2
dist/windows/strawberry-x86.nsi.in
vendored
@ -161,7 +161,6 @@ Section "Strawberry" Strawberry
|
||||
File "libxml2-2.dll"
|
||||
File "libsoup-2.4-1.dll"
|
||||
File "liblzma-5.dll"
|
||||
File "libdeezer.x86.dll"
|
||||
|
||||
; Register Strawberry with Default Programs
|
||||
Var /GLOBAL AppIcon
|
||||
@ -370,7 +369,6 @@ Section "Uninstall"
|
||||
Delete "$INSTDIR\libxml2-2.dll"
|
||||
Delete "$INSTDIR\libsoup-2.4-1.dll"
|
||||
Delete "$INSTDIR\liblzma-5.dll"
|
||||
Delete "$INSTDIR\libdeezer.x86.dll"
|
||||
|
||||
Delete "$INSTDIR\platforms\qwindows.dll"
|
||||
Delete "$INSTDIR\sqldrivers\qsqlite.dll"
|
||||
|
@ -64,14 +64,6 @@ if(HAVE_PHONON)
|
||||
include_directories(${PHONON_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBDEEZER)
|
||||
include_directories(${DEEZER_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBDZMEDIA)
|
||||
include_directories(${DZMEDIA_INCLUDE_DIRS})
|
||||
endif()
|
||||
|
||||
link_directories(${TAGLIB_LIBRARY_DIRS})
|
||||
include_directories(${TAGLIB_INCLUDE_DIRS})
|
||||
|
||||
@ -579,12 +571,6 @@ optional_source(HAVE_PHONON
|
||||
HEADERS engine/phononengine.h
|
||||
)
|
||||
|
||||
# Deezer
|
||||
optional_source(HAVE_DEEZER
|
||||
SOURCES engine/deezerengine.cpp
|
||||
HEADERS engine/deezerengine.h
|
||||
)
|
||||
|
||||
# DBUS and MPRIS - Unix specific
|
||||
if(UNIX AND HAVE_DBUS)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
|
||||
@ -888,19 +874,6 @@ optional_source(HAVE_STREAM_TIDAL
|
||||
settings/tidalsettingspage.ui
|
||||
)
|
||||
|
||||
optional_source(HAVE_STREAM_DEEZER
|
||||
SOURCES
|
||||
deezer/deezerservice.cpp
|
||||
deezer/deezerurlhandler.cpp
|
||||
settings/deezersettingspage.cpp
|
||||
HEADERS
|
||||
deezer/deezerservice.h
|
||||
deezer/deezerurlhandler.h
|
||||
settings/deezersettingspage.h
|
||||
UI
|
||||
settings/deezersettingspage.ui
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
@ -994,14 +967,6 @@ if(HAVE_PHONON)
|
||||
target_link_libraries(strawberry_lib ${PHONON_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_DEEZER)
|
||||
target_link_libraries(strawberry_lib ${LIBDEEZER_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_DZMEDIA)
|
||||
target_link_libraries(strawberry_lib ${LIBDZMEDIA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(HAVE_LIBGPOD)
|
||||
target_link_libraries(strawberry_lib ${LIBGPOD_LIBRARIES})
|
||||
endif(HAVE_LIBGPOD)
|
||||
|
@ -39,7 +39,6 @@
|
||||
#cmakedefine HAVE_SPARKLE
|
||||
#cmakedefine HAVE_CHROMAPRINT
|
||||
#cmakedefine HAVE_TAGLIB_DSFFILE
|
||||
#cmakedefine HAVE_DZMEDIA
|
||||
#cmakedefine HAVE_GLOBALSHORTCUTS
|
||||
#cmakedefine IMOBILEDEVICE_USES_UDIDS
|
||||
#cmakedefine USE_INSTALL_PREFIX
|
||||
@ -48,10 +47,8 @@
|
||||
#cmakedefine HAVE_VLC
|
||||
#cmakedefine HAVE_XINE
|
||||
#cmakedefine HAVE_PHONON
|
||||
#cmakedefine HAVE_DEEZER
|
||||
|
||||
#cmakedefine HAVE_STREAM_TIDAL
|
||||
#cmakedefine HAVE_STREAM_DEEZER
|
||||
|
||||
#cmakedefine HAVE_KEYSYMDEF_H
|
||||
#cmakedefine HAVE_XF86KEYSYM_H
|
||||
|
@ -66,9 +66,6 @@
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
# include "tidal/tidalservice.h"
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
# include "deezer/deezerservice.h"
|
||||
#endif
|
||||
|
||||
#include "scrobbler/audioscrobbler.h"
|
||||
|
||||
@ -127,17 +124,11 @@ class ApplicationImpl {
|
||||
InternetServices *internet_services = new InternetServices(app);
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
internet_services->AddService(new TidalService(app, internet_services));
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
internet_services->AddService(new DeezerService(app, internet_services));
|
||||
#endif
|
||||
return internet_services;
|
||||
}),
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
tidal_search_([=]() { return new InternetSearch(app, Song::Source_Tidal, app); }),
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
deezer_search_([=]() { return new InternetSearch(app, Song::Source_Deezer, app); }),
|
||||
#endif
|
||||
scrobbler_([=]() { return new AudioScrobbler(app, app); })
|
||||
{}
|
||||
@ -161,9 +152,6 @@ class ApplicationImpl {
|
||||
Lazy<InternetServices> internet_services_;
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
Lazy<InternetSearch> tidal_search_;
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
Lazy<InternetSearch> deezer_search_;
|
||||
#endif
|
||||
Lazy<AudioScrobbler> scrobbler_;
|
||||
|
||||
@ -236,7 +224,4 @@ InternetServices *Application::internet_services() const { return p_->internet_s
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
InternetSearch *Application::deezer_search() const { return p_->deezer_search_.get(); }
|
||||
#endif
|
||||
AudioScrobbler *Application::scrobbler() const { return p_->scrobbler_.get(); }
|
||||
|
@ -96,9 +96,6 @@ class Application : public QObject {
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
InternetSearch *tidal_search() const;
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
InternetSearch *deezer_search() const;
|
||||
#endif
|
||||
|
||||
AudioScrobbler *scrobbler() const;
|
||||
|
||||
|
@ -136,9 +136,6 @@
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
# include "settings/tidalsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
# include "settings/deezersettingspage.h"
|
||||
#endif
|
||||
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetservice.h"
|
||||
@ -206,9 +203,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
}),
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
tidal_search_view_(new InternetSearchView(app_, app_->tidal_search(), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
deezer_search_view_(new InternetSearchView(app_, app_->deezer_search(), DeezerSettingsPage::kSettingsGroup, SettingsDialog::Page_Deezer, this)),
|
||||
#endif
|
||||
playlist_menu_(new QMenu(this)),
|
||||
playlist_add_to_another_(nullptr),
|
||||
@ -266,9 +260,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), tr("Tidal"));
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
ui_->tabs->addTab(deezer_search_view_, IconLoader::Load("deezer"), tr("Deezer"));
|
||||
#endif
|
||||
|
||||
// Add the playing widget to the fancy tab widget
|
||||
ui_->tabs->addBottomWidget(ui_->widget_playing);
|
||||
@ -547,9 +538,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
connect(tidal_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
connect(deezer_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*)));
|
||||
#endif
|
||||
|
||||
// Playlist menu
|
||||
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
|
||||
@ -859,16 +847,6 @@ void MainWindow::ReloadSettings() {
|
||||
ui_->tabs->delTab("Tidal");
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
settings.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
bool enable_deezer = settings.value("enabled", false).toBool();
|
||||
settings.endGroup();
|
||||
if (enable_deezer)
|
||||
ui_->tabs->addTab(deezer_search_view_, IconLoader::Load("deezer"), tr("Deezer"));
|
||||
else
|
||||
ui_->tabs->delTab("Deezer");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void MainWindow::ReloadAllSettings() {
|
||||
@ -885,9 +863,6 @@ void MainWindow::ReloadAllSettings() {
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
tidal_search_view_->ReloadSettings();
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
deezer_search_view_->ReloadSettings();
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
|
@ -332,7 +332,6 @@ signals:
|
||||
#endif
|
||||
|
||||
InternetSearchView *tidal_search_view_;
|
||||
InternetSearchView *deezer_search_view_;
|
||||
|
||||
QAction *collection_show_all_;
|
||||
QAction *collection_show_duplicates_;
|
||||
|
@ -59,9 +59,6 @@
|
||||
#ifdef HAVE_VLC
|
||||
# include "engine/vlcengine.h"
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
# include "engine/deezerengine.h"
|
||||
#endif
|
||||
|
||||
#include "collection/collectionbackend.h"
|
||||
#include "playlist/playlist.h"
|
||||
@ -140,15 +137,6 @@ Engine::EngineType Player::CreateEngine(Engine::EngineType enginetype) {
|
||||
use_enginetype=Engine::Phonon;
|
||||
engine_.reset(new PhononEngine(app_->task_manager()));
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
case Engine::Deezer:{
|
||||
use_enginetype=Engine::Deezer;
|
||||
DeezerEngine *deezerengine = new DeezerEngine(app_->task_manager());
|
||||
connect(this, SIGNAL(Authenticated()), deezerengine, SLOT(LoadAccessToken()));
|
||||
engine_.reset(deezerengine);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
if (i > 0) { qFatal("No engine available!"); }
|
||||
@ -321,7 +309,7 @@ void Player::NextInternal(Engine::TrackChangeFlags change) {
|
||||
if (app_->playlist_manager()->active()->current_item()) {
|
||||
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
|
||||
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
// The next track is already being loaded
|
||||
if (url == loading_async_) return;
|
||||
|
||||
@ -531,7 +519,7 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
||||
if (current_item_ && change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
|
||||
emit TrackSkipped(current_item_);
|
||||
const QUrl &url = current_item_->Url();
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackSkipped();
|
||||
}
|
||||
}
|
||||
@ -550,7 +538,7 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
|
||||
current_item_ = app_->playlist_manager()->active()->current_item();
|
||||
const QUrl url = current_item_->Url();
|
||||
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
// It's already loading
|
||||
if (url == loading_async_) return;
|
||||
|
||||
@ -697,7 +685,7 @@ void Player::TrackAboutToEnd() {
|
||||
// We don't want to preload (and scrobble) the next item in the playlist if it's just going to be stopped again immediately after.
|
||||
if (app_->playlist_manager()->active()->current_item()) {
|
||||
const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
url_handlers_[url.scheme()]->TrackAboutToEnd();
|
||||
return;
|
||||
}
|
||||
@ -730,7 +718,7 @@ void Player::TrackAboutToEnd() {
|
||||
QUrl url = next_item->Url();
|
||||
|
||||
// Get the actual track URL rather than the stream URL.
|
||||
if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
|
||||
if (url_handlers_.contains(url.scheme())) {
|
||||
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
|
||||
switch (result.type_) {
|
||||
case UrlHandler::LoadResult::Error:
|
||||
|
@ -304,7 +304,7 @@ uint Song::mtime() const { return d->mtime_; }
|
||||
uint Song::ctime() const { return d->ctime_; }
|
||||
int Song::filesize() const { return d->filesize_; }
|
||||
Song::FileType Song::filetype() const { return d->filetype_; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Deezer; }
|
||||
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal; }
|
||||
bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
|
||||
bool Song::is_collection_song() const {
|
||||
return !is_cdda() && !is_stream() && id() != -1;
|
||||
@ -393,7 +393,6 @@ QString Song::TextForSource(Source source) {
|
||||
case Song::Source_Device: return QObject::tr("Device");
|
||||
case Song::Source_Stream: return QObject::tr("Stream");
|
||||
case Song::Source_Tidal: return QObject::tr("Tidal");
|
||||
case Song::Source_Deezer: return QObject::tr("Deezer");
|
||||
default: return QObject::tr("Unknown");
|
||||
}
|
||||
|
||||
@ -408,7 +407,6 @@ QIcon Song::IconForSource(Source source) {
|
||||
case Song::Source_Device: return IconLoader::Load("device");
|
||||
case Song::Source_Stream: return IconLoader::Load("applications-internet");
|
||||
case Song::Source_Tidal: return IconLoader::Load("tidal");
|
||||
case Song::Source_Deezer: return IconLoader::Load("deezer");
|
||||
default: return IconLoader::Load("edit-delete");
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,6 @@ class Song {
|
||||
Source_Device = 4,
|
||||
Source_Stream = 5,
|
||||
Source_Tidal = 6,
|
||||
Source_Deezer = 7,
|
||||
};
|
||||
|
||||
enum FileType {
|
||||
|
@ -1,827 +0,0 @@
|
||||
/*
|
||||
* 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"
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
# include <dzmedia.h>
|
||||
#endif
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QVector>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QJsonParseError>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
#include <QMenu>
|
||||
#include <QDesktopServices>
|
||||
#include <QSettings>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/player.h"
|
||||
#include "core/closure.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/mergedproxymodel.h"
|
||||
#include "core/network.h"
|
||||
#include "core/song.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/timeconstants.h"
|
||||
#include "core/utilities.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetsearch.h"
|
||||
#include "internet/localredirectserver.h"
|
||||
#include "deezerservice.h"
|
||||
#include "deezerurlhandler.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
const Song::Source DeezerService::kSource = Song::Source_Deezer;
|
||||
const char *DeezerService::kApiUrl = "https://api.deezer.com";
|
||||
const char *DeezerService::kOAuthUrl = "https://connect.deezer.com/oauth/auth.php";
|
||||
const char *DeezerService::kOAuthAccessTokenUrl = "https://connect.deezer.com/oauth/access_token.php";
|
||||
const char *DeezerService::kOAuthRedirectUrl = "https://oauth.strawbs.net";
|
||||
const int DeezerService::kAppID = 303684;
|
||||
const char *DeezerService::kSecretKey = "06911976010b9ddd7256769adf2b2e56";
|
||||
|
||||
typedef QPair<QString, QString> Param;
|
||||
|
||||
DeezerService::DeezerService(Application *app, QObject *parent)
|
||||
: InternetService(Song::Source_Deezer, "Deezer", "dzmedia", app, parent),
|
||||
network_(new NetworkAccessManager(this)),
|
||||
url_handler_(new DeezerUrlHandler(app, this)),
|
||||
#ifdef HAVE_DZMEDIA
|
||||
dzmedia_(new DZMedia(this)),
|
||||
#endif
|
||||
timer_searchdelay_(new QTimer(this)),
|
||||
searchdelay_(1500),
|
||||
albumssearchlimit_(1),
|
||||
songssearchlimit_(1),
|
||||
fetchalbums_(false),
|
||||
preview_(false),
|
||||
pending_search_id_(0),
|
||||
next_pending_search_id_(1),
|
||||
search_id_(0),
|
||||
albums_requested_(0),
|
||||
albums_received_(0)
|
||||
{
|
||||
|
||||
timer_searchdelay_->setSingleShot(true);
|
||||
connect(timer_searchdelay_, SIGNAL(timeout()), SLOT(StartSearch()));
|
||||
|
||||
connect(this, SIGNAL(Authenticated()), app->player(), SLOT(HandleAuthentication()));
|
||||
|
||||
app->player()->RegisterUrlHandler(url_handler_);
|
||||
|
||||
ReloadSettings();
|
||||
LoadAccessToken();
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
connect(dzmedia_, SIGNAL(StreamURLReceived(QUrl, QUrl, DZMedia::FileType)), this, SLOT(GetStreamURLFinished(QUrl, QUrl, DZMedia::FileType)));
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
DeezerService::~DeezerService() {}
|
||||
|
||||
void DeezerService::ShowConfig() {
|
||||
app_->OpenSettingsDialogAtPage(SettingsDialog::Page_Deezer);
|
||||
}
|
||||
|
||||
void DeezerService::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
searchdelay_ = s.value("searchdelay", 1500).toInt();
|
||||
albumssearchlimit_ = s.value("albumssearchlimit", 100).toInt();
|
||||
songssearchlimit_ = s.value("songssearchlimit", 100).toInt();
|
||||
fetchalbums_ = s.value("fetchalbums", false).toBool();
|
||||
coversize_ = s.value("coversize", "cover_big").toString();
|
||||
#if defined(HAVE_DEEZER) || defined(HAVE_DZMEDIA)
|
||||
bool preview(false);
|
||||
#else
|
||||
bool preview(true);
|
||||
#endif
|
||||
preview_ = s.value("preview", preview).toBool();
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::LoadAccessToken() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
if (s.contains("access_token") && s.contains("expiry_time")) {
|
||||
access_token_ = s.value("access_token").toString();
|
||||
expiry_time_ = s.value("expiry_time").toDateTime();
|
||||
}
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::Logout() {
|
||||
|
||||
access_token_.clear();
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.remove("access_token");
|
||||
s.remove("expiry_time");
|
||||
s.endGroup();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::StartAuthorisation() {
|
||||
|
||||
LocalRedirectServer *server = new LocalRedirectServer(this);
|
||||
server->Listen();
|
||||
|
||||
QUrl url = QUrl(kOAuthUrl);
|
||||
QUrlQuery url_query;
|
||||
//url_query.addQueryItem("response_type", "token");
|
||||
url_query.addQueryItem("response_type", "code");
|
||||
url_query.addQueryItem("app_id", QString::number(kAppID));
|
||||
QUrl redirect_url;
|
||||
QUrlQuery redirect_url_query;
|
||||
|
||||
const QString port = QString::number(server->url().port());
|
||||
|
||||
redirect_url = QUrl(kOAuthRedirectUrl);
|
||||
redirect_url_query.addQueryItem("port", port);
|
||||
redirect_url.setQuery(redirect_url_query);
|
||||
url_query.addQueryItem("redirect_uri", redirect_url.toString());
|
||||
url.setQuery(url_query);
|
||||
|
||||
NewClosure(server, SIGNAL(Finished()), this, &DeezerService::RedirectArrived, server, redirect_url);
|
||||
QDesktopServices::openUrl(url);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::RedirectArrived(LocalRedirectServer *server, QUrl url) {
|
||||
|
||||
server->deleteLater();
|
||||
QUrl request_url = server->request_url();
|
||||
RequestAccessToken(QUrlQuery(request_url).queryItemValue("code").toUtf8());
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::RequestAccessToken(const QByteArray &code) {
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
ArgList args = ArgList() << Arg("app_id", QString::number(kAppID))
|
||||
<< Arg("secret", kSecretKey)
|
||||
<< Arg("code", code);
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Arg &arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
QUrl url(kOAuthAccessTokenUrl);
|
||||
QNetworkRequest request = QNetworkRequest(url);
|
||||
QNetworkReply *reply = network_->post(request, url_query.toString(QUrl::FullyEncoded).toUtf8());
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(FetchAccessTokenFinished(QNetworkReply*)), reply);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::FetchAccessTokenFinished(QNetworkReply *reply) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
Error(QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()));
|
||||
return;
|
||||
}
|
||||
|
||||
forever {
|
||||
QByteArray line = reply->readLine();
|
||||
QString str(line);
|
||||
QStringList args = str.split("&");
|
||||
for (QString arg : args) {
|
||||
QStringList params = arg.split("=");
|
||||
if (params.count() < 2) continue;
|
||||
QString param1 = params.first();
|
||||
QString param2 = params[1];
|
||||
if (param1 == "access_token") access_token_ = param2;
|
||||
else if (param1 == "expires") SetExpiryTime(param2.toInt());
|
||||
}
|
||||
if (reply->atEnd()) break;
|
||||
}
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
s.setValue("access_token", access_token_);
|
||||
s.setValue("expiry_time", expiry_time_);
|
||||
s.endGroup();
|
||||
|
||||
emit Authenticated();
|
||||
emit LoginSuccess();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::SetExpiryTime(int expires_in_seconds) {
|
||||
|
||||
// Set the expiry time with two minutes' grace.
|
||||
expiry_time_ = QDateTime::currentDateTime().addSecs(expires_in_seconds - 120);
|
||||
qLog(Debug) << "Current oauth access token expires at:" << expiry_time_;
|
||||
|
||||
}
|
||||
|
||||
QNetworkReply *DeezerService::CreateRequest(const QString &ressource_name, const QList<Param> ¶ms) {
|
||||
|
||||
typedef QPair<QString, QString> Arg;
|
||||
typedef QList<Arg> ArgList;
|
||||
|
||||
typedef QPair<QByteArray, QByteArray> EncodedArg;
|
||||
typedef QList<EncodedArg> EncodedArgList;
|
||||
|
||||
ArgList args = ArgList() << Arg("access_token", access_token_)
|
||||
<< Arg("output", "json")
|
||||
<< params;
|
||||
|
||||
QUrlQuery url_query;
|
||||
for (const Arg& arg : args) {
|
||||
EncodedArg encoded_arg(QUrl::toPercentEncoding(arg.first), QUrl::toPercentEncoding(arg.second));
|
||||
url_query.addQueryItem(encoded_arg.first, encoded_arg.second);
|
||||
}
|
||||
|
||||
QUrl url(kApiUrl + QString("/") + ressource_name);
|
||||
url.setQuery(url_query);
|
||||
QNetworkRequest req(url);
|
||||
QNetworkReply *reply = network_->get(req);
|
||||
|
||||
//qLog(Debug) << "Deezer: Sending request" << url;
|
||||
|
||||
return reply;
|
||||
|
||||
}
|
||||
|
||||
QByteArray DeezerService::GetReplyData(QNetworkReply *reply) {
|
||||
|
||||
QByteArray data;
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
|
||||
data = reply->readAll();
|
||||
}
|
||||
else {
|
||||
if (reply->error() < 200) {
|
||||
// This is a network error, there is nothing more to do.
|
||||
QString failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
Error(failure_reason);
|
||||
}
|
||||
else {
|
||||
// See if there is Json data containing "error" - then use that instead.
|
||||
data = reply->readAll();
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
QString failure_reason;
|
||||
if (error.error == QJsonParseError::NoError && !json_doc.isNull() && !json_doc.isEmpty() && json_doc.isObject()) {
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
if (json_value_error.isObject()) {
|
||||
QJsonObject json_error = json_value_error.toObject();
|
||||
int code = json_error["code"].toInt();
|
||||
if (code == 300) Logout();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
failure_reason = QString("%1 (%2)").arg(message).arg(code);
|
||||
}
|
||||
else { failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error()); }
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
}
|
||||
else {
|
||||
failure_reason = QString("%1 (%2)").arg(reply->errorString()).arg(reply->error());
|
||||
}
|
||||
if (reply->error() == QNetworkReply::ContentAccessDenied || reply->error() == QNetworkReply::ContentOperationNotPermittedError || reply->error() == QNetworkReply::AuthenticationRequiredError) {
|
||||
// Session is probably expired
|
||||
Logout();
|
||||
Error(failure_reason);
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::ContentNotFoundError) { // Ignore this error
|
||||
Error(failure_reason);
|
||||
}
|
||||
else { // Fail
|
||||
Error(failure_reason);
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
return data;
|
||||
|
||||
}
|
||||
|
||||
QJsonObject DeezerService::ExtractJsonObj(QByteArray &data) {
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument json_doc = QJsonDocument::fromJson(data, &error);
|
||||
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
Error("Reply from server missing Json data.", data);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (json_doc.isNull() || json_doc.isEmpty()) {
|
||||
Error("Received empty Json document.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!json_doc.isObject()) {
|
||||
Error("Json document is not an object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
QJsonObject json_obj = json_doc.object();
|
||||
if (json_obj.isEmpty()) {
|
||||
Error("Received empty Json object.", json_doc);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
return json_obj;
|
||||
|
||||
}
|
||||
|
||||
QJsonValue DeezerService::ExtractData(QByteArray &data) {
|
||||
|
||||
QJsonObject json_obj = ExtractJsonObj(data);
|
||||
if (json_obj.isEmpty()) return QJsonObject();
|
||||
|
||||
if (json_obj.contains("error")) {
|
||||
QJsonValue json_value_error = json_obj["error"];
|
||||
if (!json_value_error.isObject()) {
|
||||
Error("Error missing object", json_obj);
|
||||
return QJsonValue();
|
||||
}
|
||||
QJsonObject json_error = json_value_error.toObject();
|
||||
int code = json_error["code"].toInt();
|
||||
if (code == 300) Logout();
|
||||
QString message = json_error["message"].toString();
|
||||
QString type = json_error["type"].toString();
|
||||
Error(QString("%1 (%2)").arg(message).arg(code));
|
||||
return QJsonValue();
|
||||
}
|
||||
|
||||
if (!json_obj.contains("data") && !json_obj.contains("DATA")) {
|
||||
Error("Json reply is missing data.", json_obj);
|
||||
return QJsonValue();
|
||||
}
|
||||
|
||||
QJsonValue json_data;
|
||||
if (json_obj.contains("data")) json_data = json_obj["data"];
|
||||
else json_data = json_obj["DATA"];
|
||||
|
||||
return json_data;
|
||||
|
||||
}
|
||||
|
||||
int DeezerService::Search(const QString &text, InternetSearch::SearchType searchby) {
|
||||
|
||||
pending_search_id_ = next_pending_search_id_;
|
||||
pending_search_text_ = text;
|
||||
pending_search_type_ = searchby;
|
||||
|
||||
next_pending_search_id_++;
|
||||
|
||||
if (text.isEmpty()) {
|
||||
timer_searchdelay_->stop();
|
||||
return pending_search_id_;
|
||||
}
|
||||
timer_searchdelay_->setInterval(searchdelay_);
|
||||
timer_searchdelay_->start();
|
||||
|
||||
return pending_search_id_;
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::StartSearch() {
|
||||
|
||||
if (access_token_.isEmpty()) {
|
||||
emit SearchError(pending_search_id_, "Not authenticated.");
|
||||
next_pending_search_id_ = 1;
|
||||
ShowConfig();
|
||||
return;
|
||||
}
|
||||
ClearSearch();
|
||||
search_id_ = pending_search_id_;
|
||||
search_text_ = pending_search_text_;
|
||||
|
||||
SendSearch();
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::CancelSearch() {
|
||||
ClearSearch();
|
||||
}
|
||||
|
||||
void DeezerService::ClearSearch() {
|
||||
search_id_ = 0;
|
||||
search_text_.clear();
|
||||
search_error_.clear();
|
||||
albums_requested_ = 0;
|
||||
albums_received_ = 0;
|
||||
requests_album_.clear();
|
||||
requests_song_.clear();
|
||||
songs_.clear();
|
||||
}
|
||||
|
||||
void DeezerService::SendSearch() {
|
||||
|
||||
emit UpdateStatus(tr("Searching..."));
|
||||
|
||||
QList<Param> parameters;
|
||||
parameters << Param("q", search_text_);
|
||||
QString searchparam;
|
||||
switch (pending_search_type_) {
|
||||
case InternetSearch::SearchType_Songs:
|
||||
searchparam = "search/track";
|
||||
parameters << Param("limit", QString::number(songssearchlimit_));
|
||||
break;
|
||||
case InternetSearch::SearchType_Albums:
|
||||
case InternetSearch::SearchType_Artists:
|
||||
default:
|
||||
searchparam = "search/album";
|
||||
parameters << Param("limit", QString::number(albumssearchlimit_));
|
||||
break;
|
||||
}
|
||||
|
||||
QNetworkReply *reply = CreateRequest(searchparam, parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(SearchFinished(QNetworkReply*, int)), reply, search_id_);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::SearchFinished(QNetworkReply *reply, int id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (id != search_id_) return;
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractData(data);
|
||||
if (!json_value.isArray()) {
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray json_data = json_value.toArray();
|
||||
if (json_data.isEmpty()) {
|
||||
Error(tr("No match."));
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
for (const QJsonValue &value : json_data) {
|
||||
|
||||
if (!value.isObject()) {
|
||||
Error("Invalid Json reply, data is not an object.", value);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (!json_obj.contains("id") || !json_obj.contains("type")) {
|
||||
Error("Invalid Json reply, item is missing ID or type.", json_obj);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString type = json_obj["type"].toString();
|
||||
|
||||
if (!json_obj.contains("artist")) {
|
||||
Error("Invalid Json reply, item missing artist.", json_obj);
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
if (!json_value_artist.isObject()) {
|
||||
Error("Invalid Json reply, item artist is not a object.", json_value_artist);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
|
||||
if (!json_artist.contains("name")) {
|
||||
Error("Invalid Json reply, artist data missing name.", json_artist);
|
||||
continue;
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
int album_id(0);
|
||||
QString album;
|
||||
QString cover;
|
||||
|
||||
if (type == "album") {
|
||||
album_id = json_obj["id"].toInt();
|
||||
album = json_obj["title"].toString();
|
||||
cover = json_obj[coversize_].toString();
|
||||
}
|
||||
else if (type == "track") {
|
||||
|
||||
if (!json_obj.contains("album")) {
|
||||
Error("Invalid Json reply, missing album data.", json_obj);
|
||||
continue;
|
||||
}
|
||||
QJsonValue json_value_album = json_obj["album"];
|
||||
if (!json_value_album.isObject()) {
|
||||
Error("Invalid Json reply, album data is not an object.", json_value_album);
|
||||
continue;
|
||||
}
|
||||
QJsonObject json_album = json_value_album.toObject();
|
||||
if (!json_album.contains("id") || !json_album.contains("title")) {
|
||||
Error("Invalid Json reply, album data is missing ID or title.", json_album);
|
||||
continue;
|
||||
}
|
||||
album_id = json_album["id"].toInt();
|
||||
album = json_album["title"].toString();
|
||||
cover = json_album[coversize_].toString();
|
||||
if (!fetchalbums_) {
|
||||
Song song = ParseSong(album_id, album, artist, cover, value);
|
||||
songs_ << song;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
DeezerAlbumContext *album_ctx;
|
||||
if (requests_album_.contains(album_id)) {
|
||||
album_ctx = requests_album_.value(album_id);
|
||||
album_ctx->search_id = search_id_;
|
||||
continue;
|
||||
}
|
||||
album_ctx = CreateAlbum(album_id, artist, album, cover);
|
||||
GetAlbum(album_ctx);
|
||||
albums_requested_++;
|
||||
if (albums_requested_ >= albumssearchlimit_) break;
|
||||
|
||||
}
|
||||
|
||||
if (albums_requested_ > 0) {
|
||||
if (albums_requested_ == 1) emit UpdateStatus(tr("Retrieving %1 album...").arg(albums_requested_));
|
||||
else emit UpdateStatus(tr("Retrieving %1 albums...").arg(albums_requested_));
|
||||
emit ProgressSetMaximum(albums_requested_);
|
||||
emit UpdateProgress(0);
|
||||
}
|
||||
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
DeezerAlbumContext *DeezerService::CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover) {
|
||||
|
||||
DeezerAlbumContext *album_ctx = new DeezerAlbumContext;
|
||||
album_ctx->id = album_id;
|
||||
album_ctx->artist = artist;
|
||||
album_ctx->album = album;
|
||||
album_ctx->cover = cover;
|
||||
album_ctx->cover_url.setUrl(cover);
|
||||
requests_album_.insert(album_id, album_ctx);
|
||||
|
||||
return album_ctx;
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::GetAlbum(const DeezerAlbumContext *album_ctx) {
|
||||
|
||||
QList<Param> parameters;
|
||||
QNetworkReply *reply = CreateRequest(QString("album/%1/tracks").arg(album_ctx->id), parameters);
|
||||
NewClosure(reply, SIGNAL(finished()), this, SLOT(GetAlbumFinished(QNetworkReply*, int, int)), reply, search_id_, album_ctx->id);
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id) {
|
||||
|
||||
reply->deleteLater();
|
||||
|
||||
if (!requests_album_.contains(album_id)) {
|
||||
qLog(Error) << "Deezer: Got reply for cancelled album request: " << album_id;
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
DeezerAlbumContext *album_ctx = requests_album_.value(album_id);
|
||||
|
||||
if (search_id != search_id_) {
|
||||
if (album_ctx->search_id == search_id) delete requests_album_.take(album_ctx->id);
|
||||
return;
|
||||
}
|
||||
|
||||
albums_received_++;
|
||||
emit UpdateProgress(albums_received_);
|
||||
|
||||
QByteArray data = GetReplyData(reply);
|
||||
if (data.isEmpty()) {
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonValue json_value = ExtractData(data);
|
||||
if (!json_value.isArray()) {
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray json_data = json_value.toArray();
|
||||
if (json_data.isEmpty()) {
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
bool compilation = false;
|
||||
bool multidisc = false;
|
||||
SongList songs;
|
||||
for (const QJsonValue &value : json_data) {
|
||||
Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->artist, album_ctx->cover, value);
|
||||
if (!song.is_valid()) continue;
|
||||
if (song.disc() >= 2) multidisc = true;
|
||||
if (song.is_compilation()) compilation = true;
|
||||
songs << song;
|
||||
}
|
||||
for (Song &song : songs) {
|
||||
if (compilation) song.set_compilation_detected(true);
|
||||
if (multidisc) {
|
||||
QString album_full(QString("%1 - (Disc %2)").arg(song.album()).arg(song.disc()));
|
||||
song.set_album(album_full);
|
||||
}
|
||||
songs_ << song;
|
||||
}
|
||||
|
||||
delete requests_album_.take(album_ctx->id);
|
||||
CheckFinish();
|
||||
|
||||
}
|
||||
|
||||
Song DeezerService::ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value) {
|
||||
|
||||
if (!value.isObject()) {
|
||||
Error("Invalid Json reply, track is not an object.", value);
|
||||
return Song();
|
||||
}
|
||||
QJsonObject json_obj = value.toObject();
|
||||
|
||||
if (
|
||||
!json_obj.contains("id") ||
|
||||
!json_obj.contains("title") ||
|
||||
!json_obj.contains("artist") ||
|
||||
!json_obj.contains("duration") ||
|
||||
!json_obj.contains("preview")
|
||||
) {
|
||||
Error("Invalid Json reply, track is missing one or more values.", json_obj);
|
||||
return Song();
|
||||
}
|
||||
|
||||
int song_id = json_obj["id"].toInt();
|
||||
QString title = json_obj["title"].toString();
|
||||
QJsonValue json_value_artist = json_obj["artist"];
|
||||
QVariant q_duration = json_obj["duration"].toVariant();
|
||||
int track(0);
|
||||
if (json_obj.contains("track_position")) track = json_obj["track_position"].toInt();
|
||||
int disc(0);
|
||||
if (json_obj.contains("disk_number")) disc = json_obj["disk_number"].toInt();
|
||||
QString preview = json_obj["preview"].toString();
|
||||
|
||||
if (!json_value_artist.isObject()) {
|
||||
Error("Invalid Json reply, track artist is not an object.", json_value_artist);
|
||||
return Song();
|
||||
}
|
||||
QJsonObject json_artist = json_value_artist.toObject();
|
||||
if (!json_artist.contains("name")) {
|
||||
Error("Invalid Json reply, track artist is missing name.", json_artist);
|
||||
return Song();
|
||||
}
|
||||
QString artist = json_artist["name"].toString();
|
||||
|
||||
Song song;
|
||||
song.set_source(Song::Source_Deezer);
|
||||
song.set_id(song_id);
|
||||
song.set_album_id(album_id);
|
||||
if (artist != album_artist) song.set_albumartist(album_artist);
|
||||
song.set_artist(artist);
|
||||
song.set_album(album);
|
||||
song.set_title(title);
|
||||
song.set_disc(disc);
|
||||
song.set_track(track);
|
||||
song.set_art_automatic(album_cover);
|
||||
|
||||
QUrl url;
|
||||
if (preview_) {
|
||||
url.setUrl(preview);
|
||||
quint64 duration = (30 * kNsecPerSec);
|
||||
song.set_length_nanosec(duration);
|
||||
}
|
||||
else {
|
||||
url.setScheme(url_handler_->scheme());
|
||||
url.setPath(QString("track/%1").arg(QString::number(song_id)));
|
||||
if (q_duration.isValid()) {
|
||||
quint64 duration = q_duration.toULongLong() * kNsecPerSec;
|
||||
song.set_length_nanosec(duration);
|
||||
}
|
||||
}
|
||||
song.set_url(url);
|
||||
song.set_valid(true);
|
||||
|
||||
return song;
|
||||
|
||||
}
|
||||
|
||||
bool DeezerService::GetStreamURL(const QUrl &original_url) {
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
stream_request_url_ = original_url;
|
||||
dzmedia_->GetStreamURL(original_url);
|
||||
return true;
|
||||
#else
|
||||
stream_request_url_ = QUrl();
|
||||
return false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
void DeezerService::GetStreamURLFinished(const QUrl original_url, const QUrl media_url, const DZMedia::FileType dzmedia_filetype) {
|
||||
|
||||
Song::FileType filetype(Song::FileType_Unknown);
|
||||
|
||||
switch (dzmedia_filetype) {
|
||||
case DZMedia::FileType_FLAC:
|
||||
filetype = Song::FileType_FLAC;
|
||||
break;
|
||||
case DZMedia::FileType_MPEG:
|
||||
filetype = Song::FileType_MPEG;
|
||||
break;
|
||||
case DZMedia::FileType_Stream:
|
||||
filetype = Song::FileType_Stream;
|
||||
break;
|
||||
default:
|
||||
filetype = Song::FileType_Unknown;
|
||||
break;
|
||||
}
|
||||
stream_request_url_ = QUrl();
|
||||
emit StreamURLReceived(original_url, media_url, filetype);
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
void DeezerService::CheckFinish() {
|
||||
|
||||
if (search_id_ == 0) return;
|
||||
|
||||
if (albums_requested_ <= albums_received_) {
|
||||
if (songs_.isEmpty()) {
|
||||
if (search_error_.isEmpty()) emit SearchError(search_id_, "Unknown error");
|
||||
else emit SearchError(search_id_, search_error_);
|
||||
}
|
||||
else emit SearchResults(search_id_, songs_);
|
||||
ClearSearch();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DeezerService::Error(QString error, QVariant debug) {
|
||||
qLog(Error) << "Deezer:" << error;
|
||||
if (debug.isValid()) qLog(Debug) << debug;
|
||||
if (search_id_ != 0) {
|
||||
if (!error.isEmpty()) {
|
||||
search_error_ += error;
|
||||
search_error_ += "<br />";
|
||||
}
|
||||
CheckFinish();
|
||||
}
|
||||
if (!stream_request_url_.isEmpty()) {
|
||||
emit StreamURLReceived(stream_request_url_, stream_request_url_, Song::FileType_Stream);
|
||||
stream_request_url_ = QUrl();
|
||||
}
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
/*
|
||||
* 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 DEEZERSERVICE_H
|
||||
#define DEEZERSERVICE_H
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#ifdef HAVE_DZMEDIA
|
||||
# include <dzmedia.h>
|
||||
#endif
|
||||
|
||||
#include <QtGlobal>
|
||||
#include <QObject>
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkReply>
|
||||
#include <QTimer>
|
||||
#include <QDateTime>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonValue>
|
||||
|
||||
#include "core/song.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "internet/internetservice.h"
|
||||
#include "internet/internetsearch.h"
|
||||
|
||||
class NetworkAccessManager;
|
||||
class LocalRedirectServer;
|
||||
class DeezerUrlHandler;
|
||||
|
||||
struct DeezerAlbumContext {
|
||||
int id;
|
||||
int search_id;
|
||||
QString artist;
|
||||
QString album;
|
||||
QString cover;
|
||||
QUrl cover_url;
|
||||
};
|
||||
Q_DECLARE_METATYPE(DeezerAlbumContext);
|
||||
|
||||
class DeezerService : public InternetService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerService(Application *app, QObject *parent);
|
||||
~DeezerService();
|
||||
|
||||
static const Song::Source kSource;
|
||||
static const int kAppID;
|
||||
|
||||
void ReloadSettings();
|
||||
|
||||
void Logout();
|
||||
int Search(const QString &query, InternetSearch::SearchType searchby);
|
||||
void CancelSearch();
|
||||
|
||||
const bool app_id() { return kAppID; }
|
||||
const bool authenticated() { return !access_token_.isEmpty(); }
|
||||
|
||||
bool GetStreamURL(const QUrl &url);
|
||||
|
||||
signals:
|
||||
void Login();
|
||||
void LoginSuccess();
|
||||
void LoginFailure(QString failure_reason);
|
||||
void Authenticated();
|
||||
void SearchResults(int id, SongList songs);
|
||||
void SearchError(int id, QString message);
|
||||
void UpdateStatus(QString text);
|
||||
void ProgressSetMaximum(int max);
|
||||
void UpdateProgress(int max);
|
||||
void StreamURLReceived(const QUrl original_url, const QUrl media_url, const Song::FileType filetype);
|
||||
|
||||
public slots:
|
||||
void ShowConfig();
|
||||
|
||||
private slots:
|
||||
void StartAuthorisation();
|
||||
void FetchAccessTokenFinished(QNetworkReply *reply);
|
||||
void StartSearch();
|
||||
void SearchFinished(QNetworkReply *reply, int search_id);
|
||||
void GetAlbumFinished(QNetworkReply *reply, int search_id, int album_id);
|
||||
#ifdef HAVE_DZMEDIA
|
||||
void GetStreamURLFinished(const QUrl original_url, const QUrl media_url, const DZMedia::FileType dzmedia_filetype);
|
||||
#endif
|
||||
|
||||
private:
|
||||
void LoadAccessToken();
|
||||
void RedirectArrived(LocalRedirectServer *server, QUrl url);
|
||||
void RequestAccessToken(const QByteArray &code);
|
||||
void SetExpiryTime(int expires_in_seconds);
|
||||
void ClearSearch();
|
||||
QNetworkReply *CreateRequest(const QString &ressource_name, const QList<QPair<QString, QString>> ¶ms);
|
||||
QByteArray GetReplyData(QNetworkReply *reply);
|
||||
QJsonObject ExtractJsonObj(QByteArray &data);
|
||||
QJsonValue ExtractData(QByteArray &data);
|
||||
void SendSearch();
|
||||
DeezerAlbumContext *CreateAlbum(const int album_id, const QString &artist, const QString &album, const QString &cover);
|
||||
void GetAlbum(const DeezerAlbumContext *album_ctx);
|
||||
Song ParseSong(const int album_id, const QString &album, const QString &album_artist, const QString &album_cover, const QJsonValue &value);
|
||||
void CheckFinish();
|
||||
void Error(QString error, QVariant debug = QString());
|
||||
|
||||
static const char *kApiUrl;
|
||||
static const char *kOAuthUrl;
|
||||
static const char *kOAuthAccessTokenUrl;
|
||||
static const char *kOAuthRedirectUrl;
|
||||
static const char *kSecretKey;
|
||||
|
||||
NetworkAccessManager *network_;
|
||||
DeezerUrlHandler *url_handler_;
|
||||
#ifdef HAVE_DZMEDIA
|
||||
DZMedia *dzmedia_;
|
||||
#endif
|
||||
QTimer *timer_searchdelay_;
|
||||
|
||||
int searchdelay_;
|
||||
int albumssearchlimit_;
|
||||
int songssearchlimit_;
|
||||
bool fetchalbums_;
|
||||
QString coversize_;
|
||||
bool preview_;
|
||||
QString access_token_;
|
||||
QDateTime expiry_time_;
|
||||
|
||||
int pending_search_id_;
|
||||
int next_pending_search_id_;
|
||||
QString pending_search_text_;
|
||||
InternetSearch::SearchType pending_search_type_;
|
||||
|
||||
int search_id_;
|
||||
QString search_text_;
|
||||
QHash<int, DeezerAlbumContext*> requests_album_;
|
||||
QHash<int, QUrl> requests_song_;
|
||||
int albums_requested_;
|
||||
int albums_received_;
|
||||
SongList songs_;
|
||||
QString search_error_;
|
||||
QUrl stream_request_url_;
|
||||
|
||||
};
|
||||
|
||||
#endif // DEEZERSERVICE_H
|
@ -1,72 +0,0 @@
|
||||
/*
|
||||
* 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 <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/application.h"
|
||||
#include "core/taskmanager.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "core/logging.h"
|
||||
#include "core/song.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
#include "deezerurlhandler.h"
|
||||
|
||||
DeezerUrlHandler::DeezerUrlHandler(
|
||||
Application *app, DeezerService *service)
|
||||
: UrlHandler(service), app_(app), service_(service), task_id_(-1) {
|
||||
|
||||
connect(service, SIGNAL(StreamURLReceived(QUrl, QUrl, Song::FileType)), this, SLOT(GetStreamURLFinished(QUrl, QUrl, Song::FileType)));
|
||||
|
||||
}
|
||||
|
||||
UrlHandler::LoadResult DeezerUrlHandler::StartLoading(const QUrl &url) {
|
||||
|
||||
LoadResult ret(url);
|
||||
if (task_id_ != -1) return ret;
|
||||
last_original_url_ = url;
|
||||
task_id_ = app_->task_manager()->StartTask(QString("Loading %1 stream...").arg(url.scheme()));
|
||||
bool wait_for_url = service_->GetStreamURL(url);
|
||||
if (wait_for_url) {
|
||||
ret.type_ = LoadResult::WillLoadAsynchronously;
|
||||
}
|
||||
else {
|
||||
CancelTask();
|
||||
ret.type_ = LoadResult::TrackAvailable;
|
||||
ret.media_url_ = url;
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void DeezerUrlHandler::GetStreamURLFinished(QUrl original_url, QUrl media_url, Song::FileType filetype) {
|
||||
|
||||
if (task_id_ == -1) return;
|
||||
CancelTask();
|
||||
emit AsyncLoadComplete(LoadResult(original_url, LoadResult::TrackAvailable, media_url, filetype));
|
||||
|
||||
}
|
||||
|
||||
void DeezerUrlHandler::CancelTask() {
|
||||
|
||||
app_->task_manager()->SetTaskFinished(task_id_);
|
||||
task_id_ = -1;
|
||||
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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 DEEZERURLHANDLER_H
|
||||
#define DEEZERURLHANDLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/urlhandler.h"
|
||||
#include "core/song.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
|
||||
class Application;
|
||||
class DeezerService;
|
||||
|
||||
class DeezerUrlHandler : public UrlHandler {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DeezerUrlHandler(Application *app, DeezerService *service);
|
||||
|
||||
QString scheme() const { return service_->url_scheme(); }
|
||||
LoadResult StartLoading(const QUrl &url);
|
||||
|
||||
void CancelTask();
|
||||
|
||||
private slots:
|
||||
void GetStreamURLFinished(QUrl original_url, QUrl media_url, Song::FileType filetype);
|
||||
|
||||
private:
|
||||
Application *app_;
|
||||
DeezerService *service_;
|
||||
int task_id_;
|
||||
QUrl last_original_url_;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@ -1,536 +0,0 @@
|
||||
/*
|
||||
* 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 "version.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 <deezer/deezer-object.h>
|
||||
#include <deezer/deezer-track.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 "core/song.h"
|
||||
#include "engine_fwd.h"
|
||||
#include "enginebase.h"
|
||||
#include "enginetype.h"
|
||||
#include "deezerengine.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
#include "settings/deezersettingspage.h"
|
||||
|
||||
const char *DeezerEngine::kAppID = "303684";
|
||||
const char *DeezerEngine::kProductID = "strawberry";
|
||||
const char *DeezerEngine::kProductVersion = STRAWBERRY_VERSION_DISPLAY;
|
||||
|
||||
DeezerEngine::DeezerEngine(TaskManager *task_manager)
|
||||
: EngineBase(),
|
||||
state_(Engine::Empty),
|
||||
position_(0),
|
||||
stopping_(false) {
|
||||
|
||||
type_ = Engine::Deezer;
|
||||
|
||||
}
|
||||
|
||||
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() << QCoreApplication::applicationName().toUtf8();
|
||||
|
||||
struct dz_connect_configuration config;
|
||||
memset(&config, 0, sizeof(struct dz_connect_configuration));
|
||||
|
||||
config.app_id = kAppID;
|
||||
config.product_id = kProductID;
|
||||
config.product_build_id = kProductVersion;
|
||||
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();
|
||||
ReloadSettings();
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::ReloadSettings() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
|
||||
QString quality = s.value("quality", "FLAC").toString();
|
||||
s.endGroup();
|
||||
dz_error_t dzerr;
|
||||
|
||||
if (quality == "MP3_128")
|
||||
dzerr = dz_player_set_track_quality(player_, nullptr, nullptr, DZ_TRACK_QUALITY_STANDARD);
|
||||
else if (quality == "MP3_320")
|
||||
dzerr = dz_player_set_track_quality(player_, nullptr, nullptr, DZ_TRACK_QUALITY_HIGHQUALITY);
|
||||
else if (quality == "FLAC")
|
||||
dzerr = dz_player_set_track_quality(player_, nullptr, nullptr, DZ_TRACK_QUALITY_CDQUALITY);
|
||||
else if (quality == "DATA_EFFICIENT")
|
||||
dzerr = dz_player_set_track_quality(player_, nullptr, nullptr, DZ_TRACK_QUALITY_DATA_EFFICIENT);
|
||||
else
|
||||
dzerr = dz_player_set_track_quality(player_, nullptr, nullptr, DZ_TRACK_QUALITY_CDQUALITY);
|
||||
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) {
|
||||
qLog(Error) << "Deezer: Failed to set quality.";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
stopping_ = 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;
|
||||
stopping_ = 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;
|
||||
stopping_ = true;
|
||||
|
||||
dz_error_t dzerr = dz_player_stop(player_, nullptr, nullptr);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
}
|
||||
|
||||
void DeezerEngine::Pause() {
|
||||
|
||||
if (!Initialised()) return;
|
||||
|
||||
dz_error_t dzerr = dz_player_pause(player_, nullptr, nullptr);
|
||||
if (dzerr != DZ_ERROR_NO_ERROR) return;
|
||||
|
||||
state_ = Engine::Paused;
|
||||
emit StateChanged(state_);
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
stopping_ = false;
|
||||
dz_useconds_t offset = (offset_nanosec / kNsecPerUsec);
|
||||
dz_error_t dzerr = dz_player_seek(player_, nullptr, nullptr, offset);
|
||||
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:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_LOADED:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_NO_RIGHT:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_NEED_NATURAL_NEXT:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_NOT_AVAILABLE_OFFLINE:
|
||||
engine->state_ = Engine::Error;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->Error("Track not available offline.");
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_RIGHTS_AFTER_AUDIOADS:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_SKIP_NO_RIGHT:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_QUEUELIST_TRACK_SELECTED:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_MEDIASTREAM_DATA_READY_AFTER_SEEK:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_START_FAILURE:
|
||||
engine->state_ = Engine::Error;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
emit engine->InvalidSongRequested(engine->media_url_);
|
||||
emit engine->Error("Track start failure.");
|
||||
break;
|
||||
|
||||
case DZ_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:
|
||||
engine->state_ = Engine::Idle;
|
||||
engine->position_ = 0;
|
||||
emit engine->TrackEnded();
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_PAUSED:
|
||||
engine->state_ = Engine::Paused;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_UNDERFLOW:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_RESUMED:
|
||||
engine->state_ = Engine::Playing;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_SEEKING:
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_RENDER_TRACK_REMOVED:
|
||||
if (!engine->stopping_) return;
|
||||
engine->state_ = Engine::Empty;
|
||||
engine->position_ = 0;
|
||||
emit engine->StateChanged(engine->state_);
|
||||
break;
|
||||
|
||||
case DZ_PLAYER_EVENT_UNKNOWN:
|
||||
default:
|
||||
qLog(Error) << "Deezer: Unknown player event" << type;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
const dz_media_track_detailed_infos_t *track_metadata = dz_track_metadata_get_format_header(metadata);
|
||||
DeezerEngine *engine = reinterpret_cast<DeezerEngine*>(userdata);
|
||||
Engine::SimpleMetaBundle bundle;
|
||||
|
||||
switch (track_metadata->format) {
|
||||
case DZ_MEDIA_FORMAT_AUDIO_MPEG:
|
||||
bundle.filetype = Song::FileType_MPEG;
|
||||
break;
|
||||
case DZ_MEDIA_FORMAT_AUDIO_FLAC:
|
||||
bundle.filetype = Song::FileType_FLAC;
|
||||
break;
|
||||
case DZ_MEDIA_FORMAT_AUDIO_PCM:
|
||||
bundle.filetype = Song::FileType_PCM;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
bundle.url = engine->original_url_;
|
||||
bundle.title = QString();
|
||||
bundle.artist = QString();
|
||||
bundle.comment = QString();
|
||||
bundle.album = QString();
|
||||
bundle.length = 0;
|
||||
bundle.year = 0;
|
||||
bundle.tracknr = 0;
|
||||
bundle.samplerate = track_metadata->audio.samples.sample_rate;
|
||||
bundle.bitdepth = 0;
|
||||
bundle.bitrate = track_metadata->average_bitrate / 1000;
|
||||
bundle.lyrics = QString();
|
||||
|
||||
emit engine->MetaData(bundle);
|
||||
|
||||
}
|
@ -1,94 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
void ReloadSettings();
|
||||
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:
|
||||
static const char *kAppID;
|
||||
static const char *kProductVersion;
|
||||
static const char *kProductID;
|
||||
static const char *kPath;
|
||||
Engine::State state_;
|
||||
dz_connect_handle connect_;
|
||||
dz_player_handle player_;
|
||||
QString access_token_;
|
||||
QDateTime expiry_time_;
|
||||
qint64 position_;
|
||||
bool stopping_;
|
||||
|
||||
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
|
@ -32,7 +32,6 @@ Engine::EngineType EngineTypeFromName(QString enginename) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -42,7 +41,6 @@ QString EngineName(Engine::EngineType enginetype) {
|
||||
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");
|
||||
}
|
||||
@ -54,7 +52,6 @@ QString EngineDescription(Engine::EngineType enginetype) {
|
||||
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");
|
||||
|
||||
|
@ -32,8 +32,7 @@ enum EngineType {
|
||||
GStreamer,
|
||||
VLC,
|
||||
Xine,
|
||||
Phonon,
|
||||
Deezer
|
||||
Phonon
|
||||
};
|
||||
|
||||
Engine::EngineType EngineTypeFromName(QString enginename);
|
||||
|
@ -75,7 +75,7 @@ class PlaylistHeader;
|
||||
// that uses Gtk to paint row backgrounds, ignoring any custom brush or palette the caller set in the QStyleOption.
|
||||
// That breaks our currently playing track animation, which relies on the background painted by Qt to be transparent.
|
||||
// This proxy style uses QCommonStyle to paint the affected elements.
|
||||
// This class is used by tidal and deezer search view as well.
|
||||
// This class is used by internet search view as well.
|
||||
class PlaylistProxyStyle : public QProxyStyle {
|
||||
public:
|
||||
PlaylistProxyStyle(QStyle *base);
|
||||
|
@ -94,9 +94,6 @@ void BackendSettingsPage::Load() {
|
||||
#ifdef HAVE_PHONON
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("speaker"), EngineDescription(Engine::Phonon), QVariant::fromValue(Engine::Phonon));
|
||||
#endif
|
||||
#ifdef HAVE_DEEZER
|
||||
ui_->combobox_engine->addItem(IconLoader::Load("deezer"), EngineDescription(Engine::Deezer), QVariant::fromValue(Engine::Deezer));
|
||||
#endif
|
||||
|
||||
enginetype_current_ = enginetype;
|
||||
output_current_ = s_.value("output", QString()).toString();
|
||||
|
@ -1,142 +0,0 @@
|
||||
/*
|
||||
* 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 <QObject>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QMessageBox>
|
||||
#include <QEvent>
|
||||
|
||||
#include "deezersettingspage.h"
|
||||
#include "ui_deezersettingspage.h"
|
||||
#include "core/application.h"
|
||||
#include "core/iconloader.h"
|
||||
#include "internet/internetservices.h"
|
||||
#include "deezer/deezerservice.h"
|
||||
|
||||
const char *DeezerSettingsPage::kSettingsGroup = "Deezer";
|
||||
|
||||
DeezerSettingsPage::DeezerSettingsPage(SettingsDialog *parent)
|
||||
: SettingsPage(parent),
|
||||
ui_(new Ui::DeezerSettingsPage),
|
||||
service_(dialog()->app()->internet_services()->Service<DeezerService>()) {
|
||||
|
||||
ui_->setupUi(this);
|
||||
setWindowIcon(IconLoader::Load("deezer"));
|
||||
|
||||
connect(ui_->button_login, SIGNAL(clicked()), SLOT(LoginClicked()));
|
||||
connect(ui_->login_state, SIGNAL(LogoutClicked()), SLOT(LogoutClicked()));
|
||||
|
||||
connect(this, SIGNAL(Login()), service_, SLOT(StartAuthorisation()));
|
||||
|
||||
connect(service_, SIGNAL(LoginFailure(QString)), SLOT(LoginFailure(QString)));
|
||||
connect(service_, SIGNAL(LoginSuccess()), SLOT(LoginSuccess()));
|
||||
|
||||
dialog()->installEventFilter(this);
|
||||
|
||||
ui_->combobox_quality->addItem("MP3 128kbps \"Standard\"", "MP3_128");
|
||||
ui_->combobox_quality->addItem("MP3 320kbps \"High Quality\"", "MP3_320");
|
||||
ui_->combobox_quality->addItem("FLAC \"CD Quality\"", "FLAC");
|
||||
ui_->combobox_quality->addItem("\"Data Efficient\"", "DATA_EFFICIENT");
|
||||
|
||||
ui_->combobox_coversize->addItem("Small", "cover_small");
|
||||
ui_->combobox_coversize->addItem("Medium", "cover_medium");
|
||||
ui_->combobox_coversize->addItem("Big", "cover_big");
|
||||
ui_->combobox_coversize->addItem("XL", "cover_xl");
|
||||
|
||||
}
|
||||
|
||||
DeezerSettingsPage::~DeezerSettingsPage() { delete ui_; }
|
||||
|
||||
void DeezerSettingsPage::Load() {
|
||||
|
||||
QSettings s;
|
||||
|
||||
s.beginGroup(kSettingsGroup);
|
||||
ui_->checkbox_enable->setChecked(s.value("enabled", false).toBool());
|
||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_quality, "quality", "FLAC");
|
||||
ui_->spinbox_searchdelay->setValue(s.value("searchdelay", 1500).toInt());
|
||||
ui_->spinbox_albumssearchlimit->setValue(s.value("albumssearchlimit", 100).toInt());
|
||||
ui_->spinbox_songssearchlimit->setValue(s.value("songssearchlimit", 100).toInt());
|
||||
ui_->checkbox_fetchalbums->setChecked(s.value("fetchalbums", false).toBool());
|
||||
dialog()->ComboBoxLoadFromSettings(s, ui_->combobox_coversize, "coversize", "cover_big");
|
||||
#if defined(HAVE_DEEZER) || defined(HAVE_DZMEDIA)
|
||||
bool preview(false);
|
||||
#else
|
||||
bool preview(true);
|
||||
#endif
|
||||
ui_->checkbox_preview->setChecked(s.value("preview", preview).toBool());
|
||||
s.endGroup();
|
||||
|
||||
if (service_->authenticated()) ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||
else ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
|
||||
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::Save() {
|
||||
|
||||
QSettings s;
|
||||
s.beginGroup(kSettingsGroup);
|
||||
s.setValue("enabled", ui_->checkbox_enable->isChecked());
|
||||
s.setValue("quality", ui_->combobox_quality->itemData(ui_->combobox_quality->currentIndex()));
|
||||
s.setValue("searchdelay", ui_->spinbox_searchdelay->value());
|
||||
s.setValue("albumssearchlimit", ui_->spinbox_albumssearchlimit->value());
|
||||
s.setValue("songssearchlimit", ui_->spinbox_songssearchlimit->value());
|
||||
s.setValue("fetchalbums", ui_->checkbox_fetchalbums->isChecked());
|
||||
s.setValue("coversize", ui_->combobox_coversize->itemData(ui_->combobox_coversize->currentIndex()));
|
||||
s.setValue("preview", ui_->checkbox_preview->isChecked());
|
||||
s.endGroup();
|
||||
|
||||
service_->ReloadSettings();
|
||||
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginClicked() {
|
||||
emit Login();
|
||||
ui_->button_login->setEnabled(false);
|
||||
}
|
||||
|
||||
bool DeezerSettingsPage::eventFilter(QObject *object, QEvent *event) {
|
||||
|
||||
if (object == dialog() && event->type() == QEvent::Enter) {
|
||||
ui_->button_login->setEnabled(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
return SettingsPage::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LogoutClicked() {
|
||||
service_->Logout();
|
||||
ui_->button_login->setEnabled(true);
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedOut);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginSuccess() {
|
||||
if (!this->isVisible()) return;
|
||||
ui_->login_state->SetLoggedIn(LoginStateWidget::LoggedIn);
|
||||
ui_->button_login->setEnabled(false);
|
||||
}
|
||||
|
||||
void DeezerSettingsPage::LoginFailure(QString failure_reason) {
|
||||
if (!this->isVisible()) return;
|
||||
QMessageBox::warning(this, tr("Authentication failed"), failure_reason);
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
* 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 DEEZERSETTINGSPAGE_H
|
||||
#define DEEZERSETTINGSPAGE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QEvent>
|
||||
|
||||
#include "settings/settingspage.h"
|
||||
|
||||
class DeezerService;
|
||||
class Ui_DeezerSettingsPage;
|
||||
|
||||
class DeezerSettingsPage : public SettingsPage {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DeezerSettingsPage(SettingsDialog* parent = nullptr);
|
||||
~DeezerSettingsPage();
|
||||
|
||||
static const char *kSettingsGroup;
|
||||
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
|
||||
signals:
|
||||
void Login();
|
||||
|
||||
private slots:
|
||||
void LoginClicked();
|
||||
void LogoutClicked();
|
||||
void LoginSuccess();
|
||||
void LoginFailure(QString failure_reason);
|
||||
|
||||
private:
|
||||
Ui_DeezerSettingsPage* ui_;
|
||||
DeezerService *service_;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,354 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DeezerSettingsPage</class>
|
||||
<widget class="QWidget" name="DeezerSettingsPage">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>715</width>
|
||||
<height>575</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Deezer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="credential_group">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Authentication</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="1">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="button_login">
|
||||
<property name="text">
|
||||
<string>Login</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_auth">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LoginStateWidget" name="login_state" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupbox_preferences">
|
||||
<property name="title">
|
||||
<string>Preferences</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_quality">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_quality">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Audio quality</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="combobox_quality"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_quality">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_searchdelay">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_searchdelay">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search delay</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_searchdelay">
|
||||
<property name="suffix">
|
||||
<string>ms</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>1500</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_searchdelay">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_albumssearchlimit">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_albumssearchlimit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Albums search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_albumssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_albumssearchlimit">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_songssearchlimit">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_songssearchlimit">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Songs search limit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinbox_songssearchlimit">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_songssearchlimit">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_fetchalbums">
|
||||
<property name="text">
|
||||
<string>Fetch entire albums when searching songs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_coversize">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_coversize">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Album cover size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="combobox_coversize"/>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_coversize">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkbox_preview">
|
||||
<property name="text">
|
||||
<string>Use 30 seconds preview streams</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="spacer_middle">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<spacer name="spacer_bottom">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_deezer">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>62</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>62</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="pixmap">
|
||||
<pixmap resource="../../data/data.qrc">:/pictures/deezer.png</pixmap>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LoginStateWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>widgets/loginstatewidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../data/data.qrc"/>
|
||||
<include location="../../data/icons.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
@ -65,9 +65,6 @@
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
# include "tidalsettingspage.h"
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
# include "deezersettingspage.h"
|
||||
#endif
|
||||
|
||||
#include "ui_settingsdialog.h"
|
||||
|
||||
@ -136,15 +133,12 @@ SettingsDialog::SettingsDialog(Application *app, QWidget *parent)
|
||||
AddPage(Page_GlobalShortcuts, new GlobalShortcutsSettingsPage(this), iface);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_STREAM_TIDAL) || defined(HAVE_STREAM_DEEZER)
|
||||
#if defined(HAVE_STREAM_TIDAL)
|
||||
QTreeWidgetItem *streaming = AddCategory(tr("Streaming"));
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_TIDAL
|
||||
AddPage(Page_Tidal, new TidalSettingsPage(this), streaming);
|
||||
#endif
|
||||
#ifdef HAVE_STREAM_DEEZER
|
||||
AddPage(Page_Deezer, new DeezerSettingsPage(this), streaming);
|
||||
#endif
|
||||
|
||||
// List box
|
||||
connect(ui_->list, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(CurrentItemChanged(QTreeWidgetItem*)));
|
||||
|
@ -83,7 +83,6 @@ public:
|
||||
Page_Proxy,
|
||||
Page_Scrobbler,
|
||||
Page_Tidal,
|
||||
Page_Deezer,
|
||||
};
|
||||
|
||||
enum Role {
|
||||
|