Emacs vs MPV Playlists: The Troubleshooting Saga Continues!

Table of Contents

Previously, On Our Emacs Hacking Adventure…

In my last post I taught Doom Emacs that when we find a single media file using the super-fast SPC f l command (consult-locate), pressing RET should not show binary blobs in text, but instead instantly play that file in out MPV media player. We did this with some Emacs Lisp (elisp) "advice", cleverly intercepting the default exit command (vertico-exit) just for media files. Nice!

But you know how it is. Give a coder a cookie, and we'll want a whole playlist…

The New Quest: Playing More Than One!

The dream evolved. What if I search for 'rick astley' with SPC f l and see multiple glorious tracks? I don't want to play them one by one! I'm no peasant! I want to select the ones I want and have them all play, queued up nicely in MPV. Sounds simple, right? Oh, how naive I was.

This turned out to be a much deeper dive into the rabbit hole of Emacs configuration, involving multiple attempts, head-scratching errors, and ultimately, a change of strategy. Grab coffee (don't spill it, you know who you are); let's walk through the attempts.

Attempt #1: `Marking` - the Vertico Way (If Only We Could!)

The first logical thought was: Emacs completion systems usually let you "mark" multiple candidates in a list. Vertico (the package showing the list for SPC f l) should be no different.

The Plan:

  1. Use SPC f l to show files.
  2. Use Vertico's marking key (I guessed maybe M-m or C-SPC) to select multiple files.
  3. Press a new key combo (I aimed for M-RET) to trigger a custom function that grabs the marked files and sends them to MPV.

The Reality: I hit a wall immediately. I tried to find the command responsible for marking in Vertico (I expected there to be a vertico-toggle-mark of some sorts). But after checking the active keymap (C-h v vertico-map RET) there was nothing related to marking… nothing! No key seemed bound to `vertico-toggle-mark`.

Result: FAIL. Couldn't mark files in Vertico, so couldn't act on marked files. Why the function seemed missing remains a mystery, but I had to move on.

Attempt #2: The Embark Select Shuffle

Okay, if Vertico's own marking is out, what about using Embark? Embark (the context-action menu package, usually triggered by C-; or M-x embark-act) has its own selection mechanisms called embark-select, embark-collect and emabark-collect-all. Thank you GuestReano and vifon from #emacs IRC.

The Intended Workflow:

  1. Use SPC f l to show files.
  2. Bind a key (e.g., C-c s) to `embark-select`.
  3. Use `embark-select`'s internal keys (like SPC) to mark multiple candidates.
  4. Finalise the selection (maybe RET?).
  5. Trigger `embark-act` (using M-x ).
  6. Choose our custom action (bound to P) which would get the selected targets using `(embark–targets)` and send them to MPV.

The Reality: This seemed to work initially – I could trigger the action. But the debug messages were baffling!

[DEBUG] embark--targets raw: ((:orig-type symbol :orig-target #("same"...) ...) (:orig-type expression :orig-target #("(with-eval-after-load 'embark..."...) ...))
[DEBUG] Filtered media files: nil
No media files found after filtering selected candidates.

The `(embark–targets)` function wasn't returning the list of files we thought we selected. It was returning… well, bits of Lisp code and symbols from whatever buffer was active - my doom config directory - when we finally ran `embark-act`! The context of our selection was getting lost.

Result: FAIL. Couldn't reliably get the list of selected files from Embark this way.

Attempt #3: Action Stations During Selection! (The Read-Only Wall)

Maybe I could trigger the action during the `embark-select` process, before finalising?

New Plan:

  1. Use SPC f l.
  2. Use C-x s to enter Embark selection mode.
  3. Use Embark's keys (like C-SPC) to mark files.
  4. Press P (bound within Embark's selection map) to run the MPV playlist function directly on the current selection (`embark–selections`).

The Reality: Weird errors!

byte-code: Buffer is read-only

Even binding P to a command that only printed a simple message (`(message "Test")`) gave this error. Something about executing a command via a keybinding within that specific read-only selection buffer state was fundamentally broken in my setup.

Result: FAIL. Hitting a wall trying to trigger actions from within `embark-select`'s state.

Familiar Territory: Retreating to Dired!

Okay, deep breaths. We got this. Trying to hijack the minibuffer completion directly was proving incredibly difficult due to missing functions, lost context, and weird read-only errors. Time for a strategic retreat to more familiar ground: Dired.

Dired is Emacs's built-in directory editor/file manager. It's rock-solid, highly customisable, and has standard, reliable ways to mark files.

The New Workflow:

  1. Use SPC f l to find the directory containing the media files we want.
  2. Press RET. Because the target is a directory, Emacs/Doom opens it in Dired.
  3. Inside Dired, use standard keys (like m) to mark the desired files.
  4. Press a custom key (let's try P again!) bound specifically within Dired mode to run a function that plays the marked files.

This felt much cleaner. We let `consult-locate` and Vertico do their job (finding and exiting), and then use Dired's established features for the multi-file selection and action.

The Final Battle: The Stubborn Keybinding

I wrote the elisp function (`tfg/dired-play-marked-media`) using the standard `dired-get-marked-files` function – much more reliable than internal Embark/Vertico functions! Then I tried to bind `P` in `dired-mode-map`.

Attempt A: `map!` / `with-eval-after-load` - Failed! Pressing `P` prompted to print the file. Attempt B: `dired-mode-hook` + `define-key` - Failed! Still prompted to print. Attempt C: `map! :after dired` - Failed! Still prompted to print.

Aaargh! Why was `P` so stubborn? The likely culprit, especially in Doom Emacs, was either Doom's own configuration overriding our binding very late, or Evil mode (the Vim emulation layer) having its own ideas about what `P` should do in Dired.

The Final Fix: Stop fighting for `P`! I'll choose a different key less likely to be globally claimed: C-c P (Control+C then uppercase P). We used the reliable `dired-mode-hook` method to bind this key.

Victory! The Working Code and Workflow

This finally worked! We now have two distinct, working methods:

  1. Single File Playback: SPC f l -> find specific media file -> RET -> Plays in MPV (using the `vertico-exit` advice).
  2. Playlist Playback: SPC f l -> find directory -> RET (opens Dired) -> Mark files in Dired with m -> Press C-c P -> Plays all marked media files in MPV.

    • Alternatively SPC f l -> find directory -> RET (opens Dired) -> Mark all files in Dired with ^-v G m -> Press C-c P -> Plays all the files in the directory.

Here's the relevant part of the final code for the Dired playlist functionality:

;; Helper function (defined elsewhere - See previous blog post if interested).
(defun tfg/is-media-file-p (target-file) ...)

;; Function to play marked files in Dired (Defined Globally or in with-eval-after-load).
(defun tfg/dired-play-marked-media ()
  "Play marked media files from Dired buffer in mpv."
  (interactive)
  (require 'dired)
  (let* ((marked-files (dired-get-marked-files))
         (media-files (cl-remove-if-not #'tfg/is-media-file-p marked-files)))
    (if media-files
        (progn
          (message "Playing %d marked media file(s) in mpv..." (length media-files))
          (apply #'start-process "dired-mpv-playlist" nil "mpv" media-files))
      (message "No marked media files found."))))

;; Set up the hook to bind the key *every time* dired-mode starts.
(defun tfg/dired-mode-hook-setup ()
  "Set custom keybindings for dired-mode."
  (define-key dired-mode-map (kbd "C-c P") #'tfg/dired-play-marked-media))

;; Add our setup function to the dired-mode-hook
(add-hook 'dired-mode-hook #'tfg/dired-mode-hook-setup)

For the diligent readers, you may notice that, since our last post, we changed from call-process-shell-command to start-process. This was because the former would hang the vertico process until mpv finished. That's no good! We still want to use our emacs instance while mpv plays. start-process hands the execution back to us nicely.

Lessons Learned

We got there! What did we learn?

  1. Emacs customisation is powerful, but sometimes requires digging into which package or mode is really in control (Vertico vs Embark vs Dired).
  2. Debugging tools like `C-h k` (describe key) and `C-h v` (describe variable) are essential for figuring out what's actually happening. Using `message` for debug output is, as always, invaluable.
  3. Internal functions (like `vertico–candidate` or `embark–targets`) can be necessary but risky, as they might change. Standard APIs (like `dired-get-marked-files`) are usually safer.
  4. Default keybindings (especially single letters like `P` or fundamental keys like `RET`) can be hard to override cleanly in complex setups like Doom Emacs. Choosing a less common key (C-c P) is often easier than fighting the system.
  5. Sometimes, changing the workflow (using Dired instead of acting directly from the initial search) leads to a simpler and more robust solution by leveraging standard, well-tested Emacs features.
  6. It often takes multiple attempts! Don't be discouraged if the first (or second, or third…) approach doesn't work. If at first you don't succeed, try, try again..

It was a little dissapointing that I didn't get the workflow I wanted from the beginning, but now playing single files or playlists from a quick search is seamless. And that was the ultimate goal, be able to find any media file(s) and play them, with as few keystrokes as possible and without any search fatigue.

– Happy Hacking!