Release v0.19.3
This commit is contained in:
commit
9dc8038025
1
.mailmap
1
.mailmap
@ -15,3 +15,4 @@ Janez Troha <janez.troha@gmail.com> <dz0ny@users.noreply.github.com>
|
|||||||
Janez Troha <janez.troha@gmail.com> <dz0ny@ubuntu.si>
|
Janez Troha <janez.troha@gmail.com> <dz0ny@ubuntu.si>
|
||||||
Luke Giuliani <luke@giuliani.com.au>
|
Luke Giuliani <luke@giuliani.com.au>
|
||||||
Colin Montgomerie <kiteflyingmonkey@gmail.com>
|
Colin Montgomerie <kiteflyingmonkey@gmail.com>
|
||||||
|
Ignasi Fosch <natx@y10k.ws> <ifosch@serenity-2.local>
|
||||||
|
|||||||
@ -5,6 +5,8 @@ python:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
- TOX_ENV=py27
|
- TOX_ENV=py27
|
||||||
|
- TOX_ENV=py27-tornado23
|
||||||
|
- TOX_ENV=py27-tornado31
|
||||||
- TOX_ENV=docs
|
- TOX_ENV=docs
|
||||||
- TOX_ENV=flake8
|
- TOX_ENV=flake8
|
||||||
|
|
||||||
|
|||||||
1
AUTHORS
1
AUTHORS
@ -41,3 +41,4 @@
|
|||||||
- Thomas Scholtes <thomas-scholtes@gmx.de>
|
- Thomas Scholtes <thomas-scholtes@gmx.de>
|
||||||
- Sam Willcocks <sam@wlcx.cc>
|
- Sam Willcocks <sam@wlcx.cc>
|
||||||
- Ignasi Fosch <natx@y10k.ws>
|
- Ignasi Fosch <natx@y10k.ws>
|
||||||
|
- Arjun Naik <arjun@arjunnaik.in>
|
||||||
|
|||||||
@ -5,26 +5,41 @@ Changelog
|
|||||||
This changelog is used to track all major changes to Mopidy.
|
This changelog is used to track all major changes to Mopidy.
|
||||||
|
|
||||||
|
|
||||||
|
v0.19.3 (2014-08-03)
|
||||||
|
====================
|
||||||
|
|
||||||
|
Bug fix release.
|
||||||
|
|
||||||
|
- Audio: Fix negative track length for radio streams. (Fixes: :issue:`662`,
|
||||||
|
PR: :issue:`796`)
|
||||||
|
|
||||||
|
- Audio: Tell GStreamer to not pick Jack sink. (Fixes: :issue:`604`)
|
||||||
|
|
||||||
|
- Zeroconf: Fix discovery by adding ``.local`` to the announced hostname. (PR:
|
||||||
|
:issue:`795`)
|
||||||
|
|
||||||
|
- Zeroconf: Fix intermittent DBus/Avahi exception.
|
||||||
|
|
||||||
|
- Extensions: Fail early if trying to setup an extension which doesn't
|
||||||
|
implement the :meth:`mopidy.ext.Extension.setup` method. (Fixes:
|
||||||
|
:issue:`813`)
|
||||||
|
|
||||||
|
|
||||||
v0.19.2 (2014-07-26)
|
v0.19.2 (2014-07-26)
|
||||||
====================
|
====================
|
||||||
|
|
||||||
Bug fix release, directly from the Mopidy development sprint at EuroPython 2014
|
Bug fix release, directly from the Mopidy development sprint at EuroPython 2014
|
||||||
in Berlin.
|
in Berlin.
|
||||||
|
|
||||||
**Audio**
|
- Audio: Make :confval:`audio/mixer_volume` work on the software mixer again. This
|
||||||
|
|
||||||
- Make :confval:`audio/mixer_volume` work on the software mixer again. This
|
|
||||||
was broken with the mixer changes in 0.19.0. (Fixes: :issue:`791`)
|
was broken with the mixer changes in 0.19.0. (Fixes: :issue:`791`)
|
||||||
|
|
||||||
**HTTP frontend**
|
- HTTP frontend: When using Tornado 4.0, allow WebSocket requests from other
|
||||||
|
hosts. (Fixes: :issue:`788`)
|
||||||
|
|
||||||
- When using Tornado 4.0, allow WebSocket requests from other hosts. (Fixes:
|
- MPD frontend: Fix crash when MPD commands are called with the wrong number of
|
||||||
:issue:`788`)
|
arguments. This was broken with the MPD command changes in 0.19.0. (Fixes:
|
||||||
|
:issue:`789`)
|
||||||
**MPD frontend**
|
|
||||||
|
|
||||||
- Fix crash when MPD commands are called with the wrong number of arguments.
|
|
||||||
This was broken with the MPD command changes in 0.19.0. (Fixes: :issue:`789`)
|
|
||||||
|
|
||||||
|
|
||||||
v0.19.1 (2014-07-23)
|
v0.19.1 (2014-07-23)
|
||||||
@ -32,21 +47,15 @@ v0.19.1 (2014-07-23)
|
|||||||
|
|
||||||
Bug fix release.
|
Bug fix release.
|
||||||
|
|
||||||
**Dependencies**
|
- Dependencies: Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This
|
||||||
|
should make Mopidy continue to work on Debian/Raspbian stable, where Tornado
|
||||||
|
2.3 is the newest version available.
|
||||||
|
|
||||||
- Mopidy now requires Tornado >= 2.3, instead of >= 3.1. This should make
|
- HTTP frontend: Add missing string interpolation placeholder.
|
||||||
Mopidy continue to work on Debian/Raspbian stable, where Tornado 2.3 is the
|
|
||||||
newest version available.
|
|
||||||
|
|
||||||
**HTTP frontend**
|
- Development: ``mopidy --version`` and :meth:`mopidy.core.Core.get_version`
|
||||||
|
now returns the correct version when Mopidy is run from a Git repo other than
|
||||||
- Add missing string interpolation placeholder.
|
Mopidy's own. (Related to :issue:`706`)
|
||||||
|
|
||||||
**Development**
|
|
||||||
|
|
||||||
- ``mopidy --version`` and :meth:`mopidy.core.Core.get_version` now returns the
|
|
||||||
correct version when Mopidy is run from a Git repo other than Mopidy's own.
|
|
||||||
(Related to :issue:`706`)
|
|
||||||
|
|
||||||
|
|
||||||
v0.19.0 (2014-07-21)
|
v0.19.0 (2014-07-21)
|
||||||
|
|||||||
@ -11,6 +11,24 @@ This list is moderated and updated on a regular basis. If you want your package
|
|||||||
to show up here, follow the :ref:`guide on creating extensions <extensiondev>`.
|
to show up here, follow the :ref:`guide on creating extensions <extensiondev>`.
|
||||||
|
|
||||||
|
|
||||||
|
Mopidy-Banshee
|
||||||
|
==============
|
||||||
|
|
||||||
|
https://github.com/tamland/mopidy-banshee
|
||||||
|
|
||||||
|
Provides a backend for playing music from the `Banshee <http://banshee.fm/>`_
|
||||||
|
music player's music library.
|
||||||
|
|
||||||
|
|
||||||
|
Mopidy-Bassdrive
|
||||||
|
================
|
||||||
|
|
||||||
|
https://github.com/felixb/mopidy-Bassdrive
|
||||||
|
|
||||||
|
Provides a backend for playing radio streams from `BassDrive
|
||||||
|
<http://bassdrive.com/>`_.
|
||||||
|
|
||||||
|
|
||||||
Mopidy-Beets
|
Mopidy-Beets
|
||||||
============
|
============
|
||||||
|
|
||||||
@ -47,6 +65,15 @@ Extension for playing music and audio from the `Internet Archive
|
|||||||
<https://archive.org/>`_.
|
<https://archive.org/>`_.
|
||||||
|
|
||||||
|
|
||||||
|
Mopidy-LeftAsRain
|
||||||
|
=================
|
||||||
|
|
||||||
|
https://github.com/naglis/mopidy-leftasrain
|
||||||
|
|
||||||
|
Extension for playing music from the `leftasrain.com
|
||||||
|
<http://leftasrain.com/>`_ music blog.
|
||||||
|
|
||||||
|
|
||||||
Mopidy-Local
|
Mopidy-Local
|
||||||
============
|
============
|
||||||
|
|
||||||
|
|||||||
@ -47,3 +47,12 @@ Mopidy-Scrobbler
|
|||||||
https://github.com/mopidy/mopidy-scrobbler
|
https://github.com/mopidy/mopidy-scrobbler
|
||||||
|
|
||||||
Extension for scrobbling played tracks to Last.fm.
|
Extension for scrobbling played tracks to Last.fm.
|
||||||
|
|
||||||
|
|
||||||
|
Mopidy-Touchscreen
|
||||||
|
==================
|
||||||
|
|
||||||
|
https://github.com/9and3r/mopidy-touchscreen
|
||||||
|
|
||||||
|
Extension for displaying track info and controlling Mopidy from a touch screen
|
||||||
|
using `PyGame <http://www.pygame.org/>`_/SDL.
|
||||||
|
|||||||
@ -5,9 +5,9 @@ Extension development
|
|||||||
*********************
|
*********************
|
||||||
|
|
||||||
Mopidy started as simply an MPD server that could play music from Spotify.
|
Mopidy started as simply an MPD server that could play music from Spotify.
|
||||||
Early on Mopidy got multiple "frontends" to expose Mopidy to more than just MPD
|
Early on, Mopidy got multiple "frontends" to expose Mopidy to more than just MPD
|
||||||
clients: for example the scrobbler frontend what scrobbles what you've listened
|
clients: for example the scrobbler frontend that scrobbles your listening
|
||||||
to to your Last.fm account, the MPRIS frontend that integrates Mopidy into the
|
history to your Last.fm account, the MPRIS frontend that integrates Mopidy into the
|
||||||
Ubuntu Sound Menu, and the HTTP server and JavaScript player API making web
|
Ubuntu Sound Menu, and the HTTP server and JavaScript player API making web
|
||||||
based Mopidy clients possible. In Mopidy 0.9 we added support for multiple
|
based Mopidy clients possible. In Mopidy 0.9 we added support for multiple
|
||||||
music sources without stopping and reconfiguring Mopidy: for example the local
|
music sources without stopping and reconfiguring Mopidy: for example the local
|
||||||
@ -27,7 +27,7 @@ Anatomy of an extension
|
|||||||
|
|
||||||
Extensions are located in a Python package called ``mopidy_something`` where
|
Extensions are located in a Python package called ``mopidy_something`` where
|
||||||
"something" is the name of the application, library or web service you want to
|
"something" is the name of the application, library or web service you want to
|
||||||
integrated with Mopidy. So for example if you plan to add support for a service
|
integrate with Mopidy. So, for example, if you plan to add support for a service
|
||||||
named Soundspot to Mopidy, you would name your extension's Python package
|
named Soundspot to Mopidy, you would name your extension's Python package
|
||||||
``mopidy_soundspot``.
|
``mopidy_soundspot``.
|
||||||
|
|
||||||
@ -37,10 +37,6 @@ be something like "Mopidy-Soundspot". Make sure to include the name "Mopidy"
|
|||||||
somewhere in that name and that you check the capitalization. This is the name
|
somewhere in that name and that you check the capitalization. This is the name
|
||||||
users will use when they install your extension from PyPI.
|
users will use when they install your extension from PyPI.
|
||||||
|
|
||||||
Also make sure the development version link in your package details work so
|
|
||||||
that people can easily install the development version into their virtualenv
|
|
||||||
simply by running e.g. ``pip install Mopidy-Soundspot==dev``.
|
|
||||||
|
|
||||||
Mopidy extensions must be licensed under an Apache 2.0 (like Mopidy itself),
|
Mopidy extensions must be licensed under an Apache 2.0 (like Mopidy itself),
|
||||||
BSD, MIT or more liberal license to be able to be enlisted in the Mopidy
|
BSD, MIT or more liberal license to be able to be enlisted in the Mopidy
|
||||||
documentation. The license text should be included in the ``LICENSE`` file in
|
documentation. The license text should be included in the ``LICENSE`` file in
|
||||||
@ -79,11 +75,11 @@ the readme of `cookiecutter-mopidy-ext
|
|||||||
Example README.rst
|
Example README.rst
|
||||||
==================
|
==================
|
||||||
|
|
||||||
The README file should quickly tell what the extension does, how to install it,
|
The README file should quickly explain what the extension does, how to install
|
||||||
and how to configure it. The README should contain a development snapshot link
|
it, and how to configure it. It should also contain a link to a tarball of the
|
||||||
to a tarball of the latest development version of the extension. It's important
|
latest development version of the extension. It's important that this link ends
|
||||||
that the development snapshot link ends with ``#egg=Mopidy-Something-dev`` for
|
with ``#egg=Mopidy-Something-dev`` for installation using
|
||||||
installation using ``pip install Mopidy-Something==dev`` to work.
|
``pip install Mopidy-Something==dev`` to work.
|
||||||
|
|
||||||
.. code-block:: rst
|
.. code-block:: rst
|
||||||
|
|
||||||
@ -124,7 +120,7 @@ installation using ``pip install Mopidy-Something==dev`` to work.
|
|||||||
|
|
||||||
- `Source code <https://github.com/mopidy/mopidy-soundspot>`_
|
- `Source code <https://github.com/mopidy/mopidy-soundspot>`_
|
||||||
- `Issue tracker <https://github.com/mopidy/mopidy-soundspot/issues>`_
|
- `Issue tracker <https://github.com/mopidy/mopidy-soundspot/issues>`_
|
||||||
- `Download development snapshot <https://github.com/mopidy/mopidy-soundspot/tarball/master#egg=Mopidy-Soundspot-dev>`_
|
- `Development branch tarball <https://github.com/mopidy/mopidy-soundspot/tarball/master#egg=Mopidy-Soundspot-dev>`_
|
||||||
|
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
@ -239,9 +235,9 @@ The root of your Python package should have an ``__version__`` attribute with a
|
|||||||
class named ``Extension`` which inherits from Mopidy's extension base class,
|
class named ``Extension`` which inherits from Mopidy's extension base class,
|
||||||
:class:`mopidy.ext.Extension`. This is the class referred to in the
|
:class:`mopidy.ext.Extension`. This is the class referred to in the
|
||||||
``entry_points`` part of ``setup.py``. Any imports of other files in your
|
``entry_points`` part of ``setup.py``. Any imports of other files in your
|
||||||
extension should be kept inside methods. This ensures that this file can be
|
extension, outside of Mopidy and it's core requirements, should be kept inside
|
||||||
imported without raising :exc:`ImportError` exceptions for missing
|
methods. This ensures that this file can be imported without raising
|
||||||
dependencies, etc.
|
:exc:`ImportError` exceptions for missing dependencies, etc.
|
||||||
|
|
||||||
The default configuration for the extension is defined by the
|
The default configuration for the extension is defined by the
|
||||||
``get_default_config()`` method in the ``Extension`` class which returns a
|
``get_default_config()`` method in the ``Extension`` class which returns a
|
||||||
@ -252,10 +248,10 @@ an ``enabled`` config which normally should default to ``true``. Provide good
|
|||||||
defaults for all config values so that as few users as possible will need to
|
defaults for all config values so that as few users as possible will need to
|
||||||
change them. The exception is if the config value has security implications; in
|
change them. The exception is if the config value has security implications; in
|
||||||
that case you should default to the most secure configuration. Leave any
|
that case you should default to the most secure configuration. Leave any
|
||||||
configurations that doesn't have meaningful defaults blank, like ``username``
|
configurations that don't have meaningful defaults blank, like ``username``
|
||||||
and ``password``. In the example below, we've chosen to maintain the default
|
and ``password``. In the example below, we've chosen to maintain the default
|
||||||
config as a separate file named ``ext.conf``. This makes it easy to e.g.
|
config as a separate file named ``ext.conf``. This makes it easy to include the
|
||||||
include the default config in documentation without duplicating it.
|
default config in documentation without duplicating it.
|
||||||
|
|
||||||
This is ``mopidy_soundspot/__init__.py``::
|
This is ``mopidy_soundspot/__init__.py``::
|
||||||
|
|
||||||
@ -321,6 +317,9 @@ This is ``mopidy_soundspot/__init__.py``::
|
|||||||
gobject.type_register(SoundspotMixer)
|
gobject.type_register(SoundspotMixer)
|
||||||
gst.element_register(
|
gst.element_register(
|
||||||
SoundspotMixer, 'soundspotmixer', gst.RANK_MARGINAL)
|
SoundspotMixer, 'soundspotmixer', gst.RANK_MARGINAL)
|
||||||
|
|
||||||
|
# Or nothing to register e.g. command extension
|
||||||
|
pass
|
||||||
|
|
||||||
And this is ``mopidy_soundspot/ext.conf``:
|
And this is ``mopidy_soundspot/ext.conf``:
|
||||||
|
|
||||||
@ -393,7 +392,7 @@ such as scanning for media, adding a command is the way to go. Your top level
|
|||||||
command name will always match your extension name, but you are free to add
|
command name will always match your extension name, but you are free to add
|
||||||
sub-commands with names of your choosing.
|
sub-commands with names of your choosing.
|
||||||
|
|
||||||
The skeleton of a commands would look like this. See :ref:`commands-api` for
|
The skeleton of a command would look like this. See :ref:`commands-api` for
|
||||||
more details.
|
more details.
|
||||||
|
|
||||||
::
|
::
|
||||||
@ -409,14 +408,14 @@ more details.
|
|||||||
self.add_argument('--foo')
|
self.add_argument('--foo')
|
||||||
|
|
||||||
def run(self, args, config, extensions):
|
def run(self, args, config, extensions):
|
||||||
# Your backend implementation
|
# Your command implementation
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
Example web application
|
Example web application
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
As of Mopidy 0.19, extensions can use Mopidy's builtin web server to host
|
As of Mopidy 0.19, extensions can use Mopidy's built-in web server to host
|
||||||
static web clients as well as Tornado and WSGI web applications. For several
|
static web clients as well as Tornado and WSGI web applications. For several
|
||||||
examples, see the :ref:`http-server-api` docs or explore with
|
examples, see the :ref:`http-server-api` docs or explore with
|
||||||
:ref:`http-explore-extension` extension.
|
:ref:`http-explore-extension` extension.
|
||||||
@ -433,6 +432,17 @@ your :meth:`~mopidy.ext.Extension.setup` method register all your custom
|
|||||||
GStreamer elements.
|
GStreamer elements.
|
||||||
|
|
||||||
|
|
||||||
|
Running an extension
|
||||||
|
====================
|
||||||
|
|
||||||
|
Once your extension is ready to go, to see it in action you'll need to register
|
||||||
|
it with Mopidy. Typically this is done by running ``python setup.py install``
|
||||||
|
from your extension's Git repo root directory. While developing your extension
|
||||||
|
and to avoid doing this every time you make a change, you can instead run
|
||||||
|
``python setup.py develop`` to effectively link Mopidy directly with your
|
||||||
|
development files.
|
||||||
|
|
||||||
|
|
||||||
Python conventions
|
Python conventions
|
||||||
==================
|
==================
|
||||||
|
|
||||||
@ -447,13 +457,13 @@ Use of Mopidy APIs
|
|||||||
|
|
||||||
When writing an extension, you should only use APIs documented at
|
When writing an extension, you should only use APIs documented at
|
||||||
:ref:`api-ref`. Other parts of Mopidy, like :mod:`mopidy.utils`, may change at
|
:ref:`api-ref`. Other parts of Mopidy, like :mod:`mopidy.utils`, may change at
|
||||||
any time, and is not something extensions should use.
|
any time and are not something extensions should use.
|
||||||
|
|
||||||
|
|
||||||
Logging in extensions
|
Logging in extensions
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
When making servers like Mopidy, logging is essential for understanding what's
|
For servers like Mopidy, logging is essential for understanding what's
|
||||||
going on. We use the :mod:`logging` module from Python's standard library. When
|
going on. We use the :mod:`logging` module from Python's standard library. When
|
||||||
creating a logger, always namespace the logger using your Python package name
|
creating a logger, always namespace the logger using your Python package name
|
||||||
as this will be visible in Mopidy's debug log::
|
as this will be visible in Mopidy's debug log::
|
||||||
|
|||||||
@ -2,25 +2,66 @@
|
|||||||
Mopidy
|
Mopidy
|
||||||
******
|
******
|
||||||
|
|
||||||
Mopidy is a music server which can play music both from multiple sources, like
|
Mopidy is an extensible music server written in Python.
|
||||||
your :ref:`local hard drive <ext-local>`, :ref:`radio streams <ext-stream>`,
|
|
||||||
and from Spotify and SoundCloud. Searches combines results from all music
|
|
||||||
sources, and you can mix tracks from all sources in your play queue. Your
|
|
||||||
playlists from Spotify or SoundCloud are also available for use.
|
|
||||||
|
|
||||||
To control your Mopidy music server, you can use one of Mopidy's :ref:`web
|
Mopidy plays music from local disk, Spotify, SoundCloud, Google Play Music, and
|
||||||
clients <http-clients>`, the :ref:`Ubuntu Sound Menu <ubuntu-sound-menu>`, any
|
more. You edit the playlist from any phone, tablet, or computer using a range
|
||||||
device on the same network which can control :ref:`UPnP MediaRenderers
|
of MPD and web clients.
|
||||||
<upnp-clients>`, or any :ref:`MPD client <mpd-clients>`. MPD clients are
|
|
||||||
available for many platforms, including Windows, OS X, Linux, Android and iOS.
|
**Stream music from the cloud**
|
||||||
|
|
||||||
|
Vanilla Mopidy only plays music from your :ref:`local disk <ext-local>` and
|
||||||
|
:ref:`radio streams <ext-stream>`. Through :ref:`extensions <ext-backends>`,
|
||||||
|
Mopidy can play music from cloud services like Spotify, SoundCloud, and Google
|
||||||
|
Play Music. With Mopidy's extension support, backends for new music sources can
|
||||||
|
be easily added.
|
||||||
|
|
||||||
|
**Mopidy is just a server**
|
||||||
|
|
||||||
|
Mopidy is a Python application that runs in a terminal or in the background on
|
||||||
|
Linux computers or Macs that have network connectivity and audio output. Out of
|
||||||
|
the box, Mopidy is an :ref:`MPD <ext-mpd>` and :ref:`HTTP <ext-http>` server.
|
||||||
|
:ref:`Additional frontends <ext-frontends>` for controlling Mopidy can be
|
||||||
|
installed from extensions.
|
||||||
|
|
||||||
|
**Everybody use their favorite client**
|
||||||
|
|
||||||
|
You and the people around you can all connect their favorite :ref:`MPD
|
||||||
|
<mpd-clients>` or :ref:`web client <http-clients>` to the Mopidy server to
|
||||||
|
search for music and manage the playlist together. With a browser or MPD
|
||||||
|
client, which is available for all popular operating systems, you can control
|
||||||
|
the music from any phone, tablet, or computer.
|
||||||
|
|
||||||
|
**Mopidy on Raspberry Pi**
|
||||||
|
|
||||||
|
The :ref:`Raspberry Pi <raspberrypi-installation>` is a popular device to run
|
||||||
|
Mopidy on, either using Raspbian or Arch Linux. It is quite slow, but it is
|
||||||
|
very affordable. In fact, the Kickstarter funded Gramofon: Modern Cloud Jukebox
|
||||||
|
project used Mopidy on a Raspberry Pi to prototype the Gramofon device. Mopidy
|
||||||
|
is also a major building block in the Pi Musicbox integrated audio jukebox
|
||||||
|
system for Raspberry Pi.
|
||||||
|
|
||||||
|
**Mopidy is hackable**
|
||||||
|
|
||||||
|
Mopidy's extension support and :ref:`Python <api-ref>`, :ref:`JSON-RPC
|
||||||
|
<http-api>`, and :ref:`JavaScript APIs <mopidy-js>` makes Mopidy perfect for
|
||||||
|
building your own hacks. In one project, a Raspberry Pi was embedded in an old
|
||||||
|
cassette player. The buttons and volume control are wired up with GPIO on the
|
||||||
|
Raspberry Pi, and is used to control playback through a custom Mopidy
|
||||||
|
extension. The cassettes have NFC tags used to select playlists from Spotify.
|
||||||
|
|
||||||
|
**Getting started**
|
||||||
|
|
||||||
To get started with Mopidy, start by reading :ref:`installation`.
|
To get started with Mopidy, start by reading :ref:`installation`.
|
||||||
|
|
||||||
|
**Getting help**
|
||||||
|
|
||||||
If you get stuck, we usually hang around at ``#mopidy`` at `irc.freenode.net
|
If you get stuck, we usually hang around at ``#mopidy`` at `irc.freenode.net
|
||||||
<http://freenode.net/>`_ and also have a `mailing list at Google Groups
|
<http://freenode.net/>`_ (with `searchable logs
|
||||||
<https://groups.google.com/forum/?fromgroups=#!forum/mopidy>`_. If you stumble
|
<https://botbot.me/freenode/mopidy/>`_) and also have a `mailing list at Google
|
||||||
into a bug or got a feature request, please create an issue in the `issue
|
Groups <https://groups.google.com/forum/?fromgroups=#!forum/mopidy>`_. If you
|
||||||
tracker <https://github.com/mopidy/mopidy/issues>`_. The `source code
|
stumble into a bug or got a feature request, please create an issue in the
|
||||||
|
`issue tracker <https://github.com/mopidy/mopidy/issues>`_. The `source code
|
||||||
<https://github.com/mopidy/mopidy>`_ may also be of help. If you want to stay
|
<https://github.com/mopidy/mopidy>`_ may also be of help. If you want to stay
|
||||||
up to date on Mopidy developments, you can follow `@mopidy
|
up to date on Mopidy developments, you can follow `@mopidy
|
||||||
<https://twitter.com/mopidy/>`_ on Twitter.
|
<https://twitter.com/mopidy/>`_ on Twitter.
|
||||||
|
|||||||
@ -21,4 +21,4 @@ if (isinstance(pykka.__version__, basestring)
|
|||||||
warnings.filterwarnings('ignore', 'could not open display')
|
warnings.filterwarnings('ignore', 'could not open display')
|
||||||
|
|
||||||
|
|
||||||
__version__ = '0.19.2'
|
__version__ = '0.19.3'
|
||||||
|
|||||||
@ -72,6 +72,7 @@ class Audio(pykka.ThreadingActor):
|
|||||||
|
|
||||||
def on_start(self):
|
def on_start(self):
|
||||||
try:
|
try:
|
||||||
|
self._setup_preferences()
|
||||||
self._setup_playbin()
|
self._setup_playbin()
|
||||||
self._setup_output()
|
self._setup_output()
|
||||||
self._setup_mixer()
|
self._setup_mixer()
|
||||||
@ -96,6 +97,14 @@ class Audio(pykka.ThreadingActor):
|
|||||||
if signal_id is not None:
|
if signal_id is not None:
|
||||||
element.disconnect(signal_id)
|
element.disconnect(signal_id)
|
||||||
|
|
||||||
|
def _setup_preferences(self):
|
||||||
|
# Fix for https://github.com/mopidy/mopidy/issues/604
|
||||||
|
registry = gst.registry_get_default()
|
||||||
|
jacksink = registry.find_feature(
|
||||||
|
'jackaudiosink', gst.TYPE_ELEMENT_FACTORY)
|
||||||
|
if jacksink:
|
||||||
|
jacksink.set_rank(gst.RANK_SECONDARY)
|
||||||
|
|
||||||
def _setup_playbin(self):
|
def _setup_playbin(self):
|
||||||
playbin = gst.element_factory_make('playbin2')
|
playbin = gst.element_factory_make('playbin2')
|
||||||
playbin.set_property('flags', PLAYBIN_FLAGS)
|
playbin.set_property('flags', PLAYBIN_FLAGS)
|
||||||
|
|||||||
@ -186,7 +186,8 @@ def audio_data_to_track(data):
|
|||||||
|
|
||||||
track_kwargs['date'] = _date(tags)
|
track_kwargs['date'] = _date(tags)
|
||||||
track_kwargs['last_modified'] = int(data.get('mtime') or 0)
|
track_kwargs['last_modified'] = int(data.get('mtime') or 0)
|
||||||
track_kwargs['length'] = (data.get(gst.TAG_DURATION) or 0) // gst.MSECOND
|
track_kwargs['length'] = max(
|
||||||
|
0, (data.get(gst.TAG_DURATION) or 0)) // gst.MSECOND
|
||||||
|
|
||||||
# Clear out any empty values we found
|
# Clear out any empty values we found
|
||||||
track_kwargs = {k: v for k, v in track_kwargs.items() if v}
|
track_kwargs = {k: v for k, v in track_kwargs.items() if v}
|
||||||
|
|||||||
@ -219,7 +219,7 @@ class Command(object):
|
|||||||
def run(self, *args, **kwargs):
|
def run(self, *args, **kwargs):
|
||||||
"""Run the command.
|
"""Run the command.
|
||||||
|
|
||||||
Must be implemented by sub-classes that are not simply and intermediate
|
Must be implemented by sub-classes that are not simply an intermediate
|
||||||
in the command namespace.
|
in the command namespace.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@ -99,7 +99,7 @@ class Extension(object):
|
|||||||
:param registry: the extension registry
|
:param registry: the extension registry
|
||||||
:type registry: :class:`Registry`
|
:type registry: :class:`Registry`
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Registry(collections.Mapping):
|
class Registry(collections.Mapping):
|
||||||
|
|||||||
@ -58,10 +58,10 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
|||||||
if self.zeroconf_name:
|
if self.zeroconf_name:
|
||||||
self.zeroconf_http = zeroconf.Zeroconf(
|
self.zeroconf_http = zeroconf.Zeroconf(
|
||||||
stype='_http._tcp', name=self.zeroconf_name,
|
stype='_http._tcp', name=self.zeroconf_name,
|
||||||
host=self.hostname, port=self.port)
|
port=self.port)
|
||||||
self.zeroconf_mopidy_http = zeroconf.Zeroconf(
|
self.zeroconf_mopidy_http = zeroconf.Zeroconf(
|
||||||
stype='_mopidy-http._tcp', name=self.zeroconf_name,
|
stype='_mopidy-http._tcp', name=self.zeroconf_name,
|
||||||
host=self.hostname, port=self.port)
|
port=self.port)
|
||||||
self.zeroconf_http.publish()
|
self.zeroconf_http.publish()
|
||||||
self.zeroconf_mopidy_http.publish()
|
self.zeroconf_mopidy_http.publish()
|
||||||
|
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener):
|
|||||||
if self.zeroconf_name:
|
if self.zeroconf_name:
|
||||||
self.zeroconf_service = zeroconf.Zeroconf(
|
self.zeroconf_service = zeroconf.Zeroconf(
|
||||||
stype='_mpd._tcp', name=self.zeroconf_name,
|
stype='_mpd._tcp', name=self.zeroconf_name,
|
||||||
host=self.hostname, port=self.port)
|
port=self.port)
|
||||||
self.zeroconf_service.publish()
|
self.zeroconf_service.publish()
|
||||||
|
|
||||||
def on_stop(self):
|
def on_stop(self):
|
||||||
|
|||||||
@ -23,8 +23,11 @@ def _is_loopback_address(host):
|
|||||||
host == '::1')
|
host == '::1')
|
||||||
|
|
||||||
|
|
||||||
def _convert_text_to_dbus_bytes(text):
|
def _convert_text_list_to_dbus_format(text_list):
|
||||||
return [dbus.Byte(ord(c)) for c in text]
|
array = dbus.Array(signature='ay')
|
||||||
|
for text in text_list:
|
||||||
|
array.append([dbus.Byte(ord(c)) for c in text])
|
||||||
|
return array
|
||||||
|
|
||||||
|
|
||||||
class Zeroconf(object):
|
class Zeroconf(object):
|
||||||
@ -43,21 +46,17 @@ class Zeroconf(object):
|
|||||||
:type text: list of str
|
:type text: list of str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name, port, stype=None, domain=None,
|
def __init__(self, name, port, stype=None, domain=None, text=None):
|
||||||
host=None, text=None):
|
|
||||||
self.group = None
|
self.group = None
|
||||||
self.stype = stype or '_http._tcp'
|
self.stype = stype or '_http._tcp'
|
||||||
self.domain = domain or ''
|
self.domain = domain or ''
|
||||||
self.port = port
|
self.port = port
|
||||||
self.text = text or []
|
self.text = text or []
|
||||||
if host in ('::', '0.0.0.0'):
|
|
||||||
self.host = ''
|
|
||||||
else:
|
|
||||||
self.host = host
|
|
||||||
|
|
||||||
template = string.Template(name)
|
template = string.Template(name)
|
||||||
self.name = template.safe_substitute(
|
self.name = template.safe_substitute(
|
||||||
hostname=self.host or socket.getfqdn(), port=self.port)
|
hostname=socket.getfqdn(), port=self.port)
|
||||||
|
self.host = '%s.local' % socket.getfqdn()
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return 'Zeroconf service %s at [%s]:%d' % (
|
return 'Zeroconf service %s at [%s]:%d' % (
|
||||||
@ -95,11 +94,11 @@ class Zeroconf(object):
|
|||||||
'org.freedesktop.Avahi', server.EntryGroupNew()),
|
'org.freedesktop.Avahi', server.EntryGroupNew()),
|
||||||
'org.freedesktop.Avahi.EntryGroup')
|
'org.freedesktop.Avahi.EntryGroup')
|
||||||
|
|
||||||
text = [_convert_text_to_dbus_bytes(t) for t in self.text]
|
|
||||||
self.group.AddService(
|
self.group.AddService(
|
||||||
_AVAHI_IF_UNSPEC, _AVAHI_PROTO_UNSPEC,
|
_AVAHI_IF_UNSPEC, _AVAHI_PROTO_UNSPEC,
|
||||||
dbus.UInt32(_AVAHI_PUBLISHFLAGS_NONE), self.name, self.stype,
|
dbus.UInt32(_AVAHI_PUBLISHFLAGS_NONE), self.name, self.stype,
|
||||||
self.domain, self.host, dbus.UInt16(self.port), text)
|
self.domain, self.host, dbus.UInt16(self.port),
|
||||||
|
_convert_text_list_to_dbus_format(self.text))
|
||||||
|
|
||||||
self.group.Commit()
|
self.group.Commit()
|
||||||
logger.debug('%s: Published', self)
|
logger.debug('%s: Published', self)
|
||||||
|
|||||||
@ -19,7 +19,8 @@ class ExtensionTest(unittest.TestCase):
|
|||||||
self.assertIsNone(self.ext.version)
|
self.assertIsNone(self.ext.version)
|
||||||
|
|
||||||
def test_get_default_config_raises_not_implemented(self):
|
def test_get_default_config_raises_not_implemented(self):
|
||||||
self.assertRaises(NotImplementedError, self.ext.get_default_config)
|
with self.assertRaises(NotImplementedError):
|
||||||
|
self.ext.get_default_config()
|
||||||
|
|
||||||
def test_get_config_schema_returns_extension_schema(self):
|
def test_get_config_schema_returns_extension_schema(self):
|
||||||
schema = self.ext.get_config_schema()
|
schema = self.ext.get_config_schema()
|
||||||
@ -27,3 +28,7 @@ class ExtensionTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_validate_environment_does_nothing_by_default(self):
|
def test_validate_environment_does_nothing_by_default(self):
|
||||||
self.assertIsNone(self.ext.validate_environment())
|
self.assertIsNone(self.ext.validate_environment())
|
||||||
|
|
||||||
|
def test_setup_raises_not_implemented(self):
|
||||||
|
with self.assertRaises(NotImplementedError):
|
||||||
|
self.ext.setup(None)
|
||||||
|
|||||||
@ -48,5 +48,6 @@ class VersionTest(unittest.TestCase):
|
|||||||
self.assertLess(SV('0.18.2'), SV('0.18.3'))
|
self.assertLess(SV('0.18.2'), SV('0.18.3'))
|
||||||
self.assertLess(SV('0.18.3'), SV('0.19.0'))
|
self.assertLess(SV('0.18.3'), SV('0.19.0'))
|
||||||
self.assertLess(SV('0.19.0'), SV('0.19.1'))
|
self.assertLess(SV('0.19.0'), SV('0.19.1'))
|
||||||
self.assertLess(SV('0.19.1'), SV(__version__))
|
self.assertLess(SV('0.19.1'), SV('0.19.2'))
|
||||||
self.assertLess(SV(__version__), SV('0.19.3'))
|
self.assertLess(SV('0.19.2'), SV(__version__))
|
||||||
|
self.assertLess(SV(__version__), SV('0.19.4'))
|
||||||
|
|||||||
16
tox.ini
16
tox.ini
@ -1,13 +1,25 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = py27, docs, flake8
|
envlist = py27, py27-tornado23, py27-tornado31, docs, flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
sitepackages = true
|
sitepackages = true
|
||||||
|
commands = nosetests -v --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=mopidy
|
||||||
deps =
|
deps =
|
||||||
coverage
|
coverage
|
||||||
mock
|
mock
|
||||||
nose
|
nose
|
||||||
commands = nosetests -v --with-xunit --xunit-file=xunit-{envname}.xml --with-coverage --cover-package=mopidy
|
|
||||||
|
[testenv:py27-tornado23]
|
||||||
|
commands = nosetests -v tests/http
|
||||||
|
deps =
|
||||||
|
{[testenv]deps}
|
||||||
|
tornado==2.3
|
||||||
|
|
||||||
|
[testenv:py27-tornado31]
|
||||||
|
commands = nosetests -v tests/http
|
||||||
|
deps =
|
||||||
|
{[testenv]deps}
|
||||||
|
tornado==3.1
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
deps = -r{toxinidir}/docs/requirements.txt
|
deps = -r{toxinidir}/docs/requirements.txt
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user