Commit d6b1adc454af

Vincent Demeester <vincent@sbr.pm>
2026-07-03 21:56:25
feat(emacs): add empv subsonic radio station support
Added internet radio browsing and playback via the Subsonic getInternetRadioStations API endpoint with new empv-subsonic-radios command bound to C-c M R.
1 parent 180deee
Changed files (1)
dots
config
emacs
dots/config/emacs/init.el
@@ -3440,11 +3440,11 @@ With \\[universal-argument] \\[universal-argument]: always prompt with completio
   :commands (empv-subsonic-search
              empv-subsonic-artists
              empv-subsonic-albums
-             empv-subsonic-playlists
              empv-subsonic-songs)
   :bind (("C-c M s" . empv-subsonic-search)
          ("C-c M a" . empv-subsonic-albums)
          ("C-c M P" . empv-subsonic-playlists)
+         ("C-c M R" . empv-subsonic-radios)
          ("C-c M SPC" . empv-toggle)
          ("C-c M i" . empv-display-current)
          ("C-c M n" . empv-playlist-next)
@@ -3516,7 +3516,59 @@ With \\[universal-argument] \\[universal-argument]: always prompt with completio
     (interactive)
     (empv--subsonic-request
      "getPlaylists.view"
-     (empv--subsonic-result-handler "Select playlist:"))))
+     (empv--subsonic-result-handler "Select playlist:")))
+
+  ;; Subsonic internet radio support
+  ;; Uses getInternetRadioStations Subsonic API endpoint.
+
+  (defun empv--subsonic-format-radio (cand)
+    "Format a Subsonic radio candidate for display."
+    (format "%s %s"
+            (propertize (alist-get 'name cand) 'face 'bold)
+            (propertize (or (alist-get 'homePageUrl cand) "") 'face 'italic)))
+
+  ;; Extend the formatter to handle radio kind
+  (advice-add 'empv--subsonic-format-candidate :around
+              (lambda (orig cand)
+                (if (eq (alist-get 'kind cand) 'radio)
+                    (empv--with-text-properties
+                     (empv--subsonic-format-radio cand)
+                     :item cand)
+                  (funcall orig cand))))
+
+  ;; Extend act-on-candidate to handle radio kind
+  ;; Radio stations have a direct streamUrl, no need to fetch sub-items
+  (advice-add 'empv--subsonic-act-on-candidate :around
+              (lambda (orig selected)
+                (if (eq (alist-get 'kind selected) 'radio)
+                    (let ((url (alist-get 'streamUrl selected)))
+                      (if url
+                          (empv-play url)
+                        (message "Radio '%s' has no stream URL" (alist-get 'name selected))))
+                  (funcall orig selected))))
+
+  (defun empv-subsonic-radios ()
+    "List Subsonic internet radio stations and play selection."
+    (interactive)
+    (empv--subsonic-request
+     "getInternetRadioStations.view"
+     (lambda (response)
+       ;; empv's result-fields doesn't include internetRadioStation,
+       ;; so we extract manually from the raw response info.
+       (let* ((raw (alist-get 'internetRadioStation response))
+              (stations (mapcar (lambda (s)
+                                 (thread-first s
+                                   (map-insert 'kind 'radio)
+                                   (map-insert 'type 'subsonic)))
+                               raw)))
+         (if stations
+             (empv--subsonic-act-on-candidate
+              (empv--completing-read-object
+               "Select radio:"
+               stations
+               :formatter #'empv--subsonic-format-candidate
+               :category 'empv-subsonic-item))
+           (message "No internet radio stations found")))))))
 
 (use-package ready-player
   :init