aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--emms-player-mpd.el599
1 files changed, 392 insertions, 207 deletions
diff --git a/emms-player-mpd.el b/emms-player-mpd.el
index 821b8ab..5a5998e 100644
--- a/emms-player-mpd.el
+++ b/emms-player-mpd.el
@@ -90,7 +90,7 @@
;; emms-playing-time, the displayed time will be accurate.
(require 'emms-player-simple)
-(require 'emms-source-playlist) ; for emms-source-file-parse-playlist
+(require 'emms-source-playlist) ; for emms-source-file-parse-playlist
(defun emms-player-mpd-get-supported-regexp ()
"Returns a regexp of file extensions that MusicPD supports,
@@ -203,11 +203,106 @@ If your EMMS playlist contains stored playlists, set this to nil."
'seek
'emms-player-mpd-seek)
+;;; Traffic Queue (with some improvements) based on tq.el
+
+(defun emms-player-mpd-tq-create (process)
+ "Create and return a transaction queue communicating with PROCESS.
+PROCESS should be a subprocess capable of sending and receiving
+streams of bytes. It may be a local process, or it may be connected
+to a tcp server on another machine."
+ (let ((tq (cons nil (cons process
+ (generate-new-buffer
+ (concat " emms-player-mpd-tq-temp-"
+ (process-name process)))))))
+ (set-process-filter process
+ `(lambda (proc string)
+ (emms-player-mpd-tq-filter ',tq string)))
+ tq))
+
+;; accessors
+(defun emms-player-mpd-tq-queue (tq)
+ (car tq))
+(defun emms-player-mpd-tq-process (tq)
+ (car (cdr tq)))
+(defun emms-player-mpd-tq-buffer (tq)
+ (cdr (cdr tq)))
+(defun emms-player-mpd-tq-queue-head-question (tq)
+ (car (car (emms-player-mpd-tq-queue tq))))
+(defun emms-player-mpd-tq-queue-head-regexp (tq)
+ (car (cdr (car (emms-player-mpd-tq-queue tq)))))
+(defun emms-player-mpd-tq-queue-head-closure (tq)
+ (car (cdr (cdr (car (emms-player-mpd-tq-queue tq))))))
+(defun emms-player-mpd-tq-queue-head-fn (tq)
+ (cdr (cdr (cdr (car (emms-player-mpd-tq-queue tq))))))
+
+(defun emms-player-mpd-tq-queue-empty (tq)
+ (not (emms-player-mpd-tq-queue tq)))
+
+(defun emms-player-mpd-tq-queue-add (tq question re closure fn)
+ (setcar tq (nconc (emms-player-mpd-tq-queue tq)
+ (cons (cons question (cons re (cons closure fn))) nil)))
+ 'ok)
+
+(defun emms-player-mpd-tq-queue-pop (tq)
+ (setcar tq (cdr (car tq)))
+ (let ((question (emms-player-mpd-tq-queue-head-question tq)))
+ (when question
+ (process-send-string (emms-player-mpd-tq-process tq) question)))
+ (null (car tq)))
+
+(defun emms-player-mpd-tq-enqueue (tq question regexp closure fn)
+ "Add a transaction to transaction queue TQ.
+This sends the string QUESTION to the process that TQ communicates with.
+When the corresponding answer comes back, we call FN
+with two arguments: CLOSURE, and the answer to the question.
+REGEXP is a regular expression to match the entire answer;
+that's how we tell where the answer ends."
+ (let ((sendp (not (emms-player-mpd-tq-queue-head-question tq))))
+ (emms-player-mpd-tq-queue-add tq question regexp closure fn)
+ (when sendp
+ (process-send-string (emms-player-mpd-tq-process tq) question))))
+
+(defun emms-player-mpd-tq-close (tq)
+ "Shut down transaction queue TQ, terminating the process."
+ (delete-process (emms-player-mpd-tq-process tq))
+ (kill-buffer (emms-player-mpd-tq-buffer tq)))
+
+(defun emms-player-mpd-tq-filter (tq string)
+ "Append STRING to the TQ's buffer; then process the new data."
+ (with-current-buffer (emms-player-mpd-tq-buffer tq)
+ (goto-char (point-max))
+ (insert string)
+ (emms-player-mpd-tq-process-buffer tq)))
+
+(defun emms-player-mpd-tq-process-buffer (tq)
+ "Check TQ's buffer for the regexp at the head of the queue."
+ (set-buffer (emms-player-mpd-tq-buffer tq))
+ (if (= 0 (buffer-size)) ()
+ (if (emms-player-mpd-tq-queue-empty tq)
+ (let ((buf (generate-new-buffer "*spurious*")))
+ (copy-to-buffer buf (point-min) (point-max))
+ (delete-region (point-min) (point))
+ (pop-to-buffer buf nil)
+ (error "Spurious communication from process %s, see buffer %s"
+ (process-name (emms-player-mpd-tq-process tq))
+ (buffer-name buf)))
+ (goto-char (point-min))
+ (if (re-search-forward (emms-player-mpd-tq-queue-head-regexp tq) nil t)
+ (let ((answer (buffer-substring (point-min) (point))))
+ (delete-region (point-min) (point))
+ (unwind-protect
+ (condition-case nil
+ (funcall (emms-player-mpd-tq-queue-head-fn tq)
+ (emms-player-mpd-tq-queue-head-closure tq)
+ answer)
+ (error nil))
+ (emms-player-mpd-tq-queue-pop tq))
+ (emms-player-mpd-tq-process-buffer tq))))))
+
;;; Dealing with the MusicPD network process
-(defvar emms-player-mpd-blocked nil)
(defvar emms-player-mpd-process nil)
-(defvar emms-player-mpd-returned-data nil)
+(defvar emms-player-mpd-queue nil)
(defvar emms-player-mpd-playlist-id nil)
(make-variable-buffer-local 'emms-player-mpd-playlist-id)
@@ -215,12 +310,19 @@ If your EMMS playlist contains stored playlists, set this to nil."
(defvar emms-player-mpd-current-song nil)
(defvar emms-player-mpd-status-timer nil)
+(defvar emms-player-mpd-status-regexp
+ "^\\(OK\\( MPD \\)?\\|ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)\\)\n+\\'"
+ "Regexp that matches the valid status strings that MusicPD can
+return at the end of a request.")
+
(defun emms-player-mpd-sentinel (proc str)
"The process sentinel for MusicPD."
(let ((status (process-status proc)))
(cond ((memq status '(exit signal closed))
(when emms-player-mpd-verbose
(message "Closed MusicPD process"))
+ (emms-player-mpd-tq-close emms-player-mpd-queue)
+ (setq emms-player-mpd-queue nil)
(setq emms-player-mpd-process nil))
((memq status '(run open))
(when emms-player-mpd-verbose
@@ -229,10 +331,6 @@ If your EMMS playlist contains stored playlists, set this to nil."
(when emms-player-mpd-verbose
(message "Other MusicPD status change: %s" status))))))
-(defun emms-player-mpd-filter (proc string)
- "The process filter for MusicPD."
- (setq emms-player-mpd-returned-data string))
-
(defun emms-player-mpd-ensure-process ()
"Make sure that a MusicPD process is currently active."
(unless (and emms-player-mpd-process
@@ -245,44 +343,21 @@ If your EMMS playlist contains stored playlists, set this to nil."
emms-player-mpd-server-port))
(set-process-sentinel emms-player-mpd-process
'emms-player-mpd-sentinel)
- (set-process-filter emms-player-mpd-process
- 'emms-player-mpd-filter)
+ (setq emms-player-mpd-queue
+ (emms-player-mpd-tq-create emms-player-mpd-process))
(if (fboundp 'set-process-query-on-exit-flag)
(set-process-query-on-exit-flag emms-player-mpd-process nil)
- (process-kill-without-query emms-player-mpd-process))
- ;; wait a bit for the process to finish starting, as it likes to
- ;; send us an "OK" message initially
- (accept-process-output emms-player-mpd-process 0 200)))
-
-(defun emms-player-mpd-block ()
- "Block input for MusicPD, waiting if currently blocked.
-The maximum amount is determined by `emms-player-mpd-timeout'."
- (with-timeout (emms-player-mpd-timeout)
- (while emms-player-mpd-blocked
- (sit-for 0.20)))
- (setq emms-player-mpd-blocked t))
-
-(defun emms-player-mpd-unblock ()
- "Unblock input for MusicPD."
- (setq emms-player-mpd-blocked nil))
-
-(defun emms-player-mpd-send (command)
- "Send the given COMMAND to the MusicPD server and await a response,
-which is returned."
+ (process-kill-without-query emms-player-mpd-process))))
+
+(defun emms-player-mpd-send (question closure fn)
+ "Send the given QUESTION to the MusicPD server.
+When a reply comes, call FN with CLOSURE and the result."
(emms-player-mpd-ensure-process)
- (unless (string= (substring command -1) "\n")
- (setq command (concat command "\n")))
- (let (response)
- (unwind-protect
- (progn
- (emms-player-mpd-block)
- (setq emms-player-mpd-returned-data nil)
- (process-send-string emms-player-mpd-process command)
- (accept-process-output emms-player-mpd-process
- emms-player-mpd-timeout))
- (setq response emms-player-mpd-returned-data)
- (emms-player-mpd-unblock))
- response))
+ (unless (string= (substring question -1) "\n")
+ (setq question (concat question "\n")))
+ (emms-player-mpd-tq-enqueue emms-player-mpd-queue question
+ emms-player-mpd-status-regexp
+ closure fn))
;;; Helper functions
@@ -300,9 +375,12 @@ Otherwise, it will be nil."
(car cruft)
(cadr cruft))))
(setcdr cruft nil)
- (when (string-match "^OK\\( MPD \\)?" (car data))
+ (when (and (stringp (car data))
+ (string-match "^OK\\( MPD \\)?" (car data)))
(setq data (cdr data)))
- (if (string-match "^ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)" status)
+ (if (and (stringp status)
+ (string-match "^ACK \\[\\([0-9]+\\)@[0-9]+\\] \\(.+\\)"
+ status))
(cons (cons (match-string 1 status)
(match-string 2 status))
data)
@@ -356,12 +434,9 @@ The list will be in reverse order."
(setq alists (cons alist alists)))
alists)))
-(defun emms-player-mpd-get-tracks ()
- "Get the current playlist from MusicPD in the form of a list of
-EMMS tracks."
+(defun emms-player-mpd-get-tracks-1 (closure response)
(let ((songs (emms-player-mpd-get-alists
- (emms-player-mpd-parse-response
- (emms-player-mpd-send "playlistinfo"))))
+ (emms-player-mpd-parse-response response)))
(tracks nil))
(when songs
(dolist (song-info songs)
@@ -369,100 +444,169 @@ EMMS tracks."
(when (setq cell (assoc "file" song-info))
(let ((track (emms-track 'file (cdr cell))))
(emms-info-mpd track song-info)
- (setq tracks (cons track tracks))))))
- tracks)))
+ (setq tracks (cons track tracks)))))))
+ (funcall (car closure) (cdr closure) tracks)))
-(defun emms-player-mpd-get-status ()
+(defun emms-player-mpd-get-tracks (closure callback)
+ "Get the current playlist from MusicPD in the form of a list of
+EMMS tracks.
+Call CALLBACK with CLOSURE and result when the request is complete."
+ (emms-player-mpd-send "playlistinfo" (cons callback closure)
+ #'emms-player-mpd-get-tracks-1))
+
+(defun emms-player-mpd-get-status-1 (closure response)
+ (funcall (car closure)
+ (cdr closure)
+ (emms-player-mpd-get-alist
+ (emms-player-mpd-parse-response response))))
+
+(defun emms-player-mpd-get-status (closure callback)
"Get status information from MusicPD.
-It will be returned in the form of an alist."
- (emms-player-mpd-get-alist
- (emms-player-mpd-parse-response
- (emms-player-mpd-send "status"))))
-
-(defun emms-player-mpd-get-playlist-id (&optional info)
+It will be returned in the form of an alist by calling CALLBACK
+with CLOSURE as its first argument, and the status as the
+second."
+ (emms-player-mpd-send "status" (cons callback closure)
+ #'emms-player-mpd-get-status-1))
+
+(defun emms-player-mpd-get-status-part (closure callback item &optional info)
+ "Get ITEM from the current MusicPD status.
+Call CALLBACK with CLOSURE and result when the request is complete.
+If INFO is specified, use that instead of acquiring the necessary
+info from MusicPD."
+ (if info
+ (funcall callback closure (cdr (assoc item info)))
+ (emms-player-mpd-get-status
+ (cons callback (cons closure item))
+ (lambda (closure info)
+ (let ((fn (car closure))
+ (close (cadr closure))
+ (item (cddr closure)))
+ (funcall fn close (cdr (assoc item info))))))))
+
+(defun emms-player-mpd-get-playlist-id (closure callback &optional info)
"Get the current playlist ID from MusicPD.
+Call CALLBACK with CLOSURE and result when the request is complete.
If INFO is specified, use that instead of acquiring the necessary
info from MusicPD."
- (unless info
- (setq info (emms-player-mpd-get-status)))
- (cdr (assoc "playlist" info)))
+ (when info
+ (setq callback (lambda (closure id) id)))
+ (emms-player-mpd-get-status-part closure callback "playlist" info))
-(defun emms-player-mpd-get-current-song (&optional info)
+(defun emms-player-mpd-get-current-song (closure callback &optional info)
"Get the current song from MusicPD.
This is in the form of a number that indicates the position of
the song on the current playlist.
+Call CALLBACK with CLOSURE and result when the request is complete.
If INFO is specified, use that instead of acquiring the necessary
info from MusicPD."
- (unless info
- (setq info (emms-player-mpd-get-status)))
- (cdr (assoc "song" info)))
+ (when info
+ (setq callback (lambda (closure id) id)))
+ (emms-player-mpd-get-status-part closure callback "song" info))
-(defun emms-player-mpd-get-state (&optional info)
+(defun emms-player-mpd-get-mpd-state (closure callback &optional info)
"Get the current state of the MusicPD server.
This is either \"play\", \"stop\", or \"pause\".
+Call CALLBACK with CLOSURE and result when the request is complete.
If INFO is specified, use that instead of acquiring the necessary
info from MusicPD."
- (unless info
- (setq info (emms-player-mpd-get-status)))
- (cdr (assoc "state" info)))
+ (when info
+ (setq callback (lambda (closure id) id)))
+ (emms-player-mpd-get-status-part closure callback "state" info))
-(defun emms-player-mpd-get-playing-time (&optional info)
+(defun emms-player-mpd-get-playing-time (closure callback &optional info)
"Get the number of seconds that the current song has been playing,
or nil if we cannot obtain this information.
+Call CALLBACK with CLOSURE and result when the request is complete.
If INFO is specified, use that instead of acquiring the necessary
info from MusicPD."
- (unless info
- (setq info (emms-player-mpd-get-status)))
- (let ((time (cdr (assoc "time" info))))
- (when (and time
- (string-match "\\`\\([0-9]+\\):" time))
- (string-to-number (match-string 1 time)))))
-
-(defun emms-player-mpd-sync-from-emms ()
+ (if info
+ (emms-player-mpd-get-status-part
+ nil
+ (lambda (closure time)
+ (and time
+ (string-match "\\`\\([0-9]+\\):" time)
+ (string-to-number (match-string 1 time))))
+ "time" info)
+ (emms-player-mpd-get-status-part
+ (cons callback closure)
+ (lambda (closure time)
+ (funcall (car closure)
+ (cdr closure)
+ (and time
+ (string-match "\\`\\([0-9]+\\):" time)
+ (string-to-number (match-string 1 time)))))
+ "time" info)))
+
+(defun emms-player-mpd-sync-from-emms-1 (closure id)
+ (let ((buffer (car closure))
+ (fn (cadr closure))
+ (close (cddr closure)))
+ (when (buffer-live-p buffer)
+ (with-current-buffer buffer
+ (setq emms-player-mpd-playlist-id id))
+ (when (functionp fn)
+ (funcall fn close)))))
+
+(defun emms-player-mpd-sync-from-emms (&optional closure callback)
"Synchronize the MusicPD playlist with the contents of the
-current EMMS playlist."
+current EMMS playlist.
+If CALLBACK is provided, call it with CLOSURE once we are done."
(emms-player-mpd-clear)
(with-current-emms-playlist
- (save-excursion
- (mapc #'emms-player-mpd-add
- (nreverse
- (emms-playlist-tracks-in-region (point-min) (point-max)))))
- (setq emms-player-mpd-playlist-id (emms-player-mpd-get-playlist-id))))
-
-(defun emms-player-mpd-sync-from-mpd ()
+ (save-excursion
+ (mapc #'emms-player-mpd-add
+ (nreverse
+ (emms-playlist-tracks-in-region (point-min) (point-max)))))
+ (emms-player-mpd-get-playlist-id
+ (cons (current-buffer) (cons callback closure))
+ #'emms-player-mpd-sync-from-emms-1)))
+
+(defun emms-player-mpd-sync-from-mpd-2 (closure info)
+ (let ((buffer (car closure))
+ (fn (cadr closure))
+ (close (cddr closure))
+ (id (emms-player-mpd-get-playlist-id nil #'ignore info))
+ (song (emms-player-mpd-get-current-song nil #'ignore info)))
+ (when (buffer-live-p buffer)
+ (let ((emms-playlist-buffer buffer))
+ (with-current-emms-playlist
+ (setq emms-player-mpd-playlist-id id)
+ (if song
+ (progn
+ (goto-line (1+ (string-to-number song)))
+ (emms-playlist-select (point)))
+ (goto-char (point-min)))))
+ (when (functionp fn)
+ (funcall fn close info)))))
+
+(defun emms-player-mpd-sync-from-mpd-1 (closure tracks)
+ (let ((buffer (car closure)))
+ (when (and tracks
+ (buffer-live-p buffer))
+ (let ((emms-playlist-buffer buffer))
+ (with-current-emms-playlist
+ (emms-playlist-clear)
+ (mapc #'emms-playlist-insert-track tracks)))
+ (emms-player-mpd-get-status closure
+ #'emms-player-mpd-sync-from-mpd-2))))
+
+(defun emms-player-mpd-sync-from-mpd (&optional closure callback)
"Synchronize the EMMS playlist with the contents of the current
MusicPD playlist."
(with-current-emms-playlist
- (emms-playlist-clear)
- (mapc #'emms-playlist-insert-track (emms-player-mpd-get-tracks))
- (let* ((info (emms-player-mpd-get-status))
- (id (emms-player-mpd-get-playlist-id info))
- (song (emms-player-mpd-get-current-song info)))
- (setq emms-player-mpd-playlist-id id)
- (if song
- (progn
- (goto-line (1+ (string-to-number song)))
- (emms-playlist-select (point)))
- (goto-char (point-min))))))
-
-(defun emms-player-mpd-detect-song-change (&optional info)
- "Detect whether a song change has occurred.
-This is usually called by a timer.
-
-If INFO is specified, use that instead of acquiring the necessary
-info from MusicPD."
- (unless info
- (setq info (emms-player-mpd-get-status)))
- (let ((song (emms-player-mpd-get-current-song info))
- (status (emms-player-mpd-get-state info))
- (time (emms-player-mpd-get-playing-time info)))
+ (emms-player-mpd-get-tracks
+ (cons emms-playlist-buffer (cons callback closure))
+ #'emms-player-mpd-sync-from-mpd-1)))
+
+(defun emms-player-mpd-detect-song-change-1 (closure info)
+ (let ((song (emms-player-mpd-get-current-song nil #'ignore info))
+ (status (emms-player-mpd-get-mpd-state nil #'ignore info))
+ (time (emms-player-mpd-get-playing-time nil #'ignore info)))
(cond ((string= status "stop")
- (emms-cancel-timer emms-player-mpd-status-timer)
- (setq emms-player-mpd-status-timer nil)
- (emms-player-stopped))
+ (emms-player-mpd-stop t))
((string= status "pause")
nil)
((string= status "play")
@@ -479,6 +623,16 @@ info from MusicPD."
(when time
(run-hook-with-args 'emms-player-seeked-functions time)))))))
+(defun emms-player-mpd-detect-song-change (&optional info)
+ "Detect whether a song change has occurred.
+This is usually called by a timer.
+
+If INFO is specified, use that instead of acquiring the necessary
+info from MusicPD."
+ (if info
+ (emms-player-mpd-detect-song-change-1 nil info)
+ (emms-player-mpd-get-status nil #'emms-player-mpd-detect-song-change-1)))
+
(defun emms-player-mpd-get-filename (file)
"Turn FILE into something that MusicPD can understand.
This usually means removing a prefix."
@@ -500,21 +654,22 @@ This usually means removing a prefix."
(defun emms-player-mpd-clear ()
"Clear the playlist."
- (emms-player-mpd-send "clear"))
+ (when emms-player-mpd-status-timer
+ (emms-cancel-timer emms-player-mpd-status-timer)
+ (setq emms-player-mpd-status-timer nil))
+ (emms-player-mpd-send "clear" nil #'ignore))
(defun emms-player-mpd-add-file (file)
"Add FILE to the current MusicPD playlist.
-If we succeed in adding the file, return non-nil, nil otherwise."
+If an error occurs, display a relevant message."
(setq file (emms-player-mpd-get-filename file))
- (let ((output (emms-player-mpd-parse-response
- (emms-player-mpd-send
- (concat "add " (emms-player-mpd-quote-file file))))))
- (if (car output)
- (progn
- (when emms-player-mpd-verbose
- (message "MusicPD error: %s: %s" file (cdar output)))
- nil)
- t)))
+ (emms-player-mpd-send
+ (concat "add " (emms-player-mpd-quote-file file))
+ file
+ (lambda (file response)
+ (let ((output (emms-player-mpd-parse-response response)))
+ (when (car output)
+ (message "MusicPD error: %s: %s" file (cdar output)))))))
(defun emms-player-mpd-add-playlist (playlist)
"Load contents of PLAYLIST into MusicPD by adding each line.
@@ -525,12 +680,9 @@ This handles both m3u and pls type playlists."
(goto-char (point-min))
(let ((format (emms-source-playlist-determine-format)))
(when format
- (let ((list (emms-source-playlist-files format))
- (any-success nil))
+ (let ((list (emms-source-playlist-files format)))
(dolist (file list)
- (when (emms-player-mpd-add-file file)
- (setq any-success t)))
- any-success)))))
+ (emms-player-mpd-add-file file)))))))
(defun emms-player-mpd-add (track)
"Add TRACK to the MusicPD playlist."
@@ -552,7 +704,7 @@ This handles both m3u and pls type playlists."
(string-match (emms-player-get emms-player-mpd 'regex)
(emms-track-name track))))
-(defun emms-player-mpd-play (&optional id)
+(defun emms-player-mpd-play (&optional id closure)
"Play whatever is in the current MusicPD playlist.
If ID is specified, play the song at that position in the MusicPD
playlist."
@@ -560,41 +712,49 @@ playlist."
(progn
(unless (stringp id)
(setq id (number-to-string id)))
- (emms-player-mpd-send (concat "play " id))
- (setq emms-player-mpd-current-song id))
- (emms-player-mpd-send "play")))
-
-(defun emms-player-mpd-start-and-sync (track)
- "Starts a process playing TRACK.
-This is called if `emms-player-mpd-sync-playlist' is non-nil.
-
-It ensures that MusicPD's playlist is up-to-date with EMMS's
-playlist, and then plays the current track."
- (let ((id (emms-player-mpd-get-playlist-id)))
- (unless (and (stringp emms-player-mpd-playlist-id)
- (string= emms-player-mpd-playlist-id id))
- (emms-player-mpd-sync-from-emms))
- (with-current-emms-playlist
- (emms-player-mpd-play (1- (line-number-at-pos
- emms-playlist-selected-marker)))))
- (when emms-player-mpd-status-timer
- (emms-cancel-timer emms-player-mpd-status-timer))
- (setq emms-player-mpd-status-timer
- (run-at-time t emms-player-mpd-check-interval
- 'emms-player-mpd-detect-song-change)))
-
-;;;###autoload
-(defun emms-player-mpd-connect ()
- "Connect to MusicPD and retrieve its current playlist.
-Afterward, the status of MusicPD will be tracked."
- (interactive)
+ (emms-player-mpd-send
+ (concat "play " id)
+ nil
+ (lambda (closure response)
+ (setq emms-player-mpd-current-song id)
+ (setq emms-player-mpd-status-timer
+ (run-at-time t emms-player-mpd-check-interval
+ 'emms-player-mpd-detect-song-change)))))
+ (emms-player-mpd-send
+ "play"
+ nil
+ (lambda (start-timer response)
+ (setq emms-player-mpd-status-timer
+ (run-at-time t emms-player-mpd-check-interval
+ 'emms-player-mpd-detect-song-change))))))
+
+(defun emms-player-mpd-start-and-sync-1 (buffer)
(when emms-player-mpd-status-timer
(emms-cancel-timer emms-player-mpd-status-timer)
(setq emms-player-mpd-status-timer nil))
- (emms-player-mpd-sync-from-mpd)
+ (let ((emms-playlist-buffer buffer))
+ (with-current-emms-playlist
+ (emms-player-mpd-play (1- (line-number-at-pos
+ emms-playlist-selected-marker))))))
+
+(defun emms-player-mpd-start-and-sync ()
+ "Ensure that MusicPD's playlist is up-to-date with EMMS's
+playlist, and then play the current track.
+
+This is called if `emms-player-mpd-sync-playlist' is non-nil."
+ (emms-player-mpd-get-playlist-id
+ nil
+ (lambda (closure id)
+ (if (and (stringp emms-player-mpd-playlist-id)
+ (string= emms-player-mpd-playlist-id id))
+ (emms-player-mpd-start-and-sync-1 emms-playlist-buffer)
+ (emms-player-mpd-sync-from-emms
+ emms-playlist-buffer
+ #'emms-player-mpd-start-and-sync-1)))))
+
+(defun emms-player-mpd-connect-1 (closure info)
(setq emms-player-mpd-current-song nil)
- (let* ((info (emms-player-mpd-get-status))
- (state (emms-player-mpd-get-state info)))
+ (let* ((state (emms-player-mpd-get-mpd-state nil #'ignore info)))
(unless (string= state "stop")
(setq emms-player-playing-p 'emms-player-mpd))
(when (string= state "pause")
@@ -605,57 +765,96 @@ Afterward, the status of MusicPD will be tracked."
(run-at-time t emms-player-mpd-check-interval
'emms-player-mpd-detect-song-change)))))
+;;;###autoload
+(defun emms-player-mpd-connect ()
+ "Connect to MusicPD and retrieve its current playlist.
+Afterward, the status of MusicPD will be tracked."
+ (interactive)
+ (when emms-player-mpd-status-timer
+ (emms-cancel-timer emms-player-mpd-status-timer)
+ (setq emms-player-mpd-status-timer nil))
+ (emms-player-mpd-sync-from-mpd
+ nil #'emms-player-mpd-connect-1))
+
(defun emms-player-mpd-start (track)
"Starts a process playing TRACK."
(interactive)
(if emms-player-mpd-sync-playlist
- (emms-player-mpd-start-and-sync track)
+ (emms-player-mpd-start-and-sync)
(emms-player-mpd-clear)
(when (emms-player-mpd-add track)
;; if we have loaded the item successfully, play it
(emms-player-mpd-play))))
-(defun emms-player-mpd-stop ()
- "Stop the currently playing song."
+(defun emms-player-mpd-stop (&optional no-send)
+ "Stop the currently playing song.
+If NO-SEND is non-nil, do not send a stop command to MusicPD,
+just terminate the timer and mark the player as stopped."
(interactive)
(emms-cancel-timer emms-player-mpd-status-timer)
(setq emms-player-mpd-status-timer nil)
- (setq emms-player-stopped-p t)
- (condition-case nil
- (emms-player-mpd-send "stop")
- (error nil))
- (emms-player-stopped))
+ (let ((emms-player-stopped-p t))
+ (unless no-send
+ (condition-case nil
+ (emms-player-mpd-send "stop" nil #'ignore)
+ (error nil)))
+ (emms-player-stopped)))
(defun emms-player-mpd-pause ()
"Pause the currently playing song."
(interactive)
- (emms-player-mpd-send "pause"))
+ (emms-player-mpd-send "pause" nil #'ignore))
(defun emms-player-mpd-seek (sec)
"Seek backward or forward by SEC seconds, depending on sign of SEC."
(interactive)
(emms-player-mpd-send (format "seek %s%d"
(if (> sec 0) "+" "")
- sec)))
+ sec)
+ nil #'ignore))
(defun emms-player-mpd-next ()
"Move forward by one track in MusicPD's internal playlist."
(interactive)
- (emms-player-mpd-send "next"))
+ (emms-player-mpd-send "next" nil #'ignore))
(defun emms-player-mpd-previous ()
"Move backward by one track in MusicPD's internal playlist."
(interactive)
- (emms-player-mpd-send "previous"))
+ (emms-player-mpd-send "previous" nil #'ignore))
;;; Track info
+(defun emms-info-mpd-process (track info)
+ (dolist (data info)
+ (let ((name (car data))
+ (value (cdr data)))
+ (setq name (cond ((string= name "artist") 'info-artist)
+ ((string= name "title") 'info-title)
+ ((string= name "album") 'info-album)
+ ((string= name "track") 'info-tracknumber)
+ ((string= name "date") 'info-year)
+ ((string= name "genre") 'info-genre)
+ ((string= name "time")
+ (setq value (string-to-number value))
+ 'info-playing-time)
+ (t nil)))
+ (when name
+ (emms-track-set track name value)))))
+
+(defun emms-info-mpd-1 (track response)
+ (let ((info (emms-player-mpd-get-alist
+ (emms-player-mpd-parse-response response))))
+ (when info
+ (emms-info-mpd-process track info))))
+
(defun emms-info-mpd (track &optional info)
"Add track information to TRACK.
This is a useful addition to `emms-info-functions'.
If INFO is specified, use that instead of acquiring the necessary
info from MusicPD."
- (unless info
+ (if info
+ (emms-info-mpd-process track info)
(let (file)
(when (and (eq 'file (emms-track-type track))
emms-player-mpd-music-directory
@@ -663,41 +862,17 @@ info from MusicPD."
(emms-track-name track)))
(string-match emms-player-mpd-supported-regexp file)
(not (string-match "\\`http://" file)))
- (setq info (condition-case nil
- (emms-player-mpd-get-alist
- (emms-player-mpd-parse-response
- (emms-player-mpd-send
- (concat "find filename "
- (emms-player-mpd-quote-file file)))))
- (error nil))))))
- (when info
- (dolist (data info)
- (let ((name (car data))
- (value (cdr data)))
- (setq name (cond ((string= name "artist") 'info-artist)
- ((string= name "title") 'info-title)
- ((string= name "album") 'info-album)
- ((string= name "track") 'info-tracknumber)
- ((string= name "date") 'info-year)
- ((string= name "genre") 'info-genre)
- ((string= name "time")
- (setq value (string-to-number value))
- 'info-playing-time)
- (t nil)))
- (when name
- (emms-track-set track name value))))))
-
-;;;###autoload
-(defun emms-player-mpd-show (&optional insertp)
- "Describe the current EMMS track in the minibuffer.
-If INSERTP is non-nil, insert the description into the current buffer instead.
-This function uses `emms-show-format' to format the current track.
-It differs from `emms-show' in that it asks MusicPD for the current track,
-rather than EMMS."
- (interactive "P")
+ (condition-case nil
+ (emms-player-mpd-send
+ (concat "find filename "
+ (emms-player-mpd-quote-file file))
+ track
+ #'emms-info-mpd-1)
+ (error nil))))))
+
+(defun emms-player-mpd-show-1 (insertp response)
(let* ((info (emms-player-mpd-get-alist
- (emms-player-mpd-parse-response
- (emms-player-mpd-send "currentsong"))))
+ (emms-player-mpd-parse-response response)))
(track (emms-dictionary '*track*))
(desc nil)
string)
@@ -713,6 +888,16 @@ rather than EMMS."
(insert string)
(message "%s" string))))
+;;;###autoload
+(defun emms-player-mpd-show (&optional insertp)
+ "Describe the current EMMS track in the minibuffer.
+If INSERTP is non-nil, insert the description into the current buffer instead.
+This function uses `emms-show-format' to format the current track.
+It differs from `emms-show' in that it asks MusicPD for the current track,
+rather than EMMS."
+ (interactive "P")
+ (emms-player-mpd-send "currentsong" insertp #'emms-player-mpd-show-1))
+
(provide 'emms-player-mpd)
;;; emms-player-mpd.el ends here