diff options
| author | Craig Jennings <c@cjennings.net> | 2025-11-15 13:16:57 -0600 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-11-15 13:16:57 -0600 |
| commit | fbae1614bf8cff06366de69f5f1694d0c813824c (patch) | |
| tree | bc8714ede3c919ac9ce0a5643669604e60be270f | |
| parent | a4e0c282c14d835b056924789afb88271216e9ac (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.el | 59 | ||||
| -rw-r--r-- | tests/test-music-config--append-track-to-m3u-file.el | 55 |
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 |
