From 26f3842724a60e5e7e86a4a54cccb9e3a21db23b Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Tue, 13 Nov 2018 01:29:24 +0100 Subject: [PATCH] Added support for Plex to Chromecast --- platypush/plugins/media/chromecast.py | 30 ++++---- platypush/plugins/media/lib/__init__.py | 0 platypush/plugins/media/lib/plexcast.py | 91 +++++++++++++++++++++++++ platypush/plugins/media/plex.py | 54 ++++++++++++--- 4 files changed, 150 insertions(+), 25 deletions(-) create mode 100644 platypush/plugins/media/lib/__init__.py create mode 100644 platypush/plugins/media/lib/plexcast.py diff --git a/platypush/plugins/media/chromecast.py b/platypush/plugins/media/chromecast.py index 7e4866900..32ba7f048 100644 --- a/platypush/plugins/media/chromecast.py +++ b/platypush/plugins/media/chromecast.py @@ -59,7 +59,7 @@ class MediaChromecastPlugin(Plugin): } for cc in pychromecast.get_chromecasts() ] - def _get_chromecast(self, chromecast=None): + def get_chromecast(self, chromecast=None): if not chromecast: if not self.chromecast: raise RuntimeError('No Chromecast specified nor default Chromecast configured') @@ -67,16 +67,12 @@ class MediaChromecastPlugin(Plugin): if chromecast not in self.chromecasts: - chromecasts = pychromecast.get_chromecasts() - cast = next(cc for cc in pychromecast.get_chromecasts() + self.chromecasts = pychromecast.get_chromecasts() + cast = next(cc for cc in self.chromecasts if cc.device.friendly_name == chromecast) - self.chromecasts[chromecast] = cast else: cast = self.chromecasts[chromecast] - if not cast: - raise RuntimeError('No such Chromecast: {}'.format(chromecast)) - return cast @action @@ -125,9 +121,11 @@ class MediaChromecastPlugin(Plugin): :type subtitle_id: int """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.wait() mc = cast.media_controller + mc.namespace = 'urn:x-cast:com.google.cast.sse' + mc.play_media(media, content_type, title=title, thumb=image_url, current_time=current_time, autoplay=autoplay, stream_type=stream_type, subtitles=subtitles, @@ -151,7 +149,7 @@ class MediaChromecastPlugin(Plugin): :type blocking: bool """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.disconnect(timeout=timeout, blocking=blocking) @action @@ -169,7 +167,7 @@ class MediaChromecastPlugin(Plugin): :type blocking: bool """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.join(timeout=timeout, blocking=blocking) @action @@ -181,7 +179,7 @@ class MediaChromecastPlugin(Plugin): :type chromecast: str """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.quit_app() @action @@ -193,7 +191,7 @@ class MediaChromecastPlugin(Plugin): :type chromecast: str """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.reboot() @action @@ -208,7 +206,7 @@ class MediaChromecastPlugin(Plugin): :type chromecast: str """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.set_volume(volume/100) @action @@ -223,7 +221,7 @@ class MediaChromecastPlugin(Plugin): :type delta: float """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) delta /= 100 cast.volume_up(min(delta, 1)) @@ -240,7 +238,7 @@ class MediaChromecastPlugin(Plugin): :type delta: float """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) delta /= 100 cast.volume_down(max(delta, 0)) @@ -254,7 +252,7 @@ class MediaChromecastPlugin(Plugin): :type chromecast: str """ - cast = self._get_chromecast(chromecast) + cast = self.get_chromecast(chromecast) cast.set_volume_muted(not cast.status.volume_muted) diff --git a/platypush/plugins/media/lib/__init__.py b/platypush/plugins/media/lib/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/platypush/plugins/media/lib/plexcast.py b/platypush/plugins/media/lib/plexcast.py new file mode 100644 index 000000000..5f077ab4b --- /dev/null +++ b/platypush/plugins/media/lib/plexcast.py @@ -0,0 +1,91 @@ +""" +Controller for Plex content on a Chromecast device +""" + +from pychromecast.controllers import BaseController + + +MESSAGE_TYPE = 'type' + +TYPE_PLAY = "PLAY" +TYPE_PAUSE = "PAUSE" +TYPE_STOP = "STOP" + +class PlexController(BaseController): + """ Controller to interact with Plex namespace. """ + + def __init__(self): + super(PlexController, self).__init__( + "urn:x-cast:plex", "9AC194DC") + self.app_id="9AC194DC" + self.namespace="urn:x-cast:plex" + self.request_id = 0 + + def stop(self): + """ Send stop command. """ + self.namespace = "urn:x-cast:plex" + self.request_id += 1 + self.send_message({MESSAGE_TYPE: TYPE_STOP}) + + def pause(self): + """ Send pause command. """ + self.namespace = "urn:x-cast:plex" + self.request_id += 1 + self.send_message({MESSAGE_TYPE: TYPE_PAUSE}) + + def play(self): + """ Send play command. """ + self.namespace = "urn:x-cast:plex" + self.request_id += 1 + self.send_message({MESSAGE_TYPE: TYPE_PLAY}) + + + def play_media(self,item,server,medtype="LOAD"): + def app_launched_callback(): + self.set_load(item,server,medtype) + + receiver_ctrl = self._socket_client.receiver_controller + if receiver_ctrl.status.app_id != self.app_id: + receiver_ctrl.launch_app(self.app_id, + callback_function=app_launched_callback) + + def set_load(self,item,server,medtype="LOAD"): + transient_token = server.query("/security/token?type=delegation&scope=all").attrib.get('token') + playqueue = server.createPlayQueue(item).playQueueID + self.request_id += 1 + address = server.url('').split(":")[1][2:] + self.namespace="urn:x-cast:com.google.cast.media" + msg = { + "type": medtype, + "requestId": self.request_id, + "sessionId": "python_player", + "autoplay": True, + "currentTime": 0, + "media":{ + "contentId": item.key, + "streamType": "BUFFERED", + "contentType": "video", + "customData": { + "offset": 0, + "server": { + "machineIdentifier": server.machineIdentifier, + "transcoderVideo": True, + "transcoderVideoRemuxOnly": False, + "transcoderAudio": True, + "version": "1.3.3.3148", + "myPlexSubscription": False, + "isVerifiedHostname": True, + "protocol": "https", + "address": address, + "port": "32400", + "accessToken": transient_token, + }, + "user": {"username": server.myPlexUsername}, + "containerKey": "/playQueues/{}?own=1&window=200".format(playqueue), + }, + } + } + self.send_message(msg, inc_session_id=True) + +# vim:sw=4:ts=4:et: + diff --git a/platypush/plugins/media/plex.py b/platypush/plugins/media/plex.py index ca4644ddf..25c16797f 100644 --- a/platypush/plugins/media/plex.py +++ b/platypush/plugins/media/plex.py @@ -1,6 +1,7 @@ from plexapi.myplex import MyPlexAccount from plexapi.video import Movie, Show +from platypush.context import get_plugin from platypush.plugins import Plugin, action @@ -123,34 +124,68 @@ class MediaPlexPlugin(Plugin): ] + def get_chromecast(self, chromecast): + from .lib.plexcast import PlexController + + hndl = PlexController() + hndl.namespace = 'urn:x-cast:com.google.cast.sse' + cast = get_plugin('media.chromecast').get_chromecast(chromecast) + cast.register_handler(hndl) + + return (cast, hndl) + + @action - def play(self, client, **kwargs): + def play(self, client=None, chromecast=None, **kwargs): """ - Search and play content on a client. If no search filter is specified, - a play event will be sent to the specified client. + Search and play content on a client or a Chromecast. If no search filter + is specified, a play event will be sent to the specified client. NOTE: Adding and managing play queues through the Plex API isn't fully supported yet, therefore in case multiple items are returned from the search only the first one will be played. :param client: Client name - :type client str + :type client: str + + :param chromecast: Chromecast name + :type chromecast: str :param kwargs: Search filter (e.g. title, section, unwatched, director etc.) :type kwargs: dict """ - client = plex.client(client) - if not kwargs: - return client.play() + if not client and not chromecast: + raise RuntimeError('No client nor chromecast specified') - results = self.search(**kwargs) + if client: + client = plex.client(client) + elif chromecast: + (chromecast, handler) = self.get_chromecast(chromecast) + + if not kwargs: + if client: + return client.play() + elif chromecast: + return handler.play() + + if 'section' in kwargs: + library = self.plex.library.section(kwargs.pop('section')) + else: + library = self.plex.library + + results = library.search(**kwargs) if not results: self.logger.info('No results for {}'.format(kwargs)) return item = results[0] - client.playMedia(results[0]) + self.logger.info('Playing {} on {}'.format(item.title, client or chromecast)) + + if client: + return client.playMedia(item) + elif chromecast: + return handler.play_media(item, self.plex) @action @@ -424,5 +459,6 @@ class MediaPlexPlugin(Plugin): return _item + # vim:sw=4:ts=4:et: