@involvex/youtube-music-cli 0.0.2 → 0.0.3
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/dist/source/components/common/ShortcutsBar.js +3 -1
- package/dist/source/components/config/KeybindingsLayout.d.ts +1 -0
- package/dist/source/components/config/KeybindingsLayout.js +107 -0
- package/dist/source/components/layouts/ExploreLayout.d.ts +1 -0
- package/dist/source/components/layouts/ExploreLayout.js +72 -0
- package/dist/source/components/layouts/LyricsLayout.d.ts +1 -0
- package/dist/source/components/layouts/LyricsLayout.js +89 -0
- package/dist/source/components/layouts/MainLayout.js +39 -1
- package/dist/source/components/layouts/MiniPlayerLayout.d.ts +1 -0
- package/dist/source/components/layouts/MiniPlayerLayout.js +19 -0
- package/dist/source/components/layouts/SearchLayout.js +10 -3
- package/dist/source/components/layouts/TrendingLayout.d.ts +1 -0
- package/dist/source/components/layouts/TrendingLayout.js +59 -0
- package/dist/source/components/player/NowPlaying.js +21 -1
- package/dist/source/components/player/PlayerControls.js +4 -2
- package/dist/source/components/search/SearchBar.js +4 -1
- package/dist/source/components/search/SearchHistory.d.ts +5 -0
- package/dist/source/components/search/SearchHistory.js +35 -0
- package/dist/source/components/settings/Settings.js +74 -11
- package/dist/source/config/themes.config.js +60 -0
- package/dist/source/hooks/usePlayer.d.ts +5 -0
- package/dist/source/hooks/usePlaylist.d.ts +2 -1
- package/dist/source/hooks/usePlaylist.js +8 -2
- package/dist/source/hooks/useSleepTimer.d.ts +9 -0
- package/dist/source/hooks/useSleepTimer.js +48 -0
- package/dist/source/services/cache/cache.service.d.ts +14 -0
- package/dist/source/services/cache/cache.service.js +67 -0
- package/dist/source/services/config/config.service.d.ts +2 -0
- package/dist/source/services/config/config.service.js +17 -0
- package/dist/source/services/discord/discord-rpc.service.d.ts +17 -0
- package/dist/source/services/discord/discord-rpc.service.js +95 -0
- package/dist/source/services/lyrics/lyrics.service.d.ts +22 -0
- package/dist/source/services/lyrics/lyrics.service.js +93 -0
- package/dist/source/services/mpris/mpris.service.d.ts +20 -0
- package/dist/source/services/mpris/mpris.service.js +78 -0
- package/dist/source/services/notification/notification.service.d.ts +14 -0
- package/dist/source/services/notification/notification.service.js +57 -0
- package/dist/source/services/player/player.service.d.ts +3 -0
- package/dist/source/services/player/player.service.js +20 -3
- package/dist/source/services/scrobbling/scrobbling.service.d.ts +23 -0
- package/dist/source/services/scrobbling/scrobbling.service.js +115 -0
- package/dist/source/services/sleep-timer/sleep-timer.service.d.ts +16 -0
- package/dist/source/services/sleep-timer/sleep-timer.service.js +45 -0
- package/dist/source/services/youtube-music/api.d.ts +6 -0
- package/dist/source/services/youtube-music/api.js +102 -2
- package/dist/source/stores/navigation.store.js +6 -0
- package/dist/source/stores/player.store.d.ts +5 -0
- package/dist/source/stores/player.store.js +141 -24
- package/dist/source/types/actions.d.ts +13 -0
- package/dist/source/types/config.types.d.ts +15 -1
- package/dist/source/types/navigation.types.d.ts +3 -2
- package/dist/source/types/player.types.d.ts +3 -2
- package/dist/source/utils/constants.d.ts +9 -0
- package/dist/source/utils/constants.js +9 -0
- package/dist/youtube-music-cli.exe +0 -0
- package/package.json +5 -2
|
@@ -4,10 +4,15 @@ import { createContext, useContext, useReducer, useEffect, useRef, } from 'react
|
|
|
4
4
|
import { getPlayerService } from "../services/player/player.service.js";
|
|
5
5
|
import { loadPlayerState, savePlayerState, } from "../services/player-state/player-state.service.js";
|
|
6
6
|
import { logger } from "../services/logger/logger.service.js";
|
|
7
|
+
import { getNotificationService } from "../services/notification/notification.service.js";
|
|
8
|
+
import { getScrobblingService } from "../services/scrobbling/scrobbling.service.js";
|
|
9
|
+
import { getDiscordRpcService } from "../services/discord/discord-rpc.service.js";
|
|
10
|
+
import { getMprisService } from "../services/mpris/mpris.service.js";
|
|
7
11
|
const initialState = {
|
|
8
12
|
currentTrack: null,
|
|
9
13
|
isPlaying: false,
|
|
10
14
|
volume: 70,
|
|
15
|
+
speed: 1.0,
|
|
11
16
|
progress: 0,
|
|
12
17
|
duration: 0,
|
|
13
18
|
queue: [],
|
|
@@ -104,6 +109,16 @@ function playerReducer(state, action) {
|
|
|
104
109
|
playerService.setVolume(newVolume);
|
|
105
110
|
return { ...state, volume: newVolume };
|
|
106
111
|
}
|
|
112
|
+
case 'VOLUME_FINE_UP': {
|
|
113
|
+
const newVolume = Math.min(100, state.volume + 1);
|
|
114
|
+
playerService.setVolume(newVolume);
|
|
115
|
+
return { ...state, volume: newVolume };
|
|
116
|
+
}
|
|
117
|
+
case 'VOLUME_FINE_DOWN': {
|
|
118
|
+
const newVolume = Math.max(0, state.volume - 1);
|
|
119
|
+
playerService.setVolume(newVolume);
|
|
120
|
+
return { ...state, volume: newVolume };
|
|
121
|
+
}
|
|
107
122
|
case 'TOGGLE_SHUFFLE':
|
|
108
123
|
return { ...state, shuffle: !state.shuffle };
|
|
109
124
|
case 'TOGGLE_REPEAT':
|
|
@@ -160,6 +175,11 @@ function playerReducer(state, action) {
|
|
|
160
175
|
return { ...state, isLoading: action.loading };
|
|
161
176
|
case 'SET_ERROR':
|
|
162
177
|
return { ...state, error: action.error, isLoading: false };
|
|
178
|
+
case 'SET_SPEED': {
|
|
179
|
+
const clampedSpeed = Math.max(0.25, Math.min(4.0, action.speed));
|
|
180
|
+
playerService.setSpeed(clampedSpeed);
|
|
181
|
+
return { ...state, speed: clampedSpeed };
|
|
182
|
+
}
|
|
163
183
|
case 'RESTORE_STATE':
|
|
164
184
|
logger.info('PlayerReducer', 'RESTORE_STATE', {
|
|
165
185
|
hasTrack: !!action.currentTrack,
|
|
@@ -189,6 +209,15 @@ function PlayerManager() {
|
|
|
189
209
|
const progressIntervalRef = useRef(null);
|
|
190
210
|
const musicService = getMusicService();
|
|
191
211
|
const playerService = getPlayerService();
|
|
212
|
+
// Initialize MPRIS (Linux only, no-ops on other platforms)
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
void getMprisService().initialize({
|
|
215
|
+
onPlay: () => dispatch({ category: 'RESUME' }),
|
|
216
|
+
onPause: () => dispatch({ category: 'PAUSE' }),
|
|
217
|
+
onNext: () => dispatch({ category: 'NEXT' }),
|
|
218
|
+
onPrevious: () => dispatch({ category: 'PREVIOUS' }),
|
|
219
|
+
});
|
|
220
|
+
}, [dispatch]);
|
|
192
221
|
// Register event handler for mpv IPC events
|
|
193
222
|
useEffect(() => {
|
|
194
223
|
let lastProgressUpdate = 0;
|
|
@@ -260,29 +289,76 @@ function PlayerManager() {
|
|
|
260
289
|
});
|
|
261
290
|
const loadAndPlayTrack = async () => {
|
|
262
291
|
dispatch({ category: 'SET_LOADING', loading: true });
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
const MAX_RETRIES = 3;
|
|
293
|
+
const RETRY_DELAY_MS = 1500;
|
|
294
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
295
|
+
try {
|
|
296
|
+
logger.debug('PlayerManager', 'Starting playback with mpv', {
|
|
297
|
+
videoId: track.videoId,
|
|
298
|
+
volume: state.volume,
|
|
299
|
+
attempt,
|
|
300
|
+
});
|
|
301
|
+
// Pass YouTube URL directly to mpv (it handles stream extraction via yt-dlp)
|
|
302
|
+
const youtubeUrl = `https://www.youtube.com/watch?v=${track.videoId}`;
|
|
303
|
+
const config = getConfigService();
|
|
304
|
+
const artists = track.artists?.map(a => a.name).join(', ') ?? 'Unknown';
|
|
305
|
+
// Fire desktop notification if enabled (only on first attempt)
|
|
306
|
+
if (attempt === 1 && config.get('notifications')) {
|
|
307
|
+
const notificationService = getNotificationService();
|
|
308
|
+
notificationService.setEnabled(true);
|
|
309
|
+
void notificationService.notifyTrackChange(track.title, artists);
|
|
310
|
+
}
|
|
311
|
+
// Discord Rich Presence
|
|
312
|
+
if (config.get('discordRichPresence')) {
|
|
313
|
+
const discord = getDiscordRpcService();
|
|
314
|
+
discord.setEnabled(true);
|
|
315
|
+
void discord.connect().then(() => discord.updateActivity({
|
|
316
|
+
title: track.title,
|
|
317
|
+
artist: artists,
|
|
318
|
+
startTimestamp: Date.now(),
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
// MPRIS (Linux)
|
|
322
|
+
const mpris = getMprisService();
|
|
323
|
+
mpris.updateTrack({
|
|
324
|
+
title: track.title,
|
|
325
|
+
artist: artists,
|
|
326
|
+
duration: (track.duration ?? 0) * 1_000_000,
|
|
327
|
+
}, true);
|
|
328
|
+
await playerService.play(youtubeUrl, {
|
|
329
|
+
volume: state.volume,
|
|
330
|
+
audioNormalization: config.get('audioNormalization') ?? false,
|
|
331
|
+
proxy: config.get('proxy'),
|
|
332
|
+
});
|
|
333
|
+
logger.info('PlayerManager', 'Playback started successfully', {
|
|
334
|
+
attempt,
|
|
335
|
+
});
|
|
336
|
+
dispatch({ category: 'SET_LOADING', loading: false });
|
|
337
|
+
return; // Success
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
logger.error('PlayerManager', 'Failed to load track', {
|
|
341
|
+
error: error instanceof Error ? error.message : String(error),
|
|
342
|
+
track: { title: track.title, videoId: track.videoId },
|
|
343
|
+
attempt,
|
|
344
|
+
});
|
|
345
|
+
if (attempt < MAX_RETRIES) {
|
|
346
|
+
logger.info('PlayerManager', 'Retrying playback', {
|
|
347
|
+
attempt,
|
|
348
|
+
nextAttempt: attempt + 1,
|
|
349
|
+
delayMs: RETRY_DELAY_MS,
|
|
350
|
+
});
|
|
351
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY_MS));
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
dispatch({
|
|
355
|
+
category: 'SET_ERROR',
|
|
356
|
+
error: error instanceof Error
|
|
357
|
+
? `${error.message} (after ${MAX_RETRIES} attempts)`
|
|
358
|
+
: 'Failed to load track',
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
286
362
|
}
|
|
287
363
|
};
|
|
288
364
|
void loadAndPlayTrack();
|
|
@@ -301,6 +377,34 @@ function PlayerManager() {
|
|
|
301
377
|
}
|
|
302
378
|
return undefined;
|
|
303
379
|
}, [state.isPlaying, state.currentTrack, dispatch]);
|
|
380
|
+
// Scrobble when >50% of track has been played
|
|
381
|
+
const scrobbledRef = useRef(null);
|
|
382
|
+
useEffect(() => {
|
|
383
|
+
if (state.currentTrack &&
|
|
384
|
+
state.duration > 0 &&
|
|
385
|
+
state.progress / state.duration > 0.5 &&
|
|
386
|
+
scrobbledRef.current !== state.currentTrack.videoId) {
|
|
387
|
+
scrobbledRef.current = state.currentTrack.videoId;
|
|
388
|
+
const config = getConfigService();
|
|
389
|
+
const scrobblingConfig = config.get('scrobbling');
|
|
390
|
+
if (scrobblingConfig) {
|
|
391
|
+
const scrobbler = getScrobblingService();
|
|
392
|
+
scrobbler.configure(scrobblingConfig);
|
|
393
|
+
const artist = state.currentTrack.artists?.[0]?.name ?? 'Unknown';
|
|
394
|
+
void scrobbler.scrobble({
|
|
395
|
+
title: state.currentTrack.title,
|
|
396
|
+
artist,
|
|
397
|
+
duration: state.duration,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
if (state.currentTrack &&
|
|
402
|
+
scrobbledRef.current !== state.currentTrack.videoId &&
|
|
403
|
+
state.progress < 1) {
|
|
404
|
+
// New track started — reset so we can scrobble again
|
|
405
|
+
scrobbledRef.current = null;
|
|
406
|
+
}
|
|
407
|
+
}, [state.progress, state.duration, state.currentTrack]);
|
|
304
408
|
// Handle play/pause state
|
|
305
409
|
useEffect(() => {
|
|
306
410
|
if (!state.isPlaying) {
|
|
@@ -441,6 +545,12 @@ export function PlayerProvider({ children }) {
|
|
|
441
545
|
logger.debug('PlayerActions', 'volumeDown called');
|
|
442
546
|
dispatch({ category: 'VOLUME_DOWN' });
|
|
443
547
|
},
|
|
548
|
+
volumeFineUp: () => {
|
|
549
|
+
dispatch({ category: 'VOLUME_FINE_UP' });
|
|
550
|
+
},
|
|
551
|
+
volumeFineDown: () => {
|
|
552
|
+
dispatch({ category: 'VOLUME_FINE_DOWN' });
|
|
553
|
+
},
|
|
444
554
|
toggleShuffle: () => dispatch({ category: 'TOGGLE_SHUFFLE' }),
|
|
445
555
|
toggleRepeat: () => dispatch({ category: 'TOGGLE_REPEAT' }),
|
|
446
556
|
setQueue: (queue) => dispatch({ category: 'SET_QUEUE', queue }),
|
|
@@ -448,7 +558,14 @@ export function PlayerProvider({ children }) {
|
|
|
448
558
|
removeFromQueue: (index) => dispatch({ category: 'REMOVE_FROM_QUEUE', index }),
|
|
449
559
|
clearQueue: () => dispatch({ category: 'CLEAR_QUEUE' }),
|
|
450
560
|
setQueuePosition: (position) => dispatch({ category: 'SET_QUEUE_POSITION', position }),
|
|
451
|
-
|
|
561
|
+
setSpeed: (speed) => dispatch({ category: 'SET_SPEED', speed }),
|
|
562
|
+
speedUp: () => {
|
|
563
|
+
dispatch({ category: 'SET_SPEED', speed: (state.speed ?? 1.0) + 0.25 });
|
|
564
|
+
},
|
|
565
|
+
speedDown: () => {
|
|
566
|
+
dispatch({ category: 'SET_SPEED', speed: (state.speed ?? 1.0) - 0.25 });
|
|
567
|
+
},
|
|
568
|
+
}), [dispatch, state.speed]);
|
|
452
569
|
const contextValue = useMemo(() => ({
|
|
453
570
|
state,
|
|
454
571
|
dispatch, // Needed by PlayerManager
|
|
@@ -32,6 +32,12 @@ export interface VolumeUpAction {
|
|
|
32
32
|
export interface VolumeDownAction {
|
|
33
33
|
readonly category: 'VOLUME_DOWN';
|
|
34
34
|
}
|
|
35
|
+
export interface VolumeFineUpAction {
|
|
36
|
+
readonly category: 'VOLUME_FINE_UP';
|
|
37
|
+
}
|
|
38
|
+
export interface VolumeFineDownAction {
|
|
39
|
+
readonly category: 'VOLUME_FINE_DOWN';
|
|
40
|
+
}
|
|
35
41
|
export interface ToggleShuffleAction {
|
|
36
42
|
readonly category: 'TOGGLE_SHUFFLE';
|
|
37
43
|
}
|
|
@@ -86,6 +92,10 @@ export interface RestoreStateAction {
|
|
|
86
92
|
shuffle: boolean;
|
|
87
93
|
repeat: 'off' | 'all' | 'one';
|
|
88
94
|
}
|
|
95
|
+
export interface SetSpeedAction {
|
|
96
|
+
readonly category: 'SET_SPEED';
|
|
97
|
+
speed: number;
|
|
98
|
+
}
|
|
89
99
|
export interface NavigateAction {
|
|
90
100
|
readonly category: 'NAVIGATE';
|
|
91
101
|
view: string;
|
|
@@ -117,3 +127,6 @@ export interface SetSearchLimitAction {
|
|
|
117
127
|
readonly category: 'SET_SEARCH_LIMIT';
|
|
118
128
|
limit: number;
|
|
119
129
|
}
|
|
130
|
+
export interface TogglePlayerModeAction {
|
|
131
|
+
readonly category: 'TOGGLE_PLAYER_MODE';
|
|
132
|
+
}
|
|
@@ -6,14 +6,28 @@ export interface KeybindingConfig {
|
|
|
6
6
|
description: string;
|
|
7
7
|
}
|
|
8
8
|
export interface Config {
|
|
9
|
-
theme: 'dark' | 'light' | 'midnight' | 'matrix' | 'custom';
|
|
9
|
+
theme: 'dark' | 'light' | 'midnight' | 'matrix' | 'dracula' | 'nord' | 'solarized' | 'catppuccin' | 'custom';
|
|
10
10
|
volume: number;
|
|
11
11
|
keybindings: Record<string, KeybindingConfig>;
|
|
12
12
|
playlists: Playlist[];
|
|
13
13
|
history: string[];
|
|
14
|
+
searchHistory: string[];
|
|
14
15
|
favorites: string[];
|
|
15
16
|
repeat: RepeatMode;
|
|
16
17
|
shuffle: boolean;
|
|
17
18
|
customTheme?: Theme;
|
|
18
19
|
streamQuality?: 'low' | 'medium' | 'high';
|
|
20
|
+
audioNormalization?: boolean;
|
|
21
|
+
notifications?: boolean;
|
|
22
|
+
scrobbling?: {
|
|
23
|
+
lastfm?: {
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
sessionKey?: string;
|
|
26
|
+
};
|
|
27
|
+
listenbrainz?: {
|
|
28
|
+
token?: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
discordRichPresence?: boolean;
|
|
32
|
+
proxy?: string;
|
|
19
33
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NavigateAction, GoBackAction, SetSearchQueryAction, SetSearchCategoryAction, SetSelectedResultAction, SetSelectedPlaylistAction, SetHasSearchedAction, SetSearchLimitAction } from './actions.ts';
|
|
1
|
+
import type { NavigateAction, GoBackAction, SetSearchQueryAction, SetSearchCategoryAction, SetSelectedResultAction, SetSelectedPlaylistAction, SetHasSearchedAction, SetSearchLimitAction, TogglePlayerModeAction } from './actions.ts';
|
|
2
2
|
export interface NavigationState {
|
|
3
3
|
currentView: string;
|
|
4
4
|
previousView: string | null;
|
|
@@ -10,5 +10,6 @@ export interface NavigationState {
|
|
|
10
10
|
hasSearched: boolean;
|
|
11
11
|
searchLimit: number;
|
|
12
12
|
history: string[];
|
|
13
|
+
playerMode: 'full' | 'mini';
|
|
13
14
|
}
|
|
14
|
-
export type NavigationAction = NavigateAction | GoBackAction | SetSearchQueryAction | SetSearchCategoryAction | SetSelectedResultAction | SetSelectedPlaylistAction | SetHasSearchedAction | SetSearchLimitAction;
|
|
15
|
+
export type NavigationAction = NavigateAction | GoBackAction | SetSearchQueryAction | SetSearchCategoryAction | SetSelectedResultAction | SetSelectedPlaylistAction | SetHasSearchedAction | SetSearchLimitAction | TogglePlayerModeAction;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { PlayAction, PauseAction, ResumeAction, StopAction, NextAction, PreviousAction, SeekAction, SetVolumeAction, VolumeUpAction, VolumeDownAction, ToggleShuffleAction, ToggleRepeatAction, SetQueueAction, AddToQueueAction, RemoveFromQueueAction, ClearQueueAction, SetQueuePositionAction, UpdateProgressAction, SetDurationAction, TickAction, SetLoadingAction, SetErrorAction, RestoreStateAction } from './actions.ts';
|
|
1
|
+
import type { PlayAction, PauseAction, ResumeAction, StopAction, NextAction, PreviousAction, SeekAction, SetVolumeAction, VolumeUpAction, VolumeDownAction, VolumeFineUpAction, VolumeFineDownAction, ToggleShuffleAction, ToggleRepeatAction, SetQueueAction, AddToQueueAction, RemoveFromQueueAction, ClearQueueAction, SetQueuePositionAction, UpdateProgressAction, SetDurationAction, TickAction, SetLoadingAction, SetErrorAction, RestoreStateAction, SetSpeedAction } from './actions.ts';
|
|
2
2
|
import type { Track } from './youtube-music.types.ts';
|
|
3
3
|
export interface PlayerState {
|
|
4
4
|
currentTrack: Track | null;
|
|
5
5
|
isPlaying: boolean;
|
|
6
6
|
volume: number;
|
|
7
|
+
speed: number;
|
|
7
8
|
progress: number;
|
|
8
9
|
duration: number;
|
|
9
10
|
queue: Track[];
|
|
@@ -13,4 +14,4 @@ export interface PlayerState {
|
|
|
13
14
|
isLoading: boolean;
|
|
14
15
|
error: string | null;
|
|
15
16
|
}
|
|
16
|
-
export type PlayerAction = PlayAction | PauseAction | ResumeAction | StopAction | NextAction | PreviousAction | SeekAction | SetVolumeAction | VolumeUpAction | VolumeDownAction | ToggleShuffleAction | ToggleRepeatAction | SetQueueAction | AddToQueueAction | RemoveFromQueueAction | ClearQueueAction | SetQueuePositionAction | UpdateProgressAction | SetDurationAction | TickAction | SetLoadingAction | SetErrorAction | RestoreStateAction;
|
|
17
|
+
export type PlayerAction = PlayAction | PauseAction | ResumeAction | StopAction | NextAction | PreviousAction | SeekAction | SetVolumeAction | VolumeUpAction | VolumeDownAction | VolumeFineUpAction | VolumeFineDownAction | ToggleShuffleAction | ToggleRepeatAction | SetQueueAction | AddToQueueAction | RemoveFromQueueAction | ClearQueueAction | SetQueuePositionAction | UpdateProgressAction | SetDurationAction | TickAction | SetLoadingAction | SetErrorAction | RestoreStateAction | SetSpeedAction;
|
|
@@ -5,6 +5,7 @@ export declare const CONFIG_FILE: string;
|
|
|
5
5
|
export declare const VIEW: {
|
|
6
6
|
readonly PLAYER: "player";
|
|
7
7
|
readonly SEARCH: "search";
|
|
8
|
+
readonly SEARCH_HISTORY: "search_history";
|
|
8
9
|
readonly PLAYLISTS: "playlists";
|
|
9
10
|
readonly ARTIST: "artist";
|
|
10
11
|
readonly ALBUM: "album";
|
|
@@ -13,6 +14,10 @@ export declare const VIEW: {
|
|
|
13
14
|
readonly SETTINGS: "settings";
|
|
14
15
|
readonly CONFIG: "config";
|
|
15
16
|
readonly PLUGINS: "plugins";
|
|
17
|
+
readonly LYRICS: "lyrics";
|
|
18
|
+
readonly KEYBINDINGS: "keybindings";
|
|
19
|
+
readonly TRENDING: "trending";
|
|
20
|
+
readonly EXPLORE: "explore";
|
|
16
21
|
};
|
|
17
22
|
export declare const SEARCH_TYPE: {
|
|
18
23
|
readonly ALL: "all";
|
|
@@ -33,10 +38,14 @@ export declare const KEYBINDINGS: {
|
|
|
33
38
|
readonly PREVIOUS: readonly ["b", "left"];
|
|
34
39
|
readonly VOLUME_UP: readonly ["="];
|
|
35
40
|
readonly VOLUME_DOWN: readonly ["-"];
|
|
41
|
+
readonly VOLUME_FINE_UP: readonly ["shift+="];
|
|
42
|
+
readonly VOLUME_FINE_DOWN: readonly ["shift+-"];
|
|
36
43
|
readonly SHUFFLE: readonly ["s"];
|
|
37
44
|
readonly REPEAT: readonly ["r"];
|
|
38
45
|
readonly SEEK_FORWARD: readonly ["shift+right"];
|
|
39
46
|
readonly SEEK_BACKWARD: readonly ["shift+left"];
|
|
47
|
+
readonly SPEED_UP: readonly [">"];
|
|
48
|
+
readonly SPEED_DOWN: readonly ["<"];
|
|
40
49
|
readonly UP: readonly ["up", "k"];
|
|
41
50
|
readonly DOWN: readonly ["down", "j"];
|
|
42
51
|
readonly SELECT: readonly ["enter", "return"];
|
|
@@ -10,6 +10,7 @@ export const CONFIG_FILE = `${CONFIG_DIR}/config.json`;
|
|
|
10
10
|
export const VIEW = {
|
|
11
11
|
PLAYER: 'player',
|
|
12
12
|
SEARCH: 'search',
|
|
13
|
+
SEARCH_HISTORY: 'search_history',
|
|
13
14
|
PLAYLISTS: 'playlists',
|
|
14
15
|
ARTIST: 'artist',
|
|
15
16
|
ALBUM: 'album',
|
|
@@ -18,6 +19,10 @@ export const VIEW = {
|
|
|
18
19
|
SETTINGS: 'settings',
|
|
19
20
|
CONFIG: 'config',
|
|
20
21
|
PLUGINS: 'plugins',
|
|
22
|
+
LYRICS: 'lyrics',
|
|
23
|
+
KEYBINDINGS: 'keybindings',
|
|
24
|
+
TRENDING: 'trending',
|
|
25
|
+
EXPLORE: 'explore',
|
|
21
26
|
};
|
|
22
27
|
// Search types
|
|
23
28
|
export const SEARCH_TYPE = {
|
|
@@ -42,10 +47,14 @@ export const KEYBINDINGS = {
|
|
|
42
47
|
PREVIOUS: ['b', 'left'],
|
|
43
48
|
VOLUME_UP: ['='], // Only '=' without shift, since '+' requires shift and causes issues
|
|
44
49
|
VOLUME_DOWN: ['-'], // Only '-' without shift
|
|
50
|
+
VOLUME_FINE_UP: ['shift+='], // Fine-grained +1 step
|
|
51
|
+
VOLUME_FINE_DOWN: ['shift+-'], // Fine-grained -1 step
|
|
45
52
|
SHUFFLE: ['s'],
|
|
46
53
|
REPEAT: ['r'],
|
|
47
54
|
SEEK_FORWARD: ['shift+right'],
|
|
48
55
|
SEEK_BACKWARD: ['shift+left'],
|
|
56
|
+
SPEED_UP: ['>'],
|
|
57
|
+
SPEED_DOWN: ['<'],
|
|
49
58
|
// Navigation
|
|
50
59
|
UP: ['up', 'k'],
|
|
51
60
|
DOWN: ['down', 'j'],
|
|
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.3",
|
|
4
4
|
"description": "- A Commandline music player for youtube-music",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"test": "prettier --check . && xo && ava",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
|
35
|
-
"clean": "rimraf dist"
|
|
35
|
+
"clean": "rimraf dist",
|
|
36
|
+
"release": "powershell -File scripts/release.ps1"
|
|
36
37
|
},
|
|
37
38
|
"xo": {
|
|
38
39
|
"extends": "xo-react",
|
|
@@ -59,6 +60,7 @@
|
|
|
59
60
|
"ink-text-input": "^6.0.0",
|
|
60
61
|
"jiti": "^2.6.1",
|
|
61
62
|
"meow": "^14.0.0",
|
|
63
|
+
"node-notifier": "^10.0.1",
|
|
62
64
|
"node-youtube-music": "^0.10.3",
|
|
63
65
|
"play-sound": "^1.1.6",
|
|
64
66
|
"react": "^19.2.4",
|
|
@@ -68,6 +70,7 @@
|
|
|
68
70
|
"devDependencies": {
|
|
69
71
|
"@eslint/js": "^10.0.1",
|
|
70
72
|
"@sindresorhus/tsconfig": "^8.1.0",
|
|
73
|
+
"@types/node-notifier": "^8.0.5",
|
|
71
74
|
"@types/react": "^19.2.14",
|
|
72
75
|
"@vdemedes/prettier-config": "^2.0.1",
|
|
73
76
|
"ava": "^6.4.1",
|