phonography.radio.af setup
| <!doctype html> | |
| <html> | |
| <head> | |
| <title>Phonography on Aleatoric Forest</title> | |
| <link rel="stylesheet" href="/style.css?v=2019-06-15" /> | |
| <meta charset="utf-8"/> | |
| <meta name="viewport" content="width=device-width, user-scalable=no" /> | |
| </head> | |
| <body> | |
| <header> | |
| <h1><em>Phonography</em> on Aleatoric Forest</h1> | |
| </header> | |
| <img id="phonography-cover" /> | |
| <section class="player"> | |
| <button id="phonography-toggle"> | |
| <svg | |
| xmlns="http://www.w3.org/2000/svg" | |
| xmlns:xlink="http://www.w3.org/1999/xlink" | |
| version="1.1" | |
| x="0px" | |
| y="0px" | |
| viewBox="0 0 100 125" | |
| enable-background="new 0 0 100 100" | |
| xml:space="preserve" | |
| > | |
| <polygon fill="#ffffff" points="4.948,21.713 53.945,50.002 4.948,78.286 "/> | |
| <rect x="53.945" y="21.713" fill="#ffffff" width="15.478" height="56.573"/> | |
| <rect x="80.77" y="21.713" fill="#ffffff" width="15.48" height="56.573"/> | |
| </svg> | |
| <span id="button-test">Play</span> | |
| </button> | |
| <audio id="phonography-player"></audio> | |
| </section> | |
| <section class="info"> | |
| <p>Now Playing: <br/><span id="phonography-info"></span></p> | |
| </section> | |
| <footer> | |
| <cite><a href="https://thenounproject.com/term/play-pause/86425/"><i>play pause</i> by Sylvain A.</a> from the Noun Project</cite> | |
| </footer> | |
| <script type="text/javascript"> | |
| document.addEventListener('DOMContentLoaded', e => { | |
| let button = document.getElementById('phonography-toggle'); | |
| let buttontext = document.getElementById('button-test'); | |
| let player = document.getElementById('phonography-player'); | |
| let meta = document.getElementById('phonography-info'); | |
| let cover = document.getElementById('phonography-cover'); | |
| let metalistener = () => { | |
| es = new EventSource('/info/metadata-stream'); | |
| es.addEventListener('meta', e => { | |
| let d = JSON.parse(e.data); | |
| meta.innerText = d.title; | |
| cover.src = `/covers/${ d.catalognumber }.jpg`; | |
| }, false); | |
| es.onerror = () => { | |
| if(es.readyState === 2) { | |
| es.close(); | |
| es = null; | |
| setTimeout(() => { | |
| metalistener(); | |
| }, 1000); | |
| } | |
| }; | |
| }; | |
| metalistener(); | |
| function seed_metadata() { | |
| let d = JSON.parse(this.responseText); | |
| meta.innerText = d.title; | |
| cover.src = `/covers/${ d.catalognumber }.jpg`; | |
| } | |
| let request = new XMLHttpRequest(); | |
| request.addEventListener('load', seed_metadata); | |
| request.open('GET', '/info/metadata-seed'); | |
| request.send(); | |
| player.pause(); | |
| button.onclick = (e) => { | |
| e.preventDefault(); | |
| if(player.paused) { | |
| player.src = 'http://phonography.radio.af/stream/'; | |
| player.play(); | |
| button.classList.add('playing'); | |
| buttontext.innerText = 'Pause'; | |
| } else { | |
| player.src = 'http://phonography.radio.af/stream/'; | |
| player.pause(); | |
| button.classList.remove('playing'); | |
| buttontext.innerText = 'Play'; | |
| } | |
| }; | |
| }); | |
| </script> | |
| </body> | |
| </html> |
| [Unit] | |
| Description=phonography info gunicorn service | |
| After=network.target | |
| [Service] | |
| User=deploy | |
| Group=www-data | |
| Restart=on-failure | |
| WorkingDirectory=/srv/radio/phonography | |
| Environment="PATH=/srv/radio/phonography/venv/bin" | |
| ExecStart=/srv/radio/phonography/venv/bin/gunicorn --workers 2 --worker-class gevent --bind 127.0.0.1:3002 --reload phonography_info:app | |
| [Install] | |
| WantedBy=multi-user.target |
| [Unit] | |
| Description=phonography liquidsoap streamer | |
| After=network.target | |
| [Service] | |
| User=deploy | |
| Group=www-data | |
| Restart=on-failure | |
| WorkingDirectory=/srv/radio/phonography | |
| ExecStart=/home/deploy/.opam/system/bin/liquidsoap /srv/radio/phonography/phonography.liq | |
| [Install] | |
| WantedBy=multi-user.target |
| #!/usr/bin/liquidsoap | |
| set('log.file.path', '/srv/radio/phonography/phonography.log') | |
| set('log.stdout', false) | |
| set('harbor.bind_addr', '127.0.0.1') | |
| url = 'http://your-ip-addess' | |
| streamuser = 'source' | |
| streampass = 'hackme' | |
| phonography = nrj(playlist('/srv/radio/phonography/phonography.m3u', mode='randomize', reload=1, reload_mode='rounds')) | |
| radio = mksafe(phonography) | |
| def post_metadata(m) = | |
| system("./venv/bin/python phonography_info.py "^quote(m['title'])^" "^quote(m['catalognumber'])) | |
| end | |
| radio = amplify(1.0, override="replay_gain", radio) | |
| radio = on_track(post_metadata(), radio) | |
| radio = smart_crossfade(fade_out=10.0, fade_in=10.0, radio) | |
| output.icecast(%mp3.vbr(quality=0, samplerate=44100, internal_quality=0, id3v2=true), radio, | |
| host='localhost', port=8000, user=streamuser, password=streampass, | |
| description='Phonography on Aleatoric Forest', url=url, genre='Field Recording', | |
| mount='phonography', name='Phonography on Aleatoric Forest') |
| <VirtualHost *:80> | |
| ServerName phonography.radio.af | |
| ServerAdmin contact@luvsound.org | |
| DocumentRoot /srv/www/phonography.radio.af | |
| ErrorLog ${APACHE_LOG_DIR}/phonography.radio.af-error.lg | |
| CustomLog ${APACHE_LOG_DIR}/phonography.radio.af-access.log combined | |
| XBitHack On | |
| ProxyPreserveHost On | |
| ProxyPass /stream/ http://localhost:8000/phonography | |
| ProxyPassReverse /stream/ http://localhost:8000/phonography | |
| ProxyPass /info http://localhost:3002/ | |
| ProxyPassReverse /info http://localhost:3002/ | |
| <Directory /srv/www/phonography.radio.af> | |
| Require all granted | |
| ExpiresActive On | |
| ExpiresDefault "access plus 120 seconds" | |
| Options +Includes | |
| </Directory> | |
| <FilesMatch "\.(cgi|wsgi)$"> | |
| SSLOptions +StdEnvVars | |
| </FilesMatch> | |
| </VirtualHost> |
| from flask import Flask, Response | |
| from redis import StrictRedis | |
| import tweepy | |
| import logging | |
| from logging.handlers import SysLogHandler | |
| import sys | |
| import json | |
| app = Flask(__name__) | |
| r = StrictRedis(host='127.0.0.1', port=6379) | |
| logger = logging.getLogger('phonography-info') | |
| logger.setLevel(logging.DEBUG) | |
| logger.addHandler(SysLogHandler(address='/dev/log')) | |
| def info_stream(): | |
| ps = r.pubsub() | |
| ps.subscribe('meta') | |
| for msg in ps.listen(): | |
| logger.info('info_stream msg %s' % msg) | |
| if msg['type'] == 'message': | |
| yield 'event: meta\ndata: %s\n\n' % msg['data'].decode('utf-8') | |
| @app.route('/metadata-seed') | |
| def info_seed(): | |
| return json.dumps({ | |
| 'title': r.get('phonography_current_title').decode('utf-8'), | |
| 'catalognumber': r.get('phonography_current_catalognumber').decode('utf-8'), | |
| }) | |
| @app.route('/metadata-stream') | |
| def stream(): | |
| resp = Response(info_stream(), mimetype="text/event-stream") | |
| resp.headers['Connection'] = 'keep-alive' | |
| resp.headers['Cache-Control'] = 'no-store' | |
| return resp | |
| if __name__ == '__main__': | |
| try: | |
| title = sys.argv[1] | |
| catalognumber = sys.argv[2] | |
| msg = json.dumps(dict(title=title, catalognumber=catalognumber)) | |
| r.publish(channel='meta', message=msg) | |
| r.set('phonography_current_title', title) | |
| r.set('phonography_current_catalognumber', catalognumber) | |
| except IndexError: | |
| pass |
| Click==7.0 | |
| Flask==1.0.2 | |
| gunicorn==19.9.0 | |
| itsdangerous==1.1.0 | |
| Jinja2==2.10 | |
| MarkupSafe==1.1.0 | |
| redis==3.0.1 | |
| six==1.11.0 | |
| Werkzeug==0.14.1 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment