diff --git a/homeassistant/components/nest/media_source.py b/homeassistant/components/nest/media_source.py index 225afd67595..2676929f2de 100644 --- a/homeassistant/components/nest/media_source.py +++ b/homeassistant/components/nest/media_source.py @@ -377,9 +377,15 @@ class NestMediaSource(MediaSource): browse_root = _browse_root() browse_root.children = [] for device_id, child_device in devices.items(): - browse_root.children.append( - _browse_device(MediaId(device_id), child_device) - ) + browse_device = _browse_device(MediaId(device_id), child_device) + if last_event_id := await _async_get_recent_event_id( + MediaId(device_id), child_device + ): + browse_device.thumbnail = EVENT_THUMBNAIL_URL_FORMAT.format( + device_id=last_event_id.device_id, + event_token=last_event_id.event_token, + ) + browse_root.children.append(browse_device) return browse_root # Browse either a device or events within a device @@ -399,11 +405,9 @@ class NestMediaSource(MediaSource): browse_device.children = [] for clip in clips.values(): event_id = MediaId(media_id.device_id, clip.event_token) - browse_event = _browse_clip_preview(event_id, device, clip) - browse_device.children.append(browse_event) - # Use thumbnail for first event in the list as the device thumbnail - if browse_device.thumbnail is None: - browse_device.thumbnail = browse_event.thumbnail + browse_device.children.append( + _browse_clip_preview(event_id, device, clip) + ) return browse_device # Browse a specific event @@ -421,11 +425,9 @@ class NestMediaSource(MediaSource): browse_device.children = [] for image in images.values(): event_id = MediaId(media_id.device_id, image.event_token) - browse_event = _browse_image_event(event_id, device, image) - browse_device.children.append(browse_event) - # Use thumbnail for first event in the list as the device thumbnail - if browse_device.thumbnail is None: - browse_device.thumbnail = browse_event.thumbnail + browse_device.children.append( + _browse_image_event(event_id, device, image) + ) return browse_device # Browse a specific event @@ -470,6 +472,21 @@ def _browse_root() -> BrowseMediaSource: ) +async def _async_get_recent_event_id( + device_id: MediaId, device: Device +) -> MediaId | None: + """Return thumbnail for most recent device event.""" + if CameraClipPreviewTrait.NAME in device.traits: + clips = await device.event_media_manager.async_clip_preview_sessions() + if not clips: + return None + return MediaId(device_id.device_id, next(iter(clips)).event_token) + images = await device.event_media_manager.async_image_sessions() + if not images: + return None + return MediaId(device_id.device_id, next(iter(images)).event_token) + + def _browse_device(device_id: MediaId, device: Device) -> BrowseMediaSource: """Return details for the specified device.""" device_info = NestDeviceInfo(device) diff --git a/tests/components/nest/test_media_source.py b/tests/components/nest/test_media_source.py index 83b299d62fa..015a14fb92c 100644 --- a/tests/components/nest/test_media_source.py +++ b/tests/components/nest/test_media_source.py @@ -104,7 +104,6 @@ def mp4() -> io.BytesIO: stream.width = 480 stream.height = 320 stream.pix_fmt = "yuv420p" - # stream.options.update({"g": str(fps), "keyint_min": str(fps)}) for frame_i in range(total_frames): img = frame_image_data(frame_i, total_frames) @@ -207,9 +206,10 @@ async def test_no_eligible_devices(hass, auth): assert not browse.children -async def test_supported_device(hass, auth): +@pytest.mark.parametrize("traits", [CAMERA_TRAITS, BATTERY_CAMERA_TRAITS]) +async def test_supported_device(hass, auth, traits): """Test a media source with a supported camera.""" - await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS) + await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, traits) assert len(hass.states.async_all()) == 1 camera = hass.states.get("camera.front") @@ -770,6 +770,17 @@ async def test_camera_event_clip_preview(hass, auth, hass_client, mp4): assert received_event.data["type"] == "camera_motion" event_identifier = received_event.data["nest_event_id"] + # List devices + browse = await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}") + assert browse.domain == DOMAIN + assert len(browse.children) == 1 + assert browse.children[0].domain == DOMAIN + assert browse.children[0].identifier == device.id + assert browse.children[0].title == "Front: Recent Events" + assert ( + browse.children[0].thumbnail + == f"/api/nest/event_media/{device.id}/{event_identifier}/thumbnail" + ) # Browse to the device browse = await media_source.async_browse_media( hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}" @@ -778,10 +789,7 @@ async def test_camera_event_clip_preview(hass, auth, hass_client, mp4): assert browse.identifier == device.id assert browse.title == "Front: Recent Events" assert browse.can_expand - assert ( - browse.thumbnail - == f"/api/nest/event_media/{device.id}/{event_identifier}/thumbnail" - ) + assert not browse.thumbnail # The device expands recent events assert len(browse.children) == 1 assert browse.children[0].domain == DOMAIN @@ -1404,12 +1412,22 @@ async def test_camera_image_resize(hass, auth, hass_client): assert contents == IMAGE_BYTES_FROM_EVENT # The event thumbnail is used for the device thumbnail + browse = await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}") + assert browse.domain == DOMAIN + assert len(browse.children) == 1 + assert browse.children[0].identifier == device.id + assert browse.children[0].title == "Front: Recent Events" + assert ( + browse.children[0].thumbnail + == f"/api/nest/event_media/{device.id}/{event_identifier}/thumbnail" + ) + + # Browse to device. No thumbnail is needed for the device on the device page browse = await media_source.async_browse_media( hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}" ) assert browse.domain == DOMAIN assert browse.identifier == device.id - assert ( - browse.thumbnail - == f"/api/nest/event_media/{device.id}/{event_identifier}/thumbnail" - ) + assert browse.title == "Front: Recent Events" + assert not browse.thumbnail + assert len(browse.children) == 1