(imperfect) music player casting local music tracks to Google Nest/Chromecast

Hi all,

As a newbie in HA I am enjoying discovering all the possible features (and probably will never master it!). However, I got a little bit frustrated not be be able to decently control local MP3 casting (while Spotify integration works perfectly for instance - but I don’t want to get on account), as I can only play local media one by one (maybe I missed something, but most of the posts I saw was about playing a particular music once). I tried the Plex server solution in docker, in an optimistic way (especially since the last release of HA) but get disapointed as I failed in properly casting my music to my google Nest. Hence, I came up with some solution based on scripts and automation.

The first step is to put the music in a folder inside config/www (/config/www/media in my case) so that it can be accessed through local shortcut (but needs to go through DuckDNS forwarding). Then, to extract the file list (and ensuring that only mp3’s are in the folder), I set a folder sensor in configuration.yaml:

sensor:
  - platform: folder
    folder: /config/www/media

which also requires

homeassistant:
  allowlist_external_dirs:
    - /config/www/media

After this first step, I set 4 scripts. The first one aims at initiating the process:

lancement_musique:
  alias: Lancement musique
  sequence:
  - service: script.lecture_musique
    data:
      piste: '{{ liste[18:] }}'
  mode: single
  icon: mdi:music
  variables:
    liste: '{{ state_attr(''sensor.media'', ''file_list'')|random }}'

You can note that the music is randomized. Also, I remove the first 18 characters that correspond actually to the folder (as the “file_list” attribute returns the full path).

So this script controls the “lecture_musique” script that actually casts the music to my nest in the living room:

lecture_musique:
  alias: Lecture Musique
  sequence:
  - service: media_player.play_media
    data:
      media_content_id: https://<HA URL>.duckdns.org:<PORT>/local/media/{{ piste
        }}
      media_content_type: audio/mp3
      entity_id: '{{ lecteur }}'
  mode: single
  icon: mdi:music
  variables:
    piste: <DEFAULT FILE>
    lecteur: media_player.nest_salon

The last two scripts are the controls for play/pause and stop:

arret_musique:
  alias: Arrêt musique
  sequence:
  - service: media_player.turn_off
    data:
      entity_id: '{{ lecteur }}'
  mode: single
  icon: mdi:stop
  variables:
    lecteur: media_player.nest_salon
basculer_musique:
  alias: Basculer Musique
  sequence:
  - service: media_player.media_play_pause
    data:
      entity_id: '{{ lecteur }}'
  mode: single
  icon: mdi:play-pause
  variables:
    lecteur: media_player.nest_salon

OK… But wait, what happen when a track has finished? It will stop! And this is where an automation detects when the player changes its state and recall the first script:

- id: '<AUTOMATION ID>'
  alias: Musique_suivante
  description: ''
  trigger:
  - platform: state
    entity_id: media_player.nest_salon
    from: playing
    to: idle
  condition:
  - condition: template
    value_template: '{{ ''https://<HA URL>.duckdns.org:<PORT>/local/media/'' in  state_attr(''media_player.nest_salon'',
      ''media_content_id'') }}'
  action:
  - service: script.lancement_musique
    data: {}
  mode: single

In order not to trigger this if another type of media is cast, it detects through media_content_id that the file comes from our local folder.

And Lovelace integration:
Mick player

Like this, I can have a shuffled, infinitely playing playlist (which I initially wanted), without defining a hard-coded playlist. There are of courses many possible enhancements (no shuffling, track selection etc.), but I think it solves stuff for which I have spent long time in trying to find a solution, including digging into the web…

Please do not hesitate to give your thoughts or idea. As I said, I’m really new in HA and I surely missed something!

Mick

Edit (02/11/2020): corrected a mistake regarding the correct folder to store music

3 Likes

Hello. i am very interested in this card you prepared. i cannont make it work yet. is it really mandatory to have the dns address or the ip address would be sufficient to define the path? also, i am running on hassos, so the media folder is located in the root. is it mandatory to create it in the local folder?
thank You in advance for your help

Hi,

Thanks for your interest in this project!

Unfortunately, for some reasons (I haven’t investigated yet) you need to access the media through the web. Hence, they have to be in www folder and be accessible from outside. I’ll be more than happy to find another 100% local way!

Regards,
Mick

I understand. no biggie at this point. another question would be if is there possible to define distinct folders so that maybe a dropdown list with access to different folders to be available? that way the user can choose the music of his liking? really appreaciate the work You put into this project.

Neat idea! I haven’t thought about it, but I guess by making a folder name table pointing to different filename table, that would be definitely possible. Another option would be filtering a table containing all files in folders and subfolders.

Great proposal anyway, thanks!

i have tried to create a folder within the media folder(the first step in defining multiple folders) but it does not seem to be working like that. is there a limitation in terms of number of folders and tree structure(folder within folder) and/or the folder name(only “media” accepted)?

The first thing I would try would be to check the folder sensor (to verify that it correctly gathered the files). Also, remember that the media must be exposed (this is something I’m not happy with but didn’t find other ways to do), and have to be within the “www” folder (subdirectories should be OK though).

i have no error in the log after restarting which states that the mount did not work. it is only when launching the script that i have an error that the url is not reacheable(i did a www/config/merdia/folder1/ tree and changes the number for the letters to be excluded accordingly). i also tried www/config/folder1, the result being the same. so, is it mandatory to have the folder name “media”? that would be a big limitation for this project.

Could you check the folder sensor value in the Development Tools? I don’t see at this stage what would prevent using another folder, as long as it is exposed (i.e., in www folder). Do you manage to access the media from a browser (using an access through internet)?

Did you also adapted the code (some parts explicitly refer to the “media” folder, which thus should be adapted in your case)?

i checked the media sensor and it shows as location the content of the media folder in www. should show me directly the files so the inside of the file i wish to play? if yes, that would mean that i have to have a sensor for each folder? thank You for Your patience.

i got it working. You opened my eyes with the last post, about the sensor. the problem i am facing now is this: i created 2 buttons, one for each folder. when i press the button for playing from the first folder, it does what it is supposed to do. but if i press the button for the second folder while the music from the first folder is playing, it randomises the same first folder. so it depends which folder i access first, then both radomisation buttons are acting on the same folder. would it be possible to switch from one folder to the other when the oppsite folder button is pressed?

I’m glad to hear that it starts working! Regarding the folder selection, have you assigned a variable dedicated to the current selected folder? This could be done, I think, either using an external variable, or sensing each time the track is changing which button is active from a Lovelace button card (I did something similar than this last solution to choose to which player - e.g., living room, corridor, bedroom - to cast the music).

here, i get a liitle bit lost unfortunately. i do not have the knowledge to perform a test. would it be possible please to give me a template to try to understand the logic behind it all?

Hi,

Basically, in configuration.yaml, I am using booleans to know on which media I should cast:

input_boolean:
  son_salon:
    name: Activer Nest salon
    icon: mdi:cast-audio
    initial: true
  son_couloir:
    name: Activer Nest couloir
    icon: mdi:cast-audio
  son_chromecast:
    name: Activer Chromecast salon
    icon: mdi:cast-audio

Then I am using buttons on Lovelace to set them True or False. When I play music, the script is checking these variables to know where to cast:

lecteur: |
        {% if is_state('input_boolean.son_salon','on') %}
          {% if is_state('input_boolean.son_couloir','on') %}
            {% if is_state('input_boolean.son_chromecast','on') and not is_state('media_player.chromecast', 'unavailable') %}
              media_player.all_nest_et_chrome
            {% else %}
              media_player.nest_salon_couloir
            {% endif %}
          {% else %}
            {% if is_state('input_boolean.son_chromecast','on') and not is_state('media_player.chromecast', 'unavailable') %}
              media_player.nest_salon_et_chrome
            {% else %}
              media_player.nest_salon
            {% endif %}
          {% endif %}
        {% else %}
          {% if is_state('input_boolean.son_couloir','on') %}
            {% if is_state('input_boolean.son_chromecast','on') and not is_state('media_player.chromecast', 'unavailable') %}
              media_player.nest_couloir_et_chro
            {% else %}
              media_player.nest_couloir
            {% endif %}
          {% else %}
            {% if is_state('input_boolean.son_chromecast','on') and not is_state('media_player.chromecast', 'unavailable') %}
              media_player.chromecast
            {% else %}
              none
            {% endif %}
          {% endif %}
        {% endif %}

Maybe at a first test you can try to hardcode the folders to see if the principles are OK. Then if it works a detection with a folder sensor can be the next step?

Mick

PS: sorry some variable names are based on French language.

Thank You for Your input. however i cannot see due to my low knowledge the link between the choice of the player to use and the switch that has to be made by the automation that checks from which folder the music is played. what i need is for the automation that checks this to be able to also switch to the randomisation of the folder when the script for the new folder to play is triggered by the button on my lovelace.

P.S. pas de souci, je pense que HA sait interpreter meme si on fait des combinaisons de langue dans la syntaxe.

What I had in mind is, at a first test, to hardcode to possible (and existing) folders. First, you have to define a boolean for each folder (let’s call them “rock” and “pop”) in the configuration.yaml (and restart to make them active):

input_boolean:
  rock:
    name: Let's rock! (or any other fancy name)
    icon: mdi:music-box
    initial: true
  pop:
    name: Loli...pop (sorry for the bad jokes)
    icon: mdi:music-circle
    initial: false

Then with a button on a lovelace panel you can selected one or the other folder. This is done either with button (but be careful with multiple or no selection - in this case you can prioritize later) or radio button. With that you can toggle these boolean value (I think this could be done through the GUI).

Finally in the script, you can test the variables to select the folder (see previous post).

Once this works with hardcoded folders, at least we can say that the basic stuff is OK. Next would be to make a scrollable menu with dynamically discovered folders (at each restart), but I have to admit I haven’t dug into this issue.

Mick

i understand the principle now. so i created my 2 booleans for the 2 folders i am using for testing. i have created the buttons on the lovelace. now the problem is the syntax which i do not master. this is what i created as script but the error that is returned says that the player cannot find < DEFAULT FILE > . i suppose it has something to do with the fact that it is missing the random command, which i do not know how to add it for my case.

lancement_musique:
  alias: Muzica locala
  sequence:
  - service: media_player.play_media
    data:
      media_content_id: |
        {% if is_state("input_boolean.breakbeat","on") %}
              http://ip.address/local/media/breakbeat/{{ piste }}
            {% else %}
              http://ip.address/local/media/music/{{ piste }}
            {% endif %}
      media_content_type: audio/mp3
      entity_id: '{{ lecteur }}'
  mode: single
  icon: mdi:music
  variables:
    piste:< DEFAULT FILE >
    lecteur: media_player.nest_stereo

Actually is a general name I gave. This should be a music file in the folder. For instance, you can put a file “aaa.mp3” in each folder (maybe with different sound/music to be able to know which folder is selected, and use
piste: aaa.mp3

Actually the variable “piste” select the track. If not using a folder/filelist variable, it will play this default file

I see. and then in my here above example how do i insert the random command? because now it only plays the same default track defined by piste:

Then this where the lancement musique script randomly selects the track. I recall it here:

lancement_musique:
  alias: Lancement musique
  sequence:
  - service: script.lecture_musique
    data:
      piste: '{{ liste[18:] }}'
  mode: single
  icon: mdi:music
  variables:
    liste: '{{ state_attr(''sensor.media'', ''file_list'')|random }}'

the track selection is made by piste: '{{ liste[18:] }}' which is passed a argument to the script lecture_musique. However, in our case, what I would do is to also define another variable which is the subfolder. However, before, I would advise to check to the hardcoded version works. Unfortunately, passed this point and for the folder selection, I will only be able to speculate as I haven’t implemented it (but I still think this is a great idea you had). And as I’m currently stuck with other thinks, I don’t know when I would have time to have a look to this…