osu-play 1.2.1 → 1.3.1
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/cli.cjs +259 -83
- package/dist/cli.cjs.map +8 -8
- package/dist/cli.js +259 -83
- package/dist/cli.js.map +8 -8
- package/dist/core/lazer/mod.d.ts +2 -1
- package/dist/core/player/session.d.ts +8 -1
- package/dist/core/playlist/mod.d.ts +6 -0
- package/dist/core/tui/player-screen.d.ts +5 -1
- package/dist/index.cjs +130 -79
- package/dist/index.cjs.map +6 -6
- package/dist/index.js +128 -77
- package/dist/index.js.map +6 -6
- package/package.json +10 -10
package/dist/cli.cjs
CHANGED
|
@@ -424,6 +424,7 @@ __export(exports_mod, {
|
|
|
424
424
|
getLazerDB: () => getLazerDB,
|
|
425
425
|
getBeatmapSets: () => getBeatmapSets,
|
|
426
426
|
formatLazerSchemaCompatibilityError: () => formatLazerSchemaCompatibilityError,
|
|
427
|
+
deleteBeatmapSet: () => deleteBeatmapSet,
|
|
427
428
|
RealmUser: () => RealmUser,
|
|
428
429
|
RealmNamedFileUsage: () => RealmNamedFileUsage,
|
|
429
430
|
RealmFile: () => RealmFile,
|
|
@@ -438,6 +439,48 @@ function inspectLazerSchema(realm) {
|
|
|
438
439
|
function getBeatmapSets(realm) {
|
|
439
440
|
return realm.objects("BeatmapSet");
|
|
440
441
|
}
|
|
442
|
+
function queryBeatmapSet(realm, track) {
|
|
443
|
+
const beatmapSets = realm.objects("BeatmapSet");
|
|
444
|
+
if (track.beatmapSetId !== null && track.beatmapSetId !== undefined) {
|
|
445
|
+
const byId = realm.objectForPrimaryKey("BeatmapSet", track.beatmapSetId) ?? beatmapSets.filtered("ID == $0", track.beatmapSetId)[0];
|
|
446
|
+
if (byId) {
|
|
447
|
+
return byId;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (track.beatmapSetHash) {
|
|
451
|
+
return beatmapSets.filtered("Hash == $0", track.beatmapSetHash)[0] ?? null;
|
|
452
|
+
}
|
|
453
|
+
return null;
|
|
454
|
+
}
|
|
455
|
+
async function deleteBeatmapSet(track, osuDataDir) {
|
|
456
|
+
const realmDBPath = getRealmDBPath({ osuDataDir });
|
|
457
|
+
if (!realmDBPath) {
|
|
458
|
+
throw new Error("Realm DB not found");
|
|
459
|
+
}
|
|
460
|
+
const realm = await import_realm7.default.open({ path: realmDBPath });
|
|
461
|
+
try {
|
|
462
|
+
let markedForDeletion = false;
|
|
463
|
+
realm.write(() => {
|
|
464
|
+
const beatmapSet = queryBeatmapSet(realm, track);
|
|
465
|
+
if (!beatmapSet) {
|
|
466
|
+
throw new Error(`Beatmap set for "${track.title}" no longer exists.`);
|
|
467
|
+
}
|
|
468
|
+
if (beatmapSet.Protected) {
|
|
469
|
+
throw new Error(`"${track.title}" is protected and cannot be deleted.`);
|
|
470
|
+
}
|
|
471
|
+
if (beatmapSet.DeletePending) {
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
beatmapSet.DeletePending = true;
|
|
475
|
+
markedForDeletion = true;
|
|
476
|
+
});
|
|
477
|
+
if (!markedForDeletion) {
|
|
478
|
+
throw new Error(`"${track.title}" is already pending deletion.`);
|
|
479
|
+
}
|
|
480
|
+
} finally {
|
|
481
|
+
realm.close();
|
|
482
|
+
}
|
|
483
|
+
}
|
|
441
484
|
var import_realm7, getLazerDB = async (realmDBPath) => {
|
|
442
485
|
return import_realm7.default.open({
|
|
443
486
|
path: realmDBPath,
|
|
@@ -453,6 +496,7 @@ var import_realm7, getLazerDB = async (realmDBPath) => {
|
|
|
453
496
|
};
|
|
454
497
|
var init_mod3 = __esm(() => {
|
|
455
498
|
init_compat();
|
|
499
|
+
init_mod();
|
|
456
500
|
init_mod2();
|
|
457
501
|
init_compat();
|
|
458
502
|
init_mod();
|
|
@@ -462,7 +506,7 @@ var init_mod3 = __esm(() => {
|
|
|
462
506
|
// src/core/cli/main.ts
|
|
463
507
|
var import_node_path3 = __toESM(require("node:path"));
|
|
464
508
|
var import_node_fs3 = require("node:fs");
|
|
465
|
-
var import_pi_tui2 = require("@
|
|
509
|
+
var import_pi_tui2 = require("@earendil-works/pi-tui");
|
|
466
510
|
var import_yargs = __toESM(require("yargs/yargs"));
|
|
467
511
|
var import_helpers = require("yargs/helpers");
|
|
468
512
|
|
|
@@ -488,6 +532,9 @@ function buildPlaylist(beatmapSets, osuDataDir) {
|
|
|
488
532
|
const seenHashes = new Set;
|
|
489
533
|
const playlist = [];
|
|
490
534
|
for (const beatmapSet of beatmapSets) {
|
|
535
|
+
if (beatmapSet.DeletePending) {
|
|
536
|
+
continue;
|
|
537
|
+
}
|
|
491
538
|
for (const beatmap of beatmapSet.Beatmaps) {
|
|
492
539
|
const hash = getNamedFileHash(beatmap.Metadata.AudioFile ?? "", beatmapSet);
|
|
493
540
|
if (!hash || seenHashes.has(hash)) {
|
|
@@ -495,6 +542,9 @@ function buildPlaylist(beatmapSets, osuDataDir) {
|
|
|
495
542
|
}
|
|
496
543
|
seenHashes.add(hash);
|
|
497
544
|
playlist.push({
|
|
545
|
+
beatmapSetHash: beatmapSet.Hash ?? null,
|
|
546
|
+
beatmapSetId: beatmapSet.ID,
|
|
547
|
+
beatmapSetKey: String(beatmapSet.ID ?? hash),
|
|
498
548
|
hash,
|
|
499
549
|
path: hashedFilePath(hash, osuDataDir),
|
|
500
550
|
title: formatTrackTitle(beatmap.Metadata)
|
|
@@ -603,11 +653,12 @@ class MpvPlayerBackend {
|
|
|
603
653
|
});
|
|
604
654
|
throw error;
|
|
605
655
|
}
|
|
656
|
+
this.idleActive = false;
|
|
606
657
|
this.snapshot = {
|
|
607
658
|
...this.snapshot,
|
|
608
659
|
currentPath: filePath,
|
|
609
660
|
errorMessage: null,
|
|
610
|
-
status:
|
|
661
|
+
status: "playing",
|
|
611
662
|
timePositionSeconds: this.snapshot.timePositionSeconds ?? 0
|
|
612
663
|
};
|
|
613
664
|
this.emit({
|
|
@@ -800,17 +851,20 @@ ${detail}` : "";
|
|
|
800
851
|
return;
|
|
801
852
|
}
|
|
802
853
|
if (response.event === "end-file") {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
854
|
+
const reason = response.reason ?? "unknown";
|
|
855
|
+
if (reason !== "stop" && reason !== "redirect") {
|
|
856
|
+
this.snapshot = {
|
|
857
|
+
...this.snapshot,
|
|
858
|
+
status: "stopped",
|
|
859
|
+
timePositionSeconds: reason === "eof" ? this.snapshot.durationSeconds : this.snapshot.timePositionSeconds
|
|
860
|
+
};
|
|
861
|
+
this.emit({
|
|
862
|
+
snapshot: this.snapshot,
|
|
863
|
+
type: "state"
|
|
864
|
+
});
|
|
865
|
+
}
|
|
812
866
|
this.emit({
|
|
813
|
-
reason
|
|
867
|
+
reason,
|
|
814
868
|
type: "ended"
|
|
815
869
|
});
|
|
816
870
|
}
|
|
@@ -942,14 +996,19 @@ class PlaylistPlayerSession {
|
|
|
942
996
|
currentIndex = null;
|
|
943
997
|
errorMessage = null;
|
|
944
998
|
loop;
|
|
999
|
+
deleteTrack;
|
|
945
1000
|
playlist;
|
|
1001
|
+
reloadPlaylist;
|
|
946
1002
|
searchQuery = "";
|
|
947
1003
|
selectedIndex = 0;
|
|
1004
|
+
operationQueue = Promise.resolve();
|
|
948
1005
|
unsubscribeBackend;
|
|
949
1006
|
constructor(playlist, backend, options = {}) {
|
|
950
1007
|
this.backend = backend;
|
|
951
1008
|
this.playlist = playlist;
|
|
1009
|
+
this.deleteTrack = options.deleteTrack;
|
|
952
1010
|
this.loop = options.loop ?? false;
|
|
1011
|
+
this.reloadPlaylist = options.reloadPlaylist;
|
|
953
1012
|
this.unsubscribeBackend = backend.subscribe((event) => {
|
|
954
1013
|
this.handleBackendEvent(event);
|
|
955
1014
|
});
|
|
@@ -1043,61 +1102,113 @@ class PlaylistPlayerSession {
|
|
|
1043
1102
|
this.emit();
|
|
1044
1103
|
}
|
|
1045
1104
|
async playSelected() {
|
|
1046
|
-
|
|
1105
|
+
const selectedIndex = this.selectedIndex;
|
|
1106
|
+
await this.enqueueOperation(async () => {
|
|
1107
|
+
await this.playIndex(selectedIndex);
|
|
1108
|
+
});
|
|
1047
1109
|
}
|
|
1048
1110
|
async playNext() {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1111
|
+
await this.enqueueOperation(async () => {
|
|
1112
|
+
const nextIndex = this.getAdjacentIndex(1, true);
|
|
1113
|
+
if (nextIndex === null) {
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
await this.playIndex(nextIndex);
|
|
1117
|
+
});
|
|
1054
1118
|
}
|
|
1055
1119
|
async playPrevious() {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1120
|
+
await this.enqueueOperation(async () => {
|
|
1121
|
+
const previousIndex = this.getAdjacentIndex(-1, true);
|
|
1122
|
+
if (previousIndex === null) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
await this.playIndex(previousIndex);
|
|
1126
|
+
});
|
|
1061
1127
|
}
|
|
1062
1128
|
async togglePause() {
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
const
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1129
|
+
const selectedIndex = this.selectedIndex;
|
|
1130
|
+
await this.enqueueOperation(async () => {
|
|
1131
|
+
if (this.playlist.length === 0) {
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
const { status } = this.backend.getSnapshot();
|
|
1135
|
+
if (status === "stopped") {
|
|
1136
|
+
const restartIndex = this.currentIndex ?? selectedIndex;
|
|
1137
|
+
await this.playIndex(restartIndex);
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
try {
|
|
1141
|
+
this.clearError();
|
|
1142
|
+
await this.backend.togglePause();
|
|
1143
|
+
} catch (error) {
|
|
1144
|
+
this.reportError(error);
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1078
1147
|
}
|
|
1079
1148
|
async seekBy(seconds) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1149
|
+
await this.enqueueOperation(async () => {
|
|
1150
|
+
if (seconds === 0) {
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const { status } = this.backend.getSnapshot();
|
|
1154
|
+
if (status === "stopped") {
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
this.clearError();
|
|
1159
|
+
await this.backend.seekBy(seconds);
|
|
1160
|
+
} catch (error) {
|
|
1161
|
+
this.reportError(error);
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1093
1164
|
}
|
|
1094
1165
|
async stop() {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1166
|
+
await this.enqueueOperation(async () => {
|
|
1167
|
+
try {
|
|
1168
|
+
this.clearError();
|
|
1169
|
+
await this.backend.stop();
|
|
1170
|
+
} catch (error) {
|
|
1171
|
+
this.reportError(error);
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
async deleteSelectedTrack() {
|
|
1176
|
+
const selectedIndex = this.selectedIndex;
|
|
1177
|
+
await this.enqueueOperation(async () => {
|
|
1178
|
+
const track = this.playlist[selectedIndex];
|
|
1179
|
+
if (!track) {
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
if (!this.deleteTrack || !this.reloadPlaylist) {
|
|
1183
|
+
this.reportError(new Error("Beatmap deletion is unavailable in this session."));
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
const currentTrack = this.currentIndex !== null ? this.playlist[this.currentIndex] ?? null : null;
|
|
1187
|
+
const deletingCurrentTrack = currentTrack !== null && currentTrack.beatmapSetKey === track.beatmapSetKey;
|
|
1188
|
+
try {
|
|
1189
|
+
this.clearError();
|
|
1190
|
+
if (deletingCurrentTrack) {
|
|
1191
|
+
await this.backend.stop();
|
|
1192
|
+
this.currentIndex = null;
|
|
1193
|
+
}
|
|
1194
|
+
await this.deleteTrack(track);
|
|
1195
|
+
this.playlist = await this.reloadPlaylist();
|
|
1196
|
+
this.selectedIndex = clampIndex(selectedIndex, this.playlist.length);
|
|
1197
|
+
if (this.searchQuery) {
|
|
1198
|
+
const matchedIndex = findTrackIndexByQuery(this.playlist, this.searchQuery);
|
|
1199
|
+
if (matchedIndex !== -1) {
|
|
1200
|
+
this.selectedIndex = matchedIndex;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
if (!deletingCurrentTrack && currentTrack) {
|
|
1204
|
+
const nextCurrentIndex = this.playlist.findIndex((playlistTrack) => playlistTrack.hash === currentTrack.hash);
|
|
1205
|
+
this.currentIndex = nextCurrentIndex === -1 ? null : nextCurrentIndex;
|
|
1206
|
+
}
|
|
1207
|
+
this.emit();
|
|
1208
|
+
} catch (error) {
|
|
1209
|
+
this.reportError(error);
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1101
1212
|
}
|
|
1102
1213
|
reportError(error) {
|
|
1103
1214
|
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1130,23 +1241,25 @@ class PlaylistPlayerSession {
|
|
|
1130
1241
|
this.reportError(error);
|
|
1131
1242
|
return;
|
|
1132
1243
|
}
|
|
1244
|
+
this.selectedIndex = index;
|
|
1133
1245
|
this.emit();
|
|
1134
1246
|
}
|
|
1135
|
-
getAdjacentIndex(delta) {
|
|
1247
|
+
getAdjacentIndex(delta, wrap = false) {
|
|
1136
1248
|
if (this.playlist.length === 0) {
|
|
1137
1249
|
return null;
|
|
1138
1250
|
}
|
|
1139
1251
|
const baseIndex = this.currentIndex ?? this.selectedIndex;
|
|
1140
1252
|
const nextIndex = baseIndex + delta;
|
|
1253
|
+
const shouldWrap = wrap || this.loop;
|
|
1141
1254
|
if (nextIndex < 0) {
|
|
1142
|
-
return
|
|
1255
|
+
return shouldWrap ? this.playlist.length - 1 : null;
|
|
1143
1256
|
}
|
|
1144
1257
|
if (nextIndex >= this.playlist.length) {
|
|
1145
|
-
return
|
|
1258
|
+
return shouldWrap ? 0 : null;
|
|
1146
1259
|
}
|
|
1147
1260
|
return nextIndex;
|
|
1148
1261
|
}
|
|
1149
|
-
|
|
1262
|
+
handleBackendEvent(event) {
|
|
1150
1263
|
switch (event.type) {
|
|
1151
1264
|
case "state":
|
|
1152
1265
|
this.emit();
|
|
@@ -1155,16 +1268,23 @@ class PlaylistPlayerSession {
|
|
|
1155
1268
|
this.reportError(event.error);
|
|
1156
1269
|
return;
|
|
1157
1270
|
case "ended":
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1271
|
+
this.enqueueOperation(async () => {
|
|
1272
|
+
if (event.reason === "eof") {
|
|
1273
|
+
const nextIndex = this.getAdjacentIndex(1);
|
|
1274
|
+
if (nextIndex !== null) {
|
|
1275
|
+
await this.playIndex(nextIndex);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1163
1278
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1279
|
+
this.emit();
|
|
1280
|
+
});
|
|
1166
1281
|
}
|
|
1167
1282
|
}
|
|
1283
|
+
enqueueOperation(operation) {
|
|
1284
|
+
const queuedOperation = this.operationQueue.then(operation, operation);
|
|
1285
|
+
this.operationQueue = queuedOperation.catch(() => {});
|
|
1286
|
+
return queuedOperation;
|
|
1287
|
+
}
|
|
1168
1288
|
syncSelectionToSearch() {
|
|
1169
1289
|
const matchedIndex = findTrackIndexByQuery(this.playlist, this.searchQuery);
|
|
1170
1290
|
if (matchedIndex !== -1) {
|
|
@@ -1174,20 +1294,32 @@ class PlaylistPlayerSession {
|
|
|
1174
1294
|
}
|
|
1175
1295
|
}
|
|
1176
1296
|
// src/core/tui/player-screen.ts
|
|
1177
|
-
var import_pi_tui = require("@
|
|
1297
|
+
var import_pi_tui = require("@earendil-works/pi-tui");
|
|
1178
1298
|
var RESET = "\x1B[0m";
|
|
1179
1299
|
var DIM = "\x1B[2m";
|
|
1180
1300
|
var RED = "\x1B[31m";
|
|
1181
1301
|
var CYAN = "\x1B[36m";
|
|
1302
|
+
var BOLD = "\x1B[1m";
|
|
1182
1303
|
var INVERSE = "\x1B[7m";
|
|
1183
1304
|
var MIN_LIST_ROWS = 4;
|
|
1184
|
-
var RESERVED_ROWS =
|
|
1305
|
+
var RESERVED_ROWS = 5;
|
|
1185
1306
|
var PAGE_SIZE = 10;
|
|
1186
1307
|
var PENDING_G_TIMEOUT_MS = 400;
|
|
1187
1308
|
var SEEK_SECONDS = 5;
|
|
1309
|
+
var STATUS_GLYPH = {
|
|
1310
|
+
paused: "⏸",
|
|
1311
|
+
playing: "▶",
|
|
1312
|
+
stopped: "■"
|
|
1313
|
+
};
|
|
1188
1314
|
function style(text, code) {
|
|
1189
1315
|
return `${code}${text}${RESET}`;
|
|
1190
1316
|
}
|
|
1317
|
+
function progressBar(position, duration, width) {
|
|
1318
|
+
const span = Math.max(4, width);
|
|
1319
|
+
const ratio = duration && duration > 0 && position !== null ? Math.max(0, Math.min(position / duration, 1)) : 0;
|
|
1320
|
+
const filled = Math.round(ratio * span);
|
|
1321
|
+
return style("━".repeat(filled), CYAN) + style("─".repeat(span - filled), DIM);
|
|
1322
|
+
}
|
|
1191
1323
|
function padIndex(index, total) {
|
|
1192
1324
|
const width = String(Math.max(total, 1)).length;
|
|
1193
1325
|
return String(index + 1).padStart(width, " ");
|
|
@@ -1227,6 +1359,7 @@ function getVisibleTrackRange(selectedIndex, totalTracks, maxVisible) {
|
|
|
1227
1359
|
class PlaylistPlayerScreen {
|
|
1228
1360
|
session;
|
|
1229
1361
|
getViewportHeight;
|
|
1362
|
+
deleteConfirmationTrackKey = null;
|
|
1230
1363
|
pendingGoToTop = false;
|
|
1231
1364
|
pendingGoToTopTimer = null;
|
|
1232
1365
|
searchMode = false;
|
|
@@ -1242,6 +1375,10 @@ class PlaylistPlayerScreen {
|
|
|
1242
1375
|
}
|
|
1243
1376
|
invalidate() {}
|
|
1244
1377
|
handleInput(data) {
|
|
1378
|
+
if (this.deleteConfirmationTrackKey !== null) {
|
|
1379
|
+
this.handleDeleteConfirmationInput(data);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1245
1382
|
if (this.searchMode) {
|
|
1246
1383
|
this.handleSearchInput(data);
|
|
1247
1384
|
return;
|
|
@@ -1312,6 +1449,10 @@ class PlaylistPlayerScreen {
|
|
|
1312
1449
|
this.session.playSelected();
|
|
1313
1450
|
return;
|
|
1314
1451
|
}
|
|
1452
|
+
if (import_pi_tui.matchesKey(data, "d")) {
|
|
1453
|
+
this.armDeleteConfirmation();
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1315
1456
|
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.space)) {
|
|
1316
1457
|
this.session.togglePause();
|
|
1317
1458
|
return;
|
|
@@ -1346,15 +1487,24 @@ class PlaylistPlayerScreen {
|
|
|
1346
1487
|
const viewportHeight = Math.max(this.getViewportHeight(), RESERVED_ROWS);
|
|
1347
1488
|
const listHeight = this.getListHeight(viewportHeight);
|
|
1348
1489
|
const { start, end } = getVisibleTrackRange(this.snapshot.selectedIndex, playlist.length, listHeight);
|
|
1490
|
+
const position = formatSeconds(this.snapshot.timePositionSeconds);
|
|
1491
|
+
const duration = formatSeconds(this.snapshot.durationSeconds);
|
|
1492
|
+
const glyph = STATUS_GLYPH[this.snapshot.status] ?? "·";
|
|
1493
|
+
const isIdle = this.snapshot.currentTrack === null;
|
|
1494
|
+
const nowPlaying = `${glyph} ${this.snapshot.currentTrack?.title ?? "nothing playing"}`;
|
|
1495
|
+
const barWidth = Math.max(4, width - position.length - duration.length - 2);
|
|
1349
1496
|
const lines = [
|
|
1350
|
-
import_pi_tui.truncateToWidth(
|
|
1351
|
-
import_pi_tui.truncateToWidth(style(
|
|
1352
|
-
import_pi_tui.truncateToWidth(style(`${formatSeconds(this.snapshot.timePositionSeconds)} / ${formatSeconds(this.snapshot.durationSeconds)} | selected ${this.snapshot.selectedIndex + 1}/${Math.max(playlist.length, 1)}${this.snapshot.searchQuery ? ` | search "${this.snapshot.searchQuery}"${this.searchMode ? " [input]" : ""}` : this.searchMode ? " | search [input]" : ""}`, DIM), width)
|
|
1497
|
+
import_pi_tui.truncateToWidth(isIdle ? style(nowPlaying, DIM) : style(nowPlaying, BOLD + CYAN), width),
|
|
1498
|
+
import_pi_tui.truncateToWidth(`${style(position, DIM)} ${progressBar(this.snapshot.timePositionSeconds, this.snapshot.durationSeconds, barWidth)} ${style(duration, DIM)}`, width)
|
|
1353
1499
|
];
|
|
1354
1500
|
if (this.snapshot.errorMessage) {
|
|
1355
|
-
lines.push(import_pi_tui.truncateToWidth(style(
|
|
1501
|
+
lines.push(import_pi_tui.truncateToWidth(style(`✗ ${this.snapshot.errorMessage}`, RED), width));
|
|
1502
|
+
} else if (this.deleteConfirmationTrackKey !== null) {
|
|
1503
|
+
lines.push(import_pi_tui.truncateToWidth(style("delete this beatmap set? enter/d/y confirm · any key cancel", RED), width));
|
|
1504
|
+
} else if (this.searchMode || this.snapshot.searchQuery) {
|
|
1505
|
+
lines.push(import_pi_tui.truncateToWidth(style(`/ ${this.snapshot.searchQuery}${this.searchMode ? "▏" : ""}`, CYAN), width));
|
|
1356
1506
|
} else {
|
|
1357
|
-
lines.push("");
|
|
1507
|
+
lines.push(import_pi_tui.truncateToWidth(style(`${this.snapshot.selectedIndex + 1}/${Math.max(playlist.length, 1)} · loop ${this.snapshot.loop ? "on" : "off"} · ${this.snapshot.backendName}`, DIM), width));
|
|
1358
1508
|
}
|
|
1359
1509
|
if (playlist.length === 0) {
|
|
1360
1510
|
lines.push(import_pi_tui.truncateToWidth("No tracks were found in your osu!lazer library.", width));
|
|
@@ -1366,15 +1516,15 @@ class PlaylistPlayerScreen {
|
|
|
1366
1516
|
}
|
|
1367
1517
|
const isCurrent = index === this.snapshot.currentIndex;
|
|
1368
1518
|
const isSelected = index === this.snapshot.selectedIndex;
|
|
1369
|
-
const
|
|
1370
|
-
const line =
|
|
1371
|
-
lines.push(import_pi_tui.truncateToWidth(isSelected ? style(line, INVERSE) : line, width));
|
|
1519
|
+
const marker = isCurrent ? "▶" : " ";
|
|
1520
|
+
const line = ` ${marker} ${padIndex(index, playlist.length)} ${track.title}`;
|
|
1521
|
+
lines.push(import_pi_tui.truncateToWidth(isSelected ? style(line, INVERSE) : isCurrent ? style(line, CYAN) : line, width));
|
|
1372
1522
|
}
|
|
1373
1523
|
}
|
|
1374
1524
|
while (lines.length < viewportHeight - 1) {
|
|
1375
1525
|
lines.push("");
|
|
1376
1526
|
}
|
|
1377
|
-
lines.push(import_pi_tui.truncateToWidth(style(this.searchMode ? "
|
|
1527
|
+
lines.push(import_pi_tui.truncateToWidth(style(this.searchMode ? "type to jump · backspace edit · enter keep · esc leave" : "j/k move · ⏎ play · space pause · n/p track · h/l seek · r loop · d delete · / search · q quit", DIM), width));
|
|
1378
1528
|
return lines;
|
|
1379
1529
|
}
|
|
1380
1530
|
armPendingGoToTop() {
|
|
@@ -1391,6 +1541,24 @@ class PlaylistPlayerScreen {
|
|
|
1391
1541
|
}
|
|
1392
1542
|
this.pendingGoToTop = false;
|
|
1393
1543
|
}
|
|
1544
|
+
armDeleteConfirmation() {
|
|
1545
|
+
const selectedTrack = this.snapshot.playlist[this.snapshot.selectedIndex];
|
|
1546
|
+
if (!selectedTrack) {
|
|
1547
|
+
return;
|
|
1548
|
+
}
|
|
1549
|
+
this.deleteConfirmationTrackKey = selectedTrack.beatmapSetKey;
|
|
1550
|
+
}
|
|
1551
|
+
clearDeleteConfirmation() {
|
|
1552
|
+
this.deleteConfirmationTrackKey = null;
|
|
1553
|
+
}
|
|
1554
|
+
handleDeleteConfirmationInput(data) {
|
|
1555
|
+
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.enter) || import_pi_tui.matchesKey(data, "d") || import_pi_tui.matchesKey(data, "y")) {
|
|
1556
|
+
this.clearDeleteConfirmation();
|
|
1557
|
+
this.session.deleteSelectedTrack();
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
this.clearDeleteConfirmation();
|
|
1561
|
+
}
|
|
1394
1562
|
handleSearchInput(data) {
|
|
1395
1563
|
if (import_pi_tui.matchesKey(data, import_pi_tui.Key.ctrl("c"))) {
|
|
1396
1564
|
this.onQuit?.();
|
|
@@ -1516,9 +1684,17 @@ async function createPlaylist(osuDataDir) {
|
|
|
1516
1684
|
realm.close();
|
|
1517
1685
|
}
|
|
1518
1686
|
}
|
|
1519
|
-
async function
|
|
1687
|
+
async function deleteTrackFromCollection(track, osuDataDir) {
|
|
1688
|
+
const { deleteBeatmapSet: deleteBeatmapSet2 } = await loadRealmDependencies();
|
|
1689
|
+
await deleteBeatmapSet2(track, osuDataDir);
|
|
1690
|
+
}
|
|
1691
|
+
async function runTuiPlayer(playlist, osuDataDir, loop) {
|
|
1520
1692
|
const backend = new MpvPlayerBackend;
|
|
1521
|
-
const session = new PlaylistPlayerSession(playlist, backend, {
|
|
1693
|
+
const session = new PlaylistPlayerSession(playlist, backend, {
|
|
1694
|
+
deleteTrack: (track) => deleteTrackFromCollection(track, osuDataDir),
|
|
1695
|
+
loop,
|
|
1696
|
+
reloadPlaylist: () => createPlaylist(osuDataDir)
|
|
1697
|
+
});
|
|
1522
1698
|
const terminal = new import_pi_tui2.ProcessTerminal;
|
|
1523
1699
|
const tui = new import_pi_tui2.TUI(terminal);
|
|
1524
1700
|
const screen = new PlaylistPlayerScreen(session, () => terminal.rows);
|
|
@@ -1565,7 +1741,7 @@ async function main() {
|
|
|
1565
1741
|
console.log(`[INFO] Use something like \`mpv --playlist=${import_node_path3.default.resolve(argv.exportPlaylist)}\` to play the playlist.`);
|
|
1566
1742
|
return;
|
|
1567
1743
|
}
|
|
1568
|
-
await runTuiPlayer(playlist, argv.loop);
|
|
1744
|
+
await runTuiPlayer(playlist, argv.osuDataDir, argv.loop);
|
|
1569
1745
|
}
|
|
1570
1746
|
|
|
1571
1747
|
// src/cli.ts
|
|
@@ -1576,4 +1752,4 @@ main().then(() => {
|
|
|
1576
1752
|
process.exit(1);
|
|
1577
1753
|
});
|
|
1578
1754
|
|
|
1579
|
-
//# debugId=
|
|
1755
|
+
//# debugId=F18831A6D4239A5F64756E2164756E21
|