6 from django.core.management.base import BaseCommand, CommandError
8 from nonstop.app_settings import app_settings
15 logger = logging.getLogger('switch-jack')
18 class ClientWebSocketResponse(aiohttp.client_ws.ClientWebSocketResponse):
19 async def pong(self, message):
20 # update jack connections regularly, in case of jack ports going down momentarily
21 self.command.update_jack_connections(self.command.currently_active)
22 await super().pong(message)
25 class Command(BaseCommand):
26 help = 'jack source switch'
28 def handle(self, verbosity, **options):
30 raise CommandError('missing jack module (install python3-jack-client?)')
31 self.verbosity = verbosity
32 asyncio.run(self.main())
35 if not app_settings.SWITCH_WS_URL:
36 raise CommandError('missing switch_ws_url')
37 self.currently_active = None
39 ClientWebSocketResponse.command = self
42 async with aiohttp.ClientSession(ws_response_class=ClientWebSocketResponse) as session:
43 async with session.ws_connect(app_settings.SWITCH_WS_URL, heartbeat=5) as ws:
44 logger.info('waiting for messages')
45 sleep_duration = 0.2 # reset sleep duration to baseline
47 if msg.type == aiohttp.WSMsgType.TEXT:
49 msg = json.loads(msg.data)
52 if msg.get('active') != self.currently_active:
53 self.currently_active = msg.get('active')
54 logger.info('setting source: %s', self.currently_active)
55 self.update_jack_connections(self.currently_active)
56 elif msg.type == aiohttp.WSMsgType.ERROR:
58 logger.debug('lost websocket connection')
59 except aiohttp.ClientError as e:
60 logger.warning('websocket error (%s)' % e)
62 await asyncio.sleep(min(sleep_duration, 5))
63 except asyncio.CancelledError:
64 # most probably because of a keyboard interrupt, quit silently
67 def update_jack_connections(self, active):
68 if active not in app_settings.SWITCH_IN_PORTS:
69 logger.info('unsupported source: %s', active)
71 out_ports = app_settings.SWITCH_OUT_PORTS
72 with jack.Client('switch-jack') as client:
73 known_ports = {x.name for x in client.get_ports(is_audio=True)}
74 for dports in out_ports.values():
75 if any(x not in known_ports for x in dports):
76 logger.error('unavailable destination ports %r', dports)
78 for port_id, port_names in app_settings.SWITCH_IN_PORTS.items():
79 if any(x not in known_ports for x in port_names):
80 logger.error('unavailable source ports %r', port_names)
84 self.jack_connect(client, port_names[0], dports[0])
85 self.jack_connect(client, port_names[1], dports[1])
87 self.jack_disconnect(client, port_names[0], dports[0])
88 self.jack_disconnect(client, port_names[1], dports[1])
89 except jack.JackError as e:
90 logger.error('jack error: %s' % e)
92 def jack_connect(self, client, in_port, out_port):
93 connections = [x.name for x in client.get_all_connections(in_port)]
94 if out_port not in connections:
96 logger.info('connecting %s and %s', in_port, out_port)
97 client.connect(in_port, out_port)
99 def jack_disconnect(self, client, in_port, out_port):
100 connections = [x.name for x in client.get_all_connections(in_port)]
101 if out_port in connections:
103 logger.info('disconnecting %s and %s', in_port, out_port)
104 client.disconnect(in_port, out_port)