Fix Plex media_player.play_media service (#27278)

* First attempt to fix play_media

* More changes to media playback

* Use playqueues, clean up play_media

* Use similar function name, add docstring
This commit is contained in:
jjlawren 2019-10-06 23:02:58 -05:00 committed by Paulus Schoutsen
parent 073bdd672a
commit 0915d927df
2 changed files with 76 additions and 77 deletions

View file

@ -2,10 +2,9 @@
from datetime import timedelta from datetime import timedelta
import json import json
import logging import logging
from xml.etree.ElementTree import ParseError
import plexapi.exceptions import plexapi.exceptions
import plexapi.playlist
import plexapi.playqueue
import requests.exceptions import requests.exceptions
from homeassistant.components.media_player import MediaPlayerDevice from homeassistant.components.media_player import MediaPlayerDevice
@ -16,6 +15,7 @@ from homeassistant.components.media_player.const import (
SUPPORT_NEXT_TRACK, SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE, SUPPORT_PAUSE,
SUPPORT_PLAY, SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK, SUPPORT_PREVIOUS_TRACK,
SUPPORT_STOP, SUPPORT_STOP,
SUPPORT_TURN_OFF, SUPPORT_TURN_OFF,
@ -543,9 +543,6 @@ class PlexClient(MediaPlayerDevice):
@property @property
def supported_features(self): def supported_features(self):
"""Flag media player features that are supported.""" """Flag media player features that are supported."""
if not self._is_player_active:
return 0
# force show all controls # force show all controls
if self.plex_server.show_all_controls: if self.plex_server.show_all_controls:
return ( return (
@ -555,13 +552,11 @@ class PlexClient(MediaPlayerDevice):
| SUPPORT_STOP | SUPPORT_STOP
| SUPPORT_VOLUME_SET | SUPPORT_VOLUME_SET
| SUPPORT_PLAY | SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF | SUPPORT_TURN_OFF
| SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_MUTE
) )
# only show controls when we know what device is connecting
if not self._make:
return 0
# no mute support # no mute support
if self.make.lower() == "shield android tv": if self.make.lower() == "shield android tv":
_LOGGER.debug( _LOGGER.debug(
@ -575,8 +570,10 @@ class PlexClient(MediaPlayerDevice):
| SUPPORT_STOP | SUPPORT_STOP
| SUPPORT_VOLUME_SET | SUPPORT_VOLUME_SET
| SUPPORT_PLAY | SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF | SUPPORT_TURN_OFF
) )
# Only supports play,pause,stop (and off which really is stop) # Only supports play,pause,stop (and off which really is stop)
if self.make.lower().startswith("tivo"): if self.make.lower().startswith("tivo"):
_LOGGER.debug( _LOGGER.debug(
@ -585,8 +582,7 @@ class PlexClient(MediaPlayerDevice):
self.entity_id, self.entity_id,
) )
return SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP | SUPPORT_TURN_OFF return SUPPORT_PAUSE | SUPPORT_PLAY | SUPPORT_STOP | SUPPORT_TURN_OFF
# Not all devices support playback functionality
# Playback includes volume, stop/play/pause, etc.
if self.device and "playback" in self._device_protocol_capabilities: if self.device and "playback" in self._device_protocol_capabilities:
return ( return (
SUPPORT_PAUSE SUPPORT_PAUSE
@ -595,6 +591,7 @@ class PlexClient(MediaPlayerDevice):
| SUPPORT_STOP | SUPPORT_STOP
| SUPPORT_VOLUME_SET | SUPPORT_VOLUME_SET
| SUPPORT_PLAY | SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF | SUPPORT_TURN_OFF
| SUPPORT_VOLUME_MUTE | SUPPORT_VOLUME_MUTE
) )
@ -682,49 +679,74 @@ class PlexClient(MediaPlayerDevice):
return return
src = json.loads(media_id) src = json.loads(media_id)
library = src.get("library_name")
shuffle = src.get("shuffle", 0)
media = None media = None
if media_type == "MUSIC": if media_type == "MUSIC":
media = ( media = self._get_music_media(library, src)
self.device.server.library.section(src["library_name"])
.get(src["artist_name"])
.album(src["album_name"])
.get(src["track_name"])
)
elif media_type == "EPISODE": elif media_type == "EPISODE":
media = self._get_tv_media( media = self._get_tv_media(library, src)
src["library_name"],
src["show_name"],
src["season_number"],
src["episode_number"],
)
elif media_type == "PLAYLIST": elif media_type == "PLAYLIST":
media = self.device.server.playlist(src["playlist_name"]) media = self.plex_server.playlist(src["playlist_name"])
elif media_type == "VIDEO": elif media_type == "VIDEO":
media = self.device.server.library.section(src["library_name"]).get( media = self.plex_server.library.section(library).get(src["video_name"])
src["video_name"]
)
if ( if media is None:
media _LOGGER.error("Media could not be found: %s", media_id)
and media_type == "EPISODE" return
and isinstance(media, plexapi.playlist.Playlist)
):
# delete episode playlist after being loaded into a play queue
self._client_play_media(media=media, delete=True, shuffle=src["shuffle"])
elif media:
self._client_play_media(media=media, shuffle=src["shuffle"])
def _get_tv_media(self, library_name, show_name, season_number, episode_number): playqueue = self.plex_server.create_playqueue(media, shuffle=shuffle)
try:
self.device.playMedia(playqueue)
except ParseError:
# Temporary workaround for Plexamp / plexapi issue
pass
except requests.exceptions.ConnectTimeout:
_LOGGER.error("Timed out playing on %s", self.name)
self.update_devices()
def _get_music_media(self, library_name, src):
"""Find music media and return a Plex media object."""
artist_name = src["artist_name"]
album_name = src.get("album_name")
track_name = src.get("track_name")
track_number = src.get("track_number")
artist = self.plex_server.library.section(library_name).get(artist_name)
if album_name:
album = artist.album(album_name)
if track_name:
return album.track(track_name)
if track_number:
for track in album.tracks():
if int(track.index) == int(track_number):
return track
return None
return album
if track_name:
return artist.searchTracks(track_name, maxresults=1)
return artist
def _get_tv_media(self, library_name, src):
"""Find TV media and return a Plex media object.""" """Find TV media and return a Plex media object."""
show_name = src["show_name"]
season_number = src.get("season_number")
episode_number = src.get("episode_number")
target_season = None target_season = None
target_episode = None target_episode = None
show = self.device.server.library.section(library_name).get(show_name) show = self.plex_server.library.section(library_name).get(show_name)
if not season_number: if not season_number:
playlist_name = f"{self.entity_id} - {show_name} Episodes" return show
return self.device.server.createPlaylist(playlist_name, show.episodes())
for season in show.seasons(): for season in show.seasons():
if int(season.seasonNumber) == int(season_number): if int(season.seasonNumber) == int(season_number):
@ -741,12 +763,7 @@ class PlexClient(MediaPlayerDevice):
) )
else: else:
if not episode_number: if not episode_number:
playlist_name = "{} - {} Season {} Episodes".format( return target_season
self.entity_id, show_name, str(season_number)
)
return self.device.server.createPlaylist(
playlist_name, target_season.episodes()
)
for episode in target_season.episodes(): for episode in target_season.episodes():
if int(episode.index) == int(episode_number): if int(episode.index) == int(episode_number):
@ -764,38 +781,6 @@ class PlexClient(MediaPlayerDevice):
return target_episode return target_episode
def _client_play_media(self, media, delete=False, **params):
"""Instruct Plex client to play a piece of media."""
if not (self.device and "playback" in self._device_protocol_capabilities):
_LOGGER.error("Client cannot play media: %s", self.entity_id)
return
playqueue = plexapi.playqueue.PlayQueue.create(
self.device.server, media, **params
)
# Delete dynamic playlists used to build playqueue (ex. play tv season)
if delete:
media.delete()
server_url = self.device.server.baseurl.split(":")
self.device.sendCommand(
"playback/playMedia",
**dict(
{
"machineIdentifier": self.device.server.machineIdentifier,
"address": server_url[1].strip("/"),
"port": server_url[-1],
"key": media.key,
"containerKey": "/playQueues/{}?window=100&own=1".format(
playqueue.playQueueID
),
},
**params,
),
)
self.update_devices()
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the scene state attributes.""" """Return the scene state attributes."""

View file

@ -1,5 +1,6 @@
"""Shared class to maintain Plex server instances.""" """Shared class to maintain Plex server instances."""
import plexapi.myplex import plexapi.myplex
import plexapi.playqueue
import plexapi.server import plexapi.server
from requests import Session from requests import Session
@ -109,3 +110,16 @@ class PlexServer:
def show_all_controls(self): def show_all_controls(self):
"""Return show_all_controls option.""" """Return show_all_controls option."""
return self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS] return self.options[MP_DOMAIN][CONF_SHOW_ALL_CONTROLS]
@property
def library(self):
"""Return library attribute from server object."""
return self._plex_server.library
def playlist(self, title):
"""Return playlist from server object."""
return self._plex_server.playlist(title)
def create_playqueue(self, media, **kwargs):
"""Create playqueue on Plex server."""
return plexapi.playqueue.PlayQueue.create(self._plex_server, media, **kwargs)