Soundcloud backend #339

Closed
wants to merge 32 commits into from

3 participants

@dz0ny
Mopidy member

Implements #293

Done:

Todo:

  • Tests

Ideas:

  • Liking (user can add song to playlist liked)
  • Playlists to Sets cross saving
@jodal jodal commented on an outdated diff Mar 14, 2013
docs/modules/backends/soundcloud.rst
@@ -0,0 +1,8 @@
+.. soundcloud-backend:
+
+*************************************************
+:mod:`mopidy.backends.soundcloud` -- Soundcloud backend
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Just doing some random review here... not going to look through everything now.

SoundCloud should be written with a capital "C". "***" rows should have the same length as the header text itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/actor.py
@@ -0,0 +1,27 @@
+from __future__ import unicode_literals
+
+import logging
+
+import pykka
+
+from mopidy.backends import base
+from mopidy import settings
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Sort this block of imports. Should be in the opposite order.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/library.py
+logger = logging.getLogger('mopidy.backends.soundcloud')
+
+
+class SoundcloudLibraryProvider(base.BaseLibraryProvider):
+ def __init__(self, *args, **kwargs):
+ super(SoundcloudLibraryProvider, self).__init__(*args, **kwargs)
+
+ def find_exact(self, **query):
+ return self.search(**query)
+
+ def search(self, **query):
+ if not query:
+ return
+
+ for (field, val) in query.iteritems():
+ if field == "any":
@jodal
Mopidy member
jodal added a note Mar 14, 2013

We prefer single quotes over double quotes. Same for the argument to split() below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on the diff Mar 14, 2013
mopidy/backends/soundcloud/library.py
+import logging
+
+from mopidy.backends import base
+from mopidy.models import SearchResult
+
+logger = logging.getLogger('mopidy.backends.soundcloud')
+
+
+class SoundcloudLibraryProvider(base.BaseLibraryProvider):
+ def __init__(self, *args, **kwargs):
+ super(SoundcloudLibraryProvider, self).__init__(*args, **kwargs)
+
+ def find_exact(self, **query):
+ return self.search(**query)
+
+ def search(self, **query):
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Should maybe add some TODOs here if there are more stuff than we can make work with the SoundCloud search API. I'm thinking of things like:

  • exact searches
  • search specific fields like artist, title, album, year, etc.
  • search for multiple terms (e.g. {'any': ['foo', 'bar']} returning sound files matching both 'foo' and 'bar')

If some of these features are not supported by SoundCloud, it should probably be documented in a docstring on the function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/playlists.py
+
+ def delete(self, uri):
+ pass # TODO
+
+ def lookup(self, uri):
+ for playlist in self._playlists:
+ if playlist.uri == uri:
+ return playlist
+
+ def refresh(self):
+ logger.info('Loading playlists from Soundcloud')
+
+ playlists = []
+
+ playlist = Playlist(
+ uri="soundcloud://playlists/liked",
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Not that we have any strong conventions on our custom URIs yet, but typically in URIs, playlists would here be interpreted as the host name. Maybe use three slashes, or skip the slashes entirely and go with colons. I'm not sure, I just think that we should align how we construct custom URIs across the backends.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal and 1 other commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
@@ -0,0 +1,147 @@
+#!/usr/local/bin/python
+# -*- coding: utf-8 -*-
+#
@jodal
Mopidy member
jodal added a note Mar 14, 2013

No need for these three lines, as there is no module level code to run here and no non-ASCII chars AFAICT.

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

I was trying to add some indication to song names of where the come from (like ☀☁)

@jodal
Mopidy member
jodal added a note Mar 14, 2013

Unit tests are perfect for experimenting with such things ;-)

I think that such markers on tracks needs to be implemented in the various frontends. E.g. the web clients got enough information to annotate the tracks on their own, while e.g. MPD would need Unicode char hacks like this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+ ## TODO: merge this to util library
+ def __init__(self, ctl=8, ttl=3600):
+ self.cache = {}
+ self.ctl = ctl
+ self.ttl = ttl
+ self._call_count = 1
+
+ def __call__(self, func):
+ def _memoized(*args):
+ self.func = func
+ now = time.time()
+ try:
+ value, last_update = self.cache[args]
+ age = now - last_update
+ if self._call_count >= self.ctl or \
+ age > self.ttl:
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Parenthesis is better than backslash, e.g.:

if (self._call_count >= self.ctrl
        or age > self.ttl):
    # ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal and 1 other commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+ return value
+
+ except (KeyError, AttributeError):
+ value = self.func(*args)
+ self.cache[args] = (value, now)
+ return value
+
+ except TypeError:
+ return self.func(*args)
+ return _memoized
+
+
+class SoundcloudClient(object):
+
+ CLIENT_ID = "93e33e327fd8a9b77becd179652272e2"
+ CLIENT_SECRET = "f1a2e1ff740f3e1e340e6993ceb18583"
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Where do these come from? Are they associated with your SoundCloud account or something like that, or did you register Mopidy as a client?

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

Registered custom client.

@jodal
Mopidy member
jodal added a note Mar 14, 2013

If there's any way to associate a client to multiple SoundCloud user accounts, you can associate it with the stein.magnus@jodal.no account as well. Wasn't immediately obvious to me without registering yet another app if that's possible.

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

No they are tied to one oner only. I was thinking of maybe providing defaults, overridable by user settings.

@jodal
Mopidy member
jodal added a note Mar 14, 2013

No problem. Will Mopidy be visible in the SoundCloud app directory, or is that a separate registration or a curated directory?

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

No idea, but that probably requires some communication with SoundCloud team.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+ return _memoized
+
+
+class SoundcloudClient(object):
+
+ CLIENT_ID = "93e33e327fd8a9b77becd179652272e2"
+ CLIENT_SECRET = "f1a2e1ff740f3e1e340e6993ceb18583"
+
+ def __init__(self, username):
+ super(SoundcloudClient, self).__init__()
+ self.user_id = self.get_userid(username)
+
+ def get_userid(self, username):
+ try:
+ user = self._get("resolve.json?url=http://soundcloud.com/%s" % username)
+ return user.get("id")
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Should use single quotes on the above two lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+class SoundcloudClient(object):
+
+ CLIENT_ID = "93e33e327fd8a9b77becd179652272e2"
+ CLIENT_SECRET = "f1a2e1ff740f3e1e340e6993ceb18583"
+
+ def __init__(self, username):
+ super(SoundcloudClient, self).__init__()
+ self.user_id = self.get_userid(username)
+
+ def get_userid(self, username):
+ try:
+ user = self._get("resolve.json?url=http://soundcloud.com/%s" % username)
+ return user.get("id")
+ except Exception:
+ raise logger.error('Can\'t get id for %s, status code %s' % (
+ username, user.status_code))
@jodal
Mopidy member
jodal added a note Mar 14, 2013

But here double quotes to avoid the escaping of single quote inside the string is totally OK.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+
+ except TypeError:
+ return self.func(*args)
+ return _memoized
+
+
+class SoundcloudClient(object):
+
+ CLIENT_ID = "93e33e327fd8a9b77becd179652272e2"
+ CLIENT_SECRET = "f1a2e1ff740f3e1e340e6993ceb18583"
+
+ def __init__(self, username):
+ super(SoundcloudClient, self).__init__()
+ self.user_id = self.get_userid(username)
+
+ def get_userid(self, username):
@jodal
Mopidy member
jodal added a note Mar 14, 2013

I think this should be named get_user_id, like the variable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal and 1 other commented on an outdated diff Mar 14, 2013
mopidy/backends/soundcloud/soundcloud.py
+ return tracks
+
+ def _get(self, url):
+
+ if '?' in url:
+ url = "%s&client_id=%s" % (url, self.CLIENT_ID)
+ else:
+ url = "%s?client_id=%s" % (url, self.CLIENT_ID)
+
+ url = 'https://api.soundcloud.com/%s' % url
+
+ logger.debug('Requesting %s' % url)
+ req = requests.get(url)
+ if req.status_code != 200:
+ raise logger.error('Request %s, failed with status code %s' % (
+ url, req.status_code))
@jodal
Mopidy member
jodal added a note Mar 14, 2013

Is there some useful error response body to include in a debug level message here?

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

No only standard http messages in the body, like { reason: "401 Unathorized" } etc

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal and 1 other commented on an outdated diff Mar 14, 2013
mopidy/settings.py
@@ -326,3 +326,8 @@
'rtmps',
'rtsp',
)
+
+#: Your `Soundcloud.comLast.fm <http://www.soundcloud.com/>`_ username.
@jodal
Mopidy member
jodal added a note Mar 14, 2013

s/Soundcloud.comLast.fm/SoundCloud/

@dz0ny
Mopidy member
dz0ny added a note Mar 14, 2013

:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal
Mopidy member

I haven't tried running the backend, but I generally like what I see. As mentioned on IRC, tests would be a huge plus.

Great work! :-)

@adamcik adamcik commented on an outdated diff Mar 15, 2013
mopidy/settings.py
@@ -326,3 +326,31 @@
'rtmps',
'rtsp',
)
+
+#: Your `SoundCloud.com <http://www.soundcloud.com/>`_ authentication token.
+#:
+#: Get yours at http://www.mopidy.com/authenticate.html
+#:
+#: Used by :mod:`mopidy.frontends.soundcloud`.
@adamcik
Mopidy member
adamcik added a note Mar 15, 2013

s/frontends/backends/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@adamcik adamcik commented on an outdated diff Mar 15, 2013
mopidy/settings.py
+
+#: Extra playlists from `SoundCloud.com <http://www.soundcloud.com/explore>`
+#:
+#: Note: this might take a while to load. You can add more if you want,
+#: for example:
+#: if you want Smooth Jazz from https://soundcloud.com/explore/jazz%2Bblues
+#: your entry would be u'jazz%2Bblues/Smooth Jazz'
+#:
+#: Default::
+#:
+#: SOUNDCLOUD_EXPLORE = [
+#: u'electronic/Ambient',
+#: u'pop/New Wave',
+#: u'rock/Indie',
+#: ]
+#: Used by :mod:`mopidy.frontends.soundcloud`.
@adamcik
Mopidy member
adamcik added a note Mar 15, 2013

s/frontends/backends/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@adamcik adamcik and 1 other commented on an outdated diff Mar 15, 2013
mopidy/backends/soundcloud/actor.py
+
+from .library import SoundcloudLibraryProvider
+from .playlists import SoundcloudPlaylistsProvider
+from .soundcloud import SoundcloudClient
+
+logger = logging.getLogger('mopidy.backends.soundcloud')
+
+
+class SoundcloudBackend(pykka.ThreadingActor, base.Backend):
+ def __init__(self, audio):
+ super(SoundcloudBackend, self).__init__()
+
+ if not settings.SOUNDCLOUD_AUTHTOKEN:
+ logger.error(("In order to use SoundCloud backend "
+ "you must provide settings.SOUNDCLOUD_AUTHTOKEN. "
+ "Get yours at http://www.mopidy.com/authenticate.html"))
@adamcik
Mopidy member
adamcik added a note Mar 15, 2013

We could also ship the js-page as part of mopidy so the user just opens some file://... url, but lets go with just the mopidy.com on for now.

@dz0ny
Mopidy member
dz0ny added a note Mar 16, 2013

That would'n work, SoundCloud has restrictions in place. First there must be callback.html file present and second callback.html file must have same URI as one set in API settings page on SoundCloud. If those conditions aren't met, authorization request will be denied.

@adamcik
Mopidy member
adamcik added a note Mar 16, 2013

Ah, good to know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Janez Troha added some commits Mar 16, 2013
@jodal jodal commented on an outdated diff Mar 20, 2013
mopidy/backends/soundcloud/__init__.py
@@ -0,0 +1,25 @@
+"""A backend for playing music from Soundcloud.
+
+This backend handles URIs starting with ``soundcloud:``.
+
+See :ref:`music-from-soundcloud-storage` for further instructions on using this
+backend.
+
+**Issues:**
+
+https://github.com/mopidy/mopidy/issues?labels=Soundcloud+backend
@jodal
Mopidy member
jodal added a note Mar 20, 2013

I've created a label now. Please do a s/Soundcloud/SoundCloud/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 20, 2013
docs/modules/backends/soundcloud.rst
@@ -0,0 +1,8 @@
+.. soundcloud-backend:
+
+*************************************************
+:mod:`mopidy.backends.soundcloud` -- SoundCloud backend
+*************************************************
@jodal
Mopidy member
jodal added a note Mar 20, 2013

*** rows should have equal length with the header text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 20, 2013
docs/modules/backends/soundcloud.rst
@@ -0,0 +1,8 @@
+.. soundcloud-backend:
+
+*************************************************
+:mod:`mopidy.backends.soundcloud` -- SoundCloud backend
+*************************************************
+
+.. automodule:: mopidy.backends.soudcloud
@jodal
Mopidy member
jodal added a note Mar 20, 2013

s/soudcloud/soundcloud/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on the diff Mar 20, 2013
mopidy/backends/soundcloud/actor.py
+
+from .library import SoundcloudLibraryProvider
+from .playlists import SoundcloudPlaylistsProvider
+from .soundcloud import SoundCloudClient
+
+logger = logging.getLogger('mopidy.backends.soundcloud')
+
+
+class SoundcloudBackend(pykka.ThreadingActor, base.Backend):
+ def __init__(self, audio):
+ super(SoundcloudBackend, self).__init__()
+
+ if not settings.SOUNDCLOUD_AUTH_TOKEN:
+ logger.error(("In order to use SoundCloud backend "
+ "you must provide settings.SOUNDCLOUD_AUTH_TOKEN. "
+ "Get yours at http://www.mopidy.com/authenticate"))
@jodal
Mopidy member
jodal added a note Mar 20, 2013

What happens if you ignore this error and just try to use the SoundCloud backend? I guess something will crash with an AttributeError on backend.sc_api now being available, which isn't very nice. The backend should shut down properly after logging the error.

@jodal
Mopidy member
jodal added a note Mar 23, 2013

I just ran into this case. Because I got a traceback about sc_api missing, it took focus away from the error message right above, stating what the root cause was.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 20, 2013
mopidy/backends/soundcloud/actor.py
+
+import logging
+import pykka
+
+from mopidy import settings
+from mopidy.backends import base
+
+
+from .library import SoundcloudLibraryProvider
+from .playlists import SoundcloudPlaylistsProvider
+from .soundcloud import SoundCloudClient
+
+logger = logging.getLogger('mopidy.backends.soundcloud')
+
+
+class SoundcloudBackend(pykka.ThreadingActor, base.Backend):
@jodal
Mopidy member
jodal added a note Mar 20, 2013

s/Soundcloud/SoundCloud/

@jodal
Mopidy member
jodal added a note Mar 20, 2013

And same for the rest of the class names. I won't comment on all of them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jodal jodal commented on an outdated diff Mar 20, 2013
mopidy/backends/soundcloud/soundcloud.py
+ # thus prevent user from selecting track
+ if not is_search:
+ if ' - ' in name:
+ name = name.split(' - ')
+ track_kwargs[b'name'] = name[1]
+ artist_kwargs[b'name'] = name[0]
+ elif 'label_name' in data and data['label_name'] != '':
+ track_kwargs[b'name'] = name
+ artist_kwargs[b'name'] = data['label_name']
+ else:
+ track_kwargs[b'name'] = name
+ artist_kwargs[b'name'] = data.get('user').get('username')
+
+ album_kwargs[b'name'] = 'SoundCloud'
+ else:
+ ## NOTE mpdroid removes ☁ from track name, probably others too
@jodal
Mopidy member
jodal added a note Mar 20, 2013

As discussed on IRC, logic/hacks like this should be added by the frontends, based upon the frontends capabilities and probably the URI scheme of the track.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Janez Troha added some commits Mar 23, 2013
Janez Troha Fix: SoundCloud naming d7f093b
Janez Troha Fix: removing // from URI 272c185
Janez Troha Review: resolve uri 7f94568
Janez Troha Merge branch 'develop' of git://github.com/mopidy/mopidy.git into sou…
…ndcloud_backend
3386353
Janez Troha Fix: Double class, super methods 9f4dfb9
Janez Troha Merge branch 'soundcloud/resolve_uri' into soundcloud_backend
Conflicts:
	mopidy/backends/soundcloud/playlists.py
8e50a94
Janez Troha Fix: docs 023a038
Janez Troha Add: More debug information 1ab2740
Janez Troha Fix: Playlist resolving
Rename: Method for fetching displaying user liked tracks
Add: Test for avaliabilty of streamable track
ea17017
Janez Troha Fix: Only add albumart if album name is defined 5dcd5fd
Janez Troha Fix: SoundCloud api unavailable, leaves backend in broken state
Fix: Document issue with playlists for mobile clients
Fix: User is resolved before api exists
df3cf02
Janez Troha Add: Playlists tests f27d2fe
@adamcik adamcik commented on an outdated diff Mar 26, 2013
mopidy/backends/soundcloud/playlists.py
+ super(SoundCloudPlaylistsProvider, self).__init__(*args, **kwargs)
+ self._playlists = []
+ self.refresh()
+
+ def create(self, name):
+ pass # TODO
+
+ def delete(self, uri):
+ pass # TODO
+
+ def lookup_get_tracks(self, uri):
+ # TODO: Figure out why some sort of internal cache is used for retrieving
+ # track-list on mobile clients. If you wan't this to work with mobile
+ # clients change defaults to streamable=True
+ if 'soundcloud:exp-' in uri:
+ logger.info('Detected lookup for explore %s' % uri)
@adamcik
Mopidy member
adamcik added a note Mar 26, 2013

Info logging also of this is likely to be to verbose in the long run, I suspect this should be debug to avoid noise in the logs. For development you should be able to set the log level for this logger to debug in your settings file by importing logging etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@adamcik adamcik commented on an outdated diff Mar 26, 2013
mopidy/backends/soundcloud/playlists.py
+ self.refresh()
+
+ def create(self, name):
+ pass # TODO
+
+ def delete(self, uri):
+ pass # TODO
+
+ def lookup_get_tracks(self, uri):
+ # TODO: Figure out why some sort of internal cache is used for retrieving
+ # track-list on mobile clients. If you wan't this to work with mobile
+ # clients change defaults to streamable=True
+ if 'soundcloud:exp-' in uri:
+ logger.info('Detected lookup for explore %s' % uri)
+ return self.create_explore_playlist(uri, True)
+ elif 'soundcloud:u-liked' in uri:
@adamcik
Mopidy member
adamcik added a note Mar 26, 2013

Why abbreviate, user-liked etc. is much easier to understand and a byte or two lost here won't really matter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@adamcik adamcik commented on an outdated diff Mar 26, 2013
mopidy/backends/soundcloud/soundcloud.py
+ value = self.func(*args)
+ self.cache[args] = (value, now)
+ return value
+
+ except TypeError:
+ return self.func(*args)
+ return _memoized
+
+
+class SoundCloudClient(object):
+
+ CLIENT_ID = '93e33e327fd8a9b77becd179652272e2'
+
+ def __init__(self, token):
+ super(SoundCloudClient, self).__init__()
+ self.SC = requests.Session()
@adamcik
Mopidy member
adamcik added a note Mar 26, 2013

SC in caps doesn't really follow our style guide as caps are normally reserved to constants.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@adamcik adamcik commented on an outdated diff Mar 26, 2013
mopidy/backends/soundcloud/soundcloud.py
+ artist_kwargs[b'name'] = name[0]
+ elif 'label_name' in data and data['label_name'] != '':
+ track_kwargs[b'name'] = name
+ artist_kwargs[b'name'] = data['label_name']
+ else:
+ track_kwargs[b'name'] = name
+ artist_kwargs[b'name'] = data.get('user').get('username')
+
+ album_kwargs[b'name'] = 'SoundCloud'
+
+ if 'date' in data:
+ track_kwargs[b'date'] = data['date']
+
+ if remote_url:
+ logger.info("Adding streamable track %s" % track_kwargs[b'name'])
+ track_kwargs[b'uri'] = '%s?client_id=%s' % (
@adamcik
Mopidy member
adamcik added a note Mar 26, 2013

Based on what I was telling you on IRC, at this point building a the uri as the actual http would cause trouble when running without the stream backend. Idea I'm suggesting as a possible alternative here is to construct something like 'soundcloud:track:%s:%s' % (stream_url, client_id) where stream url is the url with the http:// part stripped.

As for the rest of the urls soundcloud:set:foo etc might also work, loosely inspired by spotify urls, but now I'm wandering very close to bikesheding. Though, there might be some merit to using the same format where you have scheme and then a type and then a specifier using : as a delimiter as it should be among the safer chars to use for such a purpose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Janez Troha added some commits Mar 26, 2013
Janez Troha Add: Custom audio implementation
Fix: Prevent parsing of track if it isn't avaliable
a5b864b
Janez Troha Fix: Rename variable SC to http_client
Remove: Debug information for playlist, client
9aed118
Janez Troha Merge branch 'develop' of git://github.com/mopidy/mopidy.git into sou…
…ndcloud_backend
14286bf
@dz0ny dz0ny closed this Apr 1, 2013
@dz0ny dz0ny deleted the unknown repository branch Apr 1, 2013
@adamcik
Mopidy member

Postponed while we fix extension support I hope?

@dz0ny
Mopidy member

yes :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment