Beginning the upgrade of Snapcast with docker
This commit is contained in:
@ -1,10 +1,10 @@
|
||||
version: "3"
|
||||
services:
|
||||
snapserver:
|
||||
image: currucuxu/snapcastserver
|
||||
image: jaedb/snapserver
|
||||
ports:
|
||||
- 1704:1704
|
||||
- 1705:1705
|
||||
- 1780:1780
|
||||
volumes:
|
||||
- HOST_SNAPCAST_TEMP:/tmp
|
||||
- ./docker/snapserver.conf:/etc/default/snapserver.conf
|
||||
|
||||
@ -16,9 +16,4 @@ library = images
|
||||
media_dir = /var/lib/mopidy/media
|
||||
|
||||
[local-images]
|
||||
library = sqlite
|
||||
|
||||
[iris]
|
||||
snapcast_enabled = true
|
||||
snapcast_host = snapserver
|
||||
snapcast_port = 1705
|
||||
library = sqlite
|
||||
@ -37,14 +37,10 @@ class Extension( ext.Extension ):
|
||||
schema['spotify_authorization_url'] = config.String()
|
||||
schema['lastfm_authorization_url'] = config.String()
|
||||
schema['genius_authorization_url'] = config.String()
|
||||
schema['snapcast_enabled'] = config.Boolean()
|
||||
schema['snapcast_host'] = config.String()
|
||||
schema['snapcast_port'] = config.Integer()
|
||||
schema['snapcast_buffer_size'] = config.Integer()
|
||||
return schema
|
||||
|
||||
def setup(self, registry):
|
||||
|
||||
|
||||
# Add web extension
|
||||
registry.add('http:app', {
|
||||
'name': self.ext_name,
|
||||
@ -78,10 +74,10 @@ class ReactRouterHandler(tornado.web.StaticFileHandler):
|
||||
def iris_factory(config, core):
|
||||
|
||||
path = os.path.join( os.path.dirname(__file__), 'static')
|
||||
|
||||
|
||||
return [
|
||||
(
|
||||
r"/images/(.*)",
|
||||
r"/images/(.*)",
|
||||
tornado.web.StaticFileHandler,
|
||||
{
|
||||
'path': config['local-images']['image_dir']
|
||||
@ -89,7 +85,7 @@ def iris_factory(config, core):
|
||||
),
|
||||
(
|
||||
r'/http/([^/]*)',
|
||||
handlers.HttpHandler,
|
||||
handlers.HttpHandler,
|
||||
{
|
||||
'core': core,
|
||||
'config': config
|
||||
@ -98,7 +94,7 @@ def iris_factory(config, core):
|
||||
(
|
||||
r'/ws/?',
|
||||
handlers.WebsocketHandler,
|
||||
{
|
||||
{
|
||||
'core': core,
|
||||
'config': config
|
||||
}
|
||||
|
||||
@ -14,8 +14,6 @@ from pkg_resources import parse_version
|
||||
from tornado.escape import json_encode, json_decode
|
||||
|
||||
from .system import IrisSystemThread
|
||||
from .snapcast import IrisSnapcast
|
||||
from .snapcast_thread import IrisSnapcastThread
|
||||
|
||||
if sys.platform == 'win32':
|
||||
import ctypes
|
||||
@ -37,7 +35,6 @@ class IrisCore(pykka.ThreadingActor):
|
||||
"seed_tracks": [],
|
||||
"results": []
|
||||
}
|
||||
snapcast_daemon = False
|
||||
|
||||
|
||||
##
|
||||
@ -49,43 +46,13 @@ class IrisCore(pykka.ThreadingActor):
|
||||
# Load our commands from file
|
||||
self.commands = self.load_from_file('commands')
|
||||
|
||||
# Start our TCP watcher with no request, so it becomes our
|
||||
# long-running socket connection
|
||||
if self.config['iris'].get('snapcast_enabled'):
|
||||
self.snapcast_daemon = IrisSnapcastThread(self.config, self.broadcast)
|
||||
self.snapcast_daemon.start()
|
||||
|
||||
|
||||
##
|
||||
##
|
||||
# Mopidy is shutting down
|
||||
##
|
||||
def stop(self):
|
||||
logger.info('Stopping Iris')
|
||||
|
||||
if self.snapcast_daemon:
|
||||
logger.info('Stoppping Snapcast daemon')
|
||||
self.snapcast_daemon.close()
|
||||
|
||||
|
||||
##
|
||||
# Make a request to snapcast
|
||||
##
|
||||
def snapcast(self, *args, **kwargs):
|
||||
callback = kwargs.get('callback', None)
|
||||
request_id = kwargs.get('request_id', None)
|
||||
data = kwargs.get('data', {})
|
||||
|
||||
# Start a new thread, just for this request
|
||||
socket = IrisSnapcast(self.config)
|
||||
socket.connect()
|
||||
|
||||
response = socket.request(data)
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
else:
|
||||
return response
|
||||
|
||||
|
||||
##
|
||||
# Save dict object to disk
|
||||
@ -431,8 +398,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
"locale": self.config['iris']['locale'],
|
||||
"spotify_authorization_url": self.config['iris']['spotify_authorization_url'],
|
||||
"lastfm_authorization_url": self.config['iris']['lastfm_authorization_url'],
|
||||
"genius_authorization_url": self.config['iris']['genius_authorization_url'],
|
||||
"snapcast_enabled": self.config['iris']['snapcast_enabled']
|
||||
"genius_authorization_url": self.config['iris']['genius_authorization_url']
|
||||
}
|
||||
}
|
||||
|
||||
@ -939,7 +905,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
request = tornado.httpclient.HTTPRequest(command['url'], connect_timeout=5, method='POST', body=post_data, validate_cert=False, headers=headers)
|
||||
else:
|
||||
request = tornado.httpclient.HTTPRequest(command['url'], connect_timeout=5, validate_cert=False, headers=headers)
|
||||
|
||||
|
||||
# Make the request, and handle any request errors
|
||||
try:
|
||||
command_response = http_client.fetch(request)
|
||||
@ -969,7 +935,7 @@ class IrisCore(pykka.ThreadingActor):
|
||||
'message': 'Command run',
|
||||
'response': command_response_body
|
||||
}
|
||||
|
||||
|
||||
if (callback):
|
||||
callback(response)
|
||||
return
|
||||
|
||||
@ -5,7 +5,3 @@ locale = en_NZ
|
||||
spotify_authorization_url = https://jamesbarnsley.co.nz/iris/auth_spotify.php
|
||||
lastfm_authorization_url = https://jamesbarnsley.co.nz/iris/auth_lastfm.php
|
||||
genius_authorization_url = https://jamesbarnsley.co.nz/iris/auth_genius.php
|
||||
snapcast_enabled = false
|
||||
snapcast_host = localhost
|
||||
snapcast_port = 1705
|
||||
snapcast_buffer_size = 8192
|
||||
|
||||
@ -1,188 +0,0 @@
|
||||
|
||||
from threading import Thread
|
||||
import os, logging, subprocess, socket, json, random, string, select
|
||||
|
||||
# import logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class IrisSnapcast(object):
|
||||
|
||||
socket = None
|
||||
listen = False
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.path = os.path.dirname(__file__)
|
||||
|
||||
|
||||
##
|
||||
# This thread has been started
|
||||
##
|
||||
def connect(self):
|
||||
|
||||
host = str(self.config['iris']['snapcast_host'])
|
||||
port = int(self.config['iris']['snapcast_port'])
|
||||
logger.debug("Connecting to Snapcast on "+host+":"+str(port))
|
||||
|
||||
try:
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(5)
|
||||
self.sock.connect((host, port))
|
||||
|
||||
except socket.gaierror, e:
|
||||
raise Exception(e);
|
||||
|
||||
except socket.error, e:
|
||||
raise Exception(e);
|
||||
|
||||
|
||||
##
|
||||
# Listen for socket activity
|
||||
# Broadcast events to the piped broadcaster
|
||||
#
|
||||
# @param broadcast = Method
|
||||
##
|
||||
def listen(self, broadcast):
|
||||
|
||||
try:
|
||||
self.connect()
|
||||
except Exception, e:
|
||||
logger.error("Could not connect to Snapcast: "+str(e))
|
||||
return
|
||||
|
||||
self.listen = True
|
||||
buffer_size = int(self.config['iris']['snapcast_buffer_size'])
|
||||
|
||||
logger.info("Snapcast listener established")
|
||||
broadcast(data={'method':'snapcast_connected'})
|
||||
|
||||
messages = []
|
||||
data = ""
|
||||
select.select([], [self.sock], [])
|
||||
|
||||
while self.listen:
|
||||
|
||||
# This loop will run constantly in the background, so we need a relatively short
|
||||
# timeout as this would otherwise block the shutdown sequence
|
||||
timeout = 1
|
||||
|
||||
readlist, writelist, exceptionlist = select.select([self.sock], [], [], timeout)
|
||||
|
||||
# Check if we've got any lists
|
||||
if [readlist, writelist, exceptionlist] != [[], [], []]:
|
||||
|
||||
# Rread and print the available data on any of the read list
|
||||
for socket in readlist:
|
||||
|
||||
message = socket.recv(buffer_size)
|
||||
|
||||
try:
|
||||
message = json.loads(message)
|
||||
|
||||
# Prefix with snapcast
|
||||
message['method'] = "snapcast_"+message['method']
|
||||
|
||||
# Broadcast to all clients
|
||||
broadcast(data=message)
|
||||
except:
|
||||
logger.error("Malformed Snapcast message: "+message)
|
||||
|
||||
|
||||
##
|
||||
# Stop our listener
|
||||
#
|
||||
# @param broadcast = Method
|
||||
##
|
||||
def stop_listening(self):
|
||||
self.listen = False
|
||||
|
||||
|
||||
##
|
||||
# Send a single request to Snapcast
|
||||
#
|
||||
# @param data = Dict
|
||||
##
|
||||
def request(self, data):
|
||||
|
||||
# No socket to make request on
|
||||
if self.sock == None:
|
||||
raise Exception("Socket not established");
|
||||
|
||||
# Construct our request, based on the provided data
|
||||
request = {
|
||||
'id': self.generateGuid(),
|
||||
'jsonrpc': '2.0',
|
||||
'method': data['method'],
|
||||
'params': data['params'] if 'params' in data else {}
|
||||
}
|
||||
|
||||
logger.debug("Outgoing Snapcast message")
|
||||
logger.debug(request)
|
||||
|
||||
# Convert to string. For some really nuts reason we need an extra trailing curly brace...
|
||||
request = json.dumps(request)+'}'
|
||||
|
||||
# Attempt to send the request
|
||||
try:
|
||||
self.sock.send(request.encode('ascii')+b"\n")
|
||||
|
||||
except socket.error, e:
|
||||
logger.error("Iris could not send request to Snapcast: %s" % e)
|
||||
|
||||
socket_response = {
|
||||
'error': {
|
||||
'message': "Failed to send request to Snapcast",
|
||||
'data': str(e)
|
||||
}
|
||||
}
|
||||
|
||||
# Wait for response
|
||||
while True:
|
||||
|
||||
try:
|
||||
response = self.sock.recv(8192)
|
||||
|
||||
except socket.error, e:
|
||||
logger.error("Iris failed to receive Snapcast response: %s" % e)
|
||||
socket_response = {
|
||||
'error': {
|
||||
'message': "Failed to receive Snapcast response",
|
||||
'data': str(e)
|
||||
}
|
||||
}
|
||||
|
||||
if not len(response):
|
||||
break
|
||||
|
||||
try:
|
||||
response = json.loads(response)
|
||||
logger.debug("Incoming Snapcast message")
|
||||
logger.debug(response)
|
||||
|
||||
if 'result' in response:
|
||||
socket_response = response['result']
|
||||
else:
|
||||
socket_response = response['error']
|
||||
|
||||
except:
|
||||
logger.error("Iris received malformed Snapcast response: "+response)
|
||||
socket_response = {
|
||||
'error': {
|
||||
'message': "Malformed Snapcast response",
|
||||
'data': response
|
||||
}
|
||||
}
|
||||
|
||||
return socket_response
|
||||
|
||||
|
||||
##
|
||||
# Generate a random string
|
||||
#
|
||||
# Used for connection_ids where none is provided by client
|
||||
# @return string
|
||||
##
|
||||
def generateGuid(self):
|
||||
length = 12
|
||||
return ''.join(random.choice(string.lowercase) for i in range(length))
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
|
||||
from threading import Thread
|
||||
import os, logging, subprocess, socket, json, random, string
|
||||
from .snapcast import IrisSnapcast
|
||||
|
||||
# import logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class IrisSnapcastThread(Thread):
|
||||
|
||||
sock = None
|
||||
|
||||
def __init__(self, config, broadcast):
|
||||
Thread.__init__(self)
|
||||
self.config = config
|
||||
self.broadcast = broadcast
|
||||
|
||||
|
||||
##
|
||||
# This thread has been started
|
||||
##
|
||||
def run(self):
|
||||
self.sock = IrisSnapcast(self.config)
|
||||
self.sock.listen(self.broadcast)
|
||||
|
||||
|
||||
##
|
||||
# Instruction to stop this thread
|
||||
##
|
||||
def close(self):
|
||||
self.sock.stop_listening()
|
||||
@ -95,6 +95,11 @@ let state = {
|
||||
enabled: false,
|
||||
},
|
||||
snapcast: {
|
||||
enabled: false,
|
||||
connected: false,
|
||||
host: window.location.hostname,
|
||||
port: '1780',
|
||||
ssl: (window.location.protocol === 'https:'),
|
||||
streams: {},
|
||||
groups: {},
|
||||
clients: {},
|
||||
|
||||
Reference in New Issue
Block a user