Hi everyone! I’ve got a bit of an issue with displaying album art on the Media Controller card.
In my local network, I have a network streamer device (WiiM Pro) that I’m using to stream music from Apple Music/Spotify/any other music service to my speakers. I have integrated it with my HA using this custom component. In general, everything works fine - playback info and control, volume control, all that stuff. Except for album art.
I have spent some time debugging the issue and pinpointed it to the SSL verification failure. The streamer box exposes an API that the integration uses to communicate with it over HTTPS and uses a self-signed certificate (understandable, it’s a local device with no outside access). The integration itself has no issue with it but the media player component does - the album art is proxied by HA and is fetched via /api/media_player_proxy/{entity_name}
endpoint, which returns a 500 response.
This happens only when I’m using AirCast 2 protocol. So I suppose the album art is somehow fetched by the streamer and cached locally (although I don’t know how to verify it). If I use some other protocol (e.g. Spotify Connect) then the album art is correctly displayed. SO: works for Spotify over Spotify Connect, but doesn’t work for the same Spotify over AirPlay 2.
Here is the log generated by an attempt to fetch the image:
2023-11-29 19:46:01.156 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 980, in _wrap_create_connection
return await self._loop.create_connection(*args, **kwargs) # type: ignore[return-value] # noqa
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 1112, in create_connection
transport, protocol = await self._create_connection_transport(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/asyncio/base_events.py", line 1145, in _create_connection_transport
await waiter
File "/usr/local/lib/python3.11/asyncio/sslproto.py", line 575, in _on_handshake_complete
raise handshake_exc
File "/usr/local/lib/python3.11/asyncio/sslproto.py", line 557, in _do_handshake
self._sslobj.do_handshake()
File "/usr/local/lib/python3.11/ssl.py", line 979, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1006)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/aiohttp/web_protocol.py", line 433, in _handle_request
resp = await request_handler(request)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/web_app.py", line 504, in _handle
resp = await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/web_middlewares.py", line 117, in impl
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/security_filter.py", line 85, in security_filter_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/forwarded.py", line 100, in forwarded_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/request_context.py", line 28, in request_context_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 80, in ban_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 236, in auth_middleware
return await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/headers.py", line 31, in headers_middleware
response = await handler(request)
^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/http/view.py", line 148, in handle
result = await handler(request, **request.match_info)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 1179, in get
data, content_type = await player.async_get_media_image()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 595, in async_get_media_image
return await self._async_fetch_image_from_cache(url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 1097, in _async_fetch_image_from_cache
(content, content_type) = await self._async_fetch_image(url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 1108, in _async_fetch_image
return await async_fetch_image(_LOGGER, self.hass, url)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 1274, in async_fetch_image
response = await websession.get(url)
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/client.py", line 536, in _request
conn = await self._connector.connect(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 540, in connect
proto = await self._create_connection(req, traces, timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 901, in _create_connection
_, proto = await self._create_direct_connection(req, traces, timeout)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 1209, in _create_direct_connection
raise last_exc
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 1178, in _create_direct_connection
transp, proto = await self._wrap_create_connection(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/site-packages/aiohttp/connector.py", line 982, in _wrap_create_connection
raise ClientConnectorCertificateError(req.connection_key, exc) from exc
aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host 192.168.88.223:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1006)')]
The notable line is:
aiohttp.client_exceptions.ClientConnectorCertificateError: Cannot connect to host 192.168.88.223:443 ssl:True [SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1006)')]
192.168.88.223
is the IP of the streamer. So it looks like HA redirects the request to the streamer, but it sees SSL so it verifies the certificate and throws an error because it’s self-signed.
I’ve tried to hide the API of the streamer behind a reverse-proxy (I’m using Traefik for that) to present HA with an HTTP address and to let Traefik handle and ignore the self-signed cert. Unfortunately, it only partially worked. The streamer only uses HTTP API for some operations, while for others it seems UPnP is used, if I’m reading this code correctly. So, while the HTTP API worked fine, the integration overall broke completely .
I don’t know what else I could do to fix the issue. I couldn’t find any setting that would make the media player proxy ignore invalid certificates and I don’t know how else I could bypass the issue. So I hope maybe someone here will have some ideas!