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 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("@mariozechner/pi-tui");
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: this.idleActive ? "stopped" : "playing",
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
- this.snapshot = {
804
- ...this.snapshot,
805
- status: "stopped",
806
- timePositionSeconds: this.snapshot.durationSeconds
807
- };
808
- this.emit({
809
- snapshot: this.snapshot,
810
- type: "state"
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: response.reason ?? "unknown",
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
- await this.playIndex(this.selectedIndex);
1105
+ const selectedIndex = this.selectedIndex;
1106
+ await this.enqueueOperation(async () => {
1107
+ await this.playIndex(selectedIndex);
1108
+ });
1047
1109
  }
1048
1110
  async playNext() {
1049
- const nextIndex = this.getAdjacentIndex(1);
1050
- if (nextIndex === null) {
1051
- return;
1052
- }
1053
- await this.playIndex(nextIndex);
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
- const previousIndex = this.getAdjacentIndex(-1);
1057
- if (previousIndex === null) {
1058
- return;
1059
- }
1060
- await this.playIndex(previousIndex);
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
- if (this.playlist.length === 0) {
1064
- return;
1065
- }
1066
- const { status } = this.backend.getSnapshot();
1067
- if (status === "stopped") {
1068
- const restartIndex = this.currentIndex ?? this.selectedIndex;
1069
- await this.playIndex(restartIndex);
1070
- return;
1071
- }
1072
- try {
1073
- this.clearError();
1074
- await this.backend.togglePause();
1075
- } catch (error) {
1076
- this.reportError(error);
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
- if (seconds === 0) {
1081
- return;
1082
- }
1083
- const { status } = this.backend.getSnapshot();
1084
- if (status === "stopped") {
1085
- return;
1086
- }
1087
- try {
1088
- this.clearError();
1089
- await this.backend.seekBy(seconds);
1090
- } catch (error) {
1091
- this.reportError(error);
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
- try {
1096
- this.clearError();
1097
- await this.backend.stop();
1098
- } catch (error) {
1099
- this.reportError(error);
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 this.loop ? this.playlist.length - 1 : null;
1255
+ return shouldWrap ? this.playlist.length - 1 : null;
1143
1256
  }
1144
1257
  if (nextIndex >= this.playlist.length) {
1145
- return this.loop ? 0 : null;
1258
+ return shouldWrap ? 0 : null;
1146
1259
  }
1147
1260
  return nextIndex;
1148
1261
  }
1149
- async handleBackendEvent(event) {
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
- if (event.reason === "eof") {
1159
- const nextIndex = this.getAdjacentIndex(1);
1160
- if (nextIndex !== null) {
1161
- await this.playIndex(nextIndex);
1162
- return;
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
- this.emit();
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("@mariozechner/pi-tui");
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 = 6;
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(`osu-play | tracks ${playlist.length} | backend ${this.snapshot.backendName} | loop ${this.snapshot.loop ? "on" : "off"}`, width),
1351
- import_pi_tui.truncateToWidth(style(`now ${this.snapshot.status}: ${this.snapshot.currentTrack?.title ?? "nothing selected"}`, CYAN), width),
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(`error: ${this.snapshot.errorMessage}`, RED), width));
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 prefix = `${isSelected ? ">" : " "} ${isCurrent ? "*" : " "} `;
1370
- const line = `${prefix}${padIndex(index, playlist.length)}. ${track.title}`;
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 ? "/ search | type to jump | backspace edit | enter keep | esc leave" : "h/l seek | j/k wrap | gg/G bounds | C-u/C-d page | H/M/L viewport | n/p track | r loop | / search", DIM), width));
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 runTuiPlayer(playlist, loop) {
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, { loop });
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=092198B3F20C100364756E2164756E21
1755
+ //# debugId=F18831A6D4239A5F64756E2164756E21