diff options
-rw-r--r-- | emms-player-mpd.el | 599 |
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 |