Control VLC player with NativeScript

In that article I will show you how easy it is to control the VLC player software via HTTP.

 

The VLC API provides a lot of nice commands that can be executed very simply.

Setup VLC

In the main menu select Tools >> Preferences

vlc_mainmenu_tools

Show all settings and select the node Interface >> Main interfaces by activating Web in the Extra interface modules group:

vlc_preferences_maininterfaces_web

In the sub node Lua define a password in the Lua HTTP group (we will use mypassword here):

vlc_preferences_maininterfaces_lua

Now save the settings and restart the application.

By default the HTTP service runs on port 8080. If you already run a service at that port, you can change it by editing the vlcrc file that contains the configuration. Search for the http-port value and change it for your needs (you also have to restart the player after that).

Requirements

The first thing we need is a client that communicates with the VLC web interface.

For that you can use the nativescript-apiclient plugin that can be installed by the following command:

tns plugin add nativescript-apiclient

To handle the XML responses we also need the nativescript-xmlobjects plugin:

tns plugin add nativescript-xmlobjects

And we need TypeScript support:

tns plugin add nativescript-dev-typescript

Now we have all things that we need to communicate with the web interface.

Include the plugins by using the following code:

import ApiClient = require('nativescript-apiclient');
import XmlObjects = require('nativescript-xmlobjects');

Load playlists

Now let’s do our first call…

A good first start is to load the playlist.

Open http://127.0.0.1:8080/requests/playlist.xml in your browser to test if anything works fine. You will be asked for an username and password. Leave the username blank and enter the password that you have specified in your preferences.

The response will return something like this:

<node ro="rw" name="Undefined" id="1">
  <node ro="ro" name="Playlist" id="2">
    <leaf ro="rw" name="Conga Fury (Animatrix Edit)" id="7" duration="444" uri="file:///C:/Music/10AnimatrixCongaFury.mp3"/>
  </node>
  <node ro="ro" name="Medienbibliothek" id="3"/>
</node>

In your NativeScript app you have to setup a BasicAuth instance of the ApiClient plugin:

var usernameAndPassword = new ApiClient.BasicAuth("", "mypassword");

Now let’s setup the client … For that you need the IP address and the TCP port of the machine where the player runs (in that example this is 192.168.0.1:8080):

var client = ApiClient.newClient({
    baseUrl: 'http://192.168.0.1:8080/requests/playlist.xml',
 
    // "OK" section
    ok: (result) => {
        // handle the XML response
    },
 
    unauthorized: (result) => {
        console.log('Invalid password!');
    },
 
    error: (ctx) => {
        console.log('ERROR: ' + ctx.error);
    },
 
    authorizer: usernameAndPassword,
});

The “OK” section

    // "OK" section
    ok: (result) => {
        // handle the XML response
    },

is the place where we will parse the response data.

Before we do that, we should define two TypeScript interfaces that describe the “playlist objects”:

interface IPlaylist {
    entries: IPlaylistEntry[];
    name: string;
}
 
interface IPlaylistEntry {
    id: string;
    name: string;
    uri: string;
}

OK … the next thing is to create handy objects from the XML:

var xml = XmlObjects.parse(result.getString());

In the root node there is a list of child nodes that contain the playlists:

// get elements with name 'node'
var playlistNodes = xml.root.elements("node");

These child nodes itself also contain child objects that contain the entries:

var playlists: IPlaylist[] = [];
 
for (var i = 0; i < playlistNodes.length; i++) {
    var plNode = playlistNodes[i];
 
    var newPlaylist = {
        entries: [],
        name: plNode.attribute("name").value,
    };
 
    // collect entries that have
    // the name 'leaf'
    var leafNodes = plNode.elements("leaf");
    for (var ii = 0; ii < leafNodes.length; ii++) {
        var lNode = leafNodes[ii];
 
        // add entry
        newPlaylist.entries.push({
            id: lNode.attribute("id").value,
            name: lNode.attribute("name").value,
            uri: lNode.attribute("uri").value,
        });
    }
 
    playlists.push(newPlaylist);
}

Play

But the really interesting part is: “How to control the player?”

The HTTP service gives you the chance the use commands that can be invoked by the following url:

http://127.0.0.1:8080/requests/status.xml?command=<COMMAND>

In that case we will use pl_play command:

export function playEntry(entry: IPlaylistEntry) {
    var usernameAndPassword = new ApiClient.BasicAuth("", "mypassword");
 
    var client = ApiClient.newClient({
        baseUrl: 'http://192.168.0.1:8080/requests/status.xml',
 
        // "OK" section
        ok: (result) => {
            console.log('OK');
        },
 
        unauthorized: (result) => {
            console.log('Invalid password!');
        },
 
        error: (ctx) => {
            console.log('ERROR: ' + ctx.error);
        },
 
        authorizer: usernameAndPassword,
    });
 
    client.get({
       params: {
           command: 'pl_play',
           id: entry.id,
       } 
    });
}

On the other hand there is pl_stop to stop playing:

    client.get({
       params: {
           command: 'pl_stop',
       } 
    });

And pausing:

    client.get({
       params: {
           command: 'pl_pause',
       } 
    });

Go to next item:

    client.get({
       params: {
           command: 'pl_next',
       } 
    });

Or to previous one:

    client.get({
       params: {
           command: 'pl_previous',
       } 
    });

Status

If you input the URL http://127.0.0.1:8080/requests/status.xml in your browser, you can get the XML data that contain the information about the current player status.

First let’s define an interface for an object that will store data, like the length of the current track, its ID in the underlying playlist, the position and the state:

interface IStatus {
    /**
     * The length of the current track in seconds.
     */
    length?: number;
 
    /**
     * The ID of the current entry.
     */
    entry?: string;
 
    /**
     * The position (a value between 0 and 1 related to
     * the value of 'length').
     */
    position?: number;
 
    /**
     * The current state.
     */
    state?: string;
}

The client code:

export function getPlayerStatus(callback: (result: IStatus) => void) {
    var usernameAndPassword = new ApiClient.BasicAuth("", "mypassword");
 
    var client = ApiClient.newClient({
        baseUrl: 'http://192.168.0.1:8080/requests/status.xml',
 
        ok: (result) => {
            var xml = XmlObjects.parse(result.getString());
 
            // IStatus object
            var callbackResult: any = {};
 
            var positionElements = xml.root.elements("position");
            if (positionElements.length > 0) {
                // IStatus.position
                callbackResult.position = xmlElementToNumber(positionElements[0]);
            }
 
            var lengthElements = xml.root.elements("length");
            if (lengthElements.length > 0) {
                // IStatus.position
                callbackResult.length = xmlElementToNumber(lengthElements[0]);
            }
 
            var currentplidElements = xml.root.elements("currentplid");
            if (currentplidElements.length > 0) {
                var eid = currentplidElements[0].value;
                if (!isStringEmpty(eid)) {
                    // IStatus.entry
                    callbackResult.entry = eid.trim();
                }
            }
 
            var stateElements = xml.root.elements("state");
            if (stateElements.length > 0) {
                var state = stateElements[0].value;
                if (!isStringEmpty(state)) {
                    // IStatus.state
                    callbackResult.state = state.toLowerCase().trim();
                }
            }
 
            callback(callbackResult);
        },
 
        unauthorized: (result) => {
            console.log('Invalid password!');
        },
 
        error: (ctx) => {
            console.log('[ERROR] ' + ctx.error);
        },
 
        authorizer: usernameAndPassword,
    });
 
    client.get();
}
 
 
// helper functions...
 
function isStringEmpty(str: string): boolean {
    if (TypeUtils.isNullOrUndefined(str)) {
        return true;
    }
 
    str = '' + str;
    return '' === str;
}
 
function xmlElementToNumber(e: XmlObjects.XElement): number {
    if (TypeUtils.isNullOrUndefined(e)) {
        return <any>e;
    }
 
    var str = e.value;
    if (TypeUtils.isNullOrUndefined(str)) {
        return <any>str;
    }
 
    if (isStringEmpty(str)) {
        return;
    }
 
    return parseFloat('' + str);
}

Demo

You can find a simple demo at GitHub.

Have a look at the VLC.ts file (Player class) and learn how easy it is to control VLC with NativeScript by handling HTTP requests and XML responses!

Leave a Reply

Your email address will not be published. Required fields are marked *