More > JRiver Media Center 27 for Linux

Remote Control/Automation issues

(1/2) > >>

RemyJ:
Besides fiddling with Library Servers, I'm also fiddling with my remote control/automation stuff.   I think these have been mentioned before but it's been a while so I thought I'd mention them again.  None of the /MCC,  /Play, /Pause, /Stop, etc command line options do anything other than give focus to the currently running instance (which is bad by itself).   /MCWS/* options do work but because they also give focus to the running instance, they're not really useful for automation.   

Using the MCWS via the network works fine of course but it does require that the media server be started.  That's fine but could we get an option to get the MCWS output in JSON rather than XML?  JSON is much easier to work with in most scripting languages.




max096:
For many things that list stuff you actually need to parse they do have query params to return it as JSON (not sure if for everything). If you set the "Action" query parameter to JSON you get back JSON for all endpoints that support that.

For Stop/Play etc. I don't think it's a huge deal since you can just see if the response is 200 status code and contains <Response Status="OK"/> no need to parse the xml there.

The CLI never really was implemented on the Linux version of MC.

RemyJ:

--- Quote from: max096 on May 17, 2021, 06:15:34 pm ---If you set the "Action" query parameter to JSON you get back JSON for all endpoints that support that.

--- End quote ---

Unfortunately only a few endpoints support Action and the one I needed (Playback/Info) isn't one of them.

Mike Noe:
Here's some QML with the appropriate amount of JavaScript in the mix.  Perhaps you could find the javascript parts useful, specifically, XMLHttpRequest:

--- Code: ---import QtQuick 2.8
import 'utils.js' as Utils

QtObject {

    property string currentHost
    property string hostUrl

    readonly property var forEach: Array.prototype.forEach

    onCurrentHostChanged: hostUrl = "http://%1/MCWS/v1/".arg(currentHost)

    signal connectionError(var msg, var cmd)
    signal commandError(var msg, var cmd)

    // Issue xhr mcws request, handle json/xml/text results
    function __exec(cmdstr, callback, json) {
        json = json !== undefined ? json : false

        var xhr = new XMLHttpRequest()

        xhr.onerror = () => {
            connectionError("Unable to connect: ", cmdstr)
        }

        xhr.onreadystatechange = () =>
        {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                if (xhr.status === 200) {
                    if (Utils.isFunction(callback)) {
                        // FIXME: Remove MPL? Check return format
                        if (xhr.getResponseHeader('Content-Type') === 'application/x-mediajukebox-mpl')
                            console.log('MPL:', cmdstr)
                        if (xhr.getResponseHeader('Content-Type') === 'application/x-mediajukebox-mpl'
                                || xhr.getResponseHeader('Content-Type') === 'application/json')
                            callback(xhr.response)
                        else
                            callback(xhr.responseXML.documentElement.childNodes)
                    }
                } else {
                    if (xhr.getResponseHeader('Content-Type') !== 'application/x-mediajukebox-mpl'
                            & xhr.getResponseHeader('Content-Type') !== 'application/json')
                        commandError(xhr.responseXML
                                        ? xhr.responseXML.documentElement.attributes[1].value
                                        : 'Connection error'
                                     + ' <status: %1:%2>'.arg(xhr.status).arg(xhr.statusText), cmdstr)
                    else
                        commandError('<status: %1:%2>'.arg(xhr.status).arg(xhr.statusText), cmdstr)
                }
            }
        }

        xhr.open("GET", cmdstr);
        if (json)
            xhr.responseType = 'json'
        xhr.send();
    }

    // Load a model with Key/Value pairs
    function loadKVModel(cmd, model, callback) {
        if (model === undefined) {
            if (Utils.isFunction(callback))
                callback(0)
            return
        }

        __exec(hostUrl + cmd, nodes =>
        {
            // XML nodes, key = attr.name, value = node.value
            forEach.call(nodes, node =>
            {
                if (node.nodeType === 1) {
                    model.append({ key: Utils.toRoleName(node.attributes[0].value)
                                 , value: isNaN(node.firstChild.data)
                                          ? node.firstChild.data
                                          : +node.firstChild.data })
                }
            })
            if (Utils.isFunction(callback))
                callback(model.count)
        })
    }

    // Get array of objects, callback(array)
    function loadObject(cmd, callback) {
        __exec(hostUrl + cmd, (nodes) =>
        {
            // XML nodes, builds obj as single object with props = nodes
            if (Utils.isObject(nodes)) {
                var obj = {}
                forEach.call(nodes, node =>
                {
                    if (node.nodeType === 1 && node.firstChild !== null) {
                        let role = Utils.toRoleName(node.attributes[0].value)
                        obj[role] = role.includes('name') || isNaN(node.firstChild.data)
                                    ? node.firstChild.data
                                    : +node.firstChild.data
                    }
                })
                if (Utils.isFunction(callback))
                    callback(obj)
            // MPL string (multiple Items/multiple Fields for each item) builds an array of item objs
            } else if (typeof nodes === 'string') {
                var list = []
                __createObjectList(nodes, function(obj) { list.push(obj) })
                if (Utils.isFunction(callback))
                    callback(list)
            }
        })
    }

    // Get JSON array of objects, callback(array)
    function loadJSON(cmd, callback) {
        if (!Utils.isFunction(callback))
            return

        __exec(hostUrl + cmd, json =>
        {
           try {
               var arr = []
               json.forEach(item => {
                    let obj = {}
                    for (var p in item) {
                        obj[Utils.toRoleName(p)] = String(item[p])
                    }
                    arr.push(obj)
                })

               callback(arr)
           }
           catch (err) {
               console.log(commandError(err, 'JSON data'))
           }
        }, true)
    }

    // Load MCWS JSON objects => model, callback(count)
    function loadModelJSON(cmd, model, callback) {
        if (model === undefined) {
            if (Utils.isFunction(callback))
                callback(0)
            return
        }

        // Look for/remove default obj which defines the model data structure
        if (model.count === 1) {
            var defObj = Object.assign({}, model.get(0))
            model.remove(0)
        }

        __exec(hostUrl + cmd, json =>
        {
            try {
               json.forEach(item => {
                    let obj = Object.create(defObj)
                    for (var p in item)
                        obj[Utils.toRoleName(p)] = String(item[p])
                    model.append(obj)
              })
              callback(model.count)
            }
            catch (err) {
               console.log(commandError(err, 'JSON data'))
            }
        }, true)

    }

    // Load a model with mcws objects (MPL)
    function loadModel(cmd, model, callback) {
        if (model === undefined) {
            if (Utils.isFunction(callback))
                callback(0)
            return
        }

        __exec(hostUrl + cmd, xmlStr =>
        {
            var defObj = {}
            if (model.count === 1) {
                defObj = Object.assign({}, model.get(0))
                model.remove(0)
            }

            __createObjectList(xmlStr, obj => model.append(Object.assign(defObj, obj)))

            if (Utils.isFunction(callback))
                callback(model.count)
        })
    }

    // Helper to build obj list for the model
    function __createObjectList(xmlstr, callback) {
        var fldRegExp = /(?:< Name=")(.*?)(?:<\/>)/
        var items = xmlstr
//                .replace(/&quot;/g, '"')
//                .replace(/&#39;/g, "'")
//                .replace(/&lt;/g, '<')
//                .replace(/&gt;/g, '>')
            .replace(/&amp;/g, '&')
            .replace(/Field/g,'')
            .split('<Item>')

        // ignore first item, it's the MPL header info
        for (var i=1, len=items.length; i<len; ++i) {

            var fl = items[i].split('\r\n')
            var fields = {}
            fl.forEach(fldstr =>
            {
                var l = fldRegExp.exec(fldstr)
                if (l !== null) {
                    var o = l.pop().split('">')
                    // Can't convert numbers, same field will vary (string/number)
                    fields[Utils.toRoleName(o[0])] = o[1]
                }
            })
            callback(fields)
        }
    }

    // Run an mcws cmd
    function exec(cmd) {
        __exec(hostUrl + cmd)
    }

}

--- End code ---

Couple of helpers:
--- Code: ---function isFunction(f) {
    return (f && typeof f === 'function')
}
function isObject(o) {
    return (o && typeof o === 'object')
}

--- End code ---

A straight JSON reader:
--- Code: ---function jsonGet(cmdstr, cb) {
    if (!isFunction(cb)) {
        console.warn("Specify callback to get results", cmdstr)
        return
    }

    var xhr = new XMLHttpRequest()

    xhr.onerror = () => {
        console.warn("Unable to connect: ", cmdstr)
    }

    xhr.onreadystatechange = function()
    {
        if (xhr.readyState === XMLHttpRequest.DONE) {

            if (xhr.status === 0) {
                console.warn("Unable to connect: ", cmdstr)
                return
            }
            if (xhr.status !== 200) {
                console.warn('<status: %1:%2>'.arg(xhr.status).arg(xhr.statusText), cmdstr)
                return
            }
            cb(xhr.response)
        }
    }

    xhr.open("GET", cmdstr);
    xhr.responseType = 'json'
    xhr.send();
}

--- End code ---

max096:
It should be fairly doable to write a small one size fits all XML/DOM to workable object mapper (for dynamic scripting languages especially) since their XML responses all look fairly similar. I did this on Android once inflating the XML to a DOM tree and then mapping it. Was very little code, however really slow. So I eventually ended up using XmlPullParser which is a very low-level Android thing. It takes absurd amounts of coding but ended up being 10-20 times faster (I think you would be hard-pressed to beat it with JSON parsers). Since I wanted to parse the entire contents of the library that was important to me (all files and playlists). Back then there was no JSON option for anything. But for smaller things like playback info, this would totally not be an issue.

Configuring an XML parser to spit out proper objects straight away though can be a bit annoying.

What scripting language(s) are you using for your automation stuff?

Navigation

[0] Message Index

[#] Next page

Go to full version