diff --git a/docs/api/http.rst b/docs/api/http.rst new file mode 100644 index 00000000..16546683 --- /dev/null +++ b/docs/api/http.rst @@ -0,0 +1,439 @@ +.. _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 ` 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 +`_. + +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 + + + +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 +`_, 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 +`_ 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: + +.. 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 + + + +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. + diff --git a/docs/api/index.rst b/docs/api/index.rst index 5a210812..6ba44999 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -11,3 +11,4 @@ API reference core audio frontends + http diff --git a/docs/ext/http.rst b/docs/ext/http.rst new file mode 100644 index 00000000..5c7ba79b --- /dev/null +++ b/docs/ext/http.rst @@ -0,0 +1,101 @@ +.. _ext-http: + +*********** +Mopidy-HTTP +*********** + +The HTTP extension lets you control Mopidy through HTTP and WebSockets, e.g. +from a web based client. See :ref:`http-api` for details on how to integrate +with Mopidy over HTTP. + + +Known issues +============ + +https://github.com/mopidy/mopidy/issues?labels=HTTP+frontend + + +Dependencies +============ + +.. literalinclude:: ../../requirements/http.txt + + +Configuration values +==================== + +.. confval:: http/enabled + + If the HTTP extension should be enabled or not. + +.. confval:: http/hostname + + Which address the HTTP server should bind to. + + ``127.0.0.1`` + Listens only on the IPv4 loopback interface + ``::1`` + Listens only on the IPv6 loopback interface + ``0.0.0.0`` + Listens on all IPv4 interfaces + ``::`` + Listens on all interfaces, both IPv4 and IPv6 + +.. confval:: http/port + + Which TCP port the HTTP server should listen to. + +.. confval:: http/static_dir + + Which directory the HTTP server should serve at "/" + + Change this to have Mopidy serve e.g. files for your JavaScript client. + "/mopidy" will continue to work as usual even if you change this setting. + + +Default configuration +===================== + +.. literalinclude:: ../../mopidy/frontends/http/ext.conf + :language: ini + + +Setup +===== + +The extension is enabled by default if all dependencies are available. + +When it is enabled it starts a web server at the port specified by the +:confval:`http/port` config value. + +.. warning:: Security + + As a simple security measure, the web server is by default only available + from localhost. To make it available from other computers, change the + :confval:`http/hostname` config value. Before you do so, note that the HTTP + extension does not feature any form of user authentication or + authorization. Anyone able to access the web server can use the full core + API of Mopidy. Thus, you probably only want to make the web server + available from your local network or place it behind a web proxy which + takes care or user authentication. You have been warned. + + +Using a web based Mopidy client +=============================== + +The web server can also host any static files, for example the HTML, CSS, +JavaScript, and images needed for a web based Mopidy client. To host static +files, change the ``http/static_dir`` to point to the root directory of your +web client, e.g.:: + + [http] + static_dir = /home/alice/dev/the-client + +If the directory includes a file named ``index.html``, it will be served on the +root of Mopidy's web server. + +If you're making a web based client and wants to do server side development as +well, you are of course free to run your own web server and just use Mopidy's +web server for the APIs. But, for clients implemented purely in JavaScript, +letting Mopidy host the files is a simpler solution. + diff --git a/docs/modules/frontends/http.rst b/docs/modules/frontends/http.rst deleted file mode 100644 index 31366bd1..00000000 --- a/docs/modules/frontends/http.rst +++ /dev/null @@ -1,8 +0,0 @@ -.. _http-frontend: - -********************************************* -:mod:`mopidy.frontends.http` -- HTTP frontend -********************************************* - -.. automodule:: mopidy.frontends.http - :synopsis: HTTP and WebSockets frontend diff --git a/mopidy/frontends/http/__init__.py b/mopidy/frontends/http/__init__.py index 32d55f23..99399e11 100644 --- a/mopidy/frontends/http/__init__.py +++ b/mopidy/frontends/http/__init__.py @@ -1,533 +1,10 @@ from __future__ import unicode_literals +import os + import mopidy from mopidy import exceptions, ext -from mopidy.utils import config, formatting - - -default_config = """ -[http] -enabled = true -hostname = 127.0.0.1 -port = 6680 -static_dir = - -[logging.levels] -cherrypy = warning -""" - -__doc__ = """ -The HTTP frontends lets you control Mopidy through HTTP and WebSockets, e.g. -from a web based client. - -**Issues** - -https://github.com/mopidy/mopidy/issues?labels=HTTP+frontend - -**Dependencies** - -.. literalinclude:: ../../../requirements/http.txt - -**Configuration** - -.. confval:: http/enabled - - If the HTTP extension should be enabled or not. - -.. confval:: http/hostname - - Which address the HTTP server should bind to. - - ``127.0.0.1`` - Listens only on the IPv4 loopback interface - ``::1`` - Listens only on the IPv6 loopback interface - ``0.0.0.0`` - Listens on all IPv4 interfaces - ``::`` - Listens on all interfaces, both IPv4 and IPv6 - -.. confval:: http/port - - Which TCP port the HTTP server should listen to. - -.. confval:: http/static_dir - - Which directory the HTTP server should serve at "/" - - Change this to have Mopidy serve e.g. files for your JavaScript client. - "/mopidy" will continue to work as usual even if you change this setting. - -**Default config** - -.. code-block:: ini - -%(config)s - - -Setup -===== - -The frontend is enabled by default if all dependencies are available. - -When it is enabled it starts a web server at the port specified by the -:confval:`http/port` config value. - -.. warning:: Security - - As a simple security measure, the web server is by default only available - from localhost. To make it available from other computers, change the - :confval:`http/hostname` config value. Before you do so, note that the HTTP - frontend does not feature any form of user authentication or authorization. - Anyone able to access the web server can use the full core API of Mopidy. - Thus, you probably only want to make the web server available from your - local network or place it behind a web proxy which takes care or user - authentication. You have been warned. - - -Using a web based Mopidy client -=============================== - -The web server can also host any static files, for example the HTML, CSS, -JavaScript, and images needed for a web based Mopidy client. To host static -files, change the ``http/static_dir`` to point to the root directory of your -web client, e.g.:: - - [http] - static_dir = /home/alice/dev/the-client - -If the directory includes a file named ``index.html``, it will be served on the -root of Mopidy's web server. - -If you're making a web based client and wants to do server side development as -well, you are of course free to run your own web server and just use Mopidy's -web server for the APIs. But, for clients implemented purely in JavaScript, -letting Mopidy host the files is a simpler solution. - - -WebSocket API -============= - -.. warning:: API stability - - Since this frontend 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. - -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 -`_. - -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 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 frontend is running, 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 - - - -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 -`_, 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 -`_ 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: - -.. 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 the HTTP - frontend. - -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 - - - -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. -""" % {'config': formatting.indent(default_config)} +from mopidy.utils import config class Extension(ext.Extension): @@ -537,7 +14,8 @@ class Extension(ext.Extension): version = mopidy.__version__ def get_default_config(self): - return default_config + conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf') + return open(conf_file).read() def get_config_schema(self): schema = config.ExtensionConfigSchema() diff --git a/mopidy/frontends/http/ext.conf b/mopidy/frontends/http/ext.conf new file mode 100644 index 00000000..8f8f2a1e --- /dev/null +++ b/mopidy/frontends/http/ext.conf @@ -0,0 +1,8 @@ +[http] +enabled = true +hostname = 127.0.0.1 +port = 6680 +static_dir = + +[logging.levels] +cherrypy = warning