osu-play 1.2.0 → 1.3.0
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 +257 -81
- package/dist/cli.cjs.map +8 -8
- package/dist/cli.js +257 -81
- 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 +4 -0
- 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.js
CHANGED
|
@@ -409,6 +409,7 @@ __export(exports_mod, {
|
|
|
409
409
|
getLazerDB: () => getLazerDB,
|
|
410
410
|
getBeatmapSets: () => getBeatmapSets,
|
|
411
411
|
formatLazerSchemaCompatibilityError: () => formatLazerSchemaCompatibilityError,
|
|
412
|
+
deleteBeatmapSet: () => deleteBeatmapSet,
|
|
412
413
|
RealmUser: () => RealmUser,
|
|
413
414
|
RealmNamedFileUsage: () => RealmNamedFileUsage,
|
|
414
415
|
RealmFile: () => RealmFile,
|
|
@@ -424,6 +425,48 @@ function inspectLazerSchema(realm) {
|
|
|
424
425
|
function getBeatmapSets(realm) {
|
|
425
426
|
return realm.objects("BeatmapSet");
|
|
426
427
|
}
|
|
428
|
+
function queryBeatmapSet(realm, track) {
|
|
429
|
+
const beatmapSets = realm.objects("BeatmapSet");
|
|
430
|
+
if (track.beatmapSetId !== null && track.beatmapSetId !== undefined) {
|
|
431
|
+
const byId = realm.objectForPrimaryKey("BeatmapSet", track.beatmapSetId) ?? beatmapSets.filtered("ID == $0", track.beatmapSetId)[0];
|
|
432
|
+
if (byId) {
|
|
433
|
+
return byId;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (track.beatmapSetHash) {
|
|
437
|
+
return beatmapSets.filtered("Hash == $0", track.beatmapSetHash)[0] ?? null;
|
|
438
|
+
}
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
async function deleteBeatmapSet(track, osuDataDir) {
|
|
442
|
+
const realmDBPath = getRealmDBPath({ osuDataDir });
|
|
443
|
+
if (!realmDBPath) {
|
|
444
|
+
throw new Error("Realm DB not found");
|
|
445
|
+
}
|
|
446
|
+
const realm = await Realm7.open({ path: realmDBPath });
|
|
447
|
+
try {
|
|
448
|
+
let markedForDeletion = false;
|
|
449
|
+
realm.write(() => {
|
|
450
|
+
const beatmapSet = queryBeatmapSet(realm, track);
|
|
451
|
+
if (!beatmapSet) {
|
|
452
|
+
throw new Error(`Beatmap set for "${track.title}" no longer exists.`);
|
|
453
|
+
}
|
|
454
|
+
if (beatmapSet.Protected) {
|
|
455
|
+
throw new Error(`"${track.title}" is protected and cannot be deleted.`);
|
|
456
|
+
}
|
|
457
|
+
if (beatmapSet.DeletePending) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
beatmapSet.DeletePending = true;
|
|
461
|
+
markedForDeletion = true;
|
|
462
|
+
});
|
|
463
|
+
if (!markedForDeletion) {
|
|
464
|
+
throw new Error(`"${track.title}" is already pending deletion.`);
|
|
465
|
+
}
|
|
466
|
+
} finally {
|
|
467
|
+
realm.close();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
427
470
|
var getLazerDB = async (realmDBPath) => {
|
|
428
471
|
return Realm7.open({
|
|
429
472
|
path: realmDBPath,
|
|
@@ -439,6 +482,7 @@ var getLazerDB = async (realmDBPath) => {
|
|
|
439
482
|
};
|
|
440
483
|
var init_mod3 = __esm(() => {
|
|
441
484
|
init_compat();
|
|
485
|
+
init_mod();
|
|
442
486
|
init_mod2();
|
|
443
487
|
init_compat();
|
|
444
488
|
init_mod();
|
|
@@ -473,6 +517,9 @@ function buildPlaylist(beatmapSets, osuDataDir) {
|
|
|
473
517
|
const seenHashes = new Set;
|
|
474
518
|
const playlist = [];
|
|
475
519
|
for (const beatmapSet of beatmapSets) {
|
|
520
|
+
if (beatmapSet.DeletePending) {
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
476
523
|
for (const beatmap of beatmapSet.Beatmaps) {
|
|
477
524
|
const hash = getNamedFileHash(beatmap.Metadata.AudioFile ?? "", beatmapSet);
|
|
478
525
|
if (!hash || seenHashes.has(hash)) {
|
|
@@ -480,6 +527,9 @@ function buildPlaylist(beatmapSets, osuDataDir) {
|
|
|
480
527
|
}
|
|
481
528
|
seenHashes.add(hash);
|
|
482
529
|
playlist.push({
|
|
530
|
+
beatmapSetHash: beatmapSet.Hash ?? null,
|
|
531
|
+
beatmapSetId: beatmapSet.ID,
|
|
532
|
+
beatmapSetKey: String(beatmapSet.ID ?? hash),
|
|
483
533
|
hash,
|
|
484
534
|
path: hashedFilePath(hash, osuDataDir),
|
|
485
535
|
title: formatTrackTitle(beatmap.Metadata)
|
|
@@ -588,11 +638,12 @@ class MpvPlayerBackend {
|
|
|
588
638
|
});
|
|
589
639
|
throw error;
|
|
590
640
|
}
|
|
641
|
+
this.idleActive = false;
|
|
591
642
|
this.snapshot = {
|
|
592
643
|
...this.snapshot,
|
|
593
644
|
currentPath: filePath,
|
|
594
645
|
errorMessage: null,
|
|
595
|
-
status:
|
|
646
|
+
status: "playing",
|
|
596
647
|
timePositionSeconds: this.snapshot.timePositionSeconds ?? 0
|
|
597
648
|
};
|
|
598
649
|
this.emit({
|
|
@@ -785,17 +836,20 @@ ${detail}` : "";
|
|
|
785
836
|
return;
|
|
786
837
|
}
|
|
787
838
|
if (response.event === "end-file") {
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
839
|
+
const reason = response.reason ?? "unknown";
|
|
840
|
+
if (reason !== "stop" && reason !== "redirect") {
|
|
841
|
+
this.snapshot = {
|
|
842
|
+
...this.snapshot,
|
|
843
|
+
status: "stopped",
|
|
844
|
+
timePositionSeconds: reason === "eof" ? this.snapshot.durationSeconds : this.snapshot.timePositionSeconds
|
|
845
|
+
};
|
|
846
|
+
this.emit({
|
|
847
|
+
snapshot: this.snapshot,
|
|
848
|
+
type: "state"
|
|
849
|
+
});
|
|
850
|
+
}
|
|
797
851
|
this.emit({
|
|
798
|
-
reason
|
|
852
|
+
reason,
|
|
799
853
|
type: "ended"
|
|
800
854
|
});
|
|
801
855
|
}
|
|
@@ -927,14 +981,19 @@ class PlaylistPlayerSession {
|
|
|
927
981
|
currentIndex = null;
|
|
928
982
|
errorMessage = null;
|
|
929
983
|
loop;
|
|
984
|
+
deleteTrack;
|
|
930
985
|
playlist;
|
|
986
|
+
reloadPlaylist;
|
|
931
987
|
searchQuery = "";
|
|
932
988
|
selectedIndex = 0;
|
|
989
|
+
operationQueue = Promise.resolve();
|
|
933
990
|
unsubscribeBackend;
|
|
934
991
|
constructor(playlist, backend, options = {}) {
|
|
935
992
|
this.backend = backend;
|
|
936
993
|
this.playlist = playlist;
|
|
994
|
+
this.deleteTrack = options.deleteTrack;
|
|
937
995
|
this.loop = options.loop ?? false;
|
|
996
|
+
this.reloadPlaylist = options.reloadPlaylist;
|
|
938
997
|
this.unsubscribeBackend = backend.subscribe((event) => {
|
|
939
998
|
this.handleBackendEvent(event);
|
|
940
999
|
});
|
|
@@ -1028,61 +1087,113 @@ class PlaylistPlayerSession {
|
|
|
1028
1087
|
this.emit();
|
|
1029
1088
|
}
|
|
1030
1089
|
async playSelected() {
|
|
1031
|
-
|
|
1090
|
+
const selectedIndex = this.selectedIndex;
|
|
1091
|
+
await this.enqueueOperation(async () => {
|
|
1092
|
+
await this.playIndex(selectedIndex);
|
|
1093
|
+
});
|
|
1032
1094
|
}
|
|
1033
1095
|
async playNext() {
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1096
|
+
await this.enqueueOperation(async () => {
|
|
1097
|
+
const nextIndex = this.getAdjacentIndex(1, true);
|
|
1098
|
+
if (nextIndex === null) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
await this.playIndex(nextIndex);
|
|
1102
|
+
});
|
|
1039
1103
|
}
|
|
1040
1104
|
async playPrevious() {
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1105
|
+
await this.enqueueOperation(async () => {
|
|
1106
|
+
const previousIndex = this.getAdjacentIndex(-1, true);
|
|
1107
|
+
if (previousIndex === null) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
await this.playIndex(previousIndex);
|
|
1111
|
+
});
|
|
1046
1112
|
}
|
|
1047
1113
|
async togglePause() {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1114
|
+
const selectedIndex = this.selectedIndex;
|
|
1115
|
+
await this.enqueueOperation(async () => {
|
|
1116
|
+
if (this.playlist.length === 0) {
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const { status } = this.backend.getSnapshot();
|
|
1120
|
+
if (status === "stopped") {
|
|
1121
|
+
const restartIndex = this.currentIndex ?? selectedIndex;
|
|
1122
|
+
await this.playIndex(restartIndex);
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
this.clearError();
|
|
1127
|
+
await this.backend.togglePause();
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
this.reportError(error);
|
|
1130
|
+
}
|
|
1131
|
+
});
|
|
1063
1132
|
}
|
|
1064
1133
|
async seekBy(seconds) {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1134
|
+
await this.enqueueOperation(async () => {
|
|
1135
|
+
if (seconds === 0) {
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
const { status } = this.backend.getSnapshot();
|
|
1139
|
+
if (status === "stopped") {
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
this.clearError();
|
|
1144
|
+
await this.backend.seekBy(seconds);
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
this.reportError(error);
|
|
1147
|
+
}
|
|
1148
|
+
});
|
|
1078
1149
|
}
|
|
1079
1150
|
async stop() {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1151
|
+
await this.enqueueOperation(async () => {
|
|
1152
|
+
try {
|
|
1153
|
+
this.clearError();
|
|
1154
|
+
await this.backend.stop();
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
this.reportError(error);
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
async deleteSelectedTrack() {
|
|
1161
|
+
const selectedIndex = this.selectedIndex;
|
|
1162
|
+
await this.enqueueOperation(async () => {
|
|
1163
|
+
const track = this.playlist[selectedIndex];
|
|
1164
|
+
if (!track) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
if (!this.deleteTrack || !this.reloadPlaylist) {
|
|
1168
|
+
this.reportError(new Error("Beatmap deletion is unavailable in this session."));
|
|
1169
|
+
return;
|
|
1170
|
+
}
|
|
1171
|
+
const currentTrack = this.currentIndex !== null ? this.playlist[this.currentIndex] ?? null : null;
|
|
1172
|
+
const deletingCurrentTrack = currentTrack !== null && currentTrack.beatmapSetKey === track.beatmapSetKey;
|
|
1173
|
+
try {
|
|
1174
|
+
this.clearError();
|
|
1175
|
+
if (deletingCurrentTrack) {
|
|
1176
|
+
await this.backend.stop();
|
|
1177
|
+
this.currentIndex = null;
|
|
1178
|
+
}
|
|
1179
|
+
await this.deleteTrack(track);
|
|
1180
|
+
this.playlist = await this.reloadPlaylist();
|
|
1181
|
+
this.selectedIndex = clampIndex(selectedIndex, this.playlist.length);
|
|
1182
|
+
if (this.searchQuery) {
|
|
1183
|
+
const matchedIndex = findTrackIndexByQuery(this.playlist, this.searchQuery);
|
|
1184
|
+
if (matchedIndex !== -1) {
|
|
1185
|
+
this.selectedIndex = matchedIndex;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (!deletingCurrentTrack && currentTrack) {
|
|
1189
|
+
const nextCurrentIndex = this.playlist.findIndex((playlistTrack) => playlistTrack.hash === currentTrack.hash);
|
|
1190
|
+
this.currentIndex = nextCurrentIndex === -1 ? null : nextCurrentIndex;
|
|
1191
|
+
}
|
|
1192
|
+
this.emit();
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
this.reportError(error);
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1086
1197
|
}
|
|
1087
1198
|
reportError(error) {
|
|
1088
1199
|
this.errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -1115,23 +1226,25 @@ class PlaylistPlayerSession {
|
|
|
1115
1226
|
this.reportError(error);
|
|
1116
1227
|
return;
|
|
1117
1228
|
}
|
|
1229
|
+
this.selectedIndex = index;
|
|
1118
1230
|
this.emit();
|
|
1119
1231
|
}
|
|
1120
|
-
getAdjacentIndex(delta) {
|
|
1232
|
+
getAdjacentIndex(delta, wrap = false) {
|
|
1121
1233
|
if (this.playlist.length === 0) {
|
|
1122
1234
|
return null;
|
|
1123
1235
|
}
|
|
1124
1236
|
const baseIndex = this.currentIndex ?? this.selectedIndex;
|
|
1125
1237
|
const nextIndex = baseIndex + delta;
|
|
1238
|
+
const shouldWrap = wrap || this.loop;
|
|
1126
1239
|
if (nextIndex < 0) {
|
|
1127
|
-
return
|
|
1240
|
+
return shouldWrap ? this.playlist.length - 1 : null;
|
|
1128
1241
|
}
|
|
1129
1242
|
if (nextIndex >= this.playlist.length) {
|
|
1130
|
-
return
|
|
1243
|
+
return shouldWrap ? 0 : null;
|
|
1131
1244
|
}
|
|
1132
1245
|
return nextIndex;
|
|
1133
1246
|
}
|
|
1134
|
-
|
|
1247
|
+
handleBackendEvent(event) {
|
|
1135
1248
|
switch (event.type) {
|
|
1136
1249
|
case "state":
|
|
1137
1250
|
this.emit();
|
|
@@ -1140,16 +1253,23 @@ class PlaylistPlayerSession {
|
|
|
1140
1253
|
this.reportError(event.error);
|
|
1141
1254
|
return;
|
|
1142
1255
|
case "ended":
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1256
|
+
this.enqueueOperation(async () => {
|
|
1257
|
+
if (event.reason === "eof") {
|
|
1258
|
+
const nextIndex = this.getAdjacentIndex(1);
|
|
1259
|
+
if (nextIndex !== null) {
|
|
1260
|
+
await this.playIndex(nextIndex);
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1148
1263
|
}
|
|
1149
|
-
|
|
1150
|
-
|
|
1264
|
+
this.emit();
|
|
1265
|
+
});
|
|
1151
1266
|
}
|
|
1152
1267
|
}
|
|
1268
|
+
enqueueOperation(operation) {
|
|
1269
|
+
const queuedOperation = this.operationQueue.then(operation, operation);
|
|
1270
|
+
this.operationQueue = queuedOperation.catch(() => {});
|
|
1271
|
+
return queuedOperation;
|
|
1272
|
+
}
|
|
1153
1273
|
syncSelectionToSearch() {
|
|
1154
1274
|
const matchedIndex = findTrackIndexByQuery(this.playlist, this.searchQuery);
|
|
1155
1275
|
if (matchedIndex !== -1) {
|
|
@@ -1169,15 +1289,27 @@ var RESET = "\x1B[0m";
|
|
|
1169
1289
|
var DIM = "\x1B[2m";
|
|
1170
1290
|
var RED = "\x1B[31m";
|
|
1171
1291
|
var CYAN = "\x1B[36m";
|
|
1292
|
+
var BOLD = "\x1B[1m";
|
|
1172
1293
|
var INVERSE = "\x1B[7m";
|
|
1173
1294
|
var MIN_LIST_ROWS = 4;
|
|
1174
|
-
var RESERVED_ROWS =
|
|
1295
|
+
var RESERVED_ROWS = 5;
|
|
1175
1296
|
var PAGE_SIZE = 10;
|
|
1176
1297
|
var PENDING_G_TIMEOUT_MS = 400;
|
|
1177
1298
|
var SEEK_SECONDS = 5;
|
|
1299
|
+
var STATUS_GLYPH = {
|
|
1300
|
+
paused: "⏸",
|
|
1301
|
+
playing: "▶",
|
|
1302
|
+
stopped: "■"
|
|
1303
|
+
};
|
|
1178
1304
|
function style(text, code) {
|
|
1179
1305
|
return `${code}${text}${RESET}`;
|
|
1180
1306
|
}
|
|
1307
|
+
function progressBar(position, duration, width) {
|
|
1308
|
+
const span = Math.max(4, width);
|
|
1309
|
+
const ratio = duration && duration > 0 && position !== null ? Math.max(0, Math.min(position / duration, 1)) : 0;
|
|
1310
|
+
const filled = Math.round(ratio * span);
|
|
1311
|
+
return style("━".repeat(filled), CYAN) + style("─".repeat(span - filled), DIM);
|
|
1312
|
+
}
|
|
1181
1313
|
function padIndex(index, total) {
|
|
1182
1314
|
const width = String(Math.max(total, 1)).length;
|
|
1183
1315
|
return String(index + 1).padStart(width, " ");
|
|
@@ -1217,6 +1349,7 @@ function getVisibleTrackRange(selectedIndex, totalTracks, maxVisible) {
|
|
|
1217
1349
|
class PlaylistPlayerScreen {
|
|
1218
1350
|
session;
|
|
1219
1351
|
getViewportHeight;
|
|
1352
|
+
deleteConfirmationTrackKey = null;
|
|
1220
1353
|
pendingGoToTop = false;
|
|
1221
1354
|
pendingGoToTopTimer = null;
|
|
1222
1355
|
searchMode = false;
|
|
@@ -1232,6 +1365,10 @@ class PlaylistPlayerScreen {
|
|
|
1232
1365
|
}
|
|
1233
1366
|
invalidate() {}
|
|
1234
1367
|
handleInput(data) {
|
|
1368
|
+
if (this.deleteConfirmationTrackKey !== null) {
|
|
1369
|
+
this.handleDeleteConfirmationInput(data);
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1235
1372
|
if (this.searchMode) {
|
|
1236
1373
|
this.handleSearchInput(data);
|
|
1237
1374
|
return;
|
|
@@ -1302,6 +1439,10 @@ class PlaylistPlayerScreen {
|
|
|
1302
1439
|
this.session.playSelected();
|
|
1303
1440
|
return;
|
|
1304
1441
|
}
|
|
1442
|
+
if (matchesKey(data, "d")) {
|
|
1443
|
+
this.armDeleteConfirmation();
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1305
1446
|
if (matchesKey(data, Key.space)) {
|
|
1306
1447
|
this.session.togglePause();
|
|
1307
1448
|
return;
|
|
@@ -1336,15 +1477,24 @@ class PlaylistPlayerScreen {
|
|
|
1336
1477
|
const viewportHeight = Math.max(this.getViewportHeight(), RESERVED_ROWS);
|
|
1337
1478
|
const listHeight = this.getListHeight(viewportHeight);
|
|
1338
1479
|
const { start, end } = getVisibleTrackRange(this.snapshot.selectedIndex, playlist.length, listHeight);
|
|
1480
|
+
const position = formatSeconds(this.snapshot.timePositionSeconds);
|
|
1481
|
+
const duration = formatSeconds(this.snapshot.durationSeconds);
|
|
1482
|
+
const glyph = STATUS_GLYPH[this.snapshot.status] ?? "·";
|
|
1483
|
+
const isIdle = this.snapshot.currentTrack === null;
|
|
1484
|
+
const nowPlaying = `${glyph} ${this.snapshot.currentTrack?.title ?? "nothing playing"}`;
|
|
1485
|
+
const barWidth = Math.max(4, width - position.length - duration.length - 2);
|
|
1339
1486
|
const lines = [
|
|
1340
|
-
truncateToWidth(
|
|
1341
|
-
truncateToWidth(style(
|
|
1342
|
-
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)
|
|
1487
|
+
truncateToWidth(isIdle ? style(nowPlaying, DIM) : style(nowPlaying, BOLD + CYAN), width),
|
|
1488
|
+
truncateToWidth(`${style(position, DIM)} ${progressBar(this.snapshot.timePositionSeconds, this.snapshot.durationSeconds, barWidth)} ${style(duration, DIM)}`, width)
|
|
1343
1489
|
];
|
|
1344
1490
|
if (this.snapshot.errorMessage) {
|
|
1345
|
-
lines.push(truncateToWidth(style(
|
|
1491
|
+
lines.push(truncateToWidth(style(`✗ ${this.snapshot.errorMessage}`, RED), width));
|
|
1492
|
+
} else if (this.deleteConfirmationTrackKey !== null) {
|
|
1493
|
+
lines.push(truncateToWidth(style("delete this beatmap set? enter/d/y confirm · any key cancel", RED), width));
|
|
1494
|
+
} else if (this.searchMode || this.snapshot.searchQuery) {
|
|
1495
|
+
lines.push(truncateToWidth(style(`/ ${this.snapshot.searchQuery}${this.searchMode ? "▏" : ""}`, CYAN), width));
|
|
1346
1496
|
} else {
|
|
1347
|
-
lines.push("");
|
|
1497
|
+
lines.push(truncateToWidth(style(`${this.snapshot.selectedIndex + 1}/${Math.max(playlist.length, 1)} · loop ${this.snapshot.loop ? "on" : "off"} · ${this.snapshot.backendName}`, DIM), width));
|
|
1348
1498
|
}
|
|
1349
1499
|
if (playlist.length === 0) {
|
|
1350
1500
|
lines.push(truncateToWidth("No tracks were found in your osu!lazer library.", width));
|
|
@@ -1356,15 +1506,15 @@ class PlaylistPlayerScreen {
|
|
|
1356
1506
|
}
|
|
1357
1507
|
const isCurrent = index === this.snapshot.currentIndex;
|
|
1358
1508
|
const isSelected = index === this.snapshot.selectedIndex;
|
|
1359
|
-
const
|
|
1360
|
-
const line =
|
|
1361
|
-
lines.push(truncateToWidth(isSelected ? style(line, INVERSE) : line, width));
|
|
1509
|
+
const marker = isCurrent ? "▶" : " ";
|
|
1510
|
+
const line = ` ${marker} ${padIndex(index, playlist.length)} ${track.title}`;
|
|
1511
|
+
lines.push(truncateToWidth(isSelected ? style(line, INVERSE) : isCurrent ? style(line, CYAN) : line, width));
|
|
1362
1512
|
}
|
|
1363
1513
|
}
|
|
1364
1514
|
while (lines.length < viewportHeight - 1) {
|
|
1365
1515
|
lines.push("");
|
|
1366
1516
|
}
|
|
1367
|
-
lines.push(truncateToWidth(style(this.searchMode ? "
|
|
1517
|
+
lines.push(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));
|
|
1368
1518
|
return lines;
|
|
1369
1519
|
}
|
|
1370
1520
|
armPendingGoToTop() {
|
|
@@ -1381,6 +1531,24 @@ class PlaylistPlayerScreen {
|
|
|
1381
1531
|
}
|
|
1382
1532
|
this.pendingGoToTop = false;
|
|
1383
1533
|
}
|
|
1534
|
+
armDeleteConfirmation() {
|
|
1535
|
+
const selectedTrack = this.snapshot.playlist[this.snapshot.selectedIndex];
|
|
1536
|
+
if (!selectedTrack) {
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1539
|
+
this.deleteConfirmationTrackKey = selectedTrack.beatmapSetKey;
|
|
1540
|
+
}
|
|
1541
|
+
clearDeleteConfirmation() {
|
|
1542
|
+
this.deleteConfirmationTrackKey = null;
|
|
1543
|
+
}
|
|
1544
|
+
handleDeleteConfirmationInput(data) {
|
|
1545
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, "d") || matchesKey(data, "y")) {
|
|
1546
|
+
this.clearDeleteConfirmation();
|
|
1547
|
+
this.session.deleteSelectedTrack();
|
|
1548
|
+
return;
|
|
1549
|
+
}
|
|
1550
|
+
this.clearDeleteConfirmation();
|
|
1551
|
+
}
|
|
1384
1552
|
handleSearchInput(data) {
|
|
1385
1553
|
if (matchesKey(data, Key.ctrl("c"))) {
|
|
1386
1554
|
this.onQuit?.();
|
|
@@ -1506,9 +1674,17 @@ async function createPlaylist(osuDataDir) {
|
|
|
1506
1674
|
realm.close();
|
|
1507
1675
|
}
|
|
1508
1676
|
}
|
|
1509
|
-
async function
|
|
1677
|
+
async function deleteTrackFromCollection(track, osuDataDir) {
|
|
1678
|
+
const { deleteBeatmapSet: deleteBeatmapSet2 } = await loadRealmDependencies();
|
|
1679
|
+
await deleteBeatmapSet2(track, osuDataDir);
|
|
1680
|
+
}
|
|
1681
|
+
async function runTuiPlayer(playlist, osuDataDir, loop) {
|
|
1510
1682
|
const backend = new MpvPlayerBackend;
|
|
1511
|
-
const session = new PlaylistPlayerSession(playlist, backend, {
|
|
1683
|
+
const session = new PlaylistPlayerSession(playlist, backend, {
|
|
1684
|
+
deleteTrack: (track) => deleteTrackFromCollection(track, osuDataDir),
|
|
1685
|
+
loop,
|
|
1686
|
+
reloadPlaylist: () => createPlaylist(osuDataDir)
|
|
1687
|
+
});
|
|
1512
1688
|
const terminal = new ProcessTerminal;
|
|
1513
1689
|
const tui = new TUI(terminal);
|
|
1514
1690
|
const screen = new PlaylistPlayerScreen(session, () => terminal.rows);
|
|
@@ -1555,7 +1731,7 @@ async function main() {
|
|
|
1555
1731
|
console.log(`[INFO] Use something like \`mpv --playlist=${path3.resolve(argv.exportPlaylist)}\` to play the playlist.`);
|
|
1556
1732
|
return;
|
|
1557
1733
|
}
|
|
1558
|
-
await runTuiPlayer(playlist, argv.loop);
|
|
1734
|
+
await runTuiPlayer(playlist, argv.osuDataDir, argv.loop);
|
|
1559
1735
|
}
|
|
1560
1736
|
|
|
1561
1737
|
// src/cli.ts
|
|
@@ -1566,4 +1742,4 @@ main().then(() => {
|
|
|
1566
1742
|
process.exit(1);
|
|
1567
1743
|
});
|
|
1568
1744
|
|
|
1569
|
-
//# debugId=
|
|
1745
|
+
//# debugId=CC4EB1080D57FADD64756E2164756E21
|