Merge branch 'release-1.1' into develop

Fixes #1400
This commit is contained in:
Stein Magnus Jodal 2016-01-31 07:46:41 +01:00
commit adb260af05
26 changed files with 357 additions and 402 deletions

View File

@ -76,3 +76,4 @@
- Jelle van der Waa <jelle@vdwaa.nl>
- Alex Malone <jalexmalone@gmail.com>
- Daniel Hahler <git@thequod.de>
- Bryan Bennett <bbenne10@gmail.com>

View File

@ -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

View File

@ -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
<http://www.apache.org/licenses/LICENSE-2.0>`_.

View File

@ -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)
===================

View File

@ -167,5 +167,5 @@ projects are a real match made in heaven."
Partify
-------
`Partify <https://github.com/fhats/partify>`_ is a web based MPD client focussing on
making music playing collaborative and social.
`Partify <https://github.com/fhats/partify>`_ is a web based MPD client
focussing on making music playing collaborative and social.

View File

@ -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']

View File

@ -1,129 +0,0 @@
.. _debian:
***************
Debian packages
***************
The Mopidy Debian package, ``mopidy``, is available from `apt.mopidy.com
<http://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 <subcommand>``. 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 <subcommand>`` 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.

BIN
docs/ext/spotmop.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -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
==================

View File

@ -81,8 +81,8 @@ announcements related to Mopidy and Mopidy extensions.
installation/index
config
running
service
troubleshooting
debian
.. _ext:

View File

@ -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 </config>`, and
then you're ready to :doc:`run Mopidy </running>`.
then you're ready to :doc:`run Mopidy </running>` or run Mopidy as a
:ref:`service <service>`.
Installing extensions

View File

@ -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 </config>`, and then
you're ready to :doc:`run Mopidy </running>`.
you're ready to :doc:`run Mopidy </running>` or run Mopidy as a
:ref:`service <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`.

View File

@ -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
=====================================

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

View File

@ -1,75 +1,68 @@
.. _raspberrypi-installation:
*************************************
Raspberry Pi: Mopidy on a credit card
*************************************
************
Raspberry Pi
************
Mopidy runs nicely on a `Raspberry Pi <https://www.raspberrypi.org/>`_. 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 <https://www.raspberrypi.org/>`_.
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
<https://www.raspberrypi.org/documentation/installation/installing-images/README.md>`_
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 <https://github.com/Hexxeh/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
<http://geeks.noeit.com/xbmc-library-dependency-error/>`_.
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
<https://github.com/xbianonpi/xbian/issues/378#issuecomment-37723392>`_.
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``

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -39,17 +39,8 @@ using ``pkill``::
pkill mopidy
Init scripts
============
Running as a service
====================
- The ``mopidy`` package at `apt.mopidy.com <http://apt.mopidy.com/>`__ comes
with an `sysvinit init script
<https://github.com/mopidy/mopidy/blob/debian/debian/mopidy.init>`_. For
more details, see the :ref:`debian` section of the docs.
- The ``mopidy`` package in `Arch Linux
<https://www.archlinux.org/packages/community/any/mopidy/>`__ 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`.

94
docs/service.rst Normal file
View File

@ -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 <subcommand>``. 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`.

View File

@ -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'

View File

@ -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]

View File

@ -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)

View File

@ -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')

View File

@ -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

View File

@ -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')

View File

@ -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')

View File

@ -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)