fourth commit
This commit is contained in:
parent
3282d38821
commit
ab6324bf32
|
@ -3,7 +3,7 @@ A music streaming application.
|
|||
|
||||
## Requirements
|
||||
Python 3.8+
|
||||
Python packages: `gunicorn aiohttp aiohttp_jinja2 uvloop mutagen`
|
||||
Python packages: `gunicorn aiohttp aiohttp_jinja2 uvloop mutagen asyncpg`
|
||||
|
||||
## Install
|
||||
```
|
||||
|
|
22
events.py
Normal file
22
events.py
Normal 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]
|
18
scorch.py
18
scorch.py
|
@ -2,6 +2,7 @@
|
|||
"""
|
||||
A music streaming application.
|
||||
"""
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
import aiohttp
|
||||
|
@ -13,6 +14,7 @@ import uvloop
|
|||
import asyncpg
|
||||
|
||||
import config
|
||||
import events
|
||||
import database
|
||||
import buckler_aiohttp
|
||||
|
||||
|
@ -23,9 +25,10 @@ routes = web.RouteTableDef()
|
|||
async def index(request):
|
||||
"""The index page."""
|
||||
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")
|
||||
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')
|
||||
|
@ -41,10 +44,15 @@ async def websocket_handler(request):
|
|||
if msg.type != WSMsgType.TEXT:
|
||||
break
|
||||
|
||||
if msg.data == "ping":
|
||||
print('ping')
|
||||
await ws.send_str("pong")
|
||||
try:
|
||||
data = json.loads(msg.data)
|
||||
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()
|
||||
return ws
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -12,31 +12,90 @@ function init_websocket() {
|
|||
socket.onclose = onclose;
|
||||
socket.onerror = onerror;
|
||||
socket.events = {};
|
||||
socket.events['albums'] = albums_recv;
|
||||
return socket;
|
||||
}
|
||||
|
||||
function send_event(event_title, data) {
|
||||
data = JSON.stringify({'event': event_title, 'data': data});
|
||||
if (socket.readyState == 0) {
|
||||
if (this.readyState == 0) {
|
||||
console.log("Socket is still opening!");
|
||||
return;
|
||||
}
|
||||
socket.send(data);
|
||||
this.send(data);
|
||||
}
|
||||
|
||||
function onmessage(e) {
|
||||
console.log(e);
|
||||
function onmessage(event) {
|
||||
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 (e.wasClean) { return; } // no need to reconnect
|
||||
console.log(e);
|
||||
if (!data.ok) {
|
||||
throw new Error("Socket error: event = " + event_title + ", error = " + data.error);
|
||||
}
|
||||
|
||||
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...');
|
||||
//socket = init_websocket();
|
||||
//await sleep(5000);
|
||||
await sleep(3000);
|
||||
socket = init_websocket();
|
||||
}
|
||||
|
||||
function onerror(e) {
|
||||
console.log("Websocket error!")
|
||||
console.log(e);
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
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 */
|
||||
|
|
|
@ -7,15 +7,42 @@
|
|||
<script type="text/javascript" src="/static/scorch.js"></script>
|
||||
<script>window.onload = load;</script>
|
||||
<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>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Scorch</h1>
|
||||
</header>
|
||||
<main>
|
||||
<button onclick="socket.send('test')">Test</button>
|
||||
<audio controls></audio>
|
||||
<div id="header">
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in New Issue
Block a user