From fd2fb02ab77eca1b7a75fe40aac33d5fedd3dac0 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sat, 27 Jun 2026 07:54:58 -0400 Subject: feat(music): configurable in-track seek via an mpv control socket The subprocess player I just switched to dropped in-track seek. I re-added it without the startup fragility that made the IPC player unreliable. mpv still plays the track as a direct argument, so the reliable start is untouched, and --input-ipc-server opens a control socket that only carries seek commands to the already-playing process. f and b seek by cj/music-seek-seconds (default 5). The socket send is a no-op when nothing is playing, so it never errors. --- modules/music-config.el | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/modules/music-config.el b/modules/music-config.el index fb8febc36..c62f0c614 100644 --- a/modules/music-config.el +++ b/modules/music-config.el @@ -144,6 +144,9 @@ (defvar cj/music-file-extensions '("aac" "flac" "m4a" "mp3" "ogg" "opus" "wav") "List of valid music file extensions.") +(defvar cj/music-seek-seconds 5 + "Seconds to move when seeking forward or backward in the current track.") + (defvar cj/music-playlist-buffer-name "*EMMS-Playlist*" "Name of the EMMS playlist buffer used by this configuration.") @@ -169,12 +172,21 @@ "\\|\\`\\(?:https?\\|mms\\)://") "Track names the subprocess mpv player handles: music files or stream URLs.") +(defvar cj/music--mpv-socket + (expand-file-name "emms/mpv-control.sock" user-emacs-directory) + "IPC control socket for the subprocess mpv player. +mpv opens it per playback via --input-ipc-server. It does NOT affect startup: +mpv still plays the track passed as a direct argument, so the reliable start is +unchanged. The socket only carries control commands (seek) to the already +playing process, which is where the old idle + loadfile handshake failed.") + (defun cj/music--mpv-start (track) "Play TRACK by running mpv with the track name as a direct argument." (emms-player-simple-start (emms-track-name track) 'emms-player-cj/music-mpv "mpv" - (list "--no-video" "--no-config" "--really-quiet"))) + (list "--no-video" "--no-config" "--really-quiet" + (concat "--input-ipc-server=" cj/music--mpv-socket)))) (defun cj/music--mpv-stop () "Stop the mpv subprocess." @@ -198,6 +210,32 @@ (when (and proc (process-live-p proc)) (signal-process proc 'SIGCONT)))) +(defun cj/music--mpv-command (json) + "Send JSON (a one-line mpv IPC command) to the control socket. +A no-op when nothing is playing or the socket is gone, so it never errors." + (when (file-exists-p cj/music--mpv-socket) + (ignore-errors + (let ((proc (make-network-process :name "cj-music-mpv-cmd" + :family 'local + :service cj/music--mpv-socket + :noquery t))) + (unwind-protect + (progn (process-send-string proc (concat json "\n")) + (accept-process-output proc 0.1)) + (delete-process proc)))))) + +(defun cj/music-seek-forward () + "Seek `cj/music-seek-seconds' seconds forward in the current track." + (interactive) + (cj/music--mpv-command + (format "{\"command\": [\"seek\", %d, \"relative\"]}" cj/music-seek-seconds))) + +(defun cj/music-seek-backward () + "Seek `cj/music-seek-seconds' seconds backward in the current track." + (interactive) + (cj/music--mpv-command + (format "{\"command\": [\"seek\", %d, \"relative\"]}" (- cj/music-seek-seconds)))) + ;;; Buffer-local state (defvar-local cj/music-playlist-file nil @@ -951,8 +989,8 @@ For URL tracks: decoded URL." (">" . cj/music-next) ("P" . cj/music-previous) ("<" . cj/music-previous) - ("f" . emms-seek-forward) - ("b" . emms-seek-backward) + ("f" . cj/music-seek-forward) + ("b" . cj/music-seek-backward) ("q" . emms-playlist-mode-bury-buffer) ("a" . cj/music-fuzzy-select-and-add) ;; Toggles (aligned with ncmpcpp) -- cgit v1.2.3