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."