@involvex/youtube-music-cli 0.0.29 → 0.0.30
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 +2 -0
- package/dist/source/components/common/ShortcutsBar.js +4 -3
- package/dist/source/components/layouts/MiniPlayerLayout.js +2 -1
- package/dist/source/components/layouts/SearchLayout.js +2 -1
- package/dist/source/components/player/NowPlaying.js +2 -1
- package/dist/source/components/player/PlayerControls.js +2 -1
- package/dist/source/utils/icons.d.ts +19 -0
- package/dist/source/utils/icons.js +26 -0
- package/dist/youtube-music-cli.exe +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
// Shortcuts bar component
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
5
5
|
import { useTheme } from "../../hooks/useTheme.js";
|
|
6
6
|
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
7
7
|
import { KEYBINDINGS } from "../../utils/constants.js";
|
|
8
|
+
import { ICONS } from "../../utils/icons.js";
|
|
8
9
|
export default function ShortcutsBar() {
|
|
9
10
|
const { theme } = useTheme();
|
|
10
11
|
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, volumeFineUp, volumeFineDown, toggleShuffle, toggleRepeat, } = usePlayer();
|
|
@@ -27,7 +28,7 @@ export default function ShortcutsBar() {
|
|
|
27
28
|
useKeyBinding(KEYBINDINGS.SHUFFLE, toggleShuffle);
|
|
28
29
|
useKeyBinding(KEYBINDINGS.REPEAT, toggleRepeat);
|
|
29
30
|
// Note: SETTINGS keybinding handled by MainLayout to avoid double-dispatch
|
|
30
|
-
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: [
|
|
31
|
+
return (_jsxs(Box, { borderStyle: "single", borderColor: theme.colors.dim, paddingX: 1, justifyContent: "space-between", children: [_jsxs(Text, { color: theme.colors.dim, children: [_jsxs(Text, { color: theme.colors.text, children: [ICONS.PLAY_PAUSE_ON, "/", ICONS.PAUSE, " [Space]"] }), ' ', "| ", _jsxs(Text, { color: theme.colors.text, children: [ICONS.PREV, " [B/\u2190]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.NEXT, " [N/\u2192]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.SHUFFLE, " [Shift+S]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.REPEAT_ALL, " [R]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.PLAYLIST, " [Shift+P]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.DOWNLOAD, " [Shift+D]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.SEARCH, " [/]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.HELP, " [?]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.BG_PLAY, " [Shift+Q]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.RESUME, " [Shift+R]"] }), " |", ' ', _jsxs(Text, { color: theme.colors.text, children: [ICONS.QUIT, " [Q]"] })] }), _jsxs(Text, { color: theme.colors.text, children: [_jsx(Text, { color: playerState.shuffle ? theme.colors.primary : theme.colors.dim, children: ICONS.SHUFFLE }), ' ', _jsx(Text, { color: playerState.repeat === 'off'
|
|
31
32
|
? theme.colors.dim
|
|
32
|
-
: theme.colors.secondary, children: playerState.repeat === 'one' ?
|
|
33
|
+
: theme.colors.secondary, children: playerState.repeat === 'one' ? ICONS.REPEAT_ONE : ICONS.REPEAT_ALL }), ' ', _jsxs(Text, { color: theme.colors.dim, children: [ICONS.VOLUME, " [=/-]"] }), ' ', _jsxs(Text, { color: theme.colors.primary, children: [playerState.volume, "%"] })] })] }));
|
|
33
34
|
}
|
|
@@ -4,6 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
5
5
|
import { useTheme } from "../../hooks/useTheme.js";
|
|
6
6
|
import { formatTime } from "../../utils/format.js";
|
|
7
|
+
import { ICONS } from "../../utils/icons.js";
|
|
7
8
|
export default function MiniPlayerLayout() {
|
|
8
9
|
const { theme } = useTheme();
|
|
9
10
|
const { state } = usePlayer();
|
|
@@ -12,7 +13,7 @@ export default function MiniPlayerLayout() {
|
|
|
12
13
|
const title = track?.title ?? 'No track playing';
|
|
13
14
|
const progress = formatTime(state.progress);
|
|
14
15
|
const duration = formatTime(state.duration);
|
|
15
|
-
const playIcon = state.isPlaying ?
|
|
16
|
+
const playIcon = state.isPlaying ? ICONS.PLAY : ICONS.PAUSE;
|
|
16
17
|
const vol = `${state.volume}%`;
|
|
17
18
|
const speed = (state.speed ?? 1.0) !== 1.0 ? ` ${(state.speed ?? 1.0).toFixed(2)}x` : '';
|
|
18
19
|
return (_jsxs(Box, { flexDirection: "row", paddingX: 1, gap: 1, children: [_jsx(Text, { color: state.isPlaying ? theme.colors.success : theme.colors.dim, children: playIcon }), _jsx(Text, { bold: true, color: theme.colors.primary, children: title }), _jsx(Text, { color: theme.colors.dim, children: "\u2014" }), _jsx(Text, { color: theme.colors.secondary, children: artist }), _jsx(Text, { color: theme.colors.dim, children: "|" }), _jsxs(Text, { color: theme.colors.text, children: [progress, "/", duration] }), _jsx(Text, { color: theme.colors.dim, children: "|" }), _jsxs(Text, { color: theme.colors.text, children: ["vol:", vol] }), speed && _jsx(Text, { color: theme.colors.accent, children: speed }), state.isLoading && _jsx(Text, { color: theme.colors.accent, children: "Loading..." })] }));
|
|
@@ -11,6 +11,7 @@ import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
|
11
11
|
import { KEYBINDINGS, VIEW } from "../../utils/constants.js";
|
|
12
12
|
import { Box, Text } from 'ink';
|
|
13
13
|
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
14
|
+
import { ICONS } from "../../utils/icons.js";
|
|
14
15
|
function SearchLayout() {
|
|
15
16
|
const { theme } = useTheme();
|
|
16
17
|
const { state: navState, dispatch } = useNavigation();
|
|
@@ -118,7 +119,7 @@ function SearchLayout() {
|
|
|
118
119
|
lastAutoSearchedQueryRef.current = null;
|
|
119
120
|
};
|
|
120
121
|
}, [dispatch]);
|
|
121
|
-
return (_jsxs(Box, { flexDirection: "column", children: [playerState.currentTrack && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.dim, children: playerState.isPlaying ?
|
|
122
|
+
return (_jsxs(Box, { flexDirection: "column", children: [playerState.currentTrack && (_jsxs(Box, { children: [_jsx(Text, { color: theme.colors.dim, children: playerState.isPlaying ? `${ICONS.PLAY} ` : `${ICONS.PAUSE} ` }), _jsx(Text, { color: theme.colors.primary, bold: true, children: playerState.currentTrack.title }), playerState.currentTrack.artists &&
|
|
122
123
|
playerState.currentTrack.artists.length > 0 && (_jsxs(Text, { color: theme.colors.secondary, children: [' • ', playerState.currentTrack.artists.map(a => a.name).join(', ')] }))] })), _jsxs(Text, { color: theme.colors.dim, children: ["Limit: ", navState.searchLimit, " (Use [ or ] to adjust)"] }), _jsx(SearchBar, { isActive: isTyping && !isSearching, onInput: input => {
|
|
123
124
|
void performSearch(input);
|
|
124
125
|
} }), (isLoading || isSearching) && (_jsx(Text, { color: theme.colors.accent, children: "Searching..." })), error && _jsx(Text, { color: theme.colors.error, children: error }), !isLoading && navState.hasSearched && (_jsx(SearchResults, { results: results, selectedIndex: navState.selectedResult, isActive: !isTyping, onMixCreated: handleMixCreated, onDownloadStatus: handleDownloadStatus })), !isLoading && navState.hasSearched && results.length === 0 && !error && (_jsx(Text, { color: theme.colors.dim, children: "No results found" })), actionMessage && (_jsx(Text, { color: theme.colors.accent, children: actionMessage })), _jsx(Text, { color: theme.colors.dim, children: isTyping
|
|
@@ -7,6 +7,7 @@ import { formatTime } from "../../utils/format.js";
|
|
|
7
7
|
import { useTerminalSize } from "../../hooks/useTerminalSize.js";
|
|
8
8
|
import { getSleepTimerService } from "../../services/sleep-timer/sleep-timer.service.js";
|
|
9
9
|
import { useState, useEffect } from 'react';
|
|
10
|
+
import { ICONS } from "../../utils/icons.js";
|
|
10
11
|
export default function NowPlaying() {
|
|
11
12
|
const { theme } = useTheme();
|
|
12
13
|
const { state: playerState } = usePlayer();
|
|
@@ -40,5 +41,5 @@ export default function NowPlaying() {
|
|
|
40
41
|
const percentage = duration > 0 ? Math.min(100, Math.floor((progress / duration) * 100)) : 0;
|
|
41
42
|
const barWidth = Math.max(10, columns - 8);
|
|
42
43
|
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 && (
|
|
44
|
+
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 && (_jsxs(Text, { color: theme.colors.dim, children: [" ", ICONS.PAUSE] })), playerState.shuffle && (_jsxs(Text, { color: theme.colors.primary, children: [" ", ICONS.SHUFFLE] })), sleepRemaining !== null && (_jsxs(Text, { color: theme.colors.warning, children: [' ', "\u23FE ", formatTime(sleepRemaining)] }))] }), playerState.error && (_jsx(Text, { color: theme.colors.error, children: playerState.error }))] }));
|
|
44
45
|
}
|
|
@@ -7,6 +7,7 @@ import { useTheme } from "../../hooks/useTheme.js";
|
|
|
7
7
|
import { Box, Text } from 'ink';
|
|
8
8
|
import { useEffect } from 'react';
|
|
9
9
|
import { logger } from "../../services/logger/logger.service.js";
|
|
10
|
+
import { ICONS } from "../../utils/icons.js";
|
|
10
11
|
let mountCount = 0;
|
|
11
12
|
export default function PlayerControls() {
|
|
12
13
|
const instanceId = ++mountCount;
|
|
@@ -40,5 +41,5 @@ export default function PlayerControls() {
|
|
|
40
41
|
useKeyBinding(KEYBINDINGS.SPEED_UP, speedUp);
|
|
41
42
|
useKeyBinding(KEYBINDINGS.SPEED_DOWN, speedDown);
|
|
42
43
|
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 ?
|
|
44
|
+
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 ? `${ICONS.SHUFFLE} ON` : `${ICONS.SHUFFLE} 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"] }))] }));
|
|
44
45
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const ICONS: {
|
|
2
|
+
readonly PLAY: "▶";
|
|
3
|
+
readonly PAUSE: "‖";
|
|
4
|
+
readonly PLAY_PAUSE_ON: "▶";
|
|
5
|
+
readonly PLAY_PAUSE_OFF: "‖";
|
|
6
|
+
readonly NEXT: "▶|";
|
|
7
|
+
readonly PREV: "|◀";
|
|
8
|
+
readonly SHUFFLE: "⇄";
|
|
9
|
+
readonly REPEAT_ALL: "↻";
|
|
10
|
+
readonly REPEAT_ONE: "↺";
|
|
11
|
+
readonly PLAYLIST: "☰";
|
|
12
|
+
readonly SEARCH: "/";
|
|
13
|
+
readonly HELP: "?";
|
|
14
|
+
readonly DOWNLOAD: "↓";
|
|
15
|
+
readonly QUIT: "×";
|
|
16
|
+
readonly RESUME: "⟳";
|
|
17
|
+
readonly BG_PLAY: "○";
|
|
18
|
+
readonly VOLUME: "♪";
|
|
19
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// Universal icon constants using widely-supported Unicode BMP characters.
|
|
2
|
+
// No emoji, no Nerd Font codepoints — renders correctly on any terminal font.
|
|
3
|
+
export const ICONS = {
|
|
4
|
+
// Playback controls
|
|
5
|
+
PLAY: '▶', // U+25B6
|
|
6
|
+
PAUSE: '‖', // U+2016
|
|
7
|
+
PLAY_PAUSE_ON: '▶', // when playing
|
|
8
|
+
PLAY_PAUSE_OFF: '‖', // when paused
|
|
9
|
+
NEXT: '▶|', // next track
|
|
10
|
+
PREV: '|◀', // previous track
|
|
11
|
+
// Playback modes
|
|
12
|
+
SHUFFLE: '⇄', // U+21C4
|
|
13
|
+
REPEAT_ALL: '↻', // U+21BB
|
|
14
|
+
REPEAT_ONE: '↺', // U+21BA
|
|
15
|
+
// Navigation / views
|
|
16
|
+
PLAYLIST: '☰', // U+2630
|
|
17
|
+
SEARCH: '/', // ASCII
|
|
18
|
+
HELP: '?', // ASCII
|
|
19
|
+
// Actions
|
|
20
|
+
DOWNLOAD: '↓', // U+2193
|
|
21
|
+
QUIT: '×', // U+00D7
|
|
22
|
+
RESUME: '⟳', // U+27F3
|
|
23
|
+
BG_PLAY: '○', // U+25CB
|
|
24
|
+
// Status
|
|
25
|
+
VOLUME: '♪', // U+266A
|
|
26
|
+
};
|
|
Binary file
|