emacs-elpa-diffs
[Top][All Lists]
Advanced

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

[elpa] externals/listen ffb0a91371 01/12: Add: (listen-mpv) MPV support


From: ELPA Syncer
Subject: [elpa] externals/listen ffb0a91371 01/12: Add: (listen-mpv) MPV support
Date: Sun, 18 Aug 2024 06:58:36 -0400 (EDT)

branch: externals/listen
commit ffb0a91371beed804db67e168320859aac8ea81d
Author: Adam Porter <adam@alphapapa.net>
Commit: Adam Porter <adam@alphapapa.net>

    Add: (listen-mpv) MPV support
    
    Seems to work well so far.
---
 README.org      |   2 +
 docs/README.org |   2 +
 listen-lib.el   |   4 +-
 listen-mpv.el   | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 listen.el       |  10 ++++
 5 files changed, 183 insertions(+), 3 deletions(-)

diff --git a/README.org b/README.org
index d742680b8d..d1df928a03 100644
--- a/README.org
+++ b/README.org
@@ -226,6 +226,8 @@ 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.
+- 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.
 
 *Fixes*
diff --git a/docs/README.org b/docs/README.org
index 63db70a7fb..398978af09 100644
--- a/docs/README.org
+++ b/docs/README.org
@@ -238,6 +238,8 @@ 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.
++ 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.
 
 *Fixes*
diff --git a/listen-lib.el b/listen-lib.el
index 600afc5056..7287e774f4 100644
--- a/listen-lib.el
+++ b/listen-lib.el
@@ -139,12 +139,10 @@ return a list of values; otherwise return the sole value."
 
 ;;;; Functions
 
-;; FIXME: Declare this differently or something.
-(declare-function make-listen-player-vlc "listen-vlc")
 (defun listen-current-player ()
   "Return variable `listen-player' or a newly set one if nil."
   (or listen-player
-      (setf listen-player (make-listen-player-vlc))))
+      (setf listen-player (funcall (defvar listen-backend)))))
 
 (cl-defun listen-current-track (&optional (player listen-player))
   "Return track playing on PLAYER, if any."
diff --git a/listen-mpv.el b/listen-mpv.el
new file mode 100755
index 0000000000..ecdb7c0638
--- /dev/null
+++ b/listen-mpv.el
@@ -0,0 +1,168 @@
+;;; listen-mpv.el --- MPV support for Emacs Music Player                    
-*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024  Free Software Foundation, Inc.
+
+;; Author: Adam Porter <adam@alphapapa.net>
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; 
+
+;;; Code:
+
+;;;; Requirements
+
+(require 'cl-lib)
+(require 'map)
+
+(require 'listen-lib)
+
+;;;; Types
+
+(cl-defstruct
+    (listen-player-mpv
+     (:include listen-player
+               (command "mpv")
+               (args '("--no-msg-color" "--idle"))
+               (max-volume 100)
+               (etc '((:request-id . 0))))))
+
+;;;; Functions
+
+(cl-defmethod listen--info ((player listen-player-mpv))
+  (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))
+             (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))))
+
+(cl-defmethod listen--title ((player listen-player-mpv))
+  (listen-mpv--get-property player "media-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)))))
+    (unless (process-live-p process)
+      (setf (listen-player-process player)
+            (apply #'start-process "listen-player-mpv" (generate-new-buffer " 
*listen-player-mpv*")
+                   command args))
+      (sleep-for 1)
+      (setf (map-elt (listen-player-etc player) :network-process)
+            (make-network-process :name "listen-player-mpv-socket" :family 
'local
+                                  :remote socket
+                                  :buffer (generate-new-buffer " 
*listen-player-mpv-socket*")))
+      (set-process-query-on-exit-flag (listen-player-process player) nil))))
+
+(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)))
+
+;; (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")))
+
+(cl-defmethod listen--pause ((player listen-player-mpv))
+  "Pause playing with PLAYER."
+  (if (listen-mpv--get-property listen-player "pause")
+      (listen-mpv--set-property listen-player "pause" "no")
+    (listen-mpv--set-property listen-player "pause" "yes")))
+
+(cl-defmethod listen--playing-p ((player listen-player-mpv))
+  "Return non-nil if PLAYER is playing."
+  (not (listen-mpv--get-property player "idle-active")))
+
+(cl-defmethod listen--elapsed ((player listen-player-mpv))
+  "Return seconds elapsed for PLAYER's track."
+  (listen-mpv--get-property player "time-pos"))
+
+(cl-defmethod listen--length ((player listen-player-mpv))
+  "Return length of PLAYER's track in seconds."
+  (listen-mpv--get-property player "duration"))
+
+(require 'json)
+
+(cl-defmethod listen--send ((player listen-player-mpv) command &rest args)
+  "Send COMMAND to PLAYER and return output."
+  (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 ((pos (marker-position (process-mark network-process)))
+            (json (json-encode
+                   `(("command" ,command ,@args)
+                     ("request_id" . ,request-id)))))
+        (process-send-string network-process json)
+        (process-send-string network-process "\n")
+        (with-local-quit
+          (accept-process-output network-process 2))
+        ;; TODO: Consider using `json-read', etc.
+        (goto-char (point-min))
+        (let ((json-false nil))
+          (prog1 (cl-loop for result = (json-read)
+                          while result
+                          when (equal request-id (map-elt result 'request_id))
+                          return result)
+            (unless listen-debug-p
+              (erase-buffer))))))))
+
+(cl-defmethod listen--seek ((player listen-player-mpv) seconds)
+  "Seek PLAYER to SECONDS."
+  (listen--send player "seek" seconds "absolute"))
+
+(cl-defmethod listen--volume ((player listen-player-mpv) &optional volume)
+  "Return or set PLAYER's VOLUME.
+VOLUME is an integer percentage."
+  (pcase-let (((cl-struct listen-player max-volume) player))
+    (if volume
+        (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)
+      (_ (error "listen-mpv--get-property: Getting property %S failed: %S" 
property error)))))
+
+(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)))))
+
+(provide 'listen-mpv)
+
+;;; listen-mpv.el ends here
diff --git a/listen.el b/listen.el
index 8b16f24941..e21f769f0b 100755
--- a/listen.el
+++ b/listen.el
@@ -60,6 +60,8 @@
 (require 'map)
 
 (require 'listen-lib)
+;; TODO: Can we load these as-needed?
+(require 'listen-mpv)
 (require 'listen-vlc)
 
 ;;;; Variables
@@ -119,6 +121,14 @@ Called with one argument, the player (if the player has a 
queue,
 its current track will be the one that just finished playing)."
   :type 'hook)
 
+(defcustom listen-backend
+  (cond ((executable-find "mpv") #'make-listen-player-mpv)
+        ((executable-find "vlc") #'make-listen-player-vlc)
+        (t (display-warning 'listen-backend "Unable to find MPV or VLC." 
:error)))
+  "Player backend."
+  :type '(choice (const :tag "MPV" make-listen-player-mpv)
+                 (const :tag "VLC" make-listen-player-vlc)))
+
 ;;;; Commands
 
 (defun listen-quit (player)



reply via email to

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