osu-play 1.3.2 → 1.3.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/README.md +4 -2
- package/dist/cli.cjs +106 -13
- package/dist/cli.cjs.map +6 -6
- package/dist/cli.js +105 -12
- package/dist/cli.js.map +6 -6
- package/dist/core/player/mod.d.ts +1 -1
- package/dist/core/player/session.d.ts +5 -0
- package/dist/core/utils/mod.d.ts +1 -0
- package/dist/index.cjs +30 -4
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +29 -3
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,15 +54,17 @@ osu-play --loop
|
|
|
54
54
|
- `Left/Right` or `h/l`: Seek backward or forward by 5 seconds
|
|
55
55
|
- `Enter`: Play the selected track
|
|
56
56
|
- `Space`: Pause or resume playback
|
|
57
|
+
- `o`: Reveal the selected track in the native file manager
|
|
57
58
|
- `n` / `p`: Next or previous track
|
|
58
59
|
- `PageUp` / `PageDown`: Jump faster through the playlist
|
|
59
60
|
- `r`: Toggle looping
|
|
60
61
|
- `s`: Stop playback
|
|
61
62
|
- `q`: Quit
|
|
62
63
|
- `/`: Enter search mode
|
|
63
|
-
- Type in search mode:
|
|
64
|
+
- Type in search mode: Filter tracks by title
|
|
65
|
+
- `Up`/`Down` or `Ctrl+P`/`Ctrl+N` in search mode: Navigate matching tracks
|
|
64
66
|
- `Backspace`: Edit the search query
|
|
65
|
-
- `Enter
|
|
67
|
+
- `Enter` in search mode: Play the selected match, leave search entry, and keep the filter
|
|
66
68
|
- `Esc`: Leave search mode, or clear the current query outside search mode
|
|
67
69
|
|
|
68
70
|
### API
|
package/dist/cli.cjs
CHANGED
|
@@ -42,6 +42,30 @@ var __export = (target, all) => {
|
|
|
42
42
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
43
43
|
|
|
44
44
|
// src/core/utils/mod.ts
|
|
45
|
+
var exports_mod = {};
|
|
46
|
+
__export(exports_mod, {
|
|
47
|
+
revealFile: () => revealFile,
|
|
48
|
+
hashedFilePath: () => hashedFilePath,
|
|
49
|
+
getRealmDBPath: () => getRealmDBPath,
|
|
50
|
+
getDefaultOsuDataDir: () => getDefaultOsuDataDir,
|
|
51
|
+
getDefaultConfigPath: () => getDefaultConfigPath,
|
|
52
|
+
getDataDir: () => getDataDir,
|
|
53
|
+
getConfigDir: () => getConfigDir
|
|
54
|
+
});
|
|
55
|
+
function revealFile(filePath) {
|
|
56
|
+
const command = process.platform === "darwin" ? { args: ["-R", filePath], executable: "open" } : process.platform === "win32" ? { args: ["/select,", filePath], executable: "explorer.exe" } : { args: [import_node_path.default.dirname(filePath)], executable: "xdg-open" };
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const child = import_node_child_process.spawn(command.executable, command.args, {
|
|
59
|
+
detached: true,
|
|
60
|
+
stdio: "ignore"
|
|
61
|
+
});
|
|
62
|
+
child.once("error", reject);
|
|
63
|
+
child.once("spawn", () => {
|
|
64
|
+
child.unref();
|
|
65
|
+
resolve();
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
45
69
|
function getDataDir() {
|
|
46
70
|
switch (process.platform) {
|
|
47
71
|
case "linux":
|
|
@@ -109,10 +133,11 @@ function getRealmDBPath(legacyAppConfigDirOrOptions = {}, maybeOptions = {}) {
|
|
|
109
133
|
}
|
|
110
134
|
return osuDBPath;
|
|
111
135
|
}
|
|
112
|
-
var import_node_path, import_node_fs;
|
|
136
|
+
var import_node_path, import_node_fs, import_node_child_process;
|
|
113
137
|
var init_mod = __esm(() => {
|
|
114
138
|
import_node_path = __toESM(require("node:path"));
|
|
115
139
|
import_node_fs = require("node:fs");
|
|
140
|
+
import_node_child_process = require("node:child_process");
|
|
116
141
|
});
|
|
117
142
|
|
|
118
143
|
// src/core/lazer/compat.ts
|
|
@@ -415,8 +440,8 @@ var init_mod2 = __esm(() => {
|
|
|
415
440
|
});
|
|
416
441
|
|
|
417
442
|
// src/core/lazer/mod.ts
|
|
418
|
-
var
|
|
419
|
-
__export(
|
|
443
|
+
var exports_mod2 = {};
|
|
444
|
+
__export(exports_mod2, {
|
|
420
445
|
inspectLazerSchemaEntries: () => inspectLazerSchemaEntries,
|
|
421
446
|
inspectLazerSchema: () => inspectLazerSchema,
|
|
422
447
|
hashedFilePath: () => hashedFilePath,
|
|
@@ -555,7 +580,7 @@ function buildPlaylist(beatmapSets, osuDataDir) {
|
|
|
555
580
|
}
|
|
556
581
|
|
|
557
582
|
// src/core/player/mpv.ts
|
|
558
|
-
var
|
|
583
|
+
var import_node_child_process2 = require("node:child_process");
|
|
559
584
|
var import_node_fs2 = require("node:fs");
|
|
560
585
|
var import_node_net = __toESM(require("node:net"));
|
|
561
586
|
var import_node_os = __toESM(require("node:os"));
|
|
@@ -710,7 +735,7 @@ class MpvPlayerBackend {
|
|
|
710
735
|
this.cleanupSocketPath();
|
|
711
736
|
this.disposing = false;
|
|
712
737
|
this.stderrBuffer = "";
|
|
713
|
-
const processHandle =
|
|
738
|
+
const processHandle = import_node_child_process2.spawn("mpv", [
|
|
714
739
|
"--no-config",
|
|
715
740
|
"--no-terminal",
|
|
716
741
|
"--idle=yes",
|
|
@@ -989,6 +1014,13 @@ function findTrackIndexByQuery(playlist, query) {
|
|
|
989
1014
|
}
|
|
990
1015
|
return playlist.findIndex((track) => normalizeSearchText(track.title).includes(normalizedQuery));
|
|
991
1016
|
}
|
|
1017
|
+
function findTrackIndicesByQuery(playlist, query) {
|
|
1018
|
+
const normalizedQuery = normalizeSearchText(query);
|
|
1019
|
+
if (!normalizedQuery) {
|
|
1020
|
+
return playlist.map((_, index) => index);
|
|
1021
|
+
}
|
|
1022
|
+
return playlist.flatMap((track, index) => normalizeSearchText(track.title).includes(normalizedQuery) ? [index] : []);
|
|
1023
|
+
}
|
|
992
1024
|
|
|
993
1025
|
class PlaylistPlayerSession {
|
|
994
1026
|
backend;
|
|
@@ -999,6 +1031,7 @@ class PlaylistPlayerSession {
|
|
|
999
1031
|
deleteTrack;
|
|
1000
1032
|
playlist;
|
|
1001
1033
|
reloadPlaylist;
|
|
1034
|
+
revealTrack;
|
|
1002
1035
|
searchQuery = "";
|
|
1003
1036
|
selectedIndex = 0;
|
|
1004
1037
|
operationQueue = Promise.resolve();
|
|
@@ -1009,6 +1042,7 @@ class PlaylistPlayerSession {
|
|
|
1009
1042
|
this.deleteTrack = options.deleteTrack;
|
|
1010
1043
|
this.loop = options.loop ?? false;
|
|
1011
1044
|
this.reloadPlaylist = options.reloadPlaylist;
|
|
1045
|
+
this.revealTrack = options.revealTrack;
|
|
1012
1046
|
this.unsubscribeBackend = backend.subscribe((event) => {
|
|
1013
1047
|
this.handleBackendEvent(event);
|
|
1014
1048
|
});
|
|
@@ -1049,24 +1083,44 @@ class PlaylistPlayerSession {
|
|
|
1049
1083
|
if (this.playlist.length === 0) {
|
|
1050
1084
|
return;
|
|
1051
1085
|
}
|
|
1086
|
+
if (this.searchQuery) {
|
|
1087
|
+
this.moveSearchSelection(delta);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1052
1090
|
this.selectedIndex = wrapIndex(this.selectedIndex + delta, this.playlist.length);
|
|
1053
1091
|
this.emit();
|
|
1054
1092
|
}
|
|
1055
1093
|
moveSelectionPage(delta, pageSize) {
|
|
1094
|
+
if (this.searchQuery) {
|
|
1095
|
+
this.moveSearchSelection(delta * Math.max(1, pageSize));
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1056
1098
|
this.moveSelection(delta * Math.max(1, pageSize));
|
|
1057
1099
|
}
|
|
1100
|
+
moveSearchSelection(delta) {
|
|
1101
|
+
const matches = findTrackIndicesByQuery(this.playlist, this.searchQuery);
|
|
1102
|
+
if (matches.length === 0) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const currentMatchIndex = matches.indexOf(this.selectedIndex);
|
|
1106
|
+
const nextMatchIndex = wrapIndex(currentMatchIndex + delta, matches.length);
|
|
1107
|
+
this.selectedIndex = matches[nextMatchIndex] ?? this.selectedIndex;
|
|
1108
|
+
this.emit();
|
|
1109
|
+
}
|
|
1058
1110
|
selectHome() {
|
|
1059
1111
|
if (this.playlist.length === 0) {
|
|
1060
1112
|
return;
|
|
1061
1113
|
}
|
|
1062
|
-
this.
|
|
1114
|
+
const matches = findTrackIndicesByQuery(this.playlist, this.searchQuery);
|
|
1115
|
+
this.selectedIndex = matches[0] ?? 0;
|
|
1063
1116
|
this.emit();
|
|
1064
1117
|
}
|
|
1065
1118
|
selectEnd() {
|
|
1066
1119
|
if (this.playlist.length === 0) {
|
|
1067
1120
|
return;
|
|
1068
1121
|
}
|
|
1069
|
-
|
|
1122
|
+
const matches = findTrackIndicesByQuery(this.playlist, this.searchQuery);
|
|
1123
|
+
this.selectedIndex = matches.at(-1) ?? this.playlist.length - 1;
|
|
1070
1124
|
this.emit();
|
|
1071
1125
|
}
|
|
1072
1126
|
setSelectionIndex(index) {
|
|
@@ -1172,6 +1226,22 @@ class PlaylistPlayerSession {
|
|
|
1172
1226
|
}
|
|
1173
1227
|
});
|
|
1174
1228
|
}
|
|
1229
|
+
async revealSelectedTrack() {
|
|
1230
|
+
const track = this.playlist[this.selectedIndex];
|
|
1231
|
+
if (!track) {
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
if (!this.revealTrack) {
|
|
1235
|
+
this.reportError(new Error("Opening the containing folder is unavailable."));
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
try {
|
|
1239
|
+
this.clearError();
|
|
1240
|
+
await this.revealTrack(track);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
this.reportError(error);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1175
1245
|
async deleteSelectedTrack() {
|
|
1176
1246
|
const selectedIndex = this.selectedIndex;
|
|
1177
1247
|
await this.enqueueOperation(async () => {
|
|
@@ -1473,6 +1543,10 @@ class PlaylistPlayerScreen {
|
|
|
1473
1543
|
this.session.toggleLoop();
|
|
1474
1544
|
return;
|
|
1475
1545
|
}
|
|
1546
|
+
if (import_pi_tui.matchesKey(data, "o")) {
|
|
1547
|
+
this.session.revealSelectedTrack();
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1476
1550
|
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.backspace)) {
|
|
1477
1551
|
this.session.deleteSearchCharacter();
|
|
1478
1552
|
return;
|
|
@@ -1484,9 +1558,11 @@ class PlaylistPlayerScreen {
|
|
|
1484
1558
|
}
|
|
1485
1559
|
render(width) {
|
|
1486
1560
|
const { playlist } = this.snapshot;
|
|
1561
|
+
const visibleIndices = this.snapshot.searchQuery ? findTrackIndicesByQuery(playlist, this.snapshot.searchQuery) : playlist.map((_, index) => index);
|
|
1562
|
+
const selectedVisibleIndex = Math.max(0, visibleIndices.indexOf(this.snapshot.selectedIndex));
|
|
1487
1563
|
const viewportHeight = Math.max(this.getViewportHeight(), RESERVED_ROWS);
|
|
1488
1564
|
const listHeight = this.getListHeight(viewportHeight);
|
|
1489
|
-
const { start, end } = getVisibleTrackRange(
|
|
1565
|
+
const { start, end } = getVisibleTrackRange(selectedVisibleIndex, visibleIndices.length, listHeight);
|
|
1490
1566
|
const position = formatSeconds(this.snapshot.timePositionSeconds);
|
|
1491
1567
|
const duration = formatSeconds(this.snapshot.durationSeconds);
|
|
1492
1568
|
const glyph = STATUS_GLYPH[this.snapshot.status] ?? "·";
|
|
@@ -1508,8 +1584,14 @@ class PlaylistPlayerScreen {
|
|
|
1508
1584
|
}
|
|
1509
1585
|
if (playlist.length === 0) {
|
|
1510
1586
|
lines.push(import_pi_tui.truncateToWidth("No tracks were found in your osu!lazer library.", width));
|
|
1587
|
+
} else if (visibleIndices.length === 0) {
|
|
1588
|
+
lines.push(import_pi_tui.truncateToWidth("No tracks match the current search.", width));
|
|
1511
1589
|
} else {
|
|
1512
|
-
for (let
|
|
1590
|
+
for (let visibleIndex = start;visibleIndex < end; visibleIndex += 1) {
|
|
1591
|
+
const index = visibleIndices[visibleIndex];
|
|
1592
|
+
if (index === undefined) {
|
|
1593
|
+
continue;
|
|
1594
|
+
}
|
|
1513
1595
|
const track = playlist[index];
|
|
1514
1596
|
if (!track) {
|
|
1515
1597
|
continue;
|
|
@@ -1524,7 +1606,7 @@ class PlaylistPlayerScreen {
|
|
|
1524
1606
|
while (lines.length < viewportHeight - 1) {
|
|
1525
1607
|
lines.push("");
|
|
1526
1608
|
}
|
|
1527
|
-
lines.push(import_pi_tui.truncateToWidth(style(this.searchMode ? "type to
|
|
1609
|
+
lines.push(import_pi_tui.truncateToWidth(style(this.searchMode ? "type to filter · ↑/↓ results · enter play · backspace edit · esc leave" : "j/k move · ⏎ play · space pause · n/p track · h/l seek · o reveal · r loop · d delete · / search · q quit", DIM), width));
|
|
1528
1610
|
return lines;
|
|
1529
1611
|
}
|
|
1530
1612
|
armPendingGoToTop() {
|
|
@@ -1566,6 +1648,7 @@ class PlaylistPlayerScreen {
|
|
|
1566
1648
|
}
|
|
1567
1649
|
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.enter)) {
|
|
1568
1650
|
this.searchMode = false;
|
|
1651
|
+
this.session.playSelected();
|
|
1569
1652
|
return;
|
|
1570
1653
|
}
|
|
1571
1654
|
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.escape)) {
|
|
@@ -1580,6 +1663,14 @@ class PlaylistPlayerScreen {
|
|
|
1580
1663
|
this.session.clearSearch();
|
|
1581
1664
|
return;
|
|
1582
1665
|
}
|
|
1666
|
+
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.up) || import_pi_tui.matchesKey(data, import_pi_tui.Key.ctrl("p"))) {
|
|
1667
|
+
this.session.moveSearchSelection(-1);
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.down) || import_pi_tui.matchesKey(data, import_pi_tui.Key.ctrl("n"))) {
|
|
1671
|
+
this.session.moveSearchSelection(1);
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1583
1674
|
const printable = decodePrintableText(data);
|
|
1584
1675
|
if (!printable) {
|
|
1585
1676
|
return;
|
|
@@ -1624,7 +1715,7 @@ async function loadRealmDependencies() {
|
|
|
1624
1715
|
try {
|
|
1625
1716
|
const [{ default: Realm8 }, lazerModule] = await Promise.all([
|
|
1626
1717
|
import("realm"),
|
|
1627
|
-
Promise.resolve().then(() => (init_mod3(),
|
|
1718
|
+
Promise.resolve().then(() => (init_mod3(), exports_mod2))
|
|
1628
1719
|
]);
|
|
1629
1720
|
return {
|
|
1630
1721
|
Realm: Realm8,
|
|
@@ -1689,11 +1780,13 @@ async function deleteTrackFromCollection(track, osuDataDir) {
|
|
|
1689
1780
|
await deleteBeatmapSet2(track, osuDataDir);
|
|
1690
1781
|
}
|
|
1691
1782
|
async function runTuiPlayer(playlist, osuDataDir, loop) {
|
|
1783
|
+
const { revealFile: revealFile2 } = await Promise.resolve().then(() => (init_mod(), exports_mod));
|
|
1692
1784
|
const backend = new MpvPlayerBackend;
|
|
1693
1785
|
const session = new PlaylistPlayerSession(playlist, backend, {
|
|
1694
1786
|
deleteTrack: (track) => deleteTrackFromCollection(track, osuDataDir),
|
|
1695
1787
|
loop,
|
|
1696
|
-
reloadPlaylist: () => createPlaylist(osuDataDir)
|
|
1788
|
+
reloadPlaylist: () => createPlaylist(osuDataDir),
|
|
1789
|
+
revealTrack: (track) => revealFile2(track.path)
|
|
1697
1790
|
});
|
|
1698
1791
|
const terminal = new import_pi_tui2.ProcessTerminal;
|
|
1699
1792
|
const tui = new import_pi_tui2.TUI(terminal);
|
|
@@ -1752,4 +1845,4 @@ main().then(() => {
|
|
|
1752
1845
|
process.exit(1);
|
|
1753
1846
|
});
|
|
1754
1847
|
|
|
1755
|
-
//# debugId=
|
|
1848
|
+
//# debugId=9FB5B0E40D3B9A4364756E2164756E21
|