HifiBerry OS – Media Player integration

Hello,

I would love to have a integration for HifiBerry OS

HiFiBerryOS is is our version of a minimal Linux distribution optimised for audio playback. The goal isn’t to add as much functionality as possible, but focus on music. We don’t think, you should have to deal with sample rates or file format, but focus on what’s really important: listening to music.

The following services are available:

Airplay
Analoge input of the DAC+ ADC
Bluetooth (not on Raspberry Pi 3B)
Logitech Media Server / Squeezebox
MPD (unused at the moment)
Spotify
Roon

There is an api for the Player that I think could be used for the integration.

Thanks
Jan

May I ask what this has, which the more usual Max2Play or PiCorePlayer’s do not ?
Thanks

I like the minimal approach of it and I like to use the HifiBerry DSP Board and this comes with the toolkit to program the DSP.

I would like to build my own smart mini DAC/Preamp with this.

It also offers more sources, e.g. Roon that is not supported by Max2Play or PiCorePlayer.

1 Like

And its free, so far. They get paided from us buying products (amp2, digi+ etc) from them instead of an yearly subscription.

1 Like

@kmplng, I’ve created an integration here - https://github.com/willholdoway/core/tree/HifiBerry-Integration/homeassistant/components/hifiberry

I would really appreciate your feedback, it seems to work fine with my hardware but this is my first time writing an integration from scratch :slight_smile:

1 Like

Hey, I’m trying to use your compontent but i can’t get it to work.
I’ve put the 3 files into

homeassistant/custom_components/hifiberry/

In the configuration.yaml I’ve put:

media_player:
  - platform: hifiberry
    host: ip.of.hifi.berry
    name: HifiBerry
    port: 81

But in Home Assistant when i let it check the configuration.yaml I get the following message:

Platform error media_player.hifiberry - Integration 'hifiberry' not found.

Can you help me out?

Edit: had a typo:
homeassistant/custom_components/hifiberry/ was the one iwas already trying, but I wrote:
homeassistant/custom/components/hifiberry/

The location of the custom component is wrong it should be

homeassistant/custom_components/hifiberry

Instead of

homeassistant/custom/components/hifiberry

I’ve changed it to:

homeassistant/custom/components/hifiberry/

but still the same error:

Platform error media_player.hifiberry - Integration 'hifiberry' not found.

Change to homeassistant/custom_components/hifiberry/ and restart home assistant…

No you chose the wrong one, I said it should be like the first example in my post not the second one. I removed the * from my post, wanted to make the text bold but didn’t realize that it was inside brackets.

I changed it back to /custom_components/ and got it working by also renaming init.py to __init__.py
I’ve got it working now but I get the following error message when i change the volume:

local variable 'mediaurl' referenced before assignment

aswell as the following error over 100 times already:

UnboundLocalError: local variable 'mediaurl' referenced before assignment


I got it working without throwing errors, and most of the functions seem to work.
I’ve changed the following part

    def media_image_url(self):
        """Image url of current playing media."""
        url = self._state.get("artUrl", None)
        mediaurl = self._state.get("externalArtUrl", None)
        # Utilise external artwork if no local is available.
        if mediaurl is None:
            return url
        return mediaurl

@T-mo how do you feel about this?

    def media_image_url(self):
        """Image url of current playing media."""
        artUrl = self._state.get("artUrl", None)
        externalArtUrl = self._state.get("externalArtUrl", None)
        if artUrl is None:
            return externalArtUrl
        if artUrl[0] != "h":
            if "artwork" in artUrl:
                artUrl = f"http://{self.host}:{self.port}/{artUrl}"
            else:
                return externalArtUrl
        return artUrl

I’ve put the preference on the artUrl rather than externalArtUrl tag as I’d rather it downloads from the local source where possible as this will make it match the players artwork and prevent extraneous download.

The artUrl is either an http:// with an internet streaming service like Spotify or artwork/*.* with a local streaming service such as Roon (which I predominantly built this for). This has conditional options for either.

I look forward to hearing your thoughts!

For the “quick fix” in my previous post I just wanted to get it working, so I had no preference on which image it should use.
I don’t use the Hifiberry with Roon, so far I’ve only used it with spotify connect, but I tested a few songs and the artUrl seems to result in higher resolution images than the externalArtUrl.
Since you also want to prefer the artUrl, it seems like a good idea to use the artUrl if its available. I’m not able to test the following part:

        if artUrl[0] != "h":
            if "artwork" in artUrl:
                artUrl = f"http://{self.host}:{self.port}/{artUrl}"
            else:
                return externalArtUrl

Do you know if artUrl can only be None, “http://” (online) or “artwork/” (local)?
In that case I would structure it something like this to increase readablity:

def media_image_url(self):
        """Image url of current playing media."""
        artUrl = self._state.get("artUrl", None)
        externalArtUrl = self._state.get("externalArtUrl", None)
        if artUrl is not None:
            if artUrl.startswith("artwork/"):
                return f"http://{self.host}:{self.port}/{artUrl}"
            return artUrl
        return externalArtUrl

What do you think of that?

hi, thanks for the plugins - seems to be working fine, however after using it my logs are full of warnings (each second on status pull):

2020-05-08 09:23:36 WARNING (MainThread) [homeassistant.util.async_] Detected I/O inside
 the event loop. This is causing stability issues. Please report issue to the custom component
 author for hifiberry doing I/O at custom_components/hifiberry/media_player.py, line 111:
 response = requests.get(url, params=params)

can someone please fix this? maybe async needs to be used?

thanks

Thanks for the integration @willholdoway
Happy hifiberry and home assistant user here!

1 Like

It was a bit of a mission, but I’ve now updated this to use async.

@willholdoway thanks for great work! :slight_smile:

I’ve found 2 issues:

first:

2020-05-25 10:56:05 ERROR (SyncWorker_1) [homeassistant.loader] Error parsing manifest.json file at /config/custom_components/hifiberry/manifest.json: Expecting value: line 7 column 18 (char 186)

can be solved by quotes in manifest.json, codeowners:

{
  "domain": "hifiberry",
  "name": "Hifiberry",
  "documentation": "https://www.home-assistant.io/integrations/HifiBerry",
  "requirements": [],
  "dependencies": [],
  "codeowners": ["@willholdoway"]
}

second, not sure why this happens:

2020-05-25 11:05:49 ERROR (MainThread) [aiohttp.server] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/yarl/__init__.py", line 161, in __new__
    port = val.port
  File "/usr/local/lib/python3.7/urllib/parse.py", line 169, in port
    port = int(port, 10)
ValueError: invalid literal for int() with base 10: '8123static'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/aiohttp/client.py", line 380, in _request
    url = URL(str_or_url)
  File "/usr/local/lib/python3.7/site-packages/yarl/__init__.py", line 165, in __new__
    ) from e
ValueError: Invalid URL: port can't be converted to integer

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_protocol.py", line 418, in start
    resp = await task
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_app.py", line 458, in _handle
    resp = await handler(request)
  File "/usr/local/lib/python3.7/site-packages/aiohttp/web_middlewares.py", line 119, in impl
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/real_ip.py", line 39, in real_ip_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/ban.py", line 73, in ban_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/auth.py", line 127, in auth_middleware
    return await handler(request)
  File "/usr/src/homeassistant/homeassistant/components/http/view.py", line 125, in handle
    result = await result
  File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 881, in get
    data, content_type = await player.async_get_media_image()
  File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 424, in async_get_media_image
    return await _async_fetch_image(self.hass, url)
  File "/usr/src/homeassistant/homeassistant/components/media_player/__init__.py", line 837, in _async_fetch_image
    response = await websession.get(url)
  File "/usr/local/lib/python3.7/site-packages/aiohttp/client.py", line 382, in _request
    raise InvalidURL(str_or_url)
aiohttp.client_exceptions.InvalidURL: http://172.16.172.2:8123static/unknown.png
2

my config (base_url: has been deprecated):

homeassistant:
  external_url: https://my.external-url.tld
  internal_url: http://172.16.172.2:8123

media_player:
  - platform: hifiberry
    host: 172.16.172.3
    name: HifiBerry
    port: 81

home assistant version: 0.110.2

thanks for help

I’ve just updated this to prevent it from trying to parse the static/unkown.png artwork when the HomeAssistant is starting, please try my new files and I’m hoping your issue will be solved.

1 Like

@willholdoway works like a charm :slight_smile: many thanks for your work! :slight_smile: I would love if you considered this merging into the HA main branch.

1 Like