HTTP API

The Mopidy-HTTP extension makes Mopidy’s Core API available over HTTP using WebSockets. We also provide a JavaScript wrapper, called Mopidy.js around the HTTP API for use both from browsers and Node.js.

Warning

API stability

Since the HTTP API exposes our internal core API directly it is to be regarded as experimental. We cannot promise to keep any form of backwards compatibility between releases as we will need to change the core API while working out how to support new use cases. Thus, if you use this API, you must expect to do small adjustments to your client for every release of Mopidy.

From Mopidy 1.0 and onwards, we intend to keep the core API far more stable.

WebSocket API

The web server exposes a WebSocket at /mopidy/ws/. The WebSocket gives you access to Mopidy’s full API and enables Mopidy to instantly push events to the client, as they happen.

On the WebSocket we send two different kind of messages: The client can send JSON-RPC 2.0 requests, and the server will respond with JSON-RPC 2.0 responses. In addition, the server will send event messages when something happens on the server. Both message types are encoded as JSON objects.

Event messages

Event objects will always have a key named event whose value is the event type. Depending on the event type, the event may include additional fields for related data. The events maps directly to the mopidy.core.CoreListener API. Refer to the CoreListener method names is the available event types. The CoreListener method’s keyword arguments are all included as extra fields on the event objects. Example event message:

{"event": "track_playback_started", "track": {...}}

JSON-RPC 2.0 messaging

JSON-RPC 2.0 messages can be recognized by checking for the key named jsonrpc with the string value 2.0. For details on the messaging format, please refer to the JSON-RPC 2.0 spec.

All methods (not attributes) in the Core API is made available through JSON-RPC calls over the WebSocket. For example, mopidy.core.PlaybackController.play() is available as the JSON-RPC method core.playback.play.

The core API’s attributes is made available through setters and getters. For example, the attribute mopidy.core.PlaybackController.current_track is available as the JSON-RPC method core.playback.get_current_track.

Example JSON-RPC request:

{"jsonrpc": "2.0", "id": 1, "method": "core.playback.get_current_track"}

Example JSON-RPC response:

{"jsonrpc": "2.0", "id": 1, "result": {"__model__": "Track", "...": "..."}}

The JSON-RPC method core.describe returns a data structure describing all available methods. If you’re unsure how the core API maps to JSON-RPC, having a look at the core.describe response can be helpful.

Mopidy.js JavaScript library

We’ve made a JavaScript library, Mopidy.js, which wraps the WebSocket and gets you quickly started with working on your client instead of figuring out how to communicate with Mopidy.

Getting the library for browser use

Regular and minified versions of Mopidy.js, ready for use, is installed together with Mopidy. When the HTTP extension is enabled, the files are available at:

You may need to adjust hostname and port for your local setup.

Thus, if you use Mopidy to host your web client, like described above, you can load the latest version of Mopidy.js by adding the following script tag to your HTML file:

<script type="text/javascript" src="/mopidy/mopidy.min.js"></script>

If you don’t use Mopidy to host your web client, you can find the JS files in the Git repo at:

  • mopidy/http/data/mopidy.js
  • mopidy/http/data/mopidy.min.js

Getting the library for Node.js use

If you want to use Mopidy.js from Node.js instead of a browser, you can install Mopidy.js using npm:

npm install mopidy

After npm completes, you can import Mopidy.js using require():

var Mopidy = require("mopidy");

Getting the library for development on the library

If you want to work on the Mopidy.js library itself, you’ll find a complete development setup in the js/ dir in our repo. The instructions in js/README.md will guide you on your way.

Creating an instance

Once you got Mopidy.js loaded, you need to create an instance of the wrapper:

var mopidy = new Mopidy();

When you instantiate Mopidy() without arguments, it will connect to the WebSocket at /mopidy/ws/ on the current host. Thus, if you don’t host your web client using Mopidy’s web server, or if you use Mopidy.js from a Node.js environment, you’ll need to pass the URL to the WebSocket end point:

var mopidy = new Mopidy({
    webSocketUrl: "ws://localhost:6680/mopidy/ws/"
});

It is also possible to create an instance first and connect to the WebSocket later:

var mopidy = new Mopidy({autoConnect: false});
// ... do other stuff, like hooking up events ...
mopidy.connect();

Hooking up to events

Once you have a Mopidy.js object, you can hook up to the events it emits. To explore your possibilities, it can be useful to subscribe to all events and log them:

mopidy.on(console.log.bind(console));

Several types of events are emitted:

  • You can get notified about when the Mopidy.js object is connected to the server and ready for method calls, when it’s offline, and when it’s trying to reconnect to the server by looking at the events state:online, state:offline, reconnectionPending, and reconnecting.
  • You can get events sent from the Mopidy server by looking at the events with the name prefix event:, like event:trackPlaybackStarted.
  • You can introspect what happens internally on the WebSocket by looking at the events emitted with the name prefix websocket:.

Mopidy.js uses the event emitter library BANE, so you should refer to BANE’s short API documentation to see how you can hook up your listeners to the different events.

Calling core API methods

Once your Mopidy.js object has connected to the Mopidy server and emits the state:online event, it is ready to accept core API method calls:

mopidy.on("state:online", function () {
    mopidy.playback.next();
});

Any calls you make before the state:online event is emitted will fail. If you’ve hooked up an errback (more on that a bit later) to the promise returned from the call, the errback will be called with an error message.

All methods in Mopidy’s Core API is available via Mopidy.js. The core API attributes is not available, but that shouldn’t be a problem as we’ve added (undocumented) getters and setters for all of them, so you can access the attributes as well from JavaScript.

Both the WebSocket API and the JavaScript API are based on introspection of the core Python API. Thus, they will always be up to date and immediately reflect any changes we do to the core API.

The best way to explore the JavaScript API, is probably by opening your browser’s console, and using its tab completion to navigate the API. You’ll find the Mopidy core API exposed under mopidy.playback, mopidy.tracklist, mopidy.playlists, and mopidy.library.

All methods in the JavaScript API have an associated data structure describing the Python params it expects, and most methods also have the Python API documentation available. This is available right there in the browser console, by looking at the method’s description and params attributes:

console.log(mopidy.playback.next.params);
console.log(mopidy.playback.next.description);

JSON-RPC 2.0 limits method parameters to be sent either by-position or by-name. Combinations of both, like we’re used to from Python, isn’t supported by JSON-RPC 2.0. To further limit this, Mopidy.js currently only supports passing parameters by-position.

Obviously, you’ll want to get a return value from many of your method calls. Since everything is happening across the WebSocket and maybe even across the network, you’ll get the results asynchronously. Instead of having to pass callbacks and errbacks to every method you call, the methods return “promise” objects, which you can use to pipe the future result as input to another method, or to hook up callback and errback functions.

var track = mopidy.playback.getCurrentTrack();
// => ``track`` isn't a track, but a "promise" object

Instead, typical usage will look like this:

var printCurrentTrack = function (track) {
    if (track) {
        console.log("Currently playing:", track.name, "by",
            track.artists[0].name, "from", track.album.name);
    } else {
        console.log("No current track");
    }
};

mopidy.playback.getCurrentTrack().then(
    printCurrentTrack, console.error.bind(console));

The first function passed to then(), printCurrentTrack, is the callback that will be called if the method call succeeds. The second function, console.error, is the errback that will be called if anything goes wrong. If you don’t hook up an errback, debugging will be hard as errors will silently go missing.

For debugging, you may be interested in errors from function without interesting return values as well. In that case, you can pass null as the callback:

mopidy.playback.next().then(null, console.error.bind(console));

The promise objects returned by Mopidy.js adheres to the CommonJS Promises/A standard. We use the implementation known as when.js. Please refer to when.js’ documentation or the standard for further details on how to work with promise objects.

Cleaning up

If you for some reason want to clean up after Mopidy.js before the web page is closed or navigated away from, you can close the WebSocket, unregister all event listeners, and delete the object like this:

// Close the WebSocket without reconnecting. Letting the object be garbage
// collected will have the same effect, so this isn't strictly necessary.
mopidy.close();

// Unregister all event listeners. If you don't do this, you may have
// lingering references to the object causing the garbage collector to not
// clean up after it.
mopidy.off();

// Delete your reference to the object, so it can be garbage collected.
mopidy = null;

Example to get started with

  1. Make sure that you’ve installed all dependencies required by Mopidy-HTTP.

  2. Create an empty directory for your web client.

  3. Change the http/static_dir config value to point to your new directory.

  4. Start/restart Mopidy.

  5. Create a file in the directory named index.html containing e.g. “Hello, world!”.

  6. Visit http://localhost:6680/ to confirm that you can view your new HTML file there.

  7. Include Mopidy.js in your web page:

    <script type="text/javascript" src="/mopidy/mopidy.min.js"></script>
    
  8. Add one of the following Mopidy.js examples of how to queue and start playback of your first playlist either to your web page or a JavaScript file that you include in your web page.

    “Imperative” style:

    var consoleError = console.error.bind(console);
    
    var trackDesc = function (track) {
        return track.name + " by " + track.artists[0].name +
            " from " + track.album.name;
    };
    
    var queueAndPlayFirstPlaylist = function () {
        mopidy.playlists.getPlaylists().then(function (playlists) {
            var playlist = playlists[0];
            console.log("Loading playlist:", playlist.name);
            mopidy.tracklist.add(playlist.tracks).then(function (tlTracks) {
                mopidy.playback.play(tlTracks[0]).then(function () {
                    mopidy.playback.getCurrentTrack().then(function (track) {
                        console.log("Now playing:", trackDesc(track));
                    }, consoleError);
                }, consoleError);
            }, consoleError);
        }, consoleError);
    };
    
    var mopidy = new Mopidy();             // Connect to server
    mopidy.on(console.log.bind(console));  // Log all events
    mopidy.on("state:online", queueAndPlayFirstPlaylist);
    

    Approximately the same behavior in a more functional style, using chaining of promisies.

    var consoleError = console.error.bind(console);
    
    var getFirst = function (list) {
        return list[0];
    };
    
    var extractTracks = function (playlist) {
        return playlist.tracks;
    };
    
    var printTypeAndName = function (model) {
        console.log(model.__model__ + ": " + model.name);
        // By returning the playlist, this function can be inserted
        // anywhere a model with a name is piped in the chain.
        return model;
    };
    
    var trackDesc = function (track) {
        return track.name + " by " + track.artists[0].name +
            " from " + track.album.name;
    };
    
    var printNowPlaying = function () {
        // By returning any arguments we get, the function can be inserted
        // anywhere in the chain.
        var args = arguments;
        return mopidy.playback.getCurrentTrack().then(function (track) {
            console.log("Now playing:", trackDesc(track));
            return args;
        });
    };
    
    var queueAndPlayFirstPlaylist = function () {
        mopidy.playlists.getPlaylists()
            // => list of Playlists
            .then(getFirst, consoleError)
            // => Playlist
            .then(printTypeAndName, consoleError)
            // => Playlist
            .then(extractTracks, consoleError)
            // => list of Tracks
            .then(mopidy.tracklist.add, consoleError)
            // => list of TlTracks
            .then(getFirst, consoleError)
            // => TlTrack
            .then(mopidy.playback.play, consoleError)
            // => null
            .then(printNowPlaying, consoleError);
    };
    
    var mopidy = new Mopidy();             // Connect to server
    mopidy.on(console.log.bind(console));  // Log all events
    mopidy.on("state:online", queueAndPlayFirstPlaylist);
    
  9. The web page should now queue and play your first playlist every time your load it. See the browser’s console for output from the function, any errors, and all events that are emitted.