feat(media_session): map Android PlaybackState 6 to buffering (t55)
The media session parser only understood states 2 (paused) and 3 (playing), so a session refilling its buffer (Android state 6, which the Shield's own ExoPlayer/MediaSession sessions report exactly like stock Android) read as "idle" or vanished into the foreground tier. MediaSessionInfo gains an independent `buffering` flag (playing/paused/ buffering are now three flags off one integer), the session sort puts buffering right behind playing — it is mid-playback, so it outranks a paused session when the ladder picks one — and the running-apps enrichment names a buffering live/last-known session "buffering" instead of falling into "idle". The MediaPlayerState stub gains BUFFERING and STANDBY so an in-suite reference to a member real HA does not have fails here (real HA 2026.10 exposes off/on/idle/playing/paused/standby/buffering).
This commit is contained in:
1 parent
49bc23ae34
commit
07682ff082
4 files changed
+122
-9
No files matched your search
@@ -79,6 +79,8 @@ class MediaPlayerState(enum.StrEnum):
|
||||
IDLE = "idle"
|
||||
PLAYING = "playing"
|
||||
PAUSED = "paused"
|
||||
STANDBY = "standby"
|
||||
BUFFERING = "buffering"
|
||||
|
||||
|
||||
class MediaPlayerEntityFeature(enum.IntFlag):
|
||||
|
||||
@@ -125,6 +125,76 @@ class TestPlaybackStatePosition:
|
||||
assert sessions[0].position is None
|
||||
|
||||
|
||||
class TestPlaybackStateBufferingT55:
|
||||
"""t55: Android PlaybackState 6 is BUFFERING, mapped in the parser.
|
||||
|
||||
The Shield's own sessions report the same integer stock Android does (6),
|
||||
so no vendor-specific variant is treated as buffering — a device variant
|
||||
would be a template field on the same PlaybackState line, never a
|
||||
different integer.
|
||||
"""
|
||||
|
||||
def _buffering_dump(self, state=6, speed="0.0"):
|
||||
return (
|
||||
"Sessions Stack - have 1 sessions:\n"
|
||||
" Some app session com.netflix.ninja/Session (userId=0)\n"
|
||||
" package=com.netflix.ninja\n"
|
||||
" active=true\n"
|
||||
f" state=PlaybackState {{state={state}, position=0, "
|
||||
f"buffered position=2000, speed={speed}, updated=900, actions=1, "
|
||||
"custom actions=[], active item id=0, error=null}\n"
|
||||
" metadata: size=2, description=Movie Title, Some Artist\n"
|
||||
)
|
||||
|
||||
def test_state_6_is_buffering(self):
|
||||
sessions = parse_all_media_sessions(self._buffering_dump())
|
||||
assert len(sessions) == 1
|
||||
session = sessions[0]
|
||||
assert session.buffering is True
|
||||
assert session.playing is False
|
||||
assert session.paused is False
|
||||
assert session.title == "Movie Title"
|
||||
|
||||
def test_state_3_and_2_stay_playing_and_paused(self):
|
||||
for state, attr in ((3, "playing"), (2, "paused")):
|
||||
sessions = parse_all_media_sessions(self._buffering_dump(state=state))
|
||||
session = sessions[0]
|
||||
assert session.buffering is False
|
||||
assert getattr(session, attr) is True
|
||||
|
||||
def test_state_6_wins_when_the_dump_says_speed_zero_too(self):
|
||||
"""The buffering signal is the integer, not the speed field."""
|
||||
sessions = parse_all_media_sessions(self._buffering_dump(state=6))
|
||||
assert sessions[0].buffering is True
|
||||
|
||||
def test_buffering_outranks_a_paused_session_in_the_pick_order(self):
|
||||
sessions = parse_all_media_sessions(
|
||||
self._buffering_dump()
|
||||
+ " Other app session com.example.player/Session (userId=0)\n"
|
||||
" package=com.example.player\n"
|
||||
" active=true\n"
|
||||
" state=PlaybackState {state=2, position=1, speed=0.0, "
|
||||
"updated=1, actions=1, custom actions=[], active item id=0, "
|
||||
"error=null}\n"
|
||||
" metadata: size=1, description=Paused Tune\n"
|
||||
)
|
||||
assert [s.package for s in sessions] == [
|
||||
"com.netflix.ninja",
|
||||
"com.example.player",
|
||||
]
|
||||
# and parse_media_session (no foreground preference) picks buffering
|
||||
dump = self._buffering_dump() + (
|
||||
" Other app session com.example.player/Session (userId=0)\n"
|
||||
" package=com.example.player\n"
|
||||
" active=true\n"
|
||||
" state=PlaybackState {state=2, position=1, speed=0.0, "
|
||||
"updated=1, actions=1, custom actions=[], active item id=0, "
|
||||
"error=null}\n"
|
||||
" metadata: size=1, description=Paused Tune\n"
|
||||
)
|
||||
assert parse_media_session(dump).package == "com.netflix.ninja"
|
||||
|
||||
|
||||
class TestAudioPlaybackSection:
|
||||
"""The "Audio playback (lastly played comes first)" section lists apps
|
||||
that recently played audio even with no MediaSession (R2)."""
|
||||
@@ -160,6 +230,29 @@ class TestAudioPlaybackSection:
|
||||
assert parse_audio_playback("") == []
|
||||
|
||||
|
||||
def test_running_apps_buffering_state_t55():
|
||||
"""t55: a buffering session (and a buffering last-known) is named buffering."""
|
||||
from nsh.ha_adb_common import MediaSessionInfo, running_apps_from_tasks
|
||||
|
||||
tasks = [
|
||||
{"package": "com.netflix.ninja"},
|
||||
{"package": "ar.tvplayer.tv"},
|
||||
]
|
||||
sessions = [MediaSessionInfo(package="com.netflix.ninja", buffering=True)]
|
||||
last_known = {
|
||||
"ar.tvplayer.tv": MediaSessionInfo(package="ar.tvplayer.tv", buffering=True)
|
||||
}
|
||||
|
||||
apps = running_apps_from_tasks(
|
||||
tasks, sessions, foreground_package=None, last_known=last_known
|
||||
)
|
||||
|
||||
netflix = next(a for a in apps if a["package"] == "com.netflix.ninja")
|
||||
tivi = next(a for a in apps if a["package"] == "ar.tvplayer.tv")
|
||||
assert netflix["state"] == "buffering"
|
||||
assert tivi["last_known_state"] == "buffering"
|
||||
|
||||
|
||||
def test_running_apps_last_known_enrichment():
|
||||
"""P3-3b: apps without a live session carry their TTL-capped last-known state."""
|
||||
from nsh.ha_adb_common import MediaSessionInfo, running_apps_from_tasks
|
||||
|
||||
Reference in new issue
Block a user