Update chromecast api add ability to pause stream

This commit is contained in:
Michal Szczepanski 2020-03-03 23:13:53 +01:00
parent 63ddf6e3a6
commit 37825bb157
7 changed files with 66 additions and 31 deletions

@ -1,8 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
""""UPNPDevice model"""
""""Device model"""
import graphene
from .service import UPNPService
from graphql.execution.base import ResolveInfo
class CastStatus(graphene.ObjectType):
"""Chromecast CastStatus"""
@ -79,13 +80,18 @@ class ChromecastDevice(graphene.ObjectType):
media_controller = graphene.Field(MediaController)
status = graphene.Field(CastStatus)
class ChromecastPause(graphene.Mutation):
"""Delete resource location"""
class Arguments:
"""Delete ResourceLocation arguments"""
uid = graphene.String(required=True)
class UPNPDevice(graphene.ObjectType):
"""UPNPDevice"""
id = graphene.ID()
deviceType = graphene.String()
friendlyName = graphene.String()
manufacturer = graphene.String()
modelName = graphene.String()
UDN = graphene.String()
serviceList = graphene.List(UPNPService)
Output = graphene.Boolean
def mutate(self, info: ResolveInfo, uid: graphene.String) -> graphene.Boolean: # pylint: disable=W0622
"""Delete ResourceLocation"""
if uid not in cache.CHROMECAST:
raise error.ChromecastUUIDError(uid)
data = cache.CHROMECAST[uid]
data.device.media_controller.pause()
return True

@ -3,7 +3,7 @@
"""Mutation"""
import graphene
from .model import firststart
from .model import resource_location
from .model import resource_location, device
class Mutation(graphene.ObjectType):
@ -17,3 +17,5 @@ class Mutation(graphene.ObjectType):
resourceLocationAdd = resource_location.Add.Field()
resourceLocationChange = resource_location.Change.Field()
resourceLocationDelete = resource_location.Delete.Field()
chromecastPause = device.ChromecastPause.Field()

@ -1,7 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Cache - temporary before database introduction"""
from typing import Dict
from .model import Device
FIRST_START = True
CHROMECAST = dict()
CHROMECAST: Dict[str, Device] = dict()

@ -9,3 +9,11 @@ class ResourcePathError(tornado.web.HTTPError):
code = 1001
def __init__(self, message):
tornado.web.HTTPError.__init__(self, reason=message)
class ChromecastUUIDError(tornado.web.HTTPError):
"""Invalid Chromecast uuid"""
code = 1001
message = "Chromecast with uuid '{}' not found"
def __init__(self, uid):
tornado.web.HTTPError.__init__(self, reason=self.message.format(uid))

21
playlistcast/model.py Normal file

@ -0,0 +1,21 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Application models"""
from .api.model.device import MediaStatus
from .api.model.subscription import SubscriptionModel
from playlistcast import util
class Device:
"""Device with api and interface data"""
def __init__(self, device, data):
self.device = device
self.data = data
self.device.media_controller.register_status_listener(self)
def new_media_status(self, status):
"""Subscribe for chromecast status messages"""
s = MediaStatus()
s.uuid = self.data.uuid
util.convert(status, s, ('uuid',))
SubscriptionModel.media_status.on_next(s)
print(self.data.name, self.data.uuid, status)

@ -8,7 +8,7 @@ from datetime import timedelta
import pychromecast
import pychromecast.controllers.media as chromecast_media
from playlistcast.api.model.device import ChromecastDevice, MediaController, MediaStatus, CastStatus
from playlistcast import util, cache
from playlistcast import util, cache, model
from playlistcast.api.subscription import SubscriptionModel
LOG = logging.getLogger('playlistcast.protocol.chromecast')
@ -122,23 +122,9 @@ class DummyChromeast:
#LOG.debug(status)
pass
class Device:
def __init__(self, device, data):
self.device = device
self.data = data
self.device.media_controller.register_status_listener(self)
def new_media_status(self, status):
"""Subscribe for chromecast status messages"""
s = MediaStatus()
s.uuid = self.data.uuid
util.convert(status, s, ('uuid',))
SubscriptionModel.media_status.on_next(s)
print(self.data.name, self.data.uuid, status)
async def list_devices() -> List[ChromecastDevice]:
"""Detect and return chromecast devices"""
chromecasts = pychromecast.get_chromecasts()
chromecasts = await util.awaitable(pychromecast.get_chromecasts)
output = []
all_keys = list(cache.CHROMECAST.keys())
for pych in chromecasts:
@ -148,7 +134,7 @@ async def list_devices() -> List[ChromecastDevice]:
device = cache.CHROMECAST[uid]
output.append(device.data)
else:
pych.wait(timeout=30)
await util.awaitable(pych.wait, timeout=30)
# pychromecast
ch = ChromecastDevice()
util.convert(pych, ch, ('media_controller', 'status'))
@ -174,7 +160,7 @@ async def list_devices() -> List[ChromecastDevice]:
mc.status = ms
ch.media_controller = mc
output.append(ch)
device = Device(pych, ch)
device = model.Device(pych, ch)
cache.CHROMECAST[uid] = device
# REMOVE remaining keys cause those are expired devices
# TODO send update to UI

@ -3,7 +3,11 @@
"""Utility methods"""
from string import Template
from datetime import timedelta
from concurrent.futures import ThreadPoolExecutor
import socket
import asyncio
POOL = ThreadPoolExecutor()
#https://stackoverflow.com/a/8907269
class TimeDeltaFormatter(Template):
@ -41,3 +45,9 @@ def convert(src, dest, ignore):
continue
value = getattr(src, attr)
setattr(dest, attr, value)
# https://gist.github.com/phizaz/20c36c6734878c6ec053245a477572ec
def awaitable(fn, *args, **kwargs):
"""Turn sync method to async"""
future = POOL.submit(fn, *args, **kwargs)
return asyncio.wrap_future(future)