The pipe starts a new process in a new environment which has a copy of the parent process' environment, hence it cannot change the parent variables.
So, you need an alternative approach:
homepods=$(
printf "%s," $(
jq -r '.data.players[]|select(.type == "airplay" and .is_multiple == false)|.id' players.json
))
In this case the parent is capturing the output of the child instead.
or alternatively:
homepods=$(
jq -r '.data.players[]|select(.type == "airplay" and .is_multiple == false)|.id' players.json | while read homepod; do
echo "$homepod,"
done)
Answering your additional request: you could also echo the counter and then split it up from the captured output. However, there is an easier alternative:
homepods_array=(${homepods//,/ })
homepods_count=${#homepods_array[@]}
That is converting the string to a bash array and then recovering the length of the array. That is also using bash string manipulation to replace the commas for spaces.
BTW, using string manipulation you can get your ids in many formats with no loops at all:
homepods_lines=$(jq -r '.data.players[]|select(.type == "airplay" and .is_multiple == false)|.id' players.json)
homepods_spaces=${homepods_lines//$'\n'/ }
homepods_commas=${homepods_lines//$'\n'/,}
homepods_array=(homepods_lines)
homepods_count=${#homepods_array[@]}