| 1 | ;;; emms-player-mpg321-remote.el --- play files with mpg321 -R -*- lexical-binding: t; -*- |
| 2 | |
| 3 | ;; Copyright (C) 2006-2021 Free Software Foundation, Inc. |
| 4 | |
| 5 | ;; Author: Damien Elmes <emacs@repose.cx> |
| 6 | ;; Keywords: emms, mp3, mpeg, multimedia |
| 7 | |
| 8 | ;; This file is free software; you can redistribute it and/or modify |
| 9 | ;; it under the terms of the GNU General Public License as published by |
| 10 | ;; the Free Software Foundation; either version 3, or (at your option) |
| 11 | ;; any later version. |
| 12 | |
| 13 | ;; This file is distributed in the hope that it will be useful, |
| 14 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | ;; GNU General Public License for more details. |
| 17 | |
| 18 | ;; You should have received a copy of the GNU General Public License |
| 19 | ;; along with GNU Emacs; see the file COPYING. If not, write to |
| 20 | ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| 21 | ;; Boston, MA 02111-1307, USA. |
| 22 | |
| 23 | ;;; Commentary: |
| 24 | |
| 25 | ;; This file provides an emms-player which uses mpg321's remote mode |
| 26 | ;; to play files. This is a persistent process which isn't killed each |
| 27 | ;; time a new file is played. |
| 28 | |
| 29 | ;; The remote process copes graciously with errors in music files, and |
| 30 | ;; allows you to seek in files. |
| 31 | |
| 32 | ;; To enable this code, add the following to your emacs configuration: |
| 33 | |
| 34 | ;; (require 'emms-player-mpg321-remote) |
| 35 | ;; (push 'emms-player-mpg321-remote emms-player-list) |
| 36 | |
| 37 | ;;; Code: |
| 38 | |
| 39 | (require 'emms) |
| 40 | (require 'emms-player-simple) |
| 41 | |
| 42 | ;; -------------------------------------------------- |
| 43 | ;; Variables and configuration |
| 44 | ;; -------------------------------------------------- |
| 45 | |
| 46 | (defgroup emms-player-mpg321-remote nil |
| 47 | "*EMMS player using mpg321's remote mode." |
| 48 | :group 'emms-player |
| 49 | :prefix "emms-player-mpg321-remote") |
| 50 | |
| 51 | (defcustom emms-player-mpg321-remote-command "mpg321" |
| 52 | "The command name of mpg321." |
| 53 | :type 'string) |
| 54 | |
| 55 | (defcustom emms-player-mpg321-remote-parameters nil |
| 56 | "Extra arguments to pass to mpg321 when using remote mode |
| 57 | For example: (list \"-o\" \"alsa\")" |
| 58 | :type '(repeat string)) |
| 59 | |
| 60 | (defcustom emms-player-mpg321-remote |
| 61 | (emms-player #'emms-player-mpg321-remote-start-playing |
| 62 | #'emms-player-mpg321-remote-stop-playing |
| 63 | #'emms-player-mpg321-remote-playable-p) |
| 64 | "A player for EMMS." |
| 65 | :type '(cons symbol alist)) |
| 66 | |
| 67 | (defvar emms-player-mpg321-remote-initial-args |
| 68 | (list "--skip-printing-frames=10" "-R" "-") |
| 69 | "Initial args to pass to the mpg321 process.") |
| 70 | |
| 71 | (defvar emms-player-mpg321-remote-process-name "emms-player-mpg321-remote-proc" |
| 72 | "The name of the mpg321 remote player process") |
| 73 | |
| 74 | (defvar emms-player-mpg321-remote-ignore-stop 0 |
| 75 | "Number of stop messages to ignore, due to user action.") |
| 76 | |
| 77 | (defmacro emms-player-mpg321-remote-add (cmd func) |
| 78 | `(emms-player-set 'emms-player-mpg321-remote |
| 79 | ,cmd ,func)) |
| 80 | |
| 81 | (emms-player-mpg321-remote-add |
| 82 | 'regex (emms-player-simple-regexp "mp3" "mp2")) |
| 83 | (emms-player-mpg321-remote-add |
| 84 | 'pause 'emms-player-mpg321-remote-pause) |
| 85 | (emms-player-mpg321-remote-add |
| 86 | 'resume 'emms-player-mpg321-remote-pause) |
| 87 | (emms-player-mpg321-remote-add |
| 88 | 'seek 'emms-player-mpg321-remote-seek) |
| 89 | |
| 90 | ;; -------------------------------------------------- |
| 91 | ;; Process maintenence |
| 92 | ;; -------------------------------------------------- |
| 93 | |
| 94 | (defun emms-player-mpg321-remote-start-process () |
| 95 | "Start a new remote process, and return the process." |
| 96 | (let ((process (apply #'start-process |
| 97 | emms-player-mpg321-remote-process-name |
| 98 | nil |
| 99 | emms-player-mpg321-remote-command |
| 100 | (append emms-player-mpg321-remote-initial-args |
| 101 | emms-player-mpg321-remote-parameters)))) |
| 102 | (set-process-sentinel process #'emms-player-mpg321-remote-sentinel) |
| 103 | (set-process-filter process #'emms-player-mpg321-remote-filter) |
| 104 | process)) |
| 105 | |
| 106 | (defun emms-player-mpg321-remote-stop () |
| 107 | "Stop the currently playing process, if indeed there is one" |
| 108 | (let ((process (emms-player-mpg321-remote-process))) |
| 109 | (when process |
| 110 | (kill-process process) |
| 111 | (delete-process process)))) |
| 112 | |
| 113 | (defun emms-player-mpg321-remote-process () |
| 114 | "Return the remote process, if it exists." |
| 115 | (get-process emms-player-mpg321-remote-process-name)) |
| 116 | |
| 117 | (defun emms-player-mpg321-remote-running-p () |
| 118 | "True if the remote process exists and is running." |
| 119 | (let ((proc (emms-player-mpg321-remote-process))) |
| 120 | (and proc |
| 121 | (eq (process-status proc) 'run)))) |
| 122 | |
| 123 | (defun emms-player-mpg321-remote-sentinel (proc str) |
| 124 | "Sentinel for determining the end of process" |
| 125 | (ignore str) |
| 126 | (when (or (eq (process-status proc) 'exit) |
| 127 | (eq (process-status proc) 'signal)) |
| 128 | ;; reset |
| 129 | (setq emms-player-mpg321-remote-ignore-stop 0) |
| 130 | (message "Remote process died!"))) |
| 131 | |
| 132 | (defun emms-player-mpg321-remote-send (text) |
| 133 | "Send TEXT to the mpg321 remote process, and add a newline." |
| 134 | (let (proc) |
| 135 | ;; we shouldn't be trying to send to a dead process |
| 136 | (unless (emms-player-mpg321-remote-running-p) |
| 137 | (emms-player-mpg321-remote-start-process)) |
| 138 | (setq proc (emms-player-mpg321-remote-process)) |
| 139 | (process-send-string proc (concat text "\n")))) |
| 140 | |
| 141 | ;; -------------------------------------------------- |
| 142 | ;; Interfacing with emms |
| 143 | ;; -------------------------------------------------- |
| 144 | |
| 145 | (defun emms-player-mpg321-remote-filter (proc str) |
| 146 | (ignore proc) |
| 147 | (let* ((data-lines (split-string str "\n" t)) |
| 148 | data |
| 149 | cmd) |
| 150 | (dolist (line data-lines) |
| 151 | (setq data (split-string line)) |
| 152 | (setq cmd (car data)) |
| 153 | (cond |
| 154 | ;; stop notice |
| 155 | ((and (string= cmd "@P") |
| 156 | (or (string= (cadr data) "0") |
| 157 | (string= (cadr data) "3"))) |
| 158 | (emms-player-mpg321-remote-notify-emms)) |
| 159 | ;; frame notice |
| 160 | ((string= cmd "@F") |
| 161 | ;; even though a timer is constantly updating this variable, |
| 162 | ;; updating it here will cause it to stay pretty much in sync. |
| 163 | (run-hook-with-args 'emms-player-time-set-functions |
| 164 | (truncate (string-to-number (nth 3 data))))))))) |
| 165 | |
| 166 | (defun emms-player-mpg321-remote-start-playing (track) |
| 167 | "Start playing a song by telling the remote process to play it. |
| 168 | If the remote process is not running, launch it." |
| 169 | (unless (emms-player-mpg321-remote-running-p) |
| 170 | (emms-player-mpg321-remote-start-process)) |
| 171 | (emms-player-mpg321-remote-play-track track)) |
| 172 | |
| 173 | (defvar emms-player-ignore-stop) |
| 174 | |
| 175 | (defun emms-player-mpg321-remote-notify-emms (&optional user-action) |
| 176 | "Tell emms that the current song has finished. |
| 177 | If USER-ACTION, set `emms-player-mpg321-remote-ignore-stop' so that we |
| 178 | ignore the next message from mpg321." |
| 179 | (if user-action |
| 180 | (let ((emms-player-ignore-stop t)) |
| 181 | ;; so we ignore the next stop message |
| 182 | (setq emms-player-mpg321-remote-ignore-stop |
| 183 | (1+ emms-player-mpg321-remote-ignore-stop)) |
| 184 | (emms-player-stopped)) |
| 185 | ;; not a user action |
| 186 | (if (not (zerop emms-player-mpg321-remote-ignore-stop)) |
| 187 | (setq emms-player-mpg321-remote-ignore-stop |
| 188 | (1- emms-player-mpg321-remote-ignore-stop)) |
| 189 | (emms-player-stopped)))) |
| 190 | |
| 191 | (defun emms-player-mpg321-remote-stop-playing () |
| 192 | "Stop the current song playing." |
| 193 | (emms-player-mpg321-remote-notify-emms t) |
| 194 | (emms-player-mpg321-remote-send "stop")) |
| 195 | |
| 196 | (defun emms-player-mpg321-remote-play-track (track) |
| 197 | "Send a play command to the remote, based on TRACK." |
| 198 | (emms-player-mpg321-remote-send |
| 199 | (concat "load " (emms-track-get track 'name))) |
| 200 | (emms-player-started 'emms-player-mpg321-remote)) |
| 201 | |
| 202 | (defun emms-player-mpg321-remote-playable-p (track) |
| 203 | ;; use the simple definition. |
| 204 | (emms-player-mpg321-playable-p track)) |
| 205 | |
| 206 | (defun emms-player-mpg321-remote-pause () |
| 207 | "Pause the player." |
| 208 | (emms-player-mpg321-remote-send "pause")) |
| 209 | |
| 210 | (defun emms-player-mpg321-remote-resume () |
| 211 | "Resume the player." |
| 212 | (emms-player-mpg321-remote-send "pause")) |
| 213 | |
| 214 | (defun emms-player-mpg321-remote-seek (seconds) |
| 215 | "Seek forward or backward in the file." |
| 216 | ;; since mpg321 only supports seeking by frames, not seconds, we |
| 217 | ;; make a very rough guess as to how much a second constitutes |
| 218 | (let ((frame-string (number-to-string (* 35 seconds)))) |
| 219 | ;; if we're not going backwards, we need to add a '+' |
| 220 | (unless (eq ?- (string-to-char frame-string)) |
| 221 | (setq frame-string (concat "+" frame-string))) |
| 222 | (emms-player-mpg321-remote-send (concat "jump " frame-string)))) |
| 223 | |
| 224 | (provide 'emms-player-mpg321-remote) |
| 225 | ;;; emms-player-mpg321-remote.el ends here |