Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Record the SPOTIFY_REFRESH_TOKEN.


Create your Spotify script. Update the SPOTIFY_CLIENT_ID,  SPOTIFY_CLIENT_SECRET and SPOTIFY_REFRESH_TOKEN.

You can also add the device name, URI and starting volume:

Code Block
SPOTIFY_DEVICE_NAME="Kitchen"
SPOTIFY_URI='spotify:user:spotify:playlist:37i9dQZF1DXebxttQCq0zA'
START_VOLUME=10





Code Block
titlespotify-sonos-cmd4.sh
#!/usr/bin/env bash
# spotify-sonos-cmd4.sh
# Works with Cmd4 in either call style:
#  1) "<AccessoryName>" On get|set [0|1]
#  2) Get|Set "<AccessoryName>" "On" [0|1]

set -euo pipefail

SPOTIFY_CLIENT_ID=<SONOS_CLIENT_ID>
SPOTIFY_CLIENT_SECRET=<SONOS_SECRET>
SPOTIFY_REFRESH_TOKEN="<SONOS_TOKEN>"


# ====== REQUIRED ENV ======
# Create a Spotify app at https://developer.spotify.com, then set:
#   SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET, SPOTIFY_REFRESH_TOKEN
: "${SPOTIFY_CLIENT_ID:?Set SPOTIFY_CLIENT_ID}"
: "${SPOTIFY_CLIENT_SECRET:?Set SPOTIFY_CLIENT_SECRET}"
: "${SPOTIFY_REFRESH_TOKEN:?Set SPOTIFY_REFRESH_TOKEN}"

# Sonos shows up as a Spotify Connect device. Use the *device name* as seen in the Spotify app.
: "${SPOTIFY_DEVICE_NAME:?Set SPOTIFY_DEVICE_NAME (e.g., 'Kitchen')}"
# What to play when turning ON (can be track/album/playlist/artist/show)
# Examples:
#   spotify:track:3n3Ppam7vgaVa1iaRUc9Lp
#   spotify:album:1ATL5GLyefJaxhQzSPVrLX
#   spotify:playlist:37i9dQZF1DXcBWIGoYBM5M
: "${SPOTIFY_URI:?Set SPOTIFY_URI}"

# Optional: start position in ms
START_POSITION_MS="${START_POSITION_MS:-0}"
# Optional: volume (0-100) set after transfer; leave empty to skip
START_VOLUME="${START_VOLUME:-}"

# ====== ARGUMENT PARSING ======
A1="${1:-}"; A2="${2:-}"; A3="${3:-}"; A4="${4:-}"
lower(){ printf '%s' "${1:-}" | tr '[:upper:]' '[:lower:]'; }

if [[ "$(lower "$A1")" == "get" || "$(lower "$A1")" == "set" ]]; then
  ACTION="$(lower "$A1")"; ACCESSORY="$A2"; CHAR="$(lower "$A3")"; VALUE="${A4:-}"
else
  ACCESSORY="$A1"; CHAR="$(lower "$A2")"; ACTION="$(lower "$A3")"; VALUE="${A4:-}"
fi
[[ "$CHAR" == "on" || "$CHAR" == "On" ]] || { echo "Unsupported characteristic: $CHAR" >&2; exit 1; }

# ====== SPOTIFY HELPERS ======
API="https://api.spotify.com/v1"
TOKEN_ENDPOINT="https://accounts.spotify.com/api/token"

get_access_token() {
  curl -sS --fail -u "${SPOTIFY_CLIENT_ID}:${SPOTIFY_CLIENT_SECRET}" \
    -d grant_type=refresh_token \
    --data-urlencode "refresh_token=${SPOTIFY_REFRESH_TOKEN}" \
    "$TOKEN_ENDPOINT" | jq -r '.access_token'
}

api() {
  local method="$1"; shift
  local path="$1"; shift
  local body="${1:-}"
  if [[ -n "$body" ]]; then
    curl -sS --fail -X "$method" "$API$path" \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      --data "$body"
  else
    curl -sS --fail -X "$method" "$API$path" \
      -H "Authorization: Bearer $ACCESS_TOKEN"
  fi
}

get_device_id_by_name() {
  api GET "/me/player/devices" | jq -r --arg NAME "$SPOTIFY_DEVICE_NAME" '
    .devices[]? | select(.name==$NAME) | .id' | head -n1
}

transfer_and_play() {
  local dev_id="$1"

  # Transfer playback to the Sonos device (and auto-play)
  api PUT "/me/player" "{\"device_ids\":[\"$dev_id\"],\"play\":true}" >/dev/null

  # Start the requested context/track(s)
  if [[ "$SPOTIFY_URI" == spotify:track:* ]]; then
    api PUT "/me/player/play?device_id=$dev_id" \
      "{\"uris\":[\"$SPOTIFY_URI\"],\"position_ms\":$START_POSITION_MS}" >/dev/null
  else
    api PUT "/me/player/play?device_id=$dev_id" \
      "{\"context_uri\":\"$SPOTIFY_URI\",\"offset\":{\"position\":0},\"position_ms\":$START_POSITION_MS}" >/dev/null
  fi

  # Optional volume
  if [[ -n "${START_VOLUME}" ]]; then
    api PUT "/me/player/volume?device_id=$dev_id&volume_percent=$START_VOLUME" >/dev/null || true
  fi
}

pause_playback() {
  # Pause regardless of device
  api PUT "/me/player/pause" "" >/dev/null || true
}

is_playing_here() {
  local cur
  cur="$(api GET "/me/player" || true)"
  [[ -n "$cur" ]] || return 1
  local dev_name playing
  dev_name="$(jq -r '.device.name // empty' <<<"$cur")"
  playing="$(jq -r '.is_playing // false' <<<"$cur")"
  [[ "$dev_name" == "$SPOTIFY_DEVICE_NAME" && "$playing" == "true" ]]
}

# ====== RUN ======
ACCESS_TOKEN="$(get_access_token)"

case "$ACTION" in
  get)
    if is_playing_here; then echo 1; else echo 0; fi
    ;;
  set)
    if [[ "${VALUE:-0}" == "1" ]]; then
      # Ensure device appears: if Spotify hasn’t seen the Sonos recently, ask user to start Spotify once on that speaker via the app
      DEV_ID="$(get_device_id_by_name || true)"
      if [[ -z "${DEV_ID:-}" ]]; then
        echo "0"
        echo "Spotify device '$SPOTIFY_DEVICE_NAME' not found. Make sure it is online and visible in Spotify Connect, then try again." >&2
        exit 3
      fi
      transfer_and_play "$DEV_ID"
    else
      pause_playback
    fi
    ;;
  *)
    echo "Unknown action: $ACTION" >&2; exit 1;;
esac


Code Block
themeEmacs