Merge branch 'master' into PanderMusubi-master
|
@ -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)
|
|
@ -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)
|
||||
|
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 590 B |
Before Width: | Height: | Size: 905 B After Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
@ -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 |
|
@ -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 |
|
@ -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 d’aquesta 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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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_;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -54,6 +54,7 @@ class CommandlineOptions {
|
|||
Player_Previous = 5,
|
||||
Player_Next = 6,
|
||||
Player_RestartOrPrevious = 7,
|
||||
Player_StopAfterCurrent = 8,
|
||||
};
|
||||
|
||||
bool Parse();
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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_
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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() {}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|