Play Media Script w/ Shuffle, Repeat, Multi-room, Volume support

This script blueprint provides an easy to way play media. The script has the following field parameters that can be set when called:

  • Shuffle
  • Repeat
  • Specify multiple media players to group together for a multi-room setup (e.g. Sonos)
  • Specify volume (and it will apply to all grouped players)

The script also has a few fail safes based on observed behaviour including double checking that shuffle and repeat did take, and in the case of shuffle, if it didn’t originally take, the script sets shuffle again and then forces to the next song so you don’t always hear the first song.

This blueprint has no inputs, but is meant to be a script to call from automations.
 

Open your Home Assistant instance and show the blueprint import dialog with a specific blueprint pre-filled.

# ***************************************************************************
# *  Copyright 2022 Joseph Molnar
# *
# *  Licensed under the Apache License, Version 2.0 (the "License");
# *  you may not use this file except in compliance with the License.
# *  You may obtain a copy of the License at
# *
# *      http://www.apache.org/licenses/LICENSE-2.0
# *
# *  Unless required by applicable law or agreed to in writing, software
# *  distributed under the License is distributed on an "AS IS" BASIS,
# *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# *  See the License for the specific language governing permissions and
# *  limitations under the License.
# ***************************************************************************

# Plays the specified media on the specified devices with various options for shuffle, repeat, etc.

blueprint:
  name: "Play Media"
  description:
    "This blueprint is used to add a script for playing media. The script plays the media
    specified and provides shuffle, repeat and volume options. \
    If you have a multi-room setup (e.g. Sonos) additional media players can be specified. The players \
    will be grouped together with the primary media player."
  domain: script

fields:
  media_content_id:
    name: Content ID
    description: The ID of the content to play. Platform dependent.
    example: https://open.spotify.com/playlist/37i9dQZF1DX4fQhfyVRsHW?si=9e78114227474022
    required: true
    selector:
      text:
  media_content_type:
    name: "Content Type"
    description: "The type of content that will play. Platform dependent."
    example: "music"
    required: true
    selector:
      text:
  entity_id:
    description: "The entity id that will be used to play and control the specified media."
    name: "Entity"
    required: true
    selector:
      entity:
        domain: media_player
  group_members:
    description: "The entity ids of the additional entities to group together in a multi-room system. This ignores all types but entity ids."
    name: "Group Members"
    required: false
    selector:
      target:
        entity:
          domain: media_player
  shuffle:
    name: "Shuffle"
    description: "True/false for enabling/disabling shuffle. If not set, current setting is used."
    required: false
    selector:
      boolean: null
  repeat:
    name: "Repeat Mode"
    description: "Repeat mode set to off, all, one. If not set, current mode is used."
    required: false
    selector:
      select:
        options:
          - "off"
          - "all"
          - "one"
  volume_level:
    name: "Volume Level"
    description: "Float for volume level. Range 0..1. If not set, current value is used. If you specified Group Members the volume will be applied to all members."
    required: false
    selector:
      number:
        min: 0
        max: 1
        step: 0.01
        mode: slider

sequence:
  # if group members are defined then we do a grouping
  - choose:
      - conditions: >
          {{ group_members is defined }}
        sequence:
          - service: media_player.join
            data:
              group_members: >-
                {# can't modify a list or reset a variable, so using a replaceable array in a mutable container as work around#}
                {# we only uses entity_id, and there will only be one given it is a dictionary #}
                {# but in the future we can look at converting areas and devices into entities #}
                {%- set workaround = namespace(members = []) -%}
                {%- for key in group_members if key == 'entity_id'-%} 
                  {%- set workaround.members = group_members[key] -%}
                {%- endfor -%} 
                {{ workaround.members }}
              entity_id: "{{ entity_id }}"

  # we only set repeat if the repeat value was given
  - choose:
      - conditions: >
          {{ repeat is defined }}
        sequence:
          - service: media_player.repeat_set
            data:
              repeat: "{{ repeat }}"
              entity_id: "{{ entity_id }}"
    default: []

  # it appears that if you set shuffle and repeat
  # that both do not happen all the time (at least
  # on Sonos), so delay is here to help
  - choose:
      - conditions: >
          {{ repeat is defined and shuffle is defined }}
        sequence:
          - delay:
              hours: 0
              minutes: 0
              seconds: 1
              milliseconds: 0
    default: []

  # we only set shuffle if the shuffle value was given
  - choose:
      - conditions: >
          {{ shuffle is defined }}
        sequence:
          - service: media_player.shuffle_set
            data:
              shuffle: "{{ shuffle }}"
              entity_id: "{{ entity_id }}"
    default: []

  # we only set the volume if the volume value was given
  - choose:
      - conditions: >
          {{ volume_level is defined }}
        sequence:
          - service: media_player.volume_set
            data:
              volume_level: "{{ volume_level }}"
              entity_id: "{{ entity_id }}"
          - choose:
              # if we have additional members, we set all their volumes
              - conditions: >
                  {{ group_members is defined }}
                sequence:
                  # need to make the right target definition
                  - service: media_player.volume_set
                    data:
                      volume_level: "{{ volume_level }}"
                    target: >-
                      {{ group_members }}
            default: []
    default: []

  # no matter what, we get the media playing
  # assuming we have the right media type
  - service: media_player.play_media
    data:
      media_content_id: "{{media_content_id}}" 
      media_content_type: "{{ media_content_type }}"
      entity_id: "{{ entity_id }}"

  # now we double check shuffle state and set
  # again because sometimes it doesn't take
  - choose:
      - conditions: >
          {{ shuffle is defined and is_state_attr(entity_id, 'shuffle', shuffle) == false }}
        sequence:
          - service: media_player.shuffle_set
            data:
              shuffle: "{{ shuffle }}"
              entity_id: "{{ entity_id }}"
          # in this case we wil also force the next
          # track just to be sure
          - choose:
              - conditions: >
                  {{ shuffle == true }}
                sequence:
                  - service: media_player.media_next_track
                    data:
                      entity_id: "{{ entity_id }}"
            default: []
    default: []

  # now we double check repeat state and set
  # again because sometimes it doesn't take
  - choose:
      - conditions: >
          {{ repeat is defined and is_state_attr(entity_id, 'repeat', repeat) == false }}
        sequence:
          - service: media_player.repeat_set
            data:
              repeat: "{{ repeat }}"
              entity_id: "{{ entity_id }}"
    default: []

mode: parallel
icon: mdi:music-box-outline

As a note, I have a very similar blueprint that takes a list of media content ids (instead of one) and will randomly choose which to play whenever the script is invoked. It can be found here.

3 Likes