diff --git a/homeassistant/components/plex/const.py b/homeassistant/components/plex/const.py index ad62bade1fd..635c981b531 100644 --- a/homeassistant/components/plex/const.py +++ b/homeassistant/components/plex/const.py @@ -3,6 +3,7 @@ from homeassistant.const import __version__ DOMAIN = "plex" NAME_FORMAT = "Plex ({})" +COMMON_PLAYERS = ["Plex Web"] DEFAULT_PORT = 32400 DEFAULT_SSL = False diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index d8155d1a43b..1e8d0e6cfd2 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -21,19 +21,14 @@ from homeassistant.components.media_player.const import ( SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET, ) -from homeassistant.const import ( - DEVICE_DEFAULT_NAME, - STATE_IDLE, - STATE_OFF, - STATE_PAUSED, - STATE_PLAYING, -) +from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_PAUSED, STATE_PLAYING from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.util import dt as dt_util from .const import ( + COMMON_PLAYERS, CONF_SERVER_IDENTIFIER, DISPATCHERS, DOMAIN as PLEX_DOMAIN, @@ -114,6 +109,8 @@ class PlexMediaPlayer(MediaPlayerDevice): self._is_player_active = False self._machine_identifier = device.machineIdentifier self._make = "" + self._device_product = None + self._device_title = None self._name = None self._player_state = "idle" self._previous_volume_level = 1 # Used in fake muting @@ -188,7 +185,6 @@ class PlexMediaPlayer(MediaPlayerDevice): self._clear_media_details() self._available = self.device or self.session - name_base = None if self.device: try: @@ -197,7 +193,8 @@ class PlexMediaPlayer(MediaPlayerDevice): device_url = "127.0.0.1" if "127.0.0.1" in device_url: self.device.proxyThroughServer() - name_base = self.device.title or self.device.product + self._device_product = self.device.product + self._device_title = self.device.title self._device_protocol_capabilities = self.device.protocolCapabilities self._player_state = self.device.state @@ -215,11 +212,13 @@ class PlexMediaPlayer(MediaPlayerDevice): if session_device: self._make = session_device.device or "" self._player_state = session_device.state - name_base = name_base or session_device.title or session_device.product + self._device_product = self._device_product or session_device.product + self._device_title = self._device_title or session_device.title else: _LOGGER.warning("No player associated with active session") - self._session_username = self.session.usernames[0] + if self.session.usernames: + self._session_username = self.session.usernames[0] # Calculate throttled position for proper progress display. position = int(self.session.viewOffset / 1000) @@ -237,7 +236,14 @@ class PlexMediaPlayer(MediaPlayerDevice): self._media_content_id = self.session.ratingKey self._media_content_rating = getattr(self.session, "contentRating", None) - self._name = self._name or NAME_FORMAT.format(name_base or DEVICE_DEFAULT_NAME) + name_parts = [self._device_product, self._device_title] + if (self._device_product in COMMON_PLAYERS) and self.make: + # Add more context in name for likely duplicates + name_parts.append(self.make) + if self.username and self.username != self.plex_server.owner: + # Prepend username for shared/managed clients + name_parts.insert(0, self.username) + self._name = NAME_FORMAT.format(" - ".join(name_parts)) self._set_player_state() if self._is_player_active and self.session is not None: @@ -348,6 +354,11 @@ class PlexMediaPlayer(MediaPlayerDevice): """Return the name of the device.""" return self._name + @property + def username(self): + """Return the username of the client owner.""" + return self._session_username + @property def app_name(self): """Return the library name of playing media.""" @@ -699,7 +710,7 @@ class PlexMediaPlayer(MediaPlayerDevice): """Return the scene state attributes.""" attr = { "media_content_rating": self._media_content_rating, - "session_username": self._session_username, + "session_username": self.username, "media_library_name": self._app_name, } diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index ab5d79ff81c..fcc7e5dda17 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -51,6 +51,7 @@ class PlexServer: self._verify_ssl = server_config.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL) self.options = options self.server_choice = None + self._owner_username = None # Header conditionally added as it is not available in config entry v1 if CONF_CLIENT_IDENTIFIER in server_config: @@ -93,6 +94,14 @@ class PlexServer: else: _connect_with_token() + owner_account = [ + account.name + for account in self._plex_server.systemAccounts() + if account.accountID == 1 + ] + if owner_account: + self._owner_username = owner_account[0] + def refresh_entity(self, machine_identifier, device, session): """Forward refresh dispatch to media_player.""" unique_id = f"{self.machine_identifier}:{machine_identifier}" @@ -182,6 +191,11 @@ class PlexServer: """Return the plexapi PlexServer instance.""" return self._plex_server + @property + def owner(self): + """Return the Plex server owner username.""" + return self._owner_username + @property def friendly_name(self): """Return name of connected Plex server.""" diff --git a/tests/components/plex/mock_classes.py b/tests/components/plex/mock_classes.py index de6ffa51170..ed354138cb2 100644 --- a/tests/components/plex/mock_classes.py +++ b/tests/components/plex/mock_classes.py @@ -53,6 +53,15 @@ class MockPlexAccount: return self._resources +class MockPlexSystemAccount: + """Mock a PlexSystemAccount instance.""" + + def __init__(self): + """Initialize the object.""" + self.name = "Dummy" + self.accountID = 1 + + class MockPlexServer: """Mock a PlexServer instance.""" @@ -68,6 +77,11 @@ class MockPlexServer: ] prefix = "https" if ssl else "http" self._baseurl = f"{prefix}://{host}:{port}" + self._systemAccount = MockPlexSystemAccount() + + def systemAccounts(self): + """Mock the systemAccounts lookup method.""" + return [self._systemAccount] @property def url_in_use(self):