Merge branch 'master' into PanderMusubi-master

This commit is contained in:
John Maguire 2016-04-06 15:43:32 +01:00
commit 9291f09b1b
140 changed files with 15395 additions and 15019 deletions

18
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,18 @@
### Before posting
Please follow the steps below and check the boxes with [x] once you did the step.
- [ ] I checked the issue tracker for similar issues
- [ ] I checked the [changelog](https://github.com/clementine-player/Clementine/blob/master/Changelog) if the issue is already resolved
- [ ] I tried the latest Clementine build from [here](https://builds.clementine-player.org/)
### System information
Please provide information about your system and the version of Clementine used.
- Operating System:
- Clementine version:
### Expected behaviour / actual behaviour
### Steps to reproduce the problem (only for bugs)

View File

@ -32,7 +32,7 @@ if (UNIX AND NOT APPLE)
set(LINUX 1)
endif (UNIX AND NOT APPLE)
find_package(Qt4 4.5.0 REQUIRED QtCore QtGui QtOpenGL QtSql QtNetwork QtXml)
find_package(Qt4 4.8.1 REQUIRED QtCore QtGui QtOpenGL QtSql QtNetwork QtXml)
if(NOT APPLE)
find_package(Qt4 COMPONENTS QtWebKit)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 659 B

After

Width:  |  Height:  |  Size: 590 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 B

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,19 +1,16 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg7365" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs7367">
<linearGradient id="linearGradient4444" y2="-13" gradientUnits="userSpaceOnUse" x2="-26" gradientTransform="translate(50 50)" y1="-38" x1="-26">
<stop id="stop4436" stop-color="#eeeeec" offset="0"/>
<stop id="stop4438" stop-color="#babdb6" offset=".75692"/>
<stop id="stop4440" stop-color="#a1a59b" offset="1"/>
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.1" viewBox="0 0 48 48">
<defs>
<linearGradient id="b" y2="-13" gradientUnits="userSpaceOnUse" x2="-26" gradientTransform="translate(50,50)" y1="-38" x1="-26">
<stop stop-color="#ccc" offset="0"/>
<stop stop-color="#999" offset=".75"/>
<stop stop-color="#555" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient4448" y2="37" gradientUnits="userSpaceOnUse" x2="24" y1="13" x1="24">
<stop id="stop3203" stop-color="#fff" offset="0"/>
<stop id="stop3205" stop-color="#fff" stop-opacity="0" offset="1"/>
<linearGradient id="a" y2="37" gradientUnits="userSpaceOnUse" x2="24" y1="13" x1="24">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
</defs>
<g id="layer1" stroke-dashoffset=".7" stroke-linecap="round">
<path id="rect7213" stroke-linejoin="round" d="m13.5 14.5c-2.7359 0-5 2.2641-5 5v9.0625c0 2.7359 2.2641 5 5 5h5v-4h-5c-0.58909 0-1-0.41091-1-1v-9.0625c0-0.58909 0.41091-1 1-1h21c0.58909 0 1 0.41091 1 1v9.0625c0 0.58909-0.41091 1-1 1h-5v-3.0625l-7 5 7 5v-2.9375h5c2.7359 0 5-2.2641 5-5v-9.0625c0-2.7359-2.2641-5-5-5h-21z" stroke="#888a85" fill="url(#linearGradient4444)"/>
<path id="path7239" opacity=".6" d="m13.5 15.406c-2.2359 0-4.0938 1.8578-4.0938 4.0938v9.0625c0 2.2359 1.8578 4.0938 4.0938 4.0938h4.0938v-2.1875h-4.0938c-1.0105 0-1.9062-0.89573-1.9062-1.9062v-9.0625c0-1.0105 0.89573-1.9062 1.9062-1.9062h21c1.0105 0 1.9062 0.89574 1.9062 1.9062v9.0625c0 1.0105-0.89573 1.9062-1.9062 1.9062h-5a0.90634 0.90634 0 0 1 -0.906 -0.906v-1.2812l-4.5 3.2188 4.5 3.2188v-1.1562a0.90634 0.90634 0 0 1 0.906 -0.906h5c2.2359 0 4.0937-1.8578 4.0938-4.0938v-9.0625c0-2.2359-1.8578-4.0938-4.0938-4.0938h-21z" transform="translate(-0.00625 -.018750)" stroke="url(#linearGradient4448)" stroke-width="0.8" fill="none"/>
</g>
<path fill="url(#b)" d="m13.5 14.5c-2.7359 0-5 2.2641-5 5v9.0625c0 2.7359 2.2641 5 5 5h5v-4h-5c-0.58909 0-1-0.41091-1-1v-9.062c0-0.58909 0.41091-1 1-1h21c0.58909 0 1 0.41091 1 1v9.0625c0 0.58909-0.41091 1-1 1h-5v-3.062l-7 5 7 5v-2.9375h5c2.7359 0 5-2.2641 5-5v-9.062c0-2.7359-2.2641-5-5-5z" stroke="#555"/>
<path opacity=".6" d="m13.5 15.406c-2.2359 0-4.0938 1.8578-4.0938 4.0938v9.0625c0 2.2359 1.8578 4.0938 4.0938 4.0938h4.0938v-2.187h-4.094c-1.0105 0-1.9062-0.89573-1.9062-1.9062v-9.0625c0-1.0105 0.89573-1.9062 1.9062-1.9062h21c1.0105 0 1.9062 0.89574 1.9062 1.9062v9.0625c0 1.0105-0.89573 1.9062-1.9062 1.9062h-5a0.90634 0.90634 0 0 1 -0.906 -0.906v-1.2812l-4.5 3.2188 4.5 3.2188v-1.1562a0.90634 0.90634 0 0 1 0.906 -0.906h5c2.2359 0 4.0937-1.8578 4.0938-4.0938v-9.062c0-2.2359-1.8578-4.0938-4.0938-4.0938h-21z" stroke="url(#a)" stroke-width="0.8" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,35 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg id="svg7306" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs id="defs7308">
<linearGradient id="linearGradient3752" y2="-13" gradientUnits="userSpaceOnUse" x2="22" gradientTransform="translate(-.000419 50)" y1="-41" x1="22">
<stop id="stop3736" stop-color="#eeeeec" offset="0"/>
<stop id="stop3738" stop-color="#babdb6" offset=".74950"/>
<stop id="stop3740" stop-color="#a1a59b" offset="1"/>
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.1" viewBox="0 0 48 48">
<defs>
<linearGradient id="e" y2="-13" gradientUnits="userSpaceOnUse" x2="22" gradientTransform="translate(0,50)" y1="-41" x1="22">
<stop stop-color="#ccc" offset="0"/>
<stop stop-color="#999" offset=".75"/>
<stop stop-color="#555" offset="1"/>
</linearGradient>
<radialGradient id="radialGradient3755" gradientUnits="userSpaceOnUse" cy="24.484" cx="21.578" gradientTransform="matrix(1 0 0 2.2315 -.000419 -30.153)" r="3.1719">
<stop id="stop6891" offset="0"/>
<stop id="stop6893" stop-opacity="0" offset="1"/>
<radialGradient id="a" gradientUnits="userSpaceOnUse" cy="24.484" cx="21.578" gradientTransform="matrix(1,0,0,2.2315,0,-30.153)" r="3.1719">
<stop offset="0"/>
<stop stop-opacity="0" offset="1"/>
</radialGradient>
<linearGradient id="linearGradient3759" y2="-13" gradientUnits="userSpaceOnUse" x2="22" gradientTransform="translate(-.000419 50)" y1="-40" x1="22">
<stop id="stop3744" stop-color="#eeeeec" offset="0"/>
<stop id="stop3746" stop-color="#babdb6" offset=".74034"/>
<stop id="stop3748" stop-color="#a1a59b" offset="1"/>
<linearGradient id="d" y2="-13" gradientUnits="userSpaceOnUse" x2="22" gradientTransform="translate(0,50)" y1="-40" x1="22">
<stop stop-color="#ccc" offset="0"/>
<stop stop-color="#999" offset=".75"/>
<stop stop-color="#555" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient3766" y2="33" gradientUnits="userSpaceOnUse" x2="22" y1="13" x1="22">
<stop id="stop3214" stop-color="#fff" offset="0"/>
<stop id="stop3216" stop-color="#fff" stop-opacity="0" offset="1"/>
<linearGradient id="c" y2="33" gradientUnits="userSpaceOnUse" x2="22" y1="13" x1="22">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#fff" stop-opacity=".2" offset="1"/>
</linearGradient>
<linearGradient id="linearGradient3768" y2="35" gradientUnits="userSpaceOnUse" x2="22" y1="12" x1="22">
<stop id="stop6868" stop-color="#fff" offset="0"/>
<stop id="stop6870" stop-color="#fff" stop-opacity="0" offset="1"/>
<linearGradient id="b" y2="35" gradientUnits="userSpaceOnUse" x2="22" y1="12" x1="22">
<stop stop-color="#fff" offset="0"/>
<stop stop-color="#fff" stop-opacity=".2" offset="1"/>
</linearGradient>
</defs>
<g id="layer1">
<path id="path6793" d="m31.5 36.5v-3h-3c-2.1222-0.000001-4.247-0.90356-5.9688-2.4375s-3.0312-3.8276-3.0312-6.5625c0-3.336-2.0391-5-4.5-5h-4.5-2v-4h2 4.5c4.5391 0 8.5 3.7914 8.5 9 0 1.4651 0.69052 2.6777 1.7188 3.5938 1.0282 0.91606 2.4035 1.4062 3.2812 1.4062h3v-3l7 5-7 5z" fill-rule="evenodd" stroke="#888a85" stroke-linecap="square" fill="url(#linearGradient3759)"/>
<path id="path6854" opacity=".6" d="m9.3125 16.312v2.375h1.1875 4.5c1.4136 0 2.7663 0.49751 3.75 1.5 0.98371 1.0025 1.5625 2.4789 1.5625 4.3125 0 2.4756 1.1758 4.5663 2.75 5.9688 1.5922 1.4185 3.5459 2.2187 5.4375 2.2188h3a0.81258 0.81258 0 0 1 0.812 0.813v1.4375l4.8125-3.4375-4.8125-3.4375v1.4375a0.81258 0.81258 0 0 1 -0.812 0.812h-3c-1.1803 0-2.6231-0.56534-3.8125-1.625-1.1592-1.0328-2-2.4671-2-4.1875 0-4.7966-3.6008-8.1875-7.6875-8.1875h-4.5-1.1875z" transform="translate(0 -0.0375)" stroke="url(#linearGradient3768)" stroke-linecap="square" stroke-width="0.8" fill="none"/>
<path id="path6876" opacity=".6" d="m20.968 17.406c-1.15 1.1828-2.0687 2.6675-2.5625 4.4062 0.40105 0.68112 0.625 1.5619 0.625 2.6875 0 2.8876 1.3788 5.2949 3.1875 6.9062 0.06175 0.05502 0.12476 0.10276 0.1875 0.15625 1.1268-1.2986 1.9555-2.9182 2.3438-4.7188-0.4785-0.67914-0.78125-1.4512-0.78125-2.3438 0-2.9001-1.1734-5.3943-3-7.0938z" fill-rule="evenodd" fill="url(#radialGradient3755)"/>
<path id="path6784" d="m31.5 12.5v3h-3c-2.1222 0.000001-4.247 0.90356-5.9688 2.4375s-3.0312 3.8276-3.0312 6.5625c0 3.336-2.0391 5-4.5 5h-4.5-2v4h2 4.5c4.5391 0 8.5-3.7914 8.5-9 0-1.4651 0.69052-2.6777 1.7188-3.5938 1.0282-0.91606 2.4035-1.4062 3.2812-1.4062h3v3l7-5-7-5z" fill-rule="evenodd" stroke="#888a85" stroke-linecap="square" fill="url(#linearGradient3752)"/>
<path id="path6816" opacity=".6" d="m32.344 14.156v1.3438a0.85262 0.85262 0 0 1 -0.844 0.844h-3c-1.8802 0.000001-3.8204 0.80593-5.4062 2.2188-1.5669 1.396-2.75 3.4747-2.75 5.9375 0 1.8417-0.5706 3.3329-1.5625 4.3438-0.9919 1.0108-2.3587 1.5-3.7812 1.5h-4.5-1.1562v2.3125h1.1562 4.5c4.0644 0 7.6562-3.3799 7.6562-8.1562 0-1.733 0.83431-3.1802 2-4.2188 1.1974-1.0667 2.6486-1.625 3.8438-1.625h3a0.85262 0.85262 0 0 1 0.844 0.844v1.3438l4.6875-3.3438-4.6875-3.3438z" transform="translate(0 -0.0375)" stroke="url(#linearGradient3766)" stroke-linecap="square" stroke-width="0.8" fill="none"/>
</g>
<path d="m31.5 36.5v-3h-3c-2.1222-0.000001-4.247-0.90356-5.9688-2.4375-1.722-1.533-3.031-3.827-3.031-6.562 0-3.336-2.0391-5-4.5-5h-6.5v-4h6.5c4.5391 0 8.5 3.7914 8.5 9 0 1.4651 0.69052 2.6777 1.7188 3.5938 1.028 0.916 2.403 1.406 3.281 1.406h3v-3l7 5z" fill-rule="evenodd" stroke="#444" stroke-linecap="square" fill="url(#d)"/>
<path opacity=".6" d="m9.3125 18.687h5.6875c1.4136 0 2.7663 0.49751 3.75 1.5 0.98371 1.0025 1.5625 2.4789 1.5625 4.3125 0 2.4756 1.1758 4.5663 2.75 5.9688 1.5922 1.4185 3.5459 2.2187 5.4375 2.2188h3c0.44871 0.00032 0.81223 0.36429 0.812 0.813v1.4375l4.8125-3.4375-4.8125-3.4375v1.4375c-0.00032 0.44832-0.36368 0.81168-0.812 0.812h-3c-1.1803 0-2.6231-0.56534-3.8125-1.625-1.1592-1.0328-2-2.4671-2-4.1875 0-4.7966-3.6008-8.1875-7.6875-8.1875l-5.6875-0.00005z" stroke="url(#b)" stroke-width="0.8" fill="none"/>
<path opacity=".6" fill="url(#a)" d="m20.968 17.406c-1.15 1.1828-2.0687 2.6675-2.5625 4.4062 0.40105 0.68112 0.625 1.5619 0.625 2.6875 0 2.8876 1.3788 5.2949 3.1875 6.9062 0.06175 0.05502 0.12476 0.10276 0.1875 0.15625 1.1268-1.2986 1.9555-2.9182 2.3438-4.7188-0.4785-0.67914-0.78125-1.4512-0.78125-2.3438 0-2.9001-1.1734-5.3943-3-7.0938z" fill-rule="evenodd"/>
<path d="m31.5 12.5v3h-3c-2.1222 0.000001-4.247 0.90356-5.9688 2.4375-1.722 1.533-3.031 3.827-3.031 6.562 0 3.336-2.0391 5-4.5 5h-6.5v4h6.5c4.5391 0 8.5-3.7914 8.5-9 0-1.4651 0.69052-2.6777 1.7188-3.5938 1.028-0.916 2.403-1.406 3.281-1.406h3v3l7-5z" fill-rule="evenodd" stroke="#444" fill="url(#e)"/>
<path opacity=".6" d="m32.344 15.5c-0.0047 0.46418-0.37982 0.83933-0.844 0.844h-3c-1.8802 0.000001-3.8204 0.80593-5.4062 2.2188-1.5669 1.396-2.75 3.4747-2.75 5.9375 0 1.8417-0.5706 3.3329-1.5625 4.3438-0.9919 1.0108-2.3587 1.5-3.7812 1.5h-5.6561v2.3125h5.6562c4.0644 0 7.6562-3.3799 7.6562-8.1562 0-1.733 0.83431-3.1802 2-4.2188 1.1974-1.0667 2.6486-1.625 3.8438-1.625h3c0.46418 0.0047 0.83933 0.37982 0.844 0.844v1.3438l4.6875-3.3438-4.6876-3.3441z" stroke="url(#c)" stroke-width="0.8" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -33,7 +33,7 @@ Terminal=false
Categories=AudioVideo;Player;Qt;Audio;
StartupNotify=false
MimeType=application/ogg;application/x-ogg;application/x-ogm-audio;audio/aac;audio/mp4;audio/mpeg;audio/mpegurl;audio/ogg;audio/vnd.rn-realaudio;audio/vorbis;audio/x-flac;audio/x-mp3;audio/x-mpeg;audio/x-mpegurl;audio/x-ms-wma;audio/x-musepack;audio/x-oggflac;audio/x-pn-realaudio;audio/x-scpls;audio/x-speex;audio/x-vorbis;audio/x-vorbis+ogg;audio/x-wav;video/x-ms-asf;x-content/audio-player;x-scheme-handler/zune;x-scheme-handler/itpc;x-scheme-handler/itms;x-scheme-handler/feed;
Actions=Play;Pause;Stop;Previous;Next;
Actions=Play;Pause;Stop;StopAfterCurrent;Previous;Next;
[Desktop Action Play]
Name=Play
@ -181,6 +181,54 @@ Name[vi]=Dừng
Name[zh_CN]=停止
Name[zh_TW]=停止
[Desktop Action StopAfterCurrent]
Name=Stop after this track
Exec=clementine --stop-after-current
OnlyShowIn=Unity;
Name[be]=Спыніць пасьля гэтага трэку
Name[bg]=Спри след тази песен
Name[br]=Paouez goude ar roud-mañ
Name[ca]=Atura després daquesta peça
Name[cs]=Zastavit po této skladbě
Name[da]=Stop efter dette spor
Name[de]=Wiedergabe nach diesem Titel anhalten
Name[el]=Σταμάτημα μετά από αυτό το κομμάτι
Name[es]=Detener reproducción al finalizar la pista
Name[eu]=Gelditu pista honen ondoren
Name[fa]=ایست پس از این آهنگ
Name[fi]=Pysäytä toistettavan kappaleen jälkeen
Name[fr]=Arrêter la lecture après cette piste
Name[ga]=Stad i ndiaidh an rian seo
Name[gl]=Deter a reprodución despois da pista actual
Name[he]=הפסקה אחרי רצועה זו
Name[hr]=Zaustavi reprodukciju nakon ove pjesme
Name[hu]=Leállítás az aktuális szám után
Name[it]=Ferma dopo questa traccia
Name[ja]=このトラック後に停止
Name[ko]=이번 트랙 이후 정지
Name[lt]=Sustabdyti po šio takelio
Name[lv]=Apturēt pēc šīs dziesmas
Name[ms]=Henti selepas trek ini
Name[nb]=Stopp etter denne sangen
Name[nl]=Na dit nummer stoppen
Name[pl]=Zatrzymaj po tym utworze
Name[pt]=Parar após esta faixa
Name[pt_BR]=Parar depois desta música
Name[ro]=Oprește după această piesă
Name[ru]=Остановить после этого трека
Name[sk]=Zastaviť po tejto skladbe
Name[sl]=Zaustavi po tej skladbi
Name[sr]=Заустави после ове нумере
Name[sr@ijekavian]=
Name[sr@ijekavianlatin]=
Name[sr@latin]=Zaustavi posle ove numere
Name[sv]=Stoppa efter detta spår
Name[tr]=Bu parçadan sonra durdur
Name[uk]=Зупинити після цієї доріжки
Name[vi]=Dừng sau khi phát xong bài này
Name[zh_CN]=在此曲目后停止
Name[zh_TW]=在這首歌之後停止
[Desktop Action Previous]
Name=Previous
Exec=clementine --previous

View File

@ -27,10 +27,10 @@ add_executable(clementine-tagreader
target_link_libraries(clementine-tagreader
${TAGLIB_LIBRARIES}
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
libclementine-common
libclementine-tagreader
${QT_QTCORE_LIBRARY}
${QT_QTNETWORK_LIBRARY}
)
if(${CMAKE_SYSTEM_NAME} MATCHES "FreeBSD")

View File

@ -0,0 +1,65 @@
/* This file is part of Clementine.
Copyright 2016, John Maguire <john.maguire@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LAZY_H
#define LAZY_H
#include <functional>
#include <memory>
// Helper for lazy initialisation of objects.
// Usage:
// Lazy<Foo> my_lazy_object([]() { return new Foo; });
template <typename T>
class Lazy {
public:
explicit Lazy(std::function<T*()> init) : init_(init) {}
// Convenience constructor that will lazily default construct the object.
Lazy() : init_([]() { return new T; }) {}
T* get() const {
CheckInitialised();
return ptr_.get();
}
typename std::add_lvalue_reference<T>::type operator*() const {
CheckInitialised();
return *ptr_;
}
T* operator->() const { return get(); }
// Returns true if the object is not yet initialised.
explicit operator bool() const { return ptr_; }
// Deletes the underlying object and will re-run the initialisation function
// if the object is requested again.
void reset() { ptr_.reset(nullptr); }
private:
void CheckInitialised() const {
if (!ptr_) {
ptr_.reset(init_());
}
}
const std::function<T*()> init_;
mutable std::unique_ptr<T> ptr_;
};
#endif // LAZY_H

View File

@ -25,6 +25,8 @@
#include <execinfo.h>
#endif
#include <iostream>
#include <QCoreApplication>
#include <QDateTime>
#include <QStringList>
@ -202,10 +204,11 @@ QDebug CreateLogger(Level level, const QString& class_name, int line) {
}
QDebug ret(type);
ret.nospace() << kMessageHandlerMagic << QDateTime::currentDateTime()
.toString("hh:mm:ss.zzz")
.toAscii()
.constData() << level_name
ret.nospace() << kMessageHandlerMagic
<< QDateTime::currentDateTime()
.toString("hh:mm:ss.zzz")
.toAscii()
.constData() << level_name
<< function_line.leftJustified(32).toAscii().constData();
return ret.space();
@ -257,7 +260,8 @@ void DumpStackTrace() {
backtrace_symbols(reinterpret_cast<void**>(&callstack), callstack_size);
// Start from 1 to skip ourself.
for (int i = 1; i < callstack_size; ++i) {
qLog(Debug) << DemangleSymbol(QString::fromAscii(symbols[i]));
std::cerr << DemangleSymbol(QString::fromAscii(symbols[i])).toStdString()
<< std::endl;
}
free(symbols);
#else
@ -269,7 +273,7 @@ void DumpStackTrace() {
namespace {
template<typename T>
template <typename T>
QString print_duration(T duration, const std::string& unit) {
return QString("%1%2").arg(duration.count()).arg(unit.c_str());
}

View File

@ -896,17 +896,6 @@ optional_source(LINUX SOURCES widgets/osd_x11.cpp)
if(HAVE_DBUS)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/dbus)
# MPRIS DBUS interfaces
qt4_add_dbus_adaptor(SOURCES
dbus/org.freedesktop.MediaPlayer.player.xml
core/mpris1.h mpris::Mpris1Player core/mpris_player MprisPlayer)
qt4_add_dbus_adaptor(SOURCES
dbus/org.freedesktop.MediaPlayer.root.xml
core/mpris1.h mpris::Mpris1Root core/mpris_root MprisRoot)
qt4_add_dbus_adaptor(SOURCES
dbus/org.freedesktop.MediaPlayer.tracklist.xml
core/mpris1.h mpris::Mpris1TrackList core/mpris_tracklist MprisTrackList)
# MPRIS 2.0 DBUS interfaces
qt4_add_dbus_adaptor(SOURCES
dbus/org.mpris.MediaPlayer2.Player.xml
@ -986,13 +975,11 @@ endif(HAVE_DBUS)
optional_source(HAVE_DBUS
SOURCES
core/mpris.cpp
core/mpris1.cpp
core/mpris2.cpp
networkremote/avahi.cpp
ui/dbusscreensaver.cpp
HEADERS
core/mpris.h
core/mpris1.h
core/mpris2.h
)
@ -1351,6 +1338,7 @@ if (WIN32)
tinysvcmdns
qtwin
dsound
${QT_QTGUI_LIBRARY}
)
endif (WIN32)

View File

@ -21,31 +21,39 @@
*/
#include "application.h"
#include "appearance.h"
#include "config.h"
#include "database.h"
#include "player.h"
#include "tagreaderclient.h"
#include "taskmanager.h"
#include "core/appearance.h"
#include "core/database.h"
#include "core/lazy.h"
#include "core/player.h"
#include "core/tagreaderclient.h"
#include "core/taskmanager.h"
#include "covers/albumcoverloader.h"
#include "covers/amazoncoverprovider.h"
#include "covers/coverproviders.h"
#include "covers/currentartloader.h"
#include "covers/musicbrainzcoverprovider.h"
#include "devices/devicemanager.h"
#include "internet/core/internetmodel.h"
#include "globalsearch/globalsearch.h"
#include "library/library.h"
#include "library/librarybackend.h"
#include "networkremote/networkremote.h"
#include "networkremote/networkremotehelper.h"
#include "playlist/playlistbackend.h"
#include "playlist/playlistmanager.h"
#include "internet/core/internetmodel.h"
#include "internet/core/scrobbler.h"
#include "internet/podcasts/gpoddersync.h"
#include "internet/podcasts/podcastbackend.h"
#include "internet/podcasts/podcastdeleter.h"
#include "internet/podcasts/podcastdownloader.h"
#include "internet/podcasts/podcastupdater.h"
#include "library/librarybackend.h"
#include "library/library.h"
#include "moodbar/moodbarcontroller.h"
#include "moodbar/moodbarloader.h"
#include "networkremote/networkremote.h"
#include "networkremote/networkremotehelper.h"
#include "playlist/playlistbackend.h"
#include "playlist/playlistmanager.h"
#ifdef HAVE_LIBLASTFM
#include "covers/lastfmcoverprovider.h"
#include "internet/lastfm/lastfmservice.h"
#endif // HAVE_LIBLASTFM
@ -56,101 +64,137 @@
bool Application::kIsPortable = false;
Application::Application(QObject* parent)
: QObject(parent),
tag_reader_client_(nullptr),
database_(nullptr),
album_cover_loader_(nullptr),
playlist_backend_(nullptr),
podcast_backend_(nullptr),
appearance_(nullptr),
cover_providers_(nullptr),
task_manager_(nullptr),
player_(nullptr),
playlist_manager_(nullptr),
current_art_loader_(nullptr),
global_search_(nullptr),
internet_model_(nullptr),
library_(nullptr),
device_manager_(nullptr),
podcast_updater_(nullptr),
podcast_deleter_(nullptr),
podcast_downloader_(nullptr),
gpodder_sync_(nullptr),
moodbar_loader_(nullptr),
moodbar_controller_(nullptr),
network_remote_(nullptr),
network_remote_helper_(nullptr),
scrobbler_(nullptr) {
tag_reader_client_ = new TagReaderClient(this);
MoveToNewThread(tag_reader_client_);
tag_reader_client_->Start();
database_ = new Database(this, this);
MoveToNewThread(database_);
album_cover_loader_ = new AlbumCoverLoader(this);
MoveToNewThread(album_cover_loader_);
playlist_backend_ = new PlaylistBackend(this, this);
MoveToThread(playlist_backend_, database_->thread());
podcast_backend_ = new PodcastBackend(this, this);
MoveToThread(podcast_backend_, database_->thread());
appearance_ = new Appearance(this);
cover_providers_ = new CoverProviders(this);
task_manager_ = new TaskManager(this);
player_ = new Player(this, this);
playlist_manager_ = new PlaylistManager(this, this);
current_art_loader_ = new CurrentArtLoader(this, this);
global_search_ = new GlobalSearch(this, this);
internet_model_ = new InternetModel(this, this);
library_ = new Library(this, this);
device_manager_ = new DeviceManager(this, this);
podcast_updater_ = new PodcastUpdater(this, this);
podcast_deleter_ = new PodcastDeleter(this, this);
MoveToNewThread(podcast_deleter_);
podcast_downloader_ = new PodcastDownloader(this, this);
gpodder_sync_ = new GPodderSync(this, this);
class ApplicationImpl {
public:
ApplicationImpl(Application* app)
: tag_reader_client_([=]() {
TagReaderClient* client = new TagReaderClient(app);
app->MoveToNewThread(client);
client->Start();
return client;
}),
database_([=]() {
Database* db = new Database(app, app);
app->MoveToNewThread(db);
DoInAMinuteOrSo(db, SLOT(DoBackup()));
return db;
}),
album_cover_loader_([=]() {
AlbumCoverLoader* loader = new AlbumCoverLoader(app);
app->MoveToNewThread(loader);
return loader;
}),
playlist_backend_([=]() {
PlaylistBackend* backend = new PlaylistBackend(app, app);
app->MoveToThread(backend, database_->thread());
return backend;
}),
podcast_backend_([=]() {
PodcastBackend* backend = new PodcastBackend(app, app);
app->MoveToThread(backend, database_->thread());
return backend;
}),
appearance_([=]() { return new Appearance(app); }),
cover_providers_([=]() {
CoverProviders* cover_providers = new CoverProviders(app);
// Initialize the repository of cover providers.
cover_providers->AddProvider(new AmazonCoverProvider);
cover_providers->AddProvider(new MusicbrainzCoverProvider);
#ifdef HAVE_LIBLASTFM
cover_providers->AddProvider(new LastFmCoverProvider(app));
#endif
return cover_providers;
}),
task_manager_([=]() { return new TaskManager(app); }),
player_([=]() { return new Player(app, app); }),
playlist_manager_([=]() { return new PlaylistManager(app); }),
current_art_loader_([=]() { return new CurrentArtLoader(app, app); }),
global_search_([=]() { return new GlobalSearch(app, app); }),
internet_model_([=]() { return new InternetModel(app, app); }),
library_([=]() { return new Library(app, app); }),
device_manager_([=]() { return new DeviceManager(app, app); }),
podcast_updater_([=]() { return new PodcastUpdater(app, app); }),
podcast_deleter_([=]() {
PodcastDeleter* deleter = new PodcastDeleter(app, app);
app->MoveToNewThread(deleter);
return deleter;
}),
podcast_downloader_([=]() { return new PodcastDownloader(app, app); }),
gpodder_sync_([=]() { return new GPodderSync(app, app); }),
moodbar_loader_([=]() {
#ifdef HAVE_MOODBAR
moodbar_loader_ = new MoodbarLoader(this, this);
moodbar_controller_ = new MoodbarController(this, this);
return new MoodbarLoader(app, app);
#else
return nullptr;
#endif
}),
moodbar_controller_([=]() {
#ifdef HAVE_MOODBAR
return new MoodbarController(app, app);
#else
return nullptr;
#endif
}),
network_remote_([=]() {
NetworkRemote* remote = new NetworkRemote(app);
app->MoveToNewThread(remote);
return remote;
}),
network_remote_helper_([=]() { return new NetworkRemoteHelper(app); }),
scrobbler_([=]() {
#ifdef HAVE_LIBLASTFM
return new LastFMService(app, app);
#else
return nullptr;
#endif
}) {
}
// Network Remote
network_remote_ = new NetworkRemote(this);
MoveToNewThread(network_remote_);
Lazy<TagReaderClient> tag_reader_client_;
Lazy<Database> database_;
Lazy<AlbumCoverLoader> album_cover_loader_;
Lazy<PlaylistBackend> playlist_backend_;
Lazy<PodcastBackend> podcast_backend_;
Lazy<Appearance> appearance_;
Lazy<CoverProviders> cover_providers_;
Lazy<TaskManager> task_manager_;
Lazy<Player> player_;
Lazy<PlaylistManager> playlist_manager_;
Lazy<CurrentArtLoader> current_art_loader_;
Lazy<GlobalSearch> global_search_;
Lazy<InternetModel> internet_model_;
Lazy<Library> library_;
Lazy<DeviceManager> device_manager_;
Lazy<PodcastUpdater> podcast_updater_;
Lazy<PodcastDeleter> podcast_deleter_;
Lazy<PodcastDownloader> podcast_downloader_;
Lazy<GPodderSync> gpodder_sync_;
Lazy<MoodbarLoader> moodbar_loader_;
Lazy<MoodbarController> moodbar_controller_;
Lazy<NetworkRemote> network_remote_;
Lazy<NetworkRemoteHelper> network_remote_helper_;
Lazy<Scrobbler> scrobbler_;
};
// This must be before libraray_->Init();
Application::Application(QObject* parent)
: QObject(parent), p_(new ApplicationImpl(this)) {
// This must be before library_->Init();
// In the constructor the helper waits for the signal
// PlaylistManagerInitialized
// to start the remote. Without the playlist manager clementine can
// crash when a client connects before the manager is initialized!
network_remote_helper_ = new NetworkRemoteHelper(this);
network_remote_helper();
library()->Init();
#ifdef HAVE_LIBLASTFM
scrobbler_ = new LastFMService(this, this);
#endif // HAVE_LIBLASTFM
library_->Init();
DoInAMinuteOrSo(database_, SLOT(DoBackup()));
// TODO(John Maguire): Make this not a weird singleton.
tag_reader_client();
}
Application::~Application() {
// It's important that the device manager is deleted before the database.
// Deleting the database deletes all objects that have been created in its
// thread, including some device library backends.
delete device_manager_;
device_manager_ = nullptr;
for (QObject* object : objects_in_threads_) {
object->deleteLater();
}
p_->device_manager_.reset();
for (QThread* thread : threads_) {
thread->quit();
@ -173,7 +217,6 @@ void Application::MoveToNewThread(QObject* object) {
void Application::MoveToThread(QObject* object, QThread* thread) {
object->setParent(nullptr);
object->moveToThread(thread);
objects_in_threads_ << object;
}
void Application::AddError(const QString& message) { emit ErrorAdded(message); }
@ -186,14 +229,100 @@ QString Application::language_without_region() const {
return language_name_;
}
void Application::ReloadSettings() { emit SettingsChanged(); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
emit SettingsDialogRequested(page);
}
AlbumCoverLoader* Application::album_cover_loader() const {
return p_->album_cover_loader_.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(); }
DeviceManager* Application::device_manager() const {
return p_->device_manager_.get();
}
GlobalSearch* Application::global_search() const {
return p_->global_search_.get();
}
GPodderSync* Application::gpodder_sync() const {
return p_->gpodder_sync_.get();
}
InternetModel* Application::internet_model() const {
return p_->internet_model_.get();
}
Library* Application::library() const { return p_->library_.get(); }
LibraryBackend* Application::library_backend() const {
return library()->backend();
}
LibraryModel* Application::library_model() const { return library()->model(); }
void Application::ReloadSettings() { emit SettingsChanged(); }
void Application::OpenSettingsDialogAtPage(SettingsDialog::Page page) {
emit SettingsDialogRequested(page);
MoodbarController* Application::moodbar_controller() const {
return p_->moodbar_controller_.get();
}
MoodbarLoader* Application::moodbar_loader() const {
return p_->moodbar_loader_.get();
}
NetworkRemoteHelper* Application::network_remote_helper() const {
return p_->network_remote_helper_.get();
}
NetworkRemote* Application::network_remote() const {
return p_->network_remote_.get();
}
Player* Application::player() const { return p_->player_.get(); }
PlaylistBackend* Application::playlist_backend() const {
return p_->playlist_backend_.get();
}
PlaylistManager* Application::playlist_manager() const {
return p_->playlist_manager_.get();
}
PodcastBackend* Application::podcast_backend() const {
return p_->podcast_backend_.get();
}
PodcastDeleter* Application::podcast_deleter() const {
return p_->podcast_deleter_.get();
}
PodcastDownloader* Application::podcast_downloader() const {
return p_->podcast_downloader_.get();
}
PodcastUpdater* Application::podcast_updater() const {
return p_->podcast_updater_.get();
}
Scrobbler* Application::scrobbler() const { return p_->scrobbler_.get(); }
TagReaderClient* Application::tag_reader_client() const {
return p_->tag_reader_client_.get();
}
TaskManager* Application::task_manager() const {
return p_->task_manager_.get();
}

View File

@ -22,12 +22,15 @@
#ifndef CORE_APPLICATION_H_
#define CORE_APPLICATION_H_
#include "ui/settingsdialog.h"
#include <memory>
#include <QObject>
#include "ui/settingsdialog.h"
class AlbumCoverLoader;
class Appearance;
class ApplicationImpl;
class CoverProviders;
class CurrentArtLoader;
class Database;
@ -44,10 +47,10 @@ class NetworkRemote;
class NetworkRemoteHelper;
class Player;
class PlaylistBackend;
class PodcastDeleter;
class PodcastDownloader;
class PlaylistManager;
class PodcastBackend;
class PodcastDeleter;
class PodcastDownloader;
class PodcastUpdater;
class Scrobbler;
class TagReaderClient;
@ -68,35 +71,32 @@ class Application : public QObject {
QString language_without_region() const;
void set_language_name(const QString& name) { language_name_ = name; }
TagReaderClient* tag_reader_client() const { return tag_reader_client_; }
Database* database() const { return database_; }
AlbumCoverLoader* album_cover_loader() const { return album_cover_loader_; }
PlaylistBackend* playlist_backend() const { return playlist_backend_; }
PodcastBackend* podcast_backend() const { return podcast_backend_; }
Appearance* appearance() const { return appearance_; }
CoverProviders* cover_providers() const { return cover_providers_; }
TaskManager* task_manager() const { return task_manager_; }
Player* player() const { return player_; }
PlaylistManager* playlist_manager() const { return playlist_manager_; }
CurrentArtLoader* current_art_loader() const { return current_art_loader_; }
GlobalSearch* global_search() const { return global_search_; }
InternetModel* internet_model() const { return internet_model_; }
Library* library() const { return library_; }
DeviceManager* device_manager() const { return device_manager_; }
PodcastUpdater* podcast_updater() const { return podcast_updater_; }
PodcastDeleter* podcast_deleter() const { return podcast_deleter_; }
PodcastDownloader* podcast_downloader() const { return podcast_downloader_; }
GPodderSync* gpodder_sync() const { return gpodder_sync_; }
MoodbarLoader* moodbar_loader() const { return moodbar_loader_; }
MoodbarController* moodbar_controller() const { return moodbar_controller_; }
NetworkRemote* network_remote() const { return network_remote_; }
NetworkRemoteHelper* network_remote_helper() const {
return network_remote_helper_;
}
Scrobbler* scrobbler() const { return scrobbler_; }
AlbumCoverLoader* album_cover_loader() const;
Appearance* appearance() const;
CoverProviders* cover_providers() const;
CurrentArtLoader* current_art_loader() const;
Database* database() const;
DeviceManager* device_manager() const;
GlobalSearch* global_search() const;
GPodderSync* gpodder_sync() const;
InternetModel* internet_model() const;
Library* library() const;
LibraryBackend* library_backend() const;
LibraryModel* library_model() const;
MoodbarController* moodbar_controller() const;
MoodbarLoader* moodbar_loader() const;
NetworkRemoteHelper* network_remote_helper() const;
NetworkRemote* network_remote() const;
Player* player() const;
PlaylistBackend* playlist_backend() const;
PlaylistManager* playlist_manager() const;
PodcastBackend* podcast_backend() const;
PodcastDeleter* podcast_deleter() const;
PodcastDownloader* podcast_downloader() const;
PodcastUpdater* podcast_updater() const;
Scrobbler* scrobbler() const;
TagReaderClient* tag_reader_client() const;
TaskManager* task_manager() const;
void MoveToNewThread(QObject* object);
void MoveToThread(QObject* object, QThread* thread);
@ -106,40 +106,14 @@ class Application : public QObject {
void ReloadSettings();
void OpenSettingsDialogAtPage(SettingsDialog::Page page);
signals:
signals:
void ErrorAdded(const QString& message);
void SettingsChanged();
void SettingsDialogRequested(SettingsDialog::Page page);
private:
QString language_name_;
TagReaderClient* tag_reader_client_;
Database* database_;
AlbumCoverLoader* album_cover_loader_;
PlaylistBackend* playlist_backend_;
PodcastBackend* podcast_backend_;
Appearance* appearance_;
CoverProviders* cover_providers_;
TaskManager* task_manager_;
Player* player_;
PlaylistManager* playlist_manager_;
CurrentArtLoader* current_art_loader_;
GlobalSearch* global_search_;
InternetModel* internet_model_;
Library* library_;
DeviceManager* device_manager_;
PodcastUpdater* podcast_updater_;
PodcastDeleter* podcast_deleter_;
PodcastDownloader* podcast_downloader_;
GPodderSync* gpodder_sync_;
MoodbarLoader* moodbar_loader_;
MoodbarController* moodbar_controller_;
NetworkRemote* network_remote_;
NetworkRemoteHelper* network_remote_helper_;
Scrobbler* scrobbler_;
QList<QObject*> objects_in_threads_;
std::unique_ptr<ApplicationImpl> p_;
QList<QThread*> threads_;
};

View File

@ -42,30 +42,31 @@ const char* CommandlineOptions::kHelpText =
" -t, --play-pause %6\n"
" -u, --pause %7\n"
" -s, --stop %8\n"
" -r, --previous %9\n"
" -f, --next %10\n"
" -v, --volume <value> %11\n"
" --volume-up %12\n"
" --volume-down %13\n"
" --volume-increase-by %14\n"
" --volume-decrease-by %15\n"
" --seek-to <seconds> %16\n"
" --seek-by <seconds> %17\n"
" --restart-or-previous %18\n"
" -q, --stop-after-current %9\n"
" -r, --previous %10\n"
" -f, --next %11\n"
" -v, --volume <value> %12\n"
" --volume-up %13\n"
" --volume-down %14\n"
" --volume-increase-by %15\n"
" --volume-decrease-by %16\n"
" --seek-to <seconds> %17\n"
" --seek-by <seconds> %18\n"
" --restart-or-previous %19\n"
"\n"
"%19:\n"
" -a, --append %20\n"
" -l, --load %21\n"
" -k, --play-track <n> %22\n"
"%20:\n"
" -a, --append %21\n"
" -l, --load %22\n"
" -k, --play-track <n> %23\n"
"\n"
"%23:\n"
" -o, --show-osd %24\n"
" -y, --toggle-pretty-osd %25\n"
" -g, --language <lang> %26\n"
" --quiet %27\n"
" --verbose %28\n"
" --log-levels <levels> %29\n"
" --version %30\n";
"%24:\n"
" -o, --show-osd %25\n"
" -y, --toggle-pretty-osd %26\n"
" -g, --language <lang> %27\n"
" --quiet %28\n"
" --verbose %29\n"
" --log-levels <levels> %30\n"
" --version %31\n";
const char* CommandlineOptions::kVersionText = "Clementine %1";
@ -111,6 +112,7 @@ bool CommandlineOptions::Parse() {
{"play-pause", no_argument, 0, 't'},
{"pause", no_argument, 0, 'u'},
{"stop", no_argument, 0, 's'},
{"stop-after-current", no_argument, 0, 'q'},
{"previous", no_argument, 0, 'r'},
{"next", no_argument, 0, 'f'},
{"volume", required_argument, 0, 'v'},
@ -136,7 +138,7 @@ bool CommandlineOptions::Parse() {
// Parse the arguments
bool ok = false;
forever {
int c = getopt_long(argc_, argv_, "hptusrfv:alk:oyg:", kOptions, nullptr);
int c = getopt_long(argc_, argv_, "hptusqrfv:alk:oyg:", kOptions, nullptr);
// End of the options
if (c == -1) break;
@ -150,8 +152,9 @@ bool CommandlineOptions::Parse() {
tr("Start the playlist currently playing"),
tr("Play if stopped, pause if playing"),
tr("Pause playback"), tr("Stop playback"),
tr("Skip backwards in playlist"))
.arg(tr("Skip forwards in playlist"),
tr("Stop playback after current track"))
.arg(tr("Skip backwards in playlist"),
tr("Skip forwards in playlist"),
tr("Set the volume to <value> percent"),
tr("Increase the volume by 4%"),
tr("Decrease the volume by 4%"),
@ -191,6 +194,9 @@ bool CommandlineOptions::Parse() {
case 's':
player_action_ = Player_Stop;
break;
case 'q':
player_action_ = Player_StopAfterCurrent;
break;
case 'r':
player_action_ = Player_Previous;
break;

View File

@ -54,6 +54,7 @@ class CommandlineOptions {
Player_Previous = 5,
Player_Next = 6,
Player_RestartOrPrevious = 7,
Player_StopAfterCurrent = 8,
};
bool Parse();

View File

@ -18,15 +18,12 @@
*/
#include "mpris.h"
#include "mpris1.h"
#include "mpris2.h"
namespace mpris {
Mpris::Mpris(Application* app, QObject* parent)
: QObject(parent),
mpris1_(new mpris::Mpris1(app, this)),
mpris2_(new mpris::Mpris2(app, mpris1_, this)) {
: QObject(parent), mpris2_(new mpris::Mpris2(app, this)) {
connect(mpris2_, SIGNAL(RaiseMainWindow()), SIGNAL(RaiseMainWindow()));
}

View File

@ -1,377 +0,0 @@
/* This file is part of Clementine.
Copyright 2011, Paweł Bara <keirangtp@gmail.com>
Copyright 2011-2012, David Sansome <me@davidsansome.com>
Copyright 2013, Uwe Klotz <uwe.klotz@gmail.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "mpris1.h"
#include "mpris_common.h"
#include "core/application.h"
#include "core/logging.h"
#include "covers/currentartloader.h"
#include <QCoreApplication>
#include <QDBusConnection>
#include "core/mpris_player.h"
#include "core/mpris_root.h"
#include "core/mpris_tracklist.h"
#include "core/timeconstants.h"
#include "engines/enginebase.h"
#include "playlist/playlist.h"
#include "playlist/playlistmanager.h"
#include "playlist/playlistsequence.h"
namespace mpris {
const char* Mpris1::kDefaultDbusServiceName = "org.mpris.clementine";
Mpris1::Mpris1(Application* app, QObject* parent,
const QString& dbus_service_name)
: QObject(parent),
dbus_service_name_(dbus_service_name),
root_(nullptr),
player_(nullptr),
tracklist_(nullptr) {
qDBusRegisterMetaType<DBusStatus>();
qDBusRegisterMetaType<Version>();
if (dbus_service_name_.isEmpty()) {
dbus_service_name_ = kDefaultDbusServiceName;
}
if (!QDBusConnection::sessionBus().registerService(dbus_service_name_)) {
qLog(Warning) << "Failed to register" << dbus_service_name_
<< "on the session bus";
return;
}
root_ = new Mpris1Root(app, this);
player_ = new Mpris1Player(app, this);
tracklist_ = new Mpris1TrackList(app, this);
connect(app->current_art_loader(),
SIGNAL(ArtLoaded(const Song&, const QString&, const QImage&)),
player_,
SLOT(CurrentSongChanged(const Song&, const QString&, const QImage&)));
}
Mpris1::~Mpris1() {
QDBusConnection::sessionBus().unregisterService(dbus_service_name_);
}
Mpris1Root::Mpris1Root(Application* app, QObject* parent)
: QObject(parent), app_(app) {
new MprisRoot(this);
QDBusConnection::sessionBus().registerObject("/", this);
}
Mpris1Player::Mpris1Player(Application* app, QObject* parent)
: QObject(parent), app_(app) {
new MprisPlayer(this);
QDBusConnection::sessionBus().registerObject("/Player", this);
connect(app_->player()->engine(), SIGNAL(StateChanged(Engine::State)),
SLOT(EngineStateChanged(Engine::State)));
connect(app_->playlist_manager(), SIGNAL(PlaylistManagerInitialized()),
SLOT(PlaylistManagerInitialized()));
}
// when PlaylistManager gets it ready, we connect PlaylistSequence with this
void Mpris1Player::PlaylistManagerInitialized() {
connect(app_->playlist_manager()->sequence(),
SIGNAL(ShuffleModeChanged(PlaylistSequence::ShuffleMode)),
SLOT(ShuffleModeChanged()));
connect(app_->playlist_manager()->sequence(),
SIGNAL(RepeatModeChanged(PlaylistSequence::RepeatMode)),
SLOT(RepeatModeChanged()));
}
Mpris1TrackList::Mpris1TrackList(Application* app, QObject* parent)
: QObject(parent), app_(app) {
new MprisTrackList(this);
QDBusConnection::sessionBus().registerObject("/TrackList", this);
connect(app_->playlist_manager(), SIGNAL(PlaylistChanged(Playlist*)),
SLOT(PlaylistChanged(Playlist*)));
}
void Mpris1TrackList::PlaylistChanged(Playlist* playlist) {
emit TrackListChange(playlist->rowCount());
}
// we use the state from event and don't try to obtain it from Player
// later because only the event's version is really the current one
void Mpris1Player::EngineStateChanged(Engine::State state) {
emit StatusChange(GetStatus(state));
emit CapsChange(GetCaps(state));
}
void Mpris1Player::CurrentSongChanged(const Song& song, const QString& art_uri,
const QImage&) {
last_metadata_ = Mpris1::GetMetadata(song);
if (!art_uri.isEmpty()) {
AddMetadata("arturl", art_uri, &last_metadata_);
}
emit TrackChange(last_metadata_);
emit StatusChange(GetStatus());
emit CapsChange(GetCaps());
}
QString Mpris1Root::Identity() {
return QString("%1 %2").arg(QCoreApplication::applicationName(),
QCoreApplication::applicationVersion());
}
Version Mpris1Root::MprisVersion() {
Version version;
version.major = 1;
version.minor = 0;
return version;
}
void Mpris1Root::Quit() { qApp->quit(); }
void Mpris1Player::Pause() { app_->player()->PlayPause(); }
void Mpris1Player::Stop() { app_->player()->Stop(); }
void Mpris1Player::Prev() { app_->player()->Previous(); }
void Mpris1Player::Play() { app_->player()->Play(); }
void Mpris1Player::Next() { app_->player()->Next(); }
void Mpris1Player::Repeat(bool repeat) {
app_->playlist_manager()->sequence()->SetRepeatMode(
repeat ? PlaylistSequence::Repeat_Track : PlaylistSequence::Repeat_Off);
}
void Mpris1Player::ShuffleModeChanged() { emit StatusChange(GetStatus()); }
void Mpris1Player::RepeatModeChanged() { emit StatusChange(GetStatus()); }
DBusStatus Mpris1Player::GetStatus() const {
return GetStatus(app_->player()->GetState());
}
DBusStatus Mpris1Player::GetStatus(Engine::State state) const {
DBusStatus status;
switch (state) {
case Engine::Playing:
status.play = DBusStatus::Mpris_Playing;
break;
case Engine::Paused:
status.play = DBusStatus::Mpris_Paused;
break;
case Engine::Empty:
case Engine::Idle:
default:
status.play = DBusStatus::Mpris_Stopped;
break;
}
if (app_->playlist_manager()->sequence()) {
PlaylistManagerInterface* playlists_ = app_->playlist_manager();
PlaylistSequence::RepeatMode repeat_mode =
playlists_->sequence()->repeat_mode();
status.random =
playlists_->sequence()->shuffle_mode() == PlaylistSequence::Shuffle_Off
? 0
: 1;
status.repeat = repeat_mode == PlaylistSequence::Repeat_Track ? 1 : 0;
status.repeat_playlist =
(repeat_mode == PlaylistSequence::Repeat_Album ||
repeat_mode == PlaylistSequence::Repeat_Playlist ||
repeat_mode == PlaylistSequence::Repeat_Track)
? 1
: 0;
}
return status;
}
void Mpris1Player::VolumeSet(int volume) { app_->player()->SetVolume(volume); }
int Mpris1Player::VolumeGet() const { return app_->player()->GetVolume(); }
void Mpris1Player::PositionSet(int pos_msec) {
app_->player()->SeekTo(pos_msec / kMsecPerSec);
}
int Mpris1Player::PositionGet() const {
return app_->player()->engine()->position_nanosec() / kNsecPerMsec;
}
QVariantMap Mpris1Player::GetMetadata() const { return last_metadata_; }
int Mpris1Player::GetCaps() const {
return GetCaps(app_->player()->GetState());
}
int Mpris1Player::GetCaps(Engine::State state) const {
int caps = CAN_HAS_TRACKLIST;
PlaylistItemPtr current_item = app_->player()->GetCurrentItem();
PlaylistManagerInterface* playlists = app_->playlist_manager();
if (playlists->active()) {
// play is disabled when playlist is empty or when last.fm stream is already
// playing
if (playlists->active() && playlists->active()->rowCount() != 0 &&
!(state == Engine::Playing &&
(app_->player()->GetCurrentItem()->options() &
PlaylistItem::LastFMControls))) {
caps |= CAN_PLAY;
}
if (playlists->active()->next_row() != -1) {
caps |= CAN_GO_NEXT;
}
if (playlists->active()->previous_row() != -1 ||
app_->player()->PreviousWouldRestartTrack()) {
caps |= CAN_GO_PREV;
}
}
if (current_item) {
caps |= CAN_PROVIDE_METADATA;
if (state == Engine::Playing &&
!(current_item->options() & PlaylistItem::PauseDisabled)) {
caps |= CAN_PAUSE;
}
if (state != Engine::Empty && !current_item->Metadata().is_stream()) {
caps |= CAN_SEEK;
}
}
return caps;
}
void Mpris1Player::VolumeUp(int change) { VolumeSet(VolumeGet() + change); }
void Mpris1Player::VolumeDown(int change) { VolumeSet(VolumeGet() - change); }
void Mpris1Player::Mute() { app_->player()->Mute(); }
void Mpris1Player::ShowOSD() { app_->player()->ShowOSD(); }
int Mpris1TrackList::AddTrack(const QString& track, bool play) {
app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(track),
-1, play);
return 0;
}
void Mpris1TrackList::DelTrack(int index) {
app_->playlist_manager()->active()->removeRows(index, 1);
}
int Mpris1TrackList::GetCurrentTrack() const {
return app_->playlist_manager()->active()->current_row();
}
int Mpris1TrackList::GetLength() const {
return app_->playlist_manager()->active()->rowCount();
}
QVariantMap Mpris1TrackList::GetMetadata(int pos) const {
PlaylistItemPtr item = app_->player()->GetItemAt(pos);
if (!item) return QVariantMap();
return Mpris1::GetMetadata(item->Metadata());
}
void Mpris1TrackList::SetLoop(bool enable) {
app_->playlist_manager()->active()->sequence()->SetRepeatMode(
enable ? PlaylistSequence::Repeat_Playlist
: PlaylistSequence::Repeat_Off);
}
void Mpris1TrackList::SetRandom(bool enable) {
app_->playlist_manager()->active()->sequence()->SetShuffleMode(
enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
}
void Mpris1TrackList::PlayTrack(int index) {
app_->player()->PlayAt(index, Engine::Manual, true);
}
QVariantMap Mpris1::GetMetadata(const Song& song) {
QVariantMap ret;
AddMetadata("location", song.url().toString(), &ret);
AddMetadata("title", song.PrettyTitle(), &ret);
AddMetadata("artist", song.artist(), &ret);
AddMetadata("album", song.album(), &ret);
AddMetadata("time", song.length_nanosec() / kNsecPerSec, &ret);
AddMetadata("mtime", song.length_nanosec() / kNsecPerMsec, &ret);
AddMetadata("tracknumber", song.track(), &ret);
AddMetadata("year", song.year(), &ret);
AddMetadata("genre", song.genre(), &ret);
AddMetadata("disc", song.disc(), &ret);
AddMetadata("comment", song.comment(), &ret);
AddMetadata("audio-bitrate", song.bitrate(), &ret);
AddMetadata("audio-samplerate", song.samplerate(), &ret);
AddMetadata("bpm", song.bpm(), &ret);
AddMetadata("composer", song.composer(), &ret);
AddMetadata("performer", song.performer(), &ret);
AddMetadata("grouping", song.grouping(), &ret);
AddMetadata("lyrics", song.lyrics(), &ret);
if (song.rating() != -1.0) {
AddMetadata("rating", song.rating() * 5, &ret);
}
return ret;
}
} // namespace mpris
QDBusArgument& operator<<(QDBusArgument& arg, const Version& version) {
arg.beginStructure();
arg << version.major << version.minor;
arg.endStructure();
return arg;
}
const QDBusArgument& operator>>(const QDBusArgument& arg, Version& version) {
arg.beginStructure();
arg >> version.major >> version.minor;
arg.endStructure();
return arg;
}
QDBusArgument& operator<<(QDBusArgument& arg, const DBusStatus& status) {
arg.beginStructure();
arg << status.play;
arg << status.random;
arg << status.repeat;
arg << status.repeat_playlist;
arg.endStructure();
return arg;
}
const QDBusArgument& operator>>(const QDBusArgument& arg, DBusStatus& status) {
arg.beginStructure();
arg >> status.play;
arg >> status.random;
arg >> status.repeat;
arg >> status.repeat_playlist;
arg.endStructure();
return arg;
}

View File

@ -1,201 +0,0 @@
/* This file is part of Clementine.
Copyright 2011-2012, David Sansome <me@davidsansome.com>
Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
Copyright 2014, John Maguire <john.maguire@gmail.com>
Clementine is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Clementine is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CORE_MPRIS1_H_
#define CORE_MPRIS1_H_
#include "core/player.h"
#include <QDateTime>
#include <QDBusArgument>
#include <QObject>
class Application;
class Playlist;
struct DBusStatus { // From Amarok.
DBusStatus()
: play(Mpris_Stopped), random(0), repeat(0), repeat_playlist(0) {}
int play; // Playing = 0, Paused = 1, Stopped = 2
int random; // Linearly = 0, Randomly = 1
int repeat; // Go_To_Next = 0, Repeat_Current = 1
int repeat_playlist; // Stop_When_Finished = 0, Never_Give_Up_Playing = 1,
// Never_Let_You_Down = 42
enum MprisPlayState {
Mpris_Playing = 0,
Mpris_Paused = 1,
Mpris_Stopped = 2,
};
};
Q_DECLARE_METATYPE(DBusStatus);
QDBusArgument& operator<<(QDBusArgument& arg, const DBusStatus& status);
const QDBusArgument& operator>>(const QDBusArgument& arg, DBusStatus& status);
struct Version {
quint16 minor;
quint16 major;
};
Q_DECLARE_METATYPE(Version);
QDBusArgument& operator<<(QDBusArgument& arg, const Version& version);
const QDBusArgument& operator>>(const QDBusArgument& arg, Version& version);
namespace mpris {
enum DBusCaps {
NONE = 0,
CAN_GO_NEXT = 1 << 0,
CAN_GO_PREV = 1 << 1,
CAN_PAUSE = 1 << 2,
CAN_PLAY = 1 << 3,
CAN_SEEK = 1 << 4,
CAN_PROVIDE_METADATA = 1 << 5,
CAN_HAS_TRACKLIST = 1 << 6,
};
class Mpris1Root;
class Mpris1Player;
class Mpris1TrackList;
class Mpris1 : public QObject {
Q_OBJECT
public:
Mpris1(Application* app, QObject* parent = nullptr,
const QString& dbus_service_name = QString());
~Mpris1();
static QVariantMap GetMetadata(const Song& song);
Mpris1Root* root() const { return root_; }
Mpris1Player* player() const { return player_; }
Mpris1TrackList* tracklist() const { return tracklist_; }
private:
static const char* kDefaultDbusServiceName;
QString dbus_service_name_;
Mpris1Root* root_;
Mpris1Player* player_;
Mpris1TrackList* tracklist_;
};
class Mpris1Root : public QObject {
Q_OBJECT
public:
explicit Mpris1Root(Application* app, QObject* parent = nullptr);
QString Identity();
void Quit();
Version MprisVersion();
private:
Application* app_;
};
class Mpris1Player : public QObject {
Q_OBJECT
public:
explicit Mpris1Player(Application* app, QObject* parent = nullptr);
void Pause();
void Stop();
void Prev();
void Play();
void Next();
void Repeat(bool);
// those methods will use engine's state obtained with player->GetState()
// method
DBusStatus GetStatus() const;
int GetCaps() const;
// those methods will use engine's state provided as an argument
DBusStatus GetStatus(Engine::State state) const;
int GetCaps(Engine::State state) const;
void VolumeSet(int volume);
int VolumeGet() const;
void PositionSet(int pos_msec);
int PositionGet() const;
QVariantMap GetMetadata() const;
// Amarok extensions
void VolumeUp(int vol);
void VolumeDown(int vol);
void Mute();
void ShowOSD();
public slots:
void CurrentSongChanged(const Song& song, const QString& art_uri,
const QImage&);
signals:
void CapsChange(int);
void TrackChange(const QVariantMap&);
void StatusChange(DBusStatus);
private slots:
void PlaylistManagerInitialized();
void EngineStateChanged(Engine::State state);
void ShuffleModeChanged();
void RepeatModeChanged();
private:
Application* app_;
QVariantMap last_metadata_;
};
class Mpris1TrackList : public QObject {
Q_OBJECT
public:
explicit Mpris1TrackList(Application* app, QObject* parent = nullptr);
int AddTrack(const QString&, bool);
void DelTrack(int index);
int GetCurrentTrack() const;
int GetLength() const;
QVariantMap GetMetadata(int) const;
void SetLoop(bool enable);
void SetRandom(bool enable);
// Amarok extension
void PlayTrack(int index);
signals:
void TrackListChange(int i);
private slots:
void PlaylistChanged(Playlist* playlist);
private:
Application* app_;
};
} // namespace mpris
#endif // CORE_MPRIS1_H_

View File

@ -25,11 +25,14 @@
#include <algorithm>
#include <QApplication>
#include <QDBusConnection>
#include <QtConcurrentRun>
#include "config.h"
#include "mpris_common.h"
#include "mpris1.h"
#include "core/application.h"
#include "core/logging.h"
#include "core/mpris_common.h"
#include "core/mpris2_player.h"
#include "core/mpris2_playlists.h"
#include "core/mpris2_root.h"
@ -43,10 +46,6 @@
#include "playlist/playlistsequence.h"
#include "ui/mainwindow.h"
#include <QApplication>
#include <QDBusConnection>
#include <QtConcurrentRun>
QDBusArgument& operator<<(QDBusArgument& arg, const MprisPlaylist& playlist) {
arg.beginStructure();
arg << playlist.id << playlist.name << playlist.icon;
@ -84,8 +83,7 @@ const char* Mpris2::kMprisObjectPath = "/org/mpris/MediaPlayer2";
const char* Mpris2::kServiceName = "org.mpris.MediaPlayer2.clementine";
const char* Mpris2::kFreedesktopPath = "org.freedesktop.DBus.Properties";
Mpris2::Mpris2(Application* app, Mpris1* mpris1, QObject* parent)
: QObject(parent), app_(app), mpris1_(mpris1) {
Mpris2::Mpris2(Application* app, QObject* parent) : QObject(parent), app_(app) {
new Mpris2Root(this);
new Mpris2TrackList(this);
new Mpris2Player(this);
@ -134,13 +132,19 @@ void Mpris2::EngineStateChanged(Engine::State newState) {
}
EmitNotification("PlaybackStatus", PlaybackStatus(newState));
if (newState == Engine::Playing)
EmitNotification("CanSeek", CanSeek(newState));
}
void Mpris2::VolumeChanged() { EmitNotification("Volume"); }
void Mpris2::ShuffleModeChanged() { EmitNotification("Shuffle"); }
void Mpris2::RepeatModeChanged() { EmitNotification("LoopStatus"); }
void Mpris2::RepeatModeChanged() {
EmitNotification("LoopStatus");
EmitNotification("CanGoNext", CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious());
}
void Mpris2::EmitNotification(const QString& name, const QVariant& val) {
EmitNotification(name, val, "org.mpris.MediaPlayer2.Player");
@ -171,11 +175,17 @@ void Mpris2::EmitNotification(const QString& name) {
value = Volume();
else if (name == "Position")
value = Position();
else if (name == "CanGoNext")
value = CanGoNext();
else if (name == "CanGoPrevious")
value = CanGoPrevious();
else if (name == "CanSeek")
value = CanSeek();
if (value.isValid()) EmitNotification(name, value);
}
// ------------------Root Interface--------------- //
// ------------------Root Interface--------------- //
bool Mpris2::CanQuit() const { return true; }
@ -293,40 +303,35 @@ double Mpris2::Rate() const { return 1.0; }
void Mpris2::SetRate(double rate) {
if (rate == 0) {
if (mpris1_->player()) {
mpris1_->player()->Pause();
}
app_->player()->Pause();
}
}
bool Mpris2::Shuffle() const {
if (mpris1_->player()) {
return mpris1_->player()->GetStatus().random;
} else {
return false;
}
return app_->playlist_manager()->sequence()->shuffle_mode() !=
PlaylistSequence::Shuffle_Off;
}
void Mpris2::SetShuffle(bool value) {
if (mpris1_->tracklist()) {
mpris1_->tracklist()->SetRandom(value);
}
void Mpris2::SetShuffle(bool enable) {
app_->playlist_manager()->active()->sequence()->SetShuffleMode(
enable ? PlaylistSequence::Shuffle_All : PlaylistSequence::Shuffle_Off);
}
QVariantMap Mpris2::Metadata() const { return last_metadata_; }
QString Mpris2::current_track_id() const {
if (!mpris1_->tracklist()) {
return QString();
}
return QString("/org/mpris/MediaPlayer2/Track/%1")
.arg(QString::number(mpris1_->tracklist()->GetCurrentTrack()));
.arg(QString::number(app_->playlist_manager()->active()->current_row()));
}
// We send Metadata change notification as soon as the process of
// changing song starts...
void Mpris2::CurrentSongChanged(const Song& song) { ArtLoaded(song, ""); }
void Mpris2::CurrentSongChanged(const Song& song) {
ArtLoaded(song, "");
EmitNotification("CanGoNext", CanGoNext());
EmitNotification("CanGoPrevious", CanGoPrevious());
EmitNotification("CanSeek", CanSeek());
}
// ... and we add the cover information later, when it's available.
void Mpris2::ArtLoaded(const Song& song, const QString& art_uri) {
@ -349,13 +354,7 @@ void Mpris2::ArtLoaded(const Song& song, const QString& art_uri) {
EmitNotification("Metadata", last_metadata_);
}
double Mpris2::Volume() const {
if (mpris1_->player()) {
return static_cast<double>(mpris1_->player()->VolumeGet()) / 100;
} else {
return 0.0;
}
}
double Mpris2::Volume() const { return app_->player()->GetVolume() / 100.0; }
void Mpris2::SetVolume(double value) { app_->player()->SetVolume(value * 100); }
@ -368,40 +367,39 @@ double Mpris2::MaximumRate() const { return 1.0; }
double Mpris2::MinimumRate() const { return 1.0; }
bool Mpris2::CanGoNext() const {
if (mpris1_->player()) {
return mpris1_->player()->GetCaps() & CAN_GO_NEXT;
} else {
return true;
}
return app_->playlist_manager()->active() &&
app_->playlist_manager()->active()->next_row() != -1;
}
bool Mpris2::CanGoPrevious() const {
if (mpris1_->player()) {
return mpris1_->player()->GetCaps() & CAN_GO_PREV;
} else {
return true;
}
return app_->playlist_manager()->active() &&
(app_->playlist_manager()->active()->previous_row() != -1 ||
app_->player()->PreviousWouldRestartTrack());
}
bool Mpris2::CanPlay() const { return mpris1_->player()->GetCaps() & CAN_PLAY; }
bool Mpris2::CanPlay() const {
return app_->playlist_manager()->active() &&
app_->playlist_manager()->active()->rowCount() != 0 &&
!(app_->player()->GetState() == Engine::Playing &&
(app_->player()->GetCurrentItem()->options() &
PlaylistItem::LastFMControls));
}
// This one's a bit different than MPRIS 1 - we want this to be true even when
// the song is already paused or stopped.
bool Mpris2::CanPause() const {
if (mpris1_->player()) {
return mpris1_->player()->GetCaps() & CAN_PAUSE ||
PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
} else {
return true;
}
return (app_->player()->GetCurrentItem() &&
app_->player()->GetState() == Engine::Playing &&
!(app_->player()->GetCurrentItem()->options() &
PlaylistItem::PauseDisabled)) ||
PlaybackStatus() == "Paused" || PlaybackStatus() == "Stopped";
}
bool Mpris2::CanSeek() const {
if (mpris1_->player()) {
return mpris1_->player()->GetCaps() & CAN_SEEK;
} else {
return true;
}
bool Mpris2::CanSeek() const { return CanSeek(app_->player()->GetState()); }
bool Mpris2::CanSeek(Engine::State state) const {
return app_->player()->GetCurrentItem() && state != Engine::Empty &&
!app_->player()->GetCurrentItem()->Metadata().is_stream();
}
bool Mpris2::CanControl() const { return true; }
@ -458,9 +456,8 @@ void Mpris2::SetPosition(const QDBusObjectPath& trackId, qlonglong offset) {
}
void Mpris2::OpenUri(const QString& uri) {
if (mpris1_->tracklist()) {
mpris1_->tracklist()->AddTrack(uri, true);
}
app_->playlist_manager()->active()->InsertUrls(QList<QUrl>() << QUrl(uri), -1,
true);
}
TrackIds Mpris2::Tracks() const {

View File

@ -22,12 +22,12 @@
#ifndef CORE_MPRIS2_H_
#define CORE_MPRIS2_H_
#include "playlist/playlistitem.h"
#include <QMetaObject>
#include <QObject>
#include <QtDBus>
#include "playlist/playlistitem.h"
class Application;
class MainWindow;
class Playlist;
@ -61,12 +61,12 @@ const QDBusArgument& operator>>(const QDBusArgument& arg,
namespace mpris {
class Mpris1;
class Mpris2 : public QObject {
Q_OBJECT
public:
Mpris2(Application* app, QObject* parent = nullptr);
// org.mpris.MediaPlayer2 MPRIS 2.0 Root interface
Q_PROPERTY(bool CanQuit READ CanQuit)
Q_PROPERTY(bool CanRaise READ CanRaise)
@ -106,8 +106,6 @@ class Mpris2 : public QObject {
Q_PROPERTY(QStringList Orderings READ Orderings)
Q_PROPERTY(MaybePlaylist ActivePlaylist READ ActivePlaylist)
Mpris2(Application* app, Mpris1* mpris1, QObject* parent = nullptr);
// Root Properties
bool CanQuit() const;
bool CanRaise() const;
@ -179,7 +177,7 @@ class Mpris2 : public QObject {
QList<MprisPlaylist> GetPlaylists(quint32 index, quint32 max_count,
const QString& order, bool reverse_order);
signals:
signals:
// Player
void Seeked(qlonglong position);
@ -217,6 +215,8 @@ class Mpris2 : public QObject {
QString current_track_id() const;
bool CanSeek(Engine::State state) const;
QString DesktopEntryAbsolutePath() const;
private:
@ -227,7 +227,6 @@ class Mpris2 : public QObject {
QVariantMap last_metadata_;
Application* app_;
Mpris1* mpris1_;
};
} // namespace mpris

View File

@ -26,6 +26,7 @@
#include "core/application.h"
#include "covers/albumcoverloader.h"
#include "playlist/playlistmanager.h"
#include "ui/iconloader.h"
CurrentArtLoader::CurrentArtLoader(Application* app, QObject* parent)
: QObject(parent),
@ -34,7 +35,9 @@ CurrentArtLoader::CurrentArtLoader(Application* app, QObject* parent)
id_(0) {
options_.scale_output_image_ = false;
options_.pad_output_image_ = false;
options_.default_output_image_ = QImage(":nocover.png");
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
options_.default_output_image_ = nocover.pixmap(nocover.availableSizes()
.last()).toImage();
connect(app_->album_cover_loader(), SIGNAL(ImageLoaded(quint64, QImage)),
SLOT(TempArtLoaded(quint64, QImage)));

View File

@ -1,87 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="Pause">
</method>
<method name="Stop">
</method>
<method name="Play">
</method>
<method name="Prev">
</method>
<method name="Next">
</method>
<method name="Repeat">
<arg type="b" direction="in"/>
</method>
<method name="GetStatus">
<arg type="(iiii)" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="DBusStatus"/>
</method>
<method name="VolumeSet">
<arg type="i" direction="in"/>
</method>
<method name="VolumeGet">
<arg type="i" direction="out"/>
</method>
<method name="PositionSet">
<arg type="i" direction="in"/>
</method>
<method name="PositionGet">
<arg type="i" direction="out"/>
</method>
<method name="GetMetadata">
<arg type="a{sv}" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<method name="GetCaps">
<arg type="i" direction="out" />
</method>
<signal name="TrackChange">
<arg type="a{sv}"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="QVariantMap"/>
</signal>
<signal name="StatusChange">
<arg type="(iiii)"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.In0" value="DBusStatus"/>
</signal>
<signal name="CapsChange">
<arg type="i" />
</signal>
<!-- NB: Amarok extensions to the mpris spec -->
<method name="VolumeUp">
<arg type="i" direction="in"/>
</method>
<method name="VolumeDown">
<arg type="i" direction="in"/>
</method>
<method name="Mute">
</method>
<method name="ShowOSD">
</method>
</interface>
</node>

View File

@ -1,20 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="Identity">
<arg type="s" direction="out"/>
</method>
<method name="Quit">
</method>
<method name="MprisVersion">
<arg type="(qq)" direction="out"/>
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="Version"/>
</method>
</interface>
</node>

View File

@ -1,48 +0,0 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.freedesktop.MediaPlayer">
<method name="GetMetadata">
<arg type="i" direction="in" />
<arg type="a{sv}" direction="out" />
<annotation name="com.trolltech.QtDBus.QtTypeName.Out0" value="QVariantMap"/>
</method>
<method name="GetCurrentTrack">
<arg type="i" direction="out" />
</method>
<method name="GetLength">
<arg type="i" direction="out" />
</method>
<method name="AddTrack">
<arg type="s" direction="in" />
<arg type="b" direction="in" />
<arg type="i" direction="out" />
</method>
<method name="DelTrack">
<arg direction="in" type="i" />
</method>
<method name="SetLoop">
<arg direction="in" type="b" />
</method>
<method name="SetRandom">
<arg direction="in" type="b" />
</method>
<method name="PlayTrack">
<arg direction="in" type="i" />
</method>
<signal name="TrackListChange">
<arg type="i" />
</signal>
</interface>
</node>

View File

@ -174,12 +174,12 @@ bool GstEnginePipeline::ReplaceDecodeBin(const QUrl& url) {
gst_object_unref(GST_OBJECT(pad));
// Tell spotify to start sending data to us.
SpotifyServer* spotify_server = InternetModel::Service<SpotifyService>()->server();
SpotifyServer* spotify_server =
InternetModel::Service<SpotifyService>()->server();
// Need to schedule this in the spotify server's thread
QMetaObject::invokeMethod(spotify_server, "StartPlayback",
Qt::QueuedConnection,
Q_ARG(QString, url.toString()),
Q_ARG(quint16, port));
QMetaObject::invokeMethod(
spotify_server, "StartPlayback", Qt::QueuedConnection,
Q_ARG(QString, url.toString()), Q_ARG(quint16, port));
} else {
new_bin = engine_->CreateElement("uridecodebin");
g_object_set(G_OBJECT(new_bin), "uri", url.toEncoded().constData(),
@ -334,13 +334,35 @@ bool GstEnginePipeline::Init() {
// Configure the fakesink properly
g_object_set(G_OBJECT(probe_sink), "sync", TRUE, nullptr);
// Set the equalizer bands
g_object_set(G_OBJECT(equalizer_), "num-bands", 10, nullptr);
// Setting the equalizer bands:
//
// GStreamer's GstIirEqualizerNBands sets up shelve filters for the first and
// last bands as corner cases. That was causing the "inverted slider" bug.
// As a workaround, we create two dummy bands at both ends of the spectrum.
// This causes the actual first and last adjustable bands to be
// implemented using band-pass filters.
g_object_set(G_OBJECT(equalizer_), "num-bands", 10 + 2, nullptr);
// Dummy first band (bandwidth 0, cutting below 20Hz):
GstObject* first_band = GST_OBJECT(
gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), 0));
g_object_set(G_OBJECT(first_band), "freq", 20.0, "bandwidth", 0, "gain", 0.0f,
nullptr);
g_object_unref(G_OBJECT(first_band));
// Dummy last band (bandwidth 0, cutting over 20KHz):
GstObject* last_band = GST_OBJECT(gst_child_proxy_get_child_by_index(
GST_CHILD_PROXY(equalizer_), kEqBandCount + 1));
g_object_set(G_OBJECT(last_band), "freq", 20000.0, "bandwidth", 0, "gain",
0.0f, nullptr);
g_object_unref(G_OBJECT(last_band));
int last_band_frequency = 0;
for (int i = 0; i < kEqBandCount; ++i) {
GstObject* band = GST_OBJECT(
gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), i));
const int index_in_eq = i + 1;
GstObject* band = GST_OBJECT(gst_child_proxy_get_child_by_index(
GST_CHILD_PROXY(equalizer_), index_in_eq));
const float frequency = kEqBandFrequencies[i];
const float bandwidth = frequency - last_band_frequency;
@ -1097,8 +1119,10 @@ void GstEnginePipeline::UpdateEqualizer() {
else
gain *= 0.12;
GstObject* band = GST_OBJECT(
gst_child_proxy_get_child_by_index(GST_CHILD_PROXY(equalizer_), i));
const int index_in_eq = i + 1;
// Offset because of the first dummy band we created.
GstObject* band = GST_OBJECT(gst_child_proxy_get_child_by_index(
GST_CHILD_PROXY(equalizer_), index_in_eq));
g_object_set(G_OBJECT(band), "gain", gain, nullptr);
g_object_unref(G_OBJECT(band));
}

View File

@ -33,8 +33,10 @@ GlobalSearchModel::GlobalSearchModel(GlobalSearch* engine, QObject* parent)
group_by_[1] = LibraryModel::GroupBy_Album;
group_by_[2] = LibraryModel::GroupBy_None;
no_cover_icon_ = QPixmap(":nocover.png").scaled(
LibraryModel::kPrettyCoverSize, LibraryModel::kPrettyCoverSize,
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(
LibraryModel::kPrettyCoverSize,
LibraryModel::kPrettyCoverSize,
Qt::KeepAspectRatio, Qt::SmoothTransformation);
}

View File

@ -42,10 +42,32 @@ InternetService::InternetService(const QString& name, Application* app,
app_(app),
model_(model),
name_(name),
append_to_playlist_(nullptr),
replace_playlist_(nullptr),
open_in_new_playlist_(nullptr),
separator_(nullptr) {}
append_to_playlist_([&]() {
QAction* action = new QAction(
IconLoader::Load("media-playback-start", IconLoader::Base),
tr("Append to current playlist"), nullptr);
connect(action, SIGNAL(triggered()), this, SLOT(AppendToPlaylist()));
return action;
}),
replace_playlist_([&]() {
QAction* action = new QAction(
IconLoader::Load("media-playback-start", IconLoader::Base),
tr("Replace current playlist"), nullptr);
connect(action, SIGNAL(triggered()), this, SLOT(ReplacePlaylist()));
return action;
}),
open_in_new_playlist_([&]() {
QAction* action =
new QAction(IconLoader::Load("document-new", IconLoader::Base),
tr("Open in new playlist"), nullptr);
connect(action, SIGNAL(triggered()), this, SLOT(OpenInNewPlaylist()));
return action;
}),
separator_([]() {
QAction* action = new QAction(nullptr);
action->setSeparator(true);
return action;
}) {}
void InternetService::ShowUrlBox(const QString& title, const QString& url) {
QMessageBox url_box;
@ -64,50 +86,21 @@ void InternetService::ShowUrlBox(const QString& title, const QString& url) {
}
QList<QAction*> InternetService::GetPlaylistActions() {
if (!separator_) {
separator_ = new QAction(this);
separator_->setSeparator(true);
}
return QList<QAction*>() << GetAppendToPlaylistAction()
<< GetReplacePlaylistAction()
<< GetOpenInNewPlaylistAction() << separator_;
<< GetOpenInNewPlaylistAction() << separator_.get();
}
QAction* InternetService::GetAppendToPlaylistAction() {
if (!append_to_playlist_) {
append_to_playlist_ = new QAction(IconLoader::Load("media-playback-start",
IconLoader::Base),
tr("Append to current playlist"), this);
connect(append_to_playlist_, SIGNAL(triggered()), this,
SLOT(AppendToPlaylist()));
}
return append_to_playlist_;
return append_to_playlist_.get();
}
QAction* InternetService::GetReplacePlaylistAction() {
if (!replace_playlist_) {
replace_playlist_ = new QAction(IconLoader::Load("media-playback-start",
IconLoader::Base),
tr("Replace current playlist"), this);
connect(replace_playlist_, SIGNAL(triggered()), this,
SLOT(ReplacePlaylist()));
}
return replace_playlist_;
return replace_playlist_.get();
}
QAction* InternetService::GetOpenInNewPlaylistAction() {
if (!open_in_new_playlist_) {
open_in_new_playlist_ = new QAction(IconLoader::Load("document-new",
IconLoader::Base),
tr("Open in new playlist"), this);
connect(open_in_new_playlist_, SIGNAL(triggered()), this,
SLOT(OpenInNewPlaylist()));
}
return open_in_new_playlist_;
return open_in_new_playlist_.get();
}
void InternetService::AddItemToPlaylist(const QModelIndex& index,

View File

@ -23,10 +23,12 @@
#ifndef INTERNET_CORE_INTERNETSERVICE_H_
#define INTERNET_CORE_INTERNETSERVICE_H_
#include <QAction>
#include <QObject>
#include <QList>
#include <QUrl>
#include "core/lazy.h"
#include "core/song.h"
#include "playlist/playlistitem.h"
#include "smartplaylists/generator.h"
@ -83,7 +85,7 @@ class InternetService : public QObject {
virtual QString Icon() { return QString(); }
signals:
signals:
void StreamError(const QString& message);
void StreamMetadataFound(const QUrl& original_url, const Song& song);
@ -135,10 +137,10 @@ class InternetService : public QObject {
InternetModel* model_;
QString name_;
QAction* append_to_playlist_;
QAction* replace_playlist_;
QAction* open_in_new_playlist_;
QAction* separator_;
Lazy<QAction> append_to_playlist_;
Lazy<QAction> replace_playlist_;
Lazy<QAction> open_in_new_playlist_;
Lazy<QAction> separator_;
};
Q_DECLARE_METATYPE(InternetService*);

View File

@ -83,15 +83,15 @@ LastFMService::LastFMService(Application* app, QObject* parent)
scrobbling_enabled_(false),
connection_problems_(false),
app_(app) {
#ifdef HAVE_LIBLASTFM1
lastfm::ws::setScheme(lastfm::ws::Https);
#endif
ReloadSettings();
// we emit the signal the first time to be sure the buttons are in the right
// state
emit ScrobblingEnabledChanged(scrobbling_enabled_);
app_->cover_providers()->AddProvider(new LastFmCoverProvider(this));
}
LastFMService::~LastFMService() {}

View File

@ -291,7 +291,12 @@ void PodcastParser::ParseOutline(QXmlStreamReader* reader,
// Parse the feed and add it to this container
Podcast podcast;
podcast.set_description(attributes.value("description").toString());
podcast.set_title(attributes.value("text").toString());
QString title = attributes.value("title").toString();
if (title.isEmpty()) {
title = attributes.value("text").toString();
}
podcast.set_title(title);
podcast.set_image_url_large(QUrl::fromEncoded(
attributes.value("imageHref").toString().toAscii()));
podcast.set_url(QUrl::fromEncoded(

View File

@ -413,7 +413,8 @@ void SpotifyService::PlaylistsUpdated(const pb::spotify::Playlists& response) {
search_->setData(InternetModel::PlayBehaviour_MultipleItems,
InternetModel::Role_PlayBehaviour);
starred_ = new QStandardItem(QIcon(":/star-on.png"), tr("Starred"));
starred_ = new QStandardItem(IconLoader::Load("star-on", IconLoader::Other),
tr("Starred"));
starred_->setData(Type_StarredPlaylist, InternetModel::Role_Type);
starred_->setData(true, InternetModel::Role_CanLazyLoad);
starred_->setData(InternetModel::PlayBehaviour_MultipleItems,
@ -610,7 +611,8 @@ QList<QAction*> SpotifyService::playlistitem_actions(const Song& song) {
}
QAction* add_to_starred =
new QAction(QIcon(":/star-on.png"), tr("Add to Spotify starred"), this);
new QAction(IconLoader::Load("star-on", IconLoader::Other),
tr("Add to Spotify starred"), this);
connect(add_to_starred, SIGNAL(triggered()),
SLOT(AddCurrentSongToStarredPlaylist()));
playlistitem_actions_.append(add_to_starred);

View File

@ -138,7 +138,7 @@ SubsonicService::SubsonicService(Application* app, InternetModel* parent)
tr("Refresh catalogue"), this,
SLOT(ReloadDatabase()));
QAction* config_action = context_menu_->addAction(
IconLoader::Load("configure", IconLoader::Base), tr("Configure Subsonic..."),
IconLoader::Load("configure", IconLoader::Base), tr("Configure Subsonic..."),
this, SLOT(ShowConfig()));
context_menu_->addSeparator();
context_menu_->addMenu(library_filter_->menu());
@ -153,7 +153,7 @@ SubsonicService::SubsonicService(Application* app, InternetModel* parent)
SubsonicService::~SubsonicService() {}
QStandardItem* SubsonicService::CreateRootItem() {
root_ = new QStandardItem(IconLoader::Load("subsonic", IconLoader::Provider),
root_ = new QStandardItem(IconLoader::Load("subsonic", IconLoader::Provider),
kServiceName);
root_->setData(true, InternetModel::Role_CanLazyLoad);
return root_;
@ -403,6 +403,7 @@ void SubsonicService::UpdateServer(const QString& server) {
const int SubsonicLibraryScanner::kAlbumChunkSize = 500;
const int SubsonicLibraryScanner::kConcurrentRequests = 8;
const int SubsonicLibraryScanner::kCoverArtSize = 1024;
SubsonicLibraryScanner::SubsonicLibraryScanner(SubsonicService* service,
QObject* parent)

View File

@ -122,6 +122,8 @@ class SubsonicService : public InternetService {
static const char* kFtsTable;
static const int kMaxRedirects;
static const int kCoverArtSize;
signals:
void LoginStateChanged(SubsonicService::LoginState newstate);
@ -181,6 +183,7 @@ class SubsonicLibraryScanner : public QObject {
static const int kAlbumChunkSize;
static const int kConcurrentRequests;
static const int kCoverArtSize;
signals:
void ScanFinished();

View File

@ -107,9 +107,11 @@ LibraryModel::LibraryModel(LibraryBackend* backend, Application* app,
Utilities::GetConfigPath(Utilities::Path_CacheRoot) + "/pixmapcache");
icon_cache_->setMaximumCacheSize(LibraryModel::kIconCacheSize);
no_cover_icon_ = QPixmap(":nocover.png")
.scaled(kPrettyCoverSize, kPrettyCoverSize,
Qt::KeepAspectRatio, Qt::SmoothTransformation);
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
no_cover_icon_ = nocover.pixmap(nocover.availableSizes().last()).scaled(
kPrettyCoverSize, kPrettyCoverSize,
Qt::KeepAspectRatio,
Qt::SmoothTransformation);
connect(backend_, SIGNAL(SongsDiscovered(SongList)),
SLOT(SongsDiscovered(SongList)));

View File

@ -173,9 +173,10 @@ LibraryView::LibraryView(QWidget* parent)
app_(nullptr),
filter_(nullptr),
total_song_count_(-1),
nomusic_(":nomusic.png"),
context_menu_(nullptr),
is_in_keyboard_search_(false) {
QIcon nocover = IconLoader::Load("nocover", IconLoader::Other);
nomusic_ = nocover.pixmap(nocover.availableSizes().last());
setItemDelegate(new LibraryItemDelegate(this));
setAttribute(Qt::WA_MacShowFocusRect, false);
setHeaderHidden(true);

View File

@ -491,8 +491,9 @@ SongList LibraryWatcher::ScanNewFile(const QString& file, const QString& path,
// Ignore FILEs pointing to other media files. Also, watch out for incorrect
// media files. Playlist parser for CUEs considers every entry in sheet
// valid and we don't want invalid media getting into library!
QString file_nfd = file.normalized(QString::NormalizationForm_D);
for (const Song& cue_song : cue_parser_->Load(&cue, matching_cue, path)) {
if (cue_song.url().toLocalFile() == file) {
if (cue_song.url().toLocalFile().normalized(QString::NormalizationForm_D) == file_nfd) {
if (TagReaderClient::Instance()->IsMediaFileBlocking(file)) {
song_list << cue_song;
}

View File

@ -56,9 +56,6 @@
#include "core/song.h"
#include "core/ubuntuunityhack.h"
#include "core/utilities.h"
#include "covers/amazoncoverprovider.h"
#include "covers/coverproviders.h"
#include "covers/musicbrainzcoverprovider.h"
#include "engines/enginebase.h"
#include "smartplaylists/generator.h"
#include "ui/iconloader.h"
@ -448,11 +445,6 @@ int main(int argc, char* argv[]) {
QNetworkProxyFactory::setApplicationProxyFactory(
NetworkProxyFactory::Instance());
// Initialize the repository of cover providers. Last.fm registers itself
// when its service is created.
app.cover_providers()->AddProvider(new AmazonCoverProvider);
app.cover_providers()->AddProvider(new MusicbrainzCoverProvider);
#ifdef Q_OS_LINUX
// In 11.04 Ubuntu decided that the system tray should be reserved for certain
// whitelisted applications. Clementine will override this setting and insert

View File

@ -270,10 +270,15 @@ QRect MoodbarProxyStyle::subControlRect(ComplexControl cc,
case SC_SliderHandle: {
const QStyleOptionSlider* slider_opt =
qstyleoption_cast<const QStyleOptionSlider*>(opt);
int x = 0;
const int x = (slider_opt->sliderValue - slider_opt->minimum) *
(opt->rect.width() - kArrowWidth) /
(slider_opt->maximum - slider_opt->minimum);
/* slider_opt->{maximum,minimum} can have the value 0 (their default
values), so this check avoids a division by 0. */
if (slider_opt->maximum > slider_opt->minimum) {
x = (slider_opt->sliderValue - slider_opt->minimum) *
(opt->rect.width() - kArrowWidth) /
(slider_opt->maximum - slider_opt->minimum);
}
return QRect(QPoint(opt->rect.left() + x, opt->rect.top()),
QSize(kArrowWidth, kArrowHeight));

View File

@ -17,15 +17,18 @@
#include "networkremote.h"
#include "core/logging.h"
#include "covers/currentartloader.h"
#include "networkremote/zeroconf.h"
#include "playlist/playlistmanager.h"
#include <QDataStream>
#include <QSettings>
#include <QHostInfo>
#include <QNetworkProxy>
#include <QTcpServer>
#include "core/logging.h"
#include "covers/currentartloader.h"
#include "networkremote/incomingdataparser.h"
#include "networkremote/outgoingdatacreator.h"
#include "networkremote/zeroconf.h"
#include "playlist/playlistmanager.h"
const char* NetworkRemote::kSettingsGroup = "NetworkRemote";
const quint16 NetworkRemote::kDefaultServerPort = 5500;

View File

@ -3,14 +3,17 @@
#include <memory>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
#include <QObject>
#include "core/player.h"
#include "core/application.h"
#include "incomingdataparser.h"
#include "outgoingdatacreator.h"
#include "remoteclient.h"
class Application;
class IncomingDataParser;
class OutgoingDataCreator;
class QHostAddress;
class QImage;
class QTcpServer;
class QTcpSocket;
class RemoteClient;
class NetworkRemote : public QObject {
Q_OBJECT

View File

@ -15,10 +15,12 @@
along with Clementine. If not, see <http://www.gnu.org/licenses/>.
*/
#include "core/logging.h"
#include "networkremote/networkremotehelper.h"
#include "networkremote.h"
#include "networkremotehelper.h"
#include "core/application.h"
#include "core/logging.h"
#include "networkremote/networkremote.h"
#include "playlist/playlistmanager.h"
NetworkRemoteHelper* NetworkRemoteHelper::sInstance = nullptr;

View File

@ -17,14 +17,15 @@
#include "songsender.h"
#include "networkremote.h"
#include <QFileInfo>
#include "core/application.h"
#include "core/logging.h"
#include "core/utilities.h"
#include "library/librarybackend.h"
#include "networkremote/networkremote.h"
#include "networkremote/outgoingdatacreator.h"
#include "networkremote/remoteclient.h"
#include "playlist/playlistitem.h"
const quint32 SongSender::kFileChunkSize = 100000; // in Bytes
@ -32,16 +33,18 @@ const quint32 SongSender::kFileChunkSize = 100000; // in Bytes
SongSender::SongSender(Application* app, RemoteClient* client)
: app_(app),
client_(client),
transcoder_(new Transcoder(this, NetworkRemote::kTranscoderSettingPostfix)) {
transcoder_(
new Transcoder(this, NetworkRemote::kTranscoderSettingPostfix)) {
QSettings s;
s.beginGroup(NetworkRemote::kSettingsGroup);
transcode_lossless_files_ = s.value("convert_lossless", false).toBool();
// Load preset
QString last_output_format = s.value("last_output_format", "audio/x-vorbis").toString();
QString last_output_format =
s.value("last_output_format", "audio/x-vorbis").toString();
QList<TranscoderPreset> presets = transcoder_->GetAllPresets();
for (int i = 0; i<presets.count(); ++i) {
for (int i = 0; i < presets.count(); ++i) {
if (last_output_format == presets.at(i).codec_mimetype_) {
transcoder_preset_ = presets.at(i);
break;
@ -58,8 +61,9 @@ SongSender::SongSender(Application* app, RemoteClient* client)
SongSender::~SongSender() {
disconnect(transcoder_, SIGNAL(JobComplete(QString, QString, bool)), this,
SLOT(TranscodeJobComplete(QString, QString, bool)));
disconnect(transcoder_, SIGNAL(AllJobsComplete()), this, SLOT(StartTransfer()));
SLOT(TranscodeJobComplete(QString, QString, bool)));
disconnect(transcoder_, SIGNAL(AllJobsComplete()), this,
SLOT(StartTransfer()));
transcoder_->Cancel();
}
@ -102,8 +106,7 @@ void SongSender::SendSongs(const pb::remote::RequestDownloadSongs& request) {
void SongSender::TranscodeLosslessFiles() {
for (DownloadItem item : download_queue_) {
// Check only lossless files
if (!item.song_.IsFileLossless())
continue;
if (!item.song_.IsFileLossless()) continue;
// Add the file to the transcoder
QString local_file = item.song_.url().toLocalFile();
@ -122,7 +125,8 @@ void SongSender::TranscodeLosslessFiles() {
}
}
void SongSender::TranscodeJobComplete(const QString& input, const QString& output, bool success) {
void SongSender::TranscodeJobComplete(const QString& input,
const QString& output, bool success) {
qLog(Debug) << input << "transcoded to" << output << success;
// If it wasn't successful send original file
@ -204,7 +208,8 @@ void SongSender::OfferNextSong() {
chunk->set_file_number(item.song_no_);
chunk->set_size(file.size());
OutgoingDataCreator::CreateSong(item.song_, QImage(), -1, chunk->mutable_song_metadata());
OutgoingDataCreator::CreateSong(item.song_, QImage(), -1,
chunk->mutable_song_metadata());
}
client_->SendData(&msg);
@ -215,8 +220,7 @@ void SongSender::ResponseSongOffer(bool accepted) {
// Get the item and send the single song
DownloadItem item = download_queue_.dequeue();
if (accepted)
SendSingleSong(item);
if (accepted) SendSingleSong(item);
// And offer the next song
OfferNextSong();
@ -273,7 +277,8 @@ void SongSender::SendSingleSong(DownloadItem download_item) {
int i = app_->playlist_manager()->active()->current_row();
pb::remote::SongMetadata* song_metadata =
msg.mutable_response_song_file_chunk()->mutable_song_metadata();
OutgoingDataCreator::CreateSong(download_item.song_, null_image, i,song_metadata);
OutgoingDataCreator::CreateSong(download_item.song_, null_image, i,
song_metadata);
// if the file was transcoded, we have to change the filename and filesize
if (is_transcoded) {
@ -341,7 +346,7 @@ void SongSender::SendPlaylist(int playlist_id) {
}
}
void SongSender::SendUrls(const pb::remote::RequestDownloadSongs &request) {
void SongSender::SendUrls(const pb::remote::RequestDownloadSongs& request) {
SongList song_list;
// First gather all valid songs

View File

@ -633,10 +633,6 @@ void Playlist::set_current_row(int i, bool is_stopping) {
old_current_item_index.row(), ColumnCount - 1));
}
if (current_item_index_.isValid() && !is_stopping) {
InformOfCurrentSongChange();
}
// Update the virtual index
if (i == -1) {
current_virtual_index_ = -1;
@ -655,6 +651,10 @@ void Playlist::set_current_row(int i, bool is_stopping) {
current_virtual_index_ = i;
}
if (current_item_index_.isValid() && !is_stopping) {
InformOfCurrentSongChange();
}
// The structure of a dynamic playlist is as follows:
// history - active song - future
// We have to ensure that this invariant is maintained.

View File

@ -67,7 +67,10 @@ PlaylistContainer::PlaylistContainer(QWidget* parent)
no_matches_palette.setColor(QPalette::Inactive, QPalette::WindowText,
no_matches_color);
no_matches_label_->setPalette(no_matches_palette);
// Remove QFrame border
ui_->toolbar->setStyleSheet("QFrame { border: 0px; }");
// Make it bold
QFont no_matches_font = no_matches_label_->font();
no_matches_font.setBold(true);
@ -224,11 +227,11 @@ void PlaylistContainer::SetViewModel(Playlist* playlist) {
}
void PlaylistContainer::ActivePlaying() {
UpdateActiveIcon(QIcon(":tiny-start.png"));
UpdateActiveIcon(IconLoader::Load("tiny-start", IconLoader::Other));
}
void PlaylistContainer::ActivePaused() {
UpdateActiveIcon(QIcon(":tiny-pause.png"));
UpdateActiveIcon(IconLoader::Load("tiny-pause", IconLoader::Other));
}
void PlaylistContainer::ActiveStopped() { UpdateActiveIcon(QIcon()); }
@ -394,10 +397,18 @@ void PlaylistContainer::resizeEvent(QResizeEvent* e) {
void PlaylistContainer::FocusOnFilter(QKeyEvent* event) {
ui_->filter->setFocus();
if (event->key() == Qt::Key_Escape) {
ui_->filter->clear();
} else {
ui_->filter->setText(ui_->filter->text() + event->text());
switch (event->key()) {
case Qt::Key_Backspace:
break;
case Qt::Key_Escape:
ui_->filter->clear();
break;
default:
ui_->filter->setText(ui_->filter->text() + event->text());
break;
}
}

View File

@ -104,13 +104,6 @@
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QSearchField" name="filter" native="true"/>
</item>

View File

@ -26,6 +26,7 @@
#include "core/player.h"
#include "covers/currentartloader.h"
#include "ui/qt_blurimage.h"
#include "ui/iconloader.h"
#include <QCleanlooksStyle>
#include <QClipboard>
@ -127,8 +128,6 @@ PlaylistView::PlaylistView(QWidget* parent)
inhibit_autoscroll_(false),
currently_autoscrolling_(false),
row_height_(-1),
currenttrack_play_(":currenttrack_play.png"),
currenttrack_pause_(":currenttrack_pause.png"),
cached_current_row_row_(-1),
drop_indicator_row_(-1),
drag_over_(false),
@ -138,6 +137,17 @@ PlaylistView::PlaylistView(QWidget* parent)
setStyle(style_);
setMouseTracking(true);
QIcon currenttrack_play = IconLoader::Load("currenttrack_play",
IconLoader::Other);
currenttrack_play_ = currenttrack_play.pixmap(currenttrack_play
.availableSizes()
.last());
QIcon currenttrack_pause = IconLoader::Load("currenttrack_pause",
IconLoader::Other);
currenttrack_pause_ = currenttrack_pause.pixmap(currenttrack_pause
.availableSizes()
.last());
connect(header_, SIGNAL(sectionResized(int, int, int)), SLOT(SaveGeometry()));
connect(header_, SIGNAL(sectionMoved(int, int, int)), SLOT(SaveGeometry()));
connect(header_, SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)),

View File

@ -17,46 +17,128 @@
#include "echonestimages.h"
#include <algorithm>
#include <memory>
#include <echonest/Artist.h>
#include <qjson/parser.h>
#include "core/closure.h"
#include "core/logging.h"
#include "core/network.h"
struct EchoNestImages::Request {
Request(int id) : id_(id), artist_(new Echonest::Artist) {}
int id_;
std::unique_ptr<Echonest::Artist> artist_;
};
void EchoNestImages::FetchInfo(int id, const Song& metadata) {
std::shared_ptr<Request> request(new Request(id));
request->artist_->setName(metadata.artist());
QNetworkReply* reply = request->artist_->fetchImages();
connect(reply, SIGNAL(finished()), SLOT(RequestFinished()));
requests_[reply] = request;
namespace {
static const char* kSpotifyBucket = "spotify";
static const char* kSpotifyArtistUrl = "https://api.spotify.com/v1/artists/%1";
}
void EchoNestImages::RequestFinished() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
if (!reply || !requests_.contains(reply)) return;
EchoNestImages::EchoNestImages() : network_(new NetworkAccessManager) {}
EchoNestImages::~EchoNestImages() {}
void EchoNestImages::FetchInfo(int id, const Song& metadata) {
Echonest::Artist artist;
artist.setName(metadata.artist());
// Search for images directly on echonest.
// This is currently a bit limited as most results are for last.fm urls that
// no longer work.
QNetworkReply* reply = artist.fetchImages();
RegisterReply(reply, id);
NewClosure(reply, SIGNAL(finished()), this,
SLOT(RequestFinished(QNetworkReply*, int, Echonest::Artist)),
reply, id, artist);
// Also look up the artist id for the spotify API so we can directly request
// images from there too.
Echonest::Artist::SearchParams params;
params.push_back(
qMakePair(Echonest::Artist::Name, QVariant(metadata.artist())));
QNetworkReply* rosetta_reply = Echonest::Artist::search(
params,
Echonest::ArtistInformation(Echonest::ArtistInformation::NoInformation,
QStringList() << kSpotifyBucket));
RegisterReply(rosetta_reply, id);
NewClosure(rosetta_reply, SIGNAL(finished()), this,
SLOT(IdsFound(QNetworkReply*, int)), rosetta_reply, id);
}
void EchoNestImages::RequestFinished(QNetworkReply* reply, int id,
Echonest::Artist artist) {
reply->deleteLater();
RequestPtr request = requests_.take(reply);
try {
request->artist_->parseProfile(reply);
}
catch (Echonest::ParseError e) {
artist.parseProfile(reply);
} catch (Echonest::ParseError e) {
qLog(Warning) << "Error parsing echonest reply:" << e.errorType()
<< e.what();
}
for (const Echonest::ArtistImage& image : request->artist_->images()) {
emit ImageReady(request->id_, image.url());
for (const Echonest::ArtistImage& image : artist.images()) {
// Echonest still sends these broken URLs for last.fm.
if (image.url().authority() != "userserve-ak.last.fm") {
emit ImageReady(id, image.url());
}
}
emit Finished(request->id_);
}
void EchoNestImages::IdsFound(QNetworkReply* reply, int request_id) {
reply->deleteLater();
try {
Echonest::Artists artists = Echonest::Artist::parseSearch(reply);
if (artists.isEmpty()) {
return;
}
const Echonest::ForeignIds& foreign_ids = artists.first().foreignIds();
for (const Echonest::ForeignId& id : foreign_ids) {
if (id.catalog.contains("spotify")) {
DoSpotifyImageRequest(id.foreign_id, request_id);
}
}
} catch (Echonest::ParseError e) {
qLog(Warning) << "Error parsing echonest reply:" << e.errorType()
<< e.what();
}
}
void EchoNestImages::DoSpotifyImageRequest(const QString& id, int request_id) {
QString artist_id = id.split(":").last();
QUrl url(QString(kSpotifyArtistUrl).arg(artist_id));
QNetworkReply* reply = network_->get(QNetworkRequest(url));
RegisterReply(reply, request_id);
NewClosure(reply, SIGNAL(finished()), [this, reply, request_id]() {
reply->deleteLater();
QJson::Parser parser;
QVariantMap result = parser.parse(reply).toMap();
QVariantList images = result["images"].toList();
QList<QPair<QUrl, QSize>> image_urls;
for (const QVariant& image : images) {
QVariantMap image_result = image.toMap();
image_urls.append(qMakePair(image_result["url"].toUrl(),
QSize(image_result["width"].toInt(),
image_result["height"].toInt())));
}
// All the images are the same just different sizes; just pick the largest.
std::sort(image_urls.begin(), image_urls.end(),
[](const QPair<QUrl, QSize>& a,
const QPair<QUrl, QSize>& b) {
// Sorted by area ascending.
return (a.second.height() * a.second.width()) <
(b.second.height() * b.second.width());
});
if (!image_urls.isEmpty()) {
emit ImageReady(request_id, image_urls.last().first);
}
});
}
// Keeps track of replies and emits Finished() when all replies associated with
// a request are finished with.
void EchoNestImages::RegisterReply(QNetworkReply* reply, int id) {
replies_.insert(id, reply);
NewClosure(reply, SIGNAL(destroyed()), [this, reply, id]() {
replies_.remove(id, reply);
if (!replies_.contains(id)) {
emit Finished(id);
}
});
}

View File

@ -20,24 +20,33 @@
#include <memory>
#include "songinfoprovider.h"
#include <QMultiMap>
#include <echonest/Artist.h>
#include "songinfo/songinfoprovider.h"
class NetworkAccessManager;
class QNetworkReply;
class EchoNestImages : public SongInfoProvider {
Q_OBJECT
public:
EchoNestImages();
virtual ~EchoNestImages();
void FetchInfo(int id, const Song& metadata);
private slots:
void RequestFinished();
void RequestFinished(QNetworkReply*, int id, Echonest::Artist artist);
void IdsFound(QNetworkReply* reply, int id);
private:
struct Request;
typedef std::shared_ptr<Request> RequestPtr;
void DoSpotifyImageRequest(const QString& id, int request_id);
QMap<QNetworkReply*, RequestPtr> requests_;
void RegisterReply(QNetworkReply* reply, int id);
QMultiMap<int, QNetworkReply*> replies_;
std::unique_ptr<NetworkAccessManager> network_;
};
#endif // ECHONESTIMAGES_H

View File

@ -22,6 +22,7 @@
#include <QXmlStreamWriter>
#include <echonest/Artist.h>
#include <echonest/TypeInformation.h>
#include <qjson/parser.h>
@ -30,7 +31,7 @@
#include "songkickconcertwidget.h"
#include "ui/iconloader.h"
const char* SongkickConcerts::kSongkickArtistBucket = "id:songkick";
const char* SongkickConcerts::kSongkickArtistBucket = "songkick";
const char* SongkickConcerts::kSongkickArtistCalendarUrl =
"https://api.songkick.com/api/3.0/artists/%1/calendar.json?"
"per_page=5&"
@ -49,10 +50,11 @@ void SongkickConcerts::FetchInfo(int id, const Song& metadata) {
Echonest::Artist::SearchParams params;
params.push_back(
qMakePair(Echonest::Artist::Name, QVariant(metadata.artist())));
params.push_back(
qMakePair(Echonest::Artist::IdSpace, QVariant(kSongkickArtistBucket)));
qLog(Debug) << "Params:" << params;
QNetworkReply* reply = Echonest::Artist::search(params);
QNetworkReply* reply = Echonest::Artist::search(
params,
Echonest::ArtistInformation(Echonest::ArtistInformation::NoInformation,
QStringList() << kSongkickArtistBucket));
qLog(Debug) << reply->request().url();
NewClosure(reply, SIGNAL(finished()), this,
SLOT(ArtistSearchFinished(QNetworkReply*, int)), reply, id);
@ -92,8 +94,7 @@ void SongkickConcerts::ArtistSearchFinished(QNetworkReply* reply, int id) {
}
FetchSongkickCalendar(split[2], id);
}
catch (Echonest::ParseError& e) {
} catch (Echonest::ParseError& e) {
qLog(Error) << "Error parsing echonest reply:" << e.errorType() << e.what();
emit Finished(id);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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