.. _http-api:

********
HTTP API
********

The :ref:`ext-http` extension makes Mopidy's :ref:`core-api` available over
HTTP using WebSockets. We also provide a JavaScript wrapper, called
:ref:`Mopidy.js <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:

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 :class:`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
<http://www.jsonrpc.org/specification>`_.

All methods (not attributes) in the :ref:`core-api` is made available through
JSON-RPC calls over the WebSocket. For example,
:meth:`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 :attr:`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:

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:

- http://localhost:6680/mopidy/mopidy.js
- http://localhost:6680/mopidy/mopidy.min.js

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:

.. code-block:: html

    <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/frontends/http/data/mopidy.js``
- ``mopidy/frontends/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()``:

.. code-block:: js

    var Mopidy = require("mopidy").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:

.. code-block:: js

    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:

.. code-block:: js

    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:

.. code-block:: js

    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:

.. code-block:: js

    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
<https://github.com/busterjs/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:

.. code-block:: js

    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 :ref:`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:

.. code-block:: js

    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.

.. code-block:: js

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

Instead, typical usage will look like this:

.. code-block:: js

    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:

.. code-block:: js

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

The promise objects returned by Mopidy.js adheres to the `CommonJS Promises/A
<http://wiki.commonjs.org/wiki/Promises/A>`_ standard. We use the
implementation known as `when.js <https://github.com/cujojs/when>`_. 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:

.. code-block:: js

    // 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
   :ref:`ext-http`.

2. Create an empty directory for your web client.

3. Change the :confval:`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:

   .. code-block:: html

       <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:

   .. code-block:: js

        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.

   .. code-block:: js

        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.

