#!/usr/bin/env bash URL_BASE="https://www.radiorecord.ru/api" MPV_PID_FILE="/tmp/mpv_radio_pid" RADIO_ID_FILE="/tmp/radio_id" STATION_IDS='[507,522,523,536,537,42532,42602]' # Custom (non-radiorecord) stations # id must be a unique integer >= 1000000 to avoid collision with radiorecord IDs. # icon_fill_white: URL to station icon image, or "" for none. CUSTOM_STATIONS='[ ]' STATIONS="[]" DEFAULT_INFO='{"artist":"","song":"","image600":""}' DEFAULT_MEDIA='{"player":"","status":"Stopped","artist":"","title":"","art":""}' PID=$([ -e "$MPV_PID_FILE" ] && cat "$MPV_PID_FILE" || echo 0) RADIO_ID=$([ -e "$RADIO_ID_FILE" ] && cat "$RADIO_ID_FILE" || echo 0) PAUSED=$(( PID == 0 ? 1 : 0 )) INFO="$DEFAULT_INFO" MEDIA="$DEFAULT_MEDIA" STATUS="{}" get_stations() { local rr rr=$(curl -s --compressed "$URL_BASE/stations/" 2>/dev/null \ | jq --argjson ids "$STATION_IDS" \ '.result.stations | map(select(.id | IN($ids[]))) | map({id, title, stream_hls, icon_fill_white, "radiorecord": true})' 2>/dev/null) jq -n \ --argjson rr "${rr:-[]}" \ --argjson custom "$CUSTOM_STATIONS" \ '$rr + ($custom | map(. + {"radiorecord": false}))' } get_song() { curl -s --compressed "$URL_BASE/station/history/?id=$RADIO_ID" \ | jq '.result.history[0] | {artist, song, image600}' } get_stream_url() { echo "$STATIONS" | jq -r --argjson id "$RADIO_ID" 'map(select(.id == $id))[0].stream_hls // empty' } get_player_info() { local player status artist title art # exclude mpv (radio) so it never appears as "external player" player=$(playerctl -l 2>/dev/null | grep -Ev '^mpv' | head -1) if [ -z "$player" ]; then echo '{"player":"","status":"Stopped","artist":"","title":"","art":""}' return fi status=$(playerctl -p "$player" status 2>/dev/null || echo "Stopped") artist=$(playerctl -p "$player" metadata artist 2>/dev/null || echo "") title=$(playerctl -p "$player" metadata title 2>/dev/null || echo "") art=$(playerctl -p "$player" metadata mpris:artUrl 2>/dev/null || echo "") jq -cnr --arg player "$player" --arg status "$status" \ --arg artist "$artist" --arg title "$title" --arg art "$art" \ '{player:$player, status:$status, artist:$artist, title:$title, art:$art}' } update_state() { PID=$([ -e "$MPV_PID_FILE" ] && cat "$MPV_PID_FILE" || echo 0) RADIO_ID=$([ -e "$RADIO_ID_FILE" ] && cat "$RADIO_ID_FILE" || echo 0) if [ "$PID" -gt 0 ] && ! kill -0 "$PID" 2>/dev/null; then PID=0 rm -f "$MPV_PID_FILE" fi PAUSED=$(( PID == 0 ? 1 : 0 )) } emit_status() { jq -cnr \ --argjson stations "${STATIONS:-[]}" \ --argjson radio_id "${RADIO_ID:-0}" \ --argjson is_paused "${PAUSED:-1}" \ --argjson info "${INFO:-$DEFAULT_INFO}" \ --argjson media "${MEDIA:-$DEFAULT_MEDIA}" \ '{"is_paused": $is_paused, "song": $info, "radio": $radio_id, "stations": $stations, "media": $media}' } get_stream_node() { local name="$1" wpctl status 2>/dev/null | awk -v pat="$name" ' /Streams:/ { in_s = 1 } /Sinks:|Sources:|Clients:/ { in_s = 0 } in_s && /[0-9]+\./ && tolower($0) ~ tolower(pat) { match($0, /[0-9]+/); print substr($0, RSTART, RLENGTH); exit } ' } get_mpv_node() { get_stream_node "mpv" } get_volume() { if [ "$PID" -gt 0 ] && kill -0 "$PID" 2>/dev/null; then local node_id; node_id=$(get_mpv_node) [ -n "$node_id" ] && wpctl get-volume "$node_id" 2>/dev/null \ | awk '{printf "%d", $2 * 100}' else local player; player=$(playerctl -l 2>/dev/null | grep -Ev '^mpv' | head -1) [ -n "$player" ] && playerctl -p "$player" volume 2>/dev/null \ | awk '{printf "%d", $1 * 100}' fi } do_vol() { local vol="${1%.*}" if [ "$PID" -gt 0 ] && kill -0 "$PID" 2>/dev/null; then local node_id; node_id=$(get_mpv_node) [ -n "$node_id" ] && wpctl set-volume "$node_id" "${vol}%" else playerctl volume "$(awk -v v="$vol" 'BEGIN{printf "%.2f", v/100}')" 2>/dev/null fi eww update radio-vol="$vol" } do_mute() { local node_id if [ "$PID" -gt 0 ] && kill -0 "$PID" 2>/dev/null; then node_id=$(get_mpv_node) else local player; player=$(playerctl -l 2>/dev/null | grep -Ev '^mpv' | head -1) [ -n "$player" ] && node_id=$(get_stream_node "${player%%.*}") fi [ -z "$node_id" ] && return wpctl set-mute "$node_id" toggle if wpctl get-volume "$node_id" 2>/dev/null | grep -q MUTED; then eww update radio-muted=true else eww update radio-muted=false fi } do_start() { [ "$RADIO_ID" -le 0 ] && return STATIONS=$(get_stations) RADIO_URL=$(get_stream_url) [ -z "$RADIO_URL" ] || [ "$RADIO_URL" = "null" ] && return [ "$PID" -gt 0 ] && kill "$PID" 2>/dev/null nohup mpv --no-video --quiet "$RADIO_URL" >/dev/null 2>&1 & echo $! > "$MPV_PID_FILE" echo "$RADIO_ID" > "$RADIO_ID_FILE" } status_loop() { STATIONS=$(get_stations) echo "$(emit_status)" last_pid_check=0 last_song_fetch=0 last_media_fetch=0 while true; do now=$(date -u +%s%3N) if (( now - last_pid_check > 1000 )); then update_state NEW_STATUS=$(emit_status) if [ "$NEW_STATUS" != "$STATUS" ]; then STATUS=$NEW_STATUS; echo "$STATUS"; fi last_pid_check=$now fi if (( now - last_song_fetch > 15000 )); then if [ "$PAUSED" = 0 ]; then IS_RR=$(echo "$STATIONS" | jq --argjson id "$RADIO_ID" 'map(select(.id == $id))[0].radiorecord // true') if [ "$IS_RR" = "true" ]; then FETCHED=$(get_song) INFO=$(echo "$INFO" "$FETCHED" | jq -s ' reduce .[] as $x ({}; . + ($x | with_entries(select(.value != null))))') else STATION_TITLE=$(echo "$STATIONS" | jq -r --argjson id "$RADIO_ID" 'map(select(.id == $id))[0].title // ""') INFO=$(jq -cnr --arg title "$STATION_TITLE" '{artist:"",song:$title,image600:""}') fi else STATION_IMG=$(echo "$STATIONS" \ | jq -r --argjson id "$RADIO_ID" 'map(select(.id == $id))[0].icon_fill_white // ""') STATION_TITLE=$(echo "$STATIONS" \ | jq -r --argjson id "$RADIO_ID" 'map(select(.id == $id))[0].title // ""') INFO=$(jq -cnr --arg img "$STATION_IMG" --arg title "$STATION_TITLE" \ '{artist:"",song:$title,image600:$img}') fi NEW_STATUS=$(emit_status) if [ "$NEW_STATUS" != "$STATUS" ]; then STATUS=$NEW_STATUS; echo "$STATUS"; fi last_song_fetch=$now fi if (( now - last_media_fetch > 3000 )); then MEDIA=$(get_player_info) NEW_STATUS=$(emit_status) if [ "$NEW_STATUS" != "$STATUS" ]; then STATUS=$NEW_STATUS; echo "$STATUS"; fi VOL=$(get_volume) [ -n "$VOL" ] && eww update radio-vol="$VOL" 2>/dev/null last_media_fetch=$now fi sleep 0.5 done } case "$1" in start) RADIO_ID="$2" echo "$RADIO_ID" > "$RADIO_ID_FILE" do_start ;; vol) do_vol "$2" ;; mute) update_state do_mute ;; toggle) update_state if [ "$PAUSED" = 1 ]; then [ "$RADIO_ID" -gt 0 ] && do_start else [ "$PID" -gt 0 ] && kill "$PID" 2>/dev/null rm -f "$MPV_PID_FILE" fi ;; *) status_loop ;; esac