Added support for Plex to Chromecast
This commit is contained in:
parent
42053dcf3b
commit
26f3842724
4 changed files with 150 additions and 25 deletions
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
0
platypush/plugins/media/lib/__init__.py
Normal file
0
platypush/plugins/media/lib/__init__.py
Normal file
91
platypush/plugins/media/lib/plexcast.py
Normal file
91
platypush/plugins/media/lib/plexcast.py
Normal 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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue