branch: externals/listen
commit d13622a95fd142c8b626ecd956d1d3006f181a07
Merge: 9d8825a2ff 95eae0c0ec
Author: Adam Porter <[email protected]>
Commit: Adam Porter <[email protected]>

    Merge: Asynchronous MPV support
---
 README.org      |   2 +-
 docs/README.org |   2 +-
 listen-lib.el   |  72 +++++++++++++-
 listen-mpv.el   | 286 ++++++++++++++++++++++++++++++++++++++------------------
 listen-vlc.el   |  22 +++--
 listen.el       |  29 +++---
 6 files changed, 296 insertions(+), 117 deletions(-)

diff --git a/README.org b/README.org
index bf059c81f1..0cb163af0d 100644
--- a/README.org
+++ b/README.org
@@ -201,7 +201,7 @@ The ~listen-mode~ minor mode runs a timer which plays the 
next track in the curr
 ** v0.10-pre
 
 *Additions*
-- [[https://mpv.io/][MPV]] support.
+- [[https://mpv.io/][MPV]] support (works asynchronously, to improve 
performance and avoid blocking Emacs).
 - Command ~listen-queue-add-tracks~, when used in a Dired buffer, uses the 
marked files or the one at point.
 - Option ~listen-backend~, which sets the backend to use: MPV or VLC.  (The 
default is to auto-detect which is available at load time, with MPV being 
preferred due to more robust IPC support.)
 - Faces for parts of mode line lighter.
diff --git a/docs/README.org b/docs/README.org
index 5edf3e3be6..0d4809599a 100644
--- a/docs/README.org
+++ b/docs/README.org
@@ -238,7 +238,7 @@ The ~listen-mode~ minor mode runs a timer which plays the 
next track in the curr
 ** v0.10-pre
 
 *Additions*
-+ [[https://mpv.io/][MPV]] support.
++ [[https://mpv.io/][MPV]] support (works asynchronously, to improve 
performance and avoid blocking Emacs).
 + Command ~listen-queue-add-tracks~, when used in a Dired buffer, uses the 
marked files or the one at point.
 + Option ~listen-backend~, which sets the backend to use: MPV or VLC.  (The 
default is to auto-detect which is available at load time, with MPV being 
preferred due to more robust IPC support.)
 + Faces for parts of mode line lighter.
diff --git a/listen-lib.el b/listen-lib.el
index 852b5beed6..73823f2db5 100644
--- a/listen-lib.el
+++ b/listen-lib.el
@@ -27,6 +27,65 @@
 
 ;;;; Macros
 
+(cl-defmacro listen-debug (&rest args)
+  "Display a debug warning showing the runtime value of ARGS.
+The warning automatically includes the name of the containing
+function, and it is only displayed if `warning-minimum-log-level'
+is `:debug' at expansion time (otherwise the macro expands to a
+call to `ignore' with ARGS and is eliminated by the
+byte-compiler).  When debugging, the form also returns nil so,
+e.g. it may be used in a conditional in place of nil.
+
+Each of ARGS may be a string, which is displayed as-is, or a
+symbol, the value of which is displayed prefixed by its name, or
+a Lisp form, which is displayed prefixed by its first symbol.
+
+Before the actual ARGS arguments, you can write keyword
+arguments, i.e. alternating keywords and values.  The following
+keywords are supported:
+
+  :buffer BUFFER   Name of buffer to pass to `display-warning'.
+  :level  LEVEL    Level passed to `display-warning', which see.
+                   Default is :debug."
+  ;; TODO: Can we use a compiler macro to handle this more elegantly?
+  (pcase-let* ((fn-name (when byte-compile-current-buffer
+                          (with-current-buffer byte-compile-current-buffer
+                            ;; This is a hack, but a nifty one.
+                            (save-excursion
+                              (beginning-of-defun)
+                              (cl-second (read (current-buffer)))))))
+               (plist-args (cl-loop while (keywordp (car args))
+                                    collect (pop args)
+                                    collect (pop args)))
+               ((map (:buffer buffer) (:level level)) plist-args)
+               (level (or level :debug))
+               (string (cl-loop for arg in args
+                                concat (pcase arg
+                                         ((pred stringp) "%S ")
+                                         ((pred symbolp)
+                                          (concat (upcase (symbol-name arg)) 
":%S "))
+                                         ((pred listp)
+                                          (concat "(" (upcase (symbol-name 
(car arg)))
+                                                  (pcase (length arg)
+                                                    (1 ")")
+                                                    (_ "...)"))
+                                                  ":%S "))))))
+    (if (eq :debug warning-minimum-log-level)
+        `(let ((fn-name ,(if fn-name
+                             `',fn-name
+                           ;; In an interpreted function: use 
`backtrace-frame' to get the
+                           ;; function name (we have to use a little hackery 
to figure out
+                           ;; how far up the frame to look, but this seems to 
work).
+                           `(cl-loop for frame in (backtrace-frames)
+                                     for fn = (cl-second frame)
+                                     when (not (or (subrp fn)
+                                                   (special-form-p fn)
+                                                   (eq 'backtrace-frames fn)))
+                                     return (make-symbol (format "%s 
[interpreted]" fn))))))
+           (display-warning fn-name (format ,string ,@args) ,level ,buffer)
+           nil)
+      `(ignore ,@args))))
+
 (defmacro listen-once-per (value-form &rest body)
   "Evaluate BODY at most once while VALUE-FORM has the same value."
   (declare (indent defun))
@@ -45,9 +104,20 @@
 (cl-defstruct listen-player
   ;; TODO: Add queue slot.
   process command args
+  (status
+   nil :documentation "Symbol representing player's playback status (e.g. 
`playing', `paused', `stopped', or nil if unknown).")
+  (etc nil :documentation "Alist used to store other information about the 
player.")
+  (path nil :documentation "Filename path or URL to currently playing track, 
if any.")
+  (metadata nil :documentation "Metadata alist.")
+  (volume nil :documentation "Volume in percent.")
   (max-volume
    100 :documentation "Maximum volume in percent (may be greater than 100 for 
some players).")
-  etc)
+  (playback-started-at
+   nil :documentation "Time at which playback started (used to compute 
elapsed/remaining).")
+  (playback-started-from
+   nil :documentation "Track position at which playback last started/unpaused, 
in seconds (used to compute elapsed/remaining).")
+  (duration
+   nil :documentation "Duration of current track, in seconds (used to compute 
elapsed/remaining)."))
 
 (cl-defstruct listen-queue
   name tracks current etc)
diff --git a/listen-mpv.el b/listen-mpv.el
index 8702897761..76db22f691 100755
--- a/listen-mpv.el
+++ b/listen-mpv.el
@@ -26,6 +26,7 @@
 ;;;; Requirements
 
 (require 'cl-lib)
+(require 'json)
 (require 'map)
 
 (require 'listen-lib)
@@ -54,29 +55,44 @@
 
 (cl-defmethod listen--info ((player listen-player-mpv))
   "Return metadata from MPV PLAYER, or nil if a track is not playing."
-  ;; If the metadata property isn't available, ignore the error.
-  (when-let ((metadata (ignore-errors (listen-mpv--get-property player 
"metadata"))))
-    (map-apply (lambda (key value)
-                 ;; TODO: Consider using symbols as keys (VLC returns strings, 
MPV's decodes as
-                 ;; symbols).
-                 (cons (downcase (symbol-name key)) value))
-               metadata)))
+  (or (listen-player-metadata player)
+      (listen--update-metadata player)))
+
+(cl-defmethod listen--update-metadata ((player listen-player-mpv) &optional 
then)
+  "Update PLAYER's metadata slot, then call THEN without arguments."
+  (let ((callback (lambda (metadata)
+                    (pcase metadata
+                      ((and (or `nil :unknown) value)
+                       ;; May happen between tracks.
+                       (listen-debug "Metadata response was" value))
+                      (_
+                       (setf (listen-player-metadata player)
+                             (map-apply (lambda (key value)
+                                          (cons (intern (downcase (symbol-name 
key))) value))
+                                        metadata))
+                       (when then
+                         (funcall then)))))))
+    (if then
+        (listen-mpv--get-property player "metadata" :then callback)
+      (funcall callback (listen-mpv--get-property player "metadata")))))
 
 (cl-defmethod listen--filename ((player listen-player-mpv))
   "Return filename of PLAYER's current track."
-  (let ((status (listen-mpv--get-property player "path" )))
-    (when (string-match (rx bol "( new input: file://" (group (1+ nonl)) " )" 
) status)
-      (match-string 1 status))))
+  (let ((new-status (listen-mpv--get-property player "path")))
+    (when (string-match (rx bol "( new input: file://" (group (1+ nonl)) " )" 
) new-status)
+      (match-string 1 new-status))))
 
 (cl-defmethod listen--title ((player listen-player-mpv))
-  (listen-mpv--get-property player "media-title" ))
+  (map-elt (listen-player-metadata player) 'title))
 
 (cl-defmethod listen--ensure ((player listen-player-mpv))
   "Ensure PLAYER is ready."
   (pcase-let* (((cl-struct listen-player command args process) player)
                (socket (make-temp-name (expand-file-name "listen-mpv-socket-" 
temporary-file-directory)))
                (args (append args (list (format "--input-ipc-server=%s" socket)
-                                        (format "--volume=%s" 
listen-mpv-volume)))))
+                                        "--msg-level=ipc=debug"
+                                        (format "--volume=%s" 
listen-mpv-volume)
+                                        "--terminal=no"))))
     (unless (process-live-p process)
       (let ((process-buffer (generate-new-buffer " *listen-player-mpv*"))
             (socket-buffer (generate-new-buffer " 
*listen-player-mpv-socket*")))
@@ -90,98 +106,187 @@
         (setf (map-elt (listen-player-etc player) :network-process)
               (make-network-process :name "listen-player-mpv-socket" :family 
'local
                                     :remote socket :noquery t
-                                    :buffer socket-buffer)))
-      (set-process-query-on-exit-flag (listen-player-process player) nil))))
+                                    :buffer socket-buffer
+                                    :service nil)
+              (process-filter (map-elt (listen-player-etc player) 
:network-process))
+              (lambda (proc text)
+                (listen--filter player proc text))
+              (process-sentinel (map-elt (listen-player-etc player) 
:network-process))
+              (lambda (proc msg)
+                (display-warning 'listen-mpv
+                                 (format-message "listen-process-sentinel: 
PROC:%S  MSG:%S"
+                                                 proc msg)
+                                 :debug "*listen-mpv*")
+                (internal-default-process-sentinel proc msg))))
+      (set-process-query-on-exit-flag (listen-player-process player) nil)
+      ;; Observe relevant properties.
+      (dolist (property '("volume" "mute" "pause" "playback-time" "duration" 
"path" "metadata"))
+        (listen--send* player `("observe_property" ,property) :then 
#'ignore)))))
+
+(cl-defmethod listen--filter ((player listen-player-mpv) proc text)
+  (listen-debug :buffer "*listen-mpv*" (listen-player-process player) proc 
text)
+  (cl-labels ((next-message ()
+                (if-let ((msg (ignore-errors (let ((json-false nil))
+                                               (json-read)))))
+                    (progn
+                      (listen-debug :buffer "*listen-mpv*" "Parsed" msg)
+                      (delete-region (point-min) (point))
+                      msg)
+                  ;; Unparseable: return point so we can try again later.
+                  (listen-debug :buffer "*listen-mpv*" "Unparseable")
+                  (goto-char (point-min))
+                  nil)))
+    (with-current-buffer (process-buffer proc)
+      (goto-char (point-max))
+      (insert text)
+      (goto-char (point-min))
+      (while-let ((msg (next-message)))
+        (listen--act player msg)))))
+
+(cl-defmethod listen--act ((player listen-player-mpv) msg)
+  (listen-debug :buffer "*listen-mpv*" (listen-player-process player) msg)
+  (pcase-let (((map event request_id _reason data _error name) msg))
+    (pcase event
+      ((or "start-file" "playback-restart")
+       (listen--status-is player 'playing)
+       (setf (listen-player-playback-started-at player) (current-time)
+             (listen-player-playback-started-from player) 0)
+       (listen--update-metadata player)
+       (setf (listen-player-duration player) (listen-mpv--get-property player 
"duration"))
+       (setf (listen-player-volume player) (listen-mpv--get-property player 
"volume")))
+      ((or "end-file" "idle") (listen--status-is player 'stopped))
+      ((or 'nil "data")
+       (if-let ((callback (map-elt (map-elt (listen-player-etc player) 
:requests) request_id)))
+           (prog1
+               (funcall callback msg)
+             (setf (map-elt (listen-player-etc player) :requests)
+                   (map-delete (map-elt (listen-player-etc player) :requests) 
request_id)))
+         (listen-debug :buffer "*listen-mpv*" "No callback for" msg)))
+      ("property-change"
+       ;; NOTE: Even though we explicitly observe these properties, if they 
change as a result of a
+       ;; command that we send, MPV does not send messages for these 
properties changing (e.g. if we
+       ;; tell it to pause, we don't get a pause property-change event).
+       (pcase name
+         ("duration" (setf (listen-player-duration player) data))
+         ("metadata" (setf (listen-player-metadata player) data))
+         ("path" (setf (listen-player-path player) data))
+         ("pause"
+          (listen--status-is
+           player (pcase data
+                    ('t 'paused)
+                    ('nil 'playing)
+                    (_ (listen-debug :buffer "*listen-mpv*" "Unrecognized 
pause" data)))))
+         ;; ("playback-time" (setf (listen-player-position player) data
+         ;;                        (listen-player-playback-started-from 
player) data))
+         ("volume" (setf (listen-player-volume player) data))))
+      (_ (listen-debug :buffer "*listen-mpv*" "Unrecognized event" event)))))
+
+(cl-defmethod listen--status-is ((player listen-player-mpv) new-status)
+  "Update PLAYER's status slot according to NEW-STATUS and return it.
+When NEW-STATUS is `playing', updates started-at and started-from slots."
+  (pcase-exhaustive new-status
+    ('paused nil)
+    ('playing
+     (setf (listen-player-playback-started-at player) (current-time)
+           (listen-player-playback-started-from player)
+           (listen-mpv--get-property player "playback-time")))
+    ('stopped (setf (listen-player-playback-started-at player) nil
+                    (listen-player-playback-started-from player) nil)))
+  (setf (listen-player-status player) new-status))
 
 (cl-defmethod listen--play ((player listen-player-mpv) file)
   "Play FILE with PLAYER.
 Stops playing, clears playlist, adds FILE, and plays it."
-  (listen--send player "loadfile" (expand-file-name file)))
+  (listen--send* player `("loadfile" ,(expand-file-name file)) :then #'ignore))
 
 ;; (cl-defmethod listen--stop ((player listen-player-mpv))
 ;;   "Stop playing with PLAYER."
 ;;   (listen--send player "stop"))
 
 (cl-defmethod listen--status ((player listen-player-mpv))
-  (if (and (listen--playing-p player)
-           (not (listen-mpv--get-property listen-player "pause")))
-      "playing"
-    ;; TODO: Consider using "eof-reached" proeprty.
-    (if (listen-mpv--get-property listen-player "pause")
-        "paused"
-      "stopped")))
+  (listen-player-status player))
 
 (cl-defmethod listen--pause ((player listen-player-mpv))
   "Pause playing with PLAYER."
-  (if (listen-mpv--get-property player "pause")
-      (listen-mpv--set-property player "pause" "no")
-    (listen-mpv--set-property player "pause" "yes")))
+  (let ((new-status (pcase (listen-player-status player)
+                      ('playing "yes")
+                      ('paused "no")
+                      ('nil "no"))))
+    (listen-mpv--set-property
+     player "pause" new-status
+     :then (lambda (msg)
+             (pcase (map-elt msg 'error)
+               ("success" (listen--status-is
+                           player (pcase-exhaustive new-status ("yes" 'paused) 
("no" 'playing))))
+               (_ (display-warning 'listen--pause (format-message "Unexpected 
response: %S" msg)
+                                   :warning "*listen-mpv*")))))))
 
 (cl-defmethod listen--playing-p ((player listen-player-mpv))
   "Return non-nil if PLAYER is playing."
-  (not (listen-mpv--get-property player "idle-active")))
+  (equal (listen-player-status player) 'playing))
 
 (cl-defmethod listen--elapsed ((player listen-player-mpv))
   "Return seconds elapsed for PLAYER's track."
-  (listen-mpv--get-property player "time-pos"))
+  (if (listen--playing-p player)
+      (setf (map-elt (listen-player-etc player) :elapsed)
+            (+ (time-to-seconds
+                (time-subtract (current-time) 
(listen-player-playback-started-at player)))
+               (listen-player-playback-started-from player)))
+    (map-elt (listen-player-etc player) :elapsed)))
 
 (cl-defmethod listen--length ((player listen-player-mpv))
   "Return length of PLAYER's track in seconds."
-  (listen-mpv--get-property player "duration"))
-
-(require 'json)
+  (listen-player-duration player))
 
 (cl-defmethod listen--send ((player listen-player-mpv) command &rest args)
-  "Send COMMAND to PLAYER and return output."
+  "Not implemented for MPV; use `listen--send*'.
+For checkdoc: PLAYER, COMMAND, ARGS."
+  (ignore player command args)
+  (error "Method `listen--send' is not implemented for player 
`listen-player-mpv'; use `listen--send*'"))
+
+(cl-defmethod listen--send* ((player listen-player-mpv) command-args &key then)
+  "Send COMMAND-ARGS to PLAYER.
+The first string in COMMAND-ARGS is the MPV command, and the remaining
+ones are arguments to it.  If THEN is provided, it should be a function
+which will be called asynchronously with the message alist returned by
+MPV, and the request ID number is returned from this function;
+otherwise, the MPV command is called synchronously and the message alist
+is returned from this function."
   (listen--ensure player)
-  (pcase-let* (((cl-struct listen-player (etc (map :network-process))) player)
-               (request-id (cl-incf (map-elt (listen-player-etc player) 
:request-id))))
-    (with-current-buffer (process-buffer network-process)
-      (let ((json (json-encode `(("command" ,command ,@args)
-                                 ("request_id" . ,request-id)))))
-        ;; (message "SENDING: %S" json)
-        (process-send-string network-process json)
-        (process-send-string network-process "\n")
-        (goto-char (point-max))
-        (with-local-quit
-          (accept-process-output network-process 2))
-        (save-excursion
-          (goto-char (point-min))
-          (let ((json-false nil))
-            (cl-loop
-             ;; do (message "BUFFER-CONTENTS:%S  POS:%s  BUFFER-SIZE:%s  
EOBP:%s"
-             ;;             (buffer-string) (point) (buffer-size) (eobp))
-             until (or (eobp) (looking-at-p (rx (0+ space) eos)))
-             for start-pos = (point)
-             for result = (condition-case-unless-debug err
-                              (json-read)
-                            (error
-                             (message "listen--send: JSON-READ signaled error: 
%S  BUFFER-CONTENTS:%S  POS:%s  BUFFER-SIZE:%s  EOBP:%s"
-                                      err (buffer-string) (point) 
(buffer-size) (eobp))))
-             while result
-             for value = (pcase (map-elt result 'request_id)
-                           ((pred (equal request-id))
-                            ;; Event is the one we're looking for: delete the 
event from the
-                            ;; buffer and return it.
-                            (unless listen-debug-p
-                              (delete-region start-pos (point)))
-                            result)
-                           ('nil
-                            ;; Event has no request ID: delete it from the 
buffer.
-                            (unless listen-debug-p
-                              (delete-region start-pos (point)))
-                            nil)
-                           (_
-                            ;; Event is for a different request: ignore it 
(this probably
-                            ;; won't happen in practice, since we process 
commands
-                            ;; synchronously, but it's good to be careful).
-                            nil))
-             when value
-             return value)))))))
+  (cl-macrolet
+      ((wrap-callback (callback)
+         `(lambda (msg)
+            (unwind-protect
+                (funcall ,callback msg)
+              (setf (map-elt (listen-player-etc player) :requests)
+                    (map-delete (map-elt (listen-player-etc player) :requests) 
request-id))))))
+    (pcase-let* (((cl-struct listen-player (etc (map :network-process))) 
player)
+                 (request-id (cl-incf (map-elt (listen-player-etc player) 
:request-id)))
+                 (`(,command . ,args) command-args)
+                 (json (json-encode `(("command" ,command ,@args)
+                                      ("request_id" . ,request-id)))))
+      (listen-debug :buffer "*listen-mpv*" (listen-player-process player) json)
+      (process-send-string network-process json)
+      (process-send-string network-process "\n")
+      ;; TODO: Maybe check for success/error.
+      (if then
+          (progn
+            (setf (map-elt (map-elt (listen-player-etc player) :requests) 
request-id)
+                  (wrap-callback then))
+            request-id)
+        (let ((value :unknown))
+          (setf (map-elt (map-elt (listen-player-etc player) :requests) 
request-id)
+                (wrap-callback
+                 (lambda (msg)
+                   ;; Save the callback's value to the map so we can retrieve 
it.
+                   (setf value (map-elt msg 'data)))))
+          (accept-process-output (listen-player-process player) 0.05)
+          ;; Return the then's value.
+          value)))))
 
 (cl-defmethod listen--seek ((player listen-player-mpv) seconds)
   "Seek PLAYER to SECONDS."
-  (listen--send player "seek" seconds "absolute"))
+  (listen--send* player `("seek" ,seconds "absolute") :then #'ignore))
 
 (cl-defmethod listen--volume ((player listen-player-mpv) &optional volume)
   "Return or set PLAYER's VOLUME.
@@ -191,23 +296,18 @@ VOLUME is an integer percentage."
         (progn
           (unless (<= 0 volume max-volume)
             (error "VOLUME must be 0-%s" max-volume))
-          (listen-mpv--set-property player "volume" volume))
-      (listen-mpv--get-property player "volume"))))
-
-(cl-defmethod listen-mpv--get-property ((player listen-player-mpv) property)
-  (pcase-let (((map error data) (listen--send player "get_property" property)))
-    (pcase error
-      ("success" data)
-      (_ (condition-case-unless-debug _
-             ;; Between tracks, getting a property may fail, which should 
generally be ignored.
-             (error "listen-mpv--get-property: Getting property %S failed: %S" 
property error)
-           (error nil))))))
-
-(cl-defmethod listen-mpv--set-property ((player listen-player-mpv) property 
&rest args)
-  (pcase-let (((map error data) (apply #'listen--send player "set_property" 
property args)))
-    (pcase error
-      ("success" data)
-      (_ (error "listen-mpv--set-property: Setting property %S failed: %S" 
property error)))))
+          ;; We assume that the command will work, and we set the volume that 
is being set,
+          ;; because the Transient description uses the value from the player 
slot, and the
+          ;; callback can't make the Transient update itself.
+          (listen-mpv--set-property player "volume" volume)
+          (setf (listen-player-volume player) volume))
+      (listen-player-volume player))))
+
+(cl-defmethod listen-mpv--get-property ((player listen-player-mpv) property 
&key then)
+  (listen--send* player `("get_property" ,property) :then then))
+
+(cl-defmethod listen-mpv--set-property ((player listen-player-mpv) property 
value &key then)
+  (listen--send* player `("set_property" ,property ,value) :then then))
 
 (provide 'listen-mpv)
 
diff --git a/listen-vlc.el b/listen-vlc.el
index c799f9ab72..9da0456209 100755
--- a/listen-vlc.el
+++ b/listen-vlc.el
@@ -48,12 +48,18 @@
 ;;;; Functions
 
 (cl-defmethod listen--info ((player listen-player-vlc))
+  "Return metadata from VLC PLAYER, or nil if a track is not playing."
+  (or (listen-player-metadata player)
+      (listen--update-metadata player)))
+
+(cl-defmethod listen--update-metadata ((player listen-player-vlc))
   (with-temp-buffer
     (save-excursion
       (insert (listen--send player "info")))
-    (cl-loop while (re-search-forward (rx bol "| " (group (1+ (not blank))) ": 
"
-                                          (group (1+ (not (any "
"))))) nil t)
-             collect (cons (match-string 1) (match-string 2)))))
+    (setf (listen-player-metadata player)
+          (cl-loop while (re-search-forward (rx bol "| " (group (1+ (not 
blank))) ": "
+                                                (group (1+ (not (any "
"))))) nil t)
+                   collect (cons (intern (downcase (match-string 1))) 
(match-string 2))))))
 
 (cl-defmethod listen--filename ((player listen-player-vlc))
   "Return filename of PLAYER's current track."
@@ -86,7 +92,10 @@ Stops playing, clears playlist, adds FILE, and plays it."
 (cl-defmethod listen--status ((player listen-player-vlc))
   (let ((status (listen--send player "status")))
     (when (string-match (rx "( state " (group (1+ alnum)) " )") status)
-      (match-string 1 status))))
+      (pcase (match-string 1 status)
+        ("paused" 'paused)
+        ("playing" 'playing)
+        ("stopped" 'stopped)))))
 
 (cl-defmethod listen--pause ((player listen-player-vlc))
   "Pause playing with PLAYER."
@@ -134,8 +143,9 @@ VOLUME is an integer percentage."
         (progn
           (unless (<= 0 volume max-volume)
             (error "VOLUME must be 0-%s" max-volume))
-          (listen--send player (format "volume %s" (* 255 (/ volume 100.0)))))
-      (* 100 (/ (string-to-number (listen--send player "volume")) 255.0)))))
+          (listen--send player (format "volume %s" (* 255 (/ volume 100.0))))
+          (setf (listen-player-volume player) volume))
+      (listen-player-volume player))))
 
 (provide 'listen-vlc)
 
diff --git a/listen.el b/listen.el
index 842094c030..76fb0e5ad7 100755
--- a/listen.el
+++ b/listen.el
@@ -136,9 +136,9 @@ its current track will be the one that just finished 
playing)."
 Interactively, uses the default player."
   (interactive
    (list (listen-current-player)))
-  (delete-process (listen-player-process player))
   (when (eq player listen-player)
     (setf listen-player nil))
+  (delete-process (listen-player-process player))
   (listen-mode--update))
 
 (defun listen-next (player)
@@ -244,19 +244,20 @@ Interactively, jump to current queue's current track."
 (defun listen-mode-lighter ()
   "Return lighter for `listen-mode'.
 According to `listen-lighter-format', which see."
-  (when-let ((listen-player)
-             ((listen--running-p listen-player))
-             ((listen--playing-p listen-player))
-             (info (listen--info listen-player)))
+  (when-let* ((player listen-player)
+              ((listen--running-p player))
+              ((pcase (listen--status player)
+                 ((or 'playing 'paused) t)))
+              (metadata (listen--info player)))
     (format-spec listen-lighter-format
                  `((?a . ,(lambda ()
-                            (propertize (or (alist-get "artist" info nil nil 
#'equal) "")
+                            (propertize (or (alist-get 'artist metadata nil 
nil #'equal) "")
                                         'face 'listen-lighter-artist)))
                    (?A . ,(lambda ()
-                            (propertize (or (alist-get "album" info nil nil 
#'equal) "")
+                            (propertize (or (alist-get 'album metadata nil nil 
#'equal) "")
                                         'face 'listen-lighter-album)))
                    (?t . ,(lambda ()
-                            (if-let ((title (alist-get "title" info nil nil 
#'equal)))
+                            (if-let ((title (alist-get 'title metadata nil nil 
#'equal)))
                                 (propertize
                                  (truncate-string-to-width title 
listen-lighter-title-max-length
                                                            nil nil t)
@@ -272,9 +273,9 @@ According to `listen-lighter-format', which see."
                                         'face 'listen-lighter-time)))
                    (?s . ,(lambda ()
                             (propertize (pcase (listen--status listen-player)
-                                          ("playing" "▶")
-                                          ("paused" "⏸")
-                                          ("stopped" "■")
+                                          ('playing "▶")
+                                          ('paused "⏸")
+                                          ('stopped "■")
                                           (_ ""))
                                         'face 'bold)))
                    (?E . ,(lambda ()
@@ -302,15 +303,13 @@ According to `listen-lighter-format', which see."
       (unless (or (listen--playing-p listen-player)
                   ;; HACK: It seems that sometimes the player gets restarted
                   ;; even when paused: this extra check should prevent that.
-                  (member (listen--status listen-player) '("playing" 
"paused")))
+                  (member (listen--status listen-player) '(playing paused)))
         (setf playing-next-p
               (run-hook-with-args 'listen-track-end-functions listen-player))))
     (setf listen-mode-lighter
           (when (and listen-player (listen--running-p listen-player))
             (listen-mode-lighter)))
-    (when playing-next-p
-      ;; TODO: Remove this (I think it's not necessary anymore).
-      (force-mode-line-update 'all))))
+    (force-mode-line-update 'all)))
 
 (defun listen-play-next (player)
   "Play PLAYER's queue's next track and return non-nil if playing."

Reply via email to