@involvex/youtube-music-cli 0.0.46 → 0.0.48
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 +4 -0
- package/dist/cli.js.map +1004 -0
- package/dist/source/hooks/usePlayer.d.ts +1 -0
- package/dist/source/services/player-state/player-state.service.d.ts +1 -0
- package/dist/source/stores/player.store.d.ts +1 -0
- package/dist/source/types/actions.d.ts +4 -0
- package/dist/source/types/player.types.d.ts +3 -2
- package/dist/source/utils/constants.d.ts +1 -0
- package/dist/source/utils/icons.d.ts +1 -0
- package/dist/youtube-music-cli +0 -0
- package/package.json +1 -1
- package/dist/eslint.config.js +0 -55
- package/dist/package.json +0 -120
- package/dist/scripts/build-cli.js +0 -46
- package/dist/source/app.js +0 -17
- package/dist/source/cli.js +0 -504
- package/dist/source/components/common/ErrorBoundary.js +0 -22
- package/dist/source/components/common/Help.js +0 -18
- package/dist/source/components/common/ShortcutsBar.js +0 -80
- package/dist/source/components/config/ConfigLayout.js +0 -84
- package/dist/source/components/config/KeybindingsLayout.js +0 -107
- package/dist/source/components/export/ExportLayout.js +0 -111
- package/dist/source/components/import/ImportLayout.js +0 -119
- package/dist/source/components/import/ImportProgress.js +0 -73
- package/dist/source/components/layouts/ExploreLayout.js +0 -72
- package/dist/source/components/layouts/HistoryLayout.js +0 -37
- package/dist/source/components/layouts/LyricsLayout.js +0 -89
- package/dist/source/components/layouts/MainLayout.js +0 -190
- package/dist/source/components/layouts/MiniPlayerLayout.js +0 -20
- package/dist/source/components/layouts/PlayerLayout.js +0 -9
- package/dist/source/components/layouts/PluginsLayout.js +0 -77
- package/dist/source/components/layouts/SearchLayout.js +0 -193
- package/dist/source/components/layouts/TrendingLayout.js +0 -59
- package/dist/source/components/player/NowPlaying.js +0 -45
- package/dist/source/components/player/PlayerControls.js +0 -83
- package/dist/source/components/player/ProgressBar.js +0 -19
- package/dist/source/components/player/QueueList.js +0 -36
- package/dist/source/components/player/Suggestions.js +0 -50
- package/dist/source/components/playlist/PlaylistList.js +0 -138
- package/dist/source/components/plugins/PluginInstallDialog.js +0 -41
- package/dist/source/components/plugins/PluginsAvailable.js +0 -55
- package/dist/source/components/plugins/PluginsList.js +0 -18
- package/dist/source/components/search/SearchBar.js +0 -55
- package/dist/source/components/search/SearchHistory.js +0 -35
- package/dist/source/components/search/SearchResults.js +0 -280
- package/dist/source/components/settings/Settings.js +0 -211
- package/dist/source/components/theme/ThemeSwitcher.js +0 -11
- package/dist/source/config/themes.config.js +0 -123
- package/dist/source/contexts/theme.context.js +0 -29
- package/dist/source/hooks/useKeyboard.js +0 -188
- package/dist/source/hooks/useKeyboardBlocker.js +0 -45
- package/dist/source/hooks/useNavigation.js +0 -5
- package/dist/source/hooks/usePlayer.js +0 -43
- package/dist/source/hooks/usePlaylist.js +0 -65
- package/dist/source/hooks/useSearch.js +0 -76
- package/dist/source/hooks/useSleepTimer.js +0 -48
- package/dist/source/hooks/useTerminalSize.js +0 -24
- package/dist/source/hooks/useTheme.js +0 -5
- package/dist/source/hooks/useYouTubeMusic.js +0 -112
- package/dist/source/main.js +0 -127
- package/dist/source/services/cache/cache.service.js +0 -67
- package/dist/source/services/completions/completions.service.js +0 -313
- package/dist/source/services/config/config.service.js +0 -191
- package/dist/source/services/discord/discord-rpc.service.js +0 -95
- package/dist/source/services/download/download.service.js +0 -350
- package/dist/source/services/export/export.service.js +0 -131
- package/dist/source/services/history/history.service.js +0 -83
- package/dist/source/services/import/import.service.js +0 -272
- package/dist/source/services/import/spotify.service.js +0 -171
- package/dist/source/services/import/track-matcher.service.js +0 -271
- package/dist/source/services/import/youtube-import.service.js +0 -84
- package/dist/source/services/logger/logger.service.js +0 -52
- package/dist/source/services/lyrics/lyrics.service.js +0 -93
- package/dist/source/services/mpris/mpris.service.js +0 -78
- package/dist/source/services/notification/notification.service.js +0 -57
- package/dist/source/services/player/dependency-check.service.js +0 -140
- package/dist/source/services/player/player.service.js +0 -478
- package/dist/source/services/player-state/player-state.service.js +0 -122
- package/dist/source/services/plugin/plugin-audio-api.js +0 -36
- package/dist/source/services/plugin/plugin-context.js +0 -256
- package/dist/source/services/plugin/plugin-hooks.service.js +0 -135
- package/dist/source/services/plugin/plugin-installer.service.js +0 -248
- package/dist/source/services/plugin/plugin-loader.service.js +0 -161
- package/dist/source/services/plugin/plugin-permissions.service.js +0 -194
- package/dist/source/services/plugin/plugin-registry.service.js +0 -215
- package/dist/source/services/plugin/plugin-ui-api.js +0 -46
- package/dist/source/services/plugin/plugin-updater.service.js +0 -206
- package/dist/source/services/scrobbling/scrobbling.service.js +0 -115
- package/dist/source/services/sleep-timer/sleep-timer.service.js +0 -45
- package/dist/source/services/version-check/version-check.service.js +0 -121
- package/dist/source/services/web/static-file.service.js +0 -185
- package/dist/source/services/web/web-server-manager.js +0 -506
- package/dist/source/services/web/web-streaming.service.js +0 -290
- package/dist/source/services/web/websocket.server.js +0 -267
- package/dist/source/services/youtube-music/api.js +0 -649
- package/dist/source/services/youtube-music/search.service.js +0 -38
- package/dist/source/stores/history.store.js +0 -64
- package/dist/source/stores/navigation.store.js +0 -90
- package/dist/source/stores/player.store.js +0 -724
- package/dist/source/stores/plugins.store.js +0 -177
- package/dist/source/types/actions.js +0 -1
- package/dist/source/types/cli.types.js +0 -1
- package/dist/source/types/config.types.js +0 -1
- package/dist/source/types/history.types.js +0 -1
- package/dist/source/types/import.types.js +0 -2
- package/dist/source/types/keyboard.types.js +0 -1
- package/dist/source/types/navigation.types.js +0 -1
- package/dist/source/types/player.types.js +0 -1
- package/dist/source/types/playlist.types.js +0 -1
- package/dist/source/types/plugin.types.js +0 -1
- package/dist/source/types/theme.types.js +0 -1
- package/dist/source/types/web.types.js +0 -2
- package/dist/source/types/youtube-music.types.js +0 -1
- package/dist/source/types/youtubei.types.js +0 -3
- package/dist/source/utils/constants.js +0 -134
- package/dist/source/utils/format.js +0 -24
- package/dist/source/utils/icons.js +0 -26
- package/dist/source/utils/search-filters.js +0 -100
package/dist/source/cli.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import App from "./app.js";
|
|
4
|
-
import { render } from 'ink';
|
|
5
|
-
import meow from 'meow';
|
|
6
|
-
import { getPluginInstallerService } from "./services/plugin/plugin-installer.service.js";
|
|
7
|
-
import { getPluginUpdaterService } from "./services/plugin/plugin-updater.service.js";
|
|
8
|
-
import { getPluginRegistryService } from "./services/plugin/plugin-registry.service.js";
|
|
9
|
-
import { getImportService } from "./services/import/import.service.js";
|
|
10
|
-
import { getWebServerManager } from "./services/web/web-server-manager.js";
|
|
11
|
-
import { getWebStreamingService } from "./services/web/web-streaming.service.js";
|
|
12
|
-
import { getVersionCheckService } from "./services/version-check/version-check.service.js";
|
|
13
|
-
import { generateCompletion, } from "./services/completions/completions.service.js";
|
|
14
|
-
import { getConfigService } from "./services/config/config.service.js";
|
|
15
|
-
import { getPlayerService } from "./services/player/player.service.js";
|
|
16
|
-
import { APP_VERSION } from "./utils/constants.js";
|
|
17
|
-
import { ensurePlaybackDependencies } from "./services/player/dependency-check.service.js";
|
|
18
|
-
import { getMusicService } from "./services/youtube-music/api.js";
|
|
19
|
-
const isStandalone = process
|
|
20
|
-
.isStandaloneExecutable ||
|
|
21
|
-
globalThis.Bun?.isStandalone;
|
|
22
|
-
const argv = isStandalone ? process.argv.slice(1) : process.argv.slice(2);
|
|
23
|
-
const cli = meow(`
|
|
24
|
-
youtube-music-cli@${APP_VERSION}
|
|
25
|
-
|
|
26
|
-
Usage
|
|
27
|
-
$ youtube-music-cli
|
|
28
|
-
$ youtube-music-cli play <track-id|youtube-url>
|
|
29
|
-
$ youtube-music-cli search <query>
|
|
30
|
-
$ youtube-music-cli playlist <playlist-id>
|
|
31
|
-
$ youtube-music-cli suggestions
|
|
32
|
-
$ youtube-music-cli pause
|
|
33
|
-
$ youtube-music-cli resume
|
|
34
|
-
$ youtube-music-cli skip
|
|
35
|
-
$ youtube-music-cli back
|
|
36
|
-
|
|
37
|
-
Plugin Commands
|
|
38
|
-
$ youtube-music-cli plugins list
|
|
39
|
-
$ youtube-music-cli plugins install <name|url>
|
|
40
|
-
$ youtube-music-cli plugins remove <name>
|
|
41
|
-
$ youtube-music-cli plugins update <name>
|
|
42
|
-
$ youtube-music-cli plugins enable <name>
|
|
43
|
-
$ youtube-music-cli plugins disable <name>
|
|
44
|
-
|
|
45
|
-
Import Commands
|
|
46
|
-
$ youtube-music-cli import spotify <url-or-id>
|
|
47
|
-
$ youtube-music-cli import youtube <url-or-id>
|
|
48
|
-
|
|
49
|
-
Options
|
|
50
|
-
--theme, -t Theme to use (dark, light, midnight, matrix)
|
|
51
|
-
--volume, -v Initial volume (0-100)
|
|
52
|
-
--shuffle, -s Enable shuffle mode
|
|
53
|
-
--repeat, -r Repeat mode (off, all, one)
|
|
54
|
-
--headless Run without TUI (just play)
|
|
55
|
-
--web Enable web UI server
|
|
56
|
-
--web-host Web server host (default: localhost)
|
|
57
|
-
--web-port Web server port (default: 8080)
|
|
58
|
-
--web-only Run web server without CLI UI
|
|
59
|
-
--web-auth Authentication token for web server
|
|
60
|
-
--name Custom name for imported playlist
|
|
61
|
-
--help, -h Show this help
|
|
62
|
-
|
|
63
|
-
Shell Completions
|
|
64
|
-
$ youtube-music-cli completions bash
|
|
65
|
-
$ youtube-music-cli completions zsh
|
|
66
|
-
$ youtube-music-cli completions powershell
|
|
67
|
-
$ youtube-music-cli completions fish
|
|
68
|
-
|
|
69
|
-
Examples
|
|
70
|
-
$ youtube-music-cli
|
|
71
|
-
$ youtube-music-cli play dQw4w9WgXcQ
|
|
72
|
-
$ youtube-music-cli search "Rick Astley"
|
|
73
|
-
$ youtube-music-cli play dQw4w9WgXcQ --headless
|
|
74
|
-
$ youtube-music-cli plugins install adblock
|
|
75
|
-
$ youtube-music-cli import spotify "https://open.spotify.com/playlist/..."
|
|
76
|
-
$ youtube-music-cli --web --web-port 3000
|
|
77
|
-
$ youtube-music-cli completions powershell | Out-File $PROFILE
|
|
78
|
-
`, {
|
|
79
|
-
importMeta: import.meta,
|
|
80
|
-
argv,
|
|
81
|
-
flags: {
|
|
82
|
-
theme: {
|
|
83
|
-
type: 'string',
|
|
84
|
-
shortFlag: 't',
|
|
85
|
-
},
|
|
86
|
-
volume: {
|
|
87
|
-
type: 'number',
|
|
88
|
-
shortFlag: 'v',
|
|
89
|
-
},
|
|
90
|
-
shuffle: {
|
|
91
|
-
type: 'boolean',
|
|
92
|
-
shortFlag: 's',
|
|
93
|
-
default: false,
|
|
94
|
-
},
|
|
95
|
-
repeat: {
|
|
96
|
-
type: 'string',
|
|
97
|
-
shortFlag: 'r',
|
|
98
|
-
},
|
|
99
|
-
headless: {
|
|
100
|
-
type: 'boolean',
|
|
101
|
-
default: false,
|
|
102
|
-
},
|
|
103
|
-
web: {
|
|
104
|
-
type: 'boolean',
|
|
105
|
-
default: false,
|
|
106
|
-
},
|
|
107
|
-
webHost: {
|
|
108
|
-
type: 'string',
|
|
109
|
-
},
|
|
110
|
-
webPort: {
|
|
111
|
-
type: 'number',
|
|
112
|
-
},
|
|
113
|
-
webOnly: {
|
|
114
|
-
type: 'boolean',
|
|
115
|
-
default: false,
|
|
116
|
-
},
|
|
117
|
-
webAuth: {
|
|
118
|
-
type: 'string',
|
|
119
|
-
},
|
|
120
|
-
name: {
|
|
121
|
-
type: 'string',
|
|
122
|
-
},
|
|
123
|
-
help: {
|
|
124
|
-
type: 'boolean',
|
|
125
|
-
shortFlag: 'h',
|
|
126
|
-
default: false,
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
autoVersion: true,
|
|
130
|
-
autoHelp: false,
|
|
131
|
-
});
|
|
132
|
-
if (cli.flags.help) {
|
|
133
|
-
cli.showHelp(0);
|
|
134
|
-
}
|
|
135
|
-
// Handle plugin commands
|
|
136
|
-
const command = cli.input[0];
|
|
137
|
-
const args = cli.input.slice(1);
|
|
138
|
-
const isInteractiveTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
139
|
-
function requiresImmediatePlayback(flags) {
|
|
140
|
-
return Boolean(flags.playTrack || flags.searchQuery || flags.playPlaylist);
|
|
141
|
-
}
|
|
142
|
-
function shouldCheckPlaybackDependencies(commandName, flags) {
|
|
143
|
-
if (flags.webOnly) {
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
if (requiresImmediatePlayback(flags)) {
|
|
147
|
-
return true;
|
|
148
|
-
}
|
|
149
|
-
return (commandName === undefined ||
|
|
150
|
-
commandName === 'suggestions' ||
|
|
151
|
-
Boolean(flags.web));
|
|
152
|
-
}
|
|
153
|
-
async function runDirectPlaybackCommand(flags) {
|
|
154
|
-
const musicService = getMusicService();
|
|
155
|
-
const playerService = getPlayerService();
|
|
156
|
-
const config = getConfigService();
|
|
157
|
-
const playbackOptions = {
|
|
158
|
-
volume: flags.volume ?? config.get('volume'),
|
|
159
|
-
audioNormalization: config.get('audioNormalization'),
|
|
160
|
-
volumeFadeDuration: config.get('volumeFadeDuration'),
|
|
161
|
-
};
|
|
162
|
-
let track;
|
|
163
|
-
if (flags.playTrack) {
|
|
164
|
-
track = await musicService.getTrack(flags.playTrack);
|
|
165
|
-
if (!track) {
|
|
166
|
-
throw new Error(`Track not found: ${flags.playTrack}`);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
else if (flags.searchQuery) {
|
|
170
|
-
const response = await musicService.search(flags.searchQuery, {
|
|
171
|
-
type: 'songs',
|
|
172
|
-
limit: 1,
|
|
173
|
-
});
|
|
174
|
-
const firstSong = response.results.find(result => result.type === 'song');
|
|
175
|
-
if (!firstSong) {
|
|
176
|
-
throw new Error(`No playable tracks found for: "${flags.searchQuery}"`);
|
|
177
|
-
}
|
|
178
|
-
track = firstSong.data;
|
|
179
|
-
}
|
|
180
|
-
else if (flags.playPlaylist) {
|
|
181
|
-
const playlist = await musicService.getPlaylist(flags.playPlaylist);
|
|
182
|
-
track = playlist.tracks[0];
|
|
183
|
-
if (!track) {
|
|
184
|
-
throw new Error(`No playable tracks found in playlist: ${flags.playPlaylist}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
if (!track) {
|
|
188
|
-
throw new Error('No track resolved for playback command.');
|
|
189
|
-
}
|
|
190
|
-
const artists = track.artists.length > 0
|
|
191
|
-
? track.artists.map(artist => artist.name).join(', ')
|
|
192
|
-
: 'Unknown Artist';
|
|
193
|
-
console.log(`Playing: ${track.title} — ${artists}`);
|
|
194
|
-
const youtubeUrl = `https://www.youtube.com/watch?v=${track.videoId}`;
|
|
195
|
-
await playerService.play(youtubeUrl, playbackOptions);
|
|
196
|
-
}
|
|
197
|
-
if (command === 'plugins') {
|
|
198
|
-
const subCommand = args[0];
|
|
199
|
-
const pluginArg = args[1];
|
|
200
|
-
void (async () => {
|
|
201
|
-
const installer = getPluginInstallerService();
|
|
202
|
-
const updater = getPluginUpdaterService();
|
|
203
|
-
const registry = getPluginRegistryService();
|
|
204
|
-
// Load existing plugins
|
|
205
|
-
await registry.loadAllPlugins();
|
|
206
|
-
switch (subCommand) {
|
|
207
|
-
case 'list': {
|
|
208
|
-
const plugins = registry.getAllPlugins();
|
|
209
|
-
if (plugins.length === 0) {
|
|
210
|
-
console.log('No plugins installed.');
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
console.log('Installed plugins:');
|
|
214
|
-
for (const plugin of plugins) {
|
|
215
|
-
const status = plugin.enabled ? '●' : '○';
|
|
216
|
-
console.log(` ${status} ${plugin.manifest.name} v${plugin.manifest.version}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
process.exit(0);
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
case 'install': {
|
|
223
|
-
if (!pluginArg) {
|
|
224
|
-
console.error('Usage: youtube-music-cli plugins install <name|url>');
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
console.log(`Installing ${pluginArg}...`);
|
|
228
|
-
let result;
|
|
229
|
-
if (pluginArg.startsWith('http')) {
|
|
230
|
-
result = await installer.installFromGitHub(pluginArg);
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
result = await installer.installFromDefaultRepo(pluginArg);
|
|
234
|
-
}
|
|
235
|
-
if (result.success) {
|
|
236
|
-
console.log(`✓ Successfully installed ${result.pluginId}`);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
console.error(`✗ Failed: ${result.error}`);
|
|
240
|
-
process.exit(1);
|
|
241
|
-
}
|
|
242
|
-
process.exit(0);
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case 'remove':
|
|
246
|
-
case 'uninstall': {
|
|
247
|
-
if (!pluginArg) {
|
|
248
|
-
console.error('Usage: youtube-music-cli plugins remove <name>');
|
|
249
|
-
process.exit(1);
|
|
250
|
-
}
|
|
251
|
-
console.log(`Removing ${pluginArg}...`);
|
|
252
|
-
try {
|
|
253
|
-
await registry.unloadPlugin(pluginArg);
|
|
254
|
-
}
|
|
255
|
-
catch {
|
|
256
|
-
// Plugin may not be loaded
|
|
257
|
-
}
|
|
258
|
-
const result = await installer.uninstall(pluginArg);
|
|
259
|
-
if (result.success) {
|
|
260
|
-
console.log(`✓ Successfully removed ${pluginArg}`);
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
console.error(`✗ Failed: ${result.error}`);
|
|
264
|
-
process.exit(1);
|
|
265
|
-
}
|
|
266
|
-
process.exit(0);
|
|
267
|
-
break;
|
|
268
|
-
}
|
|
269
|
-
case 'update': {
|
|
270
|
-
if (!pluginArg) {
|
|
271
|
-
console.error('Usage: youtube-music-cli plugins update <name>');
|
|
272
|
-
process.exit(1);
|
|
273
|
-
}
|
|
274
|
-
console.log(`Updating ${pluginArg}...`);
|
|
275
|
-
const result = await updater.updatePlugin(pluginArg);
|
|
276
|
-
if (result.success) {
|
|
277
|
-
console.log(`✓ Updated ${pluginArg} from ${result.oldVersion} to ${result.newVersion}`);
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
console.error(`✗ Failed: ${result.error}`);
|
|
281
|
-
process.exit(1);
|
|
282
|
-
}
|
|
283
|
-
process.exit(0);
|
|
284
|
-
break;
|
|
285
|
-
}
|
|
286
|
-
case 'enable': {
|
|
287
|
-
if (!pluginArg) {
|
|
288
|
-
console.error('Usage: youtube-music-cli plugins enable <name>');
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
try {
|
|
292
|
-
await registry.enablePlugin(pluginArg);
|
|
293
|
-
console.log(`✓ Enabled ${pluginArg}`);
|
|
294
|
-
}
|
|
295
|
-
catch (error) {
|
|
296
|
-
console.error(`✗ Failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
297
|
-
process.exit(1);
|
|
298
|
-
}
|
|
299
|
-
process.exit(0);
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
case 'disable': {
|
|
303
|
-
if (!pluginArg) {
|
|
304
|
-
console.error('Usage: youtube-music-cli plugins disable <name>');
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
try {
|
|
308
|
-
await registry.disablePlugin(pluginArg);
|
|
309
|
-
console.log(`✓ Disabled ${pluginArg}`);
|
|
310
|
-
}
|
|
311
|
-
catch (error) {
|
|
312
|
-
console.error(`✗ Failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
313
|
-
process.exit(1);
|
|
314
|
-
}
|
|
315
|
-
process.exit(0);
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
default:
|
|
319
|
-
console.error('Usage: youtube-music-cli plugins <list|install|remove|update|enable|disable>');
|
|
320
|
-
process.exit(1);
|
|
321
|
-
}
|
|
322
|
-
})();
|
|
323
|
-
}
|
|
324
|
-
else {
|
|
325
|
-
// Handle other direct commands
|
|
326
|
-
if (command === 'completions') {
|
|
327
|
-
const shell = args[0];
|
|
328
|
-
const validShells = ['bash', 'zsh', 'powershell', 'fish'];
|
|
329
|
-
if (!shell || !validShells.includes(shell)) {
|
|
330
|
-
console.error('Usage: youtube-music-cli completions <bash|zsh|powershell|fish>');
|
|
331
|
-
process.exit(1);
|
|
332
|
-
}
|
|
333
|
-
console.log(generateCompletion(shell));
|
|
334
|
-
process.exit(0);
|
|
335
|
-
}
|
|
336
|
-
else if (command === 'play' && args[0]) {
|
|
337
|
-
// Play specific track
|
|
338
|
-
cli.flags.playTrack = args[0];
|
|
339
|
-
}
|
|
340
|
-
else if (command === 'search' && args[0]) {
|
|
341
|
-
// Search for query
|
|
342
|
-
cli.flags.searchQuery = args.join(' ');
|
|
343
|
-
}
|
|
344
|
-
else if (command === 'playlist' && args[0]) {
|
|
345
|
-
// Play specific playlist
|
|
346
|
-
cli.flags.playPlaylist = args[0];
|
|
347
|
-
}
|
|
348
|
-
else if (command === 'suggestions') {
|
|
349
|
-
// Show suggestions
|
|
350
|
-
cli.flags.showSuggestions = true;
|
|
351
|
-
}
|
|
352
|
-
else if (command === 'pause') {
|
|
353
|
-
cli.flags.action = 'pause';
|
|
354
|
-
}
|
|
355
|
-
else if (command === 'resume') {
|
|
356
|
-
cli.flags.action = 'resume';
|
|
357
|
-
}
|
|
358
|
-
else if (command === 'skip') {
|
|
359
|
-
cli.flags.action = 'next';
|
|
360
|
-
}
|
|
361
|
-
else if (command === 'back') {
|
|
362
|
-
cli.flags.action = 'previous';
|
|
363
|
-
}
|
|
364
|
-
const flags = cli.flags;
|
|
365
|
-
const shouldRunDirectPlayback = requiresImmediatePlayback(flags) &&
|
|
366
|
-
(flags.headless || !isInteractiveTerminal);
|
|
367
|
-
if (shouldRunDirectPlayback) {
|
|
368
|
-
void (async () => {
|
|
369
|
-
const dependencyCheck = await ensurePlaybackDependencies({
|
|
370
|
-
interactive: isInteractiveTerminal,
|
|
371
|
-
});
|
|
372
|
-
if (!dependencyCheck.ready) {
|
|
373
|
-
process.exit(1);
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
|
-
try {
|
|
377
|
-
await runDirectPlaybackCommand(flags);
|
|
378
|
-
process.exit(0);
|
|
379
|
-
}
|
|
380
|
-
catch (error) {
|
|
381
|
-
console.error(`✗ Playback failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
382
|
-
process.exit(1);
|
|
383
|
-
}
|
|
384
|
-
})();
|
|
385
|
-
}
|
|
386
|
-
else if (command === 'import') {
|
|
387
|
-
// Handle import commands
|
|
388
|
-
void (async () => {
|
|
389
|
-
const source = args[0];
|
|
390
|
-
const url = args[1];
|
|
391
|
-
if (!source || !url) {
|
|
392
|
-
console.error('Usage: youtube-music-cli import <spotify|youtube> <url-or-id>');
|
|
393
|
-
process.exit(1);
|
|
394
|
-
}
|
|
395
|
-
if (source !== 'spotify' && source !== 'youtube') {
|
|
396
|
-
console.error('Invalid source. Use "spotify" or "youtube".');
|
|
397
|
-
process.exit(1);
|
|
398
|
-
}
|
|
399
|
-
const importService = getImportService();
|
|
400
|
-
const customName = cli.flags.name;
|
|
401
|
-
try {
|
|
402
|
-
console.log(`Importing ${source} playlist...`);
|
|
403
|
-
const result = await importService.importPlaylist(source, url, customName);
|
|
404
|
-
console.log(`\n✓ Import completed!`);
|
|
405
|
-
console.log(` Playlist: ${result.playlistName}`);
|
|
406
|
-
console.log(` Matched: ${result.matched}/${result.total} tracks`);
|
|
407
|
-
if (result.errors.length > 0) {
|
|
408
|
-
console.log(`\nErrors:`);
|
|
409
|
-
for (const error of result.errors.slice(0, 10)) {
|
|
410
|
-
console.log(` - ${error}`);
|
|
411
|
-
}
|
|
412
|
-
if (result.errors.length > 10) {
|
|
413
|
-
console.log(` ... and ${result.errors.length - 10} more`);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
process.exit(0);
|
|
417
|
-
}
|
|
418
|
-
catch (error) {
|
|
419
|
-
console.error(`✗ Import failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
420
|
-
process.exit(1);
|
|
421
|
-
}
|
|
422
|
-
})();
|
|
423
|
-
}
|
|
424
|
-
else if (cli.flags.web || cli.flags.webOnly) {
|
|
425
|
-
// Handle web server flags
|
|
426
|
-
void (async () => {
|
|
427
|
-
const webManager = getWebServerManager();
|
|
428
|
-
try {
|
|
429
|
-
if (shouldCheckPlaybackDependencies(command, flags)) {
|
|
430
|
-
const dependencyCheck = await ensurePlaybackDependencies({
|
|
431
|
-
interactive: isInteractiveTerminal,
|
|
432
|
-
});
|
|
433
|
-
if (!dependencyCheck.ready && requiresImmediatePlayback(flags)) {
|
|
434
|
-
process.exit(1);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
await webManager.start({
|
|
439
|
-
enabled: true,
|
|
440
|
-
host: cli.flags.webHost ?? 'localhost',
|
|
441
|
-
port: cli.flags.webPort ?? 8080,
|
|
442
|
-
webOnly: cli.flags.webOnly,
|
|
443
|
-
auth: cli.flags.webAuth,
|
|
444
|
-
});
|
|
445
|
-
const serverUrl = webManager.getServerUrl();
|
|
446
|
-
console.log(`Web UI server running at: ${serverUrl}`);
|
|
447
|
-
// Set up import progress streaming
|
|
448
|
-
const streamingService = getWebStreamingService();
|
|
449
|
-
const importService = getImportService();
|
|
450
|
-
importService.onProgress(progress => {
|
|
451
|
-
streamingService.onImportProgress(progress);
|
|
452
|
-
});
|
|
453
|
-
// If web-only mode, just keep the server running
|
|
454
|
-
if (cli.flags.webOnly) {
|
|
455
|
-
console.log('Running in web-only mode. Press Ctrl+C to exit.');
|
|
456
|
-
// Keep process alive
|
|
457
|
-
process.on('SIGINT', () => {
|
|
458
|
-
console.log('\nShutting down web server...');
|
|
459
|
-
void webManager.stop().then(() => process.exit(0));
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
else {
|
|
463
|
-
// Also render the CLI UI
|
|
464
|
-
render(_jsx(App, { flags: flags }));
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
catch (error) {
|
|
468
|
-
console.error(`Failed to start web server: ${error instanceof Error ? error.message : String(error)}`);
|
|
469
|
-
process.exit(1);
|
|
470
|
-
}
|
|
471
|
-
})();
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
void (async () => {
|
|
475
|
-
if (shouldCheckPlaybackDependencies(command, flags)) {
|
|
476
|
-
const dependencyCheck = await ensurePlaybackDependencies({
|
|
477
|
-
interactive: isInteractiveTerminal,
|
|
478
|
-
});
|
|
479
|
-
if (!dependencyCheck.ready && requiresImmediatePlayback(flags)) {
|
|
480
|
-
process.exit(1);
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
// Check for updates before rendering the app (skip in web-only mode)
|
|
485
|
-
if (!cli.flags.webOnly) {
|
|
486
|
-
const versionCheck = getVersionCheckService();
|
|
487
|
-
const config = getConfigService();
|
|
488
|
-
const lastCheck = config.getLastVersionCheck();
|
|
489
|
-
if (versionCheck.shouldCheck(lastCheck)) {
|
|
490
|
-
const result = await versionCheck.checkForUpdates(APP_VERSION);
|
|
491
|
-
config.setLastVersionCheck(versionCheck.markChecked());
|
|
492
|
-
if (result.hasUpdate) {
|
|
493
|
-
console.log('');
|
|
494
|
-
console.log(` Update available: ${APP_VERSION} → ${result.latestVersion}`);
|
|
495
|
-
console.log('Run: npm install -g @involvex/youtube-music-cli');
|
|
496
|
-
console.log('');
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
// Render the app
|
|
501
|
-
render(_jsx(App, { flags: flags }));
|
|
502
|
-
})();
|
|
503
|
-
}
|
|
504
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Error boundary component for robust error handling
|
|
3
|
-
import { Component } from 'react';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
export class ErrorBoundary extends Component {
|
|
6
|
-
state = {
|
|
7
|
-
hasError: false,
|
|
8
|
-
error: null,
|
|
9
|
-
};
|
|
10
|
-
static getDerivedStateFromError(error) {
|
|
11
|
-
return { hasError: true, error };
|
|
12
|
-
}
|
|
13
|
-
componentDidCatch(error, errorInfo) {
|
|
14
|
-
console.error('Uncaught error:', error, errorInfo);
|
|
15
|
-
}
|
|
16
|
-
render() {
|
|
17
|
-
if (this.state.hasError) {
|
|
18
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { color: "red", bold: true, children: "Something went wrong!" }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "white", children: this.state.error?.message }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "dim", children: "Press Ctrl+C to exit and restart the CLI." }) })] }));
|
|
19
|
-
}
|
|
20
|
-
return this.props.children;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Help component for keyboard shortcuts
|
|
3
|
-
import { Box, Text } from 'ink';
|
|
4
|
-
import { useTheme } from "../../hooks/useTheme.js";
|
|
5
|
-
import { useNavigation } from "../../hooks/useNavigation.js";
|
|
6
|
-
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
7
|
-
import { KEYBINDINGS } from "../../utils/constants.js";
|
|
8
|
-
import { useCallback } from 'react';
|
|
9
|
-
export default function Help() {
|
|
10
|
-
const { theme } = useTheme();
|
|
11
|
-
const { dispatch } = useNavigation();
|
|
12
|
-
const closeHelp = useCallback(() => {
|
|
13
|
-
dispatch({ category: 'GO_BACK' });
|
|
14
|
-
}, [dispatch]);
|
|
15
|
-
useKeyBinding(KEYBINDINGS.BACK, closeHelp);
|
|
16
|
-
useKeyBinding(KEYBINDINGS.SELECT, closeHelp);
|
|
17
|
-
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, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+Q" }), " - Background Play", _jsx(Text, { children: " | " }), _jsx(Text, { color: theme.colors.text, children: "Shift+R" }), " - Resume Control"] }) }), _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" }), ",", ' ', _jsx(Text, { color: theme.colors.text, children: "Enter" }), ",", ' ', _jsx(Text, { color: theme.colors.text, children: "q" }), ", or", ' ', _jsx(Text, { color: theme.colors.text, children: "?" }), " to close"] })] })] }));
|
|
18
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
// Shortcuts bar component
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { Box, Text } from 'ink';
|
|
5
|
-
import { usePlayer } from "../../hooks/usePlayer.js";
|
|
6
|
-
import { useTheme } from "../../hooks/useTheme.js";
|
|
7
|
-
import { useKeyBinding } from "../../hooks/useKeyboard.js";
|
|
8
|
-
import { KEYBINDINGS } from "../../utils/constants.js";
|
|
9
|
-
import { ICONS } from "../../utils/icons.js";
|
|
10
|
-
const FLASH_DURATION_MS = 300;
|
|
11
|
-
export default function ShortcutsBar() {
|
|
12
|
-
const { theme } = useTheme();
|
|
13
|
-
const { state: playerState, pause, resume, next, previous, volumeUp, volumeDown, volumeFineUp, volumeFineDown, toggleShuffle, toggleRepeat, } = usePlayer();
|
|
14
|
-
const [flashState, setFlashState] = useState({});
|
|
15
|
-
const flash = (key) => {
|
|
16
|
-
setFlashState(prev => ({ ...prev, [key]: true }));
|
|
17
|
-
setTimeout(() => {
|
|
18
|
-
setFlashState(prev => ({ ...prev, [key]: false }));
|
|
19
|
-
}, FLASH_DURATION_MS);
|
|
20
|
-
};
|
|
21
|
-
const shortcutColor = (key) => flashState[key] ? theme.colors.success : theme.colors.text;
|
|
22
|
-
// Register key bindings globally
|
|
23
|
-
const handlePlayPause = () => {
|
|
24
|
-
flash('playPause');
|
|
25
|
-
if (playerState.isPlaying) {
|
|
26
|
-
pause();
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
resume();
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
useKeyBinding(KEYBINDINGS.PLAY_PAUSE, handlePlayPause);
|
|
33
|
-
useKeyBinding(KEYBINDINGS.NEXT, () => {
|
|
34
|
-
flash('next');
|
|
35
|
-
next();
|
|
36
|
-
});
|
|
37
|
-
useKeyBinding(KEYBINDINGS.PREVIOUS, () => {
|
|
38
|
-
flash('prev');
|
|
39
|
-
previous();
|
|
40
|
-
});
|
|
41
|
-
useKeyBinding(KEYBINDINGS.VOLUME_UP, () => {
|
|
42
|
-
flash('volume');
|
|
43
|
-
volumeUp();
|
|
44
|
-
});
|
|
45
|
-
useKeyBinding(KEYBINDINGS.VOLUME_DOWN, () => {
|
|
46
|
-
flash('volume');
|
|
47
|
-
volumeDown();
|
|
48
|
-
});
|
|
49
|
-
useKeyBinding(KEYBINDINGS.VOLUME_FINE_UP, () => {
|
|
50
|
-
flash('volume');
|
|
51
|
-
volumeFineUp();
|
|
52
|
-
});
|
|
53
|
-
useKeyBinding(KEYBINDINGS.VOLUME_FINE_DOWN, () => {
|
|
54
|
-
flash('volume');
|
|
55
|
-
volumeFineDown();
|
|
56
|
-
});
|
|
57
|
-
useKeyBinding(KEYBINDINGS.SHUFFLE, () => {
|
|
58
|
-
flash('shuffle');
|
|
59
|
-
toggleShuffle();
|
|
60
|
-
});
|
|
61
|
-
useKeyBinding(KEYBINDINGS.REPEAT, () => {
|
|
62
|
-
flash('repeat');
|
|
63
|
-
toggleRepeat();
|
|
64
|
-
});
|
|
65
|
-
// Note: SETTINGS keybinding handled by MainLayout to avoid double-dispatch
|
|
66
|
-
const shuffleColor = flashState['shuffle']
|
|
67
|
-
? theme.colors.success
|
|
68
|
-
: playerState.shuffle
|
|
69
|
-
? theme.colors.primary
|
|
70
|
-
: theme.colors.dim;
|
|
71
|
-
const repeatColor = flashState['repeat']
|
|
72
|
-
? theme.colors.success
|
|
73
|
-
: playerState.repeat !== 'off'
|
|
74
|
-
? theme.colors.secondary
|
|
75
|
-
: theme.colors.dim;
|
|
76
|
-
const volumeColor = flashState['volume']
|
|
77
|
-
? theme.colors.success
|
|
78
|
-
: theme.colors.primary;
|
|
79
|
-
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: shortcutColor('playPause'), children: [playerState.isPlaying ? ICONS.PAUSE : ICONS.PLAY_PAUSE_ON, " [Space]"] }), ' ', "| ", _jsxs(Text, { color: shortcutColor('prev'), children: [ICONS.PREV, " [B/\u2190]"] }), " |", ' ', _jsxs(Text, { color: shortcutColor('next'), children: [ICONS.NEXT, " [N/\u2192]"] }), " |", ' ', _jsxs(Text, { color: shuffleColor, children: [ICONS.SHUFFLE, " [Shift+S]"] }), " |", ' ', _jsxs(Text, { color: repeatColor, children: [playerState.repeat === 'one' ? ICONS.REPEAT_ONE : 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: shuffleColor, children: ICONS.SHUFFLE }), ' ', _jsx(Text, { color: repeatColor, children: playerState.repeat === 'one' ? ICONS.REPEAT_ONE : ICONS.REPEAT_ALL }), ' ', _jsxs(Text, { color: theme.colors.dim, children: [ICONS.VOLUME, " [+/-]"] }), ' ', _jsxs(Text, { color: volumeColor, children: [playerState.volume, "%"] })] })] }));
|
|
80
|
-
}
|