diff --git a/.gitignore b/.gitignore index b179bf0378fc2e4fb74907acda433ba71686a009..37e2980805c4f43abdb7bd04cc3e465905bb3ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,5 @@ logs tmp .vscode/tags configuration/engine.ini -configuration/gunicorn.conf.py -web/clock.html -web/trackservice.html script/.engine.install-db.lock .engine.install-db.lock \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d7df05edfd13b971dd4437692392e5403dc4a989..44f3267c752963fbf4853fa7358f730d6992c345 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,7 +72,6 @@ RUN pip3 install -r /tmp/requirements.txt # Default configuration COPY configuration/sample-docker.engine.ini /srv/configuration/engine.ini -COPY configuration/sample-docker.gunicorn.conf.py /srv/configuration/gunicorn.conf.py # Update OPAM @@ -86,7 +85,3 @@ RUN opam install depext -y RUN opam depext taglib mad lame vorbis flac opus cry samplerate pulseaudio bjack alsa ssl liquidsoap -y RUN opam install taglib mad lame vorbis flac opus cry samplerate pulseaudio bjack alsa ssl liquidsoap -y RUN eval $(opam env) - -# Expose the API - -EXPOSE 3333 \ No newline at end of file diff --git a/README.md b/README.md index 5b7f001480c013a48da706c000490d4e73ceb91f..36afba3aeec94a0724416d3ba9c287be848ce712 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,9 @@ the requirements of community radios. - Multichannel Line-out - Blank Detenction / Silence Detecter - Auto Pilot a.k.a. Fallback Handling -- API to query Track-Service -- API to query monthly reports -- API to query data for a studio clock -- Web Application for displaying the Track-Service -- Web Application for displaying the studio clock +- API to query Track-Service, monthly reports and information for displaying the Studio Clock (see [Engine API](https://gitlab.servus.at/aura/engine-api)) +- Web Application for displaying the Track-Service (see [AURA Player](https://gitlab.servus.at/aura/player)) +- Web Application for displaying the Studio Clock (see [Engine Clock](https://gitlab.servus.at/aura/engine-clock)) Read more on the [Engine Features](docs/engine-features.md) page. @@ -50,7 +48,6 @@ To learn more, checkout the [Engine Developer Guide](docs/developer-guide.md) or - [Installation for Production](docs/installation-production.md) - [Running with Docker](docs/running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](docs/configuration-guide.md) ## Read more diff --git a/configuration/sample-docker.gunicorn.conf.py b/configuration/sample-docker.gunicorn.conf.py deleted file mode 100644 index 7034f54c5578e0380706e24c99adac9c9d350a9a..0000000000000000000000000000000000000000 --- a/configuration/sample-docker.gunicorn.conf.py +++ /dev/null @@ -1,217 +0,0 @@ -# Sample Gunicorn configuration file. - -# -# Server socket -# -# bind - The socket to bind. -# -# A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. -# An IP is a valid HOST. -# -# backlog - The number of pending connections. This refers -# to the number of clients that can be waiting to be -# served. Exceeding this number results in the client -# getting an error when attempting to connect. It should -# only affect servers under significant load. -# -# Must be a positive integer. Generally set in the 64-2048 -# range. -# - -pythonpath = "/opt/aura/engine" -bind = '172.17.0.1:3333' -backlog = 2048 - -# -# Worker processes -# -# workers - The number of worker processes that this server -# should keep alive for handling requests. -# -# A positive integer generally in the 2-4 x $(NUM_CORES) -# range. You'll want to vary this a bit to find the best -# for your particular application's work load. -# -# worker_class - The type of workers to use. The default -# sync class should handle most 'normal' types of work -# loads. You'll want to read -# http://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type -# for information on when you might want to choose one -# of the other worker classes. -# -# A string referring to a Python path to a subclass of -# gunicorn.workers.base.Worker. The default provided values -# can be seen at -# http://docs.gunicorn.org/en/latest/settings.html#worker-class -# -# worker_connections - For the eventlet and gevent worker classes -# this limits the maximum number of simultaneous clients that -# a single process can handle. -# -# A positive integer generally set to around 1000. -# -# timeout - If a worker does not notify the master process in this -# number of seconds it is killed and a new worker is spawned -# to replace it. -# -# Generally set to thirty seconds. Only set this noticeably -# higher if you're sure of the repercussions for sync workers. -# For the non sync workers it just means that the worker -# process is still communicating and is not tied to the length -# of time required to handle a single request. -# -# keepalive - The number of seconds to wait for the next request -# on a Keep-Alive HTTP connection. -# -# A positive integer. Generally set in the 1-5 seconds range. -# - -workers = 4 -worker_class = 'sync' -worker_connections = 1000 -timeout = 30 -keepalive = 2 - -# -# spew - Install a trace function that spews every line of Python -# that is executed when running the server. This is the -# nuclear option. -# -# True or False -# - -spew = False - -# -# Server mechanics -# -# daemon - Detach the main Gunicorn process from the controlling -# terminal with a standard fork/fork sequence. -# -# True or False -# -# raw_env - Pass environment variables to the execution environment. -# -# pidfile - The path to a pid file to write -# -# A path string or None to not write a pid file. -# -# user - Switch worker processes to run as this user. -# -# A valid user id (as an integer) or the name of a user that -# can be retrieved with a call to pwd.getpwnam(value) or None -# to not change the worker process user. -# -# group - Switch worker process to run as this group. -# -# A valid group id (as an integer) or the name of a user that -# can be retrieved with a call to pwd.getgrnam(value) or None -# to change the worker processes group. -# -# umask - A mask for file permissions written by Gunicorn. Note that -# this affects unix socket permissions. -# -# A valid value for the os.umask(mode) call or a string -# compatible with int(value, 0) (0 means Python guesses -# the base, so values like "0", "0xFF", "0022" are valid -# for decimal, hex, and octal representations) -# -# tmp_upload_dir - A directory to store temporary request data when -# requests are read. This will most likely be disappearing soon. -# -# A path to a directory where the process owner can write. Or -# None to signal that Python should choose one on its own. -# - -daemon = False -raw_env = [ - 'DJANGO_SECRET_KEY=something', - 'SPAM=eggs', -] -pidfile = None -umask = 0 -user = None -group = None -tmp_upload_dir = None - -# -# Logging -# -# logfile - The path to a log file to write to. -# -# A path string. "-" means log to stdout. -# -# loglevel - The granularity of log output -# -# A string of "debug", "info", "warning", "error", "critical" -# - -errorlog = '-' -loglevel = 'info' -accesslog = '-' -access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' - -# -# Process naming -# -# proc_name - A base to use with setproctitle to change the way -# that Gunicorn processes are reported in the system process -# table. This affects things like 'ps' and 'top'. If you're -# going to be running more than one instance of Gunicorn you'll -# probably want to set a name to tell them apart. This requires -# that you install the setproctitle module. -# -# A string or None to choose a default of something like 'gunicorn'. -# - -proc_name = None - -# -# Server hooks -# -# post_fork - Called just after a worker has been forked. -# -# A callable that takes a server and worker instance -# as arguments. -# -# pre_fork - Called just prior to forking the worker subprocess. -# -# A callable that accepts the same arguments as after_fork -# -# pre_exec - Called just prior to forking off a secondary -# master process during things like config reloading. -# -# A callable that takes a server instance as the sole argument. -# - -def post_fork(server, worker): - server.log.info("Worker spawned (pid: %s)", worker.pid) - -def pre_fork(server, worker): - pass - -def pre_exec(server): - server.log.info("Forked child, re-executing.") - -def when_ready(server): - server.log.info("Server is ready. Spawning workers") - -def worker_int(worker): - worker.log.info("worker received INT or QUIT signal") - - ## get traceback info - import threading, sys, traceback - id2name = {th.ident: th.name for th in threading.enumerate()} - code = [] - for threadId, stack in sys._current_frames().items(): - code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), - threadId)) - for filename, lineno, name, line in traceback.extract_stack(stack): - code.append('File: "%s", line %d, in %s' % (filename, - lineno, name)) - if line: - code.append(" %s" % (line.strip())) - worker.log.debug("\n".join(code)) - -def worker_abort(worker): - worker.log.info("worker received SIGABRT signal") \ No newline at end of file diff --git a/configuration/sample-production.gunicorn.conf.py b/configuration/sample-production.gunicorn.conf.py deleted file mode 100644 index ec5cc9566d8c180602099ca44990587985b4c5f2..0000000000000000000000000000000000000000 --- a/configuration/sample-production.gunicorn.conf.py +++ /dev/null @@ -1,217 +0,0 @@ -# Sample Gunicorn configuration file. - -# -# Server socket -# -# bind - The socket to bind. -# -# A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. -# An IP is a valid HOST. -# -# backlog - The number of pending connections. This refers -# to the number of clients that can be waiting to be -# served. Exceeding this number results in the client -# getting an error when attempting to connect. It should -# only affect servers under significant load. -# -# Must be a positive integer. Generally set in the 64-2048 -# range. -# - -pythonpath = "/opt/aura/engine" -bind = '127.0.0.1:3333' -backlog = 2048 - -# -# Worker processes -# -# workers - The number of worker processes that this server -# should keep alive for handling requests. -# -# A positive integer generally in the 2-4 x $(NUM_CORES) -# range. You'll want to vary this a bit to find the best -# for your particular application's work load. -# -# worker_class - The type of workers to use. The default -# sync class should handle most 'normal' types of work -# loads. You'll want to read -# http://docs.gunicorn.org/en/latest/design.html#choosing-a-worker-type -# for information on when you might want to choose one -# of the other worker classes. -# -# A string referring to a Python path to a subclass of -# gunicorn.workers.base.Worker. The default provided values -# can be seen at -# http://docs.gunicorn.org/en/latest/settings.html#worker-class -# -# worker_connections - For the eventlet and gevent worker classes -# this limits the maximum number of simultaneous clients that -# a single process can handle. -# -# A positive integer generally set to around 1000. -# -# timeout - If a worker does not notify the master process in this -# number of seconds it is killed and a new worker is spawned -# to replace it. -# -# Generally set to thirty seconds. Only set this noticeably -# higher if you're sure of the repercussions for sync workers. -# For the non sync workers it just means that the worker -# process is still communicating and is not tied to the length -# of time required to handle a single request. -# -# keepalive - The number of seconds to wait for the next request -# on a Keep-Alive HTTP connection. -# -# A positive integer. Generally set in the 1-5 seconds range. -# - -workers = 4 -worker_class = 'sync' -worker_connections = 1000 -timeout = 30 -keepalive = 2 - -# -# spew - Install a trace function that spews every line of Python -# that is executed when running the server. This is the -# nuclear option. -# -# True or False -# - -spew = False - -# -# Server mechanics -# -# daemon - Detach the main Gunicorn process from the controlling -# terminal with a standard fork/fork sequence. -# -# True or False -# -# raw_env - Pass environment variables to the execution environment. -# -# pidfile - The path to a pid file to write -# -# A path string or None to not write a pid file. -# -# user - Switch worker processes to run as this user. -# -# A valid user id (as an integer) or the name of a user that -# can be retrieved with a call to pwd.getpwnam(value) or None -# to not change the worker process user. -# -# group - Switch worker process to run as this group. -# -# A valid group id (as an integer) or the name of a user that -# can be retrieved with a call to pwd.getgrnam(value) or None -# to change the worker processes group. -# -# umask - A mask for file permissions written by Gunicorn. Note that -# this affects unix socket permissions. -# -# A valid value for the os.umask(mode) call or a string -# compatible with int(value, 0) (0 means Python guesses -# the base, so values like "0", "0xFF", "0022" are valid -# for decimal, hex, and octal representations) -# -# tmp_upload_dir - A directory to store temporary request data when -# requests are read. This will most likely be disappearing soon. -# -# A path to a directory where the process owner can write. Or -# None to signal that Python should choose one on its own. -# - -daemon = False -raw_env = [ - 'DJANGO_SECRET_KEY=something', - 'SPAM=eggs', -] -pidfile = None -umask = 0 -user = None -group = None -tmp_upload_dir = None - -# -# Logging -# -# logfile - The path to a log file to write to. -# -# A path string. "-" means log to stdout. -# -# loglevel - The granularity of log output -# -# A string of "debug", "info", "warning", "error", "critical" -# - -errorlog = '-' -loglevel = 'info' -accesslog = '-' -access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"' - -# -# Process naming -# -# proc_name - A base to use with setproctitle to change the way -# that Gunicorn processes are reported in the system process -# table. This affects things like 'ps' and 'top'. If you're -# going to be running more than one instance of Gunicorn you'll -# probably want to set a name to tell them apart. This requires -# that you install the setproctitle module. -# -# A string or None to choose a default of something like 'gunicorn'. -# - -proc_name = None - -# -# Server hooks -# -# post_fork - Called just after a worker has been forked. -# -# A callable that takes a server and worker instance -# as arguments. -# -# pre_fork - Called just prior to forking the worker subprocess. -# -# A callable that accepts the same arguments as after_fork -# -# pre_exec - Called just prior to forking off a secondary -# master process during things like config reloading. -# -# A callable that takes a server instance as the sole argument. -# - -def post_fork(server, worker): - server.log.info("Worker spawned (pid: %s)", worker.pid) - -def pre_fork(server, worker): - pass - -def pre_exec(server): - server.log.info("Forked child, re-executing.") - -def when_ready(server): - server.log.info("Server is ready. Spawning workers") - -def worker_int(worker): - worker.log.info("worker received INT or QUIT signal") - - ## get traceback info - import threading, sys, traceback - id2name = {th.ident: th.name for th in threading.enumerate()} - code = [] - for threadId, stack in sys._current_frames().items(): - code.append("\n# Thread: %s(%d)" % (id2name.get(threadId,""), - threadId)) - for filename, lineno, name, line in traceback.extract_stack(stack): - code.append('File: "%s", line %d, in %s' % (filename, - lineno, name)) - if line: - code.append(" %s" % (line.strip())) - worker.log.debug("\n".join(code)) - -def worker_abort(worker): - worker.log.info("worker received SIGABRT signal") \ No newline at end of file diff --git a/configuration/scheduler.xml b/configuration/scheduler.xml deleted file mode 100644 index f8b4890d3c2e1f2bdc6e0ee0b31689590b560484..0000000000000000000000000000000000000000 --- a/configuration/scheduler.xml +++ /dev/null @@ -1,31 +0,0 @@ -<Config> - <Jobs multiple="true"> - <job> - <time>00:00</time> - <until>23:00</until> - <job>play_playlist</job> - <params>no_stop</params> - </job> - <job> - <job>start_recording</job> - <until>00:00</until> - <day>all</day> - <time>00:00</time> - <params>no_stop</params> - </job> - <job> - <daysolder>4</daysolder> - <job>clean_cached</job> - <day>1</day> - <time>00:03</time> - <params></params> - </job> - <job> - <time>01:00</time> - <day>all</day> - <job>precache</job> - <params></params> - </job> - </Jobs> -</Config> - diff --git a/configuration/supervisor/aura-engine-api.conf b/configuration/supervisor/aura-engine-api.conf deleted file mode 100644 index d4e5207135259e0423ae641d2ce12dd5fe98d872..0000000000000000000000000000000000000000 --- a/configuration/supervisor/aura-engine-api.conf +++ /dev/null @@ -1,13 +0,0 @@ -[program:aura-engine-api] -user = engineuser -directory = /opt/aura/engine -command = /opt/aura/engine/run.sh api - -priority = 999 -autostart = true -autorestart = true -stopsignal = TERM - -redirect_stderr = true -stdout_logfile = /var/log/aura/engine-api-stdout.log -stderr_logfile = /var/log/aura/engine-api-error.log \ No newline at end of file diff --git a/configuration/systemd/aura-engine-api.service b/configuration/systemd/aura-engine-api.service deleted file mode 100644 index 6cbca82a5d3b3fa0556d07ae56b5c23ce759cf69..0000000000000000000000000000000000000000 --- a/configuration/systemd/aura-engine-api.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=Aura Engine - API -After=network.target - -[Service] -Type=simple -User=engineuser -WorkingDirectory=/opt/aura/engine -ExecStart=/opt/aura/engine/run.sh api -Restart=always - -[Install] -WantedBy=multi-user.target diff --git a/docs/configuration-guide.md b/docs/configuration-guide.md deleted file mode 100644 index 53c24003842108f87883266a53ce85caafcba680..0000000000000000000000000000000000000000 --- a/docs/configuration-guide.md +++ /dev/null @@ -1,203 +0,0 @@ - -# Aura Engine Configuration Guide - -This page goes into detail on what is possible to configure within the engine. - -<!-- TOC --> - -- [Aura Engine Configuration Guide](#aura-engine-configuration-guide) - - [Station](#station) - - [Soundcard](#soundcard) - - [Recordings](#recordings) - - [Streams](#streams) - - [Fallbacks](#fallbacks) - - [Database](#database) - - [Monitoring](#monitoring) - - [API Endpoints](#api-endpoints) - - [Fading](#fading) - - [Logging](#logging) - - [User](#user) - - [Socket](#socket) - - [Redis](#redis) - - [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) - - [Read more](#read-more) - -<!-- /TOC --> - -## Station - -These properties are used to style the included web applications such as *Track Service* -and *Studio Clock* . - -Set the radio station name - -```ini -station_name="Radio Orange" -``` - -Set the URL to the radio station logo - -```ini -station_logo_url="https://your-radio.station/logo.png" -``` - -Set the `width` of the radio station logo - -```ini -station_logo_size="120px" -``` - -## Soundcard - -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**: Beside ALSA the sound servers such as -is supported. - -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 -``` - - -## Recordings - -You can configure up to **five** recorders in the `[recording]`. - -## Streams - -You can configure up to **five** streams in the `[streams]`. - -## Fallbacks - -Configure fallback handling in the `[fallback]` section. - -## Database - -Configure your engine database in the `[database]` section. - -## Monitoring - -Configure monitoring parameters such as admin emails in the `[monitoring]` section. - -## API Endpoints - -Configure connections to the other Aura components in the `[api]` section. - -Sets the API URL exposed to external clients. This is required by the included -web applications which access the API. - -```ini -exposed_api_url="https://your-radio.station/api/v3" -``` - -## Fading - -Configure fading parameters in the `[fading]` section. - -## Logging - -Configure log handling in the `[logging]` section. - -## User - -Configure the executing system user in the `[user]` section. - -## Socket - -Configure socket connectivity in the `[socket]` section. - -## Redis - -Configure Redis connectivity in the `[redis]` section. - - -## Frequently Asked Questions - -### 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 - * install the realtime kernel with - - ```bash - apt install linux-image-rt-amd64 - reboot - ``` - - or - * invest in better hardware - - -## 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) -- [Configuration Guide](configuration-guide.md) -- [Developer Guide](developer-guide.md) -- [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) diff --git a/docs/developer-guide.md b/docs/developer-guide.md index 1f822f8544d8b49ca697d5ee439aa15a5b98f384..6dae0345689673796a0eff856524d864e8d9cec7 100644 --- a/docs/developer-guide.md +++ b/docs/developer-guide.md @@ -8,12 +8,10 @@ This page gives insights on extending Aura Engine internals or through the API. - [AURA Componentes](#aura-componentes) - [Engine Components](#engine-components) - [API](#api) - - [Required Data Sources](#required-data-sources) - - [Provided API Endpoints](#provided-api-endpoints) - - [Web Applications using the Engine API](#web-applications-using-the-engine-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 --> @@ -50,66 +48,14 @@ There's a convenience script to start all of the three main dependencies (Steeri **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. It records everything and streams everything depending on your settings in aura.ini. - -**engine-api.py**: A Flask web server which provides the API endpoints. This component can be (re-) started independently from the core engine. +**Liquidsoap**: The heart of AURA Engine. It uses the built in mixer, to switch between different sources. ## API -### Required Data Sources - -The AURA Project "**Dashboard**" provides the GUI to organize shows, schedules/timelsots -and organize uploads in form of playlists. Those playlists can be organized in timeslots -using a fancy calendar interface. - -These data-sources need to be configurated in the "engine.ini" configuration file: - - # STEERING - api_steering_status = "http://localhost:8000/api/v1/" - # The URL to get the Calendar via Steering - api_steering_calendar="http://localhost:8000/api/v1/playout" - # The URL to get show details via Steering - api_steering_show="http://localhost:8000/api/v1/shows/${ID}/" - - -The AURA Project "**Tank**" on the other hand delivers information on the tracks, related playlists -to be played and its meta-data: - - # TANK - api_tank_status = "http://localhost:8040/ui/" - # The URL to get playlist details via Tank - api_tank_playlist="http://localhost:8040/api/v1/shows/${SLUG}/playlists" - - -You can find more information here: <https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md> - -### Provided API Endpoints - -**Soundserverstate:** Returns true and false values of the internal In- and Outputs - - /api/v1/soundserver_state - -**Trackservice:** - -/api/v1/trackservice/<selected_date> -/api/v1/trackservice/ - - -## Web Applications using the Engine API - -Under `./contrib` you'll find two Web Applications which utilize the Engine API: - -- [Track Service](contrib/aura-player/README.md) -- [Studio Clock](contrib/aura-clock/README.md) - -When you start the engine-api using +You can find the AURA API definition here: https://gitlab.servus.at/autoradio/meta/blob/master/api-definition.md -```shell - ./run.sh api-dev -``` +OpenAPI definition for Engine API: https://app.swaggerhub.com/apis/AURA-Engine/engine-api/ -this automatically builds these web applications and copies the resulting assets to the -relevant `./web/**` folders. ## More infos for debugging @@ -151,6 +97,43 @@ 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) @@ -158,7 +141,6 @@ Push some audio file to the filesystem `queue 0` - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/engine-features.md b/docs/engine-features.md index 9ca724f6f11cc94969cfc783e47a66777b3e84a1..9488fc30e86c3598bdd57166bf4cf38396c426ad 100644 --- a/docs/engine-features.md +++ b/docs/engine-features.md @@ -14,11 +14,9 @@ This page gives a more detailed overview of the Aura Engine features and how to - [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) - - [API Endpoints](#api-endpoints) - - [Web Applications](#web-applications) - [Monitoring](#monitoring) - [Send mails on errors and warnings](#send-mails-on-errors-and-warnings) - - [Engine Status Information](#engine-status-information) + - [Engine Health Information via Engine API](#engine-health-information-via-engine-api) - [Engine Heartbeat](#engine-heartbeat) - [Logging](#logging) - [Read more](#read-more) @@ -198,124 +196,6 @@ fallback_min_noise="0." fallback_threshold="-50." ``` - -## API Endpoints - -**Track Service API**: These endpoints provide information on the played tracks, their schedules and shows. - -* `/api/v1/trackservice` .............. Returns all Track Service entries for the current day -* `/api/v1/trackservice/$ID` .......... Returns a Track Service entry by ID -* `/api/v1/trackservice/current` ...... Returns the track currently playing -* `/api/v1/trackservice/day/$DAY` ..... Returns all tracks for a given day formated as `YYYY-MM-DD` - -The Swagger Specification of a Track Service entry as YAML looks like this: - -```yaml - components: - schemas: - TrackService: - properties: - album: {} - artist: {} - duration: {} - fallback: {} - id: {} - schedule_start: {} - title: {} - track_start: {} - type: object - info: - title: Swagger API Specification for Aura Engine - version: 1.0.0 - openapi: 3.0.2 - paths: {} -``` - -**Reporting API**: Create monthly reports using this endpoint. - -* `/api/v1/report/$MONTH` ...... Returns all playout details for the given month in the format `YYYY-MM` - -```yaml -schemas: - Report: - properties: - fallback_type: {} - id: {} - playlist_id: {} - schedule.category: {} - schedule.is_repetition: {} - schedule.languages: {} - schedule.musicfocus: {} - schedule.schedule_end: {} - schedule.schedule_id: {} - schedule.schedule_start: {} - schedule.show_funding_category: {} - schedule.show_hosts: {} - schedule.show_id: {} - schedule.show_name: {} - schedule.show_type: {} - schedule.topic: {} - schedule.type: {} - schedule_fallback_id: {} - show_fallback_id: {} - station_fallback_id: {} - track: {} - track_start: {} - type: object - info: - title: Swagger API Specification for Aura Engine - version: 1.0.0 - openapi: 3.0.2 -``` - -**Schedule API**: Retrieves information on the programme. - -* `/api/v1/schedule/upcoming` .. Returns the next three schedules, after the one currently playing. - -```yaml -schemas: - Schedule: - properties: - id: {} - schedule: {} - schedule_id: {} - schedule_start: {} - show_hosts: {} - show_id: {} - show_name: {} - show_type: {} - type: object - info: - title: Swagger API Specification for Aura Engine - version: 1.0.0 - openapi: 3.0.2 -``` - -**Clock API**: Retrieve all data relevant for a studio clock. - -* `/api/v1/clock` .............. Returns the current show, next show, playlist and time left until the next show. - -```yaml -schemas: - Clock: - properties: - current: {} - next: {} - track: {} - track_id: {} - track_start: {} - type: object - info: - title: Swagger API Specification for Aura Engine - version: 1.0.0 - openapi: 3.0.2 -``` - -## Web Applications - -* `/app/trackservice` ................. Web Application for displaying the Track-Service -* `/app/clock` ........................ Web Application for displaying the studio clock - ## Monitoring You have following options to monitor the Engine: @@ -346,12 +226,10 @@ from_mail="monitoring@aura.engine" mailsubject_prefix="[Aura Engine]" ``` -### Engine Status Information - -You can get various status fields & flags using the `/status` endpoint. +### Engine Health Information via Engine API -> Please note this is a rather expensive call. If you need to call this on a regular basis -for continious monitoring, the *Heartbeat* option might be preferred. +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 @@ -402,7 +280,6 @@ Additionally you'll finde Supervisor specific logs under`/var/log/supervisor`. - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/frequently-asked-questions.md b/docs/frequently-asked-questions.md index 0e4c9e298402dc469e4d62fdde59939274fb70d8..44447bef31a3d1e8d7d80e56366866c864f64f5b 100644 --- a/docs/frequently-asked-questions.md +++ b/docs/frequently-asked-questions.md @@ -4,12 +4,58 @@ <!-- 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** @@ -38,7 +84,6 @@ - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/installation-development.md b/docs/installation-development.md index b5cf3fe0890cdabed8c5d9764db212ce6b7c6206..a628aa58ebafffbabe5e0e5c07a08d8579fc49c5 100644 --- a/docs/installation-development.md +++ b/docs/installation-development.md @@ -126,17 +126,30 @@ Set the URLs to the *Steering* and *Tank* API: ```ini [api] -# STEERING -api_steering_status = "http://localhost:8000/api/v1/" +# 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://localhost:8000/api/v1/playout" +api_steering_calendar="http://aura.local:8000/api/v1/playout" # The URL to get show details via Steering -api_steering_show="http://localhost:8000/api/v1/shows/${ID}/" +api_steering_show="http://aura.local:8000/api/v1/shows/${ID}/" -# TANK -api_tank_status = "http://localhost:8040/healthz" +## 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://localhost:8040/api/v1/shows/${SLUG}/playlists" +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: @@ -163,7 +176,6 @@ Read about all other available settings in the [Configuration Guide](docs/config ## Running Engine - Use the convencience script `run.sh` to get engine started in different ways: **Run the Engine** @@ -192,19 +204,6 @@ Liquidsoap, you can run following: ./run.sh lqs ``` -**Run the Engine API** - -This requires to start the core component in another terminal. - -```shell - ./run.sh api-dev -``` - -In development mode Engine uses the default [Flask](https://palletsprojects.com/p/flask/) web server. -Please be careful not to use this type of server in your production environment. - -Check out more ways of running the engine in the [Developer Guide](docs/developer-guide.md). - ## Logging All Engine logs for development can be found under `./logs`. @@ -216,7 +215,6 @@ All Engine logs for development can be found under `./logs`. - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/installation-production.md b/docs/installation-production.md index dcd0b3fc70c7efd26a37fd40b973a838caaec41c..4afb67fa75fe8a95b1f11891805bf9ba1f48ac39 100644 --- a/docs/installation-production.md +++ b/docs/installation-production.md @@ -7,8 +7,8 @@ - [Installation](#installation) - [Configuration](#configuration) - [Running Engine](#running-engine) - - [The API Server](#the-api-server) - - [Maintanence using Supervisor](#maintanence-using-supervisor) + - [Running with Systemd](#running-with-systemd) + - [Running with Supervisor](#running-with-supervisor) - [Logging](#logging) - [Read more](#read-more) @@ -136,7 +136,6 @@ 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` -- Creates a default Gunicorn configuration file in `gunicorn.conf.py` 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. @@ -158,22 +157,35 @@ Now, specify at least following settings to get started: db_pass="---SECRET--PASSWORD---" ``` -Set the URLs to the *Steering* and *Tank* API: +Set the URLs to the *Steering*, *Tank* and *Engine* API: ```ini [api] -# STEERING -api_steering_status = "http://localhost:8000/api/v1/" +# 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://localhost:8000/api/v1/playout" +api_steering_calendar="http://aura.local:8000/api/v1/playout" # The URL to get show details via Steering -api_steering_show="http://localhost:8000/api/v1/shows/${ID}/" +api_steering_show="http://aura.local:8000/api/v1/shows/${ID}/" -# TANK -api_tank_status = "http://localhost:8040/healthz" +## 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://localhost:8040/api/v1/shows/${SLUG}/playlists" +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: @@ -183,17 +195,6 @@ Ensure that the Liquidsoap installation path is valid: liquidsoap_path="/home/engineuser/.opam/4.08.0/bin/liquidsoap" ``` -**Configuring the API Server** - -Set the correct IP in `/opt/aura/engine/configuration# nano gunicorn.conf.py` and -the exposed `exposed_api_url` in `engine.ini`. - -Also open the `api_port` defined in `engine.ini` in your `iptables` (Default is 3333) - -```shell -iptables -A INPUT -p tcp -m state --state NEW -m tcp --dport 3333 -j ACCEPT -``` - **Configuring the Audio Store** Finally Engine needs to be able to access the audio folder, where all the tracks of the playlists @@ -219,12 +220,13 @@ In production the process of starting the engine is slightly different compared 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 we utilize [Supervisor](http://supervisord.org/). +For this you can utilize either [Systemd](https://systemd.io/) or [Supervisor](http://supervisord.org/). -Also note, while running the engine might also work using a `systemd` service, the -recommened option to use in combination with Gunicorn ([API server](Running the API Server), see below), -is Supervisor. Beside others pros, Supervisor has the advantage that you are able to run services without -having superuser rights. +### 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: @@ -251,25 +253,14 @@ starting services individually, please check-out the next section. engineuser:/opt/aura/engine$ supervisorctl avail ``` -You should get these two services with their actual state listed: +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 ``` -## The API Server - -For production Engine API uses the WSGI HTTP Server [`Gunicorn`](https://gunicorn.org/). - -In production Gunicorn is used in combination with some proxy server, such as Nginx. - -> Although there are many HTTP proxies available, we strongly advise that you use Nginx. If you choose another proxy -server you need to make sure that it buffers slow clients when you use default Gunicorn workers. Without this buffering -Gunicorn will be easily susceptible to denial-of-service attacks. You can use Hey to check if your proxy is behaving properly. -— [**Gunicorn Docs**](http://docs.gunicorn.org/en/latest/deploy.html). - -## Maintanence using Supervisor +**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`. @@ -336,7 +327,6 @@ Additionally you'll finde Supervisor specific logs under: - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/running-docker.md b/docs/running-docker.md index 624ed453c76c2fda3509eb89666e9b57166dea68..5553bf0cab71b17b8fccc93586db9c94037dd786 100644 --- a/docs/running-docker.md +++ b/docs/running-docker.md @@ -54,7 +54,6 @@ This section is only relevant if you are an Engine Developer. - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) -- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) +- [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) \ No newline at end of file diff --git a/docs/setup-audio-store.md b/docs/setup-audio-store.md index d427e916c4aed5caefee43c725432582625d38b6..dffbceacb538f2bddd1c9ae1eef18e5d7a375a61 100644 --- a/docs/setup-audio-store.md +++ b/docs/setup-audio-store.md @@ -157,7 +157,6 @@ this file. Then restart your Tank Docker container and you should be good to go. - [Installation for Production](installation-production.md) - [Running with Docker](running-docker.md) - [Setup the Audio Store](docs/setup-audio-store.md) -- [Configuration Guide](configuration-guide.md) - [Developer Guide](developer-guide.md) - [Engine Features](engine-features.md) - [Frequently Asked Questions (FAQ)](docs/frequently-asked-questions.md) diff --git a/engine-api.py b/engine-api.py deleted file mode 100644 index 67eaeda5e99926156351bfe0d3f61725d251dfd8..0000000000000000000000000000000000000000 --- a/engine-api.py +++ /dev/null @@ -1,446 +0,0 @@ -#!/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 logging -import os, os.path -import subprocess -import json - -from datetime import datetime, date, timedelta - -from flask import Flask, Response -from flask_caching import Cache -from flask_cors import CORS -from flask_sqlalchemy import SQLAlchemy -from flask_marshmallow import Marshmallow -from marshmallow import Schema, fields, post_dump -from flask_restful import Api, Resource, abort -from apispec import APISpec -from apispec.ext.marshmallow import MarshmallowPlugin -from apispec_webframeworks.flask import FlaskPlugin -from werkzeug.exceptions import HTTPException, default_exceptions, Aborter - -from modules.base.logger import AuraLogger -from modules.base.config import AuraConfig -from modules.base.models import AuraDatabaseModel, Schedule, Playlist, PlaylistEntry, PlaylistEntryMetaData - - - - -# -# Initialize the Aura Web App and API. -# - -config = AuraConfig() -app = Flask(__name__, - static_url_path='', - static_folder='web/') - # static_folder='contrib/aura-player/public/') - -app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False -app.config["SQLALCHEMY_DATABASE_URI"] = config.get_database_uri() -app.config["CACHE_TYPE"] = "simple" -app.config["CACHE_DEFAULT_TIMEOUT"] = 0 -app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 -cache = Cache(app) -cors = CORS(app, resources={r"/*": {"origins": "*"}}) # FIXME Update CORS for production use -db = SQLAlchemy(app) -ma = Marshmallow(app) -api = Api(app) - - -# -# Werkzeug HTTP code mappings -# -class NoDataAvailable(HTTPException): - code = 204 - description = "There is currently no content available." -default_exceptions[204] = NoDataAvailable -abort = Aborter() - - -class EngineApi: - """ - Provides the Aura Engine API services. - """ - config = None - api = None - logger = None - - # trackservice_schema = None - - - def __init__(self, config, api): - """ - Initializes the API. - - Args: - config (AuraConfig): The Engine configuration. - api (Api): The Flask restful API object. - """ - self.config = config - self.logger = AuraLogger(self.config, "engine-api") - self.logger = logging.getLogger("engine-api") - self.api = api - - # Generate HTML files - self.generate_html("web/templates/clock.html", "web/clock.html") - self.generate_html("web/templates/trackservice.html", "web/trackservice.html") - - # API Spec - spec.components.schema("TrackService", schema=TrackServiceSchema) - spec.components.schema("Report", schema=ReportSchema) - spec.components.schema("Schedule", schema=ScheduleSchema) - spec.components.schema("Clock", schema=ClockDataSchema) - spec.components.schema("Status", schema=StatusSchema) - - # TODO Generates HTML for specification - #self.logger.info(spec.to_yaml()) - - # Schema instances - EngineApi.trackservice_schema = TrackServiceSchema(many=True) - EngineApi.track_schema = TrackServiceSchema() - EngineApi.report_schema = ReportSchema(many=True) - EngineApi.schedule_schema = ScheduleSchema(many=True) - EngineApi.clockdata_schema = ClockDataSchema() - EngineApi.status_schema = StatusSchema() - - # Define API routes - self.api.add_resource(TrackServiceResource, config.api_prefix + "/trackservice/") - self.api.add_resource(TrackResource, config.api_prefix + "/trackservice/<int:track_id>") - self.api.add_resource(CurrentTrackResource, config.api_prefix + "/trackservice/current") - self.api.add_resource(TracksByDayResource, config.api_prefix + "/trackservice/date/<string:date_string>") - self.api.add_resource(ReportResource, config.api_prefix + "/report/<string:year_month>") - self.api.add_resource(UpcomingSchedulesResource, config.api_prefix + "/schedule/upcoming") - self.api.add_resource(ClockDataResource, config.api_prefix + "/clock") - self.api.add_resource(StatusResource, "/status") - - self.logger.info("Engine API routes successfully set!") - - # Static resources - @app.route('/app/trackservice', methods=['GET']) - def trackservice(): - content = open(os.path.join("web/", "trackservice.html")) - return Response(content, mimetype="text/html") - - # Static resources - @app.route('/app/clock', methods=['GET']) - def clock(): - content = open(os.path.join("web/", "clock.html")) - return Response(content, mimetype="text/html") - - - def generate_html(self, src_file, target_file): - """ - Generates HTML based on the configuration options and templates. - - Args: - src_file (String): The template file - target_file (String): The HTML file to be generated - """ - src_file = open(src_file, "r") - target_file = open(target_file, "w") - content = src_file.read() - - config_options = { - "CONFIG-STATION-NAME": config.get("station_name"), - "CONFIG-STATION-LOGO-URL": config.get("station_logo_url"), - "CONFIG-STATION-LOGO-SIZE": config.get("station_logo_size"), - "CONFIG-API-URL": config.get("exposed_api_url") - } - - for key, value in config_options.items(): - content = content.replace(":::"+key+":::", value) - - target_file.write(content) - src_file.close() - target_file.close() - - - def run(self): - """ - Starts the API server. - """ - # Set debug=False if you want to use your native IDE debugger - self.api.app.run(port=self.config.api_port, debug=False) - - - - -# -# API SPEC -# - - -spec = APISpec( - title="Swagger API Specification for Aura Engine", - version="1.0.0", - openapi_version="3.0.2", - plugins=[FlaskPlugin(), MarshmallowPlugin()], -) - - -# -# API SCHEMA -# - - -class TrackServiceSchema(ma.Schema): - class Meta: - fields = ( - "id", - "schedule.schedule_id", - "schedule.schedule_start", - "schedule.schedule_end", - "schedule.languages", - "schedule.type", - "schedule.category", - "schedule.topic", - "schedule.musicfocus", - "schedule.is_repetition", - - "track", - "track_start", - - "show" - ) - - -class ClockDataSchema(ma.Schema): - class Meta: - fields = ( - "current", - "next", - "track_id", - "track_start", - "track" - ) - - -class ScheduleSchema(ma.Schema): - class Meta: - fields = ( - "id", - "schedule_id", - "schedule_start", - "schedule", - - "show_id", - "show_name", - "show_hosts", - "show_type" - ) - - - -class ReportSchema(ma.Schema): - class Meta: - fields = ( - "id", - "schedule.schedule_id", - "schedule.schedule_start", - "schedule.schedule_end", - "schedule.languages", - "schedule.type", - "schedule.category", - "schedule.topic", - "schedule.musicfocus", - "schedule.is_repetition", - - "schedule.show_id", - "schedule.show_name", - "schedule.show_hosts", - "schedule.show_type", - "schedule.show_funding_category", - - "track", - "track_start", - - "playlist_id", - "fallback_type", - "schedule_fallback_id", - "show_fallback_id", - "station_fallback_id" - ) - - -class StatusSchema(ma.Schema): - class Meta: - fields = ( - "engine", - "soundsystem", - "api", - "redis_ready", - "audio_store" - ) - - -# -# API RESOURCES -# - - -class TrackServiceResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self): - today = date.today() - today = datetime(today.year, today.month, today.day) - tracks = TrackService.select_by_day(today) - return EngineApi.trackservice_schema.dump(tracks) - - -class TrackResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self, track_id): - track = TrackService.select_one(track_id) - return EngineApi.track_schema.dump(track) - - -class ClockDataResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self): - item = TrackService.select_current() - next_schedule = Schedule.select_upcoming(1) - if next_schedule: - next_schedule = next_schedule[0].as_dict() - next_schedule["playlist"] = None - else: - next_schedule = {} - - clockdata = { - "track_id": item.id, - "track_start": item.track_start, - "track": item.track, - "current": {}, - "next": next_schedule - } - - if item.schedule: - clockdata["current"] = item.schedule.as_dict() - if item.schedule.playlist: - clockdata["current"]["playlist"] = item.schedule.playlist[0].as_dict() - - clockdata["current"]["show"] = item.show - return EngineApi.clockdata_schema.dump(clockdata) - - -class CurrentTrackResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self): - track = TrackService.select_current() - if not track: - return abort(204) # No content available - return EngineApi.track_schema.dump(track) - - -class TracksByDayResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self, date_string): - date = datetime.strptime(date_string, "%Y-%m-%d") - self.logger.debug("Query track-service by day: %s" % str(date)) - tracks = TrackService.select_by_day(date) - if not tracks: - return abort(204) # No content available - return EngineApi.trackservice_schema.dump(tracks) - - -class UpcomingSchedulesResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self): - now = datetime.now() - self.logger.debug("Query upcoming schedules after %s" % str(now)) - schedules = Schedule.select_upcoming(3) - if not schedules: - return abort(204) # No content available - return EngineApi.schedule_schema.dump(schedules) - - -class ReportResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self, year_month): - year = int(year_month.split("-")[0]) - month = int(year_month.split("-")[1]) - - first_day = datetime(year, month, 1) - next_month = first_day.replace(day=28) + timedelta(days=4) - next_month - timedelta(days=next_month.day) - - self.logger.debug("Query report for month: %s - %s" % (str(first_day), str(next_month))) - report = TrackService.select_by_range(first_day, next_month) - if not report: - return abort(204) # No content available - return EngineApi.report_schema.dump(report) - - -class StatusResource(Resource): - logger = None - - def __init__(self): - self.logger = logging.getLogger("engine-api") - - def get(self): - status = subprocess.check_output(["python3", "guru.py", "-s", "-q"]) - status = status.decode("utf-8").replace("'", '"') - status = json.loads(status, strict=False) - - if not status: - return abort(204) # No content available - return EngineApi.status_schema.dump(status) - - - -# -# Initialization calls -# - - -engine_api = EngineApi(config, api) - -if __name__ == "__main__": - engine_api.run() diff --git a/install.sh b/install.sh index 10027f6a55da605c96ef4f718416be335f4bbeb4..a7718e297bb4d3992c0e98d0da275304eb32bff1 100755 --- a/install.sh +++ b/install.sh @@ -33,8 +33,6 @@ if [ $mode == "dev" ]; then echo "Copy configuration to './configuration/engine.ini'" cp -n configuration/sample-development.engine.ini configuration/engine.ini - echo "Installing Web Application Packages ..." - bash script/install-web.sh fi @@ -48,15 +46,6 @@ if [ $mode == "prod" ]; then echo "Copy default Engine configuration to '/etc/aura/engine.ini'" cp -n configuration/sample-production.engine.ini /etc/aura/engine.ini - echo "Copy default Gunicorn configuration to '/etc/aura/engine.ini'" - cp -n configuration/sample-production.gunicorn.conf.py configuration/gunicorn.conf.py - - echo "Create Virtual Env for Gunicorn" - virtualenv -p /usr/bin/python3.7 ../python-env - source ../python-env/bin/activate - - echo "Install Requirements to Virtual Env" - pip3 install -r requirements.txt fi diff --git a/requirements.txt b/requirements.txt index 7db9e5992267a1bd838fb94e12d7b16a51206ce6..1c13beaf3270bf49497ec850a860b258b49891ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,6 @@ sqlalchemy==1.3.13 Flask==1.1.1 -Flask-Caching==1.8.0 Flask-SQLAlchemy==2.4.1 -Flask-RESTful==0.3.8 -flask-marshmallow==0.11.0 -flask-cors==3.0.8 -marshmallow-sqlalchemy==0.22.2 -apispec==3.3.0 -apispec-webframeworks==0.5.2 mysqlclient==1.3.12 redis==3.5.3 mutagen==1.44.0 @@ -15,5 +8,4 @@ validators==0.12.1 simplejson==3.17.0 accessify==0.3.1 librosa==0.7.2 -gunicorn==20.0.4 pyyaml==5.3.1 \ No newline at end of file diff --git a/run.sh b/run.sh index e7cf5ab1b0ff98c9676eef01f0b8072f66fd361a..1a7698122d6145b78280f057c64e66ff4a903f72 100755 --- a/run.sh +++ b/run.sh @@ -10,8 +10,6 @@ docker="false" # - engine # - core # - lqs -# - api-dev -# - api # - recreate-database # - docker:engine @@ -19,10 +17,9 @@ docker="false" # - docker:lqs # - docker:recreate-database # - docker:build -# - docker:api # -if [[ $* =~ ^(engine|core|lqs|api-dev|api)$ ]]; then +if [[ $* =~ ^(engine|core|lqs)$ ]]; then mode=$1 fi @@ -60,24 +57,6 @@ if [[ $docker == "false" ]]; then eval "$lqs" fi - ### Runs the API Server (Development) ### - - if [[ $mode == "api-dev" ]]; then - echo "Building Web Applications" - sh ./script/build-web.sh - echo "Starting API Server" - /usr/bin/env python3.7 engine-api.py - fi - - ### Runs the API Server (Production) ### - - if [[ $mode == "api" ]]; then - echo "Activating Python Environment" - source ../python-env/bin/activate - echo "Starting API Server" - gunicorn -c configuration/gunicorn.conf.py engine-api:app - fi - ### CAUTION: This deletes everything in your database ### if [[ $mode == "recreate-database" ]]; then @@ -130,19 +109,6 @@ if [[ $docker == "true" ]]; then fi - ### Runs Engine API using Gunicorn ### - - if [[ $mode == "api" ]]; then - exec sudo docker run --name aura-engine-api --rm -it \ - -u $UID:$GID \ - -p 127.0.0.1:8050:5000 \ - -v "$BASE_D":/srv \ - -v "$BASE_D/configuration/":/etc/aura \ - --tmpfs /var/log/aura/ autoradio/engine /srv/engine-api.py \ - --device autoradio/engine /bin/bash \ - -c "gunicorn -c configuration/gunicorn.conf.py engine-api:app" - fi - ### CAUTION: This deletes everything in your database ### if [[ $mode == "recreate-database" ]]; then diff --git a/script/build-web.sh b/script/build-web.sh deleted file mode 100755 index d42ecdbced75f23d244fcd4e15a4b2c50ea4606d..0000000000000000000000000000000000000000 --- a/script/build-web.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -echo "Building AURA Clock ..." -( - cd contrib/aura-clock - npm run build -) -cp contrib/aura-clock/public/build/aura-clock-bundle.css web/css/aura-clock-bundle.css -cp contrib/aura-clock/public/build/aura-clock-bundle.js web/js/aura-clock-bundle.js - -echo "Building AURA Player ..." -( - cd contrib/aura-player - npm run build -) -cp contrib/aura-player/public/build/aura-player-bundle.css web/css/aura-player-bundle.css -cp contrib/aura-player/public/build/aura-player-bundle.js web/js/aura-player-bundle.js diff --git a/script/install-web.sh b/script/install-web.sh deleted file mode 100755 index 45f3818baae4dc2eb0ab0f9d052354af4d8a27f7..0000000000000000000000000000000000000000 --- a/script/install-web.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -echo "Installing AURA Clock Packages ..." -(cd contrib/aura-clock && npm install) - -echo "Installing AURA Player Packages ..." -(cd contrib/aura-player && npm install) diff --git a/script/kill-web.sh b/script/kill-web.sh deleted file mode 100755 index f12fc1dae06e21f1b855e1fb28269b74012e473e..0000000000000000000000000000000000000000 --- a/script/kill-web.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -kill -9 `ps -eo pid,command | grep 'gunicorn.*engine-api:run_app()' | grep -v grep | sort | head -1 | awk '{print $1}'` \ No newline at end of file diff --git a/web/css/aura-clock-bundle.css b/web/css/aura-clock-bundle.css deleted file mode 100644 index bb60feb8dffbe21560f9e0387e8f298e0282d144..0000000000000000000000000000000000000000 --- a/web/css/aura-clock-bundle.css +++ /dev/null @@ -1,2 +0,0 @@ - -/*# sourceMappingURL=aura-clock-bundle.css.map */ \ No newline at end of file diff --git a/web/css/aura-player-bundle.css b/web/css/aura-player-bundle.css deleted file mode 100644 index cfba9bb956d3217c4d7ef6e36e5709cf1b3be543..0000000000000000000000000000000000000000 --- a/web/css/aura-player-bundle.css +++ /dev/null @@ -1,2 +0,0 @@ - -/*# sourceMappingURL=aura-player-bundle.css.map */ \ No newline at end of file diff --git a/web/css/aura.css b/web/css/aura.css deleted file mode 100644 index ec905f5ece9f725a3a3c33400392da8b7bf9b123..0000000000000000000000000000000000000000 --- a/web/css/aura.css +++ /dev/null @@ -1,66 +0,0 @@ -html, body { - position: relative; - width: 100%; - height: 100%; -} - -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; -} diff --git a/web/favicon.png b/web/favicon.png deleted file mode 100644 index b210b336cd8f214dd83f2ea3dc6ce6d9cc413937..0000000000000000000000000000000000000000 Binary files a/web/favicon.png and /dev/null differ diff --git a/web/js/aura-clock-bundle.js b/web/js/aura-clock-bundle.js deleted file mode 100644 index 75ce17c855c1d67ef5c3bd598110d4b7230ba215..0000000000000000000000000000000000000000 --- a/web/js/aura-clock-bundle.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(){"use strict";function t(){}function e(t){return t()}function n(){return Object.create(null)}function r(t){t.forEach(e)}function o(t){return"function"==typeof t}function l(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}function i(t,e){t.appendChild(e)}function s(t,e,n){t.insertBefore(e,n||null)}function c(t){t.parentNode.removeChild(t)}function a(t,e){for(let n=0;n<t.length;n+=1)t[n]&&t[n].d(e)}function u(t){return document.createElement(t)}function d(t){return document.createElementNS("http://www.w3.org/2000/svg",t)}function h(t){return document.createTextNode(t)}function f(){return h(" ")}function g(){return h("")}function p(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function m(t,e){e=""+e,t.data!==e&&(t.data=e)}function b(t,e,n,r){t.style.setProperty(e,n,r?"important":"")}class x{constructor(t,e=null){this.e=u("div"),this.a=e,this.u(t)}m(t,e=null){for(let n=0;n<this.n.length;n+=1)s(t,this.n[n],e);this.t=t}u(t){this.e.innerHTML=t,this.n=Array.from(this.e.childNodes)}p(t){this.d(),this.u(t),this.m(this.t,this.a)}d(){this.n.forEach(c)}}let y;function k(t){y=t}function w(){if(!y)throw new Error("Function called outside component initialization");return y}const $=[],v=[],S=[],_=[],E=Promise.resolve();let z=!1;function M(t){S.push(t)}let A=!1;const C=new Set;function N(){if(!A){A=!0;do{for(let t=0;t<$.length;t+=1){const e=$[t];k(e),L(e.$$)}for($.length=0;v.length;)v.pop()();for(let t=0;t<S.length;t+=1){const e=S[t];C.has(e)||(C.add(e),e())}S.length=0}while($.length);for(;_.length;)_.pop()();z=!1,A=!1,C.clear()}}function L(t){if(null!==t.fragment){t.update(),r(t.before_update);const e=t.dirty;t.dirty=[-1],t.fragment&&t.fragment.p(t.ctx,e),t.after_update.forEach(M)}}const T=new Set;let D,j;function H(t,e){t&&t.i&&(T.delete(t),t.i(e))}function I(t,e){const n=e.token={};function o(t,o,l,i){if(e.token!==n)return;e.resolved=i;let s=e.ctx;void 0!==l&&(s=s.slice(),s[l]=i);const c=t&&(e.current=t)(s);let a=!1;e.block&&(e.blocks?e.blocks.forEach((t,n)=>{n!==o&&t&&(D={r:0,c:[],p:D},function(t,e,n,r){if(t&&t.o){if(T.has(t))return;T.add(t),D.c.push(()=>{T.delete(t),r&&(n&&t.d(1),r())}),t.o(e)}}(t,1,1,()=>{e.blocks[n]=null}),D.r||r(D.c),D=D.p)}):e.block.d(1),c.c(),H(c,1),c.m(e.mount(),e.anchor),a=!0),e.block=c,e.blocks&&(e.blocks[o]=c),a&&N()}if((l=t)&&"object"==typeof l&&"function"==typeof l.then){const n=w();if(t.then(t=>{k(n),o(e.then,1,e.value,t),k(null)},t=>{k(n),o(e.catch,2,e.error,t),k(null)}),e.current!==e.pending)return o(e.pending,0),!0}else{if(e.current!==e.then)return o(e.then,1,e.value,t),!0;e.resolved=t}var l}function O(t,e){-1===t.$$.dirty[0]&&($.push(t),z||(z=!0,E.then(N)),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<<e%31}function R(l,i,s,c,a,u,d=[-1]){const h=y;k(l);const f=i.props||{},g=l.$$={fragment:null,ctx:null,props:u,update:t,not_equal:a,bound:n(),on_mount:[],on_destroy:[],before_update:[],after_update:[],context:new Map(h?h.$$.context:[]),callbacks:n(),dirty:d};let p=!1;g.ctx=s?s(l,f,(t,e,...n)=>{const r=n.length?n[0]:e;return g.ctx&&a(g.ctx[t],g.ctx[t]=r)&&(g.bound[t]&&g.bound[t](r),p&&O(l,t)),e}):[],g.update(),p=!0,r(g.before_update),g.fragment=!!c&&c(g.ctx),i.target&&(i.hydrate?g.fragment&&g.fragment.l(function(t){return Array.from(t.childNodes)}(i.target)):g.fragment&&g.fragment.c(),i.intro&&H(l.$$.fragment),function(t,n,l){const{fragment:i,on_mount:s,on_destroy:c,after_update:a}=t.$$;i&&i.m(n,l),M(()=>{const n=s.map(e).filter(o);c?c.push(...n):r(n),t.$$.on_mount=[]}),a.forEach(M)}(l,i.target,i.anchor),N()),k(h)}function B(t,e,n){const r=t.slice();return r[21]=e[n],r[23]=n,r}function P(t,e,n){const r=t.slice();return r[27]=e[n],r}function q(t,e,n){const r=t.slice();return r[24]=e[n],r}function F(t){let e,n;return{c(){e=d("line"),p(e,"class","minor"),p(e,"y1","42"),p(e,"y2","45"),p(e,"transform",n="rotate("+6*(t[24]+t[27])+")")},m(t,n){s(t,e,n)},d(t){t&&c(e)}}}function J(e){let n,r,o,l=[1,2,3,4],i=[];for(let t=0;t<4;t+=1)i[t]=F(P(e,l,t));return{c(){n=d("line");for(let t=0;t<4;t+=1)i[t].c();o=g(),p(n,"class","major"),p(n,"y1","35"),p(n,"y2","45"),p(n,"transform",r="rotate("+30*e[24]+")")},m(t,e){s(t,n,e);for(let n=0;n<4;n+=1)i[n].m(t,e);s(t,o,e)},p:t,d(t){t&&c(n),a(i,t),t&&c(o)}}}function G(t){let e,n,r,o=t[20]+"";return{c(){e=u("div"),n=u("p"),r=h(o),p(e,"class","error")},m(t,o){s(t,e,o),i(e,n),i(n,r)},p(t,e){16&e&&o!==(o=t[20]+"")&&m(r,o)},d(t){t&&c(e)}}}function K(t){let e,n,r,o=t[9](t[19])+"",l=t[19].current.show&&Q(t);return{c(){e=h(o),n=f(),l&&l.c(),r=g()},m(t,o){s(t,e,o),s(t,n,o),l&&l.m(t,o),s(t,r,o)},p(t,n){16&n&&o!==(o=t[9](t[19])+"")&&m(e,o),t[19].current.show?l?l.p(t,n):(l=Q(t),l.c(),l.m(r.parentNode,r)):l&&(l.d(1),l=null)},d(t){t&&c(e),t&&c(n),l&&l.d(t),t&&c(r)}}}function Q(t){let e,n,r,o,l,a,d,g,b,y,k,w,$,v,S=t[11](t[19].current.show)+"",_=nt(t[19].current)+"",E=t[11](t[19].next.show)+"",z=nt(t[19])+"";function M(t,e){return t[19].current.playlist?V:U}let A=M(t),C=A(t);return{c(){e=u("div"),n=u("h1"),o=f(),l=h(_),a=f(),d=u("div"),C.c(),g=f(),b=u("div"),y=u("h3"),k=h("Next: "),$=f(),v=h(z),r=new x(S,o),p(n,"class","schedule-title"),p(e,"id","current-schedule"),p(d,"id","playlist"),w=new x(E,$),p(y,"class","schedule-title"),p(b,"id","next-schedule")},m(t,c){s(t,e,c),i(e,n),r.m(n),i(n,o),i(n,l),s(t,a,c),s(t,d,c),C.m(d,null),s(t,g,c),s(t,b,c),i(b,y),i(y,k),w.m(y),i(y,$),i(y,v)},p(t,e){16&e&&S!==(S=t[11](t[19].current.show)+"")&&r.p(S),16&e&&_!==(_=nt(t[19].current)+"")&&m(l,_),A===(A=M(t))&&C?C.p(t,e):(C.d(1),C=A(t),C&&(C.c(),C.m(d,null))),16&e&&E!==(E=t[11](t[19].next.show)+"")&&w.p(E),16&e&&z!==(z=nt(t[19])+"")&&m(v,z)},d(t){t&&c(e),t&&c(a),t&&c(d),C.d(),t&&c(g),t&&c(b)}}}function U(t){let e,n,r,o,l,a,d,g=et(t[19].track)+"",b=t[10](t[5])+"";return{c(){e=u("div"),n=u("h2"),r=u("span"),o=h(g),l=f(),a=u("span"),d=h(b),p(r,"class","track-title"),p(a,"class","track-time-left"),p(e,"id","current-track"),p(e,"class","is-active")},m(t,c){s(t,e,c),i(e,n),i(n,r),i(r,o),i(n,l),i(n,a),i(a,d)},p(t,e){16&e&&g!==(g=et(t[19].track)+"")&&m(o,g),32&e&&b!==(b=t[10](t[5])+"")&&m(d,b)},d(t){t&&c(e)}}}function V(t){let e,n=t[19].current.playlist.entries,r=[];for(let e=0;e<n.length;e+=1)r[e]=Y(B(t,n,e));return{c(){e=u("ol");for(let t=0;t<r.length;t+=1)r[t].c()},m(t,n){s(t,e,n);for(let t=0;t<r.length;t+=1)r[t].m(e,null)},p(t,o){if(1072&o){let l;for(n=t[19].current.playlist.entries,l=0;l<n.length;l+=1){const i=B(t,n,l);r[l]?r[l].p(i,o):(r[l]=Y(i),r[l].c(),r[l].m(e,null))}for(;l<r.length;l+=1)r[l].d(1);r.length=n.length}},d(t){t&&c(e),a(r,t)}}}function W(t){let e,n,r,o,l,a,d,g,b,x=et(t[21])+"",y=t[10](t[21].duration)+"";return{c(){e=u("li"),n=u("span"),r=h(x),o=f(),l=u("span"),a=h("("),d=h(y),g=h(")"),b=f(),p(n,"class","track-title"),p(l,"class","track-duration"),p(e,"class","playlist-entry")},m(t,c){s(t,e,c),i(e,n),i(n,r),i(e,o),i(e,l),i(l,a),i(l,d),i(l,g),i(e,b)},p(t,e){16&e&&x!==(x=et(t[21])+"")&&m(r,x),16&e&&y!==(y=t[10](t[21].duration)+"")&&m(d,y)},d(t){t&&c(e)}}}function X(t){let e,n,r,o,l,a,d,g,b,x=et(t[21])+"",y=t[10](t[5])+"";return{c(){e=u("li"),n=u("span"),r=h(x),o=f(),l=u("span"),a=h("("),d=h(y),g=h(")"),b=f(),p(n,"class","track-title"),p(l,"class","track-time-left"),p(e,"id","current-playlist-entry"),p(e,"class","playlist-entry is-active")},m(t,c){s(t,e,c),i(e,n),i(n,r),i(e,o),i(e,l),i(l,a),i(l,d),i(l,g),i(e,b)},p(t,e){16&e&&x!==(x=et(t[21])+"")&&m(r,x),32&e&&y!==(y=t[10](t[5])+"")&&m(d,y)},d(t){t&&c(e)}}}function Y(t){let e,n;function r(t,n){return(null==e||16&n)&&(e=!!function(t,e){if(null!=e&&t.id==e.id)return location.hash="#current-playlist-entry",!0;return!1}(t[21],t[19].track)),e?X:W}let o=r(t,-1),l=o(t);return{c(){l.c(),n=g()},m(t,e){l.m(t,e),s(t,n,e)},p(t,e){o===(o=r(t,e))&&l?l.p(t,e):(l.d(1),l=o(t),l&&(l.c(),l.m(n.parentNode,n)))},d(t){l.d(t),t&&c(n)}}}function Z(e){let n;return{c(){n=u("div"),n.innerHTML='<span class="sr-only">Loading...</span>',p(n,"class","spinner-border mt-5"),p(n,"role","status")},m(t,e){s(t,n,e)},p:t,d(t){t&&c(n)}}}function tt(e){let n,r,o,l,g,x,y,k,w,$,v,S,_,E,z,M,A,C,N,L,T,D,j,H,O,R,B=[0,5,10,15,20,25,30,35,40,45,50,55],P=[];for(let t=0;t<12;t+=1)P[t]=J(q(e,B,t));let F={ctx:e,current:null,token:null,pending:Z,then:K,catch:G,value:19,error:20};return I(H=e[4],F),{c(){n=u("main"),r=f(),o=u("div"),l=u("img"),x=f(),y=u("h1"),k=h(e[0]),w=f(),$=u("div"),v=u("div"),S=d("svg"),_=d("circle");for(let t=0;t<12;t+=1)P[t].c();E=d("line"),M=d("line"),C=d("g"),N=d("line"),L=d("line"),D=f(),j=u("div"),F.block.c(),O=f(),R=u("footer"),R.innerHTML='<a href="https://gitlab.servus.at/aura/meta"><img id="aura-logo" src="https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png" alt="Aura Logo"></a> \n\t<br>\n\tStudio Clock is powered by <a href="https://gitlab.servus.at/autoradio">Aura Engine</a>',this.c=t,p(l,"id","station-logo"),l.src!==(g=e[1])&&p(l,"src",g),b(l,"width",e[2]),p(l,"alt","Radio Station"),p(l,"align","left"),p(y,"id","station-name"),p(o,"id","station-header"),p(_,"class","clock-face"),p(_,"r","48"),p(E,"class","hour"),p(E,"y1","2"),p(E,"y2","-20"),p(E,"transform",z="rotate("+(30*e[6]+e[7]/2)+")"),p(M,"class","minute"),p(M,"y1","4"),p(M,"y2","-30"),p(M,"transform",A="rotate("+(6*e[7]+e[8]/10)+")"),p(N,"class","second"),p(N,"y1","10"),p(N,"y2","-38"),p(L,"class","second-counterweight"),p(L,"y1","10"),p(L,"y2","2"),p(C,"transform",T="rotate("+6*e[8]+")"),p(S,"viewBox","-50 -50 100 100"),p(v,"id","left-column"),p(v,"class","column"),p(j,"id","right-column"),p(j,"class","column"),p($,"id","studio-clock")},m(t,c){s(t,n,c),e[18](n),s(t,r,c),s(t,o,c),i(o,l),i(o,x),i(o,y),i(y,k),s(t,w,c),s(t,$,c),i($,v),i(v,S),i(S,_);for(let t=0;t<12;t+=1)P[t].m(S,null);i(S,E),i(S,M),i(S,C),i(C,N),i(C,L),i($,D),i($,j),F.block.m(j,F.anchor=null),F.mount=()=>j,F.anchor=null,s(t,O,c),s(t,R,c)},p(t,[n]){if(e=t,2&n&&l.src!==(g=e[1])&&p(l,"src",g),4&n&&b(l,"width",e[2]),1&n&&m(k,e[0]),192&n&&z!==(z="rotate("+(30*e[6]+e[7]/2)+")")&&p(E,"transform",z),384&n&&A!==(A="rotate("+(6*e[7]+e[8]/10)+")")&&p(M,"transform",A),256&n&&T!==(T="rotate("+6*e[8]+")")&&p(C,"transform",T),F.ctx=e,16&n&&H!==(H=e[4])&&I(H,F));else{const t=e.slice();t[19]=F.resolved,F.block.p(t,n)}},i:t,o:t,d(t){t&&c(n),e[18](null),t&&c(r),t&&c(o),t&&c(w),t&&c($),a(P,t),F.block.d(),F.token=null,F=null,t&&c(O),t&&c(R)}}}"function"==typeof HTMLElement&&(j=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){for(const t in this.$$.slotted)this.appendChild(this.$$.slotted[t])}attributeChangedCallback(t,e,n){this[t]=n}$destroy(){!function(t,e){const n=t.$$;null!==n.fragment&&(r(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}(this,1),this.$destroy=t}$on(t,e){const n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(){}});function et(t){if(null!=t){let e="";return""!=t.artist&&(e=t.artist+" - "),e+t.title}return""}function nt(t){let e="";if(null!=t&&null!=t.schedule_start){let n="";if(null!=t.schedule_start){let n=new Date(Date.parse(t.schedule_start));n=n.toLocaleTimeString(navigator.language,{hour:"2-digit",minute:"2-digit"}),e="("+n}null!=t.schedule_end?(n=new Date(Date.parse(t.schedule_end)),n=n.toLocaleTimeString(navigator.language,{hour:"2-digit",minute:"2-digit"}),e=e+" - "+n+")"):e+=")"}return e}function rt(t,e,n){let r,o,l,{css:i=""}=e,{api:s="http://localhost:3333/api/v1"}=e,{name:c="Studio Clock"}=e,{logo:a="https://gitlab.servus.at/aura/meta/-/raw/master/images/aura-logo.png"}=e,{logosize:u="100px"}=e,{noScheduleMessage:d="Nothing scheduled!"}=e,h=new Date,f=null;var g;async function p(t){let e,n;try{e=await fetch(s+t)}catch{throw new Error("Cannot connect to Engine!")}try{n=await e.json()}catch(t){throw console.log("Error while converting response to JSON!",t),new Error(e.statusText)}if(e.ok)return n;throw console.log("Error:",n),new Error(n.message)}let m,b,x;return o=p("/clock"),g=()=>{const t=setInterval(()=>{n(15,h=new Date),n(5,l-=1),(l<=0||null==o)&&(f=null,n(4,o=null),n(4,o=p("/clock")))},1e3);return()=>{clearInterval(t)}},w().$$.on_mount.push(g),t.$set=t=>{"css"in t&&n(12,i=t.css),"api"in t&&n(13,s=t.api),"name"in t&&n(0,c=t.name),"logo"in t&&n(1,a=t.logo),"logosize"in t&&n(2,u=t.logosize),"noScheduleMessage"in t&&n(14,d=t.noScheduleMessage)},t.$$.update=()=>{32768&t.$$.dirty&&n(6,m=h.getHours()),32768&t.$$.dirty&&n(7,b=h.getMinutes()),32768&t.$$.dirty&&n(8,x=h.getSeconds())},[c,a,u,r,o,l,m,b,x,function(t){if(null!=i&&function(t,e){let n=document.createElement("link");n.setAttribute("rel","stylesheet"),n.setAttribute("type","text/css"),n.setAttribute("href",e),t.appendChild(n)}(r,i),null==f&&null!=t&&null!=t.track){f=t;let e=h-Date.parse(t.track_start);e=parseInt(e/1e3),n(5,l=t.track.duration-e-3),console.log("Current Data",t)}return""},function(t){if(null!=t&&Number.isInteger(t)){let e,n=new Date(null);return n.setSeconds(t),e=t>3600?n.toISOString().substr(11,8):n.toISOString().substr(14,5),e}return""},function(t){let e="";return e=null==t||null==t.name?'<span class="error">'+d+"</span>":t.name,e},i,s,d,h,f,p,function(t){v[t?"unshift":"push"](()=>{n(3,r=t)})}]}customElements.define("aura-clock",class extends j{constructor(t){super(),this.shadowRoot.innerHTML='<style>#station-header{width:100%;height:50px;padding:40px 100px}#station-name{margin:0;font-size:3em;line-height:80px}#station-logo{align-content:left;text-align:right;margin:0 40px 0 10px;opacity:0.5;filter:invert(100%)}#studio-clock{width:calc(100% - 200px);height:calc(100% - 500px);margin:100px;display:-webkit-flex;display:-ms-flexbox;display:flex;flex-direction:row}#left-column{width:30%;padding:25px}#right-column{width:70%;padding:25px 25px 25px 50px}#current-schedule,#next-schedule{margin:0 0 40px 20px}#next-schedule{background-color:rgb(24, 24, 24);margin-right:20px;padding:12px}#current-schedule .schedule-title{color:#ccc;font-size:3.5em}#next-schedule .schedule-title{color:gray !important;font-size:2em}#playlist{border:2px solid #333;margin:20px 20px 40px 20px;padding:10px;height:calc(80% - 100px);overflow-y:auto;scroll-behavior:smooth;background-color:#111;display:flex;align-items:center}#playlist::-webkit-scrollbar-track{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);background-color:rgb(77, 73, 73)}#playlist::-webkit-scrollbar{width:12px;background-color:rgb(0, 0, 0)}#playlist::-webkit-scrollbar-thumb{border-radius:10px;-webkit-box-shadow:inset 0 0 6px rgba(0,0,0,.3);background-color:rgb(34, 32, 32)}.playlist-entry{font-size:1.9em;padding-left:53px}#current-track *{font-size:1.5em}.track-time-left{margin:25px 50px}.is-active{color:green;padding-left:0}.is-active .track-title::before{content:"\\00a0\\00a0▶\\00a0\\00a0\\00a0";font-size:larger;color:green}.is-active .track-time-left{color:rgb(43, 241, 36);background-color:#222;padding:5px 15px}.error{font-size:1.3em;color:red;height:100%;display:flex;align-items:center;justify-content:center}svg{width:100%;height:100%}.clock-face{stroke:rgb(66, 66, 66);fill:black}.minor{stroke:rgb(132, 132, 132);stroke-width:0.5}.major{stroke:rgb(162, 162, 162);stroke-width:1}.hour{stroke:rgba(255, 255, 255, 0.705)}.minute{stroke:rgba(255, 255, 255, 0.705)}.second,.second-counterweight{stroke:rgb(180,0,0)}footer{width:100%;text-align:center;font-size:0.8em;color:gray;opacity:0.5}footer a{color:gray;text-decoration:underline}footer #aura-logo{filter:invert(100%);width:75px;margin:0 0 20px 0}</style>',R(this,{target:this.shadowRoot},rt,tt,l,{css:12,api:13,name:0,logo:1,logosize:2,noScheduleMessage:14}),t&&(t.target&&s(t.target,this,t.anchor),t.props&&(this.$set(t.props),N()))}static get observedAttributes(){return["css","api","name","logo","logosize","noScheduleMessage"]}get css(){return this.$$.ctx[12]}set css(t){this.$set({css:t}),N()}get api(){return this.$$.ctx[13]}set api(t){this.$set({api:t}),N()}get name(){return this.$$.ctx[0]}set name(t){this.$set({name:t}),N()}get logo(){return this.$$.ctx[1]}set logo(t){this.$set({logo:t}),N()}get logosize(){return this.$$.ctx[2]}set logosize(t){this.$set({logosize:t}),N()}get noScheduleMessage(){return this.$$.ctx[14]}set noScheduleMessage(t){this.$set({noScheduleMessage:t}),N()}})}(); -//# sourceMappingURL=aura-clock-bundle.js.map diff --git a/web/js/aura-player-bundle.js b/web/js/aura-player-bundle.js deleted file mode 100644 index 377e1aaab620e7ec83570e9427b8ad2b4cd251fb..0000000000000000000000000000000000000000 --- a/web/js/aura-player-bundle.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(){"use strict";function t(){}function e(t){return t()}function n(){return Object.create(null)}function r(t){t.forEach(e)}function o(t){return"function"==typeof t}function a(t,e){return t!=t?e==e:t!==e||t&&"object"==typeof t||"function"==typeof t}function c(t,e){t.appendChild(e)}function i(t,e,n){t.insertBefore(e,n||null)}function l(t){t.parentNode.removeChild(t)}function s(t){return document.createElement(t)}function u(t){return document.createTextNode(t)}function d(){return u(" ")}function f(t,e,n){null==n?t.removeAttribute(e):t.getAttribute(e)!==n&&t.setAttribute(e,n)}function p(t,e){e=""+e,t.data!==e&&(t.data=e)}let h;function m(t){h=t}function g(){if(!h)throw new Error("Function called outside component initialization");return h}const b=[],k=[],$=[],y=[],x=Promise.resolve();let v=!1;function w(t){$.push(t)}function _(){const t=new Set;do{for(;b.length;){const t=b.shift();m(t),S(t.$$)}for(;k.length;)k.pop()();for(let e=0;e<$.length;e+=1){const n=$[e];t.has(n)||(n(),t.add(n))}$.length=0}while(b.length);for(;y.length;)y.pop()();v=!1}function S(t){if(null!==t.fragment){t.update(),r(t.before_update);const e=t.dirty;t.dirty=[-1],t.fragment&&t.fragment.p(t.ctx,e),t.after_update.forEach(w)}}const T=new Set;let E,L;function D(t,e){t&&t.i&&(T.delete(t),t.i(e))}function A(t,e){const n=e.token={};function o(t,o,a,c){if(e.token!==n)return;e.resolved=c;let i=e.ctx;void 0!==a&&(i=i.slice(),i[a]=c);const l=t&&(e.current=t)(i);let s=!1;e.block&&(e.blocks?e.blocks.forEach((t,n)=>{n!==o&&t&&(E={r:0,c:[],p:E},function(t,e,n,r){if(t&&t.o){if(T.has(t))return;T.add(t),E.c.push(()=>{T.delete(t),r&&(n&&t.d(1),r())}),t.o(e)}}(t,1,1,()=>{e.blocks[n]=null}),E.r||r(E.c),E=E.p)}):e.block.d(1),l.c(),D(l,1),l.m(e.mount(),e.anchor),s=!0),e.block=l,e.blocks&&(e.blocks[o]=l),s&&_()}if((a=t)&&"object"==typeof a&&"function"==typeof a.then){const n=g();if(t.then(t=>{m(n),o(e.then,1,e.value,t),m(null)},t=>{m(n),o(e.catch,2,e.error,t),m(null)}),e.current!==e.pending)return o(e.pending,0),!0}else{if(e.current!==e.then)return o(e.then,1,e.value,t),!0;e.resolved=t}var a}function C(t,e){-1===t.$$.dirty[0]&&(b.push(t),v||(v=!0,x.then(_)),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<<e%31}function I(a,c,i,l,s,u,d=[-1]){const f=h;m(a);const p=c.props||{},g=a.$$={fragment:null,ctx:null,props:u,update:t,not_equal:s,bound:n(),on_mount:[],on_destroy:[],before_update:[],after_update:[],context:new Map(f?f.$$.context:[]),callbacks:n(),dirty:d};let b=!1;g.ctx=i?i(a,p,(t,e,n=e)=>(g.ctx&&s(g.ctx[t],g.ctx[t]=n)&&(g.bound[t]&&g.bound[t](n),b&&C(a,t)),e)):[],g.update(),b=!0,r(g.before_update),g.fragment=!!l&&l(g.ctx),c.target&&(c.hydrate?g.fragment&&g.fragment.l(function(t){return Array.from(t.childNodes)}(c.target)):g.fragment&&g.fragment.c(),c.intro&&D(a.$$.fragment),function(t,n,a){const{fragment:c,on_mount:i,on_destroy:l,after_update:s}=t.$$;c&&c.m(n,a),w(()=>{const n=i.map(e).filter(o);l?l.push(...n):r(n),t.$$.on_mount=[]}),s.forEach(w)}(a,c.target,c.anchor),_()),m(f)}function M(t,e,n){const r=t.slice();return r[9]=e[n],r}function H(t){let e,n,r=t[8].message+"";return{c(){var t,o,a;e=s("p"),n=u(r),t="color",o="red",e.style.setProperty(t,o,a?"important":"")},m(t,r){i(t,e,r),c(e,n)},p(t,e){2&e&&r!==(r=t[8].message+"")&&p(n,r)},d(t){t&&l(e)}}}function N(t){let e,n=t[7],r=[];for(let e=0;e<n.length;e+=1)r[e]=j(M(t,n,e));return{c(){for(let t=0;t<r.length;t+=1)r[t].c();e=u("")},m(t,n){for(let e=0;e<r.length;e+=1)r[e].m(t,n);i(t,e,n)},p(t,o){if(6&o){let a;for(n=t[7],a=0;a<n.length;a+=1){const c=M(t,n,a);r[a]?r[a].p(c,o):(r[a]=j(c),r[a].c(),r[a].m(e.parentNode,e))}for(;a<r.length;a+=1)r[a].d(1);r.length=n.length}},d(t){!function(t,e){for(let n=0;n<t.length;n+=1)t[n]&&t[n].d(e)}(r,t),t&&l(e)}}}function j(t){let e,n,r,o,a,h,m,g,b,k,$,y,x,v,w,_,S=t[2](t[9])+"",T=R(t[9])+"",E=t[9].track.artist+"",L=t[9].track.title+"",D=B(t[9])+"";return{c(){e=s("h4"),n=u(S),r=d(),o=s("div"),a=s("div"),h=s("h5"),m=s("b"),g=u(T),b=u(" | "),k=u(E),$=u(" - "),y=u(L),x=d(),v=u(D),w=d(),f(e,"class","current-date"),f(h,"class","card-title"),f(a,"class","card-body"),f(o,"class",_="card mt-5 "+q(t[9]))},m(t,l){i(t,e,l),c(e,n),i(t,r,l),i(t,o,l),c(o,a),c(a,h),c(h,m),c(m,g),c(h,b),c(h,k),c(h,$),c(h,y),c(h,x),c(h,v),c(o,w)},p(t,e){2&e&&S!==(S=t[2](t[9])+"")&&p(n,S),2&e&&T!==(T=R(t[9])+"")&&p(g,T),2&e&&E!==(E=t[9].track.artist+"")&&p(k,E),2&e&&L!==(L=t[9].track.title+"")&&p(y,L),2&e&&D!==(D=B(t[9])+"")&&p(v,D),2&e&&_!==(_="card mt-5 "+q(t[9]))&&f(o,"class",_)},d(t){t&&l(e),t&&l(r),t&&l(o)}}}function z(e){let n;return{c(){n=s("div"),n.innerHTML='<div class="lds-dual-ring"></div> \n\t\t\t\t\t<div class="loading">Loading...</div>',f(n,"class","spinner"),f(n,"role","status")},m(t,e){i(t,n,e)},p:t,d(t){t&&l(n)}}}function O(e){let n,r,o,a,h,m,g,b,k,$,y,x,v,w={ctx:e,current:null,token:null,pending:z,then:N,catch:H,value:7,error:8};return A(k=e[1],w),{c(){n=s("div"),r=s("div"),o=s("div"),a=d(),h=s("div"),m=s("h1"),g=u(e[0]),b=d(),w.block.c(),$=d(),y=s("div"),x=d(),v=s("footer"),v.innerHTML='Track Service is powered by <a href="https://gitlab.servus.at/autoradio">Aura</a>',this.c=t,f(o,"class","col-md"),f(m,"class","display-4"),f(h,"class","col-md-8 text-center"),f(y,"class","col-md"),f(r,"class","row"),f(n,"class","container mt-5")},m(t,e){i(t,n,e),c(n,r),c(r,o),c(r,a),c(r,h),c(h,m),c(m,g),c(h,b),w.block.m(h,w.anchor=null),w.mount=()=>h,w.anchor=null,c(r,$),c(r,y),i(t,x,e),i(t,v,e)},p(t,[n]){if(e=t,1&n&&p(g,e[0]),w.ctx=e,2&n&&k!==(k=e[1])&&A(k,w));else{const t=e.slice();t[7]=w.resolved,w.block.p(t,n)}},i:t,o:t,d(t){t&&l(n),w.block.d(),w.token=null,w=null,t&&l(x),t&&l(v)}}}"function"==typeof HTMLElement&&(L=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){for(const t in this.$$.slotted)this.appendChild(this.$$.slotted[t])}attributeChangedCallback(t,e,n){this[t]=n}$destroy(){!function(t,e){const n=t.$$;null!==n.fragment&&(r(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}(this,1),this.$destroy=t}$on(t,e){const n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{const t=n.indexOf(e);-1!==t&&n.splice(t,1)}}$set(){}});let P="trackservice";function R(t){return new Date(t.track_start).toLocaleTimeString("de-at",{hour:"2-digit",minute:"2-digit"})}function q(t){if(null!=t.track.duration&&parseInt(t.track.duration)>0){let e=new Date(t.track_start),n=new Date(e.getTime());n.setSeconds(n.getSeconds()+parseInt(t.track.duration));let r=new Date;return e<r&&r<n?"active-track":""}return""}function B(t){return null!=t.track.duration&&parseInt(t.track.duration)>0?"("+function(t){if(null!=t&&Number.isInteger(t)){let e,n=new Date(null);return n.setSeconds(t),e=t>3600?n.toISOString().substr(11,8):n.toISOString().substr(14,5),e}return""}(t.track.duration)+")":""}function F(t,e,n){let r,{api:o="http://localhost:3333/api/v1/"}=e,{name:a="Track Service"}=e,c="";return r=async function(t){let e=await fetch(`${o}${t}`),n=await e.json();if(e.ok)return n;throw new Error(n)}(P),t.$set=t=>{"api"in t&&n(3,o=t.api),"name"in t&&n(0,a=t.name)},[a,r,function(t){let e="";if(null!=t.track_start&&(e=t.track_start.split("T")[0]),c!=e){c=e;let t={weekday:"long",year:"numeric",month:"short",day:"numeric"};return new Date(c).toLocaleDateString("de-at",t)}return""},o]}customElements.define("aura-trackservice",class extends L{constructor(t){super(),this.shadowRoot.innerHTML='<style>h1,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>',I(this,{target:this.shadowRoot},F,O,a,{api:3,name:0}),t&&(t.target&&i(t.target,this,t.anchor),t.props&&(this.$set(t.props),_()))}static get observedAttributes(){return["api","name"]}get api(){return this.$$.ctx[3]}set api(t){this.$set({api:t}),_()}get name(){return this.$$.ctx[0]}set name(t){this.$set({name:t}),_()}})}(); -//# sourceMappingURL=aura-player-bundle.js.map diff --git a/web/templates/clock.html b/web/templates/clock.html deleted file mode 100644 index 7770d882272aa4b4c3f0f12b7ef4b3bed6ac8454..0000000000000000000000000000000000000000 --- a/web/templates/clock.html +++ /dev/null @@ -1,24 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset='utf-8'> - <meta name='viewport' content='width=device-width,initial-scale=1'> - - <title>Aura Engine - Studio Clock</title> - - <link rel='icon' type='image/png' href='/favicon.png'> - <link rel='stylesheet' href='/css/aura.css'> - <link rel='stylesheet' href='/css/aura-clock-bundle.css'> - - <script defer src='/js/aura-clock-bundle.js'></script> -</head> - -<body style="background-color: black;"> - <aura-clock - css="/css/aura.css" - name=":::CONFIG-STATION-NAME:::" - logo=":::CONFIG-STATION-LOGO-URL:::" - logosize=":::CONFIG-STATION-LOGO-SIZE:::" - api=":::CONFIG-API-URL:::" /> -</body> -</html> diff --git a/web/templates/trackservice.html b/web/templates/trackservice.html deleted file mode 100644 index bea6aee7cc76ce7182fb3b7c687d8c9d74fe3d00..0000000000000000000000000000000000000000 --- a/web/templates/trackservice.html +++ /dev/null @@ -1,18 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> -<head> - <meta charset='utf-8'> - <meta name='viewport' content='width=device-width,initial-scale=1'> - - <title>Aura Engine - Track Service</title> - - <link rel='icon' type='image/png' href='/favicon.png'> - <link rel='stylesheet' href='/css/aura.css'> - <link rel='stylesheet' href='/css/aura-player-bundle.css'> - - <script defer src='/js/aura-player-bundle.js'></script> -</head> -<body> - <aura-trackservice api=":::CONFIG-API-URL:::" name=":::CONFIG-STATION-NAME::: - Track Service" /> -</body> -</html>