Blame


1 508e5f5f 2022-07-10 op #!/usr/bin/env perl
2 508e5f5f 2022-07-10 op #
3 508e5f5f 2022-07-10 op # Copyright (c) 2022 Omar Polo <op@omarpolo.com>
4 508e5f5f 2022-07-10 op #
5 508e5f5f 2022-07-10 op # Permission to use, copy, modify, and distribute this software for any
6 508e5f5f 2022-07-10 op # purpose with or without fee is hereby granted, provided that the above
7 508e5f5f 2022-07-10 op # copyright notice and this permission notice appear in all copies.
8 508e5f5f 2022-07-10 op #
9 508e5f5f 2022-07-10 op # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 508e5f5f 2022-07-10 op # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 508e5f5f 2022-07-10 op # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 508e5f5f 2022-07-10 op # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 508e5f5f 2022-07-10 op # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 508e5f5f 2022-07-10 op # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 508e5f5f 2022-07-10 op # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 508e5f5f 2022-07-10 op
17 508e5f5f 2022-07-10 op use strict;
18 508e5f5f 2022-07-10 op use warnings;
19 508e5f5f 2022-07-10 op use v5.12;
20 508e5f5f 2022-07-10 op
21 508e5f5f 2022-07-10 op use Curses;
22 508e5f5f 2022-07-10 op use POSIX ":sys_wait_h";
23 508e5f5f 2022-07-10 op use Text::CharWidth qw(mbswidth);
24 508e5f5f 2022-07-10 op use IO::Poll qw(POLLIN);
25 508e5f5f 2022-07-10 op use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
26 508e5f5f 2022-07-10 op use Getopt::Long qw(:config bundling);
27 508e5f5f 2022-07-10 op use Pod::Usage;
28 508e5f5f 2022-07-10 op
29 3a0b3cb8 2022-07-10 op my $run = 1;
30 3a0b3cb8 2022-07-10 op
31 508e5f5f 2022-07-10 op my $pfile;
32 508e5f5f 2022-07-10 op my $trim = "";
33 508e5f5f 2022-07-10 op
34 508e5f5f 2022-07-10 op my $pair_n = 1;
35 508e5f5f 2022-07-10 op
36 508e5f5f 2022-07-10 op my @songs;
37 508e5f5f 2022-07-10 op my $playlist_cur;
38 508e5f5f 2022-07-10 op my $playlist_max;
39 508e5f5f 2022-07-10 op my $time_cur;
40 508e5f5f 2022-07-10 op my $time_dur;
41 508e5f5f 2022-07-10 op my $status;
42 508e5f5f 2022-07-10 op my $repeat;
43 508e5f5f 2022-07-10 op
44 508e5f5f 2022-07-10 op my $last_lines;
45 508e5f5f 2022-07-10 op
46 508e5f5f 2022-07-10 op sub min {
47 508e5f5f 2022-07-10 op my ($a, $b) = @_;
48 508e5f5f 2022-07-10 op return $a < $b ? $a : $b;
49 508e5f5f 2022-07-10 op }
50 508e5f5f 2022-07-10 op
51 508e5f5f 2022-07-10 op sub max {
52 508e5f5f 2022-07-10 op my ($a, $b) = @_;
53 508e5f5f 2022-07-10 op return $a > $b ? $a : $b;
54 508e5f5f 2022-07-10 op }
55 508e5f5f 2022-07-10 op
56 508e5f5f 2022-07-10 op sub excerpt {
57 508e5f5f 2022-07-10 op my $lines = int(shift);
58 508e5f5f 2022-07-10 op my $ctx = $lines * 2 + 1;
59 508e5f5f 2022-07-10 op my @tmp;
60 508e5f5f 2022-07-10 op
61 508e5f5f 2022-07-10 op open (my $fh, "-|", "amused show -p | grep -C$ctx '^>'");
62 508e5f5f 2022-07-10 op my $x = 0;
63 508e5f5f 2022-07-10 op my $cur = 0;
64 508e5f5f 2022-07-10 op while (<$fh>) {
65 508e5f5f 2022-07-10 op chomp;
66 508e5f5f 2022-07-10 op s,$trim,,;
67 508e5f5f 2022-07-10 op push @tmp, $_;
68 508e5f5f 2022-07-10 op $cur = $x if m/^>/;
69 508e5f5f 2022-07-10 op $x++;
70 508e5f5f 2022-07-10 op }
71 508e5f5f 2022-07-10 op close($fh);
72 508e5f5f 2022-07-10 op
73 508e5f5f 2022-07-10 op my $from;
74 508e5f5f 2022-07-10 op if ($cur - $lines < 0) {
75 508e5f5f 2022-07-10 op $from = max($cur - $lines, 0);
76 508e5f5f 2022-07-10 op } elsif ($cur + $lines >= $#tmp) {
77 508e5f5f 2022-07-10 op $from = max($#tmp - $lines * 2 - 1, 0);
78 508e5f5f 2022-07-10 op } else {
79 508e5f5f 2022-07-10 op $from = $cur - $lines;
80 508e5f5f 2022-07-10 op }
81 508e5f5f 2022-07-10 op
82 508e5f5f 2022-07-10 op my $len = min($lines * 2 + 1, $#tmp);
83 508e5f5f 2022-07-10 op my @x = splice @tmp, $from, $len + 1;
84 508e5f5f 2022-07-10 op return @x;
85 508e5f5f 2022-07-10 op }
86 508e5f5f 2022-07-10 op
87 508e5f5f 2022-07-10 op sub playlist_numbers {
88 508e5f5f 2022-07-10 op my ($cur, $tot, $found) = (0, 0, 0);
89 508e5f5f 2022-07-10 op open (my $fh, "-|", "amused", "show", "-p");
90 508e5f5f 2022-07-10 op while (<$fh>) {
91 508e5f5f 2022-07-10 op $tot++;
92 508e5f5f 2022-07-10 op $cur++ unless $found;
93 508e5f5f 2022-07-10 op $found = 1 if m/^>/;
94 508e5f5f 2022-07-10 op }
95 508e5f5f 2022-07-10 op close($fh);
96 508e5f5f 2022-07-10 op return ($cur, $tot);
97 508e5f5f 2022-07-10 op }
98 508e5f5f 2022-07-10 op
99 508e5f5f 2022-07-10 op sub status {
100 508e5f5f 2022-07-10 op my ($pos, $dur, $repeat);
101 508e5f5f 2022-07-10 op
102 508e5f5f 2022-07-10 op open (my $fh, "-|", "amused", "status", "-f",
103 508e5f5f 2022-07-10 op "status,time:raw,repeat:oneline");
104 508e5f5f 2022-07-10 op my $status = <$fh>;
105 508e5f5f 2022-07-10 op chomp $status;
106 508e5f5f 2022-07-10 op while (<$fh>) {
107 508e5f5f 2022-07-10 op chomp;
108 508e5f5f 2022-07-10 op $pos = s/position //r if m/^position /;
109 508e5f5f 2022-07-10 op $dur = s/duration //r if m/^duration /;
110 508e5f5f 2022-07-10 op $repeat = $_ if m/^repeat/;
111 508e5f5f 2022-07-10 op }
112 508e5f5f 2022-07-10 op close($fh);
113 508e5f5f 2022-07-10 op return ($status, $pos, $dur, $repeat);
114 508e5f5f 2022-07-10 op }
115 508e5f5f 2022-07-10 op
116 508e5f5f 2022-07-10 op sub showtime {
117 508e5f5f 2022-07-10 op my $seconds = shift;
118 508e5f5f 2022-07-10 op my $str = "";
119 508e5f5f 2022-07-10 op
120 508e5f5f 2022-07-10 op if ($seconds > 3600) {
121 508e5f5f 2022-07-10 op my $hours = int($seconds / 3600);
122 508e5f5f 2022-07-10 op $seconds -= $hours * 3600;
123 508e5f5f 2022-07-10 op $str = sprintf("%02:", $hours);
124 508e5f5f 2022-07-10 op }
125 508e5f5f 2022-07-10 op
126 508e5f5f 2022-07-10 op my $minutes = int($seconds / 60);
127 508e5f5f 2022-07-10 op $seconds -= $minutes * 60;
128 508e5f5f 2022-07-10 op $str .= sprintf "%02d:%02d", $minutes, $seconds;
129 508e5f5f 2022-07-10 op return $str;
130 508e5f5f 2022-07-10 op }
131 508e5f5f 2022-07-10 op
132 508e5f5f 2022-07-10 op sub center {
133 508e5f5f 2022-07-10 op my ($str, $pstr) = @_;
134 508e5f5f 2022-07-10 op my $width = mbswidth($str);
135 508e5f5f 2022-07-10 op return $str if $width > $COLS;
136 508e5f5f 2022-07-10 op my $pre = ($COLS - $width) / 2;
137 508e5f5f 2022-07-10 op my $lpad = $pstr x $pre;
138 508e5f5f 2022-07-10 op my $rpad = $pstr x ($COLS - $width - $pre + 1);
139 508e5f5f 2022-07-10 op return ($lpad, $str, $rpad);
140 508e5f5f 2022-07-10 op }
141 508e5f5f 2022-07-10 op
142 508e5f5f 2022-07-10 op sub offsets {
143 508e5f5f 2022-07-10 op my ($y, $x, $cur, $max) = @_;
144 508e5f5f 2022-07-10 op my ($pre, $c, $post) = center(" $cur / $max ", '-');
145 508e5f5f 2022-07-10 op addstring $y, $x, "";
146 508e5f5f 2022-07-10 op
147 508e5f5f 2022-07-10 op my $p = COLOR_PAIR($pair_n);
148 508e5f5f 2022-07-10 op
149 508e5f5f 2022-07-10 op attron $p;
150 508e5f5f 2022-07-10 op addstring $pre;
151 508e5f5f 2022-07-10 op attroff $p;
152 508e5f5f 2022-07-10 op
153 508e5f5f 2022-07-10 op addstring $c;
154 508e5f5f 2022-07-10 op
155 508e5f5f 2022-07-10 op attron $p;
156 508e5f5f 2022-07-10 op addstring $post;
157 508e5f5f 2022-07-10 op attroff $p;
158 508e5f5f 2022-07-10 op }
159 508e5f5f 2022-07-10 op
160 508e5f5f 2022-07-10 op sub progress {
161 508e5f5f 2022-07-10 op my ($y, $x, $pos, $dur) = @_;
162 508e5f5f 2022-07-10 op
163 508e5f5f 2022-07-10 op my $pstr = showtime $pos;
164 508e5f5f 2022-07-10 op my $dstr = showtime $dur;
165 508e5f5f 2022-07-10 op
166 508e5f5f 2022-07-10 op my $len = $COLS - length($pstr) - length($dstr) - 4;
167 127f376a 2022-07-11 op return if $len <= 0 or $dur <= 0;
168 508e5f5f 2022-07-10 op my $filled = int($pos * $len / $dur);
169 508e5f5f 2022-07-10 op
170 508e5f5f 2022-07-10 op addstring $y, $x, "$pstr [";
171 508e5f5f 2022-07-10 op addstring "#" x $filled;
172 508e5f5f 2022-07-10 op addstring " " x max($len - $filled, 0);
173 508e5f5f 2022-07-10 op addstring "] $dstr";
174 508e5f5f 2022-07-10 op }
175 508e5f5f 2022-07-10 op
176 508e5f5f 2022-07-10 op sub show_status {
177 508e5f5f 2022-07-10 op my ($y, $x, $status) = @_;
178 508e5f5f 2022-07-10 op my ($pre, $c, $post) = center($status, ' ');
179 508e5f5f 2022-07-10 op addstring $y, $x, $pre;
180 508e5f5f 2022-07-10 op addstring $c;
181 508e5f5f 2022-07-10 op addstring $post;
182 508e5f5f 2022-07-10 op }
183 508e5f5f 2022-07-10 op
184 508e5f5f 2022-07-10 op sub show_repeat {
185 508e5f5f 2022-07-10 op my ($y, $x, $repeat) = @_;
186 508e5f5f 2022-07-10 op my ($pre, $c, $post) = center($repeat, ' ');
187 508e5f5f 2022-07-10 op addstring $y, $x, $pre;
188 508e5f5f 2022-07-10 op addstring $c;
189 508e5f5f 2022-07-10 op addstring $post;
190 508e5f5f 2022-07-10 op }
191 508e5f5f 2022-07-10 op
192 508e5f5f 2022-07-10 op sub render {
193 508e5f5f 2022-07-10 op erase;
194 508e5f5f 2022-07-10 op if ($LINES < 4 || $COLS < 20) {
195 508e5f5f 2022-07-10 op addstring "window too small";
196 508e5f5f 2022-07-10 op refresh;
197 508e5f5f 2022-07-10 op return;
198 508e5f5f 2022-07-10 op }
199 508e5f5f 2022-07-10 op
200 508e5f5f 2022-07-10 op my $song_pad = "";
201 508e5f5f 2022-07-10 op my $longest = 0;
202 508e5f5f 2022-07-10 op $longest = max $longest, length($_) foreach @songs;
203 508e5f5f 2022-07-10 op if ($longest < $COLS) {
204 508e5f5f 2022-07-10 op $song_pad = " " x (($COLS - $longest)/2);
205 508e5f5f 2022-07-10 op }
206 508e5f5f 2022-07-10 op
207 508e5f5f 2022-07-10 op my $line = 0;
208 508e5f5f 2022-07-10 op map {
209 508e5f5f 2022-07-10 op attron(A_BOLD) if m/^>/;
210 508e5f5f 2022-07-10 op addstring $line++, 0, $song_pad . $_;
211 508e5f5f 2022-07-10 op standend;
212 508e5f5f 2022-07-10 op } @songs;
213 508e5f5f 2022-07-10 op
214 508e5f5f 2022-07-10 op offsets $LINES - 4, 0, $playlist_cur, $playlist_max;
215 508e5f5f 2022-07-10 op progress $LINES - 3, 0, $time_cur, $time_dur;
216 508e5f5f 2022-07-10 op show_status $LINES - 2, 0, $status;
217 508e5f5f 2022-07-10 op show_repeat $LINES - 1, 0, $repeat;
218 508e5f5f 2022-07-10 op
219 508e5f5f 2022-07-10 op refresh;
220 508e5f5f 2022-07-10 op }
221 508e5f5f 2022-07-10 op
222 508e5f5f 2022-07-10 op sub getsongs {
223 508e5f5f 2022-07-10 op $last_lines = $LINES;
224 508e5f5f 2022-07-10 op @songs = excerpt($LINES / 2 - 3);
225 508e5f5f 2022-07-10 op }
226 508e5f5f 2022-07-10 op
227 508e5f5f 2022-07-10 op sub getnums {
228 508e5f5f 2022-07-10 op ($playlist_cur, $playlist_max) = playlist_numbers;
229 508e5f5f 2022-07-10 op }
230 508e5f5f 2022-07-10 op
231 508e5f5f 2022-07-10 op sub getstatus {
232 508e5f5f 2022-07-10 op ($status, $time_cur, $time_dur, $repeat) = status;
233 508e5f5f 2022-07-10 op }
234 508e5f5f 2022-07-10 op
235 508e5f5f 2022-07-10 op sub save {
236 508e5f5f 2022-07-10 op return unless defined $pfile;
237 508e5f5f 2022-07-10 op
238 508e5f5f 2022-07-10 op open(my $fh, ">", $pfile);
239 508e5f5f 2022-07-10 op open(my $ph, "-|", "amused", "show", "-p");
240 508e5f5f 2022-07-10 op
241 508e5f5f 2022-07-10 op print $fh $_ while (<$ph>);
242 508e5f5f 2022-07-10 op }
243 508e5f5f 2022-07-10 op
244 508e5f5f 2022-07-10 op sub hevent {
245 508e5f5f 2022-07-10 op my $fh = shift;
246 508e5f5f 2022-07-10 op my $l = <$fh>;
247 508e5f5f 2022-07-10 op die "monitor quit" unless defined($l);
248 508e5f5f 2022-07-10 op getstatus;
249 508e5f5f 2022-07-10 op getnums if $l =~ m/load|jump|next|prev/;
250 508e5f5f 2022-07-10 op getsongs if $l =~ m/load|jump|next|prev/;
251 508e5f5f 2022-07-10 op }
252 508e5f5f 2022-07-10 op
253 508e5f5f 2022-07-10 op sub hinput {
254 508e5f5f 2022-07-10 op my ($ch, $key) = getchar;
255 508e5f5f 2022-07-10 op if (defined $ch) {
256 508e5f5f 2022-07-10 op if ($ch eq " ") {
257 508e5f5f 2022-07-10 op system "amused", "toggle";
258 508e5f5f 2022-07-10 op } elsif ($ch eq "<" or $ch eq "p") {
259 508e5f5f 2022-07-10 op system "amused", "prev";
260 508e5f5f 2022-07-10 op } elsif ($ch eq ">" or $ch eq "n") {
261 508e5f5f 2022-07-10 op system "amused", "next";
262 508e5f5f 2022-07-10 op } elsif ($ch eq ",") {
263 508e5f5f 2022-07-10 op system "amused", "seek", "-5";
264 508e5f5f 2022-07-10 op } elsif ($ch eq ".") {
265 508e5f5f 2022-07-10 op system "amused", "seek", "+5";
266 508e5f5f 2022-07-10 op } elsif ($ch eq "S") {
267 508e5f5f 2022-07-10 op system "amused show | sort -u | amused load";
268 508e5f5f 2022-07-10 op } elsif ($ch eq "R") {
269 508e5f5f 2022-07-10 op system "amused show | sort -R | amused load";
270 508e5f5f 2022-07-10 op } elsif ($ch eq "s") {
271 508e5f5f 2022-07-10 op save;
272 508e5f5f 2022-07-10 op } elsif ($ch eq "q") {
273 3a0b3cb8 2022-07-10 op $run = 0;
274 508e5f5f 2022-07-10 op }
275 508e5f5f 2022-07-10 op } elsif (defined $key) {
276 508e5f5f 2022-07-10 op # todo?
277 508e5f5f 2022-07-10 op }
278 508e5f5f 2022-07-10 op }
279 508e5f5f 2022-07-10 op
280 508e5f5f 2022-07-10 op GetOptions(
281 508e5f5f 2022-07-10 op "p:s" => \$pfile,
282 508e5f5f 2022-07-10 op "t:s" => \$trim,
283 508e5f5f 2022-07-10 op ) or pod2usage(1);
284 508e5f5f 2022-07-10 op
285 508e5f5f 2022-07-10 op my $mpid = open(my $monitor, "-|", "amused", "monitor",
286 508e5f5f 2022-07-10 op "load,jump,seek,next,prev,play,pause,toggle,stop,repeat")
287 508e5f5f 2022-07-10 op or die "can't spawn amused monitor";
288 508e5f5f 2022-07-10 op
289 508e5f5f 2022-07-10 op initscr;
290 508e5f5f 2022-07-10 op start_color;
291 508e5f5f 2022-07-10 op use_default_colors;
292 508e5f5f 2022-07-10 op init_pair $pair_n, 250, -1;
293 508e5f5f 2022-07-10 op
294 508e5f5f 2022-07-10 op timeout 1000;
295 508e5f5f 2022-07-10 op scrollok 0;
296 508e5f5f 2022-07-10 op curs_set 0;
297 508e5f5f 2022-07-10 op
298 508e5f5f 2022-07-10 op my $poll = IO::Poll->new();
299 508e5f5f 2022-07-10 op $poll->mask(\*STDIN => POLLIN);
300 508e5f5f 2022-07-10 op $poll->mask($monitor => POLLIN);
301 508e5f5f 2022-07-10 op
302 508e5f5f 2022-07-10 op my $tick = 0;
303 508e5f5f 2022-07-10 op my $tbefore = clock_gettime(CLOCK_MONOTONIC);
304 508e5f5f 2022-07-10 op
305 508e5f5f 2022-07-10 op getsongs;
306 508e5f5f 2022-07-10 op getnums;
307 508e5f5f 2022-07-10 op getstatus;
308 508e5f5f 2022-07-10 op render;
309 508e5f5f 2022-07-10 op
310 3a0b3cb8 2022-07-10 op while ($run) {
311 508e5f5f 2022-07-10 op $poll->poll(0.25);
312 508e5f5f 2022-07-10 op my $now = clock_gettime(CLOCK_MONOTONIC);
313 508e5f5f 2022-07-10 op my $elapsed = $now - $tbefore;
314 508e5f5f 2022-07-10 op if ($elapsed > 1) {
315 508e5f5f 2022-07-10 op $tbefore = $now;
316 508e5f5f 2022-07-10 op $time_cur += int($elapsed + 0.5)
317 508e5f5f 2022-07-10 op if $status =~ m/^playing/;
318 508e5f5f 2022-07-10 op }
319 508e5f5f 2022-07-10 op
320 508e5f5f 2022-07-10 op ($status, $time_cur, $time_dur, $repeat) = status
321 508e5f5f 2022-07-10 op unless $tick++ % 8;
322 508e5f5f 2022-07-10 op
323 508e5f5f 2022-07-10 op hinput if $poll->events(\*STDIN) & POLLIN;
324 508e5f5f 2022-07-10 op hevent $monitor if $poll->events($monitor) & POLLIN;
325 508e5f5f 2022-07-10 op
326 508e5f5f 2022-07-10 op getsongs if $LINES != $last_lines;
327 508e5f5f 2022-07-10 op
328 508e5f5f 2022-07-10 op render;
329 508e5f5f 2022-07-10 op }
330 508e5f5f 2022-07-10 op
331 508e5f5f 2022-07-10 op endwin;
332 508e5f5f 2022-07-10 op save;
333 508e5f5f 2022-07-10 op
334 508e5f5f 2022-07-10 op kill 'INT', $mpid;
335 508e5f5f 2022-07-10 op wait;
336 508e5f5f 2022-07-10 op
337 508e5f5f 2022-07-10 op __END__
338 508e5f5f 2022-07-10 op
339 508e5f5f 2022-07-10 op =pod
340 508e5f5f 2022-07-10 op
341 508e5f5f 2022-07-10 op =head1 NAME
342 508e5f5f 2022-07-10 op
343 508e5f5f 2022-07-10 op amused-monitor - curses interface for amused(1)
344 508e5f5f 2022-07-10 op
345 508e5f5f 2022-07-10 op =head1 SYNOPSIS
346 508e5f5f 2022-07-10 op
347 508e5f5f 2022-07-10 op B<amused-monitor> [B<-p> I<playlist>] [B<-t> I<string>]
348 508e5f5f 2022-07-10 op
349 508e5f5f 2022-07-10 op =head1 DESCRIPTION
350 508e5f5f 2022-07-10 op
351 508e5f5f 2022-07-10 op amused-monitor is a simple curses interface for amused(1).
352 508e5f5f 2022-07-10 op
353 508e5f5f 2022-07-10 op The following options are available:
354 508e5f5f 2022-07-10 op
355 508e5f5f 2022-07-10 op =over 12
356 508e5f5f 2022-07-10 op
357 508e5f5f 2022-07-10 op =item B<-p> I<playlist>
358 508e5f5f 2022-07-10 op
359 508e5f5f 2022-07-10 op Save the current playling queue to the file I<playlist> upon exit or
360 508e5f5f 2022-07-10 op I<s> key.
361 508e5f5f 2022-07-10 op
362 508e5f5f 2022-07-10 op =item B<-t> I<string>
363 508e5f5f 2022-07-10 op
364 508e5f5f 2022-07-10 op Trim out the given I<string> from every song in the playlist view.
365 508e5f5f 2022-07-10 op
366 508e5f5f 2022-07-10 op =back
367 508e5f5f 2022-07-10 op
368 508e5f5f 2022-07-10 op The following key-bindings are available:
369 508e5f5f 2022-07-10 op
370 508e5f5f 2022-07-10 op =over 8
371 508e5f5f 2022-07-10 op
372 508e5f5f 2022-07-10 op =item space
373 508e5f5f 2022-07-10 op
374 508e5f5f 2022-07-10 op Toggle play/pause.
375 508e5f5f 2022-07-10 op
376 508e5f5f 2022-07-10 op =item < or p
377 508e5f5f 2022-07-10 op
378 508e5f5f 2022-07-10 op Play previous song.
379 508e5f5f 2022-07-10 op
380 508e5f5f 2022-07-10 op =item > or n
381 508e5f5f 2022-07-10 op
382 508e5f5f 2022-07-10 op Play next song.
383 508e5f5f 2022-07-10 op
384 508e5f5f 2022-07-10 op =item ,
385 508e5f5f 2022-07-10 op
386 508e5f5f 2022-07-10 op Seek backward by five seconds.
387 508e5f5f 2022-07-10 op
388 508e5f5f 2022-07-10 op =item .
389 508e5f5f 2022-07-10 op
390 508e5f5f 2022-07-10 op Seek forward by five seconds.
391 508e5f5f 2022-07-10 op
392 508e5f5f 2022-07-10 op =item R
393 508e5f5f 2022-07-10 op
394 508e5f5f 2022-07-10 op Randomize the playlist.
395 508e5f5f 2022-07-10 op
396 508e5f5f 2022-07-10 op =item S
397 508e5f5f 2022-07-10 op
398 508e5f5f 2022-07-10 op Sort the playlist.
399 508e5f5f 2022-07-10 op
400 508e5f5f 2022-07-10 op =item s
401 508e5f5f 2022-07-10 op
402 508e5f5f 2022-07-10 op Save the status to the file given with the B<-p> flag.
403 508e5f5f 2022-07-10 op
404 508e5f5f 2022-07-10 op =item q
405 508e5f5f 2022-07-10 op
406 508e5f5f 2022-07-10 op Quit.
407 508e5f5f 2022-07-10 op
408 508e5f5f 2022-07-10 op =back
409 508e5f5f 2022-07-10 op
410 508e5f5f 2022-07-10 op =head1 SEE ALSO
411 508e5f5f 2022-07-10 op
412 508e5f5f 2022-07-10 op amused(1)
413 508e5f5f 2022-07-10 op
414 508e5f5f 2022-07-10 op =cut