forked from platypush/platypush
[media.chromecast] Resource clean up + new API adaptations.
- `pychromecast.get_chromecasts` returns both a list of devices and a browser object. Since the Chromecast plugin is the most likely culprit of the excessive number of open MDNS sockets, it seems that we may need to explicitly stop discovery on the browser and close the ZeroConf object after the discovery is done. - I was still using an ancient version of pychromecast on my RPi4, and I didn't notice that more recent versions implemented several breaking changes. Adapted the code to cope with those changes.
This commit is contained in:
parent
4972c8bdcf
commit
f99f6bdab9
2 changed files with 55 additions and 41 deletions
|
@ -31,7 +31,6 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
:param poll_interval: How often the plugin should poll for new/removed
|
:param poll_interval: How often the plugin should poll for new/removed
|
||||||
Chromecast devices (default: 30 seconds).
|
Chromecast devices (default: 30 seconds).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(poll_interval=poll_interval, **kwargs)
|
super().__init__(poll_interval=poll_interval, **kwargs)
|
||||||
|
|
||||||
self._is_local = False
|
self._is_local = False
|
||||||
|
@ -42,12 +41,19 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
|
|
||||||
def _get_chromecasts(self, *args, **kwargs):
|
def _get_chromecasts(self, *args, **kwargs):
|
||||||
with self._refresh_lock:
|
with self._refresh_lock:
|
||||||
chromecasts = pychromecast.get_chromecasts(*args, **kwargs)
|
ret = pychromecast.get_chromecasts(*args, **kwargs)
|
||||||
|
|
||||||
|
if isinstance(ret, tuple):
|
||||||
|
chromecasts, browser = ret
|
||||||
|
if browser:
|
||||||
|
browser.stop_discovery()
|
||||||
|
if browser.zc:
|
||||||
|
browser.zc.close()
|
||||||
|
|
||||||
if isinstance(chromecasts, tuple):
|
|
||||||
return chromecasts[0]
|
|
||||||
return chromecasts
|
return chromecasts
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_device_property(cc, prop: str):
|
def _get_device_property(cc, prop: str):
|
||||||
if hasattr(cc, 'device'): # Previous pychromecast API
|
if hasattr(cc, 'device'): # Previous pychromecast API
|
||||||
|
@ -58,14 +64,25 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
"""
|
"""
|
||||||
Convert a Chromecast object and its status to a dictionary.
|
Convert a Chromecast object and its status to a dictionary.
|
||||||
"""
|
"""
|
||||||
|
if hasattr(cc, 'cast_info'): # Newer PyChromecast API
|
||||||
|
host = cc.cast_info.host
|
||||||
|
port = cc.cast_info.port
|
||||||
|
elif hasattr(cc, 'host'):
|
||||||
|
host = getattr(cc, 'host', None)
|
||||||
|
port = getattr(cc, 'port', None)
|
||||||
|
elif hasattr(cc, 'uri'):
|
||||||
|
host, port = cc.uri.split(':')
|
||||||
|
else:
|
||||||
|
raise RuntimeError('Invalid Chromecast object')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'type': cc.cast_type,
|
'type': cc.cast_type,
|
||||||
'name': cc.name,
|
'name': cc.name,
|
||||||
'manufacturer': self._get_device_property(cc, 'manufacturer'),
|
'manufacturer': self._get_device_property(cc, 'manufacturer'),
|
||||||
'model_name': cc.model_name,
|
'model_name': cc.model_name,
|
||||||
'uuid': str(cc.uuid),
|
'uuid': str(cc.uuid),
|
||||||
'address': cc.host if hasattr(cc, 'host') else cc.uri.split(':')[0],
|
'address': host,
|
||||||
'port': cc.port if hasattr(cc, 'port') else int(cc.uri.split(':')[1]),
|
'port': port,
|
||||||
'status': (
|
'status': (
|
||||||
{
|
{
|
||||||
'app': {
|
'app': {
|
||||||
|
@ -284,24 +301,23 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
chromecast = chromecast or self.chromecast
|
chromecast = chromecast or self.chromecast
|
||||||
cast = self.get_chromecast(chromecast)
|
cast = self.get_chromecast(chromecast)
|
||||||
|
|
||||||
if cast.media_controller.is_paused:
|
if cast.media_controller.status.player_is_paused:
|
||||||
cast.media_controller.play()
|
cast.media_controller.play()
|
||||||
elif cast.media_controller.is_playing:
|
elif cast.media_controller.status.player_is_playing:
|
||||||
cast.media_controller.pause()
|
cast.media_controller.pause()
|
||||||
|
|
||||||
cast.wait()
|
cast.wait()
|
||||||
return self.status(chromecast=chromecast)
|
return self.status(chromecast=chromecast)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop(self, *_, chromecast: Optional[str] = None, **__):
|
def stop(self, *_, chromecast: Optional[str] = None, **__): # type: ignore
|
||||||
chromecast = chromecast or self.chromecast
|
chromecast = chromecast or self.chromecast
|
||||||
if not chromecast:
|
if not chromecast:
|
||||||
return
|
return None
|
||||||
|
|
||||||
cast = self.get_chromecast(chromecast)
|
cast = self.get_chromecast(chromecast)
|
||||||
cast.media_controller.stop()
|
cast.media_controller.stop()
|
||||||
cast.wait()
|
cast.wait()
|
||||||
|
|
||||||
return self.status(chromecast=chromecast)
|
return self.status(chromecast=chromecast)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -347,51 +363,51 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
def is_playing(self, chromecast: Optional[str] = None, **_):
|
def is_playing(self, chromecast: Optional[str] = None, **_):
|
||||||
return self.get_chromecast(
|
return self.get_chromecast(
|
||||||
chromecast or self.chromecast
|
chromecast or self.chromecast
|
||||||
).media_controller.is_playing
|
).media_controller.status.player_is_playing
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def is_paused(self, chromecast: Optional[str] = None, **_):
|
def is_paused(self, chromecast: Optional[str] = None, **_):
|
||||||
return self.get_chromecast(
|
return self.get_chromecast(
|
||||||
chromecast or self.chromecast
|
chromecast or self.chromecast
|
||||||
).media_controller.is_paused
|
).media_controller.status.player_is_paused
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def is_idle(self, chromecast: Optional[str] = None):
|
def is_idle(self, chromecast: Optional[str] = None):
|
||||||
return self.get_chromecast(
|
return self.get_chromecast(
|
||||||
chromecast or self.chromecast
|
chromecast or self.chromecast
|
||||||
).media_controller.is_idle
|
).media_controller.status.player_is_idle
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def list_subtitles(self, chromecast: Optional[str] = None):
|
def list_subtitles(self, chromecast: Optional[str] = None):
|
||||||
return self.get_chromecast(
|
return self.get_chromecast(
|
||||||
chromecast or self.chromecast
|
chromecast or self.chromecast
|
||||||
).media_controller.subtitle_tracks
|
).media_controller.status.subtitle_tracks
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def enable_subtitles(
|
def enable_subtitles(
|
||||||
self, chromecast: Optional[str] = None, track_id: Optional[str] = None, **_
|
self, chromecast: Optional[str] = None, track_id: Optional[int] = None, **_
|
||||||
):
|
):
|
||||||
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
||||||
if track_id is not None:
|
if track_id is not None:
|
||||||
return mc.enable_subtitle(track_id)
|
return mc.enable_subtitle(track_id)
|
||||||
if mc.subtitle_tracks:
|
if mc.status.subtitle_tracks:
|
||||||
return mc.enable_subtitle(mc.subtitle_tracks[0].get('trackId'))
|
return mc.enable_subtitle(mc.status.subtitle_tracks[0].get('trackId'))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def disable_subtitles(
|
def disable_subtitles(
|
||||||
self, chromecast: Optional[str] = None, track_id: Optional[str] = None, **_
|
self, chromecast: Optional[str] = None, track_id: Optional[int] = None, **_
|
||||||
):
|
):
|
||||||
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
||||||
if track_id:
|
if track_id:
|
||||||
return mc.disable_subtitle(track_id)
|
return mc.disable_subtitle(track_id)
|
||||||
if mc.current_subtitle_tracks:
|
if mc.status.current_subtitle_tracks:
|
||||||
return mc.disable_subtitle(mc.current_subtitle_tracks[0])
|
return mc.disable_subtitle(mc.status.current_subtitle_tracks[0])
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def toggle_subtitles(self, chromecast: Optional[str] = None, **_):
|
def toggle_subtitles(self, chromecast: Optional[str] = None, **_):
|
||||||
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
mc = self.get_chromecast(chromecast or self.chromecast).media_controller
|
||||||
all_subs = mc.status.subtitle_tracks
|
all_subs = mc.status.subtitle_tracks
|
||||||
cur_subs = mc.status.status.current_subtitle_tracks
|
cur_subs = mc.status.current_subtitle_tracks
|
||||||
|
|
||||||
if cur_subs:
|
if cur_subs:
|
||||||
return self.disable_subtitles(chromecast, cur_subs[0])
|
return self.disable_subtitles(chromecast, cur_subs[0])
|
||||||
|
@ -511,7 +527,6 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
self,
|
self,
|
||||||
chromecast: Optional[str] = None,
|
chromecast: Optional[str] = None,
|
||||||
timeout: Optional[float] = None,
|
timeout: Optional[float] = None,
|
||||||
blocking: bool = True,
|
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Disconnect a Chromecast and wait for it to terminate
|
Disconnect a Chromecast and wait for it to terminate
|
||||||
|
@ -520,11 +535,9 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
the default configured Chromecast will be used.
|
the default configured Chromecast will be used.
|
||||||
:param timeout: Number of seconds to wait for disconnection (default:
|
:param timeout: Number of seconds to wait for disconnection (default:
|
||||||
None: block until termination).
|
None: block until termination).
|
||||||
:param blocking: If set (default), then the code will wait until
|
|
||||||
disconnection, otherwise it will return immediately.
|
|
||||||
"""
|
"""
|
||||||
cast = self.get_chromecast(chromecast)
|
cast = self.get_chromecast(chromecast)
|
||||||
cast.disconnect(timeout=timeout, blocking=blocking)
|
cast.disconnect(timeout=timeout)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def join(self, chromecast: Optional[str] = None, timeout: Optional[float] = None):
|
def join(self, chromecast: Optional[str] = None, timeout: Optional[float] = None):
|
||||||
|
@ -550,17 +563,6 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
cast = self.get_chromecast(chromecast)
|
cast = self.get_chromecast(chromecast)
|
||||||
cast.quit_app()
|
cast.quit_app()
|
||||||
|
|
||||||
@action
|
|
||||||
def reboot(self, chromecast: Optional[str] = None):
|
|
||||||
"""
|
|
||||||
Reboots the Chromecast
|
|
||||||
|
|
||||||
:param chromecast: Chromecast to cast to. If none is specified, then
|
|
||||||
the default configured Chromecast will be used.
|
|
||||||
"""
|
|
||||||
cast = self.get_chromecast(chromecast)
|
|
||||||
cast.reboot()
|
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_volume(self, volume: float, chromecast: Optional[str] = None):
|
def set_volume(self, volume: float, chromecast: Optional[str] = None):
|
||||||
"""
|
"""
|
||||||
|
@ -621,7 +623,7 @@ class MediaChromecastPlugin(MediaPlugin, RunnablePlugin):
|
||||||
"""
|
"""
|
||||||
chromecast = chromecast or self.chromecast
|
chromecast = chromecast or self.chromecast
|
||||||
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.media_controller.status.volume_muted)
|
||||||
cast.wait()
|
cast.wait()
|
||||||
return self.status(chromecast=chromecast)
|
return self.status(chromecast=chromecast)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
# pylint: disable=too-few-public-methods
|
import logging
|
||||||
class SubtitlesAsyncHandler:
|
|
||||||
|
from pychromecast.controllers.media import MediaStatusListener
|
||||||
|
|
||||||
|
|
||||||
|
class SubtitlesAsyncHandler(MediaStatusListener):
|
||||||
"""
|
"""
|
||||||
This class is used to enable subtitles when the media is loaded.
|
This class is used to enable subtitles when the media is loaded.
|
||||||
"""
|
"""
|
||||||
|
@ -8,9 +12,17 @@ class SubtitlesAsyncHandler:
|
||||||
self.mc = mc
|
self.mc = mc
|
||||||
self.subtitle_id = subtitle_id
|
self.subtitle_id = subtitle_id
|
||||||
self.initialized = False
|
self.initialized = False
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def new_media_status(self, *_):
|
def new_media_status(self, *_):
|
||||||
if self.subtitle_id and not self.initialized:
|
if self.subtitle_id and not self.initialized:
|
||||||
self.mc.update_status()
|
self.mc.update_status()
|
||||||
self.mc.enable_subtitle(self.subtitle_id)
|
self.mc.enable_subtitle(self.subtitle_id)
|
||||||
self.initialized = True
|
self.initialized = True
|
||||||
|
|
||||||
|
def load_media_failed(self, queue_item_id: int, error_code: int) -> None:
|
||||||
|
self.logger.warning(
|
||||||
|
"Failed to load media with queue_item_id %d, error code: %d",
|
||||||
|
queue_item_id,
|
||||||
|
error_code,
|
||||||
|
)
|
||||||
|
|
Loading…
Reference in a new issue