mip

mipod

A node module for exposing an MPD API, either as REST service, Websocket or as a library to include. Written in Typescript, executed in Javascript.

Showing:

Popularity

Downloads/wk

4

GitHub Stars

15

Maintenance

Last Commit

6yrs ago

Contributors

0

Package

Dependencies

5

Size (min+gzip)

16.5KB

License

MIT

Type Definitions

Tree-Shakeable

No?

Categories

Readme

mipod

A nodejs module for exposing a REST API and Websocket for the Music Player Daemon (MPD). Written in Typescript, it generates ready-to-use Javascript.

It can be used either as a nodejs dependency module or as a stand-alone server.

Mipod provides a mapping for most of MPD commands (play, pause, add, current and much more). It also provides more advanced library management, you can for instance browse the musics files folder by folder or get all in a go. There are also some non-MPD features such as songs rating.

Installation

You can either grab the sources from github:

Note that it contains both Typescript sources and generated Javascript

or you can install through NPM:

  • npm install mipod

Note that you wouldn't get Typescript sources this way

Usage

Stand-alone REST server

As a stand-alone server, you only need the Javascript files, not Typescript. Run mipod-rest.js with node:

node mipod-rest.js

That's all you need to start the server with default parameters. It will listen to requests on port 80 and connects to a MPD server on localhost:6600. Try out http://localhost/mipod/play or http://localhost/mipod/pause, if you have an MPD server running, you should hear immediate results.

Command-line options are:

  • -p=$X, --port=$X setup server port (default 80)
  • --prefix=$path setup root for REST requests (default empty)
  • --mpdHost=$host MPD server hostname (default localhost)
  • --mpdPort=$X MPD server port (default 6600)
  • --dataPath=$path local path where data files will be stored
  • --dontUseLibCache deactivate MPD caching (will be slower, but saves memory - see dedicated section below for more information).
  • --loadLibOnStartup load the whole library from MPD on startup and refresh its cache.
  • -h, --help help

Stand-alone Websocket server

It's very similar to the REST server. Run:

node mipod-ws.js

And the server will listen on default port (80) for websocket events. Options are the same than for the REST server, only --prefix will differ a bit since it won't prefix the REST resource obviously, but the websocket word event.

Node module inclusion

Since mipod is written in Typescript, you may want to benefit from this and import it in your own typescript code:

    import mipod = require('mipod');

or do the equivalent in Javascript:

    var mipod = require('mipod');

Then, once again, you can either run it as a REST server or as a websocket server.

  • For REST, register routes by calling:

        mipod.asRest(app, opts);
    

    Where app is your own express application and opts is a set of options equivalent to the command-line arguments described above:

    • dataPath: string
    • useLibCache: boolean
    • prefix: string
    • loadLibOnStartup: boolean
    • mpdHost: string
    • mpdPort: number
  • For websocket:

        mipod.asWebSocket(socket, opts);
    

    Where socket is a Socket object from ''socket.io''. opts is the same than above.

Commands

CommandRESTWebsocketDescription
PlayGET /playplayEnter "play" mode
Play pathPOST {entry: String} /playplay-entry {entry: String}Play given file (song or playlist), replacing the current playlist
Play indexGET /playidx/:idxplay-idx {idx: Number}Play song from playlist at given index
AddPOST {entry: String} /mipod/addadd {entry: String}Add given file (song or playlist) to current playlist
ClearGET /clearclearClear current playlist
PauseGET /pausepausePause current song
StopGET /stopstopStop current song
NextGET /nextnextNext song in playlist
PrevGET /prevprevPrevious song in playlist
VolumeGET /volume/:valuevolume {value: Number}Set the volume (from 0 to 100)
RepeatGET /repeat/:enabledrepeat {enabled: Boolean}Enable or disable repeat mode (expect 0/1)
RandomGET /random/:enabledrandom {enabled: Boolean}Enable or disable random mode (expect 0/1)
SingleGET /single/:enabledsingle {enabled: Boolean}Enable or disable single mode (expect 0/1)
ConsumeGET /consume/:enabledconsume {enabled: Boolean}Enable or disable consume mode (expect 0/1)
SeekGET /seek/:songIdx/:posInSongseek {songIdx: Number, posInSong: Number}Seek song position
Remove from queueGET /rmqueue/:songIdxrmqueue {songIdx: Number}Remove song from its index in current playlist
Delete playlistGET /deletelist/:namedeletelist {name: String}Delete saved playlist
Save playlistGET /savelist/:namesavelist {name: String}Save current playlist with given file name
Play allPOST {entries: [String]} /playallplayall {entries: [String]}Play all songs / playlists from json (replaces current playlist)
Add allPOST {entries: [String]} /addalladdal {entries: [String]}Add all songs / playlists from json to current playlist
UpdatePOST {path: String} /updateupdate {path: String}Update MPD database on given path (empty path = whole db). If a library cache is used, it's cleared.
CurrentGET /currentcurrentGet current song info being played
StatusGET /statusstatusGet current status (various information, such as active flags, current song id, bitrate, etc.)
IdleGET /idleidleWait until an event occurs
Notifyn/anotifyGet status information right now (same as "status"), and get notified each time something changed. It's a combination of "idle" and "status" run in loop.
Playlist infoGET /playlistInfoplaylistInfo (n/a yet!)Get current playlist content
Playlist item infoGET /playlistInfo/:idxplaylistItemInfo {idx: Number} (n/a yet!)Get information on a specific item in current playlist
CustomPOST {command: String, stopper: Maybe String, parser: Maybe String} /customcustom {command: String, stopper: Maybe String, parser: Maybe String}Run a custom MPD command. Using a stopper allows to read more data from the socket until the stopper pattern is found (usual stopper for MPD is \nOK\n). Parser can be used to format response in json. 2 parsers are implemented: "entries" for parsing MPD entries (files, directories, playlists) and "status" for status-like information.
Load lib onceGET /lib-loadoncelib-loadonceTrigger library scan, which will put result in cache and available for "get" calls
Reload libGET /lib-reloadlib-reloadForce rescanning the library (clears cache)
Loading progressGET /lib-progresslib-progressGet progress information on library loading. This call will returns a number in range [0, number of songs]
Get libraryPOST {treeDesc: Maybe [String], leafDesc: Maybe [String]} /lib-get/:start/:countlib-get {start: Number, count: Number, treeDesc: Maybe [String], leafDesc: Maybe [String]}Get a map of currently loaded songs, using paginating info provided
  • Returned json is {"status":(status code as String),"finished":(boolean, false if there's still items to pick up),"next":(number, the next item id to pick up),"data":(a map representing data as requested)}
  • "start" is the start item id of requested page. Note that you should use the "next" returned number as subsequent "start" call
  • "count" is the number of items you try to get. Note that you may receive less than "count" items when MPD scanning is still ongoing or if you've reached the total number of items
  • "treeDesc" is the tree descriptor (see dedicated section below). If unset, ["genre","albumArtist|artist","album"] will be used
  • "leafDesc" is the leaf descriptor (see dedicated section below). If unset, all available data will be returned (which may affect performance on large libraries)
Lib push moden/alib-push {maxBatchSize: Number, treeDesc: Maybe [String], leafDesc: Maybe [String]}Configure push mode (Websocket only), and receive pushed data (similar to "lib-get") as soon as "loadonce" or "reload" is emitted.
Loading finishedn/alib-finished-loadingThis websocket event is fired as soon as the library has completely finished to load (one-way event, server to client).
List directoryPOST {path: String, leafDesc: Maybe [String]} /lsinfolsinfo {token: Maybe Number, path: String, leafDesc: Maybe [String]}An equivalent method of "lib-get" that returns only a flat reprensentation of a given path. If a token was provided, it's returned in response event (Websocket only).
SearchPOST {search: String, leafDesc: Maybe [String]} /search/:modesearch {token: Maybe Number, mode: String, search: String, leafDesc: Maybe [String]}Search for an MPD entry matching posted given string. "mode" can be any type od data recognized by MPD (check MPD documentation), for instance "file" or "any". If a token was provided, it's returned in response event (Websocket only).
TagPOST {targets: [{targetType: String, target: String}]} /tag/:tagName/:tagValue?tag {tagName: String, tagValue: String, targets: [{targetType: String, target: String}]}Get (if tagValue undefined) or set (if tagValue defined) a custom tag associated to a given target. Expecting POST data:
  • "targetType" refers to a MPD tag (song, artist, album etc.)
  • "target" depends on "targetType": for a song, will be the MPD path for instance
On websockets, the context parameters are returned in the response event.
Delete tagDELETE {targets: [{targetType: String, target: String}]} /tag/:tagNamedeltag {tagName: String, targets: [{targetType: String, target: String}]}Delete a given tag. Expecting JSON:
  • "targetType" refers to a MPD tag (song, artist, album etc.)
  • "target" depends on "targetType": for a song, will be the MPD path for instance
On websockets, the context parameters are returned in the response event.

Tree and leaf descriptors

Some commands sent to MPD will return a list of entries, which are basically directories, playlist files and song files with metadata. Mipod has the ability to organize them the way you want. You would like sometimes to get them as flat lists, or as trees organized by albums, artists, etc. That's what treeDesc and leafDesc are for.

A tree descriptor describes the successive levels of the tree. For instance if you want to get all songs organized by genre, then inside genres organized by artists, and finally by albums, you would write the following tree descriptor (the order matters!): ["genre","artist","album"]. A special character, "|", can be used as a "if exists / else" selector. If instead of "artist" you write "albumArtist|artist", it means that mipod will first search for the album artist of a song, then if it's not found it will search for the artist. You can have more than one pipe in a tree level.

A leaf descriptor describes the final level of the tree (that is, leaves). For instance, if your tree is just ["album"] and your leaf is ["artist","title","track","file"], then a map of albums will be returned, and for each album an array of objects, each containing artist, title, track and file information.

The available names are:

  • file
  • lastModified
  • time
  • artist
  • albumArtist
  • title
  • album
  • track
  • date
  • genre
  • composer

Examples

REST

Examples here use jquery ($.ajax)

Swith to play mode

    $.ajax({
        type: 'GET',
        url: '/play',
        cache: false,
        success: function(data) {},
    });

Play a given file

    $.ajax({
        type: 'POST',
        url: '/play',
        data: JSON.stringify({entry: "some/music/file.mp3"}),
        contentType: "application/json; charset=utf-8",
        success: function(data) {},
    });

Load the full library

    $.get("/lib-loadonce", function(data) {
        loadPage({
            start: 0,
            count: 1000
        });
    }, 'json');

    function loadPage(loadingInfo) {
        $.post('/lib-get/' + loadingInfo.start + '/' + loadingInfo.count, {},
            function(pageInfo) {
                // Process data (add "pageInfo.data" to client layout)
                if (pageInfo.finished) {
                    // Finished..
                } else {
                    loadingInfo.start = pageInfo.next;
                    setTimeout(function() {
                        loadPage(loadingInfo);
                    }, 300);
                }
            }, 'json')
        .fail(function() {
            // Report failure...
            setTimeout(function() {
                loadPage(loadingInfo);
            }, 300);
        });
    }

Websocket

Initializing socket.io

    var socket = io();

Swith to play mode

    socket.emit("play");

Play a given file

    socket.emit("play-entry", {entry: "some/music/file.mp3"});

Load the full library (push mode)

    function startLoading() {
        socket.emit("lib-push", {
            maxBatchSize: 500,
            treeDesc: ["genre", "albumArtist|artist", "album"],
            leafDesc: ["file","track","title"]
        });
        socket.emit("lib-loadonce");
    }

    // Register listeners
    socket.on("lib-finished-loading", function(body) {
        console.log("Finished to scan " + body.nbItems + " items");
    });

    socket.on("lib-push", function(body) {
        console.log("Scanned " + body.progress + " items");
        // Process data (add "body.data" to client layout)
    });
    

License

Copyright 2014 Joël Takvorian, MIT License

Contact

Feel free to report issues on Github or contact me (contact information available on npmjs)

Rate & Review

Great Documentation0
Easy to Use0
Performant0
Highly Customizable0
Bleeding Edge0
Responsive Maintainers0
Poor Documentation0
Hard to Use0
Slow0
Buggy0
Abandoned0
Unwelcoming Community0
100