We’ve made a JavaScript library, Mopidy.js, which wraps the WebSocket API and gets you quickly started with working on your client instead of figuring out how to communicate with Mopidy.
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:
If you want to use Mopidy.js from Node.js or on the web through Browserify, you can install Mopidy.js using npm:
npm install mopidy
After npm completes, you can import Mopidy.js using require():
var Mopidy = require("mopidy");
If you want to work on the Mopidy.js library itself, you’ll find the source code and a complete development setup in the Mopidy.js Git repo. The instructions in README.md will guide you on your way.
Once you have 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();
When creating an instance, you can specify the following settings:
Which calling convention to use when calling methods.
If set to “by-position-only”, methods expect to be called with positional arguments, like mopidy.foo.bar(null, true, 2).
If set to “by-position-or-by-name”, methods expect to be called either with an array of position arguments, like mopidy.foo.bar([null, true, 2]), or with an object of named arguments, like mopidy.foo.bar({id: 2}). The advantage of the “by-position-or-by-name” calling convention is that arguments with default values can be left out of the named argument object. Using named arguments also makes the code more readable, and more resistent to future API changes.
Note
For backwards compatibility, the default is “by-position-only”. In the future, the default will change to “by-position-or-by-name”. You should explicitly set this setting to your choice, so you won’t be affected when the default changes.
New in version 0.19: (Mopidy.js 0.4)
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:
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.
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 a Mopidy.ConnectionError instance.
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. For example, the mopidy.core.PlaybackController.state attribute is available in JSON-RPC as the method core.playback.get_state and in Mopidy.js as mopidy.playback.getState().
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()
.done(printCurrentTrack);
The function passed to done(), printCurrentTrack, is the callback that will be called if the method call succeeds. If anything goes wrong, done() will throw an exception.
If you want to explicitly handle any errors and avoid an exception being thrown, you can register an error handler function anywhere in a promise chain. The function will be called with the error object as the only argument:
mopidy.playback.getCurrentTrack()
.catch(console.error.bind(console));
.done(printCurrentTrack);
You can also register the error handler at the end of the promise chain by passing it as the second argument to done():
mopidy.playback.getCurrentTrack()
.done(printCurrentTrack, console.error.bind(console));
If you don’t hook up an error handler function and never call done() on the promise object, when.js will log warnings to the console that you have unhandled errors. In general, unhandled errors will not go silently missing.
The promise objects returned by Mopidy.js adheres to the CommonJS Promises/A standard. We use the implementation known as when.js, and reexport it as Mopidy.when so you don’t have to duplicate the dependency. Please refer to when.js’ documentation or the standard for further details on how to work with promise objects.
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;
Make sure that you’ve installed all dependencies required by Mopidy-HTTP.
Create an empty directory for your web client.
Change the http/static_dir config value to point to your new directory.
Start/restart Mopidy.
Create a file in the directory named index.html containing e.g. “Hello, world!”.
Visit http://localhost:6680/ to confirm that you can view your new HTML file there.
Include Mopidy.js in your web page:
<script type="text/javascript" src="/mopidy/mopidy.min.js"></script>
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 trackDesc = function (track) {
return track.name + " by " + track.artists[0].name +
" from " + track.album.name;
};
var queueAndPlay = function (playlistNum, trackNum) {
playlistNum = playlistNum || 0;
trackNum = trackNum || 0;
mopidy.playlists.getPlaylists().then(function (playlists) {
var playlist = playlists[playlistNum];
console.log("Loading playlist:", playlist.name);
return mopidy.tracklist.add(playlist.tracks).then(function (tlTracks) {
return mopidy.playback.play(tlTracks[trackNum]).then(function () {
return mopidy.playback.getCurrentTrack().then(function (track) {
console.log("Now playing:", trackDesc(track));
});
});
});
})
.catch(console.error.bind(console)) // Handle errors here
.done(); // ...or they'll be thrown here
};
var mopidy = new Mopidy(); // Connect to server
mopidy.on(console.log.bind(console)); // Log all events
mopidy.on("state:online", queueAndPlay);
Approximately the same behavior in a more functional style, using chaining of promises.
var get = function (key, object) {
return object[key];
};
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 queueAndPlay = function (playlistNum, trackNum) {
playlistNum = playlistNum || 0;
trackNum = trackNum || 0;
mopidy.playlists.getPlaylists()
// => list of Playlists
.fold(get, playlistNum)
// => Playlist
.then(printTypeAndName)
// => Playlist
.fold(get, 'tracks')
// => list of Tracks
.then(mopidy.tracklist.add)
// => list of TlTracks
.fold(get, trackNum)
// => TlTrack
.then(mopidy.playback.play)
// => null
.then(printNowPlaying)
// => null
.catch(console.error.bind(console)) // Handle errors here
// => null
.done(); // ...or they'll be thrown here
};
var mopidy = new Mopidy(); // Connect to server
mopidy.on(console.log.bind(console)); // Log all events
mopidy.on("state:online", queueAndPlay);
The web page should now queue and play your first playlist every time you load it. See the browser’s console for output from the function, any errors, and all events that are emitted.