diff --git a/AUTHORS b/AUTHORS index 38c394dc..4cf69baa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -76,3 +76,4 @@ - Jelle van der Waa - Alex Malone - Daniel Hahler +- Bryan Bennett diff --git a/docs/api/models.rst b/docs/api/models.rst index 27c7647f..cc8518ba 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -55,20 +55,28 @@ Data model API :synopsis: Data model API .. autoclass:: mopidy.models.Ref + :members: .. autoclass:: mopidy.models.Track + :members: .. autoclass:: mopidy.models.Album + :members: .. autoclass:: mopidy.models.Artist + :members: .. autoclass:: mopidy.models.Playlist + :members: .. autoclass:: mopidy.models.Image + :members: .. autoclass:: mopidy.models.TlTrack + :members: .. autoclass:: mopidy.models.SearchResult + :members: Data model helpers diff --git a/docs/authors.rst b/docs/authors.rst index 90ec6f23..f4f93d56 100644 --- a/docs/authors.rst +++ b/docs/authors.rst @@ -4,7 +4,7 @@ Authors ******* -Mopidy is copyright 2009-2015 Stein Magnus Jodal and contributors. Mopidy is +Mopidy is copyright 2009-2016 Stein Magnus Jodal and contributors. Mopidy is licensed under the `Apache License, Version 2.0 `_. diff --git a/docs/changelog.rst b/docs/changelog.rst index bdda493d..22d80ad3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -141,11 +141,28 @@ Gapless cases. (Fixes: :issue:`1305` PR: :issue:`1346`) -v1.1.2 (UNRELEASED) +v1.1.2 (2016-01-18) =================== Bug fix release. +- Main: Catch errors when loading the :confval:`logging/config_file` file. + (Fixes: :issue:`1320`) + +- Core: If changing to another track while the player is paused, the new track + would not be added to the history or marked as currently playing. (Fixes: + :issue:`1352`, PR: :issue:`1356`) + +- Core: Skips over unplayable tracks if the user attempts to change tracks + while paused, like we already did if in playing state. (Fixes :issue:`1378`, + PR: :issue:`1379`) + +- Core: Make :meth:`~mopidy.core.LibraryController.lookup` ignore tracks with + empty URIs. (Partly fixes: :issue:`1340`, PR: :issue:`1381`) + +- Core: Fix crash if backends emits events with wrong names or arguments. + (Fixes: :issue:`1383`) + - Stream: If an URI is considered playable, don't consider it as a candidate for playlist parsing. Just looking at MIME type prefixes isn't enough, as for example Ogg Vorbis has the MIME type ``application/ogg``. (Fixes: @@ -154,6 +171,18 @@ Bug fix release. - Local: If the scan or clear commands are used on a library that does not exist, exit with an error. (Fixes: :issue:`1298`) +- MPD: Notify idling clients when a seek is performed. (Fixes: :issue:`1331`) + +- MPD: Don't return tracks with empty URIs. (Partly fixes: :issue:`1340`, PR: + :issue:`1343`) + +- MPD: Add ``volume`` command that was reintroduced, though still as a + deprecated command, in MPD 0.18 and is in use by some clients like mpc. + (Fixes: :issue:`1393`, PR: :issue:`1397`) + +- Proxy: Handle case where :confval:`proxy/port` is either missing from config + or set to an empty string. (PR: :issue:`1371`) + v1.1.1 (2015-09-14) =================== diff --git a/docs/clients/mpd.rst b/docs/clients/mpd.rst index b070092a..ee1b1903 100644 --- a/docs/clients/mpd.rst +++ b/docs/clients/mpd.rst @@ -167,5 +167,5 @@ projects are a real match made in heaven." Partify ------- -`Partify `_ is a web based MPD client focussing on -making music playing collaborative and social. +`Partify `_ is a web based MPD client +focussing on making music playing collaborative and social. diff --git a/docs/conf.py b/docs/conf.py index bd553ae4..5dcebc31 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -93,14 +93,14 @@ source_suffix = '.rst' master_doc = 'index' project = 'Mopidy' -copyright = '2009-2015, Stein Magnus Jodal and contributors' +copyright = '2009-2016, Stein Magnus Jodal and contributors' from mopidy.internal.versioning import get_version release = get_version() version = '.'.join(release.split('.')[:2]) # To make the build reproducible, avoid using today's date in the manpages -today = '2015' +today = '2016' exclude_trees = ['_build'] diff --git a/docs/debian.rst b/docs/debian.rst deleted file mode 100644 index f761c4b0..00000000 --- a/docs/debian.rst +++ /dev/null @@ -1,129 +0,0 @@ -.. _debian: - -*************** -Debian packages -*************** - -The Mopidy Debian package, ``mopidy``, is available from `apt.mopidy.com -`__ as well as from Debian, Ubuntu and other -Debian-based Linux distributions. - -Some extensions are also available from all of these sources, while others, -like Mopidy-Spotify and its dependencies, are only available from -apt.mopidy.com. This may either be temporary until the package is uploaded to -Debian and with time propagates to the other distributions. It may also be more -long term, like in the Mopidy-Spotify case where there is uncertainities around -licensing and distribution of non-free packages. - - -Installation -============ - -See :ref:`debian-install`. - - -Running as a system service -=========================== - -The Debian package comes with an init script. It starts Mopidy as a system -service running as the ``mopidy`` user, which is created by the package. - -The Debian package version 0.18.3-1 and older starts Mopidy as a system -service by default. Version 0.18.3-2 and newer asks if you want to run Mopidy -as a system service, defaulting to not doing so. - -If you're running 0.18.3-2 or newer, and you've changed your mind about whether -or not to run Mopidy as a system service, just run the following command to -reconfigure the package:: - - sudo dpkg-reconfigure mopidy - -If you're running 0.18.3-1 or older, and don't want to use the init script to -run Mopidy as a system service, but instead just run Mopidy manually using your -own user, you need to disable the init script and stop Mopidy by running:: - - sudo update-rc.d mopidy disable - sudo service mopidy stop - -This way of disabling the system service is compatible with the improved -0.18.3-2 or newer version of the Debian package, so if you later upgrade to a -newer version, you can change your mind using the ``dpkg-reconfigure`` command -above. - - -Differences when running as a system service -============================================ - -If you want to run Mopidy using the init script, there's a few differences -from a regular Mopidy setup you'll want to know about. - -- All configuration is in :file:`/etc/mopidy`, not in your user's home - directory. The main configuration file is :file:`/etc/mopidy/mopidy.conf`. - You can do all your changes in this file. - -- Mopidy extensions installed from Debian packages will sometimes install - additional configuration files in :file:`/usr/share/mopidy/conf.d/`. These - files just provide different defaults for the extension when run as a system - service. You can override anything from :file:`/usr/share/mopidy/conf.d/` in - the :file:`/etc/mopidy/mopidy.conf` configuration file. - - Previously, the extension's default config was installed in - :file:`/etc/mopidy/extensions.d/`. This was removed with the Debian - package mopidy 0.19.4-3. If you have modified any files in - :file:`/etc/mopidy/extensions.d/`, you should redo your modifications in - :file:`/etc/mopidy/mopidy.conf` and delete the - :file:`/etc/mopidy/extensions.d/` directory. - -- The init script runs Mopidy as the ``mopidy`` user. The ``mopidy`` user will - need read access to any local music you want Mopidy to play. - -- To run Mopidy subcommands with the same user and config files as the init - script uses, you can use ``sudo mopidyctl ``. In other words, - where you'll usually run:: - - mopidy config - - You should instead run the following to inspect the system service's - configuration:: - - sudo mopidyctl config - - The same applies to scanning your local music collection. Where you'll - normally run:: - - mopidy local scan - - You should instead run:: - - sudo mopidyctl local scan - - Previously, you used ``sudo service mopidy run `` instead of - ``mopidyctl``. This was deprecated in Debian package version 0.19.4-3 in - favor of ``mopidyctl``, which also work for systems using systemd instead of - sysvinit and traditional init scripts. - -- Mopidy is started, stopped, and restarted just like any other system - service:: - - sudo service mopidy start - sudo service mopidy stop - sudo service mopidy restart - -- You can check if Mopidy is currently running as a system service by running:: - - sudo service mopidy status - -- Mopidy installed from a Debian package can use Mopidy extensions installed - both from Debian packages and with pip. This has always been the case. - - Mopidy installed with pip can use extensions installed with pip, but - not extensions installed from a Debian package released before August 2015. - This is because the Debian packages used to install extensions into - :file:`/usr/share/mopidy` which is normally not on your ``PYTHONPATH``. - Thus, your pip-installed Mopidy would not find the Debian package-installed - extensions. - - In August 2015, all Mopidy extension Debian packages was modified to install - into :file:`/usr/lib/python2.7/dist-packages`, like any other Python Debian - package. Thus, Mopidy installed with pip can now use extensions installed - from Debian. diff --git a/docs/ext/spotmop.jpg b/docs/ext/spotmop.jpg new file mode 100644 index 00000000..88393f0b Binary files /dev/null and b/docs/ext/spotmop.jpg differ diff --git a/docs/ext/web.rst b/docs/ext/web.rst index a6e2d748..4c2b6c6c 100644 --- a/docs/ext/web.rst +++ b/docs/ext/web.rst @@ -164,6 +164,22 @@ To install, run:: pip install Mopidy-Simple-Webclient +Mopidy-Spotmop +============== + +https://github.com/jaedb/spotmop + +A client targeted at Spotify users. Made by James Barnsley. + +.. image:: /ext/spotmop.jpg + :width: 720 + :height: 455 + +To install, run:: + + pip install Mopidy-Spotmop + + Mopidy-WebSettings ================== diff --git a/docs/index.rst b/docs/index.rst index 70d14a73..e6b2da98 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -81,8 +81,8 @@ announcements related to Mopidy and Mopidy extensions. installation/index config running + service troubleshooting - debian .. _ext: diff --git a/docs/installation/arch.rst b/docs/installation/arch.rst index c5675403..59928a3a 100644 --- a/docs/installation/arch.rst +++ b/docs/installation/arch.rst @@ -16,7 +16,8 @@ If you are running Arch Linux, you can install Mopidy using the pacman -Syu #. Finally, you need to set a couple of :doc:`config values `, and - then you're ready to :doc:`run Mopidy `. + then you're ready to :doc:`run Mopidy ` or run Mopidy as a + :ref:`service `. Installing extensions diff --git a/docs/installation/debian.rst b/docs/installation/debian.rst index a04dfa25..a1ad339e 100644 --- a/docs/installation/debian.rst +++ b/docs/installation/debian.rst @@ -48,12 +48,9 @@ and armhf (compatible with Raspberry Pi 1 and 2). sudo apt-get update sudo apt-get install mopidy -#. Before continuing, make sure you've read the :ref:`debian` section to learn - about the differences between running Mopidy as a system service and - manually as your own system user. - #. Finally, you need to set a couple of :doc:`config values `, and then - you're ready to :doc:`run Mopidy `. + you're ready to :doc:`run Mopidy ` or run Mopidy as a + :ref:`service `. When a new release of Mopidy is out, and you can't wait for you system to figure it out for itself, run the following to upgrade right away:: @@ -87,44 +84,3 @@ about any other requirements needed for the extension to work properly. For a full list of available Mopidy extensions, including those not installable from apt.mopidy.com, see :ref:`ext`. - - -Missing extensions -================== - -If you've installed a Mopidy extension with pip, restarted Mopidy, and Mopidy -doesn't find the extension, there's probably a simple explanation and solution. - -Mopidy installed with APT can detect and use Mopidy extensions installed with -both APT and pip. APT installs Mopidy as :file:`/usr/bin/mopidy`. - -Mopidy installed with pip can only detect Mopidy extensions installed with pip. -pip usually installs Mopidy as :file:`/usr/local/bin/mopidy`. - -If you have Mopidy installed from both APT and pip, then the pip-installed -Mopidy will probably shadow the APT-installed Mopidy because -:file:`/usr/local/bin` usually has precedence over :file:`/usr/bin` in the -``PATH`` environment variable. To check if this is the case on your system, you -can use ``which`` to see what installation of Mopidy you use when you run -``mopidy`` in your shell:: - - $ which mopidy - /usr/local/bin/mopidy - -If this is the case on your system, the recommended solution is to check that -you have Mopidy installed from APT too:: - - $ /usr/bin/mopidy --version - Mopidy 0.19.5 - -And then uninstall the pip-installed Mopidy:: - - sudo pip uninstall mopidy - -Depending on what shell you use, the shell may still try to use -:file:`/usr/local/bin/mopidy` even if it no longer exists. Check again with -``which mopidy`` what your shell believes is the right ``mopidy`` executable to -run. If the shell is still confused, you may need to restart it, or in the case -of zsh, run ``rehash`` to update the shell. - -For more details on why this works this way, see :ref:`debian`. diff --git a/docs/installation/osx.rst b/docs/installation/osx.rst index e9ce16e3..45c554ea 100644 --- a/docs/installation/osx.rst +++ b/docs/installation/osx.rst @@ -86,6 +86,8 @@ For a full list of available Mopidy extensions, including those not installable from Homebrew, see :ref:`ext`. +.. _osx-service: + Running Mopidy automatically on login ===================================== diff --git a/docs/installation/raspberry-pi-by-jwrodgers.jpg b/docs/installation/raspberry-pi-by-jwrodgers.jpg deleted file mode 100644 index d093bb88..00000000 Binary files a/docs/installation/raspberry-pi-by-jwrodgers.jpg and /dev/null differ diff --git a/docs/installation/raspberrypi.rst b/docs/installation/raspberrypi.rst index 9bf9f550..6d2dd3cd 100644 --- a/docs/installation/raspberrypi.rst +++ b/docs/installation/raspberrypi.rst @@ -1,75 +1,68 @@ .. _raspberrypi-installation: -************************************* -Raspberry Pi: Mopidy on a credit card -************************************* +************ +Raspberry Pi +************ -Mopidy runs nicely on a `Raspberry Pi `_. As of -January 2013, Mopidy will run with Spotify support on both the armel -(soft-float) and armhf (hard-float) architectures, which includes the Raspbian -distribution. +Mopidy runs on all versions of `Raspberry Pi `_. +However, note that Raspberry Pi 2 B's CPU is approximately six times as +powerful as Raspberry Pi 1 and Raspberry Pi Zero, so Mopidy will be more joyful +to use on a Raspberry Pi 2. -.. image:: raspberry-pi-by-jwrodgers.jpg +.. image:: raspberrypi2.jpg :width: 640 - :height: 427 + :height: 363 .. _raspi-wheezy: -How to for Raspbian "wheezy" and Debian "wheezy" -================================================ +How to for Raspbian Jessie +========================== -This guide applies for both: +#. Download the latest Jessie or Jessie Lite disk image from + http://www.raspberrypi.org/downloads/raspbian/. -- Raspbian "wheezy" for armhf (hard-float), and -- Debian "wheezy" for armel (soft-float) + If you're only using your Pi for Mopidy, go with Jessie Lite as you won't + need the full graphical desktop included in the Jessie image. -If you don't know which one to select, go for the armhf variant, as it'll give -you a lot better performance. +#. Flash the Raspbian image you downloaded to your SD card. -#. Download the latest "wheezy" disk image from - https://www.raspberrypi.org/downloads/. This was last tested with the images - from 2013-05-25 for armhf and 2013-05-29 for armel. + See the `Raspberry Pi installation docs + `_ + for instructions. -#. Flash the OS image to your SD card. See - http://elinux.org/RPi_Easy_SD_Card_Setup for help. +#. If you connect a monitor and a keyboard, you'll see that the Pi boots right + into the ``raspi-config`` tool. -#. If you have an SD card that's >2 GB, you don't have to resize the file - systems on another computer. Just boot up your Raspberry Pi with the - unaltered partions, and it will boot right into the ``raspi-config`` tool, - which will let you grow the root file system to fill the SD card. This tool - will also allow you do other useful stuff, like turning on the SSH server. + If you boot with only a network cable connected, you'll have to find the IP + address of the Pi yourself, e.g. by looking in the client list on your + router/DHCP server. When you have found the Pi's IP address, you can SSH to + the IP address and login with the user ``pi`` and password ``raspberry``. + Once logged in, run ``sudo raspi-config`` to start the config tool as the + ``root`` user. -#. You can login to the default user using username ``pi`` and password - ``raspberry``. To become root, just enter ``sudo -i``. +#. Use the ``raspi-config`` tool to setup the basics of your Pi. You might want + to do one or more of the following: -#. To avoid a couple of potential problems with Mopidy, turn on IPv6 support: + - Expand the file system to fill the SD card. + - Change the password of the ``pi`` user. + - Change the time zone. - - Load the IPv6 kernel module now:: + Under "Advanced Options": - sudo modprobe ipv6 + - Set a hostname. + - Enable SSH if not already enabled. + - If your will use HDMI for display and 3.5mm jack for audio, force the + audio output to the 3.5mm jack. By default it will use HDMI for audio + output if an HDMI cable is connected and the 3.5mm jack if not. - - Add ``ipv6`` to ``/etc/modules`` to ensure the IPv6 kernel module is - loaded on boot:: + Once done, select "Finish" and restart your Pi. - echo ipv6 | sudo tee -a /etc/modules + If you want to change any settings later, you can simply rerun ``sudo + raspi-config``. -#. Since I have a HDMI cable connected, but want the sound on the analog sound - connector, I have to run:: - - sudo amixer cset numid=3 1 - - to force it to use analog output. ``1`` means analog, ``0`` means auto, and - is the default, while ``2`` means HDMI. You can test sound output - independent of Mopidy by running:: - - aplay /usr/share/sounds/alsa/Front_Center.wav - - If you hear a voice saying "Front Center", then your sound is working. - - To make the change to analog output stick, you can add the ``amixer`` - command to e.g. ``/etc/rc.local``, which will be executed when the system is - booting. +#. Once you've rebooted and has logged in as the ``pi`` user, you can enter + ``sudo -i`` to become ``root``. #. Install Mopidy and its dependencies as described in :ref:`debian-install`. @@ -79,114 +72,19 @@ you a lot better performance. starting at boot. -Appendix A: Fixing audio quality issues -======================================= +Testing sound output +==================== -As of about April 2013 the following steps should resolve any audio -issues for HDMI and analog without the use of an external USB sound -card. +You can test sound output independent of Mopidy by running:: -#. Ensure your system is up to date. On Debian based systems run:: + aplay /usr/share/sounds/alsa/Front_Center.wav - sudo apt-get update - sudo apt-get dist-upgrade +If you hear a voice saying "Front Center", then your sound is working. -#. Ensure you have a new enough firmware. On Debian based systems - `rpi-update `_ - can be used. +If you want to change your audio output setting, simply rerun ``sudo +raspi-config``. Alternatively, you can change the audio output setting +directly by running: -#. Update either ``~/.asoundrc`` or ``/etc/asound.conf`` to the - following:: - - pcm.!default { - type hw - card 0 - } - ctl.!default { - type hw - card 0 - } - - Note that if you have an ``~/.asoundrc`` it will overide any global - settings from ``/etc/asound.conf``. - -#. For Mopidy to output audio directly to ALSA, instead of Jack which - GStreamer usually defaults to on Raspberry Pi, install the - ``gstreamer0.10-alsa`` package:: - - sudo apt-get install gstreamer0.10-alsa - - Then update your ``~/.config/mopidy/mopidy.conf`` to contain:: - - [audio] - output = alsasink - -Following these steps you should be able to get crackle free sound on either -HDMI or analog. Note that you might need to ensure that PulseAudio is no longer -running to get this working nicely. - -This recipe has been confirmed as working by a number of users on our issue -tracker and IRC. As a reference, the following versions where used for testing -this, however all newer and some older version are likely to work as we have -not determined the exact revision that fixed this:: - - $ uname -a - Linux raspberrypi 3.6.11+ #408 PREEMPT Wed Apr 10 20:33:39 BST 2013 armv6l GNU/Linux - - $ /opt/vc/bin/vcgencmd version - Apr 25 2013 01:07:36 - Copyright (c) 2012 Broadcom - version 386589 (release) - -The only remaining known issue is a slight gap in playback at track changes -this is likely due to gapless playback not being implemented and is being -worked on irrespective of Raspberry Pi related work. - - -Appendix B: Raspbmc not booting -=============================== - -Due to a dependency version problem where XBMC uses another version of libtag -than what Debian originally ships with, you might have to make some minor -changes for Raspbmc to start properly after installing Mopidy. - -If you notice that XBMC is not starting but gets stuck in a loop, -you need to make the following changes:: - - sudo ln -sf /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system/libtag.so.1 \ - /usr/lib/arm-linux-gnueabihf/libtag.so.1 - -However, this will not persist the changes. To persist the changes edit -:file:`/etc/ld.so.conf.d/arm-linux-gnueabihf.conf` and add the following at the -top:: - - /home/pi/.xbmc-current/xbmc-bin/lib/xbmc/system - -It's very important to add it at the top of the file as this indicates the -priority of the folder in which to look for shared libraries. - -XBMC doesn't play nicely with the system wide installed version of libtag that -got installed together with Mopidy, but rather vendors in its own version. - -More info about this issue can be found in `this post -`_. - -Please note that if you're running Xbian or another XBMC distribution these -instructions might vary for your system. - - -Appendix C: Installation on XBian -================================= - -Similar to the Raspbmc issue outlined in Appendix B, it's not possible to -install Mopidy on XBian without first resolving a dependency problem between -``gstreamer0.10-plugins-good`` and ``libtag1c2a``. More information can be -found in `this issue -`_. - -Run the following commands to remedy this and then install Mopidy as normal:: - - cd /tmp - wget http://apt.xbian.org/pool/stable/rpi-wheezy/l/libtag1c2a/libtag1c2a_1.7.2-1_armhf.deb - sudo dpkg -i libtag1c2a_1.7.2-1_armhf.deb - rm libtag1c2a_1.7.2-1_armhf.deb +- Auto (HDMI if connected, else 3.5mm jack): ``sudo amixer cset numid=3 0`` +- Use 3.5mm jack: ``sudo amixer cset numid=3 1`` +- Use HDMI: ``sudo amixer cset numid=3 2`` diff --git a/docs/installation/raspberrypi2.jpg b/docs/installation/raspberrypi2.jpg new file mode 100644 index 00000000..8af91864 Binary files /dev/null and b/docs/installation/raspberrypi2.jpg differ diff --git a/docs/running.rst b/docs/running.rst index 73bd211f..1aa0a657 100644 --- a/docs/running.rst +++ b/docs/running.rst @@ -39,17 +39,8 @@ using ``pkill``:: pkill mopidy -Init scripts -============ +Running as a service +==================== -- The ``mopidy`` package at `apt.mopidy.com `__ comes - with an `sysvinit init script - `_. For - more details, see the :ref:`debian` section of the docs. - -- The ``mopidy`` package in `Arch Linux - `__ comes with a systemd init - script. - -- Issue :issue:`266` contains a bunch of init scripts for Mopidy, including - Upstart init scripts. +Once you're done exploring Mopidy and want to run it as a proper service, check +out :ref:`service`. diff --git a/docs/service.rst b/docs/service.rst new file mode 100644 index 00000000..2b608ed6 --- /dev/null +++ b/docs/service.rst @@ -0,0 +1,94 @@ +.. _service: + +******************** +Running as a service +******************** + +If you want to run Mopidy as a service using either an init script or a systemd +service, there's a few differences from running Mopidy as your own user you'll +want to know about. The following applies to Debian, Ubuntu, Raspbian, and +Arch. Hopefully, other distributions packaging Mopidy will make sure this works +the same way on their distribution. + + +Configuration +============= + +All configuration is in :file:`/etc/mopidy/mopidy.conf`, not in your user's +home directory. + + +mopidy user +=========== + +The Mopidy service runs as the ``mopidy`` user, which is automatically created +when you install the Mopidy package. The ``mopidy`` user will need read access +to any local music you want Mopidy to play. + + +Subcommands +=========== + +To run Mopidy subcommands with the same user and config files as the service +uses, you can use ``sudo mopidyctl ``. In other words, where you'll +usually run:: + + mopidy config + +You should instead run the following to inspect the service's configuration:: + + sudo mopidyctl config + +The same applies to scanning your local music collection. Where you'll normally +run:: + + mopidy local scan + +You should instead run:: + + sudo mopidyctl local scan + + +Service management with systemd +=============================== + +On modern systems using systemd you can enable the Mopidy service by running:: + + sudo systemctl enable mopidy + +This will make Mopidy start when the system boots. + +Mopidy is started, stopped, and restarted just like any other systemd service:: + + sudo systemctl start mopidy + sudo systemctl stop mopidy + sudo systemctl restart mopidy + +You can check if Mopidy is currently running as a service by running:: + + sudo systemctl status mopidy + + +Service management on Debian +============================ + +On Debian systems (both those using systemd and not) you can enable the Mopidy +service by running:: + + sudo dpkg-reconfigure mopidy + +Mopidy can be started, stopped, and restarted using the ``service`` command:: + + sudo service mopidy start + sudo service mopidy stop + sudo service mopidy restart + +You can check if Mopidy is currently running as a service by running:: + + sudo service mopidy status + + +Service on OS X +=============== + +If you're installing Mopidy on OS X, see :ref:`osx-service`. diff --git a/mopidy/__init__.py b/mopidy/__init__.py index df9aacc3..59d0444e 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -14,4 +14,4 @@ if not (2, 7) <= sys.version_info < (3,): warnings.filterwarnings('ignore', 'could not open display') -__version__ = '1.1.1' +__version__ = '1.1.2' diff --git a/mopidy/core/library.py b/mopidy/core/library.py index 30064e5a..04fe0a7e 100644 --- a/mopidy/core/library.py +++ b/mopidy/core/library.py @@ -236,7 +236,9 @@ class LibraryController(object): result = future.get() if result is not None: validation.check_instances(result, models.Track) - results[u] = result + # TODO Consider making Track.uri field mandatory, and + # then remove this filtering of tracks without URIs. + results[u] = [r for r in result if r.uri] if uri: return results[uri] diff --git a/mopidy/models/__init__.py b/mopidy/models/__init__.py index 1e63d02f..f477a323 100644 --- a/mopidy/models/__init__.py +++ b/mopidy/models/__init__.py @@ -353,14 +353,14 @@ class SearchResult(ValidatedImmutableObject): :type albums: list of :class:`Album` elements """ - # The search result URI. Read-only. + #: The search result URI. Read-only. uri = fields.URI() - # The tracks matching the search query. Read-only. + #: The tracks matching the search query. Read-only. tracks = fields.Collection(type=Track, container=tuple) - # The artists matching the search query. Read-only. + #: The artists matching the search query. Read-only. artists = fields.Collection(type=Artist, container=tuple) - # The albums matching the search query. Read-only. + #: The albums matching the search query. Read-only. albums = fields.Collection(type=Album, container=tuple) diff --git a/mopidy/mpd/protocol/playback.py b/mopidy/mpd/protocol/playback.py index 9124d99a..7b943930 100644 --- a/mopidy/mpd/protocol/playback.py +++ b/mopidy/mpd/protocol/playback.py @@ -426,3 +426,27 @@ def stop(context): Stops playing. """ context.core.playback.stop() + + +@protocol.commands.add('volume', change=protocol.INT) +def volume(context, change): + """ + *musicpd.org, playback section:* + + ``volume {CHANGE}`` + + Changes volume by amount ``CHANGE``. + + Note: ``volume`` is deprecated, use ``setvol`` instead. + """ + if change < -100 or change > 100: + raise exceptions.MpdArgError('Invalid volume value') + + old_volume = context.core.mixer.get_volume().get() + if old_volume is None: + raise exceptions.MpdSystemError('problems setting volume') + + new_volume = min(max(0, old_volume + change), 100) + success = context.core.mixer.set_volume(new_volume).get() + if not success: + raise exceptions.MpdSystemError('problems setting volume') diff --git a/mopidy/mpd/translator.py b/mopidy/mpd/translator.py index 4aa4bdb9..a76d6d59 100644 --- a/mopidy/mpd/translator.py +++ b/mopidy/mpd/translator.py @@ -1,11 +1,15 @@ from __future__ import absolute_import, unicode_literals import datetime +import logging import re from mopidy.models import TlTrack from mopidy.mpd.protocol import tagtype_list + +logger = logging.getLogger(__name__) + # TODO: special handling of local:// uri scheme normalize_path_re = re.compile(r'[^/]+') @@ -34,8 +38,12 @@ def track_to_mpd_format(track, position=None, stream_title=None): else: (tlid, track) = (None, track) + if not track.uri: + logger.warning('Ignoring track without uri') + return [] + result = [ - ('file', track.uri or ''), + ('file', track.uri), ('Time', track.length and (track.length // 1000) or 0), ('Artist', concat_multi_values(track.artists, 'name')), ('Album', track.album and track.album.name or ''), @@ -164,7 +172,9 @@ def tracks_to_mpd_format(tracks, start=0, end=None): assert len(tracks) == len(positions) result = [] for track, position in zip(tracks, positions): - result.append(track_to_mpd_format(track, position)) + formatted_track = track_to_mpd_format(track, position) + if formatted_track: + result.append(formatted_track) return result diff --git a/tests/core/test_library.py b/tests/core/test_library.py index 92b22bfb..750f371f 100644 --- a/tests/core/test_library.py +++ b/tests/core/test_library.py @@ -153,8 +153,8 @@ class CoreLibraryTest(BaseCoreLibraryTest): self.core.library.lookup('dummy1:a', ['dummy2:a']) def test_lookup_can_handle_uris(self): - track1 = Track(name='abc') - track2 = Track(name='def') + track1 = Track(uri='dummy1:a', name='abc') + track2 = Track(uri='dummy2:a', name='def') self.library1.lookup().get.return_value = [track1] self.library2.lookup().get.return_value = [track2] @@ -169,6 +169,15 @@ class CoreLibraryTest(BaseCoreLibraryTest): self.assertFalse(self.library1.lookup.called) self.assertFalse(self.library2.lookup.called) + def test_lookup_ignores_tracks_without_uri_set(self): + track1 = Track(uri='dummy1:a', name='abc') + track2 = Track() + + self.library1.lookup().get.return_value = [track1, track2] + + result = self.core.library.lookup(uris=['dummy1:a']) + self.assertEqual(result, {'dummy1:a': [track1]}) + def test_refresh_with_uri_selects_dummy1_backend(self): self.core.library.refresh('dummy1:a') diff --git a/tests/mpd/protocol/test_playback.py b/tests/mpd/protocol/test_playback.py index 51112057..9f13fc22 100644 --- a/tests/mpd/protocol/test_playback.py +++ b/tests/mpd/protocol/test_playback.py @@ -80,41 +80,6 @@ class PlaybackOptionsHandlerTest(protocol.BaseTestCase): self.assertTrue(self.core.tracklist.repeat.get()) self.assertInResponse('OK') - def test_setvol_below_min(self): - self.send_request('setvol "-10"') - self.assertEqual(0, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_min(self): - self.send_request('setvol "0"') - self.assertEqual(0, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_middle(self): - self.send_request('setvol "50"') - self.assertEqual(50, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_max(self): - self.send_request('setvol "100"') - self.assertEqual(100, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_above_max(self): - self.send_request('setvol "110"') - self.assertEqual(100, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_plus_is_ignored(self): - self.send_request('setvol "+10"') - self.assertEqual(10, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - - def test_setvol_without_quotes(self): - self.send_request('setvol 50') - self.assertEqual(50, self.core.mixer.get_volume().get()) - self.assertInResponse('OK') - def test_single_off(self): self.send_request('single "0"') self.assertFalse(self.core.tracklist.single.get()) @@ -455,9 +420,83 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase): self.assertInResponse('OK') -class PlaybackOptionsHandlerNoneMixerTest(protocol.BaseTestCase): +class VolumeTest(protocol.BaseTestCase): + + def test_setvol_below_min(self): + self.send_request('setvol "-10"') + self.assertEqual(0, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_min(self): + self.send_request('setvol "0"') + self.assertEqual(0, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_middle(self): + self.send_request('setvol "50"') + self.assertEqual(50, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_max(self): + self.send_request('setvol "100"') + self.assertEqual(100, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_above_max(self): + self.send_request('setvol "110"') + self.assertEqual(100, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_plus_is_ignored(self): + self.send_request('setvol "+10"') + self.assertEqual(10, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_setvol_without_quotes(self): + self.send_request('setvol 50') + self.assertEqual(50, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_volume_plus(self): + self.core.mixer.set_volume(50) + + self.send_request('volume +20') + + self.assertEqual(70, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_volume_minus(self): + self.core.mixer.set_volume(50) + + self.send_request('volume -20') + + self.assertEqual(30, self.core.mixer.get_volume().get()) + self.assertInResponse('OK') + + def test_volume_less_than_minus_100(self): + self.core.mixer.set_volume(50) + + self.send_request('volume -110') + + self.assertEqual(50, self.core.mixer.get_volume().get()) + self.assertInResponse('ACK [2@0] {volume} Invalid volume value') + + def test_volume_more_than_plus_100(self): + self.core.mixer.set_volume(50) + + self.send_request('volume +110') + + self.assertEqual(50, self.core.mixer.get_volume().get()) + self.assertInResponse('ACK [2@0] {volume} Invalid volume value') + + +class VolumeWithNoMixerTest(protocol.BaseTestCase): enable_mixer = False - def test_setvol_max_error(self): + def test_setvol_without_mixer_fails(self): self.send_request('setvol "100"') self.assertInResponse('ACK [52@0] {setvol} problems setting volume') + + def test_volume_without_mixer_failes(self): + self.send_request('volume +100') + self.assertInResponse('ACK [52@0] {volume} problems setting volume') diff --git a/tests/mpd/test_translator.py b/tests/mpd/test_translator.py index 65c80bbb..e1ef703d 100644 --- a/tests/mpd/test_translator.py +++ b/tests/mpd/test_translator.py @@ -56,7 +56,7 @@ class TrackMpdFormatTest(unittest.TestCase): def test_track_to_mpd_format_with_position_and_tlid(self): result = translator.track_to_mpd_format( - TlTrack(2, Track()), position=1) + TlTrack(2, Track(uri='a uri')), position=1) self.assertIn(('Pos', 1), result) self.assertIn(('Id', 2), result) @@ -153,13 +153,17 @@ class PlaylistMpdFormatTest(unittest.TestCase): def test_mpd_format(self): playlist = Playlist(tracks=[ - Track(track_no=1), Track(track_no=2), Track(track_no=3)]) + Track(uri='foo', track_no=1), + Track(uri='bar', track_no=2), + Track(uri='baz', track_no=3)]) result = translator.playlist_to_mpd_format(playlist) self.assertEqual(len(result), 3) def test_mpd_format_with_range(self): playlist = Playlist(tracks=[ - Track(track_no=1), Track(track_no=2), Track(track_no=3)]) + Track(uri='foo', track_no=1), + Track(uri='bar', track_no=2), + Track(uri='baz', track_no=3)]) result = translator.playlist_to_mpd_format(playlist, 1, 2) self.assertEqual(len(result), 1) self.assertEqual(dict(result[0])['Track'], 2)