@involvex/youtube-music-cli 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/source/components/common/Help.js +1 -1
- package/dist/source/components/common/ShortcutsBar.js +1 -1
- package/dist/source/components/player/NowPlaying.js +1 -1
- package/dist/source/components/player/PlayerControls.js +3 -2
- package/dist/source/hooks/useKeyboard.js +4 -1
- package/dist/source/services/player/player.service.d.ts +1 -0
- package/dist/source/services/player/player.service.js +12 -1
- package/dist/source/stores/player.store.d.ts +1 -0
- package/dist/source/stores/player.store.js +19 -2
- package/dist/source/utils/constants.d.ts +2 -2
- package/dist/source/utils/constants.js +2 -2
- package/dist/youtube-music-cli.exe +0 -0
- package/package.json +2 -1
- package/readme.md +11 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## [0.0.16](https://github.com/involvex/youtube-music-cli/compare/v0.0.15...v0.0.16) (2026-02-18)
|
|
2
|
+
|
|
3
|
+
### Bug Fixes
|
|
4
|
+
|
|
5
|
+
- correct shuffle hotkey and mpv error handling ([7a08643](https://github.com/involvex/youtube-music-cli/commit/7a0864395b48c8bc1b30d817edc5b2f7cb9c79c6))
|
|
6
|
+
|
|
7
|
+
### Features
|
|
8
|
+
|
|
9
|
+
- implement shuffle mode with Shift+S hotkey ([77be9ae](https://github.com/involvex/youtube-music-cli/commit/77be9ae53650dc3bf40a89427fbb1b6eef32683a))
|
|
10
|
+
|
|
11
|
+
## [0.0.15](https://github.com/involvex/youtube-music-cli/compare/v0.0.14...v0.0.15) (2026-02-18)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- **ui:** remove escape key from quit keybinding ([df6e794](https://github.com/involvex/youtube-music-cli/commit/df6e794583aa96a1dd27b1ff208f3f1b69249829))
|
|
16
|
+
|
|
17
|
+
### BREAKING CHANGES
|
|
18
|
+
|
|
19
|
+
- **ui:** 'escape' no longer quits; use 'q' instead.
|
|
20
|
+
|
|
1
21
|
## [0.0.14](https://github.com/involvex/youtube-music-cli/compare/v0.0.13...v0.0.14) (2026-02-18)
|
|
2
22
|
|
|
3
23
|
### Features
|
|
@@ -6,5 +6,5 @@ import { useNavigation } from "../../hooks/useNavigation.js";
|
|
|
6
6
|
export default function Help() {
|
|
7
7
|
const { theme } = useTheme();
|
|
8
8
|
const { dispatch: _dispatch } = useNavigation();
|
|
9
|
-
return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Keyboard Shortcuts" }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Global" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "q
|
|
9
|
+
return (_jsxs(Box, { flexDirection: "column", gap: 1, padding: 1, children: [_jsx(Box, { borderStyle: "single", borderColor: theme.colors.secondary, paddingX: 1, children: _jsx(Text, { bold: true, color: theme.colors.primary, children: "Keyboard Shortcuts" }) }), _jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsx(Text, { bold: true, color: theme.colors.secondary, children: "Global" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "q" }), " - Quit", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "?" }), " - Help", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "/" }), " - Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+P" }), " - Playlists", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "p" }), " - Plugins", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "g" }), " - Suggestions", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "," }), " - Settings"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Player" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Space" }), " - Play/Pause", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "n" }), " - Next", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "b" }), " - Previous", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "=" }), " - Volume Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "-" }), " - Volume Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+S" }), " - Toggle Shuffle", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "r" }), " - Toggle Repeat"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Navigation" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Up" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "k" }), " - Move Up", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Down" }), " /", _jsx(Text, { children: " " }), _jsx(Text, { color: theme.colors.text, children: "j" }), " - Move Down", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Enter" }), " - Select", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Go Back"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Search" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "Tab" }), " - Switch Search Type", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "m" }), " - Create Mix Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+D" }), " - Download selection", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Esc" }), " - Clear Search", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "[ / ]" }), " - Results Limit"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "Playlist" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "a" }), " - Add to Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "d" }), " - Remove from Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "c" }), " - Create Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+D" }), " - Download Playlist", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "D" }), " - Delete Playlist"] }) }), _jsx(Text, { bold: true, color: theme.colors.secondary, children: "View" }), _jsx(Box, { paddingX: 2, children: _jsxs(Text, { children: [_jsx(Text, { color: theme.colors.text, children: "M" }), " - Toggle Mini Player", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "l" }), " - Lyrics", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "T" }), " - Trending", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "e" }), " - Explore"] }) }), _jsxs(Text, { color: theme.colors.dim, children: ["Press ", _jsx(Text, { color: theme.colors.text, children: "Esc" }), " or", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " to close"] })] })] }));
|
|
10
10
|
}
|
|
@@ -25,5 +25,5 @@ export default function ShortcutsBar() {
|
|
|
25
25
|
useKeyBinding(KEYBINDINGS.VOLUME_FINE_UP, volumeFineUp);
|
|
26
26
|
useKeyBinding(KEYBINDINGS.VOLUME_FINE_DOWN, volumeFineDown);
|
|
27
27
|
// Note: SETTINGS keybinding handled by MainLayout to avoid double-dispatch
|
|
28
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: ["Shortcuts: ", _jsx(Text, { color: theme.colors.text, children: "Space" }), " Play/Pause |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2192" }), " Next |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2190" }), " Prev |", ' ', _jsx(Text, { color: theme.colors.text, children: "Shift+P" }), " Playlists |", ' ', _jsx(Text, { color: theme.colors.text, children: "Shift+D" }), " Download |", ' ', _jsx(Text, { color: theme.colors.text, children: "m" }), " Mix |", ' ', _jsx(Text, { color: theme.colors.text, children: "M" }), " Mini |", ' ', _jsx(Text, { color: theme.colors.text, children: "/" }), " Search |", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " Help |", ' ', _jsx(Text, { color: theme.colors.text, children: "q" }), " Quit"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.dim, children: "[=/" }), "-", _jsx(Text, { color: theme.colors.dim, children: "]" }), " Vol:", ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
|
|
28
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: ["Shortcuts: ", _jsx(Text, { color: theme.colors.text, children: "Space" }), " Play/Pause |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2192" }), " Next |", ' ', _jsx(Text, { color: theme.colors.text, children: "\u2190" }), " Prev |", ' ', _jsx(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.text, children: "Shift+S" }), ' ', _jsxs(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ["Shuffle", playerState.shuffle ? ':ON' : ''] }), ' ', "| ", _jsx(Text, { color: theme.colors.text, children: "Shift+P" }), " Playlists |", ' ', _jsx(Text, { color: theme.colors.text, children: "Shift+D" }), " Download |", ' ', _jsx(Text, { color: theme.colors.text, children: "m" }), " Mix |", ' ', _jsx(Text, { color: theme.colors.text, children: "M" }), " Mini |", ' ', _jsx(Text, { color: theme.colors.text, children: "/" }), " Search |", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " Help |", ' ', _jsx(Text, { color: theme.colors.text, children: "q" }), " Quit"] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: theme.colors.dim, children: "[=/" }), "-", _jsx(Text, { color: theme.colors.dim, children: "]" }), " Vol:", ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
|
|
29
29
|
}
|
|
@@ -40,5 +40,5 @@ export default function NowPlaying() {
|
|
|
40
40
|
const percentage = duration > 0 ? Math.min(100, Math.floor((progress / duration) * 100)) : 0;
|
|
41
41
|
const barWidth = Math.max(10, columns - 8);
|
|
42
42
|
const filledWidth = duration > 0 ? Math.floor((progress / duration) * barWidth) : 0;
|
|
43
|
-
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.primary, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: track.title }), _jsx(Text, { color: theme.colors.dim, children: " \u2022 " }), _jsx(Text, { color: theme.colors.secondary, children: artists })] }), track.album && _jsx(Text, { color: theme.colors.dim, children: track.album.name }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '█'.repeat(Math.min(filledWidth, barWidth)) }), _jsx(Text, { color: theme.colors.dim, children: '░'.repeat(Math.max(0, barWidth - filledWidth)) })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.text, children: formatTime(progress) }), _jsxs(Text, { color: theme.colors.dim, children: [" / ", formatTime(duration), " "] }), _jsxs(Text, { color: theme.colors.dim, children: ["[", percentage, "%]"] }), playerState.isLoading && (_jsx(Text, { color: theme.colors.accent, children: " Loading..." })), !playerState.isPlaying && progress > 0 && (_jsx(Text, { color: theme.colors.dim, children: " \u23F8" })), sleepRemaining !== null && (_jsxs(Text, { color: theme.colors.warning, children: [' ', "\u23FE ", formatTime(sleepRemaining)] }))] }), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
|
|
43
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: theme.colors.primary, paddingX: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: theme.colors.primary, children: track.title }), _jsx(Text, { color: theme.colors.dim, children: " \u2022 " }), _jsx(Text, { color: theme.colors.secondary, children: artists })] }), track.album && _jsx(Text, { color: theme.colors.dim, children: track.album.name }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.primary, children: '█'.repeat(Math.min(filledWidth, barWidth)) }), _jsx(Text, { color: theme.colors.dim, children: '░'.repeat(Math.max(0, barWidth - filledWidth)) })] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.colors.text, children: formatTime(progress) }), _jsxs(Text, { color: theme.colors.dim, children: [" / ", formatTime(duration), " "] }), _jsxs(Text, { color: theme.colors.dim, children: ["[", percentage, "%]"] }), playerState.isLoading && (_jsx(Text, { color: theme.colors.accent, children: " Loading..." })), !playerState.isPlaying && progress > 0 && (_jsx(Text, { color: theme.colors.dim, children: " \u23F8" })), playerState.shuffle && _jsx(Text, { color: theme.colors.primary, children: " \uD83D\uDD00" }), sleepRemaining !== null && (_jsxs(Text, { color: theme.colors.warning, children: [' ', "\u23FE ", formatTime(sleepRemaining)] }))] }), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
|
|
44
44
|
}
|
|
@@ -17,7 +17,7 @@ export default function PlayerControls() {
|
|
|
17
17
|
};
|
|
18
18
|
}, [instanceId]);
|
|
19
19
|
const { theme } = useTheme();
|
|
20
|
-
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, speedUp, speedDown, } = usePlayer();
|
|
20
|
+
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, speedUp, speedDown, toggleShuffle, } = usePlayer();
|
|
21
21
|
// DEBUG: Log when callbacks change (detect instability)
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
// Temporarily output to stderr to debug without triggering Ink re-render
|
|
@@ -39,5 +39,6 @@ export default function PlayerControls() {
|
|
|
39
39
|
useKeyBinding(KEYBINDINGS.VOLUME_DOWN, volumeDown);
|
|
40
40
|
useKeyBinding(KEYBINDINGS.SPEED_UP, speedUp);
|
|
41
41
|
useKeyBinding(KEYBINDINGS.SPEED_DOWN, speedDown);
|
|
42
|
-
|
|
42
|
+
useKeyBinding(KEYBINDINGS.SHUFFLE, toggleShuffle);
|
|
43
|
+
return (_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", paddingX: 2, borderStyle: "classic", borderColor: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2190 / b" }), "] Prev"] }), _jsx(Text, { color: theme.colors.primary, children: playerState.isPlaying ? (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Pause"] })) : (_jsxs(Text, { children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Space" }), "] Play"] })) }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "\u2192 / n" }), "] Next"] }), _jsxs(Text, { color: theme.colors.text, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "+/-" }), "] Vol: ", playerState.volume, "%"] }), _jsxs(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "Shift+S" }), "]", ' ', playerState.shuffle ? '🔀 ON' : '🔀 OFF'] }), (playerState.speed ?? 1.0) !== 1.0 && (_jsxs(Text, { color: theme.colors.accent, children: ["[", _jsx(Text, { color: theme.colors.dim, children: "<>" }), "]", ' ', (playerState.speed ?? 1.0).toFixed(2), "x"] }))] }));
|
|
43
44
|
}
|
|
@@ -75,11 +75,14 @@ export function KeyboardManager() {
|
|
|
75
75
|
const hasMeta = parts.includes('meta') || parts.includes('alt');
|
|
76
76
|
const hasShift = parts.includes('shift');
|
|
77
77
|
const mainKey = parts[parts.length - 1];
|
|
78
|
+
const uppercaseShiftInput = input.length === 1 &&
|
|
79
|
+
input === input.toUpperCase() &&
|
|
80
|
+
input.toLowerCase() === mainKey;
|
|
78
81
|
if (hasCtrl && !key.ctrl)
|
|
79
82
|
return false;
|
|
80
83
|
if (hasMeta && !key.meta)
|
|
81
84
|
return false;
|
|
82
|
-
if (hasShift && !key.shift)
|
|
85
|
+
if (hasShift && !key.shift && !uppercaseShiftInput)
|
|
83
86
|
return false;
|
|
84
87
|
if (!hasShift && key.shift)
|
|
85
88
|
return false;
|
|
@@ -44,6 +44,13 @@ class PlayerService {
|
|
|
44
44
|
return `/tmp/mpvsocket-${process.pid}-${this.playSessionId}`;
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
+
getMpvCommand() {
|
|
48
|
+
const configuredPath = process.env['MPV_PATH']?.trim();
|
|
49
|
+
if (configuredPath) {
|
|
50
|
+
return configuredPath;
|
|
51
|
+
}
|
|
52
|
+
return process.platform === 'win32' ? 'mpv.exe' : 'mpv';
|
|
53
|
+
}
|
|
47
54
|
/**
|
|
48
55
|
* Connect to mpv IPC socket
|
|
49
56
|
*/
|
|
@@ -228,7 +235,7 @@ class PlayerService {
|
|
|
228
235
|
mpvArgs.push(playUrl);
|
|
229
236
|
// Capture process in local var so stale exit handlers from a killed
|
|
230
237
|
// process don't overwrite state belonging to a newly-spawned process.
|
|
231
|
-
const spawnedProcess = spawn(
|
|
238
|
+
const spawnedProcess = spawn(this.getMpvCommand(), mpvArgs);
|
|
232
239
|
this.mpvProcess = spawnedProcess;
|
|
233
240
|
if (!spawnedProcess.stdout || !spawnedProcess.stderr) {
|
|
234
241
|
throw new Error('Failed to create mpv process streams');
|
|
@@ -288,6 +295,10 @@ class PlayerService {
|
|
|
288
295
|
this.isPlaying = false;
|
|
289
296
|
this.mpvProcess = null;
|
|
290
297
|
}
|
|
298
|
+
if ('code' in error && error.code === 'ENOENT') {
|
|
299
|
+
reject(new Error("mpv executable not found. Install mpv and ensure it's in PATH (or set MPV_PATH)."));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
291
302
|
reject(error);
|
|
292
303
|
});
|
|
293
304
|
logger.info('PlayerService', 'mpv process started successfully');
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { PlayerState, PlayerAction } from '../types/player.types.ts';
|
|
3
|
+
export declare function playerReducer(state: PlayerState, action: PlayerAction): PlayerState;
|
|
3
4
|
import type { Track } from '../types/youtube-music.types.ts';
|
|
4
5
|
type PlayerContextValue = {
|
|
5
6
|
state: PlayerState;
|
|
@@ -24,7 +24,7 @@ const initialState = {
|
|
|
24
24
|
};
|
|
25
25
|
// Get player service instance
|
|
26
26
|
const playerService = getPlayerService();
|
|
27
|
-
function playerReducer(state, action) {
|
|
27
|
+
export function playerReducer(state, action) {
|
|
28
28
|
switch (action.category) {
|
|
29
29
|
case 'PLAY':
|
|
30
30
|
return {
|
|
@@ -45,7 +45,23 @@ function playerReducer(state, action) {
|
|
|
45
45
|
progress: 0,
|
|
46
46
|
currentTrack: null,
|
|
47
47
|
};
|
|
48
|
-
case 'NEXT':
|
|
48
|
+
case 'NEXT': {
|
|
49
|
+
if (state.queue.length === 0)
|
|
50
|
+
return state;
|
|
51
|
+
// Shuffle mode: pick a random track excluding the current position
|
|
52
|
+
if (state.shuffle && state.queue.length > 1) {
|
|
53
|
+
let randomIndex;
|
|
54
|
+
do {
|
|
55
|
+
randomIndex = Math.floor(Math.random() * state.queue.length);
|
|
56
|
+
} while (randomIndex === state.queuePosition);
|
|
57
|
+
return {
|
|
58
|
+
...state,
|
|
59
|
+
queuePosition: randomIndex,
|
|
60
|
+
currentTrack: state.queue[randomIndex] ?? null,
|
|
61
|
+
progress: 0,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Sequential mode
|
|
49
65
|
const nextPosition = state.queuePosition + 1;
|
|
50
66
|
if (nextPosition >= state.queue.length) {
|
|
51
67
|
if (state.repeat === 'all') {
|
|
@@ -64,6 +80,7 @@ function playerReducer(state, action) {
|
|
|
64
80
|
currentTrack: state.queue[nextPosition] ?? null,
|
|
65
81
|
progress: 0,
|
|
66
82
|
};
|
|
83
|
+
}
|
|
67
84
|
case 'PREVIOUS':
|
|
68
85
|
const prevPosition = state.queuePosition - 1;
|
|
69
86
|
if (prevPosition < 0) {
|
|
@@ -27,7 +27,7 @@ export declare const SEARCH_TYPE: {
|
|
|
27
27
|
readonly PLAYLISTS: "playlists";
|
|
28
28
|
};
|
|
29
29
|
export declare const KEYBINDINGS: {
|
|
30
|
-
readonly QUIT: readonly ["q"
|
|
30
|
+
readonly QUIT: readonly ["q"];
|
|
31
31
|
readonly HELP: readonly ["?"];
|
|
32
32
|
readonly SEARCH: readonly ["/"];
|
|
33
33
|
readonly PLAYLISTS: readonly ["shift+p"];
|
|
@@ -41,7 +41,7 @@ export declare const KEYBINDINGS: {
|
|
|
41
41
|
readonly VOLUME_DOWN: readonly ["-"];
|
|
42
42
|
readonly VOLUME_FINE_UP: readonly ["shift+="];
|
|
43
43
|
readonly VOLUME_FINE_DOWN: readonly ["shift+-"];
|
|
44
|
-
readonly SHUFFLE: readonly ["s"];
|
|
44
|
+
readonly SHUFFLE: readonly ["shift+s"];
|
|
45
45
|
readonly REPEAT: readonly ["r"];
|
|
46
46
|
readonly SEEK_FORWARD: readonly ["shift+right"];
|
|
47
47
|
readonly SEEK_BACKWARD: readonly ["shift+left"];
|
|
@@ -35,7 +35,7 @@ export const SEARCH_TYPE = {
|
|
|
35
35
|
// Keybindings
|
|
36
36
|
export const KEYBINDINGS = {
|
|
37
37
|
// Global
|
|
38
|
-
QUIT: ['q'
|
|
38
|
+
QUIT: ['q'],
|
|
39
39
|
HELP: ['?'],
|
|
40
40
|
SEARCH: ['/'],
|
|
41
41
|
PLAYLISTS: ['shift+p'],
|
|
@@ -50,7 +50,7 @@ export const KEYBINDINGS = {
|
|
|
50
50
|
VOLUME_DOWN: ['-'], // Only '-' without shift
|
|
51
51
|
VOLUME_FINE_UP: ['shift+='], // Fine-grained +1 step
|
|
52
52
|
VOLUME_FINE_DOWN: ['shift+-'], // Fine-grained -1 step
|
|
53
|
-
SHUFFLE: ['s'],
|
|
53
|
+
SHUFFLE: ['shift+s'],
|
|
54
54
|
REPEAT: ['r'],
|
|
55
55
|
SEEK_FORWARD: ['shift+right'],
|
|
56
56
|
SEEK_BACKWARD: ['shift+left'],
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@involvex/youtube-music-cli",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"description": "- A Commandline music player for youtube-music",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -78,6 +78,7 @@
|
|
|
78
78
|
"devDependencies": {
|
|
79
79
|
"@eslint/js": "^10.0.1",
|
|
80
80
|
"@sindresorhus/tsconfig": "^8.1.0",
|
|
81
|
+
"@types/node": "^25.2.3",
|
|
81
82
|
"@types/node-notifier": "^8.0.5",
|
|
82
83
|
"@types/react": "^19.2.14",
|
|
83
84
|
"@vdemedes/prettier-config": "^2.0.1",
|
package/readme.md
CHANGED
|
@@ -163,15 +163,15 @@ youtube-music-cli play dQw4w9WgXcQ --shuffle
|
|
|
163
163
|
|
|
164
164
|
### Global
|
|
165
165
|
|
|
166
|
-
| Key
|
|
167
|
-
|
|
|
168
|
-
| `?`
|
|
169
|
-
| `/`
|
|
170
|
-
| `p`
|
|
171
|
-
| `g`
|
|
172
|
-
| `,`
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
166
|
+
| Key | Action |
|
|
167
|
+
| ----- | --------------- |
|
|
168
|
+
| `?` | Show help |
|
|
169
|
+
| `/` | Search |
|
|
170
|
+
| `p` | Plugins manager |
|
|
171
|
+
| `g` | Suggestions |
|
|
172
|
+
| `,` | Settings |
|
|
173
|
+
| `Esc` | Go back |
|
|
174
|
+
| `q` | Quit |
|
|
175
175
|
|
|
176
176
|
### Playback
|
|
177
177
|
|
|
@@ -309,7 +309,7 @@ mpv --version
|
|
|
309
309
|
|
|
310
310
|
### TUI rendering issues
|
|
311
311
|
|
|
312
|
-
|
|
312
|
+
If rendering looks wrong, try resizing your terminal window or restarting the app.
|
|
313
313
|
|
|
314
314
|
### Plugin not loading
|
|
315
315
|
|
|
@@ -377,4 +377,4 @@ Made with ❤️ for music lovers
|
|
|
377
377
|
|
|
378
378
|
**[🪙 Paypal](https://paypal.me/involvex)**
|
|
379
379
|
|
|
380
|
-
|
|
380
|
+
**[⌨️ Github Sponsors](https://github.com/sponsors/involvex)**
|