osu-play 1.2.1 → 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.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: this.idleActive ? "stopped" : "playing",
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
- this.snapshot = {
789
- ...this.snapshot,
790
- status: "stopped",
791
- timePositionSeconds: this.snapshot.durationSeconds
792
- };
793
- this.emit({
794
- snapshot: this.snapshot,
795
- type: "state"
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: response.reason ?? "unknown",
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
- await this.playIndex(this.selectedIndex);
1090
+ const selectedIndex = this.selectedIndex;
1091
+ await this.enqueueOperation(async () => {
1092
+ await this.playIndex(selectedIndex);
1093
+ });
1032
1094
  }
1033
1095
  async playNext() {
1034
- const nextIndex = this.getAdjacentIndex(1);
1035
- if (nextIndex === null) {
1036
- return;
1037
- }
1038
- await this.playIndex(nextIndex);
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
- const previousIndex = this.getAdjacentIndex(-1);
1042
- if (previousIndex === null) {
1043
- return;
1044
- }
1045
- await this.playIndex(previousIndex);
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
- if (this.playlist.length === 0) {
1049
- return;
1050
- }
1051
- const { status } = this.backend.getSnapshot();
1052
- if (status === "stopped") {
1053
- const restartIndex = this.currentIndex ?? this.selectedIndex;
1054
- await this.playIndex(restartIndex);
1055
- return;
1056
- }
1057
- try {
1058
- this.clearError();
1059
- await this.backend.togglePause();
1060
- } catch (error) {
1061
- this.reportError(error);
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
- if (seconds === 0) {
1066
- return;
1067
- }
1068
- const { status } = this.backend.getSnapshot();
1069
- if (status === "stopped") {
1070
- return;
1071
- }
1072
- try {
1073
- this.clearError();
1074
- await this.backend.seekBy(seconds);
1075
- } catch (error) {
1076
- this.reportError(error);
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
- try {
1081
- this.clearError();
1082
- await this.backend.stop();
1083
- } catch (error) {
1084
- this.reportError(error);
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 this.loop ? this.playlist.length - 1 : null;
1240
+ return shouldWrap ? this.playlist.length - 1 : null;
1128
1241
  }
1129
1242
  if (nextIndex >= this.playlist.length) {
1130
- return this.loop ? 0 : null;
1243
+ return shouldWrap ? 0 : null;
1131
1244
  }
1132
1245
  return nextIndex;
1133
1246
  }
1134
- async handleBackendEvent(event) {
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
- if (event.reason === "eof") {
1144
- const nextIndex = this.getAdjacentIndex(1);
1145
- if (nextIndex !== null) {
1146
- await this.playIndex(nextIndex);
1147
- return;
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
- this.emit();
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 = 6;
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(`osu-play | tracks ${playlist.length} | backend ${this.snapshot.backendName} | loop ${this.snapshot.loop ? "on" : "off"}`, width),
1341
- truncateToWidth(style(`now ${this.snapshot.status}: ${this.snapshot.currentTrack?.title ?? "nothing selected"}`, CYAN), width),
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(`error: ${this.snapshot.errorMessage}`, RED), width));
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 prefix = `${isSelected ? ">" : " "} ${isCurrent ? "*" : " "} `;
1360
- const line = `${prefix}${padIndex(index, playlist.length)}. ${track.title}`;
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 ? "/ 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));
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 runTuiPlayer(playlist, loop) {
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, { loop });
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=FF1120811E982FF064756E2164756E21
1745
+ //# debugId=CC4EB1080D57FADD64756E2164756E21