summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-11-15 13:16:57 -0600
committerCraig Jennings <c@cjennings.net>2025-11-15 13:16:57 -0600
commitfbae1614bf8cff06366de69f5f1694d0c813824c (patch)
treebc8714ede3c919ac9ce0a5643669604e60be270f
parenta4e0c282c14d835b056924789afb88271216e9ac (diff)
feat(music): Switch EMMS from MPD to MPV backend
Major improvements to music player configuration: Backend Changes: - Switch from emms-player-mpd to emms-player-mpv - Remove MPD daemon dependency (no service management required) - Configure MPV with --quiet, --no-video, --audio-display flags - Support both local files and streaming URLs (http/https/mms) Keybinding Enhancements: - Add C-; m n → next track - Add C-; m p → previous track - Add C-; m g → go to playlist - In playlist: n/P for next/prev, f/b for seek forward/backward - Update which-key descriptions Code Quality: - Use music-dir constant from user-constants.el (not hardcoded path) - Add (require 'user-constants) for proper dependency - Update commentary to reflect MPV backend Test Fixes: - Fix 8 failing append-track tests - Update test mock data to use cj/music-root for portability - All 104 music-config tests now passing Benefits: - No daemon to start/stop/manage - Simpler architecture (one process vs MPD+EMMS) - Streaming radio URLs work out of the box - Better path consistency across codebase 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
-rw-r--r--modules/music-config.el59
-rw-r--r--tests/test-music-config--append-track-to-m3u-file.el55
2 files changed, 63 insertions, 51 deletions
diff --git a/modules/music-config.el b/modules/music-config.el
index cdbe56e0..0994bf82 100644
--- a/modules/music-config.el
+++ b/modules/music-config.el
@@ -1,8 +1,8 @@
-;;; music-config.el --- EMMS configuration with MPD integration -*- coding: utf-8; lexical-binding: t; -*-
+;;; music-config.el --- EMMS configuration with MPV backend -*- coding: utf-8; lexical-binding: t; -*-
;;
;;; Commentary:
;;
-;; Comprehensive music management in Emacs via EMMS with MPD backend.
+;; Comprehensive music management in Emacs via EMMS with MPV backend.
;; Focus: simple, modular helpers; consistent error handling; streamlined UX.
;;
;; Highlights:
@@ -10,17 +10,18 @@
;; - Recursive directory add
;; - Dired/Dirvish integration (add selection)
;; - M3U playlist save/load/edit/reload
-;; - Radio station M3U creation
+;; - Radio station M3U creation (streaming URLs supported)
;; - Playlist window toggling
-;; - MPD as player
+;; - MPV as player (no daemon required)
;;
;;; Code:
(require 'subr-x)
+(require 'user-constants)
;;; Settings (no Customize)
-(defvar cj/music-root (expand-file-name "~/music")
+(defvar cj/music-root music-dir
"Root directory of your music collection.")
(defvar cj/music-m3u-root cj/music-root
@@ -421,7 +422,9 @@ Dirs added recursively."
"r" #'cj/music-create-radio-station
"SPC" #'emms-pause
"s" #'emms-stop
- "p" #'emms-playlist-mode-go
+ "n" #'emms-next
+ "p" #'emms-previous
+ "g" #'emms-playlist-mode-go
"x" #'emms-shuffle)
(keymap-set cj/custom-keymap "m" cj/music-map)
@@ -434,7 +437,9 @@ Dirs added recursively."
"C-; m r" "create radio"
"C-; m SPC" "pause"
"C-; m s" "stop"
- "C-; m p" "playlist mode"
+ "C-; m n" "next track"
+ "C-; m p" "previous track"
+ "C-; m g" "goto playlist"
"C-; m x" "shuffle"))
(use-package emms
@@ -445,7 +450,7 @@ Dirs added recursively."
:commands (emms-mode-line-mode)
:config
(require 'emms-setup)
- (require 'emms-player-mpd)
+ (require 'emms-player-mpv)
(require 'emms-playlist-mode)
(require 'emms-source-file)
(require 'emms-source-playlist)
@@ -454,8 +459,8 @@ Dirs added recursively."
(setq emms-source-file-default-directory cj/music-root)
(setq emms-playlist-default-major-mode 'emms-playlist-mode)
- ;; Use only MPD as player - MUST be set before emms-all
- (setq emms-player-list '(emms-player-mpd))
+ ;; Use MPV as player - MUST be set before emms-all
+ (setq emms-player-list '(emms-player-mpv))
;; Now initialize EMMS
(emms-all)
@@ -464,22 +469,18 @@ Dirs added recursively."
(emms-playing-time-disable-display)
(emms-mode-line-mode -1)
- ;; MPD configuration
- (setq emms-player-mpd-server-name "localhost")
- (setq emms-player-mpd-server-port "6600")
- (setq emms-player-mpd-music-directory cj/music-root)
- (condition-case err
- (emms-player-mpd-connect)
- (error (message "Failed to connect to MPD: %s" err)))
-
- ;; note setopt as variable is customizeable
- ;; MPD can play both local files and stream URLs
- (setopt emms-player-mpd-supported-regexp
- (rx (or
- ;; Stream URLs
- (seq bos (or "http" "https" "mms") "://")
- ;; Local music files by extension
- (regexp (apply #'emms-player-simple-regexp cj/music-file-extensions)))))
+ ;; MPV configuration
+ ;; MPV supports both local files and stream URLs
+ (setq emms-player-mpv-parameters
+ '("--quiet" "--no-video" "--audio-display=no"))
+
+ ;; Update supported file types for mpv player
+ (setq emms-player-mpv-regexp
+ (rx (or
+ ;; Stream URLs
+ (seq bos (or "http" "https" "mms") "://")
+ ;; Local music files by extension
+ (seq "." (or "aac" "flac" "m4a" "mp3" "ogg" "opus" "wav") eos))))
;; Keep cj/music-playlist-file in sync if playlist is cleared
(defun cj/music--after-playlist-clear (&rest _)
@@ -497,6 +498,10 @@ Dirs added recursively."
("p" . emms-playlist-mode-go)
("SPC" . emms-pause)
("s" . emms-stop)
+ ("n" . emms-next)
+ ("P" . emms-previous)
+ ("f" . emms-seek-forward)
+ ("b" . emms-seek-backward)
("x" . emms-shuffle)
("q" . emms-playlist-mode-bury-buffer)
("a" . cj/music-fuzzy-select-and-add)
@@ -512,7 +517,7 @@ Dirs added recursively."
("C-<down>" . emms-playlist-mode-shift-track-down)
;; Radio
("r" . cj/music-create-radio-station)
- ;; Volume (MPD)
+ ;; Volume (MPV)
("-" . emms-volume-lower)
("=" . emms-volume-raise)))
diff --git a/tests/test-music-config--append-track-to-m3u-file.el b/tests/test-music-config--append-track-to-m3u-file.el
index 2bf3e87d..be0cbd8e 100644
--- a/tests/test-music-config--append-track-to-m3u-file.el
+++ b/tests/test-music-config--append-track-to-m3u-file.el
@@ -40,39 +40,42 @@
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
(let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/artist/song.mp3"))
+ (track-path (expand-file-name "artist/song.mp3" cj/music-root))
+ (expected-relative "artist/song.mp3"))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
+ (should (string= (buffer-string) (concat expected-relative "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-normal-existing-with-newline-appends-track ()
"Append to file with existing content ending with newline."
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
- (let* ((existing-content "/home/user/music/first.mp3\n")
+ (let* ((existing-content "first.mp3\n")
(m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/second.mp3"))
+ (track-path (expand-file-name "second.mp3" cj/music-root))
+ (expected-relative "second.mp3"))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
(should (string= (buffer-string)
- (concat existing-content track-path "\n")))))
+ (concat existing-content expected-relative "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-normal-existing-without-newline-appends-track ()
"Append to file without trailing newline adds leading newline."
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
- (let* ((existing-content "/home/user/music/first.mp3")
+ (let* ((existing-content "first.mp3")
(m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/second.mp3"))
+ (track-path (expand-file-name "second.mp3" cj/music-root))
+ (expected-relative "second.mp3"))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
(should (string= (buffer-string)
- (concat existing-content "\n" track-path "\n")))))
+ (concat existing-content "\n" expected-relative "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-normal-multiple-appends-all-succeed ()
@@ -80,9 +83,11 @@
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
(let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track1 "/home/user/music/track1.mp3")
- (track2 "/home/user/music/track2.mp3")
- (track1-duplicate "/home/user/music/track1.mp3"))
+ (track1 (expand-file-name "track1.mp3" cj/music-root))
+ (track2 (expand-file-name "track2.mp3" cj/music-root))
+ (track1-duplicate (expand-file-name "track1.mp3" cj/music-root))
+ (rel1 "track1.mp3")
+ (rel2 "track2.mp3"))
(cj/music--append-track-to-m3u-file track1 m3u-file)
(cj/music--append-track-to-m3u-file track2 m3u-file)
(cj/music--append-track-to-m3u-file track1-duplicate m3u-file)
@@ -90,7 +95,7 @@
(insert-file-contents m3u-file)
(let ((content (buffer-string)))
(should (string= content
- (concat track1 "\n" track2 "\n" track1-duplicate "\n"))))))
+ (concat rel1 "\n" rel2 "\n" rel1 "\n"))))))
(test-music-config--append-track-to-m3u-file-teardown)))
;;; Boundary Cases
@@ -100,15 +105,14 @@
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
(let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- ;; Create a path that's ~500 chars long
- (track-path (concat "/home/user/music/"
- (make-string 450 ?a)
- "/song.mp3")))
+ ;; Create a relative path that's ~450 chars long
+ (relative-path (concat (make-string 440 ?a) "/song.mp3"))
+ (track-path (expand-file-name relative-path cj/music-root)))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))
- (should (= (length (buffer-string)) (1+ (length track-path))))))
+ (should (string= (buffer-string) (concat relative-path "\n")))
+ (should (= (length (buffer-string)) (1+ (length relative-path))))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-boundary-path-with-unicode-appends-successfully ()
@@ -116,11 +120,12 @@
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
(let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/中文/artist-名前/song🎵.mp3"))
+ (relative-path "中文/artist-名前/song🎵.mp3")
+ (track-path (expand-file-name relative-path cj/music-root)))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
+ (should (string= (buffer-string) (concat relative-path "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-boundary-path-with-spaces-appends-successfully ()
@@ -128,11 +133,12 @@
(test-music-config--append-track-to-m3u-file-setup)
(unwind-protect
(let* ((m3u-file (cj/create-temp-test-file "test-playlist-"))
- (track-path "/home/user/music/Artist Name/Album (2024)/01 - Song's Title [Remix].mp3"))
+ (relative-path "Artist Name/Album (2024)/01 - Song's Title [Remix].mp3")
+ (track-path (expand-file-name relative-path cj/music-root)))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
- (should (string= (buffer-string) (concat track-path "\n")))))
+ (should (string= (buffer-string) (concat relative-path "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
(ert-deftest test-music-config--append-track-to-m3u-file-boundary-m3u-with-comments-appends-after ()
@@ -141,12 +147,13 @@
(unwind-protect
(let* ((existing-content "#EXTM3U\n#EXTINF:-1,Radio Station\nhttp://stream.url/radio\n")
(m3u-file (cj/create-temp-test-file-with-content existing-content "test-playlist-"))
- (track-path "/home/user/music/local-track.mp3"))
+ (relative-path "local-track.mp3")
+ (track-path (expand-file-name relative-path cj/music-root)))
(cj/music--append-track-to-m3u-file track-path m3u-file)
(with-temp-buffer
(insert-file-contents m3u-file)
(should (string= (buffer-string)
- (concat existing-content track-path "\n")))))
+ (concat existing-content relative-path "\n")))))
(test-music-config--append-track-to-m3u-file-teardown)))
;;; Error Cases