emms-help
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH] Fix handling of EMMS consume & random modes


From: Ian Eure
Subject: [PATCH] Fix handling of EMMS consume & random modes
Date: Fri, 24 Mar 2023 13:53:43 -0700
User-agent: mu4e 1.4.15; emacs 28.2

There was a bug report many years ago about the MPD player not advancing tracks when MPD’s consume mode was enabled. I also ran into this, and decided to fix it.

The problem is that `emms-player-mpd-detect-song-change-1` determines whether MPD has changed songs by comparing the playlist position of the last song it saw playing to the one that’s currently playing. When consume mode is on (and random is off), the positions are identical. The last seen song is removed from the playlist, and the next takes its place.

The crux of the fix is using MPD’s songid to detect changes. This is a unique, immutable value per playlist entry (even if those entries are the same file), and can be used to reliably detect track changes. Effecting this fix this properly, so EMMS works correctly for every combination of shuffle/consume required some surgery.

- `emms-player-mpd-current-song`: Removed, this is replaced with `emms-player-mpd-current-status`, which saves the complete output of the last `status` command. - `emms-player-mpd-get-current-songid`, `emms-player-mpd-get-current-consume`: helper functions added. - `emms-player-mpd-select-song`: Now takes an optional `consume` argument, which tells it to delete `prev-song` before moving to `new-song`; this also fudges the `new-song` position to account for the removed track.
- `emms-player-mod-detect-song-change-1`:
 - `cond` form broken up for clarity.
- Computes & provides `consume` arg to `emms-player-mpd-select-song` - Minor cleanups: indentation of changed functions has been corrected; Series of `setq`s have been combined into a single one.

commit da054ea31964b1c6c36acf4e741f7b6ead99ee44
Author: Ian Eure <ian@retrospec.tv>
Date:   Fri Mar 24 13:26:01 2023 -0700

    Fix consume & random modes in MPD player.

diff --git a/emms-player-mpd.el b/emms-player-mpd.el
index e249684..fb3915d 100644
--- a/emms-player-mpd.el
+++ b/emms-player-mpd.el
@@ -267,7 +267,7 @@ If your EMMS playlist contains stored playlists, set this 
to nil."
 (defvar emms-player-mpd-playlist-id nil)
 (make-variable-buffer-local 'emms-player-mpd-playlist-id)
 
-(defvar emms-player-mpd-current-song nil)
+(defvar emms-player-mpd-current-status nil)
 (defvar emms-player-mpd-last-state nil)
 (defvar emms-player-mpd-status-timer nil)
 
@@ -518,6 +518,30 @@ info from MusicPD."
     (setq callback (lambda (closure id) (ignore closure) id)))
   (emms-player-mpd-get-status-part closure callback "song" info))
 
+(defun emms-player-mpd-get-current-songid (closure callback &optional info)
+  "Get the current songid from MusicPD.
+This is in the form of a number, which is an immutable, unique
+identifier per track in a 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."
+  (when info
+    (setq callback (lambda (closure id) (ignore closure) id)))
+  (emms-player-mpd-get-status-part closure callback "songid" info))
+
+(defun emms-player-mpd-get-current-consume (closure callback &optional info)
+  "Get the current consume mode from MusicPD.
+When consume mode is enabled, MPD deletes tracks after they've been
+played, as it moves to the next track in the 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."
+  (when info
+    (setq callback (lambda (closure id) (ignore closure) id)))
+  (emms-player-mpd-get-status-part closure callback "consume" 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\".
@@ -555,43 +579,48 @@ info from MusicPD."
                     (string-to-number (match-string 1 time)))))
      "time" info)))
 
-(defun emms-player-mpd-select-song (prev-song new-song)
+(defun emms-player-mpd-select-song (prev-song new-song &optional consume)
   "Move to the given song position.
 
 The amount to move is the number difference between PREV-SONG and
 NEW-SONG.  NEW-SONG should be a string containing a number.
 PREV-SONG may be either a string containing a number or nil,
 which indicates that we should start from the beginning of the
-buffer and move to NEW-SONG."
+buffer and move to NEW-SONG. When CONSUME is non-nil, delete PREV-SONG
+from the playlist."
   (with-current-emms-playlist
     ;; move to current track
     (goto-char (if (and (stringp prev-song)
-                       emms-playlist-selected-marker
-                       (marker-position emms-playlist-selected-marker))
-                  emms-playlist-selected-marker
-                (point-min)))
+                                   emms-playlist-selected-marker
+                                   (marker-position 
emms-playlist-selected-marker))
+                          emms-playlist-selected-marker
+                        (point-min)))
     ;; seek forward or backward
     (let ((diff (if (stringp prev-song)
-                   (- (string-to-number new-song)
-                      (string-to-number prev-song))
-                 (string-to-number new-song))))
+                           (- (string-to-number new-song)
+                              (string-to-number prev-song))
+                         (string-to-number new-song))))
+      (when consume
+        (emms-playlist-mode-kill-track)
+        (when (> diff 0)
+          (setq diff (1- diff))))
       (condition-case nil
-         (progn
-           ;; skip to first track if not on one
-           (when (and (> diff 0)
-                      (not (emms-playlist-track-at (point))))
-             (emms-playlist-next))
-           ;; move to new track
-           (while (> diff 0)
-             (emms-playlist-next)
-             (setq diff (- diff 1)))
-           (while (< diff 0)
-             (emms-playlist-previous)
-             (setq diff (+ diff 1)))
-           ;; select track at point
-           (unless (emms-playlist-selected-track-at-p)
-             (emms-playlist-select (point))))
-       (error (concat "Could not move to position " new-song))))))
+             (progn
+               ;; skip to first track if not on one
+               (when (and (> diff 0)
+                              (not (emms-playlist-track-at (point))))
+                 (emms-playlist-next))
+
+               ;; move to new track
+               (while (> diff 0)
+                 (emms-playlist-next)
+                 (setq diff (- diff 1)))
+               (while (< diff 0)
+                 (emms-playlist-previous)
+                 (setq diff (+ diff 1)))
+               ;; select track at point
+               (emms-playlist-select (point)))
+           (error (concat "Could not move to position " new-song))))))
 
 (defun emms-player-mpd-sync-from-emms-1 (closure)
   (emms-player-mpd-get-playlist-id
@@ -666,58 +695,70 @@ main EMMS playlist buffer."
 
 (defun emms-player-mpd-detect-song-change-2 (state info)
   "Perform post-sync tasks after returning from a stop."
-  (setq emms-player-mpd-current-song nil)
-  (setq emms-player-playing-p 'emms-player-mpd)
-  (when (string= state "pause")
-    (setq emms-player-paused-p t))
+  (setq emms-player-mpd-current-status nil
+        emms-player-playing-p 'emms-player-mpd
+        emms-player-paused-p (string= state "pause"))
   (emms-player-mpd-detect-song-change info))
 
 (defun emms-player-mpd-detect-song-change-1 (closure info)
   (ignore closure)
-  (let ((song (emms-player-mpd-get-current-song nil #'ignore info))
-       (state (emms-player-mpd-get-mpd-state nil #'ignore info))
-       (time (emms-player-mpd-get-playing-time nil #'ignore info))
-       (err-msg (cdr (assoc "error" info))))
+  (let ((last-id (emms-player-mpd-get-current-songid nil #'ignore 
emms-player-mpd-current-status))
+        (current-id (emms-player-mpd-get-current-songid nil #'ignore info))
+        (last-pos (emms-player-mpd-get-current-song nil #'ignore 
emms-player-mpd-current-status))
+        (current-pos (emms-player-mpd-get-current-song nil #'ignore info))
+           (state (emms-player-mpd-get-mpd-state nil #'ignore info))
+           (time (emms-player-mpd-get-playing-time nil #'ignore info))
+           (err-msg (cdr (assoc "error" info))))
     (if (stringp err-msg)
-       (progn
-         (message "MusicPD error: %s" err-msg)
-         (emms-player-mpd-send
-          "clearerror"
-          nil #'ignore))
-      (cond ((string= state "stop")
-            (if song
-                ;; a track remains: the user probably stopped MusicPD
-                ;; manually, so we'll stop EMMS completely
-                (let ((emms-player-stopped-p t))
-                  (setq emms-player-mpd-last-state "stop")
-                  (emms-player-stopped))
-              ;; no more tracks are left: we probably ran out of things
-              ;; to play, so let EMMS do something further if it wants
-              (unless (string= emms-player-mpd-last-state "stop")
-                (setq emms-player-mpd-last-state "stop")
-                (emms-player-stopped))))
-           ((and emms-player-mpd-last-state
-                 (string= emms-player-mpd-last-state "stop"))
-            ;; resume from a stop that occurred outside of EMMS
-            (setq emms-player-mpd-last-state nil)
-            (emms-player-mpd-sync-from-mpd
-             state
-             #'emms-player-mpd-detect-song-change-2))
-           ((string= state "pause")
-            nil)
-           ((string= state "play")
-            (setq emms-player-mpd-last-state "play")
-            (unless (or (null song)
-                        (and (stringp emms-player-mpd-current-song)
-                             (string= song emms-player-mpd-current-song)))
-              (let ((emms-player-stopped-p t))
-                (emms-player-stopped))
-              (emms-player-mpd-select-song emms-player-mpd-current-song song)
-              (setq emms-player-mpd-current-song song)
-              (emms-player-started 'emms-player-mpd)
-              (when time
-                (run-hook-with-args 'emms-player-time-set-functions
-                                    time))))))))
+           (progn
+             (message "MusicPD error: %s" err-msg)
+             (emms-player-mpd-send
+              "clearerror"
+              nil #'ignore))
+
+      (cond
+       ((string= state "stop")
+           (if current-pos
+                   ;; a track remains: the user probably stopped MusicPD
+                   ;; manually, so we'll stop EMMS completely
+                   (let ((emms-player-stopped-p t))
+                     (setq emms-player-mpd-last-state "stop")
+                     (emms-player-stopped))
+             ;; no more tracks are left: we probably ran out of things
+             ;; to play, so let EMMS do something further if it wants
+             (unless (string= emms-player-mpd-last-state "stop")
+                   (setq emms-player-mpd-last-state "stop")
+                   (emms-player-stopped))))
+
+          ((and emms-player-mpd-last-state
+                    (string= emms-player-mpd-last-state "stop"))
+           ;; resume from a stop that occurred outside of EMMS
+           (setq emms-player-mpd-last-state nil)
+           (emms-player-mpd-sync-from-mpd
+            state
+            #'emms-player-mpd-detect-song-change-2))
+
+          ((string= state "pause") nil)
+
+          ((string= state "play")
+           (setq emms-player-mpd-last-state "play")
+           (unless (or (null current-id)
+                               (and (stringp last-id)
+                                    (string= current-id last-id)))
+             (let ((emms-player-stopped-p t))
+                   (emms-player-stopped))
+             (emms-player-mpd-select-song
+           last-pos current-pos
+           (and emms-player-mpd-current-status
+                (string= "1" (emms-player-mpd-get-current-consume nil #'ignore 
emms-player-mpd-current-status))
+                (string= "play" (emms-player-mpd-get-mpd-state nil #'ignore 
emms-player-mpd-current-status))
+                (stringp current-id) (stringp last-id)
+                (not (string= current-id last-id))))
+             (emms-player-started 'emms-player-mpd)
+             (when time
+                   (run-hook-with-args 'emms-player-time-set-functions
+                                               time))))))
+    (setq emms-player-mpd-current-status info)))
 
 (defun emms-player-mpd-detect-song-change (&optional info)
   "Detect whether a song change has occurred.
@@ -870,7 +911,7 @@ playlist."
         nil
         (lambda (closure response)
           (ignore closure response)
-          (setq emms-player-mpd-current-song nil)
+          (setq emms-player-mpd-current-status nil)
           (if emms-player-mpd-check-interval
               (setq emms-player-mpd-status-timer
                     (run-at-time t emms-player-mpd-check-interval
@@ -934,7 +975,7 @@ This is called if `emms-player-mpd-sync-playlist' is 
non-nil."
 
 (defun emms-player-mpd-connect-1 (closure info)
   (ignore closure)
-  (setq emms-player-mpd-current-song nil)
+  (setq emms-player-mpd-current-status nil)
   (let* ((state (emms-player-mpd-get-mpd-state nil #'ignore info)))
     (unless (string= state "stop")
       (setq emms-player-playing-p 'emms-player-mpd))
@@ -982,9 +1023,9 @@ stopped.  This argument is meant to be used when calling 
this
 from other functions."
   (interactive)
   (emms-cancel-timer emms-player-mpd-status-timer)
-  (setq emms-player-mpd-status-timer nil)
-  (setq emms-player-mpd-current-song nil)
-  (setq emms-player-mpd-last-state nil)
+  (setq emms-player-mpd-status-timer nil
+        emms-player-mpd-current-status nil
+        emms-player-mpd-last-state nil)
   (emms-player-mpd-close-process)
   (unless no-stop
     (let ((emms-player-stopped-p t))

reply via email to

[Prev in Thread] Current Thread [Next in Thread]