[Extension development] How to play an album?

I would like to play an album from my mopidy frontend extension.

So far it was easy as I just called the external mpc command:

def playAlbum(uri):
    doMPC('clear')
    doMPC('add', uri)
    doMPC('play')

playAlbum('spotify:playlist:37i9dQZF1DXbMYUPb05hjJ')

How should this be done the right way as an extension?

Thanks
bluepuma

There is a lot of great documentation on mopidy. Sometimes I just miss some easy snippets on how to do stuff in practice.

[rant on] Bad example, but how do I log? It states logger = logging.getLogger(__name__) in the docs multiple times, but the obvious log('An Exception occured', e) is missing in the docs. Python experts might know, but I think the extra line would make life easier for newcomers. [rant off]

Anyway, through our previous discussions I was just blinded, thought I need to look up playlists and add only single tracks. Playing a Spotify album and playlist is as easy as it gets:

self.core.tracklist.clear()
self.core.tracklist.add(uris=['spotify:playlist:xyz'])
self.core.playback.play()

True, we are missing a basic example of actually logging in the docs. You’d normally want to use the namespaced logger instance e.g. logging.error('some error happened') or logging.debug('some debug info'). We have lots of examples in our codebase of usage. The source code is arguably a large part of the project documentation, we can’t cover everything in the docs pages.

Also:

Keep in mind that the normal core and frontend methods will usually return pykka.ThreadingFuture objects, so you will need to add .get() at the end of most method calls in order to get to the actual return values.

Another good example to read through is the mpd frontend e.g. https://github.com/mopidy/mopidy-mpd/blob/master/mopidy_mpd/protocol/current_playlist.py

For reference, here is example code how to play a folder with MP3s:

uri = 'file:/home/pi/music/folder/'

self.core.tracklist.clear()
uris = []
for ref in self.core.library.browse(uri).get():
    uris.append(ref.uri)
self.core.tracklist.add(uris=uris)
self.core.playback.play()

It seems the “Files/” prefix from mpc can not be used, instead you need the full path.

Thanks, I took some inspiration from current_playlist.py for add and dispatcher.py for browse.

I came up with the following minimal code to play folders and Spotify:

def playAlbum(uri):
    logger.debug('playAlbum URI: ' + uri)                       

    self.core.tracklist.clear()

    # for mpc compatibility
    if uri.startswith('Files/'):
        uri = uri.replace('Files/', 'file:/home/pi/music/')

    # if uri is a directory, browse and add tracks    
    if os.path.isdir(uri.replace('file://','').replace('file:','')):                        
        logger.debug('Using browse to add directory: ' + uri)   
        for ref in self.core.library.browse(uri).get():
            self.core.tracklist.add(uris=[ref.uri])
    else:
        logger.debug('Add URI directly: ' + uri)                       
        self.core.tracklist.add(uris=[uri])      

    self.core.playback.play()

Then you can call it like this:

playAlbum('Files/folder')
playAlbum('file:/home/pi/music/folder')
playAlbum('spotify:album:a1b2c3')
playAlbum('spotify:playlist:1a2b3c')

Overall I think it would be great if the mopidy file interface could handle folders automagically :star_struck:

# for mpc compatibility
    if uri.startswith('Files/'):
        uri = uri.replace('Files/', 'file:/home/pi/music/')

To be clear, ‘Files’ and ‘Files/foo’ are names and not URIs. A Mopidy URI and it’s form is explained here. It’s a bit confusing if you are coming from using MPD where the difference is hidden from you; where MPD uses names there is usually a mapping to the URI occurring at some point. I wouldn’t try to implement the mapping with string replacements:

playAlbum('Files/MyFiles/TheseFiles/folder')
playAlbum('Files/file:/folder')

And what about nested folders? Looks like they are skipped (after failing).

playAlbum('Files/folder/folder2')

There’s an argument for handling directory lookups in Mopidy-File backend but I think it’d be quite slow, maybe try implementing the improvement and see what it’s like to use? You’d want to call browse internally anyway, to filter out all the non-track stuff and make it less slow. And would you always want to be recursive? Requiring the usage of browse instead, to explicitly first gather the tracks to add, is more work but maybe that simpler interface is better.

I’d rather see LibraryController.browse handle track URIs better, it currently causes an exception (a bug).