diff options
Diffstat (limited to 'mediaplayer.py')
-rw-r--r-- | mediaplayer.py | 186 |
1 files changed, 186 insertions, 0 deletions
diff --git a/mediaplayer.py b/mediaplayer.py new file mode 100644 index 0000000..365eef2 --- /dev/null +++ b/mediaplayer.py @@ -0,0 +1,186 @@ +import json +from json import JSONDecodeError +from random import randint +import sys +import asyncio +from random import randint +from websockets import connect, ConnectionClosedError +import concurrent +from enum import IntEnum +from os import environ +from argparse import ArgumentParser + +IPADDR = '192.168.234.202' +PORT = '30016' +# PORT = '30031' +TOKEN = 'HELLO' +UUID = 'magic' +URL = f'ws://{IPADDR}:{PORT}/api?token={TOKEN}&uuid={UUID}' + +# x-AFB-ws-json1 message Type +class AFBT(IntEnum): + REQUEST = 2, + RESPONSE = 3, + ERROR = 4, + EVENT = 5 + +msgq = {} + +def addrequest(msgid, msg): + msgq[msgid] = {'request': msg, 'response': None} + +def addresponse(msgid, msg): + if msgid in msgq.keys(): + msgq[msgid]['response'] = msg + + +class MediaPlayerService: + api = None + url = None + ip = None + port = None + token = None + uuid = None + + def __init__(self, url=None, api='mediaplayer', ip='127.0.0.1', port='30000', token='HELLO', uuid='magic'): + self.api = api + self.url = url + self.ip = ip + self.port = port + self.token = token + self.uuid = uuid + + # if url is set, disregard other params; if not - construct url +#TODO: finish implementing constructor with params + + def __await__(self): + return self._async_init().__await__() + + async def __aenter__(self): + return self._async_init() + + async def _async_init(self): + self._conn = connect(uri=URL, subprotocols=['x-afb-ws-json1'], close_timeout=0, ping_interval=None) + self.websocket = await self._conn.__aenter__() + self.url = URL + self.api = 'mediaplayer' + return self + + + async def __aexit__(self, *args, **kwargs): + await self._conn.__aexit__(*args, **kwargs) + + + async def close(self): + await self._conn.__aexit__(*sys.exc_info()) + + async def send(self, message): + await self.websocket.send(message) + + async def receive(self): + return await self.websocket.recv() + + async def listener(self): + try: + while True: + msg = await self.receive() + print(f"received {msg}") + try: + data = json.loads(msg) + if isinstance(data,list): + if data[0] == AFBT.RESPONSE and str.isnumeric(data[1]): + msgid = int(data[1]) + if msgid in msgq: + addresponse(msgid, data) + + + except JSONDecodeError: + print("not decoding a non-json message") + + except KeyboardInterrupt: + pass + except asyncio.CancelledError: + print("websocket listener coroutine stopped") + + async def playlist(self): + verb = 'playlist' + msgid = randint(0, 999999) + addrequest(msgid, msg) + await self.send(f'[2,"{msgid}","{self.api}/{verb}",""]') + + async def subscribe(self, event='metadata'): # event could also be 'playlist' instead of 'metadata' + verb = 'subscribe' + msgid = randint(0, 999999) + msg = f'[2,"{msgid}","{self.api}/{verb}",{{"value": "{event}"}}]' + addrequest(msgid, msg) + await self.send(msg) + + async def unsubscribe(self, event='metadata'): + verb = 'unsubscribe' + msgid = randint(0, 999999) + msg = f'[2,"{msgid}","{self.api}/{verb}",{{"value": "{event}"}}]' + addrequest(msgid, msg) + await self.send(msg) + + async def control(self, name, value=None): + verb = 'controls' + loopstate = ['off', 'playlist', 'track'] + + controls = { + 'play': None, + 'pause': None, + 'previous': None, + 'next': None, + 'seek': 'position', + 'fast-forward': 'position', + 'rewind': 'position', + 'pick-track': 'index', + 'volume': 'volume', + 'loop': 'state' + } + assert name in controls.keys(), 'Tried to use non-existant {name} as control for {self.api}' + + msgid = randint(0, 999999) + if name in ['play', 'pause', 'previous', 'next']: + msg = f'[2,"{msgid}","{self.api}/{verb}", {{"value": "{name}"}}]' + elif name in ['seek', 'fast-forward', 'rewind']: + assert value > 0, "Tried to seek with negative integer" + msg = f'[2,"{msgid}","{self.api}/{verb}", {{"value": "{name}", "position": "{str(value)}"}}]' + elif name == 'pick-track': + assert type(value) is int, "Try picking a song with an integer" + assert value > 0, "Tried to pick a song with a negative integer" + msg = f'[2,"{msgid}","{self.api}/{verb}", {{"value": "{name}", "index": {str(value)}}}]' + elif name == 'volume': + assert type(value) is int, "Try setting the volume with an integer" + assert value > 0, "Tried to set the volume with a negative integer, use values betwen 0-100" + assert value < 100, "Tried to set the volume over 100%, use values betwen 0-100" + msg = f'[2,"{msgid}","{self.api}/{verb}", {{"value": "{name}", "{name}": {str(value)}}}]' + elif name == 'loop': + assert value in loopstate, f'Tried to set invalid loopstate - {value}, use "off", "playlist" or "track"' + msg = f'[2,"{msgid}","{self.api}/{verb}", {{"value": "{name}", "{controls[name]}": {str(value)}}}]' + addrequest(msgid, msg) + + await self.send(msg) + + +async def main(loop): + MPS = await MediaPlayerService() + listener = loop.create_task(MPS.listener()) + try: + await MPS.subscribe() + await MPS.subscribe('playlist') + await MPS.control('pick-track', 6) + await listener + except ConnectionClosedError as e: + print("Connection timed out or closed abnormally. Trying to reconnect...") + result = await MPS._async_init() + print(result) + except KeyboardInterrupt: + pass + + listener.cancel() + await MPS.unsubscribe() + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(main(loop)) |