branch: elpa/radio
commit 9136d5b64f535452cab765cb29106ce56b22924f
Author: Roi Martin <jroi.mar...@gmail.com>
Commit: Roi Martin <jroi.mar...@gmail.com>

    radio.el: initial commit
---
 radio.el | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)

diff --git a/radio.el b/radio.el
new file mode 100644
index 0000000000..b8be849b9e
--- /dev/null
+++ b/radio.el
@@ -0,0 +1,150 @@
+;;; radio.el --- listen to Internet radio -*- lexical-binding: t -*-
+
+;; Copyright (C) 2025 Roi Martin
+
+;; Author: Roi Martin <jroi.mar...@gmail.com>
+;; Maintainer: Roi Martin <jroi.mar...@gmail.com>
+;; URL: https://github.com/jroimartin/radio.el
+;; Version: 0.1.0
+;; Package-Requires: ((emacs "29.1"))
+;; Keywords: radio
+
+;; This file is NOT part of GNU Emacs.
+
+;; This program is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; radio.el is a GNU Emacs package that enables users to listen to
+;; Internet radio stations.
+
+;;; Code:
+
+(defcustom radio-stations-alist nil
+  "List of radio stations.
+
+Elements are of the form (NAME . URL).
+
+NAME is the name of the radio station.
+URL  is the URL of the radio station."
+  :type '(alist :key-type string :value-type string)
+  :group 'radio)
+
+(defcustom radio-command "mpv --terminal=no --video=no"
+  "Command used to play a radio station."
+  :type 'string
+  :group 'radio)
+
+(defvar radio--current-station nil
+  "Radio station currently being played.")
+
+(defvar radio--current-proc nil
+  "Current media player process.")
+
+;; From tabulated-list.el
+(defvar tabulated-list-entries)
+(defvar tabulated-list-format)
+(defvar tabulated-list-sort-key)
+
+(defun radio--play (station)
+  "Play radio station.
+
+STATION must be a cons of the form (NAME . URL).  If a station is
+being played, it is stopped first."
+  (radio-stop)
+  (setq radio--current-station station)
+  (let* ((url (cdr station))
+        (cmd (split-string-shell-command radio-command))
+        (program (car cmd))
+        (program-args `(,@(cdr cmd) ,url))
+        (start-process-args `(,program nil ,program ,@program-args))
+        (proc (apply #'start-process start-process-args)))
+    (setq radio--current-proc proc)))
+
+;;;###autoload
+(defun radio-stop ()
+  "Stop playing current radio station.
+
+If no station is being played, calling this function has no
+effect."
+  (interactive)
+  (setq radio--current-station nil)
+  (when radio--current-proc
+    (delete-process radio--current-proc))
+  (setq radio--current-proc nil))
+
+(defun radio-list-stations--play ()
+  "Play the selected radio station and refresh the station list."
+  (interactive)
+  (when-let ((station (tabulated-list-get-id)))
+    (radio--play station)
+    (tabulated-list-revert)))
+
+(defun radio-list-stations--stop ()
+  "Stop playing current radio station and refresh the station list."
+  (interactive)
+  (radio-stop)
+  (tabulated-list-revert))
+
+(defun radio-list-stations--refresh ()
+  "Refresh the radio station list."
+  (setq tabulated-list-entries nil)
+  (dolist (station radio-stations-alist)
+    (let ((name (car station))
+         (url (cdr station))
+         (status (if (equal station radio--current-station) "▶" "")))
+      (push (list station (vector status name url))
+           tabulated-list-entries)))
+  (tabulated-list-init-header))
+
+(defvar-keymap radio-mode-map
+  :doc "Keymap used by `radio-mode'."
+  "RET" #'radio-list-stations--play
+  "s" #'radio-list-stations--stop)
+
+(define-derived-mode radio-mode tabulated-list-mode "Radio Stations"
+  "Major mode for listing radio stations."
+  (setq tabulated-list-format [("Status" 6 t)
+                              ("Station" 30 t)
+                              ("URL" 0 t)])
+  (setq tabulated-list-sort-key '("Station" . nil))
+  (add-hook 'tabulated-list-revert-hook #'radio-list-stations--refresh nil t))
+
+;;;###autoload
+(defun radio-list-stations ()
+  (interactive)
+  "Display a list of all radio stations."
+  (let ((buf (get-buffer-create "*Station List*")))
+    (with-current-buffer buf
+      (radio-mode)
+      (radio-list-stations--refresh)
+      (tabulated-list-print))
+    (pop-to-buffer-same-window buf)))
+
+;;;###autoload
+(defun radio (station-name)
+  "Play a radio station.
+
+When called from Lisp, STATION-NAME must be the name of one of
+the stations defined in `radio-stations-alist'."
+  (interactive (list (completing-read "Play radio station: "
+                                     (mapcar #'car radio-stations-alist)
+                                     nil t)))
+  (if-let ((station (assoc station-name radio-stations-alist)))
+      (radio--play station)
+    (message "Unknown station `%s'" station-name)))
+
+(provide 'radio)
+
+;;; radio.el ends here

Reply via email to