Compare commits

...

83 Commits
0.3.2 ... 0.4.1

Author SHA1 Message Date
Jonas Kvinge
b782a2c8a2 Release 0.4.1 2018-11-01 21:18:57 +01:00
Jonas Kvinge
298783e2c5 Attempt to login if streamurl fails 2018-11-01 21:18:09 +01:00
Jonas Kvinge
bc9ec8025c Move some settings 2018-10-31 19:44:24 +01:00
Jonas Kvinge
26459763e5 Only set QtDebugMsg when build type is debug 2018-10-31 19:41:52 +01:00
Jonas Kvinge
1213306657 Update libcdio 2018-10-31 01:30:15 +01:00
Jonas Kvinge
50add12e4b Update changelog 2018-10-30 23:47:51 +01:00
Jonas Kvinge
67c889f982 Improve Xine engine code 2018-10-30 23:40:41 +01:00
Jonas Kvinge
08cba25071 Improve VLC error handling 2018-10-30 23:40:05 +01:00
Jonas Kvinge
ff35b01bac Improve gst engine error handling 2018-10-30 23:39:08 +01:00
Jonas Kvinge
faee1977fe Add better error handling between engine and player 2018-10-30 23:21:51 +01:00
Jonas Kvinge
6105b99a7f Add option to continue to next song in playlist on error
Also rename some settings. Add option to grey out songs both on playback and
on startup.
2018-10-30 23:20:02 +01:00
Jonas Kvinge
4993201b70 Fix deezer url handler 2018-10-30 23:18:25 +01:00
Jonas Kvinge
f456ca674d Update taglib 2018-10-27 12:15:52 +02:00
Jonas Kvinge
2ba350f76f Attempt to fix crash in setStyleSheet() 2018-10-26 20:05:37 +02:00
Jonas Kvinge
3fe92a62ac Add DSF and DSDIFF to IsFileLossless 2018-10-24 00:58:03 +02:00
Jonas Kvinge
f4dcf6821f Rename class InternetModel to InternetServices 2018-10-23 23:25:02 +02:00
Jonas Kvinge
0c10013858 Ops 2018-10-23 21:14:58 +02:00
Jonas Kvinge
3e9530fa8d Set metadata from deezer engine 2018-10-23 20:21:33 +02:00
Jonas Kvinge
0f4ffe4fce Add PCM as filetype 2018-10-23 20:21:01 +02:00
Jonas Kvinge
e1c01e5d25 Fix device selection 2018-10-23 20:18:07 +02:00
Jonas Kvinge
51021131d7 Allow compile on Qt 5.5.1 or above 2018-10-23 20:17:29 +02:00
Jonas Kvinge
89a08d1d5e Fix RPM build 2018-10-22 23:23:25 +02:00
Jonas Kvinge
ba8717f95a Fix Deezer engine error state 2018-10-22 23:04:34 +02:00
Jonas Kvinge
c8dfb9b0db Fix loading custom device 2018-10-22 22:10:27 +02:00
Jonas Kvinge
3a02ece169 Add EnsureInitialised() 2018-10-22 20:40:02 +02:00
Jonas Kvinge
262df6b461 Merge pull request #24 from eclipseo/use_qt5_deps
Use qt5 libraries of system-wide libraries
2018-10-22 19:00:07 +02:00
Jonas Kvinge
b9516fdfd4 Merge pull request #23 from eclipseo/add_appdata_file
Add AppStream data file
2018-10-22 18:49:28 +02:00
Robert-André Mauchin
9de41bc027 Use qt5 version of system-wide libraries 2018-10-22 17:45:47 +02:00
Robert-André Mauchin
53b5cf3855 Add AppStream data file 2018-10-22 16:44:35 +02:00
Jonas Kvinge
0567358783 Update README.md 2018-10-21 20:49:35 +02:00
Jonas Kvinge
c97bc55c6f Move queuemanager to fancytabbar 2018-10-21 15:13:48 +02:00
Jonas Kvinge
3ef0bf60d0 Correct text for right click configure 2018-10-20 22:33:23 +02:00
Jonas Kvinge
b7b8084a38 Remove setExpanding 2018-10-20 22:18:56 +02:00
Jonas Kvinge
5221f13498 Minor code cleanup and fixes 2018-10-20 22:16:22 +02:00
Jonas Kvinge
0c0a8d70f3 Change darwin to macos 2018-10-20 22:15:32 +02:00
Jonas Kvinge
2ff971878d Enable debug logging on Fedora 2018-10-20 22:13:51 +02:00
Jonas Kvinge
1a0dc1e614 Update README 2018-10-20 22:13:21 +02:00
Jonas Kvinge
4a0bd99654 Enable deezer engine by default 2018-10-20 22:13:02 +02:00
Jonas Kvinge
5cb98e44cb Update README 2018-10-19 20:42:58 +02:00
Jonas Kvinge
13ed99b9c3 Update README and Changelog 2018-10-19 20:37:42 +02:00
Jonas Kvinge
0cda4e27aa Replace qSort/qStableSort/qSwap 2018-10-19 20:18:46 +02:00
Jonas Kvinge
0969e7f504 Remove cachedlist.h 2018-10-19 19:38:30 +02:00
Jonas Kvinge
cad73e18e2 Remove unused code 2018-10-19 19:20:29 +02:00
Jonas Kvinge
9eadeddfd9 Fixup and finish deezer engine 2018-10-19 19:15:33 +02:00
Jonas Kvinge
de11cb173b Use previews as default setting if deezer engine and dzmedia is missing 2018-10-19 19:14:49 +02:00
Jonas Kvinge
5853bc68d1 Replace depreciated macro 2018-10-19 19:13:40 +02:00
Jonas Kvinge
7d1fd9d46f Add missing names 2018-10-19 19:13:24 +02:00
Jonas Kvinge
c977c822d5 Add info about Deezer 2018-10-19 19:13:01 +02:00
Jonas Kvinge
c05fb33ea2 Replace FancyTabWidget with improved version 2018-10-19 19:10:22 +02:00
Jonas Kvinge
b9d0b3e152 Add option to diable Tidal and Deezer 2018-10-17 23:49:02 +02:00
Jonas Kvinge
a9e905b301 Fix bug not loading engine 2018-10-17 22:55:36 +02:00
Jonas Kvinge
a8a714c820 Use common classes for Tidal and Deezer 2018-10-17 21:18:39 +02:00
Jonas Kvinge
9349ad9383 Fix missing icon 2018-10-17 00:46:08 +02:00
Jonas Kvinge
f686f00951 Fix missing icon 2018-10-17 00:43:05 +02:00
Jonas Kvinge
3d13c12cb7 Update taglib 2018-10-16 23:58:26 +02:00
Jonas Kvinge
2384a42d33 Update .gitignore 2018-10-16 21:37:27 +02:00
Jonas Kvinge
f507fec905 Update Changelog 2018-10-16 21:32:03 +02:00
Jonas Kvinge
83a9724d17 Improve Tidal code 2018-10-16 21:31:28 +02:00
Jonas Kvinge
4156e26f76 Fix some minor code bugs in deezer 2018-10-16 21:30:27 +02:00
Jonas Kvinge
6d269e1786 Add libdeezer dll to windows nsi 2018-10-16 21:29:40 +02:00
Jonas Kvinge
ea447cab37 Remove quotes 2018-10-16 21:29:21 +02:00
Jonas Kvinge
4f3e7de441 Exclude debian changelog 2018-10-16 21:29:00 +02:00
Jonas Kvinge
0a81fa99fc Add Deezer support 2018-10-14 00:08:33 +02:00
Jonas Kvinge
4aad44cb62 Create timer for login attempts for Tidal 2018-10-13 00:30:52 +02:00
Jonas Kvinge
69dda39d02 Create timer for login attempts for Tidal 2018-10-13 00:18:38 +02:00
Jonas Kvinge
ca3ba6f136 Increase kTrackSliderUpdateTimeMs 2018-10-02 01:01:31 +02:00
Jonas Kvinge
044cf4624a Add hide() 2018-10-02 00:58:46 +02:00
Jonas Kvinge
1fbfabdf66 Remove whitespaces 2018-10-02 00:46:54 +02:00
Jonas Kvinge
db035351be Remove whitespaces 2018-10-02 00:38:52 +02:00
Jonas Kvinge
2883ef840e Fix track stop on error 2018-10-02 00:21:50 +02:00
Jonas Kvinge
8254ee911d Fix default setting for system tray 2018-10-01 00:31:02 +02:00
Jonas Kvinge
560bc0a150 Fix default setting for system tray 2018-10-01 00:29:06 +02:00
Jonas Kvinge
827898cd38 Fix taglib includes 2018-09-30 22:32:56 +02:00
Jonas Kvinge
184dec146c Move resume playback outside of startup group 2018-09-30 15:55:02 +02:00
Jonas Kvinge
298dbe96c8 Check if system has system tray using QSystemTrayIcon::isSystemTrayAvailable() 2018-09-30 15:33:27 +02:00
Jonas Kvinge
6d888eb51a Analyzer code cleanup and try to fix crash on Fedora 2018-09-30 15:32:21 +02:00
Jonas Kvinge
3694765611 Add error handling for enabling FTS3 2018-09-30 14:54:14 +02:00
Jonas Kvinge
b07ae3d34e Remove reset 2018-09-30 00:08:09 +02:00
Jonas Kvinge
64bcdf4734 Fix spelling 2018-09-30 00:07:59 +02:00
Jonas Kvinge
1c23756fc4 Turn on git revision 2018-09-30 00:07:05 +02:00
Jonas Kvinge
50e3eeaafd Release 0.3.3 2018-09-24 19:13:45 +02:00
Jonas Kvinge
843f528ebc Revert change to Tidal login using clientUniqueKey 2018-09-24 19:10:49 +02:00
Jonas Kvinge
faa0076988 Turn back git revision 2018-09-24 19:10:15 +02:00
227 changed files with 6304 additions and 3721 deletions

1
.gitignore vendored
View File

@@ -38,6 +38,7 @@ Thumbs.db
*.nsi *.nsi
*.plist *.plist
maketarball.sh maketarball.sh
dist/debian/changelog
# qtcreator generated files # qtcreator generated files
*.pro.user* *.pro.user*

View File

@@ -28,6 +28,7 @@
#include <id3v2tag.h> #include <id3v2tag.h>
#include <tstringlist.h> #include <tstringlist.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tagutils.h>
#include "tagunion.h" #include "tagunion.h"
#include "dsdifffile.h" #include "dsdifffile.h"
@@ -97,6 +98,18 @@ public:
bool hasDiin; bool hasDiin;
}; };
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSDIFF::File::isSupported(IOStream *stream)
{
// A DSDIFF file has to start with "FRM8????????DSD ".
const ByteVector id = Utils::readHeader(stream, 16, false);
return (id.startsWith("FRM8") && id.containsAt("DSD ", 12));
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -185,6 +185,15 @@ namespace TagLib {
*/ */
bool hasDIINTag() const; bool hasDIINTag() const;
/*!
* Returns whether or not the given \a stream can be opened as a DSDIFF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
protected: protected:
enum Endianness { BigEndian, LittleEndian }; enum Endianness { BigEndian, LittleEndian };

View File

@@ -1,5 +1,5 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2013 by Stephen F. Booth copyright : (C) 2013 - 2018 by Stephen F. Booth
email : me@sbooth.org email : me@sbooth.org
***************************************************************************/ ***************************************************************************/
@@ -28,6 +28,7 @@
#include <id3v2tag.h> #include <id3v2tag.h>
#include <tstringlist.h> #include <tstringlist.h>
#include <tpropertymap.h> #include <tpropertymap.h>
#include <tagutils.h>
#include "dsffile.h" #include "dsffile.h"
@@ -56,6 +57,17 @@ public:
ID3v2::Tag *tag; ID3v2::Tag *tag;
}; };
////////////////////////////////////////////////////////////////////////////////
// static members
////////////////////////////////////////////////////////////////////////////////
bool DSF::File::isSupported(IOStream *stream)
{
// A DSF file has to start with "DSD "
const ByteVector id = Utils::readHeader(stream, 4, false);
return id.startsWith("DSD ");
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// public members // public members
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,5 +1,5 @@
/*************************************************************************** /***************************************************************************
copyright : (C) 2013 by Stephen F. Booth copyright : (C) 2013 - 2018 by Stephen F. Booth
email : me@sbooth.org email : me@sbooth.org
***************************************************************************/ ***************************************************************************/
@@ -103,6 +103,15 @@ namespace TagLib {
*/ */
virtual bool save(); virtual bool save();
/*!
* Returns whether or not the given \a stream can be opened as a DSF
* file.
*
* \note This method is designed to do a quick check. The result may
* not necessarily be correct.
*/
static bool isSupported(IOStream *stream);
private: private:
File(const File &); File(const File &);
File &operator=(const File &); File &operator=(const File &);

View File

@@ -180,10 +180,10 @@ namespace
file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle); file = new RIFF::WAV::File(stream, readAudioProperties, audioPropertiesStyle);
else if(APE::File::isSupported(stream)) else if(APE::File::isSupported(stream))
file = new APE::File(stream, readAudioProperties, audioPropertiesStyle); file = new APE::File(stream, readAudioProperties, audioPropertiesStyle);
//else if(DSF::File::isSupported(stream)) else if(DSDIFF::File::isSupported(stream))
//return new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle); file = new DSDIFF::File(stream, readAudioProperties, audioPropertiesStyle);
//else if(DSDIFF::File::isSupported(stream)) else if(DSF::File::isSupported(stream))
//return new DSF::File(stream, readAudioProperties, audioPropertiesStyle); file = new DSF::File(stream, readAudioProperties, audioPropertiesStyle);
// isSupported() only does a quick check, so double check the file here. // isSupported() only does a quick check, so double check the file here.

View File

@@ -216,7 +216,23 @@ void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
String TableOfContentsFrame::toString() const String TableOfContentsFrame::toString() const
{ {
return String(); String s = String(d->elementID) +
": top level: " + (d->isTopLevel ? "true" : "false") +
", ordered: " + (d->isOrdered ? "true" : "false");
if(!d->childElements.isEmpty()) {
s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]";
}
if(!d->embeddedFrameList.isEmpty()) {
StringList frameIDs;
for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
it != d->embeddedFrameList.end(); ++it)
frameIDs.append((*it)->frameID());
s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
}
return s;
} }
PropertyMap TableOfContentsFrame::asProperties() const PropertyMap TableOfContentsFrame::asProperties() const

View File

@@ -339,7 +339,13 @@ UserTextIdentificationFrame::UserTextIdentificationFrame(const String &descripti
String UserTextIdentificationFrame::toString() const String UserTextIdentificationFrame::toString() const
{ {
return "[" + description() + "] " + fieldList().toString(); // first entry is the description itself, drop from values list
StringList l = fieldList();
for(StringList::Iterator it = l.begin(); it != l.end(); ++it) {
l.erase(it);
break;
}
return "[" + description() + "] " + l.toString();
} }
String UserTextIdentificationFrame::description() const String UserTextIdentificationFrame::description() const

View File

@@ -231,11 +231,21 @@ void Ogg::FLAC::File::scan()
if(!metadataHeader.startsWith("fLaC")) { if(!metadataHeader.startsWith("fLaC")) {
// FLAC 1.1.2+ // FLAC 1.1.2+
// See https://xiph.org/flac/ogg_mapping.html for the header specification.
if(metadataHeader.size() < 13)
return;
if(metadataHeader[0] != 0x7f)
return;
if(metadataHeader.mid(1, 4) != "FLAC") if(metadataHeader.mid(1, 4) != "FLAC")
return; return;
if(metadataHeader[5] != 1) if(metadataHeader[5] != 1 && metadataHeader[6] != 0)
return; // not version 1 return; // not version 1.0
if(metadataHeader.mid(9, 4) != "fLaC")
return;
metadataHeader = metadataHeader.mid(13); metadataHeader = metadataHeader.mid(13);
} }

View File

@@ -58,6 +58,11 @@ namespace
#endif #endif
} }
FileHandle openFile(const int fileDescriptor, bool readOnly)
{
return InvalidFileHandle;
}
void closeFile(FileHandle file) void closeFile(FileHandle file)
{ {
CloseHandle(file); CloseHandle(file);
@@ -98,6 +103,11 @@ namespace
return fopen(path, readOnly ? "rb" : "rb+"); return fopen(path, readOnly ? "rb" : "rb+");
} }
FileHandle openFile(const int fileDescriptor, bool readOnly)
{
return fdopen(fileDescriptor, readOnly ? "rb" : "rb+");
}
void closeFile(FileHandle file) void closeFile(FileHandle file)
{ {
fclose(file); fclose(file);
@@ -149,13 +159,28 @@ FileStream::FileStream(FileName fileName, bool openReadOnly)
d->file = openFile(fileName, true); d->file = openFile(fileName, true);
if(d->file == InvalidFileHandle) if(d->file == InvalidFileHandle)
{
# ifdef _WIN32 # ifdef _WIN32
debug("Could not open file " + fileName.toString()); debug("Could not open file " + fileName.toString());
# else # else
debug("Could not open file " + String(static_cast<const char *>(d->name))); debug("Could not open file " + String(static_cast<const char *>(d->name)));
# endif # endif
} }
FileStream::FileStream(int fileDescriptor, bool openReadOnly)
: d(new FileStreamPrivate(""))
{
// First try with read / write mode, if that fails, fall back to read only.
if(!openReadOnly)
d->file = openFile(fileDescriptor, false);
if(d->file != InvalidFileHandle)
d->readOnly = false;
else
d->file = openFile(fileDescriptor, true);
if(d->file == InvalidFileHandle)
debug("Could not open file using file descriptor");
} }
FileStream::~FileStream() FileStream::~FileStream()
@@ -255,8 +280,7 @@ void FileStream::insert(const ByteVector &data, unsigned long start, unsigned lo
ByteVector buffer = data; ByteVector buffer = data;
ByteVector aboutToOverwrite(static_cast<unsigned int>(bufferLength)); ByteVector aboutToOverwrite(static_cast<unsigned int>(bufferLength));
while(true) while(true) {
{
// Seek to the current read position and read the data that we're about // Seek to the current read position and read the data that we're about
// to overwrite. Appropriately increment the readPosition. // to overwrite. Appropriately increment the readPosition.
@@ -304,8 +328,7 @@ void FileStream::removeBlock(unsigned long start, unsigned long length)
ByteVector buffer(static_cast<unsigned int>(bufferLength)); ByteVector buffer(static_cast<unsigned int>(bufferLength));
for(unsigned int bytesRead = -1; bytesRead != 0;) for(unsigned int bytesRead = -1; bytesRead != 0;) {
{
seek(readPosition); seek(readPosition);
bytesRead = static_cast<unsigned int>(readFile(d->file, buffer)); bytesRead = static_cast<unsigned int>(readFile(d->file, buffer));
readPosition += bytesRead; readPosition += bytesRead;
@@ -401,7 +424,8 @@ long FileStream::tell() const
const LARGE_INTEGER zero = {}; const LARGE_INTEGER zero = {};
LARGE_INTEGER position; LARGE_INTEGER position;
if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) && position.QuadPart <= LONG_MAX) { if(SetFilePointerEx(d->file, zero, &position, FILE_CURRENT) &&
position.QuadPart <= LONG_MAX) {
return static_cast<long>(position.QuadPart); return static_cast<long>(position.QuadPart);
} }
else { else {
@@ -470,9 +494,8 @@ void FileStream::truncate(long length)
#else #else
const int error = ftruncate(fileno(d->file), length); const int error = ftruncate(fileno(d->file), length);
if(error != 0) { if(error != 0)
debug("FileStream::truncate() -- Coundn't truncate the file."); debug("FileStream::truncate() -- Coundn't truncate the file.");
}
#endif #endif
} }

View File

@@ -54,6 +54,11 @@ namespace TagLib {
*/ */
FileStream(FileName file, bool openReadOnly = false); FileStream(FileName file, bool openReadOnly = false);
/*!
* Construct a File object and opens the \a file using file descriptor.
*/
FileStream(int fileDescriptor, bool openReadOnly = false);
/*! /*!
* Destroys this FileStream instance. * Destroys this FileStream instance.
*/ */

View File

@@ -1,7 +1,5 @@
# Strawberry Music Player # Strawberry Music Player
# Copyright 2013, Jonas Kvinge <jonas@strawbs.net> # Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
# This file was part of Clementine.
# Copyright 2010, David Sansome <me@davidsansome.com>
# #
# Strawberry is free software: you can redistribute it and/or modify # Strawberry is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -37,24 +35,27 @@ include(cmake/Rpm.cmake)
include(cmake/Deb.cmake) include(cmake/Deb.cmake)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(LINUX 1) set(LINUX ON)
endif() endif()
if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") if (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(FREEBSD 1) set(FREEBSD ON)
endif() endif()
if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD") if (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
set(OPENBSD 1) set(OPENBSD ON)
endif() endif()
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)
set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
#set(CMAKE_BUILD_TYPE Debug)
if(${CMAKE_BUILD_TYPE} MATCHES "Release") if(${CMAKE_BUILD_TYPE} MATCHES "Release")
add_definitions(-DNDEBUG) add_definitions(-DNDEBUG)
add_definitions(-DQT_NO_DEBUG_OUTPUT) add_definitions(-DQT_NO_DEBUG_OUTPUT)
#add_definitions(-DQT_NO_WARNING_OUTPUT) #add_definitions(-DQT_NO_WARNING_OUTPUT)
endif(${CMAKE_BUILD_TYPE} MATCHES "Release") endif()
if(${CMAKE_BUILD_TYPE} MATCHES "Debug")
set(DEBUG ON)
endif()
if (CMAKE_CXX_COMPILER MATCHES ".*clang") if (CMAKE_CXX_COMPILER MATCHES ".*clang")
set(CMAKE_COMPILER_IS_CLANGXX 1) set(CMAKE_COMPILER_IS_CLANGXX 1)
@@ -111,21 +112,20 @@ pkg_check_modules(PHONON phonon4qt5)
pkg_check_modules(SQLITE REQUIRED sqlite3>=3.7) pkg_check_modules(SQLITE REQUIRED sqlite3>=3.7)
pkg_check_modules(LIBPULSE libpulse) pkg_check_modules(LIBPULSE libpulse)
pkg_check_modules(CHROMAPRINT libchromaprint) pkg_check_modules(CHROMAPRINT libchromaprint)
#if(CHROMAPRINT_FOUND)
# set(HAVE_CHROMAPRINT ON)
#endif()
pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92) pkg_check_modules(LIBGPOD libgpod-1.0>=0.7.92)
pkg_check_modules(LIBMTP libmtp>=1.0) pkg_check_modules(LIBMTP libmtp>=1.0)
pkg_check_modules(IMOBILEDEVICE libimobiledevice-1.0) pkg_check_modules(IMOBILEDEVICE libimobiledevice-1.0)
pkg_check_modules(USBMUXD libusbmuxd) pkg_check_modules(USBMUXD libusbmuxd)
pkg_check_modules(PLIST libplist) pkg_check_modules(PLIST libplist)
pkg_check_modules(LIBDEEZER libdeezer)
pkg_check_modules(LIBDZMEDIA libdzmedia)
if(WIN32) if(WIN32)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
endif(WIN32) endif(WIN32)
# QT # QT
set(QT_MIN_VERSION 5.6.0) set(QT_MIN_VERSION 5.5.1)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Concurrent Widgets Network Sql OpenGL Xml) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS Core Concurrent Widgets Network Sql OpenGL Xml)
if(X11_FOUND) if(X11_FOUND)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED COMPONENTS X11Extras)
@@ -211,9 +211,9 @@ endif()
option(USE_SYSTEM_QTSINGLEAPPLICATION "Use system QtSingleApplication library" OFF) option(USE_SYSTEM_QTSINGLEAPPLICATION "Use system QtSingleApplication library" OFF)
if(USE_SYSTEM_QTSINGLEAPPLICATION) if(USE_SYSTEM_QTSINGLEAPPLICATION)
message(STATUS "Using system QtSingleApplication library") message(STATUS "Using system QtSingleApplication library")
find_path(QTSINGLEAPPLICATION_INCLUDE_DIRS qtsingleapplication.h PATH_SUFFIXES QtSolutions) find_path(QTSINGLEAPPLICATION_INCLUDE_DIRS qtsingleapplication.h PATH_SUFFIXES qt5/QtSolutions)
find_library(QTSINGLEAPPLICATION_LIBRARIES QtSolutions_SingleApplication-2.6) find_library(QTSINGLEAPPLICATION_LIBRARIES Qt5Solutions_SingleApplication-2.6)
find_library(QTSINGLECOREAPPLICATION_LIBRARIES QtSolutions_SingleCoreApplication-2.6) find_library(QTSINGLECOREAPPLICATION_LIBRARIES Qt5Solutions_SingleCoreApplication-2.6)
else(USE_SYSTEM_QTSINGLEAPPLICATION) else(USE_SYSTEM_QTSINGLEAPPLICATION)
message(STATUS "Using builtin QtSingleApplication library") message(STATUS "Using builtin QtSingleApplication library")
add_subdirectory(3rdparty/qtsingleapplication) add_subdirectory(3rdparty/qtsingleapplication)
@@ -225,11 +225,11 @@ endif(USE_SYSTEM_QTSINGLEAPPLICATION)
option(USE_SYSTEM_QXT "Use system Qxt library" OFF) option(USE_SYSTEM_QXT "Use system Qxt library" OFF)
if (USE_SYSTEM_QXT) if (USE_SYSTEM_QXT)
message(STATUS "Using system Qxt library") message(STATUS "Using system Qxt library")
find_path(QXTCORE_INCLUDE_DIRS qxtglobal.h PATH_SUFFIXES QxtCore) find_path(QXTCORE_INCLUDE_DIRS qxtglobal.h PATH_SUFFIXES qt5/QxtCore)
find_path(QXTGUI_INCLUDE_DIRS qxtglobalshortcut.h PATH_SUFFIXES QxtGui) find_path(QXTGUI_INCLUDE_DIRS qxtglobalshortcut.h PATH_SUFFIXES qt5/QxtWidgets)
set(QXT_INCLUDE_DIRS ${QXTCORE_INCLUDE_DIRS} ${QXTGUI_INCLUDE_DIRS}) set(QXT_INCLUDE_DIRS ${QXTCORE_INCLUDE_DIRS} ${QXTGUI_INCLUDE_DIRS})
# We only need its header. We don't need to link to QxtCore. # We only need its header. We don't need to link to QxtCore.
find_library(QXT_LIBRARIES QxtGui) find_library(QXT_LIBRARIES QxtWidgets-qt5)
else (USE_SYSTEM_QXT) else (USE_SYSTEM_QXT)
message(STATUS "Using builtin Qxt library") message(STATUS "Using builtin Qxt library")
add_definitions(-DQXT_STATIC -DBUILD_QXT_GUI -DBUILD_QXT_CORE) add_definitions(-DQXT_STATIC -DBUILD_QXT_GUI -DBUILD_QXT_CORE)
@@ -283,10 +283,22 @@ optional_component(VLC ON "Engine: VLC backend"
DEPENDS "libvlc" LIBVLC_FOUND DEPENDS "libvlc" LIBVLC_FOUND
) )
optional_component(PHONON OFF "Engine: Phonon backend" optional_component(PHONON OFF "Engine: Phonon backend (UNSTABLE)"
DEPENDS "phonon4qt5" PHONON_FOUND 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(LIBPULSE ON "Pulse audio integration" optional_component(LIBPULSE ON "Pulse audio integration"
DEPENDS "libpulse" LIBPULSE_FOUND DEPENDS "libpulse" LIBPULSE_FOUND
) )
@@ -336,6 +348,18 @@ optional_component(SPARKLE ON "Sparkle integration"
DEPENDS "Sparkle" SPARKLE DEPENDS "Sparkle" SPARKLE
) )
optional_component(STREAM_TIDAL ON "Streaming: Tidal support")
optional_component(STREAM_DEEZER ON "Streaming: Deezer support")
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(IMOBILEDEVICE_FOUND AND PLIST_FOUND) #if(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
#add_subdirectory(ext/gstafc) #add_subdirectory(ext/gstafc)
#endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND) #endif(IMOBILEDEVICE_FOUND AND PLIST_FOUND)
@@ -374,6 +398,8 @@ add_custom_target(uninstall
# Show a summary of what we have enabled # Show a summary of what we have enabled
summary_show() summary_show()
if(NOT HAVE_GSTREAMER AND NOT HAVE_XINE AND NOT HAVE_VLC AND NOT HAVE_PHONON) 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 enable either GStreamer, Xine, VLC or Phonon to compile!") message(FATAL_ERROR "You need to have either GStreamer, Xine, VLC, Phonon or Deezer 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() endif()

View File

@@ -2,10 +2,32 @@ Strawberry Music Player
======================= =======================
ChangeLog ChangeLog
Version 0.4.1:
* Fixed crash in analyzer
* Fixed trying to use systray even if the desktop had no systray
* Fixed Tidal login loop bug
* Added Deezer support
* New improved fancy tabwidget
* Fixed bug not loading engine settings
* Moved queue manager into tabbar for easier access
* Fixed crash when changing appearance colors
* Improved error handling between player and engine
* Added setting to allow continue to the next song in the playlist based on error
* Improved Xine engine code
* Moved some settings
* Updated builtin taglib
* Added AppStream data file
* Fixed compiling with Qt 5 versions of system QtSingleApplication and Qxt library
Version 0.3.3:
* Fixed Tidal login
Version 0.3.2: Version 0.3.2:
* Fixed search error not shown in Tidal search * Fixed search error not shown in Tidal search
* Added URL handler for Tidal, now retriving URL's when playing instead of when searching * Added URL handler for Tidal, now retrieving URL's when playing instead of when searching
* Fixed bug in pipeline not setting url * Fixed bug in pipeline not setting url
* Fixed bug setting wrong temporary metadata * Fixed bug setting wrong temporary metadata
* Removed device module from windows, since it's not implemented for windows * Removed device module from windows, since it's not implemented for windows
@@ -61,7 +83,7 @@ Version 0.1.4:
Version 0.1.3: Version 0.1.3:
* Audio file detection by content * Audio file detection by content
* Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension * Added builtin taglib to 3rdparty to support detecting audio by content instead of just file extension
* Removed unneeded bulitin qsqlite from 3rdparty * Removed unneeded qsqlite from 3rdparty
* Added sqldrivers\qsqlite.dll for windows build * Added sqldrivers\qsqlite.dll for windows build
* Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build * Replaced incorrect DLL libgstdirectsoundsink.dll (from gst 1.12.4) instead of libgstdirectsound.dll (from gst 1.14.0) for windows build
* Fixed git versioning * Fixed git versioning

View File

@@ -25,7 +25,7 @@ It's written in C++ and Qt 5. The name is inspired by the band Strawbs.
* Audio analyzer * Audio analyzer
* Equalizer * Equalizer
* Transfer music to iPod, iPhone, MTP or mass-storage USB player * Transfer music to iPod, iPhone, MTP or mass-storage USB player
* Integrated Tidal support * Integrated Tidal and Deezer support
It has so far been tested to work on Linux, OpenBSD, MacOs and Windows. It has so far been tested to work on Linux, OpenBSD, MacOs and Windows.
@@ -47,9 +47,13 @@ To build Strawberry from source you need the following installed on your system
* [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/) * [DBus (linux)](https://www.freedesktop.org/wiki/Software/dbus/)
* [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?) * [PulseAudio (linux optional)](https://www.freedesktop.org/wiki/Software/PulseAudio/?)
Either GStreamer, Xine or VLC engine is required, but only GStreamer is fully implemented so far. Either GStreamer, Xine, VLC, Deezer 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. You should also install the gstreamer plugins base and good, and optionally bad and ugly.
Deezer streams with full songs are encrypted and only urls for preview streams (MP3) are exposed by the API.
Full length songs requires the use of deezers own engine (Deezer SDK) or the dzmedia library (I dont have it).
Deezer SDK can be found here: https://build-repo.deezer.com/native_sdk/deezer-native-sdk-v1.2.10.zip
Optional: Optional:
* The Qt 5 LastFM library is required for fetching album covers from LastFM. * The Qt 5 LastFM library is required for fetching album covers from LastFM.
@@ -73,4 +77,7 @@ Optional:
(dont change to the source directory, if you created the build directory inside the source directory type: cmake .. instead). (dont change to the source directory, if you created the build directory inside the source directory type: cmake .. instead).
### :computer: Screenshot
![Browse](https://www.strawbs.org/pictures/screenshot-002-large.png)

View File

@@ -1,6 +1,7 @@
find_program(LSB_RELEASE_EXEC lsb_release) find_program(LSB_RELEASE_EXEC lsb_release)
find_program(RPMBUILD_EXEC rpmbuild)
if (LSB_RELEASE_EXEC) if (LSB_RELEASE_EXEC AND RPMBUILD_EXEC)
execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -is | tr '[:upper:]' '[:lower:]' | cut -d' ' -f1" execute_process(COMMAND /bin/sh "-c" "${LSB_RELEASE_EXEC} -is | tr '[:upper:]' '[:lower:]' | cut -d' ' -f1"
OUTPUT_VARIABLE DIST_NAME OUTPUT_VARIABLE DIST_NAME
OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_STRIP_TRAILING_WHITESPACE
@@ -42,8 +43,8 @@ if (LSB_RELEASE_EXEC)
add_custom_target(rpm add_custom_target(rpm
COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh COMMAND ${CMAKE_SOURCE_DIR}/dist/scripts/maketarball.sh
COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/ COMMAND ${CMAKE_COMMAND} -E copy strawberry-${STRAWBERRY_VERSION_PACKAGE}.tar.xz ${RPMBUILD_DIR}/SOURCES/
COMMAND rpmbuild -bs ${CMAKE_SOURCE_DIR}/dist/opensuse/strawberry.spec COMMAND ${RPMBUILD_EXEC} -bs ${CMAKE_SOURCE_DIR}/dist/opensuse/strawberry.spec
COMMAND rpmbuild -bb ${CMAKE_SOURCE_DIR}/dist/opensuse/strawberry.spec COMMAND ${RPMBUILD_EXEC} -bb ${CMAKE_SOURCE_DIR}/dist/opensuse/strawberry.spec
) )
elseif (${DIST_NAME} STREQUAL "fedora") elseif (${DIST_NAME} STREQUAL "fedora")
if (DIST_VERSION) if (DIST_VERSION)

View File

@@ -1,6 +1,6 @@
set(STRAWBERRY_VERSION_MAJOR 0) set(STRAWBERRY_VERSION_MAJOR 0)
set(STRAWBERRY_VERSION_MINOR 3) set(STRAWBERRY_VERSION_MINOR 4)
set(STRAWBERRY_VERSION_PATCH 2) set(STRAWBERRY_VERSION_PATCH 1)
#set(STRAWBERRY_VERSION_PRERELEASE rc1) #set(STRAWBERRY_VERSION_PRERELEASE rc1)
set(INCLUDE_GIT_REVISION OFF) set(INCLUDE_GIT_REVISION OFF)

View File

@@ -7,6 +7,7 @@
<file>schema/device-schema.sql</file> <file>schema/device-schema.sql</file>
<file>style/strawberry.css</file> <file>style/strawberry.css</file>
<file>misc/playing_tooltip.txt</file> <file>misc/playing_tooltip.txt</file>
<file>misc/oauthsuccess.html</file>
<file>pictures/strawberry.png</file> <file>pictures/strawberry.png</file>
<file>pictures/strawbs-transparent.png</file> <file>pictures/strawbs-transparent.png</file>
<file>pictures/noalbumart.png</file> <file>pictures/noalbumart.png</file>
@@ -27,442 +28,7 @@
<file>pictures/osd_background.png</file> <file>pictures/osd_background.png</file>
<file>pictures/osd_shadow_corner.png</file> <file>pictures/osd_shadow_corner.png</file>
<file>pictures/osd_shadow_edge.png</file> <file>pictures/osd_shadow_edge.png</file>
<file>icons/128x128/albums.png</file> <file>pictures/deezer.png</file>
<file>icons/128x128/alsa.png</file>
<file>icons/128x128/application-exit.png</file>
<file>icons/128x128/applications-internet.png</file>
<file>icons/128x128/bluetooth.png</file>
<file>icons/128x128/cdcase.png</file>
<file>icons/128x128/cd.png</file>
<file>icons/128x128/configure.png</file>
<file>icons/128x128/device-ipod-nano.png</file>
<file>icons/128x128/device-ipod.png</file>
<file>icons/128x128/device-phone.png</file>
<file>icons/128x128/device.png</file>
<file>icons/128x128/device-usb-drive.png</file>
<file>icons/128x128/device-usb-flash.png</file>
<file>icons/128x128/dialog-error.png</file>
<file>icons/128x128/dialog-information.png</file>
<file>icons/128x128/dialog-ok-apply.png</file>
<file>icons/128x128/dialog-password.png</file>
<file>icons/128x128/dialog-warning.png</file>
<file>icons/128x128/document-download.png</file>
<file>icons/128x128/document-new.png</file>
<file>icons/128x128/document-open-folder.png</file>
<file>icons/128x128/document-open.png</file>
<file>icons/128x128/document-save.png</file>
<file>icons/128x128/document-search.png</file>
<file>icons/128x128/download.png</file>
<file>icons/128x128/edit-clear-list.png</file>
<file>icons/128x128/edit-clear-locationbar-ltr.png</file>
<file>icons/128x128/edit-copy.png</file>
<file>icons/128x128/edit-delete.png</file>
<file>icons/128x128/edit-find.png</file>
<file>icons/128x128/edit-redo.png</file>
<file>icons/128x128/edit-rename.png</file>
<file>icons/128x128/edit-undo.png</file>
<file>icons/128x128/electrocompaniet.png</file>
<file>icons/128x128/equalizer.png</file>
<file>icons/128x128/folder-new.png</file>
<file>icons/128x128/folder.png</file>
<file>icons/128x128/folder-sound.png</file>
<file>icons/128x128/footsteps.png</file>
<file>icons/128x128/go-down.png</file>
<file>icons/128x128/go-home.png</file>
<file>icons/128x128/go-jump.png</file>
<file>icons/128x128/go-next.png</file>
<file>icons/128x128/go-previous.png</file>
<file>icons/128x128/go-up.png</file>
<file>icons/128x128/gstreamer.png</file>
<file>icons/128x128/headset.png</file>
<file>icons/128x128/help-hint.png</file>
<file>icons/128x128/intel.png</file>
<file>icons/128x128/jack.png</file>
<file>icons/128x128/keyboard.png</file>
<file>icons/128x128/list-add.png</file>
<file>icons/128x128/list-remove.png</file>
<file>icons/128x128/mcintosh-player.png</file>
<file>icons/128x128/mcintosh-text.png</file>
<file>icons/128x128/media-eject.png</file>
<file>icons/128x128/media-forward.png</file>
<file>icons/128x128/media-pause.png</file>
<file>icons/128x128/media-play.png</file>
<file>icons/128x128/media-rewind.png</file>
<file>icons/128x128/media-stop.png</file>
<file>icons/128x128/nvidia.png</file>
<file>icons/128x128/play2.png</file>
<file>icons/128x128/realtek.png</file>
<file>icons/128x128/search.png</file>
<file>icons/128x128/soundcard.png</file>
<file>icons/128x128/speaker.png</file>
<file>icons/128x128/star-grey.png</file>
<file>icons/128x128/star.png</file>
<file>icons/128x128/strawberry.png</file>
<file>icons/128x128/strawberry.svg</file>
<file>icons/128x128/tools-wizard.png</file>
<file>icons/128x128/view-choose.png</file>
<file>icons/128x128/view-fullscreen.png</file>
<file>icons/128x128/view-media-lyrics.png</file>
<file>icons/128x128/view-media-playlist.png</file>
<file>icons/128x128/view-media-visualization.png</file>
<file>icons/128x128/view-refresh.png</file>
<file>icons/128x128/vinyl.png</file>
<file>icons/128x128/vlc.png</file>
<file>icons/128x128/xine.png</file>
<file>icons/128x128/zoom-in.png</file>
<file>icons/128x128/zoom-out.png</file>
<file>icons/128x128/tidal.png</file>
<file>icons/64x64/albums.png</file>
<file>icons/64x64/alsa.png</file>
<file>icons/64x64/application-exit.png</file>
<file>icons/64x64/applications-internet.png</file>
<file>icons/64x64/bluetooth.png</file>
<file>icons/64x64/cdcase.png</file>
<file>icons/64x64/cd.png</file>
<file>icons/64x64/configure.png</file>
<file>icons/64x64/device-ipod-nano.png</file>
<file>icons/64x64/device-ipod.png</file>
<file>icons/64x64/device-phone.png</file>
<file>icons/64x64/device.png</file>
<file>icons/64x64/device-usb-drive.png</file>
<file>icons/64x64/device-usb-flash.png</file>
<file>icons/64x64/dialog-error.png</file>
<file>icons/64x64/dialog-information.png</file>
<file>icons/64x64/dialog-ok-apply.png</file>
<file>icons/64x64/dialog-password.png</file>
<file>icons/64x64/dialog-warning.png</file>
<file>icons/64x64/document-download.png</file>
<file>icons/64x64/document-new.png</file>
<file>icons/64x64/document-open-folder.png</file>
<file>icons/64x64/document-open.png</file>
<file>icons/64x64/document-save.png</file>
<file>icons/64x64/document-search.png</file>
<file>icons/64x64/download.png</file>
<file>icons/64x64/edit-clear-list.png</file>
<file>icons/64x64/edit-clear-locationbar-ltr.png</file>
<file>icons/64x64/edit-copy.png</file>
<file>icons/64x64/edit-delete.png</file>
<file>icons/64x64/edit-find.png</file>
<file>icons/64x64/edit-redo.png</file>
<file>icons/64x64/edit-rename.png</file>
<file>icons/64x64/edit-undo.png</file>
<file>icons/64x64/electrocompaniet.png</file>
<file>icons/64x64/equalizer.png</file>
<file>icons/64x64/folder-new.png</file>
<file>icons/64x64/folder.png</file>
<file>icons/64x64/folder-sound.png</file>
<file>icons/64x64/footsteps.png</file>
<file>icons/64x64/go-down.png</file>
<file>icons/64x64/go-home.png</file>
<file>icons/64x64/go-jump.png</file>
<file>icons/64x64/go-next.png</file>
<file>icons/64x64/go-previous.png</file>
<file>icons/64x64/go-up.png</file>
<file>icons/64x64/gstreamer.png</file>
<file>icons/64x64/headset.png</file>
<file>icons/64x64/help-hint.png</file>
<file>icons/64x64/intel.png</file>
<file>icons/64x64/jack.png</file>
<file>icons/64x64/keyboard.png</file>
<file>icons/64x64/list-add.png</file>
<file>icons/64x64/list-remove.png</file>
<file>icons/64x64/mcintosh-player.png</file>
<file>icons/64x64/mcintosh-text.png</file>
<file>icons/64x64/media-eject.png</file>
<file>icons/64x64/media-forward.png</file>
<file>icons/64x64/media-pause.png</file>
<file>icons/64x64/media-play.png</file>
<file>icons/64x64/media-rewind.png</file>
<file>icons/64x64/media-stop.png</file>
<file>icons/64x64/nvidia.png</file>
<file>icons/64x64/play2.png</file>
<file>icons/64x64/pulseaudio.png</file>
<file>icons/64x64/realtek.png</file>
<file>icons/64x64/search.png</file>
<file>icons/64x64/soundcard.png</file>
<file>icons/64x64/speaker.png</file>
<file>icons/64x64/star-grey.png</file>
<file>icons/64x64/star.png</file>
<file>icons/64x64/strawberry.png</file>
<file>icons/64x64/tools-wizard.png</file>
<file>icons/64x64/view-choose.png</file>
<file>icons/64x64/view-fullscreen.png</file>
<file>icons/64x64/view-media-lyrics.png</file>
<file>icons/64x64/view-media-playlist.png</file>
<file>icons/64x64/view-media-visualization.png</file>
<file>icons/64x64/view-refresh.png</file>
<file>icons/64x64/vinyl.png</file>
<file>icons/64x64/vlc.png</file>
<file>icons/64x64/xine.png</file>
<file>icons/64x64/zoom-in.png</file>
<file>icons/64x64/zoom-out.png</file>
<file>icons/64x64/tidal.png</file>
<file>icons/48x48/albums.png</file>
<file>icons/48x48/alsa.png</file>
<file>icons/48x48/application-exit.png</file>
<file>icons/48x48/applications-internet.png</file>
<file>icons/48x48/bluetooth.png</file>
<file>icons/48x48/cdcase.png</file>
<file>icons/48x48/cd.png</file>
<file>icons/48x48/configure.png</file>
<file>icons/48x48/device-ipod-nano.png</file>
<file>icons/48x48/device-ipod.png</file>
<file>icons/48x48/device-phone.png</file>
<file>icons/48x48/device.png</file>
<file>icons/48x48/device-usb-drive.png</file>
<file>icons/48x48/device-usb-flash.png</file>
<file>icons/48x48/dialog-error.png</file>
<file>icons/48x48/dialog-information.png</file>
<file>icons/48x48/dialog-ok-apply.png</file>
<file>icons/48x48/dialog-password.png</file>
<file>icons/48x48/dialog-warning.png</file>
<file>icons/48x48/document-download.png</file>
<file>icons/48x48/document-new.png</file>
<file>icons/48x48/document-open-folder.png</file>
<file>icons/48x48/document-open.png</file>
<file>icons/48x48/document-save.png</file>
<file>icons/48x48/document-search.png</file>
<file>icons/48x48/download.png</file>
<file>icons/48x48/edit-clear-list.png</file>
<file>icons/48x48/edit-clear-locationbar-ltr.png</file>
<file>icons/48x48/edit-copy.png</file>
<file>icons/48x48/edit-delete.png</file>
<file>icons/48x48/edit-find.png</file>
<file>icons/48x48/edit-redo.png</file>
<file>icons/48x48/edit-rename.png</file>
<file>icons/48x48/edit-undo.png</file>
<file>icons/48x48/electrocompaniet.png</file>
<file>icons/48x48/equalizer.png</file>
<file>icons/48x48/folder-new.png</file>
<file>icons/48x48/folder.png</file>
<file>icons/48x48/folder-sound.png</file>
<file>icons/48x48/footsteps.png</file>
<file>icons/48x48/go-down.png</file>
<file>icons/48x48/go-home.png</file>
<file>icons/48x48/go-jump.png</file>
<file>icons/48x48/go-next.png</file>
<file>icons/48x48/go-previous.png</file>
<file>icons/48x48/go-up.png</file>
<file>icons/48x48/gstreamer.png</file>
<file>icons/48x48/headset.png</file>
<file>icons/48x48/help-hint.png</file>
<file>icons/48x48/intel.png</file>
<file>icons/48x48/jack.png</file>
<file>icons/48x48/keyboard.png</file>
<file>icons/48x48/list-add.png</file>
<file>icons/48x48/list-remove.png</file>
<file>icons/48x48/mcintosh-player.png</file>
<file>icons/48x48/mcintosh.png</file>
<file>icons/48x48/mcintosh-text.png</file>
<file>icons/48x48/media-eject.png</file>
<file>icons/48x48/media-forward.png</file>
<file>icons/48x48/media-pause.png</file>
<file>icons/48x48/media-playlist-repeat.png</file>
<file>icons/48x48/media-playlist-shuffle.png</file>
<file>icons/48x48/media-play.png</file>
<file>icons/48x48/media-rewind.png</file>
<file>icons/48x48/media-stop.png</file>
<file>icons/48x48/nvidia.png</file>
<file>icons/48x48/play2.png</file>
<file>icons/48x48/pulseaudio.png</file>
<file>icons/48x48/realtek.png</file>
<file>icons/48x48/search.png</file>
<file>icons/48x48/soundcard.png</file>
<file>icons/48x48/speaker.png</file>
<file>icons/48x48/star-grey.png</file>
<file>icons/48x48/star.png</file>
<file>icons/48x48/strawberry.png</file>
<file>icons/48x48/tools-wizard.png</file>
<file>icons/48x48/view-choose.png</file>
<file>icons/48x48/view-fullscreen.png</file>
<file>icons/48x48/view-media-lyrics.png</file>
<file>icons/48x48/view-media-playlist.png</file>
<file>icons/48x48/view-media-visualization.png</file>
<file>icons/48x48/view-refresh.png</file>
<file>icons/48x48/vinyl.png</file>
<file>icons/48x48/vlc.png</file>
<file>icons/48x48/xine.png</file>
<file>icons/48x48/zoom-in.png</file>
<file>icons/48x48/zoom-out.png</file>
<file>icons/48x48/tidal.png</file>
<file>icons/32x32/albums.png</file>
<file>icons/32x32/alsa.png</file>
<file>icons/32x32/application-exit.png</file>
<file>icons/32x32/applications-internet.png</file>
<file>icons/32x32/bluetooth.png</file>
<file>icons/32x32/cdcase.png</file>
<file>icons/32x32/cd.png</file>
<file>icons/32x32/configure.png</file>
<file>icons/32x32/device-ipod-nano.png</file>
<file>icons/32x32/device-ipod.png</file>
<file>icons/32x32/device-phone.png</file>
<file>icons/32x32/device.png</file>
<file>icons/32x32/device-usb-drive.png</file>
<file>icons/32x32/device-usb-flash.png</file>
<file>icons/32x32/dialog-error.png</file>
<file>icons/32x32/dialog-information.png</file>
<file>icons/32x32/dialog-ok-apply.png</file>
<file>icons/32x32/dialog-password.png</file>
<file>icons/32x32/dialog-warning.png</file>
<file>icons/32x32/document-download.png</file>
<file>icons/32x32/document-new.png</file>
<file>icons/32x32/document-open-folder.png</file>
<file>icons/32x32/document-open.png</file>
<file>icons/32x32/document-save.png</file>
<file>icons/32x32/document-search.png</file>
<file>icons/32x32/download.png</file>
<file>icons/32x32/edit-clear-list.png</file>
<file>icons/32x32/edit-clear-locationbar-ltr.png</file>
<file>icons/32x32/edit-copy.png</file>
<file>icons/32x32/edit-delete.png</file>
<file>icons/32x32/edit-find.png</file>
<file>icons/32x32/edit-redo.png</file>
<file>icons/32x32/edit-rename.png</file>
<file>icons/32x32/edit-undo.png</file>
<file>icons/32x32/electrocompaniet.png</file>
<file>icons/32x32/equalizer.png</file>
<file>icons/32x32/folder-new.png</file>
<file>icons/32x32/folder.png</file>
<file>icons/32x32/folder-sound.png</file>
<file>icons/32x32/footsteps.png</file>
<file>icons/32x32/go-down.png</file>
<file>icons/32x32/go-home.png</file>
<file>icons/32x32/go-jump.png</file>
<file>icons/32x32/go-next.png</file>
<file>icons/32x32/go-previous.png</file>
<file>icons/32x32/go-up.png</file>
<file>icons/32x32/gstreamer.png</file>
<file>icons/32x32/headset.png</file>
<file>icons/32x32/help-hint.png</file>
<file>icons/32x32/intel.png</file>
<file>icons/32x32/jack.png</file>
<file>icons/32x32/keyboard.png</file>
<file>icons/32x32/list-add.png</file>
<file>icons/32x32/list-remove.png</file>
<file>icons/32x32/mcintosh-player.png</file>
<file>icons/32x32/mcintosh.png</file>
<file>icons/32x32/mcintosh-text.png</file>
<file>icons/32x32/media-eject.png</file>
<file>icons/32x32/media-forward.png</file>
<file>icons/32x32/media-pause.png</file>
<file>icons/32x32/media-playlist-repeat.png</file>
<file>icons/32x32/media-playlist-shuffle.png</file>
<file>icons/32x32/media-play.png</file>
<file>icons/32x32/media-rewind.png</file>
<file>icons/32x32/media-stop.png</file>
<file>icons/32x32/nvidia.png</file>
<file>icons/32x32/play2.png</file>
<file>icons/32x32/pulseaudio.png</file>
<file>icons/32x32/realtek.png</file>
<file>icons/32x32/search.png</file>
<file>icons/32x32/soundcard.png</file>
<file>icons/32x32/speaker.png</file>
<file>icons/32x32/star-grey.png</file>
<file>icons/32x32/star.png</file>
<file>icons/32x32/strawberry.png</file>
<file>icons/32x32/strawberry.svg</file>
<file>icons/32x32/tools-wizard.png</file>
<file>icons/32x32/view-choose.png</file>
<file>icons/32x32/view-fullscreen.png</file>
<file>icons/32x32/view-media-lyrics.png</file>
<file>icons/32x32/view-media-playlist.png</file>
<file>icons/32x32/view-media-visualization.png</file>
<file>icons/32x32/view-refresh.png</file>
<file>icons/32x32/vinyl.png</file>
<file>icons/32x32/vlc.png</file>
<file>icons/32x32/xine.png</file>
<file>icons/32x32/zoom-in.png</file>
<file>icons/32x32/zoom-out.png</file>
<file>icons/32x32/tidal.png</file>
<file>icons/22x22/albums.png</file>
<file>icons/22x22/alsa.png</file>
<file>icons/22x22/application-exit.png</file>
<file>icons/22x22/applications-internet.png</file>
<file>icons/22x22/bluetooth.png</file>
<file>icons/22x22/cdcase.png</file>
<file>icons/22x22/cd.png</file>
<file>icons/22x22/configure.png</file>
<file>icons/22x22/device-ipod-nano.png</file>
<file>icons/22x22/device-ipod.png</file>
<file>icons/22x22/device-phone.png</file>
<file>icons/22x22/device.png</file>
<file>icons/22x22/device-usb-drive.png</file>
<file>icons/22x22/device-usb-flash.png</file>
<file>icons/22x22/dialog-error.png</file>
<file>icons/22x22/dialog-information.png</file>
<file>icons/22x22/dialog-ok-apply.png</file>
<file>icons/22x22/dialog-password.png</file>
<file>icons/22x22/dialog-warning.png</file>
<file>icons/22x22/document-download.png</file>
<file>icons/22x22/document-new.png</file>
<file>icons/22x22/document-open-folder.png</file>
<file>icons/22x22/document-open.png</file>
<file>icons/22x22/document-save.png</file>
<file>icons/22x22/document-search.png</file>
<file>icons/22x22/download.png</file>
<file>icons/22x22/edit-clear-list.png</file>
<file>icons/22x22/edit-clear-locationbar-ltr.png</file>
<file>icons/22x22/edit-copy.png</file>
<file>icons/22x22/edit-delete.png</file>
<file>icons/22x22/edit-find.png</file>
<file>icons/22x22/edit-redo.png</file>
<file>icons/22x22/edit-rename.png</file>
<file>icons/22x22/edit-undo.png</file>
<file>icons/22x22/electrocompaniet.png</file>
<file>icons/22x22/equalizer.png</file>
<file>icons/22x22/folder-new.png</file>
<file>icons/22x22/folder.png</file>
<file>icons/22x22/folder-sound.png</file>
<file>icons/22x22/footsteps.png</file>
<file>icons/22x22/go-down.png</file>
<file>icons/22x22/go-home.png</file>
<file>icons/22x22/go-jump.png</file>
<file>icons/22x22/go-next.png</file>
<file>icons/22x22/go-previous.png</file>
<file>icons/22x22/go-up.png</file>
<file>icons/22x22/gstreamer.png</file>
<file>icons/22x22/headset.png</file>
<file>icons/22x22/help-hint.png</file>
<file>icons/22x22/intel.png</file>
<file>icons/22x22/jack.png</file>
<file>icons/22x22/keyboard.png</file>
<file>icons/22x22/list-add.png</file>
<file>icons/22x22/list-remove.png</file>
<file>icons/22x22/mcintosh-player.png</file>
<file>icons/22x22/mcintosh.png</file>
<file>icons/22x22/mcintosh-text.png</file>
<file>icons/22x22/media-eject.png</file>
<file>icons/22x22/media-forward.png</file>
<file>icons/22x22/media-pause.png</file>
<file>icons/22x22/media-playlist-repeat.png</file>
<file>icons/22x22/media-playlist-shuffle.png</file>
<file>icons/22x22/media-play.png</file>
<file>icons/22x22/media-rewind.png</file>
<file>icons/22x22/media-stop.png</file>
<file>icons/22x22/nvidia.png</file>
<file>icons/22x22/play2.png</file>
<file>icons/22x22/pulseaudio.png</file>
<file>icons/22x22/realtek.png</file>
<file>icons/22x22/search.png</file>
<file>icons/22x22/soundcard.png</file>
<file>icons/22x22/speaker.png</file>
<file>icons/22x22/star-grey.png</file>
<file>icons/22x22/star.png</file>
<file>icons/22x22/strawberry.png</file>
<file>icons/22x22/strawberry.svg</file>
<file>icons/22x22/tools-wizard.png</file>
<file>icons/22x22/view-choose.png</file>
<file>icons/22x22/view-fullscreen.png</file>
<file>icons/22x22/view-media-lyrics.png</file>
<file>icons/22x22/view-media-playlist.png</file>
<file>icons/22x22/view-media-visualization.png</file>
<file>icons/22x22/view-refresh.png</file>
<file>icons/22x22/vinyl.png</file>
<file>icons/22x22/vlc.png</file>
<file>icons/22x22/xine.png</file>
<file>icons/22x22/zoom-in.png</file>
<file>icons/22x22/zoom-out.png</file>
<file>icons/22x22/tidal.png</file>
<file>fonts/HumongousofEternitySt.ttf</file> <file>fonts/HumongousofEternitySt.ttf</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -1,4 +1,444 @@
<RCC> <RCC>
<qresource prefix="/"> <qresource prefix="/">
</qresource> <file>icons/128x128/albums.png</file>
<file>icons/128x128/alsa.png</file>
<file>icons/128x128/application-exit.png</file>
<file>icons/128x128/applications-internet.png</file>
<file>icons/128x128/bluetooth.png</file>
<file>icons/128x128/cdcase.png</file>
<file>icons/128x128/cd.png</file>
<file>icons/128x128/configure.png</file>
<file>icons/128x128/device-ipod-nano.png</file>
<file>icons/128x128/device-ipod.png</file>
<file>icons/128x128/device-phone.png</file>
<file>icons/128x128/device.png</file>
<file>icons/128x128/device-usb-drive.png</file>
<file>icons/128x128/device-usb-flash.png</file>
<file>icons/128x128/dialog-error.png</file>
<file>icons/128x128/dialog-information.png</file>
<file>icons/128x128/dialog-ok-apply.png</file>
<file>icons/128x128/dialog-password.png</file>
<file>icons/128x128/dialog-warning.png</file>
<file>icons/128x128/document-download.png</file>
<file>icons/128x128/document-new.png</file>
<file>icons/128x128/document-open-folder.png</file>
<file>icons/128x128/document-open.png</file>
<file>icons/128x128/document-save.png</file>
<file>icons/128x128/document-search.png</file>
<file>icons/128x128/download.png</file>
<file>icons/128x128/edit-clear-list.png</file>
<file>icons/128x128/edit-clear-locationbar-ltr.png</file>
<file>icons/128x128/edit-copy.png</file>
<file>icons/128x128/edit-delete.png</file>
<file>icons/128x128/edit-find.png</file>
<file>icons/128x128/edit-redo.png</file>
<file>icons/128x128/edit-rename.png</file>
<file>icons/128x128/edit-undo.png</file>
<file>icons/128x128/electrocompaniet.png</file>
<file>icons/128x128/equalizer.png</file>
<file>icons/128x128/folder-new.png</file>
<file>icons/128x128/folder.png</file>
<file>icons/128x128/folder-sound.png</file>
<file>icons/128x128/footsteps.png</file>
<file>icons/128x128/go-down.png</file>
<file>icons/128x128/go-home.png</file>
<file>icons/128x128/go-jump.png</file>
<file>icons/128x128/go-next.png</file>
<file>icons/128x128/go-previous.png</file>
<file>icons/128x128/go-up.png</file>
<file>icons/128x128/gstreamer.png</file>
<file>icons/128x128/headset.png</file>
<file>icons/128x128/help-hint.png</file>
<file>icons/128x128/intel.png</file>
<file>icons/128x128/jack.png</file>
<file>icons/128x128/keyboard.png</file>
<file>icons/128x128/list-add.png</file>
<file>icons/128x128/list-remove.png</file>
<file>icons/128x128/mcintosh-player.png</file>
<file>icons/128x128/mcintosh-text.png</file>
<file>icons/128x128/media-eject.png</file>
<file>icons/128x128/media-forward.png</file>
<file>icons/128x128/media-pause.png</file>
<file>icons/128x128/media-play.png</file>
<file>icons/128x128/media-rewind.png</file>
<file>icons/128x128/media-stop.png</file>
<file>icons/128x128/nvidia.png</file>
<file>icons/128x128/play2.png</file>
<file>icons/128x128/realtek.png</file>
<file>icons/128x128/search.png</file>
<file>icons/128x128/soundcard.png</file>
<file>icons/128x128/speaker.png</file>
<file>icons/128x128/star-grey.png</file>
<file>icons/128x128/star.png</file>
<file>icons/128x128/strawberry.png</file>
<file>icons/128x128/strawberry.svg</file>
<file>icons/128x128/tools-wizard.png</file>
<file>icons/128x128/view-choose.png</file>
<file>icons/128x128/view-fullscreen.png</file>
<file>icons/128x128/view-media-lyrics.png</file>
<file>icons/128x128/view-media-playlist.png</file>
<file>icons/128x128/view-media-visualization.png</file>
<file>icons/128x128/view-refresh.png</file>
<file>icons/128x128/vinyl.png</file>
<file>icons/128x128/vlc.png</file>
<file>icons/128x128/xine.png</file>
<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/64x64/albums.png</file>
<file>icons/64x64/alsa.png</file>
<file>icons/64x64/application-exit.png</file>
<file>icons/64x64/applications-internet.png</file>
<file>icons/64x64/bluetooth.png</file>
<file>icons/64x64/cdcase.png</file>
<file>icons/64x64/cd.png</file>
<file>icons/64x64/configure.png</file>
<file>icons/64x64/device-ipod-nano.png</file>
<file>icons/64x64/device-ipod.png</file>
<file>icons/64x64/device-phone.png</file>
<file>icons/64x64/device.png</file>
<file>icons/64x64/device-usb-drive.png</file>
<file>icons/64x64/device-usb-flash.png</file>
<file>icons/64x64/dialog-error.png</file>
<file>icons/64x64/dialog-information.png</file>
<file>icons/64x64/dialog-ok-apply.png</file>
<file>icons/64x64/dialog-password.png</file>
<file>icons/64x64/dialog-warning.png</file>
<file>icons/64x64/document-download.png</file>
<file>icons/64x64/document-new.png</file>
<file>icons/64x64/document-open-folder.png</file>
<file>icons/64x64/document-open.png</file>
<file>icons/64x64/document-save.png</file>
<file>icons/64x64/document-search.png</file>
<file>icons/64x64/download.png</file>
<file>icons/64x64/edit-clear-list.png</file>
<file>icons/64x64/edit-clear-locationbar-ltr.png</file>
<file>icons/64x64/edit-copy.png</file>
<file>icons/64x64/edit-delete.png</file>
<file>icons/64x64/edit-find.png</file>
<file>icons/64x64/edit-redo.png</file>
<file>icons/64x64/edit-rename.png</file>
<file>icons/64x64/edit-undo.png</file>
<file>icons/64x64/electrocompaniet.png</file>
<file>icons/64x64/equalizer.png</file>
<file>icons/64x64/folder-new.png</file>
<file>icons/64x64/folder.png</file>
<file>icons/64x64/folder-sound.png</file>
<file>icons/64x64/footsteps.png</file>
<file>icons/64x64/go-down.png</file>
<file>icons/64x64/go-home.png</file>
<file>icons/64x64/go-jump.png</file>
<file>icons/64x64/go-next.png</file>
<file>icons/64x64/go-previous.png</file>
<file>icons/64x64/go-up.png</file>
<file>icons/64x64/gstreamer.png</file>
<file>icons/64x64/headset.png</file>
<file>icons/64x64/help-hint.png</file>
<file>icons/64x64/intel.png</file>
<file>icons/64x64/jack.png</file>
<file>icons/64x64/keyboard.png</file>
<file>icons/64x64/list-add.png</file>
<file>icons/64x64/list-remove.png</file>
<file>icons/64x64/mcintosh-player.png</file>
<file>icons/64x64/mcintosh-text.png</file>
<file>icons/64x64/media-eject.png</file>
<file>icons/64x64/media-forward.png</file>
<file>icons/64x64/media-pause.png</file>
<file>icons/64x64/media-play.png</file>
<file>icons/64x64/media-rewind.png</file>
<file>icons/64x64/media-stop.png</file>
<file>icons/64x64/nvidia.png</file>
<file>icons/64x64/play2.png</file>
<file>icons/64x64/pulseaudio.png</file>
<file>icons/64x64/realtek.png</file>
<file>icons/64x64/search.png</file>
<file>icons/64x64/soundcard.png</file>
<file>icons/64x64/speaker.png</file>
<file>icons/64x64/star-grey.png</file>
<file>icons/64x64/star.png</file>
<file>icons/64x64/strawberry.png</file>
<file>icons/64x64/tools-wizard.png</file>
<file>icons/64x64/view-choose.png</file>
<file>icons/64x64/view-fullscreen.png</file>
<file>icons/64x64/view-media-lyrics.png</file>
<file>icons/64x64/view-media-playlist.png</file>
<file>icons/64x64/view-media-visualization.png</file>
<file>icons/64x64/view-refresh.png</file>
<file>icons/64x64/vinyl.png</file>
<file>icons/64x64/vlc.png</file>
<file>icons/64x64/xine.png</file>
<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/48x48/albums.png</file>
<file>icons/48x48/alsa.png</file>
<file>icons/48x48/application-exit.png</file>
<file>icons/48x48/applications-internet.png</file>
<file>icons/48x48/bluetooth.png</file>
<file>icons/48x48/cdcase.png</file>
<file>icons/48x48/cd.png</file>
<file>icons/48x48/configure.png</file>
<file>icons/48x48/device-ipod-nano.png</file>
<file>icons/48x48/device-ipod.png</file>
<file>icons/48x48/device-phone.png</file>
<file>icons/48x48/device.png</file>
<file>icons/48x48/device-usb-drive.png</file>
<file>icons/48x48/device-usb-flash.png</file>
<file>icons/48x48/dialog-error.png</file>
<file>icons/48x48/dialog-information.png</file>
<file>icons/48x48/dialog-ok-apply.png</file>
<file>icons/48x48/dialog-password.png</file>
<file>icons/48x48/dialog-warning.png</file>
<file>icons/48x48/document-download.png</file>
<file>icons/48x48/document-new.png</file>
<file>icons/48x48/document-open-folder.png</file>
<file>icons/48x48/document-open.png</file>
<file>icons/48x48/document-save.png</file>
<file>icons/48x48/document-search.png</file>
<file>icons/48x48/download.png</file>
<file>icons/48x48/edit-clear-list.png</file>
<file>icons/48x48/edit-clear-locationbar-ltr.png</file>
<file>icons/48x48/edit-copy.png</file>
<file>icons/48x48/edit-delete.png</file>
<file>icons/48x48/edit-find.png</file>
<file>icons/48x48/edit-redo.png</file>
<file>icons/48x48/edit-rename.png</file>
<file>icons/48x48/edit-undo.png</file>
<file>icons/48x48/electrocompaniet.png</file>
<file>icons/48x48/equalizer.png</file>
<file>icons/48x48/folder-new.png</file>
<file>icons/48x48/folder.png</file>
<file>icons/48x48/folder-sound.png</file>
<file>icons/48x48/footsteps.png</file>
<file>icons/48x48/go-down.png</file>
<file>icons/48x48/go-home.png</file>
<file>icons/48x48/go-jump.png</file>
<file>icons/48x48/go-next.png</file>
<file>icons/48x48/go-previous.png</file>
<file>icons/48x48/go-up.png</file>
<file>icons/48x48/gstreamer.png</file>
<file>icons/48x48/headset.png</file>
<file>icons/48x48/help-hint.png</file>
<file>icons/48x48/intel.png</file>
<file>icons/48x48/jack.png</file>
<file>icons/48x48/keyboard.png</file>
<file>icons/48x48/list-add.png</file>
<file>icons/48x48/list-remove.png</file>
<file>icons/48x48/mcintosh-player.png</file>
<file>icons/48x48/mcintosh.png</file>
<file>icons/48x48/mcintosh-text.png</file>
<file>icons/48x48/media-eject.png</file>
<file>icons/48x48/media-forward.png</file>
<file>icons/48x48/media-pause.png</file>
<file>icons/48x48/media-playlist-repeat.png</file>
<file>icons/48x48/media-playlist-shuffle.png</file>
<file>icons/48x48/media-play.png</file>
<file>icons/48x48/media-rewind.png</file>
<file>icons/48x48/media-stop.png</file>
<file>icons/48x48/nvidia.png</file>
<file>icons/48x48/play2.png</file>
<file>icons/48x48/pulseaudio.png</file>
<file>icons/48x48/realtek.png</file>
<file>icons/48x48/search.png</file>
<file>icons/48x48/soundcard.png</file>
<file>icons/48x48/speaker.png</file>
<file>icons/48x48/star-grey.png</file>
<file>icons/48x48/star.png</file>
<file>icons/48x48/strawberry.png</file>
<file>icons/48x48/tools-wizard.png</file>
<file>icons/48x48/view-choose.png</file>
<file>icons/48x48/view-fullscreen.png</file>
<file>icons/48x48/view-media-lyrics.png</file>
<file>icons/48x48/view-media-playlist.png</file>
<file>icons/48x48/view-media-visualization.png</file>
<file>icons/48x48/view-refresh.png</file>
<file>icons/48x48/vinyl.png</file>
<file>icons/48x48/vlc.png</file>
<file>icons/48x48/xine.png</file>
<file>icons/48x48/zoom-in.png</file>
<file>icons/48x48/zoom-out.png</file>
<file>icons/48x48/tidal.png</file>
<file>icons/32x32/albums.png</file>
<file>icons/32x32/alsa.png</file>
<file>icons/32x32/application-exit.png</file>
<file>icons/32x32/applications-internet.png</file>
<file>icons/32x32/bluetooth.png</file>
<file>icons/32x32/cdcase.png</file>
<file>icons/32x32/cd.png</file>
<file>icons/32x32/configure.png</file>
<file>icons/32x32/device-ipod-nano.png</file>
<file>icons/32x32/device-ipod.png</file>
<file>icons/32x32/device-phone.png</file>
<file>icons/32x32/device.png</file>
<file>icons/32x32/device-usb-drive.png</file>
<file>icons/32x32/device-usb-flash.png</file>
<file>icons/32x32/dialog-error.png</file>
<file>icons/32x32/dialog-information.png</file>
<file>icons/32x32/dialog-ok-apply.png</file>
<file>icons/32x32/dialog-password.png</file>
<file>icons/32x32/dialog-warning.png</file>
<file>icons/32x32/document-download.png</file>
<file>icons/32x32/document-new.png</file>
<file>icons/32x32/document-open-folder.png</file>
<file>icons/32x32/document-open.png</file>
<file>icons/32x32/document-save.png</file>
<file>icons/32x32/document-search.png</file>
<file>icons/32x32/download.png</file>
<file>icons/32x32/edit-clear-list.png</file>
<file>icons/32x32/edit-clear-locationbar-ltr.png</file>
<file>icons/32x32/edit-copy.png</file>
<file>icons/32x32/edit-delete.png</file>
<file>icons/32x32/edit-find.png</file>
<file>icons/32x32/edit-redo.png</file>
<file>icons/32x32/edit-rename.png</file>
<file>icons/32x32/edit-undo.png</file>
<file>icons/32x32/electrocompaniet.png</file>
<file>icons/32x32/equalizer.png</file>
<file>icons/32x32/folder-new.png</file>
<file>icons/32x32/folder.png</file>
<file>icons/32x32/folder-sound.png</file>
<file>icons/32x32/footsteps.png</file>
<file>icons/32x32/go-down.png</file>
<file>icons/32x32/go-home.png</file>
<file>icons/32x32/go-jump.png</file>
<file>icons/32x32/go-next.png</file>
<file>icons/32x32/go-previous.png</file>
<file>icons/32x32/go-up.png</file>
<file>icons/32x32/gstreamer.png</file>
<file>icons/32x32/headset.png</file>
<file>icons/32x32/help-hint.png</file>
<file>icons/32x32/intel.png</file>
<file>icons/32x32/jack.png</file>
<file>icons/32x32/keyboard.png</file>
<file>icons/32x32/list-add.png</file>
<file>icons/32x32/list-remove.png</file>
<file>icons/32x32/mcintosh-player.png</file>
<file>icons/32x32/mcintosh.png</file>
<file>icons/32x32/mcintosh-text.png</file>
<file>icons/32x32/media-eject.png</file>
<file>icons/32x32/media-forward.png</file>
<file>icons/32x32/media-pause.png</file>
<file>icons/32x32/media-playlist-repeat.png</file>
<file>icons/32x32/media-playlist-shuffle.png</file>
<file>icons/32x32/media-play.png</file>
<file>icons/32x32/media-rewind.png</file>
<file>icons/32x32/media-stop.png</file>
<file>icons/32x32/nvidia.png</file>
<file>icons/32x32/play2.png</file>
<file>icons/32x32/pulseaudio.png</file>
<file>icons/32x32/realtek.png</file>
<file>icons/32x32/search.png</file>
<file>icons/32x32/soundcard.png</file>
<file>icons/32x32/speaker.png</file>
<file>icons/32x32/star-grey.png</file>
<file>icons/32x32/star.png</file>
<file>icons/32x32/strawberry.png</file>
<file>icons/32x32/strawberry.svg</file>
<file>icons/32x32/tools-wizard.png</file>
<file>icons/32x32/view-choose.png</file>
<file>icons/32x32/view-fullscreen.png</file>
<file>icons/32x32/view-media-lyrics.png</file>
<file>icons/32x32/view-media-playlist.png</file>
<file>icons/32x32/view-media-visualization.png</file>
<file>icons/32x32/view-refresh.png</file>
<file>icons/32x32/vinyl.png</file>
<file>icons/32x32/vlc.png</file>
<file>icons/32x32/xine.png</file>
<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/22x22/albums.png</file>
<file>icons/22x22/alsa.png</file>
<file>icons/22x22/application-exit.png</file>
<file>icons/22x22/applications-internet.png</file>
<file>icons/22x22/bluetooth.png</file>
<file>icons/22x22/cdcase.png</file>
<file>icons/22x22/cd.png</file>
<file>icons/22x22/configure.png</file>
<file>icons/22x22/device-ipod-nano.png</file>
<file>icons/22x22/device-ipod.png</file>
<file>icons/22x22/device-phone.png</file>
<file>icons/22x22/device.png</file>
<file>icons/22x22/device-usb-drive.png</file>
<file>icons/22x22/device-usb-flash.png</file>
<file>icons/22x22/dialog-error.png</file>
<file>icons/22x22/dialog-information.png</file>
<file>icons/22x22/dialog-ok-apply.png</file>
<file>icons/22x22/dialog-password.png</file>
<file>icons/22x22/dialog-warning.png</file>
<file>icons/22x22/document-download.png</file>
<file>icons/22x22/document-new.png</file>
<file>icons/22x22/document-open-folder.png</file>
<file>icons/22x22/document-open.png</file>
<file>icons/22x22/document-save.png</file>
<file>icons/22x22/document-search.png</file>
<file>icons/22x22/download.png</file>
<file>icons/22x22/edit-clear-list.png</file>
<file>icons/22x22/edit-clear-locationbar-ltr.png</file>
<file>icons/22x22/edit-copy.png</file>
<file>icons/22x22/edit-delete.png</file>
<file>icons/22x22/edit-find.png</file>
<file>icons/22x22/edit-redo.png</file>
<file>icons/22x22/edit-rename.png</file>
<file>icons/22x22/edit-undo.png</file>
<file>icons/22x22/electrocompaniet.png</file>
<file>icons/22x22/equalizer.png</file>
<file>icons/22x22/folder-new.png</file>
<file>icons/22x22/folder.png</file>
<file>icons/22x22/folder-sound.png</file>
<file>icons/22x22/footsteps.png</file>
<file>icons/22x22/go-down.png</file>
<file>icons/22x22/go-home.png</file>
<file>icons/22x22/go-jump.png</file>
<file>icons/22x22/go-next.png</file>
<file>icons/22x22/go-previous.png</file>
<file>icons/22x22/go-up.png</file>
<file>icons/22x22/gstreamer.png</file>
<file>icons/22x22/headset.png</file>
<file>icons/22x22/help-hint.png</file>
<file>icons/22x22/intel.png</file>
<file>icons/22x22/jack.png</file>
<file>icons/22x22/keyboard.png</file>
<file>icons/22x22/list-add.png</file>
<file>icons/22x22/list-remove.png</file>
<file>icons/22x22/mcintosh-player.png</file>
<file>icons/22x22/mcintosh.png</file>
<file>icons/22x22/mcintosh-text.png</file>
<file>icons/22x22/media-eject.png</file>
<file>icons/22x22/media-forward.png</file>
<file>icons/22x22/media-pause.png</file>
<file>icons/22x22/media-playlist-repeat.png</file>
<file>icons/22x22/media-playlist-shuffle.png</file>
<file>icons/22x22/media-play.png</file>
<file>icons/22x22/media-rewind.png</file>
<file>icons/22x22/media-stop.png</file>
<file>icons/22x22/nvidia.png</file>
<file>icons/22x22/play2.png</file>
<file>icons/22x22/pulseaudio.png</file>
<file>icons/22x22/realtek.png</file>
<file>icons/22x22/search.png</file>
<file>icons/22x22/soundcard.png</file>
<file>icons/22x22/speaker.png</file>
<file>icons/22x22/star-grey.png</file>
<file>icons/22x22/star.png</file>
<file>icons/22x22/strawberry.png</file>
<file>icons/22x22/strawberry.svg</file>
<file>icons/22x22/tools-wizard.png</file>
<file>icons/22x22/view-choose.png</file>
<file>icons/22x22/view-fullscreen.png</file>
<file>icons/22x22/view-media-lyrics.png</file>
<file>icons/22x22/view-media-playlist.png</file>
<file>icons/22x22/view-media-visualization.png</file>
<file>icons/22x22/view-refresh.png</file>
<file>icons/22x22/vinyl.png</file>
<file>icons/22x22/vlc.png</file>
<file>icons/22x22/xine.png</file>
<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>
</qresource>
</RCC> </RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
data/icons/22x22/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
data/icons/32x32/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
data/icons/48x48/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
data/icons/64x64/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
data/icons/full/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -0,0 +1,41 @@
<!doctype html>
<html>
<head>
<link href="http://www.strawbs.org/favicon.ico" rel="shortcut icon">
<title>tr("Return to Strawberry")</title>
<style>
#container {
margin: 6em auto 0px auto;
max-width: 400px;
font-family: 'arial regular', arial, sans-serif;
}
#container img {
width: 16px;
height: 16px;
float: left;
margin-right: 0.5em;
}
#container h1 {
margin: 0px 0px 0.75em 0px;
font-size: 16px;
}
#container p {
margin-top: 0px;
margin-left: 10px;
font-size: 13px;
}
</style>
</head>
<body>
<div id="container">
<h1>tr("Success!")</h1>
<img src="data:image/png;base64,@IMAGE_DATA@"/>
<p>tr("Please close your browser and return to Strawberry.")</p>
</div>
</body>
</html>

BIN
data/pictures/deezer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -1,7 +1,6 @@
#playlist { #playlist {
background-color: %palette-base; background-color: %palette-base;
alternate-background-color: %palette-alternate-base; alternate-background-color: %palette-alternate-base;
} }
#playlist[default_background_enabled = "true"] { #playlist[default_background_enabled = "true"] {
@@ -45,11 +44,11 @@ QToolButton:pressed[popupMode="1"] {
padding-right: 16px; padding-right: 16px;
} }
darwin { macos {
font-size: 11pt; font-size: 11pt;
} }
darwin QMenu { macos QMenu {
font-size: 13pt; font-size: 13pt;
} }

1
dist/CMakeLists.txt vendored
View File

@@ -19,6 +19,7 @@ if (UNIX AND NOT APPLE)
install(FILES ../data/icons/128x128/strawberry.png DESTINATION share/icons/hicolor/128x128/apps/) install(FILES ../data/icons/128x128/strawberry.png DESTINATION share/icons/hicolor/128x128/apps/)
install(FILES ../data/icons/128x128/strawberry.svg DESTINATION share/icons/hicolor/scalable/apps/) install(FILES ../data/icons/128x128/strawberry.svg DESTINATION share/icons/hicolor/scalable/apps/)
install(FILES unix/strawberry.desktop DESTINATION share/applications) install(FILES unix/strawberry.desktop DESTINATION share/applications)
install(FILES unix/strawberry.appdata.xml DESTINATION share/metainfo)
install(FILES man/strawberry.1 man/strawberry-tagreader.1 DESTINATION share/man/man1) install(FILES man/strawberry.1 man/strawberry-tagreader.1 DESTINATION share/man/man1)
endif() endif()

2
dist/debian/control vendored
View File

@@ -1,7 +1,7 @@
Source: strawberry Source: strawberry
Section: sound Section: sound
Priority: optional Priority: optional
Maintainer: "Jonas Kvinge" <jonas@jkvinge.net> Maintainer: Jonas Kvinge <jonas@jkvinge.net>
Build-Depends: debhelper (>= 7), Build-Depends: debhelper (>= 7),
make, make,
cmake, cmake,

View File

@@ -2,9 +2,8 @@ Name: strawberry
Version: @STRAWBERRY_VERSION_RPM_V@ Version: @STRAWBERRY_VERSION_RPM_V@
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@ Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
Summary: A audio player and music collection organiser Summary: A audio player and music collection organiser
Group: Applications/Multimedia Group: Applications/Multimedia
License: GPL-3.0+ License: GPLv3+
URL: http://www.strawbs.org/ URL: http://www.strawbs.org/
Source0: %{name}-@STRAWBERRY_VERSION_PACKAGE@.tar.xz Source0: %{name}-@STRAWBERRY_VERSION_PACKAGE@.tar.xz
@@ -82,7 +81,6 @@ export CXXFLAGS="%{optflags} -fpermissive"
mkdir %{_target_platform} mkdir %{_target_platform}
pushd %{_target_platform} pushd %{_target_platform}
%{cmake} \ %{cmake} \
-DBUILD_WERROR:BOOL=OFF \ -DBUILD_WERROR:BOOL=OFF \
-DCMAKE_BUILD_TYPE:STRING=Release \ -DCMAKE_BUILD_TYPE:STRING=Release \
@@ -93,18 +91,15 @@ popd
%install %install
make install DESTDIR=%{buildroot} -C %{_target_platform} make install DESTDIR=%{buildroot} -C %{_target_platform}
rm -rf %{buildroot}%{_datadir}/man rm -rf %{buildroot}%{_datadir}/metainfo
%check %check
desktop-file-validate %{buildroot}%{_datadir}/applications/strawberry.desktop desktop-file-validate %{buildroot}%{_datadir}/applications/strawberry.desktop
pushd %{_target_platform}
popd
%clean
%files %files
%defattr(-,root,root,-) %defattr(-,root,root,-)
%doc %doc README.md Changelog
%license COPYING
%{_bindir}/strawberry %{_bindir}/strawberry
%{_bindir}/strawberry-tagreader %{_bindir}/strawberry-tagreader
%{_datadir}/applications/strawberry.desktop %{_datadir}/applications/strawberry.desktop
@@ -112,6 +107,8 @@ popd
%{_datadir}/icons/hicolor/64x64/apps/strawberry.png %{_datadir}/icons/hicolor/64x64/apps/strawberry.png
%{_datadir}/icons/hicolor/128x128/apps/strawberry.png %{_datadir}/icons/hicolor/128x128/apps/strawberry.png
%{_datadir}/icons/hicolor/scalable/apps/strawberry.svg %{_datadir}/icons/hicolor/scalable/apps/strawberry.svg
%{_mandir}/man1/strawberry.1.*
%{_mandir}/man1/strawberry-tagreader.1.*
%changelog %changelog
* @RPM_DATE@ Jonas Kvinge <jonas@strawbs.net> - @STRAWBERRY_VERSION_RPM_V@ * @RPM_DATE@ Jonas Kvinge <jonas@strawbs.net> - @STRAWBERRY_VERSION_RPM_V@

View File

@@ -2,7 +2,6 @@ Name: strawberry
Version: @STRAWBERRY_VERSION_RPM_V@ Version: @STRAWBERRY_VERSION_RPM_V@
Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@ Release: @STRAWBERRY_VERSION_RPM_R@.@RPM_DISTRO@
Summary: A audio player and music collection organiser Summary: A audio player and music collection organiser
Group: Applications/Multimedia Group: Applications/Multimedia
License: GPL-3.0+ License: GPL-3.0+
URL: http://www.strawbs.org/ URL: http://www.strawbs.org/
@@ -89,6 +88,7 @@ make %{?_smp_mflags}
%install %install
cd build cd build
make install DESTDIR=$RPM_BUILD_ROOT make install DESTDIR=$RPM_BUILD_ROOT
rm -rf %{buildroot}%{_datadir}/metainfo
%clean %clean
cd build cd build
@@ -96,7 +96,8 @@ make clean
%files %files
%defattr(-,root,root,-) %defattr(-,root,root,-)
%doc %doc README.md Changelog
%license COPYING
%{_bindir}/strawberry %{_bindir}/strawberry
%{_bindir}/strawberry-tagreader %{_bindir}/strawberry-tagreader
%{_datadir}/applications/strawberry.desktop %{_datadir}/applications/strawberry.desktop

View File

@@ -26,4 +26,5 @@ tar -cJf $name-$version.tar.xz \
--exclude="*.nsi" \ --exclude="*.nsi" \
--exclude="$root/CMakeLists.txt.user" \ --exclude="$root/CMakeLists.txt.user" \
--exclude="$root/build" \ --exclude="$root/build" \
--exclude="$root/dist/debian/changelog" \
"$root" "$root"

38
dist/unix/strawberry.appdata.xml vendored Normal file
View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<application>
<id type="desktop">strawberry.desktop</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<name>Strawberry Music Player</name>
<summary>An audio player and music collection organizer</summary>
<description>
<p>
Strawberry is a audio player and music collection organizer.
It is a fork of Clementine. The name is inspired by the band Strawbs.
</p>
<p>Features::</p>
<ul>
<li>Play and organize music</li>
<li>Supports WAV, FLAC, WavPack, DSF, DSDIFF, Ogg Vorbis, Speex, MPC, TrueAudio, AIFF, MP4, MP3 and ASF</li>
<li>Audio CD playback</li>
<li>Native desktop notifications</li>
<li>Playlists in multiple formats</li>
<li>Advanced output and device options with support for bit perfect playback on Linux</li>
<li>Edit tags on music files</li>
<li>Fetch tags from MusicBrainz</li>
<li>Album cover art from Last.fm, Musicbrainz and Discogs</li>
<li>Song lyrics from AudD and API Seeds</li>
<li>Support for multiple backends</li>
<li>Audio analyzer</li>
<li>Equalizer</li>
<li>Transfer music to iPod, iPhone, MTP or mass-storage USB player</li>
<li>Integrated Tidal support</li>
</ul>
</description>
<screenshots>
<screenshot type="default" width="1920" height="1049">https://www.strawbs.org/pictures/screenshot-002-large.png</screenshot>
<screenshot width="1920" height="1080">https://www.strawbs.org/pictures/screenshot-006-large.png</screenshot>
</screenshots>
<url type="homepage">http://www.strawbs.org/</url>
<updatecontact>eclipseo@fedoraproject.org</updatecontact>
</application>

View File

@@ -102,7 +102,7 @@ Section "Strawberry" Strawberry
File "strawberry.ico" File "strawberry.ico"
File "libbz2.dll" File "libbz2.dll"
File "libcdio-16.dll" File "libcdio-18.dll"
File "libchromaprint.dll" File "libchromaprint.dll"
File "libcrypto-1_1-x64.dll" File "libcrypto-1_1-x64.dll"
File "libfaad-2.dll" File "libfaad-2.dll"
@@ -163,6 +163,7 @@ Section "Strawberry" Strawberry
File "libxml2-2.dll" File "libxml2-2.dll"
File "libsoup-2.4-1.dll" File "libsoup-2.4-1.dll"
File "liblzma-5.dll" File "liblzma-5.dll"
File "libdeezer.x64.dll"
; Register Strawberry with Default Programs ; Register Strawberry with Default Programs
Var /GLOBAL AppIcon Var /GLOBAL AppIcon
@@ -312,7 +313,7 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry-tagreader.exe" Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\libbz2.dll" Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-16.dll" Delete "$INSTDIR\libcdio-18.dll"
Delete "$INSTDIR\libchromaprint.dll" Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libcrypto-1_1.dll" Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libfaad-2.dll" Delete "$INSTDIR\libfaad-2.dll"
@@ -373,6 +374,7 @@ Section "Uninstall"
Delete "$INSTDIR\libxml2-2.dll" Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll" Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\liblzma-5.dll" Delete "$INSTDIR\liblzma-5.dll"
Delete "$INSTDIR\libdeezer.x64.dll"
Delete "$INSTDIR\platforms\qwindows.dll" Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll" Delete "$INSTDIR\sqldrivers\qsqlite.dll"

View File

@@ -102,7 +102,7 @@ Section "Strawberry" Strawberry
File "strawberry.ico" File "strawberry.ico"
File "libbz2.dll" File "libbz2.dll"
File "libcdio-16.dll" File "libcdio-18.dll"
File "libchromaprint.dll" File "libchromaprint.dll"
File "libcrypto-1_1-x64.dll" File "libcrypto-1_1-x64.dll"
File "libfaad-2.dll" File "libfaad-2.dll"
@@ -164,6 +164,7 @@ Section "Strawberry" Strawberry
File "libxml2-2.dll" File "libxml2-2.dll"
File "libsoup-2.4-1.dll" File "libsoup-2.4-1.dll"
File "liblzma-5.dll" File "liblzma-5.dll"
File "libdeezer.x64.dll"
; Register Strawberry with Default Programs ; Register Strawberry with Default Programs
Var /GLOBAL AppIcon Var /GLOBAL AppIcon
@@ -344,7 +345,7 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry-tagreader.exe" Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\libbz2.dll" Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-16.dll" Delete "$INSTDIR\libcdio-18.dll"
Delete "$INSTDIR\libchromaprint.dll" Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libcrypto-1_1.dll" Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libfaad-2.dll" Delete "$INSTDIR\libfaad-2.dll"
@@ -406,6 +407,7 @@ Section "Uninstall"
Delete "$INSTDIR\libxml2-2.dll" Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll" Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\liblzma-5.dll" Delete "$INSTDIR\liblzma-5.dll"
Delete "$INSTDIR\libdeezer.x64.dll"
Delete "$INSTDIR\platforms\qwindows.dll" Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll" Delete "$INSTDIR\sqldrivers\qsqlite.dll"

View File

@@ -102,7 +102,7 @@ Section "Strawberry" Strawberry
File "strawberry.ico" File "strawberry.ico"
File "libbz2.dll" File "libbz2.dll"
File "libcdio-16.dll" File "libcdio-18.dll"
File "libchromaprint.dll" File "libchromaprint.dll"
File "libcrypto-1_1.dll" File "libcrypto-1_1.dll"
File "libfaad-2.dll" File "libfaad-2.dll"
@@ -164,6 +164,7 @@ Section "Strawberry" Strawberry
File "libxml2-2.dll" File "libxml2-2.dll"
File "libsoup-2.4-1.dll" File "libsoup-2.4-1.dll"
File "liblzma-5.dll" File "liblzma-5.dll"
File "libdeezer.x86.dll"
; Register Strawberry with Default Programs ; Register Strawberry with Default Programs
Var /GLOBAL AppIcon Var /GLOBAL AppIcon
@@ -344,7 +345,7 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry-tagreader.exe" Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\libbz2.dll" Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-16.dll" Delete "$INSTDIR\libcdio-18.dll"
Delete "$INSTDIR\libchromaprint.dll" Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libcrypto-1_1.dll" Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libfaad-2.dll" Delete "$INSTDIR\libfaad-2.dll"
@@ -406,6 +407,7 @@ Section "Uninstall"
Delete "$INSTDIR\libxml2-2.dll" Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll" Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\liblzma-5.dll" Delete "$INSTDIR\liblzma-5.dll"
Delete "$INSTDIR\libdeezer.x86.dll"
Delete "$INSTDIR\platforms\qwindows.dll" Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll" Delete "$INSTDIR\sqldrivers\qsqlite.dll"

View File

@@ -102,7 +102,7 @@ Section "Strawberry" Strawberry
File "strawberry.ico" File "strawberry.ico"
File "libbz2.dll" File "libbz2.dll"
File "libcdio-16.dll" File "libcdio-18.dll"
File "libchromaprint.dll" File "libchromaprint.dll"
File "libcrypto-1_1.dll" File "libcrypto-1_1.dll"
File "libfaad-2.dll" File "libfaad-2.dll"
@@ -163,6 +163,7 @@ Section "Strawberry" Strawberry
File "libxml2-2.dll" File "libxml2-2.dll"
File "libsoup-2.4-1.dll" File "libsoup-2.4-1.dll"
File "liblzma-5.dll" File "liblzma-5.dll"
File "libdeezer.x86.dll"
; Register Strawberry with Default Programs ; Register Strawberry with Default Programs
Var /GLOBAL AppIcon Var /GLOBAL AppIcon
@@ -312,7 +313,7 @@ Section "Uninstall"
Delete "$INSTDIR\strawberry-tagreader.exe" Delete "$INSTDIR\strawberry-tagreader.exe"
Delete "$INSTDIR\libbz2.dll" Delete "$INSTDIR\libbz2.dll"
Delete "$INSTDIR\libcdio-16.dll" Delete "$INSTDIR\libcdio-18.dll"
Delete "$INSTDIR\libchromaprint.dll" Delete "$INSTDIR\libchromaprint.dll"
Delete "$INSTDIR\libcrypto-1_1.dll" Delete "$INSTDIR\libcrypto-1_1.dll"
Delete "$INSTDIR\libfaad-2.dll" Delete "$INSTDIR\libfaad-2.dll"
@@ -373,6 +374,7 @@ Section "Uninstall"
Delete "$INSTDIR\libxml2-2.dll" Delete "$INSTDIR\libxml2-2.dll"
Delete "$INSTDIR\libsoup-2.4-1.dll" Delete "$INSTDIR\libsoup-2.4-1.dll"
Delete "$INSTDIR\liblzma-5.dll" Delete "$INSTDIR\liblzma-5.dll"
Delete "$INSTDIR\libdeezer.x86.dll"
Delete "$INSTDIR\platforms\qwindows.dll" Delete "$INSTDIR\platforms\qwindows.dll"
Delete "$INSTDIR\sqldrivers\qsqlite.dll" Delete "$INSTDIR\sqldrivers\qsqlite.dll"

View File

@@ -43,7 +43,7 @@
#include <taglib/tag.h> #include <taglib/tag.h>
#include <taglib/apetag.h> #include <taglib/apetag.h>
#include <taglib/id3v2tag.h> #include <taglib/id3v2tag.h>
#include "taglib/id3v2frame.h" #include <taglib/id3v2frame.h>
#include <taglib/flacfile.h> #include <taglib/flacfile.h>
#include <taglib/oggflacfile.h> #include <taglib/oggflacfile.h>
#include <taglib/flacproperties.h> #include <taglib/flacproperties.h>
@@ -54,12 +54,12 @@
#include <taglib/wavpackfile.h> #include <taglib/wavpackfile.h>
#include <taglib/aifffile.h> #include <taglib/aifffile.h>
#include <taglib/asffile.h> #include <taglib/asffile.h>
#include "taglib/asftag.h" #include <taglib/asftag.h>
#include "taglib/asfattribute.h" #include <taglib/asfattribute.h>
#include "taglib/asfproperties.h" #include <taglib/asfproperties.h>
#include <taglib/mp4file.h> #include <taglib/mp4file.h>
#include <taglib/mp4tag.h> #include <taglib/mp4tag.h>
#include "taglib/mp4item.h" #include <taglib/mp4item.h>
#include <taglib/mp4coverart.h> #include <taglib/mp4coverart.h>
#include <taglib/mp4properties.h> #include <taglib/mp4properties.h>
#include <taglib/mpcfile.h> #include <taglib/mpcfile.h>

View File

@@ -21,6 +21,7 @@ message SongMetadata {
TRUEAUDIO = 13; TRUEAUDIO = 13;
DSF = 14; DSF = 14;
DSDIFF = 15; DSDIFF = 15;
PCM = 16;
CDDA = 90; CDDA = 90;
STREAM = 91; STREAM = 91;
} }

View File

@@ -1,7 +1,5 @@
# Strawberry Music Player # Strawberry Music Player
# Copyright 2013, Jonas Kvinge <jonas@strawbs.net> # Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
# This file was part of Clementine.
# Copyright 2010, David Sansome <me@davidsansome.com>
# #
# Strawberry is free software: you can redistribute it and/or modify # Strawberry is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@@ -59,6 +57,14 @@ if(HAVE_PHONON)
include_directories(${PHONON_INCLUDE_DIRS}) include_directories(${PHONON_INCLUDE_DIRS})
endif() endif()
if(HAVE_LIBDEEZER)
include_directories(${DEEZER_INCLUDE_DIRS})
endif()
if(HAVE_LIBDZMEDIA)
include_directories(${DZMEDIA_INCLUDE_DIRS})
endif()
link_directories(${TAGLIB_LIBRARY_DIRS}) link_directories(${TAGLIB_LIBRARY_DIRS})
include_directories(${TAGLIB_INCLUDE_DIRS}) include_directories(${TAGLIB_INCLUDE_DIRS})
@@ -158,11 +164,12 @@ set(SOURCES
playlist/playlisttabbar.cpp playlist/playlisttabbar.cpp
playlist/playlistundocommands.cpp playlist/playlistundocommands.cpp
playlist/playlistview.cpp playlist/playlistview.cpp
playlist/queue.cpp
playlist/queuemanager.cpp
playlist/songloaderinserter.cpp playlist/songloaderinserter.cpp
playlist/songplaylistitem.cpp playlist/songplaylistitem.cpp
queue/queue.cpp
queue/queueview.cpp
playlistparsers/asxiniparser.cpp playlistparsers/asxiniparser.cpp
playlistparsers/asxparser.cpp playlistparsers/asxparser.cpp
playlistparsers/cueparser.cpp playlistparsers/cueparser.cpp
@@ -211,7 +218,6 @@ set(SOURCES
settings/shortcutssettingspage.cpp settings/shortcutssettingspage.cpp
settings/appearancesettingspage.cpp settings/appearancesettingspage.cpp
settings/notificationssettingspage.cpp settings/notificationssettingspage.cpp
settings/tidalsettingspage.cpp
dialogs/about.cpp dialogs/about.cpp
dialogs/console.cpp dialogs/console.cpp
@@ -254,17 +260,15 @@ set(SOURCES
globalshortcuts/qxtglobalshortcutbackend.cpp globalshortcuts/qxtglobalshortcutbackend.cpp
globalshortcuts/globalshortcutgrabber.cpp globalshortcuts/globalshortcutgrabber.cpp
internet/internetmodel.cpp internet/internetservices.cpp
internet/internetservice.cpp internet/internetservice.cpp
internet/internetplaylistitem.cpp internet/internetplaylistitem.cpp
internet/internetsearch.cpp
tidal/tidalservice.cpp internet/internetsearchview.cpp
tidal/tidalsearch.cpp internet/internetsearchmodel.cpp
tidal/tidalsearchview.cpp internet/internetsearchsortmodel.cpp
tidal/tidalsearchmodel.cpp internet/internetsearchitemdelegate.cpp
tidal/tidalsearchsortmodel.cpp internet/localredirectserver.cpp
tidal/tidalsearchitemdelegate.cpp
tidal/tidalurlhandler.cpp
) )
@@ -328,11 +332,12 @@ set(HEADERS
playlist/playlisttabbar.h playlist/playlisttabbar.h
playlist/playlistview.h playlist/playlistview.h
playlist/playlistitemmimedata.h playlist/playlistitemmimedata.h
playlist/queue.h
playlist/queuemanager.h
playlist/songloaderinserter.h playlist/songloaderinserter.h
playlist/songmimedata.h playlist/songmimedata.h
queue/queue.h
queue/queueview.h
playlistparsers/asxiniparser.h playlistparsers/asxiniparser.h
playlistparsers/asxparser.h playlistparsers/asxparser.h
playlistparsers/cueparser.h playlistparsers/cueparser.h
@@ -378,7 +383,6 @@ set(HEADERS
settings/shortcutssettingspage.h settings/shortcutssettingspage.h
settings/appearancesettingspage.h settings/appearancesettingspage.h
settings/notificationssettingspage.h settings/notificationssettingspage.h
settings/tidalsettingspage.h
dialogs/about.h dialogs/about.h
dialogs/errordialog.h dialogs/errordialog.h
@@ -418,16 +422,13 @@ set(HEADERS
globalshortcuts/gnomeglobalshortcutbackend.h globalshortcuts/gnomeglobalshortcutbackend.h
globalshortcuts/globalshortcutgrabber.h globalshortcuts/globalshortcutgrabber.h
internet/internetmodel.h internet/internetservices.h
internet/internetservice.h internet/internetservice.h
internet/internetmimedata.h
internet/internetsongmimedata.h internet/internetsongmimedata.h
internet/internetsearch.h
tidal/tidalservice.h internet/internetsearchview.h
tidal/tidalsearch.h internet/internetsearchmodel.h
tidal/tidalsearchview.h internet/localredirectserver.h
tidal/tidalsearchmodel.h
tidal/tidalurlhandler.h
) )
@@ -446,7 +447,8 @@ set(UI
playlist/playlistlistcontainer.ui playlist/playlistlistcontainer.ui
playlist/playlistsaveoptionsdialog.ui playlist/playlistsaveoptionsdialog.ui
playlist/playlistsequence.ui playlist/playlistsequence.ui
playlist/queuemanager.ui
queue/queueview.ui
covermanager/albumcoverexport.ui covermanager/albumcoverexport.ui
covermanager/albumcovermanager.ui covermanager/albumcovermanager.ui
@@ -464,7 +466,6 @@ set(UI
settings/shortcutssettingspage.ui settings/shortcutssettingspage.ui
settings/appearancesettingspage.ui settings/appearancesettingspage.ui
settings/notificationssettingspage.ui settings/notificationssettingspage.ui
settings/tidalsettingspage.ui
equalizer/equalizer.ui equalizer/equalizer.ui
equalizer/equalizerslider.ui equalizer/equalizerslider.ui
@@ -482,11 +483,11 @@ set(UI
globalshortcuts/globalshortcutgrabber.ui globalshortcuts/globalshortcutgrabber.ui
tidal/tidalsearchview.ui internet/internetsearchview.ui
) )
set(RESOURCES ../data/data.qrc) set(RESOURCES ../data/data.qrc ../data/icons.qrc)
set(OTHER_SOURCES) set(OTHER_SOURCES)
option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON) option(USE_INSTALL_PREFIX "Look for data in CMAKE_INSTALL_PREFIX" ON)
@@ -524,6 +525,12 @@ optional_source(HAVE_PHONON
HEADERS engine/phononengine.h HEADERS engine/phononengine.h
) )
# Deezer
optional_source(HAVE_DEEZER
SOURCES engine/deezerengine.cpp
HEADERS engine/deezerengine.h
)
# Lastfm # Lastfm
optional_source(HAVE_LIBLASTFM optional_source(HAVE_LIBLASTFM
SOURCES SOURCES
@@ -848,6 +855,32 @@ optional_source(WIN32
widgets/osd_win.cpp widgets/osd_win.cpp
) )
optional_source(HAVE_STREAM_TIDAL
SOURCES
tidal/tidalservice.cpp
tidal/tidalurlhandler.cpp
settings/tidalsettingspage.cpp
HEADERS
tidal/tidalservice.h
tidal/tidalurlhandler.h
settings/tidalsettingspage.h
UI
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 configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in
${CMAKE_CURRENT_BINARY_DIR}/config.h) ${CMAKE_CURRENT_BINARY_DIR}/config.h)
@@ -880,6 +913,7 @@ target_link_libraries(strawberry_lib
${QT_LIBRARIES} ${QT_LIBRARIES}
${CHROMAPRINT_LIBRARIES} ${CHROMAPRINT_LIBRARIES}
${QTSINGLEAPPLICATION_LIBRARIES} ${QTSINGLEAPPLICATION_LIBRARIES}
${QTSINGLECOREAPPLICATION_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
${SQLITE_LIBRARIES} ${SQLITE_LIBRARIES}
${QOCOA_LIBRARIES} ${QOCOA_LIBRARIES}
@@ -906,6 +940,14 @@ if(HAVE_PHONON)
target_link_libraries(strawberry_lib ${PHONON_LIBRARIES}) target_link_libraries(strawberry_lib ${PHONON_LIBRARIES})
endif() 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_LIBLASTFM) if(HAVE_LIBLASTFM)
target_link_libraries(strawberry_lib ${LASTFM5_LIBRARIES}) target_link_libraries(strawberry_lib ${LASTFM5_LIBRARIES})
endif(HAVE_LIBLASTFM) endif(HAVE_LIBLASTFM)

View File

@@ -27,14 +27,13 @@
#include "analyzerbase.h" #include "analyzerbase.h"
#include "core/logging.h"
#include "engine/enginebase.h" #include "engine/enginebase.h"
// INSTRUCTIONS Base2D // INSTRUCTIONS Base2D
// 1. do anything that depends on height() in init(), Base2D will call it before // 1. do anything that depends on height() in init(), Base2D will call it before you are shown
// you are shown
// 2. otherwise you can use the constructor to initialise things // 2. otherwise you can use the constructor to initialise things
// 3. reimplement analyze(), and paint to canvas(), Base2D will update the // 3. reimplement analyze(), and paint to canvas(), Base2D will update the widget when you return control to it
// widget when you return control to it
// 4. if you want to manipulate the scope, reimplement transform() // 4. if you want to manipulate the scope, reimplement transform()
// 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included // 5. for convenience <vector> <qpixmap.h> <qwdiget.h> are pre-included
// TODO make an INSTRUCTIONS file // TODO make an INSTRUCTIONS file
@@ -50,37 +49,32 @@ template class Analyzer::Base<QWidget>;
Analyzer::Base::Base(QWidget *parent, uint scopeSize) Analyzer::Base::Base(QWidget *parent, uint scopeSize)
: QWidget(parent), : QWidget(parent),
m_timeout(40) // msec timeout_(40),
, fht_(new FHT(scopeSize)),
m_fht(new FHT(scopeSize)), engine_(nullptr),
m_engine(nullptr), lastscope_(512),
m_lastScope(512),
current_chunk_(0), current_chunk_(0),
new_frame_(false), new_frame_(false),
is_playing_(false) {} is_playing_(false) {}
void Analyzer::Base::hideEvent(QHideEvent*) { m_timer.stop(); } void Analyzer::Base::hideEvent(QHideEvent*) { timer_.stop(); }
void Analyzer::Base::showEvent(QShowEvent*) { m_timer.start(timeout(), this); } void Analyzer::Base::showEvent(QShowEvent*) { timer_.start(timeout(), this); }
void Analyzer::Base::transform(Scope& scope) // virtual void Analyzer::Base::transform(Scope& scope) {
{
// this is a standard transformation that should give // This is a standard transformation that should give an FFT scope that has bands for pretty analyzers
// an FFT scope that has bands for pretty analyzers
// NOTE resizing here is redundant as FHT routines only calculate FHT::size() // NOTE: Resizing here is redundant as FHT routines only calculate FHT::size() values scope.resize( fht_->size() );
// values
// scope.resize( m_fht->size() );
float *front = static_cast<float*>(&scope.front()); float *front = static_cast<float*>(&scope.front());
float *f = new float[m_fht->size()]; float *f = new float[fht_->size()];
m_fht->copy(&f[0], front); fht_->copy(&f[0], front);
m_fht->logSpectrum(front, &f[0]); fht_->logSpectrum(front, &f[0]);
m_fht->scale(front, 1.0 / 20); fht_->scale(front, 1.0 / 20);
scope.resize(m_fht->size() / 2); // second half of values are rubbish scope.resize(fht_->size() / 2); // second half of values are rubbish
delete[] f; delete[] f;
} }
@@ -90,28 +84,28 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
QPainter p(this); QPainter p(this);
p.fillRect(e->rect(), palette().color(QPalette::Window)); p.fillRect(e->rect(), palette().color(QPalette::Window));
switch (m_engine->state()) { switch (engine_->state()) {
case Engine::Playing: { case Engine::Playing: {
const Engine::Scope& thescope = m_engine->scope(m_timeout); const Engine::Scope& thescope = engine_->scope(timeout_);
int i = 0; int i = 0;
// convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm // convert to mono here - our built in analyzers need mono, but the engines provide interleaved pcm
for (uint x = 0; (int)x < m_fht->size(); ++x) { for (uint x = 0; (int)x < fht_->size(); ++x) {
m_lastScope[x] = double(thescope[i] + thescope[i + 1]) / (2 * (1 << 15)); lastscope_[x] = double(thescope[i] + thescope[i + 1]) / (2 * (1 << 15));
i += 2; i += 2;
} }
is_playing_ = true; is_playing_ = true;
transform(m_lastScope); transform(lastscope_);
analyze(p, m_lastScope, new_frame_); analyze(p, lastscope_, new_frame_);
// scope.resize( m_fht->size() ); lastscope_.resize(fht_->size());
break; break;
} }
case Engine::Paused: case Engine::Paused:
is_playing_ = false; is_playing_ = false;
analyze(p, m_lastScope, new_frame_); analyze(p, lastscope_, new_frame_);
break; break;
default: default:
@@ -124,16 +118,18 @@ void Analyzer::Base::paintEvent(QPaintEvent *e) {
} }
int Analyzer::Base::resizeExponent(int exp) { int Analyzer::Base::resizeExponent(int exp) {
if (exp < 3) if (exp < 3)
exp = 3; exp = 3;
else if (exp > 9) else if (exp > 9)
exp = 9; exp = 9;
if (exp != m_fht->sizeExp()) { if (exp != fht_->sizeExp()) {
delete m_fht; delete fht_;
m_fht = new FHT(exp); fht_ = new FHT(exp);
} }
return exp; return exp;
} }
int Analyzer::Base::resizeForBands(int bands) { int Analyzer::Base::resizeForBands(int bands) {
@@ -153,12 +149,11 @@ int Analyzer::Base::resizeForBands(int bands) {
exp = 9; exp = 9;
resizeExponent(exp); resizeExponent(exp);
return m_fht->size() / 2; return fht_->size() / 2;
} }
void Analyzer::Base::demo(QPainter& p) // virtual void Analyzer::Base::demo(QPainter& p) {
{
static int t = 201; // FIXME make static to namespace perhaps static int t = 201; // FIXME make static to namespace perhaps
@@ -171,7 +166,8 @@ void Analyzer::Base::demo(QPainter& p) // virtual
s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0); s[i] = dt * (sin(M_PI + (i * M_PI) / s.size()) + 1.0);
analyze(p, s, new_frame_); analyze(p, s, new_frame_);
} else }
else
analyze(p, Scope(32, 0), new_frame_); analyze(p, Scope(32, 0), new_frame_);
++t; ++t;
@@ -179,7 +175,7 @@ void Analyzer::Base::demo(QPainter& p) // virtual
} }
void Analyzer::Base::polishEvent() { void Analyzer::Base::polishEvent() {
init(); // virtual init();
} }
void Analyzer::interpolate(const Scope& inVec, Scope& outVec) { void Analyzer::interpolate(const Scope& inVec, Scope& outVec) {
@@ -189,13 +185,13 @@ void Analyzer::interpolate(const Scope& inVec, Scope& outVec) {
for (uint i = 0; i < outVec.size(); ++i, pos += step) { for (uint i = 0; i < outVec.size(); ++i, pos += step) {
const double error = pos - std::floor(pos); const double error = pos - std::floor(pos);
const unsigned long offset = (unsigned long)pos; const uint64_t offset = (uint64_t)pos;
unsigned long indexLeft = offset + 0; uint64_t indexLeft = offset + 0;
if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1; if (indexLeft >= inVec.size()) indexLeft = inVec.size() - 1;
unsigned long indexRight = offset + 1; uint64_t indexRight = offset + 1;
if (indexRight >= inVec.size()) indexRight = inVec.size() - 1; if (indexRight >= inVec.size()) indexRight = inVec.size() - 1;
@@ -205,6 +201,7 @@ void Analyzer::interpolate(const Scope& inVec, Scope& outVec) {
} }
void Analyzer::initSin(Scope& v, const uint size) { void Analyzer::initSin(Scope& v, const uint size) {
double step = (M_PI * 2) / size; double step = (M_PI * 2) / size;
double radian = 0; double radian = 0;
@@ -212,12 +209,15 @@ void Analyzer::initSin(Scope& v, const uint size) {
v.push_back(sin(radian)); v.push_back(sin(radian));
radian += step; radian += step;
} }
} }
void Analyzer::Base::timerEvent(QTimerEvent *e) { void Analyzer::Base::timerEvent(QTimerEvent *e) {
QWidget::timerEvent(e); QWidget::timerEvent(e);
if (e->timerId() != m_timer.timerId()) return; if (e->timerId() != timer_.timerId()) return;
new_frame_ = true; new_frame_ = true;
update(); update();
} }

View File

@@ -37,17 +37,17 @@ class Base : public QWidget {
Q_OBJECT Q_OBJECT
public: public:
~Base() { delete m_fht; } ~Base() { delete fht_; }
uint timeout() const { return m_timeout; } uint timeout() const { return timeout_; }
void set_engine(EngineBase *engine) { m_engine = engine; } void set_engine(EngineBase *engine) { engine_ = engine; }
void changeTimeout(uint newTimeout) { void changeTimeout(uint newTimeout) {
m_timeout = newTimeout; timeout_ = newTimeout;
if (m_timer.isActive()) { if (timer_.isActive()) {
m_timer.stop(); timer_.stop();
m_timer.start(m_timeout, this); timer_.start(timeout_, this);
} }
} }
@@ -71,13 +71,12 @@ class Base : public QWidget {
virtual void demo(QPainter& p); virtual void demo(QPainter& p);
protected: protected:
QBasicTimer m_timer; QBasicTimer timer_;
uint m_timeout; uint timeout_;
FHT* m_fht; FHT *fht_;
EngineBase* m_engine; EngineBase *engine_;
Scope m_lastScope; Scope lastscope_;
int current_chunk_; int current_chunk_;
bool new_frame_; bool new_frame_;
bool is_playing_; bool is_playing_;
}; };

View File

@@ -26,109 +26,105 @@ const uint BlockAnalyzer::MIN_COLUMNS = 32; // arbituary
const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n const uint BlockAnalyzer::MAX_COLUMNS = 256; // must be 2**n
const uint BlockAnalyzer::FADE_SIZE = 90; const uint BlockAnalyzer::FADE_SIZE = 90;
const char *BlockAnalyzer::kName = const char *BlockAnalyzer::kName = QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
QT_TRANSLATE_NOOP("AnalyzerContainer", "Block analyzer");
BlockAnalyzer::BlockAnalyzer(QWidget *parent) BlockAnalyzer::BlockAnalyzer(QWidget *parent)
: Analyzer::Base(parent, 9), : Analyzer::Base(parent, 9),
m_columns(0) // uint columns_(0),
, rows_(0),
m_rows(0) // uint y_(0),
, barpixmap_(1, 1),
m_y(0) // uint topbarpixmap_(WIDTH, HEIGHT),
, scope_(MIN_COLUMNS),
m_barPixmap(1, 1) // null qpixmaps cause crashes store_(1 << 8, 0),
, fade_bars_(FADE_SIZE),
m_topBarPixmap(WIDTH, HEIGHT), fade_pos_(1 << 8, 50),
m_scope(MIN_COLUMNS) // Scope fade_intensity_(1 << 8, 32) {
,
m_store(1 << 8, 0) // vector<uint>
,
m_fade_bars(FADE_SIZE) // vector<QPixmap>
,
m_fade_pos(1 << 8, 50) // vector<uint>
,
m_fade_intensity(1 << 8, 32) // vector<uint>
{
setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1, MIN_ROWS * (HEIGHT + 1) - 1); //-1 is padding, no drawing takes place there setMinimumSize(MIN_COLUMNS * (WIDTH + 1) - 1, MIN_ROWS * (HEIGHT + 1) - 1); //-1 is padding, no drawing takes place there
setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1); setMaximumWidth(MAX_COLUMNS * (WIDTH + 1) - 1);
// mxcl says null pixmaps cause crashes, so let's play it safe // mxcl says null pixmaps cause crashes, so let's play it safe
for (uint i = 0; i < FADE_SIZE; ++i) m_fade_bars[i] = QPixmap(1, 1); for (uint i = 0; i < FADE_SIZE; ++i) fade_bars_[i] = QPixmap(1, 1);
} }
BlockAnalyzer::~BlockAnalyzer() {} BlockAnalyzer::~BlockAnalyzer() {}
void BlockAnalyzer::resizeEvent(QResizeEvent *e) { void BlockAnalyzer::resizeEvent(QResizeEvent *e) {
QWidget::resizeEvent(e); QWidget::resizeEvent(e);
m_background = QPixmap(size()); background_ = QPixmap(size());
canvas_ = QPixmap(size()); canvas_ = QPixmap(size());
const uint oldRows = m_rows; const uint oldRows = rows_;
// all is explained in analyze().. // all is explained in analyze()..
//+1 to counter -1 in maxSizes, trust me we need this! //+1 to counter -1 in maxSizes, trust me we need this!
m_columns = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS); columns_ = qMax(uint(double(width() + 1) / (WIDTH + 1)), MAX_COLUMNS);
m_rows = uint(double(height() + 1) / (HEIGHT + 1)); rows_ = uint(double(height() + 1) / (HEIGHT + 1));
// this is the y-offset for drawing from the top of the widget // this is the y-offset for drawing from the top of the widget
m_y = (height() - (m_rows * (HEIGHT + 1)) + 2) / 2; y_ = (height() - (rows_ * (HEIGHT + 1)) + 2) / 2;
m_scope.resize(m_columns); scope_.resize(columns_);
if (m_rows != oldRows) { if (rows_ != oldRows) {
m_barPixmap = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); barpixmap_ = QPixmap(WIDTH, rows_ * (HEIGHT + 1));
for (uint i = 0; i < FADE_SIZE; ++i) for (uint i = 0; i < FADE_SIZE; ++i)
m_fade_bars[i] = QPixmap(WIDTH, m_rows * (HEIGHT + 1)); fade_bars_[i] = QPixmap(WIDTH, rows_ * (HEIGHT + 1));
m_yscale.resize(m_rows + 1); yscale_.resize(rows_ + 1);
const uint PRE = 1, const uint PRE = 1, PRO = 1; // PRE and PRO allow us to restrict the range somewhat
PRO = 1; // PRE and PRO allow us to restrict the range somewhat
for (uint z = 0; z < m_rows; ++z) for (uint z = 0; z < rows_; ++z)
m_yscale[z] = 1 - (log10(PRE + z) / log10(PRE + m_rows + PRO)); yscale_[z] = 1 - (log10(PRE + z) / log10(PRE + rows_ + PRO));
m_yscale[m_rows] = 0; yscale_[rows_] = 0;
determineStep(); determineStep();
paletteChange(palette()); paletteChange(palette());
} }
drawBackground(); drawBackground();
} }
void BlockAnalyzer::determineStep() { void BlockAnalyzer::determineStep() {
// falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels) // falltime is dependent on rowcount due to our digital resolution (ie we have boxes/blocks of pixels)
// I calculated the value 30 based on some trial and error // I calculated the value 30 based on some trial and error
// the fall time of 30 is too slow on framerates above 50fps // the fall time of 30 is too slow on framerates above 50fps
const double fallTime = timeout() < 20 ? 20 * m_rows : 30 * m_rows; const double fallTime = timeout() < 20 ? 20 * rows_ : 30 * rows_;
step_ = double(rows_ * timeout()) / fallTime;
m_step = double(m_rows * timeout()) / fallTime;
} }
void BlockAnalyzer::framerateChanged() { // virtual void BlockAnalyzer::framerateChanged() {
determineStep(); determineStep();
} }
void BlockAnalyzer::transform(Analyzer::Scope& s) // pure virtual void BlockAnalyzer::transform(Analyzer::Scope &s) {
{
for (uint x = 0; x < s.size(); ++x) s[x] *= 2; for (uint x = 0; x < s.size(); ++x) s[x] *= 2;
float* front = static_cast<float*>(&s.front()); float* front = static_cast<float*>(&s.front());
m_fht->spectrum(front); fht_->spectrum(front);
m_fht->scale(front, 1.0 / 20); fht_->scale(front, 1.0 / 20);
// the second half is pretty dull, so only show it if the user has a large analyzer by setting to scope_.size() if large we prevent interpolation of large analyzers, this is good!
s.resize(scope_.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : scope_.size());
// the second half is pretty dull, so only show it if the user has a large analyzer by setting to m_scope.size() if large we prevent interpolation of large analyzers, this is good!
s.resize(m_scope.size() <= MAX_COLUMNS / 2 ? MAX_COLUMNS / 2 : m_scope.size());
} }
void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s, void BlockAnalyzer::analyze(QPainter &p, const Analyzer::Scope &s, bool new_frame) {
bool new_frame) {
// y = 2 3 2 1 0 2 // y = 2 3 2 1 0 2
// . . . . # . // . . . . # .
// . . . # # . // . . . # # .
@@ -139,7 +135,7 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
// y represents the number of blanks // y represents the number of blanks
// y starts from the top and increases in units of blocks // y starts from the top and increases in units of blocks
// m_yscale looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 } // yscale_ looks similar to: { 0.7, 0.5, 0.25, 0.15, 0.1, 0 }
// if it contains 6 elements there are 5 rows in the analyzer // if it contains 6 elements there are 5 rows in the analyzer
if (!new_frame) { if (!new_frame) {
@@ -149,50 +145,51 @@ void BlockAnalyzer::analyze(QPainter& p, const Analyzer::Scope& s,
QPainter canvas_painter(&canvas_); QPainter canvas_painter(&canvas_);
Analyzer::interpolate(s, m_scope); Analyzer::interpolate(s, scope_);
// Paint the background // Paint the background
canvas_painter.drawPixmap(0, 0, m_background); canvas_painter.drawPixmap(0, 0, background_);
for (uint y, x = 0; x < m_scope.size(); ++x) { for (uint y, x = 0; x < scope_.size(); ++x) {
// determine y // determine y
for (y = 0; m_scope[x] < m_yscale[y]; ++y) for (y = 0; scope_[x] < yscale_[y]; ++y) continue;
;
// this is opposite to what you'd think, higher than y means the bar is lower than y (physically) // this is opposite to what you'd think, higher than y means the bar is lower than y (physically)
if ((float)y > m_store[x]) if ((float)y > store_[x])
y = int(m_store[x] += m_step); y = int(store_[x] += step_);
else else
m_store[x] = y; store_[x] = y;
// if y is lower than m_fade_pos, then the bar has exceeded the height of the fadeout // if y is lower than fade_pos_, then the bar has exceeded the height of the fadeout
// if the fadeout is quite faded now, then display the new one // if the fadeout is quite faded now, then display the new one
if (y <= m_fade_pos[x] /*|| m_fade_intensity[x] < FADE_SIZE / 3*/) { if (y <= fade_pos_[x] /*|| fade_intensity_[x] < FADE_SIZE / 3*/) {
m_fade_pos[x] = y; fade_pos_[x] = y;
m_fade_intensity[x] = FADE_SIZE; fade_intensity_[x] = FADE_SIZE;
} }
if (m_fade_intensity[x] > 0) { if (fade_intensity_[x] > 0) {
const uint offset = --m_fade_intensity[x]; const uint offset = --fade_intensity_[x];
const uint y = m_y + (m_fade_pos[x] * (HEIGHT + 1)); const uint y = y_ + (fade_pos_[x] * (HEIGHT + 1));
canvas_painter.drawPixmap(x * (WIDTH + 1), y, m_fade_bars[offset], 0, 0, WIDTH, height() - y); canvas_painter.drawPixmap(x * (WIDTH + 1), y, fade_bars_[offset], 0, 0, WIDTH, height() - y);
} }
if (m_fade_intensity[x] == 0) m_fade_pos[x] = m_rows; if (fade_intensity_[x] == 0) fade_pos_[x] = rows_;
// REMEMBER: y is a number from 0 to m_rows, 0 means all blocks are glowing, m_rows means none are // REMEMBER: y is a number from 0 to rows_, 0 means all blocks are glowing, rows_ means none are
canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, *bar(), canvas_painter.drawPixmap(x * (WIDTH + 1), y * (HEIGHT + 1) + y_, *bar(),
0, y * (HEIGHT + 1), bar()->width(), 0, y * (HEIGHT + 1), bar()->width(),
bar()->height()); bar()->height());
} }
for (uint x = 0; x < m_store.size(); ++x) for (uint x = 0; x < store_.size(); ++x)
canvas_painter.drawPixmap(x * (WIDTH + 1), int(m_store[x]) * (HEIGHT + 1) + m_y, m_topBarPixmap); canvas_painter.drawPixmap(x * (WIDTH + 1), int(store_[x]) * (HEIGHT + 1) + y_, topbarpixmap_);
p.drawPixmap(0, 0, canvas_); p.drawPixmap(0, 0, canvas_);
} }
static inline void adjustToLimits(int& b, int& f, uint& amount) { static inline void adjustToLimits(int &b, int &f, uint &amount) {
// with a range of 0-255 and maximum adjustment of amount, maximise the difference between f and b // with a range of 0-255 and maximum adjustment of amount, maximise the difference between f and b
if (b < f) { if (b < f) {
@@ -215,6 +212,7 @@ static inline void adjustToLimits(int& b, int& f, uint& amount) {
f = 255; f = 255;
} }
} }
} }
/** /**
@@ -225,17 +223,18 @@ static inline void adjustToLimits(int& b, int& f, uint& amount) {
* It won't modify the hue of fg unless absolutely necessary * It won't modify the hue of fg unless absolutely necessary
* @return the adjusted form of fg * @return the adjusted form of fg
*/ */
QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) { QColor ensureContrast(const QColor &bg, const QColor &fg, uint _amount = 150) {
class OutputOnExit { class OutputOnExit {
public: public:
OutputOnExit(const QColor& color) : c(color) {} OutputOnExit(const QColor &color) : c(color) {}
~OutputOnExit() { ~OutputOnExit() {
int h, s, v; int h, s, v;
c.getHsv(&h, &s, &v); c.getHsv(&h, &s, &v);
} }
private: private:
const QColor& c; const QColor &c;
}; };
// hack so I don't have to cast everywhere // hack so I don't have to cast everywhere
@@ -352,24 +351,25 @@ QColor ensureContrast(const QColor& bg, const QColor& fg, uint _amount = 150) {
#undef amount #undef amount
// #undef STAMP // #undef STAMP
} }
void BlockAnalyzer::paletteChange(const QPalette&) // virtual void BlockAnalyzer::paletteChange(const QPalette&) {
{
const QColor bg = palette().color(QPalette::Background); const QColor bg = palette().color(QPalette::Background);
const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight)); const QColor fg = ensureContrast(bg, palette().color(QPalette::Highlight));
m_topBarPixmap.fill(fg); topbarpixmap_.fill(fg);
const double dr = 15 * double(bg.red() - fg.red()) / (m_rows * 16); const double dr = 15 * double(bg.red() - fg.red()) / (rows_ * 16);
const double dg = 15 * double(bg.green() - fg.green()) / (m_rows * 16); const double dg = 15 * double(bg.green() - fg.green()) / (rows_ * 16);
const double db = 15 * double(bg.blue() - fg.blue()) / (m_rows * 16); const double db = 15 * double(bg.blue() - fg.blue()) / (rows_ * 16);
const int r = fg.red(), g = fg.green(), b = fg.blue(); const int r = fg.red(), g = fg.green(), b = fg.blue();
bar()->fill(bg); bar()->fill(bg);
QPainter p(bar()); QPainter p(bar());
for (int y = 0; (uint)y < m_rows; ++y) for (int y = 0; (uint)y < rows_; ++y)
// graduate the fg color // graduate the fg color
p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * y), g + int(dg * y), b + int(db * y))); p.fillRect(0, y * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * y), g + int(dg * y), b + int(db * y)));
@@ -389,9 +389,9 @@ void BlockAnalyzer::paletteChange(const QPalette&) // virtual
// Precalculate all fade-bar pixmaps // Precalculate all fade-bar pixmaps
for (uint y = 0; y < FADE_SIZE; ++y) { for (uint y = 0; y < FADE_SIZE; ++y) {
m_fade_bars[y].fill(palette().color(QPalette::Background)); fade_bars_[y].fill(palette().color(QPalette::Background));
QPainter f(&m_fade_bars[y]); QPainter f(&fade_bars_[y]);
for (int z = 0; (uint)z < m_rows; ++z) { for (int z = 0; (uint)z < rows_; ++z) {
const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE)); const double Y = 1.0 - (log10(FADE_SIZE - y) / log10(FADE_SIZE));
f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * Y), g + int(dg * Y), b + int(db * Y))); f.fillRect(0, z * (HEIGHT + 1), WIDTH, HEIGHT, QColor(r + int(dr * Y), g + int(dg * Y), b + int(db * Y)));
} }
@@ -399,16 +399,19 @@ void BlockAnalyzer::paletteChange(const QPalette&) // virtual
} }
drawBackground(); drawBackground();
} }
void BlockAnalyzer::drawBackground() { void BlockAnalyzer::drawBackground() {
const QColor bg = palette().color(QPalette::Background); const QColor bg = palette().color(QPalette::Background);
const QColor bgdark = bg.dark(112); const QColor bgdark = bg.dark(112);
m_background.fill(bg); background_.fill(bg);
QPainter p(&background_);
for (int x = 0; (uint)x < columns_; ++x)
for (int y = 0; (uint)y < rows_; ++y)
p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + y_, WIDTH, HEIGHT, bgdark);
QPainter p(&m_background);
for (int x = 0; (uint)x < m_columns; ++x)
for (int y = 0; (uint)y < m_rows; ++y)
p.fillRect(x * (WIDTH + 1), y * (HEIGHT + 1) + m_y, WIDTH, HEIGHT, bgdark);
} }

View File

@@ -51,24 +51,24 @@ class BlockAnalyzer : public Analyzer::Base {
void determineStep(); void determineStep();
private: private:
QPixmap* bar() { return &m_barPixmap; } QPixmap *bar() { return &barpixmap_; }
uint m_columns, m_rows; // number of rows and columns of blocks uint columns_, rows_; // number of rows and columns of blocks
uint m_y; // y-offset from top of widget uint y_; // y-offset from top of widget
QPixmap m_barPixmap; QPixmap barpixmap_;
QPixmap m_topBarPixmap; QPixmap topbarpixmap_;
QPixmap m_background; QPixmap background_;
QPixmap canvas_; QPixmap canvas_;
Analyzer::Scope m_scope; // so we don't create a vector every frame Analyzer::Scope scope_; // so we don't create a vector every frame
std::vector<float> m_store; // current bar heights std::vector<float> store_; // current bar heights
std::vector<float> m_yscale; std::vector<float> yscale_;
// FIXME why can't I namespace these? c++ issue? // FIXME why can't I namespace these? c++ issue?
std::vector<QPixmap> m_fade_bars; std::vector<QPixmap> fade_bars_;
std::vector<uint> m_fade_pos; std::vector<uint> fade_pos_;
std::vector<int> m_fade_intensity; std::vector<int> fade_intensity_;
float m_step; // rows to fall per frame float step_; // rows to fall per frame
}; };
#endif #endif

View File

@@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <functional> #include <functional>
#include <algorithm>
#include <QObject> #include <QObject>
#include <QtGlobal> #include <QtGlobal>
@@ -61,6 +62,8 @@
#include "playlist/songmimedata.h" #include "playlist/songmimedata.h"
#include "covermanager/albumcoverloader.h" #include "covermanager/albumcoverloader.h"
using std::bind;
using std::sort;
using std::placeholders::_1; using std::placeholders::_1;
using std::placeholders::_2; using std::placeholders::_2;
@@ -1312,7 +1315,7 @@ void CollectionModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls, Son
const_cast<CollectionModel*>(this)->LazyPopulate(item); const_cast<CollectionModel*>(this)->LazyPopulate(item);
QList<CollectionItem*> children = item->children; QList<CollectionItem*> children = item->children;
qSort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, _1, _2)); std::sort(children.begin(), children.end(), std::bind(&CollectionModel::CompareItems, this, _1, _2));
for (CollectionItem *child : children) for (CollectionItem *child : children)
GetChildSongs(child, urls, songs, song_ids); GetChildSongs(child, urls, songs, song_ids);

View File

@@ -14,7 +14,7 @@
<string>Collection advanced grouping</string> <string>Collection advanced grouping</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@@ -358,6 +358,7 @@
</tabstops> </tabstops>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -106,6 +106,7 @@
</widget> </widget>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -1,25 +1,30 @@
/* This file is part of Strawberry. /*
* Strawberry Music Player
Strawberry is free software: you can redistribute it and/or modify * Copyright 2013, Jonas Kvinge <jonas@strawbs.net>
it under the terms of the GNU General Public License as published by *
the Free Software Foundation, either version 3 of the License, or * Strawberry is free software: you can redistribute it and/or modify
(at your option) any later version. * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
Strawberry is distributed in the hope that it will be useful, * (at your option) any later version.
but WITHOUT ANY WARRANTY; without even the implied warranty of *
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * Strawberry is distributed in the hope that it will be useful,
GNU General Public License for more details. * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
You should have received a copy of the GNU General Public License * GNU General Public License for more details.
along with Strawberry. If not, see <http://www.gnu.org/licenses/>. *
*/ * You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef CONFIG_H_IN #ifndef CONFIG_H_IN
#define CONFIG_H_IN #define CONFIG_H_IN
#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" #define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
#define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}" #define CMAKE_EXECUTABLE_SUFFIX "${CMAKE_EXECUTABLE_SUFFIX}"
#cmakedefine DEBUG
#cmakedefine HAVE_GIO #cmakedefine HAVE_GIO
#cmakedefine HAVE_DBUS #cmakedefine HAVE_DBUS
#cmakedefine HAVE_X11 #cmakedefine HAVE_X11
@@ -38,6 +43,7 @@
#cmakedefine HAVE_SPARKLE #cmakedefine HAVE_SPARKLE
#cmakedefine HAVE_CHROMAPRINT #cmakedefine HAVE_CHROMAPRINT
#cmakedefine HAVE_TAGLIB_DSFFILE #cmakedefine HAVE_TAGLIB_DSFFILE
#cmakedefine HAVE_DZMEDIA
#cmakedefine IMOBILEDEVICE_USES_UDIDS #cmakedefine IMOBILEDEVICE_USES_UDIDS
#cmakedefine USE_INSTALL_PREFIX #cmakedefine USE_INSTALL_PREFIX
#cmakedefine USE_SYSTEM_SHA2 #cmakedefine USE_SYSTEM_SHA2
@@ -46,6 +52,10 @@
#cmakedefine HAVE_VLC #cmakedefine HAVE_VLC
#cmakedefine HAVE_XINE #cmakedefine HAVE_XINE
#cmakedefine HAVE_PHONON #cmakedefine HAVE_PHONON
#cmakedefine HAVE_DEEZER
#cmakedefine HAVE_STREAM_TIDAL
#cmakedefine HAVE_STREAM_DEEZER
#endif // CONFIG_H_IN #endif // CONFIG_H_IN

View File

@@ -22,6 +22,7 @@
#include "config.h" #include "config.h"
#include <functional> #include <functional>
#include <algorithm>
#include <QObject> #include <QObject>
#include <QtGlobal> #include <QtGlobal>
@@ -55,6 +56,8 @@
#include "contextalbumsmodel.h" #include "contextalbumsmodel.h"
using std::bind;
using std::sort;
using std::placeholders::_1; using std::placeholders::_1;
using std::placeholders::_2; using std::placeholders::_2;
@@ -479,7 +482,7 @@ void ContextAlbumsModel::GetChildSongs(CollectionItem *item, QList<QUrl> *urls,
const_cast<ContextAlbumsModel*>(this)->LazyPopulate(item); const_cast<ContextAlbumsModel*>(this)->LazyPopulate(item);
QList<CollectionItem*> children = item->children; QList<CollectionItem*> children = item->children;
qSort(children.begin(), children.end(), std::bind(&ContextAlbumsModel::CompareItems, this, _1, _2)); std::sort(children.begin(), children.end(), std::bind(&ContextAlbumsModel::CompareItems, this, _1, _2));
for (CollectionItem *child : children) for (CollectionItem *child : children)
GetChildSongs(child, urls, songs, song_ids); GetChildSongs(child, urls, songs, song_ids);

View File

@@ -562,6 +562,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@@ -26,11 +26,13 @@
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QVariant>
#include <QString> #include <QString>
#include "core/closure.h" #include "core/closure.h"
#include "core/lazy.h" #include "core/lazy.h"
#include "core/tagreaderclient.h" #include "core/tagreaderclient.h"
#include "core/song.h"
#include "database.h" #include "database.h"
#include "taskmanager.h" #include "taskmanager.h"
@@ -58,8 +60,15 @@
#include "lyrics/auddlyricsprovider.h" #include "lyrics/auddlyricsprovider.h"
#include "lyrics/apiseedslyricsprovider.h" #include "lyrics/apiseedslyricsprovider.h"
#include "internet/internetmodel.h" #include "internet/internetservices.h"
#include "tidal/tidalsearch.h" #include "internet/internetsearch.h"
#ifdef HAVE_STREAM_TIDAL
# include "tidal/tidalservice.h"
#endif
#ifdef HAVE_STREAM_DEEZER
# include "deezer/deezerservice.h"
#endif
bool Application::kIsPortable = false; bool Application::kIsPortable = false;
@@ -108,15 +117,30 @@ class ApplicationImpl {
return loader; return loader;
}), }),
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }), current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
internet_model_([=]() { return new InternetModel(app, app); }),
tidal_search_([=]() { return new TidalSearch(app, app); }),
lyrics_providers_([=]() { lyrics_providers_([=]() {
LyricsProviders *lyrics_providers = new LyricsProviders(app); LyricsProviders *lyrics_providers = new LyricsProviders(app);
lyrics_providers->AddProvider(new AuddLyricsProvider(app)); lyrics_providers->AddProvider(new AuddLyricsProvider(app));
lyrics_providers->AddProvider(new APISeedsLyricsProvider(app)); lyrics_providers->AddProvider(new APISeedsLyricsProvider(app));
return lyrics_providers; return lyrics_providers;
}) }),
{ } internet_services_([=]() {
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
dummy_([=]() { return new QVariant; })
{}
Lazy<TagReaderClient> tag_reader_client_; Lazy<TagReaderClient> tag_reader_client_;
Lazy<Database> database_; Lazy<Database> database_;
@@ -133,9 +157,15 @@ class ApplicationImpl {
Lazy<CoverProviders> cover_providers_; Lazy<CoverProviders> cover_providers_;
Lazy<AlbumCoverLoader> album_cover_loader_; Lazy<AlbumCoverLoader> album_cover_loader_;
Lazy<CurrentArtLoader> current_art_loader_; Lazy<CurrentArtLoader> current_art_loader_;
Lazy<InternetModel> internet_model_;
Lazy<TidalSearch> tidal_search_;
Lazy<LyricsProviders> lyrics_providers_; Lazy<LyricsProviders> lyrics_providers_;
Lazy<InternetServices> internet_services_;
#ifdef HAVE_STREAM_TIDAL
Lazy<InternetSearch> tidal_search_;
#endif
#ifdef HAVE_STREAM_DEEZER
Lazy<InternetSearch> deezer_search_;
#endif
Lazy<QVariant> dummy_;
}; };
@@ -181,73 +211,31 @@ void Application::MoveToThread(QObject *object, QThread *thread) {
} }
void Application::AddError(const QString& message) { emit ErrorAdded(message); } void Application::AddError(const QString& message) { emit ErrorAdded(message); }
void Application::ReloadSettings() { emit SettingsChanged(); } void Application::ReloadSettings() { emit SettingsChanged(); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { emit SettingsDialogRequested(page); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) { TagReaderClient *Application::tag_reader_client() const { return p_->tag_reader_client_.get(); }
emit SettingsDialogRequested(page);
}
AlbumCoverLoader *Application::album_cover_loader() const {
return p_->album_cover_loader_.get();
}
Appearance *Application::appearance() const { return p_->appearance_.get(); } Appearance *Application::appearance() const { return p_->appearance_.get(); }
CoverProviders *Application::cover_providers() const {
return p_->cover_providers_.get();
}
CurrentArtLoader *Application::current_art_loader() const {
return p_->current_art_loader_.get();
}
Database *Application::database() const { return p_->database_.get(); } Database *Application::database() const { return p_->database_.get(); }
TaskManager *Application::task_manager() const { return p_->task_manager_.get(); }
#ifndef Q_OS_WIN
DeviceManager *Application::device_manager() const {
return p_->device_manager_.get();
}
#endif
SCollection *Application::collection() const { return p_->collection_.get(); }
CollectionBackend *Application::collection_backend() const {
return collection()->backend();
}
CollectionModel *Application::collection_model() const { return collection()->model(); }
Player *Application::player() const { return p_->player_.get(); } Player *Application::player() const { return p_->player_.get(); }
EngineDevice *Application::enginedevice() const { return p_->enginedevice_.get(); }
PlaylistBackend *Application::playlist_backend() const { #ifndef Q_OS_WIN
return p_->playlist_backend_.get(); DeviceManager *Application::device_manager() const { return p_->device_manager_.get(); }
} #endif
SCollection *Application::collection() const { return p_->collection_.get(); }
PlaylistManager *Application::playlist_manager() const { CollectionBackend *Application::collection_backend() const { return collection()->backend(); }
return p_->playlist_manager_.get(); CollectionModel *Application::collection_model() const { return collection()->model(); }
} AlbumCoverLoader *Application::album_cover_loader() const { return p_->album_cover_loader_.get(); }
CoverProviders *Application::cover_providers() const { return p_->cover_providers_.get(); }
TagReaderClient *Application::tag_reader_client() const { CurrentArtLoader *Application::current_art_loader() const { return p_->current_art_loader_.get(); }
return p_->tag_reader_client_.get(); LyricsProviders *Application::lyrics_providers() const { return p_->lyrics_providers_.get(); }
} PlaylistBackend *Application::playlist_backend() const { return p_->playlist_backend_.get(); }
PlaylistManager *Application::playlist_manager() const { return p_->playlist_manager_.get(); }
TaskManager *Application::task_manager() const { InternetServices *Application::internet_services() const { return p_->internet_services_.get(); }
return p_->task_manager_.get(); #ifdef HAVE_STREAM_TIDAL
} InternetSearch *Application::tidal_search() const { return p_->tidal_search_.get(); }
#endif
EngineDevice *Application::enginedevice() const { #ifdef HAVE_STREAM_DEEZER
return p_->enginedevice_.get(); InternetSearch *Application::deezer_search() const { return p_->deezer_search_.get(); }
} #endif
InternetModel* Application::internet_model() const {
return p_->internet_model_.get();
}
TidalSearch* Application::tidal_search() const {
return p_->tidal_search_.get();
}
LyricsProviders *Application::lyrics_providers() const {
return p_->lyrics_providers_.get();
}

View File

@@ -33,6 +33,8 @@
#include "settings/settingsdialog.h" #include "settings/settingsdialog.h"
using std::unique_ptr;
class TaskManager; class TaskManager;
class ApplicationImpl; class ApplicationImpl;
class TagReaderClient; class TagReaderClient;
@@ -51,9 +53,9 @@ class DeviceManager;
class CoverProviders; class CoverProviders;
class AlbumCoverLoader; class AlbumCoverLoader;
class CurrentArtLoader; class CurrentArtLoader;
class InternetModel;
class TidalSearch;
class LyricsProviders; class LyricsProviders;
class InternetServices;
class InternetSearch;
class Application : public QObject { class Application : public QObject {
Q_OBJECT Q_OBJECT
@@ -75,6 +77,8 @@ class Application : public QObject {
#endif #endif
SCollection *collection() const; SCollection *collection() const;
CollectionBackend *collection_backend() const;
CollectionModel *collection_model() const;
PlaylistBackend *playlist_backend() const; PlaylistBackend *playlist_backend() const;
PlaylistManager *playlist_manager() const; PlaylistManager *playlist_manager() const;
@@ -83,14 +87,16 @@ class Application : public QObject {
AlbumCoverLoader *album_cover_loader() const; AlbumCoverLoader *album_cover_loader() const;
CurrentArtLoader *current_art_loader() const; CurrentArtLoader *current_art_loader() const;
CollectionBackend *collection_backend() const;
CollectionModel *collection_model() const;
InternetModel *internet_model() const;
TidalSearch *tidal_search() const;
LyricsProviders *lyrics_providers() const; LyricsProviders *lyrics_providers() const;
InternetServices *internet_services() const;
#ifdef HAVE_STREAM_TIDAL
InternetSearch *tidal_search() const;
#endif
#ifdef HAVE_STREAM_DEEZER
InternetSearch *deezer_search() const;
#endif
void MoveToNewThread(QObject *object); void MoveToNewThread(QObject *object);
void MoveToThread(QObject *object, QThread *thread); void MoveToThread(QObject *object, QThread *thread);

View File

@@ -1,105 +0,0 @@
/*
* Strawberry Music Player
* This file was part of Clementine.
* Copyright 2011, David Sansome <me@davidsansome.com>
*
* 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 CACHEDLIST_H
#define CACHEDLIST_H
#include "config.h"
#include <QList>
#include <QString>
#include <QDateTime>
#include <QSettings>
template <typename T>
class CachedList {
public:
// Use a CachedList when you want to download and save a list of things from a
// remote service, updating it only periodically.
// T must be a registered metatype and must support being stored in
// QSettings. This usually means you have to implement QDataStream streaming
// operators, and use qRegisterMetaTypeStreamOperators.
typedef QList<T> ListType;
CachedList(const QString &settings_group, const QString &name, int cache_duration_secs)
: settings_group_(settings_group), name_(name), cache_duration_secs_(cache_duration_secs) {
}
void Load() {
QSettings s;
s.beginGroup(settings_group_);
last_updated_ = s.value("last_refreshed_" + name_).toDateTime();
data_.clear();
const int count = s.beginReadArray(name_ + "_data");
for (int i = 0; i < count; ++i) {
s.setArrayIndex(i);
data_ << s.value("value").value<T>();
}
s.endArray();
}
void Save() const {
QSettings s;
s.beginGroup(settings_group_);
s.setValue("last_refreshed_" + name_, last_updated_);
s.beginWriteArray(name_ + "_data", data_.size());
for (int i = 0; i < data_.size(); ++i) {
s.setArrayIndex(i);
s.setValue("value", QVariant::fromValue(data_[i]));
}
s.endArray();
}
void Update(const ListType &data) {
data_ = data;
last_updated_ = QDateTime::currentDateTime();
Save();
}
bool IsStale() const {
return last_updated_.isNull() || last_updated_.secsTo(QDateTime::currentDateTime()) > cache_duration_secs_;
}
void Sort() { qSort(data_); }
const ListType &Data() const { return data_; }
operator ListType() const { return data_; }
// Q_FOREACH support
typedef typename ListType::const_iterator const_iterator;
const_iterator begin() const { return data_.begin(); }
const_iterator end() const { return data_.end(); }
private:
const QString settings_group_;
const QString name_;
const int cache_duration_secs_;
QDateTime last_updated_;
ListType data_;
};
#endif // CACHEDLIST_H

View File

@@ -283,7 +283,11 @@ QSqlDatabase Database::Connect() {
QVariant v = db.driver()->handle(); QVariant v = db.driver()->handle();
if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) { if (v.isValid() && qstrcmp(v.typeName(), "sqlite3*") == 0) {
sqlite3 *handle = *static_cast<sqlite3**>(v.data()); sqlite3 *handle = *static_cast<sqlite3**>(v.data());
if (handle) sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, NULL); if (handle) {
int result = sqlite3_db_config(handle, SQLITE_DBCONFIG_ENABLE_FTS3_TOKENIZER, 1, NULL);
if (result != SQLITE_OK) qLog(Fatal) << "Unable to enable FTS3 tokenizer";
}
else qLog(Fatal) << "Unable to enable FTS3 tokenizer";
} }
#endif #endif
QSqlQuery set_fts_tokenizer(db); QSqlQuery set_fts_tokenizer(db);

View File

@@ -58,6 +58,7 @@
#include <QString> #include <QString>
#include <QImage> #include <QImage>
#include <QSettings> #include <QSettings>
#include <QLoggingCategory>
#include <QtDebug> #include <QtDebug>
#ifdef HAVE_DBUS #ifdef HAVE_DBUS
# include <QDBusArgument> # include <QDBusArgument>
@@ -200,6 +201,11 @@ int main(int argc, char* argv[]) {
// Resources // Resources
Q_INIT_RESOURCE(data); Q_INIT_RESOURCE(data);
Q_INIT_RESOURCE(icons);
#ifdef DEBUG
QLoggingCategory::defaultCategory()->setEnabled(QtDebugMsg, true);
#endif
Application app; Application app;

View File

@@ -29,6 +29,7 @@
#include <QApplication> #include <QApplication>
#include <QObject> #include <QObject>
#include <QWidget> #include <QWidget>
#include <QSystemTrayIcon>
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include <QByteArray> #include <QByteArray>
#include <QDir> #include <QDir>
@@ -111,8 +112,8 @@
#include "playlist/playlistmanager.h" #include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h" #include "playlist/playlistsequence.h"
#include "playlist/playlistview.h" #include "playlist/playlistview.h"
#include "playlist/queue.h" #include "queue/queue.h"
#include "playlist/queuemanager.h" #include "queue/queueview.h"
#include "playlistparsers/playlistparser.h" #include "playlistparsers/playlistparser.h"
#include "analyzer/analyzercontainer.h" #include "analyzer/analyzercontainer.h"
#include "equalizer/equalizer.h" #include "equalizer/equalizer.h"
@@ -128,12 +129,20 @@
# include "device/deviceviewcontainer.h" # include "device/deviceviewcontainer.h"
#endif #endif
#include "transcoder/transcodedialog.h" #include "transcoder/transcodedialog.h"
#include "settings/settingsdialog.h"
#include "settings/behavioursettingspage.h" #include "settings/behavioursettingspage.h"
#include "settings/playbacksettingspage.h" #include "settings/playbacksettingspage.h"
#include "settings/playlistsettingspage.h" #include "settings/playlistsettingspage.h"
#include "settings/settingsdialog.h" #ifdef HAVE_STREAM_TIDAL
# include "settings/tidalsettingspage.h"
#endif
#ifdef HAVE_STREAM_DEEZER
# include "settings/deezersettingspage.h"
#endif
#include "tidal/tidalsearchview.h" #include "internet/internetservices.h"
#include "internet/internetservice.h"
#include "internet/internetsearchview.h"
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
# include "musicbrainz/tagfetcher.h" # include "musicbrainz/tagfetcher.h"
@@ -143,6 +152,10 @@
# include "core/macsystemtrayicon.h" # include "core/macsystemtrayicon.h"
#endif #endif
using std::bind;
using std::floor;
using std::stable_sort;
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
// Non exported mac-specific function. // Non exported mac-specific function.
void qt_mac_set_dock_menu(QMenu*); void qt_mac_set_dock_menu(QMenu*);
@@ -152,7 +165,7 @@ const char *MainWindow::kSettingsGroup = "MainWindow";
const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)"); const char *MainWindow::kAllFilesFilterSpec = QT_TR_NOOP("All Files (*)");
namespace { namespace {
const int kTrackSliderUpdateTimeMs = 40; const int kTrackSliderUpdateTimeMs = 200;
const int kTrackPositionUpdateTimeMs = 1000; const int kTrackPositionUpdateTimeMs = 1000;
} }
@@ -174,6 +187,7 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
device_view_(device_view_container_->view()), device_view_(device_view_container_->view()),
#endif #endif
playlist_list_(new PlaylistListContainer(this)), playlist_list_(new PlaylistListContainer(this)),
queue_view_(new QueueView(this)),
settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)), settings_dialog_(std::bind(&MainWindow::CreateSettingsDialog, this)),
cover_manager_([=]() { cover_manager_([=]() {
AlbumCoverManager *cover_manager = new AlbumCoverManager(app, app->collection_backend()); AlbumCoverManager *cover_manager = new AlbumCoverManager(app, app->collection_backend());
@@ -193,12 +207,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
return dialog; return dialog;
}), }),
#endif #endif
queue_manager_([=]() { #ifdef HAVE_STREAM_TIDAL
QueueManager *manager = new QueueManager; tidal_search_view_(new InternetSearchView(app_, app_->tidal_search(), TidalSettingsPage::kSettingsGroup, SettingsDialog::Page_Tidal, this)),
manager->SetPlaylistManager(app->playlist_manager()); #endif
return manager; #ifdef HAVE_STREAM_DEEZER
}), deezer_search_view_(new InternetSearchView(app_, app_->deezer_search(), DeezerSettingsPage::kSettingsGroup, SettingsDialog::Page_Deezer, this)),
tidal_search_view_(new TidalSearchView(app_, this)), #endif
playlist_menu_(new QMenu(this)), playlist_menu_(new QMenu(this)),
playlist_add_to_another_(nullptr), playlist_add_to_another_(nullptr),
playlistitem_actions_separator_(nullptr), playlistitem_actions_separator_(nullptr),
@@ -242,24 +256,29 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
ui_->volume->setValue(volume); ui_->volume->setValue(volume);
VolumeChanged(volume); VolumeChanged(volume);
// Initialise the tidal search widget // Initialise the search widget
StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker()); StyleHelper::setBaseColor(palette().color(QPalette::Highlight).darker());
// Add tabs to the fancy tab widget // Add tabs to the fancy tab widget
ui_->tabs->addTab(context_view_, IconLoader::Load("strawberry"), tr("Context")); ui_->tabs->addTab(context_view_, IconLoader::Load("strawberry"), "Context");
ui_->tabs->addTab(collection_view_, IconLoader::Load("vinyl"), tr("Collection")); ui_->tabs->addTab(collection_view_, IconLoader::Load("vinyl"), "Collection");
ui_->tabs->addTab(file_view_, IconLoader::Load("document-open"), tr("Files")); ui_->tabs->addTab(file_view_, IconLoader::Load("document-open"), "Files");
ui_->tabs->addTab(playlist_list_, IconLoader::Load("view-media-playlist"), tr("Playlists")); ui_->tabs->addTab(playlist_list_, IconLoader::Load("view-media-playlist"), "Playlists");
ui_->tabs->addTab(queue_view_, IconLoader::Load("footsteps"), "Queue");
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
ui_->tabs->addTab(device_view_, IconLoader::Load("device"), tr("Devices")); ui_->tabs->addTab(device_view_, IconLoader::Load("device"), "Devices");
#endif
#ifdef HAVE_STREAM_TIDAL
ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), "Tidal");
#endif
#ifdef HAVE_STREAM_DEEZER
ui_->tabs->addTab(deezer_search_view_, IconLoader::Load("deezer"), "Deezer");
#endif #endif
ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), tr("Tidal", "Tidal"));
//ui_->tabs->AddSpacer();
// Add the playing widget to the fancy tab widget // Add the playing widget to the fancy tab widget
ui_->tabs->addBottomWidget(ui_->widget_playing); ui_->tabs->addBottomWidget(ui_->widget_playing);
//ui_->tabs->SetBackgroundPixmap(QPixmap(":/pictures/strawberry-background.png")); //ui_->tabs->SetBackgroundPixmap(QPixmap(":/pictures/strawberry-background.png"));
ui_->tabs->loadSettings(kSettingsGroup);
track_position_timer_->setInterval(kTrackPositionUpdateTimeMs); track_position_timer_->setInterval(kTrackPositionUpdateTimeMs);
connect(track_position_timer_, SIGNAL(timeout()), SLOT(UpdateTrackPosition())); connect(track_position_timer_, SIGNAL(timeout()), SLOT(UpdateTrackPosition()));
@@ -338,7 +357,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
// Configure // Configure
ui_->action_cover_manager->setIcon(IconLoader::Load("document-download")); ui_->action_cover_manager->setIcon(IconLoader::Load("document-download"));
ui_->action_queue_manager->setIcon(IconLoader::Load("footsteps"));
ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename")); ui_->action_edit_track->setIcon(IconLoader::Load("edit-rename"));
ui_->action_equalizer->setIcon(IconLoader::Load("equalizer")); ui_->action_equalizer->setIcon(IconLoader::Load("equalizer"));
ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh")); ui_->action_update_collection->setIcon(IconLoader::Load("view-refresh"));
@@ -391,7 +409,6 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
connect(ui_->action_jump, SIGNAL(triggered()), ui_->playlist->view(), SLOT(JumpToCurrentlyPlayingTrack())); connect(ui_->action_jump, SIGNAL(triggered()), ui_->playlist->view(), SLOT(JumpToCurrentlyPlayingTrack()));
connect(ui_->action_update_collection, SIGNAL(triggered()), app_->collection(), SLOT(IncrementalScan())); connect(ui_->action_update_collection, SIGNAL(triggered()), app_->collection(), SLOT(IncrementalScan()));
connect(ui_->action_full_collection_scan, SIGNAL(triggered()), app_->collection(), SLOT(FullScan())); connect(ui_->action_full_collection_scan, SIGNAL(triggered()), app_->collection(), SLOT(FullScan()));
connect(ui_->action_queue_manager, SIGNAL(triggered()), SLOT(ShowQueueManager()));
//connect(ui_->action_add_files_to_transcoder, SIGNAL(triggered()), SLOT(AddFilesToTranscoder())); //connect(ui_->action_add_files_to_transcoder, SIGNAL(triggered()), SLOT(AddFilesToTranscoder()));
// Playlist view actions // Playlist view actions
@@ -512,8 +529,12 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
collection_view_->filter()->AddMenuAction(separator); collection_view_->filter()->AddMenuAction(separator);
collection_view_->filter()->AddMenuAction(collection_config_action); collection_view_->filter()->AddMenuAction(collection_config_action);
// Tidal #ifdef HAVE_STREAM_TIDAL
connect(tidal_search_view_, SIGNAL(AddToPlaylist(QMimeData*)), SLOT(AddToPlaylist(QMimeData*))); 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 menu
playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay())); playlist_play_pause_ = playlist_menu_->addAction(tr("Play"), this, SLOT(PlaylistPlay()));
@@ -574,15 +595,16 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
mac::SetApplicationHandler(this); mac::SetApplicationHandler(this);
#endif #endif
// Tray icon // Tray icon
tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_quit); if (tray_icon_) {
tray_icon_->SetupMenu(ui_->action_previous_track, ui_->action_play_pause, ui_->action_stop, ui_->action_stop_after_this_track, ui_->action_next_track, ui_->action_mute, ui_->action_quit);
connect(tray_icon_, SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause())); connect(tray_icon_, SIGNAL(PlayPause()), app_->player(), SLOT(PlayPause()));
connect(tray_icon_, SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward())); connect(tray_icon_, SIGNAL(SeekForward()), app_->player(), SLOT(SeekForward()));
connect(tray_icon_, SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward())); connect(tray_icon_, SIGNAL(SeekBackward()), app_->player(), SLOT(SeekBackward()));
connect(tray_icon_, SIGNAL(NextTrack()), app_->player(), SLOT(Next())); connect(tray_icon_, SIGNAL(NextTrack()), app_->player(), SLOT(Next()));
connect(tray_icon_, SIGNAL(PreviousTrack()), app_->player(), SLOT(Previous())); connect(tray_icon_, SIGNAL(PreviousTrack()), app_->player(), SLOT(Previous()));
connect(tray_icon_, SIGNAL(ShowHide()), SLOT(ToggleShowHide())); connect(tray_icon_, SIGNAL(ShowHide()), SLOT(ToggleShowHide()));
connect(tray_icon_, SIGNAL(ChangeVolume(int)), SLOT(VolumeWheelEvent(int))); connect(tray_icon_, SIGNAL(ChangeVolume(int)), SLOT(VolumeWheelEvent(int)));
}
// Windows 7 thumbbar buttons // Windows 7 thumbbar buttons
thumbbar_->SetActions(QList<QAction*>() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr); // spacer thumbbar_->SetActions(QList<QAction*>() << ui_->action_previous_track << ui_->action_play_pause << ui_->action_stop << ui_->action_next_track << nullptr); // spacer
@@ -672,6 +694,8 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
// Load playlists // Load playlists
app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist); app_->playlist_manager()->Init(app_->collection_backend(), app_->playlist_backend(), ui_->playlist_sequence, ui_->playlist);
queue_view_->SetPlaylistManager(app_->playlist_manager());
// This connection must be done after the playlists have been initialized. // This connection must be done after the playlists have been initialized.
connect(this, SIGNAL(StopAfterToggled(bool)), osd_, SLOT(StopAfterToggle(bool))); connect(this, SIGNAL(StopAfterToggled(bool)), osd_, SLOT(StopAfterToggle(bool)));
@@ -698,7 +722,10 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt()); ui_->tabs->setCurrentIndex(settings_.value("current_tab", 1 /* Collection tab */ ).toInt());
FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar; FancyTabWidget::Mode default_mode = FancyTabWidget::Mode_LargeSidebar;
ui_->tabs->SetMode(FancyTabWidget::Mode(settings_.value("tab_mode", default_mode).toInt())); int tab_mode_int = settings_.value("tab_mode", default_mode).toInt();
FancyTabWidget::Mode tab_mode = FancyTabWidget::Mode(tab_mode_int);
if (tab_mode == FancyTabWidget::Mode_None) tab_mode = default_mode;
ui_->tabs->SetMode(tab_mode);
file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString()); file_view_->SetPath(settings_.value("file_path", QDir::homePath()).toString());
TabSwitched(); TabSwitched();
@@ -708,43 +735,38 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
ReloadSettings(); ReloadSettings();
// Tidal search shortcut
QAction *tidal_search_action = new QAction(this);
tidal_search_action->setShortcuts(QList<QKeySequence>() << QKeySequence("Ctrl+F") << QKeySequence("Ctrl+L"));
addAction(tidal_search_action);
connect(tidal_search_action, SIGNAL(triggered()), SLOT(FocusTidalSearchField()));
// Reload pretty OSD to avoid issues with fonts // Reload pretty OSD to avoid issues with fonts
osd_->ReloadPrettyOSDSettings(); osd_->ReloadPrettyOSDSettings();
// Reload playlist settings, for BG and glowing // Reload playlist settings, for BG and glowing
ui_->playlist->view()->ReloadSettings(); ui_->playlist->view()->ReloadSettings();
#ifdef Q_OS_MACOS // Always show mainwindow on startup if on macos
#ifndef Q_OS_MACOS show();
#else
QSettings settings; QSettings settings;
settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); settings.beginGroup(BehaviourSettingsPage::kSettingsGroup);
StartupBehaviour behaviour = StartupBehaviour(settings.value("startupbehaviour", Startup_Remember).toInt()); StartupBehaviour behaviour = StartupBehaviour(settings.value("startupbehaviour", Startup_Remember).toInt());
settings.endGroup(); settings.endGroup();
bool hidden = settings_.value("hidden", false).toBool(); bool hidden = settings_.value("hidden", false).toBool();
if (hidden && (!QSystemTrayIcon::isSystemTrayAvailable() || !tray_icon_ || !tray_icon_->IsVisible())) {
hidden = false;
switch (behaviour) {
case Startup_AlwaysHide: hide(); break;
case Startup_AlwaysShow: show(); break;
case Startup_Remember:
setVisible(!hidden);
break;
}
// Force the window to show in case somehow the config has tray and window set to hide
if (hidden && !tray_icon_->IsVisible()) {
settings_.setValue("hidden", false); settings_.setValue("hidden", false);
show(); show();
} }
#else // Q_OS_MACOS else {
// Always show mainwindow on startup on OS X. switch (behaviour) {
show(); case Startup_AlwaysHide:
hide();
break;
case Startup_AlwaysShow:
show();
break;
case Startup_Remember:
setVisible(!hidden);
break;
}
}
#endif #endif
QShortcut *close_window_shortcut = new QShortcut(this); QShortcut *close_window_shortcut = new QShortcut(this);
@@ -757,9 +779,9 @@ MainWindow::MainWindow(Application *app, SystemTrayIcon *tray_icon, OSD *osd, co
if (!options.contains_play_options()) LoadPlaybackStatus(); if (!options.contains_play_options()) LoadPlaybackStatus();
qLog(Debug) << "Started";
RefreshStyleSheet(); RefreshStyleSheet();
qLog(Debug) << "Started";
initialised_ = true; initialised_ = true;
} }
@@ -774,16 +796,14 @@ void MainWindow::ReloadSettings() {
QSettings settings; QSettings settings;
#ifndef Q_OS_MACOS #ifndef Q_OS_MACOS
settings.beginGroup(BehaviourSettingsPage::kSettingsGroup); settings.beginGroup(BehaviourSettingsPage::kSettingsGroup);
bool showtrayicon = settings.value("showtrayicon", true).toBool(); bool showtrayicon = settings.value("showtrayicon", QSystemTrayIcon::isSystemTrayAvailable()).toBool();
settings.endGroup(); settings.endGroup();
if (tray_icon_) tray_icon_->SetVisible(showtrayicon);
tray_icon_->SetVisible(showtrayicon); if ((!showtrayicon || !QSystemTrayIcon::isSystemTrayAvailable()) && !isVisible()) show();
if (!showtrayicon && !isVisible()) show();
#endif #endif
settings.beginGroup(PlaylistSettingsPage::kSettingsGroup); settings.beginGroup(BehaviourSettingsPage::kSettingsGroup);
doubleclick_addmode_ = AddBehaviour(settings.value("doubleclick_addmode", AddBehaviour_Append).toInt()); doubleclick_addmode_ = AddBehaviour(settings.value("doubleclick_addmode", AddBehaviour_Append).toInt());
doubleclick_playmode_ = PlayBehaviour(settings.value("doubleclick_playmode", PlayBehaviour_IfStopped).toInt()); doubleclick_playmode_ = PlayBehaviour(settings.value("doubleclick_playmode", PlayBehaviour_IfStopped).toInt());
doubleclick_playlist_addmode_ = PlaylistAddBehaviour(settings.value("doubleclick_playlist_addmode", PlaylistAddBehaviour_Play).toInt()); doubleclick_playlist_addmode_ = PlaylistAddBehaviour(settings.value("doubleclick_playlist_addmode", PlaylistAddBehaviour_Play).toInt());
@@ -794,6 +814,26 @@ void MainWindow::ReloadSettings() {
album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings.value("search_for_cover_auto", true).toBool()); album_cover_choice_controller_->search_cover_auto_action()->setChecked(settings.value("search_for_cover_auto", true).toBool());
settings.endGroup(); settings.endGroup();
#ifdef HAVE_STREAM_TIDAL
settings.beginGroup(TidalSettingsPage::kSettingsGroup);
bool enable_tidal = settings.value("enabled", false).toBool();
settings.endGroup();
if (enable_tidal)
ui_->tabs->addTab(tidal_search_view_, IconLoader::Load("tidal"), "Tidal");
else
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"), "Deezer");
else
ui_->tabs->delTab("Deezer");
#endif
} }
void MainWindow::ReloadAllSettings() { void MainWindow::ReloadAllSettings() {
@@ -807,13 +847,21 @@ void MainWindow::ReloadAllSettings() {
osd_->ReloadSettings(); osd_->ReloadSettings();
collection_view_->ReloadSettings(); collection_view_->ReloadSettings();
ui_->playlist->view()->ReloadSettings(); ui_->playlist->view()->ReloadSettings();
#ifdef HAVE_STREAM_TIDAL
tidal_search_view_->ReloadSettings(); tidal_search_view_->ReloadSettings();
#endif
#ifdef HAVE_STREAM_DEEZER
deezer_search_view_->ReloadSettings();
#endif
} }
void MainWindow::RefreshStyleSheet() { void MainWindow::RefreshStyleSheet() {
setStyleSheet(styleSheet()); QString contents(styleSheet());
setStyleSheet("");
setStyleSheet(contents);
} }
void MainWindow::MediaStopped() { void MainWindow::MediaStopped() {
setWindowTitle("Strawberry Music Player"); setWindowTitle("Strawberry Music Player");
@@ -828,8 +876,10 @@ void MainWindow::MediaStopped() {
track_position_timer_->stop(); track_position_timer_->stop();
track_slider_timer_->stop(); track_slider_timer_->stop();
ui_->track_slider->SetStopped(); ui_->track_slider->SetStopped();
tray_icon_->SetProgress(0); if (tray_icon_) {
tray_icon_->SetStopped(); tray_icon_->SetProgress(0);
tray_icon_->SetStopped();
}
song_playing_ = Song(); song_playing_ = Song();
song_ = Song(); song_ = Song();
@@ -849,7 +899,7 @@ void MainWindow::MediaPaused() {
track_position_timer_->stop(); track_position_timer_->stop();
track_slider_timer_->stop(); track_slider_timer_->stop();
tray_icon_->SetPaused(); if (tray_icon_) tray_icon_->SetPaused();
} }
@@ -860,13 +910,15 @@ void MainWindow::MediaPlaying() {
ui_->action_play_pause->setIcon(IconLoader::Load("media-pause")); ui_->action_play_pause->setIcon(IconLoader::Load("media-pause"));
ui_->action_play_pause->setText(tr("Pause")); ui_->action_play_pause->setText(tr("Pause"));
bool enable_play_pause = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled); bool enable_play_pause(false);
bool can_seek(false);
if (app_->player()->GetCurrentItem()) {
enable_play_pause = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::PauseDisabled);
can_seek = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::SeekDisabled);
}
ui_->action_play_pause->setEnabled(enable_play_pause); ui_->action_play_pause->setEnabled(enable_play_pause);
bool can_seek = !(app_->player()->GetCurrentItem()->options() & PlaylistItem::SeekDisabled);
ui_->track_slider->SetCanSeek(can_seek); ui_->track_slider->SetCanSeek(can_seek);
if (tray_icon_) tray_icon_->SetPlaying(enable_play_pause);
tray_icon_->SetPlaying(enable_play_pause);
track_position_timer_->start(); track_position_timer_->start();
track_slider_timer_->start(); track_slider_timer_->start();
@@ -876,7 +928,7 @@ void MainWindow::MediaPlaying() {
void MainWindow::VolumeChanged(int volume) { void MainWindow::VolumeChanged(int volume) {
ui_->action_mute->setChecked(!volume); ui_->action_mute->setChecked(!volume);
tray_icon_->MuteButtonStateChanged(!volume); if (tray_icon_) tray_icon_->MuteButtonStateChanged(!volume);
} }
void MainWindow::SongChanged(const Song &song) { void MainWindow::SongChanged(const Song &song) {
@@ -884,11 +936,12 @@ void MainWindow::SongChanged(const Song &song) {
song_playing_ = song; song_playing_ = song;
song_ = song; song_ = song;
setWindowTitle(song.PrettyTitleWithArtist()); setWindowTitle(song.PrettyTitleWithArtist());
tray_icon_->SetProgress(0); if (tray_icon_) tray_icon_->SetProgress(0);
} }
void MainWindow::TrackSkipped(PlaylistItemPtr item) { void MainWindow::TrackSkipped(PlaylistItemPtr item) {
// If it was a collection item then we have to increment its skipped count in the database. // If it was a collection item then we have to increment its skipped count in the database.
if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) { if (item && item->IsLocalCollectionItem() && item->Metadata().id() != -1) {
@@ -905,6 +958,7 @@ void MainWindow::TrackSkipped(PlaylistItemPtr item) {
app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage); app_->collection_backend()->IncrementSkipCountAsync(song.id(), percentage);
} }
} }
} }
void MainWindow::changeEvent(QEvent *event) { void MainWindow::changeEvent(QEvent *event) {
@@ -913,16 +967,18 @@ void MainWindow::changeEvent(QEvent *event) {
} }
void MainWindow::resizeEvent(QResizeEvent *event) { void MainWindow::resizeEvent(QResizeEvent *event) {
if (!initialised_) return;
SaveGeometry(); SaveGeometry();
} }
void MainWindow::TabSwitched() { void MainWindow::TabSwitched() {
if (ui_->tabs->currentIndex() > 0) if (ui_->tabs->tabBar()->tabData(ui_->tabs->currentIndex()).toString().toLower() == "context")
ui_->widget_playing->SetEnabled();
else
ui_->widget_playing->SetDisabled(); ui_->widget_playing->SetDisabled();
else
ui_->widget_playing->SetEnabled();
if (!initialised_) return;
SaveGeometry(); SaveGeometry();
} }
@@ -936,6 +992,7 @@ void MainWindow::SaveGeometry() {
settings_.setValue("splitter_state", ui_->splitter->saveState()); settings_.setValue("splitter_state", ui_->splitter->saveState());
settings_.setValue("current_tab", ui_->tabs->currentIndex()); settings_.setValue("current_tab", ui_->tabs->currentIndex());
settings_.setValue("tab_mode", ui_->tabs->mode()); settings_.setValue("tab_mode", ui_->tabs->mode());
ui_->tabs->saveSettings(kSettingsGroup);
} }
@@ -1078,7 +1135,7 @@ void MainWindow::closeEvent(QCloseEvent *event) {
bool keep_running = settings.value("keeprunning", false).toBool(); bool keep_running = settings.value("keeprunning", false).toBool();
settings.endGroup(); settings.endGroup();
if (keep_running && event->spontaneous()) { if (keep_running && event->spontaneous() && QSystemTrayIcon::isSystemTrayAvailable()) {
event->ignore(); event->ignore();
SetHiddenInTray(true); SetHiddenInTray(true);
} }
@@ -1111,7 +1168,7 @@ void MainWindow::Seeked(qlonglong microseconds) {
const int position = microseconds / kUsecPerSec; const int position = microseconds / kUsecPerSec;
const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec; const int length = app_->player()->GetCurrentItem()->Metadata().length_nanosec() / kNsecPerSec;
tray_icon_->SetProgress(double(position) / length * 100); if (tray_icon_) tray_icon_->SetProgress(double(position) / length * 100);
} }
@@ -1121,6 +1178,7 @@ void MainWindow::UpdateTrackPosition() {
//Playlist *playlist = app_->playlist_manager()->active(); //Playlist *playlist = app_->playlist_manager()->active();
PlaylistItemPtr item(app_->player()->GetCurrentItem()); PlaylistItemPtr item(app_->player()->GetCurrentItem());
if (!item) return;
const int position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5); const int position = std::floor(float(app_->player()->engine()->position_nanosec()) / kNsecPerSec + 0.5);
const int length = item->Metadata().length_nanosec() / kNsecPerSec; const int length = item->Metadata().length_nanosec() / kNsecPerSec;
@@ -1132,9 +1190,8 @@ void MainWindow::UpdateTrackPosition() {
} }
// Update the tray icon every 10 seconds // Update the tray icon every 10 seconds
if (position % 10 == 0) { if (position % 10 == 0 && tray_icon_) tray_icon_->SetProgress(double(position) / length * 100);
tray_icon_->SetProgress(double(position) / length * 100);
}
} }
void MainWindow::UpdateTrackSliderPosition() { void MainWindow::UpdateTrackSliderPosition() {
@@ -1344,10 +1401,6 @@ void MainWindow::PlaylistRightClick(const QPoint &global_pos, const QModelIndex
#endif #endif
playlist_open_in_browser_->setVisible(false); playlist_open_in_browser_->setVisible(false);
//qLog(Debug) << "selected" << selected;
//qLog(Debug) << "in_queue" << in_queue << "not_in_queue" << not_in_queue;
//qLog(Debug) << "in_skipped" << in_skipped << "not_in_skipped" << not_in_skipped;
if (selected < 1) { if (selected < 1) {
playlist_queue_->setVisible(false); playlist_queue_->setVisible(false);
playlist_skip_->setVisible(false); playlist_skip_->setVisible(false);
@@ -1471,6 +1524,7 @@ void MainWindow::PlaylistStopAfter() {
} }
void MainWindow::EditTracks() { void MainWindow::EditTracks() {
SongList songs; SongList songs;
PlaylistItemList items; PlaylistItemList items;
@@ -1489,9 +1543,11 @@ void MainWindow::EditTracks() {
//EnsureEditTagDialogCreated(); //EnsureEditTagDialogCreated();
edit_tag_dialog_->SetSongs(songs, items); edit_tag_dialog_->SetSongs(songs, items);
edit_tag_dialog_->show(); edit_tag_dialog_->show();
} }
void MainWindow::EditTagDialogAccepted() { void MainWindow::EditTagDialogAccepted() {
for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) { for (PlaylistItemPtr item : edit_tag_dialog_->playlist_items()) {
item->Reload(); item->Reload();
} }
@@ -1500,14 +1556,16 @@ void MainWindow::EditTagDialogAccepted() {
ui_->playlist->view()->update(); ui_->playlist->view()->update();
app_->playlist_manager()->current()->Save(); app_->playlist_manager()->current()->Save();
} }
void MainWindow::RenumberTracks() { void MainWindow::RenumberTracks() {
QModelIndexList indexes =ui_->playlist->view()->selectionModel()->selection().indexes(); QModelIndexList indexes =ui_->playlist->view()->selectionModel()->selection().indexes();
int track = 1; int track = 1;
// Get the index list in order // Get the index list in order
qStableSort(indexes); std::stable_sort(indexes.begin(), indexes.end());
// if first selected song has a track number set, start from that offset // if first selected song has a track number set, start from that offset
if (!indexes.isEmpty()) { if (!indexes.isEmpty()) {
@@ -1532,6 +1590,7 @@ void MainWindow::RenumberTracks() {
} }
track++; track++;
} }
} }
void MainWindow::SongSaveComplete(TagReaderReply *reply,const QPersistentModelIndex &index) { void MainWindow::SongSaveComplete(TagReaderReply *reply,const QPersistentModelIndex &index) {
@@ -1542,6 +1601,7 @@ void MainWindow::SongSaveComplete(TagReaderReply *reply,const QPersistentModelIn
} }
void MainWindow::SelectionSetValue() { void MainWindow::SelectionSetValue() {
Playlist::Column column = (Playlist::Column)playlist_menu_index_.column(); Playlist::Column column = (Playlist::Column)playlist_menu_index_.column();
QVariant column_value =app_->playlist_manager()->current()->data(playlist_menu_index_); QVariant column_value =app_->playlist_manager()->current()->data(playlist_menu_index_);
@@ -1560,9 +1620,11 @@ void MainWindow::SelectionSetValue() {
NewClosure(reply, SIGNAL(Finished(bool)), this, SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),reply, QPersistentModelIndex(source_index)); NewClosure(reply, SIGNAL(Finished(bool)), this, SLOT(SongSaveComplete(TagReaderReply*, QPersistentModelIndex)),reply, QPersistentModelIndex(source_index));
} }
} }
} }
void MainWindow::EditValue() { void MainWindow::EditValue() {
QModelIndex current = ui_->playlist->view()->currentIndex(); QModelIndex current = ui_->playlist->view()->currentIndex();
if (!current.isValid()) return; if (!current.isValid()) return;
@@ -1605,9 +1667,11 @@ void MainWindow::AddFile() {
MimeData *data = new MimeData; MimeData *data = new MimeData;
data->setUrls(urls); data->setUrls(urls);
AddToPlaylist(data); AddToPlaylist(data);
} }
void MainWindow::AddFolder() { void MainWindow::AddFolder() {
// Last used directory // Last used directory
QString directory =settings_.value("add_folder_path", QDir::currentPath()).toString(); QString directory =settings_.value("add_folder_path", QDir::currentPath()).toString();
@@ -1622,6 +1686,7 @@ void MainWindow::AddFolder() {
MimeData *data = new MimeData; MimeData *data = new MimeData;
data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(QFileInfo(directory).canonicalFilePath())); data->setUrls(QList<QUrl>() << QUrl::fromLocalFile(QFileInfo(directory).canonicalFilePath()));
AddToPlaylist(data); AddToPlaylist(data);
} }
void MainWindow::AddCDTracks() { void MainWindow::AddCDTracks() {
@@ -2152,20 +2217,12 @@ void MainWindow::CheckFullRescanRevisions() {
} }
message += "</ul>" + tr("Would you like to run a full rescan right now?"); message += "</ul>" + tr("Would you like to run a full rescan right now?");
if(QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { if (QMessageBox::question(this, tr("Collection rescan notice"), message, QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
app_->collection()->FullScan(); app_->collection()->FullScan();
} }
} }
} }
void MainWindow::ShowQueueManager() {
//if (!queue_manager_) {
//queue_manager_.reset(new QueueManager);
//queue_manager_->SetPlaylistManager(app_->playlist_manager());
//}
queue_manager_->show();
}
void MainWindow::PlaylistViewSelectionModelChanged() { void MainWindow::PlaylistViewSelectionModelChanged() {
connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex))); connect(ui_->playlist->view()->selectionModel(),SIGNAL(currentChanged(QModelIndex, QModelIndex)), SLOT(PlaylistCurrentChanged(QModelIndex)));
@@ -2193,8 +2250,8 @@ bool MainWindow::winEvent(MSG *msg, long*) {
void MainWindow::Exit() { void MainWindow::Exit() {
SaveGeometry();
SavePlaybackStatus(); SavePlaybackStatus();
//settings_.setValue("show_sidebar", ui_->action_toggle_show_sidebar->isChecked());
if (app_->player()->engine()->is_fadeout_enabled()) { if (app_->player()->engine()->is_fadeout_enabled()) {
// To shut down the application when fadeout will be finished // To shut down the application when fadeout will be finished
@@ -2202,7 +2259,7 @@ void MainWindow::Exit() {
if (app_->player()->GetState() == Engine::Playing) { if (app_->player()->GetState() == Engine::Playing) {
app_->player()->Stop(); app_->player()->Stop();
hide(); hide();
tray_icon_->SetVisible(false); if (tray_icon_) tray_icon_->SetVisible(false);
return; // Don't quit the application now: wait for the fadeout finished signal return; // Don't quit the application now: wait for the fadeout finished signal
} }
} }
@@ -2309,39 +2366,6 @@ void MainWindow::keyPressEvent(QKeyEvent *event) {
} }
} }
void MainWindow::FocusTidalSearchField() {
ui_->tabs->setCurrentWidget(tidal_search_view_);
tidal_search_view_->FocusSearchField();
}
void MainWindow::DoTidalSearch(const QString& query) {
FocusTidalSearchField();
tidal_search_view_->StartSearch(query);
}
void MainWindow::SearchForArtist() {
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
Song song = item->Metadata();
if (!song.albumartist().isEmpty()) {
DoTidalSearch(song.albumartist().simplified());
}
else if (!song.artist().isEmpty()) {
DoTidalSearch(song.artist().simplified());
}
}
void MainWindow::SearchForAlbum() {
PlaylistItemPtr item(app_->playlist_manager()->current()->item_at(playlist_menu_index_.row()));
Song song = item->Metadata();
if (!song.album().isEmpty()) {
DoTidalSearch(song.album().simplified());
}
}
void MainWindow::LoadCoverFromFile() { void MainWindow::LoadCoverFromFile() {
album_cover_choice_controller_->LoadCoverFromFile(&song_); album_cover_choice_controller_->LoadCoverFromFile(&song_);
} }
@@ -2400,4 +2424,3 @@ void MainWindow::GetCoverAutomatically() {
if (search) album_cover_choice_controller_->SearchCoverAutomatically(song_); if (search) album_cover_choice_controller_->SearchCoverAutomatically(song_);
} }

View File

@@ -57,6 +57,8 @@
#include "playlist/playlistitem.h" #include "playlist/playlistitem.h"
#include "settings/settingsdialog.h" #include "settings/settingsdialog.h"
using std::unique_ptr;
class About; class About;
class AlbumCoverManager;; class AlbumCoverManager;;
class Application; class Application;
@@ -76,7 +78,7 @@ class GlobalShortcuts;
class MimeData; class MimeData;
class OrganiseDialog; class OrganiseDialog;
class PlaylistListContainer; class PlaylistListContainer;
class QueueManager; class QueueView;
class Song; class Song;
class SystemTrayIcon; class SystemTrayIcon;
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
@@ -88,7 +90,7 @@ class TranscodeDialog;
#endif #endif
class Ui_MainWindow; class Ui_MainWindow;
class Windows7ThumbBar; class Windows7ThumbBar;
class TidalSearchView; class InternetSearchView;
class MainWindow : public QMainWindow, public PlatformInterface { class MainWindow : public QMainWindow, public PlatformInterface {
Q_OBJECT Q_OBJECT
@@ -250,7 +252,6 @@ signals:
void ShowTranscodeDialog(); void ShowTranscodeDialog();
#endif #endif
void ShowErrorDialog(const QString& message); void ShowErrorDialog(const QString& message);
void ShowQueueManager();
void EnsureSettingsDialogCreated(); void EnsureSettingsDialogCreated();
void EnsureEditTagDialogCreated(); void EnsureEditTagDialogCreated();
SettingsDialog *CreateSettingsDialog(); SettingsDialog *CreateSettingsDialog();
@@ -273,11 +274,6 @@ signals:
void ShowConsole(); void ShowConsole();
void FocusTidalSearchField();
void DoTidalSearch(const QString& query);
void SearchForArtist();
void SearchForAlbum();
void LoadCoverFromFile(); void LoadCoverFromFile();
void SaveCoverToFile(); void SaveCoverToFile();
void LoadCoverFromURL(); void LoadCoverFromURL();
@@ -320,6 +316,7 @@ signals:
DeviceView *device_view_; DeviceView *device_view_;
#endif #endif
PlaylistListContainer *playlist_list_; PlaylistListContainer *playlist_list_;
QueueView *queue_view_;
Lazy<SettingsDialog> settings_dialog_; Lazy<SettingsDialog> settings_dialog_;
Lazy<AlbumCoverManager> cover_manager_; Lazy<AlbumCoverManager> cover_manager_;
@@ -331,7 +328,6 @@ signals:
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
Lazy<OrganiseDialog> organise_dialog_; Lazy<OrganiseDialog> organise_dialog_;
#endif #endif
Lazy<QueueManager> queue_manager_;
#if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT) #if defined(HAVE_GSTREAMER) && defined(HAVE_CHROMAPRINT)
std::unique_ptr<TagFetcher> tag_fetcher_; std::unique_ptr<TagFetcher> tag_fetcher_;
@@ -341,7 +337,8 @@ signals:
PlaylistItemList autocomplete_tag_items_; PlaylistItemList autocomplete_tag_items_;
#endif #endif
TidalSearchView *tidal_search_view_; InternetSearchView *tidal_search_view_;
InternetSearchView *deezer_search_view_;
QAction *collection_show_all_; QAction *collection_show_all_;
QAction *collection_show_duplicates_; QAction *collection_show_duplicates_;

View File

@@ -14,7 +14,7 @@
<string>Strawberry Music Player</string> <string>Strawberry Music Player</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<widget class="QWidget" name="centralWidget"> <widget class="QWidget" name="centralWidget">
@@ -435,7 +435,6 @@
<string>&amp;Tools</string> <string>&amp;Tools</string>
</property> </property>
<addaction name="action_cover_manager"/> <addaction name="action_cover_manager"/>
<addaction name="action_queue_manager"/>
<addaction name="action_equalizer"/> <addaction name="action_equalizer"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="action_update_collection"/> <addaction name="action_update_collection"/>
@@ -553,7 +552,7 @@
</action> </action>
<action name="action_about_strawberry"> <action name="action_about_strawberry">
<property name="icon"> <property name="icon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<property name="text"> <property name="text">
@@ -674,11 +673,6 @@
<string>&amp;Update changed collection folders</string> <string>&amp;Update changed collection folders</string>
</property> </property>
</action> </action>
<action name="action_queue_manager">
<property name="text">
<string>&amp;Queue Manager</string>
</property>
</action>
<action name="action_about_qt"> <action name="action_about_qt">
<property name="text"> <property name="text">
<string>About &amp;Qt</string> <string>About &amp;Qt</string>
@@ -782,6 +776,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@@ -60,7 +60,7 @@
# include "dbus/metatypes.h" # include "dbus/metatypes.h"
#endif #endif
#include "tidal/tidalsearch.h" #include "internet/internetsearch.h"
void RegisterMetaTypes() { void RegisterMetaTypes() {
@@ -115,7 +115,7 @@ void RegisterMetaTypes() {
#endif #endif
#endif #endif
qRegisterMetaType<TidalSearch::ResultList>("TidalSearch::ResultList"); qRegisterMetaType<InternetSearch::ResultList>("InternetSearch::ResultList");
qRegisterMetaType<TidalSearch::Result>("TidalSearch::Result"); qRegisterMetaType<InternetSearch::Result>("InternetSearch::Result");
} }

View File

@@ -59,6 +59,9 @@
#ifdef HAVE_VLC #ifdef HAVE_VLC
# include "engine/vlcengine.h" # include "engine/vlcengine.h"
#endif #endif
#ifdef HAVE_DEEZER
# include "engine/deezerengine.h"
#endif
#include "collection/collectionbackend.h" #include "collection/collectionbackend.h"
#include "playlist/playlist.h" #include "playlist/playlist.h"
@@ -68,8 +71,11 @@
#include "equalizer/equalizer.h" #include "equalizer/equalizer.h"
#include "analyzer/analyzercontainer.h" #include "analyzer/analyzercontainer.h"
#include "settings/backendsettingspage.h" #include "settings/backendsettingspage.h"
#include "settings/playbacksettingspage.h"
#include "settings/behavioursettingspage.h" #include "settings/behavioursettingspage.h"
#include "settings/playlistsettingspage.h" #include "settings/playlistsettingspage.h"
#include "internet/internetservices.h"
#include "internet/internetservice.h"
using std::shared_ptr; using std::shared_ptr;
@@ -81,6 +87,8 @@ Player::Player(Application *app, QObject *parent)
nb_errors_received_(0), nb_errors_received_(0),
volume_before_mute_(50), volume_before_mute_(50),
last_pressed_previous_(QDateTime::currentDateTime()), last_pressed_previous_(QDateTime::currentDateTime()),
continue_on_error_(false),
greyout_(true),
menu_previousmode_(PreviousBehaviour_DontRestart), menu_previousmode_(PreviousBehaviour_DontRestart),
seek_step_sec_(10) { seek_step_sec_(10) {
@@ -101,9 +109,9 @@ Player::~Player() {
settings_.endGroup(); settings_.endGroup();
} }
void Player::CreateEngine(Engine::EngineType enginetype) { Engine::EngineType Player::CreateEngine(Engine::EngineType enginetype) {
Engine::EngineType use_enginetype = Engine::None; Engine::EngineType use_enginetype(Engine::None);
for (int i = 0 ; use_enginetype == Engine::None ; i++) { for (int i = 0 ; use_enginetype == Engine::None ; i++) {
switch(enginetype) { switch(enginetype) {
@@ -131,6 +139,15 @@ void Player::CreateEngine(Engine::EngineType enginetype) {
use_enginetype=Engine::Phonon; use_enginetype=Engine::Phonon;
engine_.reset(new PhononEngine(app_->task_manager())); engine_.reset(new PhononEngine(app_->task_manager()));
break; 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 #endif
default: default:
if (i > 0) { qFatal("No engine available!"); } if (i > 0) { qFatal("No engine available!"); }
@@ -144,7 +161,7 @@ void Player::CreateEngine(Engine::EngineType enginetype) {
s.beginGroup(BackendSettingsPage::kSettingsGroup); s.beginGroup(BackendSettingsPage::kSettingsGroup);
s.setValue("engine", EngineName(use_enginetype)); s.setValue("engine", EngineName(use_enginetype));
s.setValue("output", engine_->DefaultOutput()); s.setValue("output", engine_->DefaultOutput());
s.setValue("device", QVariant("")); s.setValue("device", QVariant());
s.endGroup(); s.endGroup();
} }
@@ -152,6 +169,8 @@ void Player::CreateEngine(Engine::EngineType enginetype) {
qFatal("Failed to create engine!"); qFatal("Failed to create engine!");
} }
return use_enginetype;
} }
void Player::Init() { void Player::Init() {
@@ -161,6 +180,7 @@ void Player::Init() {
analyzer_->SetEngine(engine_.get()); analyzer_->SetEngine(engine_.get());
connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString))); connect(engine_.get(), SIGNAL(Error(QString)), SIGNAL(Error(QString)));
connect(engine_.get(), SIGNAL(FatalError()), SLOT(FatalError()));
connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl))); connect(engine_.get(), SIGNAL(ValidSongRequested(QUrl)), SLOT(ValidSongRequested(QUrl)));
connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl))); connect(engine_.get(), SIGNAL(InvalidSongRequested(QUrl)), SLOT(InvalidSongRequested(QUrl)));
connect(engine_.get(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State))); connect(engine_.get(), SIGNAL(StateChanged(Engine::State)), SLOT(EngineStateChanged(Engine::State)));
@@ -190,6 +210,8 @@ void Player::ReloadSettings() {
QSettings s; QSettings s;
s.beginGroup(PlaylistSettingsPage::kSettingsGroup); s.beginGroup(PlaylistSettingsPage::kSettingsGroup);
continue_on_error_ = s.value("continue_on_error", false).toBool();
greyout_ = s.value("greyout_songs_play", true).toBool();
menu_previousmode_ = PreviousBehaviour(s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt()); menu_previousmode_ = PreviousBehaviour(s.value("menu_previousmode", PreviousBehaviour_DontRestart).toInt());
s.endGroup(); s.endGroup();
@@ -272,7 +294,7 @@ void Player::NextInternal(Engine::TrackChangeFlags change) {
if (app_->playlist_manager()->active()->current_item()) { if (app_->playlist_manager()->active()->current_item()) {
const QUrl url = app_->playlist_manager()->active()->current_item()->Url(); const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
if (url_handlers_.contains(url.scheme())) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
// The next track is already being loaded // The next track is already being loaded
if (url == loading_async_) return; if (url == loading_async_) return;
@@ -283,6 +305,7 @@ void Player::NextInternal(Engine::TrackChangeFlags change) {
} }
NextItem(change); NextItem(change);
} }
void Player::NextItem(Engine::TrackChangeFlags change) { void Player::NextItem(Engine::TrackChangeFlags change) {
@@ -332,6 +355,7 @@ bool Player::HandleStopAfter() {
return true; return true;
} }
return false; return false;
} }
void Player::TrackEnded() { void Player::TrackEnded() {
@@ -343,6 +367,7 @@ void Player::TrackEnded() {
} }
NextInternal(Engine::Auto); NextInternal(Engine::Auto);
} }
void Player::PlayPause() { void Player::PlayPause() {
@@ -476,10 +501,10 @@ int Player::GetVolume() const { return engine_->volume(); }
void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) { void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle) {
if (change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) { if (current_item_ && change == Engine::Manual && engine_->position_nanosec() != engine_->length_nanosec()) {
emit TrackSkipped(current_item_); emit TrackSkipped(current_item_);
const QUrl &url = current_item_->Url(); const QUrl &url = current_item_->Url();
if (url_handlers_.contains(url.scheme())) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
url_handlers_[url.scheme()]->TrackSkipped(); url_handlers_[url.scheme()]->TrackSkipped();
} }
} }
@@ -490,7 +515,6 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices(); if (reshuffle) app_->playlist_manager()->active()->ReshuffleIndices();
app_->playlist_manager()->active()->set_current_row(index); app_->playlist_manager()->active()->set_current_row(index);
if (app_->playlist_manager()->active()->current_row() == -1) { if (app_->playlist_manager()->active()->current_row() == -1) {
// Maybe index didn't exist in the playlist. // Maybe index didn't exist in the playlist.
return; return;
@@ -499,7 +523,7 @@ void Player::PlayAt(int index, Engine::TrackChangeFlags change, bool reshuffle)
current_item_ = app_->playlist_manager()->active()->current_item(); current_item_ = app_->playlist_manager()->active()->current_item();
const QUrl url = current_item_->Url(); const QUrl url = current_item_->Url();
if (url_handlers_.contains(url.scheme())) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
// It's already loading // It's already loading
if (url == loading_async_) return; if (url == loading_async_) return;
@@ -631,7 +655,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. // 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()) { if (app_->playlist_manager()->active()->current_item()) {
const QUrl url = app_->playlist_manager()->active()->current_item()->Url(); const QUrl url = app_->playlist_manager()->active()->current_item()->Url();
if (url_handlers_.contains(url.scheme())) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
url_handlers_[url.scheme()]->TrackAboutToEnd(); url_handlers_[url.scheme()]->TrackAboutToEnd();
return; return;
} }
@@ -664,7 +688,7 @@ void Player::TrackAboutToEnd() {
QUrl url = next_item->Url(); QUrl url = next_item->Url();
// Get the actual track URL rather than the stream URL. // Get the actual track URL rather than the stream URL.
if (url_handlers_.contains(url.scheme())) { if (url_handlers_.contains(url.scheme()) && !(engine_->type() == Engine::Deezer && url.scheme() == "dzmedia")) {
UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url); UrlHandler::LoadResult result = url_handlers_[url.scheme()]->LoadNext(url);
switch (result.type_) { switch (result.type_) {
case UrlHandler::LoadResult::NoMoreTracks: case UrlHandler::LoadResult::NoMoreTracks:
@@ -685,17 +709,25 @@ void Player::TrackAboutToEnd() {
void Player::IntroPointReached() { NextInternal(Engine::Intro); } void Player::IntroPointReached() { NextInternal(Engine::Intro); }
void Player::FatalError() {
nb_errors_received_ = 0;
Stop();
}
void Player::ValidSongRequested(const QUrl &url) { void Player::ValidSongRequested(const QUrl &url) {
emit SongChangeRequestProcessed(url, true); emit SongChangeRequestProcessed(url, true);
} }
void Player::InvalidSongRequested(const QUrl &url) { void Player::InvalidSongRequested(const QUrl &url) {
// first send the notification to others... if (greyout_) emit SongChangeRequestProcessed(url, false);
emit SongChangeRequestProcessed(url, false);
// ... and now when our listeners have completed their processing of the current item we can change if (!continue_on_error_) {
// the current item by skipping to the next song FatalError();
//NextItem(Engine::Auto); return;
}
NextItem(Engine::Auto);
} }
@@ -749,3 +781,7 @@ void Player::UrlHandlerDestroyed(QObject *object) {
} }
} }
void Player::HandleAuthentication() {
emit Authenticated();
}

View File

@@ -114,6 +114,9 @@ class PlayerInterface : public QObject {
// The toggle parameter is true when user requests to toggle visibility for Pretty OSD // The toggle parameter is true when user requests to toggle visibility for Pretty OSD
void ForceShowOSD(Song, bool toogle); void ForceShowOSD(Song, bool toogle);
void Authenticated();
}; };
class Player : public PlayerInterface { class Player : public PlayerInterface {
@@ -131,7 +134,7 @@ class Player : public PlayerInterface {
PreviousBehaviour_Restart = 2 PreviousBehaviour_Restart = 2
}; };
void CreateEngine(Engine::EngineType enginetype); Engine::EngineType CreateEngine(Engine::EngineType enginetype);
void Init(); void Init();
EngineBase *engine() const { return engine_.get(); } EngineBase *engine() const { return engine_.get(); }
@@ -177,6 +180,8 @@ class Player : public PlayerInterface {
void ShowOSD(); void ShowOSD();
void TogglePrettyOSD(); void TogglePrettyOSD();
void HandleAuthentication();
private slots: private slots:
void EngineStateChanged(Engine::State); void EngineStateChanged(Engine::State);
void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle); void EngineMetadataReceived(const Engine::SimpleMetaBundle &bundle);
@@ -188,6 +193,7 @@ class Player : public PlayerInterface {
void NextInternal(Engine::TrackChangeFlags); void NextInternal(Engine::TrackChangeFlags);
void FatalError();
void ValidSongRequested(const QUrl&); void ValidSongRequested(const QUrl&);
void InvalidSongRequested(const QUrl&); void InvalidSongRequested(const QUrl&);
@@ -214,10 +220,11 @@ class Player : public PlayerInterface {
QMap<QString, UrlHandler*> url_handlers_; QMap<QString, UrlHandler*> url_handlers_;
QUrl loading_async_; QUrl loading_async_;
int volume_before_mute_; int volume_before_mute_;
QDateTime last_pressed_previous_; QDateTime last_pressed_previous_;
bool continue_on_error_;
bool greyout_;
PreviousBehaviour menu_previousmode_; PreviousBehaviour menu_previousmode_;
int seek_step_sec_; int seek_step_sec_;

View File

@@ -44,23 +44,12 @@ QtSystemTrayIcon::QtSystemTrayIcon(QObject *parent)
action_play_pause_(nullptr), action_play_pause_(nullptr),
action_stop_(nullptr), action_stop_(nullptr),
action_stop_after_this_track_(nullptr), action_stop_after_this_track_(nullptr),
action_mute_(nullptr) action_mute_(nullptr) {
{
QIcon theme_icon = IconLoader::Load("strawberry", 48); QIcon icon(":/icons/48x48/strawberry.png");
QIcon theme_icon_grey = IconLoader::Load("strawberry-grey", 48);
if (theme_icon.isNull() || theme_icon_grey.isNull()) { normal_icon_ = icon.pixmap(48, QIcon::Normal);
// Load the default icon grey_icon_ = icon.pixmap(48, QIcon::Disabled);
QIcon icon(":/icons/48x48/strawberry.png");
normal_icon_ = icon.pixmap(48, QIcon::Normal);
grey_icon_ = icon.pixmap(48, QIcon::Disabled);
}
else {
// Use the icons from the theme
normal_icon_ = theme_icon.pixmap(48);
grey_icon_ = theme_icon_grey.pixmap(48);
}
tray_->setIcon(normal_icon_); tray_->setIcon(normal_icon_);
tray_->installEventFilter(this); tray_->installEventFilter(this);

View File

@@ -21,6 +21,8 @@
#include "config.h" #include "config.h"
#include <algorithm>
#include <taglib/fileref.h> #include <taglib/fileref.h>
#include <taglib/id3v1genres.h> #include <taglib/id3v1genres.h>
@@ -66,6 +68,8 @@
#include "covermanager/albumcoverloader.h" #include "covermanager/albumcoverloader.h"
#include "tagreadermessages.pb.h" #include "tagreadermessages.pb.h"
using std::sort;
const QStringList Song::kColumns = QStringList() << "title" const QStringList Song::kColumns = QStringList() << "title"
<< "album" << "album"
<< "artist" << "artist"
@@ -298,7 +302,7 @@ uint Song::mtime() const { return d->mtime_; }
uint Song::ctime() const { return d->ctime_; } uint Song::ctime() const { return d->ctime_; }
int Song::filesize() const { return d->filesize_; } int Song::filesize() const { return d->filesize_; }
Song::FileType Song::filetype() const { return d->filetype_; } Song::FileType Song::filetype() const { return d->filetype_; }
bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal; } bool Song::is_stream() const { return d->source_ == Source_Stream || d->source_ == Source_Tidal || d->source_ == Source_Deezer; }
bool Song::is_cdda() const { return d->source_ == Source_CDDA; } bool Song::is_cdda() const { return d->source_ == Source_CDDA; }
bool Song::is_collection_song() const { bool Song::is_collection_song() const {
return !is_cdda() && !is_stream() && id() != -1; return !is_cdda() && !is_stream() && id() != -1;
@@ -384,6 +388,7 @@ QString Song::TextForSource(Source source) {
case Song::Source_Device: return QObject::tr("Device"); case Song::Source_Device: return QObject::tr("Device");
case Song::Source_Stream: return QObject::tr("Stream"); case Song::Source_Stream: return QObject::tr("Stream");
case Song::Source_Tidal: return QObject::tr("Tidal"); case Song::Source_Tidal: return QObject::tr("Tidal");
case Song::Source_Deezer: return QObject::tr("Deezer");
default: return QObject::tr("Unknown"); default: return QObject::tr("Unknown");
} }
@@ -398,6 +403,7 @@ QIcon Song::IconForSource(Source source) {
case Song::Source_Device: return IconLoader::Load("device"); case Song::Source_Device: return IconLoader::Load("device");
case Song::Source_Stream: return IconLoader::Load("applications-internet"); case Song::Source_Stream: return IconLoader::Load("applications-internet");
case Song::Source_Tidal: return IconLoader::Load("tidal"); case Song::Source_Tidal: return IconLoader::Load("tidal");
case Song::Source_Deezer: return IconLoader::Load("deezer");
default: return IconLoader::Load("edit-delete"); default: return IconLoader::Load("edit-delete");
} }
@@ -421,6 +427,7 @@ QString Song::TextForFiletype(FileType filetype) {
case Song::FileType_TrueAudio: return QObject::tr("TrueAudio"); case Song::FileType_TrueAudio: return QObject::tr("TrueAudio");
case Song::FileType_DSF: return QObject::tr("DSF"); case Song::FileType_DSF: return QObject::tr("DSF");
case Song::FileType_DSDIFF: return QObject::tr("DSDIFF"); case Song::FileType_DSDIFF: return QObject::tr("DSDIFF");
case Song::FileType_PCM: return QObject::tr("PCM");
case Song::FileType_CDDA: return QObject::tr("CDDA"); case Song::FileType_CDDA: return QObject::tr("CDDA");
case Song::FileType_Stream: return QObject::tr("Stream"); case Song::FileType_Stream: return QObject::tr("Stream");
case Song::FileType_Unknown: case Song::FileType_Unknown:
@@ -447,6 +454,7 @@ QIcon Song::IconForFiletype(FileType filetype) {
case Song::FileType_TrueAudio: return IconLoader::Load("trueaudio"); case Song::FileType_TrueAudio: return IconLoader::Load("trueaudio");
case Song::FileType_DSF: return IconLoader::Load("dsf"); case Song::FileType_DSF: return IconLoader::Load("dsf");
case Song::FileType_DSDIFF: return IconLoader::Load("dsd"); case Song::FileType_DSDIFF: return IconLoader::Load("dsd");
case Song::FileType_PCM: return IconLoader::Load("pcm");
case Song::FileType_CDDA: return IconLoader::Load("cd"); case Song::FileType_CDDA: return IconLoader::Load("cd");
case Song::FileType_Stream: return IconLoader::Load("applications-internet"); case Song::FileType_Stream: return IconLoader::Load("applications-internet");
case Song::FileType_Unknown: case Song::FileType_Unknown:
@@ -462,6 +470,8 @@ bool Song::IsFileLossless() const {
case Song::FileType_OggFlac: case Song::FileType_OggFlac:
case Song::FileType_WavPack: case Song::FileType_WavPack:
case Song::FileType_AIFF: case Song::FileType_AIFF:
case Song::FileType_DSF:
case Song::FileType_DSDIFF:
return true; return true;
default: default:
return false; return false;
@@ -493,7 +503,7 @@ int CompareSongsName(const Song &song1, const Song &song2) {
void Song::SortSongsListAlphabetically(SongList *songs) { void Song::SortSongsListAlphabetically(SongList *songs) {
Q_ASSERT(songs); Q_ASSERT(songs);
qSort(songs->begin(), songs->end(), CompareSongsName); std::sort(songs->begin(), songs->end(), CompareSongsName);
} }
void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec) { void Song::Init(const QString &title, const QString &artist, const QString &album, qint64 length_nanosec) {
@@ -996,12 +1006,14 @@ void Song::MergeFromSimpleMetaBundle(const Engine::SimpleMetaBundle &bundle) {
if (!bundle.album.isEmpty()) d->album_ = bundle.album; if (!bundle.album.isEmpty()) d->album_ = bundle.album;
if (!bundle.comment.isEmpty()) d->comment_ = bundle.comment; if (!bundle.comment.isEmpty()) d->comment_ = bundle.comment;
if (!bundle.genre.isEmpty()) d->genre_ = bundle.genre; if (!bundle.genre.isEmpty()) d->genre_ = bundle.genre;
if (bundle.bitrate > 0) d->bitrate_ = bundle.bitrate;
if (bundle.samplerate > 0) d->samplerate_ = bundle.samplerate;
if (bundle.bitdepth > 0) d->samplerate_ = bundle.bitdepth;
if (bundle.length > 0) set_length_nanosec(bundle.length); if (bundle.length > 0) set_length_nanosec(bundle.length);
if (bundle.year > 0) d->year_ = bundle.year; if (bundle.year > 0) d->year_ = bundle.year;
if (bundle.tracknr > 0) d->track_ = bundle.tracknr; if (bundle.tracknr > 0) d->track_ = bundle.tracknr;
if (bundle.filetype != FileType_Unknown) d->filetype_ = bundle.filetype;
if (bundle.samplerate > 0) d->samplerate_ = bundle.samplerate;
if (bundle.bitdepth > 0) d->samplerate_ = bundle.bitdepth;
if (bundle.bitrate > 0) d->bitrate_ = bundle.bitrate;
if (!bundle.lyrics.isEmpty()) d->lyrics_ = bundle.lyrics;
} }

View File

@@ -98,6 +98,7 @@ class Song {
Source_Device = 4, Source_Device = 4,
Source_Stream = 5, Source_Stream = 5,
Source_Tidal = 6, Source_Tidal = 6,
Source_Deezer = 7,
}; };
enum FileType { enum FileType {
@@ -117,6 +118,7 @@ class Song {
FileType_TrueAudio = 13, FileType_TrueAudio = 13,
FileType_DSF = 14, FileType_DSF = 14,
FileType_DSDIFF = 15, FileType_DSDIFF = 15,
FileType_PCM = 16,
FileType_CDDA = 90, FileType_CDDA = 90,
FileType_Stream = 91, FileType_Stream = 91,
}; };

View File

@@ -22,6 +22,7 @@
#include <stdlib.h> #include <stdlib.h>
#include <memory> #include <memory>
#include <algorithm>
#ifdef HAVE_GSTREAMER #ifdef HAVE_GSTREAMER
# include <gst/gst.h> # include <gst/gst.h>
@@ -65,6 +66,9 @@
#endif #endif
using std::placeholders::_1; using std::placeholders::_1;
using std::bind;
using std::stable_sort;
using std::shared_ptr;
QSet<QString> SongLoader::sRawUriSchemes; QSet<QString> SongLoader::sRawUriSchemes;
const int SongLoader::kDefaultTimeout = 5000; const int SongLoader::kDefaultTimeout = 5000;
@@ -323,7 +327,7 @@ void SongLoader::LoadLocalDirectory(const QString &filename) {
LoadLocalPartial(it.next()); LoadLocalPartial(it.next());
} }
qStableSort(songs_.begin(), songs_.end(), CompareSongs); std::stable_sort(songs_.begin(), songs_.end(), CompareSongs);
// Load the first song: // Load the first song:
// all songs will be loaded async, but we want the first one in our list to be fully loaded, // all songs will be loaded async, but we want the first one in our list to be fully loaded,

View File

@@ -44,6 +44,8 @@ void StyleSheetLoader::SetStyleSheet(QWidget *widget, const QString &filename) {
void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) { void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) {
if (!widget || !filenames_.contains(widget)) return;
QString filename(filenames_[widget]); QString filename(filenames_[widget]);
// Load the file // Load the file
@@ -88,9 +90,10 @@ void StyleSheetLoader::UpdateStyleSheet(QWidget *widget) {
ReplaceColor(&contents, "LinkVisited", p, QPalette::LinkVisited); ReplaceColor(&contents, "LinkVisited", p, QPalette::LinkVisited);
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
contents.replace("darwin", "*"); contents.replace("macos", "*");
#endif #endif
widget->setStyleSheet("");
widget->setStyleSheet(contents); widget->setStyleSheet(contents);
} }

View File

@@ -23,6 +23,7 @@
#include <cmath> #include <cmath>
#include <QObject> #include <QObject>
#include <QSystemTrayIcon>
#include <QPixmap> #include <QPixmap>
#include <QPainter> #include <QPainter>
#include <QPoint> #include <QPoint>
@@ -105,10 +106,11 @@ void SystemTrayIcon::SetStopped() {
UpdateIcon(); UpdateIcon();
} }
SystemTrayIcon* SystemTrayIcon::CreateSystemTrayIcon(QObject *parent) { SystemTrayIcon *SystemTrayIcon::CreateSystemTrayIcon(QObject *parent) {
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
return new MacSystemTrayIcon(parent); return new MacSystemTrayIcon(parent);
#else #else
return new QtSystemTrayIcon(parent); if (QSystemTrayIcon::isSystemTrayAvailable()) return new QtSystemTrayIcon(parent);
else return nullptr;
#endif #endif
} }

View File

@@ -20,7 +20,7 @@
<string>Export covers</string> <string>Export covers</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@@ -202,6 +202,7 @@
</widget> </widget>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -43,6 +43,8 @@
#include "coverprovider.h" #include "coverprovider.h"
#include "coverproviders.h" #include "coverproviders.h"
using std::stable_sort;
const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 12000; const int AlbumCoverFetcherSearch::kSearchTimeoutMs = 12000;
const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000; const int AlbumCoverFetcherSearch::kImageLoadTimeoutMs = 3000;
const int AlbumCoverFetcherSearch::kTargetSize = 500; const int AlbumCoverFetcherSearch::kTargetSize = 500;
@@ -148,7 +150,7 @@ void AlbumCoverFetcherSearch::AllProvidersFinished() {
// Now we have to load some images and figure out which one is the best. // Now we have to load some images and figure out which one is the best.
// We'll sort the list of results by category, then load the first few images from each category and use some heuristics to score them. // We'll sort the list of results by category, then load the first few images from each category and use some heuristics to score them.
// If no images are good enough we'll keep loading more images until we find one that is or we run out of results. // If no images are good enough we'll keep loading more images until we find one that is or we run out of results.
qStableSort(results_.begin(), results_.end(), CompareProviders); std::stable_sort(results_.begin(), results_.end(), CompareProviders);
FetchMoreImages(); FetchMoreImages();
} }

View File

@@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <memory> #include <memory>
#include <algorithm>
#include <QtGlobal> #include <QtGlobal>
#include <QObject> #include <QObject>
@@ -81,6 +82,8 @@
#include "ui_albumcovermanager.h" #include "ui_albumcovermanager.h"
using std::stable_sort;
const char *AlbumCoverManager::kSettingsGroup = "CoverManager"; const char *AlbumCoverManager::kSettingsGroup = "CoverManager";
AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent, QNetworkAccessManager *network) AlbumCoverManager::AlbumCoverManager(Application *app, CollectionBackend *collection_backend, QWidget *parent, QNetworkAccessManager *network)
@@ -286,7 +289,7 @@ void AlbumCoverManager::Reset() {
new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists); new QListWidgetItem(artist_icon_, tr("Various artists"), ui_->artists, Various_Artists);
QStringList artists(collection_backend_->GetAllArtistsWithAlbums()); QStringList artists(collection_backend_->GetAllArtistsWithAlbums());
qStableSort(artists.begin(), artists.end(), CompareNocase); std::stable_sort(artists.begin(), artists.end(), CompareNocase);
for (const QString &artist : artists) { for (const QString &artist : artists) {
if (artist.isEmpty()) continue; if (artist.isEmpty()) continue;
@@ -326,7 +329,7 @@ void AlbumCoverManager::ArtistChanged(QListWidgetItem *current) {
} }
// Sort by album name. The list is already sorted by sqlite but it was done case sensitively. // Sort by album name. The list is already sorted by sqlite but it was done case sensitively.
qStableSort(albums.begin(), albums.end(), CompareAlbumNameNocase); std::stable_sort(albums.begin(), albums.end(), CompareAlbumNameNocase);
for (const CollectionBackend::Album &info : albums) { for (const CollectionBackend::Album &info : albums) {
// Don't show songs without an album, obviously // Don't show songs without an album, obviously

View File

@@ -14,7 +14,7 @@
<string>Cover Manager</string> <string>Cover Manager</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<widget class="QWidget" name="centralWidget"> <widget class="QWidget" name="centralWidget">
@@ -297,6 +297,7 @@
</tabstops> </tabstops>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

View File

@@ -14,7 +14,7 @@
<string>Load cover from URL</string> <string>Load cover from URL</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@@ -79,6 +79,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -47,7 +47,7 @@ void CoverProviders::RemoveProvider(CoverProvider *provider) {
if (!provider) return; if (!provider) return;
// It's not safe to dereference provider at this pointbecause it might have already been destroyed. // It's not safe to dereference provider at this point because it might have already been destroyed.
QString name; QString name;

View File

@@ -20,6 +20,8 @@
#include "config.h" #include "config.h"
#include <algorithm>
#include <QWidget> #include <QWidget>
#include <QMap> #include <QMap>
#include <QString> #include <QString>
@@ -34,6 +36,8 @@
#include "coversearchstatisticsdialog.h" #include "coversearchstatisticsdialog.h"
#include "ui_coversearchstatisticsdialog.h" #include "ui_coversearchstatisticsdialog.h"
using std::sort;
CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent) CoverSearchStatisticsDialog::CoverSearchStatisticsDialog(QWidget *parent)
: QDialog(parent), ui_(new Ui_CoverSearchStatisticsDialog) { : QDialog(parent), ui_(new Ui_CoverSearchStatisticsDialog) {
@@ -61,7 +65,7 @@ CoverSearchStatisticsDialog::~CoverSearchStatisticsDialog() { delete ui_; }
void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics &statistics) { void CoverSearchStatisticsDialog::Show(const CoverSearchStatistics &statistics) {
QStringList providers(statistics.total_images_by_provider_.keys()); QStringList providers(statistics.total_images_by_provider_.keys());
qSort(providers); std::sort(providers.begin(), providers.end());
ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)") ui_->summary->setText(tr("Got %1 covers out of %2 (%3 failed)")
.arg(statistics.chosen_images_) .arg(statistics.chosen_images_)

View File

@@ -0,0 +1,838 @@
/*
* 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);
quality_ = s.value("quality", "FLAC").toString();
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> &params) {
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);
//qLog(Debug) << json_doc;
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();
}
//qLog(Debug) << json_obj;
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::SearchBy searchby) {
pending_search_id_ = next_pending_search_id_;
pending_search_text_ = text;
pending_searchby_ = 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("Searching...");
QList<Param> parameters;
parameters << Param("q", search_text_);
QString searchparam;
switch (pending_searchby_) {
case InternetSearch::SearchBy_Songs:
searchparam = "search/track";
parameters << Param("limit", QString::number(songssearchlimit_));
break;
case InternetSearch::SearchBy_Albums:
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("No match.");
CheckFinish();
return;
}
//qLog(Debug) << json_data;
for (const QJsonValue &value : json_data) {
//qLog(Debug) << value;
if (!value.isObject()) {
Error("Invalid Json reply, data is not an object.", value);
continue;
}
QJsonObject json_obj = value.toObject();
//qLog(Debug) << json_obj;
if (!json_obj.contains("id") || !json_obj.contains("type")) {
Error("Invalid Json reply, item is missing ID or type.", json_obj);
continue;
}
//int id = json_obj["id"].toInt();
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, 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) {
emit UpdateStatus(QString("Retriving %1 album%2...").arg(albums_requested_).arg(albums_requested_ == 1 ? "" : "s"));
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;
Song first_song;
SongList songs;
for (const QJsonValue &value : json_data) {
Song song = ParseSong(album_ctx->id, album_ctx->album, album_ctx->cover, value);
if (!song.is_valid()) continue;
if (song.disc() >= 2) multidisc = true;
if (song.is_compilation() || (first_song.is_valid() && song.artist() != first_song.artist())) compilation = true;
if (!first_song.is_valid()) first_song = song;
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_cover, const QJsonValue &value) {
if (!value.isObject()) {
Error("Invalid Json reply, track is not an object.", value);
return Song();
}
QJsonObject json_obj = value.toObject();
//qLog(Debug) << json_obj;
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);
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();
}
}

163
src/deezer/deezerservice.h Normal file
View File

@@ -0,0 +1,163 @@
/*
* 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::SearchBy 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>> &params);
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_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_;
QString quality_;
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::SearchBy pending_searchby_;
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

View File

@@ -0,0 +1,72 @@
/*
* 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;
}

View File

@@ -1,7 +1,6 @@
/* /*
* Strawberry Music Player * Strawberry Music Player
* This file was part of Clementine. * Copyright 2018, Jonas Kvinge <jonas@jkvinge.net>
* Copyright 2010, David Sansome <me@davidsansome.com>
* *
* Strawberry is free software: you can redistribute it and/or modify * Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@@ -18,26 +17,40 @@
* *
*/ */
#ifndef INTERNETMIMEDATA_H #ifndef DEEZERURLHANDLER_H
#define INTERNETMIMEDATA_H #define DEEZERURLHANDLER_H
#include "config.h"
#include <QObject> #include <QObject>
#include <QModelIndexList> #include <QString>
#include <QUrl>
#include "core/mimedata.h" #include "core/urlhandler.h"
#include "core/song.h"
#include "deezer/deezerservice.h"
class InternetModel; class Application;
class DeezerService;
class InternetMimeData : public MimeData { class DeezerUrlHandler : public UrlHandler {
Q_OBJECT Q_OBJECT
public: public:
explicit InternetMimeData(const InternetModel *_model) : model(_model) {} 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_;
const InternetModel *model;
QModelIndexList indexes;
}; };
#endif #endif

View File

@@ -64,7 +64,6 @@ ConnectedDevice::ConnectedDevice(const QUrl &url, DeviceLister *lister, const QS
// Create the model // Create the model
model_ = new CollectionModel(backend_, app_, this); model_ = new CollectionModel(backend_, app_, this);
model_->Reset();
} }

View File

@@ -14,7 +14,7 @@
<string>Device Properties</string> <string>Device Properties</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
@@ -453,6 +453,7 @@
</tabstops> </tabstops>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -69,8 +69,7 @@
const int DeviceItemDelegate::kIconPadding = 6; const int DeviceItemDelegate::kIconPadding = 6;
DeviceItemDelegate::DeviceItemDelegate(QObject *parent) DeviceItemDelegate::DeviceItemDelegate(QObject *parent) : CollectionItemDelegate(parent) {}
: CollectionItemDelegate(parent) {}
void DeviceItemDelegate::paint(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const { void DeviceItemDelegate::paint(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &index) const {

View File

@@ -68,7 +68,10 @@ About::About(QWidget *parent):QDialog(parent) {
<< Person(QString::fromUtf8("Bartłomiej Burdukiewicz"), "dev.strikeu@gmail.com") << Person(QString::fromUtf8("Bartłomiej Burdukiewicz"), "dev.strikeu@gmail.com")
<< Person("Andre Siviero", "altsiviero@gmail.com") << Person("Andre Siviero", "altsiviero@gmail.com")
<< Person("Santiago Gil") << Person("Santiago Gil")
<< Person("Tyler Rhodes", "tyler.s.rhodes@gmail.com"); << Person("Tyler Rhodes", "tyler.s.rhodes@gmail.com")
<< Person("Vikram Ambrose", "ambroseworks@gmail.com")
<< Person("David Guillen", "david@davidgf.net")
<< Person("Krzysztof Sobiecki", "sobkas@gmail.com");
QString Title("About Strawberry"); QString Title("About Strawberry");

View File

@@ -12,7 +12,7 @@
<enum>Qt::StrongFocus</enum> <enum>Qt::StrongFocus</enum>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
@@ -32,7 +32,7 @@
<item> <item>
<widget class="QLabel" name="label_icon"> <widget class="QLabel" name="label_icon">
<property name="pixmap"> <property name="pixmap">
<pixmap resource="../../data/data.qrc">:/icons/64x64/strawberry.png</pixmap> <pixmap resource="../../data/icons.qrc">:/icons/64x64/strawberry.png</pixmap>
</property> </property>
<property name="scaledContents"> <property name="scaledContents">
<bool>false</bool> <bool>false</bool>
@@ -145,6 +145,7 @@
</widget> </widget>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -14,7 +14,7 @@
<string>Edit track information</string> <string>Edit track information</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
@@ -861,6 +861,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -21,6 +21,7 @@
#include "config.h" #include "config.h"
#include <memory> #include <memory>
#include <algorithm>
#include <QtGlobal> #include <QtGlobal>
#include <QtConcurrentRun> #include <QtConcurrentRun>
@@ -63,6 +64,9 @@
#include "organiseerrordialog.h" #include "organiseerrordialog.h"
#include "ui_organisedialog.h" #include "ui_organisedialog.h"
using std::shared_ptr;
using std::stable_sort;
const char *OrganiseDialog::kDefaultFormat = "%artist/%album{ (Disc %disc)}/{%track - }%title.%extension"; const char *OrganiseDialog::kDefaultFormat = "%artist/%album{ (Disc %disc)}/{%track - }%title.%extension";
const char *OrganiseDialog::kSettingsGroup = "OrganiseDialog"; const char *OrganiseDialog::kSettingsGroup = "OrganiseDialog";
@@ -111,7 +115,7 @@ OrganiseDialog::OrganiseDialog(TaskManager *task_manager, QWidget *parent)
// Get the titles of the tags to put in the insert menu // Get the titles of the tags to put in the insert menu
QStringList tag_titles = tags.keys(); QStringList tag_titles = tags.keys();
qStableSort(tag_titles); std::stable_sort(tag_titles.begin(), tag_titles.end());
// Build the insert menu // Build the insert menu
QMenu *tag_menu = new QMenu(this); QMenu *tag_menu = new QMenu(this);

View File

@@ -14,7 +14,7 @@
<string>Organise Files</string> <string>Organise Files</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <layout class="QVBoxLayout" name="verticalLayout_2">
@@ -271,6 +271,7 @@
</tabstops> </tabstops>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

View File

@@ -20,6 +20,8 @@
#include "config.h" #include "config.h"
#include <algorithm>
#include <QWidget> #include <QWidget>
#include <QDialog> #include <QDialog>
#include <QtAlgorithms> #include <QtAlgorithms>
@@ -33,6 +35,8 @@
#include "organiseerrordialog.h" #include "organiseerrordialog.h"
#include "ui_organiseerrordialog.h" #include "ui_organiseerrordialog.h"
using std::stable_sort;
OrganiseErrorDialog::OrganiseErrorDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_OrganiseErrorDialog) { OrganiseErrorDialog::OrganiseErrorDialog(QWidget *parent) : QDialog(parent), ui_(new Ui_OrganiseErrorDialog) {
ui_->setupUi(this); ui_->setupUi(this);
@@ -61,7 +65,7 @@ void OrganiseErrorDialog::Show(OperationType type, const SongList &songs_with_er
void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors) { void OrganiseErrorDialog::Show(OperationType type, const QStringList &files_with_errors) {
QStringList sorted_files = files_with_errors; QStringList sorted_files = files_with_errors;
qStableSort(sorted_files); std::stable_sort(sorted_files.begin(), sorted_files.end());
switch (type) { switch (type) {
case Type_Copy: case Type_Copy:

View File

@@ -14,7 +14,7 @@
<string>Tag fetcher</string> <string>Tag fetcher</string>
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset resource="../../data/data.qrc"> <iconset resource="../../data/icons.qrc">
<normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset> <normaloff>:/icons/64x64/strawberry.png</normaloff>:/icons/64x64/strawberry.png</iconset>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
@@ -261,6 +261,7 @@
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../data/data.qrc"/> <include location="../../data/data.qrc"/>
<include location="../../data/icons.qrc"/>
</resources> </resources>
<connections> <connections>
<connection> <connection>

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

@@ -0,0 +1,511 @@
/*
* 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;
ReloadSettings();
}
DeezerEngine::~DeezerEngine() {
if (player_) {
dz_object_release((dz_object_handle) player_);
player_ = nullptr;
}
if (connect_) {
dz_object_release((dz_object_handle) connect_);
connect_ = nullptr;
}
}
bool DeezerEngine::Init() {
qLog(Debug) << "Deezer native SDK Version:" << dz_connect_get_build_id() << 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();
return true;
}
bool DeezerEngine::Initialised() const {
if (connect_ && player_) return true;
return false;
}
void DeezerEngine::LoadAccessToken() {
QSettings s;
s.beginGroup(DeezerSettingsPage::kSettingsGroup);
if (!s.contains("access_token") || !s.contains("expiry_time")) return;
access_token_ = s.value("access_token").toString();
expiry_time_ = s.value("expiry_time").toDateTime();
s.endGroup();
dz_error_t dzerr = dz_connect_set_access_token(connect_, nullptr, nullptr, access_token_.toUtf8().constData());
if (dzerr != DZ_ERROR_NO_ERROR) {
qLog(Error) << "Deezer: Failed to set access token.";
}
}
bool DeezerEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::TrackChangeFlags change, bool force_stop_at_end, quint64 beginning_nanosec, qint64 end_nanosec) {
if (!Initialised()) return false;
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);
}

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

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

View File

@@ -42,6 +42,7 @@
#include "engine_fwd.h" #include "engine_fwd.h"
#include "enginetype.h" #include "enginetype.h"
#include "enginedevice.h" #include "enginedevice.h"
#include "core/song.h"
namespace Engine { namespace Engine {
@@ -136,19 +137,20 @@ signals:
void FadeoutFinishedSignal(); void FadeoutFinishedSignal();
void StatusText(const QString&); void StatusText(const QString &text);
void Error(const QString&); void Error(const QString &text);
// Emitted when there was a fatal error
void FatalError();
// Emitted when Engine was unable to play a song with the given QUrl. // Emitted when Engine was unable to play a song with the given QUrl.
void InvalidSongRequested(const QUrl&); void InvalidSongRequested(const QUrl &url);
// Emitted when Engine successfully started playing a song with the given QUrl. // Emitted when Engine successfully started playing a song with the given QUrl.
void ValidSongRequested(const QUrl&); void ValidSongRequested(const QUrl &url);
void MetaData(const Engine::SimpleMetaBundle&); void MetaData(const Engine::SimpleMetaBundle&);
// Signals that the engine's state has changed (a stream was stopped for example). // Signals that the engine's state has changed (a stream was stopped for example).
// Always use the state from event, because it's not guaranteed that immediate // Always use the state from event, because it's not guaranteed that immediate subsequent call to state() won't return a stale value.
// subsequent call to state() won't return a stale value.
void StateChanged(Engine::State); void StateChanged(Engine::State);
protected: protected:
@@ -213,6 +215,7 @@ struct SimpleMetaBundle {
qlonglong length; qlonglong length;
int year; int year;
int tracknr; int tracknr;
Song::FileType filetype;
int samplerate; int samplerate;
int bitdepth; int bitdepth;
qlonglong bitrate; qlonglong bitrate;

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> * * Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> * * Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> * * Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* * * *
* This program is free software; you can redistribute it and/or modify * * This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by * * it under the terms of the GNU General Public License as published by *
@@ -206,6 +206,7 @@ bool GstEngine::Load(const QUrl &media_url, const QUrl &original_url, Engine::Tr
current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward); current_pipeline_->StartFader(fadeout_duration_nanosec_, QTimeLine::Forward);
return true; return true;
} }
bool GstEngine::Play(quint64 offset_nanosec) { bool GstEngine::Play(quint64 offset_nanosec) {
@@ -368,6 +369,8 @@ const Engine::Scope &GstEngine::scope(int chunk_length) {
EngineBase::OutputDetailsList GstEngine::GetOutputsList() const { EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
const_cast<GstEngine*>(this)->EnsureInitialised();
EngineBase::OutputDetailsList ret; EngineBase::OutputDetailsList ret;
PluginDetailsList plugins = GetPluginList("Sink/Audio"); PluginDetailsList plugins = GetPluginList("Sink/Audio");
@@ -391,6 +394,8 @@ EngineBase::OutputDetailsList GstEngine::GetOutputsList() const {
bool GstEngine::ValidOutput(const QString &output) { bool GstEngine::ValidOutput(const QString &output) {
EnsureInitialised();
PluginDetailsList plugins = GetPluginList("Sink/Audio"); PluginDetailsList plugins = GetPluginList("Sink/Audio");
for (const PluginDetails &plugin : plugins) { for (const PluginDetails &plugin : plugins) {
if (plugin.name == output) return(true); if (plugin.name == output) return(true);
@@ -466,18 +471,17 @@ void GstEngine::SetEnvironment() {
} }
GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool fatal, bool showerror) { GstElement *GstEngine::CreateElement(const QString &factoryName, GstElement *bin, bool showerror) {
// Make a unique name // Make a unique name
QString name = factoryName + "-" + QString::number(next_element_id_++); QString name = factoryName + "-" + QString::number(next_element_id_++);
GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData()); GstElement *element = gst_element_factory_make(factoryName.toUtf8().constData(), name.toUtf8().constData());
if (!element) { if (!element) {
if (showerror) if (showerror) emit Error(QString("GStreamer could not create the element: %1.").arg(factoryName));
emit Error(QString("GStreamer could not create the element: %1. Please make sure that you have installed all necessary GStreamer plugins").arg(factoryName));
else qLog(Error) << "GStreamer could not create the element:" << factoryName; else qLog(Error) << "GStreamer could not create the element:" << factoryName;
//if (fatal) gst_object_unref(GST_OBJECT(bin)); emit StateChanged(Engine::Error);
emit FatalError();
return nullptr; return nullptr;
} }
@@ -562,21 +566,25 @@ void GstEngine::EndOfStreamReached(int pipeline_id, bool has_next_track) {
BufferingFinished(); BufferingFinished();
} }
emit TrackEnded(); emit TrackEnded();
} }
void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) { void GstEngine::HandlePipelineError(int pipeline_id, const QString &message, int domain, int error_code) {
if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) if (!current_pipeline_.get() || current_pipeline_->id() != pipeline_id) return;
return;
qLog(Warning) << "Gstreamer error:" << domain << error_code << message; qLog(Error) << "Gstreamer error:" << domain << error_code << message;
current_pipeline_.reset(); current_pipeline_.reset();
BufferingFinished(); BufferingFinished();
emit StateChanged(Engine::Error); emit StateChanged(Engine::Error);
// unable to play media stream with this url
emit InvalidSongRequested(media_url_); if (domain == GST_RESOURCE_ERROR && (error_code == GST_RESOURCE_ERROR_NOT_FOUND || error_code == GST_RESOURCE_ERROR_NOT_AUTHORIZED)) {
emit InvalidSongRequested(media_url_);
}
else {
emit FatalError();
}
emit Error(message); emit Error(message);
@@ -701,6 +709,8 @@ void GstEngine::BufferingFinished() {
GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const { GstEngine::PluginDetailsList GstEngine::GetPluginList(const QString &classname) const {
const_cast<GstEngine*>(this)->EnsureInitialised();
PluginDetailsList ret; PluginDetailsList ret;
GstRegistry *registry = gst_registry_get(); GstRegistry *registry = gst_registry_get();
@@ -750,7 +760,7 @@ QByteArray GstEngine::FixupUrl(const QUrl &url) {
QStringList path = url.path().split('/'); QStringList path = url.path().split('/');
str = QString("cdda://%1a").arg(path.takeLast()); str = QString("cdda://%1a").arg(path.takeLast());
QString device = path.join("/"); QString device = path.join("/");
current_pipeline_->SetSourceDevice(device); if (current_pipeline_) current_pipeline_->SetSourceDevice(device);
} }
uri = str.toLocal8Bit(); uri = str.toLocal8Bit();
} }

View File

@@ -1,8 +1,8 @@
/*************************************************************************** /***************************************************************************
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> * * Copyright (C) 2003-2005 by Mark Kretschmann <markey@web.de> *
* Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> * * Copyright (C) 2005 by Jakub Stachowski <qbast@go2.pl> *
* Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> * * Copyright (C) 2006 Paul Cifarelli <paul@cifarelli.net> *
* Copyright (C) 2017-2018 Jonas Kvinge <jonas@jkvinge.net> *
* * * *
* This program is free software; you can redistribute it and/or modify * * This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by * * it under the terms of the GNU General Public License as published by *
@@ -93,7 +93,7 @@ class GstEngine : public Engine::Base, public GstBufferConsumer {
void InitialiseGStreamer(); void InitialiseGStreamer();
void SetEnvironment(); void SetEnvironment();
GstElement *CreateElement(const QString &factoryName, GstElement *bin = 0, bool fatal = true, bool showerror = true); GstElement *CreateElement(const QString &factoryName, GstElement *bin = nullptr, bool showerror = true);
void ConsumeBuffer(GstBuffer *buffer, int pipeline_id); void ConsumeBuffer(GstBuffer *buffer, int pipeline_id);
public slots: public slots:

View File

@@ -239,9 +239,9 @@ bool GstEnginePipeline::InitAudioBin() {
probe_sink = engine_->CreateElement("fakesink", audiobin_); probe_sink = engine_->CreateElement("fakesink", audiobin_);
audio_queue = engine_->CreateElement("queue", audiobin_); audio_queue = engine_->CreateElement("queue", audiobin_);
equalizer_preamp_ = engine_->CreateElement("volume", audiobin_, false, true); equalizer_preamp_ = engine_->CreateElement("volume", audiobin_, false);
equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_, false, true); equalizer_ = engine_->CreateElement("equalizer-nbands", audiobin_, false);
audio_panorama_ = engine_->CreateElement("audiopanorama", audiobin_, false, false); audio_panorama_ = engine_->CreateElement("audiopanorama", audiobin_, false);
volume_ = engine_->CreateElement("volume", audiobin_); volume_ = engine_->CreateElement("volume", audiobin_);
audioscale_ = engine_->CreateElement("audioresample", audiobin_); audioscale_ = engine_->CreateElement("audioresample", audiobin_);
convert = engine_->CreateElement("audioconvert", audiobin_); convert = engine_->CreateElement("audioconvert", audiobin_);
@@ -257,9 +257,9 @@ bool GstEnginePipeline::InitAudioBin() {
GstElement *convert_sink = tee; GstElement *convert_sink = tee;
if (rg_enabled_) { if (rg_enabled_) {
rgvolume_ = engine_->CreateElement("rgvolume", audiobin_, false, true); rgvolume_ = engine_->CreateElement("rgvolume", audiobin_, false);
rglimiter_ = engine_->CreateElement("rglimiter", audiobin_, false, true); rglimiter_ = engine_->CreateElement("rglimiter", audiobin_, false);
audioconvert2_ = engine_->CreateElement("audioconvert", audiobin_, false, true); audioconvert2_ = engine_->CreateElement("audioconvert", audiobin_, false);
if (rgvolume_ && rglimiter_ && audioconvert2_) { if (rgvolume_ && rglimiter_ && audioconvert2_) {
event_probe = audioconvert2_; event_probe = audioconvert2_;
convert_sink = rgvolume_; convert_sink = rgvolume_;
@@ -592,6 +592,7 @@ void GstEnginePipeline::TagMessageReceived(GstMessage *msg) {
bundle.length = 0; bundle.length = 0;
bundle.year = 0; bundle.year = 0;
bundle.tracknr = 0; bundle.tracknr = 0;
bundle.filetype = Song::FileType_Unknown;
bundle.samplerate = 0; bundle.samplerate = 0;
bundle.bitdepth = 0; bundle.bitdepth = 0;
bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000; bundle.bitrate = ParseUIntTag(taglist, GST_TAG_BITRATE) / 1000;

Some files were not shown because too many files have changed in this diff Show More