Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • aura/engine
  • hermannschwaerzler/engine
  • sumpfralle/aura-engine
3 results
Show changes
Showing
with 2320 additions and 0 deletions
html, body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
input[type="range"] {
height: 0;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<title>Aura Track Service</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/global.css'>
<link rel='stylesheet' href='/build/aura-player-bundle.css'>
<script defer src='/build/aura-player-bundle.js'></script>
</head>
<body>
<aura-trackservice api="http://localhost:3333/api/v1/" name="Track Service" />
</body>
</html>
import svelte from 'rollup-plugin-svelte';
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import livereload from 'rollup-plugin-livereload';
import { terser } from 'rollup-plugin-terser';
const production = !process.env.ROLLUP_WATCH;
export default {
input: 'src/main.js',
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: 'public/build/aura-player-bundle.js'
},
plugins: [
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write('public/build/aura-player-bundle.css');
},
customElement: true
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve({
browser: true,
dedupe: ['svelte']
}),
commonjs(),
// In dev mode, call `npm run start` once
// the bundle has been generated
!production && serve(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload('public'),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser()
],
watch: {
clearScreen: false
}
};
function serve() {
let started = false;
return {
writeBundle() {
if (!started) {
started = true;
require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], {
stdio: ['ignore', 'inherit', 'inherit'],
shell: true
});
}
}
};
}
\ No newline at end of file
<svelte:options tag="aura-trackservice"/>
<script>
export let api = "http://localhost:3333/api/v1/";
export let name = "Track Service";
let queryTrackservice = "trackservice";
let result;
let data = [];
let currentDate = "";
async function getData(query) {
let response = await fetch(`${api}${query}`);
let data = await response.json();
if (response.ok) {
return data;
} else {
throw new Error(data)
}
}
function getDate(item) {
let track_date = "";
if (item.track_start != null)
track_date = item.track_start.split('T')[0];
if (currentDate != track_date) {
currentDate = track_date;
let date = new Date(currentDate);
let options = {
weekday: "long", year: "numeric", month: "short",
day: "numeric"
};
return date.toLocaleDateString("de-at", options);
} else {
return "";
}
}
function getTime(item) {
let date = new Date(item.track_start);
let options = {
hour: "2-digit", minute: "2-digit"
};
return date.toLocaleTimeString("de-at", options);
}
function isActive(item) {
if (item.track.duration != null && parseInt(item.track.duration) > 0) {
let startDate = new Date(item.track_start);
let endDate = new Date(startDate.getTime());
endDate.setSeconds(endDate.getSeconds() + parseInt(item.track.duration));
let now = new Date();
if (startDate < now && now < endDate) {
return "active-track";
} else {
return "";
}
} else {
return "";
}
}
function printDuration(item) {
if (item.track.duration != null && parseInt(item.track.duration) > 0) {
return "("+formatTime(item.track.duration)+")";
} else {
return "";
}
}
function formatTime(seconds) {
if (seconds != null && Number.isInteger(seconds)) {
let d = new Date(null);
d.setSeconds(seconds);
let s;
if (seconds > 3600)
s = d.toISOString().substr(11, 8);
else
s = d.toISOString().substr(14, 5);
return s;
}
return "";
}
result = getData(queryTrackservice);
</script>
<style>
h1, h2, h3, h4, h5 {
text-align: center;
}
.card {
margin-bottom: 38px;
font-size: 1.3em;
}
.card.active-track {
border: 3px solid greenyellow;
}
.spinner {
text-align: center;
}
.spinner .loading {
margin: 10px 0 0 0;
color: gray;
}
.lds-dual-ring {
display: inline-block;
width: 80px;
height: 80px;
}
.lds-dual-ring:after {
content: " ";
display: block;
width: 64px;
height: 64px;
margin: 8px;
border-radius: 50%;
border: 6px solid #000;
border-color: #aaa transparent #aaa transparent;
animation: lds-dual-ring 1.2s linear infinite;
}
@keyframes lds-dual-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.current-date {
font-size: 1.3em;
}
footer {
font-size: 0.8em;
margin: 40px 0;
text-align: center;
color: gray;
}
footer a {
color: gray;
text-decoration: underline;
}
</style>
<div class="container mt-5">
<div class="row">
<div class="col-md"></div>
<div class="col-md-8 text-center">
<h1 class="display-4">{name}</h1>
{#await result}
<div class="spinner" role="status">
<div class="lds-dual-ring"></div>
<div class="loading">Loading...</div>
</div>
{:then value}
{#each value as item}
<h4 class="current-date">{getDate(item)}</h4>
<div class="card mt-5 {isActive(item)}">
<div class="card-body">
<h5 class="card-title"><b>{getTime(item)}</b> | {item.track.artist} - {item.track.title} {printDuration(item)}</h5>
</div>
</div>
{/each}
{:catch error}
<p style="color:red">{error.message}</p>
{/await}
</div>
<div class="col-md"></div>
</div>
</div>
<footer>Track Service is powered by <a href="https://gitlab.servus.at/autoradio">Aura</a></footer>
\ No newline at end of file
import TrackService from './TrackService.svelte';
\ No newline at end of file
*Looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)*
---
# svelte app
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Deploying to the web
### With [now](https://zeit.co/now)
Install `now` if you haven't already:
```bash
npm install -g now
```
Then, from within your project folder:
```bash
cd public
now deploy --name my-project
```
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```
engine-api @ ae560241
Subproject commit ae560241e5947f595a793413dfc48657807233b0
engine-clock @ 6ae833ad
Subproject commit 6ae833ad05501cfed65d54becf1f749bded1fd0d
#!/usr/bin/env python2.7
# Copyright (c) 2001, Nicola Larosa
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
# * Neither the name of the <ORGANIZATION> nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
""" PyHeartBeat server: receives and tracks UDP packets from all clients.
While the BeatLog thread logs each UDP packet in a dictionary, the main
thread periodically scans the dictionary and prints the IP addresses of the
clients that sent at least one packet during the run, but have
not sent any packet since a time longer than the definition of the timeout.
Adjust the constant parameters as needed, or call as:
PyHBServer.py [timeout [udpport]]
https://www.oreilly.com/library/view/python-cookbook/0596001673/ch10s13.html
"""
HBPORT = 43334
CHECKWAIT = 10
from socket import socket, gethostbyname, AF_INET, SOCK_DGRAM
from threading import Lock, Thread, Event
from time import time, ctime, sleep
import sys
class BeatDict:
"Manage heartbeat dictionary"
def __init__(self):
self.beatDict = {}
if __debug__:
self.beatDict['127.0.0.1'] = time( )
self.dictLock = Lock( )
def __repr__(self):
list = ''
self.dictLock.acquire( )
for key in self.beatDict.keys( ):
list = "%sIP address: %s - Last time: %s\n" % (
list, key, ctime(self.beatDict[key]))
self.dictLock.release( )
return list
def update(self, entry):
"Create or update a dictionary entry"
self.dictLock.acquire( )
self.beatDict[entry] = time( )
self.dictLock.release( )
def extractSilent(self, howPast):
"Returns a list of entries older than howPast"
silent = []
when = time( ) - howPast
self.dictLock.acquire( )
for key in self.beatDict.keys( ):
if self.beatDict[key] < when:
silent.append(key)
self.dictLock.release( )
return silent
class BeatRec(Thread):
"Receive UDP packets, log them in heartbeat dictionary"
def __init__(self, goOnEvent, updateDictFunc, port):
Thread.__init__(self)
self.goOnEvent = goOnEvent
self.updateDictFunc = updateDictFunc
self.port = port
self.recSocket = socket(AF_INET, SOCK_DGRAM)
self.recSocket.bind(('', port))
def __repr__(self):
return "Heartbeat Server on port: %d\n" % self.port
def run(self):
while self.goOnEvent.isSet( ):
if __debug__:
print "Waiting to receive..."
data, addr = self.recSocket.recvfrom(6)
if __debug__:
print "Received packet from " + `addr`
self.updateDictFunc(addr[0])
def main( ):
"Listen to the heartbeats and detect inactive clients"
global HBPORT, CHECKWAIT
if len(sys.argv)>1:
HBPORT=sys.argv[1]
if len(sys.argv)>2:
CHECKWAIT=sys.argv[2]
beatRecGoOnEvent = Event( )
beatRecGoOnEvent.set( )
beatDictObject = BeatDict( )
beatRecThread = BeatRec(beatRecGoOnEvent, beatDictObject.update, HBPORT)
if __debug__:
print beatRecThread
beatRecThread.start( )
print "PyHeartBeat server listening on port %d" % HBPORT
print "\n*** Press Ctrl-C to stop ***\n"
while 1:
try:
if __debug__:
print "Beat Dictionary"
print `beatDictObject`
silent = beatDictObject.extractSilent(CHECKWAIT)
if silent:
print "Silent clients"
print `silent`
sleep(CHECKWAIT)
except KeyboardInterrupt:
print "Exiting."
beatRecGoOnEvent.clear( )
beatRecThread.join( )
if __name__ == '__main__':
main( )
\ No newline at end of file
# Aura Engine Development Guide
This page gives insights on extending Aura Engine internals or through the API.
<!-- TOC -->
- [Aura Engine Development Guide](#aura-engine-development-guide)
- [AURA Componentes](#aura-componentes)
- [Engine Components](#engine-components)
- [API](#api)
- [More infos for debugging](#more-infos-for-debugging)
- [Default ports used by Engine](#default-ports-used-by-engine)
- [Debugging Liquidsoap](#debugging-liquidsoap)
- [Tips on configuring the audo interface](#tips-on-configuring-the-audo-interface)
- [Read more](#read-more)
<!-- /TOC -->
## AURA Componentes
AURA Engine as part of the AURA Radio Suite uses an modulear architecture
based on a REST API. All external information is retrieved using JSON data-structures.
To get the basic architectural overview, visit the [Aura Meta](https://gitlab.servus.at/autoradio/meta) repository.
Starting development of engine can be quite tedious, as it requires all most all other AURA components to be up and running.
For example:
- Steering, to get the main incredient of an play-out engine: schedules (or "timeslots" in Steering terms),
which hold the actual information on playlists and their entries.
- Dashboard, to have a neat interface, being able to programm the schedules
- Tank, to get the references to audio files and other audio sources. Plus the actual files.
If you need to test and develop against the Engine's API you'll also need to get the `engine-api` project running.
For a start it's recommended to create a general `aura` project folder. In there you start cloning all the sub-projects.
After having all the sub-projects configured, and verified that they are working, take a look at the AURA `meta` project.
There's a convenience script to start all of the three main dependencies (Steering, Dashboard, Tank) all at once:
```bash
~/code/aura/meta$ ./run.sh aura local
```
### Engine Components
**engine-core.py**: It is the server which is connected to the external programme source (e.g. aura steering and tank), to liquidsoap and is listening for redis pubsub messages. This precious little server is telling liquidsoap what to play and when.
**Liquidsoap**: The heart of AURA Engine. It uses the built in mixer, to switch between different sources.
## API
You can find the AURA API definition here: https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md
OpenAPI definition for Engine API: https://app.swaggerhub.com/apis/AURA-Engine/engine-api/
## More infos for debugging
### Default ports used by Engine
Aura Engine requires a number of ports for internal and external communication.
Those are the default port numbers:
* `1234` ... Liquidsoap Telnet Server
* `8008` ... Exposes the Engine API
* `5000` ... Svelte development mode; dynamically uses some other port if occupied
### Debugging Liquidsoap
Connect to Liquidsoap via Telnet
telnet 127.0.0.1 1234
List available commands
help
List all available channels
list
List all input channels connected to the mixer
mixer.input
Set the volume of mixer `input 0` to `100%`
mixer.volume 0 100
Push some audio file to the filesystem `queue 0`
in_filesystem_0.push /path/to/your/file.mp3
### Tips on configuring the audo interface
Configure your audio device in the `[soundcard]` section of `engine.ini`.
You can configure up to **five** line IN and OUT stereo channels. Your hardware should
support that. When you use JACK, you will see the additional elements popping up when
viewing your connections (with e.g. Patchage).
**Pulse Audio:** When using Ubuntu, Pulse Audio is selected by default. This is convenient,
as you won't have the need to adapt any Engine setting to get audio playing initially.
**ALSA:** When you use ALSA, you will have to play around with ALSA settings. In the folder
`./modules/liquidsoap` is a scipt called alsa_settings_tester.liq. You can start it
with 'liquidsoap -v --debug alsa_settings_tester.liq'. Changing and playing with
settings may help you to find correct ALSA settings.
**Jack Audio**: Install the JACK daemon and GUI:
```bash
sudo apt-get install jackd qjackctl
```
Please ensure to enable "*realtime process priority*" when installing JACK to keep latency low.
Now, you are able to configure your hardware settings using following command:
```bash
qjackctl
```
Next you need to install the JACK plugin for Liquidsoap:
```bash
sudo apt install \
liquidsoap-plugin-jack
```
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
# Aura Engine Features
This page gives a more detailed overview of the Aura Engine features and how to configure them.
<!-- TOC -->
- [Aura Engine Features](#aura-engine-features)
- [Multi-channel Input (Filesystem, Stream, Analog)](#multi-channel-input-filesystem-stream-analog)
- [Multi-channel output](#multi-channel-output)
- [Analog line-out](#analog-line-out)
- [Stream to Icecast](#stream-to-icecast)
- [Record output to filesystem](#record-output-to-filesystem)
- [Scheduling](#scheduling)
- [Fallback Handling](#fallback-handling)
- [Pro-active Fallback Handling (1st Level Fallback)](#pro-active-fallback-handling-1st-level-fallback)
- [Fallback Handling using the Silence Detector (2nd Level Fallback)](#fallback-handling-using-the-silence-detector-2nd-level-fallback)
- [Monitoring](#monitoring)
- [Send mails on errors and warnings](#send-mails-on-errors-and-warnings)
- [Engine Health Information via Engine API](#engine-health-information-via-engine-api)
- [Engine Heartbeat](#engine-heartbeat)
- [Logging](#logging)
- [Read more](#read-more)
<!-- /TOC -->
## Multi-channel Input (Filesystem, Stream, Analog)
It's possible to schedules playlists with music or pre-recorded show stored on the **filessystem**,
via external **streams** or live from an **analog input** in the studio. All types of sources can
be mixed in a single playlist.
> Note: Since live sources and streams do not specify a length property, any playlists populated
with such source are expecting this item to run until the end of the schedule. Any other entries
following such item are therfore skipped. This might change in a future version of AURA Tank,
allowing to set a length property.
The switching between types of audio source is handled automatically. To learn more check out the
[Scheduling](### Secure Scheduling) section.
## Multi-channel output
### Analog line-out
In most scenarios it might be sufficient to broadcast via only one analog stereo output.
If needed you can configure up to five stereo pairs.
### Stream to Icecast
Engine allows to stream to multiple Icecast Servers simultaniousely. It is also sending meta information
to the streaming server, using the *Icy* protocol.
To configure your Icecast connectivity check-out the `[stream]` section in your configuration.
### Record output to filesystem
Engine allows recording broadcasts to the filesystem using up to multiple recorders. Each recorder can
be configured to save in a different resolution in audio quality.
## Scheduling
Engine provide a scheduling functionality by polling external API endpoints frequently.
Scheduling is split into multiple phase. Below you see a timline with one schedule planned at a certain
point in time and the involved phase before:
```
================= [ Scheduling Window ] ============= [ Schedule Play-out ] ====
== (FILESYSTEM A) ========================== [ Pre-Roll ] [ Play 4 ] ====================================
== (STREAM A) ========================================== [ Pre-Roll ] [ Play 1 ] ==========================
== (LIVE 1) ====================================================== [ Pre-Roll ] [ Play 1 ] ================
== (FILESYSTEM B) ========================================================== [ Pre-Roll ] [ Play 4 ] ====
```
- **Scheduling Window**: Within the scheduling window any commands for controlling
the mixer of the soundsystem are prepared and queued.
Until the start of the window, schedules can be added or removed
via external API Endpoints (e.g. using Steering or Dashboard). Until here any
changes on the schedule itself will be reflected in the actual play-out. Be aware,
that this only affects the schedule ("timeslot") itself, it does not involve related
playlists and their entries. The latter can still be modified within the scheduling
window.
The start and the end of the window is defined by the start of the schedule minus
a configured amount of seconds (see `scheduling_window_start` and `scheduling_window_end`
in `engine.ini`).
During the scheduling window, the external API Endpoints are pulled continiously, to
check for updated schedules and related playlists. Also, any changes to playlists and
its entries are respected within that window (see `fetching_frequency` in `engine.ini`).
<u>Pro-active fallback handling</u> by verifying the existence of playlists is already
happening at this stage.
> Important: It's vital that the the scheduling window is wider than the fetching frequency.
Otherwise one fetch might never hit a scheduling window, hence not being able to schedule stuff.
- **Queuing and Pre-Rolling**: Before any playlist entries of the schedule can be turned into
sound, they need to be grouped, queued an pre-rolled.
Within this stage users can neither change playlists nor its entries anymore. <u>Pro-active
fallback handling</u> will happen though (see next chapter below for an explanation).
1. First, all entries are aggregated when they hold filesystem entries.
Given you have a playlist with 10 entries, the first 4 are consisting of files, the next two
of a a stream and a live source. The last 4 are files again. These entries are now
aggregated into 4 groups: one for the files, one for the stream, one for the live entry
and another one for files. For each group a timer for executing the next step is created.
2. Now, the playlist entries are going to be "pre-rolled". This means that filesystem
entries are queued and pre-loaded and entries which are based on audio streams are buffered.
This is required to allow a seamless play-out, when its time to do so (in the next stage).
Due to their nature, playlist entries which hold live audio sources are not affected by
this stage at all.
Set the maximum time reserved for pre-rolling in your configuration (compare `preroll_offset`
in `engine.ini`).
> Important: This stage can require up to 4x pre-rolling steps, one for default play-out
and for each of the 3 fallback types. So the expected time to load some source multiplied
by 4 should be set as the actual value. Also, the offset should not exceed the time between
the end of the scheduling-window and the start of the actual schedule playout.
- **Play-out**: Finally the actual play-out is happening. The faders of the virtual mixers are pushed
all the way up, as soon it's "time to play" for one of the pre-rolled entries.
Transitions between playlist entries with different types of sources (file, stream and analog
inputs) are performed automatically. At the end of each schedule the channel is faded-out,
no matter if the total length of the playlist entries would require a longer timeslot.
Since each scheduled playlist can consist of multiple entry types such as *file*, *live*,
and *stream*, the play-out of the schedule is actually a bit more complex. The paragraphs
below go into more details.
If for some reason the playout is corrupted, stopped or too silent to make any sense, then
this <u>triggers a 2nd level fallback using the silence detector</u> (see chapter below).
## Fallback Handling
Engine performs scheduling with some additional handling for extended security, to meet the requirements
of community radios. It tries to pro-actively reacted to scenarios, where the upload of some
pre-recorded show has been forgotten, or some live schedule is not taking place.
Usually in such cases the broadcast might end up with some timeslot filled with silence.
To avoid this, Engine implements multiple levels of fallback handling.
To understand how fallbacks are handled by Engine in detail, it's good to know how the Engine's
scheduling is executed. Read more about the scheduling approach in the chapter above.
The available fallbacks are evaluated in following order:
1. **Schedule Fallback**: If the show fallback is not assigned, a configured fallback
playlist for the related timeslot is used.
2. **Show Fallback**: If the schedule for some show has no playlist assigned, the
playlist assigned as a *show fallback* is used instead. In the dashboard this can
be done as seen in the screenshot below.
![Setting for the Show Fallback in AURA Dashboard](images/dashboard-fallback-setting.png "Show Fallback in Dashboard")
3. **Station Fallback**: If everything goes wrong, meaning all the previous fallback
playlists are missing or are invalid, the *station fallback* will be triggered. This
fallback type is specified by a playlist ID set in the Engine's configuration
(see `scheduling_station_fallback_id` in `engine.ini`)
### Pro-active Fallback Handling (1st Level Fallback)
This type of fallback handling is performed before the actual playout, within the scheduling
window and queuing/pre-rolling stage. Here Engine pro-actively verifies the validity of the playlists.
For example it checks if some playlist ist existing at all, if it holds entries or if the
entries are existing.
Furthermore, it checks for any unexpected error when entries are pre-rolled. For example Engine
tries to buffer some HTTP Audio Stream, but it is not reachable. In this case this will
trigger the scheduling of some fallback playlist.
Pro-active fallback handling has the advantage that any (even short) periods of silence
during playlist can be avoided. Still, there are cases, where any failure cannot be detected
beforehand. Therefore a 2nd level fallback solution is required (see below).
### Fallback Handling using the Silence Detector (2nd Level Fallback)
Engine offers a simple way to detect situations where no sound is on air, the so-called
Silence Detector. It allows detection of absoulte silence, weak signals or even noise.
To configure the sensitivity of the Silence Detector check-out following properties in
`engine.ini`:
```
# max_blank => maximum time of blank from source (defaults to 20., seconds, float)
# min_noise => minimum duration of noise on source to switch back over (defaults to 0, seconds, float)
# threshold => power in dB under which the stream is considered silent (defaults to -40., float)
fallback_max_blank="20."
fallback_min_noise="0."
fallback_threshold="-50."
```
## Monitoring
You have following options to monitor the Engine:
* Send mails on errors and warnings
* Engine Status Information
* Engine Heartbeat
* Logging
### Send mails on errors and warnings
To activate you'll need to set some mail account within the `[monitoring]` section of your configuration.
```ini
[monitoring]
mail_server="mail.o94.at"
mail_server_port="587"
mail_user="aura@o94.at"
mail_pass="---SECRET--PASSWORD---"
# If you want to send multiple adminmails, make them space separated
admin_mail="admin-email@your.domain"
# Which from mailadress should be used
from_mail="monitoring@aura.engine"
# The beginning of the subject. With that you can easily apply filter rules using a mail client
mailsubject_prefix="[Aura Engine]"
```
### Engine Health Information via Engine API
Whenever the Engine's status turns into some unhealthy state this is logged to [Engine API](https://gitlab.servus.at/aura/engine-api).
Also, when it returns to some valid state this is logged to the Engine API.
### Engine Heartbeat
Instead of checking all status properties, the Heartbeat only validates the vital ones
required to run the engine. If all of those are valid, as network socket request is sent
to a defined server.
This heartbeat is sent continiously based on the configured `heartbeat_frequency`.
```ini
# Server where heartbeat info is sent to
heartbeat_server = "127.0.0.1"
# Some UDP port
heartbeat_port = 43334
# Seconds how often the vitality of the Engine should be checked (0 = disabled)
heartbeat_frequency = 1
```
The service receiving this heartbeat ticks can decide what to do with that information.
One scenario could be switching to another Engine instance or any other failover scenario.
Under `contrib/heartbeat-monitor` you'll find some sample application digesting these
heartbeat signals.
### Logging
In development all Engine logs can be found under `./log`, and for production they can
are located in `/var/log/aura`. Adapt the log-level within your configuration to get
more or less verbose log output:
```ini
logdir="/var/log/aura"
# Possible values: debug, info, warning, error, critical
loglevel="info"
```
The log directory holds individual logs from Engine Core, Liquidsoap and the API.
But also `stout` outputs from supervisor services are written there.
Additionally you'll finde Supervisor specific logs under`/var/log/supervisor`.
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
# Frequently Asked Questions
<!-- TOC -->
- [Frequently Asked Questions](#frequently-asked-questions)
- [Which Audio Interface / Soundcard is compatible with Aura?](#which-audio-interface--soundcard-is-compatible-with-aura)
- [ALSA Settings](#alsa-settings)
- [In the Liquidsoap Logs I get 'Error when starting output output_lineout_0: Failure("Error while setting open_pcm: Device or resource busy")!'. What does it mean?](#in-the-liquidsoap-logs-i-get-error-when-starting-output-output_lineout_0-failureerror-while-setting-open_pcm-device-or-resource-busy-what-does-it-mean)
- [How can I find the audio device IDs, required for settings in engine.ini?](#how-can-i-find-the-audio-device-ids-required-for-settings-in-engineini)
- [I have issues with starting the Engine](#i-have-issues-with-starting-the-engine)
- [I have issues during some Engine play-out](#i-have-issues-during-some-engine-play-out)
- [Read More](#read-more)
<!-- /TOC -->
## Which Audio Interface / Soundcard is compatible with Aura?
Basically any audio device which is supported by Linux Debian/Ubuntu and has ALSA drivers.
Engine has been tested with following audio interfaces:
- ASUS Xonar DGX,
- Roland Duo-Capture Ex
- Onboard Soundcard (HDA Intel ALC262)
- Native Instruments Komplete Audio 6
## ALSA Settings
### In the Liquidsoap Logs I get 'Error when starting output output_lineout_0: Failure("Error while setting open_pcm: Device or resource busy")!'. What does it mean?
You probably have set a wrong or occupied device ID.
### How can I find the audio device IDs, required for settings in engine.ini?
* **ALSA**: You can get the device numbers or IDs by executing:
cat /proc/asound/cards
* **Pulse Audio**: You might not need this for Pulse Audio, but still, to see all available devices use:
pactl list
**If you cannot find correct ALSA settings**
Well, this is - at least for me - a hard one. I could not manage to find correct ALSA settings for the above mentioned soundcards. The best experience i had with the ASUS Xonar DGX, but still very problematic (especially the first couple of minutes after starting liquidsoap). Since i enabled JACK support i only use that. It is also a bit of trial and error, but works pretty much out of the box.
**If you experience 'hangs' or other artefacts on the output signal**
* Reduce the quality (especially, when hangs are on the stream) or
* Check the logs (especially the Liquidsoap logs) for any configuration issues
* Check ther performance of your computer or audio hardware
* Install the realtime kernel with
```bash
apt install linux-image-rt-amd64
reboot
```
## I have issues with starting the Engine
**Cannot connect to socketpath /opt/aura/engine/modules/liquidsoap/engine.sock. Reason: [Errno 111] Connection refused**
- This indicates some issue with the proper startup of Liquidsoap or any related audio device. Check the Liquidsoap logs for details.
**[clock.wallclock_alsa:2] Error when starting output lineout: Failure("Error while setting open_pcm: No such file or directory")!**
- This might indicate some issue with your ALSA device. Check if `aplay -l` and `aplay -L` returns some valid device(s). You can also try `alsamixer`.
- It might be also helpful to set your default audio device in `/etc/asound.conf`.
- Also check if your user (è.g. `engineuser`) belongs to the group `audio`.
- Check the audio interface configuration section in `engine.ini`. Verify if the default settings `input_device_0="hw:0"` and `output_device_0="hw:0"` are valid device IDs.
## I have issues during some Engine play-out
**Some stuttering sound is hearable. The logs say "strange error flushing buffer ..."**
- Check your Icecast connection. Is it up and running?
- Maybe there is some authentication issue or an Icecast limitation of max clients. See https://github.com/savonet/liquidsoap/issues/524.
## Read More
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
docs/images/dashboard-fallback-setting.png

20.9 KiB

# Install for Development
<!-- TOC -->
- [Install for Development](#install-for-development)
- [Prerequisites](#prerequisites)
- [Setup Database](#setup-database)
- [Installation](#installation)
- [Configuration](#configuration)
- [Running Engine](#running-engine)
- [Logging](#logging)
- [Read more](#read-more)
<!-- /TOC -->
## Prerequisites
Aura Engine runs on any modern Debian-based OS. It requires at least
- `Node 13`
- `Python 3.7`
- `git`
Additionally you'll need these system packages:
```bash
sudo apt-get install \
opam \
redis-server \
libsndfile1 \
ffmpeg \
quelcom \
python3-pip \
virtualenv \
libssl-dev
```
Depending on the database management system you gonna use, you'll also need to install those packages.
In case of MariaDB this is:
```shell
sudo apt-get install \
python-dev \
default-libmysqlclient-dev \
mariadb-server \
libmariadbclient-dev
```
**Liquidsoap Repository**
Engine requires at least `Liquidsoap 1.4.2` or newer, installed using [OPAM (OCaml Package Manager)](https://opam.ocaml.org/).
Add the current Liquidsoap repository from [this installation guide](https://www.liquidsoap.info/doc-1.4.2/install.html).
The other steps required for the Liquidsoap installation are handled by the `install.sh` script. If you experience any
errors, carefully review them and consult the official documentation for installing Liquidsoap.
## Setup Database
The following installation script sets up the database.
```bash
bash script/setup-db.sh
```
By default Aura Engine uses MariaDB for persistence. When starting this script, please
ensure you have root access to your database instance. The installation script automatically
creates a database plus an associated user with password. If you want to use your own database
system, select "Other / Manually" during the database installation step.
In case of MariaDB the script also installs these system dependencies:
apt-get install mariadb-server libmariadbclient-dev
When this is completed, carefully check if any error occured. In case your database has been setup
automatically, note the relevant credentials for later use in your `engine.ini` configuration.
**Cloning the project**
Then clone the project to some development folder:
```bash
git clone https://gitlab.servus.at/autoradio/engine
```
## Installation
```shell
./install.sh
```
This script does the following:
- Install Liquidsoap components using OPAM (`script/install-opam-packages`)
- NodeJS Modules (`script/install-web.sh`)
- Python Packages (`requirements.txt`)
- Creates a default configuration file in `configuration/engine.ini`
When this is completed, carefully check if any error occured.
## Configuration
In your development environment edit following file to configure the engine:
```shell
./configuration/engine.ini
```
Please note if some configuration exists under `/etc/aura/engine.ini` the configuration is
read from there by default.
Now, specify at least following settings to get started:
```ini
[database]
db_user="aura"
db_name="aura_engine"
db_pass="---SECRET--PASSWORD---"
```
Set the URLs to the *Steering* and *Tank* API:
```ini
[api]
# The URL to get the health status
api_steering_status = "http://aura.local:8000/api/v1/"
# The URL to get the Calendar via Steering
api_steering_calendar="http://aura.local:8000/api/v1/playout"
# The URL to get show details via Steering
api_steering_show="http://aura.local:8000/api/v1/shows/${ID}/"
## TANK ##
# The URL to get the health status
api_tank_status = "http://aura.local:8040/healthz/"
# The URL to get playlist details via Tank
api_tank_playlist="http://aura.local:8040/api/v1/playlists/${ID}"
## ENGINE-API ##
# Engine ID (1 or 2)
api_engine_number = 1
# Engine API endpoint to store playlogs
api_engine_store_playlog = "http://localhost:8008/api/v1/playlog/store"
# Engine API endpoint to store clock information
api_engine_store_clock = "http://localhost:8008/api/v1/clock"
# Engine API endpoint to store health information
api_engine_store_health = "http://localhost:8008/api/v1/source/health/${ENGINE_NUMBER}"
```
Ensure that the Liquidsoap installation path is valid:
```ini
[lqs]
liquidsoap_path="/home/david/.opam/4.08.0/bin/liquidsoap"
```
Finally Engine needs to be able to access the audio folder, where all the tracks of the playlists
are stored via *Tank*:
```ini
[audiofolder]
audiofolder="/var/audio"
```
If the audio device desired for playback is set as `default`, the Engine now should be ready to play
sound. You can check the default audio hardware by executing `aplay -L` on the command line. If that's
not the case you can set the default device in `/etc/asound.conf`. More advanced audio device settings
can be looked up in the [Configuration Guide](docs/configuration-guide.md).
Read about all other available settings in the [Configuration Guide](docs/configuration-guide.md).
## Running Engine
Use the convencience script `run.sh` to get engine started in different ways:
**Run the Engine**
This includes the Liquidsoap audio engine, but does not start the API server.
```shell
./run.sh
```
**Run the Engine Core and Liquidsoap separately**
When developing and debugging engine it is helpful to start the core and the Liquidsoap
component separately. In such case it is important to start both in the correct order.
First start the core of the engine:
```shell
./run.sh core
```
When engine-core completed its boot phase, indicated by a log that it is waiting for
Liquidsoap, you can run following:
```shell
./run.sh lqs
```
## Logging
All Engine logs for development can be found under `./logs`.
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
# Install for Production
<!-- TOC -->
- [Install for Production](#install-for-production)
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [Configuration](#configuration)
- [Running Engine](#running-engine)
- [Running with Systemd](#running-with-systemd)
- [Running with Supervisor](#running-with-supervisor)
- [Logging](#logging)
- [Read more](#read-more)
<!-- /TOC -->
## Prerequisites
Aura Engine runs on any modern Debian-based OS. It requires at least
- `Python 3.7`
- `git`
Additionally you'll need these system packages below.
```shell
sudo apt-get update
sudo apt-get install \
supervisor \
opam \
redis-server \
libsndfile1 \
ffmpeg \
quelcom \
python3-pip \
virtualenv \
libssl-dev
```
Depending on the database management system you gonna use, you'll also need to install those packages.
In case of MariaDB this is:
```shell
sudo apt-get install \
python-dev \
default-libmysqlclient-dev \
mariadb-server \
libmariadbclient-dev
```
**Create an user**
While previous packages need superuser rights to be installed, the following ones are installed for the user which is
executing the engine. In your development environment you can skip this step. In production you first need to create
a user called `engineuser`.
```shell
sudo adduser engineuser
sudo adduser engineuser audio sudo
```
And switch to that user
```shell
su engineuser
```
**Liquidsoap Repository**
Engine requires at least `Liquidsoap 1.4.2` or newer, installed using [OPAM (OCaml Package Manager)](https://opam.ocaml.org/).
Add the current Liquidsoap repository from [this installation guide](https://www.liquidsoap.info/doc-1.4.2/install.html).
The other steps required for the Liquidsoap installation are handled by the `install.sh` script. If you experience any
errors, carefully review them and consult the official documentation for installing Liquidsoap.
**Cloning the project**
Create the folder `/opt/aura` and clone the engine project from there:
```shell
engineuser:/opt/aura/$ git clone https://gitlab.servus.at/autoradio/engine
```
Now you should have `/opt/aura/engine/`.
Let's move inside the home of engine:
```shell
engineuser:/opt/aura/$ cd engine
```
**Setup the database**
The following installation script sets up the database. You either need to be logged in as root
or have sudo rights.
```shell
root:/opt/aura/engine/$ bash script/setup-db.sh
```
By default Aura Engine uses MariaDB for persistence. When starting this script, please
ensure you have root access to your database instance. The installation script automatically
creates a database plus an associated user with password. If you want to use your own database
system, select "Other / Manually" during the database installation step.
If you have chosen to setup your database automatically, note the relevant credentials.
**Initialize folders and permissions**
Call this script to create the required log folders and update all permissions.
```bash
root:/opt/aura/engine$ bash script/initialize.sh
```
## Installation
The following installation script also sets up the database.
By default Aura Engine uses MariaDB for persistence. When starting the installation, please
ensure you have root access to your database instance. The installation script automatically
creates a database plus an associated user with password. If you want to use your own database
system, select "Other / Manually" during the database installation step.
```shell
engineuser:/opt/aura/engine$ ./install.sh prod
```
This script does the following:
- Install Liquidsoap components using OPAM (`script/install-opam-packages`)
- Python Packages (`requirements.txt`)
- Creates a default Engine configuration file in `/etc/aura/engine.ini`
When this is completed, carefully check if any error occured. In case your database has been setup
automatically, note the relevant credentials for later use in your `engine.ini` configuration.
## Configuration
In your production environment edit following file to configure the engine:
```shell
engineuser:/opt/aura/engine$ nano /etc/aura/engine.ini
```
Now, specify at least following settings to get started:
```ini
[database]
db_user="aura"
db_name="aura_engine"
db_pass="---SECRET--PASSWORD---"
```
Set the URLs to the *Steering*, *Tank* and *Engine* API:
```ini
[api]
# The URL to get the health status
api_steering_status = "http://aura.local:8000/api/v1/"
# The URL to get the Calendar via Steering
api_steering_calendar="http://aura.local:8000/api/v1/playout"
# The URL to get show details via Steering
api_steering_show="http://aura.local:8000/api/v1/shows/${ID}/"
## TANK ##
# The URL to get the health status
api_tank_status = "http://aura.local:8040/healthz/"
# The URL to get playlist details via Tank
api_tank_playlist="http://aura.local:8040/api/v1/playlists/${ID}"
## ENGINE-API ##
# Engine ID (1 or 2)
api_engine_number = 1
# Engine API endpoint to store playlogs
api_engine_store_playlog = "http://localhost:8008/api/v1/playlog/store"
# Engine API endpoint to store clock information
api_engine_store_clock = "http://localhost:8008/api/v1/clock"
# Engine API endpoint to store health information
api_engine_store_health = "http://localhost:8008/api/v1/source/health/${ENGINE_NUMBER}"
```
Ensure that the Liquidsoap installation path is valid:
```ini
[lqs]
liquidsoap_path="/home/engineuser/.opam/4.08.0/bin/liquidsoap"
```
**Configuring the Audio Store**
Finally Engine needs to be able to access the audio folder, where all the tracks of the playlists
are stored via *Tank*:
```ini
[audiofolder]
audiofolder="/var/audio"
```
There is some document on how to [Setup the Audio Store](docs/setup-audio-store.md).
If the audio device desired for playback is set as `default`, the Engine now should be ready to play
sound. You can check the default audio hardware by executing `aplay -L` on the command line. If that's
not the case you can set the default device in `/etc/asound.conf`. More advanced audio device settings
can be looked up in the [Configuration Guide](docs/configuration-guide.md).
Read about all other available settings in the [Configuration Guide](docs/configuration-guide.md).
## Running Engine
In production the process of starting the engine is slightly different compared to some development environment.
This is due to the need of ensuring the engine's components are always running i.e. letting them to restart
automatically after some system restart or crash has happened.
For this you can utilize either [Systemd](https://systemd.io/) or [Supervisor](http://supervisord.org/).
### Running with Systemd
-- TO BE ADDED --
### Running with Supervisor
Now, given you are in the engine's home directory `/opt/aura/engine/`, simply type following to start
the services:
```shell
supervisord
```
This picks up the supervisor configuration provided in the local `supervisord.conf` and the service configurations
located in `configuration/supervisor/*.conf`.
Experience has shown it might be helpful to reload the supervisor configuration using `sudo`:
```shell
sudo supervisorctl reload
```
Note that the supervisor daemon starts all (both) services at once. If you want more fine-grained control for
starting services individually, please check-out the next section.
**Listing available Services**
```shell
engineuser:/opt/aura/engine$ supervisorctl avail
```
You should get these all services with their actual state listed:
```c++
aura-engine in use auto 666:666
aura-engine-api in use auto 999:999
```
**Maintanence using Supervisor**
Please remember to call all `supervisorctl` commands from within your engine home directory (`/opt/aura/engine/`),
to pickup the correct `supervisord.conf`.
**Starting Services**
```shell
supervisorctl start <service-name>
```
**Stopping Services**
```shell
supervisorctl stop <service-name>
```
**Restarting Services**
```shell
supervisorctl restart <service-name>
```
**Refresh after changing configurations**
```shell
supervisorctl restart <service-name>
```
**Start the API service with Supervisor**
```shell
sudo supervisorctl update
sudo supervisorctl restart engine-api
```
In case you want to reload whole supervisor service
```shell
sudo service supervisor restart
```
## Logging
All Engine logs for production can be found under:
```shell
`/var/log/aura`
```
This includes individual logs from Engine Core, Liquidsoap and the API.
But also `stdout` outputs from supervisor services are written there.
Additionally you'll finde Supervisor specific logs under:
```
`/var/log/supervisor`
```
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
# Running Engine with Docker
Docker provides a simple way to get your engine with all dependencies running.
Here you can find the official AURA Engine Docker images:
https://hub.docker.com/repository/docker/autoradio/engine
> Note: The Engine Docker image is in *POC* state and waiting to be fully implemented.
It's not yet ready to be offically used. If you want to try AURA Engine meanwhile
try the [Standard Installation](docs/installation-development).
<!-- TOC -->
- [Running Engine with Docker](#running-engine-with-docker)
- [Start an image](#start-an-image)
- [Configure an image](#configure-an-image)
- [Making Docker Releases](#making-docker-releases)
- [Build your own, local Docker image](#build-your-own-local-docker-image)
- [Releasing a new version to DockerHub](#releasing-a-new-version-to-dockerhub)
- [Read more](#read-more)
<!-- /TOC -->
## Start an image
*To be extended ...*
## Configure an image
*To be extended ...*
## Making Docker Releases
This section is only relevant if you are an Engine Developer.
### Build your own, local Docker image
```shell
./run.sh docker:build
```
### Releasing a new version to DockerHub
```shell
./run.sh docker:push
```
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
\ No newline at end of file
# Setting up the Audio Store
The *Audio Store* is a folder which is utilized by AURA Tank and Engine to exchange audio files.
Assuming AURA Engine and Tank are hosted on different machines, the `audiofolder` must by shared
using some network share.
In case you are hosting Engine and Tank on the same machine (e.g. in development), you can skip
this documentation. Just think about pointing them to the same directory.
<!-- TOC -->
- [Setting up the Audio Store](#setting-up-the-audio-store)
- [Share Location](#share-location)
- [Share Type](#share-type)
- [Setting up SSHFS](#setting-up-sshfs)
- [Configuring Engine](#configuring-engine)
- [Configuring Tank](#configuring-tank)
- [Read more](#read-more)
<!-- /TOC -->
By default Engine expects audio files shared by Tank in `/var/audio`.
This can be configurated in `engine.ini`:
```ini
[audiofolder]
audiofolder="/var/audio"
```
Now, this folder must be somehow writable by Tank.
## Share Location
You have following options where your share can be located:
1. **Engine and all other AURA components (Tank, Dashboard, Steering) are running on the same instance.** This is the most simple solution,
as Engine and Tank can share the same directory locally. But this scenario requires some more sophisticated tuning of the system resources
to avoid e.g. some overload of multiple Uploads in Tank may affect the performance of engine. You can eliminate this risk by setting CPU and
memory limits for Steering, Dashboard and Tank using Docker or `systemd-cgroups`. A disadvantage here is the case of maintainence of system
reboot. This would mean that all components are offline at once.
2. **Physical directory where the Engine lives, mounted to Tank**. This may cause an issue with the mount, when no network connection to Engine
is unavailable or the instance is rebooting.
3. **Physical directory where the Tank lives, mounted to Engine.** This may cause an issue with the mount, when no network connection to Tank
is unavailable or the instance is rebooting.
4. **Central Data Store or *Storage Box*** which is mountet to Engine and Tank. In this case a downtime of the store make both, Engine and Tank
dysfunctional.
5. **Replicated storage solution using [Gluster](https://www.gluster.org/), both Engine and Tank have their virtual audio directory mounted.**
That's the ideal approach, because if any of the instances is down, the other has all the data available.
In any case, you should think about some backup solution involving this directory.
## Share Type
Then, there's the question how the share is managed. Beside other you have the options
to use [NFS](https://en.wikipedia.org/wiki/Network_File_System), [SSHFS](https://en.wikipedia.org/wiki/SSHFS) or even something like [Gluster](https://www.gluster.org/).
For our initial setup we have chosen to use *SSHFS*.
Please share your experience with other share types, and we will include it in further releases of
this documentation.
## Setting up SSHFS
SSHFS allows you to access the filesystem on a remote computer via SSH. Interaction with files and folders behaves similar to any local data.
This example is setting up the `audiofolder` on the Engine instance.
### Configuring Engine
First, you'll need to create an user which enables Tank to access the `audiofolder` on Engine:
```shell
adduser tankuser
chown tankuser:engineuser /var/audio
```
Ensure that `engineuser` has no permissions to write the directory:
```shell
chmod u=+rwx,go=+rx-w /var/audio
```
### Configuring Tank
On the Tank side you need to install `sshfs`:
```shell
sudo apt-get install sshfs
```
Then create an `audio-store` folder inside the AURA home:
```shell
:/opt/aura/$ mkdir audio-store
```
Try if you can connect to the engine over SSH using your `tankuser`:
```shell
ssh tankuser@192.168.0.111 -p22
```
Replace `-p22` with the actual port number your SSH service is running with. For security reasons it's recommended
to run SSH not over the default port 22.
Uncomment following setting in `/etc/fuse.conf` to allow the tank-user access the share with write permissions:
```conf
# Allow non-root users to specify the allow_other or allow_root mount options.
user_allow_other
```
Now create the mount:
```shell
sudo sshfs -o allow_other -o IdentityFile=~/.ssh/id_rsa tankuser@192.168.0.111:/var/audio /opt/aura/audio-store -p22
```
Replace `192.168.0.111` with the actual IP for your Engine and `-p22` with the actual port number your Engine's SSH service
is running with.
To make this mount persistent i.e. keep it alive even after a system reboot, you'll need to add a configuration
in the `/etc/fstab` file by adding this at the end:
```yaml
# Audio Store @ AURA Engine
sshfs#tankuser@192.168.0.111:/var/audio /opt/aura/audio-store fuse auto,port=22,identityfile=~/.ssh/id_rsa,allow_other,_netdev 0 0
```
Again, check for the correct port number in the line above.
To take this into effect you'll need to remount the filesystem with `sudo mount -a` or reboot the machine. When mounting
you'll need to authenticate with the `tankuser` password once.
Then review if your Tank's Docker configuration mounts the exact same volume (`/opt/aura/audio-store`).
If not edit the tank configuration `/etc/aura/tank.env` and set following property:
```shell
TANK_STORE_PATH=/opt/aura/audio-store
```
Finally, do some testing if the directory is writable from Tank's system (`touch some-file`) and if the Engine's side can read
this file. Then restart your Tank Docker container and you should be good to go.
## Read more
- [Overview](/README.md)
- [Installation for Development](installation-development.md)
- [Installation for Production](installation-production.md)
- [Running with Docker](running-docker.md)
- [Setup the Audio Store](docs/setup-audio-store.md)
- [Developer Guide](developer-guide.md)
- [Engine Features](engine-features.md)
- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md)
#!/usr/bin/env python3.7
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
import sys
import signal
import logging
import subprocess
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from modules.base.logger import AuraLogger
from modules.base.config import AuraConfig
from modules.base.utils import SimpleUtil
config = AuraConfig()
def configure_flask():
app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri()
app.config['BABEL_DEFAULT_LOCALE'] = 'de'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# FIXME Instatiate SQLAlchemy without the need for Flask
app = Flask(__name__)
configure_flask()
DB = SQLAlchemy(app)
class AuraEngine:
"""
AuraEngine does the following:
1. Initialize the soundsystem and scheduler
2. Initialize Redis
3. Start Liquidsoap in a separate thread which connects to the engine
"""
logger = None
config = None
server = None
messenger = None
controller = None
soundsystem = None
scheduler = None
lqs = None
lqs_startup = None
def __init__(self):
"""
Initializes Engine Core.
"""
self.config = config
def startup(self, lqs_startup):
"""
Starts Engine Core.
"""
AuraLogger(self.config)
self.logger = logging.getLogger("AuraEngine")
from modules.scheduling.scheduler import AuraScheduler
from modules.core.engine import SoundSystem
from modules.cli.redis.adapter import ServerRedisAdapter
# If Liquidsoap should be started automatically
self.lqs_startup = lqs_startup
# Check if the database has to be re-created
if self.config.get("recreate_db") is not None:
AuraScheduler(self.config, None, None)
# Create scheduler and Liquidsoap communicator
self.soundsystem = SoundSystem(self.config)
self.scheduler = AuraScheduler(self.config, self.soundsystem, self.on_initialized)
# Create the Redis adapter
self.messenger = ServerRedisAdapter(self.config)
self.messenger.scheduler = self.scheduler
self.messenger.soundsystem = self.soundsystem
# And finally wait for redis message / start listener thread
self.messenger.start()
def on_initialized(self):
"""
Called when the engine is initialized, before the Liquidsoap connection is established."
"""
self.logger.info(SimpleUtil.green("Engine Core initialized - Waiting for Liquidsoap connection ..."))
if self.lqs_startup:
self.start_lqs(False, False)
else:
self.logger.info(SimpleUtil.yellow("Please note, Liquidsoap needs to be started manually."))
def start_lqs(self, debug_output, verbose_output):
"""
Starts Liquidsoap.
"""
lqs_path = self.config.get("liquidsoap_path")
lqs_cwd = os.getcwd() + "/" + self.config.get("liquidsoap_working_dir")
lqs_output = ""
lqs_output = self.get_debug_flags(debug_output, verbose_output)
self.lqs = subprocess.Popen([lqs_path, lqs_output, "engine.liq"], \
cwd=lqs_cwd, \
stdout=subprocess.PIPE, \
shell=False)
def get_lqs_cmd(self, debug_output, verbose_output):
"""
Returns a shell command string to start Liquidsoap
"""
lqs_path = self.config.get("liquidsoap_path")
lqs_cwd = os.getcwd() + "/" + self.config.get("liquidsoap_working_dir")
lqs_output = self.get_debug_flags(debug_output, verbose_output)
return "(cd %s && %s %s ./engine.liq)" % (lqs_cwd, lqs_path, lqs_output)
def get_debug_flags(self, debug_output, verbose_output):
"""
Build Liquidssoap debug parameters.
"""
output = ""
if debug_output:
output += "--debug "
if verbose_output:
output += "--verbose "
return output
def exit_gracefully(self, signum, frame):
"""
Shutdown of the engine. Also terminates the Liquidsoap thread.
"""
if self.lqs:
self.lqs.terminate()
self.logger.info("Terminated Liquidsoap")
if self.messenger:
self.messenger.terminate()
self.logger.info("Gracefully terminated Aura Engine!" + str(self.lqs))
sys.exit(0)
#
# START THE ENGINE
#
if __name__ == "__main__":
engine = AuraEngine()
start_lqs = True
lqs_cmd = False
signal.signal(signal.SIGINT, engine.exit_gracefully)
signal.signal(signal.SIGTERM, engine.exit_gracefully)
if len(sys.argv) >= 2:
if "--without-lqs" in sys.argv:
start_lqs = False
if "--get-lqs-command" in sys.argv:
lqs_cmd = True
if "--use-test-data" in sys.argv:
engine.config.set("use_test_data", True)
if "--recreate-database" in sys.argv:
engine.config.set("recreate_db", True)
if lqs_cmd:
print(engine.get_lqs_cmd(True, True))
else:
engine.startup(start_lqs)
#!/usr/bin/env python3.7
#
# Aura Engine (https://gitlab.servus.at/aura/engine)
#
# Copyright (C) 2017-2020 - The Aura Engine Team.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import time
import sys
import redis
from argparse import ArgumentParser
from modules.cli.padavan import Padavan
from modules.base.exceptions import PlaylistException
from modules.base.config import AuraConfig
class Guru():
"""
Command Line Interface (CLI) for Aura Engine.
"""
# config_path = "%s/configuration/engine.ini" % Path(__file__).parent.absolute()
config = AuraConfig()
parser = None
args = None
# ------------------------------------------------------------------------------------------ #
def __init__(self):
self.init_argument_parser()
self.handle_arguments()
def handle_arguments(self):
if self.args.stoptime:
start = time.time()
if not self.args.quiet:
print("Guru thinking...")
try:
p = Padavan(self.args, self.config)
p.meditate()
except PlaylistException as pe:
# typically there is no next file found
if not self.args.quiet:
print(pe)
else:
print("")
exit(4)
except redis.exceptions.TimeoutError:
print("Timeout when waiting for redis message. Is AURA daemon running? Exiting...")
exit(3)
if not self.args.quiet:
print("...result: ")
if p.stringreply != "":
#print(p.stringreply)
if p.stringreply[len(p.stringreply)-1] == "\n":
print(p.stringreply[0:len(p.stringreply) - 1])
else:
print(p.stringreply[0:len(p.stringreply)])
if self.args.stoptime:
end = time.time()
exectime = end-start
print("execution time: "+str(exectime)+"s")
def init_argument_parser(self):
try:
self.create_parser()
self.args = self.parser.parse_args()
except (ValueError, TypeError) as e:
if self.parser is not None:
self.parser.print_help()
print()
print(e)
exit(1)
def create_parser(self):
self.parser = ArgumentParser()
# options
self.parser.add_argument("-sep", "--stop-execution-time", action="store_true", dest="stoptime", default=False, help="Prints the execution time at the end of the skript")
self.parser.add_argument("-q", "--quiet", action="store_true", dest="quiet", default=False, help="Just the result will outputed to stout")
self.parser.add_argument("-rd", "--recreate-database", action="store_true", dest="recreatedb", default=False, help="Do you want to recreate the database?")
# getter
self.parser.add_argument("-pcs", "--print-connection-status", action="store_true", dest="get_connection_status", default=False, help="Prints the status of the connection to liquidsoap, pv and tank")
self.parser.add_argument("-gam", "--get-active-mixer", action="store_true", dest="mixer_channels_selected",default=False, help="Which mixer channels are selected?")
self.parser.add_argument("-pms", "--print-mixer-status", action="store_true", dest="mixer_status", default=False, help="Prints all mixer sources and their states")
self.parser.add_argument("-pap", "--print-act-programme", action="store_true", dest="get_act_programme", default=False, help="Prints the actual Programme, the controller holds")
self.parser.add_argument("-s", "--status", action="store_true", dest="get_status", default=False, help="Returns the Engine Status as JSON")
# liquid manipulation
self.parser.add_argument("-am", "--select-mixer", action="store", dest="select_mixer", default=-1, metavar="MIXERNAME", help="Which mixer should be activated?")
self.parser.add_argument("-dm", "--de-select-mixer", action="store", dest="deselect_mixer", default=-1, metavar="MIXERNAME", help="Which mixer should be activated?")
self.parser.add_argument("-vm", "--volume", action="store", dest="set_volume", default=0, metavar=("MIXERNUM", "VOLUME"), nargs=2, help="Set volume of a mixer source", type=int)
# shutdown server
self.parser.add_argument("-sd", "--shutdown", action="store_true", dest="shutdown", default=False, help="Shutting down aura server")
# playlist in/output
self.parser.add_argument("-fnp", "--fetch-new-programmes", action="store_true", dest="fetch_new_programme", default=False, help="Fetch new programmes from api_steering_calendar in engine.ini")
self.parser.add_argument("-pmq", "--print-message-queue", action="store_true", dest="print_message_queue", default=False, help="Prints message queue")
# send a redis message
self.parser.add_argument("-rm", "--redis-message", action="store", dest="redis_message", default=False, metavar=("CHANNEL", "MESSAGE"), nargs=2, help="Send a redis message to the Listeners")
# calls from liquidsoap
self.parser.add_argument("-gnf", "--get-next-file-for", action="store", dest="get_file_for", default=False, metavar="PLAYLISTTYPE", help="For which type you wanna GET a next audio file?")
self.parser.add_argument("-snf", "--set-next-file-for", action="store", dest="set_file_for", default=False, metavar=("PLAYLISTTYPE", "FILE"), nargs=2, help="For which type you wanna SET a next audio file?")
self.parser.add_argument("-np", "--now-playing", action="store_true", dest="now_playing", default=False, help="Which source is now playing")
self.parser.add_argument("-ip", "--init-player", action="store_true", dest="init_player", default=False, help="Reset liquidsoap volume and mixer activations?")
self.parser.add_argument("-ts", "--on_play", action="store", dest="on_play", default=False, metavar="INFO", help="Event handling when some entry started playing")
if len(sys.argv) == 1:
raise ValueError("No Argument passed!")
def valid_playlist_entry(argument):
from datetime import datetime
try:
index = int(argument[0])
fromtime = datetime.strptime(argument[1], "%Y-%m-%d")
source = argument[2]
return index, fromtime, source
except:
msg = "Not a valid date: '{0}'.".format(argument[0])
print(msg)
raise
# # ## ## ## ## ## # #
# # ENTRY FUNCTION # #
# # ## ## ## ## ## # #
def main():
Guru()
# # ## ## ## ## ## ## # #
# # End ENTRY FUNCTION # #
# # ## ## ## ## ## ## # #
if __name__ == "__main__":
main()
#!/bin/bash
mode="dev"
if [[ $* =~ ^(prod)$ ]]; then
mode="prod"
fi
if [ $mode == "dev" ]; then
echo "[Installing AURA ENGINE for Development]"
fi
if [ $mode == "prod" ]; then
echo "[Installing AURA ENGINE for Production]"
fi
# Development and Production
echo "Installing OPAM Packages ..."
bash script/install-opam-packages.sh
echo "Installing Python Requirements ..."
python3.7 $(which pip3) install -r requirements.txt
# Development
if [ $mode == "dev" ]; then
echo "Create local 'logs' Folder ..."
mkdir -p logs
echo "Copy configuration to './configuration/engine.ini'"
cp -n configuration/sample-development.engine.ini configuration/engine.ini
fi
# Production
if [ $mode == "prod" ]; then
echo "Create local 'tmp' Folder ..."
mkdir -p tmp
echo "Copy default Engine configuration to '/etc/aura/engine.ini'"
cp -n configuration/sample-production.engine.ini /etc/aura/engine.ini
fi
echo
echo "+++ Installation of AURA Engine finished! +++"
echo
\ No newline at end of file