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:
James Mackay committed 2026-10-12 06:52:58 +01:00
1 parent 49bc23ae34
commit 07682ff082
4 files changed
+122 -9

No files matched your search

@@ -17,6 +17,12 @@ class MediaSessionInfo:
**device uptime-ms** timestamp (verified on Shield Android 11, R9), not a
wallclock value. Use :meth:`position_at_uptime` with the device's current
uptime to extrapolate the live position.
``playing``/``paused``/``buffering`` are independent flags mapped from the
Android PlaybackState integer: 2 = paused, 3 = playing, 6 = buffering
(t55). The Shield's own ExoPlayer/MediaSession sessions report state 6
while buffering just as stock Android does, so no vendor-specific state
integer is treated as buffering.
"""
title: str | None = None
@@ -25,6 +31,7 @@ class MediaSessionInfo:
package: str | None = None
playing: bool = False
paused: bool = False
buffering: bool = False
position: float | None = None
speed: float | None = None
updated: int | None = None
@@ -81,7 +88,8 @@ def parse_all_media_sessions(dump: str) -> list[MediaSessionInfo]:
"""Parse `dumpsys media_session` output for ALL active media sessions.
Returns a list of every active session that has metadata or a known
playback state, sorted: playing first, then paused, then others.
playback state, sorted: playing first, then buffering, then paused, then
others (t55).
"""
if not dump:
return []
@@ -89,7 +97,8 @@ def parse_all_media_sessions(dump: str) -> list[MediaSessionInfo]:
sessions: list[MediaSessionInfo] = []
current_pkg: str | None = None
in_active = False
current_state = 0 # 0=unknown, 2=paused, 3=playing
# Android PlaybackState: 0=unknown, 2=paused, 3=playing, 6=buffering
current_state = 0
current_position: float | None = None
current_speed: float | None = None
current_updated: int | None = None
@@ -138,6 +147,7 @@ def parse_all_media_sessions(dump: str) -> list[MediaSessionInfo]:
info = MediaSessionInfo()
info.package = current_pkg
info.playing = current_state == 3
info.buffering = current_state == 6
info.paused = current_state == 2
info.position = current_position
info.speed = current_speed
@@ -158,13 +168,16 @@ def parse_all_media_sessions(dump: str) -> list[MediaSessionInfo]:
current_meta.package = current_pkg
sessions.append(current_meta)
# Sort: playing first, then paused, then others
# Sort: playing first, then buffering (mid-playback), then paused, then
# others — a buffering session outranks a paused one (t55).
def sort_key(s: MediaSessionInfo) -> int:
if s.playing:
return 0
if s.paused:
if s.buffering:
return 1
return 2
if s.paused:
return 2
return 3
sessions.sort(key=sort_key)
return sessions
@@ -206,8 +219,9 @@ def parse_media_session(
Returns the best match from all active sessions:
1. The session whose package matches the foreground app (if provided)
2. The session that is currently playing (state=3)
3. The session that is paused (state=2)
4. Any active session with metadata
3. The session that is buffering (state=6)
4. The session that is paused (state=2)
5. Any active session with metadata
"""
sessions = parse_all_media_sessions(dump)
if not sessions:
@@ -218,6 +232,5 @@ def parse_media_session(
for s in sessions:
if s.package == foreground_package:
return s
# Already sorted: playing > paused > other
# Already sorted: playing > buffering > paused > other
return sessions[0]