fourth commit

This commit is contained in:
iou1name 2020-06-15 09:57:23 -04:00
parent 3282d38821
commit ab6324bf32
6 changed files with 186 additions and 24 deletions

View File

@ -3,7 +3,7 @@ A music streaming application.
## Requirements ## Requirements
Python 3.8+ Python 3.8+
Python packages: `gunicorn aiohttp aiohttp_jinja2 uvloop mutagen` Python packages: `gunicorn aiohttp aiohttp_jinja2 uvloop mutagen asyncpg`
## Install ## Install
``` ```

22
events.py Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env python3
"""
WebSocket events.
"""
import types
async def select(request, ws, data):
"""Retrieve information about an artist, album or track."""
if data.get('type') == 'artist':
async with request.app['pool'].acquire() as conn:
albums = await conn.fetch(
"SELECT DISTINCT album, date FROM track "
"WHERE albumartist = $1 ORDER BY date ASC",
data.get('artist', ''))
albums = [record['album'] for record in albums]
ret = {'event': 'albums', 'ok': True, 'data': albums}
await ws.send_json(ret)
events = {}
for obj in dir():
if type(locals()[obj]) == types.FunctionType:
events[locals()[obj].__name__] = locals()[obj]

View File

@ -2,6 +2,7 @@
""" """
A music streaming application. A music streaming application.
""" """
import json
import asyncio import asyncio
import aiohttp import aiohttp
@ -13,6 +14,7 @@ import uvloop
import asyncpg import asyncpg
import config import config
import events
import database import database
import buckler_aiohttp import buckler_aiohttp
@ -23,9 +25,10 @@ routes = web.RouteTableDef()
async def index(request): async def index(request):
"""The index page.""" """The index page."""
async with request.app['pool'].acquire() as conn: async with request.app['pool'].acquire() as conn:
artists = await conn.execute( artists = await conn.fetch(
"SELECT DISTINCT albumartist FROM track ORDER BY albumartist ASC") "SELECT DISTINCT albumartist FROM track ORDER BY albumartist ASC")
return render_template('index.html', request, {}) artists = [record['albumartist'] for record in artists]
return render_template('index.html', request, locals())
@routes.get('/ws', name='ws') @routes.get('/ws', name='ws')
@ -41,10 +44,15 @@ async def websocket_handler(request):
if msg.type != WSMsgType.TEXT: if msg.type != WSMsgType.TEXT:
break break
if msg.data == "ping": try:
print('ping') data = json.loads(msg.data)
await ws.send_str("pong") except json.JSONDecodeError:
break
event = data.get('event')
if not event or event not in events.events.keys():
break
await events.events[event](request, ws, data.get('data'))
await ws.close() await ws.close()
return ws return ws

View File

@ -0,0 +1,46 @@
body {
height: 100vh;
margin: 0;
padding: 8px;
box-sizing: border-box;
}
main {
height: 100%;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
grid-template-areas:
"h h h"
"a b c"
"f f f";
}
#header {
grid-area: h;
}
.list {
height: 100%;
width: 100%;
}
#artistListContainer {
grid-area: a;
}
#albumListContainer {
grid-area: b;
}
#trackListContainer {
grid-area: c;
}
#playerContainer {
grid-area: f;
height: auto;
max-height: 30vh;
display: flex;
justify-content: center;
}

View File

@ -12,31 +12,90 @@ function init_websocket() {
socket.onclose = onclose; socket.onclose = onclose;
socket.onerror = onerror; socket.onerror = onerror;
socket.events = {}; socket.events = {};
socket.events['albums'] = albums_recv;
return socket; return socket;
} }
function send_event(event_title, data) { function send_event(event_title, data) {
data = JSON.stringify({'event': event_title, 'data': data}); data = JSON.stringify({'event': event_title, 'data': data});
if (socket.readyState == 0) { if (this.readyState == 0) {
console.log("Socket is still opening!"); console.log("Socket is still opening!");
return; return;
} }
socket.send(data); this.send(data);
} }
function onmessage(e) { function onmessage(event) {
console.log(e); let data;
let event_title;
try {
data = JSON.parse(event.data);
} catch(err) {
// not JSON
console.log(err);
console.log(event);
throw new Error("Error decoding JSON");
return;
} }
function onclose(e) { if (!data.ok) {
if (e.wasClean) { return; } // no need to reconnect throw new Error("Socket error: event = " + event_title + ", error = " + data.error);
console.log(e); }
try {
event_title = data.event;
data = data.data;
} catch(err) {
// not proper event
console.log(err);
console.log(event);
throw new Error("Event malformed");
return;
}
if (socket.events[event_title] === undefined) {
console.log("Unknown socket event: " + event_title);
return;
}
socket.events[event_title](data);
}
async function onclose(event) {
if (event.wasClean) { return; } // no need to reconnect
console.log(event);
console.log('Websocket lost connection to server. Re-trying...'); console.log('Websocket lost connection to server. Re-trying...');
//socket = init_websocket(); await sleep(3000);
//await sleep(5000); socket = init_websocket();
} }
function onerror(e) { function sleep(ms) {
console.log("Websocket error!") return new Promise(resolve => setTimeout(resolve, ms));
console.log(e);
} }
function onerror(event) {
console.log("Websocket error!")
console.log(event);
}
/* Websocket receive */
function albums_recv(data) {
let albums_list = document.querySelector('#albumList');
while (albums_list.firstChild) {
albums_list.removeChild(albums_list.lastChild);
}
for (let album of data) {
let option = document.createElement('option');
option.value = album;
option.innerText = album;
albums_list.appendChild(option);
}
}
/* Websocket send */
function select_artist(option) {
let artist = option.value;
let data = {'type': 'artist', 'artist': artist};
socket.send_event('select', data);
}
/* DOM */

View File

@ -7,15 +7,42 @@
<script type="text/javascript" src="/static/scorch.js"></script> <script type="text/javascript" src="/static/scorch.js"></script>
<script>window.onload = load;</script> <script>window.onload = load;</script>
<meta name="viewport" content="width=device-width, initial-scale=0.8"> <meta name="viewport" content="width=device-width, initial-scale=0.8">
<meta name="description" content="A music streaming service."> <meta name="description" content="A music streaming platform.">
</head> </head>
<body> <body>
<header>
<h1>Scorch</h1>
</header>
<main> <main>
<button onclick="socket.send('test')">Test</button> <div id="header">
<audio controls></audio> <h1>Scorch</h1>
</div>
<div id="artistListContainer" class="list">
<select id="artistList" size="2" class="list" onchange="select_artist(this)">
{% for artist in artists %}
<option value="{{ artist }}">{{ artist }}</option>
{% endfor %}
</select>
</div>
<div id="albumListContainer" class="list">
<select id="albumList" size="2" class="list" onchange="select_album(this)">
</select>
</div>
<div id="trackListContainer" class="list">
<select id="trackList" size="2" class="list" onchange="select_track(this)">
</select>
</div>
<div id="playerContainer">
<span id="albumCover"><img src=""></span>
<span id="playerControls">
<h4><span id="nowPlayingArtist"></span> - <span id="nowPlayingAlbum"></span></h4>
<h3 id="nowPlayingTitle"></h3>
<audio id="player" controls>
<source id="stream" src="" type="audio/ogg">
</audio>
<div>
<input type="checkbox" name="shuffle" id="shuffle" checked="true">
<label for="shuffle">Shuffle</label>
</div>
</span>
</div>
</main> </main>
</body> </body>
</html> </html>