Added support for Plex to Chromecast

This commit is contained in:
Fabio Manganiello 2018-11-13 01:29:24 +01:00
parent 42053dcf3b
commit 26f3842724
4 changed files with 150 additions and 25 deletions

View file

@ -59,7 +59,7 @@ class MediaChromecastPlugin(Plugin):
} for cc in pychromecast.get_chromecasts() ] } for cc in pychromecast.get_chromecasts() ]
def _get_chromecast(self, chromecast=None): def get_chromecast(self, chromecast=None):
if not chromecast: if not chromecast:
if not self.chromecast: if not self.chromecast:
raise RuntimeError('No Chromecast specified nor default Chromecast configured') raise RuntimeError('No Chromecast specified nor default Chromecast configured')
@ -67,16 +67,12 @@ class MediaChromecastPlugin(Plugin):
if chromecast not in self.chromecasts: if chromecast not in self.chromecasts:
chromecasts = pychromecast.get_chromecasts() self.chromecasts = pychromecast.get_chromecasts()
cast = next(cc for cc in pychromecast.get_chromecasts() cast = next(cc for cc in self.chromecasts
if cc.device.friendly_name == chromecast) if cc.device.friendly_name == chromecast)
self.chromecasts[chromecast] = cast
else: else:
cast = self.chromecasts[chromecast] cast = self.chromecasts[chromecast]
if not cast:
raise RuntimeError('No such Chromecast: {}'.format(chromecast))
return cast return cast
@action @action
@ -125,9 +121,11 @@ class MediaChromecastPlugin(Plugin):
:type subtitle_id: int :type subtitle_id: int
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.wait() cast.wait()
mc = cast.media_controller mc = cast.media_controller
mc.namespace = 'urn:x-cast:com.google.cast.sse'
mc.play_media(media, content_type, title=title, thumb=image_url, mc.play_media(media, content_type, title=title, thumb=image_url,
current_time=current_time, autoplay=autoplay, current_time=current_time, autoplay=autoplay,
stream_type=stream_type, subtitles=subtitles, stream_type=stream_type, subtitles=subtitles,
@ -151,7 +149,7 @@ class MediaChromecastPlugin(Plugin):
:type blocking: bool :type blocking: bool
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.disconnect(timeout=timeout, blocking=blocking) cast.disconnect(timeout=timeout, blocking=blocking)
@action @action
@ -169,7 +167,7 @@ class MediaChromecastPlugin(Plugin):
:type blocking: bool :type blocking: bool
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.join(timeout=timeout, blocking=blocking) cast.join(timeout=timeout, blocking=blocking)
@action @action
@ -181,7 +179,7 @@ class MediaChromecastPlugin(Plugin):
:type chromecast: str :type chromecast: str
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.quit_app() cast.quit_app()
@action @action
@ -193,7 +191,7 @@ class MediaChromecastPlugin(Plugin):
:type chromecast: str :type chromecast: str
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.reboot() cast.reboot()
@action @action
@ -208,7 +206,7 @@ class MediaChromecastPlugin(Plugin):
:type chromecast: str :type chromecast: str
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.set_volume(volume/100) cast.set_volume(volume/100)
@action @action
@ -223,7 +221,7 @@ class MediaChromecastPlugin(Plugin):
:type delta: float :type delta: float
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
delta /= 100 delta /= 100
cast.volume_up(min(delta, 1)) cast.volume_up(min(delta, 1))
@ -240,7 +238,7 @@ class MediaChromecastPlugin(Plugin):
:type delta: float :type delta: float
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
delta /= 100 delta /= 100
cast.volume_down(max(delta, 0)) cast.volume_down(max(delta, 0))
@ -254,7 +252,7 @@ class MediaChromecastPlugin(Plugin):
:type chromecast: str :type chromecast: str
""" """
cast = self._get_chromecast(chromecast) cast = self.get_chromecast(chromecast)
cast.set_volume_muted(not cast.status.volume_muted) cast.set_volume_muted(not cast.status.volume_muted)

View file

View file

@ -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:

View file

@ -1,6 +1,7 @@
from plexapi.myplex import MyPlexAccount from plexapi.myplex import MyPlexAccount
from plexapi.video import Movie, Show from plexapi.video import Movie, Show
from platypush.context import get_plugin
from platypush.plugins import Plugin, action 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 @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, Search and play content on a client or a Chromecast. If no search filter
a play event will be sent to the specified client. 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 NOTE: Adding and managing play queues through the Plex API isn't fully
supported yet, therefore in case multiple items are returned from the supported yet, therefore in case multiple items are returned from the
search only the first one will be played. search only the first one will be played.
:param client: Client name :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.) :param kwargs: Search filter (e.g. title, section, unwatched, director etc.)
:type kwargs: dict :type kwargs: dict
""" """
client = plex.client(client) if not client and not chromecast:
if not kwargs: raise RuntimeError('No client nor chromecast specified')
return client.play()
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: if not results:
self.logger.info('No results for {}'.format(kwargs)) self.logger.info('No results for {}'.format(kwargs))
return return
item = results[0] 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 @action
@ -424,5 +459,6 @@ class MediaPlexPlugin(Plugin):
return _item return _item
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et: