13
0
livetrax/share/web_surfaces/shared/ardour.js
Luciano Iam d65e1170b9
WS: make ArdourClient extend Component
This allows the client object to be observed using the on()
method just like with the mixer and transport components,
instead of passing separate callbacks.

See mixer-demo/js/main.js for an example.
2020-06-23 22:00:58 +02:00

153 lines
3.8 KiB
JavaScript

/*
* Copyright © 2020 Luciano Iam <lucianito@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import { Component } from './base/component.js';
import { StateNode } from './base/protocol.js';
import MessageChannel from './base/channel.js';
import Mixer from './components/mixer.js';
import Transport from './components/transport.js';
function getOption (options, key, defaultValue) {
return options ? (key in options ? options[key] : defaultValue) : defaultValue;
}
export default class ArdourClient extends Component {
constructor (options) {
super(new MessageChannel(getOption(options, 'host', location.host)));
if (getOption(options, 'components', true)) {
this._mixer = new Mixer(this.channel);
this._transport = new Transport(this.channel);
this._components = [this._mixer, this._transport];
} else {
this._components = [];
}
this._autoReconnect = getOption(options, 'autoReconnect', true);
this._connected = false;
this.channel.onMessage = (msg, inbound) => this._handleMessage(msg, inbound);
this.channel.onError = (err) => this.notifyObservers('error', err);
}
// Access to the object-oriented API (enabled by default)
get mixer () {
return this._mixer;
}
get transport () {
return this._transport;
}
// Low level control messages flow through a WebSocket
async connect () {
this.channel.onClose = async () => {
if (this._connected) {
this._setConnected(false);
}
if (this._autoReconnect) {
await this._sleep(1000);
await this._connect();
}
};
await this._connect();
}
disconnect () {
this.channel.onClose = () => {};
this.channel.close();
this._connected = false;
}
send (msg) {
this.channel.send(msg);
}
async sendAndReceive (msg) {
return await this.channel.sendAndReceive(msg);
}
// Surface metadata API goes over HTTP
async getAvailableSurfaces () {
const response = await fetch('/surfaces.json');
if (response.status == 200) {
return await response.json();
} else {
throw this._fetchResponseStatusError(response.status);
}
}
async getSurfaceManifest () {
const response = await fetch('manifest.xml');
if (response.status == 200) {
const manifest = {};
const xmlText = await response.text();
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
for (const child of xmlDoc.children[0].children) {
manifest[child.tagName.toLowerCase()] = child.getAttribute('value');
}
return manifest;
} else {
throw this._fetchResponseStatusError(response.status);
}
}
// Private methods
async _sleep (t) {
return new Promise(resolve => setTimeout(resolve, t));
}
async _connect () {
await this.channel.open();
this._setConnected(true);
}
_setConnected (connected) {
this._connected = connected;
this.notifyPropertyChanged('connected');
}
_handleMessage (msg, inbound) {
this.notifyObservers('message', msg, inbound);
if (inbound) {
for (const component of this._components) {
if (component.handleMessage(msg)) {
break;
}
}
}
}
_fetchResponseStatusError (status) {
return new Error(`HTTP response status ${status}`);
}
}