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:
parent
073bdd672a
commit
0915d927df
2 changed files with 76 additions and 77 deletions
|
@ -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."""
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue