Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 62 additions & 77 deletions homeassistant/components/plex/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
from datetime import timedelta
import json
import logging
from xml.etree.ElementTree import ParseError

import plexapi.exceptions
import plexapi.playlist
import plexapi.playqueue
import requests.exceptions

from homeassistant.components.media_player import MediaPlayerDevice
Expand All @@ -16,6 +15,7 @@
SUPPORT_NEXT_TRACK,
SUPPORT_PAUSE,
SUPPORT_PLAY,
SUPPORT_PLAY_MEDIA,
SUPPORT_PREVIOUS_TRACK,
SUPPORT_STOP,
SUPPORT_TURN_OFF,
Expand Down Expand Up @@ -543,9 +543,6 @@ def make(self):
@property
def supported_features(self):
"""Flag media player features that are supported."""
if not self._is_player_active:
return 0

# force show all controls
if self.plex_server.show_all_controls:
return (
Expand All @@ -555,13 +552,11 @@ def supported_features(self):
| SUPPORT_STOP
| SUPPORT_VOLUME_SET
| SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF
| SUPPORT_VOLUME_MUTE
)

# only show controls when we know what device is connecting
if not self._make:
return 0
# no mute support
if self.make.lower() == "shield android tv":
_LOGGER.debug(
Expand All @@ -575,8 +570,10 @@ def supported_features(self):
| SUPPORT_STOP
| SUPPORT_VOLUME_SET
| SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF
)

# Only supports play,pause,stop (and off which really is stop)
if self.make.lower().startswith("tivo"):
_LOGGER.debug(
Expand All @@ -585,8 +582,7 @@ def supported_features(self):
self.entity_id,
)
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:
return (
SUPPORT_PAUSE
Expand All @@ -595,6 +591,7 @@ def supported_features(self):
| SUPPORT_STOP
| SUPPORT_VOLUME_SET
| SUPPORT_PLAY
| SUPPORT_PLAY_MEDIA
| SUPPORT_TURN_OFF
| SUPPORT_VOLUME_MUTE
)
Expand Down Expand Up @@ -682,49 +679,74 @@ def play_media(self, media_type, media_id, **kwargs):
return

src = json.loads(media_id)
library = src.get("library_name")
shuffle = src.get("shuffle", 0)

media = None

if media_type == "MUSIC":
media = (
self.device.server.library.section(src["library_name"])
.get(src["artist_name"])
.album(src["album_name"])
.get(src["track_name"])
)
media = self._get_music_media(library, src)
elif media_type == "EPISODE":
media = self._get_tv_media(
src["library_name"],
src["show_name"],
src["season_number"],
src["episode_number"],
)
media = self._get_tv_media(library, src)
elif media_type == "PLAYLIST":
media = self.device.server.playlist(src["playlist_name"])
media = self.plex_server.playlist(src["playlist_name"])
elif media_type == "VIDEO":
media = self.device.server.library.section(src["library_name"]).get(
src["video_name"]
)
media = self.plex_server.library.section(library).get(src["video_name"])

if (
media
and media_type == "EPISODE"
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"])
if media is None:
_LOGGER.error("Media could not be found: %s", media_id)
return

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_tv_media(self, library_name, show_name, season_number, episode_number):
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."""
show_name = src["show_name"]
season_number = src.get("season_number")
episode_number = src.get("episode_number")
target_season = 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:
playlist_name = f"{self.entity_id} - {show_name} Episodes"
return self.device.server.createPlaylist(playlist_name, show.episodes())
return show

for season in show.seasons():
if int(season.seasonNumber) == int(season_number):
Expand All @@ -741,12 +763,7 @@ def _get_tv_media(self, library_name, show_name, season_number, episode_number):
)
else:
if not episode_number:
playlist_name = "{} - {} Season {} Episodes".format(
self.entity_id, show_name, str(season_number)
)
return self.device.server.createPlaylist(
playlist_name, target_season.episodes()
)
return target_season

for episode in target_season.episodes():
if int(episode.index) == int(episode_number):
Expand All @@ -764,38 +781,6 @@ def _get_tv_media(self, library_name, show_name, season_number, episode_number):

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
def device_state_attributes(self):
"""Return the scene state attributes."""
Expand Down
14 changes: 14 additions & 0 deletions homeassistant/components/plex/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Shared class to maintain Plex server instances."""
import plexapi.myplex
import plexapi.playqueue
import plexapi.server
from requests import Session

Expand Down Expand Up @@ -109,3 +110,16 @@ def use_episode_art(self):
def show_all_controls(self):
"""Return show_all_controls option."""
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)