Music Assistant DJ Announcer Blueprint for Home Assistant – Automated DJ-Style Music Playback

Bring the party home with this fun DJ Announcer blueprint! It plays your favorite playlists on any speaker or room, adding cool DJ-style voice intros between tracks. Shuffle your music, pick randomly from several of your playlists, and get lively announcements with track names and artists—perfect for events or just vibing at home.
Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

Requirements:

Music Assistant configured properly and playlists configured
Airplay speakers have latency with TTS announcements.

  • One or more media_player entities (speakers, smart speakers, etc.) configured in Home Assistant.
  • Playlists managed by Music Assistant (or compatible playlists that work with the music_assistant.play_media service).
  • Access to a Text-To-Speech (TTS) engine in Home Assistant (default is tts.home_assistant_cloud, but any configured TTS can be used).
  • Recommended: Smart speakers or devices supporting media playback and TTS announcements.
  • Optional: Custom room aliases defined in the blueprint to easily select speakers by name.

Make sure your media players support playlist shuffling and playback control for the best experience.

Instructions to Use the DJ Announcer Blueprint

  1. Import the Blueprint
    Import the blueprint YAML into Home Assistant via Configuration > Blueprints > Import Blueprint or by clicking the import badge link provided in the post.
  2. Create Automation or Script Instance
    Once imported, create a new automation or script using the DJ Announcer blueprint as the base.
  3. Configure Inputs
  • Choose the Room alias (e.g., Living Room, Kitchen) or directly specify a Speaker(media_player entity).
  • Select from the available Playlists or paste a Music Assistant playlist URI.
  • Optionally select the TTS engine (defaults to Home Assistant Cloud TTS if left blank).
  • Adjust the Cue breath (chime_wait_sec) if your speaker needs a short delay after introductory phrases before starting music (default 2 seconds).
  1. Run the Automation/Script
    Trigger the automation/script to start the DJ session. This will:
  • Shuffle the selected playlist.
  • Play it on the chosen media player.
  • Announce the playlist and each track with DJ-style voice intros using TTS.
  • Continue announcing until playback stops.
  1. Requirements
  • Ensure at least one compatible media_player is configured in Home Assistant.
  • Have Music Assistant integration managing your playlists.
  • Confirm TTS integration is set up and functional for voice announcements.
  1. Troubleshooting
  • If no player or room name is recognized, a persistent notification will alert you to choose a valid speaker or alias.
  • If the primary playlist fails, a fallback playlist will be selected and a notification raised.
  • Check media player compatibility with playlist playback and TTS announcements.
blueprint:
  name: DJ Announcer (Music Assistant)
  description: >
    Play a playlist on any speaker, with DJ-style TTS intros between tracks.
    Select speakers/rooms/playlists via aliases, entities or URIs.
  domain: script

fields:
  room:
    name: Room (alias)
    description: Choose a room alias to resolve the target player, or leave blank if providing explicit speaker.
    selector:
      select:
        options:
          - Patio
          - Living Room
          - Office
          - Bedroom
          - Hall
          - Master Bathroom
          - Kitchen
        custom_value: true

  speaker:
    name: Speaker (entity override)
    description: Optional explicit media_player.* to use instead of alias.
    selector:
      entity:
        domain: media_player

  playlist:
    name: Playlist
    description: Pick a playlist from the pool, or paste a Music Assistant playlist URI.
    selector:
      select:
        options:
          - 90s Hits Essentials
          - BBQ Classics
          - Yacht Rock Essentials
          - 70s Hits Essentials
        custom_value: true

  tts_engine:
    name: TTS engine
    description: Optional TTS engine entity_id, blank for tts.home_assistant_cloud.
    selector:
      entity:
        domain: tts

  chime_wait_sec:
    name: Cue breath (seconds)
    description: Breath after Jetsons cue (default 2s).
    default: 2
    selector:
      number:
        min: 0
        max: 6
        step: 0.5

variables:
  _room_in: "{{ (room | default('') | string) | trim | lower }}"
  _speaker_in: "{{ (speaker | default('') | string) | trim }}"
  _tts_in: "{{ (tts_engine | default('tts.home_assistant_cloud') | string) | trim }}"
  _playlist_in: "{{ (playlist | default('') | string) | trim }}"
  _chime_wait: "{{ (chime_wait_sec | default(2.0)) | float(2) }}"
  _aliases:
    patio: media_player.patio_nabu
    living room: media_player.homepod_ma
    office: media_player.office_homepod_mini
    bedroom: media_player.bedroom_homepod_mini_ma
    hall: media_player.hall_homepod_mini_ma
    master bathroom: media_player.master_bathroom_homepod_mini_ma
    kitchen: media_player.kitchen_nabu
  _playlist_name_map:
    90s Hits Essentials: library://playlist/42
    BBQ Classics: library://playlist/84
    Yacht Rock Essentials: library://playlist/83
    70s Hits Essentials: library://playlist/85
    library://playlist/42: 90s Hits Essentials
    library://playlist/84: BBQ Classics
    library://playlist/83: Yacht Rock Essentials
    library://playlist/85: 70s Hits Essentials
  _playlist_pool_default:
    - library://playlist/42
    - library://playlist/84
    - library://playlist/83
    - library://playlist/85
  _player: >-
    {% set s = _speaker_in %}
    {% if s %}{{ s }}
    {% elif _aliases.get(_room_in) %}{{ _aliases.get(_room_in) }}
    {% else %}{% endif %}
  _tts: |-
    {% if '.' in _tts_in %}
      {{ _tts_in }}
    {% else %}
      tts.home_assistant_cloud
    {% endif %}
  _playlist_uri: |-
    {% set p = _playlist_in %}
    {% if p.startswith('library://playlist/') %}
      {{ p }}
    {% elif _playlist_name_map.get(p) %}
      {{ _playlist_name_map.get(p) }}
    {% else %}
      {{ (_playlist_pool_default | random) | string }}
    {% endif %}
  _picked_playlist_speak: |-
    {% set uri = _playlist_uri %}
    {% if uri.startswith('library://playlist/') %}
      {{ _playlist_name_map.get(uri, 'your playlist') }}
    {% else %}
      {{ uri }}
    {% endif %}
  _intro_openers:
    - Alright, the lights are warm and the faders are live
    - Hey there music lovers, welcome to the mix
    - Good vibes coming your way
    - Time to turn up the energy
    - Let’s get this party started
    - Welcome back to the sound zone
    - Ready to dive into some great music
    - The beats are calling and we’re answering
  _intro_connectors:
    - Spinning up
    - Starting with
    - Diving into
    - Kicking off with
    - Opening up
    - Beginning our journey through
  _track_intros:
    - First up
    - Starting strong with
    - Opening with
    - Leading off with
    - Kicking things off with
    - Here’s our opener
  _transition_phrases:
    - Up next on
    - Coming up from
    - Here’s another gem from
    - Keep the energy flowing with
    - Don’t touch that dial, we’ve got
    - Staying in the groove with
    - Next up from
    - Continuing the vibe with
    - Here comes
    - Rolling into
  _artist_connectors:
    - by
    - from
    - courtesy of
    - brought to you by
  _outro_phrases:
    - That’s the end of
    - We’ve reached the final notes of
    - Thanks for vibing with
    - That’s a wrap on
    - Hope you enjoyed the journey through
    - Until next time, that was
  _outro_closers:
    - See you next time
    - Keep the music playing
    - Thanks for listening
    - Catch you on the flip side
    - Stay tuned for more great music
    - Keep those good vibes going

sequence:
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ not _player }}"
        sequence:
          - service: persistent_notification.create
            data:
              title: DJ Announcer Error
              message: >-
                Could not resolve a player. Provide a speaker entity or a known room alias (Patio, Living Room, Office, Bedroom, Hall, Master Bathroom, Kitchen).
              notification_id: dj_announcer_error
          - stop: Missing/invalid player
  - service: logbook.log
    data:
      name: DJ Announcer
      message: >
        Chosen playlist: {{ _playlist_uri }} (speaks as: {{ _picked_playlist_speak }})
  - service: media_player.shuffle_set
    target:
      entity_id: "{{ _player }}"
    data:
      shuffle: true
  - service: music_assistant.play_media
    target:
      entity_id: "{{ _player }}"
    data:
      media_type: playlist
      media_id: "{{ _playlist_uri }}"
      enqueue: replace
  - wait_template: "{{ states(_player) in ['playing','buffering'] }}"
    timeout: "00:01:00"
  - choose:
      - conditions:
          - condition: template
            value_template: "{{ states(_player) not in ['playing','buffering'] }}"
        sequence:
          - service: music_assistant.play_media
            target:
              entity_id: "{{ _player }}"
            data:
              media_type: playlist
              media_id: "{{ (_playlist_pool_default | random) }}"
              enqueue: replace
          - service: persistent_notification.create
            data:
              title: Music DJ Warning
              message: Primary playlist failed. Fallback chosen.
              notification_id: music_dj_fallback
  - wait_template: "{{ (state_attr(_player,'media_title') | default('',true) | trim) != '' }}"
    timeout: "00:00:06"
    continue_on_timeout: true
  - variables:
      _raw_title: "{{ state_attr(_player,'media_title') | default('this one', true) }}"
      _raw_artist: >-
        {% set a = state_attr(_player,'media_artist') | default('', true) %}
        {% if not a %}
          {% set al = state_attr(_player,'media_artist_list') | default([], true) %}
          {% set a = (al | list | join(', ')) %}
        {% endif %}
        {% set a = a | replace('VEVO','') | replace('- Topic','') | replace(' - Topic','') | trim %}
        {{ a }}
      _speak_title: "{{ _raw_title }}"
      _speak_artist: >-
        {% set t = (_raw_title | lower) %}
        {% set a = (_raw_artist | lower) %}
        {% if a and a not in t and a != t %}{{ _raw_artist }}{% endif %}
  - wait_template: "{{ as_timestamp(now()) - as_timestamp(states[_player].last_changed) > _chime_wait }}"
    timeout: "00:00:10"
    continue_on_timeout: true
  - choose:
      - conditions:
          - condition: template
            value_template: >
              {{ states(_player) in ['playing','buffering']
              and (state_attr(_player,'media_title') | default('', true) | trim) != '' }}
        sequence:
          - service: tts.speak
            target:
              entity_id: "{{ _tts }}"
            data:
              media_player_entity_id: "{{ _player }}"
              cache: false
              message: >-
                {{ _intro_openers | random }}, {{ _intro_connectors | random }}
                {{ _picked_playlist_speak }}, and {{ _track_intros | random }}
                it's {{ _speak_title }}{% if _speak_artist %} by {{ _speak_artist }}{% endif %}.
  - variables:
      _last_title: "{{ state_attr(_player, 'media_title') | default('', true) }}"
  - repeat:
      while:
        - condition: template
          value_template: >
            {{ states(_player) in ['playing','buffering']
            and (state_attr(_player,'media_title') | default('', true) | trim) != '' }}
      sequence:
        - wait_template: >
            {{ state_attr(_player,'media_title') | default('', true) != _last_title }}
          timeout: "00:30:00"
          continue_on_timeout: true
        - variables:
            _last_title: "{{ state_attr(_player, 'media_title') | default(_last_title, true) }}"
            _raw_title: "{{ _last_title | default('this one', true) }}"
            _raw_artist: >-
              {% set a = state_attr(_player,'media_artist') | default('', true) %}
              {% if not a %}
                {% set al = state_attr(_player,'media_artist_list') | default([], true) %}
                {% set a = (al | list | join(', ')) %}
              {% endif %}
              {% set a = a | replace('VEVO','') | replace('- Topic','') | replace(' - Topic','') | trim %}
              {{ a }}
            _speak_title: "{{ _raw_title }}"
            _speak_artist: >-
              {% set t = (_raw_title | lower) %}
              {% set a = (_raw_artist | lower) %}
              {% if a and a not in t and a != t %}{{ _raw_artist }}{% endif %}
        - choose:
            - conditions:
                - condition: template
                  value_template: |
                    {{ states(_player) in ['playing','buffering']
                       and (state_attr(_player,'media_title') | default('', true) | trim) != '' }}
              sequence:
                - service: tts.speak
                  target:
                    entity_id: "{{ _tts }}"
                  data:
                    media_player_entity_id: "{{ _player }}"
                    cache: false
                    message: >-
                      {{ _transition_phrases | random }} {{ _picked_playlist_speak }}:{% if _speak_title %} {{ _speak_title }}{% if _speak_artist %} {{ _artist_connectors | random }} {{ _speak_artist }}{% endif %} {% else %} another great track{% endif %}.
  - wait_template: "{{ states(_player) in ['idle', 'off'] }}"
    timeout: "01:00:00"
    continue_on_timeout: false
  - service: tts.speak
    target:
      entity_id: "{{ _tts }}"
    data:
      media_player_entity_id: "{{ _player }}"
      cache: false
      message: >-
        {{ _outro_phrases | random }} {{ _picked_playlist_speak }}. {{ _outro_closers | random }}
1 Like