diff --git a/homeassistant/components/plex/models.py b/homeassistant/components/plex/models.py
index af1343095f0..2dc7b83b439 100644
--- a/homeassistant/components/plex/models.py
+++ b/homeassistant/components/plex/models.py
@@ -1,4 +1,6 @@
"""Models to represent various Plex objects used in the integration."""
+import logging
+
from homeassistant.components.media_player.const import (
MEDIA_TYPE_MOVIE,
MEDIA_TYPE_MUSIC,
@@ -7,7 +9,15 @@ from homeassistant.components.media_player.const import (
)
from homeassistant.util import dt as dt_util
-LIVE_TV_SECTION = -4
+LIVE_TV_SECTION = "Live TV"
+TRANSIENT_SECTION = "Preroll"
+UNKNOWN_SECTION = "Unknown"
+SPECIAL_SECTIONS = {
+ -2: TRANSIENT_SECTION,
+ -4: LIVE_TV_SECTION,
+}
+
+_LOGGER = logging.getLogger(__name__)
class PlexSession:
@@ -66,8 +76,15 @@ class PlexSession:
if media.duration:
self.media_duration = int(media.duration / 1000)
- if media.librarySectionID == LIVE_TV_SECTION:
- self.media_library_title = "Live TV"
+ if media.librarySectionID in SPECIAL_SECTIONS:
+ self.media_library_title = SPECIAL_SECTIONS[media.librarySectionID]
+ elif media.librarySectionID < 1:
+ self.media_library_title = UNKNOWN_SECTION
+ _LOGGER.warning(
+ "Unknown library section ID (%s) for title '%s', please create an issue",
+ media.librarySectionID,
+ media.title,
+ )
else:
self.media_library_title = (
media.section().title if media.librarySectionID is not None else ""
@@ -115,7 +132,7 @@ class PlexSession:
"""Get the image URL from a media object."""
thumb_url = media.thumbUrl
if media.type == "episode" and not self.plex_server.option_use_episode_art:
- if media.librarySectionID == LIVE_TV_SECTION:
+ if SPECIAL_SECTIONS.get(media.librarySectionID) == LIVE_TV_SECTION:
thumb_url = media.grandparentThumb
else:
thumb_url = media.url(media.grandparentThumb)
diff --git a/tests/components/plex/conftest.py b/tests/components/plex/conftest.py
index 358ab293bf0..bd2fc6a7fa8 100644
--- a/tests/components/plex/conftest.py
+++ b/tests/components/plex/conftest.py
@@ -278,6 +278,30 @@ def session_plexweb_fixture():
return load_fixture("plex/session_plexweb.xml")
+@pytest.fixture(name="session_transient", scope="session")
+def session_transient_fixture():
+ """Load a transient session payload and return it."""
+ return load_fixture("plex/session_transient.xml")
+
+
+@pytest.fixture(name="session_unknown", scope="session")
+def session_unknown_fixture():
+ """Load a hypothetical unknown session payload and return it."""
+ return load_fixture("plex/session_unknown.xml")
+
+
+@pytest.fixture(name="session_live_tv", scope="session")
+def session_live_tv_fixture():
+ """Load a Live TV session payload and return it."""
+ return load_fixture("plex/session_live_tv.xml")
+
+
+@pytest.fixture(name="livetv_sessions", scope="session")
+def livetv_sessions_fixture():
+ """Load livetv/sessions payload and return it."""
+ return load_fixture("plex/livetv_sessions.xml")
+
+
@pytest.fixture(name="security_token", scope="session")
def security_token_fixture():
"""Load a security token payload and return it."""
@@ -393,18 +417,23 @@ def mock_plex_calls(
def setup_plex_server(
hass,
entry,
+ livetv_sessions,
mock_websocket,
mock_plex_calls,
requests_mock,
empty_payload,
session_default,
+ session_live_tv,
session_photo,
session_plexweb,
+ session_transient,
+ session_unknown,
):
"""Set up and return a mocked Plex server instance."""
async def _wrapper(**kwargs):
"""Wrap the fixture to allow passing arguments to the setup method."""
+ url = plex_server_url(entry)
config_entry = kwargs.get("config_entry", entry)
disable_clients = kwargs.pop("disable_clients", False)
disable_gdm = kwargs.pop("disable_gdm", True)
@@ -415,10 +444,16 @@ def setup_plex_server(
session = session_plexweb
elif session_type == "photo":
session = session_photo
+ elif session_type == "live_tv":
+ session = session_live_tv
+ requests_mock.get(f"{url}/livetv/sessions/live_tv_1", text=livetv_sessions)
+ elif session_type == "transient":
+ session = session_transient
+ elif session_type == "unknown":
+ session = session_unknown
else:
session = session_default
- url = plex_server_url(entry)
requests_mock.get(f"{url}/status/sessions", text=session)
if disable_clients:
diff --git a/tests/components/plex/test_init.py b/tests/components/plex/test_init.py
index 2e5a30ce11a..dc46c2ca771 100644
--- a/tests/components/plex/test_init.py
+++ b/tests/components/plex/test_init.py
@@ -8,13 +8,24 @@ import plexapi
import requests
import homeassistant.components.plex.const as const
+from homeassistant.components.plex.models import (
+ LIVE_TV_SECTION,
+ TRANSIENT_SECTION,
+ UNKNOWN_SECTION,
+)
from homeassistant.config_entries import (
ENTRY_STATE_LOADED,
ENTRY_STATE_NOT_LOADED,
ENTRY_STATE_SETUP_ERROR,
ENTRY_STATE_SETUP_RETRY,
)
-from homeassistant.const import CONF_TOKEN, CONF_URL, CONF_VERIFY_SSL, STATE_IDLE
+from homeassistant.const import (
+ CONF_TOKEN,
+ CONF_URL,
+ CONF_VERIFY_SSL,
+ STATE_IDLE,
+ STATE_PLAYING,
+)
from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
@@ -108,6 +119,66 @@ async def test_setup_with_photo_session(hass, entry, setup_plex_server):
assert sensor.state == "0"
+async def test_setup_with_live_tv_session(hass, entry, setup_plex_server):
+ """Test setup component with a Live TV session."""
+ await setup_plex_server(session_type="live_tv")
+
+ assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
+ assert entry.state == ENTRY_STATE_LOADED
+ await hass.async_block_till_done()
+
+ media_player = hass.states.get(
+ "media_player.plex_plex_for_android_tv_shield_android_tv"
+ )
+ assert media_player.state == STATE_PLAYING
+ assert media_player.attributes["media_library_title"] == LIVE_TV_SECTION
+
+ await wait_for_debouncer(hass)
+
+ sensor = hass.states.get("sensor.plex_plex_server_1")
+ assert sensor.state == "1"
+
+
+async def test_setup_with_transient_session(hass, entry, setup_plex_server):
+ """Test setup component with a transient session."""
+ await setup_plex_server(session_type="transient")
+
+ assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
+ assert entry.state == ENTRY_STATE_LOADED
+ await hass.async_block_till_done()
+
+ media_player = hass.states.get(
+ "media_player.plex_plex_for_android_tv_shield_android_tv"
+ )
+ assert media_player.state == STATE_PLAYING
+ assert media_player.attributes["media_library_title"] == TRANSIENT_SECTION
+
+ await wait_for_debouncer(hass)
+
+ sensor = hass.states.get("sensor.plex_plex_server_1")
+ assert sensor.state == "1"
+
+
+async def test_setup_with_unknown_session(hass, entry, setup_plex_server):
+ """Test setup component with an unknown session."""
+ await setup_plex_server(session_type="unknown")
+
+ assert len(hass.config_entries.async_entries(const.DOMAIN)) == 1
+ assert entry.state == ENTRY_STATE_LOADED
+ await hass.async_block_till_done()
+
+ media_player = hass.states.get(
+ "media_player.plex_plex_for_android_tv_shield_android_tv"
+ )
+ assert media_player.state == STATE_PLAYING
+ assert media_player.attributes["media_library_title"] == UNKNOWN_SECTION
+
+ await wait_for_debouncer(hass)
+
+ sensor = hass.states.get("sensor.plex_plex_server_1")
+ assert sensor.state == "1"
+
+
async def test_setup_when_certificate_changed(
hass,
requests_mock,
diff --git a/tests/fixtures/plex/livetv_sessions.xml b/tests/fixtures/plex/livetv_sessions.xml
new file mode 100644
index 00000000000..faec345db2c
--- /dev/null
+++ b/tests/fixtures/plex/livetv_sessions.xml
@@ -0,0 +1,17 @@
+
+
+
diff --git a/tests/fixtures/plex/session_live_tv.xml b/tests/fixtures/plex/session_live_tv.xml
new file mode 100644
index 00000000000..d71ef146f38
--- /dev/null
+++ b/tests/fixtures/plex/session_live_tv.xml
@@ -0,0 +1,14 @@
+
+
+
diff --git a/tests/fixtures/plex/session_transient.xml b/tests/fixtures/plex/session_transient.xml
new file mode 100644
index 00000000000..0bb007e195c
--- /dev/null
+++ b/tests/fixtures/plex/session_transient.xml
@@ -0,0 +1,13 @@
+
+
+
diff --git a/tests/fixtures/plex/session_unknown.xml b/tests/fixtures/plex/session_unknown.xml
new file mode 100644
index 00000000000..6bbe87d8b27
--- /dev/null
+++ b/tests/fixtures/plex/session_unknown.xml
@@ -0,0 +1,13 @@
+
+
+