From 431595d1f1d8e789a3fdedc95223c9d45afbf114 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Tue, 1 Apr 2014 14:49:18 -0400 Subject: * lisp/emms-librefm-stream.el: Add file. --- lisp/emms-librefm-stream.el | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lisp/emms-librefm-stream.el (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el new file mode 100644 index 0000000..fc9f5e4 --- /dev/null +++ b/lisp/emms-librefm-stream.el @@ -0,0 +1,39 @@ +;;; emms-librefm-stream.el --- Libre.FM streaming + +;; Copyright (C) 2014 Free Software Foundation, Inc. + +;; Author: Yoni Rabkin + +;; Keywords: emms, libre.fm, GNU FM + +;; EMMS 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, or (at your option) +;; any later version. +;; +;; EMMS 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 EMMS; see the file COPYING. If not, write to the Free +;; Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +;; MA 02110-1301, USA. + + +;;; Code: + +(require 'emms-librefm-scrobbler) + +(defvar emms-librefm-stream-host-url + "alpha.libre.fm" + "URL for the streaming host") + + + + +(provide 'emms-librefm-stream) + + +;;; emms-librefm-stream.el ends here -- cgit v1.2.3 From 56c71749e76b3836a1106febc805bec7764cff79 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Wed, 2 Apr 2014 14:47:43 -0400 Subject: * lisp/emms-librefm-stream.el: Implement radio handshake call. --- lisp/emms-librefm-stream.el | 85 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el index fc9f5e4..2b232ba 100644 --- a/lisp/emms-librefm-stream.el +++ b/lisp/emms-librefm-stream.el @@ -30,10 +30,93 @@ "alpha.libre.fm" "URL for the streaming host") +(defvar emms-librefm-stream-debug + "" + "Temporary debug information.") +;;; ------------------------------------------------------------------ +;;; radio handshake +;;; ------------------------------------------------------------------ -(provide 'emms-librefm-stream) +;; http://alpha.libre.fm/radio/handshake.php?version=1.3.0.58&platform=linux&username=USERNAME&passwordmd5=PASSWORDMD5&language=en + +(defun emms-librefm-stream-tune-handshake-string () + (when (not emms-librefm-scrobbler-username) + (error "null username")) + (when (not emms-librefm-scrobbler-password) + (error "null password")) + (let ((url (concat "http://" + emms-librefm-stream-host-url + "/radio/handshake.php?" + "version=1.3.0.58" "&" + "platform=linux" "&" + "username=" (url-encode-url emms-librefm-scrobbler-username) "&" + "passwordmd5=" (md5 emms-librefm-scrobbler-password) "&" + "language=en"))) + url)) + +(defun emms-librefm-stream-tune-handshake-call () + "" + (let ((url-request-method "POST")) + (let ((response + (url-retrieve-synchronously + (emms-librefm-stream-tune-handshake-string)))) + (setq emms-librefm-stream-debug + (with-current-buffer response + (buffer-substring-no-properties (point-min) + (point-max)))) + response))) + + +;;; ------------------------------------------------------------------ +;;; tuning +;;; ------------------------------------------------------------------ +(defun emms-librefm-stream-tune-string (session-id station) + "" + (when (not session-id) + (error "null session id")) + (when (not station) + (error "null station")) + (let ((url (concat "http://" + emms-librefm-stream-host-url + "/radio/adjust.php?" + "session=" session-id "&" + "url=" (url-encode-url station)))) + url)) + +(defun emms-librefm-stream-tune-call (session-id station) + "" + (let ((url-request-method "POST")) + (let ((response + (url-retrieve-synchronously + (emms-librefm-stream-tune-string + session-id station)))) + (setq emms-librefm-stream-debug + (with-current-buffer response + (buffer-substring-no-properties (point-min) + (point-max)))) + response))) + +(defun emms-librefm-stream-handle-tune-response (resbuf) + "Handle the tune server response." + (when (not (bufferp resbuf)) + (error "response not a buffer")) + (with-current-buffer resbuf + (goto-char (point-min)) + (when (not (re-search-forward "^.*200 OK$" (point-at-eol) t)) + (error "bad HTTP server response")) + ;; go to the start of the FM response + (when (not (re-search-forward "\n\n" (point-max) t)) + (error "bad FM server response")) + (let ((status (buffer-substring (point-at-bol) + (point-at-eol)))) + (cond ((string= status "OK") 'ok) + ((string= status "BADSESSION") 'badsession) + (t (error "unhandled response status: [%s]" status)))))) + + +(provide 'emms-librefm-stream) ;;; emms-librefm-stream.el ends here -- cgit v1.2.3 From 9797d3ec430628a3ae3c56df40656af38da3af58 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Thu, 3 Apr 2014 14:45:32 -0400 Subject: * lisp/emms-librefm-stream.el: Implement getting a playlist. --- lisp/emms-librefm-stream.el | 195 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 180 insertions(+), 15 deletions(-) (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el index 2b232ba..20b5570 100644 --- a/lisp/emms-librefm-stream.el +++ b/lisp/emms-librefm-stream.el @@ -24,24 +24,53 @@ ;;; Code: +(require 'xml) (require 'emms-librefm-scrobbler) (defvar emms-librefm-stream-host-url "alpha.libre.fm" "URL for the streaming host") +(defvar emms-librefm-stream-host-base-path + "" + "URL for the streaming host base path") + +(defvar emms-librefm-stream-session-id + "" + "Session ID for radio.") + (defvar emms-librefm-stream-debug "" "Temporary debug information.") +(defvar emms-librefm-stream-station-name + "" + "Last station name tuned to.") + ;;; ------------------------------------------------------------------ -;;; radio handshake +;;; HTTP ;;; ------------------------------------------------------------------ -;; http://alpha.libre.fm/radio/handshake.php?version=1.3.0.58&platform=linux&username=USERNAME&passwordmd5=PASSWORDMD5&language=en +(defun emms-librefm-stream-assert-http () + "Assert a sane HTTP response from the server. + +This function must be called inside the response buffer. Leaves +point after the HTTP headers." + (goto-char (point-min)) + (when (not (re-search-forward "^.*200 OK$" (point-at-eol) t)) + (error "bad HTTP server response")) + ;; go to the start of the FM response + (when (not (re-search-forward "\n\n" (point-max) t)) + (error "bad FM server response"))) + + +;;; ------------------------------------------------------------------ +;;; radio handshake +;;; ------------------------------------------------------------------ (defun emms-librefm-stream-tune-handshake-string () + "Create the tune handshake string." (when (not emms-librefm-scrobbler-username) (error "null username")) (when (not emms-librefm-scrobbler-password) @@ -57,7 +86,7 @@ url)) (defun emms-librefm-stream-tune-handshake-call () - "" + "Make the tune handshake call." (let ((url-request-method "POST")) (let ((response (url-retrieve-synchronously @@ -68,26 +97,63 @@ (point-max)))) response))) +(defun emms-librefm-stream-handle-tune-handshake-response (resbuf) + "Handle the tune handshake server response." + (when (not (bufferp resbuf)) + (error "response not a buffer")) + (with-current-buffer resbuf + (emms-librefm-stream-assert-http) + (let (radio-session-id + base-url + base-path + (start (point))) + + (if (re-search-forward "^session=\\(.*\\)$" (point-max) t) + (setq radio-session-id (match-string-no-properties 1)) + (error "no radio session ID from server")) + + (goto-char start) + (if (re-search-forward "^base_url=\\(.*\\)$" (point-max) t) + (setq base-url (match-string-no-properties 1)) + (error "no base url from server")) + + (goto-char start) + (if (re-search-forward "^base_path=\\(.*\\)$" (point-max) t) + (setq base-path (match-string-no-properties 1)) + (error "no base path from server")) + + (setq emms-librefm-stream-session-id radio-session-id + emms-librefm-stream-host-url base-url + emms-librefm-stream-host-base-path base-path)) + + (message "radio handshake successful"))) + +(defun emms-librefm-stream-tune-handshake () + "Make and handle the tune handshake." + (emms-librefm-stream-handle-tune-handshake-response + (emms-librefm-stream-tune-handshake-call))) + ;;; ------------------------------------------------------------------ ;;; tuning ;;; ------------------------------------------------------------------ (defun emms-librefm-stream-tune-string (session-id station) - "" + "Create the tune string." (when (not session-id) (error "null session id")) (when (not station) (error "null station")) (let ((url (concat "http://" emms-librefm-stream-host-url - "/radio/adjust.php?" + emms-librefm-stream-host-base-path + "/adjust.php?" "session=" session-id "&" "url=" (url-encode-url station)))) url)) (defun emms-librefm-stream-tune-call (session-id station) - "" + "Make the tune call." (let ((url-request-method "POST")) (let ((response (url-retrieve-synchronously @@ -104,17 +170,116 @@ (when (not (bufferp resbuf)) (error "response not a buffer")) (with-current-buffer resbuf - (goto-char (point-min)) - (when (not (re-search-forward "^.*200 OK$" (point-at-eol) t)) - (error "bad HTTP server response")) - ;; go to the start of the FM response - (when (not (re-search-forward "\n\n" (point-max) t)) - (error "bad FM server response")) + (emms-librefm-stream-assert-http) (let ((status (buffer-substring (point-at-bol) (point-at-eol)))) - (cond ((string= status "OK") 'ok) - ((string= status "BADSESSION") 'badsession) - (t (error "unhandled response status: [%s]" status)))))) + (let (response + url + stationname + (start (point))) + + (if (re-search-forward "^response=\\(.*\\)$" (point-max) t) + (setq response (match-string-no-properties 1)) + (error "no response status code")) + (when (not (string= response "OK")) + (error "tune response not OK")) + + (goto-char start) + (if (re-search-forward "^url=\\(.*\\)$" (point-max) t) + (setq url (match-string-no-properties 1)) + (error "no url from server")) + + (goto-char start) + (if (re-search-forward "^stationname=\\(.*\\)$" (point-max) t) + (setq stationname (match-string-no-properties 1)) + (error "no stationname from server")) + + (setq emms-librefm-stream-station-name stationname) + + (message "successfully tuned to: %s" stationname) + + (emms-librefm-stream-getplaylist))))) + +(defun emms-librefm-stream-tune (station) + "Make and handle tune call." + (emms-librefm-stream-handle-tune-response + (emms-librefm-stream-tune-call + emms-librefm-stream-session-id + station))) + + +;;; ------------------------------------------------------------------ +;;; radio.getPlaylist +;;; ------------------------------------------------------------------ + +(defun emms-librefm-stream-getplaylist-string (radio-session-id) + "Create the getplaylist string." + (when (not radio-session-id) + (error "null radio session id")) + (let ((url (concat "http://" + emms-librefm-stream-host-url + emms-librefm-stream-host-base-path + "/xspf.php?" + "sk=" radio-session-id "&" + "discovery=0" "&" + "desktop=1.3.0.58"))) + url)) + +(defun emms-librefm-stream-getplaylist-call (session-id) + "Make the getplaylist call." + (let ((url-request-method "POST")) + (let ((response + (url-retrieve-synchronously + (emms-librefm-stream-getplaylist-string session-id)))) + (setq emms-librefm-stream-debug + (with-current-buffer response + (buffer-substring-no-properties (point-min) + (point-max)))) + response))) + +(defun emms-librefm-stream-handle-getplaylist-response (resbuf) + "Handle the getplaylist server response." + (when (not (bufferp resbuf)) + (error "response not a buffer")) + (with-current-buffer resbuf + (emms-librefm-stream-assert-http) + (xml-parse-region (point) (point-max)))) + +(defun emms-librefm-stream-getplaylist () + "Make and handle radio.getPlaylist." + (emms-librefm-stream-handle-getplaylist-response + (emms-librefm-stream-getplaylist-call + emms-librefm-stream-session-id))) + + +;;; ------------------------------------------------------------------ +;;; Parse XSPF +;;; ------------------------------------------------------------------ + +(defun emms-librefm-stream-xspf-tracklist (playlist) + "Return the tracklist portion of PLAYLIST or nil." + (let ((tree (copy-tree playlist)) + result) + (while (and tree (not result)) + (let ((this (car tree))) + (when (and (listp this) + (eq (car this) 'trackList)) + (setq result this))) + (setq tree (cdr tree))) + result)) + + +;;; ------------------------------------------------------------------ +;;; stream +;;; ------------------------------------------------------------------ + +(defun emms-librefm-stream (station) + "Stream STATION from a GNU FM server." + (interactive) + (when (not (stringp station)) + (error "bad argument")) + (emms-librefm-stream-tune-handshake) + (emms-librefm-stream-tune station)) (provide 'emms-librefm-stream) -- cgit v1.2.3 From d7572520abe1b95c25f9f2f6c1ab50ca927f3541 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Thu, 3 Apr 2014 20:27:42 -0400 Subject: * lisp/emms-librefm-stream.el: Convert playlist to Emms tracks. --- lisp/emms-librefm-stream.el | 74 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 62 insertions(+), 12 deletions(-) (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el index 20b5570..f3c6506 100644 --- a/lisp/emms-librefm-stream.el +++ b/lisp/emms-librefm-stream.el @@ -47,6 +47,10 @@ "" "Last station name tuned to.") +(defvar emms-librefm-stream-emms-tracklist + "" + "List of tracks for streaming.") + ;;; ------------------------------------------------------------------ ;;; HTTP @@ -107,7 +111,7 @@ point after the HTTP headers." base-url base-path (start (point))) - + (if (re-search-forward "^session=\\(.*\\)$" (point-max) t) (setq radio-session-id (match-string-no-properties 1)) (error "no radio session ID from server")) @@ -125,7 +129,7 @@ point after the HTTP headers." (setq emms-librefm-stream-session-id radio-session-id emms-librefm-stream-host-url base-url emms-librefm-stream-host-base-path base-path)) - + (message "radio handshake successful"))) (defun emms-librefm-stream-tune-handshake () @@ -177,7 +181,7 @@ point after the HTTP headers." url stationname (start (point))) - + (if (re-search-forward "^response=\\(.*\\)$" (point-max) t) (setq response (match-string-no-properties 1)) (error "no response status code")) @@ -195,10 +199,8 @@ point after the HTTP headers." (error "no stationname from server")) (setq emms-librefm-stream-station-name stationname) - - (message "successfully tuned to: %s" stationname) - - (emms-librefm-stream-getplaylist))))) + + (message "successfully tuned to: %s" stationname))))) (defun emms-librefm-stream-tune (station) "Make and handle tune call." @@ -253,21 +255,61 @@ point after the HTTP headers." ;;; ------------------------------------------------------------------ -;;; Parse XSPF +;;; XSPF ;;; ------------------------------------------------------------------ -(defun emms-librefm-stream-xspf-tracklist (playlist) +(defun emms-librefm-stream-xspf-find (tag data) "Return the tracklist portion of PLAYLIST or nil." - (let ((tree (copy-tree playlist)) + (let ((tree (copy-tree data)) result) (while (and tree (not result)) (let ((this (car tree))) (when (and (listp this) - (eq (car this) 'trackList)) + (eq (car this) tag)) (setq result this))) (setq tree (cdr tree))) result)) +(defun emms-librefm-stream-xspf-tracklist (playlist) + "Return the tracklist portion of PLAYLIST or nil." + (emms-librefm-stream-xspf-find 'trackList (car playlist))) + +(defun emms-librefm-stream-xspf-get (tag track) + "Return the data associated with TAG in TRACK." + (nth 2 (emms-librefm-stream-xspf-find tag track))) + +(defun emms-librefm-stream-xspf-convert-track (track) + "Convert TRACK to an Emms track." + (let ((location (emms-librefm-stream-xspf-get 'location track)) + (title (emms-librefm-stream-xspf-get 'title track)) + (album (emms-librefm-stream-xspf-get 'album track)) + (creator (emms-librefm-stream-xspf-get 'creator track)) + (duration (emms-librefm-stream-xspf-get 'duration track)) + (image (emms-librefm-stream-xspf-get 'image track))) + (let ((emms-track (emms-dictionary '*track*))) + (emms-track-set emms-track 'name location) + (emms-track-set emms-track 'info-artist creator) + (emms-track-set emms-track 'info-title title) + (emms-track-set emms-track 'info-album album) + (emms-track-set emms-track 'info-playing-time + (/ (parse-integer duration) + 1000)) + (emms-track-set emms-track 'type 'gnufm-streaming) + emms-track))) + +(defun emms-librefm-stream-xspf-convert-tracklist (tracklist) + "Convert TRACKLIST to a list of Emms tracks." + (let (tracks) + (mapc + #'(lambda (e) + (when (and (listp e) + (eq 'track (car e))) + (setq tracks + (append tracks + `(,(emms-librefm-stream-xspf-convert-track e)))))) + tracklist) + tracks)) + ;;; ------------------------------------------------------------------ ;;; stream @@ -279,7 +321,15 @@ point after the HTTP headers." (when (not (stringp station)) (error "bad argument")) (emms-librefm-stream-tune-handshake) - (emms-librefm-stream-tune station)) + (emms-librefm-stream-tune station) + (let ((tracklist + (emms-librefm-stream-xspf-tracklist + (emms-librefm-stream-getplaylist)))) + (when (not tracklist) + (setq emms-librefm-stream-emms-tracklist nil) + (error "could not find tracklist")) + (setq emms-librefm-stream-emms-tracklist + (emms-librefm-stream-xspf-convert-tracklist tracklist)))) (provide 'emms-librefm-stream) -- cgit v1.2.3 From ac0ae7719268fc430dce8b395d131db055cd6435 Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Sun, 6 Apr 2014 23:26:04 -0400 Subject: Add continuous streaming. --- lisp/emms-librefm-stream.el | 69 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 9 deletions(-) (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el index f3c6506..b1e31f4 100644 --- a/lisp/emms-librefm-stream.el +++ b/lisp/emms-librefm-stream.el @@ -51,6 +51,13 @@ "" "List of tracks for streaming.") +(defvar emms-librefm-stream-playlist-buffer-name + "*Emms GNU FM*" + "Name for non-interactive Emms GNU FM buffer.") + +(defvar emms-librefm-stream-playlist-buffer nil + "Non-interactive Emms GNU FM buffer.") + ;;; ------------------------------------------------------------------ ;;; HTTP @@ -294,7 +301,7 @@ point after the HTTP headers." (emms-track-set emms-track 'info-playing-time (/ (parse-integer duration) 1000)) - (emms-track-set emms-track 'type 'gnufm-streaming) + (emms-track-set emms-track 'type 'url) emms-track))) (defun emms-librefm-stream-xspf-convert-tracklist (tracklist) @@ -315,13 +322,16 @@ point after the HTTP headers." ;;; stream ;;; ------------------------------------------------------------------ -(defun emms-librefm-stream (station) - "Stream STATION from a GNU FM server." - (interactive) - (when (not (stringp station)) - (error "bad argument")) - (emms-librefm-stream-tune-handshake) - (emms-librefm-stream-tune station) +(defun emms-librefm-stream-set-librefm-playlist-buffer () + "Setup the GNU FM buffer and make it `emms-playlist-buffer'." + (when (not (buffer-live-p emms-librefm-stream-playlist-buffer)) + (setq emms-librefm-stream-playlist-buffer + (emms-playlist-new + emms-librefm-stream-playlist-buffer-name))) + (setq emms-playlist-buffer emms-librefm-stream-playlist-buffer)) + +(defun emms-librefm-stream-queue () + "Queue streaming tracks." (let ((tracklist (emms-librefm-stream-xspf-tracklist (emms-librefm-stream-getplaylist)))) @@ -329,7 +339,48 @@ point after the HTTP headers." (setq emms-librefm-stream-emms-tracklist nil) (error "could not find tracklist")) (setq emms-librefm-stream-emms-tracklist - (emms-librefm-stream-xspf-convert-tracklist tracklist)))) + (emms-librefm-stream-xspf-convert-tracklist tracklist)) + + (emms-librefm-stream-set-librefm-playlist-buffer) + + (with-current-emms-playlist + (goto-char (point-max)) + (save-excursion + (mapc + #'(lambda (track) + (emms-playlist-insert-track track)) + emms-librefm-stream-emms-tracklist))))) + +(defun emms-librefm-stream-queue-loader () + "Queue more streaming music if needed." + (with-current-emms-playlist + (goto-char (if emms-playlist-mode-selected-overlay + (overlay-start emms-playlist-mode-selected-overlay) + (point-min))) + (when (and (eq (current-buffer) + emms-librefm-stream-playlist-buffer) + (not (next-single-property-change (point-at-eol) + 'emms-track))) + (emms-librefm-stream-queue)))) + +(defun emms-librefm-stream (station) + "Stream STATION from a GNU FM server." + (interactive) + (when (not (stringp station)) + (error "bad argument")) + + (add-hook 'emms-player-finished-hook + 'emms-librefm-stream-queue-loader) + + (emms-librefm-stream-tune-handshake) + (emms-librefm-stream-tune station) + + (message "tuned to %s, getting playlist..." + emms-librefm-stream-station-name) + + (emms-librefm-stream-queue) + (with-current-emms-playlist + (emms-playlist-mode-play-current-track))) (provide 'emms-librefm-stream) -- cgit v1.2.3 From 6c638958dd4a3b4c90b78359a6ac467c174fc4ed Mon Sep 17 00:00:00 2001 From: Yoni Rabkin Date: Sun, 6 Apr 2014 23:52:11 -0400 Subject: Solve dependencies and compiler issues. --- lisp/emms-librefm-scrobbler.el | 3 +++ lisp/emms-librefm-stream.el | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'lisp/emms-librefm-stream.el') diff --git a/lisp/emms-librefm-scrobbler.el b/lisp/emms-librefm-scrobbler.el index bf34790..4f83cdd 100644 --- a/lisp/emms-librefm-scrobbler.el +++ b/lisp/emms-librefm-scrobbler.el @@ -24,6 +24,9 @@ ;;; Code: +(require 'emms-playing-time) + + (defvar emms-librefm-scrobbler-handshake-url "turtle.libre.fm" "Endpoint for client handshake.") diff --git a/lisp/emms-librefm-stream.el b/lisp/emms-librefm-stream.el index b1e31f4..9c55e41 100644 --- a/lisp/emms-librefm-stream.el +++ b/lisp/emms-librefm-stream.el @@ -25,8 +25,10 @@ ;;; Code: (require 'xml) +(require 'emms-playlist-mode) (require 'emms-librefm-scrobbler) + (defvar emms-librefm-stream-host-url "alpha.libre.fm" "URL for the streaming host") @@ -299,7 +301,7 @@ point after the HTTP headers." (emms-track-set emms-track 'info-title title) (emms-track-set emms-track 'info-album album) (emms-track-set emms-track 'info-playing-time - (/ (parse-integer duration) + (/ (string-to-number duration) 1000)) (emms-track-set emms-track 'type 'url) emms-track))) @@ -365,7 +367,7 @@ point after the HTTP headers." (defun emms-librefm-stream (station) "Stream STATION from a GNU FM server." - (interactive) + (interactive "sEnter station URL: ") (when (not (stringp station)) (error "bad argument")) -- cgit v1.2.3