More > JRiver Media Center 27 for Linux
Remote Control/Automation issues
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(/"/g, '"')
// .replace(/'/g, "'")
// .replace(/</g, '<')
// .replace(/>/g, '>')
.replace(/&/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